cosmic-comp/src/shell/layout/tiling.rs
2022-03-30 23:56:53 +02:00

371 lines
14 KiB
Rust

// SPDX-License-Identifier: GPL-3.0-only
use crate::shell::layout::Layout;
use id_tree::{InsertBehavior, MoveBehavior, Node, NodeId, NodeIdError, RemoveBehavior, Tree};
use smithay::{
desktop::{layer_map_for_output, Kind, Space, Window},
reexports::wayland_protocols::xdg_shell::server::xdg_toplevel::State as XdgState,
utils::Rectangle,
wayland::{output::Output, seat::Seat},
};
use std::cell::RefCell;
#[derive(Debug)]
pub struct TilingLayout {
gaps: (i32, i32),
}
#[derive(Debug)]
pub enum Orientation {
Horizontal,
Vertical,
}
#[derive(Debug)]
pub enum Data {
Fork {
orientation: Orientation,
ratio: f64,
},
Stack {
active: usize,
len: usize,
},
Window(Window),
}
#[derive(Debug, Clone)]
pub struct WindowInfo {
node: NodeId,
output: Output,
}
pub struct OutputInfo {
tree: Tree<Data>,
}
impl std::fmt::Debug for OutputInfo {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.tree.write_formatted(f)
}
}
impl Default for OutputInfo {
fn default() -> OutputInfo {
OutputInfo { tree: Tree::new() }
}
}
impl Data {
fn fork() -> Data {
Data::Fork {
orientation: Orientation::Vertical,
ratio: 0.5,
}
}
}
impl TilingLayout {
pub fn new() -> TilingLayout {
TilingLayout { gaps: (0, 4) }
}
}
impl Layout for TilingLayout {
fn map_window<'a>(
&mut self,
space: &mut Space,
window: &Window,
seat: &Seat,
mut focus_stack: Box<dyn Iterator<Item = &'a Window> + 'a>,
) {
{
let output = super::output_from_seat(seat, space);
output
.user_data()
.insert_if_missing(|| RefCell::new(OutputInfo::default()));
let output_info = output.user_data().get::<RefCell<OutputInfo>>().unwrap();
let tree = &mut output_info.borrow_mut().tree;
let new_window = Node::new(Data::Window(window.clone()));
let last_active = focus_stack
.find_map(|window| tree.root_node_id()
.and_then(|root| tree.traverse_pre_order_ids(root).unwrap()
.find(|id| matches!(tree.get(id).map(|n| n.data()), Ok(Data::Window(w)) if w == window))
)
);
let window_id = if let Some(ref node_id) = last_active {
let parent_id = tree.get(node_id).unwrap().parent().cloned();
if let Some(stack_id) = parent_id
.filter(|id| matches!(tree.get(id).unwrap().data(), Data::Stack { .. }))
{
// we add to the stack
let window_id = tree
.insert(new_window, InsertBehavior::UnderNode(&stack_id))
.unwrap();
if let Data::Stack {
ref mut len,
ref mut active,
} = tree.get_mut(&stack_id).unwrap().data_mut()
{
*active = *len;
*len += 1;
}
Ok(window_id)
} else {
// we create a new fork
new_fork(tree, node_id, new_window)
}
} else {
// nothing? then we add to the root
if let Some(root_id) = tree.root_node_id().cloned() {
new_fork(tree, &root_id, new_window)
} else {
tree.insert(new_window, InsertBehavior::AsRoot)
}
}
.unwrap();
{
let user_data = window.user_data();
let window_info = WindowInfo {
node: window_id.clone(),
output: output.clone(),
};
// insert or update
if !user_data.insert_if_missing(|| RefCell::new(window_info.clone())) {
*user_data.get::<RefCell<WindowInfo>>().unwrap().borrow_mut() = window_info;
}
}
}
self.refresh(space);
}
fn refresh(&mut self, space: &mut Space) {
while let Some(dead_windows) =
Some(update_space_positions(space, self.gaps)).filter(|v| !v.is_empty())
{
for window in dead_windows {
self.unmap_window(&window);
}
}
}
}
impl TilingLayout {
fn unmap_window(&mut self, window: &Window) {
if let Some(info) = window.user_data().get::<RefCell<WindowInfo>>() {
let output = &info.borrow().output;
let output_info = output.user_data().get::<RefCell<OutputInfo>>().unwrap();
let tree = &mut output_info.borrow_mut().tree;
let node_id = info.borrow().node.clone();
let parent_id = tree
.get(&node_id)
.ok()
.and_then(|node| node.parent())
.cloned();
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
slog_scope::debug!("Remove window {:?}", window);
let _ = tree.remove_node(node_id.clone(), RemoveBehavior::DropChildren);
// fixup parent node
match parent_id {
Some(id) if matches!(tree.get(&id).unwrap().data(), Data::Fork { .. }) => {
slog_scope::debug!("Removing Fork");
let other_child = tree.children_ids(&id).unwrap().cloned().next().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(|parent_id| MoveBehavior::ToParent(parent_id))
.unwrap_or(MoveBehavior::ToRoot),
)
.unwrap();
if let Some(old_pos) = fork_pos {
tree.make_nth_sibling(&other_child, old_pos).unwrap();
}
}
Some(id) if matches!(tree.get(&id).unwrap().data(), Data::Stack { .. }) => {
if tree.children_ids(&id).unwrap().count() == 0 {
slog_scope::debug!("Removing Stack");
// remove stack
let _ = tree.remove_node(id.clone(), RemoveBehavior::DropChildren);
// TODO now we need to untangle the parent_parent
// So we should REFACTOR this unmap function to not only work with windows
} else {
// fixup stack values
if let Data::Stack {
ref mut active,
ref mut len,
} = tree.get_mut(&id).unwrap().data_mut()
{
*len -= 1;
*active = std::cmp::max(*active, *len - 1);
}
}
}
None => {} // root
_ => unreachable!(),
}
// update_space_positions(space, self.gaps);
}
}
}
fn new_fork(
tree: &mut Tree<Data>,
old_id: &NodeId,
new: Node<Data>,
) -> Result<NodeId, NodeIdError> {
let new_fork = Node::new(Data::fork());
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 fork_id = tree
.insert(
new_fork,
if let Some(parent) = parent_id.as_ref() {
InsertBehavior::UnderNode(parent)
} else {
InsertBehavior::AsRoot
},
)
.unwrap();
tree.move_node(old_id, MoveBehavior::ToParent(&fork_id))
.unwrap();
// keep position
if let Some(old_pos) = pos {
tree.make_nth_sibling(&fork_id, old_pos).unwrap();
}
tree.insert(new, InsertBehavior::UnderNode(&fork_id))
}
fn update_space_positions(space: &mut Space, gaps: (i32, i32)) -> Vec<Window> {
let mut dead_windows = Vec::new();
let (outer, inner) = gaps;
for output in space.outputs().cloned().collect::<Vec<_>>().into_iter() {
if let Some(mut info) = output
.user_data()
.get::<RefCell<OutputInfo>>()
.map(|x| x.borrow_mut())
{
slog_scope::trace!("Tree:\n{:?}", info);
let tree = &mut info.tree;
if let Some(root) = tree.root_node_id() {
let mut stack = Vec::new();
let mut geo = Some(layer_map_for_output(&output).non_exclusive_zone());
// TODO saturate? minimum?
if let Some(mut geo) = geo.as_mut() {
geo.loc.x += outer;
geo.loc.y += outer;
geo.size.w -= outer * 2;
geo.size.h -= outer * 2;
}
for node in tree.traverse_pre_order(root).unwrap() {
let geo = stack.pop().unwrap_or(geo);
match node.data() {
Data::Fork { orientation, ratio } => {
if let Some(geo) = geo {
match orientation {
Orientation::Horizontal => {
let top_size = (
geo.size.w,
((geo.size.h as f64) * ratio).ceil() as i32,
);
stack.push(Some(Rectangle::from_loc_and_size(
(geo.loc.x, geo.loc.y + top_size.1),
(geo.size.w, geo.size.h - top_size.1),
)));
stack.push(Some(Rectangle::from_loc_and_size(
geo.loc, top_size,
)));
}
Orientation::Vertical => {
let left_size = (
((geo.size.w as f64) * ratio).ceil() as i32,
geo.size.h,
);
stack.push(Some(Rectangle::from_loc_and_size(
(geo.loc.x + left_size.0, geo.loc.y),
(geo.size.w - left_size.0, geo.size.h),
)));
stack.push(Some(Rectangle::from_loc_and_size(
geo.loc, left_size,
)));
}
}
} else {
stack.push(None);
stack.push(None);
}
}
Data::Stack { active, len } => {
for i in 0..*len {
if i == *active {
stack.push(geo);
} else {
stack.push(None);
}
}
}
Data::Window(window) => {
if window.toplevel().alive() {
if let Some(geo) = geo {
#[allow(irrefutable_let_patterns)]
if let Kind::Xdg(xdg) = &window.toplevel() {
let ret = xdg.with_pending_state(|state| {
state.size = Some(
(geo.size.w - inner * 2, geo.size.h - inner * 2)
.into(),
);
state.states.set(XdgState::TiledLeft);
state.states.set(XdgState::TiledRight);
state.states.set(XdgState::TiledTop);
state.states.set(XdgState::TiledBottom);
});
if ret.is_ok() {
xdg.send_configure();
}
}
let window_geo = window.geometry();
space.map_window(
&window,
(
geo.loc.x + inner - window_geo.loc.x,
geo.loc.y + inner - window_geo.loc.y,
),
false,
);
}
} else {
dead_windows.push(window.clone());
}
}
}
}
}
}
}
dead_windows
}