From 01b34aadd2357923625f132b7441bd20f92a16d3 Mon Sep 17 00:00:00 2001 From: Victoria Brekenfeld Date: Wed, 26 Oct 2022 15:26:07 +0200 Subject: [PATCH] shell/floating: Add interactive move grab --- src/backend/render/mod.rs | 51 +-- src/input/mod.rs | 4 +- src/shell/grabs.rs | 368 ------------------ src/shell/layout/floating/grabs/mod.rs | 5 + src/shell/layout/floating/grabs/moving.rs | 195 ++++++++++ .../floating/{grabs.rs => grabs/resize.rs} | 0 src/shell/mod.rs | 6 +- src/shell/workspace.rs | 56 ++- src/wayland/handlers/xdg_shell/mod.rs | 31 +- 9 files changed, 314 insertions(+), 402 deletions(-) delete mode 100644 src/shell/grabs.rs create mode 100644 src/shell/layout/floating/grabs/mod.rs create mode 100644 src/shell/layout/floating/grabs/moving.rs rename src/shell/layout/floating/{grabs.rs => grabs/resize.rs} (100%) diff --git a/src/backend/render/mod.rs b/src/backend/render/mod.rs index 0ba64e8d..a7cba0f0 100644 --- a/src/backend/render/mod.rs +++ b/src/backend/render/mod.rs @@ -6,9 +6,12 @@ use crate::{ state::Fps, utils::prelude::*, }; -//grabs::{MoveGrabRenderElement, SeatMoveGrabState}, use crate::{ - shell::WorkspaceRenderElement, state::Common, wayland::handlers::data_device::get_dnd_icon, + shell::{ + layout::floating::SeatMoveGrabState, CosmicMappedRenderElement, WorkspaceRenderElement, + }, + state::Common, + wayland::handlers::data_device::get_dnd_icon, }; use smithay::{ @@ -45,7 +48,7 @@ smithay::render_elements! { pub CosmicElement where R: ImportAll; WorkspaceElement=WorkspaceRenderElement, CursorElement=CursorRenderElement, - //MoveGrabRenderElement=MoveGrabRenderElement, + MoveGrabRenderElement=CosmicMappedRenderElement, //#[cfg(feature = "debug")] //EguiFrame=EguiFrame, } @@ -59,7 +62,7 @@ pub fn cursor_elements( where R: Renderer + ImportAll + ImportMem, ::TextureId: Clone + 'static, - E: From>, + E: From> + From>, { let scale = output.current_scale().fractional_scale(); let mut elements = Vec::new(); @@ -73,27 +76,6 @@ where .shell .map_global_to_space(pointer.current_location().to_i32_round(), output); - /* - if let Some(grab) = seat - .user_data() - .get::() - .unwrap() - .borrow() - .as_ref() - .and_then(|state| state.render(seat, output)) - { - custom_elements.push(grab); - } - */ - - if let Some(wl_surface) = get_dnd_icon(seat) { - elements.extend( - cursor::draw_dnd_icon(&wl_surface, location.to_i32_round(), scale) - .into_iter() - .map(E::from), - ); - } - elements.extend( cursor::draw_cursor( renderer, @@ -106,6 +88,25 @@ where .into_iter() .map(E::from), ); + + if let Some(wl_surface) = get_dnd_icon(seat) { + elements.extend( + cursor::draw_dnd_icon(&wl_surface, location.to_i32_round(), scale) + .into_iter() + .map(E::from), + ); + } + + if let Some(grab_elements) = seat + .user_data() + .get::() + .unwrap() + .borrow() + .as_ref() + .map(|state| state.render::(seat, output)) + { + elements.extend(grab_elements); + } } elements diff --git a/src/input/mod.rs b/src/input/mod.rs index bc922c51..23410731 100644 --- a/src/input/mod.rs +++ b/src/input/mod.rs @@ -2,7 +2,7 @@ use crate::{ config::{Action, Config}, - shell::{focus::target::PointerFocusTarget, Workspace}, // shell::grabs::SeatMoveGrabState + shell::{focus::target::PointerFocusTarget, layout::floating::SeatMoveGrabState, Workspace}, // shell::grabs::SeatMoveGrabState state::Common, utils::prelude::*, }; @@ -112,7 +112,7 @@ pub fn add_seat( userdata.insert_if_missing(SeatId::default); userdata.insert_if_missing(Devices::default); userdata.insert_if_missing(SupressedKeys::default); - //userdata.insert_if_missing(SeatMoveGrabState::default); + userdata.insert_if_missing(SeatMoveGrabState::default); userdata.insert_if_missing(|| ActiveOutput(RefCell::new(output.clone()))); userdata.insert_if_missing(|| RefCell::new(CursorImageStatus::Default)); diff --git a/src/shell/grabs.rs b/src/shell/grabs.rs deleted file mode 100644 index d27a2f62..00000000 --- a/src/shell/grabs.rs +++ /dev/null @@ -1,368 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0-only - -use super::Shell; -use crate::utils::prelude::*; - -use cosmic_protocols::workspace::v1::server::zcosmic_workspace_handle_v1::State as WState; -use smithay::{ - backend::renderer::{ImportAll, Renderer}, - desktop::{ - draw_window, - space::{RenderElement, SpaceOutputTuple}, - Kind, Window, - }, - input::{ - pointer::{ - AxisFrame, ButtonEvent, Focus, GrabStartData as PointerGrabStartData, MotionEvent, - PointerGrab, PointerInnerHandle, - }, - Seat, - }, - output::Output, - reexports::{ - wayland_protocols::xdg::shell::server::xdg_toplevel::State as XdgState, - wayland_server::protocol::wl_surface::WlSurface, - }, - utils::{IsAlive, Logical, Physical, Point, Rectangle, Scale, Serial}, -}; -use std::cell::RefCell; - -impl Shell { - pub fn move_request( - state: &mut State, - window: &Window, - seat: &Seat, - serial: Serial, - start_data: PointerGrabStartData, - ) { - // TODO touch grab - if let Some(pointer) = seat.get_pointer() { - let workspace = state - .common - .shell - .space_for_window_mut(window.toplevel().wl_surface()) - .unwrap(); - if workspace.fullscreen.values().any(|w| w == window) { - return; - } - - let pos = pointer.current_location(); - let output = match workspace - .space - .outputs_for_window(&window) - .into_iter() - .find(|o| o.geometry().contains(pos.to_i32_round())) - { - Some(o) => o, - None => return, - }; - let mut initial_window_location = workspace.space.window_location(&window).unwrap(); - - match &window.toplevel() { - Kind::Xdg(surface) => { - // If surface is maximized then unmaximize it - let current_state = surface.current_state(); - if current_state.states.contains(XdgState::Maximized) { - workspace - .floating_layer - .unmaximize_request(&mut workspace.space, window); - let new_size = surface.with_pending_state(|state| state.size); - let ratio = pos.x / output.geometry().size.w as f64; - - initial_window_location = new_size - .map(|size| (pos.x - (size.w as f64 * ratio), pos.y).into()) - .unwrap_or_else(|| pos) - .to_i32_round(); - } - } - }; - - let was_tiled = if workspace.tiling_layer.windows.contains(&window) { - workspace - .tiling_layer - .unmap_window(&mut workspace.space, &window); - true - } else { - workspace - .floating_layer - .unmap_window(&mut workspace.space, &window); - false - }; - - let workspace_handle = workspace.handle; - let workspace_is_empty = workspace.space.windows().next().is_none(); - - if workspace_is_empty { - state - .common - .shell - .workspace_state - .update() - .add_workspace_state(&workspace_handle, WState::Hidden); - } - state - .common - .shell - .toplevel_info_state - .toplevel_leave_workspace(&window, &workspace_handle); - state - .common - .shell - .toplevel_info_state - .toplevel_leave_output(&window, &output); - - let grab_state = MoveGrabState { - window: window.clone(), - was_tiled, - initial_cursor_location: pointer.current_location(), - initial_window_location, - initial_output_location: output.geometry().loc, - }; - let grab = MoveSurfaceGrab::new(start_data, window.clone(), seat); - - *seat - .user_data() - .get::() - .unwrap() - .borrow_mut() = Some(grab_state); - pointer.set_grab(state, grab, serial, Focus::Clear); - } - } - - fn drop_move(state: &mut State, seat: &Seat, output: &Output) { - if let Some(move_state) = seat - .user_data() - .get::() - .unwrap() - .borrow_mut() - .take() - { - let pointer = seat.get_pointer().unwrap(); - let window = move_state.window; - - if window.alive() { - let delta = pointer.current_location() - move_state.initial_cursor_location; - let window_location = (move_state.initial_window_location.to_f64() - + move_state.initial_output_location.to_f64() - - output.geometry().loc.to_f64() - + delta) - .to_i32_round(); - let surface = window.toplevel().wl_surface().clone(); - - let workspace_handle = state.common.shell.active_space(output).handle; - state - .common - .shell - .workspace_state - .update() - .remove_workspace_state(&workspace_handle, WState::Hidden); - state - .common - .shell - .toplevel_info_state - .toplevel_enter_workspace(&window, &workspace_handle); - state - .common - .shell - .toplevel_info_state - .toplevel_enter_output(&window, &output); - - let workspace = state.common.shell.active_space_mut(output); - if move_state.was_tiled { - let focus_stack = workspace.focus_stack(&seat); - workspace.tiling_layer.map_window( - &mut workspace.space, - window, - &seat, - focus_stack.iter(), - ); - } else { - workspace.floating_layer.map_window( - &mut workspace.space, - window, - &seat, - window_location, - ); - } - - Shell::set_focus(state, Some(&surface), &seat, None); - - for window in state.common.shell.active_space(output).space.windows() { - state.common.shell.update_reactive_popups(window); - } - } - } - } -} - -pub type SeatMoveGrabState = RefCell>; - -pub struct MoveGrabState { - window: Window, - was_tiled: bool, - initial_cursor_location: Point, - initial_window_location: Point, - initial_output_location: Point, -} - -pub struct MoveGrabRenderElement { - seat_id: usize, - window: Window, - window_location: Point, -} - -impl RenderElement for MoveGrabRenderElement -where - R: Renderer + ImportAll, - ::TextureId: 'static, -{ - fn id(&self) -> usize { - self.seat_id - } - - fn location(&self, scale: impl Into>) -> Point { - (self.window_location - self.window.geometry().loc.to_f64()).to_physical(scale) - } - - fn geometry(&self, scale: impl Into>) -> Rectangle { - let scale = scale.into(); - self.window - .physical_bbox_with_popups(RenderElement::::location(self, scale), scale) - } - - fn accumulated_damage( - &self, - scale: impl Into>, - for_values: Option>, - ) -> Vec> { - let scale = scale.into(); - self.window.accumulated_damage( - RenderElement::::location(self, scale), - scale, - for_values.map(|t| (t.0, t.1)), - ) - } - - fn opaque_regions( - &self, - scale: impl Into>, - ) -> Option>> { - let scale = scale.into(); - self.window - .opaque_regions(RenderElement::::location(self, scale), scale) - } - - fn draw( - &self, - renderer: &mut R, - frame: &mut ::Frame, - scale: impl Into>, - position: Point, - damage: &[Rectangle], - log: &slog::Logger, - ) -> Result<(), ::Error> { - draw_window(renderer, frame, &self.window, scale, position, damage, log) - } -} - -impl MoveGrabState { - pub fn render(&self, seat: &Seat, output: &Output) -> Option - where - I: From, - { - let cursor_at = seat.get_pointer().unwrap().current_location(); - let delta = cursor_at - self.initial_cursor_location; - let location = - self.initial_output_location.to_f64() + self.initial_window_location.to_f64() + delta; - - let mut window_geo = self.window.bbox(); - window_geo.loc += location.to_i32_round(); - if !output.geometry().intersection(window_geo).is_some() { - return None; - } - - Some(I::from(MoveGrabRenderElement { - seat_id: seat.id(), - window: self.window.clone(), - window_location: location - output.geometry().loc.to_f64(), - })) - } -} - -pub struct MoveSurfaceGrab { - window: Window, - start_data: PointerGrabStartData, - seat: Seat, -} - -impl PointerGrab for MoveSurfaceGrab { - fn motion( - &mut self, - state: &mut State, - handle: &mut PointerInnerHandle<'_, State>, - _focus: Option<(WlSurface, Point)>, - event: &MotionEvent, - ) { - // While the grab is active, no client has pointer focus - handle.motion(state, None, event); - if !self.window.alive() { - self.ungrab(state, handle, event.serial, event.time); - } - } - - fn button( - &mut self, - state: &mut State, - handle: &mut PointerInnerHandle<'_, State>, - event: &ButtonEvent, - ) { - handle.button(state, event); - if handle.current_pressed().is_empty() { - self.ungrab(state, handle, event.serial, event.time); - } - } - - fn axis( - &mut self, - state: &mut State, - handle: &mut PointerInnerHandle<'_, State>, - details: AxisFrame, - ) { - handle.axis(state, details); - } - - fn start_data(&self) -> &PointerGrabStartData { - &self.start_data - } -} - -impl MoveSurfaceGrab { - pub fn new( - start_data: PointerGrabStartData, - window: Window, - seat: &Seat, - ) -> MoveSurfaceGrab { - MoveSurfaceGrab { - window, - start_data, - seat: seat.clone(), - } - } - - fn ungrab( - &mut self, - state: &mut State, - handle: &mut PointerInnerHandle<'_, State>, - serial: Serial, - time: u32, - ) { - // No more buttons are pressed, release the grab. - let output = self.seat.active_output(); - let seat = self.seat.clone(); - - state.common.event_loop_handle.insert_idle(move |data| { - Shell::drop_move(&mut data.state, &seat, &output); - }); - handle.unset_grab(state, serial, time); - } -} diff --git a/src/shell/layout/floating/grabs/mod.rs b/src/shell/layout/floating/grabs/mod.rs new file mode 100644 index 00000000..65700de7 --- /dev/null +++ b/src/shell/layout/floating/grabs/mod.rs @@ -0,0 +1,5 @@ +mod moving; +mod resize; + +pub use self::moving::*; +pub use self::resize::*; diff --git a/src/shell/layout/floating/grabs/moving.rs b/src/shell/layout/floating/grabs/moving.rs new file mode 100644 index 00000000..96d2e73a --- /dev/null +++ b/src/shell/layout/floating/grabs/moving.rs @@ -0,0 +1,195 @@ +// SPDX-License-Identifier: GPL-3.0-only + +use crate::{ + shell::{ + element::{CosmicMapped, CosmicMappedRenderElement}, + focus::target::{KeyboardFocusTarget, PointerFocusTarget}, + }, + utils::prelude::*, +}; + +use smithay::{ + backend::renderer::{element::AsRenderElements, ImportAll, Renderer}, + desktop::space::SpaceElement, + input::{ + pointer::{ + AxisFrame, ButtonEvent, Focus, GrabStartData as PointerGrabStartData, MotionEvent, + PointerGrab, PointerInnerHandle, + }, + Seat, + }, + output::Output, + utils::{IsAlive, Logical, Physical, Point, Rectangle, Scale, Serial}, +}; +use std::cell::RefCell; + +pub type SeatMoveGrabState = RefCell>; + +pub struct MoveGrabState { + window: CosmicMapped, + initial_cursor_location: Point, + initial_window_location: Point, + initial_output_location: Point, +} + +impl MoveGrabState { + pub fn render(&self, seat: &Seat, output: &Output) -> Vec + where + R: Renderer + ImportAll, + ::TextureId: 'static, + I: From>, + { + let cursor_at = seat.get_pointer().unwrap().current_location(); + let delta = cursor_at - self.initial_cursor_location; + let location = + self.initial_output_location.to_f64() + self.initial_window_location.to_f64() + delta; + + let mut window_geo = self.window.geometry(); + window_geo.loc += location.to_i32_round(); + if !output.geometry().intersection(window_geo).is_some() { + return Vec::new(); + } + + let scale = output.current_scale().fractional_scale().into(); + self.window.render_elements::( + (location.to_i32_round() - output.geometry().loc - self.window.geometry().loc) + .to_physical_precise_round(scale), + scale, + ) + } +} + +pub struct MoveSurfaceGrab { + window: CosmicMapped, + start_data: PointerGrabStartData, + seat: Seat, +} + +impl PointerGrab for MoveSurfaceGrab { + fn motion( + &mut self, + state: &mut State, + handle: &mut PointerInnerHandle<'_, State>, + _focus: Option<(PointerFocusTarget, Point)>, + event: &MotionEvent, + ) { + // While the grab is active, no client has pointer focus + handle.motion(state, None, event); + if !self.window.alive() { + self.ungrab(state, handle, event.serial, event.time); + } + } + + fn button( + &mut self, + state: &mut State, + handle: &mut PointerInnerHandle<'_, State>, + event: &ButtonEvent, + ) { + handle.button(state, event); + if handle.current_pressed().is_empty() { + self.ungrab(state, handle, event.serial, event.time); + } + } + + fn axis( + &mut self, + state: &mut State, + handle: &mut PointerInnerHandle<'_, State>, + details: AxisFrame, + ) { + handle.axis(state, details); + } + + fn start_data(&self) -> &PointerGrabStartData { + &self.start_data + } +} + +impl MoveSurfaceGrab { + pub fn new( + start_data: PointerGrabStartData, + window: CosmicMapped, + seat: &Seat, + initial_cursor_location: Point, + initial_window_location: Point, + initial_output_location: Point, + ) -> MoveSurfaceGrab { + let grab_state = MoveGrabState { + window: window.clone(), + initial_cursor_location, + initial_window_location, + initial_output_location, + }; + + *seat + .user_data() + .get::() + .unwrap() + .borrow_mut() = Some(grab_state); + + MoveSurfaceGrab { + window, + start_data, + seat: seat.clone(), + } + } + + fn ungrab( + &mut self, + state: &mut State, + handle: &mut PointerInnerHandle<'_, State>, + serial: Serial, + time: u32, + ) { + // No more buttons are pressed, release the grab. + let output = self.seat.active_output(); + + if let Some(grab_state) = self + .seat + .user_data() + .get::() + .and_then(|s| s.borrow_mut().take()) + { + if grab_state.window.alive() { + let delta = handle.current_location() - grab_state.initial_cursor_location; + let window_location = (grab_state.initial_window_location.to_f64() + + grab_state.initial_output_location.to_f64() + - output.geometry().loc.to_f64() + + delta) + .to_i32_round(); + + let workspace_handle = state.common.shell.active_space(&output).handle; + for (window, _) in grab_state.window.windows() { + state + .common + .shell + .toplevel_info_state + .toplevel_enter_workspace(&window, &workspace_handle); + state + .common + .shell + .toplevel_info_state + .toplevel_enter_output(&window, &output); + } + + state + .common + .shell + .active_space_mut(&output) + .floating_layer + .map_internal(grab_state.window, &output, Some(window_location)); + } + } + + handle.unset_grab(state, serial, time); + if self.window.alive() { + Common::set_focus( + state, + Some(&KeyboardFocusTarget::from(self.window.clone())), + &self.seat, + Some(serial), + ) + } + } +} diff --git a/src/shell/layout/floating/grabs.rs b/src/shell/layout/floating/grabs/resize.rs similarity index 100% rename from src/shell/layout/floating/grabs.rs rename to src/shell/layout/floating/grabs/resize.rs diff --git a/src/shell/mod.rs b/src/shell/mod.rs index 3a1e8300..fa83f184 100644 --- a/src/shell/mod.rs +++ b/src/shell/mod.rs @@ -1,5 +1,5 @@ use serde::{Deserialize, Serialize}; -use std::{cell::Cell, collections::HashMap}; +use std::collections::HashMap; use cosmic_protocols::workspace::v1::server::zcosmic_workspace_handle_v1::State as WState; use smithay::{ @@ -7,7 +7,7 @@ use smithay::{ input::{pointer::MotionEvent, Seat}, output::Output, reexports::wayland_server::{protocol::wl_surface::WlSurface, DisplayHandle}, - utils::{Logical, Point, Rectangle, SERIAL_COUNTER}, + utils::{Logical, Point, Rectangle}, wayland::{ compositor::with_states, shell::{ @@ -34,9 +34,9 @@ use crate::{ mod element; pub mod focus; -//pub mod grabs; pub mod layout; mod workspace; +pub use self::element::CosmicMappedRenderElement; pub use self::workspace::*; use self::{ element::{CosmicMapped, CosmicWindow}, diff --git a/src/shell/workspace.rs b/src/shell/workspace.rs index 6790d7df..0ecb3c7b 100644 --- a/src/shell/workspace.rs +++ b/src/shell/workspace.rs @@ -1,7 +1,10 @@ use crate::{ shell::{ element::CosmicWindow, - layout::{floating::FloatingLayout, tiling::TilingLayout}, + layout::{ + floating::{FloatingLayout, MoveSurfaceGrab}, + tiling::TilingLayout, + }, }, state::State, utils::prelude::*, @@ -179,8 +182,8 @@ impl Workspace { } pub fn unmaximize_request(&mut self, window: &Window) { if self.fullscreen.values().any(|w| w == window) { + self.unfullscreen_request(window); self.floating_layer.unmaximize_request(window); - return self.unfullscreen_request(window); } } @@ -278,6 +281,55 @@ impl Workspace { } } + pub fn move_request( + &mut self, + window: &Window, + seat: &Seat, + output: &Output, + _serial: Serial, + start_data: PointerGrabStartData, + ) -> Option { + let pointer = seat.get_pointer().unwrap(); + let pos = pointer.current_location(); + + let mapped = self + .element_for_surface(window.toplevel().wl_surface())? + .clone(); + let mut initial_window_location = self.element_geometry(&mapped).unwrap().loc; + + if mapped.is_fullscreen() || mapped.is_maximized() { + // If surface is maximized then unmaximize it + self.unmaximize_request(window); + let new_size = match window.toplevel() { + Kind::Xdg(toplevel) => toplevel.with_pending_state(|state| state.size), + //_ => unreachable!(), // TODO x11 + }; + let ratio = pos.x / output.geometry().size.w as f64; + + initial_window_location = new_size + .map(|size| (pos.x - (size.w as f64 * ratio), pos.y).into()) + .unwrap_or_else(|| pos) + .to_i32_round(); + } + + let was_floating = self.floating_layer.unmap(&mapped); + //let was_tiled = self.tiling_layer.unmap(&mapped); + //assert!(was_floating != was_tiled); + + if was_floating { + Some(MoveSurfaceGrab::new( + start_data, + mapped, + seat, + pos, + initial_window_location, + output.geometry().loc, + )) + } else { + None // TODO + } + } + pub fn toggle_tiling(&mut self, seat: &Seat) { if self.tiling_enabled { for window in self diff --git a/src/wayland/handlers/xdg_shell/mod.rs b/src/wayland/handlers/xdg_shell/mod.rs index f320b129..2d87311a 100644 --- a/src/wayland/handlers/xdg_shell/mod.rs +++ b/src/wayland/handlers/xdg_shell/mod.rs @@ -154,8 +154,35 @@ impl XdgShellHandler for State { fn move_request(&mut self, surface: ToplevelSurface, seat: WlSeat, serial: Serial) { let seat = Seat::from_resource(&seat).unwrap(); if let Some(start_data) = check_grab_preconditions(&seat, surface.wl_surface(), serial) { - if let Some(mapped) = self.common.shell.element_for_surface(surface.wl_surface()) { - // Shell::move_request(self, &window, &seat, serial, start_data); + if let Some(mapped) = self + .common + .shell + .element_for_surface(surface.wl_surface()) + .cloned() + { + if let Some(workspace) = self.common.shell.space_for_mut(&mapped) { + let output = seat.active_output(); + let (window, _) = mapped + .windows() + .find(|(w, _)| matches!(w.toplevel(), Kind::Xdg(s) if s == &surface)) + .unwrap(); + if let Some(grab) = + workspace.move_request(&window, &seat, &output, serial, start_data) + { + let handle = workspace.handle; + self.common + .shell + .toplevel_info_state + .toplevel_leave_workspace(&window, &handle); + self.common + .shell + .toplevel_info_state + .toplevel_leave_output(&window, &output); + seat.get_pointer() + .unwrap() + .set_grab(self, grab, serial, Focus::Clear); + } + } } } }