Merge pull request #4240 from dotCooCoo/fix/brush-art-symbol-renames

Render: brush art is made again when a symbol or pattern it shows is renamed
This commit is contained in:
BFlat
2026-10-26 16:40:00 +07:00
committed by GitHub
2 changed files with 70 additions and 8 deletions
+8 -8
View File
@@ -46,9 +46,9 @@ fn shows_definitions(brush: &Brush, st: &StrokeLayer) -> bool {
matches!(st.paint, vectorcraft_color::Paint::Pattern { .. }) || crate::fx::Reads::of_all(art.into_iter()).shows_definitions()
}
/// The art of `doc`'s patterns and symbols, in order.
fn definitions(doc: &Document) -> impl Iterator<Item = &Arc<Node>> {
doc.patterns.iter().flat_map(|p| &p.art).chain(doc.symbols.iter().map(|s| &s.art))
/// The names and art of `doc`'s patterns and symbols, in order.
fn definitions(doc: &Document) -> impl Iterator<Item = (&str, &Arc<Node>)> {
doc.patterns.iter().flat_map(|p| p.art.iter().map(|a| (p.name.as_str(), a))).chain(doc.symbols.iter().map(|s| (s.name.as_str(), &s.art)))
}
/// Parsed brush library and generated brush art.
@@ -61,9 +61,9 @@ pub(crate) struct BrushCache {
/// (frame stamp, document address) the library was last validated for.
checked: (u64, usize),
lib: HashMap<String, Arc<Brush>>,
/// The art of the document's patterns and symbols ([`definitions`]) when the library was last
/// validated, held weakly as the pattern tiles hold theirs.
definitions: Vec<Weak<Node>>,
/// The names and art of the document's patterns and symbols ([`definitions`]) when the library
/// was last validated, the art held weakly as the pattern tiles hold theirs.
definitions: Vec<(String, Weak<Node>)>,
/// Brush art per (node id, stroke item index, [`path_key`] of the stroked path).
pub(crate) entries: FrameCache<(u64, usize, u64), Entry>,
/// How many times [`Renderer::draw_brush`] made brush art.
@@ -93,8 +93,8 @@ impl BrushCache {
return;
}
self.checked = key;
if !self.definitions.iter().map(Weak::as_ptr).eq(definitions(doc).map(Arc::as_ptr)) {
self.definitions = definitions(doc).map(Arc::downgrade).collect();
if !self.definitions.iter().map(|(n, a)| (n.as_str(), a.as_ptr())).eq(definitions(doc).map(|(n, a)| (n, Arc::as_ptr(a)))) {
self.definitions = definitions(doc).map(|(n, a)| (n.to_string(), Arc::downgrade(a))).collect();
self.entries.retain(|e| !e.definitions);
}
let cur = doc.unknown.get(vectorcraft_brush::DOC_KEY);
+62
View File
@@ -801,6 +801,68 @@ fn brush_art_draws_edits_of_the_symbols_and_patterns_it_shows() {
}
}
/// As above, with symbols `Dot` (a blue square) and `Other` (a red square with a brushed line),
/// and patterns `Fill` (a green square) and `Spare` (a red square with a brushed line). After a
/// frame, the next one draws as a new renderer does when two symbols or two patterns swap names,
/// their art left as it is: the brush art's instance of `Dot` or its square painted with `Fill`
/// then shows art that draws a brushed stroke.
#[test]
fn brush_art_draws_renamed_symbols_and_patterns_it_shows() {
let _fonts = fonts_steady();
use vectorcraft_brush::{Brush, BrushKind, Scatter};
use vectorcraft_doc::pattern::{PatternDef, pattern_paint};
use vectorcraft_geom::shapes::{line, rectangle};
let square = |d: &mut Document, paint: Paint| filled(d, rectangle(Rect::new(-4.0, -4.0, 4.0, 4.0)), paint, vec![]);
let art = |d: &mut Document, paint: Paint, brushed: bool| {
let mut members = vec![Arc::new(square(d, paint))];
if brushed {
members.push(Arc::new(brushed_line(d)));
}
Node::group(d.alloc_id(), members)
};
let doc = || {
let mut d = Document::new(200.0, 100.0);
for (name, paint, brushed) in
[("Dot", Paint::solid(Color::rgb(0.0, 0.0, 1.0)), false), ("Other", Paint::solid(Color::rgb(1.0, 0.0, 0.0)), true)]
{
let a = art(&mut d, paint, brushed);
d.symbols.push(vectorcraft_doc::Symbol { name: name.into(), art: Arc::new(a) });
}
for (name, paint, brushed) in
[("Fill", Paint::solid(Color::rgb(0.0, 0.6, 0.0)), false), ("Spare", Paint::solid(Color::rgb(1.0, 0.0, 0.0)), true)]
{
let a = art(&mut d, paint, brushed);
let mut def = PatternDef::new(name, vec![Arc::new(a)]);
def.tile = Rect::new(-6.0, -6.0, 6.0, 6.0);
d.patterns.push(def);
}
let id = d.alloc_id();
let instance = Node::new(id, NodeKind::SymbolInstance { symbol: "Dot".into(), xf: Affine::translate((0.0, -8.0)) });
let mut painted = square(&mut d, pattern_paint("Fill"));
painted.transform(Affine::translate((0.0, 8.0)), true);
let scatter = Node::group(d.alloc_id(), vec![Arc::new(instance), Arc::new(painted)]);
let mut lib = vectorcraft_brush::library(&d);
lib.push(Brush { name: "Dots".into(), kind: BrushKind::Scatter(Scatter { art: scatter, spacing: (150.0, 150.0), ..Default::default() }) });
vectorcraft_brush::store(&mut d, &lib);
let mut ap = vectorcraft_doc::Appearance::basic(Paint::None, Paint::solid(Color::BLACK), 1.0);
if let Some(s) = ap.stroke_mut() {
s.brush = Some("Dots".into());
}
let path = Node::path(d.alloc_id(), line((20.0, 50.0).into(), (180.0, 50.0).into()), ap);
let l = d.layers[0].id;
d.insert(Some(l), usize::MAX, path).unwrap();
d
};
edit_draws_as_new(doc(), |d| {
d.symbols[0].name = "Other".into();
d.symbols[1].name = "Dot".into();
});
edit_draws_as_new(doc(), |d| {
d.patterns[0].name = "Spare".into();
d.patterns[1].name = "Fill".into();
});
}
/// A path of the document and an instance of symbol `Wave` (a group holding another path) are
/// stroked with scatter brush `Bent`, whose art is an arc envelope around a green band: the second
/// frame evaluates none of the envelopes in their brush art again, and draws what the first drew