diff --git a/src/shell/element/mod.rs b/src/shell/element/mod.rs index 75c70658..e6a50968 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; @@ -73,7 +73,10 @@ use tracing::debug; use super::{ focus::FocusDirection, - layout::{floating::ResizeState, tiling::NodeDesc}, + layout::{ + floating::{ResizeState, TiledCorners}, + tiling::NodeDesc, + }, Direction, ManagedLayer, }; @@ -103,6 +106,8 @@ pub struct CosmicMapped { //floating pub(super) resize_state: Arc>>, pub last_geometry: Arc>>>, + pub moved_since_mapped: Arc, + pub floating_tiled: Arc>>, #[cfg(feature = "debug")] debug: Arc>>, @@ -117,6 +122,8 @@ 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) + .field("floating_tiled", &self.floating_tiled) .finish() } } @@ -224,7 +231,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 +1109,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)), + floating_tiled: Arc::new(Mutex::new(None)), #[cfg(feature = "debug")] debug: Arc::new(Mutex::new(None)), } @@ -1117,6 +1126,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)), + floating_tiled: Arc::new(Mutex::new(None)), #[cfg(feature = "debug")] debug: Arc::new(Mutex::new(None)), } @@ -1143,6 +1154,12 @@ where TiledOverlay( RelocateRenderElement>>, ), + MovingStack( + RelocateRenderElement>>, + ), + MovingWindow( + RelocateRenderElement>>, + ), GrabbedStack(RescaleRenderElement>), GrabbedWindow(RescaleRenderElement>), FocusIndicator(PixelShaderElement), @@ -1164,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(), @@ -1181,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(), @@ -1198,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(), @@ -1215,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), @@ -1232,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), @@ -1249,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(), @@ -1270,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), @@ -1289,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), @@ -1306,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(), @@ -1333,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) => { @@ -1358,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), @@ -1390,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) => { @@ -1424,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/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..dd2d83b9 100644 --- a/src/shell/layout/floating/mod.rs +++ b/src/shell/layout/floating/mod.rs @@ -1,14 +1,24 @@ // SPDX-License-Identifier: GPL-3.0-only +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::{Logical, Point, Rectangle, Size}, + utils::{IsAlive, Logical, Point, Rectangle, Scale, Size}, wayland::seat::WaylandFocus, }; @@ -26,16 +36,92 @@ 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)] +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 { @@ -50,9 +136,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(); } @@ -79,6 +197,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 +217,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 +228,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 +249,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 +263,141 @@ 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 + && output_geometry.contains_rect(*geo) + }) + .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); @@ -155,7 +408,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( @@ -171,6 +436,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 +459,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 +558,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() @@ -395,7 +668,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(); @@ -418,12 +691,139 @@ 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 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 + ( + 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(), + )); + } + + // 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, + (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, + }; - MoveResult::MoveFurther(KeyboardFocusTarget::Element(focused.clone())) + 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); + + if tiled_state.is_none() && focused.is_maximized(false) { + *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.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 { @@ -439,6 +839,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() @@ -453,6 +858,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 @@ -489,37 +906,124 @@ 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)) + .or_else(|| { + elem.is_maximized(true) + .then_some(output_geometry.as_local()) + }) + { + 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), @@ -537,7 +1041,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 }