diff options
Diffstat (limited to 'src/grabs')
| -rw-r--r-- | src/grabs/mod.rs | 5 | ||||
| -rw-r--r-- | src/grabs/move_grab.rs | 75 | ||||
| -rw-r--r-- | src/grabs/resize_grab.rs | 278 |
3 files changed, 358 insertions, 0 deletions
diff --git a/src/grabs/mod.rs b/src/grabs/mod.rs new file mode 100644 index 00000000..5756e1be --- /dev/null +++ b/src/grabs/mod.rs @@ -0,0 +1,5 @@ +pub mod move_grab; +pub use move_grab::MoveSurfaceGrab; + +pub mod resize_grab; +pub use resize_grab::ResizeSurfaceGrab; diff --git a/src/grabs/move_grab.rs b/src/grabs/move_grab.rs new file mode 100644 index 00000000..c9aacbec --- /dev/null +++ b/src/grabs/move_grab.rs @@ -0,0 +1,75 @@ +use crate::Smallvil; +use smithay::{ + desktop::Window, + input::pointer::{ + AxisFrame, ButtonEvent, GrabStartData as PointerGrabStartData, MotionEvent, PointerGrab, + PointerInnerHandle, RelativeMotionEvent, + }, + reexports::wayland_server::protocol::wl_surface::WlSurface, + utils::{Logical, Point}, +}; + +pub struct MoveSurfaceGrab { + pub start_data: PointerGrabStartData<Smallvil>, + pub window: Window, + pub initial_window_location: Point<i32, Logical>, +} + +impl PointerGrab<Smallvil> for MoveSurfaceGrab { + fn motion( + &mut self, + data: &mut Smallvil, + handle: &mut PointerInnerHandle<'_, Smallvil>, + _focus: Option<(WlSurface, Point<i32, Logical>)>, + event: &MotionEvent, + ) { + // While the grab is active, no client has pointer focus + handle.motion(data, None, event); + + let delta = event.location - self.start_data.location; + let new_location = self.initial_window_location.to_f64() + delta; + data.space + .map_element(self.window.clone(), new_location.to_i32_round(), true); + } + + fn relative_motion( + &mut self, + data: &mut Smallvil, + handle: &mut PointerInnerHandle<'_, Smallvil>, + focus: Option<(WlSurface, Point<i32, Logical>)>, + event: &RelativeMotionEvent, + ) { + handle.relative_motion(data, focus, event); + } + + fn button( + &mut self, + data: &mut Smallvil, + handle: &mut PointerInnerHandle<'_, Smallvil>, + event: &ButtonEvent, + ) { + handle.button(data, event); + + // The button is a button code as defined in the + // Linux kernel's linux/input-event-codes.h header file, e.g. BTN_LEFT. + const BTN_LEFT: u32 = 0x110; + + if !handle.current_pressed().contains(&BTN_LEFT) { + // No more buttons are pressed, release the grab. + handle.unset_grab(data, event.serial, event.time); + } + } + + fn axis( + &mut self, + data: &mut Smallvil, + handle: &mut PointerInnerHandle<'_, Smallvil>, + details: AxisFrame, + ) { + handle.axis(data, details) + } + + fn start_data(&self) -> &PointerGrabStartData<Smallvil> { + &self.start_data + } +} diff --git a/src/grabs/resize_grab.rs b/src/grabs/resize_grab.rs new file mode 100644 index 00000000..6a39cfcd --- /dev/null +++ b/src/grabs/resize_grab.rs @@ -0,0 +1,278 @@ +use crate::Smallvil; +use smithay::{ + desktop::{Space, Window}, + input::pointer::{ + AxisFrame, ButtonEvent, GrabStartData as PointerGrabStartData, MotionEvent, PointerGrab, + PointerInnerHandle, RelativeMotionEvent, + }, + reexports::{ + wayland_protocols::xdg::shell::server::xdg_toplevel, wayland_server::protocol::wl_surface::WlSurface, + }, + utils::{Logical, Point, Rectangle, Size}, + wayland::{compositor, shell::xdg::SurfaceCachedState}, +}; +use std::cell::RefCell; + +bitflags::bitflags! { + #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] + pub struct ResizeEdge: u32 { + const TOP = 0b0001; + const BOTTOM = 0b0010; + const LEFT = 0b0100; + const RIGHT = 0b1000; + + const TOP_LEFT = Self::TOP.bits() | Self::LEFT.bits(); + const BOTTOM_LEFT = Self::BOTTOM.bits() | Self::LEFT.bits(); + + const TOP_RIGHT = Self::TOP.bits() | Self::RIGHT.bits(); + const BOTTOM_RIGHT = Self::BOTTOM.bits() | Self::RIGHT.bits(); + } +} + +impl From<xdg_toplevel::ResizeEdge> for ResizeEdge { + #[inline] + fn from(x: xdg_toplevel::ResizeEdge) -> Self { + Self::from_bits(x as u32).unwrap() + } +} + +pub struct ResizeSurfaceGrab { + start_data: PointerGrabStartData<Smallvil>, + window: Window, + + edges: ResizeEdge, + + initial_rect: Rectangle<i32, Logical>, + last_window_size: Size<i32, Logical>, +} + +impl ResizeSurfaceGrab { + pub fn start( + start_data: PointerGrabStartData<Smallvil>, + window: Window, + edges: ResizeEdge, + initial_window_rect: Rectangle<i32, Logical>, + ) -> Self { + let initial_rect = initial_window_rect; + + ResizeSurfaceState::with(window.toplevel().wl_surface(), |state| { + *state = ResizeSurfaceState::Resizing { edges, initial_rect }; + }); + + Self { + start_data, + window, + edges, + initial_rect, + last_window_size: initial_rect.size, + } + } +} + +impl PointerGrab<Smallvil> for ResizeSurfaceGrab { + fn motion( + &mut self, + data: &mut Smallvil, + handle: &mut PointerInnerHandle<'_, Smallvil>, + _focus: Option<(WlSurface, Point<i32, Logical>)>, + event: &MotionEvent, + ) { + // While the grab is active, no client has pointer focus + handle.motion(data, None, event); + + let mut delta = event.location - self.start_data.location; + + let mut new_window_width = self.initial_rect.size.w; + let mut new_window_height = self.initial_rect.size.h; + + if self.edges.intersects(ResizeEdge::LEFT | ResizeEdge::RIGHT) { + if self.edges.intersects(ResizeEdge::LEFT) { + delta.x = -delta.x; + } + + new_window_width = (self.initial_rect.size.w as f64 + delta.x) as i32; + } + + if self.edges.intersects(ResizeEdge::TOP | ResizeEdge::BOTTOM) { + if self.edges.intersects(ResizeEdge::TOP) { + delta.y = -delta.y; + } + + new_window_height = (self.initial_rect.size.h as f64 + delta.y) as i32; + } + + let (min_size, max_size) = compositor::with_states(self.window.toplevel().wl_surface(), |states| { + let data = states.cached_state.current::<SurfaceCachedState>(); + (data.min_size, data.max_size) + }); + + let min_width = min_size.w.max(1); + let min_height = min_size.h.max(1); + + let max_width = (max_size.w == 0).then(i32::max_value).unwrap_or(max_size.w); + let max_height = (max_size.h == 0).then(i32::max_value).unwrap_or(max_size.h); + + self.last_window_size = Size::from(( + new_window_width.max(min_width).min(max_width), + new_window_height.max(min_height).min(max_height), + )); + + let xdg = self.window.toplevel(); + xdg.with_pending_state(|state| { + state.states.set(xdg_toplevel::State::Resizing); + state.size = Some(self.last_window_size); + }); + + xdg.send_pending_configure(); + } + + fn relative_motion( + &mut self, + data: &mut Smallvil, + handle: &mut PointerInnerHandle<'_, Smallvil>, + focus: Option<(WlSurface, Point<i32, Logical>)>, + event: &RelativeMotionEvent, + ) { + handle.relative_motion(data, focus, event); + } + + fn button( + &mut self, + data: &mut Smallvil, + handle: &mut PointerInnerHandle<'_, Smallvil>, + event: &ButtonEvent, + ) { + handle.button(data, event); + + // The button is a button code as defined in the + // Linux kernel's linux/input-event-codes.h header file, e.g. BTN_LEFT. + const BTN_LEFT: u32 = 0x110; + + if !handle.current_pressed().contains(&BTN_LEFT) { + // No more buttons are pressed, release the grab. + handle.unset_grab(data, event.serial, event.time); + + let xdg = self.window.toplevel(); + xdg.with_pending_state(|state| { + state.states.unset(xdg_toplevel::State::Resizing); + state.size = Some(self.last_window_size); + }); + + xdg.send_pending_configure(); + + ResizeSurfaceState::with(xdg.wl_surface(), |state| { + *state = ResizeSurfaceState::WaitingForLastCommit { + edges: self.edges, + initial_rect: self.initial_rect, + }; + }); + } + } + + fn axis( + &mut self, + data: &mut Smallvil, + handle: &mut PointerInnerHandle<'_, Smallvil>, + details: AxisFrame, + ) { + handle.axis(data, details) + } + + fn start_data(&self) -> &PointerGrabStartData<Smallvil> { + &self.start_data + } +} + +/// State of the resize operation. +/// +/// It is stored inside of WlSurface, +/// and can be accessed using [`ResizeSurfaceState::with`] +#[derive(Debug, Clone, Copy, Eq, PartialEq, Default)] +enum ResizeSurfaceState { + #[default] + Idle, + Resizing { + edges: ResizeEdge, + /// The initial window size and location. + initial_rect: Rectangle<i32, Logical>, + }, + /// Resize is done, we are now waiting for last commit, to do the final move + WaitingForLastCommit { + edges: ResizeEdge, + /// The initial window size and location. + initial_rect: Rectangle<i32, Logical>, + }, +} + +impl ResizeSurfaceState { + fn with<F, T>(surface: &WlSurface, cb: F) -> T + where + F: FnOnce(&mut Self) -> T, + { + compositor::with_states(surface, |states| { + states.data_map.insert_if_missing(RefCell::<Self>::default); + let state = states.data_map.get::<RefCell<Self>>().unwrap(); + + cb(&mut state.borrow_mut()) + }) + } + + fn commit(&mut self) -> Option<(ResizeEdge, Rectangle<i32, Logical>)> { + match *self { + Self::Resizing { edges, initial_rect } => Some((edges, initial_rect)), + Self::WaitingForLastCommit { edges, initial_rect } => { + // The resize is done, let's go back to idle + *self = Self::Idle; + + Some((edges, initial_rect)) + } + Self::Idle => None, + } + } +} + +/// Should be called on `WlSurface::commit` +pub fn handle_commit(space: &mut Space<Window>, surface: &WlSurface) -> Option<()> { + let window = space + .elements() + .find(|w| w.toplevel().wl_surface() == surface) + .cloned()?; + + let mut window_loc = space.element_location(&window)?; + let geometry = window.geometry(); + + let new_loc: Point<Option<i32>, Logical> = ResizeSurfaceState::with(surface, |state| { + state + .commit() + .and_then(|(edges, initial_rect)| { + // If the window is being resized by top or left, its location must be adjusted + // accordingly. + edges.intersects(ResizeEdge::TOP_LEFT).then(|| { + let new_x = edges + .intersects(ResizeEdge::LEFT) + .then_some(initial_rect.loc.x + (initial_rect.size.w - geometry.size.w)); + + let new_y = edges + .intersects(ResizeEdge::TOP) + .then_some(initial_rect.loc.y + (initial_rect.size.h - geometry.size.h)); + + (new_x, new_y).into() + }) + }) + .unwrap_or_default() + }); + + if let Some(new_x) = new_loc.x { + window_loc.x = new_x; + } + if let Some(new_y) = new_loc.y { + window_loc.y = new_y; + } + + if new_loc.x.is_some() || new_loc.y.is_some() { + // If TOP or LEFT side of the window got resized, we have to move it + space.map_element(window, window_loc, false); + } + + Some(()) +} |
