diff --git a/wlx-overlay-s/src/assets/gui/adjust.xml b/wlx-overlay-s/src/assets/gui/adjust.xml new file mode 100644 index 0000000..4845e57 --- /dev/null +++ b/wlx-overlay-s/src/assets/gui/adjust.xml @@ -0,0 +1,44 @@ + + + + + + + + + + + +
+ +
+
+
+ + + + + + + + + + + + +
+
+
+
+
+
+
diff --git a/wlx-overlay-s/src/backend/input.rs b/wlx-overlay-s/src/backend/input.rs index dfccfdd..bd7eb2d 100644 --- a/wlx-overlay-s/src/backend/input.rs +++ b/wlx-overlay-s/src/backend/input.rs @@ -19,7 +19,7 @@ use super::task::{TaskContainer, TaskType}; #[derive(Clone, Default)] pub struct HoverResult { pub haptics: Option, - /// If true, the laster shows at this position and no further raycasting will be done. + /// If true, the laser shows at this position and no further raycasting will be done. pub consume: bool, } @@ -344,15 +344,18 @@ where // focus change if let Some(hovered_id) = hovered_id && hovered_id != hit.overlay - && let Some(old_hovered) = overlays.mut_by_id(hovered_id) { - if old_hovered.primary_pointer.is_some_and(|i| i == idx) { - old_hovered.primary_pointer = None; + if let Some(old_hovered) = overlays.mut_by_id(hovered_id) { + if old_hovered.primary_pointer.is_some_and(|i| i == idx) { + old_hovered.primary_pointer = None; + } + log::debug!("{} on_left (focus changed)", old_hovered.config.name); + old_hovered.config.backend.on_left(app, idx); } - log::debug!("{} on_left (focus changed)", old_hovered.config.name); - old_hovered.config.backend.on_left(app, idx); + overlays.edit_overlay(hovered_id, false, app); } + overlays.edit_overlay(hit.overlay, true, app); let Some(hovered) = overlays.mut_by_id(hit.overlay) else { log::warn!("Hit overlay {:?} does not exist", hit.overlay); return (0.0, None); // no hit @@ -424,11 +427,12 @@ fn handle_no_hit( overlays: &mut OverlayWindowManager, app: &mut AppState, ) { - if let Some(hovered_id) = hovered_id - && let Some(hovered) = overlays.mut_by_id(hovered_id) - { - log::debug!("{} on_left (no hit)", hovered.config.name); - hovered.config.backend.on_left(app, pointer_idx); + if let Some(hovered_id) = hovered_id { + if let Some(hovered) = overlays.mut_by_id(hovered_id) { + log::debug!("{} on_left (no hit)", hovered.config.name); + hovered.config.backend.on_left(app, pointer_idx); + } + overlays.edit_overlay(hovered_id, false, app); } // in case click released while not aiming at anything @@ -568,7 +572,7 @@ where }; let result = overlay.config.backend.on_hover(app, &hit); - if result.consume { + if result.consume || overlay.config.editing { return (Some(hit), result.haptics); } } diff --git a/wlx-overlay-s/src/gui/panel/mod.rs b/wlx-overlay-s/src/gui/panel/mod.rs index e2c938d..e8e062b 100644 --- a/wlx-overlay-s/src/gui/panel/mod.rs +++ b/wlx-overlay-s/src/gui/panel/mod.rs @@ -12,6 +12,7 @@ use wgui::{ InternalStateChangeEvent, MouseButtonIndex, MouseDownEvent, MouseLeaveEvent, MouseMotionEvent, MouseUpEvent, MouseWheelEvent, }, + gfx::cmd::WGfxClearMode, layout::{Layout, LayoutParams, WidgetID}, parser::ParserState, renderer_vk::context::Context as WguiContext, @@ -32,8 +33,7 @@ mod button; mod helper; mod label; -const MAX_SIZE: u32 = 2048; -const MAX_SIZE_VEC2: Vec2 = vec2(MAX_SIZE as _, MAX_SIZE as _); +const DEFAULT_MAX_SIZE: f32 = 2048.0; const COLOR_ERR: drawing::Color = drawing::Color::new(1., 0., 1., 1.); @@ -42,6 +42,7 @@ pub struct GuiPanel { pub state: S, pub timers: Vec, pub parser_state: ParserState, + pub max_size: Vec2, interaction_transform: Option, context: WguiContext, timestep: Timestep, @@ -63,6 +64,7 @@ impl GuiPanel { path: &str, state: S, on_custom_id: Option, + resize_to_parent: bool, ) -> anyhow::Result { let custom_elems = Rc::new(RefCell::new(vec![])); @@ -81,7 +83,7 @@ impl GuiPanel { }; let (mut layout, mut parser_state) = - wgui::parser::new_layout_from_assets(&doc_params, &LayoutParams::default())?; + wgui::parser::new_layout_from_assets(&doc_params, &LayoutParams { resize_to_parent })?; if let Some(on_element_id) = on_custom_id { let ids = parser_state.data.ids.clone(); // FIXME: copying all ids? @@ -125,13 +127,14 @@ impl GuiPanel { timestep, state, parser_state, + max_size: vec2(DEFAULT_MAX_SIZE as _, DEFAULT_MAX_SIZE as _), timers: vec![], interaction_transform: None, }) } - pub fn new_blank(app: &mut AppState, state: S) -> anyhow::Result { - let layout = Layout::new(app.wgui_globals.clone(), &LayoutParams::default())?; + pub fn new_blank(app: &mut AppState, state: S, resize_to_parent: bool) -> anyhow::Result { + let layout = Layout::new(app.wgui_globals.clone(), &LayoutParams { resize_to_parent })?; let context = WguiContext::new(&mut app.wgui_shared, 1.0)?; let mut timestep = Timestep::new(); timestep.set_tps(60.0); @@ -142,13 +145,14 @@ impl GuiPanel { timestep, state, parser_state: ParserState::default(), + max_size: vec2(DEFAULT_MAX_SIZE as _, DEFAULT_MAX_SIZE as _), timers: vec![], interaction_transform: None, }) } pub fn update_layout(&mut self) -> anyhow::Result<()> { - self.layout.update(MAX_SIZE_VEC2, 0.0) + self.layout.update(self.max_size, 0.0) } pub fn push_event(&mut self, app: &mut AppState, event: &WguiEvent) -> EventResult { @@ -223,21 +227,25 @@ impl OverlayBackend for GuiPanel { app: &mut AppState, tgt: Arc, buf: &mut CommandBuffers, - alpha: f32, + mut alpha: f32, ) -> anyhow::Result { self.context .update_viewport(&mut app.wgui_shared, tgt.extent_u32arr(), 1.0)?; - self.layout.update(MAX_SIZE_VEC2, self.timestep.alpha)?; + self.layout.update(self.max_size, self.timestep.alpha)?; + + // FIXME: pass this properly + let mut clear = WGfxClearMode::Clear([0., 0., 0., 0.]); + if alpha < 0. { + alpha *= -1.; + clear = WGfxClearMode::Keep; + } let mut cmd_buf = app .gfx .create_gfx_command_buffer(CommandBufferUsage::OneTimeSubmit) .unwrap(); // want panic - cmd_buf.begin_rendering( - tgt, - wgui::gfx::cmd::WGfxClearMode::Clear([0.0, 0.0, 0.0, 0.0]), - )?; + cmd_buf.begin_rendering(tgt, clear)?; let globals = self.layout.state.globals.clone(); // sorry let mut globals = globals.get(); @@ -263,8 +271,8 @@ impl OverlayBackend for GuiPanel { fn frame_meta(&mut self) -> Option { Some(FrameMeta { extent: [ - MAX_SIZE.min(self.layout.content_size.x as _), - MAX_SIZE.min(self.layout.content_size.y as _), + self.max_size.x.min(self.layout.content_size.x) as _, + self.max_size.y.min(self.layout.content_size.y) as _, 1, ], ..Default::default() @@ -301,7 +309,7 @@ impl OverlayBackend for GuiPanel { } fn on_left(&mut self, app: &mut AppState, pointer: usize) { - log::info!("panel: on left"); + log::debug!("panel: on left"); let e = WguiEvent::MouseLeave(MouseLeaveEvent { device: pointer }); self.push_event(app, &e); } diff --git a/wlx-overlay-s/src/overlays/adjust.rs b/wlx-overlay-s/src/overlays/adjust.rs new file mode 100644 index 0000000..e060c9c --- /dev/null +++ b/wlx-overlay-s/src/overlays/adjust.rs @@ -0,0 +1,195 @@ +use std::{ + any::Any, + mem::{self, ManuallyDrop}, + sync::Arc, +}; + +use glam::vec2; +use vulkano::image::{view::ImageView, ImageUsage}; + +use crate::{ + backend::input::HoverResult, + gui::panel::GuiPanel, + state::AppState, + windowing::{ + backend::{DummyBackend, OverlayBackend, ShouldRender}, + window::OverlayWindowConfig, + }, +}; + +type EditModeWrapPanel = GuiPanel>; + +#[derive(Default)] +pub struct EditModeManager { + panel_pool: Vec, +} + +impl EditModeManager { + pub fn wrap_edit_mode( + &mut self, + owc: &mut OverlayWindowConfig, + app: &mut AppState, + ) -> anyhow::Result<()> { + if owc.editing { + return Ok(()); + } + + log::debug!("EditMode wrap on {}", owc.name); + let inner = mem::replace(&mut owc.backend, Box::new(DummyBackend {})); + let mut panel = self.panel_pool.pop(); + if panel.is_none() { + panel = Some(make_adjustment_panel(app)?); + } + let mut panel = panel.unwrap(); + panel.state = owc.name.clone(); + owc.backend = Box::new(EditModeBackendWrapper { + inner: ManuallyDrop::new(inner), + panel: ManuallyDrop::new(panel), + can_render_inner: false, + image: None, + }); + owc.editing = true; + + Ok(()) + } + + pub fn unwrap_edit_mode(&mut self, owc: &mut OverlayWindowConfig) { + if !owc.editing { + return; + } + + log::debug!("EditMode unwrap on {}", owc.name); + let wrapper = mem::replace(&mut owc.backend, Box::new(DummyBackend {})); + let mut wrapper: Box = wrapper; + let wrapper = wrapper + .downcast_mut::() + .expect("Wrong type to unwrap"); + + let panel = unsafe { ManuallyDrop::take(&mut wrapper.panel) }; + self.panel_pool.push(panel); + + let inner = unsafe { ManuallyDrop::take(&mut wrapper.inner) }; + owc.backend = inner; + owc.editing = false; + + // wrapper is destroyed with nothing left inside + } +} + +pub struct EditModeBackendWrapper { + panel: ManuallyDrop, + inner: ManuallyDrop>, + image: Option>, + can_render_inner: bool, +} + +impl OverlayBackend for EditModeBackendWrapper { + fn init(&mut self, app: &mut crate::state::AppState) -> anyhow::Result<()> { + self.inner.init(app)?; + self.panel.init(app) + } + fn pause(&mut self, app: &mut crate::state::AppState) -> anyhow::Result<()> { + self.inner.pause(app)?; + self.panel.pause(app) + } + fn resume(&mut self, app: &mut crate::state::AppState) -> anyhow::Result<()> { + self.inner.resume(app)?; + self.panel.resume(app) + } + fn should_render(&mut self, app: &mut crate::state::AppState) -> anyhow::Result { + let i = self.inner.should_render(app)?; + + self.can_render_inner = !matches!(i, ShouldRender::Unable); + if self.can_render_inner + && let Some(ref frame_meta) = self.inner.frame_meta() + { + let new_size = vec2(frame_meta.extent[0] as _, frame_meta.extent[1] as _); + if self.panel.max_size != new_size { + log::debug!("EditWrapperGui size {} → {new_size}", self.panel.max_size); + self.panel.max_size = new_size; + self.panel.update_layout()?; + } + } + + let p = self.panel.should_render(app)?; + + Ok(match (i, p) { + (ShouldRender::Should, ShouldRender::Should) => ShouldRender::Should, + (ShouldRender::Should, ShouldRender::Can) => ShouldRender::Should, + (ShouldRender::Can, ShouldRender::Should) => ShouldRender::Should, + (ShouldRender::Can, ShouldRender::Can) => ShouldRender::Can, + // (ShouldRender::Unable, ShouldRender::Should) if self.image.is_some() => { + // ShouldRender::Should + // } + // (ShouldRender::Unable, ShouldRender::Can) if self.image.is_some() => ShouldRender::Can, + _ => ShouldRender::Unable, + }) + } + fn render( + &mut self, + app: &mut crate::state::AppState, + tgt: std::sync::Arc, + buf: &mut crate::graphics::CommandBuffers, + alpha: f32, + ) -> anyhow::Result { + if self.can_render_inner { + if self.image.is_none() + && let Some(ref meta) = self.inner.frame_meta() + { + let image = app.gfx.new_image( + meta.extent[0], + meta.extent[1], + app.gfx.surface_format, + ImageUsage::COLOR_ATTACHMENT | ImageUsage::SAMPLED, + )?; + self.image = Some(ImageView::new_default(image)?); + } + self.inner.render(app, tgt.clone(), buf, alpha)?; + } + + self.panel.render(app, tgt, buf, -1.) + } + fn frame_meta(&mut self) -> Option { + self.inner.frame_meta().or_else(|| self.panel.frame_meta()) + } + fn on_hover( + &mut self, + app: &mut crate::state::AppState, + hit: &crate::backend::input::PointerHit, + ) -> HoverResult { + // pass through hover events to force pipewire to capture frames for us + let _ = self.inner.on_hover(app, hit); + self.panel.on_hover(app, hit) + } + fn on_left(&mut self, app: &mut crate::state::AppState, pointer: usize) { + self.inner.on_left(app, pointer); + self.panel.on_left(app, pointer); + } + fn on_pointer( + &mut self, + app: &mut crate::state::AppState, + hit: &crate::backend::input::PointerHit, + pressed: bool, + ) { + self.panel.on_pointer(app, hit, pressed); + } + fn on_scroll( + &mut self, + app: &mut crate::state::AppState, + hit: &crate::backend::input::PointerHit, + delta_y: f32, + delta_x: f32, + ) { + self.panel.on_scroll(app, hit, delta_y, delta_x); + } + fn get_interaction_transform(&mut self) -> Option { + self.inner.get_interaction_transform() + } +} + +fn make_adjustment_panel(app: &mut AppState) -> anyhow::Result { + let mut panel = GuiPanel::new_from_template(app, "gui/adjust.xml", "".into(), None, true)?; + panel.update_layout()?; + + Ok(panel) +} diff --git a/wlx-overlay-s/src/overlays/anchor.rs b/wlx-overlay-s/src/overlays/anchor.rs index ede4a13..90d0f98 100644 --- a/wlx-overlay-s/src/overlays/anchor.rs +++ b/wlx-overlay-s/src/overlays/anchor.rs @@ -9,7 +9,7 @@ use crate::windowing::Z_ORDER_ANCHOR; pub static ANCHOR_NAME: LazyLock> = LazyLock::new(|| Arc::from("anchor")); pub fn create_anchor(app: &mut AppState) -> anyhow::Result { - let mut panel = GuiPanel::new_from_template(app, "gui/anchor.xml", (), None)?; + let mut panel = GuiPanel::new_from_template(app, "gui/anchor.xml", (), None, false)?; panel.update_layout()?; Ok(OverlayWindowConfig { diff --git a/wlx-overlay-s/src/overlays/bar.rs b/wlx-overlay-s/src/overlays/bar.rs index c13f963..fa61351 100644 --- a/wlx-overlay-s/src/overlays/bar.rs +++ b/wlx-overlay-s/src/overlays/bar.rs @@ -15,7 +15,7 @@ struct BarState {} #[allow(clippy::match_same_arms)] // TODO: remove later pub fn create_bar(app: &mut AppState) -> anyhow::Result { let state = BarState {}; - let mut panel = GuiPanel::new_from_template(app, "gui/bar.xml", state, None)?; + let mut panel = GuiPanel::new_from_template(app, "gui/bar.xml", state, None, false)?; for (id, _widget_id) in &panel.parser_state.data.ids { match id.as_ref() { diff --git a/wlx-overlay-s/src/overlays/custom.rs b/wlx-overlay-s/src/overlays/custom.rs index 9cef94b..79bc183 100644 --- a/wlx-overlay-s/src/overlays/custom.rs +++ b/wlx-overlay-s/src/overlays/custom.rs @@ -18,7 +18,7 @@ pub fn create_custom(app: &mut AppState, name: Arc) -> Option Option { toast.title }; - let mut panel = GuiPanel::new_blank(app, ()).ok()?; + let mut panel = GuiPanel::new_blank(app, (), false).ok()?; let globals = panel.layout.state.globals.clone(); diff --git a/wlx-overlay-s/src/overlays/watch.rs b/wlx-overlay-s/src/overlays/watch.rs index bafde43..3c4f076 100644 --- a/wlx-overlay-s/src/overlays/watch.rs +++ b/wlx-overlay-s/src/overlays/watch.rs @@ -37,6 +37,7 @@ pub fn create_watch(app: &mut AppState, num_sets: usize) -> anyhow::Result anyhow::Result<()>; fn pause(&mut self, app: &mut AppState) -> anyhow::Result<()>; @@ -46,7 +46,7 @@ pub trait OverlayBackend { /// Called to retrieve the effective extent of the image /// Used for creating swapchains. /// - /// Must be true if should_render was also true on the same frame. + /// Must be Some if should_render was Should or Can on the same frame. fn frame_meta(&mut self) -> Option; fn on_hover(&mut self, app: &mut AppState, hit: &PointerHit) -> HoverResult; @@ -69,3 +69,42 @@ pub fn ui_transform(extent: [u32; 2]) -> Affine2 { let center = Vec2 { x: 0.5, y: 0.5 }; Affine2::from_scale_angle_translation(scale, 0.0, center) } + +pub struct DummyBackend {} + +impl OverlayBackend for DummyBackend { + fn init(&mut self, _: &mut AppState) -> anyhow::Result<()> { + Ok(()) + } + fn pause(&mut self, _: &mut AppState) -> anyhow::Result<()> { + Ok(()) + } + fn resume(&mut self, _: &mut AppState) -> anyhow::Result<()> { + Ok(()) + } + fn should_render(&mut self, _: &mut AppState) -> anyhow::Result { + Ok(ShouldRender::Unable) + } + fn render( + &mut self, + _: &mut AppState, + _: Arc, + _: &mut CommandBuffers, + _: f32, + ) -> anyhow::Result { + Ok(false) + } + fn frame_meta(&mut self) -> Option { + None + } + + fn on_hover(&mut self, _: &mut AppState, _: &PointerHit) -> HoverResult { + HoverResult::default() + } + fn on_left(&mut self, _: &mut AppState, _: usize) {} + fn on_pointer(&mut self, _: &mut AppState, _: &PointerHit, _: bool) {} + fn on_scroll(&mut self, _: &mut AppState, _: &PointerHit, _: f32, _: f32) {} + fn get_interaction_transform(&mut self) -> Option { + None + } +} diff --git a/wlx-overlay-s/src/windowing/manager.rs b/wlx-overlay-s/src/windowing/manager.rs index b4a64eb..92309f4 100644 --- a/wlx-overlay-s/src/windowing/manager.rs +++ b/wlx-overlay-s/src/windowing/manager.rs @@ -5,8 +5,8 @@ use slotmap::{HopSlotMap, Key, SecondaryMap}; use crate::{ overlays::{ - anchor::create_anchor, keyboard::builder::create_keyboard, screen::create_screens, - watch::create_watch, + adjust::EditModeManager, anchor::create_anchor, keyboard::builder::create_keyboard, + screen::create_screens, watch::create_watch, }, state::AppState, windowing::{ @@ -18,6 +18,7 @@ use crate::{ }; pub struct OverlayWindowManager { + wrappers: EditModeManager, overlays: HopSlotMap>, sets: Vec, /// The set that is currently visible. @@ -37,6 +38,7 @@ where let mut maybe_keymap = None; let mut me = Self { + wrappers: EditModeManager::default(), overlays: HopSlotMap::with_key(), current_set: Some(0), restore_set: 0, @@ -168,6 +170,20 @@ impl OverlayWindowManager { self.restore_set = (app.session.config.last_set as usize).min(self.sets.len() - 1); } + pub fn edit_overlay(&mut self, id: OverlayID, enabled: bool, app: &mut AppState) { + let Some(overlay) = self.overlays.get_mut(id) else { + return; + }; + + if enabled { + self.wrappers + .wrap_edit_mode(&mut overlay.config, app) + .unwrap(); // FIXME: unwrap + } else { + self.wrappers.unwrap_edit_mode(&mut overlay.config); + } + } + pub fn mut_by_selector( &mut self, selector: &OverlaySelector, diff --git a/wlx-overlay-s/src/windowing/window.rs b/wlx-overlay-s/src/windowing/window.rs index afc1f27..e52e1a4 100644 --- a/wlx-overlay-s/src/windowing/window.rs +++ b/wlx-overlay-s/src/windowing/window.rs @@ -99,6 +99,8 @@ pub struct OverlayWindowConfig { pub global: bool, /// True if transform, curvature, alpha has changed. Only used by OpenVR. pub dirty: bool, + /// True if the window is showing the edit overlay + pub editing: bool, pub saved_transform: Option, } @@ -118,6 +120,7 @@ impl OverlayWindowConfig { show_on_spawn: false, global: false, dirty: true, + editing: false, } }