working bar context menus + kbd downsize

This commit is contained in:
galister
2026-01-07 19:03:47 +09:00
parent 165070da51
commit ac9f3c6d23
12 changed files with 237 additions and 142 deletions

View File

@@ -23,7 +23,7 @@ use wgui::{
task::Tasks, task::Tasks,
widget::{div::WidgetDiv, label::WidgetLabel, rectangle::WidgetRectangle}, widget::{div::WidgetDiv, label::WidgetLabel, rectangle::WidgetRectangle},
windowing::{ windowing::{
context_menu, context_menu::{self, TickResult},
window::{WguiWindow, WguiWindowParams, WguiWindowParamsExtra}, window::{WguiWindow, WguiWindowParams, WguiWindowParamsExtra},
}, },
}; };
@@ -285,7 +285,7 @@ impl Testbed for TestbedGeneric {
let res = data let res = data
.context_menu .context_menu
.tick(&mut self.layout, &mut self.parser_state)?; .tick(&mut self.layout, &mut self.parser_state)?;
if let Some(action_name) = res.action_name { if let TickResult::Action(action_name) = res {
log::info!("got action: {}", action_name); log::info!("got action: {}", action_name);
} }

View File

@@ -1,7 +1,7 @@
<layout> <layout>
<!-- text: str --> <!-- text: str -->
<template name="Cell"> <template name="Cell">
<Button id="button" text="${text}" weight="bold" border="0" color="#FFFFFF00" /> <Button id="button" text="${text}" weight="bold" border="0" padding="4" color="#FFFFFF00" />
</template> </template>
<template name="Separator"> <template name="Separator">

View File

@@ -32,7 +32,6 @@ use crate::{
windowing::context_menu, windowing::context_menu,
}; };
use anyhow::Context; use anyhow::Context;
use glam::Vec2;
use ouroboros::self_referencing; use ouroboros::self_referencing;
use smallvec::SmallVec; use smallvec::SmallVec;
use std::{cell::RefMut, collections::HashMap, path::Path, rc::Rc}; use std::{cell::RefMut, collections::HashMap, path::Path, rc::Rc};
@@ -97,7 +96,7 @@ pub trait Fetchable {
} }
impl ParserData { impl ParserData {
fn take_results_from(&mut self, from: &mut Self) { pub(crate) fn take_results_from(&mut self, from: &mut Self) {
let ids = std::mem::take(&mut from.ids); let ids = std::mem::take(&mut from.ids);
let components = std::mem::take(&mut from.components); let components = std::mem::take(&mut from.components);
let components_by_id = std::mem::take(&mut from.components_by_id); let components_by_id = std::mem::take(&mut from.components_by_id);
@@ -140,7 +139,7 @@ impl Fetchable for ParserData {
}; };
let Some(component) = weak.upgrade() else { let Some(component) = weak.upgrade() else {
anyhow::bail!("Component by widget ID \"{widget_id:?}\" doesn't exist"); anyhow::bail!("Component by widget ID \"{widget_id:?}\" has disappeared");
}; };
Ok(Component(component)) Ok(Component(component))
@@ -264,7 +263,6 @@ impl ParserState {
&mut self, &mut self,
template_name: &str, template_name: &str,
template_params: &HashMap<Rc<str>, Rc<str>>, template_params: &HashMap<Rc<str>, Rc<str>>,
position: Vec2,
) -> anyhow::Result<context_menu::Blueprint> { ) -> anyhow::Result<context_menu::Blueprint> {
let Some(template) = self.data.templates.get(template_name) else { let Some(template) = self.data.templates.get(template_name) else {
anyhow::bail!("no template named \"{template_name}\" found"); anyhow::bail!("no template named \"{template_name}\" found");
@@ -320,7 +318,6 @@ impl ParserState {
Ok( Ok(
context_menu::Blueprint { context_menu::Blueprint {
cells, cells,
position,
} }
) )
} }

View File

@@ -16,7 +16,7 @@ use crate::drawing::{self};
pub static SWASH_CACHE: LazyLock<Mutex<SwashCache>> = LazyLock::new(|| Mutex::new(SwashCache::new())); pub static SWASH_CACHE: LazyLock<Mutex<SwashCache>> = LazyLock::new(|| Mutex::new(SwashCache::new()));
/// Used in case no `font_size` is defined /// Used in case no `font_size` is defined
const DEFAULT_FONT_SIZE: f32 = 14.; pub(crate) const DEFAULT_FONT_SIZE: f32 = 14.;
/// In case no `line_height` is defined, use `font_size` * `DEFAULT_LINE_HEIGHT_RATIO` /// In case no `line_height` is defined, use `font_size` * `DEFAULT_LINE_HEIGHT_RATIO`
const DEFAULT_LINE_HEIGHT_RATIO: f32 = 1.43; const DEFAULT_LINE_HEIGHT_RATIO: f32 = 1.43;
@@ -77,7 +77,11 @@ impl From<&TextStyle> for Metrics {
impl From<&TextStyle> for Wrap { impl From<&TextStyle> for Wrap {
fn from(value: &TextStyle) -> Self { fn from(value: &TextStyle) -> Self {
if value.wrap { Self::WordOrGlyph } else { Self::None } if value.wrap {
Self::WordOrGlyph
} else {
Self::None
}
} }
} }

View File

@@ -20,7 +20,6 @@ pub struct Cell {
} }
pub(crate) struct Blueprint { pub(crate) struct Blueprint {
pub position: Vec2,
pub cells: Vec<Cell>, pub cells: Vec<Cell>,
} }
@@ -43,23 +42,25 @@ pub struct ContextMenu {
tasks: Tasks<Task>, tasks: Tasks<Task>,
} }
fn doc_params<'a>( fn doc_params<'a>(globals: &WguiGlobals) -> parser::ParseDocumentParams<'a> {
globals: &WguiGlobals,
on_custom_attribs: Option<parser::OnCustomAttribsFunc>,
) -> parser::ParseDocumentParams<'a> {
parser::ParseDocumentParams { parser::ParseDocumentParams {
globals: globals.clone(), globals: globals.clone(),
path: AssetPath::WguiInternal("wgui/context_menu.xml"), path: AssetPath::WguiInternal("wgui/context_menu.xml"),
extra: parser::ParseDocumentExtra { extra: Default::default(),
on_custom_attribs,
..Default::default()
},
} }
} }
#[derive(Default)] #[derive(Default)]
pub struct TickResult { pub enum TickResult {
pub action_name: Option<Rc<str>>, /// Nothing happened
#[default]
None,
/// The context menu was opened.
Opened,
/// User has selected an action.
Action(Rc<str>),
/// The context menu was closed without an action.
Closed,
} }
impl ContextMenu { impl ContextMenu {
@@ -77,8 +78,7 @@ impl ContextMenu {
layout: &mut Layout, layout: &mut Layout,
parser_state: &mut ParserState, parser_state: &mut ParserState,
) -> anyhow::Result<()> { ) -> anyhow::Result<()> {
let blueprint = let blueprint = parser_state.context_menu_create_blueprint(&params.template_name, &params.template_params)?;
parser_state.context_menu_create_blueprint(&params.template_name, &params.template_params, params.position)?;
let globals = layout.state.globals.clone(); let globals = layout.state.globals.clone();
@@ -94,19 +94,20 @@ impl ContextMenu {
})?; })?;
let content = self.window.get_content(); let content = self.window.get_content();
let doc_params = doc_params(&globals, params.on_custom_attribs.clone()); let doc_params = doc_params(&globals);
let mut state = parser::parse_from_assets(&doc_params, layout, content.id)?; let mut inner_parser = parser::parse_from_assets(&doc_params, layout, content.id)?;
let id_buttons = state.get_widget_id("buttons")?; let id_buttons = inner_parser.get_widget_id("buttons")?;
for (idx, cell) in blueprint.cells.iter().enumerate() { for (idx, cell) in blueprint.cells.iter().enumerate() {
let mut par = HashMap::new(); let mut par = HashMap::new();
par.insert(Rc::from("text"), cell.title.generate(&mut globals.i18n())); par.insert(Rc::from("text"), cell.title.generate(&mut globals.i18n()));
let data_cell = state.parse_template(&doc_params, "Cell", layout, id_buttons, par)?; let mut data_cell = inner_parser.parse_template(&doc_params, "Cell", layout, id_buttons, par)?;
let button = data_cell.fetch_component_as::<ComponentButton>("button")?; let button = data_cell.fetch_component_as::<ComponentButton>("button")?;
let button_id = button.base().get_id(); let button_id = button.base().get_id();
parser_state.data.take_results_from(&mut data_cell);
self self
.tasks .tasks
.handle_button(&button, Task::ActionClicked(cell.action_name.clone())); .handle_button(&button, Task::ActionClicked(cell.action_name.clone()));
@@ -121,7 +122,7 @@ impl ContextMenu {
} }
if idx < blueprint.cells.len() - 1 { if idx < blueprint.cells.len() - 1 {
state.parse_template(&doc_params, "Separator", layout, id_buttons, Default::default())?; inner_parser.parse_template(&doc_params, "Separator", layout, id_buttons, Default::default())?;
} }
} }
Ok(()) Ok(())
@@ -130,14 +131,20 @@ impl ContextMenu {
pub fn tick(&mut self, layout: &mut Layout, parser_state: &mut ParserState) -> anyhow::Result<TickResult> { pub fn tick(&mut self, layout: &mut Layout, parser_state: &mut ParserState) -> anyhow::Result<TickResult> {
if let Some(mut p) = self.pending_open.take() { if let Some(mut p) = self.pending_open.take() {
self.open_process(&mut p, layout, parser_state)?; self.open_process(&mut p, layout, parser_state)?;
let _ = self.tasks.drain();
return Ok(TickResult::Opened);
} }
let mut result = TickResult::default(); let mut result = TickResult::default();
for task in self.tasks.drain() { for task in self.tasks.drain() {
match task { match task {
Task::ActionClicked(action_name) => { Task::ActionClicked(Some(action_name)) => {
result.action_name = action_name; result = TickResult::Action(action_name);
self.close();
}
Task::ActionClicked(None) => {
result = TickResult::Closed;
self.close(); self.close();
} }
} }

View File

@@ -14,10 +14,10 @@ use crate::{
layout::{Layout, LayoutTask, LayoutTasks, WidgetPair}, layout::{Layout, LayoutTask, LayoutTasks, WidgetPair},
parser::{self, Fetchable, ParserState}, parser::{self, Fetchable, ParserState},
widget::{ widget::{
EventResult,
div::WidgetDiv, div::WidgetDiv,
label::WidgetLabel, label::WidgetLabel,
rectangle::{WidgetRectangle, WidgetRectangleParams}, rectangle::{WidgetRectangle, WidgetRectangleParams},
EventResult,
}, },
}; };

View File

@@ -249,4 +249,7 @@ pub struct GeneralConfig {
#[serde(default)] #[serde(default)]
pub xwayland_by_default: bool, pub xwayland_by_default: bool,
#[serde(default)]
pub context_menu_hold_and_release: bool,
} }

View File

@@ -1,12 +1,12 @@
<layout> <layout>
<macro name="keycap_rect" <macro name="keycap_rect"
margin="2" width="100%" overflow="hidden" box_sizing="border_box" margin="2" width="100%" overflow="hidden" box_sizing="border_box"
border_color="~color_accent_translucent" border="2" round="8" color="~color_accent_40" color2="~color_accent_10" gradient="vertical" border_color="~color_accent_translucent" border="2" round="6" color="~color_accent_40" color2="~color_accent_10" gradient="vertical"
align_items="center" justify_content="center" /> align_items="center" justify_content="center" />
<macro name="tray_rect" <macro name="tray_rect"
margin="2" width="100%" overflow="hidden" box_sizing="border_box" margin="2" width="100%" overflow="hidden" box_sizing="border_box"
border_color="~color_accent_translucent" border="2" round="8" color="~color_bg" color2="~color_accent_10" gradient="vertical" border_color="~color_accent_translucent" border="2" round="6" color="~color_bg" color2="~color_accent_10" gradient="vertical"
align_items="center" justify_content="center" /> align_items="center" justify_content="center" />
<macro name="keycap_div" <macro name="keycap_div"
@@ -24,7 +24,7 @@
<template name="KeySpecial"> <template name="KeySpecial">
<div macro="keycap_div"> <div macro="keycap_div">
<rectangle id="${id}" macro="keycap_rect"> <rectangle id="${id}" macro="keycap_rect">
<sprite color="~color_text" width="32" height="32" src="keyboard/${text}.svg" /> <sprite color="~color_text" width="21" height="21" src="keyboard/${text}.svg" />
</rectangle> </rectangle>
</div> </div>
</template> </template>
@@ -34,7 +34,7 @@
<template name="KeyLetter"> <template name="KeyLetter">
<div macro="keycap_div"> <div macro="keycap_div">
<rectangle id="${id}" macro="keycap_rect"> <rectangle id="${id}" macro="keycap_rect">
<label text="${text}" size="24" /> <label text="${text}" size="16" />
</rectangle> </rectangle>
</div> </div>
</template> </template>
@@ -43,9 +43,9 @@
<!-- Used for letter keys on layouts with AltGr. --> <!-- Used for letter keys on layouts with AltGr. -->
<template name="KeyLetterAltGr"> <template name="KeyLetterAltGr">
<div macro="keycap_div"> <div macro="keycap_div">
<rectangle id="${id}" macro="keycap_rect" gap="4"> <rectangle id="${id}" macro="keycap_rect" gap="3">
<label text="${text}" size="24" /> <label text="${text}" size="16" />
<label color="~color_text_translucent" text="${text_altgr}" size="24" /> <label color="~color_text_translucent" text="${text_altgr}" size="16" />
</rectangle> </rectangle>
</div> </div>
</template> </template>
@@ -54,9 +54,9 @@
<!-- Used for number & symbol keys on layouts without AltGr. --> <!-- Used for number & symbol keys on layouts without AltGr. -->
<template name="KeySymbol"> <template name="KeySymbol">
<div macro="keycap_div"> <div macro="keycap_div">
<rectangle id="${id}" macro="keycap_rect" gap="4"> <rectangle id="${id}" macro="keycap_rect" gap="3">
<label color="~color_text_translucent" text="${text_shift}" size="24" /> <label color="~color_text_translucent" text="${text_shift}" size="16" />
<label text="${text}" size="24" /> <label text="${text}" size="16" />
</rectangle> </rectangle>
</div> </div>
</template> </template>
@@ -67,149 +67,139 @@
<div macro="keycap_div"> <div macro="keycap_div">
<rectangle id="${id}" macro="keycap_rect" flex_direction="row" flex_wrap="wrap"> <rectangle id="${id}" macro="keycap_rect" flex_direction="row" flex_wrap="wrap">
<div width="50%" height="50%" align_items="center" justify_content="center"> <div width="50%" height="50%" align_items="center" justify_content="center">
<label color="~color_text_translucent" text="${text_shift}" size="24" /> <label color="~color_text_translucent" text="${text_shift}" size="16" />
</div> </div>
<div width="50%" height="50%" align_items="center" justify_content="center" /> <div width="50%" height="50%" align_items="center" justify_content="center" />
<div width="50%" height="50%" align_items="center" justify_content="center"> <div width="50%" height="50%" align_items="center" justify_content="center">
<label text="${text}" size="24" /> <label text="${text}" size="16" />
</div> </div>
<div width="50%" height="50%" align_items="center" justify_content="center"> <div width="50%" height="50%" align_items="center" justify_content="center">
<label color="~color_text_translucent" text="${text_altgr}" size="24" /> <label color="~color_text_translucent" text="${text_altgr}" size="16" />
</div> </div>
</rectangle> </rectangle>
</div> </div>
</template> </template>
<macro name="button_style" border="2" border_color="~color_accent_translucent" color="~color_bg" round="8" <macro name="button_style" border="2" border_color="~color_accent_translucent" color="~color_bg" round="6"
align_items="center" justify_content="center" padding="8" width="80" height="80" overflow="visible"/> align_items="center" justify_content="center" padding="6" width="60" height="60" overflow="visible"/>
<macro name="menu_button_style" border="2" border_color="~color_accent_translucent" color="~color_bg" round="8" <macro name="bg_rect" width="100%" color="~color_bg" round="10" border="2" border_color="~color_accent" />
align_items="center" justify_content="center" padding="8" width="100%" height="60" />
<macro name="bg_rect" width="100%" color="~color_bg" round="16" border="2" border_color="~color_accent" /> <blueprint name="menu_app">
<macro name="dropdown_root" new_pass="1" width="200" color="~color_bg" flex_direction="column" position="absolute" margin_top="80" display="none" /> <context_menu >
<macro name="dropdown_title" color="~color_faded_50" width="100%" padding="16" overflow="hidden" /> <!-- title text="${name}" /-->
<cell translation="BAR.TOGGLE_VISIBILITY" _release="::OverlayToggle ${name}" />
<template name="MenuButton"> <cell translation="BAR.RESET_POSITION" _release="::OverlayReset ${name}" />
<Button macro="menu_button_style" _release="${action}"> <cell translation="BAR.CLOSE_APP" _release="::WvrOverlayTermProcess ${name}" />
<label translation="${translation}" size="24" /> <cell translation="BAR.FORCE_CLOSE_APP" _release="::WvrOverlayKillProcess ${name}" />
</Button> </context_menu>
</template> </blueprint>
<!-- An app with a single icon. --> <!-- An app with a single icon. -->
<template name="App"> <template name="App">
<div> <Button macro="button_style" id="overlay_${idx}" tooltip_str="${name}" _press="::ContextMenuOpen menu_app">
<Button macro="button_style" id="overlay_${idx}" _press="::OverlayToggle ${name}" _release="::ElementSetDisplay dropdown_${idx} none"> <sprite width="38" height="38" color="~text_color" src_ext="${icon}" />
<sprite width="56" height="56" color="~text_color" src_ext="${icon}" /> </Button>
</Button>
<div macro="dropdown_root" id="dropdown_${idx}">
<rectangle macro="dropdown_title">
<label text="${name}" weight="bold" size="24" />
</rectangle>
<MenuButton translation="BAR.TOGGLE_IN_SET" action="::OverlayToggle ${name}" />
<MenuButton translation="BAR.CLOSE" action="::WvrOverlayTermProcess ${name}" />
<MenuButton translation="BAR.FORCE_CLOSE" action="::WvrOverlayKillProcess ${name}" />
</div>
</div>
</template> </template>
<blueprint name="menu_screen">
<context_menu >
<!-- title text="${name}" /-->
<cell translation="BAR.TOGGLE_VISIBILITY" _press="::OverlayToggle ${name}" />
<cell translation="BAR.RESET_POSITION" _press="::OverlayReset ${name}" />
</context_menu>
</blueprint>
<!-- A screen with a shortened connector name, e.g. "H1" for HDMI-A-1 or "D2" for DP-2 --> <!-- A screen with a shortened connector name, e.g. "H1" for HDMI-A-1 or "D2" for DP-2 -->
<template name="Screen"> <template name="Screen">
<div> <Button macro="button_style" id="overlay_${idx}" tooltip_str="${name}" _context_name="${name}" _press="::ContextMenuOpen menu_screen">
<Button macro="button_style" id="overlay_${idx}" _press="::OverlayToggle ${name}" _release="::ElementSetDisplay dropdown_${idx} none"> <sprite width="38" height="38" color="~text_color" src_builtin="edit/screen.svg" />
<sprite width="56" height="56" color="~text_color" src_builtin="edit/screen.svg" /> <div position="absolute" margin_top="-7" margin_left="-1">
<div position="absolute" margin_top="-10"> <label text="${display}" size="18" color="~color_faded_20" weight="bold" />
<label text="${display}" size="26" color="~color_faded_20" weight="bold" />
</div>
</Button>
<div macro="dropdown_root" id="dropdown_${idx}">
<rectangle macro="dropdown_title">
<label text="${name}" weight="bold" size="24" />
</rectangle>
<MenuButton translation="BAR.TOGGLE_IN_SET" action="::OverlayToggle ${name}" />
</div> </div>
</div> </Button>
</template> </template>
<blueprint name="menu_panel">
<context_menu >
<!-- title text="${name}" /-->
<cell translation="BAR.TOGGLE_VISIBILITY" _release="::OverlayToggle ${name}" />
<cell translation="BAR.RESET_POSITION" _release="::OverlayReset ${name}" />
<cell translation="BAR.RELOAD_FROM_DISK" _release="::CustomOverlayReload ${name}" />
</context_menu>
</blueprint>
<template name="Panel"> <template name="Panel">
<div> <Button macro="button_style" id="overlay_${idx}" tooltip_str="${name}" _press="::ContextMenuOpen menu_panel">
<Button macro="button_style" id="overlay_${idx}" _press="::OverlayToggle ${name}" _release="::ElementSetDisplay dropdown_${idx} none"> <sprite width="38" height="38" color="~text_color" src_builtin="edit/panel.svg" />
<sprite width="56" height="56" color="~text_color" src_builtin="edit/panel.svg" /> </Button>
</Button>
<div macro="dropdown_root" id="dropdown_${idx}">
<rectangle macro="dropdown_title">
<label text="${name}" weight="bold" size="24" />
</rectangle>
<MenuButton translation="BAR.TOGGLE_IN_SET" action="::OverlayToggle ${name}" />
</div>
</div>
</template> </template>
<blueprint name="menu_mirror">
<context_menu >
<!-- title text="${name}" /-->
<cell translation="BAR.TOGGLE_VISIBILITY" _release="::OverlayToggle ${name}" />
<cell translation="BAR.RESET_POSITION" _release="::OverlayReset ${name}" />
<cell translation="BAR.CLOSE_MIRROR" _release="::OverlayDrop ${name}" />
</context_menu>
</blueprint>
<template name="Mirror"> <template name="Mirror">
<div> <Button macro="button_style" id="overlay_${idx}" tooltip_str="${name}" _press="::ContextMenuOpen menu_mirror">
<Button macro="button_style" id="overlay_${idx}" _press="::OverlayToggle ${name}" _release="::ElementSetDisplay dropdown_${idx} none"> <sprite width="38" height="38" color="~text_color" src_builtin="edit/mirror.svg" />
<sprite width="56" height="56" color="~text_color" src_builtin="edit/mirror.svg" /> <div position="absolute" margin_top="5" margin_left="13">
<div position="absolute" margin_top="7" margin_left="20"> <label text="${display}" size="20" color="~color_faded_20" weight="bold" />
<label text="${display}" size="26" color="~color_faded_20" weight="bold" />
</div>
</Button>
<div macro="dropdown_root" id="dropdown_${idx}">
<rectangle macro="dropdown_title">
<label text="${name}" weight="bold" size="24" />
</rectangle>
<MenuButton translation="BAR.TOGGLE_IN_SET" action="::OverlayToggle ${name}" />
<MenuButton translation="BAR.CLOSE" action="::OverlayDrop ${name}" />
</div> </div>
</div> </Button>
</template> </template>
<template name="Set"> <template name="Set">
<Button macro="button_style" id="set_${idx}" _press="::SetSwitch ${idx}" tooltip="WATCH.SWITCH_TO_SET" tooltip_side="bottom"> <Button macro="button_style" id="set_${idx}" _press="::SetSwitch ${idx}" tooltip="WATCH.SWITCH_TO_SET" tooltip_side="bottom">
<sprite width="56" height="56" color="~text_color" src_builtin="watch/set2.svg" /> <sprite width="38" height="38" color="~text_color" src_builtin="watch/set2.svg" />
<div position="absolute" margin_top="16" margin_left="-8"> <div position="absolute" margin_top="10" margin_left="-7">
<label text="${display}" size="26" color="~color_faded_20" weight="bold" /> <label text="${display}" size="20" color="~color_faded_20" weight="bold" />
</div> </div>
</Button> </Button>
</template> </template>
<elements> <elements>
<div flex_direction="column" interactable="0"> <div flex_direction="column" interactable="0">
<rectangle macro="bg_rect" padding="16" align_items="center" justify_content="space_between"> <rectangle macro="bg_rect" padding="10" align_items="center" justify_content="space_between">
<div gap="16"> <div gap="10">
<Button macro="button_style" id="btn_dashboard" _press="::DashToggle"> <Button macro="button_style" id="btn_dashboard" _press="::DashToggle">
<sprite width="56" height="56" color="~text_color" src="watch/wayvr_dashboard_mono.svg" /> <sprite width="38" height="38" color="~text_color" src="watch/wayvr_dashboard_mono.svg" />
</Button> </Button>
<VerticalSeparator /> <VerticalSeparator />
<div id="panels_root" gap="8"> <div id="panels_root" gap="6">
<Screen idx="0" display="H1" name="HDMI-A-1" /> <Screen idx="0" display="H1" name="HDMI-A-1" />
<Screen idx="1" display="D2" name="Screen: DP-2" /> <Screen idx="1" display="D2" name="Screen: DP-2" />
<Mirror idx="1" display="1" name="M1" /> <Mirror idx="1" display="1" name="M1" />
<Panel idx="1" display="Test" name="Test" /> <Panel idx="1" display="Test" name="Test" />
</div> </div>
<VerticalSeparator /> <VerticalSeparator />
<div id="apps_root" gap="8"> <div id="apps_root" gap="6">
<App id="test1" name="Blender" icon="/usr/share/icons/hicolor/scalable/apps/blender-5.0.svg" /> <App id="test1" name="Blender" icon="/usr/share/icons/hicolor/scalable/apps/blender-5.0.svg" />
<App id="test2" name="Inkscape" icon="/usr/share/icons/hicolor/scalable/apps/org.inkscape.Inkscape.svg" /> <App id="test2" name="Inkscape" icon="/usr/share/icons/hicolor/scalable/apps/org.inkscape.Inkscape.svg" />
<App id="test3" name="GIMP" icon="/usr/share/icons/hicolor/scalable/apps/gimp.svg" /> <App id="test3" name="GIMP" icon="/usr/share/icons/hicolor/scalable/apps/gimp.svg" />
</div> </div>
</div> </div>
<div id="tray_root" flex_direction="row" gap="16"> <div id="tray_root" flex_direction="row" gap="10">
<div id="sets_root" flex_direction="row" gap="8"> <div id="sets_root" flex_direction="row" gap="6">
<Set idx="0" display="1" /> <Set idx="0" display="1" />
<Set idx="1" display="2" /> <Set idx="1" display="2" />
</div> </div>
<VerticalSeparator /> <VerticalSeparator />
<div flex_direction="column" gap="4" align_items="center"> <div flex_direction="column" gap="3" align_items="center">
<label text="23:59" _source="clock" _display="time" size="32" weight="bold" /> <label text="23:59" _source="clock" _display="time" size="21" weight="bold" />
<label text="Tuesday" _source="clock" _display="dow" size="22" /> <label text="Tuesday" _source="clock" _display="dow" size="15" />
<label text="22/2/2022" _source="clock" _display="date" size="22" /> <label text="22/2/2022" _source="clock" _display="date" size="15" />
</div> </div>
</div> </div>
</rectangle> </rectangle>
<div width="100%" height="20" interactable="0" /> <div width="100%" height="13" interactable="0" />
<rectangle id="keyboard_root" macro="bg_rect" flex_direction="column" padding="16"> <rectangle id="keyboard_root" macro="bg_rect" flex_direction="column" padding="10">
</rectangle> </rectangle>
</div> </div>
</elements> </elements>

View File

@@ -3,10 +3,12 @@
"CENTER": "Center" "CENTER": "Center"
}, },
"BAR": { "BAR": {
"HIDE": "Hide", "TOGGLE_VISIBILITY": "Toggle visibility",
"TOGGLE_IN_SET": "Toggle in set", "RESET_POSITION": "Reset position",
"CLOSE": "Close", "RELOAD_FROM_DISK": "Reload XML from disk",
"FORCE_CLOSE": "Force close" "CLOSE_MIRROR": "Close mirror",
"CLOSE_APP": "Close app",
"FORCE_CLOSE_APP": "Force close app"
}, },
"DEFAULT": "Default", "DEFAULT": "Default",
"DISABLED": "Disabled", "DISABLED": "Disabled",

View File

@@ -1,5 +1,6 @@
use std::{ use std::{
cell::RefCell, cell::RefCell,
collections::HashMap,
process::{Child, Command, Stdio}, process::{Child, Command, Stdio},
rc::Rc, rc::Rc,
str::FromStr, str::FromStr,
@@ -16,19 +17,21 @@ use wgui::{
}, },
layout::Layout, layout::Layout,
log::LogErr, log::LogErr,
parser::{CustomAttribsInfoOwned, Fetchable, ParserState}, parser::{self, AttribPair, CustomAttribsInfoOwned, Fetchable, ParserState},
taffy, taffy,
widget::EventResult, widget::EventResult,
windowing::context_menu::{ContextMenu, OpenParams},
}; };
use wlx_common::overlays::ToastTopic; use wlx_common::overlays::ToastTopic;
use crate::{ use crate::{
RUNNING, RUNNING,
backend::{ backend::{
XrBackend,
task::{OverlayTask, PlayspaceTask, TaskType}, task::{OverlayTask, PlayspaceTask, TaskType},
wayvr::process::KillSignal, wayvr::process::KillSignal,
}, },
overlays::{custom::create_custom, dashboard::DASH_NAME, toast::Toast, wayvr::WvrCommand}, overlays::{custom::create_custom, toast::Toast, wayvr::WvrCommand},
state::AppState, state::AppState,
subsystem::hid::VirtualKey, subsystem::hid::VirtualKey,
windowing::{OverlaySelector, backend::OverlayEventData, window::OverlayCategory}, windowing::{OverlaySelector, backend::OverlayEventData, window::OverlayCategory},
@@ -186,7 +189,8 @@ pub(super) fn setup_custom_button<S: 'static>(
layout: &mut Layout, layout: &mut Layout,
parser_state: &ParserState, parser_state: &ParserState,
attribs: &CustomAttribsInfoOwned, attribs: &CustomAttribsInfoOwned,
_app: &AppState, context_menu: &Rc<RefCell<ContextMenu>>,
on_custom_attribs: &parser::OnCustomAttribsFunc,
button: Rc<ComponentButton>, button: Rc<ComponentButton>,
) { ) {
for (name, kind, test_button, test_duration) in &BUTTON_EVENTS { for (name, kind, test_button, test_duration) in &BUTTON_EVENTS {
@@ -202,6 +206,49 @@ pub(super) fn setup_custom_button<S: 'static>(
let button = button.clone(); let button = button.clone();
let callback: EventCallback<AppState, S> = match command { let callback: EventCallback<AppState, S> = match command {
"::ContextMenuOpen" => {
let Some(template_name) = args.next() else {
log::warn!(
"{command} has incorrect arguments. Should be: {command} <context_menu>"
);
return;
};
// pass attribs with key `_context_{name}` to the context_menu template
let mut template_params = HashMap::new();
for AttribPair { attrib, value } in &attribs.pairs {
const PREFIX: &'static str = "_context_";
if attrib.starts_with(PREFIX) {
template_params.insert(attrib[PREFIX.len()..].into(), value.clone());
}
}
log::warn!("Context params: {template_params:?}");
let template_name: Rc<str> = template_name.into();
let context_menu = context_menu.clone();
let on_custom_attribs = on_custom_attribs.clone();
Box::new({
move |_common, data, _app, _| {
context_menu.borrow_mut().open(OpenParams {
on_custom_attribs: Some(on_custom_attribs.clone()),
template_name: template_name.clone(),
template_params: template_params.clone(),
position: data.metadata.get_mouse_pos_absolute().unwrap(), //want panic
});
Ok(EventResult::Consumed)
}
})
}
"::ContextMenuClose" => {
let context_menu = context_menu.clone();
Box::new(move |_common, _data, _app, _| {
context_menu.borrow_mut().close();
Ok(EventResult::Consumed)
})
}
"::ElementSetDisplay" => { "::ElementSetDisplay" => {
let (Some(id), Some(value)) = (args.next(), args.next()) else { let (Some(id), Some(value)) = (args.next(), args.next()) else {
log::warn!( log::warn!(
@@ -286,6 +333,8 @@ pub(super) fn setup_custom_button<S: 'static>(
return; return;
}; };
log::warn!("{command} {arg}");
Box::new(move |_common, data, app, _| { Box::new(move |_common, data, app, _| {
if !test_button(data) || !test_duration(&button, app) { if !test_button(data) || !test_duration(&button, app) {
return Ok(EventResult::Pass); return Ok(EventResult::Pass);
@@ -506,6 +555,23 @@ pub(super) fn setup_custom_button<S: 'static>(
RUNNING.store(false, Ordering::Relaxed); RUNNING.store(false, Ordering::Relaxed);
Ok(EventResult::Consumed) Ok(EventResult::Consumed)
}), }),
"::Restart" => Box::new(move |_common, data, app, _| {
if !test_button(data) || !test_duration(&button, app) {
return Ok(EventResult::Pass);
}
let runtime = match app.xr_backend {
XrBackend::OpenVR => "--openvr",
XrBackend::OpenXR => "--openxr",
};
Command::new("/proc/self/exe")
.arg(runtime) // ensure same runtime
.arg("--replace") // SIGTERM the previous process
.arg("--show");
Ok(EventResult::Consumed)
}),
"::SendKey" => { "::SendKey" => {
let Some(key) = args.next().and_then(|s| VirtualKey::from_str(s).ok()) else { let Some(key) = args.next().and_then(|s| VirtualKey::from_str(s).ok()) else {
log::error!("{command} has bad/missing arguments"); log::error!("{command} has bad/missing arguments");

View File

@@ -14,9 +14,10 @@ use wgui::{
}, },
gfx::cmd::WGfxClearMode, gfx::cmd::WGfxClearMode,
layout::{Layout, LayoutParams, LayoutUpdateParams, WidgetID}, layout::{Layout, LayoutParams, LayoutUpdateParams, WidgetID},
parser::{CustomAttribsInfoOwned, Fetchable, ParseDocumentExtra, ParserState}, parser::{self, CustomAttribsInfoOwned, Fetchable, ParseDocumentExtra, ParserState},
renderer_vk::context::Context as WguiContext, renderer_vk::context::Context as WguiContext,
widget::{EventResult, label::WidgetLabel}, widget::{EventResult, label::WidgetLabel},
windowing::context_menu::{self, ContextMenu},
}; };
use wlx_common::overlays::{BackendAttrib, BackendAttribValue}; use wlx_common::overlays::{BackendAttrib, BackendAttribValue};
use wlx_common::timestep::Timestep; use wlx_common::timestep::Timestep;
@@ -59,7 +60,9 @@ pub struct GuiPanel<S> {
has_focus: [bool; 2], has_focus: [bool; 2],
last_content_size: Vec2, last_content_size: Vec2,
custom_elems: Rc<RefCell<Vec<CustomAttribsInfoOwned>>>, custom_elems: Rc<RefCell<Vec<CustomAttribsInfoOwned>>>,
context_menu: Rc<RefCell<ContextMenu>>,
on_custom_attrib: Option<OnCustomAttribFunc>, on_custom_attrib: Option<OnCustomAttribFunc>,
on_custom_attrib_inner: parser::OnCustomAttribsFunc,
} }
pub type OnCustomIdFunc<S> = Box< pub type OnCustomIdFunc<S> = Box<
@@ -105,6 +108,13 @@ impl<S: 'static> GuiPanel<S> {
) -> anyhow::Result<Self> { ) -> anyhow::Result<Self> {
let custom_elems = Rc::new(RefCell::new(vec![])); let custom_elems = Rc::new(RefCell::new(vec![]));
let on_custom_attrib_inner: parser::OnCustomAttribsFunc = Rc::new({
let custom_elems = custom_elems.clone();
move |attribs| {
custom_elems.borrow_mut().push(attribs.to_owned());
}
});
let doc_params = wgui::parser::ParseDocumentParams { let doc_params = wgui::parser::ParseDocumentParams {
globals: app.wgui_globals.clone(), globals: app.wgui_globals.clone(),
path: if params.external_xml { path: if params.external_xml {
@@ -113,12 +123,7 @@ impl<S: 'static> GuiPanel<S> {
AssetPath::FileOrBuiltIn(path) AssetPath::FileOrBuiltIn(path)
}, },
extra: wgui::parser::ParseDocumentExtra { extra: wgui::parser::ParseDocumentExtra {
on_custom_attribs: Some(Rc::new({ on_custom_attribs: Some(on_custom_attrib_inner.clone()),
let custom_elems = custom_elems.clone();
move |attribs| {
custom_elems.borrow_mut().push(attribs.to_owned());
}
})),
..Default::default() ..Default::default()
}, },
}; };
@@ -164,13 +169,16 @@ impl<S: 'static> GuiPanel<S> {
last_content_size: Vec2::ZERO, last_content_size: Vec2::ZERO,
doc_extra: Some(doc_params.extra), doc_extra: Some(doc_params.extra),
custom_elems, custom_elems,
context_menu: Default::default(),
on_custom_attrib: params.on_custom_attrib, on_custom_attrib: params.on_custom_attrib,
on_custom_attrib_inner,
}; };
me.process_custom_elems(app); me.process_custom_elems(app);
Ok(me) Ok(me)
} }
/// Perform initial setup on newly added elements.
pub fn process_custom_elems(&mut self, app: &mut AppState) { pub fn process_custom_elems(&mut self, app: &mut AppState) {
let mut elems = self.custom_elems.borrow_mut(); let mut elems = self.custom_elems.borrow_mut();
for elem in elems.iter() { for elem in elems.iter() {
@@ -186,7 +194,14 @@ impl<S: 'static> GuiPanel<S> {
.parser_state .parser_state
.fetch_component_from_widget_id_as::<ComponentButton>(elem.widget_id) .fetch_component_from_widget_id_as::<ComponentButton>(elem.widget_id)
{ {
setup_custom_button::<S>(&mut self.layout, &self.parser_state, elem, app, button); setup_custom_button::<S>(
&mut self.layout,
&self.parser_state,
elem,
&self.context_menu,
&self.on_custom_attrib_inner,
button,
);
} }
if let Some(on_custom_attrib) = &self.on_custom_attrib { if let Some(on_custom_attrib) = &self.on_custom_attrib {
@@ -268,6 +283,14 @@ impl<S: 'static> OverlayBackend for GuiPanel<S> {
return Ok(ShouldRender::Unable); return Ok(ShouldRender::Unable);
} }
let tick_result = self
.context_menu
.borrow_mut()
.tick(&mut self.layout, &mut self.parser_state)?;
if matches!(tick_result, context_menu::TickResult::Opened) {
self.process_custom_elems(app);
}
if !self if !self
.last_content_size .last_content_size
.abs_diff_eq(self.layout.content_size, 0.1 /* pixels */) .abs_diff_eq(self.layout.content_size, 0.1 /* pixels */)

View File

@@ -30,7 +30,7 @@ use super::{
layout::{self, KeyCapType}, layout::{self, KeyCapType},
}; };
const PIXELS_PER_UNIT: f32 = 80.; const PIXELS_PER_UNIT: f32 = 60.;
fn new_doc_params(panel: &mut GuiPanel<KeyboardState>) -> ParseDocumentParams<'static> { fn new_doc_params(panel: &mut GuiPanel<KeyboardState>) -> ParseDocumentParams<'static> {
ParseDocumentParams { ParseDocumentParams {
@@ -335,7 +335,10 @@ pub(super) fn create_keyboard_panel(
("Screen", panels_root) ("Screen", panels_root)
} }
OverlayCategory::Mirror => { OverlayCategory::Mirror => {
params.insert("display".into(), meta.name.as_ref().into()); params.insert(
"display".into(),
(*meta.name).chars().last().unwrap().to_string().into(),
);
("Mirror", panels_root) ("Mirror", panels_root)
} }
OverlayCategory::Panel => ("Panel", panels_root), OverlayCategory::Panel => ("Panel", panels_root),