From 8b4ce48a662118127bc3a3219daad715f39013f3 Mon Sep 17 00:00:00 2001 From: LGUG2Z Date: Sun, 8 Aug 2021 22:47:53 -0700 Subject: [PATCH] feat(wm): add container resizing The last remaining feature to bring komorebi to feature parity with yatta. Implementing this in komorebi was a lot harder because I had to make sure that resizing worked even when the layout is flipped (in any one of the three possible ways). In yatta, resize dimension information was stored on the window. In komorebi, I initially tried storing this information on the Container itself, but eventually decided to store it for all Containers in the Workspace. There is some additional work required to ensure that this Vec is kept up to date whenever containers are added or destroyed, but it all seems to be working fairly well. I got rid of the iterative fibonacci code that I adapted from leftwm and went back and reworked the recursive code that I was using in yatta (originally from umberwm I think) to integrate layout flipping. At least for me, it is much easier to reason about. --- Cargo.lock | 4 +- README.md | 4 +- komorebi-core/src/layout.rs | 395 +++++++++++++++++------ komorebi-core/src/lib.rs | 11 +- komorebi-core/src/operation_direction.rs | 17 +- komorebi-core/src/rect.rs | 2 +- komorebi/src/process_command.rs | 15 +- komorebi/src/process_event.rs | 88 ++++- komorebi/src/window_manager.rs | 76 ++++- komorebi/src/window_manager_event.rs | 10 - komorebi/src/windows_api.rs | 2 + komorebi/src/workspace.rs | 47 ++- komorebic/src/main.rs | 13 + 13 files changed, 536 insertions(+), 148 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e5017149..bd12c8c8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -689,9 +689,9 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.2.9" +version = "0.2.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ab49abadf3f9e1c4bc499e8845e152ad87d2ad2d30371841171169e9d75feee" +checksum = "8383f39639269cde97d255a32bdb68c047337295414940c68bdd30c2e13203ff" dependencies = [ "bitflags", ] diff --git a/README.md b/README.md index 77c2ae3f..0c9eee2f 100644 --- a/README.md +++ b/README.md @@ -100,7 +100,10 @@ sample [komorebi.ahk](komorebi.sample.ahk) AHK script that you can use as a star - [x] Move focused window container in direction - [x] Move focused window container to monitor - [x] Move focused window container to workspace +- [x] Resize window container in direction +- [ ] Resize child window containers by split ratio - [x] Mouse drag to swap window container position +- [x] Mouse drag to resize window container - [x] Configurable workspace and container gaps - [x] BSP tree layout - [x] Flip BSP tree layout horizontally or vertically @@ -112,7 +115,6 @@ sample [komorebi.ahk](komorebi.sample.ahk) AHK script that you can use as a star - [x] Toggle monocle window - [x] Pause all window management - [x] View window manager state -- [ ] Configure split ratio like *bspwm* ## Development diff --git a/komorebi-core/src/layout.rs b/komorebi-core/src/layout.rs index 3bf9d40f..0d4dc21d 100644 --- a/komorebi-core/src/layout.rs +++ b/komorebi-core/src/layout.rs @@ -4,7 +4,9 @@ use serde::Serialize; use strum::Display; use strum::EnumString; +use crate::OperationDirection; use crate::Rect; +use crate::Sizing; #[derive(Clone, Copy, Debug, Serialize, Deserialize, Display, EnumString)] #[strum(serialize_all = "snake_case")] @@ -25,21 +27,125 @@ pub enum LayoutFlip { } impl Layout { + pub fn resize( + &self, + unaltered: &Rect, + resize: &Option, + edge: OperationDirection, + sizing: Sizing, + step: Option, + ) -> Option { + let max_divisor = 1.005; + let mut r = resize.unwrap_or_default(); + + let resize_step = if let Some(step) = step { step } else { 50 }; + + match edge { + OperationDirection::Left => match sizing { + Sizing::Increase => { + // Some final checks to make sure the user can't infinitely resize to + // the point of pushing other windows out of bounds + + // Note: These checks cannot take into account the changes made to the + // edges of adjacent windows at operation time, so it is still possible + // to push windows out of bounds by maxing out an Increase Left on a + // Window with index 1, and then maxing out a Decrease Right on a Window + // with index 0. I don't think it's worth trying to defensively program + // against this; if people end up in this situation they are better off + // just hitting the retile command + let diff = ((r.left + -resize_step) as f32).abs(); + let max = unaltered.right as f32 / max_divisor; + if diff < max { + r.left += -resize_step; + } + } + Sizing::Decrease => { + let diff = ((r.left - -resize_step) as f32).abs(); + let max = unaltered.right as f32 / max_divisor; + if diff < max { + r.left -= -resize_step; + } + } + }, + OperationDirection::Up => match sizing { + Sizing::Increase => { + let diff = ((r.top + resize_step) as f32).abs(); + let max = unaltered.bottom as f32 / max_divisor; + if diff < max { + r.top += -resize_step; + } + } + Sizing::Decrease => { + let diff = ((r.top - resize_step) as f32).abs(); + let max = unaltered.bottom as f32 / max_divisor; + if diff < max { + r.top -= -resize_step; + } + } + }, + OperationDirection::Right => match sizing { + Sizing::Increase => { + let diff = ((r.right + resize_step) as f32).abs(); + let max = unaltered.right as f32 / max_divisor; + if diff < max { + r.right += resize_step; + } + } + Sizing::Decrease => { + let diff = ((r.right - resize_step) as f32).abs(); + let max = unaltered.right as f32 / max_divisor; + if diff < max { + r.right -= resize_step; + } + } + }, + OperationDirection::Down => match sizing { + Sizing::Increase => { + let diff = ((r.bottom + resize_step) as f32).abs(); + let max = unaltered.bottom as f32 / max_divisor; + if diff < max { + r.bottom += resize_step; + } + } + Sizing::Decrease => { + let diff = ((r.bottom - resize_step) as f32).abs(); + let max = unaltered.bottom as f32 / max_divisor; + if diff < max { + r.bottom -= resize_step; + } + } + }, + }; + + if !r.eq(&Rect::default()) { + Option::from(r) + } else { + None + } + } + pub fn calculate( &self, area: &Rect, - count: usize, + len: usize, container_padding: Option, layout_flip: Option, + resize_dimensions: &[Option], ) -> Vec { let mut dimensions = match self { - Layout::BSP => self.fibonacci(area, count, layout_flip), + Layout::BSP => recursive_fibonacci( + 0, + len, + area, + layout_flip, + calculate_resize_adjustments(resize_dimensions), + ), Layout::Columns => { - let right = area.right / count as i32; + let right = area.right / len as i32; let mut left = 0; let mut layouts: Vec = vec![]; - for _ in 0..count { + for _ in 0..len { layouts.push(Rect { left: area.left + left, top: area.top, @@ -53,11 +159,11 @@ impl Layout { layouts } Layout::Rows => { - let bottom = area.bottom / count as i32; + let bottom = area.bottom / len as i32; let mut top = 0; let mut layouts: Vec = vec![]; - for _ in 0..count { + for _ in 0..len { layouts.push(Rect { left: area.left, top: area.top + top, @@ -78,116 +184,213 @@ impl Layout { dimensions } +} - pub fn fibonacci( - &self, - area: &Rect, - count: usize, - layout_flip: Option, - ) -> Vec { - let mut dimensions = vec![]; +impl Layout { + pub fn next(&mut self) { + match self { + Layout::BSP => *self = Layout::Columns, + Layout::Columns => *self = Layout::Rows, + Layout::Rows => *self = Layout::BSP, + } + } - for _ in 0..count { - dimensions.push(Rect::default()) + pub fn previous(&mut self) { + match self { + Layout::BSP => *self = Layout::Rows, + Layout::Columns => *self = Layout::BSP, + Layout::Rows => *self = Layout::Columns, } + } +} - let mut left = area.left; - let mut top = area.top; - let mut bottom = area.bottom; - let mut right = area.right; +fn calculate_resize_adjustments(resize_dimensions: &[Option]) -> Vec> { + let mut resize_adjustments = resize_dimensions.to_vec(); - for i in 0..count { - if i % 2 != 0 { - continue; - } + // This needs to be aware of layout flips + for (i, opt) in resize_dimensions.iter().enumerate() { + if let Some(resize_ref) = opt { + if i > 0 { + if resize_ref.left != 0 { + let range = if i == 1 { + 0..1 + } else if i & 1 != 0 { + i - 1..i + } else { + i - 2..i + }; - let half_width = right / 2; - let half_height = bottom / 2; + for n in range { + let should_adjust = n % 2 == 0; + if should_adjust { + if let Some(Some(adjacent_resize)) = resize_adjustments.get_mut(n) { + adjacent_resize.right += resize_ref.left; + } else { + resize_adjustments[n] = Option::from(Rect { + left: 0, + top: 0, + right: resize_ref.left, + bottom: 0, + }); + } + } + } - let (main_x, alt_x, new_y, alt_y); + if let Some(rr) = resize_adjustments[i].as_mut() { + rr.left = 0; + } + } - match layout_flip { - Some(flip) => match flip { - LayoutFlip::Horizontal => { - main_x = left + half_width; - alt_x = left; + if resize_ref.top != 0 { + let range = if i == 1 { + 0..1 + } else if i & 1 == 0 { + i - 1..i + } else { + i - 2..i + }; - new_y = top + half_height; - alt_y = top; + for n in range { + let should_adjust = n % 2 != 0; + if should_adjust { + if let Some(Some(adjacent_resize)) = resize_adjustments.get_mut(n) { + adjacent_resize.bottom += resize_ref.top; + } else { + resize_adjustments[n] = Option::from(Rect { + left: 0, + top: 0, + right: 0, + bottom: resize_ref.top, + }); + } + } } - LayoutFlip::Vertical => { - new_y = top; - alt_y = top + half_height; - main_x = left; - alt_x = left + half_width; - } - LayoutFlip::HorizontalAndVertical => { - main_x = left + half_width; - alt_x = left; - new_y = top; - alt_y = top + half_height; + if let Some(Some(resize)) = resize_adjustments.get_mut(i) { + resize.top = 0; } - }, - None => { - main_x = left; - alt_x = left + half_width; - new_y = top + half_height; - alt_y = top; - } - } - - match count - i { - 1 => { - set_dimensions(&mut dimensions[i], left, top, right, bottom); - } - 2 => { - set_dimensions(&mut dimensions[i], main_x, top, half_width, bottom); - set_dimensions(&mut dimensions[i + 1], alt_x, top, half_width, bottom); - } - _ => { - set_dimensions(&mut dimensions[i], main_x, top, half_width, bottom); - set_dimensions( - &mut dimensions[i + 1], - alt_x, - alt_y, - half_width, - half_height, - ); - - left = alt_x; - top = new_y; - right = half_width; - bottom = half_height; } } } - - dimensions } + + let cleaned_resize_adjustments: Vec<_> = resize_adjustments + .iter() + .map(|adjustment| match adjustment { + None => None, + Some(rect) if rect.eq(&Rect::default()) => None, + Some(_) => *adjustment, + }) + .collect(); + + cleaned_resize_adjustments } -impl Layout { - pub fn next(&mut self) { - match self { - Layout::BSP => *self = Layout::Columns, - Layout::Columns => *self = Layout::Rows, - Layout::Rows => *self = Layout::BSP, - } - } +fn recursive_fibonacci( + idx: usize, + count: usize, + area: &Rect, + layout_flip: Option, + resize_adjustments: Vec>, +) -> Vec { + let mut a = *area; - pub fn previous(&mut self) { - match self { - Layout::BSP => *self = Layout::Rows, - Layout::Columns => *self = Layout::BSP, - Layout::Rows => *self = Layout::Columns, + let resized = if let Some(Some(r)) = resize_adjustments.get(idx) { + a.left += r.left; + a.top += r.top; + a.right += r.right; + a.bottom += r.bottom; + a + } else { + *area + }; + + let half_width = area.right / 2; + let half_height = area.bottom / 2; + let half_resized_width = resized.right / 2; + let half_resized_height = resized.bottom / 2; + + let (main_x, alt_x, alt_y, main_y); + + match layout_flip { + Some(flip) => match flip { + LayoutFlip::Horizontal => { + main_x = resized.left + half_width + (half_width - half_resized_width); + alt_x = resized.left; + + alt_y = resized.top + half_resized_height; + main_y = resized.top; + } + LayoutFlip::Vertical => { + main_y = resized.top + half_height + (half_height - half_resized_height); + alt_y = resized.top; + + main_x = resized.left; + alt_x = resized.left + half_resized_width; + } + LayoutFlip::HorizontalAndVertical => { + main_x = resized.left + half_width + (half_width - half_resized_width); + alt_x = resized.left; + main_y = resized.top + half_height + (half_height - half_resized_height); + alt_y = resized.top; + } + }, + None => { + main_x = resized.left; + alt_x = resized.left + half_resized_width; + main_y = resized.top; + alt_y = resized.top + half_resized_height; } } -} -fn set_dimensions(rect: &mut Rect, left: i32, top: i32, right: i32, bottom: i32) { - rect.bottom = bottom; - rect.right = right; - rect.left = left; - rect.top = top; + if count == 0 { + vec![] + } else if count == 1 { + vec![Rect { + left: resized.left, + top: resized.top, + right: resized.right, + bottom: resized.bottom, + }] + } else if idx % 2 != 0 { + let mut res = vec![Rect { + left: resized.left, + top: main_y, + right: resized.right, + bottom: half_resized_height, + }]; + res.append(&mut recursive_fibonacci( + idx + 1, + count - 1, + &Rect { + left: area.left, + top: alt_y, + right: area.right, + bottom: area.bottom - half_resized_height, + }, + layout_flip, + resize_adjustments, + )); + res + } else { + let mut res = vec![Rect { + left: main_x, + top: resized.top, + right: half_resized_width, + bottom: resized.bottom, + }]; + res.append(&mut recursive_fibonacci( + idx + 1, + count - 1, + &Rect { + left: alt_x, + top: area.top, + right: area.right - half_resized_width, + bottom: area.bottom, + }, + layout_flip, + resize_adjustments, + )); + res + } } diff --git a/komorebi-core/src/lib.rs b/komorebi-core/src/lib.rs index 75c206f9..2c729d08 100644 --- a/komorebi-core/src/lib.rs +++ b/komorebi-core/src/lib.rs @@ -24,6 +24,7 @@ pub enum SocketMessage { FocusWindow(OperationDirection), MoveWindow(OperationDirection), StackWindow(OperationDirection), + ResizeWindow(OperationDirection, Sizing), UnstackWindow, CycleStack(CycleDirection), MoveContainerToMonitorNumber(usize), @@ -94,13 +95,3 @@ impl Sizing { } } } - -#[derive(Clone, Copy, Debug, Serialize, Deserialize, Display, EnumString)] -#[strum(serialize_all = "snake_case")] -#[derive(Clap)] -pub enum ResizeEdge { - Left, - Top, - Right, - Bottom, -} diff --git a/komorebi-core/src/operation_direction.rs b/komorebi-core/src/operation_direction.rs index b4c180b7..4913e24f 100644 --- a/komorebi-core/src/operation_direction.rs +++ b/komorebi-core/src/operation_direction.rs @@ -18,15 +18,12 @@ pub enum OperationDirection { } impl OperationDirection { - pub fn can_resize(&self, layout: Layout, idx: usize, len: usize) -> bool { - match layout { - Layout::BSP => match self { - Self::Left => len != 0 && idx != 0, - Self::Up => len > 2 && idx != 0 && idx != 1, - Self::Right => len > 1 && idx % 2 == 0 && idx != len - 1, - Self::Down => len > 2 && idx != len - 1 && idx % 2 != 0, - }, - _ => false, + pub fn opposite(self) -> Self { + match self { + OperationDirection::Left => OperationDirection::Right, + OperationDirection::Right => OperationDirection::Left, + OperationDirection::Up => OperationDirection::Down, + OperationDirection::Down => OperationDirection::Up, } } @@ -90,7 +87,7 @@ impl OperationDirection { Layout::Rows => false, }, OperationDirection::Right => match layout { - Layout::BSP => len > 1 && idx % 2 == 0, + Layout::BSP => len > 1 && idx % 2 == 0 && idx != len - 1, Layout::Columns => idx != len - 1, Layout::Rows => false, }, diff --git a/komorebi-core/src/rect.rs b/komorebi-core/src/rect.rs index 431d71ba..fb8e15e7 100644 --- a/komorebi-core/src/rect.rs +++ b/komorebi-core/src/rect.rs @@ -2,7 +2,7 @@ use serde::Serialize; use bindings::Windows::Win32::Foundation::RECT; -#[derive(Debug, Clone, Copy, Serialize)] +#[derive(Debug, Clone, Copy, Serialize, Eq, PartialEq)] pub struct Rect { pub left: i32, pub top: i32, diff --git a/komorebi/src/process_command.rs b/komorebi/src/process_command.rs index 9b36de28..ac5b20a7 100644 --- a/komorebi/src/process_command.rs +++ b/komorebi/src/process_command.rs @@ -108,10 +108,16 @@ impl WindowManager { SocketMessage::Retile => { for monitor in self.monitors_mut() { let work_area = *monitor.work_area_size(); - monitor + let workspace = monitor .focused_workspace_mut() - .context("there is no workspace")? - .update(&work_area)?; + .context("there is no workspace")?; + + // Reset any resize adjustments if we want to force a retile + for resize in workspace.resize_dimensions_mut() { + *resize = None; + } + + workspace.update(&work_area)?; } } SocketMessage::FlipLayout(layout_flip) => self.flip_layout(layout_flip)?, @@ -144,6 +150,9 @@ impl WindowManager { let mut stream = UnixStream::connect(&socket)?; stream.write_all(state.as_bytes())?; } + SocketMessage::ResizeWindow(direction, sizing) => { + self.resize_window(direction, sizing, Option::from(50))?; + } } tracing::info!("processed"); diff --git a/komorebi/src/process_event.rs b/komorebi/src/process_event.rs index 3a1d964d..82f73d33 100644 --- a/komorebi/src/process_event.rs +++ b/komorebi/src/process_event.rs @@ -7,8 +7,13 @@ use color_eyre::eyre::ContextCompat; use color_eyre::Result; use crossbeam_channel::select; +use komorebi_core::OperationDirection; +use komorebi_core::Rect; +use komorebi_core::Sizing; + use crate::window_manager::WindowManager; use crate::window_manager_event::WindowManagerEvent; +use crate::windows_api::WindowsApi; use crate::HIDDEN_HWNDS; use crate::MULTI_WINDOW_EXES; @@ -46,7 +51,6 @@ impl WindowManager { match event { WindowManagerEvent::FocusChange(_, window) | WindowManagerEvent::Show(_, window) - | WindowManagerEvent::MoveResizeStart(_, window) | WindowManagerEvent::MoveResizeEnd(_, window) => { let monitor_idx = self .monitor_idx_from_window(*window) @@ -141,9 +145,6 @@ impl WindowManager { self.update_focused_workspace(false)?; } } - WindowManagerEvent::MoveResizeStart(_, _window) => { - // TODO: Implement dragging resize (one day) - } WindowManagerEvent::MoveResizeEnd(_, window) => { let workspace = self.focused_workspace_mut()?; if workspace @@ -155,13 +156,82 @@ impl WindowManager { } let focused_idx = workspace.focused_container_idx(); + let old_position = *workspace + .latest_layout() + .get(focused_idx) + .context("there is no latest layout")?; + let mut new_position = WindowsApi::window_rect(window.hwnd())?; + + // See Window.set_position() in window.rs for comments + let border = Rect { + left: 12, + top: 0, + right: 24, + bottom: 12, + }; + + // Adjust for the invisible border + new_position.left += border.left; + new_position.top += border.top; + new_position.right -= border.right; + new_position.bottom -= border.bottom; + + let resize = Rect { + left: new_position.left - old_position.left, + top: new_position.top - old_position.top, + right: new_position.right - old_position.right, + bottom: new_position.bottom - old_position.bottom, + }; + + let is_move = resize.right == 0 && resize.bottom == 0; + + if is_move { + tracing::info!("moving with mouse"); + match workspace.container_idx_from_current_point() { + Some(target_idx) => { + workspace.swap_containers(focused_idx, target_idx); + self.update_focused_workspace(false)?; + } + None => self.update_focused_workspace(true)?, + } + } else { + tracing::info!("resizing with mouse"); + let mut ops = vec![]; + + macro_rules! resize_op { + ($coordinate:expr, $comparator:tt, $direction:expr) => {{ + let adjusted = $coordinate * 2; + let sizing = if adjusted $comparator 0 { + Sizing::Decrease + } else { + Sizing::Increase + }; + + ($direction, sizing, adjusted.abs()) + }}; + } + + if resize.left != 0 { + ops.push(resize_op!(resize.left, >, OperationDirection::Left)); + } - match workspace.container_idx_from_current_point() { - Some(target_idx) => { - workspace.swap_containers(focused_idx, target_idx); - self.update_focused_workspace(false)?; + if resize.top != 0 { + ops.push(resize_op!(resize.top, >, OperationDirection::Up)); } - None => self.update_focused_workspace(true)?, + + if resize.right != 0 && resize.left == 0 { + ops.push(resize_op!(resize.right, <, OperationDirection::Right)); + } + + if resize.bottom != 0 && resize.top == 0 { + ops.push(resize_op!(resize.bottom, <, OperationDirection::Down)); + } + + for (edge, sizing, step) in ops { + self.resize_window(edge, sizing, Option::from(step))?; + } + + self.update_focused_workspace(false)?; } } WindowManagerEvent::MouseCapture(..) => {} diff --git a/komorebi/src/window_manager.rs b/komorebi/src/window_manager.rs index 0c685a9d..7cc49db9 100644 --- a/komorebi/src/window_manager.rs +++ b/komorebi/src/window_manager.rs @@ -94,6 +94,80 @@ impl WindowManager { Ok(()) } + #[tracing::instrument(skip(self))] + pub fn resize_window( + &mut self, + direction: OperationDirection, + sizing: Sizing, + step: Option, + ) -> Result<()> { + tracing::info!("resizing window"); + + let work_area = self.focused_monitor_work_area()?; + let workspace = self.focused_workspace_mut()?; + let len = workspace.containers().len(); + let focused_idx = workspace.focused_container_idx(); + let focused_idx_resize = workspace + .resize_dimensions() + .get(focused_idx) + .context("there is no resize adjustment for this container")?; + + if direction.is_valid( + workspace.layout(), + workspace.layout_flip(), + focused_idx, + len, + ) { + let unaltered = workspace.layout().calculate( + &work_area, + len, + workspace.container_padding(), + workspace.layout_flip(), + &[], + ); + + let mut direction = direction; + + // We only ever want to operate on the unflipped Rect positions when resizing, then we + // can flip them however they need to be flipped once the resizing has been done + if let Some(flip) = workspace.layout_flip() { + match flip { + LayoutFlip::Horizontal => { + if matches!(direction, OperationDirection::Left) + || matches!(direction, OperationDirection::Right) + { + direction = direction.opposite(); + } + } + LayoutFlip::Vertical => { + if matches!(direction, OperationDirection::Up) + || matches!(direction, OperationDirection::Down) + { + direction = direction.opposite(); + } + } + LayoutFlip::HorizontalAndVertical => direction = direction.opposite(), + } + } + + let resize = workspace.layout().resize( + unaltered + .get(focused_idx) + .context("there is no last layout")?, + focused_idx_resize, + direction, + sizing, + step, + ); + + workspace.resize_dimensions_mut()[focused_idx] = resize; + self.update_focused_workspace(false) + } else { + tracing::warn!("cannot resize container in this direction"); + Ok(()) + } + } + #[tracing::instrument(skip(self))] pub fn restore_all_windows(&mut self) { tracing::info!("restoring all hidden windows"); @@ -340,7 +414,7 @@ impl WindowManager { #[tracing::instrument(skip(self))] pub fn flip_layout(&mut self, layout_flip: LayoutFlip) -> Result<()> { - tracing::info!("flipping layout monocle"); + tracing::info!("flipping layout"); let workspace = self.focused_workspace_mut()?; diff --git a/komorebi/src/window_manager_event.rs b/komorebi/src/window_manager_event.rs index bec4f8c6..98b1bd0e 100644 --- a/komorebi/src/window_manager_event.rs +++ b/komorebi/src/window_manager_event.rs @@ -11,7 +11,6 @@ pub enum WindowManagerEvent { Hide(WinEvent, Window), Minimize(WinEvent, Window), Show(WinEvent, Window), - MoveResizeStart(WinEvent, Window), MoveResizeEnd(WinEvent, Window), MouseCapture(WinEvent, Window), } @@ -38,13 +37,6 @@ impl Display for WindowManagerEvent { WindowManagerEvent::Show(winevent, window) => { write!(f, "Show (WinEvent: {}, Window: {})", winevent, window) } - WindowManagerEvent::MoveResizeStart(winevent, window) => { - write!( - f, - "MoveResizeStart (WinEvent: {}, Window: {})", - winevent, window - ) - } WindowManagerEvent::MoveResizeEnd(winevent, window) => { write!( f, @@ -71,7 +63,6 @@ impl WindowManagerEvent { | WindowManagerEvent::Hide(_, window) | WindowManagerEvent::Minimize(_, window) | WindowManagerEvent::Show(_, window) - | WindowManagerEvent::MoveResizeStart(_, window) | WindowManagerEvent::MoveResizeEnd(_, window) | WindowManagerEvent::MouseCapture(_, window) => window, } @@ -92,7 +83,6 @@ impl WindowManagerEvent { WinEvent::ObjectFocus | WinEvent::SystemForeground => { Some(Self::FocusChange(winevent, window)) } - WinEvent::SystemMoveSizeStart => Some(Self::MoveResizeStart(winevent, window)), WinEvent::SystemMoveSizeEnd => Some(Self::MoveResizeEnd(winevent, window)), WinEvent::SystemCaptureStart | WinEvent::SystemCaptureEnd => { Some(Self::MouseCapture(winevent, window)) diff --git a/komorebi/src/windows_api.rs b/komorebi/src/windows_api.rs index 16f60416..901ecabf 100644 --- a/komorebi/src/windows_api.rs +++ b/komorebi/src/windows_api.rs @@ -167,6 +167,7 @@ impl WindowsApi { EnumWindows(Option::from(callback), LPARAM(callback_data_address)) })) } + pub fn load_workspace_information(monitors: &mut Ring) -> Result<()> { for monitor in monitors.elements_mut() { if monitor.workspaces().is_empty() { @@ -373,6 +374,7 @@ impl WindowsApi { pub fn gwl_style(hwnd: HWND) -> Result { Self::window_long_ptr_w(hwnd, GWL_STYLE) } + pub fn gwl_ex_style(hwnd: HWND) -> Result { Self::window_long_ptr_w(hwnd, GWL_EXSTYLE) } diff --git a/komorebi/src/workspace.rs b/komorebi/src/workspace.rs index 9d7dccc6..61768e80 100644 --- a/komorebi/src/workspace.rs +++ b/komorebi/src/workspace.rs @@ -28,6 +28,8 @@ pub struct Workspace { container_padding: Option, #[serde(skip_serializing)] latest_layout: Vec, + #[serde(skip_serializing)] + resize_dimensions: Vec>, } impl Default for Workspace { @@ -43,6 +45,7 @@ impl Default for Workspace { workspace_padding: Option::from(10), container_padding: Option::from(10), latest_layout: vec![], + resize_dimensions: vec![], } } } @@ -91,6 +94,7 @@ impl Workspace { self.containers().len(), self.container_padding(), self.layout_flip(), + self.resize_dimensions(), ); let windows = self.visible_windows_mut(); @@ -100,6 +104,10 @@ impl Workspace { } } + // Always make sure that the length of the resize dimensions vec is the same as the + // number of layouts / containers. This should never actually truncate as the remove_window + // function takes care of cleaning up resize dimensions when destroying empty containers + self.resize_dimensions_mut().resize(layouts.len(), None); self.set_latest_layout(layouts); } @@ -197,6 +205,7 @@ impl Workspace { .remove_focused_container() .context("there is no container")?; self.containers_mut().push_front(container); + self.resize_dimensions_mut().insert(0, None); self.focus_container(0); Ok(()) @@ -208,6 +217,7 @@ impl Workspace { } fn remove_container_by_idx(&mut self, idx: usize) -> Option { + self.resize_dimensions_mut().remove(idx); self.containers_mut().remove(idx) } @@ -251,6 +261,15 @@ impl Workspace { self.containers_mut() .remove(container_idx) .context("there is no container")?; + + // Whenever a container is empty, we need to remove any resize dimensions for it too + self.resize_dimensions_mut().remove(container_idx); + + // The last container can never be resized to the bottom or the right + if let Some(Some(last)) = self.resize_dimensions_mut().last_mut() { + last.bottom = 0; + last.right = 0; + } } if container_idx != 0 { @@ -302,6 +321,8 @@ impl Workspace { // This is a little messy let adjusted_target_container_index = if container.windows().is_empty() { self.containers_mut().remove(focused_idx); + self.resize_dimensions_mut().remove(focused_idx); + if focused_idx < target_container_idx { target_container_idx - 1 } else { @@ -340,6 +361,7 @@ impl Workspace { if container.windows().is_empty() { self.containers_mut().remove(focused_container_idx); + self.resize_dimensions_mut().remove(focused_container_idx); } else { container.load_focused_window(); } @@ -360,22 +382,19 @@ impl Workspace { let mut container = Container::default(); container.add_window(window); self.containers_mut().insert(focused_idx, container); + self.resize_dimensions_mut().insert(focused_idx, None); Ok(()) } pub fn new_container_for_window(&mut self, window: Window) { let focused_idx = self.focused_container_idx(); - let len = self.containers().len(); let mut container = Container::default(); container.add_window(window); - if focused_idx == len - 1 { - self.containers_mut().resize(len, Container::default()); - } - self.containers_mut().insert(focused_idx + 1, container); + self.resize_dimensions_mut().insert(focused_idx + 1, None); self.focus_container(focused_idx + 1); } @@ -392,10 +411,16 @@ impl Workspace { if container.windows().is_empty() { self.containers_mut().remove(focused_idx); + self.resize_dimensions_mut().remove(focused_idx); } else { container.load_focused_window(); } + if let Some(Some(last)) = self.resize_dimensions_mut().last_mut() { + last.bottom = 0; + last.right = 0; + } + self.floating_windows_mut().push(window); Ok(()) @@ -408,6 +433,10 @@ impl Workspace { .remove(focused_idx) .context("there is not container")?; + // We don't remove any resize adjustments for a monocle, because when this container is + // inevitably reintegrated, it would be weird if it doesn't go back to the dimensions + // it had before + self.monocle_container = Option::from(container); self.monocle_restore_idx = Option::from(focused_idx); @@ -574,4 +603,12 @@ impl Workspace { pub fn set_latest_layout(&mut self, layout: Vec) { self.latest_layout = layout; } + + pub const fn resize_dimensions(&self) -> &Vec> { + &self.resize_dimensions + } + + pub fn resize_dimensions_mut(&mut self) -> &mut Vec> { + &mut self.resize_dimensions + } } diff --git a/komorebic/src/main.rs b/komorebic/src/main.rs index 849d1e07..8216ccfe 100644 --- a/komorebic/src/main.rs +++ b/komorebic/src/main.rs @@ -33,6 +33,7 @@ enum SubCommand { Focus(OperationDirection), Move(OperationDirection), Stack(OperationDirection), + Resize(Resize), Unstack, CycleStack(CycleDirection), MoveToMonitor(Target), @@ -104,6 +105,12 @@ struct FloatTarget { id: String, } +#[derive(Clap)] +struct Resize { + edge: OperationDirection, + sizing: Sizing, +} + pub fn send_message(bytes: &[u8]) -> Result<()> { let mut socket = dirs::home_dir().context("there is no home directory")?; socket.push("komorebi.sock"); @@ -309,6 +316,12 @@ fn main() -> Result<()> { restore_window(HWND(hwnd)); } } + SubCommand::Resize(resize) => { + let bytes = SocketMessage::ResizeWindow(resize.edge, resize.sizing) + .as_bytes() + .unwrap(); + send_message(&*bytes)?; + } } Ok(())