single set mode

This commit is contained in:
galister
2025-12-17 19:23:44 +09:00
parent 90bce4c369
commit cd52b3cabb
6 changed files with 225 additions and 15 deletions

View File

@@ -187,6 +187,9 @@ pub struct GeneralConfig {
#[serde(default = "def_false")]
pub double_cursor_fix: bool,
#[serde(default = "def_false")]
pub single_set_mode: bool,
#[serde(default = "def_astrset_empty")]
pub custom_panels: AStrSet,

View File

@@ -0,0 +1,133 @@
<!--
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="::SingleSetOverlayToggle ${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 flex_direction="row" id="devices" interactable="0" gap="8">
<!-- 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 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 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 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

@@ -1,6 +1,6 @@
use std::f32::consts::PI;
use std::process::{Child, Command};
use std::{collections::VecDeque, time::Instant};
use std::time::Instant;
use glam::{Affine3A, Vec2, Vec3A, Vec3Swizzles};
@@ -192,7 +192,6 @@ pub struct InteractionState {
pub grabbed: Option<GrabData>,
pub clicked_id: Option<OverlayID>,
pub hovered_id: Option<OverlayID>,
pub release_actions: VecDeque<Box<dyn Fn()>>,
pub next_push: Instant,
pub haptics: Option<f32>,
}
@@ -204,7 +203,6 @@ impl Default for InteractionState {
grabbed: None,
clicked_id: None,
hovered_id: None,
release_actions: VecDeque::new(),
next_push: Instant::now(),
haptics: None,
}

View File

@@ -59,6 +59,7 @@ pub type CreateOverlayTask = dyn FnOnce(&mut AppState) -> Option<OverlayWindowCo
pub enum OverlayTask {
AddSet,
ToggleSet(usize),
SoftToggleOverlay(OverlaySelector),
DeleteActiveSet,
ToggleEditMode,
ShowHide,

View File

@@ -51,6 +51,7 @@ struct OverlayButton {
button: Rc<ComponentButton>,
label: WidgetID,
sprite: WidgetID,
condensed: bool,
}
#[derive(Default)]
@@ -126,6 +127,25 @@ pub fn create_watch(app: &mut AppState) -> anyhow::Result<OverlayWindowConfig> {
Ok(EventResult::Consumed)
})
}
"::SingleSetOverlayToggle" => {
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| {
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::SoftToggleOverlay(
OverlaySelector::Id(overlay.id),
)));
Ok(EventResult::Consumed)
})
}
_ => return,
};
@@ -134,9 +154,16 @@ pub fn create_watch(app: &mut AppState) -> anyhow::Result<OverlayWindowConfig> {
}
});
let watch_xml = app
.session
.config
.single_set_mode
.then_some("gui/watch-noset.xml")
.unwrap_or("gui/watch.xml");
let mut panel = GuiPanel::new_from_template(
app,
"gui/watch.xml",
watch_xml,
state,
NewGuiPanelParams {
on_custom_id: Some(Box::new(
@@ -165,7 +192,7 @@ pub fn create_watch(app: &mut AppState) -> anyhow::Result<OverlayWindowConfig> {
.fetch_component_as::<ComponentButton>(&format!("set_{idx}"))?;
state.set_buttons.push(comp);
}
} else if &*id == "toolbox" {
} else if &*id == "toolbox" || &*id == "toolbox-condensed" {
for idx in 0..MAX_TOOLBOX_BUTTONS {
let id_str = format!("overlay_{idx}");
@@ -193,6 +220,7 @@ pub fn create_watch(app: &mut AppState) -> anyhow::Result<OverlayWindowConfig> {
.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") {
@@ -287,11 +315,15 @@ pub fn create_watch(app: &mut AppState) -> anyhow::Result<OverlayWindowConfig> {
match event_data {
OverlayEventData::ActiveSetChanged(current_set) => {
if let Some(old_set) = panel.state.current_set.take() {
panel.state.set_buttons[old_set].set_sticky_state(&mut com, false);
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 {
panel.state.set_buttons[new_set].set_sticky_state(&mut com, true);
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;
}
@@ -341,7 +373,11 @@ pub fn create_watch(app: &mut AppState) -> anyhow::Result<OverlayWindowConfig> {
panel.state.overlay_metas = metas;
for (idx, btn) in panel.state.overlay_buttons.iter().enumerate() {
let display = if let Some(meta) = panel.state.overlay_metas.get(idx) {
let name = sanitize_overlay_name(&meta.name);
let name = btn
.condensed
.then(|| condense_overlay_name(&meta.name))
.unwrap_or_else(|| sanitize_overlay_name(&meta.name));
if let Some(mut label) =
panel.layout.state.widgets.get_as::<WidgetLabel>(btn.label)
{
@@ -438,3 +474,12 @@ pub fn watch_fade<D>(app: &mut AppState, watch: &mut OverlayWindowData<D>) {
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

@@ -148,6 +148,31 @@ where
OverlayTask::ToggleSet(set) => {
self.switch_or_toggle_set(app, set);
}
OverlayTask::SoftToggleOverlay(sel) => {
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];
if let Some(active_state) = o.config.active_state.take() {
log::debug!("{}: soft-toggle off", o.config.name);
self.sets[self.restore_set]
.overlays
.insert(id, active_state);
} else if let Some(state) = self.sets[self.restore_set].overlays.remove(id) {
let o = &mut self.overlays[id];
log::debug!("{}: soft-toggle on", o.config.name);
o.config.dirty = true;
o.config.active_state = Some(state);
o.config.reset(app, false);
} else {
// no saved state
o.config.activate(app);
}
return Ok(());
}
OverlayTask::ToggleEditMode => {
self.set_edit_mode(!self.edit_mode, app)?;
}
@@ -428,14 +453,19 @@ impl<T> OverlayWindowManager<T> {
}
}
pub fn id_by_selector(&self, selector: &OverlaySelector) -> Option<OverlayID> {
match selector {
OverlaySelector::Id(id) => Some(*id),
OverlaySelector::Name(name) => self.lookup(name),
}
}
pub fn mut_by_selector(
&mut self,
selector: &OverlaySelector,
) -> Option<&mut OverlayWindowData<T>> {
match selector {
OverlaySelector::Id(id) => self.mut_by_id(*id),
OverlaySelector::Name(name) => self.lookup(name).and_then(|id| self.mut_by_id(id)),
}
self.id_by_selector(selector)
.and_then(|id| self.mut_by_id(id))
}
fn remove_by_selector(
@@ -559,7 +589,6 @@ impl<T> OverlayWindowManager<T> {
if let Some(current_set) = self.current_set.as_ref() {
let ws = &mut self.sets[*current_set];
ws.overlays.clear();
for (id, data) in self.overlays.iter_mut().filter(|(_, d)| !d.config.global) {
if let Some(state) = data.config.active_state.take() {
log::debug!("{}: active_state → ws{}", data.config.name, current_set);
@@ -582,6 +611,7 @@ impl<T> OverlayWindowManager<T> {
data.config.reset(app, false);
}
}
ws.overlays.clear();
self.restore_set = new_set;
}
self.current_set = new_set;