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,93 +184,6 @@ impl Layout { dimensions } - - pub fn fibonacci( - &self, - area: &Rect, - count: usize, - layout_flip: Option, - ) -> Vec { - let mut dimensions = vec![]; - - for _ in 0..count { - dimensions.push(Rect::default()) - } - - let mut left = area.left; - let mut top = area.top; - let mut bottom = area.bottom; - let mut right = area.right; - - for i in 0..count { - if i % 2 != 0 { - continue; - } - - let half_width = right / 2; - let half_height = bottom / 2; - - let (main_x, alt_x, new_y, alt_y); - - match layout_flip { - Some(flip) => match flip { - LayoutFlip::Horizontal => { - main_x = left + half_width; - alt_x = left; - - new_y = top + half_height; - alt_y = 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; - } - }, - 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 - } } impl Layout { @@ -185,9 +204,193 @@ impl Layout { } } -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; +fn calculate_resize_adjustments(resize_dimensions: &[Option]) -> Vec> { + let mut resize_adjustments = resize_dimensions.to_vec(); + + // 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 + }; + + 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, + }); + } + } + } + + if let Some(rr) = resize_adjustments[i].as_mut() { + rr.left = 0; + } + } + + if resize_ref.top != 0 { + let range = if i == 1 { + 0..1 + } else if i & 1 == 0 { + i - 1..i + } else { + i - 2..i + }; + + 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, + }); + } + } + } + + if let Some(Some(resize)) = resize_adjustments.get_mut(i) { + resize.top = 0; + } + } + } + } + } + + 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 +} + +fn recursive_fibonacci( + idx: usize, + count: usize, + area: &Rect, + layout_flip: Option, + resize_adjustments: Vec>, +) -> Vec { + let mut a = *area; + + 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; + } + } + + 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())?; - match workspace.container_idx_from_current_point() { - Some(target_idx) => { - workspace.swap_containers(focused_idx, target_idx); - self.update_focused_workspace(false)?; + // 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)?, } - 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)); + } + + if resize.top != 0 { + ops.push(resize_op!(resize.top, >, OperationDirection::Up)); + } + + 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(())