wip: grabbable resize corner

This commit is contained in:
galister 2026-07-05 20:02:36 +09:00
parent 137570f548
commit 4f76a4abfb
7 changed files with 109 additions and 5 deletions

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24"><!-- Icon from Material Symbols by Google - https://github.com/google/material-design-icons/blob/master/LICENSE --><path fill="white" d="M3 21v-2h16V3h2v16q0 .825-.587 1.413T19 21zm12-4q-.825 0-1.412-.587T13 15t.588-1.412T15 13t1.413.588T17 15t-.587 1.413T15 17M5 12v-2h3.6L3 4.4L4.4 3L10 8.6V5h2v7z"/></svg>

After

Width:  |  Height:  |  Size: 392 B

View File

@ -126,6 +126,9 @@
</div> </div>
</rectangle> </rectangle>
</div> </div>
<Button id="resize_corner" macro="button_style" tooltip="EDIT_MODE.RESIZE" _press="::EditModeResizeStart" _release="::EditModeResizeStop" border_color="~color_accent_translucent" color="~color_accent_50" color2="~color_accent_10">
<sprite width="48" height="48" src="edit/resize-corner.svg" />
</Button>
</rectangle> </rectangle>
</div> </div>
</elements> </elements>

View File

@ -392,6 +392,8 @@ impl WvrServerState {
let window_handle = wvr_server.wm.create_window( let window_handle = wvr_server.wm.create_window(
toplevel.clone(), toplevel.clone(),
process_handle, process_handle,
min_size,
max_size,
fallback_size.w as _, fallback_size.w as _,
fallback_size.h as _, fallback_size.h as _,
); );

View File

@ -8,6 +8,8 @@ use crate::{backend::wayvr::process, gen_id};
#[derive(Debug)] #[derive(Debug)]
pub struct Window { pub struct Window {
pub min_size: Size<i32, Logical>,
pub max_size: Size<i32, Logical>,
pub size_x: u32, pub size_x: u32,
pub size_y: u32, pub size_y: u32,
pub visible: bool, pub visible: bool,
@ -16,8 +18,15 @@ pub struct Window {
} }
impl Window { impl Window {
const fn new(toplevel: Rc<ToplevelSurface>, process: process::ProcessHandle) -> Self { const fn new(
toplevel: Rc<ToplevelSurface>,
process: process::ProcessHandle,
min_size: Size<i32, Logical>,
max_size: Size<i32, Logical>,
) -> Self {
Self { Self {
min_size,
max_size,
size_x: 0, size_x: 0,
size_y: 0, size_y: 0,
visible: true, visible: true,
@ -26,6 +35,10 @@ impl Window {
} }
} }
pub fn resizable(&self) -> bool {
self.min_size != self.max_size
}
pub fn configure_size(&mut self, size: Option<Size<i32, Logical>>, bounds: Size<i32, Logical>) { pub fn configure_size(&mut self, size: Option<Size<i32, Logical>>, bounds: Size<i32, Logical>) {
self.toplevel.with_pending_state(|state| { self.toplevel.with_pending_state(|state| {
state.bounds = Some(bounds); state.bounds = Some(bounds);
@ -93,10 +106,12 @@ impl WindowManager {
&mut self, &mut self,
toplevel: Rc<ToplevelSurface>, toplevel: Rc<ToplevelSurface>,
process: process::ProcessHandle, process: process::ProcessHandle,
min_size: Size<i32, Logical>,
max_size: Size<i32, Logical>,
size_x: u32, size_x: u32,
size_y: u32, size_y: u32,
) -> WindowHandle { ) -> WindowHandle {
let mut window = Window::new(toplevel, process); let mut window = Window::new(toplevel, process, min_size, max_size);
window.remember_committed_size(Size::new(size_x as i32, size_y as i32)); window.remember_committed_size(Size::new(size_x as i32, size_y as i32));
self.windows.add(window) self.windows.add(window)
} }

View File

@ -6,13 +6,14 @@ use std::{
time::{Duration, Instant}, time::{Duration, Instant},
}; };
use glam::vec2; use glam::{Vec2, vec2};
use slotmap::Key; use slotmap::Key;
use wgui::{ use wgui::{
components::{button::ComponentButton, checkbox::ComponentCheckbox, slider::ComponentSlider}, components::{button::ComponentButton, checkbox::ComponentCheckbox, slider::ComponentSlider},
event::EventCallback, event::{CallbackDataCommon, EventCallback, StyleSetRequest},
i18n::Translation, i18n::Translation,
parser::Fetchable, parser::Fetchable,
taffy::Display,
widget::EventResult, widget::EventResult,
}; };
use wlx_common::overlays::{BackendAttrib, BackendAttribValue, MouseTransform, StereoMode}; use wlx_common::overlays::{BackendAttrib, BackendAttribValue, MouseTransform, StereoMode};
@ -59,6 +60,7 @@ struct EditModeState {
pos: SpriteTabHandler<PosTabState>, pos: SpriteTabHandler<PosTabState>,
stereo: SpriteTabHandler<StereoMode>, stereo: SpriteTabHandler<StereoMode>,
mouse: SpriteTabHandler<MouseTransform>, mouse: SpriteTabHandler<MouseTransform>,
resize_uv: Option<Vec2>,
} }
type EditModeWrapPanel = GuiPanel<EditModeState>; type EditModeWrapPanel = GuiPanel<EditModeState>;
@ -94,6 +96,7 @@ impl EditWrapperManager {
owc.backend = Box::new(EditModeBackendWrapper { owc.backend = Box::new(EditModeBackendWrapper {
inner: ManuallyDrop::new(inner), inner: ManuallyDrop::new(inner),
panel: ManuallyDrop::new(panel), panel: ManuallyDrop::new(panel),
last_extent: [0, 0],
last_render: Instant::now(), last_render: Instant::now(),
can_render_inner: false, can_render_inner: false,
}); });
@ -135,6 +138,7 @@ pub struct EditModeBackendWrapper {
panel: ManuallyDrop<EditModeWrapPanel>, panel: ManuallyDrop<EditModeWrapPanel>,
inner: ManuallyDrop<Box<dyn OverlayBackend>>, inner: ManuallyDrop<Box<dyn OverlayBackend>>,
last_extent: [u32; 2],
last_render: Instant, last_render: Instant,
can_render_inner: bool, can_render_inner: bool,
} }
@ -213,6 +217,8 @@ impl OverlayBackend for EditModeBackendWrapper {
rdr.cmd_bufs.reverse(); rdr.cmd_bufs.reverse();
} }
self.last_extent = rdr.extent;
Ok(()) Ok(())
} }
fn frame_meta(&mut self) -> Option<crate::windowing::backend::FrameMeta> { fn frame_meta(&mut self) -> Option<crate::windowing::backend::FrameMeta> {
@ -225,7 +231,23 @@ impl OverlayBackend for EditModeBackendWrapper {
) -> HoverResult { ) -> HoverResult {
// pass through hover events to force pipewire to capture frames for us // pass through hover events to force pipewire to capture frames for us
let _ = self.inner.on_hover(app, hit); let _ = self.inner.on_hover(app, hit);
self.panel.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);
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:?}");
}
}
res
} }
fn on_left(&mut self, app: &mut crate::state::AppState, pointer: usize) { fn on_left(&mut self, app: &mut crate::state::AppState, pointer: usize) {
self.inner.on_left(app, pointer); self.inner.on_left(app, pointer);
@ -271,6 +293,7 @@ fn make_edit_panel(app: &mut AppState) -> anyhow::Result<EditModeWrapPanel> {
pos: SpriteTabHandler::default(), pos: SpriteTabHandler::default(),
stereo: SpriteTabHandler::default(), stereo: SpriteTabHandler::default(),
mouse: SpriteTabHandler::default(), mouse: SpriteTabHandler::default(),
resize_uv: None,
}; };
let anim_mult = app.wgui_theme.animation_mult; let anim_mult = app.wgui_theme.animation_mult;
@ -407,6 +430,20 @@ fn make_edit_panel(app: &mut AppState) -> anyhow::Result<EditModeWrapPanel> {
))); )));
Ok(EventResult::Consumed) Ok(EventResult::Consumed)
}), }),
"::EditModeResizeStart" => Box::new(move |_common, data, app, state| {
if !test_button(data) || !test_duration(&button, app) {
return Ok(EventResult::Pass);
}
state.resize_uv = data.metadata.get_mouse_pos_absolute();
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;
Ok(EventResult::Consumed)
}),
_ => return, _ => return,
}; };
@ -564,6 +601,27 @@ fn reset_panel(
c.set_checked(&mut com, adjust_mouse); c.set_checked(&mut com, adjust_mouse);
} }
let resizable = attrib_value!(
owc.backend.get_attrib(BackendAttrib::Resizable),
BackendAttribValue::Resizable
)
.unwrap_or(false);
let c = panel
.parser_state
.fetch_component_as::<ComponentButton>("resize_corner")?;
let common = CallbackDataCommon {
state: &panel.layout.state,
alterables: &mut panel.layout.alterables,
};
common.alterables.set_style(
c.get_rect(),
StyleSetRequest::Display(if resizable {
Display::Block
} else {
Display::None
}),
);
Ok(()) Ok(())
} }

View File

@ -86,6 +86,13 @@ pub fn create_wl_window_overlay(
-0.95 -0.95
}; };
let resizable = app
.wvr_server
.as_mut()
.and_then(|wvr| wvr.wm.windows.get(&window))
.map(|w| w.resizable())
.unwrap_or(false);
Ok(OverlayWindowConfig { Ok(OverlayWindowConfig {
name: name.clone(), name: name.clone(),
default_state: OverlayWindowState { default_state: OverlayWindowState {
@ -113,6 +120,7 @@ pub fn create_wl_window_overlay(
window, window,
icon, icon,
(scale, curve_scale), (scale, curve_scale),
resizable,
)?)) )?))
}) })
} }
@ -175,6 +183,7 @@ pub struct WvrWindowBackend {
scrolling: bool, scrolling: bool,
overlay_id: OverlayID, overlay_id: OverlayID,
scale: (f32, f32), scale: (f32, f32),
resizable: bool,
} }
impl WvrWindowBackend { impl WvrWindowBackend {
@ -184,6 +193,7 @@ impl WvrWindowBackend {
window: wayvr::window::WindowHandle, window: wayvr::window::WindowHandle,
icon: Arc<str>, icon: Arc<str>,
scale: (f32, f32), scale: (f32, f32),
resizable: bool,
) -> anyhow::Result<Self> { ) -> anyhow::Result<Self> {
let subsurface_pipeline = app.gfx.create_pipeline( let subsurface_pipeline = app.gfx.create_pipeline(
app.gfx_extras.shaders.get("vert_quad").unwrap(), // want panic app.gfx_extras.shaders.get("vert_quad").unwrap(), // want panic
@ -285,6 +295,7 @@ impl WvrWindowBackend {
scrolling: false, scrolling: false,
overlay_id: OverlayID::null(), overlay_id: OverlayID::null(),
scale, scale,
resizable,
}) })
} }
@ -300,6 +311,13 @@ impl WvrWindowBackend {
if (scale_delta - 1.0).abs() > f32::EPSILON if (scale_delta - 1.0).abs() > f32::EPSILON
|| (curve_scale_delta - 1.0).abs() > f32::EPSILON || (curve_scale_delta - 1.0).abs() > f32::EPSILON
{ {
self.resizable = app
.wvr_server
.as_mut()
.and_then(|wvr| wvr.wm.windows.get(&self.window))
.map(|w| w.resizable())
.unwrap_or(false);
app.tasks.enqueue(TaskType::Overlay(OverlayTask::Modify( app.tasks.enqueue(TaskType::Overlay(OverlayTask::Modify(
OverlaySelector::Id(self.overlay_id), OverlaySelector::Id(self.overlay_id),
Box::new(move |_app, owc| { Box::new(move |_app, owc| {
@ -978,6 +996,8 @@ impl OverlayBackend for WvrWindowBackend {
BackendAttrib::StereoAdjustMouse => Some(BackendAttribValue::StereoAdjustMouse( BackendAttrib::StereoAdjustMouse => Some(BackendAttribValue::StereoAdjustMouse(
self.stereo_adjust_mouse, self.stereo_adjust_mouse,
)), )),
BackendAttrib::Resizable => Some(BackendAttribValue::Resizable(self.resizable)),
_ => None, _ => None,
} }
} }

View File

@ -26,6 +26,7 @@ pub enum BackendAttrib {
StereoAdjustMouse, StereoAdjustMouse,
MouseTransform, MouseTransform,
Icon, Icon,
Resizable,
} }
#[derive(Debug, Clone, Serialize, Deserialize)] #[derive(Debug, Clone, Serialize, Deserialize)]
@ -36,9 +37,12 @@ pub enum BackendAttribValue {
MouseTransform(MouseTransform), MouseTransform(MouseTransform),
#[serde(skip_serializing, skip_deserializing)] #[serde(skip_serializing, skip_deserializing)]
Icon(Arc<str>), Icon(Arc<str>),
#[serde(skip_serializing, skip_deserializing)]
Resizable(bool),
} }
impl BackendAttribValue { impl BackendAttribValue {
/// Used to determine if value should be saved
pub fn is_default(&self) -> bool { pub fn is_default(&self) -> bool {
match self { match self {
Self::Stereo(val) => *val == StereoMode::default(), Self::Stereo(val) => *val == StereoMode::default(),
@ -46,6 +50,7 @@ impl BackendAttribValue {
Self::StereoAdjustMouse(val) => !*val, Self::StereoAdjustMouse(val) => !*val,
Self::MouseTransform(val) => *val == MouseTransform::default(), Self::MouseTransform(val) => *val == MouseTransform::default(),
Self::Icon(_) => false, Self::Icon(_) => false,
Self::Resizable(_) => false,
} }
} }
} }