From 7ed6df511f3583be259e73103a56ca38547e4f68 Mon Sep 17 00:00:00 2001 From: LGUG2Z Date: Mon, 11 Oct 2021 13:16:40 -0700 Subject: [PATCH] feat(wm): allow focusing and moving by cycle direction This commit adds focusing and moving window containers using cycle directions when the layout has not been flipped on any axis. This naive implementation simply increments or decrements the index number in the desired direction and does not accomodate for axis flipping. When the current index number is either at the beginning or the end of the collection, further operations will loop around. Ideally I would like an implementation which works coherently on any LayoutFlip state, but this can be implemented at a later date if specifically requested in the future. re #47 --- Cargo.lock | 40 ++++++++++++------------ komorebi-core/src/cycle_direction.rs | 11 ++++--- komorebi-core/src/lib.rs | 2 ++ komorebi-core/src/operation_direction.rs | 8 ++--- komorebi/src/process_command.rs | 6 ++++ komorebi/src/window_manager.rs | 38 ++++++++++++++++++++-- komorebi/src/workspace.rs | 7 +++++ komorebic/src/main.rs | 14 +++++++++ 8 files changed, 95 insertions(+), 31 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ba002955..19637e89 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -73,9 +73,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "cc" -version = "1.0.70" +version = "1.0.71" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d26a6ce4b6a484fa3edb70f7efa6fc430fd2b87285fe8b84304fd0936faa0dc0" +checksum = "79c2681d6594606957bbb8631c4b90a7fcaaa72cdb714743a437b156d6a7eedd" [[package]] name = "cfg-if" @@ -249,9 +249,9 @@ dependencies = [ [[package]] name = "ctrlc" -version = "3.2.0" +version = "3.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "377c9b002a72a0b2c1a18c62e2f3864bdfea4a015e3683a96e24aa45dd6c02d1" +checksum = "a19c6cedffdc8c03a3346d723eb20bd85a13362bb96dc2ac000842c6381ec7bf" dependencies = [ "nix", "winapi 0.3.9", @@ -701,9 +701,9 @@ dependencies = [ [[package]] name = "nix" -version = "0.22.0" +version = "0.23.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf1e25ee6b412c2a1e3fcb6a4499a5c1bfe7f43e014bdce9a6b6666e5aa2d187" +checksum = "f305c2c2e4c39a82f7bf0bf65fb557f9070ce06781d4f2454295cc34b1c43188" dependencies = [ "bitflags", "cc", @@ -892,9 +892,9 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.9" +version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3d0b9745dc2debf507c8422de05d7226cc1f0644216dfdfead988f9b1ab32a7" +checksum = "38bc8cc6a5f2e3655e0899c1b848643b2562f853f114bfec7be120678e3ace05" dependencies = [ "proc-macro2", ] @@ -1128,9 +1128,9 @@ checksum = "c307a32c1c5c437f38c7fd45d753050587732ba8628319fbdf12a7e289ccc590" [[package]] name = "smallvec" -version = "1.6.1" +version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe0f37c9e8f3c5a4a66ad655a93c74daac4ad00c441533bf5c6e7990bb42604e" +checksum = "1ecab6c735a6bb4139c0caafd0cc3635748bbb3acf4550e8138122099251f309" [[package]] name = "strsim" @@ -1161,9 +1161,9 @@ dependencies = [ [[package]] name = "syn" -version = "1.0.77" +version = "1.0.80" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5239bc68e0fef57495900cfea4e8dc75596d9a319d7e16b1e0a440d24e6fe0a0" +checksum = "d010a1623fbd906d51d650a9916aaefc05ffa0e4053ff7fe601167f3e715d194" dependencies = [ "proc-macro2", "quote", @@ -1245,9 +1245,9 @@ dependencies = [ [[package]] name = "tracing" -version = "0.1.28" +version = "0.1.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84f96e095c0c82419687c20ddf5cb3eadb61f4e1405923c9dc8e53a1adacbda8" +checksum = "375a639232caf30edfc78e8d89b2d4c375515393e7af7e16f01cd96917fb2105" dependencies = [ "cfg-if 1.0.0", "pin-project-lite", @@ -1268,9 +1268,9 @@ dependencies = [ [[package]] name = "tracing-attributes" -version = "0.1.16" +version = "0.1.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "98863d0dd09fa59a1b79c6750ad80dbda6b75f4e71c437a6a1a8cb91a8bcbd77" +checksum = "f4f480b8f81512e825f337ad51e94c1eb5d3bbdf2b363dcd01e2b19a9ffe3f8e" dependencies = [ "proc-macro2", "quote", @@ -1279,9 +1279,9 @@ dependencies = [ [[package]] name = "tracing-core" -version = "0.1.20" +version = "0.1.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46125608c26121c81b0c6d693eab5a420e416da7e43c426d2e8f7df8da8a3acf" +checksum = "1f4ed65637b8390770814083d20756f87bfa2c21bf2f110babdc5438351746e4" dependencies = [ "lazy_static", ] @@ -1319,9 +1319,9 @@ dependencies = [ [[package]] name = "tracing-subscriber" -version = "0.2.24" +version = "0.2.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fdd0568dbfe3baf7048b7908d2b32bca0d81cd56bec6d2a8f894b01d74f86be3" +checksum = "0e0d2eaa99c3c2e41547cfa109e910a68ea03823cccad4a0525dcbc9b01e8c71" dependencies = [ "ansi_term", "chrono", diff --git a/komorebi-core/src/cycle_direction.rs b/komorebi-core/src/cycle_direction.rs index fafa76b2..9b448ec1 100644 --- a/komorebi-core/src/cycle_direction.rs +++ b/komorebi-core/src/cycle_direction.rs @@ -1,6 +1,7 @@ use clap::ArgEnum; use serde::Deserialize; use serde::Serialize; +use std::num::NonZeroUsize; use strum::Display; use strum::EnumString; @@ -13,17 +14,17 @@ pub enum CycleDirection { impl CycleDirection { #[must_use] - pub const fn next_idx(&self, idx: usize, len: usize) -> usize { + pub const fn next_idx(&self, idx: usize, len: NonZeroUsize) -> usize { match self { - CycleDirection::Previous => { + Self::Previous => { if idx == 0 { - len - 1 + len.get() - 1 } else { idx - 1 } } - CycleDirection::Next => { - if idx == len - 1 { + Self::Next => { + if idx == len.get() - 1 { 0 } else { idx + 1 diff --git a/komorebi-core/src/lib.rs b/komorebi-core/src/lib.rs index 32968226..2ee01c8d 100644 --- a/komorebi-core/src/lib.rs +++ b/komorebi-core/src/lib.rs @@ -27,6 +27,8 @@ pub enum SocketMessage { // Window / Container Commands FocusWindow(OperationDirection), MoveWindow(OperationDirection), + CycleFocusWindow(CycleDirection), + CycleMoveWindow(CycleDirection), StackWindow(OperationDirection), ResizeWindow(OperationDirection, Sizing), UnstackWindow, diff --git a/komorebi-core/src/operation_direction.rs b/komorebi-core/src/operation_direction.rs index 61bec052..c4f3826b 100644 --- a/komorebi-core/src/operation_direction.rs +++ b/komorebi-core/src/operation_direction.rs @@ -57,22 +57,22 @@ impl OperationDirection { len: usize, ) -> bool { match Self::flip_direction(self, layout_flip) { - OperationDirection::Up => match layout { + Self::Up => match layout { Layout::BSP => len > 2 && idx != 0 && idx != 1, Layout::Columns => false, Layout::Rows => idx != 0, }, - OperationDirection::Down => match layout { + Self::Down => match layout { Layout::BSP => len > 2 && idx != len - 1 && idx % 2 != 0, Layout::Columns => false, Layout::Rows => idx != len - 1, }, - OperationDirection::Left => match layout { + Self::Left => match layout { Layout::BSP => len > 1 && idx != 0, Layout::Columns => idx != 0, Layout::Rows => false, }, - OperationDirection::Right => match layout { + Self::Right => match layout { Layout::BSP => len > 1 && idx % 2 == 0 && idx != len - 1, Layout::Columns => idx != len - 1, Layout::Rows => false, diff --git a/komorebi/src/process_command.rs b/komorebi/src/process_command.rs index 8df7d627..ab9cfd21 100644 --- a/komorebi/src/process_command.rs +++ b/komorebi/src/process_command.rs @@ -66,6 +66,12 @@ impl WindowManager { SocketMessage::MoveWindow(direction) => { self.move_container_in_direction(direction)?; } + SocketMessage::CycleFocusWindow(direction) => { + self.focus_container_in_cycle_direction(direction)?; + } + SocketMessage::CycleMoveWindow(direction) => { + self.move_container_in_cycle_direction(direction)?; + } SocketMessage::StackWindow(direction) => self.add_window_to_container(direction)?, SocketMessage::UnstackWindow => self.remove_window_from_container()?, SocketMessage::CycleStack(direction) => { diff --git a/komorebi/src/window_manager.rs b/komorebi/src/window_manager.rs index 38a81b32..251057e5 100644 --- a/komorebi/src/window_manager.rs +++ b/komorebi/src/window_manager.rs @@ -739,18 +739,52 @@ impl WindowManager { self.update_focused_workspace(true) } + #[tracing::instrument(skip(self))] + pub fn focus_container_in_cycle_direction(&mut self, direction: CycleDirection) -> Result<()> { + tracing::info!("focusing container"); + let workspace = self.focused_workspace_mut()?; + + let new_idx = workspace + .new_idx_for_cycle_direction(direction) + .ok_or_else(|| anyhow!("this is not a valid direction from the current position"))?; + + workspace.focus_container(new_idx); + self.focused_window_mut()?.focus()?; + + Ok(()) + } + + #[tracing::instrument(skip(self))] + pub fn move_container_in_cycle_direction(&mut self, direction: CycleDirection) -> Result<()> { + tracing::info!("moving container"); + + let workspace = self.focused_workspace_mut()?; + + let current_idx = workspace.focused_container_idx(); + let new_idx = workspace + .new_idx_for_cycle_direction(direction) + .ok_or_else(|| anyhow!("this is not a valid direction from the current position"))?; + + workspace.swap_containers(current_idx, new_idx); + workspace.focus_container(new_idx); + self.update_focused_workspace(true) + } + #[tracing::instrument(skip(self))] pub fn cycle_container_window_in_direction(&mut self, direction: CycleDirection) -> Result<()> { tracing::info!("cycling container windows"); let container = self.focused_container_mut()?; - if container.windows().len() == 1 { + let len = NonZeroUsize::new(container.windows().len()) + .ok_or_else(|| anyhow!("there must be at least one window in a container"))?; + + if len.get() == 1 { return Err(anyhow!("there is only one window in this container")); } let current_idx = container.focused_window_idx(); - let next_idx = direction.next_idx(current_idx, container.windows().len()); + let next_idx = direction.next_idx(current_idx, len); container.focus_window(next_idx); container.load_focused_window(); diff --git a/komorebi/src/workspace.rs b/komorebi/src/workspace.rs index 4b6b559f..83c005c2 100644 --- a/komorebi/src/workspace.rs +++ b/komorebi/src/workspace.rs @@ -9,6 +9,7 @@ use getset::MutGetters; use getset::Setters; use serde::Serialize; +use komorebi_core::CycleDirection; use komorebi_core::Flip; use komorebi_core::Layout; use komorebi_core::OperationDirection; @@ -457,6 +458,12 @@ impl Workspace { None } } + pub fn new_idx_for_cycle_direction(&self, direction: CycleDirection) -> Option { + Option::from(direction.next_idx( + self.focused_container_idx(), + NonZeroUsize::new(self.containers().len())?, + )) + } pub fn move_window_to_container(&mut self, target_container_idx: usize) -> Result<()> { let focused_idx = self.focused_container_idx(); diff --git a/komorebic/src/main.rs b/komorebic/src/main.rs index 68dff9db..122467f7 100644 --- a/komorebic/src/main.rs +++ b/komorebic/src/main.rs @@ -79,6 +79,8 @@ macro_rules! gen_enum_subcommand_args { gen_enum_subcommand_args! { Focus: OperationDirection, Move: OperationDirection, + CycleFocus: CycleDirection, + CycleMove: CycleDirection, Stack: OperationDirection, CycleStack: CycleDirection, FlipLayout: Flip, @@ -316,6 +318,12 @@ enum SubCommand { /// Move the focused window in the specified direction #[clap(setting = AppSettings::ArgRequiredElseHelp)] Move(Move), + /// Change focus to the window in the specified cycle direction + #[clap(setting = AppSettings::ArgRequiredElseHelp)] + CycleFocus(CycleFocus), + /// Move the focused window in the specified cycle direction + #[clap(setting = AppSettings::ArgRequiredElseHelp)] + CycleMove(CycleMove), /// Stack the focused window in the specified direction #[clap(setting = AppSettings::ArgRequiredElseHelp)] Stack(Stack), @@ -493,6 +501,12 @@ fn main() -> Result<()> { SubCommand::Move(arg) => { send_message(&*SocketMessage::MoveWindow(arg.operation_direction).as_bytes()?)?; } + SubCommand::CycleFocus(arg) => { + send_message(&*SocketMessage::CycleFocusWindow(arg.cycle_direction).as_bytes()?)?; + } + SubCommand::CycleMove(arg) => { + send_message(&*SocketMessage::CycleMoveWindow(arg.cycle_direction).as_bytes()?)?; + } SubCommand::MoveToMonitor(arg) => { send_message(&*SocketMessage::MoveContainerToMonitorNumber(arg.target).as_bytes()?)?; }