1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
|
//! Focus queries and directional/cyclic focus movement.
//! Split out of the original single `manager.rs` - see `super` (`mod.rs`) for
//! `WindowManager`'s field definitions; everything here is plain `impl WindowManager`
//! methods, unchanged from before the split.
use super::*;
impl WindowManager {
// ---- Focus ----------------------------------------------------------
pub fn focused_window(&self) -> Option<&Window> {
self.focused.and_then(|id| self.windows.get(&id))
}
pub fn focused_id(&self) -> Option<WindowId> {
self.focused
}
pub fn focus_window(&mut self, id: WindowId) {
if self.windows.contains_key(&id) {
self.focused = Some(id);
self.raise_window(id);
}
}
pub(super) fn cycle_focus(&mut self, forward: bool) {
let ids: Vec<WindowId> = self.windows_on_workspace(self.current_workspace).filter(|w| !w.minimized).map(|w| w.id).collect();
if ids.is_empty() {
self.focused = None;
return;
}
let cur_pos = self.focused.and_then(|f| ids.iter().position(|&i| i == f));
let next = match cur_pos {
None => 0,
Some(p) if forward => (p + 1) % ids.len(),
Some(p) => (p + ids.len() - 1) % ids.len(),
};
self.focus_window(ids[next]);
}
pub fn focus_next(&mut self) {
self.cycle_focus(true);
}
pub fn focus_previous(&mut self) {
self.cycle_focus(false);
}
/// Vim-style directional focus: picks the nearest window whose center
/// lies in `dir` relative to the focused window's center, on the same
/// workspace. Returns the newly focused window, if any.
/// Nearest window to the focused one in `dir`, by a distance biased
/// toward the requested axis so a window that's mostly to the left
/// (small |dy|) beats a diagonally-placed one - matching how
/// i3/sway-style directional focus feels.
///
/// Shared by [`Self::focus_direction`] and [`Self::move_window_direction`]
/// so "the window to the left" means the same thing whether you're
/// focusing it or swapping with it.
pub fn neighbour_in(&self, dir: Direction) -> Option<WindowId> {
let (fx, fy, fid) = {
let focused = self.focused_window()?;
let (fx, fy) = focused.geometry.center();
(fx, fy, focused.id)
};
let workspace = self.current_workspace;
let mut best: Option<(WindowId, i64)> = None;
for w in self.windows_on_workspace(workspace).filter(|w| w.id != fid && !w.minimized) {
let (cx, cy) = w.geometry.center();
let (dx, dy) = ((cx - fx) as i64, (cy - fy) as i64);
let matches = match dir {
Direction::Left => dx < 0,
Direction::Right => dx > 0,
Direction::Up => dy < 0,
Direction::Down => dy > 0,
};
if !matches {
continue;
}
let (primary, secondary) = match dir {
Direction::Left | Direction::Right => (dx, dy),
Direction::Up | Direction::Down => (dy, dx),
};
let dist = primary * primary + secondary * secondary * 4;
if best.is_none_or(|(_, d)| dist < d) {
best = Some((w.id, dist));
}
}
best.map(|(id, _)| id)
}
pub fn focus_direction(&mut self, dir: Direction) -> Option<WindowId> {
let target = self.neighbour_in(dir);
if let Some(id) = target {
self.focus_window(id);
}
target
}
/// Moves the focused window in `dir` by swapping places with its
/// neighbour there - the `movewindow l/r/u/d` gesture.
///
/// Swapping (rather than nudging by a fixed step) is what makes this
/// useful in both of srdwm's modes: under tiling it reorders the layout,
/// and in dynamic/floating mode two windows trade positions, which is
/// predictable either way. With no neighbour in that direction the
/// window is pushed to the corresponding edge of its monitor instead, so
/// the key still does something sensible.
pub fn move_window_direction(&mut self, dir: Direction) -> Option<WindowId> {
let focused = self.focused_id()?;
match self.neighbour_in(dir) {
Some(other) => {
let a = self.windows.get(&focused)?.geometry;
let b = self.windows.get(&other)?.geometry;
if let Some(w) = self.windows.get_mut(&focused) {
w.geometry = b;
}
if let Some(w) = self.windows.get_mut(&other) {
w.geometry = a;
}
// Keep stacking order in step so a tiling layout, which
// assigns slots from `order`, actually reflects the swap.
let (ia, ib) = (
self.order.iter().position(|&id| id == focused)?,
self.order.iter().position(|&id| id == other)?,
);
self.order.swap(ia, ib);
Some(other)
}
None => {
let mon = self.windows.get(&focused).and_then(|w| self.monitor_for(w.monitor))?.geometry;
let w = self.windows.get_mut(&focused)?;
match dir {
Direction::Left => w.geometry.x = mon.x,
Direction::Right => w.geometry.x = mon.right() - w.geometry.width as i32,
Direction::Up => w.geometry.y = mon.y,
Direction::Down => w.geometry.y = mon.bottom() - w.geometry.height as i32,
}
None
}
}
}
}
|