Commit ab9ba6872885
tarball2 files changed+73-3
@@ -87,18 +87,52 @@ impl GroupPlan {
8787
}8888
8989
/// Plans the transition from `from` to `to`. Both slices index the same windows.90
+/// When some windows appear while others vanish in the same place, no single91
+/// set of phases keeps them apart, so the vanishing ones finish first.92
+pub fn plan(from: &[Rect], to: &[Rect], cfg: &Config) -> Plan {93
+ let direct = plan_once(from, to, cfg);94
+ if !direct.groups.iter().any(|g| g.strategy == Strategy::Pinch) {95
+ return direct;96
+ }97
+ staged(from, to, cfg).unwrap_or(direct)98
+}99
+100
+/// Plans everything but the windows that appear, then lets those grow.101
+/// A window that appears from a seed sits inside the space a vanishing one102
+/// still fills, and the exact overlap check rejects growing into it.103
+fn staged(from: &[Rect], to: &[Rect], cfg: &Config) -> Option<Plan> {104
+ let appears = |i: usize| is_empty(&from[i], cfg) && !is_empty(&to[i], cfg);105
+ if !(0..from.len()).any(appears) {106
+ return None;107
+ }108
+ let mid: Vec<Rect> = (0..from.len())109
+ .map(|i| if appears(i) { from[i] } else { to[i] })110
+ .collect();111
+ let (first, second) = (plan_once(from, &mid, cfg), plan_once(&mid, to, cfg));112
+ let groups: Vec<Group> = first.groups.into_iter().chain(second.groups).collect();113
+ if groups.iter().any(|g| g.strategy == Strategy::Pinch) {114
+ return None;115
+ }116
+ let mut keyframes = first.keyframes;117
+ keyframes.extend(second.keyframes.into_iter().skip(1));118
+ Some(Plan { keyframes, groups })119
+}120
+121
+fn is_empty(r: &Rect, cfg: &Config) -> bool {122
+ r.w <= cfg.eps || r.h <= cfg.eps123
+}124
+90125
/// Windows with no area at either end, like hidden ones held at their seed,91126
/// jump straight to `to`. Planning them would let an invisible point inside92127
/// a visible window join its group, or block every strategy and pinch.93
-pub fn plan(from: &[Rect], to: &[Rect], cfg: &Config) -> Plan {128
+fn plan_once(from: &[Rect], to: &[Rect], cfg: &Config) -> Plan {94129
assert_eq!(95130
from.len(),96131
to.len(),97132
"layouts must describe the same windows"98133
);99
- let empty = |r: &Rect| r.w <= cfg.eps || r.h <= cfg.eps;100134
let live: Vec<usize> = (0..from.len())101
- .filter(|&i| !(empty(&from[i]) && empty(&to[i])))135
+ .filter(|&i| !(is_empty(&from[i], cfg) && is_empty(&to[i], cfg)))102136
.collect();103137
if live.len() == from.len() {104138
return plan_live(from, to, cfg);@@ -236,6 +236,24 @@ proptest! {
236236
}237237
238238
#[test]239
+ fn closing_while_spawning_never_pinches(seed in 1u64.., n in 2usize..9, edge in 0usize..3, direct: bool) {240
+ let mut rng = Rng(seed);241
+ let c = cfg(if direct { Style::Direct } else { Style::Orthogonal });242
+ let a = random_layout(&mut rng, n);243
+ let v = rng.below(n);244
+ let at = [a[v].x - G / 2.0, a[v].center_x(), a[v].right() + G / 2.0][edge];245
+ let line = Rect::new(at, a[v].y, 0.0, a[v].h);246
+ let mut from = a.clone();247
+ from.push(line);248
+ let mut to = a.clone();249
+ to[v] = Rect::new(a[v].center_x(), a[v].y, 0.0, a[v].h);250
+ to.push(a[v]);251
+ let p = plan(&from, &to, &c);252
+ prop_assert!(validate(&p, &c).is_empty(), "{:?}", p);253
+ prop_assert!(p.groups.iter().all(|g| g.strategy != Strategy::Pinch), "{:?}", p);254
+ }255
+256
+ #[test]239257
fn interrupts_replan_cleanly(seed in 1u64.., n in 2usize..9, at in 0.0f64..1.0, s in 0.0f64..1.0) {240258
let mut rng = Rng(seed);241259
let c = cfg(Style::Orthogonal);@@ -266,3 +284,21 @@ fn hidden_seeds_stay_out_of_the_plan() {
266284
assert!(p.groups.iter().all(|g| !g.members.contains(&2)));267285
assert!(p.keyframes.iter().all(|k| k[2] == to[2]));268286
}287
+288
+/// A window closes in the slot where another opens. The ghost shrinks to a289
+/// seed inside its own full rect while the newcomer grows from a seed on the290
+/// same line, so no single set of phases keeps them apart and every strategy291
+/// used to fail until the plan pinched the whole output.292
+#[test]293
+fn closing_and_spawning_in_one_slot_plan_in_stages() {294
+ let c = cfg(Style::Orthogonal);295
+ let t = cols(region(), 3);296
+ let seed = Rect::new(600.0, 10.0, 0.0, 680.0);297
+ let from = vec![t[0], t[1], t[2], seed];298
+ let to = vec![t[0], seed, t[2], t[1]];299
+ let p = plan(&from, &to, &c);300
+ assert!(validate(&p, &c).is_empty(), "{p:?}");301
+ assert!(p.groups.iter().all(|g| g.strategy != Strategy::Pinch));302
+ assert_eq!(p.phases(), 2);303
+ assert_eq!(p.keyframes[1], vec![t[0], seed, t[2], seed]);304
+}