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>
</rectangle>
</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>
</div>
</elements>

View File

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

View File

@ -8,6 +8,8 @@ use crate::{backend::wayvr::process, gen_id};
#[derive(Debug)]
pub struct Window {
pub min_size: Size<i32, Logical>,
pub max_size: Size<i32, Logical>,
pub size_x: u32,
pub size_y: u32,
pub visible: bool,
@ -16,8 +18,15 @@ pub struct 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 {
min_size,
max_size,
size_x: 0,
size_y: 0,
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>) {
self.toplevel.with_pending_state(|state| {
state.bounds = Some(bounds);
@ -93,10 +106,12 @@ impl WindowManager {
&mut self,
toplevel: Rc<ToplevelSurface>,
process: process::ProcessHandle,
min_size: Size<i32, Logical>,
max_size: Size<i32, Logical>,
size_x: u32,
size_y: u32,
) -> 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));
self.windows.add(window)
}

View File

@ -6,13 +6,14 @@ use std::{
time::{Duration, Instant},
};
use glam::vec2;
use glam::{Vec2, vec2};
use slotmap::Key;
use wgui::{
components::{button::ComponentButton, checkbox::ComponentCheckbox, slider::ComponentSlider},
event::EventCallback,
event::{CallbackDataCommon, EventCallback, StyleSetRequest},
i18n::Translation,
parser::Fetchable,
taffy::Display,
widget::EventResult,
};
use wlx_common::overlays::{BackendAttrib, BackendAttribValue, MouseTransform, StereoMode};
@ -59,6 +60,7 @@ struct EditModeState {
pos: SpriteTabHandler<PosTabState>,
stereo: SpriteTabHandler<StereoMode>,
mouse: SpriteTabHandler<MouseTransform>,
resize_uv: Option<Vec2>,
}
type EditModeWrapPanel = GuiPanel<EditModeState>;
@ -94,6 +96,7 @@ impl EditWrapperManager {
owc.backend = Box::new(EditModeBackendWrapper {
inner: ManuallyDrop::new(inner),
panel: ManuallyDrop::new(panel),
last_extent: [0, 0],
last_render: Instant::now(),
can_render_inner: false,
});
@ -135,6 +138,7 @@ pub struct EditModeBackendWrapper {
panel: ManuallyDrop<EditModeWrapPanel>,
inner: ManuallyDrop<Box<dyn OverlayBackend>>,
last_extent: [u32; 2],
last_render: Instant,
can_render_inner: bool,
}
@ -213,6 +217,8 @@ impl OverlayBackend for EditModeBackendWrapper {
rdr.cmd_bufs.reverse();
}
self.last_extent = rdr.extent;
Ok(())
}
fn frame_meta(&mut self) -> Option<crate::windowing::backend::FrameMeta> {
@ -225,7 +231,23 @@ impl OverlayBackend for EditModeBackendWrapper {
) -> 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)
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) {
self.inner.on_left(app, pointer);
@ -271,6 +293,7 @@ fn make_edit_panel(app: &mut AppState) -> anyhow::Result<EditModeWrapPanel> {
pos: SpriteTabHandler::default(),
stereo: SpriteTabHandler::default(),
mouse: SpriteTabHandler::default(),
resize_uv: None,
};
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)
}),
"::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,
};
@ -564,6 +601,27 @@ fn reset_panel(
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(())
}

View File

@ -86,6 +86,13 @@ pub fn create_wl_window_overlay(
-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 {
name: name.clone(),
default_state: OverlayWindowState {
@ -113,6 +120,7 @@ pub fn create_wl_window_overlay(
window,
icon,
(scale, curve_scale),
resizable,
)?))
})
}
@ -175,6 +183,7 @@ pub struct WvrWindowBackend {
scrolling: bool,
overlay_id: OverlayID,
scale: (f32, f32),
resizable: bool,
}
impl WvrWindowBackend {
@ -184,6 +193,7 @@ impl WvrWindowBackend {
window: wayvr::window::WindowHandle,
icon: Arc<str>,
scale: (f32, f32),
resizable: bool,
) -> anyhow::Result<Self> {
let subsurface_pipeline = app.gfx.create_pipeline(
app.gfx_extras.shaders.get("vert_quad").unwrap(), // want panic
@ -285,6 +295,7 @@ impl WvrWindowBackend {
scrolling: false,
overlay_id: OverlayID::null(),
scale,
resizable,
})
}
@ -300,6 +311,13 @@ impl WvrWindowBackend {
if (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(
OverlaySelector::Id(self.overlay_id),
Box::new(move |_app, owc| {
@ -978,6 +996,8 @@ impl OverlayBackend for WvrWindowBackend {
BackendAttrib::StereoAdjustMouse => Some(BackendAttribValue::StereoAdjustMouse(
self.stereo_adjust_mouse,
)),
BackendAttrib::Resizable => Some(BackendAttribValue::Resizable(self.resizable)),
_ => None,
}
}

View File

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