diff --git a/dash-frontend/assets/gui/tab/home.xml b/dash-frontend/assets/gui/tab/home.xml index ad7237ac..1c7dde58 100644 --- a/dash-frontend/assets/gui/tab/home.xml +++ b/dash-frontend/assets/gui/tab/home.xml @@ -24,7 +24,7 @@ - diff --git a/wayvr/src/assets/gui/edit.xml b/wayvr/src/assets/gui/edit.xml index 1a35c16a..eea9d3e5 100644 --- a/wayvr/src/assets/gui/edit.xml +++ b/wayvr/src/assets/gui/edit.xml @@ -34,7 +34,8 @@
- + +
@@ -126,9 +127,13 @@
- +
+
+ +
+
diff --git a/wayvr/src/assets/lang/en.json b/wayvr/src/assets/lang/en.json index 8c4a4275..27e849e2 100644 --- a/wayvr/src/assets/lang/en.json +++ b/wayvr/src/assets/lang/en.json @@ -64,6 +64,7 @@ "POS_STATIC": "Static: Not part of any set, no recenter.", "POSITIONING": "Positioning", "RESIZE_PRESS_AND_DRAG": "Resize (press & drag)", + "RESIZE_CORNER": "Click & hold to resize", "STEREO_3D_MODE": { "ADJUST_MOUSE": "Adjust mouse", "FULL_FRAME": "Full 3D", diff --git a/wayvr/src/backend/input.rs b/wayvr/src/backend/input.rs index ba8c37ca..da5dac93 100644 --- a/wayvr/src/backend/input.rs +++ b/wayvr/src/backend/input.rs @@ -735,7 +735,9 @@ where }; if uv.x < 0.0 || uv.x > 1.0 || uv.y < 0.0 || uv.y > 1.0 { - continue; + if !overlay.config.resizing { + continue; + } } let pointer_hit = PointerHit { diff --git a/wayvr/src/backend/task.rs b/wayvr/src/backend/task.rs index f841c18a..1d710256 100644 --- a/wayvr/src/backend/task.rs +++ b/wayvr/src/backend/task.rs @@ -110,6 +110,7 @@ pub enum OverlayTask { SwitchSet(Option), ToggleOverlay(OverlaySelector, ToggleMode), ResetOverlay(OverlaySelector), + ResizeOverlay(OverlaySelector, [u32; 2]), DeleteActiveSet, ToggleEditMode, ToggleDashboard, diff --git a/wayvr/src/backend/wayvr/mod.rs b/wayvr/src/backend/wayvr/mod.rs index b0cb0d39..1dbf5567 100644 --- a/wayvr/src/backend/wayvr/mod.rs +++ b/wayvr/src/backend/wayvr/mod.rs @@ -392,6 +392,7 @@ impl WvrServerState { let window_handle = wvr_server.wm.create_window( toplevel.clone(), process_handle, + output_bounds, min_size, max_size, fallback_size.w as _, diff --git a/wayvr/src/backend/wayvr/window.rs b/wayvr/src/backend/wayvr/window.rs index 8d67b8c2..68fcbd6f 100644 --- a/wayvr/src/backend/wayvr/window.rs +++ b/wayvr/src/backend/wayvr/window.rs @@ -10,6 +10,7 @@ use crate::{backend::wayvr::process, gen_id}; pub struct Window { pub min_size: Size, pub max_size: Size, + pub bounds: Size, pub size_x: u32, pub size_y: u32, pub visible: bool, @@ -21,10 +22,12 @@ impl Window { const fn new( toplevel: Rc, process: process::ProcessHandle, + bounds: Size, min_size: Size, max_size: Size, ) -> Self { Self { + bounds, min_size, max_size, size_x: 0, @@ -39,6 +42,17 @@ impl Window { self.min_size != self.max_size } + pub fn checked_configure_size(&mut self, size: Size) { + let clamped_size = size.clamp(self.min_size, self.max_size); + + self.toplevel.with_pending_state(|state| { + state.bounds = Some(self.bounds); + state.size = Some(clamped_size); + }); + self.toplevel.send_configure(); + self.remember_committed_size(size); + } + pub fn configure_size(&mut self, size: Option>, bounds: Size) { self.toplevel.with_pending_state(|state| { state.bounds = Some(bounds); @@ -46,6 +60,7 @@ impl Window { }); self.toplevel.send_configure(); + self.bounds = bounds; if let Some(size) = size { self.remember_committed_size(size); } @@ -106,12 +121,13 @@ impl WindowManager { &mut self, toplevel: Rc, process: process::ProcessHandle, + bounds: Size, min_size: Size, max_size: Size, size_x: u32, size_y: u32, ) -> WindowHandle { - let mut window = Window::new(toplevel, process, min_size, max_size); + let mut window = Window::new(toplevel, process, bounds, min_size, max_size); window.remember_committed_size(Size::new(size_x as i32, size_y as i32)); self.windows.add(window) } diff --git a/wayvr/src/gui/panel/mod.rs b/wayvr/src/gui/panel/mod.rs index 6d60d065..3ee58c82 100644 --- a/wayvr/src/gui/panel/mod.rs +++ b/wayvr/src/gui/panel/mod.rs @@ -367,7 +367,7 @@ impl OverlayBackend for GuiPanel { let e = WguiEvent::MouseWheel(MouseWheelEvent { delta: vec2(delta.x, delta.y) / 8.0, pos: hit.uv * self.layout.content_size, - device: DeviceBitmask::from_usize(hit.pointer), + device: DeviceBitmask::from_index(hit.pointer), }); self.push_event(app, &e); } @@ -375,7 +375,7 @@ impl OverlayBackend for GuiPanel { fn on_hover(&mut self, app: &mut AppState, hit: &PointerHit) -> HoverResult { let e = &WguiEvent::MouseMotion(MouseMotionEvent { pos: hit.uv * self.layout.content_size, - device: DeviceBitmask::from_usize(hit.pointer), + device: DeviceBitmask::from_index(hit.pointer), }); self.has_focus[hit.pointer] = true; @@ -397,7 +397,7 @@ impl OverlayBackend for GuiPanel { fn on_left(&mut self, app: &mut AppState, pointer: usize) { let e = WguiEvent::MouseLeave(MouseLeaveEvent { - device: DeviceBitmask::from_usize(pointer), + device: DeviceBitmask::from_index(pointer), }); self.has_focus[pointer] = false; self.push_event(app, &e); @@ -415,13 +415,13 @@ impl OverlayBackend for GuiPanel { WguiEvent::MouseDown(MouseButtonEvent { pos: hit.uv * self.layout.content_size, index, - device: DeviceBitmask::from_usize(hit.pointer), + device: DeviceBitmask::from_index(hit.pointer), }) } else { WguiEvent::MouseUp(MouseButtonEvent { pos: hit.uv * self.layout.content_size, index, - device: DeviceBitmask::from_usize(hit.pointer), + device: DeviceBitmask::from_index(hit.pointer), }) }; self.push_event(app, &e); @@ -430,11 +430,11 @@ impl OverlayBackend for GuiPanel { if !pressed && !self.has_focus[hit.pointer] { let e = WguiEvent::MouseMotion(MouseMotionEvent { pos: vec2(-1., -1.), - device: DeviceBitmask::from_usize(hit.pointer), + device: DeviceBitmask::from_index(hit.pointer), }); self.push_event(app, &e); let e = WguiEvent::MouseLeave(MouseLeaveEvent { - device: DeviceBitmask::from_usize(hit.pointer), + device: DeviceBitmask::from_index(hit.pointer), }); self.push_event(app, &e); } diff --git a/wayvr/src/overlays/dashboard.rs b/wayvr/src/overlays/dashboard.rs index bcd5f316..86eed6c2 100644 --- a/wayvr/src/overlays/dashboard.rs +++ b/wayvr/src/overlays/dashboard.rs @@ -233,7 +233,7 @@ impl OverlayBackend for DashFrontend { let e = WguiEvent::MouseWheel(MouseWheelEvent { delta: vec2(delta.x, delta.y) / 8.0, pos: hit.uv * self.inner.layout.content_size, - device: DeviceBitmask::from_usize(hit.pointer), + device: DeviceBitmask::from_index(hit.pointer), }); self.push_event(&e); } @@ -241,7 +241,7 @@ impl OverlayBackend for DashFrontend { fn on_hover(&mut self, _app: &mut AppState, hit: &PointerHit) -> HoverResult { let e = &WguiEvent::MouseMotion(MouseMotionEvent { pos: hit.uv * self.inner.layout.content_size, - device: DeviceBitmask::from_usize(hit.pointer), + device: DeviceBitmask::from_index(hit.pointer), }); self.has_focus[hit.pointer] = true; @@ -264,7 +264,7 @@ impl OverlayBackend for DashFrontend { fn on_left(&mut self, _app: &mut AppState, pointer: usize) { let e = WguiEvent::MouseLeave(MouseLeaveEvent { - device: DeviceBitmask::from_usize(pointer), + device: DeviceBitmask::from_index(pointer), }); self.has_focus[pointer] = false; self.push_event(&e); @@ -282,13 +282,13 @@ impl OverlayBackend for DashFrontend { WguiEvent::MouseDown(MouseButtonEvent { pos: hit.uv * self.inner.layout.content_size, index, - device: DeviceBitmask::from_usize(hit.pointer), + device: DeviceBitmask::from_index(hit.pointer), }) } else { WguiEvent::MouseUp(MouseButtonEvent { pos: hit.uv * self.inner.layout.content_size, index, - device: DeviceBitmask::from_usize(hit.pointer), + device: DeviceBitmask::from_index(hit.pointer), }) }; self.push_event(&e); @@ -297,11 +297,11 @@ impl OverlayBackend for DashFrontend { if !pressed && !self.has_focus[hit.pointer] { let e = WguiEvent::MouseMotion(MouseMotionEvent { pos: vec2(-1., -1.), - device: DeviceBitmask::from_usize(hit.pointer), + device: DeviceBitmask::from_index(hit.pointer), }); self.push_event(&e); let e = WguiEvent::MouseLeave(MouseLeaveEvent { - device: DeviceBitmask::from_usize(hit.pointer), + device: DeviceBitmask::from_index(hit.pointer), }); self.push_event(&e); } diff --git a/wayvr/src/overlays/edit/mod.rs b/wayvr/src/overlays/edit/mod.rs index 81410dea..8e9b7ee3 100644 --- a/wayvr/src/overlays/edit/mod.rs +++ b/wayvr/src/overlays/edit/mod.rs @@ -52,6 +52,19 @@ mod sprite_tab; mod stereo; pub mod tab; +enum ResizeState { + None, + Start { + overlay_id: OverlayID, + }, + Active { + overlay_id: OverlayID, + pointer: usize, + start_uv: Vec2, + last_uv: Vec2, + }, +} + struct EditModeState { tasks: Rc>, id: Rc>, @@ -60,7 +73,23 @@ struct EditModeState { pos: SpriteTabHandler, stereo: SpriteTabHandler, mouse: SpriteTabHandler, - resize_uv: Option, + resize: ResizeState, +} + +impl EditModeState { + fn resize_end(&mut self, app: &mut AppState) { + match std::mem::replace(&mut self.resize, ResizeState::None) { + ResizeState::Active { overlay_id, .. } => { + app.tasks.enqueue(TaskType::Overlay(OverlayTask::Modify( + OverlaySelector::Id(overlay_id), + Box::new(|_app, owc| { + owc.resizing = false; + }), + ))); + } + _ => {} + } + } } type EditModeWrapPanel = GuiPanel; @@ -96,7 +125,7 @@ impl EditWrapperManager { owc.backend = Box::new(EditModeBackendWrapper { inner: ManuallyDrop::new(inner), panel: ManuallyDrop::new(panel), - last_extent: [0, 0], + extent: [0, 0], last_render: Instant::now(), can_render_inner: false, }); @@ -138,7 +167,7 @@ pub struct EditModeBackendWrapper { panel: ManuallyDrop, inner: ManuallyDrop>, - last_extent: [u32; 2], + extent: [u32; 2], last_render: Instant, can_render_inner: bool, } @@ -217,7 +246,7 @@ impl OverlayBackend for EditModeBackendWrapper { rdr.cmd_bufs.reverse(); } - self.last_extent = rdr.extent; + self.extent = rdr.extent; Ok(()) } @@ -233,23 +262,32 @@ impl OverlayBackend for EditModeBackendWrapper { let _ = self.inner.on_hover(app, hit); let res = self.panel.on_hover(app, hit); - if let Some(start_uv) = self.panel.state.resize_uv.as_mut() { - let extent_f32 = vec2(self.last_extent[0] as f32, self.last_extent[1] as f32); + match &mut self.panel.state.resize { + ResizeState::Active { + overlay_id, + pointer, + start_uv, + last_uv, + } if *pointer == hit.pointer => { + // freshest extent we can get is from last frame, so use uv from that frame + let new_extent = resize_centered_from_uv(*start_uv, *last_uv, self.extent); - if start_uv.length_squared() > 10.0 { - // at first this is raw position in pixels, transform to UV here - *start_uv /= extent_f32; - } else { - // actually do resize - let new_res = extent_f32 * hit.uv / *start_uv; - let new_extent = [new_res.x as u32, new_res.y as u32]; - log::warn!("NEW SIZE: {new_extent:?}"); + app.tasks + .enqueue(TaskType::Overlay(OverlayTask::ResizeOverlay( + OverlaySelector::Id(*overlay_id), + new_extent, + ))); + + *last_uv = hit.uv; } + _ => {} } res } fn on_left(&mut self, app: &mut crate::state::AppState, pointer: usize) { + self.panel.state.resize_end(app); + self.inner.on_left(app, pointer); self.panel.on_left(app, pointer); } @@ -260,6 +298,26 @@ impl OverlayBackend for EditModeBackendWrapper { pressed: bool, ) { self.panel.on_pointer(app, hit, pressed); + + match &mut self.panel.state.resize { + ResizeState::Start { overlay_id } => { + app.tasks.enqueue(TaskType::Overlay(OverlayTask::Modify( + OverlaySelector::Id(*overlay_id), + Box::new(|_app, owc| { + owc.resizing = true; + }), + ))); + + let last_uv = hit.uv; + self.panel.state.resize = ResizeState::Active { + overlay_id: *overlay_id, + pointer: hit.pointer, + start_uv: last_uv, + last_uv: last_uv, + }; + } + _ => {} + } } fn on_scroll( &mut self, @@ -270,7 +328,8 @@ impl OverlayBackend for EditModeBackendWrapper { self.panel.on_scroll(app, hit, delta); } fn notify(&mut self, app: &mut AppState, event_data: OverlayEventData) -> anyhow::Result<()> { - self.panel.notify(app, event_data) + self.panel.notify(app, event_data.clone())?; + self.inner.notify(app, event_data) } fn get_interaction_transform(&mut self) -> Option { self.inner.get_interaction_transform() @@ -293,7 +352,7 @@ fn make_edit_panel(app: &mut AppState) -> anyhow::Result { pos: SpriteTabHandler::default(), stereo: SpriteTabHandler::default(), mouse: SpriteTabHandler::default(), - resize_uv: None, + resize: ResizeState::None, }; let anim_mult = app.wgui_theme.animation_mult; @@ -434,14 +493,16 @@ fn make_edit_panel(app: &mut AppState) -> anyhow::Result { if !test_button(data) || !test_duration(&button, app) { return Ok(EventResult::Pass); } - state.resize_uv = data.metadata.get_mouse_pos_absolute(); + state.resize = ResizeState::Start { + overlay_id: state.id.borrow().clone(), + }; Ok(EventResult::Consumed) }), "::EditModeResizeStop" => Box::new(move |_common, data, app, state| { if !test_button(data) || !test_duration(&button, app) { return Ok(EventResult::Pass); } - state.resize_uv = None; + state.resize_end(app); Ok(EventResult::Consumed) }), _ => return, @@ -742,3 +803,25 @@ fn set_up_checkbox( Ok(()) } + +pub fn resize_centered_from_uv(grab_uv: Vec2, cur_uv: Vec2, cur_extent: [u32; 2]) -> [u32; 2] { + const MIN_SIZE: Vec2 = Vec2::new(160.0, 160.0); + const CENTER_DEADZONE: f32 = 0.1; + + let cur_extent_v = Vec2::new(cur_extent[0] as f32, cur_extent[1] as f32); + let grab_from_center = grab_uv - Vec2::splat(0.5); + let cur_from_center = (cur_uv - Vec2::splat(0.5)) * cur_extent_v; + + let mut out = cur_extent_v; + if grab_from_center.x.abs() > CENTER_DEADZONE { + out.x = (cur_from_center.x / grab_from_center.x).abs(); + } + + if grab_from_center.y.abs() > CENTER_DEADZONE { + out.y = (cur_from_center.y / grab_from_center.y).abs(); + } + + out = out.max(MIN_SIZE); + + [out.x.round() as u32, out.y.round() as u32] +} diff --git a/wayvr/src/overlays/wayvr.rs b/wayvr/src/overlays/wayvr.rs index f2ee2c71..2f6c5cc5 100644 --- a/wayvr/src/overlays/wayvr.rs +++ b/wayvr/src/overlays/wayvr.rs @@ -62,6 +62,7 @@ use crate::{ }, }; +#[derive(Clone)] pub enum WvrCommand { CloseWindow, KillProcess(KillSignal), @@ -800,6 +801,15 @@ impl OverlayBackend for WvrWindowBackend { }; wvr_server.terminate_process(p.process, signal); } + OverlayEventData::ResizeRequest(new_size) => { + let wvr_server = app.wvr_server.as_mut().unwrap(); + let Some(win) = wvr_server.wm.windows.get_mut(&self.window) else { + log::warn!("Could not process resize request: window not found"); + return Ok(()); + }; + let size: Size = Size::new(new_size[0] as i32, new_size[1] as i32); + win.checked_configure_size(size); + } _ => {} } diff --git a/wayvr/src/windowing/backend.rs b/wayvr/src/windowing/backend.rs index a953f61c..f9bf7651 100644 --- a/wayvr/src/windowing/backend.rs +++ b/wayvr/src/windowing/backend.rs @@ -108,6 +108,7 @@ pub struct OverlayMeta { } #[allow(clippy::enum_variant_names)] +#[derive(Clone)] pub enum OverlayEventData { /// Notifies a newly added overlay of its ID, even before the overlay is shown. IdAssigned(OverlayID), @@ -128,6 +129,7 @@ pub enum OverlayEventData { command: ModifyPanelCommand, }, WvrCommand(WvrCommand), + ResizeRequest([u32; 2]), } pub trait OverlayBackend: Any { diff --git a/wayvr/src/windowing/manager.rs b/wayvr/src/windowing/manager.rs index 468f4772..a36cc364 100644 --- a/wayvr/src/windowing/manager.rs +++ b/wayvr/src/windowing/manager.rs @@ -193,6 +193,18 @@ where } } } + OverlayTask::ResizeOverlay(sel, size) => { + 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]; + + o.config + .backend + .notify(app, OverlayEventData::ResizeRequest(size))?; + } OverlayTask::ToggleOverlay(sel, mode) => { let Some(id) = self.id_by_selector(&sel) else { log::warn!("Overlay not found for task: {sel:?}"); @@ -230,8 +242,6 @@ where o.config.activate(app); } self.visible_overlays_changed(app)?; - - return Ok(()); } OverlayTask::ToggleEditMode => { self.set_edit_mode(!self.edit_mode, app)?; diff --git a/wayvr/src/windowing/window.rs b/wayvr/src/windowing/window.rs index 8799b6aa..01db2fcb 100644 --- a/wayvr/src/windowing/window.rs +++ b/wayvr/src/windowing/window.rs @@ -87,6 +87,8 @@ pub struct OverlayWindowConfig { pub dirty: bool, /// True if the window is showing the edit overlay pub editing: bool, + /// True if the window is being resized. Captures pointer hits even if not directly hit. + pub resizing: bool, /// Used by grab to pause following of HMD or other devices pub pause_movement: bool, } @@ -108,6 +110,7 @@ impl OverlayWindowConfig { global: false, dirty: true, editing: false, + resizing: false, pause_movement: false, } } diff --git a/wgui/src/event.rs b/wgui/src/event.rs index 7557c8a2..eb0547db 100644 --- a/wgui/src/event.rs +++ b/wgui/src/event.rs @@ -23,11 +23,13 @@ use crate::{ pub struct DeviceBitmask(pub u8); impl DeviceBitmask { - pub fn from_usize(mask: usize) -> Self { - // more than 8 input devices? - debug_assert!(mask & !0xff == 0); - Self(mask as u8) - } + pub fn from_index(index: usize) -> Self { + debug_assert!(index < 8); + Self(1u8 << index) + } + pub fn to_index(self) -> Option { + (self.0 != 0).then(|| self.0.trailing_zeros() as usize) + } } #[derive(Debug, Clone, Copy)]