// SPDX-License-Identifier: GPL-3.0-only use crate::{ backend::render::{ ACTIVE_GROUP_COLOR, BackdropShader, GROUP_COLOR, IndicatorShader, Key, Usage, element::AsGlowRenderer, }, shell::{ CosmicSurface, Direction, FocusResult, MoveResult, OutputNotMapped, OverviewMode, ResizeMode, Trigger, element::{ CosmicMapped, CosmicMappedRenderElement, CosmicStack, CosmicWindow, resize_indicator::ResizeIndicator, stack::{ CosmicStackRenderElement, MoveResult as StackMoveResult, TAB_HEIGHT as STACK_TAB_HEIGHT, }, swap_indicator::SwapIndicator, window::CosmicWindowRenderElement, }, focus::{ FocusStackMut, FocusTarget, target::{KeyboardFocusTarget, PointerFocusTarget, WindowGroup}, }, grabs::ResizeEdge, layout::Orientation, }, utils::{prelude::*, tween::EaseRectangle}, wayland::{ handlers::xdg_shell::popup::get_popup_toplevel, protocols::{ toplevel_info::{ toplevel_enter_output, toplevel_enter_workspace, toplevel_leave_output, toplevel_leave_workspace, }, workspace::WorkspaceHandle, }, }, }; use cosmic_comp_config::AppearanceConfig; use cosmic_settings_config::shortcuts::action::{FocusDirection, ResizeDirection}; use id_tree::{InsertBehavior, MoveBehavior, Node, NodeId, NodeIdError, RemoveBehavior, Tree}; use keyframe::{ ease, functions::{EaseInOutCubic, Linear}, }; use smithay::{ backend::renderer::{ element::{ AsRenderElements, Id, RenderElement, utils::{ ConstrainAlign, ConstrainScaleBehavior, RescaleRenderElement, constrain_render_elements, }, }, glow::GlowRenderer, }, desktop::{PopupKind, WindowSurfaceType, layer_map_for_output, space::SpaceElement}, input::Seat, output::Output, reexports::wayland_server::Client, utils::{IsAlive, Logical, Physical, Point, Rectangle, Scale, Size}, wayland::{compositor::add_blocker, seat::WaylandFocus}, }; use std::{ collections::{HashMap, VecDeque}, sync::{Arc, Weak}, time::{Duration, Instant}, }; use tracing::trace; use wayland_backend::server::ClientId; mod blocker; mod grabs; pub use self::blocker::*; pub use self::grabs::*; pub const ANIMATION_DURATION: Duration = Duration::from_millis(200); pub const MINIMIZE_ANIMATION_DURATION: Duration = Duration::from_millis(320); pub const MOUSE_ANIMATION_DELAY: Duration = Duration::from_millis(150); pub const INITIAL_MOUSE_ANIMATION_DELAY: Duration = Duration::from_millis(500); #[derive(Debug, Clone, PartialEq)] pub struct NodeDesc { pub handle: WorkspaceHandle, pub node: NodeId, pub stack_window: Option, pub focus_stack: Vec, } #[derive(Debug, Clone, PartialEq)] enum TargetZone { Initial, InitialPlaceholder(NodeId), WindowStack(NodeId, Rectangle), WindowSplit(NodeId, Direction), GroupEdge(NodeId, Direction), GroupInterior(NodeId, usize), } impl TargetZone { fn is_window_zone(&self) -> bool { matches!( self, TargetZone::WindowStack(..) | TargetZone::WindowSplit(_, _) ) } } #[derive(Debug, Clone, Default)] struct TreeQueue { trees: VecDeque<(Tree, Duration, Option)>, animation_start: Option, } impl TreeQueue { pub fn push_tree( &mut self, tree: Tree, duration: impl Into>, blocker: Option, ) { self.trees .push_back((tree, duration.into().unwrap_or(Duration::ZERO), blocker)) } } #[derive(Debug, Clone)] pub struct TilingLayout { output: Output, queue: TreeQueue, backdrop_id: Id, swapping_stack_surface_id: Id, last_overview_hover: Option<(Option, TargetZone)>, pub theme: cosmic::Theme, pub appearance: AppearanceConfig, } #[derive(Debug, Clone, PartialEq)] pub enum PillIndicator { Outer(Direction), Inner(usize), } #[derive(Debug, Clone)] pub enum Data { Group { orientation: Orientation, sizes: Vec, last_geometry: Rectangle, alive: Arc<()>, pill_indicator: Option, }, Mapped { mapped: CosmicMapped, last_geometry: Rectangle, minimize_rect: Option>, }, Placeholder { id: Id, last_geometry: Rectangle, type_: PlaceholderType, }, } #[derive(Debug, Clone)] pub enum PlaceholderType { GrabbedWindow, DropZone, } impl Data { fn new_group(orientation: Orientation, geo: Rectangle) -> Data { Data::Group { orientation, sizes: vec![ match orientation { Orientation::Vertical => geo.size.w / 2, Orientation::Horizontal => geo.size.h / 2, }; 2 ], last_geometry: geo, alive: Arc::new(()), pill_indicator: None, } } fn is_group(&self) -> bool { matches!(self, Data::Group { .. }) } fn is_mapped(&self, mapped: Option<&CosmicMapped>) -> bool { match mapped { Some(m) => matches!(self, Data::Mapped { mapped, .. } if m == mapped), None => matches!(self, Data::Mapped { .. }), } } fn is_stack(&self) -> bool { match self { Data::Mapped { mapped, .. } => mapped.is_stack(), _ => false, } } fn is_placeholder(&self) -> bool { matches!(self, Data::Placeholder { .. }) } fn orientation(&self) -> Orientation { match self { Data::Group { orientation, .. } => *orientation, _ => panic!("Not a group"), } } fn add_window(&mut self, idx: usize) { match self { Data::Group { sizes, last_geometry, orientation, .. } => { let last_length = match orientation { Orientation::Horizontal => last_geometry.size.h, Orientation::Vertical => last_geometry.size.w, }; let equal_sizing = last_length / (sizes.len() as i32 + 1); // new window size let remainder = last_length - equal_sizing; // size for the rest of the windowns for size in sizes.iter_mut() { *size = ((*size as f64 / last_length as f64) * remainder as f64).round() as i32; } let used_size: i32 = sizes.iter().sum(); let new_size = last_length - used_size; sizes.insert(idx, new_size); } _ => panic!("Adding window to leaf?"), } } fn swap_windows(&mut self, i: usize, j: usize) { match self { Data::Group { sizes, .. } => { sizes.swap(i, j); } _ => panic!("Swapping windows to a leaf?"), } } fn remove_window(&mut self, idx: usize) { match self { Data::Group { sizes, last_geometry, orientation, .. } => { let last_length = match orientation { Orientation::Horizontal => last_geometry.size.h, Orientation::Vertical => last_geometry.size.w, }; let old_size = sizes.remove(idx); let remaining_size: i32 = sizes.iter().sum(); for size in sizes.iter_mut() { *size += ((*size as f64 / remaining_size as f64) * old_size as f64).round() as i32; } let used_size: i32 = sizes.iter().sum(); let overflow = last_length - used_size; if overflow != 0 { *sizes.last_mut().unwrap() += overflow; } } _ => panic!("Added window to leaf?"), } } fn geometry(&self) -> &Rectangle { match self { Data::Group { last_geometry, .. } => last_geometry, Data::Mapped { last_geometry, .. } => last_geometry, Data::Placeholder { last_geometry, .. } => last_geometry, } } fn update_geometry(&mut self, geo: Rectangle) { match self { Data::Group { orientation, sizes, last_geometry, .. } => { let previous_length = match orientation { Orientation::Horizontal => last_geometry.size.h, Orientation::Vertical => last_geometry.size.w, }; let new_length = match orientation { Orientation::Horizontal => geo.size.h, Orientation::Vertical => geo.size.w, }; sizes.iter_mut().for_each(|len| { *len = (((*len as f64) / (previous_length as f64)) * (new_length as f64)) .round() as i32; }); let sum: i32 = sizes.iter().sum(); // fix rounding issues if sum != new_length { let diff = new_length - sum; *sizes.last_mut().unwrap() += diff; } *last_geometry = geo; } Data::Mapped { last_geometry, .. } | Data::Placeholder { last_geometry, .. } => { *last_geometry = geo; } } } fn len(&self) -> usize { match self { Data::Group { sizes, .. } => sizes.len(), _ => 1, } } } #[derive(Debug, Clone)] enum FocusedNodeData { Group(Vec, Weak<()>), Window(CosmicMapped), } #[derive(Debug, Clone)] pub struct RestoreTilingState { pub parent: Option, pub sibling: Option, pub orientation: Orientation, pub idx: usize, pub sizes: Vec, } impl TilingLayout { pub fn new( theme: cosmic::Theme, appearance: AppearanceConfig, output: &Output, ) -> TilingLayout { TilingLayout { queue: TreeQueue { trees: { let mut queue = VecDeque::new(); queue.push_back((Tree::new(), Duration::ZERO, None)); queue }, animation_start: None, }, output: output.clone(), backdrop_id: Id::new(), swapping_stack_surface_id: Id::new(), last_overview_hover: None, theme, appearance, } } pub fn set_output(&mut self, output: &Output) { let gaps = self.gaps(); let mut tree = self.queue.trees.back().unwrap().0.copy_clone(); for node in tree .root_node_id() .and_then(|root_id| tree.traverse_pre_order(root_id).ok()) .into_iter() .flatten() { if let Data::Mapped { mapped, .. } = node.data() { mapped.output_leave(&self.output); mapped.output_enter(output, mapped.bbox()); } } let blocker = TilingLayout::update_positions(output, &mut tree, gaps); self.queue.push_tree(tree, None, blocker); self.output = output.clone(); } pub fn map<'a>( &mut self, window: CosmicMapped, focus_stack: Option + 'a>, direction: Option, ) { window.output_enter(&self.output, window.bbox()); window.set_bounds(self.output.geometry().size.as_logical()); self.map_internal(window, focus_stack, direction, None); } pub fn map_internal<'a>( &mut self, window: impl Into, focus_stack: Option + 'a>, direction: Option, minimize_rect: Option>, ) { let gaps = self.gaps(); let mut tree = self.queue.trees.back().unwrap().0.copy_clone(); let last_active = focus_stack .and_then(|focus_stack| TilingLayout::last_active_window(&tree, focus_stack)) .map(|(node_id, _)| node_id); let duration = if minimize_rect.is_some() { MINIMIZE_ANIMATION_DURATION } else { ANIMATION_DURATION }; TilingLayout::map_to_tree( &mut tree, window, &self.output, last_active, direction, minimize_rect, ); let blocker = TilingLayout::update_positions(&self.output, &mut tree, gaps); self.queue.push_tree(tree, duration, blocker); } pub fn remap<'a>( &mut self, window: CosmicMapped, from: Option>, tiling_state: Option, focus_stack: Option + 'a>, ) { let gaps = self.gaps(); let mut tree = self.queue.trees.back().unwrap().0.copy_clone(); window.output_enter(&self.output, window.bbox()); window.set_bounds(self.output.geometry().size.as_logical()); if let Some(RestoreTilingState { parent, sibling, orientation, idx, mut sizes, }) = tiling_state { if let Some(node) = parent.as_ref().and_then(|parent| tree.get_mut(parent).ok()) { if let Data::Group { orientation: current_orientation, sizes: current_sizes, .. } = node.data_mut() { let parent_id = parent.unwrap(); if *current_orientation == orientation && sizes.len() == current_sizes.len() + 1 { let previous_length: i32 = sizes.iter().copied().sum(); let new_length: i32 = current_sizes.iter().copied().sum(); if previous_length != new_length { // rescale sizes sizes.iter_mut().for_each(|len| { *len = (((*len as f64) / (previous_length as f64)) * (new_length as f64)) .round() as i32; }); let sum: i32 = sizes.iter().sum(); // fix rounding issues if sum != new_length { let diff = new_length - sum; *sizes.last_mut().unwrap() += diff; } } } *current_sizes = sizes; let new_node = Node::new(Data::Mapped { mapped: window.clone(), last_geometry: Rectangle::from_size((100, 100).into()), minimize_rect: from, }); let new_id = tree .insert(new_node, InsertBehavior::UnderNode(&parent_id)) .unwrap(); tree.make_nth_sibling(&new_id, idx).unwrap(); *window.tiling_node_id.lock().unwrap() = Some(new_id); let blocker = TilingLayout::update_positions(&self.output, &mut tree, gaps); self.queue .push_tree(tree, MINIMIZE_ANIMATION_DURATION, blocker); return; } } if sibling .as_ref() .is_some_and(|sibling| tree.get(sibling).is_ok()) { let sibling_id = sibling.unwrap(); let new_node = Node::new(Data::Mapped { mapped: window.clone(), last_geometry: Rectangle::from_size((100, 100).into()), minimize_rect: from, }); let new_id = tree.insert(new_node, InsertBehavior::AsRoot).unwrap(); let group_id = TilingLayout::new_group(&mut tree, &sibling_id, &new_id, orientation).unwrap(); tree.make_nth_sibling(&new_id, idx).unwrap(); if let Data::Group { sizes: default_sizes, last_geometry, .. } = tree.get_mut(&group_id).unwrap().data_mut() { match orientation { Orientation::Horizontal => { last_geometry.size.h = sizes.iter().copied().sum() } Orientation::Vertical => last_geometry.size.w = sizes.iter().copied().sum(), }; *default_sizes = sizes; } *window.tiling_node_id.lock().unwrap() = Some(new_id); let blocker = TilingLayout::update_positions(&self.output, &mut tree, gaps); self.queue .push_tree(tree, MINIMIZE_ANIMATION_DURATION, blocker); return; } } // else add as new_window self.map_internal(window, focus_stack, None, from); } fn map_to_tree( tree: &mut Tree, window: impl Into, output: &Output, node: Option, direction: Option, minimize_rect: Option>, ) { let window = window.into(); let new_window = Node::new(Data::Mapped { mapped: window.clone(), last_geometry: Rectangle::from_size((100, 100).into()), minimize_rect, }); let window_id = if let Some(direction) = direction { if let Some(root_id) = tree.root_node_id().cloned() { let orientation = match direction { Direction::Left | Direction::Right => Orientation::Vertical, Direction::Up | Direction::Down => Orientation::Horizontal, }; let new_id = tree.insert(new_window, InsertBehavior::AsRoot).unwrap(); TilingLayout::new_group(tree, &root_id, &new_id, orientation).unwrap(); tree.make_nth_sibling( &new_id, match direction { Direction::Left | Direction::Up => 1, Direction::Right | Direction::Down => 0, }, ) .unwrap(); new_id } else { tree.insert(new_window, InsertBehavior::AsRoot).unwrap() } } else if let Some(ref node_id) = node { let orientation = { let window_size = tree.get(node_id).unwrap().data().geometry().size; if window_size.w > window_size.h { Orientation::Vertical } else { Orientation::Horizontal } }; let new_id = tree.insert(new_window, InsertBehavior::AsRoot).unwrap(); TilingLayout::new_group(tree, node_id, &new_id, orientation).unwrap(); new_id } else { // nothing? then we add to the root if let Some(root_id) = tree.root_node_id().cloned() { let orientation = { let output_size = output.geometry().size; if output_size.w > output_size.h { Orientation::Vertical } else { Orientation::Horizontal } }; let new_id = tree.insert(new_window, InsertBehavior::AsRoot).unwrap(); TilingLayout::new_group(tree, &root_id, &new_id, orientation).unwrap(); new_id } else { tree.insert(new_window, InsertBehavior::AsRoot).unwrap() } }; *window.tiling_node_id.lock().unwrap() = Some(window_id); } pub fn replace_window(&mut self, old: &CosmicMapped, new: &CosmicMapped) { let gaps = self.gaps(); let Some(old_id) = old.tiling_node_id.lock().unwrap().clone() else { return; }; let mut tree = self.queue.trees.back().unwrap().0.copy_clone(); if let Ok(node) = tree.get_mut(&old_id) { let data = node.data_mut(); match data { Data::Mapped { mapped, .. } => { assert_eq!(mapped, old); *mapped = new.clone(); *new.tiling_node_id.lock().unwrap() = Some(old_id); *old.tiling_node_id.lock().unwrap() = None; } _ => unreachable!("Tiling id points to group"), } old.output_leave(&self.output); new.output_enter(&self.output, new.bbox()); let blocker = TilingLayout::update_positions(&self.output, &mut tree, gaps); self.queue.push_tree(tree, ANIMATION_DURATION, blocker); } } pub fn move_tree<'a>( this: &mut Self, other: &mut Self, other_handle: &WorkspaceHandle, seat: &Seat, focus_stack: impl Iterator + 'a, desc: NodeDesc, direction: Option, ) -> Option { let this_handle = &desc.handle; let mut this_tree = this.queue.trees.back().unwrap().0.copy_clone(); let mut other_tree = other.queue.trees.back().unwrap().0.copy_clone(); match desc.stack_window { Some(stack_surface) => { let node = this_tree.get(&desc.node).ok()?; let Data::Mapped { mapped: this_mapped, .. } = node.data() else { return None; }; let this_stack = this_mapped.stack_ref()?; this_stack.remove_window(&stack_surface); if !this_stack.alive() { let _ = this.unmap(this_mapped, None); } let mapped: CosmicMapped = CosmicWindow::new( stack_surface, this_stack.loop_handle(), this.theme.clone(), this.appearance, ) .into(); if this.output != other.output { mapped.output_leave(&this.output); mapped.output_enter(&other.output, mapped.bbox()); for (ref surface, _) in mapped.windows() { toplevel_leave_output(surface, &this.output); toplevel_enter_output(surface, &other.output); } } for (ref surface, _) in mapped.windows() { toplevel_leave_workspace(surface, this_handle); toplevel_enter_workspace(surface, other_handle); } mapped.set_tiled(true); other.map(mapped.clone(), Some(focus_stack), direction); Some(KeyboardFocusTarget::Element(mapped)) } None => { let node = this_tree.get(&desc.node).ok()?; let mut children = node .children() .iter() .map(|child_id| (desc.node.clone(), child_id.clone())) .collect::>(); let node = Node::new(node.data().clone()); let id = match other_tree.root_node_id() { None => other_tree.insert(node, InsertBehavior::AsRoot).unwrap(), Some(_) => { let focused_node = seat .get_keyboard() .unwrap() .current_focus() .and_then(|target| { TilingLayout::currently_focused_node(&other_tree, target) }) .map(|(id, _)| id) .unwrap_or(other_tree.root_node_id().unwrap().clone()); let orientation = if let Some(direction) = direction { match direction { Direction::Left | Direction::Right => Orientation::Vertical, Direction::Up | Direction::Down => Orientation::Horizontal, } } else { let window_size = other_tree .get(&focused_node) .unwrap() .data() .geometry() .size; if window_size.w > window_size.h { Orientation::Vertical } else { Orientation::Horizontal } }; let id = other_tree.insert(node, InsertBehavior::AsRoot).unwrap(); TilingLayout::new_group(&mut other_tree, &focused_node, &id, orientation) .unwrap(); other_tree .make_nth_sibling( &id, match direction { Some(Direction::Right) | Some(Direction::Down) => 0, _ => 1, }, ) .unwrap(); id } }; for (parent_id, _) in children.iter_mut() { *parent_id = id.clone(); } if let Data::Mapped { mapped, .. } = other_tree.get_mut(&id).unwrap().data_mut() { if this.output != other.output { mapped.output_leave(&this.output); mapped.output_enter(&other.output, mapped.bbox()); for (ref surface, _) in mapped.windows() { toplevel_leave_output(surface, &this.output); toplevel_enter_output(surface, &other.output); } } for (ref surface, _) in mapped.windows() { toplevel_leave_workspace(surface, this_handle); toplevel_enter_workspace(surface, other_handle); } *mapped.tiling_node_id.lock().unwrap() = Some(id.clone()); } while !children.is_empty() { for (parent_id, child_id) in std::mem::take(&mut children) { let new_children = this_tree .children_ids(&child_id) .unwrap() .cloned() .collect::>(); let child_node = this_tree .remove_node(child_id, RemoveBehavior::OrphanChildren) .unwrap(); let maybe_mapped = match child_node.data() { Data::Mapped { mapped, .. } => Some(mapped.clone()), _ => None, }; let new_id = other_tree .insert(child_node, InsertBehavior::UnderNode(&parent_id)) .unwrap(); if let Some(mapped) = maybe_mapped { if this.output != other.output { mapped.output_leave(&this.output); mapped.output_enter(&other.output, mapped.bbox()); for (ref surface, _) in mapped.windows() { toplevel_leave_output(surface, &this.output); toplevel_enter_output(surface, &other.output); } } for (ref surface, _) in mapped.windows() { toplevel_leave_workspace(surface, this_handle); toplevel_enter_workspace(surface, other_handle); } *mapped.tiling_node_id.lock().unwrap() = Some(new_id.clone()); } children.extend( new_children .into_iter() .map(|new_child| (new_id.clone(), new_child)), ); } } let this_gaps = this.gaps(); let other_gaps = other.gaps(); TilingLayout::unmap_internal(&mut this_tree, &desc.node); let blocker = TilingLayout::update_positions(&this.output, &mut this_tree, this_gaps); this.queue.push_tree(this_tree, ANIMATION_DURATION, blocker); let blocker = TilingLayout::update_positions(&other.output, &mut other_tree, other_gaps); other .queue .push_tree(other_tree, ANIMATION_DURATION, blocker); other.node_desc_to_focus(&NodeDesc { handle: *other_handle, node: id.clone(), stack_window: None, focus_stack: Vec::new(), // node_desc_to_focus doesn't use this }) } } } pub fn swap_trees( this: &mut Self, mut other: Option<&mut Self>, this_desc: &NodeDesc, other_desc: &NodeDesc, ) -> Option { let other_output = other .as_ref() .map(|other| other.output.clone()) .unwrap_or(this.output.clone()); if this.output == other_output && this_desc.handle == other_desc.handle && this_desc.node == other_desc.node && (this_desc.stack_window == other_desc.stack_window || this_desc.stack_window.is_some() != other_desc.stack_window.is_some()) { return None; } let mut this_tree = this.queue.trees.back().unwrap().0.copy_clone(); let mut other_tree = match other.as_mut() { Some(other) => Some(other.queue.trees.back().unwrap().0.copy_clone()), None => { if this.output != other_output { Some(this.queue.trees.back().unwrap().0.copy_clone()) } else { None } } }; match (&this_desc.stack_window, &other_desc.stack_window) { (None, None) if other_tree.is_none() => { if this_desc.node != other_desc.node { this_tree .swap_nodes( &this_desc.node, &other_desc.node, id_tree::SwapBehavior::TakeChildren, ) .unwrap(); } } (None, None) => { let Some(other_tree) = other_tree.as_mut() else { unreachable!() }; let this_node = this_tree.get_mut(&this_desc.node).ok()?; let other_node = other_tree.get_mut(&other_desc.node).ok()?; if let Data::Mapped { mapped, .. } = this_node.data_mut() { if this.output != other_output { mapped.output_leave(&this.output); mapped.output_enter(&other_output, mapped.bbox()); for (ref surface, _) in mapped.windows() { toplevel_leave_output(surface, &this.output); toplevel_enter_output(surface, &other_output); } } for (ref surface, _) in mapped.windows() { toplevel_leave_workspace(surface, &this_desc.handle); toplevel_enter_workspace(surface, &other_desc.handle); } *mapped.tiling_node_id.lock().unwrap() = Some(other_desc.node.clone()); } if let Data::Mapped { mapped, .. } = other_node.data_mut() { if this.output != other_output { mapped.output_leave(&other_output); mapped.output_enter(&this.output, mapped.bbox()); for (ref surface, _) in mapped.windows() { toplevel_leave_output(surface, &other_output); toplevel_enter_output(surface, &this.output); } } for (ref surface, _) in mapped.windows() { toplevel_leave_workspace(surface, &other_desc.handle); toplevel_enter_workspace(surface, &this_desc.handle); } *mapped.tiling_node_id.lock().unwrap() = Some(this_desc.node.clone()); } // swap data let other_data = other_node.data().clone(); other_node.replace_data(this_node.replace_data(other_data)); // swap children let mut this_children = this_node .children() .iter() .map(|child_id| (other_desc.node.clone(), child_id.clone())) .collect::>(); let mut other_children = other_node .children() .iter() .map(|child_id| (this_desc.node.clone(), child_id.clone())) .collect::>(); while !this_children.is_empty() { for (parent_id, child_id) in std::mem::take(&mut this_children) { let new_children = this_tree .children_ids(&child_id) .unwrap() .cloned() .collect::>(); let child_node = this_tree .remove_node(child_id, RemoveBehavior::OrphanChildren) .unwrap(); let maybe_mapped = match child_node.data() { Data::Mapped { mapped, .. } => Some(mapped.clone()), _ => None, }; let new_id = other_tree .insert(child_node, InsertBehavior::UnderNode(&parent_id)) .unwrap(); if let Some(mapped) = maybe_mapped { if this.output != other_output { mapped.output_leave(&this.output); mapped.output_enter(&other_output, mapped.bbox()); for (ref surface, _) in mapped.windows() { toplevel_leave_output(surface, &this.output); toplevel_enter_output(surface, &other_output); } } for (ref surface, _) in mapped.windows() { toplevel_leave_workspace(surface, &this_desc.handle); toplevel_enter_workspace(surface, &other_desc.handle); } *mapped.tiling_node_id.lock().unwrap() = Some(new_id.clone()); } this_children.extend( new_children .into_iter() .map(|new_child| (new_id.clone(), new_child)), ); } } while !other_children.is_empty() { for (parent_id, child_id) in std::mem::take(&mut other_children) { let new_children = other_tree .children_ids(&child_id) .unwrap() .cloned() .collect::>(); let child_node = other_tree .remove_node(child_id, RemoveBehavior::OrphanChildren) .unwrap(); let maybe_mapped = match child_node.data() { Data::Mapped { mapped, .. } => Some(mapped.clone()), _ => None, }; let new_id = this_tree .insert(child_node, InsertBehavior::UnderNode(&parent_id)) .unwrap(); if let Some(mapped) = maybe_mapped { if this.output != other_output { mapped.output_leave(&other_output); mapped.output_enter(&this.output, mapped.bbox()); for (ref surface, _) in mapped.windows() { toplevel_leave_output(surface, &other_output); toplevel_enter_output(surface, &this.output); } } for (ref surface, _) in mapped.windows() { toplevel_leave_workspace(surface, &other_desc.handle); toplevel_enter_workspace(surface, &this_desc.handle); } *mapped.tiling_node_id.lock().unwrap() = Some(new_id.clone()); } other_children.extend( new_children .into_iter() .map(|new_child| (new_id.clone(), new_child)), ); } } } (Some(this_surface), None) => { if !this_surface.alive() { return None; } let this_node = this_tree.get_mut(&this_desc.node).ok()?; let Data::Mapped { mapped: this_mapped, .. } = this_node.data() else { return None; }; let this_mapped = this_mapped.clone(); let geometry = *this_node.data().geometry(); assert!(this_mapped.is_stack()); let other_tree = other_tree.as_mut().unwrap_or(&mut this_tree); if other_tree.get(&other_desc.node).is_err() { return None; } let surfaces = other_tree .traverse_pre_order(&other_desc.node) .unwrap() .flat_map(|node| match node.data() { Data::Mapped { mapped, .. } => Some(mapped.windows().map(|(s, _)| s)), _ => None, }) .flatten() .collect::>(); let cleanup = other_tree .children_ids(&other_desc.node) .unwrap() .cloned() .collect::>(); for node in cleanup { let _ = other_tree.remove_node(node, RemoveBehavior::DropChildren); } let this_stack = this_mapped.stack_ref()?; let this_idx = this_stack .surfaces() .position(|s| &s == this_surface) .unwrap(); for (i, surface) in surfaces.into_iter().enumerate() { if this.output != other_output { surface.output_leave(&other_output); surface.output_enter(&this.output, surface.bbox()); toplevel_leave_output(&surface, &other_output); toplevel_enter_output(&surface, &this.output); } if this_desc.handle != other_desc.handle { toplevel_leave_workspace(&surface, &other_desc.handle); toplevel_enter_workspace(&surface, &this_desc.handle); } this_stack.add_window(surface, Some(this_idx + i), None); } if this.output != other_output { this_surface.output_leave(&this.output); toplevel_leave_output(this_surface, &this.output); toplevel_enter_output(this_surface, &other_output); } if this_desc.handle != other_desc.handle { toplevel_leave_workspace(this_surface, &this_desc.handle); toplevel_enter_workspace(this_surface, &other_desc.handle); } this_stack.remove_window(this_surface); let mapped: CosmicMapped = CosmicWindow::new( this_surface.clone(), this_stack.loop_handle(), this.theme.clone(), this.appearance, ) .into(); mapped.set_tiled(true); mapped.refresh(); mapped.output_enter(&other_output, mapped.bbox()); *mapped.tiling_node_id.lock().unwrap() = Some(other_desc.node.clone()); other_tree .get_mut(&other_desc.node) .unwrap() .replace_data(Data::Mapped { mapped, last_geometry: geometry, minimize_rect: None, }); } (None, Some(other_surface)) => { if !other_surface.alive() { return None; } let other_tree = other_tree.as_mut().unwrap_or(&mut this_tree); let other_node = other_tree.get_mut(&other_desc.node).ok()?; let Data::Mapped { mapped: other_mapped, .. } = other_node.data() else { return None; }; let other_mapped = other_mapped.clone(); let geometry = *other_node.data().geometry(); assert!(other_mapped.is_stack()); let surfaces = this_tree .traverse_pre_order(&this_desc.node) .unwrap() .flat_map(|node| match node.data() { Data::Mapped { mapped, .. } => Some(mapped.windows().map(|(s, _)| s)), _ => None, }) .flatten() .collect::>(); let cleanup = this_tree .children_ids(&this_desc.node) .unwrap() .cloned() .collect::>(); for node in cleanup { let _ = this_tree.remove_node(node, RemoveBehavior::DropChildren); } let other_stack = other_mapped.stack_ref()?; let other_idx = other_stack .surfaces() .position(|s| &s == other_surface) .unwrap(); for (i, surface) in surfaces.into_iter().enumerate() { if this.output != other_output { surface.output_leave(&this.output); surface.output_enter(&other_output, surface.bbox()); toplevel_leave_output(&surface, &this.output); toplevel_enter_output(&surface, &other_output); } if this_desc.handle != other_desc.handle { toplevel_leave_workspace(&surface, &this_desc.handle); toplevel_enter_workspace(&surface, &other_desc.handle); } other_stack.add_window(surface, Some(other_idx + i), None); } if this.output != other_output { other_surface.output_leave(&other_output); toplevel_leave_output(other_surface, &other_output); toplevel_enter_output(other_surface, &this.output); } if this_desc.handle != other_desc.handle { toplevel_leave_workspace(other_surface, &other_desc.handle); toplevel_enter_workspace(other_surface, &this_desc.handle); } other_stack.remove_window(other_surface); let mapped: CosmicMapped = CosmicWindow::new( other_surface.clone(), other_stack.loop_handle(), this.theme.clone(), this.appearance, ) .into(); mapped.set_tiled(true); mapped.refresh(); mapped.output_enter(&this.output, mapped.bbox()); *mapped.tiling_node_id.lock().unwrap() = Some(this_desc.node.clone()); this_tree .get_mut(&this_desc.node) .unwrap() .replace_data(Data::Mapped { mapped, last_geometry: geometry, minimize_rect: None, }); } (Some(this_surface), Some(other_surface)) => { if !this_surface.alive() || !other_surface.alive() { return None; } let this_mapped = this_tree .get(&this_desc.node) .ok() .and_then(|node| match node.data() { Data::Mapped { mapped, .. } => Some(mapped.clone()), _ => None, })?; let other_tree = other_tree.as_mut().unwrap_or(&mut this_tree); let other_mapped = other_tree .get(&other_desc.node) .ok() .and_then(|node| match node.data() { Data::Mapped { mapped, .. } => Some(mapped.clone()), _ => None, })?; let this_stack = this_mapped.stack_ref()?; let other_stack = other_mapped.stack_ref()?; let this_idx = this_stack .surfaces() .position(|s| &s == this_surface) .unwrap(); let other_idx = other_stack .surfaces() .position(|s| &s == other_surface) .unwrap(); let this_was_active = &this_stack.active() == this_surface; let other_was_active = &other_stack.active() == other_surface; this_stack.add_window(other_surface.clone(), Some(this_idx), None); this_stack.remove_window(this_surface); other_stack.add_window(this_surface.clone(), Some(other_idx), None); if this.output != other_output { toplevel_leave_output(this_surface, &this.output); toplevel_leave_output(other_surface, &other_output); toplevel_enter_output(this_surface, &other_output); toplevel_enter_output(other_surface, &this.output); } if this_desc.handle != other_desc.handle { toplevel_leave_workspace(this_surface, &this_desc.handle); toplevel_leave_workspace(other_surface, &other_desc.handle); toplevel_enter_workspace(this_surface, &other_desc.handle); toplevel_enter_workspace(other_surface, &this_desc.handle); } other_stack.remove_window(other_surface); if this_was_active { this_stack.set_active(other_surface); } if other_was_active { other_stack.set_active(this_surface); } return other .as_ref() .unwrap_or(&this) .node_desc_to_focus(other_desc); } } let this_gaps = this.gaps(); let blocker = TilingLayout::update_positions(&this.output, &mut this_tree, this_gaps); this.queue.push_tree(this_tree, ANIMATION_DURATION, blocker); let has_other_tree = other_tree.is_some(); if let Some(mut other_tree) = other_tree { let (other_queue, gaps) = if let Some(other) = other.as_mut() { let other_gaps = other.gaps(); (&mut other.queue, other_gaps) } else { (&mut this.queue, this_gaps) }; let blocker = TilingLayout::update_positions(&other_output, &mut other_tree, gaps); other_queue.push_tree(other_tree, ANIMATION_DURATION, blocker); } match (&this_desc.stack_window, &other_desc.stack_window) { (None, None) if !has_other_tree => this.node_desc_to_focus(this_desc), //(None, Some(_)) => None, _ => other .as_ref() .unwrap_or(&this) .node_desc_to_focus(other_desc), } } pub fn node_desc_to_focus(&self, desc: &NodeDesc) -> Option { let tree = &self.queue.trees.back().unwrap().0; let data = tree.get(&desc.node).ok()?.data(); match data { Data::Mapped { mapped, .. } => Some(KeyboardFocusTarget::Element(mapped.clone())), Data::Group { alive, .. } => Some(KeyboardFocusTarget::Group(WindowGroup { node: desc.node.clone(), alive: Arc::downgrade(alive), focus_stack: tree .children_ids(&desc.node) .unwrap() .cloned() .collect::>(), })), _ => None, } } pub fn tree(&self) -> &Tree { &self.queue.trees.back().unwrap().0 } pub fn unmap( &mut self, window: &CosmicMapped, to: Option>, ) -> Result, NodeIdError> { let node_id = window .tiling_node_id .lock() .unwrap() .clone() .ok_or(NodeIdError::NodeIdNoLongerValid)?; let state = { let tree = &self.queue.trees.back().unwrap().0; tree.get(&node_id).unwrap().parent().and_then(|parent_id| { let parent = tree.get(parent_id).unwrap(); let idx = parent .children() .iter() .position(|id| id == &node_id) .unwrap(); if let Data::Group { orientation, sizes, .. } = parent.data() { if sizes.len() == 2 { // this group will be flattened Some(RestoreTilingState { parent: None, sibling: parent.children().iter().find(|&id| id != &node_id).cloned(), orientation: *orientation, idx, sizes: sizes.clone(), }) } else { Some(RestoreTilingState { parent: Some(parent_id.clone()), sibling: None, orientation: *orientation, idx, sizes: sizes.clone(), }) } } else { None } }) }; if self.unmap_window_internal(window, to.is_some()) { if let Some(to) = to { let tree = &mut self .queue .trees .get_mut(self.queue.trees.len() - 2) .unwrap() .0; if let Data::Mapped { minimize_rect: minimize_to, .. } = tree.get_mut(&node_id).unwrap().data_mut() { *minimize_to = Some(to); } } window.output_leave(&self.output); window.set_tiled(false); *window.tiling_node_id.lock().unwrap() = None; } else { return Err(NodeIdError::InvalidNodeIdForTree); } Ok(state) } pub fn unmap_as_placeholder( &mut self, window: &CosmicMapped, type_: PlaceholderType, ) -> Option { let node_id = window.tiling_node_id.lock().unwrap().take()?; // Initialize last_overview_hover to the placeholder position so that // dropping without mouse movement restores the window to its original position if matches!(type_, PlaceholderType::GrabbedWindow) { self.last_overview_hover = Some((None, TargetZone::InitialPlaceholder(node_id.clone()))); } let data = self .queue .trees .back_mut() .unwrap() .0 .get_mut(&node_id) .unwrap() .data_mut(); *data = Data::Placeholder { id: Id::new(), last_geometry: *data.geometry(), type_, }; window.output_leave(&self.output); window.set_tiled(false); Some(node_id) } fn unmap_window_internal(&mut self, mapped: &CosmicMapped, minimizing: bool) -> bool { let tiling_node_id = mapped.tiling_node_id.lock().unwrap().as_ref().cloned(); let gaps = self.gaps(); if let Some(node_id) = tiling_node_id { if self .queue .trees .back() .unwrap() .0 .get(&node_id) .map(|node| node.data().is_mapped(Some(mapped))) .unwrap_or(false) { let mut tree = self.queue.trees.back().unwrap().0.copy_clone(); TilingLayout::unmap_internal(&mut tree, &node_id); let duration = if minimizing { MINIMIZE_ANIMATION_DURATION } else { ANIMATION_DURATION }; let blocker = TilingLayout::update_positions(&self.output, &mut tree, gaps); self.queue.push_tree(tree, duration, blocker); return true; } } false } fn unmap_internal(tree: &mut Tree, node: &NodeId) { let parent_id = tree.get(node).ok().and_then(|node| node.parent()).cloned(); let position = parent_id.as_ref().and_then(|parent_id| { tree.children_ids(parent_id) .unwrap() .position(|id| id == node) }); let parent_parent_id = parent_id.as_ref().and_then(|parent_id| { tree.get(parent_id) .ok() .and_then(|node| node.parent()) .cloned() }); // remove self trace!(?node, "Removing node."); let _ = tree.remove_node(node.clone(), RemoveBehavior::DropChildren); // fixup parent node match parent_id { Some(id) => { let position = position.unwrap(); let group = tree.get_mut(&id).unwrap().data_mut(); assert!(group.is_group()); if group.len() > 2 { group.remove_window(position); } else { trace!("Removing Group"); let other_child = tree.children_ids(&id).unwrap().next().cloned().unwrap(); let fork_pos = parent_parent_id.as_ref().and_then(|parent_id| { tree.children_ids(parent_id).unwrap().position(|i| i == &id) }); let _ = tree.remove_node(id.clone(), RemoveBehavior::OrphanChildren); tree.move_node( &other_child, parent_parent_id .as_ref() .map(MoveBehavior::ToParent) .unwrap_or(MoveBehavior::ToRoot), ) .unwrap(); if let Some(old_pos) = fork_pos { tree.make_nth_sibling(&other_child, old_pos).unwrap(); } } } None => {} // root } } // TODO: Move would needs this to be accurate during animations pub fn element_geometry(&self, elem: &CosmicMapped) -> Option> { if let Some(id) = elem.tiling_node_id.lock().unwrap().as_ref() { let node = self.queue.trees.back().unwrap().0.get(id).ok()?; let data = node.data(); assert!(data.is_mapped(Some(elem))); let geo = *data.geometry(); Some(geo) } else { None } } pub fn move_current_node(&mut self, direction: Direction, seat: &Seat) -> MoveResult { let gaps = self.gaps(); let mut tree = self.queue.trees.back().unwrap().0.copy_clone(); let Some(target) = seat.get_keyboard().unwrap().current_focus() else { return MoveResult::None; }; let Some((node_id, data)) = TilingLayout::currently_focused_node(&tree, target) else { return MoveResult::None; }; // stacks may handle movement internally if let FocusedNodeData::Window(window) = data.clone() { match window.handle_move(direction) { StackMoveResult::Handled => return MoveResult::Done, StackMoveResult::MoveOut(surface, loop_handle) => { let mapped: CosmicMapped = CosmicWindow::new( surface, loop_handle, self.theme.clone(), self.appearance, ) .into(); mapped.output_enter(&self.output, mapped.bbox()); let orientation = match direction { Direction::Left | Direction::Right => Orientation::Vertical, Direction::Up | Direction::Down => Orientation::Horizontal, }; let new_node = Node::new(Data::Mapped { mapped: mapped.clone(), last_geometry: Rectangle::from_size((100, 100).into()), minimize_rect: None, }); let new_id = tree.insert(new_node, InsertBehavior::AsRoot).unwrap(); TilingLayout::new_group(&mut tree, &node_id, &new_id, orientation).unwrap(); tree.make_nth_sibling( &new_id, match direction { Direction::Left | Direction::Up => 0, Direction::Right | Direction::Down => 1, }, ) .unwrap(); *mapped.tiling_node_id.lock().unwrap() = Some(new_id); let blocker = TilingLayout::update_positions(&self.output, &mut tree, gaps); self.queue.push_tree(tree, ANIMATION_DURATION, blocker); return MoveResult::ShiftFocus(mapped.into()); } StackMoveResult::Default => {} // continue normally } } let mut child_id = node_id.clone(); // Without a parent to start with, just return let Some(og_parent) = tree.get(&node_id).unwrap().parent().cloned() else { return match data { FocusedNodeData::Window(window) => MoveResult::MoveFurther(window.into()), FocusedNodeData::Group(focus_stack, alive) => MoveResult::MoveFurther( WindowGroup { node: node_id, alive, focus_stack, } .into(), ), }; }; let og_idx = tree .children_ids(&og_parent) .unwrap() .position(|id| id == &child_id) .unwrap(); let mut maybe_parent = Some(og_parent.clone()); while let Some(parent) = maybe_parent { let parent_data = tree.get(&parent).unwrap().data(); let orientation = parent_data.orientation(); let len = parent_data.len(); // which child are we? let idx = tree .children_ids(&parent) .unwrap() .position(|id| id == &child_id) .unwrap(); // if the orientation does not match.. if matches!( (orientation, direction), (Orientation::Horizontal, Direction::Right) | (Orientation::Horizontal, Direction::Left) | (Orientation::Vertical, Direction::Up) | (Orientation::Vertical, Direction::Down) ) { // ...create a new group with our parent (cleanup will remove any one-child-groups afterwards) TilingLayout::new_group( &mut tree, &parent, &node_id, match direction { Direction::Left | Direction::Right => Orientation::Vertical, Direction::Up | Direction::Down => Orientation::Horizontal, }, ) .unwrap(); tree.make_nth_sibling( &node_id, if direction == Direction::Left || direction == Direction::Up { 0 } else { 1 }, ) .unwrap(); tree.get_mut(&og_parent) .unwrap() .data_mut() .remove_window(og_idx); let blocker = TilingLayout::update_positions(&self.output, &mut tree, gaps); self.queue.push_tree(tree, ANIMATION_DURATION, blocker); return MoveResult::Done; } // now if the orientation matches // if we are not already in this group, we just move into it (up) if child_id != node_id { tree.move_node(&node_id, MoveBehavior::ToParent(&parent)) .unwrap(); tree.make_nth_sibling( &node_id, if direction == Direction::Left || direction == Direction::Up { idx } else { idx + 1 }, ) .unwrap(); tree.get_mut(&parent).unwrap().data_mut().add_window(idx); tree.get_mut(&og_parent) .unwrap() .data_mut() .remove_window(og_idx); let blocker = TilingLayout::update_positions(&self.output, &mut tree, gaps); self.queue.push_tree(tree, ANIMATION_DURATION, blocker); return MoveResult::Done; } // we can maybe move inside the group, if we don't run out of elements if let Some(next_idx) = match (orientation, direction) { (Orientation::Horizontal, Direction::Down) | (Orientation::Vertical, Direction::Right) if idx < (len - 1) => { Some(idx + 1) } (Orientation::Horizontal, Direction::Up) | (Orientation::Vertical, Direction::Left) if idx > 0 => { Some(idx - 1) } _ => None, } { // if we can, we need to check the next element and move "into" it let next_child_id = tree .children_ids(&parent) .unwrap() .nth(next_idx) .unwrap() .clone(); let result = if tree.get(&next_child_id).unwrap().data().is_stack() && tree.get(&node_id).unwrap().data().is_mapped(None) && !tree.get(&node_id).unwrap().data().is_stack() && len == 2 { let node = tree .remove_node(node_id, RemoveBehavior::DropChildren) .unwrap(); let stack_data = tree.get_mut(&next_child_id).unwrap().data_mut(); let mapped = match stack_data { Data::Mapped { mapped, .. } => mapped.clone(), _ => unreachable!(), }; let stack = mapped.stack_ref().unwrap(); let surface = match node.data() { Data::Mapped { mapped, .. } => mapped.active_window(), _ => unreachable!(), }; stack.add_window( surface, match direction { Direction::Right => Some(0), _ => None, }, Some(seat), ); tree.get_mut(&og_parent) .unwrap() .data_mut() .remove_window(og_idx); MoveResult::ShiftFocus(mapped.into()) } else if tree.get(&next_child_id).unwrap().data().is_group() && len == 2 { // if it is a group, we want to move into the group tree.move_node(&node_id, MoveBehavior::ToParent(&next_child_id)) .unwrap(); let group_orientation = tree.get(&next_child_id).unwrap().data().orientation(); match (group_orientation, direction) { (Orientation::Horizontal, Direction::Down) | (Orientation::Vertical, Direction::Right) => { tree.make_first_sibling(&node_id).unwrap(); tree.get_mut(&next_child_id) .unwrap() .data_mut() .add_window(0); } (Orientation::Horizontal, Direction::Up) | (Orientation::Vertical, Direction::Left) => { tree.make_last_sibling(&node_id).unwrap(); let group = tree.get_mut(&next_child_id).unwrap().data_mut(); group.add_window(group.len()); } _ => { // we want the middle let group_len = tree.get(&next_child_id).unwrap().data().len(); if group_len % 2 == 0 { tree.make_nth_sibling(&node_id, group_len / 2).unwrap(); tree.get_mut(&next_child_id) .unwrap() .data_mut() .add_window(group_len / 2); } else { // we move again by making a new fork let old_id = tree .children_ids(&next_child_id) .unwrap() .nth(group_len / 2) .unwrap() .clone(); TilingLayout::new_group( &mut tree, &old_id, &node_id, !group_orientation, ) .unwrap(); tree.make_nth_sibling( &node_id, if direction == Direction::Left || direction == Direction::Up { 1 } else { 0 }, ) .unwrap(); } } }; tree.get_mut(&og_parent) .unwrap() .data_mut() .remove_window(og_idx); MoveResult::Done } else if len == 2 && child_id == node_id { // if we are just us two in the group, lets swap tree.make_nth_sibling(&node_id, next_idx).unwrap(); // also swap sizes tree.get_mut(&og_parent) .unwrap() .data_mut() .swap_windows(idx, next_idx); MoveResult::Done } else { // else we make a new fork TilingLayout::new_group(&mut tree, &next_child_id, &node_id, orientation) .unwrap(); tree.make_nth_sibling( &node_id, if direction == Direction::Left || direction == Direction::Up { 1 } else { 0 }, ) .unwrap(); tree.get_mut(&og_parent) .unwrap() .data_mut() .remove_window(og_idx); MoveResult::Done }; let blocker = TilingLayout::update_positions(&self.output, &mut tree, gaps); self.queue.push_tree(tree, ANIMATION_DURATION, blocker); return result; } // We have reached the end of our parent group, try to move out even higher. maybe_parent = tree.get(&parent).unwrap().parent().cloned(); child_id = parent.clone(); } match data { FocusedNodeData::Window(window) => MoveResult::MoveFurther(window.into()), FocusedNodeData::Group(focus_stack, alive) => MoveResult::MoveFurther( WindowGroup { node: node_id, alive, focus_stack, } .into(), ), } } pub fn next_focus<'a>( &self, direction: FocusDirection, seat: &Seat, focus_stack: impl Iterator + 'a, swap_desc: Option, ) -> FocusResult { let tree = &self.queue.trees.back().unwrap().0; let Some(target) = seat.get_keyboard().unwrap().current_focus() else { return FocusResult::None; }; let Some(focused) = TilingLayout::currently_focused_node(tree, target).or_else(|| { TilingLayout::last_active_window(tree, focus_stack) .map(|(id, mapped)| (id, FocusedNodeData::Window(mapped))) }) else { return FocusResult::None; }; let (last_node_id, data) = focused; if direction == FocusDirection::In { if swap_desc .as_ref() .map(|desc| desc.node == last_node_id) .unwrap_or(false) { return FocusResult::Handled; // abort } if let FocusedNodeData::Group(mut stack, _) = data.clone() { let maybe_id = stack.pop().unwrap(); let id = if tree .children_ids(&last_node_id) .unwrap() .any(|id| id == &maybe_id) { Some(maybe_id) } else { tree.children_ids(&last_node_id).unwrap().next().cloned() }; if let Some(id) = id { return match tree.get(&id).unwrap().data() { Data::Mapped { mapped, .. } => { if mapped.is_stack() { mapped.stack_ref().unwrap().focus_stack(); } FocusResult::Some(mapped.clone().into()) } Data::Group { alive, .. } => FocusResult::Some( WindowGroup { node: id, alive: Arc::downgrade(alive), focus_stack: stack, } .into(), ), Data::Placeholder { .. } => FocusResult::None, }; } } } let mut node_id = last_node_id.clone(); while let Some(group) = tree.get(&node_id).unwrap().parent() { let child = node_id.clone(); let group_data = tree.get(group).unwrap().data(); let main_orientation = group_data.orientation(); assert!(group_data.is_group()); if direction == FocusDirection::Out { if swap_desc .as_ref() .map(|desc| { tree.traverse_pre_order_ids(group) .unwrap() .any(|node| node == desc.node) }) .unwrap_or(false) { return FocusResult::Handled; // abort } return FocusResult::Some( WindowGroup { node: group.clone(), alive: match group_data { Data::Group { alive, .. } => Arc::downgrade(alive), _ => unreachable!(), }, focus_stack: match data { FocusedNodeData::Group(mut stack, _) => { stack.push(child); stack } _ => vec![child], }, } .into(), ); } // which child are we? let idx = tree .children_ids(group) .unwrap() .position(|id| id == &child) .unwrap(); let len = group_data.len(); let focus_subtree = match (main_orientation, direction) { (Orientation::Horizontal, FocusDirection::Down) | (Orientation::Vertical, FocusDirection::Right) if idx < (len - 1) => { tree.children_ids(group).unwrap().nth(idx + 1) } (Orientation::Horizontal, FocusDirection::Up) | (Orientation::Vertical, FocusDirection::Left) if idx > 0 => { tree.children_ids(group).unwrap().nth(idx - 1) } _ => None, // continue iterating }; if focus_subtree.is_some() { let mut node_id = focus_subtree; while node_id.is_some() { if let Some(desc) = swap_desc.as_ref() { if let Some(replacement_id) = tree .ancestor_ids(node_id.unwrap()) .unwrap() .find(|anchestor| *anchestor == &desc.node) .or_else(|| { tree.children_ids(node_id.unwrap()) .unwrap() .find(|child| *child == &desc.node) }) { return match tree.get(replacement_id).unwrap().data() { Data::Group { alive, .. } => { FocusResult::Some(KeyboardFocusTarget::Group(WindowGroup { node: replacement_id.clone(), alive: Arc::downgrade(alive), focus_stack: tree .children_ids(replacement_id) .unwrap() .cloned() .collect(), })) } Data::Mapped { mapped, .. } => { if mapped.is_stack() && desc.stack_window.is_none() && replacement_id == &desc.node { mapped.stack_ref().unwrap().focus_stack(); } FocusResult::Some(KeyboardFocusTarget::Element(mapped.clone())) } _ => unreachable!(), }; } } match tree.get(node_id.unwrap()).unwrap().data() { Data::Group { orientation, .. } if orientation == &main_orientation => { // if the group is layed out in the direction we care about, // we can just use the first or last element (depending on the direction) match direction { FocusDirection::Down | FocusDirection::Right => { node_id = tree .children_ids(node_id.as_ref().unwrap()) .unwrap() .next(); } FocusDirection::Up | FocusDirection::Left => { node_id = tree .children_ids(node_id.as_ref().unwrap()) .unwrap() .last(); } _ => unreachable!(), } } Data::Group { .. } => { let center = { let geo = tree.get(&last_node_id).unwrap().data().geometry(); let mut point = geo.loc; match direction { FocusDirection::Down => { point += Point::from((geo.size.w / 2 - 1, geo.size.h)) } FocusDirection::Up => point.x += geo.size.w / 2 - 1, FocusDirection::Left => point.y += geo.size.h / 2 - 1, FocusDirection::Right => { point += Point::from((geo.size.w, geo.size.h / 2 - 1)) } _ => unreachable!(), }; point.to_f64() }; let distance = |candidate: &&NodeId| -> f64 { let geo = tree.get(candidate).unwrap().data().geometry(); let mut point = geo.loc; match direction { FocusDirection::Up => { point += Point::from((geo.size.w / 2, geo.size.h)) } FocusDirection::Down => point.x += geo.size.w, FocusDirection::Right => point.y += geo.size.h / 2, FocusDirection::Left => { point += Point::from((geo.size.w, geo.size.h / 2)) } _ => unreachable!(), }; let point = point.to_f64(); ((point.x - center.x).powi(2) + (point.y - center.y).powi(2)).sqrt() }; node_id = tree .children_ids(node_id.as_ref().unwrap()) .unwrap() .min_by(|node1, node2| { distance(node1).abs().total_cmp(&distance(node2).abs()) }); } Data::Mapped { mapped, .. } => { if mapped.is_stack() && swap_desc .as_ref() .map(|desc| { desc.stack_window.is_none() && &desc.node == node_id.unwrap() }) .unwrap_or(false) { mapped.stack_ref().unwrap().focus_stack(); } return FocusResult::Some(mapped.clone().into()); } Data::Placeholder { .. } => return FocusResult::None, } } } else { node_id = group.clone(); } } FocusResult::None } pub fn update_orientation(&mut self, new_orientation: Option, seat: &Seat) { let gaps = self.gaps(); let Some(target) = seat.get_keyboard().unwrap().current_focus() else { return; }; let mut tree = self.queue.trees.back().unwrap().0.copy_clone(); if let Some((last_active, _)) = TilingLayout::currently_focused_node(&tree, target) { if let Some(group) = tree.get(&last_active).unwrap().parent().cloned() { if let &mut Data::Group { ref mut orientation, ref mut sizes, ref last_geometry, .. } = tree.get_mut(&group).unwrap().data_mut() { let previous_length = match orientation { Orientation::Horizontal => last_geometry.size.h, Orientation::Vertical => last_geometry.size.w, }; let new_orientation = new_orientation.unwrap_or(!*orientation); let new_length = match new_orientation { Orientation::Horizontal => last_geometry.size.h, Orientation::Vertical => last_geometry.size.w, }; sizes.iter_mut().for_each(|len| { *len = (((*len as f64) / (previous_length as f64)) * (new_length as f64)) .round() as i32; }); let sum: i32 = sizes.iter().sum(); if sum < new_length { *sizes.last_mut().unwrap() += new_length - sum; } *orientation = new_orientation; let blocker = TilingLayout::update_positions(&self.output, &mut tree, gaps); self.queue.push_tree(tree, ANIMATION_DURATION, blocker); } } } } pub fn toggle_stacking( &mut self, mapped: &CosmicMapped, mut focus_stack: FocusStackMut, ) -> Option { let gaps = self.gaps(); let node_id = mapped.tiling_node_id.lock().unwrap().clone()?; let mut tree = self.queue.trees.back().unwrap().0.copy_clone(); if tree.get(&node_id).is_err() { return None; } let result = if mapped.is_window() { // if it is just a window match tree.get_mut(&node_id).unwrap().data_mut() { Data::Mapped { mapped, .. } => { mapped.convert_to_stack( (&self.output, mapped.bbox()), self.theme.clone(), self.appearance, ); focus_stack.append(mapped.clone()); KeyboardFocusTarget::Element(mapped.clone()) } _ => unreachable!(), } } else { // if we have a stack let mut surfaces = mapped.windows().map(|(s, _)| s); let first = surfaces.next().expect("Stack without a window?"); let focused = mapped.active_window(); let mut new_elements = Vec::new(); let handle = match tree.get_mut(&node_id).unwrap().data_mut() { Data::Mapped { mapped, .. } => { let handle = mapped.loop_handle(); mapped.convert_to_surface( first, (&self.output, mapped.bbox()), self.theme.clone(), self.appearance, ); new_elements.push(mapped.clone()); handle } _ => unreachable!(), }; // map the rest let mut current_node = node_id.clone(); for other in surfaces { other.try_force_undecorated(false); other.set_tiled(false); let focused = other == focused; let window = CosmicMapped::from(CosmicWindow::new( other, handle.clone(), self.theme.clone(), self.appearance, )); window.output_enter(&self.output, window.bbox()); { let layer_map = layer_map_for_output(&self.output); window.set_bounds(layer_map.non_exclusive_zone().size); } TilingLayout::map_to_tree( &mut tree, window.clone(), &self.output, Some(current_node), None, None, ); let node = window.tiling_node_id.lock().unwrap().clone().unwrap(); if focused { new_elements.insert(0, window); } else { new_elements.push(window); } current_node = node; } let node = tree.get(&node_id).unwrap(); let node_id = if current_node != node_id { node.parent().cloned().unwrap_or(node_id) } else { node_id }; for elem in new_elements.iter().rev() { focus_stack.append(elem.clone()); } match tree.get(&node_id).unwrap().data() { Data::Group { alive, .. } => KeyboardFocusTarget::Group(WindowGroup { node: node_id.clone(), alive: Arc::downgrade(alive), focus_stack: vec![ new_elements[0] .tiling_node_id .lock() .unwrap() .clone() .unwrap(), ], }), Data::Mapped { mapped, .. } => KeyboardFocusTarget::Element(mapped.clone()), _ => unreachable!(), } }; let blocker = TilingLayout::update_positions(&self.output, &mut tree, gaps); self.queue.push_tree(tree, ANIMATION_DURATION, blocker); Some(result) } pub fn toggle_stacking_focused( &mut self, seat: &Seat, mut focus_stack: FocusStackMut, ) -> Option { let gaps = self.gaps(); let target = seat.get_keyboard().unwrap().current_focus()?; let mut tree = self.queue.trees.back().unwrap().0.copy_clone(); if let Some((last_active, last_active_data)) = TilingLayout::currently_focused_node(&tree, target) { match last_active_data { FocusedNodeData::Window(mapped) => { return self.toggle_stacking(&mapped, focus_stack); } FocusedNodeData::Group(_, _) => { let mut handle = None; let surfaces = tree .traverse_pre_order(&last_active) .unwrap() .flat_map(|node| match node.data() { Data::Mapped { mapped, .. } => { if handle.is_none() { handle = Some(mapped.loop_handle()); } Some(mapped.windows().map(|(s, _)| s)) } _ => None, }) .flatten() .collect::>(); if surfaces.is_empty() { return None; } let handle = handle.unwrap(); let stack = CosmicStack::new( surfaces.into_iter(), handle, self.theme.clone(), self.appearance, ); for child in tree .children_ids(&last_active) .unwrap() .cloned() .collect::>() .into_iter() { tree.remove_node(child, RemoveBehavior::DropChildren) .unwrap(); } let data = tree.get_mut(&last_active).unwrap().data_mut(); let geo = *data.geometry(); stack.output_enter(&self.output, stack.bbox()); stack.set_activate(true); stack.active().send_configure(); stack.refresh(); let mapped = CosmicMapped::from(stack); *mapped.last_geometry.lock().unwrap() = Some(geo); *mapped.tiling_node_id.lock().unwrap() = Some(last_active); focus_stack.append(mapped.clone()); *data = Data::Mapped { mapped: mapped.clone(), last_geometry: geo, minimize_rect: None, }; let blocker = TilingLayout::update_positions(&self.output, &mut tree, gaps); self.queue.push_tree(tree, ANIMATION_DURATION, blocker); return Some(KeyboardFocusTarget::Element(mapped)); } } } None } pub fn recalculate(&mut self) { let gaps = self.gaps(); let mut tree = self.queue.trees.back().unwrap().0.copy_clone(); let blocker = TilingLayout::update_positions(&self.output, &mut tree, gaps); self.queue.push_tree(tree, ANIMATION_DURATION, blocker); } #[profiling::function] pub fn refresh(&mut self) { let dead_windows = self .mapped() .map(|(w, _)| w.clone()) .filter(|w| !w.alive()) .collect::>(); for dead_window in dead_windows.iter() { self.unmap_window_internal(dead_window, false); } for (mapped, _) in self.mapped() { mapped.refresh(); } } pub fn animations_going(&self) -> bool { self.queue.animation_start.is_some() } pub fn update_animation_state(&mut self) -> HashMap { let mut clients = HashMap::new(); let mut ready_trees = 0; for (_, _, blocker) in self.queue.trees.iter().skip(1) { if let Some(blocker) = blocker.as_ref() { if blocker.is_processed() { ready_trees += 1; } if blocker.is_ready() { clients.extend(blocker.clients()); continue; } break; } else { ready_trees += 1; } } if let Some(start) = self.queue.animation_start { let duration_since_start = Instant::now().duration_since(start); if duration_since_start >= self .queue .trees .get(1) .expect("Animation going without second tree?") .1 { let _ = self.queue.animation_start.take(); let _ = self.queue.trees.pop_front(); ready_trees -= 1; let front = self.queue.trees.front_mut().unwrap(); if let Some(root_id) = front.0.root_node_id() { for node in front .0 .traverse_pre_order_ids(root_id) .unwrap() .collect::>() .into_iter() { if let Data::Mapped { minimize_rect, .. } = front.0.get_mut(&node).unwrap().data_mut() { minimize_rect.take(); } } } let _ = front.2.take(); } else { return clients; } } // merge let other_duration = if ready_trees > 1 { self.queue .trees .drain(1..ready_trees) .fold(None, |res, (_, duration, _)| { Some( res.map(|old_duration: Duration| old_duration.max(duration)) .unwrap_or(duration), ) }) } else { None }; // start if ready_trees > 0 { let (_, duration, _) = self.queue.trees.get_mut(1).unwrap(); *duration = other_duration .map(|other| other.max(*duration)) .unwrap_or(*duration); self.queue.animation_start = Some(Instant::now()); } clients } pub fn possible_resizes(tree: &Tree, mut node_id: NodeId) -> ResizeEdge { let mut edges = ResizeEdge::empty(); while let Some(group_id) = tree.get(&node_id).unwrap().parent().cloned() { let orientation = tree.get(&group_id).unwrap().data().orientation(); let node_idx = tree .children_ids(&group_id) .unwrap() .position(|id| id == &node_id) .unwrap(); let total = tree.children_ids(&group_id).unwrap().count(); if orientation == Orientation::Vertical { if node_idx > 0 { edges.insert(ResizeEdge::LEFT); } if node_idx < total - 1 { edges.insert(ResizeEdge::RIGHT); } } else { if node_idx > 0 { edges.insert(ResizeEdge::TOP); } if node_idx < total - 1 { edges.insert(ResizeEdge::BOTTOM); } } node_id = group_id; } edges } pub fn resize_request( &self, mut node_id: NodeId, edge: ResizeEdge, ) -> Option<(NodeId, usize, Orientation)> { let tree = self.tree(); while let Some(group_id) = tree.get(&node_id).unwrap().parent().cloned() { let orientation = tree.get(&group_id).unwrap().data().orientation(); let node_idx = tree .children_ids(&group_id) .unwrap() .position(|id| id == &node_id) .unwrap(); let total = tree.children_ids(&group_id).unwrap().count(); if orientation == Orientation::Vertical { if node_idx > 0 && edge.contains(ResizeEdge::LEFT) { return Some((group_id, node_idx - 1, orientation)); } if node_idx < total - 1 && edge.contains(ResizeEdge::RIGHT) { return Some((group_id, node_idx, orientation)); } } else { if node_idx > 0 && edge.contains(ResizeEdge::TOP) { return Some((group_id, node_idx - 1, orientation)); } if node_idx < total - 1 && edge.contains(ResizeEdge::BOTTOM) { return Some((group_id, node_idx, orientation)); } } node_id = group_id; } None } pub fn resize( &mut self, focused: &KeyboardFocusTarget, direction: ResizeDirection, edges: ResizeEdge, amount: i32, ) -> bool { let gaps = self.gaps(); let mut tree = self.queue.trees.back().unwrap().0.copy_clone(); let Some(root_id) = tree.root_node_id() else { return false; }; let Some(mut node_id) = (match TilingLayout::currently_focused_node(&tree, focused.clone()) { Some((_id, FocusedNodeData::Window(mapped))) => // we need to make sure the id belongs to this tree.. { tree.traverse_pre_order_ids(root_id) .unwrap() .find(|id| tree.get(id).unwrap().data().is_mapped(Some(&mapped))) } Some((id, FocusedNodeData::Group(_, _))) => Some(id), // in this case the workspace handle was already matched, so the id is to be trusted _ => None, }) else { return false; }; while let Some(group_id) = tree.get(&node_id).unwrap().parent().cloned() { let orientation = tree.get(&group_id).unwrap().data().orientation(); if !((orientation == Orientation::Vertical && (edges.contains(ResizeEdge::LEFT) || edges.contains(ResizeEdge::RIGHT))) || (orientation == Orientation::Horizontal && (edges.contains(ResizeEdge::TOP) || edges.contains(ResizeEdge::BOTTOM)))) { node_id = group_id.clone(); continue; } let node_idx = tree .children_ids(&group_id) .unwrap() .position(|id| id == &node_id) .unwrap(); let Some(other_idx) = (match edges { x if x.intersects(ResizeEdge::TOP_LEFT) => node_idx.checked_sub(1), _ => { if tree.children_ids(&group_id).unwrap().count() - 1 > node_idx { Some(node_idx + 1) } else { None } } }) else { node_id = group_id.clone(); continue; }; let data = tree.get_mut(&group_id).unwrap().data_mut(); match data { Data::Group { sizes, .. } => { let (shrink_idx, grow_idx) = if direction == ResizeDirection::Inwards { (node_idx, other_idx) } else { (other_idx, node_idx) }; if sizes[shrink_idx] + sizes[grow_idx] < match orientation { Orientation::Vertical => 720, Orientation::Horizontal => 480, } { return true; }; let old_size = sizes[shrink_idx]; sizes[shrink_idx] = (old_size - amount).max(if orientation == Orientation::Vertical { 360 } else { 240 }); let diff = old_size - sizes[shrink_idx]; sizes[grow_idx] += diff; } _ => unreachable!(), } let should_configure = tree.traverse_pre_order(&group_id) .unwrap() .all(|node| match node.data() { Data::Mapped { mapped, .. } => mapped.latest_size_committed(), _ => true, }); if should_configure { let blocker = TilingLayout::update_positions(&self.output, &mut tree, gaps); self.queue.push_tree(tree, None, blocker); } return true; } true } pub fn stacking_indicator(&self) -> Option> { if let Some(TargetZone::WindowStack(_, geo)) = self.last_overview_hover.as_ref().map(|(_, zone)| zone) { Some(*geo) } else { None } } pub fn cleanup_drag(&mut self) { let old_tree = &self.queue.trees.back().unwrap().0; let mut new_tree = None; if let Some(root) = old_tree.root_node_id() { for id in old_tree.traverse_pre_order_ids(root).unwrap() { match old_tree.get(&id).map(|node| node.data()) { Ok(Data::Placeholder { .. }) => { // Copy a tree on write let new_tree = new_tree.get_or_insert_with(|| old_tree.copy_clone()); TilingLayout::unmap_internal(new_tree, &id) } Ok(Data::Group { pill_indicator, .. }) if pill_indicator.is_some() => { let new_tree = new_tree.get_or_insert_with(|| old_tree.copy_clone()); match new_tree.get_mut(&id).unwrap().data_mut() { Data::Group { pill_indicator, .. } => { *pill_indicator = None; } _ => unreachable!(), } } _ => {} } } // If anything was changed, push updated tree if let Some(mut new_tree) = new_tree { let blocker = TilingLayout::update_positions(&self.output, &mut new_tree, self.gaps()); self.queue.push_tree(new_tree, ANIMATION_DURATION, blocker); } } } pub fn drop_window(&mut self, window: CosmicMapped) -> (CosmicMapped, Point) { let gaps = self.gaps(); let mut tree = self.queue.trees.back().unwrap().0.copy_clone(); window.output_enter(&self.output, window.bbox()); { let layer_map = layer_map_for_output(&self.output); window.set_bounds(layer_map.non_exclusive_zone().size); } let mapped = match self.last_overview_hover.as_ref().map(|(_, zone)| zone) { Some(TargetZone::GroupEdge(group_id, direction)) if tree.get(group_id).is_ok() => { let new_id = tree .insert( Node::new(Data::Mapped { mapped: window.clone(), last_geometry: Rectangle::from_size((100, 100).into()), minimize_rect: None, }), InsertBehavior::UnderNode(group_id), ) .unwrap(); let orientation = if matches!(direction, Direction::Left | Direction::Right) { Orientation::Vertical } else { Orientation::Horizontal }; if tree.get(group_id).unwrap().data().orientation() != orientation { TilingLayout::new_group(&mut tree, group_id, &new_id, orientation).unwrap(); } else { let data = tree.get_mut(group_id).unwrap().data_mut(); let len = data.len(); data.add_window(if matches!(direction, Direction::Left | Direction::Up) { 0 } else { len }); } if matches!(direction, Direction::Left | Direction::Up) { tree.make_first_sibling(&new_id).unwrap(); } else { tree.make_last_sibling(&new_id).unwrap(); } *window.tiling_node_id.lock().unwrap() = Some(new_id); window } Some(TargetZone::GroupInterior(group_id, idx)) if tree.get(group_id).is_ok() => { let new_id = tree .insert( Node::new(Data::Mapped { mapped: window.clone(), last_geometry: Rectangle::from_size((100, 100).into()), minimize_rect: None, }), InsertBehavior::UnderNode(group_id), ) .unwrap(); let idx = { let data = tree.get_mut(group_id).unwrap().data_mut(); let bound_idx = data.len().min(*idx + 1); data.add_window(bound_idx); bound_idx }; tree.make_nth_sibling(&new_id, idx).unwrap(); *window.tiling_node_id.lock().unwrap() = Some(new_id); window } Some(TargetZone::InitialPlaceholder(node_id)) if tree.get(node_id).is_ok() => { let data = tree.get_mut(node_id).unwrap().data_mut(); let geo = *data.geometry(); *data = Data::Mapped { mapped: window.clone(), last_geometry: geo, minimize_rect: None, }; *window.tiling_node_id.lock().unwrap() = Some(node_id.clone()); window } Some(TargetZone::WindowSplit(window_id, direction)) if tree.get(window_id).is_ok() => { let new_id = tree .insert( Node::new(Data::Mapped { mapped: window.clone(), last_geometry: Rectangle::from_size((100, 100).into()), minimize_rect: None, }), InsertBehavior::UnderNode(window_id), ) .unwrap(); let orientation = if matches!(direction, Direction::Left | Direction::Right) { Orientation::Vertical } else { Orientation::Horizontal }; TilingLayout::new_group(&mut tree, window_id, &new_id, orientation).unwrap(); if matches!(direction, Direction::Left | Direction::Up) { tree.make_first_sibling(&new_id).unwrap(); } *window.tiling_node_id.lock().unwrap() = Some(new_id.clone()); window } Some(TargetZone::WindowStack(window_id, _)) if tree.get(window_id).is_ok() => { match tree.get_mut(window_id).unwrap().data_mut() { Data::Mapped { mapped, .. } => { mapped.convert_to_stack( (&self.output, mapped.bbox()), self.theme.clone(), self.appearance, ); let Some(stack) = mapped.stack_ref() else { unreachable!() }; for surface in window.windows().map(|s| s.0) { stack.add_window(surface, None, None); } mapped.clone() } _ => unreachable!(), } } _ => { TilingLayout::map_to_tree( &mut tree, window.clone(), &self.output, None, None, None, ); window } }; if let Some(root) = tree.root_node_id() { for id in tree .traverse_pre_order_ids(root) .unwrap() .collect::>() .into_iter() { match tree.get_mut(&id).map(|node| node.data_mut()) { Ok(Data::Placeholder { .. }) => TilingLayout::unmap_internal(&mut tree, &id), Ok(Data::Group { pill_indicator, .. }) if pill_indicator.is_some() => { pill_indicator.take(); } _ => {} } } } let blocker = TilingLayout::update_positions(&self.output, &mut tree, gaps); self.queue.push_tree(tree, ANIMATION_DURATION, blocker); let location = self.element_geometry(&mapped).unwrap().loc; (mapped, location) } fn last_active_window<'a>( tree: &Tree, mut focus_stack: impl Iterator, ) -> Option<(NodeId, CosmicMapped)> { focus_stack.find_map(|target| { tree.root_node_id().and_then(|root| { tree.traverse_pre_order_ids(root).unwrap().find_map(|id| { let Ok(Data::Mapped { mapped, .. }) = tree.get(&id).map(|n| n.data()) else { return None; }; if target == mapped { Some((id, mapped.clone())) } else { None } }) }) }) } fn currently_focused_node( tree: &Tree, mut target: KeyboardFocusTarget, ) -> Option<(NodeId, FocusedNodeData)> { // if the focus is currently on a popup, treat it's toplevel as the target if let KeyboardFocusTarget::Popup(popup) = target { let toplevel_surface = match popup { PopupKind::Xdg(xdg) => get_popup_toplevel(&xdg), PopupKind::InputMethod(_) => unreachable!(), }?; let root_id = tree.root_node_id()?; let node = tree.traverse_pre_order(root_id) .unwrap() .find(|node| match node.data() { Data::Mapped { mapped, .. } => mapped .windows() .any(|(w, _)| w.wl_surface().as_deref() == Some(&toplevel_surface)), _ => false, })?; target = KeyboardFocusTarget::Element(match node.data() { Data::Mapped { mapped, .. } => mapped.clone(), _ => unreachable!(), }); } match target { KeyboardFocusTarget::Element(mapped) => { let node_id = mapped.tiling_node_id.lock().unwrap().clone()?; let node = tree.get(&node_id).ok()?; let data = node.data(); if data.is_mapped(Some(&mapped)) { return Some((node_id, FocusedNodeData::Window(mapped))); } } KeyboardFocusTarget::Group(window_group) => { let node = tree.get(&window_group.node).ok()?; if node.data().is_group() { return Some(( window_group.node, FocusedNodeData::Group(window_group.focus_stack, window_group.alive), )); } } _ => {} }; None } fn new_group( tree: &mut Tree, old_id: &NodeId, new_id: &NodeId, orientation: Orientation, ) -> Result { let new_group = Node::new(Data::new_group( orientation, Rectangle::from_size((100, 100).into()), )); let old = tree.get(old_id)?; let parent_id = old.parent().cloned(); let pos = parent_id.as_ref().and_then(|parent_id| { tree.children_ids(parent_id) .unwrap() .position(|id| id == old_id) }); let group_id = tree .insert( new_group, if let Some(parent) = parent_id.as_ref() { InsertBehavior::UnderNode(parent) } else { InsertBehavior::AsRoot }, ) .unwrap(); tree.move_node(old_id, MoveBehavior::ToParent(&group_id)) .unwrap(); // keep position if let Some(old_pos) = pos { tree.make_nth_sibling(&group_id, old_pos).unwrap(); } tree.move_node(new_id, MoveBehavior::ToParent(&group_id)) .unwrap(); Ok(group_id) } fn has_adjacent_node(tree: &Tree, node: &NodeId, direction: Direction) -> bool { let mut search_node = node; match tree.ancestor_ids(node) { Ok(mut iter) => iter.any(|parent_id| { let parent = tree.get(parent_id).unwrap(); let children = parent.children(); let idx = children.iter().position(|id| id == search_node).unwrap(); search_node = parent_id; match direction { Direction::Up => { parent.data().orientation() == Orientation::Horizontal && idx > 0 } Direction::Down => { parent.data().orientation() == Orientation::Horizontal && idx < (children.len() - 1) } Direction::Left => { parent.data().orientation() == Orientation::Vertical && idx > 0 } Direction::Right => { parent.data().orientation() == Orientation::Vertical && idx < (children.len() - 1) } } }), Err(_) => false, } } fn has_sibling_node(tree: &Tree, node: &NodeId, direction: Direction) -> bool { match tree.get(node).ok().and_then(|node| node.parent()) { Some(parent_id) => { let parent = tree.get(parent_id).unwrap(); let children = parent.children(); let idx = children.iter().position(|id| id == node).unwrap(); match direction { Direction::Up => { parent.data().orientation() == Orientation::Horizontal && idx > 0 } Direction::Down => { parent.data().orientation() == Orientation::Horizontal && idx < (children.len() - 1) } Direction::Left => { parent.data().orientation() == Orientation::Vertical && idx > 0 } Direction::Right => { parent.data().orientation() == Orientation::Vertical && idx < (children.len() - 1) } } } None => false, } } #[profiling::function] fn update_positions( output: &Output, tree: &mut Tree, gaps: (i32, i32), ) -> Option { if let Some(root_id) = tree.root_node_id() { let mut configures = Vec::new(); let (outer, inner) = gaps; let mut geo = layer_map_for_output(output).non_exclusive_zone().as_local(); geo.loc.x += outer; geo.loc.y += outer; geo.size.w -= outer * 2; geo.size.h -= outer * 2; let mut stack = vec![geo]; for node_id in tree .traverse_pre_order_ids(root_id) .unwrap() .collect::>() .into_iter() { let node = tree.get_mut(&node_id).unwrap(); let data = node.data_mut(); // flatten tree if data.is_group() && data.len() == 1 { // RemoveBehavior::LiftChildren sadly does not what we want: lifting them into the same place. // So we need to fix that manually.. let idx = node.parent().cloned().map(|parent_id| { tree.children_ids(&parent_id) .unwrap() .position(|id| id == &node_id) .unwrap() }); let child_id = tree .children_ids(&node_id) .unwrap() .next() .cloned() .unwrap(); tree.remove_node(node_id, RemoveBehavior::LiftChildren) .unwrap(); if let Some(idx) = idx { tree.make_nth_sibling(&child_id, idx).unwrap(); } else { // additionally `RemoveBehavior::LiftChildren` doesn't work, when removing the root-node, // even with just one child. *sigh* tree.move_node(&child_id, MoveBehavior::ToRoot).unwrap(); } continue; } if let Some(mut geo) = stack.pop() { let node = tree.get(&node_id).unwrap(); let data = node.data(); if data.is_mapped(None) { let gap = ( ( if TilingLayout::has_adjacent_node(tree, &node_id, Direction::Left) { inner / 2 } else { inner }, if TilingLayout::has_adjacent_node(tree, &node_id, Direction::Up) { inner / 2 } else { inner }, ), ( if TilingLayout::has_adjacent_node(tree, &node_id, Direction::Right) { inner / 2 } else { inner }, if TilingLayout::has_adjacent_node(tree, &node_id, Direction::Down) { inner / 2 } else { inner }, ), ); geo.loc += gap.0.into(); geo.size -= gap.0.into(); geo.size -= gap.1.into(); } let node = tree.get_mut(&node_id).unwrap(); let data = node.data_mut(); data.update_geometry(geo); match data { Data::Group { orientation, sizes, .. } => match orientation { Orientation::Horizontal => { let mut previous: i32 = sizes.iter().sum(); for size in sizes.iter().rev() { previous -= *size; stack.push(Rectangle::new( (geo.loc.x, geo.loc.y + previous).into(), (geo.size.w, *size).into(), )); } } Orientation::Vertical => { let mut previous: i32 = sizes.iter().sum(); for size in sizes.iter().rev() { previous -= *size; stack.push(Rectangle::new( (geo.loc.x + previous, geo.loc.y).into(), (*size, geo.size.h).into(), )); } } }, Data::Mapped { mapped, .. } => { if !(mapped.is_fullscreen(true) || mapped.is_maximized(true)) { mapped.set_tiled(true); let internal_geometry = geo.to_global(output); mapped.set_geometry(internal_geometry); if let Some(serial) = mapped.configure() { configures.push((mapped.active_window(), serial)); } } } Data::Placeholder { .. } => {} } } } if !configures.is_empty() { let blocker = TilingBlocker::new(configures); for (surface, _) in &blocker.necessary_acks { if let Some(surface) = surface.wl_surface() { add_blocker(&surface, blocker.clone()); } } return Some(blocker); } } None } pub fn popup_element_under( &self, location_f64: Point, ) -> Option { let location = location_f64.to_i32_round(); for (mapped, geo) in self.mapped() { if !mapped.bbox().contains((location - geo.loc).as_logical()) { continue; } if mapped .focus_under( (location_f64 - geo.loc.to_f64()).as_logical() + mapped.geometry().loc.to_f64(), WindowSurfaceType::POPUP | WindowSurfaceType::SUBSURFACE, ) .is_some() { return Some(mapped.clone().into()); } } None } pub fn toplevel_element_under( &self, location_f64: Point, ) -> Option { let location = location_f64.to_i32_round(); for (mapped, geo) in self.mapped() { if !mapped.bbox().contains((location - geo.loc).as_logical()) { continue; } if mapped .focus_under( (location_f64 - geo.loc.to_f64()).as_logical() + mapped.geometry().loc.to_f64(), WindowSurfaceType::TOPLEVEL | WindowSurfaceType::SUBSURFACE, ) .is_some() { return Some(mapped.clone().into()); } } None } pub fn popup_surface_under( &self, location_f64: Point, overview: OverviewMode, ) -> Option<(PointerFocusTarget, Point)> { let location = location_f64.to_i32_round(); if matches!(overview, OverviewMode::None) { for (mapped, geo) in self.mapped() { if !mapped.bbox().contains((location - geo.loc).as_logical()) { continue; } if mapped.is_maximized(false) { continue; } if let Some((target, surface_offset)) = mapped.focus_under( (location_f64 - geo.loc.to_f64()).as_logical() + mapped.geometry().loc.to_f64(), WindowSurfaceType::POPUP | WindowSurfaceType::SUBSURFACE, ) { return Some(( target, geo.loc.to_f64() - mapped.geometry().loc.as_local().to_f64() + surface_offset.as_local(), )); } } } None } pub fn toplevel_surface_under( &self, location_f64: Point, overview: OverviewMode, ) -> Option<(PointerFocusTarget, Point)> { let tree = &self.queue.trees.back().unwrap().0; let root = tree.root_node_id()?; let location = location_f64.to_i32_round(); if matches!(overview, OverviewMode::None) { for (mapped, geo) in self.mapped() { if !mapped.bbox().contains((location - geo.loc).as_logical()) { continue; } if mapped.is_maximized(false) { continue; } if let Some((target, surface_offset)) = mapped.focus_under( (location_f64 - geo.loc.to_f64()).as_logical() + mapped.geometry().loc.to_f64(), WindowSurfaceType::TOPLEVEL | WindowSurfaceType::SUBSURFACE, ) { return Some(( target, geo.loc.to_f64() - mapped.geometry().loc.as_local().to_f64() + surface_offset.as_local(), )); } } let mut result = None; let mut lookup = Some(root.clone()); while let Some(node) = lookup { let data = tree.get(&node).unwrap().data(); if data.geometry().contains(location) { result = Some(node.clone()); } lookup = None; if result.is_some() && data.is_group() { for child_id in tree.children_ids(&node).unwrap() { if tree .get(child_id) .unwrap() .data() .geometry() .contains(location) { lookup = Some(child_id.clone()); break; } } } } match result.map(|id| (id.clone(), tree.get(&id).unwrap().data().clone())) { Some(( _, Data::Mapped { mapped, last_geometry, .. }, )) => { let test_point = (location.to_f64() - last_geometry.loc.to_f64() + mapped.geometry().loc.to_f64().as_local()) .as_logical(); mapped .focus_under( test_point, WindowSurfaceType::TOPLEVEL | WindowSurfaceType::SUBSURFACE, ) .map(|(surface, surface_offset)| { ( surface, last_geometry.loc.to_f64() - mapped.geometry().loc.as_local().to_f64() + surface_offset.as_local(), ) }) } Some(( id, Data::Group { orientation, last_geometry, .. }, )) => { let idx = tree .children(&id) .unwrap() .position(|node| { let data = node.data(); match orientation { Orientation::Vertical => location.x < data.geometry().loc.x, Orientation::Horizontal => location.y < data.geometry().loc.y, } }) .and_then(|x| x.checked_sub(1))?; Some(( ResizeForkTarget { node: id.clone(), output: self.output.downgrade(), left_up_idx: idx, orientation, } .into(), (last_geometry.loc + tree .children(&id) .unwrap() .nth(idx) .map(|node| { let geo = node.data().geometry(); geo.loc + geo.size }) .unwrap()) .to_f64(), )) } _ => None, } } else { None } } pub fn update_pointer_position( &mut self, location_f64: Option>, overview: OverviewMode, ) { let gaps = self.gaps(); let last_overview_hover = &mut self.last_overview_hover; let tree = &self.queue.trees.back().unwrap().0; let Some(root) = tree.root_node_id() else { if matches!( overview.active_trigger(), Some(Trigger::Pointer(_) | Trigger::Touch(_)) ) { if location_f64.is_some() { let mut tree = tree.copy_clone(); tree.insert( Node::new(Data::Placeholder { id: Id::new(), last_geometry: Rectangle::from_size((100, 100).into()), type_: PlaceholderType::DropZone, }), InsertBehavior::AsRoot, ) .unwrap(); let blocker = TilingLayout::update_positions(&self.output, &mut tree, gaps); self.queue.push_tree(tree, ANIMATION_DURATION, blocker); } } return; }; if !matches!( overview, OverviewMode::Started(_, _) | OverviewMode::Active(_) ) || location_f64.is_none() { last_overview_hover.take(); return; } let location_f64 = location_f64.unwrap(); let location = location_f64.to_i32_round(); if matches!( overview.active_trigger(), Some(Trigger::Pointer(_) | Trigger::Touch(_)) ) { let non_exclusive_zone = layer_map_for_output(&self.output) .non_exclusive_zone() .as_local(); let geometries = geometries_for_groupview( tree, Option::<&mut GlowRenderer>::None, non_exclusive_zone, None, self.output.current_scale().fractional_scale(), 1.0, overview.alpha().unwrap(), &self.backdrop_id, Some(None), None, None, self.theme.cosmic(), ) .0; let mut result = None; let mut lookup = Some(root.clone()); while let Some(node) = lookup { let data = tree.get(&node).unwrap().data(); if geometries .get(&node) .map(|geo| geo.contains(location)) .unwrap_or(false) { result = Some(node.clone()); } lookup = None; if result.is_some() && data.is_group() { if tree.children(&node).unwrap().any(|child| { matches!( child.data(), Data::Placeholder { type_: PlaceholderType::DropZone, .. } ) }) { break; } for child_id in tree.children_ids(&node).unwrap() { if geometries .get(child_id) .map(|geo| geo.contains(location)) .unwrap_or(false) { lookup = Some(child_id.clone()); break; } } } } if let Some(res_id) = result { let Some(mut last_geometry) = geometries.get(&res_id).copied() else { return; }; let node = tree.get(&res_id).unwrap(); let data = node.data().clone(); let group_zone = if let Data::Group { orientation, .. } = &data { if node.children().iter().any(|child_id| { tree.get(child_id) .ok() .map(|child| { matches!( child.data(), Data::Placeholder { type_: PlaceholderType::DropZone, .. } ) }) .unwrap_or(false) }) { None } else { let left_edge = match &*last_overview_hover { Some((_, TargetZone::GroupEdge(id, Direction::Left))) if *id == res_id => { let zone = Rectangle::new( last_geometry.loc, (80, last_geometry.size.h).into(), ); last_geometry.loc.x += 80; last_geometry.size.w -= 80; zone } _ => { let zone = Rectangle::new( last_geometry.loc, (32, last_geometry.size.h).into(), ); last_geometry.loc.x += 32; last_geometry.size.w -= 32; zone } }; let top_edge = match &*last_overview_hover { Some((_, TargetZone::GroupEdge(id, Direction::Up))) if *id == res_id => { let zone = Rectangle::new( last_geometry.loc, (last_geometry.size.w, 80).into(), ); last_geometry.loc.y += 80; last_geometry.size.h -= 80; zone } _ => { let zone = Rectangle::new( last_geometry.loc, (last_geometry.size.w, 32).into(), ); last_geometry.loc.y += 32; last_geometry.size.h -= 32; zone } }; let right_edge = match &*last_overview_hover { Some((_, TargetZone::GroupEdge(id, Direction::Right))) if *id == res_id => { let zone = Rectangle::new( ( last_geometry.loc.x + last_geometry.size.w - 80, last_geometry.loc.y, ) .into(), (80, last_geometry.size.h).into(), ); last_geometry.size.w -= 80; zone } _ => { let zone = Rectangle::new( ( last_geometry.loc.x + last_geometry.size.w - 32, last_geometry.loc.y, ) .into(), (32, last_geometry.size.h).into(), ); last_geometry.size.w -= 32; zone } }; let bottom_edge = match &*last_overview_hover { Some((_, TargetZone::GroupEdge(id, Direction::Down))) if *id == res_id => { let zone = Rectangle::new( ( last_geometry.loc.x, last_geometry.loc.y + last_geometry.size.h - 80, ) .into(), (last_geometry.size.w, 80).into(), ); last_geometry.size.h -= 80; zone } _ => { let zone = Rectangle::new( ( last_geometry.loc.x, last_geometry.loc.y + last_geometry.size.h - 32, ) .into(), (last_geometry.size.w, 32).into(), ); last_geometry.size.h -= 32; zone } }; if left_edge.contains(location) { Some(TargetZone::GroupEdge(res_id.clone(), Direction::Left)) } else if right_edge.contains(location) { Some(TargetZone::GroupEdge(res_id.clone(), Direction::Right)) } else if top_edge.contains(location) { Some(TargetZone::GroupEdge(res_id.clone(), Direction::Up)) } else if bottom_edge.contains(location) { Some(TargetZone::GroupEdge(res_id.clone(), Direction::Down)) } else { let idx = tree .children_ids(&res_id) .unwrap() .position(|node| { let Some(geo) = geometries.get(node) else { return false; }; match orientation { Orientation::Vertical => location.x < geo.loc.x, Orientation::Horizontal => location.y < geo.loc.y, } }) .and_then(|x| x.checked_sub(1)) .unwrap_or(0); Some(TargetZone::GroupInterior(res_id.clone(), idx)) } } } else { None }; let target_zone = group_zone.unwrap_or_else(|| match &data { Data::Placeholder { .. } => TargetZone::InitialPlaceholder(res_id), Data::Group { .. } | Data::Mapped { .. } => { let id = if data.is_group() { tree.get(&res_id) .unwrap() .children() .iter() .find(|child_id| tree.get(child_id).unwrap().data().is_mapped(None)) .expect("Placeholder group without real window?") .clone() } else { res_id }; let third_width = (last_geometry.size.w as f64 / 3.0).round() as i32; let third_height = (last_geometry.size.h as f64 / 3.0).round() as i32; let stack_region = Rectangle::from_extremities( ( last_geometry.loc.x + third_width, last_geometry.loc.y + third_height, ), ( last_geometry.loc.x + 2 * third_width, last_geometry.loc.y + 2 * third_height, ), ); if stack_region.contains(location) { TargetZone::WindowStack(id, last_geometry) } else { let left_right = { let relative_loc = (location.x - last_geometry.loc.x) as f64; if relative_loc < last_geometry.size.w as f64 / 2.0 { (Direction::Left, relative_loc / last_geometry.size.w as f64) } else { ( Direction::Right, 1.0 - (relative_loc / last_geometry.size.w as f64), ) } }; let up_down = { let relative_loc = (location.y - last_geometry.loc.y) as f64; if relative_loc < last_geometry.size.h as f64 / 2.0 { (Direction::Up, relative_loc / last_geometry.size.h as f64) } else { ( Direction::Down, 1.0 - (relative_loc / last_geometry.size.h as f64), ) } }; let direction = if left_right.1 < up_down.1 { left_right.0 } else { up_down.0 }; TargetZone::WindowSplit(id, direction) } } }); match &mut *last_overview_hover { last_overview_hover @ None => { *last_overview_hover = Some(( None, tree.traverse_pre_order_ids(root) .unwrap() .find(|id| { matches!( tree.get(id).unwrap().data(), Data::Placeholder { type_: PlaceholderType::GrabbedWindow, .. } ) }) .map(TargetZone::InitialPlaceholder) .unwrap_or(TargetZone::Initial), )); } Some((instant, old_target_zone)) => { if *old_target_zone != target_zone { let overdue = if let Some(instant) = instant { match old_target_zone { TargetZone::InitialPlaceholder(_) => { Instant::now().duration_since(*instant) > INITIAL_MOUSE_ANIMATION_DELAY } _ => { Instant::now().duration_since(*instant) > MOUSE_ANIMATION_DELAY } } } else { *instant = Some(Instant::now()); false }; if overdue { let duration = if target_zone.is_window_zone() && !old_target_zone.is_window_zone() { ANIMATION_DURATION * 2 } else { ANIMATION_DURATION }; let mut tree = tree.copy_clone(); // remove old placeholders let removed = if let TargetZone::InitialPlaceholder(node_id) = old_target_zone { if tree.get(node_id).is_ok() { TilingLayout::unmap_internal(&mut tree, node_id); } true } else if let TargetZone::WindowSplit(node_id, _) = old_target_zone { if let Some(children) = tree .get(node_id) .ok() .and_then(|node| node.parent()) .and_then(|parent_id| tree.get(parent_id).ok()) .map(|node| node.children().clone()) { for id in children { let matches = matches!( tree.get(&id).unwrap().data(), Data::Placeholder { type_: PlaceholderType::DropZone, .. } ); if matches { TilingLayout::unmap_internal(&mut tree, &id); break; } } } true } else if let TargetZone::GroupEdge(node_id, _) = old_target_zone { if let Ok(node) = tree.get_mut(node_id) { match node.data_mut() { Data::Group { pill_indicator, .. } => { *pill_indicator = None; } _ => unreachable!(), } } true } else if let TargetZone::GroupInterior(node_id, _) = old_target_zone { if let Ok(node) = tree.get_mut(node_id) { match node.data_mut() { Data::Group { pill_indicator, .. } => { *pill_indicator = None; } _ => unreachable!(), } } true } else { false }; // add placeholders let added = if let TargetZone::WindowSplit(node_id, dir) = &target_zone { let id = tree .insert( Node::new(Data::Placeholder { id: Id::new(), last_geometry: Rectangle::from_size( (100, 100).into(), ), type_: PlaceholderType::DropZone, }), InsertBehavior::UnderNode(node_id), ) .unwrap(); let orientation = if matches!(dir, Direction::Left | Direction::Right) { Orientation::Vertical } else { Orientation::Horizontal }; TilingLayout::new_group(&mut tree, node_id, &id, orientation) .unwrap(); if matches!(dir, Direction::Left | Direction::Up) { tree.make_first_sibling(&id).unwrap(); } true } else if let TargetZone::GroupEdge(node_id, direction) = &target_zone { if let Ok(node) = tree.get_mut(node_id) { match node.data_mut() { Data::Group { pill_indicator, .. } => { *pill_indicator = Some(PillIndicator::Outer(*direction)); } _ => unreachable!(), } true } else { false } } else if let TargetZone::GroupInterior(node_id, idx) = &target_zone { if let Ok(node) = tree.get_mut(node_id) { match node.data_mut() { Data::Group { pill_indicator, .. } => { *pill_indicator = Some(PillIndicator::Inner(*idx)); } _ => unreachable!(), } true } else { false } } else { false }; if removed || added { let blocker = TilingLayout::update_positions( &self.output, &mut tree, gaps, ); self.queue.push_tree(tree, duration, blocker); } *instant = None; *old_target_zone = target_zone; } } else { *instant = None; } } } } } } pub fn mapped(&self) -> impl Iterator)> { let tree = &self.queue.trees.back().unwrap().0; let iter = tree.root_node_id().map(|root| { tree.traverse_pre_order(root) .unwrap() .filter(|node| node.data().is_mapped(None)) .filter(|node| match node.data() { Data::Mapped { mapped, .. } => mapped.is_activated(false), _ => unreachable!(), }) .map(|node| match node.data() { Data::Mapped { mapped, last_geometry, .. } => (mapped, *last_geometry), _ => unreachable!(), }) .chain( tree.traverse_pre_order(root) .unwrap() .filter(|node| node.data().is_mapped(None)) .filter(|node| match node.data() { Data::Mapped { mapped, .. } => !mapped.is_activated(false), _ => unreachable!(), }) .map(|node| match node.data() { Data::Mapped { mapped, last_geometry, .. } => (mapped, *last_geometry), _ => unreachable!(), }), ) }); iter.into_iter().flatten() } pub fn windows(&self) -> impl Iterator)> + '_ { self.mapped().flat_map(|(mapped, geo)| { mapped.windows().map(move |(w, p)| { (w, { let mut geo = geo; geo.loc += p.as_local(); geo.size -= p.to_size().as_local(); geo }) }) }) } pub fn element_for_node(&self, node: &NodeId) -> Option<&CosmicMapped> { let tree = &self.queue.trees.back().unwrap().0; tree.get(node).ok().and_then(|node| match node.data() { Data::Mapped { mapped, .. } => Some(mapped), _ => None, }) } pub fn has_node(&self, node: &NodeId) -> bool { let tree = &self.queue.trees.back().unwrap().0; tree.root_node_id() .map(|root| { tree.traverse_pre_order_ids(root) .unwrap() .any(|id| &id == node) }) .unwrap_or(false) } pub fn merge(&mut self, mut other: TilingLayout) { let gaps = self.gaps(); let src = other.queue.trees.pop_back().unwrap().0; let mut dst = self.queue.trees.back().unwrap().0.copy_clone(); let orientation = match self.output.geometry().size { x if x.w >= x.h => Orientation::Vertical, _ => Orientation::Horizontal, }; TilingLayout::merge_trees(src, &mut dst, orientation); let blocker = TilingLayout::update_positions(&self.output, &mut dst, gaps); self.queue.push_tree(dst, ANIMATION_DURATION, blocker); } fn merge_trees(src: Tree, dst: &mut Tree, orientation: Orientation) { if let Some(dst_root_id) = dst.root_node_id().cloned() { let mut stack = Vec::new(); if let Some(src_root_id) = src.root_node_id() { let root_node = src.get(src_root_id).unwrap(); let new_node = Node::new(root_node.data().clone()); let new_id = dst .insert(new_node, InsertBehavior::UnderNode(&dst_root_id)) .unwrap(); if let &mut Data::Mapped { ref mut mapped, .. } = dst.get_mut(&new_id).unwrap().data_mut() { *mapped.tiling_node_id.lock().unwrap() = Some(new_id.clone()); } TilingLayout::new_group(dst, &dst_root_id, &new_id, orientation).unwrap(); stack.push((src_root_id.clone(), new_id)); } while let Some((src_id, dst_id)) = stack.pop() { for child_id in src.children_ids(&src_id).unwrap() { let src_node = src.get(child_id).unwrap(); let new_node = Node::new(src_node.data().clone()); let new_child_id = dst .insert(new_node, InsertBehavior::UnderNode(&dst_id)) .unwrap(); if let &mut Data::Mapped { ref mut mapped, .. } = dst.get_mut(&new_child_id).unwrap().data_mut() { *mapped.tiling_node_id.lock().unwrap() = Some(new_child_id.clone()); } stack.push((child_id.clone(), new_child_id)); } } } else { *dst = src; } } #[profiling::function] pub fn render( &self, renderer: &mut R, seat: Option<&Seat>, non_exclusive_zone: Rectangle, overview: (OverviewMode, Option<(SwapIndicator, Option<&Tree>)>), resize_indicator: Option<(ResizeMode, ResizeIndicator)>, indicator_thickness: u8, theme: &cosmic::theme::CosmicTheme, ) -> Result>, OutputNotMapped> where R: AsGlowRenderer, R::TextureId: Send + Clone + 'static, CosmicMappedRenderElement: RenderElement, CosmicWindowRenderElement: RenderElement, CosmicStackRenderElement: RenderElement, { let output_scale = self.output.current_scale().fractional_scale(); let (target_tree, duration, _) = if self.queue.animation_start.is_some() { self.queue .trees .get(1) .expect("Animation ongoing, should have two trees") } else { self.queue.trees.front().unwrap() }; let reference_tree = self .queue .animation_start .is_some() .then(|| &self.queue.trees.front().unwrap().0); let percentage = if let Some(animation_start) = self.queue.animation_start { if *duration == Duration::ZERO { 1.0 } else { let now = Instant::now(); let total = duration.as_millis() as f32; let since = now.duration_since(animation_start).as_millis() as f32; let percentage = since / total; debug_assert!(!percentage.is_nan()); debug_assert!(!percentage.is_infinite()); ease(EaseInOutCubic, 0.0, 1.0, percentage) } } else { 1.0 }; let draw_groups = overview.0.alpha(); let mut elements = Vec::default(); let is_overview = !matches!(overview.0, OverviewMode::None); let is_mouse_tiling = (matches!(overview.0.trigger(), Some(Trigger::Pointer(_)))) .then(|| self.last_overview_hover.as_ref().map(|(_, zone)| zone)); let swap_desc = if let Some(Trigger::KeyboardSwap(_, desc)) = overview.0.trigger() { Some(desc.clone()) } else { None }; // all gone windows and fade them out let old_geometries = if let Some(reference_tree) = reference_tree.as_ref() { let (geometries, _) = if let Some(transition) = draw_groups { Some(geometries_for_groupview( reference_tree, &mut *renderer, non_exclusive_zone, seat, // TODO: Would be better to be an old focus, // but for that we have to associate focus with a tree (and animate focus changes properly) output_scale, 1.0 - transition, transition, &self.backdrop_id, is_mouse_tiling, swap_desc.clone(), overview.1.as_ref().and_then(|(_, tree)| *tree), theme, )) } else { None } .unzip(); // all old windows we want to fade out elements.extend(render_old_tree_windows( reference_tree, target_tree, renderer, geometries.clone(), output_scale, percentage, indicator_thickness, swap_desc.is_some(), theme, )); geometries } else { None }; let (geometries, group_elements) = if let Some(transition) = draw_groups { Some(geometries_for_groupview( target_tree, &mut *renderer, non_exclusive_zone, seat, output_scale, transition, transition, &self.backdrop_id, is_mouse_tiling, swap_desc.clone(), overview.1.as_ref().and_then(|(_, tree)| *tree), theme, )) } else { None } .unzip(); // all alive windows elements.extend(render_new_tree_windows( target_tree, reference_tree, renderer, non_exclusive_zone, geometries, old_geometries, is_overview, seat, &self.output, percentage, draw_groups, if let Some(transition) = draw_groups { let diff = (4u8.abs_diff(indicator_thickness) as f32 * transition).round() as u8; if 3 > indicator_thickness { indicator_thickness + diff } else { indicator_thickness - diff } } else { indicator_thickness }, overview, resize_indicator, swap_desc.clone(), &self.swapping_stack_surface_id, &self.backdrop_id, theme, )); // tiling hints if let Some(group_elements) = group_elements { elements.extend(group_elements); } Ok(elements) } #[profiling::function] pub fn render_popups( &self, renderer: &mut R, seat: Option<&Seat>, non_exclusive_zone: Rectangle, overview: (OverviewMode, Option<(SwapIndicator, Option<&Tree>)>), theme: &cosmic::theme::CosmicTheme, ) -> Result>, OutputNotMapped> where R: AsGlowRenderer, R::TextureId: Send + Clone + 'static, CosmicMappedRenderElement: RenderElement, CosmicWindowRenderElement: RenderElement, CosmicStackRenderElement: RenderElement, { let output_scale = self.output.current_scale().fractional_scale(); let (target_tree, duration, _) = if self.queue.animation_start.is_some() { self.queue .trees .get(1) .expect("Animation ongoing, should have two trees") } else { self.queue.trees.front().unwrap() }; let reference_tree = self .queue .animation_start .is_some() .then(|| &self.queue.trees.front().unwrap().0); let percentage = if let Some(animation_start) = self.queue.animation_start { let percentage = Instant::now().duration_since(animation_start).as_millis() as f32 / duration.as_millis() as f32; ease(EaseInOutCubic, 0.0, 1.0, percentage) } else { 1.0 }; let draw_groups = overview.0.alpha(); let mut elements = Vec::default(); let is_mouse_tiling = (matches!(overview.0.trigger(), Some(Trigger::Pointer(_)))) .then(|| self.last_overview_hover.as_ref().map(|(_, zone)| zone)); let swap_desc = if let Some(Trigger::KeyboardSwap(_, desc)) = overview.0.trigger() { Some(desc.clone()) } else { None }; // all gone windows and fade them out let old_geometries = if let Some(reference_tree) = reference_tree.as_ref() { let (geometries, _) = if let Some(transition) = draw_groups { Some(geometries_for_groupview( reference_tree, &mut *renderer, non_exclusive_zone, seat, // TODO: Would be better to be an old focus, // but for that we have to associate focus with a tree (and animate focus changes properly) output_scale, 1.0 - transition, transition, &self.backdrop_id, is_mouse_tiling, swap_desc.clone(), overview.1.as_ref().and_then(|(_, tree)| *tree), theme, )) } else { None } .unzip(); // all old windows we want to fade out elements.extend(render_old_tree_popups( reference_tree, target_tree, renderer, geometries.clone(), output_scale, percentage, swap_desc.is_some(), )); geometries } else { None }; let (geometries, _) = if let Some(transition) = draw_groups { Some(geometries_for_groupview( target_tree, &mut *renderer, non_exclusive_zone, seat, output_scale, transition, transition, &self.backdrop_id, is_mouse_tiling, swap_desc.clone(), overview.1.as_ref().and_then(|(_, tree)| *tree), theme, )) } else { None } .unzip(); // all alive windows elements.extend(render_new_tree_popups( target_tree, reference_tree, renderer, geometries, old_geometries, seat, &self.output, percentage, overview, swap_desc.clone(), )); Ok(elements) } fn gaps(&self) -> (i32, i32) { let g = self.theme.cosmic().gaps; (g.0 as i32, g.1 as i32) } } const GAP_KEYBOARD: i32 = 8; const GAP_MOUSE: i32 = 32; const PLACEHOLDER_GAP_MOUSE: i32 = 8; const WINDOW_BACKDROP_BORDER: i32 = 4; const WINDOW_BACKDROP_GAP: i32 = 12; const MAX_SWAP_WINDOW_SIZE: (i32, i32) = (360, 240); fn swap_factor(size: Size) -> f64 { let target_w = std::cmp::min(size.w, MAX_SWAP_WINDOW_SIZE.0); let target_h = std::cmp::min(size.h, MAX_SWAP_WINDOW_SIZE.1); (target_w as f64 / size.w as f64).min(target_h as f64 / size.h as f64) } fn swap_geometry( size: Size, relative_to: Rectangle, ) -> Rectangle { let factor = swap_factor(size); let new_size = Size::from(( (size.w as f64 * factor).round() as i32, (size.h as f64 * factor).round() as i32, )); let loc = Point::from(( relative_to.loc.x + relative_to.size.w - new_size.w, relative_to.loc.y, )); Rectangle::new(loc, new_size) } fn geometries_for_groupview<'a, R>( tree: &Tree, renderer: impl Into>, non_exclusive_zone: Rectangle, seat: Option<&Seat>, scale: f64, alpha: f32, transition: f32, backdrop_id: &Id, mouse_tiling: Option>, swap_desc: Option, swap_tree: Option<&Tree>, _theme: &cosmic::theme::CosmicTheme, ) -> ( HashMap>, Vec>, ) where R: AsGlowRenderer + 'a, R::TextureId: 'static, CosmicMappedRenderElement: RenderElement, CosmicWindowRenderElement: RenderElement, { // we need to recalculate geometry for all elements, if we are drawing groups let gap: i32 = (if mouse_tiling.is_some() { GAP_MOUSE } else { GAP_KEYBOARD } as f32 * transition) .round() as i32; let mut renderer = renderer.into(); let root = tree.root_node_id(); let mut stack = Vec::new(); if swap_tree.is_some() { // push bogos value, that will get ignored anyway stack.push((Rectangle::from_size((320, 240).into()), 0)); } if root.is_some() { stack.push((non_exclusive_zone, 0)); } let mut elements = Vec::new(); let mut geometries: HashMap> = HashMap::new(); let alpha = alpha * transition; let focused = seat .and_then(|seat| { seat.get_keyboard() .unwrap() .current_focus() .and_then(|target| TilingLayout::currently_focused_node(tree, target)) }) .map(|(id, _)| id); let focused_geo = focused .as_ref() .map(|focused_id| *tree.get(focused_id).unwrap().data().geometry()); let has_potential_groups = if let Some(focused_id) = focused.as_ref() { let focused_node = tree.get(focused_id).unwrap(); if let Some(parent) = focused_node.parent() { let parent_node = tree.get(parent).unwrap(); parent_node.children().len() > 2 } else { false } } else { false }; for (tree, node_id) in root .into_iter() .flat_map(|root| tree.traverse_pre_order_ids(root).unwrap()) .map(|id| (tree, id)) .chain( swap_tree .as_ref() .filter(|_| swap_desc.as_ref().unwrap().stack_window.is_none()) .into_iter() .flat_map(|tree| { tree.traverse_pre_order_ids(&swap_desc.as_ref().unwrap().node) .unwrap() }) .map(|id| (*swap_tree.as_ref().unwrap(), id)), ) { if let Some((mut geo, depth)) = stack.pop() { let node: &Node = tree.get(&node_id).unwrap(); let data = node.data(); let is_placeholder_sibling = node .parent() .and_then(|parent_id| tree.children_ids(parent_id).ok()) .map(|mut siblings| { siblings.any(|child_id| tree.get(child_id).unwrap().data().is_placeholder()) }) .unwrap_or(false); let render_potential_group = swap_desc.is_none() && has_potential_groups && (if let Some(focused_id) = focused.as_ref() { // `focused` can move into us directly if let Some(parent) = node.parent() { let parent_data = tree.get(parent).unwrap().data(); let idx = tree .children_ids(parent) .unwrap() .position(|id| id == &node_id) .unwrap(); if let Some(focused_idx) = tree .children_ids(parent) .unwrap() .position(|id| id == focused_id) { // only direct neighbors focused_idx.abs_diff(idx) == 1 // skip neighbors, if this is a group of two && parent_data.len() > 2 } else { false } } else { false } } else { false }); let (element_gap_left, element_gap_up, element_gap_right, element_gap_down) = { let gap = if is_placeholder_sibling { PLACEHOLDER_GAP_MOUSE } else { gap }; ( if TilingLayout::has_sibling_node(tree, &node_id, Direction::Left) && (mouse_tiling.is_some() || depth > 0) { gap / 2 } else { 0 }, if TilingLayout::has_sibling_node(tree, &node_id, Direction::Up) && (mouse_tiling.is_some() || depth > 0) { gap / 2 } else { 0 }, if TilingLayout::has_sibling_node(tree, &node_id, Direction::Right) && (mouse_tiling.is_some() || depth > 0) { gap / 2 } else { 0 }, if TilingLayout::has_sibling_node(tree, &node_id, Direction::Down) && (mouse_tiling.is_some() || depth > 0) { gap / 2 } else { 0 }, ) }; let group_color = GROUP_COLOR; match data { Data::Group { orientation, last_geometry, sizes, alive, pill_indicator, } => { let render_active_child = if let Some(focused_id) = focused.as_ref() { !has_potential_groups && swap_desc.is_none() && node .children() .iter() .any(|child_id| child_id == focused_id) } else { false }; geo.loc += (element_gap_left, element_gap_up).into(); geo.size -= (element_gap_left, element_gap_up).into(); geo.size -= (element_gap_right, element_gap_down).into(); geometries.insert(node_id.clone(), geo); if let Some(renderer) = renderer.as_mut() { if (render_potential_group || render_active_child) && Some(&node_id) != root { elements.push( IndicatorShader::element( *renderer, Key::Group(Arc::downgrade(alive)), geo, 4, [if render_active_child { 16 } else { 8 }; 4], alpha * if render_potential_group { 0.40 } else { 1.0 }, scale, group_color, ) .into(), ); } if mouse_tiling.is_some() && pill_indicator.is_some() && Some(&node_id) != root { elements.push( IndicatorShader::element( *renderer, Key::Group(Arc::downgrade(alive)), geo, 4, [8; 4], alpha * 0.40, scale, group_color, ) .into(), ); } if mouse_tiling.is_some() && node .parent() .map(|parent_id| { matches!( tree.get(parent_id).unwrap().data(), Data::Group { pill_indicator: Some(_), .. } ) }) .unwrap_or(false) { // test if parent pill-indicator is adjacent let parent_id = node.parent().unwrap(); let parent = tree.get(parent_id).unwrap(); let own_idx = tree .children_ids(parent_id) .unwrap() .position(|child_id| child_id == &node_id) .unwrap(); let draw_outline = match parent.data() { Data::Group { pill_indicator, orientation, .. } => match pill_indicator { Some(PillIndicator::Inner(pill_idx)) => { *pill_idx == own_idx || pill_idx + 1 == own_idx } Some(PillIndicator::Outer(dir)) => match (dir, orientation) { (Direction::Left, Orientation::Horizontal) | (Direction::Right, Orientation::Horizontal) | (Direction::Up, Orientation::Vertical) | (Direction::Down, Orientation::Vertical) => true, (Direction::Left, Orientation::Vertical) | (Direction::Up, Orientation::Horizontal) => own_idx == 0, (Direction::Right, Orientation::Vertical) | (Direction::Down, Orientation::Horizontal) => { own_idx + 1 == parent.data().len() } }, None => unreachable!(), }, _ => unreachable!(), }; if draw_outline { elements.push( IndicatorShader::element( *renderer, Key::Group(Arc::downgrade(alive)), geo, 4, [8; 4], alpha * 0.15, scale, group_color, ) .into(), ); } } } geo.loc += (gap, gap).into(); geo.size -= (gap * 2, gap * 2).into(); if mouse_tiling.is_some() { if let Some(PillIndicator::Outer(direction)) = pill_indicator { let (pill_geo, remaining_geo) = match direction { Direction::Left => ( Rectangle::new( (geo.loc.x, geo.loc.y).into(), (16, geo.size.h).into(), ), Rectangle::new( (geo.loc.x + 48, geo.loc.y).into(), (geo.size.w - 48, geo.size.h).into(), ), ), Direction::Up => ( Rectangle::new( (geo.loc.x, geo.loc.y).into(), (geo.size.w, 16).into(), ), Rectangle::new( (geo.loc.x, geo.loc.y + 48).into(), (geo.size.w, geo.size.h - 48).into(), ), ), Direction::Right => ( Rectangle::new( (geo.loc.x + geo.size.w - 16, geo.loc.y).into(), (16, geo.size.h).into(), ), Rectangle::new(geo.loc, (geo.size.w - 48, geo.size.h).into()), ), Direction::Down => ( Rectangle::new( (geo.loc.x, geo.loc.y + geo.size.h - 16).into(), (geo.size.w, 16).into(), ), Rectangle::new(geo.loc, (geo.size.w, geo.size.h - 48).into()), ), }; if let Some(renderer) = renderer.as_mut() { elements.push( BackdropShader::element( *renderer, backdrop_id.clone(), pill_geo, 8., alpha * 0.4, group_color, ) .into(), ); } geo = remaining_geo; }; } if matches!(swap_desc, Some(ref desc) if desc.node == node_id) { if let Some(renderer) = renderer.as_mut() { elements.push( BackdropShader::element( *renderer, Key::Group(Arc::downgrade(alive)), geo, 8., alpha * if focused .as_ref() .map(|focused| { focused == &swap_desc.as_ref().unwrap().node }) .unwrap_or(false) { 0.4 } else { 0.15 }, group_color, ) .into(), ); } let swap_geo = swap_geometry( geo.size.as_logical(), focused_geo.unwrap_or({ let mut geo = non_exclusive_zone; geo.loc += (WINDOW_BACKDROP_BORDER, WINDOW_BACKDROP_BORDER).into(); geo.size -= (WINDOW_BACKDROP_BORDER * 2, WINDOW_BACKDROP_BORDER * 2).into(); geo }), ); geo = ease( Linear, EaseRectangle(geo), EaseRectangle(swap_geo), transition, ) .unwrap(); geometries.insert(node_id.clone(), geo); }; let previous_length = match orientation { Orientation::Horizontal => last_geometry.size.h, Orientation::Vertical => last_geometry.size.w, }; let new_length = match orientation { Orientation::Horizontal => geo.size.h, Orientation::Vertical => geo.size.w, }; let mut sizes = sizes .iter() .map(|len| { (((*len as f64) / (previous_length as f64)) * (new_length as f64)) .round() as i32 }) .collect::>(); let sum: i32 = sizes.iter().sum(); if sum < new_length { *sizes.last_mut().unwrap() += new_length - sum; } match orientation { Orientation::Horizontal => { let mut previous: i32 = sizes.iter().sum(); for (idx, size) in sizes.iter().enumerate().rev() { previous -= *size; let mut geo = Rectangle::new( (geo.loc.x, geo.loc.y + previous).into(), (geo.size.w, *size).into(), ); if mouse_tiling.is_some() { if let Some(PillIndicator::Inner(pill_idx)) = pill_indicator { if *pill_idx == idx { geo.size.h -= 32; } if idx .checked_sub(1) .map(|idx| idx == *pill_idx) .unwrap_or(false) { if let Some(renderer) = renderer.as_mut() { elements.push( BackdropShader::element( *renderer, backdrop_id.clone(), Rectangle::new( (geo.loc.x, geo.loc.y - 8).into(), (geo.size.w, 16).into(), ), 8., alpha * 0.4, group_color, ) .into(), ); } geo.loc.y += 32; geo.size.h -= 32; } } } stack.push((geo, depth + 1)); } } Orientation::Vertical => { let mut previous: i32 = sizes.iter().sum(); for (idx, size) in sizes.iter().enumerate().rev() { previous -= *size; let mut geo = Rectangle::new( (geo.loc.x + previous, geo.loc.y).into(), (*size, geo.size.h).into(), ); if mouse_tiling.is_some() { if let Some(PillIndicator::Inner(pill_idx)) = pill_indicator { if *pill_idx == idx { geo.size.w -= 32; } if idx .checked_sub(1) .map(|idx| idx == *pill_idx) .unwrap_or(false) { if let Some(renderer) = renderer.as_mut() { elements.push( BackdropShader::element( *renderer, backdrop_id.clone(), Rectangle::new( (geo.loc.x - 8, geo.loc.y).into(), (16, geo.size.h).into(), ), 8., alpha * 0.4, group_color, ) .into(), ); } geo.loc.x += 32; geo.size.w -= 32; } } } stack.push((geo, depth + 1)); } } } } Data::Mapped { mapped, .. } => { geo.loc += (element_gap_left, element_gap_up).into(); geo.size -= (element_gap_left, element_gap_up).into(); geo.size -= (element_gap_right, element_gap_down).into(); if let Some(renderer) = renderer.as_mut() { if render_potential_group { elements.push( IndicatorShader::element( *renderer, Key::Window(Usage::PotentialGroupIndicator, mapped.key()), geo, 4, [8; 4], alpha * 0.40, scale, group_color, ) .into(), ); geo.loc += (gap, gap).into(); geo.size -= (gap * 2, gap * 2).into(); } let accent = ACTIVE_GROUP_COLOR; if focused .as_ref() .map(|focused_id| { !tree .ancestor_ids(&node_id) .unwrap() .any(|id| id == focused_id) }) .unwrap_or(mouse_tiling.is_some()) { let color = match mouse_tiling { Some(Some(TargetZone::WindowStack(stack_id, _))) if *stack_id == node_id => { accent } _ => group_color, }; geo.loc += (WINDOW_BACKDROP_BORDER, WINDOW_BACKDROP_BORDER).into(); geo.size -= (WINDOW_BACKDROP_BORDER * 2, WINDOW_BACKDROP_BORDER * 2).into(); elements.push( BackdropShader::element( *renderer, Key::Window(Usage::OverviewBackdrop, mapped.key()), geo, 8., alpha * if focused .as_ref() .map(|focused_id| focused_id == &node_id) .unwrap_or(color == accent) { 0.4 } else { 0.15 }, color, ) .into(), ); } geo.loc += (WINDOW_BACKDROP_GAP, WINDOW_BACKDROP_GAP).into(); geo.size -= (WINDOW_BACKDROP_GAP * 2, WINDOW_BACKDROP_GAP * 2).into(); } if matches!(swap_desc, Some(ref desc) if desc.node == node_id && desc.stack_window.is_none()) { let swap_geo = swap_geometry( geo.size.as_logical(), focused_geo.unwrap_or({ let mut geo = non_exclusive_zone; geo.loc += (WINDOW_BACKDROP_BORDER, WINDOW_BACKDROP_BORDER).into(); geo.size -= (WINDOW_BACKDROP_BORDER * 2, WINDOW_BACKDROP_BORDER * 2).into(); geo }), ); geo = ease( Linear, EaseRectangle(geo), EaseRectangle(swap_geo), transition, ) .unwrap(); }; geometries.insert(node_id.clone(), geo); } Data::Placeholder { id, .. } => { geo.loc += (element_gap_left, element_gap_up).into(); geo.size -= (element_gap_left, element_gap_up).into(); geo.size -= (element_gap_right, element_gap_down).into(); if let Some(renderer) = renderer.as_mut() { geo.loc += (WINDOW_BACKDROP_BORDER, WINDOW_BACKDROP_BORDER).into(); geo.size -= (WINDOW_BACKDROP_BORDER * 2, WINDOW_BACKDROP_BORDER * 2).into(); elements.push( BackdropShader::element( *renderer, id.clone(), geo, 8., alpha * 0.4, group_color, ) .into(), ); } geometries.insert(node_id.clone(), geo); } } } } if root.is_none() { elements.clear(); } (geometries, elements) } fn render_old_tree_popups( reference_tree: &Tree, target_tree: &Tree, renderer: &mut R, geometries: Option>>, output_scale: f64, percentage: f32, is_swap_mode: bool, ) -> Vec> where R: AsGlowRenderer, R::TextureId: Send + Clone + 'static, CosmicMappedRenderElement: RenderElement, CosmicWindowRenderElement: RenderElement, CosmicStackRenderElement: RenderElement, { let mut elements = Vec::default(); render_old_tree( reference_tree, target_tree, geometries, output_scale, percentage, is_swap_mode, |mapped, elem_geometry, geo, alpha, _| { elements.extend( mapped.popup_render_elements::>( renderer, geo.loc.as_logical().to_physical_precise_round(output_scale) - elem_geometry.loc, Scale::from(output_scale), alpha, ), ); }, ); elements } fn render_old_tree_windows( reference_tree: &Tree, target_tree: &Tree, renderer: &mut R, geometries: Option>>, output_scale: f64, percentage: f32, indicator_thickness: u8, is_swap_mode: bool, theme: &cosmic::theme::CosmicTheme, ) -> Vec> where R: AsGlowRenderer, R::TextureId: Send + Clone + 'static, CosmicMappedRenderElement: RenderElement, CosmicWindowRenderElement: RenderElement, CosmicStackRenderElement: RenderElement, { let window_hint = crate::theme::active_window_hint(theme); let mut elements = Vec::default(); let mut shadow_elements = Vec::default(); render_old_tree( reference_tree, target_tree, geometries, output_scale, percentage, is_swap_mode, |mapped, elem_geometry, geo, alpha, is_minimizing| { shadow_elements.extend(mapped.shadow_render_element( renderer, geo.loc.as_logical().to_physical_precise_round(output_scale) - elem_geometry.loc, Some(geo.size.as_logical()), Scale::from(output_scale), 1., alpha, )); let window_elements = mapped.render_elements::>( renderer, geo.loc.as_logical().to_physical_precise_round(output_scale) - elem_geometry.loc, Some(geo.size.as_logical()), Scale::from(output_scale), alpha, None, ); elements.extend(window_elements.into_iter().flat_map(|element| { match element { CosmicMappedRenderElement::Stack(elem) => constrain_render_elements( std::iter::once(elem), geo.loc.as_logical().to_physical_precise_round(output_scale) - elem_geometry.loc, geo.as_logical().to_physical_precise_round(output_scale), elem_geometry, ConstrainScaleBehavior::Stretch, ConstrainAlign::CENTER, output_scale, ) .next() .map(CosmicMappedRenderElement::TiledStack), CosmicMappedRenderElement::Window(elem) => constrain_render_elements( std::iter::once(elem), geo.loc.as_logical().to_physical_precise_round(output_scale) - elem_geometry.loc, geo.as_logical().to_physical_precise_round(output_scale), elem_geometry, ConstrainScaleBehavior::Stretch, ConstrainAlign::CENTER, output_scale, ) .next() .map(CosmicMappedRenderElement::TiledWindow), x => Some(x), } })); let radius = mapped.corner_radius(geo.size.as_logical(), indicator_thickness); if is_minimizing && indicator_thickness > 0 { elements.push(CosmicMappedRenderElement::FocusIndicator( IndicatorShader::focus_element( renderer, Key::Window(Usage::FocusIndicator, mapped.clone().key()), geo, indicator_thickness, radius, alpha, output_scale, [window_hint.red, window_hint.green, window_hint.blue], ), )); } }, ); shadow_elements .into_iter() .chain(elements.into_iter()) .collect() } fn render_old_tree( reference_tree: &Tree, target_tree: &Tree, geometries: Option>>, output_scale: f64, percentage: f32, is_swap_mode: bool, mut processor: impl FnMut(&CosmicMapped, Rectangle, Rectangle, f32, bool), ) { if let Some(root) = reference_tree.root_node_id() { let geometries = geometries.unwrap_or_default(); reference_tree .traverse_pre_order_ids(root) .unwrap() .filter(|node_id| reference_tree.get(node_id).unwrap().data().is_mapped(None)) .map( |node_id| match reference_tree.get(&node_id).unwrap().data() { Data::Mapped { mapped, last_geometry, minimize_rect, .. } => ( mapped, last_geometry, geometries.get(&node_id).copied(), minimize_rect, ), _ => unreachable!(), }, ) .filter(|(mapped, _, _, _)| !mapped.is_maximized(false)) .filter(|(mapped, _, _, _)| { if let Some(root) = target_tree.root_node_id() { is_swap_mode || !target_tree .traverse_pre_order(root) .unwrap() .any(|node| node.data().is_mapped(Some(mapped))) } else { true } }) .for_each(|(mapped, original_geo, mut scaled_geo, minimize_geo)| { if let Some(minimize_geo) = minimize_geo { scaled_geo = Some( ease( EaseInOutCubic, EaseRectangle(*original_geo), EaseRectangle(*minimize_geo), percentage, ) .unwrap(), ); } let (scale, offset) = scaled_geo .map(|adapted_geo| scale_to_center(original_geo, &adapted_geo)) .unwrap_or_else(|| (1.0, (0, 0).into())); let geo = scaled_geo .map(|adapted_geo| { Rectangle::new( adapted_geo.loc + offset, (original_geo.size.to_f64() * scale).to_i32_round(), ) }) .unwrap_or(*original_geo); let alpha = if minimize_geo.is_some() { 1.0 - ((percentage - 0.5).max(0.0) * 2.0) } else { 1.0 - percentage }; let elem_geometry = mapped.geometry().to_physical_precise_round(output_scale); processor(mapped, elem_geometry, geo, alpha, minimize_geo.is_some()) }); } } fn render_new_tree_popups( target_tree: &Tree, reference_tree: Option<&Tree>, renderer: &mut R, geometries: Option>>, old_geometries: Option>>, seat: Option<&Seat>, output: &Output, percentage: f32, overview: (OverviewMode, Option<(SwapIndicator, Option<&Tree>)>), swap_desc: Option, ) -> Vec> where R: AsGlowRenderer, R::TextureId: Send + Clone + 'static, CosmicMappedRenderElement: RenderElement, CosmicWindowRenderElement: RenderElement, CosmicStackRenderElement: RenderElement, { let mut popup_elements = Vec::new(); let output_scale = output.current_scale().fractional_scale(); let is_active_output = seat .map(|seat| &seat.active_output() == output) .unwrap_or(false); let (_, swap_tree) = overview.1.unzip(); let swap_desc = swap_desc.filter(|_| is_active_output); let swap_tree = swap_tree.flatten().filter(|_| is_active_output); render_new_tree( target_tree, reference_tree, geometries, old_geometries, percentage, swap_tree, swap_desc.as_ref(), |_node_id, data, geo, _original_geo, alpha, _| { if let Data::Mapped { mapped, .. } = data { let elem_geometry = mapped.geometry().to_physical_precise_round(output_scale); popup_elements.extend( mapped.popup_render_elements::>( renderer, geo.loc.as_logical().to_physical_precise_round(output_scale) - elem_geometry.loc, Scale::from(output_scale), alpha, ), ); } }, ); popup_elements } fn render_new_tree_windows( target_tree: &Tree, reference_tree: Option<&Tree>, renderer: &mut R, non_exclusive_zone: Rectangle, geometries: Option>>, old_geometries: Option>>, is_overview: bool, seat: Option<&Seat>, output: &Output, percentage: f32, transition: Option, indicator_thickness: u8, overview: (OverviewMode, Option<(SwapIndicator, Option<&Tree>)>), mut resize_indicator: Option<(ResizeMode, ResizeIndicator)>, swap_desc: Option, swapping_stack_surface_id: &Id, backdrop_id: &Id, theme: &cosmic::theme::CosmicTheme, ) -> Vec> where R: AsGlowRenderer, R::TextureId: Send + Clone + 'static, CosmicMappedRenderElement: RenderElement, CosmicWindowRenderElement: RenderElement, CosmicStackRenderElement: RenderElement, { let focused = seat .and_then(|seat| { seat.get_keyboard() .unwrap() .current_focus() .and_then(|target| TilingLayout::currently_focused_node(target_tree, target)) }) .map(|(id, _)| id); let focused_geo = if let Some(focused) = focused.as_ref() { geometries .as_ref() .and_then(|geometries| geometries.get(focused)) .or_else(|| { target_tree .get(focused) .ok() .map(|node| node.data().geometry()) }) .cloned() } else { None } .unwrap_or({ let mut geo = non_exclusive_zone; geo.loc += (WINDOW_BACKDROP_BORDER, WINDOW_BACKDROP_BORDER).into(); geo.size -= (WINDOW_BACKDROP_BORDER * 2, WINDOW_BACKDROP_BORDER * 2).into(); geo }); let is_active_output = seat .map(|seat| &seat.active_output() == output) .unwrap_or(false); let mut animating_window_elements = Vec::new(); let mut window_elements = Vec::new(); let mut group_backdrop = None; let mut indicators = Vec::new(); let mut resize_elements = None; let mut swap_elements = Vec::new(); let mut shadow_elements = Vec::new(); let output_geo = output.geometry(); let output_scale = output.current_scale().fractional_scale(); let (swap_indicator, swap_tree) = overview.1.unzip(); let swap_desc = swap_desc.filter(|_| is_active_output); let swap_tree = swap_tree.flatten().filter(|_| is_active_output); let window_hint = crate::theme::active_window_hint(theme); let group_color = GROUP_COLOR; // render placeholder, if we are swapping to an empty workspace if target_tree.root_node_id().is_none() && swap_desc.is_some() { window_elements.push( BackdropShader::element( renderer, backdrop_id.clone(), focused_geo, 8., transition.unwrap_or(1.0) * 0.4, group_color, ) .into(), ); } // render single stack window when swapping separately if let Some(window) = swap_desc .as_ref() .and_then(|desc| desc.stack_window.clone()) { let window_geo = window.geometry(); let origin = { let mut geo = focused_geo; geo.loc.x += STACK_TAB_HEIGHT; geo.size.h -= STACK_TAB_HEIGHT; geo }; let target = swap_geometry(window_geo.size, focused_geo); let swap_geo = ease( Linear, EaseRectangle(origin), EaseRectangle(target), transition.unwrap_or(1.0), ) .unwrap(); let scale = swap_geo.size.to_f64() / origin.size.to_f64(); let radius = theme .radius_s() .map(|x| if x < 4.0 { x } else { x + 4.0 }) .map(|val| (val * scale.x.min(scale.y) as f32).round() as u8); swap_elements.push(CosmicMappedRenderElement::FocusIndicator( IndicatorShader::focus_element( renderer, Key::from(swapping_stack_surface_id.clone()), swap_geo, 4, radius, transition.unwrap_or(1.0), output_scale, [window_hint.red, window_hint.green, window_hint.blue], ), )); let render_loc = (swap_geo.loc.as_logical() - window_geo.loc).to_physical_precise_round(output_scale); swap_elements.extend( AsRenderElements::render_elements::>( &window, renderer, render_loc, output_scale.into(), 1.0, ) .into_iter() .map(|window| { CosmicMappedRenderElement::GrabbedWindow(RescaleRenderElement::from_element( window, swap_geo .loc .as_logical() .to_physical_precise_round(output_scale), ease( Linear, 1.0, swap_factor(window_geo.size), transition.unwrap_or(1.0), ), )) }), ) } // render actual tree nodes render_new_tree( target_tree, reference_tree, geometries, old_geometries, percentage, swap_tree, swap_desc.as_ref(), |node_id, data, geo, original_geo, alpha, animating| { if swap_desc.as_ref().map(|desc| &desc.node) == Some(&node_id) || focused.as_ref() == Some(&node_id) { if indicator_thickness > 0 || data.is_group() { let mut geo = geo; let scale = geo.size.to_f64() / original_geo.size.to_f64(); let radius = match data { Data::Mapped { mapped, .. } if swap_desc .as_ref() .map(|desc| &desc.node) .is_none_or(|n| n != &node_id) => { mapped .corner_radius(geo.size.as_logical(), indicator_thickness) .map(|val| (val as f64 * scale.x.min(scale.y)).round() as u8) } _ => theme .radius_s() .map(|x| if x < 4.0 { x } else { x + 4.0 }) .map(|val| (val * scale.x.min(scale.y) as f32).round() as u8), }; if data.is_group() { let outer_gap: i32 = (if is_overview { GAP_KEYBOARD } else { 4 } as f32 * percentage) .round() as i32; geo.loc += (outer_gap, outer_gap).into(); geo.size -= (outer_gap * 2, outer_gap * 2).into(); let backdrop = BackdropShader::element( renderer, match data { Data::Group { alive, .. } => Key::Group(Arc::downgrade(alive)), _ => unreachable!(), }, geo, radius[0] as f32, 0.4, group_color, ); if focused.as_ref() == Some(&node_id) { group_backdrop = Some(backdrop); } else { indicators.push(backdrop.into()); } } if !swap_desc .as_ref() .map(|desc| desc.stack_window.is_some()) .unwrap_or(false) || focused.as_ref() == Some(&node_id) { indicators.push(CosmicMappedRenderElement::FocusIndicator( IndicatorShader::focus_element( renderer, match data { Data::Mapped { mapped, .. } => { Key::Window(Usage::FocusIndicator, mapped.clone().key()) } Data::Group { alive, .. } => Key::Group(Arc::downgrade(alive)), _ => unreachable!(), }, geo, if data.is_group() { 4 } else { indicator_thickness }, radius, alpha, output_scale, [window_hint.red, window_hint.green, window_hint.blue], ), )); } if focused.as_ref() == Some(&node_id) && (swap_desc.as_ref().map(|desc| &desc.node) != Some(&node_id) || swap_desc .as_ref() .and_then(|swap_desc| swap_desc.stack_window.as_ref()) .zip(focused.as_ref()) .map(|(stack_window, focused_id)| { target_tree .get(focused_id) .ok() .map(|focused| match focused.data() { Data::Mapped { mapped, .. } => mapped .stack_ref() .map(|stack| &stack.active() != stack_window) .unwrap_or(false), _ => false, }) .unwrap_or(false) }) .unwrap_or(false)) { if let Some(swap) = swap_indicator.as_ref() { swap.resize(geo.size.as_logical()); swap.output_enter(output, output_geo.as_logical()); swap_elements.extend( swap.render_elements::>( renderer, geo.loc.as_logical().to_physical_precise_round(output_scale), output_scale.into(), alpha * overview.0.alpha().unwrap_or(1.0), ) .into_iter() .map(CosmicMappedRenderElement::from), ); } } } if let Some((mode, resize)) = resize_indicator.as_mut() { let mut geo = geo; geo.loc -= (18, 18).into(); geo.size += (36, 36).into(); resize.resize(geo.size.as_logical()); resize.output_enter(output, output_geo.as_logical()); let possible_edges = TilingLayout::possible_resizes(target_tree, node_id.clone()); if !possible_edges.is_empty() { if resize.with_program(|internal| { let mut edges = internal.edges.lock().unwrap(); if *edges != possible_edges { *edges = possible_edges; true } else { false } }) { resize.force_update(); } resize_elements = Some( resize .render_elements::>( renderer, geo.loc.as_logical().to_physical_precise_round(output_scale), output_scale.into(), alpha * mode.alpha().unwrap_or(1.0), ) .into_iter() .map(CosmicMappedRenderElement::from) .collect::>(), ); } } } if let Data::Mapped { mapped, .. } = data { let elem_geometry = mapped.geometry().to_physical_precise_round(output_scale); let scale = geo.size.to_f64() / original_geo.size.to_f64(); // In overview mode, don't pass max_size to avoid pre-clipping. // Let constrain_render_elements handle scaling instead. let max_size = if is_overview { None } else { Some(geo.size.as_logical()) }; let shadow_element = mapped.shadow_render_element( renderer, geo.loc.as_logical().to_physical_precise_round(output_scale) - elem_geometry.loc, max_size, Scale::from(output_scale), scale.x.min(scale.y), alpha, ); let mut elements = mapped.render_elements::>( renderer, //original_location, geo.loc.as_logical().to_physical_precise_round(output_scale) - elem_geometry.loc, max_size, Scale::from(output_scale), alpha, None, ); if swap_desc .as_ref() .filter(|swap_desc| swap_desc.node == node_id) .and_then(|swap_desc| swap_desc.stack_window.as_ref()) .zip(focused.as_ref()) .map(|(stack_window, focused_id)| { target_tree .get(focused_id) .ok() .map(|focused| match focused.data() { Data::Mapped { mapped, .. } => mapped .stack_ref() .map(|stack| &stack.active() == stack_window) .unwrap_or(false), _ => false, }) .unwrap_or(false) }) .unwrap_or(false) { let mut active_geo = mapped.active_window_geometry().as_local(); active_geo.loc += geo.loc - mapped.geometry().loc.as_local(); elements.insert( 0, CosmicMappedRenderElement::Overlay(BackdropShader::element( renderer, Key::Window(Usage::Overlay, mapped.key()), active_geo, 0.0, 0.3, group_color, )), ) } let (behavior, align) = if is_overview { (ConstrainScaleBehavior::Fit, ConstrainAlign::CENTER) } else if animating { (ConstrainScaleBehavior::Stretch, ConstrainAlign::TOP_LEFT) } else { (ConstrainScaleBehavior::CutOff, ConstrainAlign::TOP_LEFT) }; let elements = elements.into_iter().flat_map(|element| match element { CosmicMappedRenderElement::Stack(elem) => constrain_render_elements( std::iter::once(elem), geo.loc.as_logical().to_physical_precise_round(output_scale) - elem_geometry.loc, geo.as_logical().to_physical_precise_round(output_scale), elem_geometry, behavior, align, output_scale, ) .next() .map(CosmicMappedRenderElement::TiledStack), CosmicMappedRenderElement::Window(elem) => constrain_render_elements( std::iter::once(elem), geo.loc.as_logical().to_physical_precise_round(output_scale) - elem_geometry.loc, geo.as_logical().to_physical_precise_round(output_scale), elem_geometry, behavior, align, output_scale, ) .next() .map(CosmicMappedRenderElement::TiledWindow), CosmicMappedRenderElement::Overlay(elem) => constrain_render_elements( std::iter::once(elem), geo.loc.as_logical().to_physical_precise_round(output_scale) - elem_geometry.loc, geo.as_logical().to_physical_precise_round(output_scale), elem_geometry, behavior, align, output_scale, ) .next() .map(CosmicMappedRenderElement::TiledOverlay), x => Some(x), }); if swap_desc .as_ref() .map(|swap_desc| { (swap_desc.node == node_id || target_tree.ancestor_ids(&node_id).ok().is_none_or( |mut ancestors| ancestors.any(|id| &swap_desc.node == id), )) && swap_desc.stack_window.is_none() }) .unwrap_or(false) { swap_elements.extend(shadow_element); swap_elements.extend(elements); } else { shadow_elements.extend(shadow_element); if animating { animating_window_elements.extend(elements); } else { window_elements.extend(elements); } } } }, ); resize_elements .into_iter() .flatten() .chain(swap_elements) .chain(indicators.into_iter().map(Into::into)) .chain(window_elements) .chain(animating_window_elements) .chain(shadow_elements) .chain(group_backdrop.into_iter().map(Into::into)) .collect() } fn render_new_tree( target_tree: &Tree, reference_tree: Option<&Tree>, geometries: Option>>, old_geometries: Option>>, percentage: f32, swap_tree: Option<&Tree>, swap_desc: Option<&NodeDesc>, mut processor: impl FnMut(NodeId, &Data, Rectangle, &Rectangle, f32, bool), ) { let old_geometries = old_geometries.unwrap_or_default(); let geometries = geometries.unwrap_or_default(); target_tree .root_node_id() .into_iter() .flat_map(|root| target_tree.traverse_pre_order_ids(root).unwrap()) .map(|id| (target_tree, id)) .chain( swap_tree .into_iter() .flat_map(|tree| { let sub_root = &swap_desc.unwrap().node; if swap_desc.unwrap().stack_window.is_none() { Some( tree.traverse_pre_order_ids(sub_root) .unwrap() .map(move |id| (tree, id)), ) } else { None } }) .flatten(), ) .for_each(|(target_tree, node_id)| { let data = target_tree.get(&node_id).unwrap().data(); let (original_geo, scaled_geo) = (data.geometry(), geometries.get(&node_id)); let (old_original_geo, old_scaled_geo) = if let Some(reference_tree) = reference_tree.as_ref() { if let Some(root) = reference_tree.root_node_id() { reference_tree .traverse_pre_order_ids(root) .unwrap() .find(|id| &node_id == id) .map(|node_id| { ( reference_tree.get(&node_id).unwrap().data().geometry(), old_geometries.get(&node_id), ) }) } else { None } } else { None } .unzip(); let mut old_geo = old_original_geo.map(|original_geo| { let (scale, offset) = old_scaled_geo .unwrap() .map(|adapted_geo| scale_to_center(original_geo, adapted_geo)) .unwrap_or_else(|| (1.0, (0, 0).into())); ( old_scaled_geo .unwrap() .map(|adapted_geo| { Rectangle::new( adapted_geo.loc + offset, (original_geo.size.to_f64() * scale).to_i32_round(), ) }) .unwrap_or(*original_geo), 1.0, ) }); let was_minimized = if let Data::Mapped { minimize_rect: Some(minimize_rect), .. } = &data { old_geo = Some((*minimize_rect, (percentage * 2.0).min(1.0))); true } else { false }; let (scale, offset) = scaled_geo .map(|adapted_geo| scale_to_center(original_geo, adapted_geo)) .unwrap_or_else(|| (1.0, (0, 0).into())); let new_geo = scaled_geo .map(|adapted_geo| { Rectangle::new( adapted_geo.loc + offset, ( (original_geo.size.w as f64 * scale).round() as i32, (original_geo.size.h as f64 * scale).round() as i32, ) .into(), ) }) .unwrap_or(*original_geo); let (geo, alpha, animating) = if let Some((old_geo, alpha)) = old_geo.filter(|_| { swap_desc .map(|desc| desc.node != node_id && desc.stack_window.is_none()) .unwrap_or(true) }) { ( if was_minimized { ease( EaseInOutCubic, EaseRectangle(old_geo), EaseRectangle(new_geo), percentage, ) .unwrap() } else { ease( Linear, EaseRectangle(old_geo), EaseRectangle(new_geo), percentage, ) .unwrap() }, alpha, old_geo != new_geo, ) } else { (new_geo, percentage, false) }; if let Data::Mapped { mapped, .. } = data { if mapped.is_maximized(false) { return; } } processor(node_id, data, geo, original_geo, alpha, animating) }); } fn scale_to_center( old_geo: &Rectangle, new_geo: &Rectangle, ) -> (f64, Point) { let scale_w = new_geo.size.w as f64 / old_geo.size.w as f64; let scale_h = new_geo.size.h as f64 / old_geo.size.h as f64; if scale_w > scale_h { ( scale_h, ( ((new_geo.size.w as f64 - old_geo.size.w as f64 * scale_h) / 2.0).round() as i32, 0, ) .into(), ) } else { ( scale_w, ( 0, ((new_geo.size.h as f64 - old_geo.size.h as f64 * scale_w) / 2.0).round() as i32, ) .into(), ) } }