diff --git a/crates/engine/src/session/edit.rs b/crates/engine/src/session/edit.rs index fd2de6ec5..71f70d25a 100644 --- a/crates/engine/src/session/edit.rs +++ b/crates/engine/src/session/edit.rs @@ -39,10 +39,13 @@ impl Session { } // Symbol editing: the symbol follows its art on the editing layer, before // inline graphics follow their symbols. - let symbol_synced = + let mut symbol_synced = if st.doc.symbol_edit.is_some() { cmd::symboledit::sync(&before, Arc::make_mut(&mut st.doc)) } else { Ok(()) }; // Inline graphics in text follow their symbols. cmd::inline::refresh(&before, Arc::make_mut(&mut st.doc)); + // The document the passes below start from, for the symbol to follow what they + // change on its editing layer. + let synced = st.doc.symbol_edit.is_some().then(|| st.doc.clone()); // Text Wrap: area type follows its wrap objects; then threads re-flow. cmd::textwrap::refresh(&before, Arc::make_mut(&mut st.doc)); // Type in no thread numbers its list paragraphs from 1 (before the mask copies @@ -56,6 +59,28 @@ impl Session { if !st.doc.text_threads.is_empty() { cmd::threads::reflow(&before, Arc::make_mut(&mut st.doc)); } + // Symbol editing: the symbol also takes the text wrap, list numbering and + // re-flow those passes gave its editing layer; when that changes the symbol, its + // inline graphics follow it and the threads they are in re-flow. + let editing = st.doc.symbol_edit.as_ref().map(|e| e.layer); + if let Some(synced) = &synced + && symbol_synced.is_ok() + && editing.is_some() + && before.symbol_edit.as_ref().map(|e| e.layer) == editing + { + let art = + |d: &Document| d.symbol_edit.as_ref().and_then(|e| d.symbols.iter().find(|x| x.name == e.symbol)).map(|x| x.art.clone()); + let old = art(&st.doc); + symbol_synced = cmd::symboledit::sync(synced, Arc::make_mut(&mut st.doc)); + let new = art(&st.doc); + if symbol_synced.is_ok() && !matches!((&old, &new), (Some(a), Some(b)) if Arc::ptr_eq(a, b)) { + let resynced = st.doc.clone(); + cmd::inline::refresh(synced, Arc::make_mut(&mut st.doc)); + if !st.doc.text_threads.is_empty() { + cmd::threads::reflow(&resynced, Arc::make_mut(&mut st.doc)); + } + } + } // Asset Export: assets let go of deleted art. if !st.doc.assets.is_empty() { Arc::make_mut(&mut st.doc).prune_assets(); diff --git a/crates/engine/src/tests/symboledit.rs b/crates/engine/src/tests/symboledit.rs index 8cd11aaa2..450572b73 100644 --- a/crates/engine/src/tests/symboledit.rs +++ b/crates/engine/src/tests/symboledit.rs @@ -676,3 +676,45 @@ fn undoing_entry_with_a_group_isolated_inside_the_art_goes_back_to_before() { assert_eq!(st.isolation, None); assert_eq!(st.active_layer, before); } + +/// The text wrap of the area type in `n`. +fn wrap_in(n: &Node) -> Vec { + let mut wrap = None; + n.walk(&mut |n| { + if let NodeKind::Text(t) = &n.kind { + wrap = Some(t.wrap.clone()); + } + }); + wrap.expect("area type") +} + +#[test] +fn moving_a_wrap_object_in_the_edit_gives_the_symbol_the_new_text_wrap() { + let mut s = Session::new(); + run(&mut s, "file.new", json!({"width": 600, "height": 600})); + let text = "word ".repeat(200); + let t = id(&run(&mut s, "text.create", json!({"x": 0, "y": 0, "text": text, "size": 12, "area": {"width": 400, "height": 400}}))); + let r = id(&run(&mut s, "shape.rectangle", json!({"x": 0, "y": 50, "width": 200, "height": 100}))); + run(&mut s, "select.set", json!({"ids": [r.0]})); + run(&mut s, "object.textWrap.make", json!({})); + run(&mut s, "select.set", json!({"ids": [t.0, r.0]})); + let instance = id(&run(&mut s, "symbol.new", json!({"name": "Wrapped"}))); + let symbol_wrap = |s: &Session| wrap_in(&s.doc().unwrap().doc.symbols.iter().find(|x| x.name == "Wrapped").expect("the symbol").art); + let entered = symbol_wrap(&s); + assert!(!entered.is_empty()); + run(&mut s, "symbol.edit", json!({"id": instance.0})); + let (layer, rect) = { + let st = s.doc().unwrap(); + let layer = st.doc.symbol_edit.as_ref().unwrap().layer; + let rect = layer_art(&s).into_iter().find(|i| st.doc.node(*i).is_some_and(|n| n.extras().wrap.is_some())).expect("the wrap object"); + (layer, rect) + }; + run(&mut s, "select.set", json!({"ids": [rect.0]})); + run(&mut s, "object.move", json!({"dx": 150, "dy": 0})); + let on_layer = wrap_in(s.doc().unwrap().doc.node(layer).unwrap()); + assert_ne!(on_layer, entered, "the editing layer's text wraps around the moved object"); + assert_eq!(symbol_wrap(&s), on_layer); + // Leaving keeps it. + run(&mut s, "symbol.exitEditing", json!({})); + assert_eq!(symbol_wrap(&s), on_layer); +}