From a098e68c3c987fb5a0b7065862b292fb4b786e1a Mon Sep 17 00:00:00 2001 From: Victoria Brekenfeld Date: Wed, 8 Nov 2023 22:59:46 +0100 Subject: [PATCH 1/5] floating: New window spawn positions --- src/shell/element/mod.rs | 9 +- src/shell/grabs/moving.rs | 2 + src/shell/layout/floating/mod.rs | 179 ++++++++++++++++++++++++++++--- 3 files changed, 172 insertions(+), 18 deletions(-) diff --git a/src/shell/element/mod.rs b/src/shell/element/mod.rs index 75c70658..4bf43b2c 100644 --- a/src/shell/element/mod.rs +++ b/src/shell/element/mod.rs @@ -50,7 +50,7 @@ use std::{ collections::HashMap, fmt, hash::Hash, - sync::{Arc, Mutex}, + sync::{atomic::AtomicBool, Arc, Mutex}, }; pub mod surface; @@ -103,6 +103,7 @@ pub struct CosmicMapped { //floating pub(super) resize_state: Arc>>, pub last_geometry: Arc>>>, + pub moved_since_mapped: Arc, #[cfg(feature = "debug")] debug: Arc>>, @@ -117,6 +118,7 @@ impl fmt::Debug for CosmicMapped { .field("tiling_node_id", &self.tiling_node_id) .field("resize_state", &self.resize_state) .field("last_geometry", &self.last_geometry) + .field("moved_since_mapped", &self.moved_since_mapped) .finish() } } @@ -224,7 +226,7 @@ impl CosmicMapped { } if surface_type.contains(WindowSurfaceType::SUBSURFACE) { - use std::sync::atomic::{AtomicBool, Ordering}; + use std::sync::atomic::Ordering; let found = AtomicBool::new(false); with_surface_tree_downward( @@ -1102,6 +1104,7 @@ impl From for CosmicMapped { tiling_node_id: Arc::new(Mutex::new(None)), resize_state: Arc::new(Mutex::new(None)), last_geometry: Arc::new(Mutex::new(None)), + moved_since_mapped: Arc::new(AtomicBool::new(false)), #[cfg(feature = "debug")] debug: Arc::new(Mutex::new(None)), } @@ -1117,6 +1120,8 @@ impl From for CosmicMapped { tiling_node_id: Arc::new(Mutex::new(None)), resize_state: Arc::new(Mutex::new(None)), last_geometry: Arc::new(Mutex::new(None)), + moved_since_mapped: Arc::new(AtomicBool::new(false)), + #[cfg(feature = "debug")] #[cfg(feature = "debug")] debug: Arc::new(Mutex::new(None)), } diff --git a/src/shell/grabs/moving.rs b/src/shell/grabs/moving.rs index 0d4a0f93..78b778f6 100644 --- a/src/shell/grabs/moving.rs +++ b/src/shell/grabs/moving.rs @@ -42,6 +42,7 @@ use smithay::{ use std::{ cell::RefCell, collections::HashSet, + sync::atomic::Ordering, time::{Duration, Instant}, }; @@ -442,6 +443,7 @@ impl MoveGrab { let mut outputs = HashSet::new(); outputs.insert(output.clone()); window.output_enter(&output, window.geometry()); // not accurate but... + window.moved_since_mapped.store(true, Ordering::SeqCst); let grab_state = MoveGrabState { window: window.clone(), diff --git a/src/shell/layout/floating/mod.rs b/src/shell/layout/floating/mod.rs index 0676ae2a..c2ed62fc 100644 --- a/src/shell/layout/floating/mod.rs +++ b/src/shell/layout/floating/mod.rs @@ -1,5 +1,7 @@ // SPDX-License-Identifier: GPL-3.0-only +use std::sync::atomic::Ordering; + use smithay::{ backend::renderer::{ element::{AsRenderElements, RenderElement}, @@ -36,6 +38,7 @@ pub use self::grabs::*; #[derive(Debug, Default)] pub struct FloatingLayout { pub(crate) space: Space, + spawn_order: Vec, } impl FloatingLayout { @@ -79,6 +82,12 @@ impl FloatingLayout { mapped.set_geometry(geometry.to_global(&output)); mapped.configure(); + if let Some(pos) = self.spawn_order.iter().position(|m| m == &mapped) { + self.spawn_order.truncate(pos); + } + + mapped.moved_since_mapped.store(true, Ordering::SeqCst); + self.space .map_element(mapped, geometry.loc.as_logical(), true); } @@ -93,9 +102,10 @@ impl FloatingLayout { let output = self.space.outputs().next().unwrap().clone(); let layers = layer_map_for_output(&output); - let geometry = layers.non_exclusive_zone(); - mapped.set_bounds(geometry.size); + let output_geometry = layers.non_exclusive_zone(); + mapped.set_bounds(output_geometry.size); let last_geometry = mapped.last_geometry.lock().unwrap().clone(); + let min_size = mapped.min_size().unwrap_or((320, 240).into()); if let Some(size) = size .map(SizeExt::as_local) @@ -103,13 +113,18 @@ impl FloatingLayout { { win_geo.size = size; } else { - let (min_size, max_size) = ( - mapped.min_size().unwrap_or((0, 0).into()), - mapped.max_size().unwrap_or((0, 0).into()), + let max_size = mapped.max_size().unwrap_or( + ( + min_size.w.max(output_geometry.size.w / 3 * 2), + min_size.h.max(output_geometry.size.h / 3 * 2), + ) + .into(), ); - if win_geo.size.w > geometry.size.w / 3 * 2 { + + // if the last_geometry is too large + if win_geo.size.w > output_geometry.size.w { // try a more reasonable size - let mut width = geometry.size.w / 3 * 2; + let mut width = output_geometry.size.w / 3 * 2; if max_size.w != 0 { // don't go larger then the max_size ... width = std::cmp::min(max_size.w, width); @@ -119,11 +134,11 @@ impl FloatingLayout { width = std::cmp::max(min_size.w, width); } // but no matter the supported sizes, don't be larger than our non-exclusive-zone - win_geo.size.w = std::cmp::min(width, geometry.size.w); + win_geo.size.w = std::cmp::min(width, output_geometry.size.w); } - if win_geo.size.h > geometry.size.h / 3 * 2 { + if win_geo.size.h > output_geometry.size.h { // try a more reasonable size - let mut height = geometry.size.h / 3 * 2; + let mut height = output_geometry.size.h / 3 * 2; if max_size.h != 0 { // don't go larger then the max_size ... height = std::cmp::min(max_size.h, height); @@ -133,18 +148,139 @@ impl FloatingLayout { height = std::cmp::max(min_size.h, height); } // but no matter the supported sizes, don't be larger than our non-exclusive-zone - win_geo.size.h = std::cmp::min(height, geometry.size.h); + win_geo.size.h = std::cmp::min(height, output_geometry.size.h); } } let position = position .or_else(|| last_geometry.map(|g| g.loc)) .unwrap_or_else(|| { - ( - geometry.loc.x + (geometry.size.w / 2) - (win_geo.size.w / 2) + win_geo.loc.x, - geometry.loc.y + (geometry.size.h / 2) - (win_geo.size.h / 2) + win_geo.loc.y, - ) - .into() + // cleanup moved windows + if let Some(pos) = self + .spawn_order + .iter() + .position(|w| !w.alive() || w.moved_since_mapped.load(Ordering::SeqCst)) + { + self.spawn_order.truncate(pos); + } + + let three_fours_width = (output_geometry.size.w / 4 * 3).max(360); + + // figure out new position + let pos = self + .spawn_order + .last() + .and_then(|window| self.space.element_geometry(window)) + .filter(|geo| { + geo.size.w < three_fours_width && win_geo.size.w < three_fours_width + }) + .map(|geometry| { + let mut geometry: Rectangle = Rectangle::from_loc_and_size( + (geometry.loc.x as u32, geometry.loc.y as u32), + (geometry.size.w as u32, geometry.size.h as u32), + ); + + // move down + geometry.loc.y += 48; + + // do we need to address the height? + let new_column = if geometry.loc.y + min_size.h as u32 + <= (output_geometry.loc.y + output_geometry.size.h - 16) as u32 + { + // alternate to the sides + let offset = if self + .spawn_order + .iter() + .flat_map(|w| self.space.element_geometry(w)) + .filter(|geo| geo.size.w < three_fours_width) + .count() + % 2 + == 0 + { + (geometry.loc.x + geometry.size.w) + .checked_sub(96 + (win_geo.size.w as u32)) + } else { + (geometry.loc.x + geometry.size.w) + .checked_sub((win_geo.size.w as u32).saturating_sub(48)) + }; + + if let Some(offset) = offset { + geometry.loc.x = offset; + // do we need to resize? + if geometry.loc.y as i32 + win_geo.size.h + <= output_geometry.loc.y + output_geometry.size.h - 16 + { + win_geo.size.h = + (output_geometry.loc.y + output_geometry.size.h - 16) + - geometry.loc.y as i32; + } + + false + } else { + true + } + } else { + true + }; + + if new_column { + let min_y = self + .spawn_order + .iter() + .flat_map(|w| { + self.space + .element_geometry(w) + .filter(|geo| geo.size.w < three_fours_width) + .map(|geo| geo.loc.y) + }) + .min() + .unwrap() as u32; + geometry.loc.y = min_y.saturating_sub(16); + + match geometry.loc.x.checked_sub(144) { + Some(new_x) => geometry.loc.x = new_x, + None => { + // if we go out to the left, cycle around to the right + geometry.loc.x = + ((output_geometry.loc.x + output_geometry.size.w) as u32) + .saturating_sub(geometry.size.w + 16) + } + }; + } + + // check padding again + if geometry.loc.x < (output_geometry.loc.x + 16) as u32 { + geometry.loc.x = (output_geometry.loc.x + 16) as u32; + } + if geometry.loc.y < (output_geometry.loc.y + 16) as u32 { + geometry.loc.y = (output_geometry.loc.y + 16) as u32; + } + // if the width would be too high, we wouldn't be here + if geometry.loc.y as i32 + win_geo.size.h + > (output_geometry.loc.y + output_geometry.size.h - 16) + { + win_geo.size.h = output_geometry.loc.y + output_geometry.size.h + - 16 + - geometry.loc.y as i32; + } + + Point::::from((geometry.loc.x as i32, geometry.loc.y as i32)) + }) + .unwrap_or_else(|| { + ( + output_geometry.loc.x + output_geometry.size.w / 2 - win_geo.size.w / 2, + output_geometry.loc.y + + (output_geometry.size.h / 2 - win_geo.size.h / 2) + .min(output_geometry.size.h / 8), + ) + .into() + }) + .as_local(); + + mapped.moved_since_mapped.store(false, Ordering::SeqCst); + self.spawn_order.push(mapped.clone()); + + pos }); mapped.set_tiled(false); @@ -171,6 +307,12 @@ impl FloatingLayout { let was_unmaped = self.space.elements().any(|e| e == window); self.space.unmap_elem(&window); + if was_unmaped { + if let Some(pos) = self.spawn_order.iter().position(|w| w == window) { + self.spawn_order.truncate(pos); + } + window.moved_since_mapped.store(true, Ordering::SeqCst); + } was_unmaped } @@ -188,6 +330,7 @@ impl FloatingLayout { if seat.get_pointer().is_some() { let location = self.space.element_location(&mapped).unwrap(); let size = mapped.geometry().size; + mapped.moved_since_mapped.store(true, Ordering::SeqCst); Some(grabs::ResizeSurfaceGrab::new( start_data, @@ -286,6 +429,7 @@ impl FloatingLayout { initial_window_size: original_geo.size, })); + mapped.moved_since_mapped.store(true, Ordering::SeqCst); mapped.set_resizing(true); mapped.set_geometry( geo.as_local() @@ -439,6 +583,9 @@ impl FloatingLayout { puffin::profile_function!(); self.space.refresh(); + if let Some(pos) = self.spawn_order.iter().position(|w| !w.alive()) { + self.spawn_order.truncate(pos); + } for element in self .space .elements() From 7f6a09ac48a4edd06ee27b0de20ef10f4ac438ed Mon Sep 17 00:00:00 2001 From: Victoria Brekenfeld Date: Thu, 9 Nov 2023 19:00:25 +0100 Subject: [PATCH 2/5] floating: Handle tiling shortcuts --- src/shell/element/mod.rs | 10 +- src/shell/layout/floating/mod.rs | 212 +++++++++++++++++++++++++++++-- 2 files changed, 209 insertions(+), 13 deletions(-) diff --git a/src/shell/element/mod.rs b/src/shell/element/mod.rs index 4bf43b2c..d21b3263 100644 --- a/src/shell/element/mod.rs +++ b/src/shell/element/mod.rs @@ -73,7 +73,10 @@ use tracing::debug; use super::{ focus::FocusDirection, - layout::{floating::ResizeState, tiling::NodeDesc}, + layout::{ + floating::{ResizeState, TiledCorners}, + tiling::NodeDesc, + }, Direction, ManagedLayer, }; @@ -104,6 +107,7 @@ pub struct CosmicMapped { pub(super) resize_state: Arc>>, pub last_geometry: Arc>>>, pub moved_since_mapped: Arc, + pub floating_tiled: Arc>>, #[cfg(feature = "debug")] debug: Arc>>, @@ -119,6 +123,7 @@ impl fmt::Debug for CosmicMapped { .field("resize_state", &self.resize_state) .field("last_geometry", &self.last_geometry) .field("moved_since_mapped", &self.moved_since_mapped) + .field("floating_tiled", &self.floating_tiled) .finish() } } @@ -1105,6 +1110,7 @@ impl From for CosmicMapped { resize_state: Arc::new(Mutex::new(None)), last_geometry: Arc::new(Mutex::new(None)), moved_since_mapped: Arc::new(AtomicBool::new(false)), + floating_tiled: Arc::new(Mutex::new(None)), #[cfg(feature = "debug")] debug: Arc::new(Mutex::new(None)), } @@ -1121,7 +1127,7 @@ impl From for CosmicMapped { resize_state: Arc::new(Mutex::new(None)), last_geometry: Arc::new(Mutex::new(None)), moved_since_mapped: Arc::new(AtomicBool::new(false)), - #[cfg(feature = "debug")] + floating_tiled: Arc::new(Mutex::new(None)), #[cfg(feature = "debug")] debug: Arc::new(Mutex::new(None)), } diff --git a/src/shell/layout/floating/mod.rs b/src/shell/layout/floating/mod.rs index c2ed62fc..9998d297 100644 --- a/src/shell/layout/floating/mod.rs +++ b/src/shell/layout/floating/mod.rs @@ -10,7 +10,7 @@ use smithay::{ desktop::{layer_map_for_output, space::SpaceElement, PopupKind, Space, WindowSurfaceType}, input::{pointer::GrabStartData as PointerGrabStartData, Seat}, output::Output, - utils::{Logical, Point, Rectangle, Size}, + utils::{IsAlive, Logical, Point, Rectangle, Size}, wayland::seat::WaylandFocus, }; @@ -41,6 +41,77 @@ pub struct FloatingLayout { spawn_order: Vec, } +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub enum TiledCorners { + Top, + TopRight, + Right, + BottomRight, + Bottom, + BottomLeft, + Left, + TopLeft, +} + +impl TiledCorners { + pub fn relative_geometry( + &self, + output_geometry: Rectangle, + ) -> Rectangle { + let (loc, size) = match self { + TiledCorners::Bottom => ( + Point::from(( + output_geometry.loc.x, + output_geometry.loc.y + (output_geometry.size.h / 2), + )), + Size::from((output_geometry.size.w, output_geometry.size.h / 2)), + ), + TiledCorners::BottomLeft => ( + Point::from(( + output_geometry.loc.x, + output_geometry.loc.y + (output_geometry.size.h / 2), + )), + Size::from((output_geometry.size.w / 2, output_geometry.size.h / 2)), + ), + TiledCorners::BottomRight => ( + Point::from(( + output_geometry.loc.x + (output_geometry.size.w / 2), + output_geometry.loc.y + (output_geometry.size.h / 2), + )), + Size::from((output_geometry.size.w / 2, output_geometry.size.h / 2)), + ), + TiledCorners::Left => ( + output_geometry.loc, + Size::from((output_geometry.size.w / 2, output_geometry.size.h)), + ), + TiledCorners::Top => ( + output_geometry.loc, + Size::from((output_geometry.size.w, output_geometry.size.h / 2)), + ), + TiledCorners::TopLeft => ( + output_geometry.loc, + Size::from((output_geometry.size.w / 2, output_geometry.size.h / 2)), + ), + TiledCorners::TopRight => ( + Point::from(( + output_geometry.loc.x + (output_geometry.size.w / 2), + output_geometry.loc.y, + )), + Size::from((output_geometry.size.w / 2, output_geometry.size.h / 2)), + ), + TiledCorners::Right => ( + Point::from(( + output_geometry.loc.x + (output_geometry.size.w / 2), + output_geometry.loc.y, + )), + Size::from((output_geometry.size.w / 2, output_geometry.size.h)), + ), + }; + + Rectangle::from_loc_and_size(loc, size).as_local() + } +} + impl FloatingLayout { pub fn new(output: &Output) -> FloatingLayout { let mut layout = Self::default(); @@ -53,9 +124,41 @@ impl FloatingLayout { self.space.unmap_output(&old_output); self.space.map_output(output, (0, 0)); - /* - TODO: rescale all positions? (evem rescale windows?) - */ + let old_output_geometry = { + let layers = layer_map_for_output(&old_output); + layers.non_exclusive_zone() + }; + let output_geometry = { + let layers = layer_map_for_output(&output); + layers.non_exclusive_zone() + }; + + for mapped in self + .space + .elements() + .cloned() + .collect::>() + .into_iter() + { + let tiled_state = mapped.floating_tiled.lock().unwrap().clone(); + if let Some(tiled_state) = tiled_state { + let geometry = tiled_state.relative_geometry(output_geometry); + self.map_internal(mapped, Some(geometry.loc), Some(geometry.size.as_logical())); + } else { + let geometry = self.space.element_geometry(&mapped).unwrap(); + let new_loc = ( + geometry.loc.x.saturating_sub(old_output_geometry.loc.x) + / old_output_geometry.size.w + * output_geometry.size.w + + output_geometry.loc.x, + geometry.loc.y.saturating_sub(old_output_geometry.loc.y) + / old_output_geometry.size.h + * output_geometry.size.h + + output_geometry.loc.y, + ); + self.map_internal(mapped, Some(Point::from(new_loc)), None); + } + } self.refresh(); } @@ -271,7 +374,7 @@ impl FloatingLayout { output_geometry.loc.x + output_geometry.size.w / 2 - win_geo.size.w / 2, output_geometry.loc.y + (output_geometry.size.h / 2 - win_geo.size.h / 2) - .min(output_geometry.size.h / 8), + .min(output_geometry.size.h / 8), ) .into() }) @@ -291,7 +394,19 @@ impl FloatingLayout { } pub fn unmap(&mut self, window: &CosmicMapped) -> bool { - if !window.is_maximized(true) || !window.is_fullscreen(true) { + if let Some(_) = window.floating_tiled.lock().unwrap().take() { + if let Some(last_size) = window.last_geometry.lock().unwrap().map(|geo| geo.size) { + if let Some(location) = self.space.element_location(window) { + window.set_tiled(false); + window.set_geometry( + Rectangle::from_loc_and_size(location, last_size.as_logical()) + .as_local() + .to_global(self.space.outputs().next().unwrap()), + ); + window.configure(); + } + } + } else if !window.is_maximized(true) || !window.is_fullscreen(true) { if let Some(location) = self.space.element_location(window) { *window.last_geometry.lock().unwrap() = Some( Rectangle::from_loc_and_size( @@ -539,7 +654,7 @@ impl FloatingLayout { }; match focused.handle_move(direction) { - StackMoveResult::Handled => return MoveResult::Done, + StackMoveResult::Handled => MoveResult::Done, StackMoveResult::MoveOut(surface, loop_handle) => { let mapped: CosmicMapped = CosmicWindow::new(surface, loop_handle, theme).into(); let output = seat.active_output(); @@ -562,12 +677,87 @@ impl FloatingLayout { .then_some(pos); self.map_internal(mapped.clone(), position.map(PointExt::as_local), None); - return MoveResult::ShiftFocus(KeyboardFocusTarget::Element(mapped)); + MoveResult::ShiftFocus(KeyboardFocusTarget::Element(mapped)) } - StackMoveResult::Default => {} - }; + StackMoveResult::Default => { + let mut tiled_state = focused.floating_tiled.lock().unwrap(); + let new_state = match (direction, &*tiled_state) { + // figure out if we are moving between workspaces/outputs + ( + Direction::Up, + Some(TiledCorners::Top) + | Some(TiledCorners::TopLeft) + | Some(TiledCorners::TopRight), + ) + | ( + Direction::Down, + Some(TiledCorners::Bottom) + | Some(TiledCorners::BottomLeft) + | Some(TiledCorners::BottomRight), + ) + | ( + Direction::Left, + Some(TiledCorners::Left) + | Some(TiledCorners::TopLeft) + | Some(TiledCorners::BottomLeft), + ) + | ( + Direction::Right, + Some(TiledCorners::Right) + | Some(TiledCorners::TopRight) + | Some(TiledCorners::BottomRight), + ) => { + return MoveResult::MoveFurther(KeyboardFocusTarget::Element( + focused.clone(), + )); + } + + // figure out if we need to quater tile + (Direction::Up, Some(TiledCorners::Left)) + | (Direction::Left, Some(TiledCorners::Top)) => TiledCorners::TopLeft, + (Direction::Right, Some(TiledCorners::Top)) + | (Direction::Up, Some(TiledCorners::Right)) => TiledCorners::TopRight, + (Direction::Down, Some(TiledCorners::Left)) + | (Direction::Left, Some(TiledCorners::Bottom)) => TiledCorners::BottomLeft, + (Direction::Right, Some(TiledCorners::Bottom)) + | (Direction::Down, Some(TiledCorners::Right)) => TiledCorners::BottomRight, + // figure out if we need to extend a quater tile + (Direction::Up, Some(TiledCorners::BottomLeft)) + | (Direction::Down, Some(TiledCorners::TopLeft)) => TiledCorners::Left, + (Direction::Up, Some(TiledCorners::BottomRight)) + | (Direction::Down, Some(TiledCorners::TopRight)) => TiledCorners::Right, + (Direction::Left, Some(TiledCorners::TopRight)) + | (Direction::Right, Some(TiledCorners::TopLeft)) => TiledCorners::Top, + (Direction::Left, Some(TiledCorners::BottomRight)) + | (Direction::Right, Some(TiledCorners::BottomLeft)) => TiledCorners::Bottom, + // else we have a simple case + (Direction::Up, _) => TiledCorners::Top, + (Direction::Right, _) => TiledCorners::Right, + (Direction::Down, _) => TiledCorners::Bottom, + (Direction::Left, _) => TiledCorners::Left, + }; + + let output = self.space.outputs().next().unwrap().clone(); + let layers = layer_map_for_output(&output); + let output_geometry = layers.non_exclusive_zone(); + std::mem::drop(layers); + + let new_geo = new_state.relative_geometry(output_geometry); + let (new_pos, new_size) = (new_geo.loc, new_geo.size); + focused.set_tiled(true); // TODO: More fine grained? - MoveResult::MoveFurther(KeyboardFocusTarget::Element(focused.clone())) + *tiled_state = Some(new_state); + std::mem::drop(tiled_state); + + *focused.last_geometry.lock().unwrap() = + self.space.element_geometry(focused).map(RectExt::as_local); + focused.moved_since_mapped.store(true, Ordering::SeqCst); + let focused = focused.clone(); + self.map_internal(focused, Some(new_pos), Some(new_size.as_logical())); + + MoveResult::Done + } + } } pub fn mapped(&self) -> impl Iterator { From 596ae5a2ba332da9e13bbcbda31dbda5047cff0e Mon Sep 17 00:00:00 2001 From: Victoria Brekenfeld Date: Tue, 14 Nov 2023 16:32:49 +0100 Subject: [PATCH 3/5] floating: Animate tiling state changes --- src/shell/element/mod.rs | 32 ++++++ src/shell/layout/floating/mod.rs | 185 +++++++++++++++++++++++++++---- src/shell/workspace.rs | 2 + 3 files changed, 199 insertions(+), 20 deletions(-) diff --git a/src/shell/element/mod.rs b/src/shell/element/mod.rs index d21b3263..e6a50968 100644 --- a/src/shell/element/mod.rs +++ b/src/shell/element/mod.rs @@ -1154,6 +1154,12 @@ where TiledOverlay( RelocateRenderElement>>, ), + MovingStack( + RelocateRenderElement>>, + ), + MovingWindow( + RelocateRenderElement>>, + ), GrabbedStack(RescaleRenderElement>), GrabbedWindow(RescaleRenderElement>), FocusIndicator(PixelShaderElement), @@ -1175,6 +1181,8 @@ where CosmicMappedRenderElement::TiledStack(elem) => elem.id(), CosmicMappedRenderElement::TiledWindow(elem) => elem.id(), CosmicMappedRenderElement::TiledOverlay(elem) => elem.id(), + CosmicMappedRenderElement::MovingStack(elem) => elem.id(), + CosmicMappedRenderElement::MovingWindow(elem) => elem.id(), CosmicMappedRenderElement::GrabbedStack(elem) => elem.id(), CosmicMappedRenderElement::GrabbedWindow(elem) => elem.id(), CosmicMappedRenderElement::FocusIndicator(elem) => elem.id(), @@ -1192,6 +1200,8 @@ where CosmicMappedRenderElement::TiledStack(elem) => elem.current_commit(), CosmicMappedRenderElement::TiledWindow(elem) => elem.current_commit(), CosmicMappedRenderElement::TiledOverlay(elem) => elem.current_commit(), + CosmicMappedRenderElement::MovingStack(elem) => elem.current_commit(), + CosmicMappedRenderElement::MovingWindow(elem) => elem.current_commit(), CosmicMappedRenderElement::GrabbedStack(elem) => elem.current_commit(), CosmicMappedRenderElement::GrabbedWindow(elem) => elem.current_commit(), CosmicMappedRenderElement::FocusIndicator(elem) => elem.current_commit(), @@ -1209,6 +1219,8 @@ where CosmicMappedRenderElement::TiledStack(elem) => elem.src(), CosmicMappedRenderElement::TiledWindow(elem) => elem.src(), CosmicMappedRenderElement::TiledOverlay(elem) => elem.src(), + CosmicMappedRenderElement::MovingStack(elem) => elem.src(), + CosmicMappedRenderElement::MovingWindow(elem) => elem.src(), CosmicMappedRenderElement::GrabbedStack(elem) => elem.src(), CosmicMappedRenderElement::GrabbedWindow(elem) => elem.src(), CosmicMappedRenderElement::FocusIndicator(elem) => elem.src(), @@ -1226,6 +1238,8 @@ where CosmicMappedRenderElement::TiledStack(elem) => elem.geometry(scale), CosmicMappedRenderElement::TiledWindow(elem) => elem.geometry(scale), CosmicMappedRenderElement::TiledOverlay(elem) => elem.geometry(scale), + CosmicMappedRenderElement::MovingStack(elem) => elem.geometry(scale), + CosmicMappedRenderElement::MovingWindow(elem) => elem.geometry(scale), CosmicMappedRenderElement::GrabbedStack(elem) => elem.geometry(scale), CosmicMappedRenderElement::GrabbedWindow(elem) => elem.geometry(scale), CosmicMappedRenderElement::FocusIndicator(elem) => elem.geometry(scale), @@ -1243,6 +1257,8 @@ where CosmicMappedRenderElement::TiledStack(elem) => elem.location(scale), CosmicMappedRenderElement::TiledWindow(elem) => elem.location(scale), CosmicMappedRenderElement::TiledOverlay(elem) => elem.location(scale), + CosmicMappedRenderElement::MovingStack(elem) => elem.location(scale), + CosmicMappedRenderElement::MovingWindow(elem) => elem.location(scale), CosmicMappedRenderElement::GrabbedStack(elem) => elem.location(scale), CosmicMappedRenderElement::GrabbedWindow(elem) => elem.location(scale), CosmicMappedRenderElement::FocusIndicator(elem) => elem.location(scale), @@ -1260,6 +1276,8 @@ where CosmicMappedRenderElement::TiledStack(elem) => elem.transform(), CosmicMappedRenderElement::TiledWindow(elem) => elem.transform(), CosmicMappedRenderElement::TiledOverlay(elem) => elem.transform(), + CosmicMappedRenderElement::MovingStack(elem) => elem.transform(), + CosmicMappedRenderElement::MovingWindow(elem) => elem.transform(), CosmicMappedRenderElement::GrabbedStack(elem) => elem.transform(), CosmicMappedRenderElement::GrabbedWindow(elem) => elem.transform(), CosmicMappedRenderElement::FocusIndicator(elem) => elem.transform(), @@ -1281,6 +1299,8 @@ where CosmicMappedRenderElement::TiledStack(elem) => elem.damage_since(scale, commit), CosmicMappedRenderElement::TiledWindow(elem) => elem.damage_since(scale, commit), CosmicMappedRenderElement::TiledOverlay(elem) => elem.damage_since(scale, commit), + CosmicMappedRenderElement::MovingStack(elem) => elem.damage_since(scale, commit), + CosmicMappedRenderElement::MovingWindow(elem) => elem.damage_since(scale, commit), CosmicMappedRenderElement::GrabbedStack(elem) => elem.damage_since(scale, commit), CosmicMappedRenderElement::GrabbedWindow(elem) => elem.damage_since(scale, commit), CosmicMappedRenderElement::FocusIndicator(elem) => elem.damage_since(scale, commit), @@ -1300,6 +1320,8 @@ where CosmicMappedRenderElement::TiledStack(elem) => elem.opaque_regions(scale), CosmicMappedRenderElement::TiledWindow(elem) => elem.opaque_regions(scale), CosmicMappedRenderElement::TiledOverlay(elem) => elem.opaque_regions(scale), + CosmicMappedRenderElement::MovingStack(elem) => elem.opaque_regions(scale), + CosmicMappedRenderElement::MovingWindow(elem) => elem.opaque_regions(scale), CosmicMappedRenderElement::GrabbedStack(elem) => elem.opaque_regions(scale), CosmicMappedRenderElement::GrabbedWindow(elem) => elem.opaque_regions(scale), CosmicMappedRenderElement::FocusIndicator(elem) => elem.opaque_regions(scale), @@ -1317,6 +1339,8 @@ where CosmicMappedRenderElement::TiledStack(elem) => elem.alpha(), CosmicMappedRenderElement::TiledWindow(elem) => elem.alpha(), CosmicMappedRenderElement::TiledOverlay(elem) => elem.alpha(), + CosmicMappedRenderElement::MovingStack(elem) => elem.alpha(), + CosmicMappedRenderElement::MovingWindow(elem) => elem.alpha(), CosmicMappedRenderElement::GrabbedStack(elem) => elem.alpha(), CosmicMappedRenderElement::GrabbedWindow(elem) => elem.alpha(), CosmicMappedRenderElement::FocusIndicator(elem) => elem.alpha(), @@ -1344,6 +1368,8 @@ impl RenderElement for CosmicMappedRenderElement { CosmicMappedRenderElement::TiledOverlay(elem) => { RenderElement::::draw(elem, frame, src, dst, damage) } + CosmicMappedRenderElement::MovingStack(elem) => elem.draw(frame, src, dst, damage), + CosmicMappedRenderElement::MovingWindow(elem) => elem.draw(frame, src, dst, damage), CosmicMappedRenderElement::GrabbedStack(elem) => elem.draw(frame, src, dst, damage), CosmicMappedRenderElement::GrabbedWindow(elem) => elem.draw(frame, src, dst, damage), CosmicMappedRenderElement::FocusIndicator(elem) => { @@ -1369,6 +1395,8 @@ impl RenderElement for CosmicMappedRenderElement { CosmicMappedRenderElement::TiledStack(elem) => elem.underlying_storage(renderer), CosmicMappedRenderElement::TiledWindow(elem) => elem.underlying_storage(renderer), CosmicMappedRenderElement::TiledOverlay(elem) => elem.underlying_storage(renderer), + CosmicMappedRenderElement::MovingStack(elem) => elem.underlying_storage(renderer), + CosmicMappedRenderElement::MovingWindow(elem) => elem.underlying_storage(renderer), CosmicMappedRenderElement::GrabbedStack(elem) => elem.underlying_storage(renderer), CosmicMappedRenderElement::GrabbedWindow(elem) => elem.underlying_storage(renderer), CosmicMappedRenderElement::FocusIndicator(elem) => elem.underlying_storage(renderer), @@ -1401,6 +1429,8 @@ impl<'a, 'b> RenderElement> RenderElement::::draw(elem, frame.glow_frame_mut(), src, dst, damage) .map_err(|err| GlMultiError::Render(err)) } + CosmicMappedRenderElement::MovingStack(elem) => elem.draw(frame, src, dst, damage), + CosmicMappedRenderElement::MovingWindow(elem) => elem.draw(frame, src, dst, damage), CosmicMappedRenderElement::GrabbedStack(elem) => elem.draw(frame, src, dst, damage), CosmicMappedRenderElement::GrabbedWindow(elem) => elem.draw(frame, src, dst, damage), CosmicMappedRenderElement::FocusIndicator(elem) => { @@ -1435,6 +1465,8 @@ impl<'a, 'b> RenderElement> CosmicMappedRenderElement::TiledOverlay(elem) => { elem.underlying_storage(renderer.glow_renderer_mut()) } + CosmicMappedRenderElement::MovingStack(elem) => elem.underlying_storage(renderer), + CosmicMappedRenderElement::MovingWindow(elem) => elem.underlying_storage(renderer), CosmicMappedRenderElement::GrabbedStack(elem) => elem.underlying_storage(renderer), CosmicMappedRenderElement::GrabbedWindow(elem) => elem.underlying_storage(renderer), CosmicMappedRenderElement::FocusIndicator(elem) => { diff --git a/src/shell/layout/floating/mod.rs b/src/shell/layout/floating/mod.rs index 9998d297..0eff452f 100644 --- a/src/shell/layout/floating/mod.rs +++ b/src/shell/layout/floating/mod.rs @@ -1,16 +1,24 @@ // SPDX-License-Identifier: GPL-3.0-only -use std::sync::atomic::Ordering; +use std::{ + collections::HashMap, + sync::atomic::{AtomicBool, Ordering}, + time::{Duration, Instant}, +}; +use keyframe::{ease, functions::EaseInOutCubic}; use smithay::{ backend::renderer::{ - element::{AsRenderElements, RenderElement}, + element::{ + utils::{Relocate, RelocateRenderElement, RescaleRenderElement}, + AsRenderElements, RenderElement, + }, ImportAll, ImportMem, Renderer, }, desktop::{layer_map_for_output, space::SpaceElement, PopupKind, Space, WindowSurfaceType}, input::{pointer::GrabStartData as PointerGrabStartData, Seat}, output::Output, - utils::{IsAlive, Logical, Point, Rectangle, Size}, + utils::{IsAlive, Logical, Point, Rectangle, Scale, Size}, wayland::seat::WaylandFocus, }; @@ -28,17 +36,21 @@ use crate::{ CosmicSurface, Direction, FocusResult, MoveResult, ResizeDirection, ResizeMode, }, state::State, - utils::prelude::*, + utils::{prelude::*, tween::EaseRectangle}, wayland::handlers::xdg_shell::popup::get_popup_toplevel, }; mod grabs; pub use self::grabs::*; +pub const ANIMATION_DURATION: Duration = Duration::from_millis(200); + #[derive(Debug, Default)] pub struct FloatingLayout { pub(crate) space: Space, spawn_order: Vec, + tiling_animations: HashMap)>, + dirty: AtomicBool, } #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] @@ -746,11 +758,47 @@ impl FloatingLayout { let (new_pos, new_size) = (new_geo.loc, new_geo.size); focused.set_tiled(true); // TODO: More fine grained? + let start_rectangle = if let Some((previous_start, previous_rect)) = + self.tiling_animations.remove(focused) + { + if let Some(target_rect) = tiled_state + .as_ref() + .map(|state| state.relative_geometry(output_geometry)) + { + ease( + EaseInOutCubic, + EaseRectangle(previous_rect), + EaseRectangle(target_rect), + Instant::now() + .duration_since(previous_start) + .max(ANIMATION_DURATION) + .as_secs_f64() + / ANIMATION_DURATION.as_secs_f64(), + ) + .unwrap() + } else { + self.space + .element_geometry(focused) + .map(RectExt::as_local) + .unwrap() + } + } else { + self.space + .element_geometry(focused) + .map(RectExt::as_local) + .unwrap() + }; + + if tiled_state.is_none() { + *focused.last_geometry.lock().unwrap() = + self.space.element_geometry(focused).map(RectExt::as_local); + } + + self.tiling_animations + .insert(focused.clone(), (Instant::now(), start_rectangle)); *tiled_state = Some(new_state); std::mem::drop(tiled_state); - *focused.last_geometry.lock().unwrap() = - self.space.element_geometry(focused).map(RectExt::as_local); focused.moved_since_mapped.store(true, Ordering::SeqCst); let focused = focused.clone(); self.map_internal(focused, Some(new_pos), Some(new_size.as_logical())); @@ -773,9 +821,11 @@ impl FloatingLayout { puffin::profile_function!(); self.space.refresh(); + if let Some(pos) = self.spawn_order.iter().position(|w| !w.alive()) { self.spawn_order.truncate(pos); } + for element in self .space .elements() @@ -790,6 +840,18 @@ impl FloatingLayout { } } + pub fn animations_going(&self) -> bool { + self.dirty.swap(false, Ordering::SeqCst) || !self.tiling_animations.is_empty() + } + + pub fn update_animation_state(&mut self) { + self.tiling_animations + .retain(|_, (start, _)| Instant::now().duration_since(*start) < ANIMATION_DURATION); + if self.tiling_animations.is_empty() { + self.dirty.store(true, Ordering::SeqCst); + } + } + pub fn merge(&mut self, other: FloatingLayout) { for element in other.space.elements() { let elem_loc = other @@ -826,37 +888,120 @@ impl FloatingLayout { puffin::profile_function!(); let output = self.space.outputs().next().unwrap(); + let output_geometry = { + let layers = layer_map_for_output(output); + layers.non_exclusive_zone() + }; let output_scale = output.current_scale().fractional_scale(); let mut window_elements = Vec::new(); let mut popup_elements = Vec::new(); self.space.elements().rev().for_each(|elem| { - let render_location = self.space.element_location(elem).unwrap() - elem.geometry().loc; - let (w_elements, p_elements) = elem.split_render_elements( + let mut geometry = self + .tiling_animations + .get(elem) + .map(|(_, rect)| *rect) + .unwrap_or_else(|| self.space.element_geometry(elem).unwrap().as_local()); + + let render_location = geometry.loc - elem.geometry().loc.as_local(); + let (mut w_elements, p_elements) = elem.split_render_elements( renderer, - render_location.to_physical_precise_round(output_scale), + render_location + .as_logical() + .to_physical_precise_round(output_scale), output_scale.into(), alpha, ); - if focused == Some(elem) && !elem.is_maximized(false) { - let mut indicator_geometry = Rectangle::from_loc_and_size( - self.space.element_location(elem).unwrap(), - elem.geometry().size, - ) - .as_local(); + if let Some((start, original_geo)) = self.tiling_animations.get(elem) { + if let Some(target_rect) = elem + .floating_tiled + .lock() + .unwrap() + .as_ref() + .map(|state| state.relative_geometry(output_geometry)) + { + geometry = ease( + EaseInOutCubic, + EaseRectangle(original_geo.clone()), + EaseRectangle(target_rect), + Instant::now() + .duration_since(*start) + .min(ANIMATION_DURATION) + .as_millis() as f32 + / ANIMATION_DURATION.as_millis() as f32, + ) + .unwrap(); + + let buffer_size = elem.geometry().size; + let scale = Scale { + x: geometry.size.w as f64 / buffer_size.w as f64, + y: geometry.size.h as f64 / buffer_size.h as f64, + }; + w_elements = w_elements + .into_iter() + .map(|element| match element { + CosmicMappedRenderElement::Stack(elem) => { + CosmicMappedRenderElement::MovingStack({ + let rescaled = RescaleRenderElement::from_element( + elem, + original_geo + .loc + .as_logical() + .to_physical_precise_round(output_scale), + scale, + ); + let relocated = RelocateRenderElement::from_element( + rescaled, + (geometry.loc - original_geo.loc) + .as_logical() + .to_physical_precise_round(output_scale), + Relocate::Relative, + ); + relocated + }) + } + CosmicMappedRenderElement::Window(elem) => { + CosmicMappedRenderElement::MovingWindow({ + let rescaled = RescaleRenderElement::from_element( + elem, + original_geo + .loc + .as_logical() + .to_physical_precise_round(output_scale), + scale, + ); + let relocated = RelocateRenderElement::from_element( + rescaled, + (geometry.loc - original_geo.loc) + .as_logical() + .to_physical_precise_round(output_scale), + Relocate::Relative, + ); + relocated + }) + } + x => x, + }) + .collect(); + } + } + + if focused == Some(elem) && !elem.is_maximized(false) { if let Some((mode, resize)) = resize_indicator.as_mut() { - indicator_geometry.loc -= (18, 18).into(); - indicator_geometry.size += (36, 36).into(); - resize.resize(indicator_geometry.size.as_logical()); + let mut resize_geometry = geometry.clone(); + resize_geometry.loc -= (18, 18).into(); + resize_geometry.size += (36, 36).into(); + + resize.resize(resize_geometry.size.as_logical()); resize.output_enter(output, Rectangle::default() /* unused */); window_elements.extend( resize .render_elements::>( renderer, - indicator_geometry + resize_geometry .loc .as_logical() .to_physical_precise_round(output_scale), @@ -874,7 +1019,7 @@ impl FloatingLayout { let element = IndicatorShader::focus_element( renderer, Key::Window(Usage::FocusIndicator, elem.clone()), - indicator_geometry, + geometry, indicator_thickness, output_scale, alpha, diff --git a/src/shell/workspace.rs b/src/shell/workspace.rs index 916d274f..06927de5 100644 --- a/src/shell/workspace.rs +++ b/src/shell/workspace.rs @@ -260,6 +260,7 @@ impl Workspace { pub fn animations_going(&self) -> bool { self.tiling_layer.animations_going() + || self.floating_layer.animations_going() || self .fullscreen .as_ref() @@ -310,6 +311,7 @@ impl Workspace { } clients.extend(self.tiling_layer.update_animation_state()); + self.floating_layer.update_animation_state(); clients } From 6d3c4295cd3c6362ed17ec882348f307dbf58d30 Mon Sep 17 00:00:00 2001 From: Victoria Brekenfeld Date: Wed, 15 Nov 2023 17:32:35 +0100 Subject: [PATCH 4/5] floating: New reference windows outside --- src/shell/layout/floating/mod.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/shell/layout/floating/mod.rs b/src/shell/layout/floating/mod.rs index 0eff452f..a251c96d 100644 --- a/src/shell/layout/floating/mod.rs +++ b/src/shell/layout/floating/mod.rs @@ -287,7 +287,9 @@ impl FloatingLayout { .last() .and_then(|window| self.space.element_geometry(window)) .filter(|geo| { - geo.size.w < three_fours_width && win_geo.size.w < three_fours_width + geo.size.w < three_fours_width + && win_geo.size.w < three_fours_width + && output_geometry.contains_rect(*geo) }) .map(|geometry| { let mut geometry: Rectangle = Rectangle::from_loc_and_size( From 7f62de03f6c7b8758b92e55c0fa1bf88af10aa60 Mon Sep 17 00:00:00 2001 From: Victoria Brekenfeld Date: Wed, 15 Nov 2023 17:32:46 +0100 Subject: [PATCH 5/5] floating: Transition via maximized state --- src/shell/layout/floating/mod.rs | 94 +++++++++++++++++++------------- 1 file changed, 57 insertions(+), 37 deletions(-) diff --git a/src/shell/layout/floating/mod.rs b/src/shell/layout/floating/mod.rs index a251c96d..dd2d83b9 100644 --- a/src/shell/layout/floating/mod.rs +++ b/src/shell/layout/floating/mod.rs @@ -695,6 +695,43 @@ impl FloatingLayout { } StackMoveResult::Default => { let mut tiled_state = focused.floating_tiled.lock().unwrap(); + + let output = self.space.outputs().next().unwrap().clone(); + let layers = layer_map_for_output(&output); + let output_geometry = layers.non_exclusive_zone(); + std::mem::drop(layers); + + let start_rectangle = if let Some((previous_start, previous_rect)) = + self.tiling_animations.remove(focused) + { + if let Some(target_rect) = tiled_state + .as_ref() + .map(|state| state.relative_geometry(output_geometry)) + { + ease( + EaseInOutCubic, + EaseRectangle(previous_rect), + EaseRectangle(target_rect), + Instant::now() + .duration_since(previous_start) + .max(ANIMATION_DURATION) + .as_secs_f64() + / ANIMATION_DURATION.as_secs_f64(), + ) + .unwrap() + } else { + self.space + .element_geometry(focused) + .map(RectExt::as_local) + .unwrap() + } + } else { + self.space + .element_geometry(focused) + .map(RectExt::as_local) + .unwrap() + }; + let new_state = match (direction, &*tiled_state) { // figure out if we are moving between workspaces/outputs ( @@ -726,6 +763,20 @@ impl FloatingLayout { )); } + // to we go maximized? + (Direction::Up, Some(TiledCorners::Bottom)) + | (Direction::Down, Some(TiledCorners::Top)) + | (Direction::Left, Some(TiledCorners::Right)) + | (Direction::Right, Some(TiledCorners::Left)) => { + self.tiling_animations + .insert(focused.clone(), (Instant::now(), start_rectangle)); + *tiled_state = None; + std::mem::drop(tiled_state); + + self.map_maximized(focused.clone()); + return MoveResult::Done; + } + // figure out if we need to quater tile (Direction::Up, Some(TiledCorners::Left)) | (Direction::Left, Some(TiledCorners::Top)) => TiledCorners::TopLeft, @@ -751,47 +802,12 @@ impl FloatingLayout { (Direction::Left, _) => TiledCorners::Left, }; - let output = self.space.outputs().next().unwrap().clone(); - let layers = layer_map_for_output(&output); - let output_geometry = layers.non_exclusive_zone(); - std::mem::drop(layers); - let new_geo = new_state.relative_geometry(output_geometry); let (new_pos, new_size) = (new_geo.loc, new_geo.size); focused.set_tiled(true); // TODO: More fine grained? + focused.set_maximized(false); - let start_rectangle = if let Some((previous_start, previous_rect)) = - self.tiling_animations.remove(focused) - { - if let Some(target_rect) = tiled_state - .as_ref() - .map(|state| state.relative_geometry(output_geometry)) - { - ease( - EaseInOutCubic, - EaseRectangle(previous_rect), - EaseRectangle(target_rect), - Instant::now() - .duration_since(previous_start) - .max(ANIMATION_DURATION) - .as_secs_f64() - / ANIMATION_DURATION.as_secs_f64(), - ) - .unwrap() - } else { - self.space - .element_geometry(focused) - .map(RectExt::as_local) - .unwrap() - } - } else { - self.space - .element_geometry(focused) - .map(RectExt::as_local) - .unwrap() - }; - - if tiled_state.is_none() { + if tiled_state.is_none() && focused.is_maximized(false) { *focused.last_geometry.lock().unwrap() = self.space.element_geometry(focused).map(RectExt::as_local); } @@ -923,6 +939,10 @@ impl FloatingLayout { .unwrap() .as_ref() .map(|state| state.relative_geometry(output_geometry)) + .or_else(|| { + elem.is_maximized(true) + .then_some(output_geometry.as_local()) + }) { geometry = ease( EaseInOutCubic,