colors: add workspaces
This commit is contained in:
parent
c4e3128528
commit
8493573721
@ -4,7 +4,6 @@ use libcolors::{
|
||||
};
|
||||
// use yggdrasil_abi::io::{KeyboardKey, KeyboardKeyEvent};
|
||||
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct InputState {
|
||||
lshift: bool,
|
||||
|
@ -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<u32, PeerAddress>,
|
||||
|
||||
workspace: Workspace<u32>,
|
||||
display: Display<u32>,
|
||||
windows: BTreeMap<u32, Window<'a>>,
|
||||
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::<u32>::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::<u32>::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(
|
||||
|
@ -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 {
|
||||
|
@ -165,7 +165,12 @@ impl<S: WindowServer> Backend<S> {
|
||||
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 _,
|
||||
|
@ -145,7 +145,7 @@ impl<S: WindowServer> 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<S: WindowServer> 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<Keyboar
|
||||
KeyboardKey::F(_) => return None,
|
||||
};
|
||||
|
||||
Some(KeyboardKeyEvent {
|
||||
key,
|
||||
state
|
||||
})
|
||||
Some(KeyboardKeyEvent { key, state })
|
||||
}
|
||||
|
212
userspace/colors/src/wm/display.rs
Normal file
212
userspace/colors/src/wm/display.rs
Normal file
@ -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<T> {
|
||||
workspaces: Vec<Workspace<T>>,
|
||||
width: u32,
|
||||
height: u32,
|
||||
reservations_top: Vec<Reservation<T>>,
|
||||
reservation_height: u32,
|
||||
pub current_workspace: usize,
|
||||
}
|
||||
|
||||
pub struct Reservation<T> {
|
||||
layout: NodeLayout,
|
||||
size: u32,
|
||||
wid: T,
|
||||
}
|
||||
|
||||
impl<T: Eq + Hash + Copy> Display<T> {
|
||||
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<T> {
|
||||
&self.workspaces[index]
|
||||
}
|
||||
|
||||
pub fn workspace_mut(&mut self, index: usize) -> &mut Workspace<T> {
|
||||
&mut self.workspaces[index]
|
||||
}
|
||||
|
||||
pub fn current_workspace(&self) -> &Workspace<T> {
|
||||
&self.workspaces[self.current_workspace]
|
||||
}
|
||||
|
||||
pub fn current_workspace_mut(&mut self) -> &mut Workspace<T> {
|
||||
&mut self.workspaces[self.current_workspace]
|
||||
}
|
||||
|
||||
pub fn all_windows(&self) -> impl Iterator<Item = (T, &NodeLayout)> + '_ {
|
||||
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<Item = (T, Rect)> + '_ {
|
||||
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<Rect> {
|
||||
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<bool> {
|
||||
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<Item = (T, Rect)> + '_ {
|
||||
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<Item = (T, &NodeLayout)> + '_ {
|
||||
self.reservations_top
|
||||
.iter()
|
||||
.map(|res| (res.wid, &res.layout))
|
||||
}
|
||||
|
||||
fn lookup_reservation(&self, wid: T) -> Option<&Reservation<T>> {
|
||||
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) {
|
||||
// }
|
||||
}
|
67
userspace/colors/src/wm/layout.rs
Normal file
67
userspace/colors/src/wm/layout.rs
Normal file
@ -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<Rect>, 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<Rect> {
|
||||
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),
|
||||
}
|
||||
}
|
||||
}
|
@ -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<T> {
|
||||
Container(ContainerNode),
|
||||
Window(WindowNode<T>),
|
||||
}
|
||||
|
||||
pub struct NodeLayout {
|
||||
rect: Cell<(Option<Rect>, bool)>,
|
||||
}
|
||||
|
||||
pub struct Node<T> {
|
||||
parent: Option<NodeId>,
|
||||
layout: NodeLayout,
|
||||
|
||||
content: NodeContent<T>,
|
||||
}
|
||||
|
||||
pub struct ContainerNode {
|
||||
children: Vec<NodeId>,
|
||||
orientation: Orientation,
|
||||
}
|
||||
|
||||
pub struct WindowNode<T> {
|
||||
pub wid: T,
|
||||
}
|
||||
|
||||
pub struct Reservation<T> {
|
||||
layout: NodeLayout,
|
||||
size: u32,
|
||||
wid: T,
|
||||
}
|
||||
|
||||
pub struct Workspace<T> {
|
||||
nodes: HashMap<NodeId, Node<T>>,
|
||||
wid_to_nid: HashMap<T, NodeId>,
|
||||
last_node_id: NodeId,
|
||||
|
||||
root: NodeId,
|
||||
focus: Option<NodeId>,
|
||||
|
||||
margin: u32,
|
||||
spacing: u32,
|
||||
|
||||
reservations_top: Vec<Reservation<T>>,
|
||||
reservation_top: u32,
|
||||
|
||||
width: u32,
|
||||
height: u32,
|
||||
}
|
||||
|
||||
impl<T> Node<T> {
|
||||
pub fn as_window(&self) -> Option<&WindowNode<T>> {
|
||||
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<Rect> {
|
||||
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<T: Eq + Hash + Copy> Workspace<T> {
|
||||
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<Item = (T, &NodeLayout)> + '_ {
|
||||
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<Item = (T, Rect)> + '_ {
|
||||
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<T> {
|
||||
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<T> {
|
||||
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<F: FnMut(T)>(&self, mut mapper: F) {
|
||||
// self.walk_windows_inner(&mut mapper, self.root)
|
||||
// }
|
||||
|
||||
// fn walk_windows_inner<F: FnMut(T)>(&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<NodeId> {
|
||||
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<T>, Option<NodeId>) {
|
||||
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<T> {
|
||||
let nid = self.focus?;
|
||||
Some(self.nodes.get(&nid)?.as_window()?.wid)
|
||||
}
|
||||
|
||||
pub fn window(&self, wid: T) -> Option<&Node<T>> {
|
||||
let nid = *self.wid_to_nid.get(&wid)?;
|
||||
self.nodes.get(&nid)
|
||||
}
|
||||
|
||||
pub fn reservation(&self, wid: T) -> Option<&Reservation<T>> {
|
||||
self.reservations_top.iter().find(|r| r.wid == wid)
|
||||
}
|
||||
|
||||
pub fn window_frame(&self, wid: T) -> Option<Rect> {
|
||||
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<T> {
|
||||
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;
|
||||
|
599
userspace/colors/src/wm/workspace.rs
Normal file
599
userspace/colors/src/wm/workspace.rs
Normal file
@ -0,0 +1,599 @@
|
||||
use std::{collections::HashMap, hash::Hash};
|
||||
|
||||
use super::layout::{Direction, NodeLayout, Orientation, Rect};
|
||||
|
||||
pub type NodeId = u32;
|
||||
|
||||
pub enum NodeContent<T> {
|
||||
Container(ContainerNode),
|
||||
Window(WindowNode<T>),
|
||||
}
|
||||
|
||||
pub struct Node<T> {
|
||||
parent: Option<NodeId>,
|
||||
layout: NodeLayout,
|
||||
|
||||
content: NodeContent<T>,
|
||||
}
|
||||
|
||||
pub struct ContainerNode {
|
||||
children: Vec<NodeId>,
|
||||
orientation: Orientation,
|
||||
}
|
||||
|
||||
pub struct WindowNode<T> {
|
||||
pub wid: T,
|
||||
}
|
||||
|
||||
pub struct Workspace<T> {
|
||||
nodes: HashMap<NodeId, Node<T>>,
|
||||
wid_to_nid: HashMap<T, NodeId>,
|
||||
last_node_id: NodeId,
|
||||
|
||||
root: NodeId,
|
||||
focus: Option<NodeId>,
|
||||
|
||||
margin: u32,
|
||||
spacing: u32,
|
||||
|
||||
// reservations_top: Vec<Reservation<T>>,
|
||||
reservation_top: u32,
|
||||
|
||||
width: u32,
|
||||
height: u32,
|
||||
}
|
||||
|
||||
impl<T> Node<T> {
|
||||
pub fn as_window(&self) -> Option<&WindowNode<T>> {
|
||||
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<T: Eq + Hash + Copy> Workspace<T> {
|
||||
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<Item = (T, &NodeLayout)> + '_ {
|
||||
self.nodes.iter().filter_map(|(_, node)| {
|
||||
let window = node.as_window()?;
|
||||
Some((window.wid, &node.layout))
|
||||
})
|
||||
}
|
||||
|
||||
pub fn dirty_windows(&self) -> impl Iterator<Item = (T, Rect)> + '_ {
|
||||
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<T> {
|
||||
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<T> {
|
||||
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<F: FnMut(T)>(&self, mut mapper: F) {
|
||||
// self.walk_windows_inner(&mut mapper, self.root)
|
||||
// }
|
||||
|
||||
// fn walk_windows_inner<F: FnMut(T)>(&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<NodeId> {
|
||||
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<T>, Option<NodeId>) {
|
||||
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<T> {
|
||||
let nid = self.focus?;
|
||||
Some(self.nodes.get(&nid)?.as_window()?.wid)
|
||||
}
|
||||
|
||||
pub(super) fn window(&self, wid: T) -> Option<&Node<T>> {
|
||||
let nid = *self.wid_to_nid.get(&wid)?;
|
||||
self.nodes.get(&nid)
|
||||
}
|
||||
|
||||
// pub fn reservation(&self, wid: T) -> Option<&Reservation<T>> {
|
||||
// self.reservations_top.iter().find(|r| r.wid == wid)
|
||||
// }
|
||||
|
||||
pub(super) fn window_frame(&self, wid: T) -> Option<Rect> {
|
||||
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<T> {
|
||||
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
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user