fix(layout): end the fill distribution loop when no child can freeze (#1926)

Each iteration froze the children whose clamping passed a 1e-9 tolerance,
but it compared the aggregate violation against the same tolerance: several
clampings each under it could add up past it, so no child froze, the
pending set never shrank and the loop never ended. An iteration that
freezes nothing now settles the remaining children as clamped.
This commit is contained in:
Dylan M. Taylor authored and GitHub committed 2026-11-03 22:25:33 -05:00
1 parent 3b4de34efe
commit 9475a815da
2 files changed
+33

No files matched your search

+12
View File
@@ -629,14 +629,26 @@ fn distribute(items: &mut [Item], mut open: Vec<usize>, free: f64) {
.collect();
let violation: f64 = sized.iter().map(|(_, want, v)| v - want).sum();
let frozen = |want: f64, v: f64| violation.abs() < 1e-9 || (violation > 0.0 && v > want + 1e-9) || (violation < 0.0 && v < want - 1e-9);
let mut progressed = false;
for (i, want, v) in &sized {
if frozen(*want, *v)
&& let Some(it) = items.get_mut(*i)
{
it.main = *v;
free -= *v;
progressed = true;
}
}
if !progressed {
// Several clampings each under the tolerance added up past it, so none froze: settle
// them all as clamped rather than go round again with nothing changed.
for (i, _, v) in &sized {
if let Some(it) = items.get_mut(*i) {
it.main = *v;
}
}
break;
}
free = free.max(0.0);
open = sized.into_iter().filter(|(_, want, v)| !frozen(*want, *v)).map(|(i, _, _)| i).collect();
}
+21
View File
@@ -447,3 +447,24 @@ fn grid_hostile_values() {
assert!(x.is_finite() && y.is_finite() && w.is_finite() && h.is_finite());
}
}
#[test]
fn fill_children_whose_tiny_clampings_add_up_past_the_tolerance_still_settle() {
// Three fill children sharing 300 px, each with a minimum 5e-10 over its 100 px share: no
// single clamping passes the 1e-9 tolerance, but together they do, so no child froze and the
// distribution loop went round forever without changing anything.
let mut d = Document::new("t");
let p = d.first_page().unwrap();
let f = d.create(p, NodeKind::Frame, "F", 0.0, 0.0, 300.0, 100.0).unwrap();
d.get_mut(f).unwrap().layout = Some(AutoLayout { mode: LayoutMode::Horizontal, gap: 0.0, padding: [0.0; 4], ..Default::default() });
let kids: Vec<NodeId> = (0..3).map(|i| d.create(f, NodeKind::Rectangle, "K", 0.0, 0.0, 10.0, 20.0 + i as f64).unwrap()).collect();
for k in &kids {
let n = d.get_mut(*k).unwrap();
n.child_layout.sizing_h = Sizing::Fill;
n.child_layout.min_w = Some(100.0 + 5e-10);
}
layout_frame(&mut d, f, &NoMeasure);
for k in &kids {
assert!((pos(&d, *k).2 - 100.0).abs() < 1e-6, "{:?}", pos(&d, *k));
}
}