aboutsummaryrefslogtreecommitdiff
path: root/src/grabs
diff options
context:
space:
mode:
Diffstat (limited to 'src/grabs')
-rw-r--r--src/grabs/mod.rs5
-rw-r--r--src/grabs/move_grab.rs75
-rw-r--r--src/grabs/resize_grab.rs278
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(())
+}