diff --git a/wlx-common/src/config.rs b/wlx-common/src/config.rs index 780a23f..db8cf2f 100644 --- a/wlx-common/src/config.rs +++ b/wlx-common/src/config.rs @@ -1,35 +1,23 @@ use std::{collections::HashMap, sync::Arc}; use chrono::Offset; -use glam::{vec3, Affine3A, Quat, Vec3}; +use glam::Affine3A; use idmap::IdMap; use serde::{Deserialize, Serialize}; use crate::{ astr_containers::{AStrMap, AStrSet}, - common::LeftRight, overlays::{ToastDisplayMethod, ToastTopic}, windowing::OverlayWindowState, }; pub type PwTokenMap = AStrMap; +pub type SerializedWindowStates = HashMap, OverlayWindowState>; #[derive(Clone, Serialize, Deserialize)] pub struct SerializedWindowSet { pub name: Arc, - pub overlays: HashMap, OverlayWindowState>, -} - -pub const fn def_watch_pos() -> Vec3 { - vec3(-0.03, -0.01, 0.125) -} - -pub const fn def_watch_rot() -> Quat { - Quat::from_xyzw(-0.707_106_6, 0.000_796_361_8, 0.707_106_6, 0.0) -} - -pub const fn def_left() -> LeftRight { - LeftRight::Left + pub overlays: SerializedWindowStates, } pub const fn def_pw_tokens() -> PwTokenMap { @@ -44,7 +32,7 @@ const fn def_click_freeze_time_ms() -> u32 { 300 } -pub const fn def_true() -> bool { +const fn def_true() -> bool { true } @@ -56,15 +44,15 @@ const fn def_one() -> f32 { 1.0 } -pub const fn def_half() -> f32 { +const fn def_half() -> f32 { 0.5 } -pub const fn def_point7() -> f32 { +const fn def_point7() -> f32 { 0.7 } -pub const fn def_point3() -> f32 { +const fn def_point3() -> f32 { 0.3 } @@ -80,6 +68,10 @@ const fn def_sets() -> Vec { Vec::new() } +fn def_global_set() -> SerializedWindowStates { + HashMap::new() +} + const fn def_zero_u32() -> u32 { 0 } @@ -137,15 +129,6 @@ pub struct GeneralConfig { #[serde(default = "def_theme_path")] pub theme_path: Arc, - #[serde(default = "def_watch_pos")] - pub watch_pos: Vec3, - - #[serde(default = "def_watch_rot")] - pub watch_rot: Quat, - - #[serde(default = "def_left")] - pub watch_hand: LeftRight, - #[serde(default = "def_click_freeze_time_ms")] pub click_freeze_time_ms: u32, @@ -287,6 +270,9 @@ pub struct GeneralConfig { #[serde(default = "def_sets")] pub sets: Vec, + #[serde(default = "def_global_set")] + pub global_set: SerializedWindowStates, + #[serde(default = "def_zero_u32")] pub last_set: u32, } diff --git a/wlx-common/src/windowing.rs b/wlx-common/src/windowing.rs index 2c6041d..4bda90f 100644 --- a/wlx-common/src/windowing.rs +++ b/wlx-common/src/windowing.rs @@ -35,6 +35,7 @@ pub struct OverlayWindowState { pub positioning: Positioning, pub curvature: Option, pub additive: bool, + #[serde(skip_serializing, skip_deserializing)] pub saved_transform: Option, } diff --git a/wlx-overlay-s/src/backend/input.rs b/wlx-overlay-s/src/backend/input.rs index 6331c54..91cc203 100644 --- a/wlx-overlay-s/src/backend/input.rs +++ b/wlx-overlay-s/src/backend/input.rs @@ -1,6 +1,5 @@ use std::f32::consts::PI; use std::process::{Child, Command}; -use std::sync::Arc; use std::{collections::VecDeque, time::Instant}; use glam::{Affine3A, Vec2, Vec3A, Vec3Swizzles}; diff --git a/wlx-overlay-s/src/backend/openxr/mod.rs b/wlx-overlay-s/src/backend/openxr/mod.rs index 94e733e..b0e78ed 100644 --- a/wlx-overlay-s/src/backend/openxr/mod.rs +++ b/wlx-overlay-s/src/backend/openxr/mod.rs @@ -1,10 +1,7 @@ use std::{ collections::VecDeque, ops::Add, - sync::{ - atomic::{AtomicBool, Ordering}, - Arc, - }, + sync::{atomic::Ordering, Arc}, time::{Duration, Instant}, }; diff --git a/wlx-overlay-s/src/backend/wayvr/egl_data.rs b/wlx-overlay-s/src/backend/wayvr/egl_data.rs index 2583bb4..d4b844c 100644 --- a/wlx-overlay-s/src/backend/wayvr/egl_data.rs +++ b/wlx-overlay-s/src/backend/wayvr/egl_data.rs @@ -6,7 +6,7 @@ use crate::backend::wayvr::egl_ex::{ }; use super::egl_ex; -use anyhow::{anyhow, Context}; +use anyhow::Context; #[derive(Debug)] pub struct EGLData { diff --git a/wlx-overlay-s/src/config.rs b/wlx-overlay-s/src/config.rs index 62d91db..84a6507 100644 --- a/wlx-overlay-s/src/config.rs +++ b/wlx-overlay-s/src/config.rs @@ -1,13 +1,9 @@ use crate::config_io; use config::{Config, File}; -use glam::{Quat, Vec3}; use log::error; use serde::{Deserialize, Serialize}; use std::path::PathBuf; -use wlx_common::{ - common::LeftRight, - config::{GeneralConfig, SerializedWindowSet}, -}; +use wlx_common::config::{GeneralConfig, SerializedWindowSet, SerializedWindowStates}; const FALLBACKS: [&str; 2] = [ include_str!("res/keyboard.yaml"), @@ -105,9 +101,6 @@ pub fn load_general_config() -> GeneralConfig { #[derive(Serialize)] pub struct AutoSettings { - pub watch_pos: Vec3, - pub watch_rot: Quat, - pub watch_hand: LeftRight, pub watch_view_angle_min: f32, pub watch_view_angle_max: f32, pub notifications_enabled: bool, @@ -125,9 +118,6 @@ fn get_settings_path() -> PathBuf { pub fn save_settings(config: &GeneralConfig) -> anyhow::Result<()> { let conf = AutoSettings { - watch_pos: config.watch_pos, - watch_rot: config.watch_rot, - watch_hand: config.watch_hand, watch_view_angle_min: config.watch_view_angle_min, watch_view_angle_max: config.watch_view_angle_max, notifications_enabled: config.notifications_enabled, @@ -148,6 +138,7 @@ pub fn save_settings(config: &GeneralConfig) -> anyhow::Result<()> { #[derive(Serialize)] pub struct AutoState { pub sets: Vec, + pub global_set: SerializedWindowStates, pub last_set: u32, } @@ -161,6 +152,7 @@ pub fn save_state(config: &GeneralConfig) -> anyhow::Result<()> { let conf = AutoState { sets: config.sets.clone(), last_set: config.last_set, + global_set: config.global_set.clone(), }; let json = serde_json::to_string_pretty(&conf).unwrap(); // want panic diff --git a/wlx-overlay-s/src/overlays/toast.rs b/wlx-overlay-s/src/overlays/toast.rs index 59d0619..2debe54 100644 --- a/wlx-overlay-s/src/overlays/toast.rs +++ b/wlx-overlay-s/src/overlays/toast.rs @@ -1,5 +1,4 @@ use std::{ - f32::consts::PI, ops::Add, sync::{Arc, LazyLock}, time::Instant, @@ -17,6 +16,7 @@ use wlx_common::{ use crate::{ backend::task::{OverlayTask, TaskType}, gui::panel::{GuiPanel, NewGuiPanelParams, OnCustomIdFunc}, + overlays::watch::{WATCH_POS, WATCH_ROT}, state::AppState, windowing::{window::OverlayWindowConfig, OverlaySelector, Z_ORDER_TOAST}, }; @@ -121,12 +121,14 @@ fn new_toast(toast: Toast, app: &mut AppState) -> Option { Positioning::FollowHead { lerp: 0.1 }, ), ToastDisplayMethod::Watch => { - let mut watch_pos = app.session.config.watch_pos + vec3(-0.005, -0.05, 0.02); - let mut watch_rot = app.session.config.watch_rot; - let relative_to = match app.session.config.watch_hand { - LeftRight::Left => Positioning::FollowHand { + //FIXME: properly follow watch + let watch_pos = WATCH_POS + vec3(-0.005, -0.05, 0.02); + let watch_rot = WATCH_ROT; + let relative_to = /*match app.session.config.watch_hand { + LeftRight::Left =>*/ Positioning::FollowHand { hand: LeftRight::Left, lerp: 1.0, + /* }, LeftRight::Right => { watch_pos.x = -watch_pos.x; @@ -135,7 +137,7 @@ fn new_toast(toast: Toast, app: &mut AppState) -> Option { hand: LeftRight::Right, lerp: 1.0, } - } + }*/ }; (watch_pos, watch_rot, relative_to) } diff --git a/wlx-overlay-s/src/overlays/toolbox.rs b/wlx-overlay-s/src/overlays/toolbox.rs deleted file mode 100644 index 724a814..0000000 --- a/wlx-overlay-s/src/overlays/toolbox.rs +++ /dev/null @@ -1,80 +0,0 @@ -use std::time::Duration; - -use glam::{Affine3A, Vec3}; - -use crate::{ - gui::{panel::GuiPanel, timer::GuiTimer}, - state::AppState, - windowing::{ - window::{OverlayWindowConfig, OverlayWindowState, Positioning}, - Z_ORDER_WATCH, - }, -}; - -pub const EDIT_NAME: &str = "edit"; - -struct EditState { - num_sets: usize, - current_set: usize, -} - -#[allow(clippy::significant_drop_tightening)] -pub fn create_edit( - app: &mut AppState, - num_sets: usize, - current_set: usize, -) -> anyhow::Result { - let state = EditState { - num_sets, - current_set, - }; - let mut panel = GuiPanel::new_from_template( - app, - "gui/watch.xml", - state, - Some(Box::new( - move |id, widget, doc_params, layout, parser_state| { - if &*id != "sets" { - return Ok(()); - } - - for idx in 0..num_sets { - let mut params: HashMap, Rc> = HashMap::new(); - params.insert("display".into(), (idx + 1).to_string().into()); - params.insert("handle".into(), idx.to_string().into()); - parser_state.instantiate_template(doc_params, "Set", layout, widget, params)?; - } - Ok(()) - }, - )), - )?; - - panel - .timers - .push(GuiTimer::new(Duration::from_millis(100), 0)); - - let positioning = Positioning::FollowHand { - hand: app.session.config.watch_hand as _, - lerp: 1.0, - }; - - panel.update_layout()?; - - Ok(OverlayWindowConfig { - name: EDIT_NAME.into(), - z_order: Z_ORDER_WATCH, - default_state: OverlayWindowState { - interactable: true, - positioning, - transform: Affine3A::from_scale_rotation_translation( - Vec3::ONE * 0.115, - app.session.config.watch_rot, - app.session.config.watch_pos, - ) * Affine3A::from_translation(Vec3::Y * 0.075), - ..OverlayWindowState::default() - }, - show_on_spawn: true, - global: true, - ..OverlayWindowConfig::from_backend(Box::new(panel)) - }) -} diff --git a/wlx-overlay-s/src/overlays/watch.rs b/wlx-overlay-s/src/overlays/watch.rs index b472ac5..c3ef2bb 100644 --- a/wlx-overlay-s/src/overlays/watch.rs +++ b/wlx-overlay-s/src/overlays/watch.rs @@ -4,7 +4,7 @@ use std::{ time::{Duration, Instant}, }; -use glam::{Affine3A, Vec3, Vec3A}; +use glam::{vec3, Affine3A, Quat, Vec3, Vec3A}; use idmap::DirectIdMap; use wgui::{ components::button::ComponentButton, @@ -16,7 +16,10 @@ use wgui::{ taffy, widget::{sprite::WidgetSprite, EventResult}, }; -use wlx_common::windowing::{OverlayWindowState, Positioning}; +use wlx_common::{ + common::LeftRight, + windowing::{OverlayWindowState, Positioning}, +}; use crate::{ backend::{ @@ -41,6 +44,9 @@ pub const WATCH_NAME: &str = "watch"; const MAX_TOOLBOX_BUTTONS: usize = 16; const MAX_DEVICES: usize = 9; +pub(crate) const WATCH_POS: Vec3 = vec3(-0.03, -0.01, 0.125); +pub(crate) const WATCH_ROT: Quat = Quat::from_xyzw(-0.707_106_6, 0.000_796_361_8, 0.707_106_6, 0.0); + #[derive(Default)] struct WatchState { current_set: Option, @@ -323,7 +329,7 @@ pub fn create_watch(app: &mut AppState) -> anyhow::Result { .push(GuiTimer::new(Duration::from_millis(100), 0)); let positioning = Positioning::FollowHand { - hand: app.session.config.watch_hand, + hand: LeftRight::Left, lerp: 1.0, }; @@ -337,8 +343,8 @@ pub fn create_watch(app: &mut AppState) -> anyhow::Result { positioning, transform: Affine3A::from_scale_rotation_translation( Vec3::ONE * 0.115, - app.session.config.watch_rot, - app.session.config.watch_pos, + WATCH_ROT, + WATCH_POS, ), ..OverlayWindowState::default() }, diff --git a/wlx-overlay-s/src/overlays/wayvr.rs b/wlx-overlay-s/src/overlays/wayvr.rs index 35974e6..d2f9045 100644 --- a/wlx-overlay-s/src/overlays/wayvr.rs +++ b/wlx-overlay-s/src/overlays/wayvr.rs @@ -251,7 +251,7 @@ where if newly_created { log::info!("Creating dashboard overlay"); - let mut overlay = OverlayWindowData::from_config(OverlayWindowConfig { + let overlay = OverlayWindowData::from_config(OverlayWindowConfig { default_state: OverlayWindowState { interactable: true, grabbable: true, @@ -284,8 +284,6 @@ where )? }); - overlay.config.reset(app, true); - let overlay_id = overlays.add(overlay, app); wayvr.set_overlay_display_handle(overlay_id, disp_handle); @@ -433,7 +431,14 @@ where app.tasks.enqueue(TaskType::Overlay(OverlayTask::Modify( OverlaySelector::Id(overlay_id), Box::new(move |app, o| { - o.toggle(app); + if visible == o.is_active() { + return; + } + if visible { + o.activate(app); + } else { + o.deactivate(); + } }), ))); } diff --git a/wlx-overlay-s/src/windowing/manager.rs b/wlx-overlay-s/src/windowing/manager.rs index 0c6244c..11a5224 100644 --- a/wlx-overlay-s/src/windowing/manager.rs +++ b/wlx-overlay-s/src/windowing/manager.rs @@ -5,7 +5,11 @@ use std::{ use glam::{Affine3A, Vec3, Vec3A}; use slotmap::{HopSlotMap, Key, SecondaryMap}; -use wlx_common::{config::SerializedWindowSet, overlays::ToastTopic}; +use wlx_common::{ + astr_containers::{AStrMap, AStrMapExt}, + config::SerializedWindowSet, + overlays::ToastTopic, +}; use crate::{ backend::task::OverlayTask, @@ -107,21 +111,24 @@ where let anchor = OverlayWindowData::from_config(create_anchor(app)?); me.add(anchor, app); - let mut watch = OverlayWindowData::from_config(create_watch(app)?); + let watch = OverlayWindowData::from_config(create_watch(app)?); + me.watch_id = me.add(watch, app); + + // overwrite default layout with saved layout, if exists + me.restore_layout(app); + me.overlays_changed(app)?; for ev in [ OverlayEventData::NumSetsChanged(me.sets.len()), OverlayEventData::EditModeChanged(false), OverlayEventData::DevicesChanged, ] { - watch.config.backend.notify(app, ev)?; + me.mut_by_id(me.watch_id) + .unwrap() + .config + .backend + .notify(app, ev)?; } - me.watch_id = me.add(watch, app); - - me.overlays_changed(app)?; - - // overwrite default layout with saved layout, if exists - me.restore_layout(app); Ok(me) } @@ -227,6 +234,7 @@ impl OverlayWindowManager { } pub fn persist_layout(&mut self, app: &mut AppState) { + app.session.config.global_set.clear(); app.session.config.sets.clear(); app.session.config.sets.reserve(self.sets.len()); app.session.config.last_set = self.restore_set as _; @@ -240,7 +248,7 @@ impl OverlayWindowManager { }; for set in &self.sets { - let overlays: HashMap<_, _> = set + let mut overlays: HashMap<_, _> = set .overlays .iter() .filter_map(|(k, v)| { @@ -249,6 +257,13 @@ impl OverlayWindowManager { }) .collect(); + // overlays that we haven't seen since startup (e.g. wayvr apps) + for (k, o) in set.inactive_overlays.iter() { + if !overlays.contains_key(k) { + overlays.insert(k.clone(), o.clone()); + } + } + let serialized = SerializedWindowSet { name: set.name.clone(), overlays, @@ -256,6 +271,23 @@ impl OverlayWindowManager { app.session.config.sets.push(serialized); } + // global overlays; watch, toast + for oid in &[self.watch_id] { + let Some(o) = self.get_by_id(*oid) else { + break; + }; + let Some(mut state) = o.config.active_state.clone() else { + break; + }; + if let Some(transform) = state.saved_transform.as_ref() { + state.transform = *transform; + } + app.session + .config + .global_set + .insert(o.config.name.clone(), state.clone()); + } + if restore_after { self.switch_to_set(app, Some(self.restore_set)); } @@ -275,18 +307,42 @@ impl OverlayWindowManager { self.sets.clear(); self.sets.reserve(app.session.config.sets.len()); - for s in &app.session.config.sets { - let overlays: SecondaryMap<_, _> = s - .overlays - .iter() - .filter_map(|(name, v)| self.lookup(name).map(|id| (id, v.clone()))) - .collect(); + for (i, s) in app.session.config.sets.iter().enumerate() { + let mut overlays = SecondaryMap::new(); + let mut inactive_overlays = AStrMap::new(); + + for (name, o) in s.overlays.iter() { + if let Some(id) = self.lookup(&*name) { + log::debug!("set {i}: loaded state for {name}"); + overlays.insert(id, o.clone()); + } else { + log::debug!("set {i} has saved state for {name} which doesn't exist. will apply state once added."); + inactive_overlays.arc_set(name.clone(), o.clone()); + } + } self.sets.push(OverlayWindowSet { name: s.name.clone(), overlays, + inactive_overlays, }); } + + // global overlays + for oid in &[self.watch_id] { + if let Some(o) = self.mut_by_id(*oid) { + if let Some(mut state) = app.session.config.global_set.get(&*o.config.name).cloned() + { + state.saved_transform = Some(state.transform); + o.config.active_state = Some(state); + o.config.reset(app, false); + log::debug!("global set: loaded state for {}", o.config.name); + } else { + log::debug!("global set: no state for {}", o.config.name); + } + } + } + self.restore_set = (app.session.config.last_set as usize).min(self.sets.len() - 1); } @@ -403,8 +459,6 @@ impl OverlayWindowManager { } pub fn add(&mut self, mut overlay: OverlayWindowData, app: &mut AppState) -> OverlayID { - let internal = matches!(overlay.config.category, OverlayCategory::Internal); - while self.lookup(&overlay.config.name).is_some() { log::error!( "An overlay with name {} already exists. Deduplicating, but things may break!", @@ -413,15 +467,41 @@ impl OverlayWindowManager { overlay.config.name = format!("{}_2", overlay.config.name).into(); } - if overlay.config.show_on_spawn { - log::debug!("activating {} due to show_on_spawn", overlay.config.name); - overlay.config.activate(app); + let name = overlay.config.name.clone(); + let global = overlay.config.global; + let internal = matches!(overlay.config.category, OverlayCategory::Internal); + let show_on_spawn = overlay.config.show_on_spawn; + + let oid = self.overlays.insert(overlay); + let mut shown = false; + + if !global { + for (i, set) in self.sets.iter_mut().enumerate() { + let Some(mut state) = set.inactive_overlays.arc_rm(&*name) else { + continue; + }; + if self.current_set == Some(i) { + let o = &mut self.overlays[oid]; + state.saved_transform = Some(state.transform); + o.config.active_state = Some(state); + o.config.reset(app, false); + shown = true; + log::debug!("loaded state for {name} to active set!"); + } else { + set.overlays.insert(oid, state); + log::debug!("loaded state for {name} to set {i}"); + } + } + } + + if !shown && show_on_spawn { + log::debug!("activating {} due to show_on_spawn", name); + self.overlays[oid].config.activate(app); } - let ret_val = self.overlays.insert(overlay); if !internal && let Err(e) = self.overlays_changed(app) { log::error!("Error while adding overlay: {e:?}"); } - ret_val + oid } pub fn switch_or_toggle_set(&mut self, app: &mut AppState, set: usize) { @@ -495,9 +575,6 @@ impl OverlayWindowManager { } else { self.switch_to_set(app, None); } - - // toggle watch back on if it was hidden - self.mut_by_id(self.watch_id).unwrap().config.activate(app); } fn overlays_changed(&mut self, app: &mut AppState) -> anyhow::Result<()> { diff --git a/wlx-overlay-s/src/windowing/set.rs b/wlx-overlay-s/src/windowing/set.rs index 05bd642..180252b 100644 --- a/wlx-overlay-s/src/windowing/set.rs +++ b/wlx-overlay-s/src/windowing/set.rs @@ -1,6 +1,6 @@ use slotmap::SecondaryMap; use std::sync::Arc; -use wlx_common::windowing::OverlayWindowState; +use wlx_common::{astr_containers::AStrMap, windowing::OverlayWindowState}; use crate::windowing::OverlayID; @@ -8,4 +8,7 @@ use crate::windowing::OverlayID; pub struct OverlayWindowSet { pub(super) name: Arc, pub(super) overlays: SecondaryMap, + + // stores overlays that have not been seen since startup. + pub(super) inactive_overlays: AStrMap, } diff --git a/wlx-overlay-s/src/windowing/window.rs b/wlx-overlay-s/src/windowing/window.rs index 8155184..ef057d3 100644 --- a/wlx-overlay-s/src/windowing/window.rs +++ b/wlx-overlay-s/src/windowing/window.rs @@ -104,6 +104,10 @@ impl OverlayWindowConfig { } } + pub fn is_active(&self) -> bool { + self.active_state.is_some() + } + pub fn activate(&mut self, app: &mut AppState) { log::debug!("activate {}", self.name.as_ref()); self.dirty = true; @@ -111,14 +115,6 @@ impl OverlayWindowConfig { self.reset(app, true); } - pub fn activate_static(&mut self, global_transform: Affine3A) { - log::debug!("activate {}", self.name.as_ref()); - self.dirty = true; - let mut state = self.default_state.clone(); - state.transform = global_transform; - self.active_state = Some(state); - } - pub fn deactivate(&mut self) { log::debug!("deactivate {}", self.name.as_ref()); self.active_state = None; @@ -128,7 +124,7 @@ impl OverlayWindowConfig { if self.active_state.take().is_none() { self.activate(app); } else { - log::debug!("deactivate {}", self.name.as_ref()); + log::debug!("deactivate {} (toggle)", self.name.as_ref()); } } @@ -249,16 +245,14 @@ pub fn realign(transform: &mut Affine3A, hmd: &Affine3A) { transform.matrix3 = Mat3A::from_cols(col_x, col_y, col_z).mul_scalar(scale) * rot; } -pub fn save_transform(state: &mut OverlayWindowState, app: &mut AppState) -> bool { +pub fn save_transform(state: &mut OverlayWindowState, app: &mut AppState) { let parent_transform = match state.positioning { Positioning::Floating => snap_upright(app.input_state.hmd, Vec3A::Y), Positioning::FollowHead { .. } => app.input_state.hmd, Positioning::FollowHand { hand, .. } => app.input_state.pointers[hand as usize].pose, Positioning::Anchored => snap_upright(app.anchor, Vec3A::Y), - Positioning::Static => return false, + Positioning::Static => return, }; state.saved_transform = Some(parent_transform.inverse() * state.transform); - - true }