Commit af095d8ce0fc
tarball5 files changed+102-14
@@ -145,6 +145,7 @@ selection_access = "focused"
145145
"Mod+o" = "flip"146146
"Mod+f" = "fullscreen"147147
"Mod+Shift+space" = "float"148
+"Mod+space" = "focus-layer"148149
"Mod+s" = "sticky"149150
150151
"Mod+comma" = "focus-output left"@@ -47,6 +47,8 @@ pub enum Command {
4747
Focus {4848
dir: Dir,4949
},50
+ /// Focuses the latest window on the other layer, floating or tiled.51
+ FocusLayer,5052
Move {5153
dir: Dir,5254
},@@ -173,6 +175,7 @@ impl Command {
173175
"fullscreen" => none(Command::Fullscreen)?,174176
"sticky" => none(Command::Sticky)?,175177
"focus" => Command::Focus { dir: dir()? },178
+ "focus-layer" => none(Command::FocusLayer)?,176179
"move" => Command::Move { dir: dir()? },177180
"move-container" => Command::MoveContainer { dir: dir()? },178181
"focus-output" => Command::FocusOutput { dir: dir()? },@@ -558,26 +558,24 @@ impl Layout {
558558
.map(|w| self.modal_leaf(w))559559
}560560
561
- /// Focuses the nearest visible window in `dir`, crossing to the next562
- /// output when there is none on this one.561
+ /// Focuses the nearest visible window in `dir` on the focused window's562
+ /// layer, tiled or floating, crossing to the next output when there is563
+ /// none on this one.563564
pub fn focus_dir(&mut self, dir: Dir) -> Option<WindowId> {564565
let o = self.active?;565566
let current = self.focused_on(o);566567
let from = current.and_then(|f| self.rect_of(f));567568
let target = from.and_then(|from| {568569
let arr = self.arrange(o);569
- let reachable = |w| {570
- arr.fullscreen571
- .is_none_or(|f| w == f || arr.over.contains(&w))572
- };573
- let candidates = arr574
- .tiled575
- .iter()576
- .filter(|p| p.visible && !self.eclipsed(p.window))577
- .map(|p| (p.window, p.rect))578
- .chain(arr.floating.iter().copied())579
- .filter(|&(w, _)| Some(self.modal_leaf(w)) != current && reachable(w));580
- nearest(from, dir, candidates)570
+ let floating = current.is_some_and(|f| arr.floating.iter().any(|p| p.0 == f));571
+ let candidates = self572
+ .layer(&arr, floating)573
+ .filter(|&(w, _)| Some(self.modal_leaf(w)) != current);574
+ if floating {575
+ nearest_centre(from, dir, candidates)576
+ } else {577
+ nearest(from, dir, candidates)578
+ }581579
});582580
if let Some(w) = target {583581
return self.focus(w);@@ -591,6 +589,39 @@ impl Layout {
591589
w592590
}593591
592
+ /// Focuses the most recently focused window on the other layer, floating593
+ /// when a tiled window has focus and tiled otherwise.594
+ pub fn focus_layer(&mut self) -> Option<WindowId> {595
+ let o = self.active?;596
+ let arr = self.arrange(o);597
+ let floating = self598
+ .focused_on(o)599
+ .is_some_and(|f| arr.floating.iter().any(|p| p.0 == f));600
+ let w = self601
+ .layer(&arr, !floating)602
+ .map(|(w, _)| w)603
+ .max_by_key(|&w| self.history.iter().position(|&h| h == w))?;604
+ self.focus(w)605
+ }606
+607
+ /// The windows focus can reach on one layer of an arrangement.608
+ fn layer<'a>(609
+ &'a self,610
+ arr: &'a Arrangement,611
+ floating: bool,612
+ ) -> impl Iterator<Item = (WindowId, Rect)> + 'a {613
+ let tiled = arr614
+ .tiled615
+ .iter()616
+ .filter(move |p| !floating && p.visible && !self.eclipsed(p.window))617
+ .map(|p| (p.window, p.rect));618
+ let floats = arr.floating.iter().copied().filter(move |_| floating);619
+ tiled.chain(floats).filter(|&(w, _)| {620
+ arr.fullscreen621
+ .is_none_or(|f| w == f || arr.over.contains(&w))622
+ })623
+ }624
+594625
pub fn output_towards(&self, from: OutputId, dir: Dir) -> Option<OutputId> {595626
let here = self.outputs.get(from)?.area;596627
let others = self@@ -1091,6 +1122,32 @@ fn nearest<K: Copy>(
10911122
.map(|(k, _)| k)10921123
}10931124
1125
+/// The candidate whose centre is nearest `from`'s within 45 degrees of1126
+/// `dir`. Floating windows overlap freely, so their edges say little about1127
+/// which way one lies from another.1128
+fn nearest_centre<K: Copy>(1129
+ from: Rect,1130
+ dir: Dir,1131
+ candidates: impl Iterator<Item = (K, Rect)>,1132
+) -> Option<K> {1133
+ candidates1134
+ .filter_map(|(k, r)| {1135
+ let (dx, dy) = (1136
+ r.center_x() - from.center_x(),1137
+ r.center_y() - from.center_y(),1138
+ );1139
+ let (along, across) = match dir {1140
+ Dir::Right => (dx, dy),1141
+ Dir::Left => (-dx, dy),1142
+ Dir::Down => (dy, dx),1143
+ Dir::Up => (-dy, dx),1144
+ };1145
+ (along > 0.0 && along >= across.abs()).then_some((k, along.hypot(across)))1146
+ })1147
+ .min_by(|a, b| a.1.total_cmp(&b.1))1148
+ .map(|(k, _)| k)1149
+}1150
+10941151
fn span_overlap(a: f64, al: f64, b: f64, bl: f64) -> f64 {10951152
(a + al).min(b + bl) - a.max(b)10961153
}@@ -979,3 +979,27 @@ fn new_floats_land_inside_the_bars() {
979979
let r = l.window(plain).and_then(|w| w.float_rect).unwrap();980980
assert_eq!(r.y + r.h / 2.0, usable.y + usable.h / 2.0);981981
}982
+983
+/// Overlapping floats have no clear edge between them, so edge-based focus984
+/// could not step from one to another it overlaps.985
+#[test]986
+fn floating_focus_steps_by_centres_and_stays_on_its_layer() {987
+ let mut l = Layout::default();988
+ let o = l.add_output("a", AREA);989
+ let tiled = l.add_window(Some(o), None, None);990
+ let a = l.add_window(Some(o), None, Some(Rect::new(100.0, 100.0, 400.0, 300.0)));991
+ let b = l.add_window(Some(o), None, Some(Rect::new(350.0, 120.0, 300.0, 200.0)));992
+ let c = l.add_window(Some(o), None, Some(Rect::new(250.0, 350.0, 200.0, 200.0)));993
+ l.focus(a);994
+ assert_eq!(l.focus_dir(Dir::Right), Some(b));995
+ l.focus(a);996
+ assert_eq!(l.focus_dir(Dir::Down), Some(c));997
+ assert_eq!(998
+ l.focus_dir(Dir::Down),999
+ None,1000
+ "no float below, and no other output"1001
+ );1002
+ assert_eq!(l.focus_layer(), Some(tiled));1003
+ assert_eq!(l.focus_dir(Dir::Right), None);1004
+ assert_eq!(l.focus_layer(), Some(c));1005
+}@@ -80,6 +80,9 @@ impl State {
8080
Command::Focus { dir: d } => {8181
self.wm.layout.focus_dir(d);8282
}83
+ Command::FocusLayer => {84
+ self.wm.layout.focus_layer();85
+ }8386
Command::Move { dir: d } if merge => {8487
self.wm.layout.merge_window(d);8588
}