feat(wm): add visual feedback for preselection

This commit adds visual feedback in the form of a ghost tile for
preselections made by the preselect-direction command.

A container with the id "PRESELECT" will be added to the workspace, and
replaced when the next manage-able window is spawned.

A new command, cancel-preselect, has been added to remove both the
preselection index and the ghost tile if the user changes their mind.
This commit is contained in:
LGUG2Z
2025-11-02 10:58:43 -08:00
parent 3491dc7590
commit d0ae92ca3a
8 changed files with 62 additions and 7 deletions

View File

@@ -0,0 +1,12 @@
# cancel-preselect
```
Cancel a workspace preselect set by the preselect-direction command, if one exists
Usage: komorebic.exe cancel-preselect
Options:
-h, --help
Print help
```

View File

@@ -41,6 +41,18 @@ impl Lockable for Container {
}
impl Container {
pub fn preselect() -> Self {
Self {
id: "PRESELECT".to_string(),
locked: false,
windows: Default::default(),
}
}
pub fn is_preselect(&self) -> bool {
self.id == "PRESELECT"
}
pub fn hide(&self, omit: Option<isize>) {
for window in self.windows().iter().rev() {
let mut should_hide = omit.is_none();

View File

@@ -56,6 +56,7 @@ pub enum SocketMessage {
FocusWindow(OperationDirection),
MoveWindow(OperationDirection),
PreselectDirection(OperationDirection),
CancelPreselect,
CycleFocusWindow(CycleDirection),
CycleMoveWindow(CycleDirection),
StackWindow(OperationDirection),

View File

@@ -305,9 +305,25 @@ impl WindowManager {
}
SocketMessage::PreselectDirection(direction) => {
let focused_workspace = self.focused_workspace()?;
if matches!(focused_workspace.layer, WorkspaceLayer::Tiling) {
let mut update = false;
if focused_workspace.preselected_container_idx.is_some() {
tracing::warn!(
"ignoring command as this workspace already has a direction preselect set"
);
} else if matches!(focused_workspace.layer, WorkspaceLayer::Tiling) {
self.preselect_container_in_direction(direction)?;
update = true;
}
if update {
self.focused_workspace_mut()?.update()?;
}
}
SocketMessage::CancelPreselect => {
let focused_workspace = self.focused_workspace_mut()?;
focused_workspace.cancel_preselect();
focused_workspace.update()?;
}
SocketMessage::MoveWindow(direction) => {
let focused_workspace = self.focused_workspace()?;

View File

@@ -2077,7 +2077,7 @@ impl WindowManager {
_ => new_idx,
};
workspace.preselect_container_index(adjusted_idx);
workspace.preselect_container_idx(adjusted_idx);
}
None => {
tracing::debug!(

View File

@@ -449,7 +449,8 @@ impl Workspace {
}
// make sure we are never holding on to empty containers
self.containers_mut().retain(|c| !c.windows().is_empty());
self.containers_mut()
.retain(|c| c.is_preselect() || !c.windows().is_empty());
let container_padding = self
.container_padding
@@ -979,8 +980,16 @@ impl Workspace {
container
}
pub fn preselect_container_index(&mut self, insertion_index: usize) {
self.preselected_container_idx = Some(insertion_index);
pub fn preselect_container_idx(&mut self, insertion_idx: usize) {
self.preselected_container_idx = Some(insertion_idx);
self.insert_container_at_idx(insertion_idx, Container::preselect());
}
pub fn cancel_preselect(&mut self) {
if let Some(idx) = self.preselected_container_idx {
self.containers_mut().remove_respecting_locks(idx);
self.preselected_container_idx = None;
}
}
pub fn new_idx_for_direction(&self, direction: OperationDirection) -> Option<usize> {
@@ -1084,9 +1093,8 @@ impl Workspace {
pub fn new_container_for_window(&mut self, window: Window) {
let next_idx = if let Some(idx) = self.preselected_container_idx {
let next = idx;
self.preselected_container_idx = None;
self.remove_container_by_idx(next);
next
} else if self.containers().is_empty() {
0

View File

@@ -1099,6 +1099,8 @@ enum SubCommand {
/// Preselect the specified direction for the next window to be spawned on supported layouts
#[clap(arg_required_else_help = true)]
PreselectDirection(PreselectDirection),
/// Cancel a workspace preselect set by the preselect-direction command, if one exists
CancelPreselect,
/// Minimize the focused window
Minimize,
/// Close the focused window
@@ -2045,6 +2047,9 @@ fn main() -> eyre::Result<()> {
SubCommand::PreselectDirection(arg) => {
send_message(&SocketMessage::PreselectDirection(arg.operation_direction))?;
}
SubCommand::CancelPreselect => {
send_message(&SocketMessage::CancelPreselect)?;
}
SubCommand::CycleFocus(arg) => {
send_message(&SocketMessage::CycleFocusWindow(arg.cycle_direction))?;
}

View File

@@ -112,6 +112,7 @@ nav:
- cli/focus.md
- cli/move.md
- cli/preselect-direction.md
- cli/cancel-preselect.md
- cli/minimize.md
- cli/close.md
- cli/force-focus.md