Commit 21e8e8e5aa3d
tarball4 files changed+129-33
@@ -624,12 +624,12 @@ impl Layout {
624624
moved625625
}626626
627
- pub fn move_container(&mut self, dir: Dir) -> bool {627
+ pub fn move_container(&mut self, dir: Dir, merge: bool) -> bool {628628
let Some((w, o)) = self.focused_tiled() else {629629
return false;630630
};631631
let mut tree = std::mem::take(&mut self.outputs[o].tree);632
- let moved = tree.move_container(w, dir, self.vis_on(o));632
+ let moved = tree.move_container(w, dir, merge, self.vis_on(o));633633
self.outputs[o].tree = tree;634634
moved635635
}@@ -9,7 +9,7 @@ enum Op {
99
Remove(u8),1010
Focus(u8),1111
Move(Dir),12
- Block(Dir),12
+ Block(Dir, bool),1313
Merge(Dir),1414
View(u8),1515
ToggleView(u8),@@ -39,7 +39,7 @@ fn op() -> impl Strategy<Value = Op> {
3939
1 => any::<u8>().prop_map(Op::Remove),4040
3 => any::<u8>().prop_map(Op::Focus),4141
4 => dir().prop_map(Op::Move),42
- 2 => dir().prop_map(Op::Block),42
+ 2 => (dir(), any::<bool>()).prop_map(|(d, m)| Op::Block(d, m)),4343
3 => dir().prop_map(Op::Merge),4444
1 => (0u8..4).prop_map(Op::View),4545
2 => (0u8..4).prop_map(Op::ToggleView),@@ -82,8 +82,8 @@ fn apply(l: &mut Layout, tags: &[TagId], op: &Op) {
8282
Op::Move(d) => {8383
l.move_window(d);8484
}85
- Op::Block(d) => {86
- l.move_container(d);85
+ Op::Block(d, m) => {86
+ l.move_container(d, m);8787
}8888
Op::Merge(d) => {8989
l.merge_window(d);@@ -200,7 +200,7 @@ proptest! {
200200
l.add_output("b", Rect::new(1000.0, 0.0, 800.0, 600.0));201201
let tags = [l.homeless(), l.tag("1"), l.tag("2"), l.tag("3")];202202
for op in &ops {203
- let moving = matches!(op, Op::Move(_) | Op::Merge(_) | Op::Block(_));203
+ let moving = matches!(op, Op::Move(_) | Op::Merge(_) | Op::Block(..));204204
let shown_before = l.focused().filter(|&w| tiled_area(&l, w) > 1.0);205205
apply(&mut l, &tags, op);206206
l.check();@@ -318,6 +318,38 @@ fn merging_steps_out_of_a_stack_before_joining_another() {
318318
assert!(rect(a).bottom() <= rect(b).y && rect(b).y == rect(c).y);319319
}320320
321
+/// An extended window's group moves whole, even into a container running322
+/// its own way, where it would otherwise dissolve and show what it covers.323
+#[test]324
+fn extended_windows_move_their_group() {325
+ let (a, b, c) = (1, 2, 3);326
+ let all = |_| true;327
+ let mut t: Tree<u32> = Tree::default();328
+ t.insert_row(a);329
+ t.insert_after(b, a);330
+ t.insert_after(c, b);331
+ assert!(t.merge_window(b, Dir::Left, all));332
+ assert!(t.toggle_expand(b, 0, 1, all));333
+ let rects = |t: &Tree<u32>| {334
+ t.check();335
+ assert!(t.grouped(b, all) && t.eclipsed(a, all));336
+ let placed = t.arrange(AREA, 0.0, all);337
+ move |w| placed.iter().find(|p: &&(u32, Rect)| p.0 == w).unwrap().1338
+ };339
+340
+ assert!(t.move_window(b, Dir::Right, all));341
+ let rect = rects(&t);342
+ assert!(rect(c).x < rect(b).x);343
+344
+ assert!(t.move_window(b, Dir::Down, all));345
+ let rect = rects(&t);346
+ assert!(rect(c).bottom() <= rect(b).y && rect(b).w == AREA.w);347
+348
+ assert!(t.merge_window(b, Dir::Up, all));349
+ let rect = rects(&t);350
+ assert!(rect(c).right() <= rect(b).x && rect(b).y == rect(c).y);351
+}352
+321353
/// A window alone on screen in a container of hidden ones expands among the322354
/// windows it is shown beside, not inside the container nobody can see.323355
#[test]@@ -465,7 +497,7 @@ fn moves_are_seen_or_undone() {
465497
let moved = match kind {466498
1 => t.merge_window(w, d, vis),467499
2 => t.move_window(w, d, vis),468
- 3 => t.move_container(w, d, vis),500
+ 3 => t.move_container(w, d, false, vis),469501
_ => continue,470502
};471503
t.check();@@ -222,52 +222,98 @@ impl<W: Copy + Eq + Hash> Tree<W> {
222222
223223
/// Moves a window one step in `dir`. Leaves swap slots with the next224224
/// visible sibling, enter a sibling container, or step out beside their225
- /// container. Hidden windows never move.225
+ /// container. Hidden windows never move, and an extended window moves226
+ /// its group as a rigid block.226227
pub fn move_window(&mut self, w: W, dir: Dir, vis: impl Fn(W) -> bool) -> bool {227228
let Some(&n) = self.leaves.get(&w).filter(|_| vis(w)) else {228229
return false;229230
};231
+ let block = self.group(w, &vis).unwrap_or(n);230232
self.until_seen(&vis, |t| {231
- let moved = t.move_node(n, dir, &vis, true);233
+ let moved = t.move_node(block, dir, &vis, block == n);232234
t.reveal(n, moved)233235
})234236
}235237
236
- /// Moves the container holding `w` as a rigid block. A window sitting237
- /// directly in the root is its own block.238
- pub fn move_container(&mut self, w: W, dir: Dir, vis: impl Fn(W) -> bool) -> bool {238
+ /// Moves the group or container holding `w` as a block, merging it into239
+ /// the next lane in `dir` like `merge_window` when `merge` is set. A240
+ /// window sitting directly in the root is its own block.241
+ pub fn move_container(&mut self, w: W, dir: Dir, merge: bool, vis: impl Fn(W) -> bool) -> bool {239242
let Some(&n) = self.leaves.get(&w).filter(|_| vis(w)) else {240243
return false;241244
};245
+ let group = self.group(w, &vis);242246
self.until_seen(&vis, |t| {243
- let block = t.nodes[n]244
- .parent245
- .filter(|&p| Some(p) != t.root)246
- .unwrap_or(n);247
- let moved = t.move_node(block, dir, &vis, false);248
- t.reveal(n, moved)247
+ let block = group.unwrap_or_else(|| {248
+ t.nodes[n]249
+ .parent250
+ .filter(|&p| Some(p) != t.root)251
+ .unwrap_or(n)252
+ });253
+ if merge {254
+ t.merge_node(n, block, dir, &vis)255
+ } else {256
+ let moved = t.move_node(block, dir, &vis, false);257
+ t.reveal(n, moved)258
+ }249259
})250260
}251261
252262
/// Moves a window to the end of the next visible lane in `dir` among its253263
/// siblings. A lone window there becomes a stack with it. A window with no254264
/// sibling lane that way leaves its stack as `move_window` would, so it255
- /// takes one press to step out and another to merge.265
+ /// takes one press to step out and another to merge. An extended window266
+ /// takes its group along.256267
pub fn merge_window(&mut self, w: W, dir: Dir, vis: impl Fn(W) -> bool) -> bool {257268
let Some(&n) = self.leaves.get(&w).filter(|_| vis(w)) else {258269
return false;259270
};260
- self.until_seen(&vis, |t| {261
- if let Some((p, i)) = t.position(n)262
- && t.axis(p) == Some(dir.axis())263
- && let Some(j) = t.next_shown(p, i, dir, &vis)271
+ let block = self.group(w, &vis).unwrap_or(n);272
+ self.until_seen(&vis, |t| t.merge_node(n, block, dir, &vis))273
+ }274
+275
+ fn merge_node(&mut self, leaf: NodeId, n: NodeId, dir: Dir, vis: &impl Fn(W) -> bool) -> bool {276
+ if let Some((p, i)) = self.position(n)277
+ && self.axis(p) == Some(dir.axis())278
+ && let Some(j) = self.next_shown(p, i, dir, vis)279
+ {280
+ self.join_lane(n, p, j);281
+ return self.reveal(leaf, true);282
+ }283
+ let moved = self.move_node(n, dir, vis, true);284
+ self.reveal(leaf, moved)285
+ }286
+287
+ /// The outermost container `w` is extended in, the block it moves as.288
+ fn group(&self, w: W, vis: &impl Fn(W) -> bool) -> Option<NodeId> {289
+ self.shown_path(w, vis)290
+ .into_iter()291
+ .take_while(|&(p, c)| self.expanded(p) == Some(c))292
+ .last()293
+ .map(|(p, _)| p)294
+ }295
+296
+ /// Whether `w` is extended over windows it shares a container with.297
+ pub fn grouped(&self, w: W, vis: impl Fn(W) -> bool) -> bool {298
+ self.group(w, &vis).is_some()299
+ }300
+301
+ /// Whether a visible window extended over `w` takes all its room.302
+ pub fn eclipsed(&self, w: W, vis: impl Fn(W) -> bool) -> bool {303
+ let Some(&leaf) = self.leaves.get(&w) else {304
+ return false;305
+ };306
+ let mut cur = leaf;307
+ while let Some(p) = self.nodes[cur].parent {308
+ if self309
+ .expanded(p)310
+ .is_some_and(|e| e != cur && self.visible(e, &vis))264311
{265
- t.join_lane(n, p, j);266
- return t.reveal(n, true);312
+ return true;267313
}268
- let moved = t.move_node(n, dir, &vis, true);269
- t.reveal(n, moved)270
- })314
+ cur = p;315
+ }316
+ false271317
}272318
273319
/// Repeats `step` until the visible layout changes, or undoes it all when@@ -757,9 +803,16 @@ impl<W: Copy + Eq + Hash> Tree<W> {
757803
}758804
759805
/// Places `node` among `p`'s children. A container running along `p`'s760
- /// axis is dissolved into `p` so axes keep alternating.806
+ /// axis is dissolved into `p` so axes keep alternating, unless it holds an807
+ /// extended window, whose group is turned across instead to survive.761808
fn attach(&mut self, p: NodeId, at: usize, node: NodeId) {762809
if self.axis(node).is_some() && self.axis(node) == self.axis(p) {810
+ if self.expanded(node).is_some() {811
+ self.transpose(node);812
+ self.nodes[node].parent = Some(p);813
+ self.children_mut(p).insert(at, node);814
+ return;815
+ }763816
let children = self.children(node).to_vec();764817
self.nodes.remove(node);765818
for (k, &c) in children.iter().enumerate() {@@ -787,6 +840,18 @@ impl<W: Copy + Eq + Hash> Tree<W> {
787840
}788841
}789842
843
+ fn transpose(&mut self, n: NodeId) {844
+ match &mut self.nodes[n].kind {845
+ Kind::Container { axis, children, .. } => {846
+ *axis = axis.other();847
+ for c in children.clone() {848
+ self.transpose(c);849
+ }850
+ }851
+ Kind::Leaf(_, size) => size.reverse(),852
+ }853
+ }854
+790855
/// Repairs a container that lost a child.791856
fn normalize(&mut self, c: NodeId) {792857
if self.children(c).len() >= 2 {@@ -810,10 +875,9 @@ impl<W: Copy + Eq + Hash> Tree<W> {
810875
(None, None) => self.root = None,811876
(Some(only), Some((p, i))) => {812877
self.unlink_dead(p, i);813
- let leaf_like = self.axis(only) != self.axis(p);814878
self.attach(p, i, only);815879
if was_expanded816
- && leaf_like880
+ && self.nodes.contains_key(only)817881
&& let Kind::Container { expanded, .. } = &mut self.nodes[p].kind818882
{819883
*expanded = Some(only);@@ -87,7 +87,7 @@ impl State {
8787
self.wm.layout.move_window(d);8888
}8989
Command::MoveContainer { dir: d } => {90
- self.wm.layout.move_container(d);90
+ self.wm.layout.move_container(d, merge);9191
}9292
Command::Maximize => {9393
self.wm.layout.expand(ExpandScope::All);