From f29c75a2310029579f5e410ce84b7f2e7fb22b61 Mon Sep 17 00:00:00 2001 From: dotCooCoo Date: Sun, 8 Nov 2026 19:12:40 -0800 Subject: [PATCH] Plug-ins: hit tests re-evaluate live art when a swatch or color group changes A painted live effect's pieces name swatches, so a kept evaluation could outlive a swatch that was renamed or removed and answer a click with art the document no longer draws. The kept evaluation now holds the document's swatches and color groups and is stale when they change. --- crates/doc/src/hit.rs | 22 ++++++++++++++++++---- crates/doc/src/hit/tests.rs | 32 +++++++++++++++++++++++++++++++- 2 files changed, 49 insertions(+), 5 deletions(-) diff --git a/crates/doc/src/hit.rs b/crates/doc/src/hit.rs index f099b5f95..14bda9a00 100644 --- a/crates/doc/src/hit.rs +++ b/crates/doc/src/hit.rs @@ -128,11 +128,14 @@ fn live_inputs() -> u64 { } /// A live object evaluated by [`live_art`] on this thread: the object, its document's symbols (by -/// name and art) and patterns and the [`LiveInputs`] as they were, the art and its [`art_size`]. +/// name and art), patterns, swatches and color groups and the [`LiveInputs`] as they were, the art +/// and its [`art_size`]. struct EvaluatedLive { node: Node, symbols: Vec<(String, Arc)>, patterns: Vec, + swatches: crate::Shared>, + swatch_groups: crate::Shared>, inputs: u64, art: Rc>, size: usize, @@ -146,6 +149,8 @@ impl EvaluatedLive { && self.symbols.iter().zip(&doc.symbols).all(|((name, art), s)| *name == s.name && Arc::ptr_eq(art, &s.art)) && self.node == *n && self.patterns == doc.patterns + && self.swatches == doc.swatches + && self.swatch_groups == doc.swatch_groups } } @@ -184,8 +189,8 @@ thread_local! { /// The art live object `n` of `doc` draws ([`set_live_art`]); [`crate::live::expand_live`] while /// nothing is registered. The art is evaluated again when `n`, a symbol of `doc`, a pattern of -/// `doc` or the [`LiveInputs`] have changed since, and on every click while it or the document's -/// symbols or patterns hold a placed document ([`holds_placed`]). +/// `doc`, its swatches or color groups, or the [`LiveInputs`] have changed since, and on every +/// click while it or the document's symbols or patterns hold a placed document ([`holds_placed`]). fn live_art(doc: &Document, n: &Node) -> Rc> { let inputs = live_inputs(); let kept = EVALUATED.with_borrow_mut(|kept| { @@ -206,7 +211,16 @@ fn live_art(doc: &Document, n: &Node) -> Rc> { return art; } let symbols = doc.symbols.iter().map(|s| (s.name.clone(), s.art.clone())).collect(); - let e = EvaluatedLive { node: n.clone(), symbols, patterns: doc.patterns.clone(), inputs, art: art.clone(), size }; + let e = EvaluatedLive { + node: n.clone(), + symbols, + patterns: doc.patterns.clone(), + swatches: doc.swatches.clone(), + swatch_groups: doc.swatch_groups.clone(), + inputs, + art: art.clone(), + size, + }; EVALUATED.with_borrow_mut(|kept| { kept.insert(0, e); kept.truncate(LIVE_KEPT); diff --git a/crates/doc/src/hit/tests.rs b/crates/doc/src/hit/tests.rs index d6f4cafe7..cb5b19d1a 100644 --- a/crates/doc/src/hit/tests.rs +++ b/crates/doc/src/hit/tests.rs @@ -605,11 +605,41 @@ fn evaluated_art_is_not_kept_while_a_placed_document_may_show_in_it() { fn a_kept_evaluation_is_stale_after_the_live_inputs_change() { let d = Document::new(100.0, 100.0); let n = Node::path(NodeId(3), shapes::rectangle(Rect::new(0.0, 0.0, 10.0, 10.0)), Appearance::default_art()); - let e = EvaluatedLive { node: n.clone(), symbols: vec![], patterns: vec![], inputs: 7, art: std::rc::Rc::new(vec![]), size: 0 }; + let e = kept_evaluation(&d, &n); assert!(e.is_of(&d, &n, 7)); assert!(!e.is_of(&d, &n, 8)); } +/// An evaluation of `n` in `d`, with inputs 7, holding no symbols, patterns or art. +fn kept_evaluation(d: &Document, n: &Node) -> EvaluatedLive { + EvaluatedLive { + node: n.clone(), + symbols: vec![], + patterns: vec![], + swatches: d.swatches.clone(), + swatch_groups: d.swatch_groups.clone(), + inputs: 7, + art: std::rc::Rc::new(vec![]), + size: 0, + } +} + +/// A kept evaluation is stale once a swatch changes, in a color group too: a painted live effect's +/// pieces name swatches. +#[test] +fn a_kept_evaluation_is_stale_after_a_swatch_changes() { + let mut d = Document::new(100.0, 100.0); + let n = Node::path(NodeId(3), shapes::rectangle(Rect::new(0.0, 0.0, 10.0, 10.0)), Appearance::default_art()); + let e = kept_evaluation(&d, &n); + assert!(e.is_of(&d, &n, 7)); + d.swatches.first_mut().unwrap().name = "Renamed".into(); + assert!(!e.is_of(&d, &n, 7)); + let mut d = Document::new(100.0, 100.0); + let e = kept_evaluation(&d, &n); + d.swatch_groups.push(vectorcraft_color::SwatchGroup { name: "Added".into(), swatches: vec![] }); + assert!(!e.is_of(&d, &n, 7)); +} + /// A rectangle (40, 40)–(140, 140) clipping a filled 200 × 200 square drawn over its edges, in a /// clip group (or, `layer`, as the first layer's clipping mask): the group (or the layer), the /// clipping path and the art.