diff --git a/docs/cli/preselect-direction.md b/docs/cli/preselect-direction.md new file mode 100644 index 00000000..6c93735d --- /dev/null +++ b/docs/cli/preselect-direction.md @@ -0,0 +1,16 @@ +# preselect-direction + +``` +Preselect the specified direction for the next window to be spawned on supported layouts + +Usage: komorebic.exe preselect-direction + +Arguments: + + [possible values: left, right, up, down] + +Options: + -h, --help + Print help + +``` diff --git a/komorebi/src/core/mod.rs b/komorebi/src/core/mod.rs index 4092fad2..5cec545c 100644 --- a/komorebi/src/core/mod.rs +++ b/komorebi/src/core/mod.rs @@ -55,6 +55,7 @@ pub enum SocketMessage { // Window / Container Commands FocusWindow(OperationDirection), MoveWindow(OperationDirection), + PreselectDirection(OperationDirection), CycleFocusWindow(CycleDirection), CycleMoveWindow(CycleDirection), StackWindow(OperationDirection), diff --git a/komorebi/src/process_command.rs b/komorebi/src/process_command.rs index 892d18fd..fecc843e 100644 --- a/komorebi/src/process_command.rs +++ b/komorebi/src/process_command.rs @@ -303,6 +303,12 @@ impl WindowManager { } } } + SocketMessage::PreselectDirection(direction) => { + let focused_workspace = self.focused_workspace()?; + if matches!(focused_workspace.layer, WorkspaceLayer::Tiling) { + self.preselect_container_in_direction(direction)?; + } + } SocketMessage::MoveWindow(direction) => { let focused_workspace = self.focused_workspace()?; match focused_workspace.layer { diff --git a/komorebi/src/state.rs b/komorebi/src/state.rs index 700db806..e14223a6 100644 --- a/komorebi/src/state.rs +++ b/komorebi/src/state.rs @@ -272,6 +272,7 @@ impl From<&WindowManager> for State { globals: workspace.globals, wallpaper: workspace.wallpaper.clone(), workspace_config: None, + preselected_container_idx: None, }) .collect::>(); ws.focus(monitor.workspaces.focused_idx()); diff --git a/komorebi/src/window_manager.rs b/komorebi/src/window_manager.rs index a9f2aeef..7732781b 100644 --- a/komorebi/src/window_manager.rs +++ b/komorebi/src/window_manager.rs @@ -2042,6 +2042,53 @@ impl WindowManager { Ok(()) } + #[tracing::instrument(skip(self))] + pub fn preselect_container_in_direction( + &mut self, + direction: OperationDirection, + ) -> eyre::Result<()> { + let workspace = self.focused_workspace_mut()?; + let focused_idx = workspace.focused_container_idx(); + + if matches!(workspace.layout, Layout::Default(DefaultLayout::Grid)) { + tracing::warn!("preselection is not supported on the grid layout"); + return Ok(()); + } + + tracing::info!("preselecting container"); + + let new_idx = + if workspace.maximized_window.is_some() || workspace.monocle_container.is_some() { + None + } else { + workspace.new_idx_for_direction(direction) + }; + + match new_idx { + Some(new_idx) => { + let adjusted_idx = match direction { + OperationDirection::Left | OperationDirection::Up => { + if focused_idx.abs_diff(new_idx) == 1 { + new_idx + 1 + } else { + new_idx + } + } + _ => new_idx, + }; + + workspace.preselect_container_index(adjusted_idx); + } + None => { + tracing::debug!( + "this is not a valid preselection direction from the current position" + ) + } + } + + Ok(()) + } + #[tracing::instrument(skip(self))] pub fn focus_container_in_direction( &mut self, diff --git a/komorebi/src/workspace.rs b/komorebi/src/workspace.rs index fafadeb9..75049454 100644 --- a/komorebi/src/workspace.rs +++ b/komorebi/src/workspace.rs @@ -78,6 +78,8 @@ pub struct Workspace { pub wallpaper: Option, #[serde(skip_serializing_if = "Option::is_none")] pub workspace_config: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub preselected_container_idx: Option, } #[derive(Debug, Default, Copy, Clone, Serialize, Deserialize, PartialEq, Eq)] @@ -129,6 +131,7 @@ impl Default for Workspace { globals: Default::default(), workspace_config: None, wallpaper: None, + preselected_container_idx: None, } } } @@ -976,6 +979,10 @@ impl Workspace { container } + pub fn preselect_container_index(&mut self, insertion_index: usize) { + self.preselected_container_idx = Some(insertion_index); + } + pub fn new_idx_for_direction(&self, direction: OperationDirection) -> Option { let len = NonZeroUsize::new(self.containers().len())?; @@ -1075,7 +1082,13 @@ impl Workspace { } pub fn new_container_for_window(&mut self, window: Window) { - let next_idx = if self.containers().is_empty() { + let next_idx = if let Some(idx) = self.preselected_container_idx { + let next = idx; + + self.preselected_container_idx = None; + + next + } else if self.containers().is_empty() { 0 } else { self.focused_container_idx() + 1 diff --git a/komorebic/src/main.rs b/komorebic/src/main.rs index f8946ca1..060b0204 100644 --- a/komorebic/src/main.rs +++ b/komorebic/src/main.rs @@ -151,6 +151,7 @@ macro_rules! gen_enum_subcommand_args { gen_enum_subcommand_args! { Focus: OperationDirection, Move: OperationDirection, + PreselectDirection: OperationDirection, CycleFocus: CycleDirection, CycleMove: CycleDirection, CycleMoveToWorkspace: CycleDirection, @@ -1095,6 +1096,9 @@ enum SubCommand { /// Move the focused window in the specified direction #[clap(arg_required_else_help = true)] Move(Move), + /// Preselect the specified direction for the next window to be spawned on supported layouts + #[clap(arg_required_else_help = true)] + PreselectDirection(PreselectDirection), /// Minimize the focused window Minimize, /// Close the focused window @@ -2038,6 +2042,9 @@ fn main() -> eyre::Result<()> { SubCommand::Move(arg) => { send_message(&SocketMessage::MoveWindow(arg.operation_direction))?; } + SubCommand::PreselectDirection(arg) => { + send_message(&SocketMessage::PreselectDirection(arg.operation_direction))?; + } SubCommand::CycleFocus(arg) => { send_message(&SocketMessage::CycleFocusWindow(arg.cycle_direction))?; } diff --git a/mkdocs.yml b/mkdocs.yml index ad97d573..990546fd 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -111,6 +111,7 @@ nav: - cli/load-resize.md - cli/focus.md - cli/move.md + - cli/preselect-direction.md - cli/minimize.md - cli/close.md - cli/force-focus.md