From b50b47f52301a88862db1a27227b146801d7e637 Mon Sep 17 00:00:00 2001 From: Keavon Chambers Date: Wed, 22 Apr 2026 20:23:15 -0700 Subject: [PATCH 1/2] Replace TaggedValue tabular variants for color and gradient with non-tabular forms --- .../document/graph_operation/utility_types.rs | 17 ++--- .../node_graph/document_node_definitions.rs | 2 +- .../document/node_graph/node_properties.rs | 50 ++++++------- .../document/node_graph/utility_types.rs | 4 +- .../network_interface/resolved_types.rs | 4 +- .../messages/portfolio/document_migration.rs | 25 +++++++ node-graph/graph-craft/src/document/value.rs | 25 +++---- .../interpreted-executor/src/node_registry.rs | 7 ++ node-graph/libraries/core-types/src/misc.rs | 35 +++++++--- node-graph/libraries/core-types/src/table.rs | 15 ++++ .../libraries/vector-types/src/gradient.rs | 12 +++- .../vector-types/src/vector/style.rs | 6 ++ node-graph/nodes/graphic/src/artboard.rs | 8 +-- node-graph/nodes/math/src/lib.rs | 17 +++-- node-graph/nodes/raster/src/gradient_map.rs | 16 ++++- node-graph/nodes/raster/src/std_nodes.rs | 5 +- node-graph/nodes/vector/src/vector_nodes.rs | 70 ++++++++++++++++--- 17 files changed, 230 insertions(+), 88 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 26903bd89e..7745513bab 100644 --- a/editor/src/messages/portfolio/document/graph_operation/utility_types.rs +++ b/editor/src/messages/portfolio/document/graph_operation/utility_types.rs @@ -137,7 +137,7 @@ impl<'a> ModifyInputsContext<'a> { Some(NodeInput::value(TaggedValue::Graphic(Default::default()), true)), Some(NodeInput::value(TaggedValue::DVec2(artboard.location.into()), false)), Some(NodeInput::value(TaggedValue::DVec2(artboard.dimensions.into()), false)), - Some(NodeInput::value(TaggedValue::Color(Table::new_from_element(artboard.background)), false)), + Some(NodeInput::value(TaggedValue::Color(artboard.background), false)), Some(NodeInput::value(TaggedValue::Bool(artboard.clip), false)), ]); self.network_interface.insert_node(new_id, artboard_node_template, &[]); @@ -293,10 +293,7 @@ impl<'a> ModifyInputsContext<'a> { pub fn insert_color_value(&mut self, color: Color, layer: LayerNodeIdentifier) { let color_value = resolve_proto_node_type(graphene_std::math_nodes::color_value::IDENTIFIER) .expect("Color Value node does not exist") - .node_template_input_override([ - Some(NodeInput::value(TaggedValue::None, false)), - Some(NodeInput::value(TaggedValue::Color(Table::new_from_element(color)), false)), - ]); + .node_template_input_override([Some(NodeInput::value(TaggedValue::None, false)), Some(NodeInput::value(TaggedValue::OptionalColor(Some(color)), false))]); let color_value_id = NodeId::new(); self.network_interface.insert_node(color_value_id, color_value, &[]); @@ -420,11 +417,11 @@ impl<'a> ModifyInputsContext<'a> { match &fill { Fill::None => { let input_connector = InputConnector::node(fill_node_id, backup_color_index); - self.set_input_with_refresh(input_connector, NodeInput::value(TaggedValue::Color(Table::new()), false), true); + self.set_input_with_refresh(input_connector, NodeInput::value(TaggedValue::OptionalColor(None), false), true); } Fill::Solid(color) => { let input_connector = InputConnector::node(fill_node_id, backup_color_index); - self.set_input_with_refresh(input_connector, NodeInput::value(TaggedValue::Color(Table::new_from_element(*color)), false), true); + self.set_input_with_refresh(input_connector, NodeInput::value(TaggedValue::OptionalColor(Some(*color)), false), true); } Fill::Gradient(gradient) => { let input_connector = InputConnector::node(fill_node_id, backup_gradient_index); @@ -473,10 +470,8 @@ impl<'a> ModifyInputsContext<'a> { return; }; - let stroke_color = if let Some(color) = stroke.color { Table::new_from_element(color) } else { Table::new() }; - - let input_connector = InputConnector::node(stroke_node_id, graphene_std::vector::stroke::ColorInput::INDEX); - self.set_input_with_refresh(input_connector, NodeInput::value(TaggedValue::Color(stroke_color), false), true); + let input_connector = InputConnector::node(stroke_node_id, graphene_std::vector::stroke::ColorInput::>::INDEX); + self.set_input_with_refresh(input_connector, NodeInput::value(TaggedValue::OptionalColor(stroke.color), false), true); let input_connector = InputConnector::node(stroke_node_id, graphene_std::vector::stroke::WeightInput::INDEX); self.set_input_with_refresh(input_connector, NodeInput::value(TaggedValue::F64(stroke.weight), false), true); let input_connector = InputConnector::node(stroke_node_id, graphene_std::vector::stroke::AlignInput::INDEX); diff --git a/editor/src/messages/portfolio/document/node_graph/document_node_definitions.rs b/editor/src/messages/portfolio/document/node_graph/document_node_definitions.rs index 3ee48af260..5e63c1ff78 100644 --- a/editor/src/messages/portfolio/document/node_graph/document_node_definitions.rs +++ b/editor/src/messages/portfolio/document/node_graph/document_node_definitions.rs @@ -379,7 +379,7 @@ fn document_node_definitions() -> HashMap>() => array_of_number_widget(default_info, TextInput::default()).into(), Some(x) if x == TypeId::of::>() => array_of_vec2_widget(default_info, TextInput::default()).into(), - // =========== - // TABLE TYPES - // =========== - Some(x) if x == TypeId::of::>() => color_widget(default_info, ColorInput::default().allow_none(true)), - Some(x) if x == TypeId::of::>() => color_widget(default_info, ColorInput::default().allow_none(false)), + // ========================= + // COLOR AND GRADIENT TYPES + // ========================= + Some(x) if x == TypeId::of::() => color_widget(default_info, ColorInput::default().allow_none(false)), + Some(x) if x == TypeId::of::>() => color_widget(default_info, ColorInput::default().allow_none(true)), + Some(x) if x == TypeId::of::() => color_widget(default_info, ColorInput::default().allow_none(false)), // ============ // STRUCT TYPES // ============ @@ -1180,28 +1180,28 @@ pub fn color_widget(parameter_widgets_info: ParameterWidgetsInfo, color_button: // Add the color input match &**tagged_value { - TaggedValue::Color(color_table) => widgets.push( + TaggedValue::Color(color) => widgets.push( + color_button + .value(FillChoice::Solid(*color)) + .on_update(update_value(|input: &ColorInput| TaggedValue::Color(input.value.as_solid().unwrap_or(Color::BLACK)), node_id, index)) + .on_commit(commit_value) + .widget_instance(), + ), + TaggedValue::OptionalColor(optional_color) => widgets.push( color_button - .value(match color_table.iter().next() { - Some(color) => FillChoice::Solid(*color.element), + .value(match optional_color { + Some(color) => FillChoice::Solid(*color), None => FillChoice::None, }) - .on_update(update_value( - |input: &ColorInput| TaggedValue::Color(input.value.as_solid().iter().map(|&color| TableRow::new_from_element(color)).collect()), - node_id, - index, - )) + .on_update(update_value(|input: &ColorInput| TaggedValue::OptionalColor(input.value.as_solid()), node_id, index)) .on_commit(commit_value) .widget_instance(), ), - TaggedValue::GradientTable(gradient_table) => widgets.push( + TaggedValue::GradientStops(gradient_stops) => widgets.push( color_button - .value(match gradient_table.iter().next() { - Some(row) => FillChoice::Gradient(row.element.clone()), - None => FillChoice::Gradient(GradientStops::default()), - }) + .value(FillChoice::Gradient(gradient_stops.clone())) .on_update(update_value( - |input: &ColorInput| TaggedValue::GradientTable(input.value.as_gradient().iter().map(|&gradient| TableRow::new_from_element(gradient.clone())).collect()), + |input: &ColorInput| TaggedValue::GradientStops(input.value.as_gradient().cloned().unwrap_or_default()), node_id, index, )) @@ -1884,7 +1884,7 @@ pub(crate) fn fill_properties(node_id: NodeId, context: &mut NodePropertiesConte } }; - let (fill, backup_color, backup_gradient) = if let (Some(TaggedValue::Fill(fill)), Some(TaggedValue::Color(backup_color)), Some(TaggedValue::Gradient(backup_gradient))) = ( + let (fill, backup_color, backup_gradient) = if let (Some(TaggedValue::Fill(fill)), Some(TaggedValue::OptionalColor(backup_color)), Some(TaggedValue::Gradient(backup_gradient))) = ( &document_node.inputs[FillInput::::INDEX].as_value(), &document_node.inputs[BackupColorInput::INDEX].as_value(), &document_node.inputs[BackupGradientInput::INDEX].as_value(), @@ -1894,7 +1894,7 @@ pub(crate) fn fill_properties(node_id: NodeId, context: &mut NodePropertiesConte return vec![LayoutGroup::row(widgets_first_row)]; }; let fill2 = fill.clone(); - let backup_color_fill: Fill = backup_color.clone().into(); + let backup_color_fill: Fill = (*backup_color).into(); let backup_gradient_fill: Fill = backup_gradient.clone().into(); widgets_first_row.push(Separator::new(SeparatorStyle::Unrelated).widget_instance()); @@ -1907,13 +1907,13 @@ pub(crate) fn fill_properties(node_id: NodeId, context: &mut NodePropertiesConte Fill::None => NodeGraphMessage::SetInputValue { node_id, input_index: BackupColorInput::INDEX, - value: TaggedValue::Color(Table::new()), + value: TaggedValue::OptionalColor(None), } .into(), Fill::Solid(color) => NodeGraphMessage::SetInputValue { node_id, input_index: BackupColorInput::INDEX, - value: TaggedValue::Color(Table::new_from_element(*color)), + value: TaggedValue::OptionalColor(Some(*color)), } .into(), Fill::Gradient(gradient) => NodeGraphMessage::SetInputValue { @@ -2124,7 +2124,7 @@ pub fn stroke_properties(node_id: NodeId, context: &mut NodePropertiesContext) - let miter_limit_disabled = join_value != &StrokeJoin::Miter; let color = color_widget( - ParameterWidgetsInfo::new(node_id, ColorInput::INDEX, true, context), + ParameterWidgetsInfo::new(node_id, ColorInput::>::INDEX, true, context), crate::messages::layout::utility_types::widgets::button_widgets::ColorInput::default(), ); let weight = number_widget(ParameterWidgetsInfo::new(node_id, WeightInput::INDEX, true, context), NumberInput::default().unit(" px").min(0.)); diff --git a/editor/src/messages/portfolio/document/node_graph/utility_types.rs b/editor/src/messages/portfolio/document/node_graph/utility_types.rs index 67ac7a1197..b53cba2970 100644 --- a/editor/src/messages/portfolio/document/node_graph/utility_types.rs +++ b/editor/src/messages/portfolio/document/node_graph/utility_types.rs @@ -34,8 +34,8 @@ impl FrontendGraphDataType { TaggedValue::Graphic(_) => Self::Graphic, TaggedValue::Raster(_) => Self::Raster, TaggedValue::Vector(_) => Self::Vector, - TaggedValue::Color(_) => Self::Color, - TaggedValue::Gradient(_) | TaggedValue::GradientTable(_) => Self::Gradient, + TaggedValue::Color(_) | TaggedValue::OptionalColor(_) => Self::Color, + TaggedValue::Gradient(_) | TaggedValue::GradientStops(_) => Self::Gradient, TaggedValue::String(_) | TaggedValue::VecString(_) => Self::Typography, _ => Self::General, } diff --git a/editor/src/messages/portfolio/document/utility_types/network_interface/resolved_types.rs b/editor/src/messages/portfolio/document/utility_types/network_interface/resolved_types.rs index 12e257658e..4c2611f0d3 100644 --- a/editor/src/messages/portfolio/document/utility_types/network_interface/resolved_types.rs +++ b/editor/src/messages/portfolio/document/utility_types/network_interface/resolved_types.rs @@ -66,8 +66,8 @@ impl TypeSource { TaggedValue::Graphic(_) => FrontendGraphDataType::Graphic, TaggedValue::Raster(_) => FrontendGraphDataType::Raster, TaggedValue::Vector(_) => FrontendGraphDataType::Vector, - TaggedValue::Color(_) => FrontendGraphDataType::Color, - TaggedValue::Gradient(_) | TaggedValue::GradientTable(_) => FrontendGraphDataType::Gradient, + TaggedValue::Color(_) | TaggedValue::OptionalColor(_) => FrontendGraphDataType::Color, + TaggedValue::Gradient(_) | TaggedValue::GradientStops(_) => FrontendGraphDataType::Gradient, TaggedValue::String(_) => FrontendGraphDataType::Typography, _ => FrontendGraphDataType::General, }, diff --git a/editor/src/messages/portfolio/document_migration.rs b/editor/src/messages/portfolio/document_migration.rs index 18919edd68..fe560d6ecd 100644 --- a/editor/src/messages/portfolio/document_migration.rs +++ b/editor/src/messages/portfolio/document_migration.rs @@ -1986,6 +1986,31 @@ fn migrate_node(node_id: &NodeId, node: &DocumentNode, network_path: &[NodeId], .set_input(&InputConnector::node(*node_id, 1), NodeInput::value(TaggedValue::ImageData(image), false), network_path); } + // Migrate Color Value, Fill backup_color, and Stroke color inputs from Color(Table) to OptionalColor(Option) + let color_to_optional_cases: &[(_, usize)] = &[ + (graphene_std::math_nodes::color_value::IDENTIFIER, 1), + (graphene_std::vector_nodes::fill::IDENTIFIER, 2), + (graphene_std::vector::stroke::IDENTIFIER, 1), + ]; + for &(ref identifier, input_index) in color_to_optional_cases { + if reference == DefinitionIdentifier::ProtoNode(identifier.clone()) + && let Some(NodeInput::Value { tagged_value, .. }) = node.inputs.get(input_index) + && let TaggedValue::Color(color) = &**tagged_value + { + let color = *color; + + document.network_interface.set_input( + &InputConnector::node(*node_id, input_index), + NodeInput::value(TaggedValue::OptionalColor(Some(color)), false), + network_path, + ); + } + } + + // Migrate Artboard and Empty Image color inputs from Color(Table) to Color(Color), which is handled by serde migration already (no-op here) + + // Migrate GradientTable(Table) to GradientStops(GradientStops), which is handled by serde migration via alias (no-op here) + // ================================== // PUT ALL MIGRATIONS ABOVE THIS LINE // ================================== diff --git a/node-graph/graph-craft/src/document/value.rs b/node-graph/graph-craft/src/document/value.rs index 565409937a..09b94f8bd6 100644 --- a/node-graph/graph-craft/src/document/value.rs +++ b/node-graph/graph-craft/src/document/value.rs @@ -118,9 +118,9 @@ macro_rules! tagged_value { // Tries using the default for the tagged value type. If it not implemented, then uses the default used in document_node_types. If it is not used there, then TaggedValue::None is returned. Some(match concrete_type.id? { x if x == TypeId::of::<()>() => TaggedValue::None, - // Table-wrapped types need a single-row default with the element's default, not an empty table - x if x == TypeId::of::>() => TaggedValue::Color(Table::new_from_element(Color::default())), - x if x == TypeId::of::>() => TaggedValue::GradientTable(Table::new_from_element(GradientStops::default())), + x if x == TypeId::of::>() => TaggedValue::OptionalColor(Some(Color::default())), + x if x == TypeId::of::>() => TaggedValue::OptionalColor(None), + x if x == TypeId::of::>() => TaggedValue::GradientStops(GradientStops::default()), $( x if x == TypeId::of::<$ty>() => TaggedValue::$identifier(Default::default()), )* _ => return None, }) @@ -202,14 +202,16 @@ tagged_value! { #[serde(alias = "ArtboardGroup")] Artboard(Table), #[serde(deserialize_with = "core_types::misc::migrate_color")] // TODO: Eventually remove this migration document upgrade code - #[serde(alias = "ColorTable", alias = "OptionalColor", alias = "ColorNotInTable")] - Color(Table), - #[serde(deserialize_with = "graphic_types::vector_types::gradient::migrate_gradient_stops")] // TODO: Eventually remove this migration document upgrade code - #[serde(alias = "GradientPositions", alias = "GradientStops")] - GradientTable(Table), // ============ // STRUCT TYPES // ============ + #[serde(alias = "ColorTable", alias = "ColorNotInTable")] + Color(Color), + #[serde(deserialize_with = "core_types::misc::migrate_optional_color")] // TODO: Eventually remove this migration document upgrade code + OptionalColor(Option), + #[serde(deserialize_with = "graphic_types::vector_types::gradient::migrate_gradient_stops")] // TODO: Eventually remove this migration document upgrade code + #[serde(alias = "GradientPositions", alias = "GradientTable")] + GradientStops(GradientStops), FVec2(Vec2), FAffine2(Affine2), #[serde(alias = "IVec2", alias = "UVec2")] @@ -397,10 +399,9 @@ impl TaggedValue { () if ty == TypeId::of::() => FromStr::from_str(string).map(TaggedValue::U32).ok()?, () if ty == TypeId::of::() => to_dvec2(string).map(TaggedValue::DVec2)?, () if ty == TypeId::of::() => FromStr::from_str(string).map(TaggedValue::Bool).ok()?, - // `Color` (not in a table) is still currently needed by `BlackAndWhiteNode` and `ColorOverlayNode` GPU `shader_node(PerPixelAdjust)` variants - () if ty == TypeId::of::() => to_color(string).map(|color| TaggedValue::Color(Table::new_from_element(color)))?, - () if ty == TypeId::of::>() => to_color(string).map(|color| TaggedValue::Color(Table::new_from_element(color)))?, - () if ty == TypeId::of::>() => to_gradient(string).map(|color| TaggedValue::GradientTable(Table::new_from_element(color)))?, + () if ty == TypeId::of::() => to_color(string).map(TaggedValue::Color)?, + () if ty == TypeId::of::>() => TaggedValue::OptionalColor(to_color(string)), + () if ty == TypeId::of::() => to_gradient(string).map(TaggedValue::GradientStops)?, () if ty == TypeId::of::() => to_color(string).map(|color| TaggedValue::Fill(Fill::solid(color)))?, () if ty == TypeId::of::() => to_reference_point(string).map(TaggedValue::ReferencePoint)?, _ => return None, diff --git a/node-graph/interpreted-executor/src/node_registry.rs b/node-graph/interpreted-executor/src/node_registry.rs index 68770a9a03..7a41ada14e 100644 --- a/node-graph/interpreted-executor/src/node_registry.rs +++ b/node-graph/interpreted-executor/src/node_registry.rs @@ -70,6 +70,9 @@ fn node_registry() -> HashMap, input: Context, fn_params: [Context => Table>]), async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => Table]), + async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => Color]), + async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => Option]), + async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => GradientStops]), async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => Table]), async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => Image]), async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => String]), @@ -137,6 +140,7 @@ fn node_registry() -> HashMap, input: Context, fn_params: [Context => &PlatformEditorApi, Context => graphene_std::ContextFeatures]), + async_node!(graphene_core::context_modification::ContextModificationNode<_, _>, input: Context, fn_params: [Context => Color, Context => graphene_std::ContextFeatures]), async_node!(graphene_core::context_modification::ContextModificationNode<_, _>, input: Context, fn_params: [Context => RenderIntermediate, Context => graphene_std::ContextFeatures]), async_node!(graphene_core::context_modification::ContextModificationNode<_, _>, input: Context, fn_params: [Context => RenderOutput, Context => graphene_std::ContextFeatures]), #[cfg(target_family = "wasm")] @@ -152,6 +156,9 @@ fn node_registry() -> HashMap, input: Context, fn_params: [Context => Table>]), async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => Table]), async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => Image]), + async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => Color]), + async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => Option]), + async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => GradientStops]), async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => Table]), async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => Vec]), async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => Vec]), diff --git a/node-graph/libraries/core-types/src/misc.rs b/node-graph/libraries/core-types/src/misc.rs index 45012110d9..cfd0a60220 100644 --- a/node-graph/libraries/core-types/src/misc.rs +++ b/node-graph/libraries/core-types/src/misc.rs @@ -62,7 +62,7 @@ impl Clampable for DVec2 { } // TODO: Eventually remove this migration document upgrade code -pub fn migrate_color<'de, D: serde::Deserializer<'de>>(deserializer: D) -> Result, D::Error> { +pub fn migrate_color<'de, D: serde::Deserializer<'de>>(deserializer: D) -> Result { use crate::table::Table; use no_std_types::color::Color; use serde::Deserialize; @@ -76,14 +76,29 @@ pub fn migrate_color<'de, D: serde::Deserializer<'de>>(deserializer: D) -> Resul } Ok(match ColorFormat::deserialize(deserializer)? { - ColorFormat::Color(color) => Table::new_from_element(color), - ColorFormat::OptionalColor(color) => { - if let Some(color) = color { - Table::new_from_element(color) - } else { - Table::new() - } - } - ColorFormat::ColorTable(color_table) => color_table, + ColorFormat::Color(color) => color, + ColorFormat::OptionalColor(color) => color.unwrap_or(Color::BLACK), + ColorFormat::ColorTable(color_table) => color_table.iter().next().map(|row| *row.element).unwrap_or(Color::BLACK), + }) +} + +// TODO: Eventually remove this migration document upgrade code +pub fn migrate_optional_color<'de, D: serde::Deserializer<'de>>(deserializer: D) -> Result, D::Error> { + use crate::table::Table; + use no_std_types::color::Color; + use serde::Deserialize; + + #[derive(serde::Serialize, serde::Deserialize)] + #[serde(untagged)] + enum ColorFormat { + Color(Color), + OptionalColor(Option), + ColorTable(Table), + } + + Ok(match ColorFormat::deserialize(deserializer)? { + ColorFormat::OptionalColor(color) => color, + ColorFormat::Color(color) => Some(color), + ColorFormat::ColorTable(color_table) => color_table.iter().next().map(|row| *row.element), }) } diff --git a/node-graph/libraries/core-types/src/table.rs b/node-graph/libraries/core-types/src/table.rs index 4a814aaf58..6a1df0038c 100644 --- a/node-graph/libraries/core-types/src/table.rs +++ b/node-graph/libraries/core-types/src/table.rs @@ -322,3 +322,18 @@ impl From> for Option { table.iter().nth(0).map(|row| row.element).copied() } } + +impl From for Table { + fn from(color: crate::Color) -> Self { + Table::new_from_element(color) + } +} + +impl From> for Table { + fn from(color: Option) -> Self { + match color { + Some(color) => Table::new_from_element(color), + None => Table::new(), + } + } +} diff --git a/node-graph/libraries/vector-types/src/gradient.rs b/node-graph/libraries/vector-types/src/gradient.rs index f5c241c2f0..dcb0acbd50 100644 --- a/node-graph/libraries/vector-types/src/gradient.rs +++ b/node-graph/libraries/vector-types/src/gradient.rs @@ -489,7 +489,7 @@ impl Gradient { } // TODO: Eventually remove this migration document upgrade code -pub fn migrate_gradient_stops<'de, D: serde::Deserializer<'de>>(deserializer: D) -> Result, D::Error> { +pub fn migrate_gradient_stops<'de, D: serde::Deserializer<'de>>(deserializer: D) -> Result { use core_types::table::Table; use serde::Deserialize; @@ -501,11 +501,17 @@ pub fn migrate_gradient_stops<'de, D: serde::Deserializer<'de>>(deserializer: D) } Ok(match GradientStopsFormat::deserialize(deserializer)? { - GradientStopsFormat::GradientStops(stops) => Table::new_from_element(stops), - GradientStopsFormat::GradientTable(table) => table, + GradientStopsFormat::GradientStops(stops) => stops, + GradientStopsFormat::GradientTable(table) => table.iter().next().map(|row| row.element.clone()).unwrap_or_default(), }) } +impl From for core_types::table::Table { + fn from(stops: GradientStops) -> Self { + core_types::table::Table::new_from_element(stops) + } +} + impl core_types::bounds::BoundingBox for GradientStops { fn bounding_box(&self, _transform: DAffine2, _include_stroke: bool) -> core_types::bounds::RenderBoundingBox { core_types::bounds::RenderBoundingBox::Infinite diff --git a/node-graph/libraries/vector-types/src/vector/style.rs b/node-graph/libraries/vector-types/src/vector/style.rs index 0828c4e6f2..9cf77d9dd4 100644 --- a/node-graph/libraries/vector-types/src/vector/style.rs +++ b/node-graph/libraries/vector-types/src/vector/style.rs @@ -148,6 +148,12 @@ impl From> for Fill { } } +impl From for Fill { + fn from(stops: GradientStops) -> Fill { + Fill::Gradient(Gradient { stops, ..Default::default() }) + } +} + impl From for Fill { fn from(gradient: Gradient) -> Fill { Fill::Gradient(gradient) diff --git a/node-graph/nodes/graphic/src/artboard.rs b/node-graph/nodes/graphic/src/artboard.rs index 2886c342bd..bf400786ad 100644 --- a/node-graph/nodes/graphic/src/artboard.rs +++ b/node-graph/nodes/graphic/src/artboard.rs @@ -28,8 +28,9 @@ pub async fn create_artboard( location: DVec2, /// Width and height of the artboard within the document. Only integers are valid. dimensions: DVec2, - /// Color of the artboard background. Only positive integers are valid. - background: Table, + /// Color of the artboard background. + #[default(Color::WHITE)] + background: Color, /// Whether to cut off the contained content that extends outside the artboard, or keep it visible. #[default(true)] clip: bool, @@ -50,9 +51,6 @@ pub async fn create_artboard( let dimensions = dimensions.abs(); - let background: Option = background.into(); - let background = background.unwrap_or(Color::WHITE); - Table::new_from_element(Artboard { content, label, diff --git a/node-graph/nodes/math/src/lib.rs b/node-graph/nodes/math/src/lib.rs index a5d92ec1ac..678e5789d2 100644 --- a/node-graph/nodes/math/src/lib.rs +++ b/node-graph/nodes/math/src/lib.rs @@ -761,8 +761,14 @@ fn vec2_value(_: impl Ctx, _primary: (), x: f64, y: f64) -> DVec2 { /// Constructs a color value which may be set to any color, or no color. #[node_macro::node(category("Value"))] -fn color_value(_: impl Ctx, _primary: (), #[default(Color::BLACK)] color: Table) -> Table { - color +fn color_value>>( + _: impl Ctx, + _primary: (), + #[default(Color::BLACK)] + #[implementations(Option, Table)] + color: C, +) -> Table { + color.into() } /// Constructs a color value from red, green, blue, and alpha components given as numbers from 0 to 1. @@ -809,13 +815,14 @@ fn hex_to_color(_: impl Ctx, hex_code: String) -> Table { /// Constructs a gradient value which may be set to any sequence of color stops to represent the transition between colors. #[node_macro::node(category("Value"))] -fn gradient_value(_: impl Ctx, _primary: (), gradient: Table) -> Table { - gradient +fn gradient_value>>(_: impl Ctx, _primary: (), #[implementations(GradientStops, Table)] gradient: G) -> Table { + gradient.into() } /// Gets the color at the specified position along the gradient, given a position from 0 (left) to 1 (right). #[node_macro::node(category("Color"))] -fn sample_gradient(_: impl Ctx, _primary: (), gradient: Table, position: Fraction) -> Table { +fn sample_gradient>>(_: impl Ctx, _primary: (), #[implementations(GradientStops, Table)] gradient: G, position: Fraction) -> Table { + let gradient: Table = gradient.into(); let Some(row) = gradient.get(0) else { return Table::new() }; let position = position.clamp(0., 1.); diff --git a/node-graph/nodes/raster/src/gradient_map.rs b/node-graph/nodes/raster/src/gradient_map.rs index ef9c6e734b..a2b3143ce8 100644 --- a/node-graph/nodes/raster/src/gradient_map.rs +++ b/node-graph/nodes/raster/src/gradient_map.rs @@ -10,17 +10,29 @@ use vector_types::GradientStops; // https://www.adobe.com/devnet-apps/photoshop/fileformatashtml/#:~:text=%27grdm%27%20%3D%20Gradient%20Map // https://www.adobe.com/devnet-apps/photoshop/fileformatashtml/#:~:text=Gradient%20settings%20(Photoshop%206.0) #[node_macro::node(category("Raster: Adjustment"))] -async fn gradient_map>( +async fn gradient_map, G: Into>>( _: impl Ctx, #[implementations( Table>, + Table>, + Table, Table, Table, + Table, )] mut image: T, - gradient: Table, + #[implementations( + GradientStops, + Table, + GradientStops, + Table, + GradientStops, + Table, + )] + gradient: G, reverse: bool, ) -> T { + let gradient: Table = gradient.into(); let Some(row) = gradient.get(0) else { return image }; image.adjust(|color| { diff --git a/node-graph/nodes/raster/src/std_nodes.rs b/node-graph/nodes/raster/src/std_nodes.rs index b362074ecb..e621f76ac6 100644 --- a/node-graph/nodes/raster/src/std_nodes.rs +++ b/node-graph/nodes/raster/src/std_nodes.rs @@ -280,11 +280,12 @@ pub fn extend_image_to_bounds(_: impl Ctx, image: Table>, bounds: DA } #[node_macro::node(category("Debug"))] -pub fn empty_image(_: impl Ctx, transform: DAffine2, color: Table) -> Table> { +pub fn empty_image>>(_: impl Ctx, transform: DAffine2, #[implementations(Color, Table)] color: C) -> Table> { let width = transform.transform_vector2(DVec2::new(1., 0.)).length() as u32; let height = transform.transform_vector2(DVec2::new(0., 1.)).length() as u32; - let color: Option = color.into(); + let color_table: Table = color.into(); + let color: Option = color_table.into(); let image = Image::new(width, height, color.unwrap_or(Color::WHITE)); let mut result_table = Table::new_from_element(Raster::new_cpu(image)); diff --git a/node-graph/nodes/vector/src/vector_nodes.rs b/node-graph/nodes/vector/src/vector_nodes.rs index 40fb786b06..f2ad1b6047 100644 --- a/node-graph/nodes/vector/src/vector_nodes.rs +++ b/node-graph/nodes/vector/src/vector_nodes.rs @@ -48,10 +48,10 @@ impl VectorTableIterMut for Table { /// Uniquely sets the fill and/or stroke style of every vector element to individual colors sampled along a chosen gradient. #[node_macro::node(category("Vector: Style"), path(graphene_core::vector))] -async fn assign_colors( +async fn assign_colors>>( _: impl Ctx, /// The content with vector paths to apply the fill and/or stroke style to. - #[implementations(Table, Table)] + #[implementations(Table, Table, Table, Table)] #[widget(ParsedWidgetOverride::Hidden)] mut content: T, /// Whether to style the fill. @@ -61,7 +61,8 @@ async fn assign_colors( stroke: bool, /// The range of colors to select from. #[widget(ParsedWidgetOverride::Custom = "assign_colors_gradient")] - gradient: Table, + #[implementations(GradientStops, Table, GradientStops, Table)] + gradient: G, /// Whether to reverse the gradient. reverse: bool, /// Whether to randomize the color selection for each element from throughout the gradient. @@ -77,6 +78,7 @@ async fn assign_colors( where T: VectorTableIterMut + 'n + Send, { + let gradient: Table = gradient.into(); let Some(row) = gradient.into_iter().next() else { return content }; let length = content.vector_iter_mut().count(); @@ -117,6 +119,12 @@ async fn fill + 'n + Send, V: VectorTableIterMut + 'n + Send>( Table, Table, Table, + Table, + Table, + Table, + Table, + Table, + Table, Table, Table, Table, @@ -130,13 +138,19 @@ async fn fill + 'n + Send, V: VectorTableIterMut + 'n + Send>( Table, Table, Gradient, + Color, + Option, + GradientStops, Fill, Table, Table, Gradient, + Color, + Option, + GradientStops, )] fill: F, - _backup_color: Table, + _backup_color: Option, _backup_gradient: Gradient, ) -> V { let fill: Fill = fill.into(); @@ -168,14 +182,41 @@ impl IntoF64Vec for String { /// Applies a stroke style to the vector content, giving an appearance to the area within the outline of the geometry. #[node_macro::node(category("Vector: Style"), path(graphene_core::vector), properties("stroke_properties"))] -async fn stroke( +async fn stroke>, L: IntoF64Vec>( _: impl Ctx, /// The content with vector paths to apply the stroke style to. - #[implementations(Table, Table, Table, Table, Table, Table)] + #[implementations( + Table, + Table, + Table, + Table, + Table, + Table, + Table, + Table, + Table, + Table, + Table, + Table, + )] mut content: Table, /// The stroke color. #[default(Color::BLACK)] - color: Table, + #[implementations( + Option, + Option, + Option, + Table, + Table, + Table, + Option, + Option, + Option, + Table, + Table, + Table, + )] + color: C, /// The stroke thickness. #[unit(" px")] #[default(2.)] @@ -193,7 +234,20 @@ async fn stroke( /// The order to paint the stroke on top of the fill, or the fill on top of the stroke. paint_order: PaintOrder, /// The stroke dash lengths. Each length forms a distance in a pattern where the first length is a dash, the second is a gap, and so on. If the list is an odd length, the pattern repeats with solid-gap roles reversed. - #[implementations(Vec, f64, String, Vec, f64, String)] + #[implementations( + Vec, + f64, + String, + Vec, + f64, + String, + Vec, + f64, + String, + Vec, + f64, + String, + )] dash_lengths: L, /// The phase offset distance from the starting point of the dash pattern. #[unit(" px")] From 69a9bb86e7d54037b2876d39057119a3bdc6c47c Mon Sep 17 00:00:00 2001 From: Keavon Chambers Date: Wed, 22 Apr 2026 20:23:15 -0700 Subject: [PATCH 2/2] PROMPT --- prompt.txt | 1 + 1 file changed, 1 insertion(+) create mode 100644 prompt.txt diff --git a/prompt.txt b/prompt.txt new file mode 100644 index 0000000000..6492beab9f --- /dev/null +++ b/prompt.txt @@ -0,0 +1 @@ +Is there a way we could separate the TaggedValue's type (the enum discriminant part) from its value (the enum value part) using type erasure? Then the value part could be something like Option>, making it optional. So we set it if it's provided by a widget, but otherwise we omit it if it's not controlled by a widget, and thus avoid needing a way to even serialize it if it's never given a widget (like because it's a primary input which is never given a widget).