From 8ee0218d1964f66ca42b5cb07cd60ce27c32c3c9 Mon Sep 17 00:00:00 2001 From: MortenHusted Date: Thu, 22 Oct 2026 09:33:12 +0200 Subject: [PATCH] M3: a variant switch takes the new variant's resizing unless the instance set its own Switching an instance to another variant copied the old root's sizing and min/max back over the new variant's on the rebuild, so an instance switched from a hugging variant to a fixed-width one whose children fill kept hugging at the old width. The next edit then turned those children Fixed, as a child can't fill an axis its parent hugs. Resizing still equal to the old variant's was never the instance's own, so on a sibling-variant switch it now follows the new variant; sizing or min/max the instance set itself stays. --- crates/engine/src/components.rs | 22 +++++++ .../engine/src/components_behaviour_tests.rs | 58 +++++++++++++++++++ 2 files changed, 80 insertions(+) diff --git a/crates/engine/src/components.rs b/crates/engine/src/components.rs index 67259d03..191a6a7c 100644 --- a/crates/engine/src/components.rs +++ b/crates/engine/src/components.rs @@ -335,6 +335,27 @@ fn carry_overrides(from: &Node, to: &mut Node, what: Carry) { } } +/// An instance switched from variant `was` to `new`: its resizing (sizing and min / max per axis) +/// that is still `was`'s follows `new`; what the instance set itself stays in `own`. +fn follow_variant_resizing(own: &mut ChildLayout, was: &ChildLayout, new: &ChildLayout) { + if own.sizing_h == was.sizing_h { + own.sizing_h = new.sizing_h; + } + if own.sizing_v == was.sizing_v { + own.sizing_v = new.sizing_v; + } + for (o, w, n) in [ + (&mut own.min_w, was.min_w, new.min_w), + (&mut own.max_w, was.max_w, new.max_w), + (&mut own.min_h, was.min_h, new.min_h), + (&mut own.max_h, was.max_h, new.max_h), + ] { + if *o == w { + *o = n; + } + } +} + /// Copy the values of property group `g` of `from` onto `to`, with the variables bound to them. pub(crate) fn copy_group(from: &Node, to: &mut Node, g: &str) { if from.bound.iter().chain(to.bound.iter()).any(|(k, _)| binding_group(k) == Some(g)) { @@ -779,6 +800,7 @@ fn sync_instance_depth(doc: &mut Document, inst: NodeId, depth: usize, spare: BT if let Some(prev) = root.source.and_then(|p| doc.node(p)).filter(|p| sibling_variants(doc, p.id, component)) { let differs = diff_groups(prev, &comp); root.overrides.retain(|g| g == "name" || !differs.contains(&g.as_str())); + follow_variant_resizing(&mut fresh.child_layout, &prev.child_layout, &comp.child_layout); } carry_overrides(&root, &mut fresh, Carry::Root); if let Some(r) = doc.node_mut(inst) { diff --git a/crates/engine/src/components_behaviour_tests.rs b/crates/engine/src/components_behaviour_tests.rs index 7a5c1f90..a95e1268 100644 --- a/crates/engine/src/components_behaviour_tests.rs +++ b/crates/engine/src/components_behaviour_tests.rs @@ -657,6 +657,64 @@ fn switching_variants_keeps_overrides_where_the_variants_agreed() { healthy(&s); } +/// A "Layout" set: Row (horizontal, hugging, a 40 wide "A") and Stacked (vertical, fixed 300 wide, +/// "A" filling it). Returns (set, row, stacked). +fn layout_set(s: &mut Session) -> (NodeId, NodeId, NodeId) { + let mut variants = Vec::new(); + for (k, (name, props, child)) in [ + ("Layout=Row", json!({"layout": "horizontal", "gap": 8, "sizingH": "hug", "sizingV": "hug", "fills": []}), json!({})), + ( + "Layout=Stacked", + json!({"layout": "vertical", "gap": 8, "sizingH": "fixed", "width": 300, "sizingV": "hug", "fills": []}), + json!({"sizingH": "fill"}), + ), + ] + .into_iter() + .enumerate() + { + let c = idv(&ex(s, "node.create", json!({"type": "component", "name": name, "x": k as f64 * 400.0, "props": props}))); + ex(s, "node.create", json!({"type": "rectangle", "parent": c, "name": "A", "width": 40, "height": 20, "props": child})); + variants.push(c); + } + let set = idv(&ex(s, "component.combineVariants", json!({"ids": variants.clone()}))); + (set, variants[0], variants[1]) +} + +#[test] +fn switching_variants_takes_the_new_variants_resizing_unless_the_instance_set_its_own() { + let mut s = Session::new(); + let (set, row, stacked) = layout_set(&mut s); + let i = idv(&ex(&mut s, "instance.create", json!({"component": set, "x": 0, "y": 200}))); + assert_eq!(component_of(&s, i), row); + ex(&mut s, "instance.setProperties", json!({"ids": [i], "properties": {"Layout": "Stacked"}})); + assert_eq!(component_of(&s, i), stacked); + let direct = idv(&ex(&mut s, "instance.create", json!({"component": stacked, "x": 0, "y": 400}))); + let (n, d) = (node(&s, i), node(&s, direct)); + assert_eq!(n.child_layout.sizing_h, Sizing::Fixed, "Row's Hug was never the instance's own: it takes Stacked's Fixed"); + assert_eq!((n.width, n.child_layout.sizing_h), (d.width, d.child_layout.sizing_h), "the same as an instance made from Stacked"); + let a = layer(&s, i, "A").unwrap(); + assert_eq!((node(&s, a).child_layout.sizing_h, node(&s, a).width), (Sizing::Fill, 300.0), "A fills the 300 wide instance"); + // Filling an auto layout parent afterwards leaves A filling, with nothing turned into an override. + let col = idv(&ex( + &mut s, + "node.create", + json!({"type": "frame", "name": "Column", "x": 600, "y": 0, "width": 500, "props": {"layout": "vertical", "sizingH": "fixed", "sizingV": "hug", "fills": []}}), + )); + ex(&mut s, "node.reparent", json!({"ids": [i], "parent": col})); + ex(&mut s, "node.set", json!({"ids": [i], "props": {"sizingH": "fill"}})); + let a = layer(&s, i, "A").unwrap(); + assert_eq!(node(&s, a).child_layout.sizing_h, Sizing::Fill, "A still fills"); + assert!(!node(&s, a).overrides.contains("resizing"), "A's resizing is still the main's"); + assert_eq!(node(&s, a).width, node(&s, i).width); + // Resizing the instance set itself stays across a switch. + let own = idv(&ex(&mut s, "instance.create", json!({"component": set, "x": 0, "y": 600}))); + ex(&mut s, "node.set", json!({"ids": [own], "props": {"minWidth": 120}})); + ex(&mut s, "instance.setProperties", json!({"ids": [own], "properties": {"Layout": "Stacked"}})); + assert_eq!(node(&s, own).child_layout.min_w, Some(120.0), "its own min width stays"); + assert_eq!(node(&s, own).child_layout.sizing_h, Sizing::Fixed, "its sizing, still Row's, follows Stacked"); + healthy(&s); +} + #[test] fn variant_combinations_order_default_and_conflicts() { let mut s = Session::new();