Render: brush art is made again when a symbol or pattern it shows is renamed

The brush cache dropped brush art that can show a symbol or a pattern
only when the art of the document's symbols or patterns changed. Renaming
one, for example swapping the names of two symbols, left their art as it
was, so a brush whose art holds an instance of one of those names kept
drawing the old symbol. The cache now compares the names along with the
art.
This commit is contained in:
dotCooCoo
2026-10-26 16:38:04 +07:00
committed by bflatastic
parent 1cdc98735c
commit 1edfb8f450
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