watch rework

This commit is contained in:
galister
2026-01-08 01:06:54 +09:00
parent 89e8d606a8
commit 4d1154cdcb
25 changed files with 376 additions and 730 deletions

View File

@@ -16,7 +16,6 @@
"HIDE_GRAB_HELP": "Greif-Hilfe ausblenden",
"ANIMATION_SPEED": "UI-Animationsgeschwindigkeit",
"ROUND_MULTIPLIER": "UI-Kantenrundung",
"SINGLE_SET_MODE": "Einzelnes Modus",
"USE_SKYBOX": "Skybox aktivieren",
"USE_PASSTHROUGH": "Passthrough aktivieren",
"CLOCK_12H": "12-Stunden-Uhr",
@@ -48,7 +47,6 @@
"SCREEN_RENDER_DOWN": "Bildschirm bei niedrigerer Auflösung rendern",
"UPRIGHT_SCREEN_FIX_HELP": "Behebt hochstehende Bildschirme auf einigen Desktops",
"DOUBLE_CURSOR_FIX_HELP": "Aktivieren Sie dies, wenn Sie 2 Cursor sehen",
"SINGLE_SET_MODE_HELP": "Optimieren Sie die Anzeige für die Arbeit mit einem einzelnen Monitor",
"XR_CLICK_SENSITIVITY_HELP": "Analoge Trigger-Empfindlichkeit",
"XR_CLICK_SENSITIVITY_RELEASE_HELP": "Muss niedriger als Klick sein",
"CLICK_FREEZE_TIME_MS_HELP": "Hilft bei der Präzision von Doppelklicks",
@@ -102,4 +100,4 @@
},
"TERMINATE_PROCESS": "Prozess beenden",
"GAME_LAUNCHED": "Spiel gestartet"
}
}

View File

@@ -10,7 +10,7 @@
"HIDE_GRAB_HELP": "Hide grab help",
"ANIMATION_SPEED": "UI Animation speed",
"ROUND_MULTIPLIER": "UI Edge roundness",
"SINGLE_SET_MODE": "Single set mode",
"SETS_ON_WATCH": "Sets on watch",
"USE_SKYBOX": "Enable skybox",
"USE_PASSTHROUGH": "Enable passthrough",
"CLOCK_12H": "12-hour clock",
@@ -49,7 +49,6 @@
"UPRIGHT_SCREEN_FIX_HELP": "Fixes upright screens on some desktops",
"DOUBLE_CURSOR_FIX_HELP": "Enable this if you see 2 cursors",
"SINGLE_SET_MODE_HELP": "Optimize the watch for working with a single set",
"XR_CLICK_SENSITIVITY_HELP": "Analog trigger sensitivity",
"XR_CLICK_SENSITIVITY_RELEASE_HELP": "Must be lower than click",
"CLICK_FREEZE_TIME_MS_HELP": "Helps with double-click precision",

View File

@@ -16,7 +16,6 @@
"HIDE_GRAB_HELP": "掴み操作のヘルプを非表示にする",
"ANIMATION_SPEED": "UIアニメーション速度",
"ROUND_MULTIPLIER": "UI エッジの丸み",
"SINGLE_SET_MODE": "単一設定モード",
"USE_SKYBOX": "スカイボックスを有効にする",
"USE_PASSTHROUGH": "パススルーを有効にする",
"CLOCK_12H": "12時間制",
@@ -48,7 +47,6 @@
"SCREEN_RENDER_DOWN": "低い解像度で画面をレンダリングする",
"UPRIGHT_SCREEN_FIX_HELP": "一部のデスクトップで縦向きの画面を修正",
"DOUBLE_CURSOR_FIX_HELP": "2つのカーソルが表示される場合は、これを有効にします",
"SINGLE_SET_MODE_HELP": "シングルセットでの作業用に最適化",
"XR_CLICK_SENSITIVITY_HELP": "アナログトリガの感度",
"XR_CLICK_SENSITIVITY_RELEASE_HELP": "クリックより低くする必要があります",
"CLICK_FREEZE_TIME_MS_HELP": "ダブルクリックの精度向上に役立ちます",

View File

@@ -10,7 +10,6 @@
"HIDE_GRAB_HELP": "Ukryj pomoc dotyczącą chwytania",
"ANIMATION_SPEED": "Prędkość animacji UI",
"ROUND_MULTIPLIER": "Zaokrąglenie krawędzi UI",
"SINGLE_SET_MODE": "Tryb pojedynczego zestawu",
"USE_SKYBOX": "Włącz niebo",
"USE_PASSTHROUGH": "Włącz passthrough",
"CLOCK_12H": "Zegar 12-godzinny",
@@ -42,7 +41,6 @@
"SCREEN_RENDER_DOWN": "Renderuj ekran w niższej rozdzielczości",
"UPRIGHT_SCREEN_FIX_HELP": "Naprawia pionowe ekrany na niektórych komputerach",
"DOUBLE_CURSOR_FIX_HELP": "Włącz to, jeśli widzisz 2 kursory",
"SINGLE_SET_MODE_HELP": "Zoptymalizuj oglądanie dla pracy z jednym zestawem",
"XR_CLICK_SENSITIVITY_HELP": "Czułość analogowego spustu",
"XR_CLICK_SENSITIVITY_RELEASE_HELP": "Musi być niższa niż kliknięcie",
"CLICK_FREEZE_TIME_MS_HELP": "Pomaga w precyzji podwójnego kliknięcia",
@@ -102,4 +100,4 @@
},
"TERMINATE_PROCESS": "Zakończ proces",
"GAME_LAUNCHED": "Gra uruchomiona"
}
}

View File

@@ -36,22 +36,29 @@ impl<T> Tab<T> for TabSettings<T> {
fn update(&mut self, frontend: &mut Frontend<T>, data: &mut T) -> anyhow::Result<()> {
let config = frontend.interface.general_config(data);
let mut changed = false;
for task in self.tasks.drain() {
match task {
Task::UpdateBool(setting, n) => {
setting.get_frontend_task().map(|task| frontend.tasks.push(task));
*setting.mut_bool(config) = n;
changed = true;
}
Task::UpdateFloat(setting, n) => {
setting.get_frontend_task().map(|task| frontend.tasks.push(task));
*setting.mut_f32(config) = n;
changed = true;
}
Task::UpdateInt(setting, n) => {
setting.get_frontend_task().map(|task| frontend.tasks.push(task));
*setting.mut_i32(config) = n;
changed = true;
}
}
}
if changed {
frontend.interface.config_changed(data);
}
Ok(())
}
}
@@ -70,7 +77,7 @@ enum SettingType {
KeyboardSoundEnabled,
UprightScreenFix,
DoubleCursorFix,
SingleSetMode,
SetsOnWatch,
HideGrabHelp,
XrClickSensitivity,
XrClickSensitivityRelease,
@@ -103,7 +110,7 @@ impl SettingType {
Self::KeyboardSoundEnabled => &mut config.keyboard_sound_enabled,
Self::UprightScreenFix => &mut config.upright_screen_fix,
Self::DoubleCursorFix => &mut config.double_cursor_fix,
Self::SingleSetMode => &mut config.single_set_mode,
Self::SetsOnWatch => &mut config.sets_on_watch,
Self::HideGrabHelp => &mut config.hide_grab_help,
Self::AllowSliding => &mut config.allow_sliding,
Self::FocusFollowsMouseMode => &mut config.focus_follows_mouse_mode,
@@ -158,7 +165,7 @@ impl SettingType {
Self::KeyboardSoundEnabled => Ok("APP_SETTINGS.KEYBOARD_SOUND_ENABLED"),
Self::UprightScreenFix => Ok("APP_SETTINGS.UPRIGHT_SCREEN_FIX"),
Self::DoubleCursorFix => Ok("APP_SETTINGS.DOUBLE_CURSOR_FIX"),
Self::SingleSetMode => Ok("APP_SETTINGS.SINGLE_SET_MODE"),
Self::SetsOnWatch => Ok("APP_SETTINGS.SETS_ON_WATCH"),
Self::HideGrabHelp => Ok("APP_SETTINGS.HIDE_GRAB_HELP"),
Self::XrClickSensitivity => Ok("APP_SETTINGS.XR_CLICK_SENSITIVITY"),
Self::XrClickSensitivityRelease => Ok("APP_SETTINGS.XR_CLICK_SENSITIVITY_RELEASE"),
@@ -186,7 +193,6 @@ impl SettingType {
match self {
Self::UprightScreenFix => Some("APP_SETTINGS.UPRIGHT_SCREEN_FIX_HELP"),
Self::DoubleCursorFix => Some("APP_SETTINGS.DOUBLE_CURSOR_FIX_HELP"),
Self::SingleSetMode => Some("APP_SETTINGS.SINGLE_SET_MODE_HELP"),
Self::XrClickSensitivity => Some("APP_SETTINGS.XR_CLICK_SENSITIVITY_HELP"),
Self::XrClickSensitivityRelease => Some("APP_SETTINGS.XR_CLICK_SENSITIVITY_RELEASE_HELP"),
Self::FocusFollowsMouseMode => Some("APP_SETTINGS.FOCUS_FOLLOWS_MOUSE_MODE_HELP"),
@@ -207,7 +213,7 @@ impl SettingType {
| Self::RoundMultiplier
| Self::UprightScreenFix
| Self::DoubleCursorFix
| Self::SingleSetMode
| Self::SetsOnWatch
| Self::UseSkybox
| Self::UsePassthrough
| Self::ScreenRenderDown => true,
@@ -388,7 +394,7 @@ impl<T> TabSettings<T> {
checkbox!(mp, c, SettingType::HideGrabHelp);
slider_f32!(mp, c, SettingType::AnimationSpeed, 0.5, 5.0, 0.1); // min, max, step
slider_f32!(mp, c, SettingType::RoundMultiplier, 0.5, 5.0, 0.1);
checkbox!(mp, c, SettingType::SingleSetMode);
checkbox!(mp, c, SettingType::SetsOnWatch);
checkbox!(mp, c, SettingType::UseSkybox);
checkbox!(mp, c, SettingType::UsePassthrough);
checkbox!(mp, c, SettingType::Clock12h);

View File

@@ -166,7 +166,7 @@ pub struct GeneralConfig {
pub double_cursor_fix: bool,
#[serde(default = "def_false")]
pub single_set_mode: bool,
pub sets_on_watch: bool,
#[serde(default = "def_false")]
pub hide_grab_help: bool,

View File

@@ -16,6 +16,7 @@ pub trait DashInterface<T> {
fn recenter_playspace(&mut self, data: &mut T) -> anyhow::Result<()>;
fn desktop_finder<'a>(&'a mut self, data: &'a mut T) -> &'a mut DesktopFinder;
fn general_config<'a>(&'a mut self, data: &'a mut T) -> &'a mut GeneralConfig;
fn config_changed(&mut self, data: &mut T);
}
pub type BoxDashInterface<T> = Box<dyn DashInterface<T>>;

View File

@@ -178,4 +178,6 @@ impl DashInterface<()> for DashInterfaceEmulated {
fn general_config<'a>(&'a mut self, _: &'a mut ()) -> &'a mut crate::config::GeneralConfig {
&mut self.general_config
}
fn config_changed(&mut self, _: &mut ()) {}
}

View File

@@ -1,133 +0,0 @@
<!--
Variant of the watch with no sets.
All overlays are listed on bottom row.
-->
<layout>
<theme>
<var key="set_color" value="#cad3f5" />
<var key="bgcolor" value="#010206d5" />
<var key="clock0_color" value="#cad3f5" />
<var key="clock0_size" value="46" />
<var key="clock0_date_size" value="16" />
<var key="clock0_dow_size" value="16" />
<var key="clock_alt1_color" value="#8bd5ca" />
<var key="clock_alt2_color" value="#b7bdf8" />
<var key="clock_alt_size" value="24" />
<var key="clock_alt_tz_size" value="14" />
</theme>
<macro name="decorative_rect"
padding="8" color="~bgcolor"
border="2" border_color="~color_accent" round="8" />
<macro name="button_style"
padding="8"
border_color="~color_accent_translucent" border="2" round="8" color="~color_accent_5" color2="~color_accent_1" gradient="vertical"
align_items="center" justify_content="center" />
<template name="Device">
<rectangle id="dev_${idx}" macro="decorative_rect" padding_top="4" padding_bottom="4" display="none" align_items="center" gap="8">
<sprite id="dev_${idx}_sprite" width="32" height="32" src_builtin="${src}" />
<label _source="battery" _device="${idx}" size="24" weight="bold" />
</rectangle>
</template>
<template name="Overlay">
<Button macro="button_style" id="overlay_${idx}"
tooltip="WATCH.TOGGLE_FOR_CURRENT_SET" _press="::OverlayToggle ${idx}" _long_release="::SingleSetOverlayReset ${idx}"
align_items="center"
height="40">
<sprite id="overlay_${idx}_sprite" src_builtin="${src}" width="32" height="32" />
<label id="overlay_${idx}_label" text="WLX-${idx}" size="18" />
</Button>
</template>
<!--
[!!!!!!!!] Disclaimer [!!!!!!!!]
Elements with id="norm_*" show in normal mode.
Elements with id="edit_*" show in edit mode.
-->
<elements>
<!-- padding="32" is required there (to make room for tooltips) -->
<div
padding="32" interactable="0"
flex_direction="column" gap="8">
<!-- Top elements (device battery levels) -->
<div id="devices" interactable="0" gap="6" width="100%" max_width="400" flex_direction="row" flex_wrap="wrap">
<!-- Src here may be changed, but maintain `TrackedDeviceRole` order: HMD, Left, Right, Tracker -->
<Device src="watch/hmd.svg" idx="0" />
<Device src="watch/controller_l.svg" idx="1" />
<Device src="watch/controller_r.svg" idx="2" />
<Device src="watch/track.svg" idx="3" />
<!-- Will populate additional <Device> tags at runtime -->
</div>
<!-- All other elements inside the container -->
<div flex_direction="column" gap="8">
<rectangle macro="decorative_rect" flex_direction="row" gap="8">
<!-- Clock, date and various timezones -->
<div gap="8">
<div flex_direction="column">
<label text="23:59" _source="clock" _display="time" color="~clock0_color" size="~clock0_size" weight="bold" />
<div padding="2" gap="2" flex_direction="column">
<label text="22/2/2022" _source="clock" _display="date" color="~clock0_color" size="~clock0_date_size" weight="bold" />
<label text="Tuesday" _source="clock" _display="dow" color="~clock0_color" size="~clock0_dow_size" weight="bold" />
</div>
</div>
<div flex_direction="column" gap="8">
<!-- Timezone names here are only placeholders. Set your timezones via ~/.config/wlxoverlay/conf.d -->
<div flex_direction="column">
<label text="Paris" _source="clock" _display="name" _timezone="0" color="~clock_alt1_color" size="~clock_alt_tz_size" weight="bold" />
<label text="23:59" _source="clock" _display="time" _timezone="0" color="~clock_alt1_color" size="~clock_alt_size" weight="bold" />
</div>
<div flex_direction="column">
<label text="New York" _source="clock" _display="name" _timezone="1" color="~clock_alt2_color" size="~clock_alt_tz_size" weight="bold" />
<label text="23:59" _source="clock" _display="time" _timezone="1" color="~clock_alt2_color" size="~clock_alt_size" weight="bold" />
</div>
</div>
</div>
<!-- Four buttons -->
<div flex_direction="column" gap="8">
<div gap="8">
<Button macro="button_style" _press="::NewMirror" tooltip="WATCH.MIRROR" tooltip_side="left">
<sprite width="40" height="40" color="~set_color" src="edit/mirror.svg" />
</Button>
<Button macro="button_style" _press="::CleanupMirrors" tooltip="WATCH.CLEANUP_MIRRORS" tooltip_side="left">
<sprite width="40" height="40" color="~set_color" src="watch/mirror-off.svg" />
</Button>
</div>
<div gap="8">
<Button macro="button_style" _press="::PlayspaceRecenter" tooltip="WATCH.RECENTER" tooltip_side="left">
<sprite width="40" height="40" color="~set_color" src="watch/recenter.svg" />
</Button>
<Button macro="button_style" _press="::PlayspaceFixFloor" tooltip="WATCH.FIX_FLOOR" tooltip_side="left">
<sprite width="40" height="40" color="~set_color" src="watch/fix-floor.svg" />
</Button>
</div>
</div>
</rectangle>
<!-- Bottom buttons -->
<div id="toolbox-condensed" gap="6" width="100%" max_width="400" flex_direction="row" flex_wrap="wrap">
<Button id="btn_dashboard" height="40" macro="button_style" _press="::DashToggle" tooltip="WATCH.DASHBOARD" tooltip_side="top" >
<sprite color="~set_color" width="32" height="32" src="watch/wayvr_dashboard_mono.svg" />
</Button>
<Button id="btn_edit_mode" height="40" macro="button_style" _press="::EditToggle" tooltip="WATCH.EDIT_MODE" tooltip_side="top">
<sprite color="~set_color" width="32" height="32" src="watch/edit.svg" />
</Button>
<Button id="btn_keyboard" height="40" macro="button_style" tooltip="WATCH.TOGGLE_FOR_CURRENT_SET" _press="::OverlayToggle kbd" >
<sprite src_builtin="watch/keyboard.svg" width="32" height="32" />
</Button>
<!-- Src here may be changed, but maintain `OverlayCategory` order: Panel, Screen, Mirror, WayVR -->
<Overlay src="edit/panel.svg" idx="0" />
<Overlay src="edit/screen.svg" idx="1" />
<Overlay src="edit/mirror.svg" idx="2" />
<Overlay src="edit/wayvr.svg" idx="3" />
<!-- Will populate additional <Overlay> tags at runtime -->
</div>
</div>
</div>
</elements>
</layout>

View File

@@ -19,22 +19,52 @@
align_items="center" justify_content="center" />
<template name="Device">
<rectangle id="dev_${idx}" macro="decorative_rect" padding_top="4" padding_bottom="4" display="none" align_items="center" gap="8">
<sprite id="dev_${idx}_sprite" width="32" height="32" src_builtin="${src}" />
<rectangle macro="decorative_rect" padding_top="4" padding_bottom="4" align_items="center" gap="8">
<sprite width="32" height="32" src_builtin="${src}" />
<label _source="battery" _device="${idx}" size="24" weight="bold" />
</rectangle>
</template>
<template name="Overlay">
<Button macro="button_style" id="overlay_${idx}"
tooltip="WATCH.TOGGLE_FOR_CURRENT_SET" _press="::EditModeOverlayToggle ${idx}"
align_items="center"
height="40">
<sprite id="overlay_${idx}_sprite" src_builtin="${src}" width="32" height="32" />
<label id="overlay_${idx}_label" text="WLX-${idx}" size="18" />
</Button>
<template name="Hmd">
<Device idx="${idx}" src="watch/hmd.svg" />
</template>
<template name="LeftHand">
<Device idx="${idx}" src="watch/controller_l.svg" />
</template>
<template name="RightHand">
<Device idx="${idx}" src="watch/controller_r.svg" />
</template>
<template name="Tracker">
<Device idx="${idx}" src="watch/track.svg" />
</template>
<template name="Screen">
<Button macro="button_style" id="overlay_${idx}" tooltip_str="${name}" _press="::OverlayToggle ${name}">
<sprite width="38" height="38" color="~text_color" src_builtin="edit/screen.svg" />
<div position="absolute" margin_top="-7" margin_left="-1">
<label text="${display}" size="18" color="~color_faded_20" weight="bold" />
</div>
</Button>
</template>
<template name="Panel">
<Button macro="button_style" id="overlay_${idx}" tooltip_str="${name}" _press="::OverlayToggle ${name}">
<sprite width="38" height="38" color="~text_color" src_builtin="edit/panel.svg" />
</Button>
</template>
<template name="Mirror">
<Button macro="button_style" id="overlay_${idx}" tooltip_str="${name}" _press="::OverlayToggle ${name}">
<sprite width="38" height="38" color="~text_color" src_builtin="edit/mirror.svg" />
<div position="absolute" margin_top="5" margin_left="13">
<label text="${display}" size="20" color="~color_faded_20" weight="bold" />
</div>
</Button>
</template>
<template name="App">
<Button macro="button_style" id="overlay_${idx}" tooltip_str="${name}" _press="::OverlayToggle ${name}">
<sprite width="38" height="38" color="~text_color" src_ext="${icon}" />
</Button>
</template>
<template name="Set">
<Button macro="button_style" id="set_${idx}" _press="::SetToggle ${idx}" tooltip="WATCH.SWITCH_TO_SET" tooltip_side="top">
<sprite width="40" height="40" color="~text_color" src_builtin="watch/set2.svg" />
@@ -44,63 +74,64 @@
</Button>
</template>
<template name="Clock">
<div gap="12" flex_direction="column" padding="4">
<div flex_direction="column" >
<label text="11:22 PM" _source="clock" _display="time" color="~text_color" size="~clock0_size" weight="bold" align="center" />
<div padding_left="2" gap="16" flex_direction="row" justify_content="center">
<label text="Tue" _source="clock" _display="dow_short" color="~clock0_color" size="~clock0_dow_size" weight="bold" />
<label text="22/2/2022" _source="clock" _display="date" color="~clock0_color" size="~clock0_date_size" weight="bold" />
</div>
</div>
<div flex_direction="row" gap="8" justify_content="space_around">
<!-- Timezone names here are only placeholders. Set your timezones via ~/.config/wlxoverlay/conf.d -->
<div flex_direction="column">
<label text="Paris" _source="clock" _display="name" _timezone="0" color="~clock_alt1_color" size="~clock_alt_tz_size" weight="bold" align="center" />
<label text="23:59" _source="clock" _display="time" _timezone="0" color="~clock_alt1_color" size="~clock_alt_size" weight="bold" align="center" />
</div>
<div flex_direction="column">
<label text="New York" _source="clock" _display="name" _timezone="1" color="~clock_alt2_color" size="~clock_alt_tz_size" weight="bold" align="center" />
<label text="23:59" _source="clock" _display="time" _timezone="1" color="~clock_alt2_color" size="~clock_alt_size" weight="bold" align="center" />
</div>
</div>
</div>
</template>
<template name="VerticalSeparator">
<rectangle width="2" height="100%" color="~color_accent" />
</template>
<!--
[!!!!!!!!] Disclaimer [!!!!!!!!]
Elements with id="norm_*" show in normal mode.
Elements with id="edit_*" show in edit mode.
-->
<elements>
<!-- padding="32" is required there (to make room for tooltips) -->
<div
padding="32" interactable="0"
flex_direction="column" gap="8">
<!-- Top elements (device battery levels) -->
<div id="devices" interactable="0" gap="6" width="100%" max_width="400" flex_direction="row" flex_wrap="wrap">
<!-- Src here may be changed, but maintain `TrackedDeviceRole` order: HMD, Left, Right, Tracker -->
<Device src="watch/hmd.svg" idx="0" />
<Device src="watch/controller_l.svg" idx="1" />
<Device src="watch/controller_r.svg" idx="2" />
<Device src="watch/track.svg" idx="3" />
<!-- Will populate additional <Device> tags at runtime -->
<div id="devices_root" interactable="0" gap="6" width="100%" max_width="400" flex_direction="row" flex_wrap="wrap">
<!-- Will populate tags at runtime -->
<!-- These are examples for uidev -->
<Hmd idx="0" />
<LeftHand idx="1" />
<RightHand idx="2" />
<Track idx="3" />
</div>
<!-- All other elements inside the container -->
<div flex_direction="column" gap="8">
<rectangle macro="decorative_rect" flex_direction="row" id="norm_pane" gap="8">
<div flex_direction="column" gap="8" min_width="300">
<rectangle macro="decorative_rect" flex_direction="row" gap="8" justify_content="space_evenly">
<!-- Clock, date and various timezones -->
<div gap="8">
<div flex_direction="column">
<label text="23:59" _source="clock" _display="time" color="~text_color" size="~clock0_size" weight="bold" />
<div padding="2" gap="2" flex_direction="column">
<label text="22/2/2022" _source="clock" _display="date" color="~clock0_color" size="~clock0_date_size" weight="bold" />
<label text="Tuesday" _source="clock" _display="dow" color="~clock0_color" size="~clock0_dow_size" weight="bold" />
</div>
</div>
<div flex_direction="column" gap="8">
<!-- Timezone names here are only placeholders. Set your timezones via ~/.config/wlxoverlay/conf.d -->
<div flex_direction="column">
<label text="Paris" _source="clock" _display="name" _timezone="0" color="~clock_alt1_color" size="~clock_alt_tz_size" weight="bold" />
<label text="23:59" _source="clock" _display="time" _timezone="0" color="~clock_alt1_color" size="~clock_alt_size" weight="bold" />
</div>
<div flex_direction="column">
<label text="New York" _source="clock" _display="name" _timezone="1" color="~clock_alt2_color" size="~clock_alt_tz_size" weight="bold" />
<label text="23:59" _source="clock" _display="time" _timezone="1" color="~clock_alt2_color" size="~clock_alt_size" weight="bold" />
</div>
</div>
<div id="clock_root">
<Clock />
</div>
<!-- Four buttons -->
<div flex_direction="column" gap="8">
<div gap="8">
<Button macro="button_style" _press="::NewMirror" tooltip="WATCH.MIRROR" tooltip_side="left">
<sprite width="40" height="40" color="~text_color" src="edit/mirror.svg" />
<Button id="btn_keyboard" macro="button_style" _press="::OverlayToggle kbd" tooltip="EDIT_MODE.KEYBOARD" tooltip_side="left">
<sprite src_builtin="watch/keyboard.svg" width="40" height="40" />
</Button>
<Button macro="button_style" _press="::CleanupMirrors" tooltip="WATCH.CLEANUP_MIRRORS" tooltip_side="left">
<sprite width="40" height="40" color="~text_color" src="watch/mirror-off.svg" />
<Button id="btn_edit_mode" macro="button_style" _press="::EditToggle" tooltip="WATCH.EDIT_MODE" tooltip_side="left">
<sprite color="~text_color" width="40" height="40" src="watch/edit.svg" />
</Button>
</div>
<div gap="8">
@@ -114,53 +145,28 @@
</div>
</rectangle>
<rectangle macro="decorative_rect" flex_direction="column" id="edit_pane" display="none" gap="8">
<div flex_direction="column" padding="4" align_items="center" justify_content="center">
<label translation="WATCH.EDIT_MODE_EXPLANATION" align="center" />
</div>
<div flex_direction="column" align_items="center" justify_content="center">
<div id="toolbox" gap="8" width="100%" max_width="400" flex_direction="row" flex_wrap="wrap">
<Button id="btn_keyboard" height="40" macro="button_style" tooltip="WATCH.TOGGLE_FOR_CURRENT_SET" _press="::OverlayToggle kbd">
<sprite src_builtin="watch/keyboard.svg" width="32" height="32" />
<label translation="EDIT_MODE.KEYBOARD" size="18" />
</Button>
<!-- Src here may be changed, but maintain `OverlayCategory` order: Panel, Screen, Mirror, WayVR -->
<Overlay src="edit/panel.svg" idx="0" />
<Overlay src="edit/screen.svg" idx="1" />
<Overlay src="edit/mirror.svg" idx="2" />
<Overlay src="edit/wayvr.svg" idx="3" />
<!-- Will populate additional <Overlay> tags at runtime -->
</div>
</div>
</rectangle>
<!-- Bottom buttons -->
<div flex_direction="row" gap="8">
<div gap="4">
<Button id="btn_dashboard" macro="button_style" _press="::DashToggle" tooltip="WATCH.DASHBOARD" tooltip_side="top">
<sprite color="~text_color" width="40" height="40" src="watch/wayvr_dashboard_mono.svg" />
</Button>
<Button id="btn_edit_mode" macro="button_style" _press="::EditToggle" tooltip="WATCH.EDIT_MODE" tooltip_side="top">
<sprite color="~text_color" width="40" height="40" src="watch/edit.svg" />
</Button>
<div id="edit_delete" display="none">
<Button macro="button_style" _long_release="::EditModeDeleteSet" tooltip="WATCH.LONG_PRESS_TO_DELETE_SET" tooltip_side="top" border_color="~color_danger_translucent" color="~color_danger_40" color2="~color_danger_10">
<sprite color="~text_color" width="40" height="40" src="edit/delete.svg" />
</Button>
</div>
<div id="edit_add" display="none">
<Button macro="button_style" _press="::EditModeAddSet" tooltip="WATCH.ADD_NEW_SET" tooltip_side="top">
<sprite color="~text_color" width="40" height="40" src="edit/add.svg" />
</Button>
</div>
</div>
<VerticalSeparator />
<div id="sets" gap="4">
<div id="sets_root" gap="4">
<!-- Will populate tags at runtime -->
<!-- These are examples for uidev -->
<Set idx="0" display="1" />
<!-- Will populate additional <Set> tags at runtime -->
<Set idx="1" display="2" />
</div>
<div id="panels_root" gap="4" display="none">
<!-- Will populate tags at runtime -->
<!-- These are examples for uidev -->
<Screen idx="0" display="H1" />
<Screen idx="1" display="D2" />
</div>
</div>
</div>
</div>
</elements>
</layout>
</layout>

View File

@@ -7,6 +7,7 @@ use glam::{Affine3A, Vec2, Vec3A, Vec3Swizzles};
use idmap_derive::IntegerId;
use smallvec::{SmallVec, smallvec};
use strum::AsRefStr;
use wlx_common::common::LeftRight;
use wlx_common::windowing::{OverlayWindowState, Positioning};
@@ -37,7 +38,7 @@ pub struct TrackedDevice {
}
#[repr(u8)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, IntegerId)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, IntegerId, AsRefStr)]
pub enum TrackedDeviceRole {
None,
Hmd,

View File

@@ -76,18 +76,26 @@ pub struct ModifyPanelTask {
pub command: ModifyPanelCommand,
}
pub enum ToggleMode {
EnsureOn,
EnsureOff,
Toggle,
}
pub type ModifyOverlayTask = dyn FnOnce(&mut AppState, &mut OverlayWindowConfig) + Send;
pub type CreateOverlayTask = dyn FnOnce(&mut AppState) -> Option<OverlayWindowConfig> + Send;
pub enum OverlayTask {
AddSet,
ToggleSet(usize),
SwitchSet(Option<usize>),
ToggleOverlay(OverlaySelector),
ToggleOverlay(OverlaySelector, ToggleMode),
ResetOverlay(OverlaySelector),
DeleteActiveSet,
ToggleEditMode,
ToggleDashboard,
ShowHide,
CleanupMirrors,
SettingsChanged,
Modify(OverlaySelector, Box<ModifyOverlayTask>),
Create(OverlaySelector, Box<CreateOverlayTask>),
ModifyPanel(ModifyPanelTask),

View File

@@ -28,7 +28,7 @@ use crate::{
RUNNING,
backend::{
XrBackend,
task::{OverlayTask, PlayspaceTask, TaskType},
task::{OverlayTask, PlayspaceTask, TaskType, ToggleMode},
wayvr::process::KillSignal,
},
overlays::{custom::create_custom, toast::Toast, wayvr::WvrCommand},
@@ -337,12 +337,10 @@ pub(super) fn setup_custom_button<S: 'static>(
return Ok(EventResult::Pass);
}
app.tasks.enqueue(TaskType::Overlay(OverlayTask::Modify(
OverlaySelector::Name(arg.clone()),
Box::new(move |app, owc| {
owc.activate(app);
}),
)));
app.tasks
.enqueue(TaskType::Overlay(OverlayTask::ResetOverlay(
OverlaySelector::Name(arg.clone()),
)));
Ok(EventResult::Consumed)
})
}
@@ -361,6 +359,7 @@ pub(super) fn setup_custom_button<S: 'static>(
app.tasks
.enqueue(TaskType::Overlay(OverlayTask::ToggleOverlay(
OverlaySelector::Name(arg.clone()),
ToggleMode::Toggle,
)));
Ok(EventResult::Consumed)
})
@@ -384,6 +383,23 @@ pub(super) fn setup_custom_button<S: 'static>(
Ok(EventResult::Consumed)
})
}
"::DeleteSet" => Box::new(move |_common, data, app, _state| {
if !test_button(data) || !test_duration(&button, app) {
return Ok(EventResult::Pass);
}
app.tasks
.enqueue(TaskType::Overlay(OverlayTask::DeleteActiveSet));
Ok(EventResult::Consumed)
}),
"::AddSet" => Box::new(move |_common, data, app, _state| {
if !test_button(data) || !test_duration(&button, app) {
return Ok(EventResult::Pass);
}
app.tasks.enqueue(TaskType::Overlay(OverlayTask::AddSet));
Ok(EventResult::Consumed)
}),
"::CustomOverlayReload" => {
let arg: Arc<str> = args.collect::<Vec<_>>().join(" ").into();
if arg.len() < 1 {

View File

@@ -0,0 +1,67 @@
use std::collections::HashMap;
use slotmap::Key;
use wgui::{
layout::Layout,
parser::{Fetchable, ParseDocumentParams, ParserState},
};
use crate::{
backend::input::TrackedDeviceRole, state::AppState, windowing::backend::OverlayEventData,
};
#[derive(Default)]
/// Helper for managing a list of overlays
/// Populates `id="devices_root"` with `<Hmd>`, `<LeftHand>`, `<RightHand>`, `<Tracker>` templates
pub struct DeviceList;
impl DeviceList {
pub fn on_notify(
&mut self,
app: &AppState,
layout: &mut Layout,
parser_state: &mut ParserState,
event_data: &OverlayEventData,
doc_params: &ParseDocumentParams,
) -> anyhow::Result<bool> {
let mut elements_changed = false;
match event_data {
OverlayEventData::DevicesChanged => {
let devices_root = parser_state
.get_widget_id("devices_root")
.unwrap_or_default();
if devices_root.is_null() {
return Ok(false);
}
layout.remove_children(devices_root);
for (i, device) in app.input_state.devices.iter().enumerate() {
let mut params = HashMap::new();
if matches!(device.role, TrackedDeviceRole::None) {
continue;
}
let template = device.role.as_ref();
log::warn!("creating {template} tag for {i}");
params.insert("idx".into(), i.to_string().into());
parser_state.instantiate_template(
&doc_params,
template,
layout,
devices_root,
params,
)?;
}
elements_changed = true;
}
_ => {}
}
Ok(elements_changed)
}
}

View File

@@ -89,6 +89,7 @@ pub(super) fn setup_custom_label<S: 'static>(
}
"date" => "%x",
"dow" => "%A",
"dow_short" => "%a",
"time" => {
if app.session.config.clock_12h {
"%I:%M %p"

View File

@@ -35,6 +35,7 @@ use crate::{
use super::timer::GuiTimer;
pub mod button;
pub mod device_list;
mod label;
pub mod overlay_list;
pub mod set_list;

View File

@@ -75,9 +75,18 @@ impl OverlayList {
);
("App", apps_root)
}
OverlayCategory::Dashboard => {
let overlay_button = parser_state
.fetch_component_as::<ComponentButton>("btn_dashboard")?;
OverlayCategory::Dashboard | OverlayCategory::Keyboard => {
let key = if matches!(meta.category, OverlayCategory::Dashboard) {
"btn_dashboard"
} else {
"btn_keyboard"
};
let Ok(overlay_button) =
parser_state.fetch_component_as::<ComponentButton>(key)
else {
continue;
};
if meta.visible {
let mut com = CallbackDataCommon {

View File

@@ -18,6 +18,10 @@ pub struct SetList {
}
impl SetList {
pub fn num_sets(&self) -> usize {
self.set_buttons.len()
}
pub fn on_notify(
&mut self,
layout: &mut Layout,

View File

@@ -25,7 +25,7 @@ use wlx_common::{
use crate::{
backend::{
input::{Haptics, HoverResult, PointerHit, PointerMode},
task::{OverlayTask, PlayspaceTask, TaskType},
task::{OverlayTask, PlayspaceTask, TaskType, ToggleMode},
wayvr::{
process::{KillSignal, ProcessHandle},
window::WindowHandle,
@@ -383,16 +383,14 @@ impl DashInterface<AppState> for DashInterfaceLive {
return Ok(());
};
app.tasks.enqueue(TaskType::Overlay(OverlayTask::Modify(
OverlaySelector::Id(oid),
Box::new(move |app, owc| {
if visible && !owc.is_active() {
owc.activate(app);
} else if !visible && owc.is_active() {
owc.deactivate();
}
}),
)));
app.tasks
.enqueue(TaskType::Overlay(OverlayTask::ToggleOverlay(
OverlaySelector::Id(oid),
match visible {
true => ToggleMode::EnsureOn,
false => ToggleMode::EnsureOff,
},
)));
Ok(())
}
@@ -415,4 +413,9 @@ impl DashInterface<AppState> for DashInterfaceLive {
) -> &'a mut wlx_common::config::GeneralConfig {
&mut data.session.config
}
fn config_changed(&mut self, data: &mut AppState) {
data.tasks
.enqueue(TaskType::Overlay(OverlayTask::SettingsChanged));
}
}

View File

@@ -20,7 +20,7 @@ use wlx_common::{
use crate::{
backend::{
input::{HoverResult, PointerHit},
task::{OverlayTask, TaskType},
task::{OverlayTask, TaskType, ToggleMode},
},
overlays::screen::capture::{MainThreadWlxCapture, new_wlx_capture},
state::{AppSession, AppState},
@@ -96,12 +96,11 @@ impl OverlayBackend for MirrorBackend {
app.xr_backend,
capture,
));
app.tasks.enqueue(TaskType::Overlay(OverlayTask::Modify(
OverlaySelector::Name(self.name.clone()),
Box::new(|app, o| {
o.activate(app);
}),
)));
app.tasks
.enqueue(TaskType::Overlay(OverlayTask::ToggleOverlay(
OverlaySelector::Name(self.name.clone()),
ToggleMode::EnsureOn,
)));
}
Err(e) => {
log::warn!("Failed to create mirror due to PipeWire error: {e:?}");

View File

@@ -1,17 +1,13 @@
use std::{collections::HashMap, rc::Rc, time::Duration};
use std::{rc::Rc, time::Duration};
use glam::{Affine3A, Quat, Vec3, Vec3A, vec3};
use idmap::DirectIdMap;
use slotmap::SecondaryMap;
use wgui::{
assets::AssetPath,
components::button::ComponentButton,
event::{CallbackDataCommon, EventAlterables, EventCallback, StyleSetRequest},
i18n::Translation,
event::{CallbackDataCommon, EventAlterables, StyleSetRequest},
layout::WidgetID,
parser::Fetchable,
renderer_vk::text::custom_glyph::CustomGlyphData,
parser::{Fetchable, ParseDocumentParams},
taffy,
widget::{EventResult, label::WidgetLabel, sprite::WidgetSprite},
};
use wlx_common::{
common::LeftRight,
@@ -19,20 +15,18 @@ use wlx_common::{
};
use crate::{
backend::{
input::TrackedDeviceRole,
task::{OverlayTask, TaskType},
},
gui::{
panel::{GuiPanel, NewGuiPanelParams, OnCustomAttribFunc, button::BUTTON_EVENTS},
panel::{
GuiPanel, NewGuiPanelParams, device_list::DeviceList, overlay_list::OverlayList,
set_list::SetList,
},
timer::GuiTimer,
},
state::AppState,
windowing::{
OverlayID, OverlaySelector, Z_ORDER_WATCH,
backend::{OverlayEventData, OverlayMeta},
manager::MAX_OVERLAY_SETS,
window::{OverlayCategory, OverlayWindowConfig, OverlayWindowData},
Z_ORDER_WATCH,
backend::OverlayEventData,
window::{OverlayWindowConfig, OverlayWindowData},
},
};
@@ -52,462 +46,121 @@ struct OverlayButton {
#[derive(Default)]
struct WatchState {
current_set: Option<usize>,
set_buttons: Vec<Rc<ComponentButton>>,
overlay_buttons: Vec<OverlayButton>,
overlay_metas: Vec<OverlayMeta>,
overlay_indices: SecondaryMap<OverlayID, usize>,
edit_mode_widgets: Vec<(WidgetID, bool)>,
edit_add_widget: WidgetID,
device_role_icons: DirectIdMap<TrackedDeviceRole, CustomGlyphData>,
overlay_cat_icons: DirectIdMap<OverlayCategory, CustomGlyphData>,
devices: Vec<(WidgetID, WidgetID)>,
keyboard_oid: OverlayID,
dashboard_oid: OverlayID,
num_sets: usize,
device_list: DeviceList,
overlay_list: OverlayList,
set_list: SetList,
clock_12h: bool,
}
#[allow(clippy::significant_drop_tightening)]
#[allow(clippy::too_many_lines)]
#[allow(clippy::cognitive_complexity)]
pub fn create_watch(app: &mut AppState) -> anyhow::Result<OverlayWindowConfig> {
let state = WatchState::default();
let on_custom_attrib: OnCustomAttribFunc = Box::new(move |layout, parser, attribs, _app| {
let Ok(button) =
parser.fetch_component_from_widget_id_as::<ComponentButton>(attribs.widget_id)
else {
return;
};
for (name, kind, test_button, test_duration) in &BUTTON_EVENTS {
let Some(action) = attribs.get_value(name) else {
continue;
};
let mut args = action.split_whitespace();
let Some(command) = args.next() else {
continue;
};
let button = button.clone();
let callback: EventCallback<AppState, WatchState> = match command {
"::EditModeDeleteSet" => Box::new(move |_common, data, app, _state| {
if !test_button(data) || !test_duration(&button, app) {
return Ok(EventResult::Pass);
}
app.tasks
.enqueue(TaskType::Overlay(OverlayTask::DeleteActiveSet));
Ok(EventResult::Consumed)
}),
"::EditModeAddSet" => Box::new(move |_common, data, app, _state| {
if !test_button(data) || !test_duration(&button, app) {
return Ok(EventResult::Pass);
}
app.tasks.enqueue(TaskType::Overlay(OverlayTask::AddSet));
Ok(EventResult::Consumed)
}),
"::EditModeOverlayToggle" => {
let arg = args.next().unwrap_or_default();
let Ok(idx) = arg.parse::<usize>() else {
log::error!("{command} has invalid argument: \"{arg}\"");
return;
};
Box::new(move |_common, data, app, state| {
if !test_button(data) || !test_duration(&button, app) {
return Ok(EventResult::Pass);
}
let Some(overlay) = state.overlay_metas.get(idx) else {
log::error!("No overlay at index {idx}.");
return Ok(EventResult::Consumed);
};
app.tasks
.enqueue(TaskType::Overlay(OverlayTask::ToggleOverlay(
OverlaySelector::Id(overlay.id),
)));
Ok(EventResult::Consumed)
})
}
"::SingleSetOverlayReset" => {
let arg = args.next().unwrap_or_default();
let Ok(idx) = arg.parse::<usize>() else {
log::error!("{command} has invalid argument: \"{arg}\"");
return;
};
Box::new(move |_common, data, app, state| {
if !test_button(data) || !test_duration(&button, app) {
return Ok(EventResult::Pass);
}
let Some(overlay) = state.overlay_metas.get(idx) else {
log::error!("No overlay at index {idx}.");
return Ok(EventResult::Consumed);
};
app.tasks.enqueue(TaskType::Overlay(OverlayTask::Modify(
OverlaySelector::Id(overlay.id),
Box::new(|app, owc| owc.activate(app)),
)));
Ok(EventResult::Consumed)
})
}
_ => return,
};
let id = layout.add_event_listener(attribs.widget_id, *kind, callback);
log::debug!("Registered {action} on {:?} as {id:?}", attribs.widget_id);
}
});
let watch_xml = if app.session.config.single_set_mode {
"gui/watch-noset.xml"
} else {
"gui/watch.xml"
let state = WatchState {
clock_12h: app.session.config.clock_12h,
..Default::default()
};
let watch_xml = "gui/watch.xml";
let mut panel = GuiPanel::new_from_template(
app,
watch_xml,
state,
NewGuiPanelParams {
on_custom_id: Some(Box::new(
move |id, widget, doc_params, layout, parser_state, state| {
if id.starts_with("norm_") {
state.edit_mode_widgets.push((widget, false));
} else if &*id == "edit_add" {
state.edit_add_widget = widget;
} else if id.starts_with("edit_") {
state.edit_mode_widgets.push((widget, true));
} else if &*id == "sets" {
let node = layout.state.nodes[widget];
let num_children = layout.state.tree.children(node).iter().len();
let mut panel =
GuiPanel::new_from_template(app, watch_xml, state, NewGuiPanelParams::default())?;
for idx in 0..MAX_OVERLAY_SETS {
if idx >= num_children {
let mut params: HashMap<Rc<str>, Rc<str>> = HashMap::new();
params.insert("display".into(), (idx + 1).to_string().into());
params.insert("idx".into(), idx.to_string().into());
parser_state.instantiate_template(
doc_params, "Set", layout, widget, params,
)?;
}
let comp = parser_state
.fetch_component_as::<ComponentButton>(&format!("set_{idx}"))?;
state.set_buttons.push(comp);
}
} else if &*id == "toolbox" || &*id == "toolbox-condensed" {
for idx in 0..MAX_TOOLBOX_BUTTONS {
let id_str = format!("overlay_{idx}");
let button = if let Ok(button) =
parser_state.fetch_component_as::<ComponentButton>(&id_str)
{
button
} else {
let mut params: HashMap<Rc<str>, Rc<str>> = HashMap::new();
params.insert("idx".into(), idx.to_string().into());
parser_state.instantiate_template(
doc_params, "Overlay", layout, widget, params,
)?;
parser_state.fetch_component_as::<ComponentButton>(&id_str)?
};
state.overlay_buttons.push(OverlayButton {
button,
label: parser_state
.get_widget_id(&format!("overlay_{idx}_label"))
.inspect_err(|e| log::warn!("{e:?}"))
.unwrap_or_default(),
sprite: parser_state
.get_widget_id(&format!("overlay_{idx}_sprite"))
.inspect_err(|e| log::warn!("{e:?}"))
.unwrap_or_default(),
condensed: id.ends_with("-condensed"),
});
}
} else if id.starts_with("overlay_") && id.ends_with("_sprite") {
// store device icons from xml
let id_n = id
.replace("overlay_", "")
.replace("_sprite", "")
.parse::<u64>()?;
let category = match id_n {
0 => OverlayCategory::Panel,
1 => OverlayCategory::Screen,
2 => OverlayCategory::Mirror,
3 => OverlayCategory::WayVR,
_ => return Ok(()), // not parsing the first 4 elems
};
let sprite = layout
.state
.widgets
.get_as::<WidgetSprite>(widget)
.ok_or_else(|| {
anyhow::anyhow!("{id} is expected to be a sprite, but it isn't.")
})?;
let src = sprite.get_content().ok_or_else(|| {
anyhow::anyhow!("{id} is expected to have a src, but it doesn't.")
})?;
state.overlay_cat_icons.insert(category, src);
} else if id.starts_with("dev_") && id.ends_with("_sprite") {
// store device icons from xml
let id_n = id
.replace("dev_", "")
.replace("_sprite", "")
.parse::<u64>()?;
let role = match id_n {
0 => TrackedDeviceRole::Hmd,
1 => TrackedDeviceRole::LeftHand,
2 => TrackedDeviceRole::RightHand,
3 => TrackedDeviceRole::Tracker,
_ => return Ok(()), // not parsing the first 4 elems
};
let sprite = layout
.state
.widgets
.get_as::<WidgetSprite>(widget)
.ok_or_else(|| {
anyhow::anyhow!("{id} is expected to be a sprite, but it isn't.")
})?;
let src = sprite.get_content().ok_or_else(|| {
anyhow::anyhow!("{id} is expected to have a src, but it doesn't.")
})?;
state.device_role_icons.insert(role, src);
} else if &*id == "devices" {
let node = layout.state.nodes[widget];
let num_children = layout.state.tree.children(node).iter().len();
for idx in 0..MAX_DEVICES {
if idx >= num_children {
let mut params: HashMap<Rc<str>, Rc<str>> = HashMap::new();
params.insert("idx".into(), idx.to_string().into());
params.insert("src".into(), String::new().into());
parser_state.instantiate_template(
doc_params, "Device", layout, widget, params,
)?;
}
let div = parser_state.get_widget_id(&format!("dev_{idx}"))?;
let spr = parser_state.get_widget_id(&format!("dev_{idx}_sprite"))?;
state.devices.push((div, spr));
}
}
Ok(())
},
)),
on_custom_attrib: Some(on_custom_attrib),
..Default::default()
},
)?;
let btn_edit_mode = panel
.parser_state
.fetch_component_as::<ComponentButton>("btn_edit_mode")
.ok();
let btn_keyboard = panel
.parser_state
.fetch_component_as::<ComponentButton>("btn_keyboard")
.ok();
let btn_dashboard = panel
.parser_state
.fetch_component_as::<ComponentButton>("btn_dashboard")
.ok();
let doc_params = ParseDocumentParams {
globals: panel.layout.state.globals.clone(),
path: AssetPath::FileOrBuiltIn(watch_xml),
extra: panel.doc_extra.take().unwrap_or_default(),
};
panel.on_notify = Some(Box::new(move |panel, app, event_data| {
let mut alterables = EventAlterables::default();
let mut com = CallbackDataCommon {
alterables: &mut alterables,
state: &panel.layout.state,
};
let mut elems_changed = panel.state.overlay_list.on_notify(
&mut panel.layout,
&mut panel.parser_state,
&event_data,
&mut alterables,
&doc_params,
)?;
elems_changed |= panel.state.set_list.on_notify(
&mut panel.layout,
&mut panel.parser_state,
&event_data,
&mut alterables,
&doc_params,
)?;
elems_changed |= panel.state.device_list.on_notify(
app,
&mut panel.layout,
&mut panel.parser_state,
&event_data,
&doc_params,
)?;
match event_data {
OverlayEventData::ActiveSetChanged(current_set) => {
if let Some(old_set) = panel.state.current_set.take()
&& let Some(old_set) = panel.state.set_buttons.get_mut(old_set)
{
old_set.set_sticky_state(&mut com, false);
}
if let Some(new_set) = current_set
&& let Some(new_set) = panel.state.set_buttons.get_mut(new_set)
{
new_set.set_sticky_state(&mut com, true);
}
panel.state.current_set = current_set;
}
OverlayEventData::NumSetsChanged(num_sets) => {
panel.state.num_sets = num_sets;
for (i, comp) in panel.state.set_buttons.iter().enumerate() {
let rect_id = comp.get_rect();
let display = if i < num_sets {
taffy::Display::Flex
} else {
taffy::Display::None
};
com.alterables
.set_style(rect_id, StyleSetRequest::Display(display));
}
let display = if num_sets < 7 {
taffy::Display::Flex
} else {
taffy::Display::None
};
com.alterables.set_style(
panel.state.edit_add_widget,
StyleSetRequest::Display(display),
);
}
OverlayEventData::EditModeChanged(edit_mode) => {
for (w, e) in &panel.state.edit_mode_widgets {
let display = if *e == edit_mode {
taffy::Display::Flex
} else {
taffy::Display::None
if let Ok(btn_edit_mode) = panel
.parser_state
.fetch_component_as::<ComponentButton>("btn_edit_mode")
{
let mut com = CallbackDataCommon {
alterables: &mut alterables,
state: &panel.layout.state,
};
com.alterables
.set_style(*w, StyleSetRequest::Display(display));
btn_edit_mode.set_sticky_state(&mut com, edit_mode);
}
let display = if edit_mode && panel.state.num_sets < 7 {
taffy::Display::Flex
}
OverlayEventData::SettingsChanged => {
panel.layout.mark_redraw();
let display = if app.session.config.sets_on_watch {
[taffy::Display::Flex, taffy::Display::None]
} else {
taffy::Display::None
[taffy::Display::None, taffy::Display::Flex]
};
com.alterables.set_style(
panel.state.edit_add_widget,
StyleSetRequest::Display(display),
);
if let Some(btn) = btn_edit_mode.as_ref() {
btn.set_sticky_state(&mut com, edit_mode);
}
}
OverlayEventData::OverlaysChanged(metas) => {
panel.state.overlay_metas.clear();
for meta in &*metas {
match meta.category {
OverlayCategory::Keyboard => {
panel.state.keyboard_oid = meta.id;
if let Some(btn_keyboard) = btn_keyboard.as_ref() {
btn_keyboard.set_sticky_state(&mut com, meta.visible);
}
}
OverlayCategory::Dashboard => {
if let Some(btn_dashboard) = btn_dashboard.as_ref() {
btn_dashboard.set_sticky_state(&mut com, meta.visible);
}
panel.state.dashboard_oid = meta.id;
}
OverlayCategory::Internal => {}
_ => panel.state.overlay_metas.push(meta.clone()),
}
let widget = [
panel
.parser_state
.get_widget_id("panels_root")
.unwrap_or_default(),
panel
.parser_state
.get_widget_id("sets_root")
.unwrap_or_default(),
];
for i in 0..2 {
alterables.set_style(widget[i], StyleSetRequest::Display(display[i]));
}
panel.state.overlay_indices.clear();
for (idx, meta) in panel.state.overlay_metas.iter().enumerate() {
panel.state.overlay_indices.insert(meta.id, idx);
}
if app.session.config.clock_12h != panel.state.clock_12h {
panel.state.clock_12h = app.session.config.clock_12h;
for (idx, btn) in panel.state.overlay_buttons.iter().enumerate() {
let display = if let Some(meta) = panel.state.overlay_metas.get(idx) {
let name = if btn.condensed {
condense_overlay_name(&meta.name)
} else {
sanitize_overlay_name(&meta.name)
};
let clock_root = panel.parser_state.get_widget_id("clock_root")?;
panel.layout.remove_children(clock_root);
if let Some(mut label) =
panel.layout.state.widgets.get_as::<WidgetLabel>(btn.label)
{
label.set_text(&mut com, Translation::from_raw_text_rc(name));
} else {
btn.button
.set_text(&mut com, Translation::from_raw_text_rc(name));
}
panel.parser_state.instantiate_template(
&doc_params,
"Clock",
&mut panel.layout,
clock_root,
Default::default(),
)?;
if let Some(mut sprite) = panel
.layout
.state
.widgets
.get_as::<WidgetSprite>(btn.sprite)
&& let Some(glyph) = panel.state.overlay_cat_icons.get(meta.category)
{
sprite.set_content(&mut com, Some(glyph.clone()));
}
btn.button.set_sticky_state(&mut com, meta.visible);
taffy::Display::Flex
} else {
taffy::Display::None
};
com.alterables
.set_style(btn.button.get_rect(), StyleSetRequest::Display(display));
}
}
OverlayEventData::VisibleOverlaysChanged(overlays) => {
for meta in &mut panel.state.overlay_metas {
meta.visible = false;
}
let mut keyboard_visible = false;
let mut dashboard_visible = false;
for visible in &*overlays {
if let Some(idx) = panel.state.overlay_indices.get(*visible)
&& let Some(o) = panel.state.overlay_metas.get_mut(*idx)
{
o.visible = true;
} else if panel.state.keyboard_oid == *visible {
keyboard_visible = true;
} else if panel.state.dashboard_oid == *visible {
dashboard_visible = true;
}
}
for (idx, btn) in panel.state.overlay_buttons.iter().enumerate() {
let Some(meta) = panel.state.overlay_metas.get(idx) else {
continue;
};
btn.button.set_sticky_state(&mut com, meta.visible);
}
if let Some(btn_keyboard) = btn_keyboard.as_ref() {
btn_keyboard.set_sticky_state(&mut com, keyboard_visible);
}
if let Some(btn_dashboard) = btn_dashboard.as_ref() {
btn_dashboard.set_sticky_state(&mut com, dashboard_visible);
}
}
OverlayEventData::DevicesChanged => {
for (i, (div, s)) in panel.state.devices.iter().enumerate() {
if let Some(dev) = app.input_state.devices.get(i)
&& let Some(glyph) = panel.state.device_role_icons.get(dev.role)
&& let Some(mut s) = panel.layout.state.widgets.get_as::<WidgetSprite>(*s)
{
s.set_content(&mut com, Some(glyph.clone()));
com.alterables
.set_style(*div, StyleSetRequest::Display(taffy::Display::Flex));
} else {
com.alterables
.set_style(*div, StyleSetRequest::Display(taffy::Display::None));
}
elems_changed = true;
}
}
_ => {}
}
if elems_changed {
panel.process_custom_elems(app);
}
panel.layout.process_alterables(alterables)?;
Ok(())
}));
@@ -557,16 +210,3 @@ pub fn watch_fade<D>(app: &mut AppState, watch: &mut OverlayWindowData<D>) {
state.alpha += 0.1;
state.alpha = state.alpha.clamp(0., 1.);
}
fn sanitize_overlay_name(str: &str) -> Rc<str> {
str.replace("-wvr", "").into()
}
fn condense_overlay_name(str: &str) -> Rc<str> {
str.replace("DP-", "D")
.replace("HDMI-A-", "H")
.replace("WVR-wvr_", "W")
.replace("WVR-wvr", "W0")
.replace("Keyboard", "")
.into()
}

View File

@@ -7,9 +7,8 @@
################ EXPERIENCE ################
## When enabled, sets functionality will be disabled and
## the bottom of the watch will list overlays instead of sets.
#single_set_mode: false
## The bottom of the watch will list sets instead of overlays.
#sets_on_watch: false
## Force an alternative method for Wayland desktop capture.
## `auto`: default

View File

@@ -126,6 +126,7 @@ pub enum OverlayEventData {
OverlaysChanged(Rc<[OverlayMeta]>),
VisibleOverlaysChanged(Rc<[OverlayID]>),
DevicesChanged,
SettingsChanged,
OverlayGrabbed {
name: Arc<str>,
pos: Positioning,

View File

@@ -16,7 +16,7 @@ use wlx_common::{
use crate::{
FRAME_COUNTER,
backend::task::OverlayTask,
backend::task::{OverlayTask, ToggleMode},
config::save_state,
overlays::{
anchor::{create_anchor, create_grab_help},
@@ -172,13 +172,29 @@ where
OverlayTask::SwitchSet(maybe_set) => {
self.switch_to_set(app, maybe_set, false);
}
OverlayTask::ToggleOverlay(sel) => {
OverlayTask::ResetOverlay(sel) => {
if let Some(o) = self.mut_by_selector(&sel) {
let was_active = o.config.is_active();
o.config.activate(app);
if !was_active {
self.visible_overlays_changed(app)?;
}
}
}
OverlayTask::ToggleOverlay(sel, mode) => {
let Some(id) = self.id_by_selector(&sel) else {
log::warn!("Overlay not found for task: {sel:?}");
return Ok(());
};
let o = &mut self.overlays[id];
match mode {
ToggleMode::EnsureOn if o.config.is_active() => return Ok(()),
ToggleMode::EnsureOff if !o.config.is_active() => return Ok(()),
_ => {}
};
if let Some(active_state) = o.config.active_state.take() {
log::debug!("{}: toggle off", o.config.name);
@@ -214,6 +230,7 @@ where
} else {
overlay.config.deactivate();
}
self.visible_overlays_changed(app)?;
}
}
OverlayTask::AddSet => {
@@ -267,6 +284,15 @@ where
}
}
}
OverlayTask::SettingsChanged => {
for o in self.overlays.values_mut() {
let _ = o
.config
.backend
.notify(app, OverlayEventData::SettingsChanged)
.log_err("Could not notify SettingsChanged");
}
}
OverlayTask::CleanupMirrors => {
let mut ids_to_remove = vec![];
for (oid, o) in &self.overlays {

View File

@@ -113,6 +113,8 @@ impl OverlayWindowConfig {
self.active_state.is_some()
}
/// only call this directly for `OverlayCategory::Internal`
/// for anything else, use `OverlayTask::ToggleOverlay` instead
pub fn activate(&mut self, app: &mut AppState) {
log::debug!("activate {}", self.name.as_ref());
self.dirty = true;
@@ -120,19 +122,13 @@ impl OverlayWindowConfig {
self.reset(app, true);
}
/// only call this directly for `OverlayCategory::Internal`
/// for anything else, use `OverlayTask::ToggleOverlay` instead
pub fn deactivate(&mut self) {
log::debug!("deactivate {}", self.name.as_ref());
self.active_state = None;
}
pub fn toggle(&mut self, app: &mut AppState) {
if self.active_state.take().is_none() {
self.activate(app);
} else {
log::debug!("deactivate {} (toggle)", self.name.as_ref());
}
}
pub fn auto_movement(&mut self, app: &mut AppState) {
if self.pause_movement {
return;