use std::cell::RefCell; use smithay::desktop::Window; use smithay::input::pointer::{ AxisFrame, ButtonEvent, GestureHoldBeginEvent, GestureHoldEndEvent, GesturePinchBeginEvent, GesturePinchEndEvent, GesturePinchUpdateEvent, GestureSwipeBeginEvent, GestureSwipeEndEvent, GestureSwipeUpdateEvent, GrabStartData, MotionEvent, PointerGrab, PointerInnerHandle, RelativeMotionEvent, }; use smithay::reexports::wayland_protocols::xdg::shell::server::xdg_toplevel; use smithay::reexports::wayland_server::protocol::wl_surface::WlSurface; use smithay::utils::{Logical, Point, Rectangle, Size}; use smithay::wayland::compositor::with_states; use smithay::wayland::shell::xdg::SurfaceCachedState; use crate::Ferese; #[derive(Clone, Copy, Debug, Eq, PartialEq)] pub struct ResizeEdge(xdg_toplevel::ResizeEdge); impl From for ResizeEdge { fn from(edge: xdg_toplevel::ResizeEdge) -> Self { Self(edge) } } impl ResizeEdge { pub fn at(point: Point, rect: Rectangle) -> Self { use xdg_toplevel::ResizeEdge::*; let x = (point.x - rect.loc.x as f64) / rect.size.w.max(1) as f64; let y = (point.y - rect.loc.y as f64) / rect.size.h.max(1) as f64; let (mut left, mut right) = (x < 1. / 3., x > 2. / 3.); let (mut top, mut bottom) = (y < 1. / 3., y > 2. / 3.); if !(left || right || top || bottom) { left = x < 0.5; right = !left; top = y < 0.5; bottom = !top; } Self(match (left, right, top, bottom) { (true, _, true, _) => TopLeft, (_, true, true, _) => TopRight, (true, _, _, true) => BottomLeft, (_, true, _, true) => BottomRight, (true, _, _, _) => Left, (_, true, _, _) => Right, (_, _, true, _) => Top, _ => Bottom, }) } fn left(self) -> bool { matches!( self.0, xdg_toplevel::ResizeEdge::Left | xdg_toplevel::ResizeEdge::TopLeft | xdg_toplevel::ResizeEdge::BottomLeft ) } fn right(self) -> bool { matches!( self.0, xdg_toplevel::ResizeEdge::Right | xdg_toplevel::ResizeEdge::TopRight | xdg_toplevel::ResizeEdge::BottomRight ) } fn top(self) -> bool { matches!( self.0, xdg_toplevel::ResizeEdge::Top | xdg_toplevel::ResizeEdge::TopLeft | xdg_toplevel::ResizeEdge::TopRight ) } fn bottom(self) -> bool { matches!( self.0, xdg_toplevel::ResizeEdge::Bottom | xdg_toplevel::ResizeEdge::BottomLeft | xdg_toplevel::ResizeEdge::BottomRight ) } } pub struct ResizeSurfaceGrab { start_data: GrabStartData, window: Window, edges: ResizeEdge, initial_rect: Rectangle, last_size: Size, finished: bool, snap_x: crate::floating::AxisSnap, snap_y: crate::floating::AxisSnap, } impl ResizeSurfaceGrab { pub fn new( start_data: GrabStartData, window: Window, edges: ResizeEdge, initial_rect: Rectangle, ) -> Self { ResizeState::with( window.toplevel().expect("managed window has a toplevel").wl_surface(), |state| *state = ResizeState::Resizing { edges, initial_rect }, ); Self { start_data, window, edges, initial_rect, last_size: initial_rect.size, finished: false, snap_x: Default::default(), snap_y: Default::default(), } } } impl PointerGrab for ResizeSurfaceGrab { fn motion( &mut self, data: &mut Ferese, handle: &mut PointerInnerHandle<'_, Ferese>, _focus: Option<(WlSurface, Point)>, event: &MotionEvent, ) { handle.motion(data, None, event); let mut delta = event.location - self.start_data.location; let initial = self.initial_rect; let raw_left = initial.loc.x as f64 + if self.edges.left() { delta.x } else { 0. }; let raw_top = initial.loc.y as f64 + if self.edges.top() { delta.y } else { 0. }; let raw_right = (initial.loc.x + initial.size.w) as f64 + if self.edges.right() { delta.x } else { 0. }; let raw_bottom = (initial.loc.y + initial.size.h) as f64 + if self.edges.bottom() { delta.y } else { 0. }; let raw = ferese_layout::Rect::new( raw_left, raw_top, (raw_right - raw_left).max(1.), (raw_bottom - raw_top).max(1.), ); let (xs, ys) = data.floating_snap_lines(&self.window, raw); if data.floating_snap_bypassed() { self.snap_x.clear(); self.snap_y.clear(); } else { if self.edges.left() || self.edges.right() { let moving = if self.edges.left() { raw_left } else { raw_right }; delta.x += self.snap_x.apply(moving, 0., &xs) - moving; } if self.edges.top() || self.edges.bottom() { let moving = if self.edges.top() { raw_top } else { raw_bottom }; delta.y += self.snap_y.apply(moving, 0., &ys) - moving; } } let surface = self.window.toplevel().expect("managed window has a toplevel"); let (minimum, maximum) = with_states(surface.wl_surface(), |states| { let mut cached = states.cached_state.get::(); let state = cached.current(); (state.min_size, state.max_size) }); self.last_size = constrained_size(self.initial_rect.size, delta, self.edges, minimum, maximum); let rect = resized_rect(self.initial_rect, self.last_size, self.edges); data.set_floating_window_geometry(&self.window, rect.loc, rect.size); if let Some(id) = data.windows.ids().get(&self.window).copied() { data.windows.update(id, |w| { w.resize_anchor = Some(( self.edges.left(), self.edges.top(), ferese_layout::Rect::new( initial.loc.x as f64, initial.loc.y as f64, initial.size.w as f64, initial.size.h as f64, ), )) }); } surface.with_pending_state(|state| { state.states.set(xdg_toplevel::State::Resizing); state.size = Some(self.last_size); }); surface.send_pending_configure(); } fn relative_motion( &mut self, data: &mut Ferese, handle: &mut PointerInnerHandle<'_, Ferese>, focus: Option<(WlSurface, Point)>, event: &RelativeMotionEvent, ) { handle.relative_motion(data, focus, event); } fn button(&mut self, data: &mut Ferese, handle: &mut PointerInnerHandle<'_, Ferese>, event: &ButtonEvent) { handle.button(data, event); if handle.current_pressed().is_empty() { self.finished = true; data.remember_floating(&self.window); handle.unset_grab(self, data, event.serial, event.time, true); let surface = self.window.toplevel().expect("managed window has a toplevel"); surface.with_pending_state(|state| { state.states.unset(xdg_toplevel::State::Resizing); state.size = Some(self.last_size); }); surface.send_pending_configure(); ResizeState::with(surface.wl_surface(), |state| { *state = ResizeState::WaitingForFinalCommit { edges: self.edges, initial_rect: self.initial_rect, }; }); } } fn axis(&mut self, data: &mut Ferese, handle: &mut PointerInnerHandle<'_, Ferese>, frame: AxisFrame) { handle.axis(data, frame); } fn frame(&mut self, data: &mut Ferese, handle: &mut PointerInnerHandle<'_, Ferese>) { handle.frame(data); } fn gesture_swipe_begin( &mut self, data: &mut Ferese, handle: &mut PointerInnerHandle<'_, Ferese>, event: &GestureSwipeBeginEvent, ) { handle.gesture_swipe_begin(data, event); } fn gesture_swipe_update( &mut self, data: &mut Ferese, handle: &mut PointerInnerHandle<'_, Ferese>, event: &GestureSwipeUpdateEvent, ) { handle.gesture_swipe_update(data, event); } fn gesture_swipe_end( &mut self, data: &mut Ferese, handle: &mut PointerInnerHandle<'_, Ferese>, event: &GestureSwipeEndEvent, ) { handle.gesture_swipe_end(data, event); } fn gesture_pinch_begin( &mut self, data: &mut Ferese, handle: &mut PointerInnerHandle<'_, Ferese>, event: &GesturePinchBeginEvent, ) { handle.gesture_pinch_begin(data, event); } fn gesture_pinch_update( &mut self, data: &mut Ferese, handle: &mut PointerInnerHandle<'_, Ferese>, event: &GesturePinchUpdateEvent, ) { handle.gesture_pinch_update(data, event); } fn gesture_pinch_end( &mut self, data: &mut Ferese, handle: &mut PointerInnerHandle<'_, Ferese>, event: &GesturePinchEndEvent, ) { handle.gesture_pinch_end(data, event); } fn gesture_hold_begin( &mut self, data: &mut Ferese, handle: &mut PointerInnerHandle<'_, Ferese>, event: &GestureHoldBeginEvent, ) { handle.gesture_hold_begin(data, event); } fn gesture_hold_end( &mut self, data: &mut Ferese, handle: &mut PointerInnerHandle<'_, Ferese>, event: &GestureHoldEndEvent, ) { handle.gesture_hold_end(data, event); } fn start_data(&self) -> &GrabStartData { &self.start_data } fn unset(&mut self, data: &mut Ferese) { if self.finished { return; } let surface = self.window.toplevel().expect("managed window has a toplevel"); surface.with_pending_state(|state| { state.states.unset(xdg_toplevel::State::Resizing); state.size = Some(self.initial_rect.size); }); surface.send_pending_configure(); ResizeState::with(surface.wl_surface(), |state| *state = ResizeState::Idle); data.set_floating_window_geometry(&self.window, self.initial_rect.loc, self.initial_rect.size); } } #[derive(Clone, Copy, Debug, Default)] enum ResizeState { #[default] Idle, Resizing { edges: ResizeEdge, initial_rect: Rectangle, }, WaitingForFinalCommit { edges: ResizeEdge, initial_rect: Rectangle, }, } impl ResizeState { fn with(surface: &WlSurface, callback: impl FnOnce(&mut Self) -> T) -> T { with_states(surface, |states| { states.data_map.insert_if_missing(RefCell::::default); callback( &mut states .data_map .get::>() .expect("resize state was inserted") .borrow_mut(), ) }) } fn commit(&mut self) -> Option<(ResizeEdge, Rectangle)> { match *self { Self::Idle => None, Self::Resizing { edges, initial_rect } => Some((edges, initial_rect)), Self::WaitingForFinalCommit { edges, initial_rect } => { *self = Self::Idle; Some((edges, initial_rect)) } } } } pub fn handle_resize_commit(surface: &WlSurface) { // The compositor's visual rect owns the anchored edge. A delayed client // commit must not move the window or change its stacking order. ResizeState::with(surface, ResizeState::commit); } fn constrained_size( initial: Size, delta: Point, edges: ResizeEdge, minimum: Size, maximum: Size, ) -> Size { let mut width = initial.w; let mut height = initial.h; if edges.left() { width -= delta.x as i32; } else if edges.right() { width += delta.x as i32; } if edges.top() { height -= delta.y as i32; } else if edges.bottom() { height += delta.y as i32; } let maximum_width = if maximum.w == 0 { i32::MAX } else { maximum.w }; let maximum_height = if maximum.h == 0 { i32::MAX } else { maximum.h }; ( width.clamp(minimum.w.max(1), maximum_width), height.clamp(minimum.h.max(1), maximum_height), ) .into() } fn resized_rect( initial: Rectangle, size: Size, edges: ResizeEdge, ) -> Rectangle { let mut location = initial.loc; if edges.left() { location.x += initial.size.w - size.w; } if edges.top() { location.y += initial.size.h - size.h; } Rectangle::new(location, size) } #[cfg(test)] mod tests { use super::*; #[test] fn resize_grid_selects_edges_corners_and_center_fallback_once() { use xdg_toplevel::ResizeEdge::*; let rect = Rectangle::new((100, 50).into(), (300, 300).into()); for (x, y, edge) in [ (0.1, 0.1, TopLeft), (0.5, 0.1, Top), (0.9, 0.1, TopRight), (0.1, 0.5, Left), (0.5, 0.5, BottomRight), (0.9, 0.5, Right), (0.1, 0.9, BottomLeft), (0.5, 0.9, Bottom), (0.9, 0.9, BottomRight), (0.4, 0.4, TopLeft), (0.6, 0.4, TopRight), (0.4, 0.6, BottomLeft), ] { assert_eq!( ResizeEdge::at((100. + 300. * x, 50. + 300. * y).into(), rect), ResizeEdge(edge) ); } } #[test] fn left_and_top_edges_invert_pointer_delta() { let size = constrained_size( (800, 600).into(), (100.0, 50.0).into(), ResizeEdge(xdg_toplevel::ResizeEdge::TopLeft), (1, 1).into(), (0, 0).into(), ); assert_eq!(size, (700, 550).into()); } #[test] fn right_and_bottom_edges_follow_pointer_delta() { let size = constrained_size( (800, 600).into(), (100.0, 50.0).into(), ResizeEdge(xdg_toplevel::ResizeEdge::BottomRight), (1, 1).into(), (0, 0).into(), ); assert_eq!(size, (900, 650).into()); } #[test] fn client_constraints_clamp_interactive_size() { let size = constrained_size( (800, 600).into(), (1_000.0, 1_000.0).into(), ResizeEdge(xdg_toplevel::ResizeEdge::BottomRight), (640, 480).into(), (1_024, 768).into(), ); assert_eq!(size, (1_024, 768).into()); } #[test] fn resized_rect_keeps_the_opposite_edge_fixed() { let initial = Rectangle::new((100, 80).into(), (800, 600).into()); let resized = resized_rect( initial, (700, 550).into(), ResizeEdge(xdg_toplevel::ResizeEdge::TopLeft), ); assert_eq!(resized.loc, (200, 130).into()); assert_eq!(resized.size, (700, 550).into()); } }