window management library
git clone https://git.lucas.co/cce-window-manager.git
feat: overlay-side command + next-visible-focus rule
OverlayLeft/Right join DefaultPolicy via Command::SetOverlayPosition
(new OverlaySide enum). focus::next_visible_focus is the refocus rule
behind close/minimize/unmap: the most recently focused eligible window,
else — a preserved mechanism quirk — the LAST eligible window in window
order, else nothing (focus clears). Eligibility stays the mechanism's
judgment, passed in on FocusCandidate.
Co-Authored-By: Claude Fable 5 <[email protected]>
src/actions.rs | 8 +++++++-
src/api.rs | 9 +++++++++
src/focus.rs | 45 +++++++++++++++++++++++++++++++++++++++++++++
3 files changed, 61 insertions(+), 1 deletion(-)
diff --git a/src/actions.rs b/src/actions.rs
index 0fec670..c29dfae 100644
--- a/src/actions.rs
+++ b/src/actions.rs
@@ -4,7 +4,7 @@
// execute_action arms; an action this policy doesn't claim returns an empty
// vec and the mechanism's remaining legacy arms handle it.
-use crate::api::{Action, ActionCtx, Command, Policy, WindowId};
+use crate::api::{Action, ActionCtx, Command, OverlaySide, Policy, WindowId};
use crate::camera::{self, Camera};
use crate::focus;
use crate::pan;
@@ -35,6 +35,12 @@ impl Policy for DefaultPolicy {
Action::Fullscreen => fullscreen(ctx),
Action::ModeNext => mode_next(ctx),
Action::ModeNextShared => mode_next_shared(ctx),
+ Action::OverlayLeft => {
+ vec![Command::SetOverlayPosition(OverlaySide::Left), Command::Relayout]
+ }
+ Action::OverlayRight => {
+ vec![Command::SetOverlayPosition(OverlaySide::Right), Command::Relayout]
+ }
_ => Vec::new(),
}
}
diff --git a/src/api.rs b/src/api.rs
index 5d9ee0c..5111521 100644
--- a/src/api.rs
+++ b/src/api.rs
@@ -243,6 +243,13 @@ pub struct GridSpec {
pub fade_mode: GridFadeMode,
}
+/// Which side of the viewport the overlay column docks on.
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub enum OverlaySide {
+ Left,
+ Right,
+}
+
/// Shape of a cell's inward edge fade.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum GridFadeMode {
@@ -355,6 +362,8 @@ pub enum Command {
/// Set a window's tiling mode; `locked` pins it against viewport-mode
/// resolution.
SetWindowMode { id: WindowId, mode: TilingMode, locked: bool },
+ /// Dock the overlay column on the given side.
+ SetOverlayPosition(OverlaySide),
/// Reposition a window in virtual space.
MoveWindow { id: WindowId, x: f64, y: f64 },
/// Full re-arrange (the mechanism's `dirty_windowing`).
diff --git a/src/focus.rs b/src/focus.rs
index ccd87b2..e27f4c3 100644
--- a/src/focus.rs
+++ b/src/focus.rs
@@ -86,9 +86,54 @@ pub fn directional_focus(centers: &[(f64, f64)], focused: Option<usize>, dir: Di
best.map(|(i, _)| i)
}
+/// A candidate for the next-visible-focus rule. `eligible` is the
+/// mechanism's judgment (mapped, not minimized, not a status bar or
+/// background surface).
+#[derive(Debug, Clone, Copy)]
+pub struct FocusCandidate {
+ pub id: super::api::WindowId,
+ pub eligible: bool,
+}
+
+/// Which window takes focus when the focused one goes away (close,
+/// minimize, unmap): the most recently focused eligible window, else — a
+/// preserved mechanism quirk — the LAST eligible window in window order,
+/// else nothing (focus clears). `history` is most-recent-first.
+pub fn next_visible_focus(
+ history: &[FocusCandidate],
+ windows: &[FocusCandidate],
+) -> Option<super::api::WindowId> {
+ history
+ .iter()
+ .find(|c| c.eligible)
+ .or_else(|| windows.iter().filter(|c| c.eligible).last())
+ .map(|c| c.id)
+}
+
#[cfg(test)]
mod tests {
use super::*;
+ use crate::api::WindowId;
+ use crate::slotmap::Key;
+
+ fn fc(index: u32, eligible: bool) -> FocusCandidate {
+ FocusCandidate { id: WindowId(Key { generation: 0, index }), eligible }
+ }
+
+ #[test]
+ fn next_visible_prefers_history_then_last_in_window_order() {
+ let history = [fc(3, false), fc(7, true), fc(1, true)];
+ let windows = [fc(7, true), fc(3, false), fc(1, true)];
+ // Most recent eligible history entry wins.
+ assert_eq!(next_visible_focus(&history, &windows), Some(fc(7, true).id));
+ // No eligible history: LAST eligible window in window order.
+ let history = [fc(3, false)];
+ assert_eq!(next_visible_focus(&history, &windows), Some(fc(1, true).id));
+ // Nothing eligible anywhere: focus clears.
+ let none = [fc(1, false)];
+ assert_eq!(next_visible_focus(&history, &none), None);
+ assert_eq!(next_visible_focus(&[], &[]), None);
+ }
// A 2x2-ish layout (y grows downward):
// 0:(100,100) 1:(500,100)