use super::diff::diff_groups; use super::instances::{Carry, apply_prop, carry_overrides, carry_prop_bindings, copy_group, field_group, follow_variant_resizing, path_map}; use super::{ Affine, BTreeMap, BTreeSet, DocError, Document, MAX_DEPTH, Node, NodeId, NodeKind, SLOT, Sizing, Value, default_instance_name, default_props, holds_content, inside_instance, is_slot, owning_component, variant_set, }; /// `spare`: layers the outer rebuild didn't reuse that were below `inst` (a swapped nested /// instance): its layers the outer main doesn't have, matched here so they keep ids and overrides. pub(super) fn sync_instance_depth(doc: &mut Document, inst: NodeId, depth: usize, spare: BTreeMap>) { if depth > MAX_DEPTH { return; } let Some(mut root) = doc.node(inst).cloned() else { return }; let NodeKind::Instance { component, props: root_props } = &root.kind else { return }; let component = *component; let Some(comp) = doc.node(component).cloned() else { return }; if !matches!(comp.kind, NodeKind::Component { .. }) || doc.is_ancestor(inst, component) || doc.is_ancestor(component, inst) { // A missing main, or an instance inside its own main (would recurse forever). return; } let eff_props = effective_props(&comp, root_props); // Old layers (kept alive for their overrides), by (parent, source), by source alone (when only // one layer mirrors it) and, only when those don't match, by name path. let (mut old, by_source, by_src) = index_old_layers(doc, inst, spare); let mut by_path: Option> = None; let mut main_paths: Option> = None; // Ids for the copies: reuse the matching old layer's id (a layer the main moved to another // parent keeps its id and overrides). let src: Vec = doc.subtree(component).into_iter().filter(|i| *i != component).collect(); let mut map: BTreeMap = BTreeMap::new(); map.insert(component, inst); let mut used: BTreeSet = BTreeSet::new(); // Slots holding the instance's own content (new ids), and main layers not copied: the // default content of those slots. let mut edited: BTreeSet = BTreeSet::new(); let mut skipped: BTreeSet = BTreeSet::new(); let mut fresh_ids = Vec::new(); for s in &src { let Some(sn) = doc.node(*s) else { continue }; if sn.parent.is_some_and(|p| skipped.contains(&p) || map.get(&p).is_some_and(|pn| edited.contains(pn))) { skipped.insert(*s); continue; } let parent_new = sn.parent.and_then(|p| map.get(&p).copied()); let mut found = parent_new.and_then(|pn| by_source.get(&(pn, *s)).copied()).or_else(|| by_src.get(s).copied().flatten()); if found.is_none() { let paths = by_path.get_or_insert_with(|| path_map(doc, inst, true).into_iter().map(|(k, v)| (v, k)).collect()); let mains = main_paths.get_or_insert_with(|| path_map(doc, component, false)); found = mains.get(s).and_then(|p| paths.get(p)).copied(); } let nid = match found { Some(o) if used.insert(o) => o, _ => { fresh_ids.push(*s); continue; } }; if let Some(m) = old.get(&nid).and_then(|o| variant_kept(doc, o, *s)) { old.insert(nid, std::sync::Arc::new(m)); } if old.get(&nid).is_some_and(|o| holds_content(o)) && is_slot(doc, *s) { edited.insert(nid); } map.insert(*s, nid); } for c in root.children.clone() { let _ = doc.remove(c); } for s in fresh_ids { let nid = doc.alloc_id(); map.insert(s, nid); } // Build the copies (pre-order: parents first). let mut enclosing: BTreeMap> = BTreeMap::new(); enclosing.insert(inst, vec![inst]); let mut inst_props: BTreeMap> = BTreeMap::new(); inst_props.insert(inst, eff_props); let mut nested = Vec::new(); let mut new_nodes = Vec::new(); // Text layers showing other text or style than their main layer (to re-measure). let mut remeasure = Vec::new(); for s in &src { let Some(orig) = doc.node(*s) else { continue }; let Some(&nid) = map.get(s) else { continue }; let Some(parent) = orig.parent.and_then(|p| map.get(&p).copied()) else { continue }; let mut n = orig.clone(); n.id = nid; n.parent = Some(parent); n.children = orig.children.iter().filter_map(|c| map.get(c).copied()).collect(); n.source = Some(*s); n.overrides.clear(); n.prop_refs.clear(); let prev = old.get(&nid); // Annotations belong to the layer they were added to, not to its copies. n.annotations = prev.map(|o| o.annotations.clone()).unwrap_or_default(); n.measurements = prev.map(|o| o.measurements.clone()).unwrap_or_default(); if let Some(o) = prev { carry_overrides(o, &mut n, Carry::Values); keep_laid_out(doc, orig, o, &mut n); } if !edited.contains(&nid) { // No longer a slot in the main: it mirrors the main again. n.overrides.remove(SLOT); } // Component properties: the main layer's bindings use this instance's values; layers of // nested instances use the nested instance's values (one level per source hop). let enc = enclosing.get(&parent).cloned().unwrap_or_default(); apply_enclosing_props(doc, &mut n, orig, &enc, &inst_props); if let NodeKind::Instance { component: c, props } = &n.kind { inst_props.insert(nid, props.clone()); let mut e = enc; e.push(nid); enclosing.insert(nid, e); if !matches!(orig.kind, NodeKind::Instance { component: oc, .. } if oc == *c) { nested.push(nid); } } else { enclosing.insert(nid, enc); } if let (NodeKind::Text(a), NodeKind::Text(b)) = (&orig.kind, &n.kind) && a != b { remeasure.push(nid); } new_nodes.push(n); } for n in new_nodes { let (id, parent) = (n.id, n.parent); if insert_linked(doc, n).is_err() && let Some(p) = parent.and_then(|p| doc.node_mut(p)) { p.children.retain(|c| *c != id); } } let fresh = fresh_root(doc, &mut root, &comp, inst, component, &map); if let Some(r) = doc.node_mut(inst) { *r = fresh; } // A resized instance: children follow their constraints (auto layout re-flows later). let (iw, ih) = doc.node(inst).map(|n| (n.width, n.height)).unwrap_or((comp.width, comp.height)); if (iw - comp.width).abs() > 1e-9 || (ih - comp.height).abs() > 1e-9 { figcraft_layout::apply_constraints(doc, inst, comp.width, comp.height); remeasure = map.values().copied().collect(); } follow_geometry_overrides(doc, &src, &map, &old, &mut remeasure); remeasure_text(doc, remeasure); rebuild_nested(doc, nested, &old, &used, depth); } /// Geometry overrides of children win over constraints. fn follow_geometry_overrides( doc: &mut Document, src: &[NodeId], map: &BTreeMap, old: &BTreeMap>, remeasure: &mut Vec, ) { for s in src { let Some(nid) = map.get(s).copied() else { continue }; let Some(o) = old.get(&nid) else { continue }; // A frame that kept the size its own resizing gave it: its layers (copied at their main // layers' places) follow their constraints from its main layer's size. if o.overrides.contains("resizing") && !o.overrides.contains("size") && doc.node(nid).is_some_and(|n| n.kind.is_frame_like() && n.layout.is_none()) && let Some((mw, mh)) = doc.node(*s).map(|m| (m.width, m.height)) { figcraft_layout::apply_constraints(doc, nid, mw, mh); } if !o.overrides.contains("size") && !o.overrides.contains("position") { continue; } remeasure.push(nid); let Some((ow, oh)) = doc.node(nid).map(|n| (n.width, n.height)) else { continue }; if o.overrides.contains("size") { // Through set_size so vector networks scale with the box. figcraft_layout::set_size(doc, nid, o.width, o.height); if doc.node(nid).is_some_and(|n| n.kind.is_frame_like() && n.layout.is_none()) { figcraft_layout::apply_constraints(doc, nid, ow, oh); } } if let Some(n) = doc.node_mut(nid) { carry_overrides(o, n, Carry::Geometry); } } } /// Nested instances showing another main than the main's copy (swapped): rebuild from it, /// with the old layers below them this rebuild didn't reuse. fn rebuild_nested(doc: &mut Document, nested: Vec, old: &BTreeMap>, used: &BTreeSet, depth: usize) { let unused: BTreeMap> = old.iter().filter(|(i, _)| !used.contains(*i) && !doc.contains(**i)).map(|(i, x)| (*i, x.clone())).collect(); for n in nested { let mut below: BTreeMap> = unused .iter() .filter(|(_, x)| { let mut cur = x.parent; for _ in 0..=MAX_DEPTH * 4 { match cur { Some(p) if p == n => return true, Some(p) => cur = unused.get(&p).and_then(|a| a.parent), None => return false, } } false }) .map(|(i, x)| (*i, x.clone())) .collect(); take_placing_overrides(doc, n, old, &mut below); sync_instance_depth(doc, n, depth + 1, below); } } fn apply_enclosing_props(doc: &Document, n: &mut Node, orig: &Node, enc: &[NodeId], inst_props: &BTreeMap>) { let mut chain: Vec<&Node> = vec![orig]; while chain.len() < enc.len() { let Some(next) = chain.last().and_then(|c| c.source).and_then(|x| doc.node(x)) else { break }; chain.push(next); } for (j, level) in chain.iter().enumerate().rev() { let Some(props) = enc.get(j).and_then(|e| inst_props.get(e)) else { continue }; for (field, prop) in &level.prop_refs { let g = field_group(field); if n.overrides.contains(g) || (j > 0 && orig.overrides.contains(g)) { continue; } if let Some(val) = props.get(prop) { apply_prop(doc, n, field, val); } } } } /// Text whose characters or style differ from the main re-measures. fn remeasure_text(doc: &mut Document, remeasure: Vec) { for nid in remeasure { if let Some(n) = doc.node(nid) && let Some((w, h)) = figcraft_text::auto_size(n) && ((w - n.width).abs() > 1e-6 || (h - n.height).abs() > 1e-6) { figcraft_layout::set_size(doc, nid, w, h); } } } /// The component's property defaults with the instance's values for the ones it has. fn effective_props(comp: &Node, root_props: &BTreeMap) -> BTreeMap { let mut eff_props = default_props(comp); for (k, v) in root_props { if eff_props.contains_key(k) { eff_props.insert(k.clone(), v.clone()); } } eff_props } type OldIndex = (BTreeMap>, BTreeMap<(NodeId, NodeId), NodeId>, BTreeMap>); /// The old layers of `inst` (and `spare`) by id, by (parent, source) and by source alone (`None` /// when several layers mirror it). fn index_old_layers(doc: &Document, inst: NodeId, spare: BTreeMap>) -> OldIndex { let mut old_ids = old_layers(doc, inst); old_ids.extend(spare.keys().copied()); old_ids.sort(); old_ids.dedup(); let mut old = spare; let mut by_source: BTreeMap<(NodeId, NodeId), NodeId> = BTreeMap::new(); let mut by_src: BTreeMap> = BTreeMap::new(); for i in &old_ids { let Some(n) = doc.node_arc(*i).or_else(|| old.get(i)).cloned() else { continue }; if let Some(s) = n.source { if let Some(p) = n.parent { by_source.entry((p, s)).or_insert(*i); } by_src.entry(s).and_modify(|e| *e = None).or_insert(Some(*i)); } old.insert(*i, n); } (old, by_source, by_src) } /// The rebuilt root of instance `inst` of `comp`. fn fresh_root(doc: &Document, root: &mut Node, comp: &Node, inst: NodeId, component: NodeId, map: &BTreeMap) -> Node { // Root: follows the main except its own placement, name, size and overrides. let order: Vec = comp.children.iter().filter_map(|c| map.get(c).copied()).filter(|c| doc.contains(*c)).collect(); let mut fresh = comp.clone(); fresh.id = inst; fresh.parent = root.parent; fresh.children = order; fresh.transform = root.transform; fresh.kind = root.kind.clone(); carry_prop_bindings(root, &mut fresh); // A nested instance keeps mirroring its layer of the outer main. let nested_copy = inside_instance(doc, inst); fresh.source = if nested_copy { root.source } else { Some(component) }; fresh.constraints = root.constraints; fresh.child_layout = root.child_layout.clone(); fresh.interactions = root.interactions.clone(); fresh.exports = root.exports.clone(); fresh.prop_refs = root.prop_refs.clone(); fresh.plugin_data = root.plugin_data.clone(); fresh.annotations = root.annotations.clone(); // Code links belong to the main component; instances read them from it. fresh.code_links.clear(); // An instance's own dev resources (its main's are listed with them, not copied). fresh.dev_resources = root.dev_resources.clone(); fresh.measurements = root.measurements.clone(); fresh.locked = root.locked; fresh.visible = root.visible; fresh.var_modes = root.var_modes.clone(); fresh.is_mask = root.is_mask; fresh.exposed = root.exposed; fresh.guides = root.guides.clone(); fresh.dev_status = root.dev_status; fresh.dev_changed = root.dev_changed; fresh.dev_note = root.dev_note.clone(); fresh.constrain_proportions = root.constrain_proportions; // How the instance combines in a boolean group it sits in is its own. fresh.bool_op = root.bool_op; // A swapped nested instance keeps the name its layer was given in the outer main. let renamed = nested_copy && root.source.and_then(|s| doc.node(s)).is_some_and(|s| match &s.kind { NodeKind::Instance { component: c, .. } => s.name != default_instance_name(doc, *c), _ => false, }); fresh.name = if renamed { root.name.clone() } else { default_instance_name(doc, component) }; // Switched to another variant: its own overrides stay where both variants agreed. 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); fresh } /// Before swapped nested instance `n` is rebuilt from its new main: each of its old layers (the /// copies just made of the outer main's, and `below`) takes the overrides held along the source /// chain it had before this rebuild (`before`), through layers inside instances in the mains that /// place `n`. Matched by name, they carry onto the new main's layers as the instance's own, so a /// swap takes what the outer main set once: later rebuilds find layers mirroring the new main. /// Geometry and slot content follow the new main; groups the layer overrides itself keep its value. fn take_placing_overrides( doc: &mut Document, n: NodeId, before: &BTreeMap>, below: &mut BTreeMap>, ) { let placing = placing_mains(doc, n); for id in doc.subtree(n).into_iter().filter(|i| *i != n) { let held = before.get(&id).map(|b| held_along(doc, b, &placing)).unwrap_or_default(); if let Some(layer) = doc.node_mut(id) { take(layer, &held); } } for layer in below.values_mut() { let held = held_along(doc, layer, &placing); if !held.is_empty() { take(std::sync::Arc::make_mut(layer), &held); } } } /// The mains placing nested instance `n`: those of the layers it mirrors, outward. fn placing_mains(doc: &Document, n: NodeId) -> BTreeSet { let mut out = BTreeSet::new(); let mut cur = doc.node(n).and_then(|x| x.source); for _ in 0..=MAX_DEPTH { let Some(s) = cur.and_then(|c| doc.node(c)).filter(|s| !matches!(s.kind, NodeKind::Component { .. })) else { break }; out.extend(owning_component(doc, s.id)); cur = s.source; } out } /// The overrides but geometry and slot content held along `layer`'s source chain while it runs /// through layers inside instances in `placing` mains, each with the nearest layer holding it. fn held_along(doc: &Document, layer: &Node, placing: &BTreeSet) -> BTreeMap> { let mut out = BTreeMap::new(); let mut cur = layer.source; for _ in 0..=MAX_DEPTH { let Some(s) = cur.and_then(|c| doc.node_arc(c)) else { break }; if !inside_instance(doc, s.id) || !owning_component(doc, s.id).is_some_and(|m| placing.contains(&m)) { break; } for g in s.overrides.iter().filter(|g| !matches!(g.as_str(), "size" | "position" | SLOT)) { out.entry(g.clone()).or_insert_with(|| s.clone()); } cur = s.source; } out } /// Give `layer` the `held` groups it has no override of its own for. fn take(layer: &mut Node, held: &BTreeMap>) { for (g, from) in held { if layer.overrides.insert(g.clone()) { copy_group(from, layer, g); } } } /// Whether main components `a` and `b` are two variants of one set. fn sibling_variants(doc: &Document, a: NodeId, b: NodeId) -> bool { a != b && variant_set(doc, a).is_some_and(|s| variant_set(doc, b) == Some(s)) } /// Old copy `o` matched by name to main layer `s` of another variant (the instance switched /// variants): `o` without the overrides of properties the two variants' layers differ in (`None` /// when it keeps them all). Text layers keep every override. fn variant_kept(doc: &Document, o: &Node, s: NodeId) -> Option { let from = o.source.filter(|f| *f != s).and_then(|f| doc.node(f))?; if o.overrides.is_empty() || matches!(o.kind, NodeKind::Text(_)) { return None; } if !sibling_variants(doc, owning_component(doc, from.id)?, owning_component(doc, s)?) { return None; } let differs = diff_groups(from, doc.node(s)?); let mut m = o.clone(); m.overrides.retain(|g| !differs.contains(&g.as_str())); (m.overrides.len() != o.overrides.len()).then_some(m) } /// Drop the overrides of instance `inst` but its text layers' (and its own size and the content of /// its slots): what swapping it from the Assets panel keeps. pub fn keep_text_overrides(doc: &mut Document, inst: NodeId) { for d in old_layers(doc, inst).into_iter().chain([inst]) { let Some(n) = doc.node(d) else { continue }; let text = matches!(n.kind, NodeKind::Text(_)); let keep = |g: &String| text || g == SLOT || (d == inst && g == "size"); if !n.overrides.iter().all(keep) && let Some(m) = doc.node_mut(d) { m.overrides.retain(keep); } } } /// Where auto layout places and sizes a rebuilt copy `n` of main layer `orig`, keep the old /// copy's `o` values (an instance's auto layout re-flows from its own content, so they usually /// differ from the main's): unchanged layers then stay unchanged, and auto layout re-flows wherever /// something that sizes or places layers did change. fn keep_laid_out(doc: &Document, orig: &Node, o: &Node, n: &mut Node) { if o.parent != n.parent || o.overrides.contains("size") || o.overrides.contains("position") { return; } let flows = !n.child_layout.absolute && orig.parent.and_then(|p| doc.node(p)).is_some_and(|p| p.layout.is_some()); if flows { // The main's rotation and skew, the old place. let [a, b, c, d, _, _] = n.transform.as_coeffs(); let [.., e, f] = o.transform.as_coeffs(); n.transform = Affine::new([a, b, c, d, e, f]); } // Only layers whose shape is their box (a vector's points scale with it). let boxed = n.kind.is_frame_like() || matches!(n.kind, NodeKind::Text(_) | NodeKind::Rectangle | NodeKind::Ellipse { .. }); let derived = |s: Sizing| boxed && ((s == Sizing::Hug && n.layout.is_some()) || (s == Sizing::Fill && flows)); if derived(n.child_layout.sizing_h) { n.width = o.width; } if derived(n.child_layout.sizing_v) { n.height = o.height; } } /// The layers of instance `inst` a rebuild matches against (every layer below it but the content /// of slots holding its own), ascending. fn old_layers(doc: &Document, inst: NodeId) -> Vec { let mut out = Vec::new(); let mut stack = vec![inst]; while let Some(id) = stack.pop() { let Some(n) = doc.node(id) else { continue }; for c in &n.children { out.push(*c); if out.len() > 1_000_000 { break; } if doc.node(*c).is_some_and(|cn| !holds_content(cn)) { stack.push(*c); } } } out.sort(); out.dedup(); out } /// Insert a node whose parent/children links are already set (used when rebuilding instances). pub(super) fn insert_linked(doc: &mut Document, n: Node) -> Result<(), DocError> { let parent = n.parent.ok_or_else(|| DocError::Invalid("no parent".into()))?; let id = n.id; let mut n = n; let kids = std::mem::take(&mut n.children); doc.insert(n, parent, None)?; if let Some(m) = doc.node_mut(id) { m.children = kids; } // The parent may already list us (its children were copied with ids): keep the first. if let Some(p) = doc.node_mut(parent) { let mut seen = BTreeSet::new(); p.children.retain(|c| seen.insert(*c)); } Ok(()) }