This should fix super+click on a tiled window poping it out of the tree and tiling it to the right half of the screen (default drop window behavior).
5889 lines
235 KiB
Rust
5889 lines
235 KiB
Rust
// 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::{
|
|
ImportAll, ImportMem, 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<CosmicSurface>,
|
|
pub focus_stack: Vec<NodeId>,
|
|
}
|
|
|
|
#[derive(Debug, Clone, PartialEq)]
|
|
enum TargetZone {
|
|
Initial,
|
|
InitialPlaceholder(NodeId),
|
|
WindowStack(NodeId, Rectangle<i32, Local>),
|
|
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<Data>, Duration, Option<TilingBlocker>)>,
|
|
animation_start: Option<Instant>,
|
|
}
|
|
|
|
impl TreeQueue {
|
|
pub fn push_tree(
|
|
&mut self,
|
|
tree: Tree<Data>,
|
|
duration: impl Into<Option<Duration>>,
|
|
blocker: Option<TilingBlocker>,
|
|
) {
|
|
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<Instant>, 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<i32>,
|
|
last_geometry: Rectangle<i32, Local>,
|
|
alive: Arc<()>,
|
|
pill_indicator: Option<PillIndicator>,
|
|
},
|
|
Mapped {
|
|
mapped: CosmicMapped,
|
|
last_geometry: Rectangle<i32, Local>,
|
|
minimize_rect: Option<Rectangle<i32, Local>>,
|
|
},
|
|
Placeholder {
|
|
id: Id,
|
|
last_geometry: Rectangle<i32, Local>,
|
|
type_: PlaceholderType,
|
|
},
|
|
}
|
|
|
|
#[derive(Debug, Clone)]
|
|
pub enum PlaceholderType {
|
|
GrabbedWindow,
|
|
DropZone,
|
|
}
|
|
|
|
impl Data {
|
|
fn new_group(orientation: Orientation, geo: Rectangle<i32, Local>) -> 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<i32, Local> {
|
|
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<i32, Local>) {
|
|
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<NodeId>, Weak<()>),
|
|
Window(CosmicMapped),
|
|
}
|
|
|
|
#[derive(Debug, Clone)]
|
|
pub struct RestoreTilingState {
|
|
pub parent: Option<id_tree::NodeId>,
|
|
pub sibling: Option<id_tree::NodeId>,
|
|
pub orientation: Orientation,
|
|
pub idx: usize,
|
|
pub sizes: Vec<i32>,
|
|
}
|
|
|
|
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<impl Iterator<Item = &'a FocusTarget> + 'a>,
|
|
direction: Option<Direction>,
|
|
) {
|
|
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<CosmicMapped>,
|
|
focus_stack: Option<impl Iterator<Item = &'a FocusTarget> + 'a>,
|
|
direction: Option<Direction>,
|
|
minimize_rect: Option<Rectangle<i32, Local>>,
|
|
) {
|
|
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<Rectangle<i32, Local>>,
|
|
tiling_state: Option<RestoreTilingState>,
|
|
focus_stack: Option<impl Iterator<Item = &'a FocusTarget> + '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<Data>,
|
|
window: impl Into<CosmicMapped>,
|
|
output: &Output,
|
|
node: Option<NodeId>,
|
|
direction: Option<Direction>,
|
|
minimize_rect: Option<Rectangle<i32, Local>>,
|
|
) {
|
|
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<State>,
|
|
focus_stack: impl Iterator<Item = &'a FocusTarget> + 'a,
|
|
desc: NodeDesc,
|
|
direction: Option<Direction>,
|
|
) -> Option<KeyboardFocusTarget> {
|
|
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::<Vec<_>>();
|
|
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::<Vec<_>>();
|
|
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<KeyboardFocusTarget> {
|
|
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::<Vec<_>>();
|
|
let mut other_children = other_node
|
|
.children()
|
|
.iter()
|
|
.map(|child_id| (this_desc.node.clone(), child_id.clone()))
|
|
.collect::<Vec<_>>();
|
|
|
|
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::<Vec<_>>();
|
|
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::<Vec<_>>();
|
|
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::<Vec<_>>();
|
|
let cleanup = other_tree
|
|
.children_ids(&other_desc.node)
|
|
.unwrap()
|
|
.cloned()
|
|
.collect::<Vec<_>>();
|
|
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::<Vec<_>>();
|
|
let cleanup = this_tree
|
|
.children_ids(&this_desc.node)
|
|
.unwrap()
|
|
.cloned()
|
|
.collect::<Vec<_>>();
|
|
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<KeyboardFocusTarget> {
|
|
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::<Vec<_>>(),
|
|
})),
|
|
_ => None,
|
|
}
|
|
}
|
|
|
|
pub fn tree(&self) -> &Tree<Data> {
|
|
&self.queue.trees.back().unwrap().0
|
|
}
|
|
|
|
pub fn unmap(
|
|
&mut self,
|
|
window: &CosmicMapped,
|
|
to: Option<Rectangle<i32, Local>>,
|
|
) -> Result<Option<RestoreTilingState>, 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<NodeId> {
|
|
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<Data>, 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<Rectangle<i32, Local>> {
|
|
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<State>) -> 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<State>,
|
|
focus_stack: impl Iterator<Item = &'a FocusTarget> + 'a,
|
|
swap_desc: Option<NodeDesc>,
|
|
) -> 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<Orientation>, seat: &Seat<State>) {
|
|
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<KeyboardFocusTarget> {
|
|
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<State>,
|
|
mut focus_stack: FocusStackMut,
|
|
) -> Option<KeyboardFocusTarget> {
|
|
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::<Vec<_>>();
|
|
|
|
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::<Vec<_>>()
|
|
.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::<Vec<_>>();
|
|
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<ClientId, Client> {
|
|
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::<Vec<_>>()
|
|
.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<Data>, 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<Rectangle<i32, Local>> {
|
|
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 gaps = self.gaps();
|
|
|
|
let mut tree = self.queue.trees.back().unwrap().0.copy_clone();
|
|
|
|
if let Some(root) = tree.root_node_id() {
|
|
for id in tree
|
|
.traverse_pre_order_ids(root)
|
|
.unwrap()
|
|
.collect::<Vec<_>>()
|
|
.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);
|
|
}
|
|
}
|
|
|
|
pub fn drop_window(&mut self, window: CosmicMapped) -> (CosmicMapped, Point<i32, Local>) {
|
|
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::<Vec<_>>()
|
|
.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<Data>,
|
|
mut focus_stack: impl Iterator<Item = &'a FocusTarget>,
|
|
) -> 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<Data>,
|
|
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<Data>,
|
|
old_id: &NodeId,
|
|
new_id: &NodeId,
|
|
orientation: Orientation,
|
|
) -> Result<NodeId, NodeIdError> {
|
|
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<Data>, 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<Data>, 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<Data>,
|
|
gaps: (i32, i32),
|
|
) -> Option<TilingBlocker> {
|
|
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::<Vec<_>>()
|
|
.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<f64, Local>,
|
|
) -> Option<KeyboardFocusTarget> {
|
|
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<f64, Local>,
|
|
) -> Option<KeyboardFocusTarget> {
|
|
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<f64, Local>,
|
|
overview: OverviewMode,
|
|
) -> Option<(PointerFocusTarget, Point<f64, Local>)> {
|
|
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<f64, Local>,
|
|
overview: OverviewMode,
|
|
) -> Option<(PointerFocusTarget, Point<f64, Local>)> {
|
|
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<Point<f64, Local>>,
|
|
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<Item = (&CosmicMapped, Rectangle<i32, Local>)> {
|
|
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<Item = (CosmicSurface, Rectangle<i32, Local>)> + '_ {
|
|
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<Data>, dst: &mut Tree<Data>, 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<R>(
|
|
&self,
|
|
renderer: &mut R,
|
|
seat: Option<&Seat<State>>,
|
|
non_exclusive_zone: Rectangle<i32, Local>,
|
|
overview: (OverviewMode, Option<(SwapIndicator, Option<&Tree<Data>>)>),
|
|
resize_indicator: Option<(ResizeMode, ResizeIndicator)>,
|
|
indicator_thickness: u8,
|
|
theme: &cosmic::theme::CosmicTheme,
|
|
) -> Result<Vec<CosmicMappedRenderElement<R>>, OutputNotMapped>
|
|
where
|
|
R: Renderer + ImportAll + ImportMem + AsGlowRenderer,
|
|
R::TextureId: Send + Clone + 'static,
|
|
CosmicMappedRenderElement<R>: RenderElement<R>,
|
|
CosmicWindowRenderElement<R>: RenderElement<R>,
|
|
CosmicStackRenderElement<R>: RenderElement<R>,
|
|
{
|
|
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<R>(
|
|
&self,
|
|
renderer: &mut R,
|
|
seat: Option<&Seat<State>>,
|
|
non_exclusive_zone: Rectangle<i32, Local>,
|
|
overview: (OverviewMode, Option<(SwapIndicator, Option<&Tree<Data>>)>),
|
|
theme: &cosmic::theme::CosmicTheme,
|
|
) -> Result<Vec<CosmicMappedRenderElement<R>>, OutputNotMapped>
|
|
where
|
|
R: Renderer + ImportAll + ImportMem + AsGlowRenderer,
|
|
R::TextureId: Send + Clone + 'static,
|
|
CosmicMappedRenderElement<R>: RenderElement<R>,
|
|
CosmicWindowRenderElement<R>: RenderElement<R>,
|
|
CosmicStackRenderElement<R>: RenderElement<R>,
|
|
{
|
|
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<i32, Logical>) -> 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<i32, Logical>,
|
|
relative_to: Rectangle<i32, Local>,
|
|
) -> Rectangle<i32, Local> {
|
|
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<Data>,
|
|
renderer: impl Into<Option<&'a mut R>>,
|
|
non_exclusive_zone: Rectangle<i32, Local>,
|
|
seat: Option<&Seat<State>>,
|
|
scale: f64,
|
|
alpha: f32,
|
|
transition: f32,
|
|
backdrop_id: &Id,
|
|
mouse_tiling: Option<Option<&TargetZone>>,
|
|
swap_desc: Option<NodeDesc>,
|
|
swap_tree: Option<&Tree<Data>>,
|
|
_theme: &cosmic::theme::CosmicTheme,
|
|
) -> (
|
|
HashMap<NodeId, Rectangle<i32, Local>>,
|
|
Vec<CosmicMappedRenderElement<R>>,
|
|
)
|
|
where
|
|
R: Renderer + ImportAll + ImportMem + AsGlowRenderer + 'a,
|
|
R::TextureId: 'static,
|
|
CosmicMappedRenderElement<R>: RenderElement<R>,
|
|
CosmicWindowRenderElement<R>: RenderElement<R>,
|
|
{
|
|
// 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<NodeId, Rectangle<i32, Local>> = 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<Data> = 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::<Vec<_>>();
|
|
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<R>(
|
|
reference_tree: &Tree<Data>,
|
|
target_tree: &Tree<Data>,
|
|
renderer: &mut R,
|
|
geometries: Option<HashMap<NodeId, Rectangle<i32, Local>>>,
|
|
output_scale: f64,
|
|
percentage: f32,
|
|
is_swap_mode: bool,
|
|
) -> Vec<CosmicMappedRenderElement<R>>
|
|
where
|
|
R: Renderer + ImportAll + ImportMem + AsGlowRenderer,
|
|
R::TextureId: Send + Clone + 'static,
|
|
CosmicMappedRenderElement<R>: RenderElement<R>,
|
|
CosmicWindowRenderElement<R>: RenderElement<R>,
|
|
CosmicStackRenderElement<R>: RenderElement<R>,
|
|
{
|
|
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::<R, CosmicMappedRenderElement<R>>(
|
|
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<R>(
|
|
reference_tree: &Tree<Data>,
|
|
target_tree: &Tree<Data>,
|
|
renderer: &mut R,
|
|
geometries: Option<HashMap<NodeId, Rectangle<i32, Local>>>,
|
|
output_scale: f64,
|
|
percentage: f32,
|
|
indicator_thickness: u8,
|
|
is_swap_mode: bool,
|
|
theme: &cosmic::theme::CosmicTheme,
|
|
) -> Vec<CosmicMappedRenderElement<R>>
|
|
where
|
|
R: Renderer + ImportAll + ImportMem + AsGlowRenderer,
|
|
R::TextureId: Send + Clone + 'static,
|
|
CosmicMappedRenderElement<R>: RenderElement<R>,
|
|
CosmicWindowRenderElement<R>: RenderElement<R>,
|
|
CosmicStackRenderElement<R>: RenderElement<R>,
|
|
{
|
|
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::<R, CosmicMappedRenderElement<R>>(
|
|
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<Data>,
|
|
target_tree: &Tree<Data>,
|
|
geometries: Option<HashMap<NodeId, Rectangle<i32, Local>>>,
|
|
output_scale: f64,
|
|
percentage: f32,
|
|
is_swap_mode: bool,
|
|
mut processor: impl FnMut(&CosmicMapped, Rectangle<i32, Physical>, Rectangle<i32, Local>, 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<R>(
|
|
target_tree: &Tree<Data>,
|
|
reference_tree: Option<&Tree<Data>>,
|
|
renderer: &mut R,
|
|
geometries: Option<HashMap<NodeId, Rectangle<i32, Local>>>,
|
|
old_geometries: Option<HashMap<NodeId, Rectangle<i32, Local>>>,
|
|
seat: Option<&Seat<State>>,
|
|
output: &Output,
|
|
percentage: f32,
|
|
overview: (OverviewMode, Option<(SwapIndicator, Option<&Tree<Data>>)>),
|
|
swap_desc: Option<NodeDesc>,
|
|
) -> Vec<CosmicMappedRenderElement<R>>
|
|
where
|
|
R: Renderer + ImportAll + ImportMem + AsGlowRenderer,
|
|
R::TextureId: Send + Clone + 'static,
|
|
CosmicMappedRenderElement<R>: RenderElement<R>,
|
|
CosmicWindowRenderElement<R>: RenderElement<R>,
|
|
CosmicStackRenderElement<R>: RenderElement<R>,
|
|
{
|
|
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::<R, CosmicMappedRenderElement<R>>(
|
|
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<R>(
|
|
target_tree: &Tree<Data>,
|
|
reference_tree: Option<&Tree<Data>>,
|
|
renderer: &mut R,
|
|
non_exclusive_zone: Rectangle<i32, Local>,
|
|
geometries: Option<HashMap<NodeId, Rectangle<i32, Local>>>,
|
|
old_geometries: Option<HashMap<NodeId, Rectangle<i32, Local>>>,
|
|
is_overview: bool,
|
|
seat: Option<&Seat<State>>,
|
|
output: &Output,
|
|
percentage: f32,
|
|
transition: Option<f32>,
|
|
indicator_thickness: u8,
|
|
overview: (OverviewMode, Option<(SwapIndicator, Option<&Tree<Data>>)>),
|
|
mut resize_indicator: Option<(ResizeMode, ResizeIndicator)>,
|
|
swap_desc: Option<NodeDesc>,
|
|
swapping_stack_surface_id: &Id,
|
|
backdrop_id: &Id,
|
|
theme: &cosmic::theme::CosmicTheme,
|
|
) -> Vec<CosmicMappedRenderElement<R>>
|
|
where
|
|
R: Renderer + ImportAll + ImportMem + AsGlowRenderer,
|
|
R::TextureId: Send + Clone + 'static,
|
|
CosmicMappedRenderElement<R>: RenderElement<R>,
|
|
CosmicWindowRenderElement<R>: RenderElement<R>,
|
|
CosmicStackRenderElement<R>: RenderElement<R>,
|
|
{
|
|
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::<CosmicWindowRenderElement<R>>(
|
|
&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::<CosmicWindowRenderElement<R>>(
|
|
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::<CosmicWindowRenderElement<R>>(
|
|
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::<Vec<_>>(),
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
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::<R, CosmicMappedRenderElement<R>>(
|
|
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<Data>,
|
|
reference_tree: Option<&Tree<Data>>,
|
|
geometries: Option<HashMap<NodeId, Rectangle<i32, Local>>>,
|
|
old_geometries: Option<HashMap<NodeId, Rectangle<i32, Local>>>,
|
|
percentage: f32,
|
|
swap_tree: Option<&Tree<Data>>,
|
|
swap_desc: Option<&NodeDesc>,
|
|
mut processor: impl FnMut(NodeId, &Data, Rectangle<i32, Local>, &Rectangle<i32, Local>, 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<C>(
|
|
old_geo: &Rectangle<i32, C>,
|
|
new_geo: &Rectangle<i32, C>,
|
|
) -> (f64, Point<i32, C>) {
|
|
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(),
|
|
)
|
|
}
|
|
}
|