Introduce the new brush stroke data types (#4467)

* Add the brush stroke format types

* Route brush strokes through the node graph

* Store brush style as per-item attributes instead of a struct
This commit is contained in:
Timon
2026-08-27 15:19:01 +00:00
committed by GitHub
parent 96cc520c4b
commit 10256dd22c
19 changed files with 317 additions and 6 deletions

View File

@@ -2,6 +2,7 @@ use super::DocumentNode;
use crate::application_io::PlatformEditorApi;
use crate::application_io::resource::Resource;
use crate::proto::{Any as DAny, FutureAny};
use brush_nodes::Stroke;
use brush_nodes::brush_stroke::{BrushStroke, BrushTrace};
use core_types::color::SRGBA8;
use core_types::list::{Item, List, NodeIdPath};
@@ -97,6 +98,7 @@ macro_rules! tagged_value {
#[serde(deserialize_with = "brush_nodes::migrations::migrate_to_brush_strokes")] // TODO: Eventually remove this document upgrade code
#[serde(alias = "BrushStrokeTable")]
BrushStrokes(Vec<BrushStroke>),
Strokes(Vec<Stroke>),
// =======================
// AUTO-GENERATED VARIANTS
// =======================
@@ -140,6 +142,7 @@ macro_rules! tagged_value {
Self::BoxCorners(values) => values.cache_hash(state),
Self::GradientRamp(ramp) => ramp.cache_hash(state),
Self::BrushStrokes(strokes) => strokes.cache_hash(state),
Self::Strokes(strokes) => strokes.cache_hash(state),
// =======================
// NON-SERIALIZED VARIANTS
// =======================
@@ -203,6 +206,10 @@ macro_rules! tagged_value {
Self::BoxCorners(values) => Box::new(Item::new_from_element(BoxCorners::from(values))),
Self::GradientRamp(ramp) => Box::new(Item::<Gradient>::from(ramp)),
Self::BrushStrokes(strokes) => Box::new(core_types::list::Item::new_from_element(BrushTrace::from(strokes))),
Self::Strokes(strokes) => {
let list: List<Stroke> = strokes.into_iter().map(core_types::list::Item::new_from_element).collect();
Box::new(list)
}
// =======================
// AUTO-GENERATED VARIANTS
// =======================
@@ -266,6 +273,10 @@ macro_rules! tagged_value {
Self::BoxCorners(values) => Arc::new(Item::new_from_element(BoxCorners::from(values))),
Self::GradientRamp(ramp) => Arc::new(Item::<Gradient>::from(ramp)),
Self::BrushStrokes(strokes) => Arc::new(core_types::list::Item::new_from_element(BrushTrace::from(strokes))),
Self::Strokes(strokes) => {
let list: List<Stroke> = strokes.into_iter().map(core_types::list::Item::new_from_element).collect();
Arc::new(list)
}
// =======================
// AUTO-GENERATED VARIANTS
// =======================
@@ -295,6 +306,7 @@ macro_rules! tagged_value {
Self::BoxCorners(_) => item!(BoxCorners),
Self::GradientRamp(_) => item!(Gradient),
Self::BrushStrokes(_) => item!(BrushTrace),
Self::Strokes(_) => list!(Stroke),
// =======================
// AUTO-GENERATED VARIANTS
// =======================
@@ -335,6 +347,7 @@ macro_rules! tagged_value {
x if x == TypeId::of::<Item<Gradient>>() => Ok(TaggedValue::GradientRamp(GradientRamp::from(&*downcast::<Item<Gradient>>(input).unwrap()))),
x if x == TypeId::of::<Vec<BrushStroke>>() => Ok(TaggedValue::BrushStrokes(*downcast(input).unwrap())),
x if x == TypeId::of::<Item<BrushTrace>>() => Ok(TaggedValue::BrushStrokes(downcast::<Item<BrushTrace>>(input).unwrap().into_element().0.iter_element_values().cloned().collect())),
x if x == TypeId::of::<List<Stroke>>() => Ok(TaggedValue::Strokes(downcast::<List<Stroke>>(input).unwrap().into_iter().map(Item::into_element).collect())),
// =======================
// AUTO-GENERATED VARIANTS
// =======================
@@ -369,6 +382,7 @@ macro_rules! tagged_value {
x if x == TypeId::of::<Item<Gradient>>() => Ok(TaggedValue::GradientRamp(GradientRamp::from(input.downcast_ref::<Item<Gradient>>().unwrap()))),
x if x == TypeId::of::<Vec<BrushStroke>>() => Ok(TaggedValue::BrushStrokes(input.downcast_ref::<Vec<BrushStroke>>().unwrap().clone())),
x if x == TypeId::of::<Item<BrushTrace>>() => Ok(TaggedValue::BrushStrokes(input.downcast_ref::<Item<BrushTrace>>().unwrap().element().0.iter_element_values().cloned().collect())),
x if x == TypeId::of::<List<Stroke>>() => Ok(TaggedValue::Strokes(input.downcast_ref::<List<Stroke>>().unwrap().iter_element_values().cloned().collect())),
// =======================
// AUTO-GENERATED VARIANTS
// =======================
@@ -397,6 +411,7 @@ macro_rules! tagged_value {
if name == std::any::type_name::<BoxCorners>() { return Some(TaggedValue::BoxCorners(Vec::new())) }
$( if name == std::any::type_name::<$ty>() { return Some(TaggedValue::$identifier(Default::default())) } )*
if name == std::any::type_name::<BrushTrace>() { return Some(TaggedValue::BrushStrokes(Vec::new())) }
if name == std::any::type_name::<List<Stroke>>() { return Some(TaggedValue::Strokes(Vec::new())) }
// Unranked types without a variant route through `TypeDefault`, with `to_dynany`/`to_any` constructing the actual default at execution time
macro_rules! check_bare {
($type_default:ty) => {
@@ -423,6 +438,9 @@ macro_rules! tagged_value {
if **element == concrete!(f64) {
return Some(TaggedValue::F64Array(Vec::new()));
}
if **element == concrete!(Stroke) {
return Some(TaggedValue::Strokes(Vec::new()));
}
macro_rules! check {
($type_default:ty) => {
if **element == concrete!($type_default) { return Some(TaggedValue::TypeDefault(input.clone())); }
@@ -450,6 +468,7 @@ macro_rules! tagged_value {
Self::BoxCorners(values) => format!("BoxCorners({values:?})"),
Self::GradientRamp(ramp) => format!("GradientRamp({ramp:?})"),
Self::BrushStrokes(strokes) => format!("BrushStrokes({strokes:?})"),
Self::Strokes(strokes) => format!("Strokes({strokes:?})"),
// =======================
// AUTO-GENERATED VARIANTS
// =======================

View File

@@ -1059,7 +1059,7 @@ mod test {
// If this assert fails: These NodeIds seem to be changing when you modify TaggedValue, just update them.
assert_eq!(
ids,
vec![NodeId(8464972237805743576), NodeId(3528778906331798968), NodeId(1126597937993520391), NodeId(17582929706900579130)]
vec![NodeId(12331852515109999872), NodeId(5084548161767585362), NodeId(14635346976242256925), NodeId(16015195863711239715)]
);
}