mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-11-02 06:07:06 -06:00
Brushes: renaming or deleting a brush updates every stroke that uses it
Delete Brush and a rename in Brush Options changed only the strokes in the layers. Strokes in symbol definitions, graph designs, pattern tiles, opacity masks, graphic styles and the art of other brushes kept the old brush name. After a rename they drew as plain strokes, and after a delete a brush made later with the same name painted them. brush.delete and brush.options with newName now rename the brush, or clear it, in every stroke in those places, and in the tile that pattern editing's Cancel puts back. Only the objects that change, and the groups that hold them, are copied. It is still one undo step. Docs: docs/target-app-parity.md.
This commit is contained in:
@@ -389,30 +389,78 @@ fn merge(base: &mut Value, patch: &Value) {
|
||||
}
|
||||
}
|
||||
|
||||
fn save_library(s: &mut Session, label: &str, lib: Vec<Brush>, renames: &[(String, Option<String>)]) -> Result<()> {
|
||||
/// Sets brush `to` on the strokes in `items` that paint with brush `from`.
|
||||
fn set_stroke_brush(items: &mut [AppearanceItem], from: &str, to: Option<&str>) {
|
||||
for it in items {
|
||||
if let AppearanceItem::Stroke(st) = it
|
||||
&& st.brush.as_deref() == Some(from)
|
||||
{
|
||||
st.brush = to.map(str::to_string);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Whether a stroke in `n` (its opacity masks included) paints with brush `name`.
|
||||
fn paints_with(n: &Node, name: &str) -> bool {
|
||||
let mut hit = false;
|
||||
vectorcraft_doc::swatches::walk_with_masks(n, &mut |m| {
|
||||
hit |= m.appearance.items.iter().any(|i| matches!(i, AppearanceItem::Stroke(st) if st.brush.as_deref() == Some(name)));
|
||||
});
|
||||
hit
|
||||
}
|
||||
|
||||
/// Applies [`set_stroke_brush`] to every stroke in `n` and its opacity masks. Only the objects that
|
||||
/// hold such a stroke, and those that hold them, are copied.
|
||||
fn set_brush_in(n: &mut Node, from: &str, to: Option<&str>) {
|
||||
set_stroke_brush(&mut n.appearance.items, from, to);
|
||||
if let Some(mask) = n.extras_if_any().and_then(|e| e.mask.as_mut())
|
||||
&& paints_with(&mask.art, from)
|
||||
{
|
||||
set_brush_in(Arc::make_mut(&mut mask.art), from, to);
|
||||
}
|
||||
for c in n.children_mut().into_iter().flatten() {
|
||||
if paints_with(c, from) {
|
||||
set_brush_in(Arc::make_mut(c), from, to);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the art a brush is made of ([`Brush::art`]) for editing.
|
||||
fn brush_art_mut(b: &mut Brush) -> Vec<&mut Node> {
|
||||
use vectorcraft_brush::BrushKind;
|
||||
match &mut b.kind {
|
||||
BrushKind::Scatter(s) => vec![&mut s.art],
|
||||
BrushKind::Art(a) => vec![&mut a.art],
|
||||
BrushKind::Pattern(p) => {
|
||||
std::iter::once(&mut p.side).chain([&mut p.outer_corner, &mut p.inner_corner, &mut p.start, &mut p.end].into_iter().flatten()).collect()
|
||||
}
|
||||
BrushKind::Calligraphic(_) | BrushKind::Bristle(_) => vec![],
|
||||
}
|
||||
}
|
||||
|
||||
/// Sets brush `to` (`None`: no brush) on every stroke that paints with brush `from`: in the art
|
||||
/// (opacity masks included), symbols, graph designs, pattern tiles and the tiles that pattern
|
||||
/// editing's Cancel puts back, graphic styles and the art of the brushes in `lib`.
|
||||
fn rename_brush_strokes(d: &mut Document, lib: &mut [Brush], from: &str, to: Option<&str>) {
|
||||
let trees = d.layers.iter_mut().chain(d.symbols.iter_mut().map(|s| &mut s.art)).chain(d.graph_designs.iter_mut().map(|g| &mut g.art));
|
||||
let patterns = d.patterns.iter_mut().chain(d.pattern_edit.iter_mut().filter_map(|pe| pe.original.as_mut()));
|
||||
for n in trees.chain(patterns.flat_map(|p| &mut p.art)) {
|
||||
if paints_with(n, from) {
|
||||
set_brush_in(Arc::make_mut(n), from, to);
|
||||
}
|
||||
}
|
||||
for g in &mut d.graphic_styles {
|
||||
set_stroke_brush(&mut g.appearance.items, from, to);
|
||||
}
|
||||
for n in lib.iter_mut().flat_map(brush_art_mut) {
|
||||
set_brush_in(n, from, to);
|
||||
}
|
||||
}
|
||||
|
||||
fn save_library(s: &mut Session, label: &str, mut lib: Vec<Brush>, renames: &[(String, Option<String>)]) -> Result<()> {
|
||||
s.edit(label, |d, _| {
|
||||
brush::store(d, &lib);
|
||||
for (from, to) in renames {
|
||||
let ids: Vec<NodeId> = {
|
||||
let mut v = vec![];
|
||||
d.walk(|n| {
|
||||
if n.appearance.items.iter().any(|i| matches!(i, AppearanceItem::Stroke(st) if st.brush.as_deref() == Some(from.as_str()))) {
|
||||
v.push(n.id);
|
||||
}
|
||||
});
|
||||
v
|
||||
};
|
||||
for id in ids {
|
||||
if let Some(n) = d.node_mut(id) {
|
||||
for it in &mut n.appearance.items {
|
||||
if let AppearanceItem::Stroke(st) = it
|
||||
&& st.brush.as_deref() == Some(from.as_str())
|
||||
{
|
||||
st.brush = to.clone();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
rename_brush_strokes(d, &mut lib, from, to.as_deref());
|
||||
if brush::current(d).as_deref() == Some(from.as_str()) {
|
||||
match to {
|
||||
Some(t) => d.unknown.insert(brush::CURRENT_KEY.into(), json!(t)),
|
||||
@@ -420,6 +468,7 @@ fn save_library(s: &mut Session, label: &str, lib: Vec<Brush>, renames: &[(Strin
|
||||
};
|
||||
}
|
||||
}
|
||||
brush::store(d, &lib);
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user