From 29548e4e8ab1770777f9e381fa514e21511801e4 Mon Sep 17 00:00:00 2001 From: Keavon Chambers Date: Sun, 2 Aug 2026 00:22:12 -0700 Subject: [PATCH] Replace the DashPattern and BoxCorners custom serde impls with storage-shaped Vec TaggedValue payloads --- .../document/graph_operation/utility_types.rs | 2 +- .../document/node_graph/node_properties.rs | 26 ++++------ .../messages/portfolio/document_migration.rs | 33 ++++++++----- .../graph_modification_utils.rs | 2 +- .../common_functionality/stroke_options.rs | 2 +- node-graph/graph-craft/src/document/value.rs | 48 +++++++++++++++++-- node-graph/graph-craft/src/proto.rs | 2 +- node-graph/libraries/core-types/src/misc.rs | 5 ++ .../libraries/vector-types/src/vector/misc.rs | 22 +-------- .../vector-types/src/vector/style.rs | 28 ++--------- 10 files changed, 90 insertions(+), 80 deletions(-) diff --git a/editor/src/messages/portfolio/document/graph_operation/utility_types.rs b/editor/src/messages/portfolio/document/graph_operation/utility_types.rs index 6d6b62191a..8d60852e22 100644 --- a/editor/src/messages/portfolio/document/graph_operation/utility_types.rs +++ b/editor/src/messages/portfolio/document/graph_operation/utility_types.rs @@ -674,7 +674,7 @@ impl<'a> ModifyInputsContext<'a> { let input_connector = InputConnector::node(stroke_node_id, graphene_std::vector::stroke::PaintOrderInput); self.set_input_with_refresh(input_connector, NodeInput::value(TaggedValue::PaintOrder(stroke.paint_order), false), false); let input_connector = InputConnector::node(stroke_node_id, graphene_std::vector::stroke::DashPatternInput); - self.set_input_with_refresh(input_connector, NodeInput::value(TaggedValue::DashPattern(stroke.dash_lengths.into()), false), true); + self.set_input_with_refresh(input_connector, NodeInput::value(TaggedValue::DashPattern(stroke.dash_lengths), false), true); let input_connector = InputConnector::node(stroke_node_id, graphene_std::vector::stroke::DashOffsetInput); self.set_input_with_refresh(input_connector, NodeInput::value(TaggedValue::F64(stroke.dash_offset), false), true); } diff --git a/editor/src/messages/portfolio/document/node_graph/node_properties.rs b/editor/src/messages/portfolio/document/node_graph/node_properties.rs index 3d20747b69..1fd69122ff 100644 --- a/editor/src/messages/portfolio/document/node_graph/node_properties.rs +++ b/editor/src/messages/portfolio/document/node_graph/node_properties.rs @@ -32,9 +32,7 @@ use graphene_std::vector::misc::BooleanOperation; use graphene_std::vector::misc::{ ArcType, BoxCorners, CentroidType, ExtrudeJoiningAlgorithm, GridType, InterpolationDistribution, MergeByDistanceAlgorithm, PointSpacingType, RowsOrColumns, SpiralType, }; -use graphene_std::vector::style::{ - DashPattern, FillChoiceUI, Gradient, GradientSpreadMethod, GradientType, GradientUI, PaintOrder, StrokeAlign, StrokeCap, StrokeJoin, build_transform_with_y_preservation, -}; +use graphene_std::vector::style::{FillChoiceUI, Gradient, GradientSpreadMethod, GradientType, GradientUI, PaintOrder, StrokeAlign, StrokeCap, StrokeJoin, build_transform_with_y_preservation}; use graphene_std::vector::{QRCodeErrorCorrectionLevel, VectorModification}; use graphene_std::{NodeParameter, ParameterRef}; @@ -843,12 +841,12 @@ pub fn dash_pattern_widget(parameter_widgets_info: ParameterWidgetsInfo, text_in log::warn!("A widget failed to be built because its node's input index is invalid."); return vec![]; }; - if let Some(TaggedValue::DashPattern(pattern)) = &input.as_non_exposed_value() { + if let Some(TaggedValue::DashPattern(lengths)) = &input.as_non_exposed_value() { widgets.extend_from_slice(&[ Separator::new(SeparatorStyle::Unrelated).widget_instance(), text_input - .value(pattern.0.iter_element_values().map(|length| length.to_string()).collect::>().join(", ")) - .on_update(parameter_widgets_info.optionally_update_value(move |input: &TextInput| Some(TaggedValue::DashPattern(DashPattern::from(input.value.as_str()))))) + .value(lengths.iter().map(|length| length.to_string()).collect::>().join(", ")) + .on_update(parameter_widgets_info.optionally_update_value(move |input: &TextInput| Some(TaggedValue::DashPattern(graphene_std::core_types::misc::parse_f64_list(&input.value))))) .widget_instance(), ]) } @@ -2177,7 +2175,7 @@ pub(crate) fn rectangle_properties(node_id: NodeId, context: &mut NodeProperties return vec![]; }; let corner_values = match input.as_non_exposed_value() { - Some(TaggedValue::BoxCorners(corners)) => corners.to_corner_values(), + Some(TaggedValue::BoxCorners(values)) => BoxCorners::from(values.clone()).to_corner_values(), _ => [0.; 4], }; let uniform_val = corner_values[0]; @@ -2196,7 +2194,7 @@ pub(crate) fn rectangle_properties(node_id: NodeId, context: &mut NodeProperties NodeGraphMessage::SetInputValue { node_id, input_index: CornerRadiusInput::INDEX, - value: TaggedValue::BoxCorners(BoxCorners::from(uniform_val)).into(), + value: TaggedValue::BoxCorners(vec![uniform_val]).into(), } .into(), ]), @@ -2215,7 +2213,7 @@ pub(crate) fn rectangle_properties(node_id: NodeId, context: &mut NodeProperties NodeGraphMessage::SetInputValue { node_id, input_index: CornerRadiusInput::INDEX, - value: TaggedValue::BoxCorners(BoxCorners::from(corner_values.to_vec())).into(), + value: TaggedValue::BoxCorners(corner_values.to_vec()).into(), } .into(), ]), @@ -2229,7 +2227,7 @@ pub(crate) fn rectangle_properties(node_id: NodeId, context: &mut NodeProperties TextInput::default() .value(corner_values.iter().map(|v| v.to_string()).collect::>().join(", ")) .on_update(optionally_update_value( - move |x: &TextInput| Some(TaggedValue::BoxCorners(BoxCorners::from(x.value.as_str()))), + move |x: &TextInput| Some(TaggedValue::BoxCorners(graphene_std::core_types::misc::parse_f64_list(&x.value))), node_id, CornerRadiusInput, )) @@ -2238,11 +2236,7 @@ pub(crate) fn rectangle_properties(node_id: NodeId, context: &mut NodeProperties NumberInput::default() .value(Some(uniform_val)) .unit(" px") - .on_update(update_value( - move |x: &NumberInput| TaggedValue::BoxCorners(BoxCorners::from(x.value.unwrap())), - node_id, - CornerRadiusInput, - )) + .on_update(update_value(move |x: &NumberInput| TaggedValue::BoxCorners(vec![x.value.unwrap()]), node_id, CornerRadiusInput)) .on_commit(commit_value) .widget_instance() }; @@ -2705,7 +2699,7 @@ pub fn stroke_properties(node_id: NodeId, context: &mut NodePropertiesContext) - }; let has_dash_lengths = match &document_node.input_value(DashPatternInput) { - Some(TaggedValue::DashPattern(pattern)) => pattern.0.is_empty(), + Some(TaggedValue::DashPattern(lengths)) => lengths.is_empty(), _ => true, }; let miter_limit_disabled = join_value != &StrokeJoin::Miter; diff --git a/editor/src/messages/portfolio/document_migration.rs b/editor/src/messages/portfolio/document_migration.rs index eba90c3b84..8c3c06747c 100644 --- a/editor/src/messages/portfolio/document_migration.rs +++ b/editor/src/messages/portfolio/document_migration.rs @@ -17,8 +17,7 @@ use graphene_std::text::{TextAlign, TypesettingConfig}; use graphene_std::transform::ScaleType; use graphene_std::uuid::NodeId; use graphene_std::vector::graphic_types; -use graphene_std::vector::misc::BoxCorners; -use graphene_std::vector::style::{DashPattern, PaintOrder, StrokeAlign}; +use graphene_std::vector::style::{PaintOrder, StrokeAlign}; use std::collections::HashMap; use std::f64::consts::PI; use std::ops::Range; @@ -1387,24 +1386,24 @@ pub fn document_migration_upgrades(document: &mut DocumentMessageHandler, reset_ /// Converts a legacy stroke dash input (a `List`, single `f64`, or comma/space separated `String`) to the `DashPattern` value type. fn migrate_dash_input(input: &NodeInput) -> Option { let NodeInput::Value { tagged_value, exposed } = input else { return None }; - let pattern = match &*tagged_value.clone().into_inner() { - TaggedValue::F64Array(lengths) => DashPattern::from(lengths.clone()), - TaggedValue::F64(length) => DashPattern::from(*length), - TaggedValue::String(text) => DashPattern::from(text.as_str()), + let lengths = match &*tagged_value.clone().into_inner() { + TaggedValue::F64Array(lengths) => lengths.clone(), + TaggedValue::F64(length) => vec![*length], + TaggedValue::String(text) => graphene_std::core_types::misc::parse_f64_list(text), _ => return None, }; - Some(NodeInput::value(TaggedValue::DashPattern(pattern), *exposed)) + Some(NodeInput::value(TaggedValue::DashPattern(lengths), *exposed)) } /// Converts a legacy rectangle corner radius input (a single `f64` or a `List` of up to four values) to the `BoxCorners` value type. fn migrate_corner_radius_input(input: &NodeInput) -> Option { let NodeInput::Value { tagged_value, exposed } = input else { return None }; - let corners = match &*tagged_value.clone().into_inner() { - TaggedValue::F64Array(values) => BoxCorners::from(values.clone()), - TaggedValue::F64(value) => BoxCorners::from(*value), + let values = match &*tagged_value.clone().into_inner() { + TaggedValue::F64Array(values) => values.clone(), + TaggedValue::F64(value) => vec![*value], _ => return None, }; - Some(NodeInput::value(TaggedValue::BoxCorners(corners), *exposed)) + Some(NodeInput::value(TaggedValue::BoxCorners(values), *exposed)) } fn migrate_node(node_id: &NodeId, node: &DocumentNode, network_path: &[NodeId], document: &mut DocumentMessageHandler, reset_node_definitions_on_open: bool) -> Option<()> { @@ -1811,6 +1810,18 @@ fn migrate_node(node_id: &NodeId, node: &DocumentNode, network_path: &[NodeId], .set_input(&InputConnector::node(*node_id, graphene_std::vector::stroke::DashPatternInput), migrated, network_path); } + // The corner radius became the `BoxCorners` value type; convert any already-shaped rectangle that still stores a legacy corner input + if reference == DefinitionIdentifier::ProtoNode(graphene_std::vector::generator_nodes::rectangle::IDENTIFIER) + && let Some(corner_input) = node.input(graphene_std::vector::generator_nodes::rectangle::CornerRadiusInput) + && let Some(migrated) = migrate_corner_radius_input(corner_input) + { + document.network_interface.set_input( + &InputConnector::node(*node_id, graphene_std::vector::generator_nodes::rectangle::CornerRadiusInput), + migrated, + network_path, + ); + } + // The rectangle's corner radius became the `BoxCorners` value type and its hidden individual-radii toggle moved after the // user-visible inputs. A legacy rectangle stores that toggle (a plain `bool`) at index 3, where the new shape stores the corner // radius, so a `bool` value there identifies the old input order: [width, height, individual, corner_radius, clamped]. diff --git a/editor/src/messages/tool/common_functionality/graph_modification_utils.rs b/editor/src/messages/tool/common_functionality/graph_modification_utils.rs index 89041ab8c8..dc8ca6a25a 100644 --- a/editor/src/messages/tool/common_functionality/graph_modification_utils.rs +++ b/editor/src/messages/tool/common_functionality/graph_modification_utils.rs @@ -557,7 +557,7 @@ pub fn get_stroke_options(layer: LayerNodeIdentifier, network_interface: &NodeNe _ => PaintOrder::default(), }; let dash_lengths = match parameters.value(stroke::DashPatternInput) { - Some(TaggedValue::DashPattern(value)) => value.0.iter_element_values().copied().collect(), + Some(TaggedValue::DashPattern(lengths)) => lengths.clone(), _ => Vec::new(), }; let dash_offset = match parameters.value(stroke::DashOffsetInput) { diff --git a/editor/src/messages/tool/common_functionality/stroke_options.rs b/editor/src/messages/tool/common_functionality/stroke_options.rs index 5069f7627e..95d5b9d59a 100644 --- a/editor/src/messages/tool/common_functionality/stroke_options.rs +++ b/editor/src/messages/tool/common_functionality/stroke_options.rs @@ -213,7 +213,7 @@ pub fn apply_paint_order(drawing: &mut DrawingToolState, order: PaintOrder, docu pub fn apply_dash_lengths(drawing: &mut DrawingToolState, lengths: Vec, document: &DocumentMessageHandler, responses: &mut VecDeque) { drawing.dash_lengths = Some(lengths.clone()); - graph_modification_utils::set_parameter_for_selected_layers(document, graphene_std::vector::stroke::DashPatternInput, TaggedValue::DashPattern(lengths.into()), responses); + graph_modification_utils::set_parameter_for_selected_layers(document, graphene_std::vector::stroke::DashPatternInput, TaggedValue::DashPattern(lengths), responses); } pub fn apply_dash_offset(drawing: &mut DrawingToolState, offset: f64, document: &DocumentMessageHandler, responses: &mut VecDeque) { diff --git a/node-graph/graph-craft/src/document/value.rs b/node-graph/graph-craft/src/document/value.rs index cd28de5d4b..05cede5283 100644 --- a/node-graph/graph-craft/src/document/value.rs +++ b/node-graph/graph-craft/src/document/value.rs @@ -86,6 +86,10 @@ macro_rules! tagged_value { #[serde(deserialize_with = "core_types::misc::migrate_to_f64_array")] // TODO: Eventually remove this migration document upgrade code #[serde(alias = "F64Table", alias = "VecF64", alias = "VecF32", alias = "F64Array4")] F64Array(Vec), + /// Stored compactly as a `Vec` of dash lengths, materializes as an `Item` at runtime via `to_dynany`/`to_any`. + DashPattern(Vec), + /// Stored compactly as a `Vec` of corner values, materializes as an `Item` at runtime via `to_dynany`/`to_any`. + BoxCorners(Vec), /// A plain, always-present color. Aliases recover legacy on-disk shapes; a legacy `null` payload (the old "no color") /// is routed to [`TaggedValue::no_paint`] by `deserialize_tagged_value_with_legacy_migration`. #[serde(deserialize_with = "core_types::misc::migrate_to_color")] // TODO: Eventually remove this migration document upgrade code @@ -139,6 +143,8 @@ macro_rules! tagged_value { // ======================= $( Self::$identifier(x) => { x.cache_hash(state) }),* Self::F64Array(values) => values.cache_hash(state), + Self::DashPattern(lengths) => lengths.cache_hash(state), + Self::BoxCorners(values) => values.cache_hash(state), Self::Color(color) => color.cache_hash(state), Self::Gradient(stops) => stops.cache_hash(state), Self::BrushStrokes(strokes) => strokes.cache_hash(state), @@ -201,6 +207,8 @@ macro_rules! tagged_value { let list: List = values.into_iter().map(core_types::list::Item::new_from_element).collect(); Box::new(list) } + Self::DashPattern(lengths) => Box::new(Item::new_from_element(DashPattern::from(lengths))), + Self::BoxCorners(values) => Box::new(Item::new_from_element(BoxCorners::from(values))), Self::Color(color) => Box::new(Item::new_from_element(color)), Self::Gradient(stops) => Box::new(Item::new_from_element(stops)), Self::BrushStrokes(strokes) => Box::new(core_types::list::Item::new_from_element(BrushTrace::from(strokes))), @@ -263,6 +271,8 @@ macro_rules! tagged_value { let list: List = values.into_iter().map(core_types::list::Item::new_from_element).collect(); Arc::new(list) } + Self::DashPattern(lengths) => Arc::new(Item::new_from_element(DashPattern::from(lengths))), + Self::BoxCorners(values) => Arc::new(Item::new_from_element(BoxCorners::from(values))), Self::Color(color) => Arc::new(Item::new_from_element(color)), Self::Gradient(stops) => Arc::new(Item::new_from_element(stops)), Self::BrushStrokes(strokes) => Arc::new(core_types::list::Item::new_from_element(BrushTrace::from(strokes))), @@ -291,6 +301,8 @@ macro_rules! tagged_value { Self::None => concrete!(()), Self::TypeDefault(td) => td.clone(), Self::F64Array(_) => list!(f64), + Self::DashPattern(_) => item!(DashPattern), + Self::BoxCorners(_) => item!(BoxCorners), Self::Color(_) => item!(Color), Self::Gradient(_) => item!(Gradient), Self::BrushStrokes(_) => item!(BrushTrace), @@ -322,7 +334,20 @@ macro_rules! tagged_value { // =============== // MANUAL VARIANTS // =============== + // The manual variants convert from both their payload and wire forms, with the newtypes flattening to their stored `Vec` form x if x == TypeId::of::<()>() => Ok(TaggedValue::None), + x if x == TypeId::of::>() => Ok(TaggedValue::F64Array(*downcast(input).unwrap())), + x if x == TypeId::of::>() => Ok(TaggedValue::F64Array(downcast::>(input).unwrap().iter_element_values().copied().collect())), + x if x == TypeId::of::() => Ok(TaggedValue::DashPattern(downcast::(input).unwrap().0.iter_element_values().copied().collect())), + x if x == TypeId::of::>() => Ok(TaggedValue::DashPattern(downcast::>(input).unwrap().into_element().0.iter_element_values().copied().collect())), + x if x == TypeId::of::() => Ok(TaggedValue::BoxCorners(downcast::(input).unwrap().0.iter_element_values().copied().collect())), + x if x == TypeId::of::>() => Ok(TaggedValue::BoxCorners(downcast::>(input).unwrap().into_element().0.iter_element_values().copied().collect())), + x if x == TypeId::of::() => Ok(TaggedValue::Color(*downcast(input).unwrap())), + x if x == TypeId::of::>() => Ok(TaggedValue::Color(downcast::>(input).unwrap().into_element())), + x if x == TypeId::of::() => Ok(TaggedValue::Gradient(*downcast(input).unwrap())), + x if x == TypeId::of::>() => Ok(TaggedValue::Gradient(downcast::>(input).unwrap().into_element())), + x if x == TypeId::of::>() => Ok(TaggedValue::BrushStrokes(*downcast(input).unwrap())), + x if x == TypeId::of::>() => Ok(TaggedValue::BrushStrokes(downcast::>(input).unwrap().into_element().0.iter_element_values().cloned().collect())), // ======================= // AUTO-GENERATED VARIANTS // ======================= @@ -345,7 +370,20 @@ macro_rules! tagged_value { // =============== // MANUAL VARIANTS // =============== + // The manual variants convert from both their payload and wire forms, with the newtypes flattening to their stored `Vec` form x if x == TypeId::of::<()>() => Ok(TaggedValue::None), + x if x == TypeId::of::>() => Ok(TaggedValue::F64Array(input.downcast_ref::>().unwrap().clone())), + x if x == TypeId::of::>() => Ok(TaggedValue::F64Array(input.downcast_ref::>().unwrap().iter_element_values().copied().collect())), + x if x == TypeId::of::() => Ok(TaggedValue::DashPattern(input.downcast_ref::().unwrap().0.iter_element_values().copied().collect())), + x if x == TypeId::of::>() => Ok(TaggedValue::DashPattern(input.downcast_ref::>().unwrap().element().0.iter_element_values().copied().collect())), + x if x == TypeId::of::() => Ok(TaggedValue::BoxCorners(input.downcast_ref::().unwrap().0.iter_element_values().copied().collect())), + x if x == TypeId::of::>() => Ok(TaggedValue::BoxCorners(input.downcast_ref::>().unwrap().element().0.iter_element_values().copied().collect())), + x if x == TypeId::of::() => Ok(TaggedValue::Color(*input.downcast_ref::().unwrap())), + x if x == TypeId::of::>() => Ok(TaggedValue::Color(*input.downcast_ref::>().unwrap().element())), + x if x == TypeId::of::() => Ok(TaggedValue::Gradient(input.downcast_ref::().unwrap().clone())), + x if x == TypeId::of::>() => Ok(TaggedValue::Gradient(input.downcast_ref::>().unwrap().element().clone())), + x if x == TypeId::of::>() => Ok(TaggedValue::BrushStrokes(input.downcast_ref::>().unwrap().clone())), + x if x == TypeId::of::>() => Ok(TaggedValue::BrushStrokes(input.downcast_ref::>().unwrap().element().0.iter_element_values().cloned().collect())), // ======================= // AUTO-GENERATED VARIANTS // ======================= @@ -372,6 +410,8 @@ macro_rules! tagged_value { if name == std::any::type_name::<()>() { return Some(TaggedValue::None) } if name == std::any::type_name::() { return Some(TaggedValue::Color(Color::default())) } if name == std::any::type_name::() { return Some(TaggedValue::Gradient(Gradient::default())) } + if name == std::any::type_name::() { return Some(TaggedValue::DashPattern(Vec::new())) } + if name == std::any::type_name::() { 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::() { return Some(TaggedValue::BrushStrokes(Vec::new())) } // Unranked types without a variant route through `TypeDefault`, with `to_dynany`/`to_any` constructing the actual default at execution time @@ -423,6 +463,8 @@ macro_rules! tagged_value { Self::None => "()".to_string(), Self::TypeDefault(td) => format!("TypeDefault({td})"), Self::F64Array(values) => format!("F64Array({values:?})"), + Self::DashPattern(lengths) => format!("DashPattern({lengths:?})"), + Self::BoxCorners(values) => format!("BoxCorners({values:?})"), Self::Color(color) => format!("Color({color:?})"), Self::Gradient(stops) => format!("Gradient({stops:?})"), Self::BrushStrokes(strokes) => format!("BrushStrokes({strokes:?})"), @@ -521,8 +563,6 @@ tagged_value! { StrokeJoin(vector::style::StrokeJoin), StrokeAlign(vector::style::StrokeAlign), PaintOrder(vector::style::PaintOrder), - DashPattern(vector::style::DashPattern), - BoxCorners(vector::misc::BoxCorners), GradientType(vector::style::GradientType), GradientSpreadMethod(vector::style::GradientSpreadMethod), ReferencePoint(vector::ReferencePoint), @@ -670,8 +710,8 @@ impl TaggedValue { () if ty == TypeId::of::() => to_color(string).map(TaggedValue::Color)?, () if ty == TypeId::of::() => to_gradient(string).map(TaggedValue::Gradient)?, () if ty == TypeId::of::() => to_reference_point(string).map(TaggedValue::ReferencePoint)?, - () if ty == TypeId::of::() => TaggedValue::DashPattern(DashPattern::from(string)), - () if ty == TypeId::of::() => TaggedValue::BoxCorners(BoxCorners::from(string)), + () if ty == TypeId::of::() => TaggedValue::DashPattern(core_types::misc::parse_f64_list(string)), + () if ty == TypeId::of::() => TaggedValue::BoxCorners(core_types::misc::parse_f64_list(string)), _ => return None, }; Some(ty) diff --git a/node-graph/graph-craft/src/proto.rs b/node-graph/graph-craft/src/proto.rs index b8f95a434f..db58d1b779 100644 --- a/node-graph/graph-craft/src/proto.rs +++ b/node-graph/graph-craft/src/proto.rs @@ -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(12815475172301479638), NodeId(13251389748338817266), NodeId(7166921994790432021), NodeId(15318519137317483318)] + vec![NodeId(12331852515109999872), NodeId(5084548161767585362), NodeId(14635346976242256925), NodeId(16015195863711239715)] ); } diff --git a/node-graph/libraries/core-types/src/misc.rs b/node-graph/libraries/core-types/src/misc.rs index f6d3421719..ca85291508 100644 --- a/node-graph/libraries/core-types/src/misc.rs +++ b/node-graph/libraries/core-types/src/misc.rs @@ -124,6 +124,11 @@ pub fn migrate_to_f64_array<'de, D: serde::Deserializer<'de>>(deserializer: D) - }) } +/// Parses a comma or space separated list of numbers, skipping any pieces that fail to parse. +pub fn parse_f64_list(text: &str) -> Vec { + text.split([',', ' ']).filter(|piece| !piece.is_empty()).filter_map(|piece| piece.parse::().ok()).collect() +} + /// Parse a CSS color string (named color, hex, `rgb(...)`, `hsl(...)`, etc.) into a linear-light [`Color`] using the `color` crate's CSS Color 4 parser. /// Tries the input as-is first (catches CSS named colors like `red`, `rgb(...)`, and well-formed hex like `#abcdef`), then falls back to treating the input as bare hex with length-based expansion to a CSS-parseable form: /// - 1 char `f` → `#fff` (CSS 3-char shorthand) diff --git a/node-graph/libraries/vector-types/src/vector/misc.rs b/node-graph/libraries/vector-types/src/vector/misc.rs index ea0ba630bf..f7a201f783 100644 --- a/node-graph/libraries/vector-types/src/vector/misc.rs +++ b/node-graph/libraries/vector-types/src/vector/misc.rs @@ -77,21 +77,6 @@ impl BoxCorners { } } -// `List` is a runtime-only wire type, so serialize the corners as their bare values to keep documents stable -#[cfg(feature = "serde")] -impl serde::Serialize for BoxCorners { - fn serialize(&self, serializer: S) -> Result { - serializer.collect_seq(self.0.iter_element_values()) - } -} - -#[cfg(feature = "serde")] -impl<'de> serde::Deserialize<'de> for BoxCorners { - fn deserialize>(deserializer: D) -> Result { - Ok(Self::from( as serde::Deserialize>::deserialize(deserializer)?)) - } -} - impl From for BoxCorners { fn from(value: f64) -> Self { Self(List::new_from_element(value)) @@ -106,12 +91,7 @@ impl From> for BoxCorners { impl From<&str> for BoxCorners { fn from(text: &str) -> Self { - Self::from( - text.split([',', ' ']) - .filter(|piece| !piece.is_empty()) - .filter_map(|piece| piece.parse::().ok()) - .collect::>(), - ) + Self::from(core_types::misc::parse_f64_list(text)) } } diff --git a/node-graph/libraries/vector-types/src/vector/style.rs b/node-graph/libraries/vector-types/src/vector/style.rs index 34bdcef8c3..8a3b3ed13c 100644 --- a/node-graph/libraries/vector-types/src/vector/style.rs +++ b/node-graph/libraries/vector-types/src/vector/style.rs @@ -118,18 +118,18 @@ pub enum StrokeCap { #[default] #[icon("StrokeCapButt")] Butt, - #[icon("StrokeCapRound")] - Round, #[icon("StrokeCapSquare")] Square, + #[icon("StrokeCapRound")] + Round, } impl StrokeCap { pub fn svg_name(&self) -> &'static str { match self { StrokeCap::Butt => "butt", - StrokeCap::Round => "round", StrokeCap::Square => "square", + StrokeCap::Round => "round", } } } @@ -218,21 +218,6 @@ impl DashPattern { } } -// `List` is a runtime-only wire type, so serialize the pattern as its bare lengths to keep documents stable -#[cfg(feature = "serde")] -impl serde::Serialize for DashPattern { - fn serialize(&self, serializer: S) -> Result { - serializer.collect_seq(self.0.iter_element_values()) - } -} - -#[cfg(feature = "serde")] -impl<'de> serde::Deserialize<'de> for DashPattern { - fn deserialize>(deserializer: D) -> Result { - Ok(Self::from( as serde::Deserialize>::deserialize(deserializer)?)) - } -} - impl From for DashPattern { fn from(length: f64) -> Self { Self(List::new_from_element(length)) @@ -247,12 +232,7 @@ impl From> for DashPattern { impl From<&str> for DashPattern { fn from(text: &str) -> Self { - Self::from( - text.split([',', ' ']) - .filter(|piece| !piece.is_empty()) - .filter_map(|piece| piece.parse::().ok()) - .collect::>(), - ) + Self::from(core_types::misc::parse_f64_list(text)) } }