diff --git a/userspace/colors/src/input.rs b/userspace/colors/src/input.rs index ea1fc6b6..e15af9b2 100644 --- a/userspace/colors/src/input.rs +++ b/userspace/colors/src/input.rs @@ -4,7 +4,6 @@ use libcolors::{ }; // use yggdrasil_abi::io::{KeyboardKey, KeyboardKeyEvent}; - #[derive(Default)] pub struct InputState { lshift: bool, diff --git a/userspace/colors/src/main.rs b/userspace/colors/src/main.rs index 81f871bc..eaa27ac5 100644 --- a/userspace/colors/src/main.rs +++ b/userspace/colors/src/main.rs @@ -13,12 +13,12 @@ use input::InputState; use libcolors::{ event::{EventData, KeyEvent, KeyModifiers, KeyboardKeyEvent, WindowEvent, WindowInfo}, input::Key, - message::{ClientMessage, CreateWindowInfo, WindowType}, + message::{ClientMessage, CreateWindowInfo}, }; use sys::{Backend, DisplaySurface, FromClient, Point, ServerSender, WindowServer}; use uipc::PeerAddress; use window::Window; -use wm::{Direction, Workspace}; +use wm::{display::Display, layout::Direction}; pub mod input; pub mod sys; @@ -36,7 +36,7 @@ pub struct Server<'a> { last_client_id: u32, client_map: HashMap, - workspace: Workspace, + display: Display, windows: BTreeMap>, last_window_id: u32, @@ -55,7 +55,7 @@ impl<'a> Server<'a> { client_map: HashMap::new(), windows: BTreeMap::new(), - workspace: Workspace::new(800, 600), + display: Display::new(800, 600), last_window_id: 1, _pd: PhantomData, @@ -71,19 +71,11 @@ impl<'a> Server<'a> { ) -> Result<(WindowInfo, RawFd), Error> { let wid = self.last_window_id; self.last_window_id += 1; - let need_focus = match info.ty { - WindowType::Default => { - if !self.workspace.create_window(wid) { - todo!() - } - true - } - WindowType::Reservation(height) => { - self.workspace.add_reservation(wid, height); - false - } - }; - let frame = self.workspace.window_frame(wid).unwrap(); + let need_focus = self + .display + .create_window(wid, &info.ty) + .expect("TODO: handle create window failed"); + let frame = self.display.window_frame(wid).unwrap(); let mapping_size = surface.width() * surface.height() * 4; // let mapping_size = self.display.width() * self.display.height() * 4; let surface_shm = SharedMemory::new(mapping_size).unwrap(); @@ -118,12 +110,13 @@ impl<'a> Server<'a> { if need_focus { self.focus_window(tx, wid); } + self.redraw_all(tx); Ok((info, fd)) } fn focus_window(&mut self, tx: &mut ServerSender, wid: u32) { - let (old_wid, new) = self.workspace.focus_window(wid); + let (old_wid, new) = self.display.current_workspace_mut().focus_window(wid); let old = old_wid.and_then(|wid| { let window = self.windows.get(&wid)?; Some((wid, window)) @@ -147,33 +140,41 @@ impl<'a> Server<'a> { } fn move_focus(&mut self, tx: &mut ServerSender, direction: Direction) { - if let Some(wid) = self.workspace.window_towards_wrap(direction) { + if let Some(wid) = self + .display + .current_workspace_mut() + .window_towards_wrap(direction) + { self.focus_window(tx, wid); } } + fn redraw_all(&mut self, tx: &mut ServerSender) { + self.display.all_windows().for_each(|(wid, _)| { + if let Some(window) = self.windows.get(&wid) { + tx.send_event( + EventData::WindowEvent(wid, WindowEvent::RedrawRequested), + &window.peer, + ); + } + }); + } + fn move_window( &mut self, surface: &mut DisplaySurface, tx: &mut ServerSender, direction: Direction, ) { - if self.workspace.move_window(direction) { + if self.display.move_window(direction) { surface.fill(self.background); self.flush_dirty_frames(tx); - self.workspace.all_windows().for_each(|(wid, _)| { - if let Some(window) = self.windows.get(&wid) { - tx.send_event( - EventData::WindowEvent(wid, WindowEvent::RedrawRequested), - &window.peer, - ); - } - }); + self.redraw_all(tx); } } fn flush_dirty_frames(&mut self, tx: &mut ServerSender) { - for (wid, rect) in self.workspace.dirty_windows() { + for (wid, rect) in self.display.dirty_windows() { let Some(window) = self.windows.get_mut(&wid) else { continue; }; @@ -193,11 +194,72 @@ impl<'a> Server<'a> { ); } } + + fn switch_to_workspace(&mut self, tx: &mut ServerSender, next: usize) -> bool { + if next == self.display.current_workspace || next >= Display::::MAX { + return false; + } + + log::info!( + "Switch workspace {} -> {}", + self.display.current_workspace, + next + ); + + let old_focus_wid = self.display.current_workspace().focused_window(); + let old_focus = old_focus_wid.and_then(|wid| { + let window = self.windows.get(&wid)?; + Some((wid, window)) + }); + + if let Some((old_wid, old_window)) = old_focus { + log::info!("Unfocus #{old_wid}"); + tx.send_event( + EventData::WindowEvent(old_wid, WindowEvent::FocusChanged(false)), + &old_window.peer, + ); + } + + self.display.current_workspace = next; + + let new_focus_wid = self.display.current_workspace().focused_window(); + let new_focus = new_focus_wid.and_then(|wid| { + let window = self.windows.get(&wid)?; + Some((wid, window)) + }); + + if let Some((new_wid, new_window)) = new_focus { + log::info!("Focus #{new_wid}"); + tx.send_event( + EventData::WindowEvent(new_wid, WindowEvent::FocusChanged(true)), + &new_window.peer, + ); + } + + // Send redraw to all frames + self.flush_dirty_frames(tx); + self.redraw_all(tx); + true + } + + fn send_focus_to_workspace(&mut self, tx: &mut ServerSender, target: usize) -> bool { + if target == self.display.current_workspace && target >= Display::::MAX { + return false; + } + + if !self.display.send_focused_window(target) { + return false; + } + + self.flush_dirty_frames(tx); + self.redraw_all(tx); + true + } } impl WindowServer for Server<'_> { fn handle_initial(&mut self, mut surface: DisplaySurface) { - self.workspace + self.display .resize(surface.width() as u32, surface.height() as u32); surface.fill(self.background); surface.present(); @@ -242,7 +304,7 @@ impl WindowServer for Server<'_> { } => { log::trace!("{:?}: BlitWindow", peer); if let Some(window) = self.windows.get(&window_id) { - let Some(frame) = self.workspace.window_frame(window_id) else { + let Some(frame) = self.display.window_frame(window_id) else { return; }; @@ -274,11 +336,32 @@ impl WindowServer for Server<'_> { ClientMessage::DestroyWindow(wid) => { log::info!("{:?}: DestroyWindow", peer); let window = self.windows.remove(&wid); + let old_focus = self.display.current_workspace().focused_window(); if window.is_some() { - self.workspace.remove_window(wid); - self.flush_dirty_frames(tx); + if self.display.remove_window(wid) { + surface.fill(self.background); + self.flush_dirty_frames(tx); + self.redraw_all(tx); + + let focus = self.display.current_workspace().focused_window(); + + if focus != old_focus { + let focus = focus.and_then(|wid| { + let window = self.windows.get(&wid)?; + Some((wid, window)) + }); + + if let Some((wid, window)) = focus { + tx.send_event( + EventData::WindowEvent(wid, WindowEvent::FocusChanged(true)), + &window.peer, + ); + } + } + + surface.present(); + } } - surface.present(); } } } @@ -306,6 +389,22 @@ impl WindowServer for Server<'_> { } match (input.modifiers, input.key) { + (KeyModifiers::ALT, Key::Char(c)) if c.is_ascii_digit() => { + let number = ((c + 9) - b'0') % 10; + if self.switch_to_workspace(tx, number as usize) { + surface.fill(self.background); + surface.present(); + } + return; + } + (KeyModifiers::ALT_SHIFT, Key::Char(c)) if c.is_ascii_digit() => { + let number = ((c + 9) - b'0') % 10; + if self.send_focus_to_workspace(tx, number as usize) { + surface.fill(self.background); + surface.present(); + } + return; + } (KeyModifiers::ALT, Key::Char(b'l')) => { self.move_focus(tx, Direction::Right); return; @@ -344,22 +443,32 @@ impl WindowServer for Server<'_> { } _ => (), } - - // // Window keys - // if let Some((row, col)) = self.focused_frame { - // let row_len = self.rows[row].frames.len(); - - // if let Some((_, window)) = self.get_focused_window() { - // } else { - // self.focused_frame = None; - // } } - let focus = self.workspace.focused_window().and_then(|wid| { - let window = self.windows.get(&wid)?; - Some((wid, window)) - }); + let focus = self + .display + .current_workspace() + .focused_window() + .and_then(|wid| { + let window = self.windows.get(&wid)?; + Some((wid, window)) + }); + if let Some((wid, window)) = focus { + if event.state { + match (input.modifiers, input.key) { + (KeyModifiers::ALT_SHIFT, Key::Char(b'q')) => { + log::info!("Send close request -> #{wid}"); + tx.send_event( + EventData::WindowEvent(wid, WindowEvent::CloseRequested), + &window.peer, + ); + return; + } + _ => (), + } + } + if event.state { tx.send_event( EventData::WindowEvent( diff --git a/userspace/colors/src/sys/mod.rs b/userspace/colors/src/sys/mod.rs index 8e7adde2..642dd080 100644 --- a/userspace/colors/src/sys/mod.rs +++ b/userspace/colors/src/sys/mod.rs @@ -6,15 +6,15 @@ use libcolors::{ }; use uipc::{PeerAddress, Sender}; -#[cfg(any(rust_analyzer, target_os = "yggdrasil"))] -pub mod yggdrasil; #[cfg(any(rust_analyzer, unix))] pub mod unix; - #[cfg(any(rust_analyzer, target_os = "yggdrasil"))] -pub use yggdrasil::{Backend, DisplaySurface, Error}; +pub mod yggdrasil; + #[cfg(any(rust_analyzer, unix))] pub use unix::{Backend, DisplaySurface, Error}; +#[cfg(any(rust_analyzer, target_os = "yggdrasil"))] +pub use yggdrasil::{Backend, DisplaySurface, Error}; #[derive(Debug)] pub struct FromClient { diff --git a/userspace/colors/src/sys/unix.rs b/userspace/colors/src/sys/unix.rs index d05ed04a..b95948f3 100644 --- a/userspace/colors/src/sys/unix.rs +++ b/userspace/colors/src/sys/unix.rs @@ -165,7 +165,12 @@ impl Backend { let mut surface = softbuffer::Surface::new(&context, self.window.clone())?; let size = self.window.inner_size(); - surface.resize(NonZero::new(size.width).unwrap(), NonZero::new(size.height).unwrap()).unwrap(); + surface + .resize( + NonZero::new(size.width).unwrap(), + NonZero::new(size.height).unwrap(), + ) + .unwrap(); self.server.handle_initial(DisplaySurface { inner: surface.buffer_mut().unwrap(), width: size.width as _, diff --git a/userspace/colors/src/sys/yggdrasil.rs b/userspace/colors/src/sys/yggdrasil.rs index fc9926d3..50543043 100644 --- a/userspace/colors/src/sys/yggdrasil.rs +++ b/userspace/colors/src/sys/yggdrasil.rs @@ -145,7 +145,7 @@ impl Backend<'_, S> { pub fn run(mut self) -> Result<(), Error> { self.server.handle_initial(DisplaySurface { - display: &mut self.display + display: &mut self.display, }); loop { @@ -169,7 +169,8 @@ impl Backend<'_, S> { let surface = DisplaySurface { display: &mut self.display, }; - self.server.handle_keyboard_event(surface, &mut self.tx, event); + self.server + .handle_keyboard_event(surface, &mut self.tx, event); } } } @@ -271,8 +272,5 @@ fn convert_key_event(raw: yggdrasil_abi::io::KeyboardKeyEvent) -> Option return None, }; - Some(KeyboardKeyEvent { - key, - state - }) + Some(KeyboardKeyEvent { key, state }) } diff --git a/userspace/colors/src/wm/display.rs b/userspace/colors/src/wm/display.rs new file mode 100644 index 00000000..ed601918 --- /dev/null +++ b/userspace/colors/src/wm/display.rs @@ -0,0 +1,212 @@ +use std::{fmt, hash::Hash, iter}; + +use libcolors::message::WindowType; + +use super::{ + layout::{Direction, NodeLayout, Rect}, + workspace::Workspace, +}; + +pub struct Display { + workspaces: Vec>, + width: u32, + height: u32, + reservations_top: Vec>, + reservation_height: u32, + pub current_workspace: usize, +} + +pub struct Reservation { + layout: NodeLayout, + size: u32, + wid: T, +} + +impl Display { + pub const MAX: usize = 10; + + pub fn new(width: u32, height: u32) -> Self { + let workspaces = (0..Self::MAX) + .map(|_| Workspace::new(width, height)) + .collect(); + Self { + workspaces, + width, + height, + reservations_top: Vec::new(), + reservation_height: 0, + current_workspace: 0, + } + } + + pub fn resize(&mut self, width: u32, height: u32) { + self.width = width; + self.height = height; + + // Re-do reservation layout + self.update_reservation_layout(); + + for workspace in self.workspaces.iter_mut() { + workspace.resize(width, height, self.reservation_height); + } + } + + pub fn workspace(&self, index: usize) -> &Workspace { + &self.workspaces[index] + } + + pub fn workspace_mut(&mut self, index: usize) -> &mut Workspace { + &mut self.workspaces[index] + } + + pub fn current_workspace(&self) -> &Workspace { + &self.workspaces[self.current_workspace] + } + + pub fn current_workspace_mut(&mut self) -> &mut Workspace { + &mut self.workspaces[self.current_workspace] + } + + pub fn all_windows(&self) -> impl Iterator + '_ { + let reservation_windows = self.reservation_windows(); + let workspace_windows = self.current_workspace().all_windows(); + iter::chain(reservation_windows, workspace_windows) + } + + pub fn dirty_windows(&self) -> impl Iterator + '_ { + let reservation_windows = self.dirty_reservation_windows(); + let workspace_windows = self.current_workspace().dirty_windows(); + iter::chain(reservation_windows, workspace_windows) + } + + pub fn window_frame(&self, wid: T) -> Option { + if let Some(reservation) = self.lookup_reservation(wid) { + return reservation.layout.get(); + } + self.current_workspace().window_frame(wid) + } + + pub fn create_window(&mut self, wid: T, ty: &WindowType) -> Option { + match ty { + &WindowType::Reservation(height) => { + self.add_reservation(wid, height); + Some(false) + } + WindowType::Default => { + if !self.current_workspace_mut().create_window(wid) { + return None; + } + self.update_reservation_layout(); + Some(true) + } + } + } + + pub fn move_window(&mut self, direction: Direction) -> bool + where + T: fmt::Debug, + { + if self.current_workspace_mut().move_window(direction) { + self.update_reservation_layout(); + true + } else { + false + } + } + + pub fn remove_window(&mut self, wid: T) -> bool { + // TODO check if the window is a reservation + let mut reservation_removed = false; + self.reservations_top.retain(|res| { + if res.wid == wid { + reservation_removed = true; + false + } else { + true + } + }); + + if reservation_removed { + self.update_layout(true); + return true; + } + + for workspace in self.workspaces.iter_mut() { + if workspace.remove_window(wid) { + self.update_reservation_layout(); + return true; + } + } + + false + } + + pub fn send_focused_window(&mut self, target: usize) -> bool { + let Some(focus_wid) = self.current_workspace().focused_window() else { + return false; + }; + if !self.workspaces[target].create_window(focus_wid) { + return false; + } + self.workspaces[target].focus_window(focus_wid); + self.current_workspace_mut().remove_window(focus_wid); + true + } + + pub fn add_reservation(&mut self, wid: T, height: u32) { + self.reservations_top.push(Reservation { + layout: NodeLayout::empty(), + size: height, + wid, + }); + self.update_layout(true); + } + + fn dirty_reservation_windows(&self) -> impl Iterator + '_ { + self.reservation_windows().filter_map(|(wid, layout)| { + if !layout.clear_dirty() { + return None; + } + let rect = layout.get()?; + Some((wid, rect)) + }) + } + + fn reservation_windows(&self) -> impl Iterator + '_ { + self.reservations_top + .iter() + .map(|res| (res.wid, &res.layout)) + } + + fn lookup_reservation(&self, wid: T) -> Option<&Reservation> { + self.reservations_top.iter().find(|r| r.wid == wid) + } + + // Will also update the workspaces' layouts if reservation height changes + fn update_reservation_layout(&mut self) -> bool { + let mut res_y = 0; + for reservation in self.reservations_top.iter() { + reservation.layout.set(Rect { + x: 0, + y: res_y, + w: self.width, + h: reservation.size, + }); + res_y += reservation.size; + } + let changed = res_y != self.reservation_height; + self.reservation_height = res_y; + changed + } + + fn update_layout(&mut self, force: bool) { + if self.update_reservation_layout() || force { + for workspace in self.workspaces.iter_mut() { + workspace.resize(self.width, self.height, self.reservation_height); + } + } + } + + // pub fn add_reservation(&mut self, wid: T, height: u32) { + // } +} diff --git a/userspace/colors/src/wm/layout.rs b/userspace/colors/src/wm/layout.rs new file mode 100644 index 00000000..861a20c4 --- /dev/null +++ b/userspace/colors/src/wm/layout.rs @@ -0,0 +1,67 @@ +use std::cell::Cell; + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum Direction { + Up, + Down, + Left, + Right, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct Rect { + pub x: u32, + pub y: u32, + pub w: u32, + pub h: u32, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum Orientation { + Horizontal, + Vertical, +} + +pub struct NodeLayout { + pub rect: Cell<(Option, bool)>, +} + +impl NodeLayout { + pub fn empty() -> Self { + Self { + rect: Cell::new((None, false)), + } + } + + pub fn set(&self, new: Rect) { + let (old, _) = self.rect.get(); + let dirty = old.map_or(true, |old| old != new); + self.rect.set((Some(new), dirty)); + } + + pub fn get(&self) -> Option { + let (value, _) = self.rect.get(); + value + } + + pub fn clear(&self) { + self.rect.set((None, false)); + } + + pub fn clear_dirty(&self) -> bool { + let (value, dirty) = self.rect.get(); + self.rect.set((value, false)); + dirty && value.is_some() + } +} + +impl Direction { + pub fn split(&self) -> (Orientation, isize) { + match self { + Self::Left => (Orientation::Horizontal, -1), + Self::Right => (Orientation::Horizontal, 1), + Self::Up => (Orientation::Vertical, -1), + Self::Down => (Orientation::Vertical, 1), + } + } +} diff --git a/userspace/colors/src/wm/mod.rs b/userspace/colors/src/wm/mod.rs index 3b12b2fd..11d0632a 100644 --- a/userspace/colors/src/wm/mod.rs +++ b/userspace/colors/src/wm/mod.rs @@ -1,703 +1,3 @@ -use std::{cell::Cell, collections::HashMap, hash::Hash, iter}; - -pub type NodeId = u32; - -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum Direction { - Up, - Down, - Left, - Right, -} - -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub struct Rect { - pub x: u32, - pub y: u32, - pub w: u32, - pub h: u32, -} - -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum Orientation { - Horizontal, - Vertical, -} - -pub enum NodeContent { - Container(ContainerNode), - Window(WindowNode), -} - -pub struct NodeLayout { - rect: Cell<(Option, bool)>, -} - -pub struct Node { - parent: Option, - layout: NodeLayout, - - content: NodeContent, -} - -pub struct ContainerNode { - children: Vec, - orientation: Orientation, -} - -pub struct WindowNode { - pub wid: T, -} - -pub struct Reservation { - layout: NodeLayout, - size: u32, - wid: T, -} - -pub struct Workspace { - nodes: HashMap>, - wid_to_nid: HashMap, - last_node_id: NodeId, - - root: NodeId, - focus: Option, - - margin: u32, - spacing: u32, - - reservations_top: Vec>, - reservation_top: u32, - - width: u32, - height: u32, -} - -impl Node { - pub fn as_window(&self) -> Option<&WindowNode> { - match &self.content { - NodeContent::Window(window) => Some(window), - _ => None, - } - } - pub fn as_container(&self) -> Option<&ContainerNode> { - match &self.content { - NodeContent::Container(container) => Some(container), - _ => None, - } - } - pub fn as_container_mut(&mut self) -> Option<&mut ContainerNode> { - match &mut self.content { - NodeContent::Container(container) => Some(container), - _ => None, - } - } -} - -impl NodeLayout { - pub fn set(&self, new: Rect) { - let (old, _) = self.rect.get(); - let dirty = old.map_or(true, |old| old != new); - self.rect.set((Some(new), dirty)); - } - - pub fn get(&self) -> Option { - let (value, _) = self.rect.get(); - value - } - - pub fn clear(&self) { - self.rect.set((None, false)); - } - - pub fn clear_dirty(&self) -> bool { - let (value, dirty) = self.rect.get(); - self.rect.set((value, false)); - dirty && value.is_some() - } -} - -impl Workspace { - pub fn new(width: u32, height: u32) -> Self { - let root = Node { - parent: None, - layout: NodeLayout { - rect: Cell::new((None, false)), - }, - content: NodeContent::Container(ContainerNode { - children: vec![], - orientation: Orientation::Horizontal, - }), - }; - let nodes = HashMap::from_iter([(0, root)]); - - let mut this = Self { - nodes, - wid_to_nid: HashMap::new(), - last_node_id: 0, - - margin: 4, - spacing: 4, - - root: 0, - focus: None, - - reservation_top: 0, - reservations_top: Vec::new(), - - width, - height, - }; - - this.update_layout(); - - this - } - - pub fn add_reservation(&mut self, wid: T, height: u32) { - self.reservations_top.push(Reservation { - layout: NodeLayout { - rect: Cell::new((None, false)), - }, - size: height, - wid, - }); - self.update_layout(); - } - - pub fn all_windows(&self) -> impl Iterator + '_ { - let nodes_windows = self.nodes.iter().filter_map(|(_, node)| { - let window = node.as_window()?; - Some((window.wid, &node.layout)) - // Some(node.as_window()?.wid) - }); - let reservation_windows = self - .reservations_top - .iter() - .map(|res| (res.wid, &res.layout)); - iter::chain(reservation_windows, nodes_windows) - } - - pub fn dirty_windows(&self) -> impl Iterator + '_ { - self.all_windows().filter_map(|(wid, layout)| { - if !layout.clear_dirty() { - return None; - } - let rect = layout.get()?; - Some((wid, rect)) - }) - } - - pub fn window_towards_wrap(&self, direction: Direction) -> Option { - let current_nid = self.focus?; - let current_node = self.nodes.get(¤t_nid)?; - let parent_nid = current_node.parent?; - let parent_node = self.nodes.get(&parent_nid)?; - let parents_parent_nid = parent_node.parent; - let parent_container = parent_node.as_container()?; - - let (orientation, delta) = direction.split(); - - let nid = if orientation == parent_container.orientation { - log::info!("Within parent {delta}, {:?}", parent_container.orientation); - let position_in_parent = parent_container - .children - .iter() - .position(|&n| n == current_nid)?; - - if delta > 0 { - if position_in_parent < parent_container.children.len() - 1 { - parent_container.children[position_in_parent + 1] - } else { - parent_container.children[0] - } - } else { - if position_in_parent > 0 { - parent_container.children[position_in_parent - 1] - } else { - parent_container.children[parent_container.children.len() - 1] - } - } - } else if let Some(parents_parent_nid) = parents_parent_nid { - let parents_parent_node = self.nodes.get(&parents_parent_nid)?; - let parents_parent_container = parents_parent_node.as_container()?; - assert_eq!(parents_parent_container.orientation, orientation); - let position_in_parent = parents_parent_container - .children - .iter() - .position(|&n| n == parent_nid)?; - log::info!( - "Within parent's parent {delta}, {:?}", - parents_parent_container.orientation - ); - log::info!("position_in_parent = {position_in_parent}"); - - if delta > 0 { - if position_in_parent < parent_container.children.len() - 1 { - parents_parent_container.children[position_in_parent + 1] - } else { - parents_parent_container.children[0] - } - } else { - if position_in_parent > 0 { - parents_parent_container.children[position_in_parent - 1] - } else { - parents_parent_container.children[parent_container.children.len() - 1] - } - } - } else { - return None; - }; - - self.first_window_in(nid) - } - - pub fn first_window_in(&self, nid: NodeId) -> Option { - let node = self.nodes.get(&nid)?; - - match &node.content { - NodeContent::Window(window) => Some(window.wid), - NodeContent::Container(container) => { - let first = *container.children.first()?; - self.first_window_in(first) - } - } - } - - pub fn move_window(&mut self, direction: Direction) -> bool - where - T: core::fmt::Debug, - { - if let Some(container_nid) = self.move_window_inner(direction) { - self.invalidate_layout(container_nid); - self.fixup_containers(self.root); - self.dump(self.root, 0); - true - } else { - false - } - } - - fn dump(&self, nid: NodeId, depth: usize) - where - T: core::fmt::Debug, - { - let Some(node) = self.nodes.get(&nid) else { - return; - }; - - let rect = node.layout.get(); - - match &node.content { - NodeContent::Window(window) => { - log::info!( - "{:depth$}Window #{:?} {rect:?}", - "", - window.wid, - depth = depth * 2 - ); - } - NodeContent::Container(container) => { - log::info!("{:depth$}Container {rect:?} {{", "", depth = depth * 2); - for &child_nid in container.children.iter() { - self.dump(child_nid, depth + 1); - } - log::info!("{:depth$}}}", "", depth = depth * 2); - } - } - } - - // pub fn walk_windows(&self, mut mapper: F) { - // self.walk_windows_inner(&mut mapper, self.root) - // } - - // fn walk_windows_inner(&self, mapper: &mut F, nid: NodeId) { - // // let Some(node) = self.nodes.get(&nid) else { - // // return; - // // }; - - // // match &node.content { - // // NodeContent::Container(container) => { - // // for &child_nid in container.children.iter() { - // // self.walk_windows_inner(mapper, child_nid); - // // } - // // } - // // NodeContent::Window(window) => { - // // mapper(window.wid); - // // } - // // } - // } - - fn invalidate_layout(&mut self, nid: NodeId) { - for reservation in self.reservations_top.iter() { - reservation.layout.clear(); - } - self.invalidate_layout_inner(nid); - self.update_layout(); - } - - fn invalidate_layout_inner(&self, nid: NodeId) { - let Some(node) = self.nodes.get(&nid) else { - return; - }; - - node.layout.clear(); - - match &node.content { - NodeContent::Container(container) => { - for &child_nid in container.children.iter() { - self.invalidate_layout_inner(child_nid); - } - } - _ => (), - } - } - - fn fixup_containers(&mut self, nid: NodeId) { - let Some(node) = self.nodes.get_mut(&nid) else { - return; - }; - - if let NodeContent::Container(container) = &mut node.content { - if container.children.is_empty() { - let Some(parent_nid) = node.parent else { - return; - }; - // Remove the empty container - if let Some(parent_container) = self - .nodes - .get_mut(&parent_nid) - .and_then(Node::as_container_mut) - { - parent_container.children.retain(|&n| n != nid); - self.nodes.remove(&nid); - self.invalidate_layout(parent_nid); - } - } else if container.children.len() == 1 { - // Remove the empty container and reparent its only child - if let Some(parent_nid) = node.parent { - let child_nid = container.children.remove(0); - let [Some(child), Some(parent)] = - self.nodes.get_many_mut([&child_nid, &parent_nid]) - else { - return; - }; - - child.parent = Some(parent_nid); - if let Some(parent_container) = parent.as_container_mut() { - parent_container.children.retain(|&n| n != nid); - parent_container.children.push(child_nid); - } - - self.invalidate_layout(parent_nid); - } - } else { - // TODO borrowing stuff - let children = container.children.clone(); - for child_nid in children { - self.fixup_containers(child_nid); - } - } - } - } - - fn move_window_inner(&mut self, direction: Direction) -> Option { - let current_nid = self.focus?; - let current_node = self.nodes.get(¤t_nid)?; - let parent_nid = current_node.parent?; - let parent_node = self.nodes.get_mut(&parent_nid)?; - let parents_parent_nid = parent_node.parent; - let parent_container = parent_node.as_container_mut()?; - - let (orientation, delta) = direction.split(); - - if parent_container.orientation == orientation { - let position_in_parent = parent_container - .children - .iter() - .position(|&n| n == current_nid)?; - - // TODO check if this item is also a container - - if delta < 0 && position_in_parent > 0 { - parent_container - .children - .swap(position_in_parent - 1, position_in_parent); - Some(parent_nid) - } else if delta > 0 && position_in_parent < parent_container.children.len() - 1 { - parent_container - .children - .swap(position_in_parent, position_in_parent + 1); - Some(parent_nid) - } else { - None - } - } else if let Some(parents_parent_nid) = parents_parent_nid { - // Go to parent's parent - let parents_parent_node = self.nodes.get_mut(&parents_parent_nid)?; - let parents_parent_container = parents_parent_node.as_container_mut()?; - assert_eq!(parents_parent_container.orientation, orientation); - todo!() - } else { - self.last_node_id += 1; - let new_root_nid = self.last_node_id; - - // Remove child from parent - parent_container.children.retain(|&n| n != current_nid); - - self.nodes.get_mut(¤t_nid)?.parent = Some(new_root_nid); - self.nodes.get_mut(&self.root)?.parent = Some(new_root_nid); - - let children = if delta > 0 { - vec![self.root, current_nid] - } else { - vec![current_nid, self.root] - }; - - // Create a new root - let new_root = Node { - parent: None, - layout: NodeLayout { - rect: Cell::new((None, false)), - }, - content: NodeContent::Container(ContainerNode { - children, - orientation, - }), - }; - self.root = new_root_nid; - self.nodes.insert(new_root_nid, new_root); - - Some(self.root) - } - } - - pub fn focus_window(&mut self, wid: T) -> (Option, Option) { - let old = self.focus.take().and_then(|nid| self.wid(nid)); - let new = self.wid_to_nid.get(&wid).copied(); - self.focus = new; - (old, new) - } - - pub fn focused_window(&self) -> Option { - let nid = self.focus?; - Some(self.nodes.get(&nid)?.as_window()?.wid) - } - - pub fn window(&self, wid: T) -> Option<&Node> { - let nid = *self.wid_to_nid.get(&wid)?; - self.nodes.get(&nid) - } - - pub fn reservation(&self, wid: T) -> Option<&Reservation> { - self.reservations_top.iter().find(|r| r.wid == wid) - } - - pub fn window_frame(&self, wid: T) -> Option { - if let Some(reservation) = self.reservation(wid) { - reservation.layout.get() - } else { - let node = self.window(wid)?; - node.layout.get() - } - } - - pub fn resize(&mut self, w: u32, h: u32) { - self.width = w; - self.height = h; - self.update_layout(); - } - - fn update_reservation_layout(&mut self) -> u32 { - let mut res_y = 0; - for reservation in self.reservations_top.iter() { - reservation.layout.set(Rect { - x: 0, - y: res_y, - w: self.width, - h: reservation.size, - }); - res_y += reservation.size; - } - self.reservation_top = res_y; - res_y - } - - pub fn update_layout(&mut self) { - let res_y = self.update_reservation_layout(); - self.update_layout_for( - self.root, - Rect { - x: self.margin, - y: self.margin + res_y, - w: self.width - self.margin * 2, - h: self.height - self.margin * 2 - res_y, - }, - ); - } - - pub fn create_window(&mut self, wid: T) -> bool { - if self.create_window_in(self.root, wid) { - for res in self.reservations_top.iter() { - res.layout.clear(); - } - self.update_reservation_layout(); - true - } else { - false - } - } - - pub fn remove_window(&mut self, wid: T) -> bool { - let Some(nid) = self.wid_to_nid.remove(&wid) else { - return false; - }; - if self.remove_inner(nid) { - self.fixup_containers(self.root); - self.update_layout(); - true - } else { - false - } - } - - fn create_window_in(&mut self, container_nid: NodeId, wid: T) -> bool { - self.last_node_id += 1; - let nid = self.last_node_id; - self.nodes.insert( - nid, - Node { - parent: Some(container_nid), - layout: NodeLayout { - rect: Cell::new((None, false)), - }, - content: NodeContent::Window(WindowNode { wid }), - }, - ); - self.wid_to_nid.insert(wid, nid); - if !self.add_child_to(container_nid, nid) { - self.wid_to_nid.remove(&wid); - self.nodes.remove(&nid); - return false; - } - self.update_layout(); - true - } - - fn wid(&self, nid: NodeId) -> Option { - self.nodes.get(&nid).and_then(|node| match &node.content { - NodeContent::Window(window) => Some(window.wid), - _ => None, - }) - } - - fn update_layout_for(&self, node_id: NodeId, rect: Rect) { - let Some(node) = self.nodes.get(&node_id) else { - log::warn!("update_layout_for: no node {node_id}"); - return; - }; - - node.layout.set(rect); - - match &node.content { - NodeContent::Container(container) => { - if container.children.is_empty() { - return; - } - - match container.orientation { - Orientation::Vertical => { - let hstep = (rect.h - self.spacing * container.children.len() as u32) - / container.children.len() as u32; - let ystep = hstep + self.spacing; - - for (i, &child_nid) in container.children.iter().enumerate() { - let i = i as u32; - let child_rect = Rect { - x: rect.x, - y: rect.y + i * ystep, - w: rect.w, - h: hstep, - }; - log::info!("Frame #{child_nid} size: {child_rect:?}"); - self.update_layout_for(child_nid, child_rect); - } - } - Orientation::Horizontal => { - let wstep = (rect.w - self.spacing * container.children.len() as u32) - / container.children.len() as u32; - let xstep = wstep + self.spacing; - - for (i, &child_nid) in container.children.iter().enumerate() { - let i = i as u32; - let child_rect = Rect { - x: rect.x + i * xstep, - y: rect.y, - w: wstep, - h: rect.h, - }; - log::info!("Frame #{child_nid} size: {child_rect:?}"); - self.update_layout_for(child_nid, child_rect); - } - } - } - } - _ => (), - } - } - - fn remove_inner(&mut self, node_id: NodeId) -> bool { - if node_id == self.root { - return false; - } - let Some(node) = self.nodes.remove(&node_id) else { - return false; - }; - let container_nid = unsafe { node.parent.unwrap_unchecked() }; - let Some(container_node) = self.nodes.get_mut(&container_nid) else { - return false; - }; - let NodeContent::Container(container) = &mut container_node.content else { - return false; - }; - container_node.layout.clear(); - container.children.retain(|&c| c != node_id); - // Only root container can be empty - debug_assert!(!(container_node.parent.is_some() && container.children.is_empty())); - if container.children.len() == 1 && container_node.parent.is_some() { - // Lift the remaining node to the parent container - todo!() - } - - true - } - - fn add_child_to(&mut self, container_nid: NodeId, child_nid: NodeId) -> bool { - let [Some(container_node), Some(child_node)] = - self.nodes.get_many_mut([&container_nid, &child_nid]) - else { - return false; - }; - let NodeContent::Container(container) = &mut container_node.content else { - return false; - }; - - container_node.layout.clear(); - child_node.layout.clear(); - child_node.parent = Some(container_nid); - container.children.push(child_nid); - - true - } -} - -impl Direction { - pub fn split(&self) -> (Orientation, isize) { - match self { - Self::Left => (Orientation::Horizontal, -1), - Self::Right => (Orientation::Horizontal, 1), - Self::Up => (Orientation::Vertical, -1), - Self::Down => (Orientation::Vertical, 1), - } - } -} +pub mod display; +pub mod layout; +pub mod workspace; diff --git a/userspace/colors/src/wm/workspace.rs b/userspace/colors/src/wm/workspace.rs new file mode 100644 index 00000000..15ce3647 --- /dev/null +++ b/userspace/colors/src/wm/workspace.rs @@ -0,0 +1,599 @@ +use std::{collections::HashMap, hash::Hash}; + +use super::layout::{Direction, NodeLayout, Orientation, Rect}; + +pub type NodeId = u32; + +pub enum NodeContent { + Container(ContainerNode), + Window(WindowNode), +} + +pub struct Node { + parent: Option, + layout: NodeLayout, + + content: NodeContent, +} + +pub struct ContainerNode { + children: Vec, + orientation: Orientation, +} + +pub struct WindowNode { + pub wid: T, +} + +pub struct Workspace { + nodes: HashMap>, + wid_to_nid: HashMap, + last_node_id: NodeId, + + root: NodeId, + focus: Option, + + margin: u32, + spacing: u32, + + // reservations_top: Vec>, + reservation_top: u32, + + width: u32, + height: u32, +} + +impl Node { + pub fn as_window(&self) -> Option<&WindowNode> { + match &self.content { + NodeContent::Window(window) => Some(window), + _ => None, + } + } + pub fn as_container(&self) -> Option<&ContainerNode> { + match &self.content { + NodeContent::Container(container) => Some(container), + _ => None, + } + } + pub fn as_container_mut(&mut self) -> Option<&mut ContainerNode> { + match &mut self.content { + NodeContent::Container(container) => Some(container), + _ => None, + } + } +} + +impl Workspace { + pub fn new(width: u32, height: u32) -> Self { + let root = Node { + parent: None, + layout: NodeLayout::empty(), + content: NodeContent::Container(ContainerNode { + children: vec![], + orientation: Orientation::Horizontal, + }), + }; + let nodes = HashMap::from_iter([(0, root)]); + + let mut this = Self { + nodes, + wid_to_nid: HashMap::new(), + last_node_id: 0, + + margin: 4, + spacing: 4, + + root: 0, + focus: None, + + width, + height, + reservation_top: 0, + }; + + this.update_layout(); + + this + } + + pub fn all_windows(&self) -> impl Iterator + '_ { + self.nodes.iter().filter_map(|(_, node)| { + let window = node.as_window()?; + Some((window.wid, &node.layout)) + }) + } + + pub fn dirty_windows(&self) -> impl Iterator + '_ { + self.all_windows().filter_map(|(wid, layout)| { + if !layout.clear_dirty() { + return None; + } + let rect = layout.get()?; + Some((wid, rect)) + }) + } + + pub fn window_towards_wrap(&self, direction: Direction) -> Option { + let current_nid = self.focus?; + let current_node = self.nodes.get(¤t_nid)?; + let parent_nid = current_node.parent?; + let parent_node = self.nodes.get(&parent_nid)?; + let parents_parent_nid = parent_node.parent; + let parent_container = parent_node.as_container()?; + + let (orientation, delta) = direction.split(); + + let nid = if orientation == parent_container.orientation { + log::info!("Within parent {delta}, {:?}", parent_container.orientation); + let position_in_parent = parent_container + .children + .iter() + .position(|&n| n == current_nid)?; + + if delta > 0 { + if position_in_parent < parent_container.children.len() - 1 { + parent_container.children[position_in_parent + 1] + } else { + parent_container.children[0] + } + } else { + if position_in_parent > 0 { + parent_container.children[position_in_parent - 1] + } else { + parent_container.children[parent_container.children.len() - 1] + } + } + } else if let Some(parents_parent_nid) = parents_parent_nid { + let parents_parent_node = self.nodes.get(&parents_parent_nid)?; + let parents_parent_container = parents_parent_node.as_container()?; + assert_eq!(parents_parent_container.orientation, orientation); + let position_in_parent = parents_parent_container + .children + .iter() + .position(|&n| n == parent_nid)?; + log::info!( + "Within parent's parent {delta}, {:?}", + parents_parent_container.orientation + ); + log::info!("position_in_parent = {position_in_parent}"); + + if delta > 0 { + if position_in_parent < parent_container.children.len() - 1 { + parents_parent_container.children[position_in_parent + 1] + } else { + parents_parent_container.children[0] + } + } else { + if position_in_parent > 0 { + parents_parent_container.children[position_in_parent - 1] + } else { + parents_parent_container.children[parent_container.children.len() - 1] + } + } + } else { + return None; + }; + + self.first_window_in(nid) + } + + pub fn first_window_in(&self, nid: NodeId) -> Option { + let node = self.nodes.get(&nid)?; + + match &node.content { + NodeContent::Window(window) => Some(window.wid), + NodeContent::Container(container) => { + let first = *container.children.first()?; + self.first_window_in(first) + } + } + } + + pub fn move_window(&mut self, direction: Direction) -> bool + where + T: core::fmt::Debug, + { + if let Some(container_nid) = self.move_window_inner(direction) { + self.invalidate_layout(container_nid); + self.fixup_containers(self.root); + self.dump(self.root, 0); + true + } else { + false + } + } + + fn dump(&self, nid: NodeId, depth: usize) + where + T: core::fmt::Debug, + { + let Some(node) = self.nodes.get(&nid) else { + return; + }; + + let rect = node.layout.get(); + + match &node.content { + NodeContent::Window(window) => { + log::info!( + "{:depth$}Window #{:?} {rect:?}", + "", + window.wid, + depth = depth * 2 + ); + } + NodeContent::Container(container) => { + log::info!("{:depth$}Container {rect:?} {{", "", depth = depth * 2); + for &child_nid in container.children.iter() { + self.dump(child_nid, depth + 1); + } + log::info!("{:depth$}}}", "", depth = depth * 2); + } + } + } + + // pub fn walk_windows(&self, mut mapper: F) { + // self.walk_windows_inner(&mut mapper, self.root) + // } + + // fn walk_windows_inner(&self, mapper: &mut F, nid: NodeId) { + // // let Some(node) = self.nodes.get(&nid) else { + // // return; + // // }; + + // // match &node.content { + // // NodeContent::Container(container) => { + // // for &child_nid in container.children.iter() { + // // self.walk_windows_inner(mapper, child_nid); + // // } + // // } + // // NodeContent::Window(window) => { + // // mapper(window.wid); + // // } + // // } + // } + + fn invalidate_layout(&mut self, nid: NodeId) { + // for reservation in self.reservations_top.iter() { + // reservation.layout.clear(); + // } + self.invalidate_layout_inner(nid); + self.update_layout(); + } + + fn invalidate_layout_inner(&self, nid: NodeId) { + let Some(node) = self.nodes.get(&nid) else { + return; + }; + + node.layout.clear(); + + match &node.content { + NodeContent::Container(container) => { + for &child_nid in container.children.iter() { + self.invalidate_layout_inner(child_nid); + } + } + _ => (), + } + } + + fn fixup_containers(&mut self, nid: NodeId) { + let Some(node) = self.nodes.get_mut(&nid) else { + return; + }; + + if let NodeContent::Container(container) = &mut node.content { + if container.children.is_empty() { + let Some(parent_nid) = node.parent else { + return; + }; + // Remove the empty container + if let Some(parent_container) = self + .nodes + .get_mut(&parent_nid) + .and_then(Node::as_container_mut) + { + parent_container.children.retain(|&n| n != nid); + self.nodes.remove(&nid); + self.invalidate_layout(parent_nid); + } + } else if container.children.len() == 1 { + // Remove the empty container and reparent its only child + if let Some(parent_nid) = node.parent { + let child_nid = container.children.remove(0); + let [Some(child), Some(parent)] = + self.nodes.get_many_mut([&child_nid, &parent_nid]) + else { + return; + }; + + child.parent = Some(parent_nid); + if let Some(parent_container) = parent.as_container_mut() { + parent_container.children.retain(|&n| n != nid); + parent_container.children.push(child_nid); + } + + self.invalidate_layout(parent_nid); + } + } else { + // TODO borrowing stuff + let children = container.children.clone(); + for child_nid in children { + self.fixup_containers(child_nid); + } + } + } + } + + fn move_window_inner(&mut self, direction: Direction) -> Option { + let current_nid = self.focus?; + let current_node = self.nodes.get(¤t_nid)?; + let parent_nid = current_node.parent?; + let parent_node = self.nodes.get_mut(&parent_nid)?; + let parents_parent_nid = parent_node.parent; + let parent_container = parent_node.as_container_mut()?; + + let (orientation, delta) = direction.split(); + + if parent_container.orientation == orientation { + let position_in_parent = parent_container + .children + .iter() + .position(|&n| n == current_nid)?; + + // TODO check if this item is also a container + + if delta < 0 && position_in_parent > 0 { + parent_container + .children + .swap(position_in_parent - 1, position_in_parent); + Some(parent_nid) + } else if delta > 0 && position_in_parent < parent_container.children.len() - 1 { + parent_container + .children + .swap(position_in_parent, position_in_parent + 1); + Some(parent_nid) + } else { + None + } + } else if let Some(parents_parent_nid) = parents_parent_nid { + // Go to parent's parent + let parents_parent_node = self.nodes.get_mut(&parents_parent_nid)?; + let parents_parent_container = parents_parent_node.as_container_mut()?; + assert_eq!(parents_parent_container.orientation, orientation); + todo!() + } else { + self.last_node_id += 1; + let new_root_nid = self.last_node_id; + + // Remove child from parent + parent_container.children.retain(|&n| n != current_nid); + + self.nodes.get_mut(¤t_nid)?.parent = Some(new_root_nid); + self.nodes.get_mut(&self.root)?.parent = Some(new_root_nid); + + let children = if delta > 0 { + vec![self.root, current_nid] + } else { + vec![current_nid, self.root] + }; + + // Create a new root + let new_root = Node { + parent: None, + layout: NodeLayout::empty(), + content: NodeContent::Container(ContainerNode { + children, + orientation, + }), + }; + self.root = new_root_nid; + self.nodes.insert(new_root_nid, new_root); + + Some(self.root) + } + } + + pub fn focus_window(&mut self, wid: T) -> (Option, Option) { + let old = self.focus.take().and_then(|nid| self.wid(nid)); + let new = self.wid_to_nid.get(&wid).copied(); + self.focus = new; + (old, new) + } + + pub fn focused_window(&self) -> Option { + let nid = self.focus?; + Some(self.nodes.get(&nid)?.as_window()?.wid) + } + + pub(super) fn window(&self, wid: T) -> Option<&Node> { + let nid = *self.wid_to_nid.get(&wid)?; + self.nodes.get(&nid) + } + + // pub fn reservation(&self, wid: T) -> Option<&Reservation> { + // self.reservations_top.iter().find(|r| r.wid == wid) + // } + + pub(super) fn window_frame(&self, wid: T) -> Option { + self.window(wid)?.layout.get() + } + + pub(super) fn resize(&mut self, w: u32, h: u32, reservation_top: u32) { + self.width = w; + self.height = h; + self.reservation_top = reservation_top; + self.update_layout(); + } + + // fn update_reservation_layout(&mut self) -> u32 { + // res_y + // } + + pub(super) fn update_layout(&mut self) { + self.update_layout_for( + self.root, + Rect { + x: self.margin, + y: self.margin + self.reservation_top, + w: self.width - self.margin * 2, + h: self.height - self.margin * 2 - self.reservation_top, + }, + ); + } + + pub(super) fn create_window(&mut self, wid: T) -> bool { + self.create_window_in(self.root, wid) + } + + pub fn remove_window(&mut self, wid: T) -> bool { + let Some(nid) = self.wid_to_nid.remove(&wid) else { + return false; + }; + if self.remove_inner(nid) { + if self.focus == Some(nid) { + let wid = self.first_window_in(self.root); + if let Some(wid) = wid { + self.focus_window(wid); + } else { + self.focus = None; + } + } + + self.fixup_containers(self.root); + self.update_layout(); + true + } else { + false + } + } + + fn create_window_in(&mut self, container_nid: NodeId, wid: T) -> bool { + self.last_node_id += 1; + let nid = self.last_node_id; + self.nodes.insert( + nid, + Node { + parent: Some(container_nid), + layout: NodeLayout::empty(), + content: NodeContent::Window(WindowNode { wid }), + }, + ); + self.wid_to_nid.insert(wid, nid); + if !self.add_child_to(container_nid, nid) { + self.wid_to_nid.remove(&wid); + self.nodes.remove(&nid); + return false; + } + self.update_layout(); + true + } + + fn wid(&self, nid: NodeId) -> Option { + self.nodes.get(&nid).and_then(|node| match &node.content { + NodeContent::Window(window) => Some(window.wid), + _ => None, + }) + } + + fn update_layout_for(&self, node_id: NodeId, rect: Rect) { + let Some(node) = self.nodes.get(&node_id) else { + log::warn!("update_layout_for: no node {node_id}"); + return; + }; + + node.layout.set(rect); + + match &node.content { + NodeContent::Container(container) => { + if container.children.is_empty() { + return; + } + + match container.orientation { + Orientation::Vertical => { + let hstep = (rect.h - self.spacing * container.children.len() as u32) + / container.children.len() as u32; + let ystep = hstep + self.spacing; + + for (i, &child_nid) in container.children.iter().enumerate() { + let i = i as u32; + let child_rect = Rect { + x: rect.x, + y: rect.y + i * ystep, + w: rect.w, + h: hstep, + }; + log::info!("Frame #{child_nid} size: {child_rect:?}"); + self.update_layout_for(child_nid, child_rect); + } + } + Orientation::Horizontal => { + let wstep = (rect.w - self.spacing * container.children.len() as u32) + / container.children.len() as u32; + let xstep = wstep + self.spacing; + + for (i, &child_nid) in container.children.iter().enumerate() { + let i = i as u32; + let child_rect = Rect { + x: rect.x + i * xstep, + y: rect.y, + w: wstep, + h: rect.h, + }; + log::info!("Frame #{child_nid} size: {child_rect:?}"); + self.update_layout_for(child_nid, child_rect); + } + } + } + } + _ => (), + } + } + + fn remove_inner(&mut self, node_id: NodeId) -> bool { + if node_id == self.root { + return false; + } + let Some(node) = self.nodes.remove(&node_id) else { + return false; + }; + let container_nid = unsafe { node.parent.unwrap_unchecked() }; + let Some(container_node) = self.nodes.get_mut(&container_nid) else { + return false; + }; + let NodeContent::Container(container) = &mut container_node.content else { + return false; + }; + container_node.layout.clear(); + container.children.retain(|&c| c != node_id); + // Only root container can be empty + debug_assert!(!(container_node.parent.is_some() && container.children.is_empty())); + if container.children.len() == 1 && container_node.parent.is_some() { + // Lift the remaining node to the parent container + todo!() + } + + true + } + + fn add_child_to(&mut self, container_nid: NodeId, child_nid: NodeId) -> bool { + let [Some(container_node), Some(child_node)] = + self.nodes.get_many_mut([&container_nid, &child_nid]) + else { + return false; + }; + let NodeContent::Container(container) = &mut container_node.content else { + return false; + }; + + container_node.layout.clear(); + child_node.layout.clear(); + child_node.parent = Some(container_nid); + container.children.push(child_nid); + + true + } +}