// SPDX-License-Identifier: GPL-3.0-only use crate::{ shell::{element::CosmicMapped, focus::target::PointerFocusTarget}, utils::prelude::*, }; use smithay::{ desktop::space::SpaceElement, input::pointer::{ AxisFrame, ButtonEvent, GrabStartData as PointerGrabStartData, MotionEvent, PointerGrab, PointerInnerHandle, }, reexports::wayland_protocols::xdg::shell::server::xdg_toplevel, utils::{IsAlive, Logical, Point, Size}, }; use std::convert::TryFrom; bitflags::bitflags! { 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 for ResizeEdge { #[inline] fn from(x: xdg_toplevel::ResizeEdge) -> Self { Self::from_bits(x.into()).unwrap() } } impl From for xdg_toplevel::ResizeEdge { #[inline] fn from(x: ResizeEdge) -> Self { Self::try_from(x.bits()).unwrap() } } /// Information about the resize operation. #[derive(Debug, Clone, Copy, Eq, PartialEq)] pub struct ResizeData { /// The edges the surface is being resized with. edges: ResizeEdge, /// The initial window location. initial_window_location: Point, /// The initial window size (geometry width and height). initial_window_size: Size, } /// State of the resize operation. #[derive(Debug, Clone, Copy, Eq, PartialEq)] pub enum ResizeState { /// The surface is currently being resized. Resizing(ResizeData), /// The resize has finished, and the surface needs to commit its final state. WaitingForCommit(ResizeData), } pub struct ResizeSurfaceGrab { start_data: PointerGrabStartData, window: CosmicMapped, edges: ResizeEdge, initial_window_size: Size, last_window_size: Size, } impl PointerGrab for ResizeSurfaceGrab { fn motion( &mut self, data: &mut State, handle: &mut PointerInnerHandle<'_, State>, _focus: Option<(PointerFocusTarget, Point)>, event: &MotionEvent, ) { // While the grab is active, no client has pointer focus handle.motion(data, None, event); // It is impossible to get `min_size` and `max_size` of dead toplevel, so we return early. if !self.window.alive() { handle.unset_grab(data, event.serial, event.time); return; } let (mut dx, mut dy) = (event.location - self.start_data.location).into(); let mut new_window_width = self.initial_window_size.w; let mut new_window_height = self.initial_window_size.h; let left_right = ResizeEdge::LEFT | ResizeEdge::RIGHT; let top_bottom = ResizeEdge::TOP | ResizeEdge::BOTTOM; if self.edges.intersects(left_right) { if self.edges.intersects(ResizeEdge::LEFT) { dx = -dx; } new_window_width = (self.initial_window_size.w as f64 + dx) as i32; } if self.edges.intersects(top_bottom) { if self.edges.intersects(ResizeEdge::TOP) { dy = -dy; } new_window_height = (self.initial_window_size.h as f64 + dy) as i32; } let (min_size, max_size) = (self.window.min_size(), self.window.max_size()); let min_width = min_size.w.max(1); let min_height = min_size.h.max(1); let max_width = if max_size.w == 0 { i32::max_value() } else { max_size.w }; let max_height = if max_size.h == 0 { i32::max_value() } else { max_size.h }; new_window_width = new_window_width.max(min_width).min(max_width); new_window_height = new_window_height.max(min_height).min(max_height); self.last_window_size = (new_window_width, new_window_height).into(); self.window.set_resizing(true); self.window.set_size(self.last_window_size); self.window.configure(); } fn button( &mut self, data: &mut State, handle: &mut PointerInnerHandle<'_, State>, event: &ButtonEvent, ) { handle.button(data, event); if handle.current_pressed().is_empty() { // No more buttons are pressed, release the grab. handle.unset_grab(data, event.serial, event.time); // If toplevel is dead, we can't resize it, so we return early. if !self.window.alive() { return; } self.window.set_resizing(false); self.window.set_size(self.last_window_size); self.window.configure(); let mut resize_state = self.window.resize_state.lock().unwrap(); if let Some(ResizeState::Resizing(resize_data)) = *resize_state { *resize_state = Some(ResizeState::WaitingForCommit(resize_data)); } else { panic!("invalid resize state: {:?}", resize_state); } } } fn axis( &mut self, data: &mut State, handle: &mut PointerInnerHandle<'_, State>, details: AxisFrame, ) { handle.axis(data, details) } fn start_data(&self) -> &PointerGrabStartData { &self.start_data } } impl ResizeSurfaceGrab { pub fn new( start_data: PointerGrabStartData, mapped: CosmicMapped, edges: xdg_toplevel::ResizeEdge, initial_window_location: Point, initial_window_size: Size, ) -> ResizeSurfaceGrab { let resize_state = ResizeState::Resizing(ResizeData { edges: edges.into(), initial_window_location, initial_window_size, }); *mapped.resize_state.lock().unwrap() = Some(resize_state); ResizeSurfaceGrab { start_data, window: mapped, edges: edges.into(), initial_window_size, last_window_size: initial_window_size, } } pub fn apply_resize_to_location(window: CosmicMapped, space: &mut Workspace) { if let Some(mut location) = space.floating_layer.space.element_location(&window) { let mut new_location = None; let mut resize_state = window.resize_state.lock().unwrap(); // If the window is being resized by top or left, its location must be adjusted // accordingly. match *resize_state { Some(ResizeState::Resizing(resize_data)) | Some(ResizeState::WaitingForCommit(resize_data)) => { let ResizeData { edges, initial_window_location, initial_window_size, } = resize_data; if edges.intersects(ResizeEdge::TOP_LEFT) { let size = window.geometry().size; if edges.intersects(ResizeEdge::LEFT) { location.x = initial_window_location.x + (initial_window_size.w - size.w); } if edges.intersects(ResizeEdge::TOP) { location.y = initial_window_location.y + (initial_window_size.h - size.h); } new_location = Some(location); } } _ => {} }; // Finish resizing. if let Some(ResizeState::WaitingForCommit(_)) = *resize_state { if !window.is_resizing() { *resize_state = None; } } std::mem::drop(resize_state); if let Some(new_location) = new_location { for (window, offset) in window.windows() { update_reactive_popups( &window, new_location + offset, space.floating_layer.space.outputs(), ); } space .floating_layer .space .map_element(window, new_location, false); } } } }