diff --git a/wlx-overlay-s/src/backend/openvr/mod.rs b/wlx-overlay-s/src/backend/openvr/mod.rs index 682b584..91bb56f 100644 --- a/wlx-overlay-s/src/backend/openvr/mod.rs +++ b/wlx-overlay-s/src/backend/openvr/mod.rs @@ -28,6 +28,7 @@ use crate::{ task::{SystemTask, TaskType}, BackendError, }, + config::save_state, graphics::{init_openvr_graphics, CommandBuffers}, overlays::{ toast::{Toast, ToastTopic}, @@ -368,8 +369,11 @@ pub fn openvr_run( } // chaperone + } // main_loop - // close font handles? + overlays.persist_layout(&mut app); + if let Err(e) = save_state(&app.session.config) { + log::error!("Could not save state: {e:?}"); } log::warn!("OpenVR shutdown"); diff --git a/wlx-overlay-s/src/backend/openxr/mod.rs b/wlx-overlay-s/src/backend/openxr/mod.rs index f8d3b48..3774401 100644 --- a/wlx-overlay-s/src/backend/openxr/mod.rs +++ b/wlx-overlay-s/src/backend/openxr/mod.rs @@ -22,6 +22,7 @@ use crate::{ task::{SystemTask, TaskType}, BackendError, }, + config::save_state, graphics::{init_openxr_graphics, CommandBuffers}, overlays::{ toast::{Toast, ToastTopic}, @@ -561,6 +562,11 @@ pub fn openxr_run( //FIXME: Temporary workaround for Monado bug let watch = overlays.mut_by_id(watch_id).unwrap(); // want panic watch.config.active_state.as_mut().unwrap().transform = watch_transform; + } // main_loop + + overlays.persist_layout(&mut app); + if let Err(e) = save_state(&app.session.config) { + log::error!("Could not save state: {e:?}"); } Ok(()) diff --git a/wlx-overlay-s/src/config.rs b/wlx-overlay-s/src/config.rs index 57d2537..2b792cf 100644 --- a/wlx-overlay-s/src/config.rs +++ b/wlx-overlay-s/src/config.rs @@ -4,6 +4,7 @@ use std::sync::Arc; use crate::config_io; use crate::overlays::toast::{DisplayMethod, ToastTopic}; use crate::state::LeftRight; +use crate::windowing::set::SerializedWindowSet; use chrono::Offset; use config::{Config, File}; use glam::{vec3, Affine3A, Quat, Vec3}; @@ -126,6 +127,14 @@ const fn def_empty_vec_string() -> Vec { Vec::new() } +const fn def_sets() -> Vec { + Vec::new() +} + +const fn def_zero_u32() -> u32 { + 0 +} + fn def_timezones() -> Vec { const EMEA: i32 = -60 * 60; // UTC-1 const APAC: i32 = 5 * 60 * 60; // UTC+5 @@ -309,6 +318,12 @@ pub struct GeneralConfig { #[serde(default = "def_false")] pub clock_12h: bool, + + #[serde(default = "def_sets")] + pub sets: Vec, + + #[serde(default = "def_zero_u32")] + pub last_set: u32, } impl GeneralConfig { @@ -480,9 +495,8 @@ pub fn save_settings(config: &GeneralConfig) -> anyhow::Result<()> { #[derive(Serialize)] pub struct AutoState { - pub show_screens: AStrSet, - pub curve_values: AStrMap, - pub transform_values: AStrMap, + pub sets: Vec, + pub last_set: u32, } fn get_state_path() -> PathBuf { @@ -491,15 +505,15 @@ fn get_state_path() -> PathBuf { .join("zz-saved-state.json5") } -pub fn save_layout(config: &GeneralConfig) -> anyhow::Result<()> { +pub fn save_state(config: &GeneralConfig) -> anyhow::Result<()> { let conf = AutoState { - show_screens: config.show_screens.clone(), - curve_values: config.curve_values.clone(), - transform_values: config.transform_values.clone(), + sets: config.sets.clone(), + last_set: config.last_set.clone(), }; let json = serde_json::to_string_pretty(&conf).unwrap(); // want panic std::fs::write(get_state_path(), json)?; + log::info!("State was saved successfully."); Ok(()) } diff --git a/wlx-overlay-s/src/config_wayvr.rs b/wlx-overlay-s/src/config_wayvr.rs index bbb61f8..97e470e 100644 --- a/wlx-overlay-s/src/config_wayvr.rs +++ b/wlx-overlay-s/src/config_wayvr.rs @@ -17,7 +17,8 @@ use crate::{ }, config::load_config_with_conf_d, config_io, - overlays::wayvr::{WayVRData, executable_exists_in_path}, + overlays::wayvr::{executable_exists_in_path, WayVRData}, + state::LeftRight, windowing::window::Positioning, }; @@ -36,8 +37,14 @@ impl AttachTo { pub const fn get_positioning(&self) -> Positioning { match self { Self::None => Positioning::Floating, - Self::HandLeft => Positioning::FollowHand { hand: 0, lerp: 1.0 }, - Self::HandRight => Positioning::FollowHand { hand: 1, lerp: 1.0 }, + Self::HandLeft => Positioning::FollowHand { + hand: LeftRight::Left, + lerp: 1.0, + }, + Self::HandRight => Positioning::FollowHand { + hand: LeftRight::Right, + lerp: 1.0, + }, Self::Stage => Positioning::Static, Self::Head => Positioning::FollowHead { lerp: 1.0 }, } diff --git a/wlx-overlay-s/src/overlays/toast.rs b/wlx-overlay-s/src/overlays/toast.rs index 4f27723..13a2bb1 100644 --- a/wlx-overlay-s/src/overlays/toast.rs +++ b/wlx-overlay-s/src/overlays/toast.rs @@ -147,11 +147,17 @@ fn new_toast(toast: Toast, app: &mut AppState) -> Option { 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 { hand: 0, lerp: 1.0 }, + LeftRight::Left => Positioning::FollowHand { + hand: LeftRight::Left, + lerp: 1.0, + }, LeftRight::Right => { watch_pos.x = -watch_pos.x; watch_rot = watch_rot * Quat::from_rotation_x(PI) * Quat::from_rotation_z(PI); - Positioning::FollowHand { hand: 1, lerp: 1.0 } + Positioning::FollowHand { + hand: LeftRight::Right, + lerp: 1.0, + } } }; (watch_pos, watch_rot, relative_to) diff --git a/wlx-overlay-s/src/overlays/watch.rs b/wlx-overlay-s/src/overlays/watch.rs index 37fbe5b..bafde43 100644 --- a/wlx-overlay-s/src/overlays/watch.rs +++ b/wlx-overlay-s/src/overlays/watch.rs @@ -44,7 +44,7 @@ pub fn create_watch(app: &mut AppState, num_sets: usize) -> anyhow::Result { let last_idx = data.screens.len() - 1; @@ -68,6 +75,7 @@ where keyboard.config.show_on_spawn = true; let keyboard_id = me.add(keyboard, app); + // is this needed? me.switch_to_set(app, None); // copy keyboard to all sets @@ -87,13 +95,79 @@ where let watch = OverlayWindowData::from_config(create_watch(app, me.sets.len())?); me.watch_id = me.add(watch, app); - me.switch_to_set(app, None); + // overwrite default layout with saved layout, if exists + me.restore_layout(app); Ok(me) } } impl OverlayWindowManager { + pub fn persist_layout(&mut self, app: &mut AppState) { + app.session.config.sets.clear(); + app.session.config.sets.reserve(self.sets.len()); + app.session.config.last_set = self.restore_set as _; + + let mut restore_after = false; + // only safe to save when current_set is None + if self.current_set.is_some() { + self.switch_to_set(app, None); + restore_after = true; + } + + for set in self.sets.iter() { + let overlays: HashMap<_, _> = set + .overlays + .iter() + .filter_map(|(k, v)| { + let Some(n) = self.overlays.get(k).map(|o| o.config.name.clone()) else { + return None; + }; + Some((n, v.clone())) + }) + .collect(); + + let serialized = SerializedWindowSet { + name: set.name.clone(), + overlays, + }; + app.session.config.sets.push(serialized); + } + + if restore_after { + self.switch_to_set(app, Some(self.restore_set)); + } + } + + pub fn restore_layout(&mut self, app: &mut AppState) { + if app.session.config.sets.is_empty() { + // keep defaults + return; + } + + // only safe to load when current_set is None + if self.current_set.is_some() { + self.switch_to_set(app, None); + } + + self.sets.clear(); + self.sets.reserve(app.session.config.sets.len()); + + for s in app.session.config.sets.iter() { + let overlays: SecondaryMap<_, _> = s + .overlays + .iter() + .filter_map(|(name, v)| self.lookup(&name).map(|id| (id, v.clone()))) + .collect(); + + self.sets.push(OverlayWindowSet { + name: s.name.clone(), + overlays, + }); + } + self.restore_set = (app.session.config.last_set as usize).min(self.sets.len() - 1); + } + pub fn mut_by_selector( &mut self, selector: &OverlaySelector, @@ -151,7 +225,6 @@ impl OverlayWindowManager { if overlay.config.show_on_spawn { overlay.config.activate(app); } - self.overlays.insert(overlay) } @@ -188,7 +261,7 @@ impl OverlayWindowManager { if let Some(new_set) = new_set { if new_set >= self.sets.len() { - log::warn!("switch_to_set: new_set is out of range ({new_set:?})"); + log::error!("switch_to_set: new_set is out of range ({new_set:?})"); return; } diff --git a/wlx-overlay-s/src/windowing/mod.rs b/wlx-overlay-s/src/windowing/mod.rs index 006fb5c..c3eaa32 100644 --- a/wlx-overlay-s/src/windowing/mod.rs +++ b/wlx-overlay-s/src/windowing/mod.rs @@ -4,7 +4,7 @@ use std::sync::Arc; pub mod backend; pub mod manager; -mod set; +pub mod set; pub mod window; new_key_type! { diff --git a/wlx-overlay-s/src/windowing/set.rs b/wlx-overlay-s/src/windowing/set.rs index 8a45d8b..7263aed 100644 --- a/wlx-overlay-s/src/windowing/set.rs +++ b/wlx-overlay-s/src/windowing/set.rs @@ -1,8 +1,18 @@ +use std::{collections::HashMap, sync::Arc}; + +use serde::{Deserialize, Serialize}; use slotmap::SecondaryMap; use crate::windowing::{window::OverlayWindowState, OverlayID}; #[derive(Default)] pub struct OverlayWindowSet { + pub(super) name: Arc, pub(super) overlays: SecondaryMap, } + +#[derive(Clone, Serialize, Deserialize)] +pub struct SerializedWindowSet { + pub name: Arc, + pub overlays: HashMap, OverlayWindowState>, +} diff --git a/wlx-overlay-s/src/windowing/window.rs b/wlx-overlay-s/src/windowing/window.rs index ab3ca2c..688022e 100644 --- a/wlx-overlay-s/src/windowing/window.rs +++ b/wlx-overlay-s/src/windowing/window.rs @@ -1,10 +1,11 @@ use glam::{Affine3A, Mat3A, Quat, Vec3, Vec3A}; +use serde::{Deserialize, Serialize}; use std::{f32::consts::PI, sync::Arc}; use vulkano::image::view::ImageView; use crate::{ graphics::CommandBuffers, - state::AppState, + state::{AppState, LeftRight}, subsystem::input::KeyboardFocus, windowing::{ backend::{FrameMeta, OverlayBackend, ShouldRender}, @@ -12,7 +13,7 @@ use crate::{ }, }; -#[derive(Clone, Copy, Debug, Default)] +#[derive(Clone, Copy, Debug, Default, Serialize, Deserialize)] pub enum Positioning { /// Stays in place, recenters relative to HMD #[default] @@ -26,19 +27,14 @@ pub enum Positioning { /// Normally follows HMD, but paused due to interaction FollowHeadPaused { lerp: f32 }, /// Following hand - FollowHand { hand: usize, lerp: f32 }, + FollowHand { hand: LeftRight, lerp: f32 }, /// Normally follows hand, but paused due to interaction - FollowHandPaused { hand: usize, lerp: f32 }, - /// Follow another overlay - FollowOverlay { id: usize }, + FollowHandPaused { hand: LeftRight, lerp: f32 }, } impl Positioning { pub const fn moves_with_space(&self) -> bool { - matches!( - self, - Self::Floating | Self::Anchored | Self::Static - ) + matches!(self, Self::Floating | Self::Anchored | Self::Static) } } @@ -154,9 +150,10 @@ impl OverlayWindowConfig { let (target_transform, lerp) = match state.positioning { Positioning::FollowHead { lerp } => (app.input_state.hmd * cur_transform, lerp), - Positioning::FollowHand { hand, lerp } => { - (app.input_state.pointers[hand].pose * cur_transform, lerp) - } + Positioning::FollowHand { hand, lerp } => ( + app.input_state.pointers[hand as usize].pose * cur_transform, + lerp, + ), _ => return, }; @@ -196,10 +193,10 @@ impl OverlayWindowConfig { app.input_state.hmd } Positioning::FollowHand { hand, .. } | Positioning::FollowHandPaused { hand, .. } => { - app.input_state.pointers[hand].pose + app.input_state.pointers[hand as usize].pose } Positioning::Anchored => snap_upright(app.anchor, Vec3A::Y), - Positioning::FollowOverlay { .. } | Positioning::Static => return false, + Positioning::Static => return false, }; self.saved_transform = Some(parent_transform.inverse() * state.transform); @@ -219,10 +216,10 @@ impl OverlayWindowConfig { | Positioning::FollowHead { .. } | Positioning::FollowHeadPaused { .. } => app.input_state.hmd, Positioning::FollowHand { hand, .. } | Positioning::FollowHandPaused { hand, .. } => { - app.input_state.pointers[hand].pose + app.input_state.pointers[hand as usize].pose } Positioning::Anchored => app.anchor, - Positioning::FollowOverlay { .. } | Positioning::Static => return, + Positioning::Static => return, }; if hard_reset { @@ -284,7 +281,7 @@ impl OverlayWindowConfig { } // Contains the window state for a given set -#[derive(Clone)] +#[derive(Clone, Serialize, Deserialize)] pub struct OverlayWindowState { pub transform: Affine3A, pub alpha: f32,