diff --git a/editor/src/messages/portfolio/document/data_panel/data_panel_message_handler.rs b/editor/src/messages/portfolio/document/data_panel/data_panel_message_handler.rs index 9d0588aed7..cf366a1bfe 100644 --- a/editor/src/messages/portfolio/document/data_panel/data_panel_message_handler.rs +++ b/editor/src/messages/portfolio/document/data_panel/data_panel_message_handler.rs @@ -169,12 +169,15 @@ fn generate_layout(introspected_data: &Arc>, Table, Table, + Table, + Table, + Table, + Table, GradientStops, f64, u32, u64, bool, - Vec, String, Option, DVec2, @@ -214,46 +217,6 @@ trait TableRowLayout { } } -impl TableRowLayout for Vec { - fn type_name() -> &'static str { - "Vec" - } - fn identifier(&self) -> String { - format!("Vec<{}> ({} element{})", T::type_name(), self.len(), if self.len() == 1 { "" } else { "s" }) - } - fn element_page(&self, data: &mut LayoutData) -> Vec { - if let Some(step) = data.desired_path.get(data.current_depth).cloned() { - match step { - PathStep::Element(index) => { - if let Some(row) = self.get(index) { - data.current_depth += 1; - let result = row.layout_with_breadcrumb(data); - data.current_depth -= 1; - return result; - } else { - warn!("Desired path truncated"); - data.desired_path.truncate(data.current_depth); - } - } - PathStep::Attribute { .. } => { - warn!("Attribute path step inside a Vec is unsupported"); - data.desired_path.truncate(data.current_depth); - } - } - } - - let mut rows = self - .iter() - .enumerate() - .map(|(index, row)| vec![TextLabel::new(format!("{index}")).narrow(true).widget_instance(), row.cell_widget(PathStep::Element(index))]) - .collect::>(); - - rows.insert(0, column_headings(&["", "element"])); - - vec![LayoutGroup::table(rows, false)] - } -} - impl TableRowLayout for Table { fn type_name() -> &'static str { "Table" @@ -616,6 +579,21 @@ impl TableRowLayout for f64 { } } +impl TableRowLayout for u8 { + fn type_name() -> &'static str { + "Byte" + } + fn identifier(&self) -> String { + format!("{self:02X}") + } + fn cell_widget(&self, _target: PathStep) -> WidgetInstance { + TextLabel::new(self.identifier()).narrow(true).widget_instance() + } + fn element_page(&self, _data: &mut LayoutData) -> Vec { + vec![LayoutGroup::row(vec![self.cell_widget(PathStep::Element(0))])] + } +} + impl TableRowLayout for u32 { fn type_name() -> &'static str { "Number (u32)" @@ -774,6 +752,26 @@ impl TableRowLayout for AlphaBlending { } } +impl TableRowLayout for NodeId { + fn type_name() -> &'static str { + "NodeId" + } + fn identifier(&self) -> String { + format!("Node {self}") + } + fn cell_widget(&self, _target: PathStep) -> WidgetInstance { + let node_id = *self; + TextButton::new("Go to Node") + .tooltip_description("Click to select the node with this ID in the graph.") + .on_update(move |_| NodeGraphMessage::SelectedNodesSet { nodes: vec![node_id] }.into()) + .narrow(true) + .widget_instance() + } + fn element_page(&self, _data: &mut LayoutData) -> Vec { + vec![LayoutGroup::row(vec![self.cell_widget(PathStep::Element(0))])] + } +} + impl TableRowLayout for Option { fn type_name() -> &'static str { "NodeId" @@ -812,8 +810,13 @@ macro_rules! known_table_row_types { Table>, Table, Table, + Table, + Table, + Table, + Table, GradientStops, Color, + NodeId, Option, AlphaBlending, DAffine2, @@ -822,11 +825,11 @@ macro_rules! known_table_row_types { Vec2, Option, f64, + u8, u32, u64, bool, String, - Vec, Vector, Raster, Raster, 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 101577d4c7..207ae4867f 100644 --- a/editor/src/messages/portfolio/document/graph_operation/utility_types.rs +++ b/editor/src/messages/portfolio/document/graph_operation/utility_types.rs @@ -491,8 +491,9 @@ impl<'a> ModifyInputsContext<'a> { self.set_input_with_refresh(input_connector, NodeInput::value(TaggedValue::F64(stroke.join_miter_limit), false), false); let input_connector = InputConnector::node(stroke_node_id, graphene_std::vector::stroke::PaintOrderInput::INDEX); 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::DashLengthsInput::>::INDEX); - self.set_input_with_refresh(input_connector, NodeInput::value(TaggedValue::VecF64(stroke.dash_lengths), false), true); + let input_connector = InputConnector::node(stroke_node_id, graphene_std::vector::stroke::DashLengthsInput::>::INDEX); + let dash_lengths_table = stroke.dash_lengths.into_iter().map(graphene_std::table::TableRow::new_from_element).collect(); + self.set_input_with_refresh(input_connector, NodeInput::value(TaggedValue::F64Table(dash_lengths_table), false), true); let input_connector = InputConnector::node(stroke_node_id, graphene_std::vector::stroke::DashOffsetInput::INDEX); self.set_input_with_refresh(input_connector, NodeInput::value(TaggedValue::F64(stroke.dash_offset), false), true); } @@ -579,7 +580,8 @@ impl<'a> ModifyInputsContext<'a> { let Some(brush_node_id) = self.existing_network_node_id("Brush", true) else { return; }; - self.set_input_with_refresh(InputConnector::node(brush_node_id, 1), NodeInput::value(TaggedValue::BrushStrokes(strokes), false), false); + let strokes_table = strokes.into_iter().map(graphene_std::table::TableRow::new_from_element).collect(); + self.set_input_with_refresh(InputConnector::node(brush_node_id, 1), NodeInput::value(TaggedValue::BrushStrokeTable(strokes_table), false), false); } pub fn resize_artboard(&mut self, location: IVec2, dimensions: IVec2) { 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 6168c5bda5..ea910dd63c 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 @@ -1391,7 +1391,7 @@ fn document_node_definitions() -> HashMap() => text_widget(default_info).into(), Some(x) if x == TypeId::of::() => vec2_widget(default_info, "X", "Y", "", None, false), Some(x) if x == TypeId::of::() => transform_widget(default_info, &mut extra_widgets), - // ========================== - // PRIMITIVE COLLECTION TYPES - // ========================== - Some(x) if x == TypeId::of::>() => 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::>() => array_of_number_widget(default_info, TextInput::default()).into(), 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)), // ============ @@ -776,7 +772,7 @@ pub fn array_of_number_widget(parameter_widgets_info: ParameterWidgetsInfo, text .map(str::parse::) .collect::, _>>() .ok() - .map(TaggedValue::VecF64) + .map(|values| TaggedValue::F64Table(values.into_iter().map(graphene_std::table::TableRow::new_from_element).collect())) }; let Some(document_node) = document_node else { return Vec::new() }; @@ -784,43 +780,11 @@ pub fn array_of_number_widget(parameter_widgets_info: ParameterWidgetsInfo, text log::warn!("A widget failed to be built because its node's input index is invalid."); return vec![]; }; - if let Some(TaggedValue::VecF64(x)) = &input.as_non_exposed_value() { + if let Some(TaggedValue::F64Table(table)) = &input.as_non_exposed_value() { widgets.extend_from_slice(&[ Separator::new(SeparatorStyle::Unrelated).widget_instance(), text_input - .value(x.iter().map(|v| v.to_string()).collect::>().join(", ")) - .on_update(optionally_update_value(move |x: &TextInput| from_string(&x.value), node_id, index)) - .widget_instance(), - ]) - } - widgets -} - -pub fn array_of_vec2_widget(parameter_widgets_info: ParameterWidgetsInfo, text_props: TextInput) -> Vec { - let ParameterWidgetsInfo { document_node, node_id, index, .. } = parameter_widgets_info; - - let mut widgets = start_widgets(parameter_widgets_info); - - let from_string = |string: &str| { - string - .split(|c: char| !c.is_alphanumeric() && !matches!(c, '.' | '+' | '-')) - .filter(|x| !x.is_empty()) - .map(|x| x.parse::().ok()) - .collect::>>() - .map(|numbers| numbers.chunks_exact(2).map(|values| DVec2::new(values[0], values[1])).collect()) - .map(TaggedValue::VecDVec2) - }; - - let Some(document_node) = document_node else { return Vec::new() }; - let Some(input) = document_node.inputs.get(index) else { - log::warn!("A widget failed to be built because its node's input index is invalid."); - return vec![]; - }; - if let Some(TaggedValue::VecDVec2(x)) = &input.as_non_exposed_value() { - widgets.extend_from_slice(&[ - Separator::new(SeparatorStyle::Unrelated).widget_instance(), - text_props - .value(x.iter().map(|v| format!("({}, {})", v.x, v.y)).collect::>().join(", ")) + .value(table.iter_element_values().map(|v| v.to_string()).collect::>().join(", ")) .on_update(optionally_update_value(move |x: &TextInput| from_string(&x.value), node_id, index)) .widget_instance(), ]) @@ -1839,13 +1803,13 @@ pub(crate) fn rectangle_properties(node_id: NodeId, context: &mut NodeProperties }; let uniform_val = match input.as_non_exposed_value() { Some(TaggedValue::F64(x)) => *x, - Some(TaggedValue::F64Array4(x)) => x[0], + Some(TaggedValue::F64Table(table)) => table.iter_element_values().copied().next().unwrap_or(0.), _ => 0., }; let individual_val = match input.as_non_exposed_value() { - Some(&TaggedValue::F64Array4(x)) => x, - Some(&TaggedValue::F64(x)) => [x; 4], - _ => [0.; 4], + Some(&TaggedValue::F64(x)) => vec![x; 4], + Some(TaggedValue::F64Table(table)) => table.iter_element_values().copied().collect(), + _ => vec![0.; 4], }; // Uniform/individual radio input widget @@ -1868,6 +1832,7 @@ pub(crate) fn rectangle_properties(node_id: NodeId, context: &mut NodeProperties ]), }) .on_commit(commit_value); + let individual_val_for_switch = individual_val.clone(); let individual = RadioEntryData::new("Individual") .label("Individual") .on_update(move |_| Message::Batched { @@ -1881,7 +1846,7 @@ pub(crate) fn rectangle_properties(node_id: NodeId, context: &mut NodeProperties NodeGraphMessage::SetInputValue { node_id, input_index: CornerRadiusInput::::INDEX, - value: TaggedValue::F64Array4(individual_val), + value: TaggedValue::F64Table(individual_val_for_switch.iter().copied().map(graphene_std::table::TableRow::new_from_element).collect()), } .into(), ]), @@ -1899,11 +1864,7 @@ pub(crate) fn rectangle_properties(node_id: NodeId, context: &mut NodeProperties .map(str::parse::) .collect::, _>>() .ok() - .map(|v| { - let arr: Box<[f64; 4]> = v.into_boxed_slice().try_into().unwrap_or_default(); - *arr - }) - .map(TaggedValue::F64Array4) + .map(|values| TaggedValue::F64Table(values.into_iter().take(4).map(graphene_std::table::TableRow::new_from_element).collect())) }; TextInput::default() .value(individual_val.iter().map(|v| v.to_string()).collect::>().join(", ")) @@ -2296,11 +2257,10 @@ pub fn stroke_properties(node_id: NodeId, context: &mut NodePropertiesContext) - _ => &StrokeJoin::Miter, }; - let dash_lengths_val = match &document_node.inputs[DashLengthsInput::>::INDEX].as_value() { - Some(TaggedValue::VecF64(x)) => x, - _ => &vec![], + let has_dash_lengths = match &document_node.inputs[DashLengthsInput::>::INDEX].as_value() { + Some(TaggedValue::F64Table(table)) => table.is_empty(), + _ => true, }; - let has_dash_lengths = dash_lengths_val.is_empty(); let miter_limit_disabled = join_value != &StrokeJoin::Miter; let color = color_widget( @@ -2325,7 +2285,7 @@ pub fn stroke_properties(node_id: NodeId, context: &mut NodePropertiesContext) - .property_row(); let disabled_number_input = NumberInput::default().unit(" px").disabled(has_dash_lengths); let dash_lengths = array_of_number_widget( - ParameterWidgetsInfo::new(node_id, DashLengthsInput::>::INDEX, true, context), + ParameterWidgetsInfo::new(node_id, DashLengthsInput::>::INDEX, true, context), TextInput::default().centered(true), ); let number_input = disabled_number_input; 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..b56b72540b 100644 --- a/editor/src/messages/portfolio/document/node_graph/utility_types.rs +++ b/editor/src/messages/portfolio/document/node_graph/utility_types.rs @@ -21,22 +21,14 @@ pub enum FrontendGraphDataType { impl FrontendGraphDataType { pub fn from_type(input: &Type) -> Self { match TaggedValue::from_type_or_none(input) { - TaggedValue::U32(_) - | TaggedValue::U64(_) - | TaggedValue::F32(_) - | TaggedValue::F64(_) - | TaggedValue::DVec2(_) - | TaggedValue::F64Array4(_) - | TaggedValue::VecF64(_) - | TaggedValue::VecDVec2(_) - | TaggedValue::DAffine2(_) => Self::Number, + TaggedValue::U32(_) | TaggedValue::U64(_) | TaggedValue::F32(_) | TaggedValue::F64(_) | TaggedValue::DVec2(_) | TaggedValue::F64Table(_) | TaggedValue::DAffine2(_) => Self::Number, TaggedValue::Artboard(_) => Self::Artboard, TaggedValue::Graphic(_) => Self::Graphic, TaggedValue::Raster(_) => Self::Raster, TaggedValue::Vector(_) => Self::Vector, TaggedValue::Color(_) => Self::Color, TaggedValue::Gradient(_) | TaggedValue::GradientTable(_) => Self::Gradient, - TaggedValue::String(_) | TaggedValue::VecString(_) => Self::Typography, + TaggedValue::String(_) | TaggedValue::StringTable(_) => 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..b32eb9d68b 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 @@ -53,15 +53,9 @@ impl TypeSource { }; match self.compiled_nested_type() { Some(nested_type) => match TaggedValue::from_type_or_none(nested_type) { - TaggedValue::U32(_) - | TaggedValue::U64(_) - | TaggedValue::F32(_) - | TaggedValue::F64(_) - | TaggedValue::DVec2(_) - | TaggedValue::F64Array4(_) - | TaggedValue::VecF64(_) - | TaggedValue::VecDVec2(_) - | TaggedValue::DAffine2(_) => FrontendGraphDataType::Number, + TaggedValue::U32(_) | TaggedValue::U64(_) | TaggedValue::F32(_) | TaggedValue::F64(_) | TaggedValue::DVec2(_) | TaggedValue::F64Table(_) | TaggedValue::DAffine2(_) => { + FrontendGraphDataType::Number + } TaggedValue::Artboard(_) => FrontendGraphDataType::Artboard, TaggedValue::Graphic(_) => FrontendGraphDataType::Graphic, TaggedValue::Raster(_) => FrontendGraphDataType::Raster, diff --git a/editor/src/messages/portfolio/document_migration.rs b/editor/src/messages/portfolio/document_migration.rs index 3173f60183..9d59b2cfd8 100644 --- a/editor/src/messages/portfolio/document_migration.rs +++ b/editor/src/messages/portfolio/document_migration.rs @@ -9,11 +9,9 @@ use glam::{DVec2, IVec2}; use graph_craft::document::DocumentNode; use graph_craft::document::{DocumentNodeImplementation, NodeInput, value::TaggedValue}; use graphene_std::ProtoNodeIdentifier; -use graphene_std::subpath::Subpath; use graphene_std::text::{TextAlign, TypesettingConfig}; use graphene_std::transform::ScaleType; use graphene_std::uuid::NodeId; -use graphene_std::vector::Vector; use graphene_std::vector::style::{PaintOrder, StrokeAlign}; use std::collections::HashMap; use std::f64::consts::PI; @@ -1112,80 +1110,6 @@ fn migrate_node(node_id: &NodeId, node: &DocumentNode, network_path: &[NodeId], document.network_interface.set_input(&InputConnector::node(*node_id, 9), old_inputs[4].clone(), network_path); } - // Upgrade the old "Spline" node to the new "Spline" node - if reference == DefinitionIdentifier::ProtoNode(graphene_std::vector::spline::IDENTIFIER) - || reference == DefinitionIdentifier::ProtoNode(ProtoNodeIdentifier::new("graphene_core::vector::generator_nodes::SplineNode")) - || reference == DefinitionIdentifier::ProtoNode(ProtoNodeIdentifier::new("graphene_core::vector::SplineNode")) - || reference == DefinitionIdentifier::ProtoNode(ProtoNodeIdentifier::new("graphene_core::vector::SplinesFromPointsNode")) - { - // Retrieve the proto node identifier and verify it is the old "Spline" node, otherwise skip it if this is the new "Spline" node - let identifier = document - .network_interface - .implementation(node_id, network_path) - .and_then(|implementation| implementation.get_proto_node()); - if identifier.map(|identifier| identifier.as_str()) != Some("graphene_core::vector::generator_nodes::SplineNode") { - return None; - } - - // Obtain the document node for the given node ID, extract the vector points, and create a Vector path from the list of points - let node = document.network_interface.document_node(node_id, network_path)?; - let Some(TaggedValue::VecDVec2(points)) = node.inputs.get(1).and_then(|tagged_value| tagged_value.as_value()) else { - log::error!("The old Spline node's input at index 1 is not a TaggedValue::VecDVec2"); - return None; - }; - let vector = Vector::from_subpath(Subpath::from_anchors(points.to_vec(), false)); - - // Retrieve the output connectors linked to the "Spline" node's output connector - let Some(spline_outputs) = document.network_interface.outward_wires(network_path)?.get(&OutputConnector::node(*node_id, 0)).cloned() else { - log::error!("Vec of InputConnector Spline node is connected to its output connector 0."); - return None; - }; - - // Get the node's current position in the graph - let Some(node_position) = document.network_interface.position(node_id, network_path) else { - log::error!("Could not get position of spline node."); - return None; - }; - - // Get the "Path" node definition and fill it in with the Vector path and default vector modification - let Some(path_node_type) = resolve_network_node_type("Path") else { - log::error!("Path node does not exist."); - return None; - }; - let modification = Box::new(graphene_std::vector::VectorModification::create_from_vector(&vector)); - let path_node = path_node_type.node_template_input_override([None, Some(NodeInput::value(TaggedValue::VectorModification(modification), false))]); - - // Get the "Spline" node definition and wire it up with the "Path" node as input - let Some(spline_node_type) = resolve_proto_node_type(graphene_std::vector::spline::IDENTIFIER) else { - log::error!("Spline node does not exist."); - return None; - }; - let spline_node = spline_node_type.node_template_input_override([Some(NodeInput::node(NodeId(1), 0))]); - - // Create a new node group with the "Path" and "Spline" nodes and generate new node IDs for them - let nodes = vec![(NodeId(1), path_node), (NodeId(0), spline_node)]; - let new_ids = nodes.iter().map(|(id, _)| (*id, NodeId::new())).collect::>(); - let new_spline_id = *new_ids.get(&NodeId(0))?; - let new_path_id = *new_ids.get(&NodeId(1))?; - - // Remove the old "Spline" node from the document - document.network_interface.delete_nodes(vec![*node_id], false, network_path); - - // Insert the new "Path" and "Spline" nodes into the network interface with generated IDs - document.network_interface.insert_node_group(nodes.clone(), new_ids, network_path); - - // Reposition the new "Spline" node to match the original "Spline" node's position - document.network_interface.shift_node(&new_spline_id, node_position, network_path); - - // Reposition the new "Path" node with an offset relative to the original "Spline" node's position - document.network_interface.shift_node(&new_path_id, node_position + IVec2::new(-7, 0), network_path); - - // Redirect each output connection from the old node to the new "Spline" node's output connector - for input_connector in spline_outputs { - document.network_interface.set_input(&input_connector, NodeInput::node(new_spline_id, 0), network_path); - } - } - // Upgrade Text node to include line height and character spacing, which were previously hardcoded to 1, from https://github.com/GraphiteEditor/Graphite/pull/2016 if reference == DefinitionIdentifier::ProtoNode(graphene_std::text::text::IDENTIFIER) && inputs_count == 8 { let mut template: NodeTemplate = resolve_document_node_type(&reference)?.default_node_template(); diff --git a/editor/src/messages/tool/tool_messages/brush_tool.rs b/editor/src/messages/tool/tool_messages/brush_tool.rs index 5319e21aa4..81cd8ad6f2 100644 --- a/editor/src/messages/tool/tool_messages/brush_tool.rs +++ b/editor/src/messages/tool/tool_messages/brush_tool.rs @@ -321,8 +321,8 @@ impl BrushToolData { if reference == DefinitionIdentifier::Network("Brush".into()) && node_id != layer.to_node() { let points_input = node.inputs.get(1)?; - let Some(TaggedValue::BrushStrokes(strokes)) = points_input.as_value() else { continue }; - self.strokes.clone_from(strokes); + let Some(TaggedValue::BrushStrokeTable(strokes)) = points_input.as_value() else { continue }; + self.strokes = strokes.iter_element_values().cloned().collect(); return Some(layer); } diff --git a/node-graph/graph-craft/src/document.rs b/node-graph/graph-craft/src/document.rs index 08bc7c44d1..d83062752f 100644 --- a/node-graph/graph-craft/src/document.rs +++ b/node-graph/graph-craft/src/document.rs @@ -216,7 +216,7 @@ pub enum DocumentNodeMetadata { impl DocumentNodeMetadata { pub fn ty(&self) -> Type { match self { - DocumentNodeMetadata::DocumentNodePath => concrete!(Vec), + DocumentNodeMetadata::DocumentNodePath => concrete!(core_types::table::Table), } } } @@ -930,7 +930,10 @@ impl NodeNetwork { let (tagged_value, exposed) = match previous_export { NodeInput::Value { tagged_value, exposed } => (tagged_value, exposed), NodeInput::Reflection(reflect) => match reflect { - DocumentNodeMetadata::DocumentNodePath => (TaggedValue::NodePath(path.to_vec()).into(), false), + DocumentNodeMetadata::DocumentNodePath => { + let table: core_types::table::Table = path.iter().copied().map(core_types::table::TableRow::new_from_element).collect(); + (TaggedValue::NodeIdTable(table).into(), false) + } }, previous_export => { *export = previous_export; diff --git a/node-graph/graph-craft/src/document/value.rs b/node-graph/graph-craft/src/document/value.rs index ec71d987fb..fedd24e09c 100644 --- a/node-graph/graph-craft/src/document/value.rs +++ b/node-graph/graph-craft/src/document/value.rs @@ -4,28 +4,27 @@ use crate::proto::{Any as DAny, FutureAny}; use brush_nodes::brush_cache::BrushCache; use brush_nodes::brush_stroke::BrushStroke; use core_types::table::Table; +use core_types::transform::Footprint; use core_types::uuid::NodeId; use core_types::{CacheHash, Color, ContextFeatures, MemoHash, Node, Type}; use dyn_any::DynAny; pub use dyn_any::StaticType; use glam::{Affine2, Vec2}; pub use glam::{DAffine2, DVec2, IVec2, UVec2}; -use graphic_types::Artboard; -use graphic_types::Graphic; -use graphic_types::Vector; -use graphic_types::raster_types::Image; -use graphic_types::raster_types::{CPU, Raster}; -use graphic_types::vector_types::vector; -use graphic_types::vector_types::vector::ReferencePoint; -use graphic_types::vector_types::vector::style::Fill; -use graphic_types::vector_types::vector::style::GradientStops; +use graphic_types::raster_types::{CPU, Image, Raster}; +use graphic_types::vector_types::vector::style::{Fill, Gradient, GradientStops, Stroke}; +use graphic_types::vector_types::vector::{self, ReferencePoint}; +use graphic_types::{Artboard, Graphic, Vector}; +use raster_nodes::curve::Curve; use rendering::RenderMetadata; use std::fmt::Display; use std::hash::Hash; use std::marker::PhantomData; use std::str::FromStr; pub use std::sync::Arc; +use text_nodes::Font; use text_nodes::vector_types::GradientStop; +use vector::VectorModification; pub struct TaggedValueTypeError; @@ -166,27 +165,14 @@ macro_rules! tagged_value { } tagged_value! { - // =============== - // PRIMITIVE TYPES - // =============== - F32(f32), - F64(f64), - U32(u32), - U64(u64), - Bool(bool), - String(String), - // ======================== - // LISTS OF PRIMITIVE TYPES - // ======================== - #[serde(alias = "VecF32")] // TODO: Eventually remove this alias document upgrade code - VecF64(Vec), - VecDVec2(Vec), - F64Array4([f64; 4]), - VecString(Vec), - NodePath(Vec), // =========== // TABLE TYPES // =========== + StringTable(Table), + #[serde(deserialize_with = "core_types::misc::migrate_vec_f64_to_table")] // TODO: Eventually remove this migration document upgrade code + #[serde(alias = "VecF64", alias = "VecF32", alias = "F64Array4")] + F64Table(Table), + NodeIdTable(Table), #[serde(deserialize_with = "graphic_types::migrations::migrate_vector")] // TODO: Eventually remove this migration document upgrade code #[serde(alias = "VectorData")] Vector(Table), @@ -205,24 +191,32 @@ tagged_value! { #[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), + #[serde(deserialize_with = "brush_nodes::migrations::migrate_brush_strokes_to_table")] // TODO: Eventually remove this migration document upgrade code + #[serde(alias = "BrushStrokes")] + BrushStrokeTable(Table), // ============ - // STRUCT TYPES + // SCALAR TYPES // ============ + F32(f32), + F64(f64), + U32(u32), + U64(u64), + Bool(bool), + String(String), FVec2(Vec2), FAffine2(Affine2), #[serde(alias = "IVec2", alias = "UVec2")] DVec2(DVec2), DAffine2(DAffine2), - Stroke(graphic_types::vector_types::vector::style::Stroke), - Gradient(graphic_types::vector_types::vector::style::Gradient), - Font(text_nodes::Font), - BrushStrokes(Vec), + Stroke(Stroke), + Gradient(Gradient), + Font(Font), BrushCache(BrushCache), DocumentNode(DocumentNode), ContextFeatures(ContextFeatures), - Curve(raster_nodes::curve::Curve), - Footprint(core_types::transform::Footprint), - VectorModification(Box), + Curve(Curve), + Footprint(Footprint), + VectorModification(Box), ImageData(Image), // ========== // ENUM TYPES diff --git a/node-graph/graph-craft/src/proto.rs b/node-graph/graph-craft/src/proto.rs index 7e3250590a..3a3b55ae41 100644 --- a/node-graph/graph-craft/src/proto.rs +++ b/node-graph/graph-craft/src/proto.rs @@ -951,7 +951,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(2791689253855410677), NodeId(11246167042277902310), NodeId(1014827049498980779), NodeId(4864562752646903491)] + vec![NodeId(12189222519765806511), NodeId(15012204941197567462), NodeId(15525229164021892418), NodeId(1252248957706694248)] ); } diff --git a/node-graph/interpreted-executor/src/node_registry.rs b/node-graph/interpreted-executor/src/node_registry.rs index d68dc5d0cc..d2d03335ac 100644 --- a/node-graph/interpreted-executor/src/node_registry.rs +++ b/node-graph/interpreted-executor/src/node_registry.rs @@ -80,7 +80,6 @@ fn node_registry() -> HashMap, input: Context, fn_params: [Context => f64]), async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => u32]), async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => u64]), - async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => Vec]), async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => BlendMode]), async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => ImageTexture]), async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => graphene_std::transform::ReferencePoint]), @@ -92,19 +91,18 @@ fn node_registry() -> HashMap, input: Context, fn_params: [Context => graphene_std::vector::style::StrokeAlign]), async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => graphene_std::vector::style::Stroke]), async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => graphene_std::vector::style::Gradient]), - async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => Vec]), async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => Box]), async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => graphene_std::vector::misc::CentroidType]), async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => graphene_std::vector::misc::PointSpacingType]), async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => Option]), async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => Option]), - async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => Vec]), - async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => Vec]), - async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => [f64; 4]]), - async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => Vec]), + async_node!(graphene_core::memo::MonitorNode<_, _, _>, 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 => Table]), + async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => Table]), async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => Graphic]), async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => graphene_std::text::Font]), - async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => Vec]), + async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => Table]), async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => BrushCache]), async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => DocumentNode]), async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => graphene_std::raster::curve::Curve]), @@ -155,11 +153,10 @@ fn node_registry() -> HashMap, 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 => 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]), - async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => Vec]), - async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => Vec]), - async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => Vec]), + async_node!(graphene_core::memo::MemoNode<_, _>, 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 => Table]), + async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => Table]), #[cfg(target_family = "wasm")] async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => CanvasHandle]), async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => f64]), @@ -177,14 +174,13 @@ fn node_registry() -> HashMap, input: Context, fn_params: [Context => Option]), async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => Option]), async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => Option]), - async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => [f64; 4]]), async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => Graphic]), async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => glam::f32::Vec2]), async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => glam::f32::Affine2]), async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => graphene_std::vector::style::Stroke]), async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => graphene_std::vector::style::Gradient]), async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => graphene_std::text::Font]), - async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => Vec]), + async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => Table]), async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => BrushCache]), async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => DocumentNode]), async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => graphene_std::ContextFeatures]), diff --git a/node-graph/libraries/core-types/src/misc.rs b/node-graph/libraries/core-types/src/misc.rs index 8ec42702a4..6857b75e69 100644 --- a/node-graph/libraries/core-types/src/misc.rs +++ b/node-graph/libraries/core-types/src/misc.rs @@ -87,3 +87,21 @@ pub fn migrate_color<'de, D: serde::Deserializer<'de>>(deserializer: D) -> Resul ColorFormat::ColorTable(color_table) => color_table, }) } + +// TODO: Eventually remove this migration document upgrade code +pub fn migrate_vec_f64_to_table<'de, D: serde::Deserializer<'de>>(deserializer: D) -> Result, D::Error> { + use crate::table::{Table, TableRow}; + use serde::Deserialize; + + #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] + #[cfg_attr(feature = "serde", serde(untagged))] + enum F64TableFormat { + VecF64(Vec), + F64Table(Table), + } + + Ok(match F64TableFormat::deserialize(deserializer)? { + F64TableFormat::VecF64(values) => values.into_iter().map(TableRow::new_from_element).collect(), + F64TableFormat::F64Table(table) => table, + }) +} diff --git a/node-graph/nodes/brush/src/brush.rs b/node-graph/nodes/brush/src/brush.rs index 1a8f58886e..0e18ba94d6 100644 --- a/node-graph/nodes/brush/src/brush.rs +++ b/node-graph/nodes/brush/src/brush.rs @@ -192,7 +192,7 @@ async fn brush( /// Optional raster content that may be drawn onto. mut image: Table>, /// The list of brush stroke paths drawn by the Brush tool, with each including both its coordinates and styles. - strokes: Vec, + strokes: Table, /// Internal cache data used to accelerate rendering of the brush content. cache: BrushCache, ) -> Table> { @@ -205,11 +205,15 @@ async fn brush( let bounds = Table::new_from_row(table_row.clone()).bounding_box(DAffine2::IDENTITY, false); let [start, end] = if let RenderBoundingBox::Rectangle(rect) = bounds { rect } else { [DVec2::ZERO, DVec2::ZERO] }; let image_bbox = AxisAlignedBbox { start, end }; - let stroke_bbox = strokes.iter().map(|s| s.bounding_box()).reduce(|a, b| a.union(&b)).unwrap_or(AxisAlignedBbox::ZERO); + let stroke_bbox = strokes.iter_element_values().map(|s| s.bounding_box()).reduce(|a, b| a.union(&b)).unwrap_or(AxisAlignedBbox::ZERO); let bbox = if image_bbox.size().length() < 0.1 { stroke_bbox } else { stroke_bbox.union(&image_bbox) }; let background_bounds = bbox.to_transform(); - let mut draw_strokes: Vec<_> = strokes.iter().filter(|&s| !matches!(s.style.blend_mode, BlendMode::Erase | BlendMode::Restore)).cloned().collect(); + let mut draw_strokes: Vec<_> = strokes + .iter_element_values() + .filter(|&s| !matches!(s.style.blend_mode, BlendMode::Erase | BlendMode::Restore)) + .cloned() + .collect(); let mut brush_plan = cache.compute_brush_plan(table_row, &draw_strokes); @@ -273,12 +277,12 @@ async fn brush( actual_image = blend_with_mode(actual_image, stroke_texture, stroke.style.blend_mode, (stroke.style.color.a() * 100.) as f64); } - let has_erase_or_restore_strokes = strokes.iter().any(|s| matches!(s.style.blend_mode, BlendMode::Erase | BlendMode::Restore)); + let has_erase_or_restore_strokes = strokes.iter_element_values().any(|s| matches!(s.style.blend_mode, BlendMode::Erase | BlendMode::Restore)); if has_erase_or_restore_strokes { let opaque_image = Image::new(bbox.size().x as u32, bbox.size().y as u32, Color::WHITE); let mut erase_restore_mask = TableRow::new_from_element(Raster::new_cpu(opaque_image)).with_attribute("transform", background_bounds); - for stroke in strokes { + for stroke in strokes.into_iter().map(|row| row.into_element()) { let mut brush_texture = cache.get_cached_brush(&stroke.style); if brush_texture.is_none() { let tex = create_brush_texture(&stroke.style).await; diff --git a/node-graph/nodes/brush/src/lib.rs b/node-graph/nodes/brush/src/lib.rs index 76fc7348d2..f44e511083 100644 --- a/node-graph/nodes/brush/src/lib.rs +++ b/node-graph/nodes/brush/src/lib.rs @@ -1,3 +1,25 @@ pub mod brush; pub mod brush_cache; pub mod brush_stroke; + +pub mod migrations { + use crate::brush_stroke::BrushStroke; + use core_types::table::{Table, TableRow}; + + // TODO: Eventually remove this migration document upgrade code + pub fn migrate_brush_strokes_to_table<'de, D: serde::Deserializer<'de>>(deserializer: D) -> Result, D::Error> { + use serde::Deserialize; + + #[derive(serde::Serialize, serde::Deserialize)] + #[serde(untagged)] + enum BrushStrokeTableFormat { + BrushStrokes(Vec), + BrushStrokeTable(Table), + } + + Ok(match BrushStrokeTableFormat::deserialize(deserializer)? { + BrushStrokeTableFormat::BrushStrokes(strokes) => strokes.into_iter().map(TableRow::new_from_element).collect(), + BrushStrokeTableFormat::BrushStrokeTable(table) => table, + }) + } +} diff --git a/node-graph/nodes/gcore/src/animation.rs b/node-graph/nodes/gcore/src/animation.rs index 5b96eedb5f..a5011e7ca7 100644 --- a/node-graph/nodes/gcore/src/animation.rs +++ b/node-graph/nodes/gcore/src/animation.rs @@ -73,8 +73,6 @@ async fn quantize_real_time( Context -> DAffine2, Context -> Footprint, Context -> DVec2, - Context -> Vec, - Context -> Vec, Context -> Table, Context -> Table, Context -> Table>, @@ -82,6 +80,8 @@ async fn quantize_real_time( Context -> Table, Context -> Table, Context -> Table, + Context -> Table, + Context -> Table, Context -> (), )] value: impl Node<'n, Context<'static>, Output = T>, @@ -113,8 +113,6 @@ async fn quantize_animation_time( Context -> DAffine2, Context -> Footprint, Context -> DVec2, - Context -> Vec, - Context -> Vec, Context -> Table, Context -> Table, Context -> Table>, @@ -122,6 +120,8 @@ async fn quantize_animation_time( Context -> Table, Context -> Table, Context -> Table, + Context -> Table, + Context -> Table, Context -> (), )] value: impl Node<'n, Context<'static>, Output = T>, diff --git a/node-graph/nodes/gcore/src/context_modification.rs b/node-graph/nodes/gcore/src/context_modification.rs index 0bd736f269..8e22154e3f 100644 --- a/node-graph/nodes/gcore/src/context_modification.rs +++ b/node-graph/nodes/gcore/src/context_modification.rs @@ -26,11 +26,11 @@ async fn context_modification( Context -> DAffine2, Context -> Footprint, Context -> DVec2, - Context -> Vec, Context -> Option, - Context -> Vec, - Context -> Vec, - Context -> Vec, + Context -> Table, + Context -> Table, + Context -> Table, + Context -> Table, Context -> Table, Context -> Table, Context -> Table>, diff --git a/node-graph/nodes/graphic/src/graphic.rs b/node-graph/nodes/graphic/src/graphic.rs index 22cb00782f..186595d5d8 100644 --- a/node-graph/nodes/graphic/src/graphic.rs +++ b/node-graph/nodes/graphic/src/graphic.rs @@ -16,11 +16,6 @@ pub fn index_elements( _: impl Ctx, /// The collection of data, such as a list or table. #[implementations( - Vec, - Vec, - Vec, - Vec, - Vec, Table, Table, Table, @@ -28,6 +23,10 @@ pub fn index_elements( Table>, Table, Table, + Table, + Table, + Table, + Table, )] collection: T, /// The index of the item to retrieve, starting from 0 for the first item. Negative indices count backwards from the end of the collection, starting from -1 for the last item. @@ -53,11 +52,7 @@ pub fn omit_element( _: impl Ctx, /// The collection of data, such as a list or table. #[implementations( - Vec, - Vec, - Vec, - Vec, - Vec, + Table, Table, Table, Table, @@ -181,9 +176,10 @@ where /// Used as the value source for stamping the `editor:layer` attribute on each row of a layer's output, /// which lets editor tools (e.g. selection, click target routing) trace data back to its owning layer. #[node_macro::node(category(""))] -pub fn parent_layer(_: impl Ctx, node_path: Vec) -> Option { +pub fn parent_layer(_: impl Ctx, node_path: Table) -> Option { // Get the penultimate element of the node path, or None if the path is too short - node_path.get(node_path.len().wrapping_sub(2)).copied() + let index = node_path.len().wrapping_sub(2); + node_path.element(index).copied() } /// Writes a per-row attribute column on the input table. The value-producing input is evaluated once per row, @@ -208,13 +204,13 @@ async fn write_attribute f64, Context -> u32, Context -> bool, Context -> String, Context -> Vec, Context -> DVec2, Context -> DAffine2, Context -> Option, Context -> Table, Context -> Table, - Context -> f64, Context -> u32, Context -> bool, Context -> String, Context -> Vec, Context -> DVec2, Context -> DAffine2, Context -> Option, Context -> Table, Context -> Table, - Context -> f64, Context -> u32, Context -> bool, Context -> String, Context -> Vec, Context -> DVec2, Context -> DAffine2, Context -> Option, Context -> Table, Context -> Table, - Context -> f64, Context -> u32, Context -> bool, Context -> String, Context -> Vec, Context -> DVec2, Context -> DAffine2, Context -> Option, Context -> Table, Context -> Table, - Context -> f64, Context -> u32, Context -> bool, Context -> String, Context -> Vec, Context -> DVec2, Context -> DAffine2, Context -> Option, Context -> Table, Context -> Table, - Context -> f64, Context -> u32, Context -> bool, Context -> String, Context -> Vec, Context -> DVec2, Context -> DAffine2, Context -> Option, Context -> Table, Context -> Table, - Context -> f64, Context -> u32, Context -> bool, Context -> String, Context -> Vec, Context -> DVec2, Context -> DAffine2, Context -> Option, Context -> Table, Context -> Table, + Context -> f64, Context -> u32, Context -> bool, Context -> String, Context -> Table, Context -> DVec2, Context -> DAffine2, Context -> Option, Context -> Table, Context -> Table, + Context -> f64, Context -> u32, Context -> bool, Context -> String, Context -> Table, Context -> DVec2, Context -> DAffine2, Context -> Option, Context -> Table, Context -> Table, + Context -> f64, Context -> u32, Context -> bool, Context -> String, Context -> Table, Context -> DVec2, Context -> DAffine2, Context -> Option, Context -> Table, Context -> Table, + Context -> f64, Context -> u32, Context -> bool, Context -> String, Context -> Table, Context -> DVec2, Context -> DAffine2, Context -> Option, Context -> Table, Context -> Table, + Context -> f64, Context -> u32, Context -> bool, Context -> String, Context -> Table, Context -> DVec2, Context -> DAffine2, Context -> Option, Context -> Table, Context -> Table, + Context -> f64, Context -> u32, Context -> bool, Context -> String, Context -> Table, Context -> DVec2, Context -> DAffine2, Context -> Option, Context -> Table, Context -> Table, + Context -> f64, Context -> u32, Context -> bool, Context -> String, Context -> Table, Context -> DVec2, Context -> DAffine2, Context -> Option, Context -> Table, Context -> Table, )] value: impl Node<'n, Context<'static>, Output = U>, ) -> Table { @@ -255,11 +251,14 @@ pub async fn legacy_layer_extend( #[expose] #[implementations(Table, Table, Table, Table>, Table>, Table, Table)] new: Table, - nested_node_path: Vec, + nested_node_path: Table, ) -> Table { // Get the penultimate element of the node path, or None if the path is too short // This is used to get the ID of the user-facing parent layer-style node (which encapsulates this internal node). - let layer = nested_node_path.get(nested_node_path.len().wrapping_sub(2)).copied(); + let layer = { + let index = nested_node_path.len().wrapping_sub(2); + nested_node_path.element(index).copied() + }; let mut base = base; for mut row in new.into_iter() { diff --git a/node-graph/nodes/gstd/src/platform_application_io.rs b/node-graph/nodes/gstd/src/platform_application_io.rs index 78fbecdaf9..5ad29c214f 100644 --- a/node-graph/nodes/gstd/src/platform_application_io.rs +++ b/node-graph/nodes/gstd/src/platform_application_io.rs @@ -6,7 +6,7 @@ use canvas_utils::{Canvas, CanvasHandle}; use core_types::WasmNotSend; #[cfg(target_family = "wasm")] use core_types::math::bbox::Bbox; -use core_types::table::Table; +use core_types::table::{Table, TableRow}; #[cfg(target_family = "wasm")] use core_types::transform::Footprint; use core_types::{Color, Ctx}; @@ -85,14 +85,15 @@ async fn post_request( #[name("URL")] url: String, /// The binary data to include in the body of the POST request. - body: Vec, + body: Table, /// Makes the request run in the background without waiting on a response. This is useful for triggering webhooks without blocking the continued execution of the graph. discard_result: bool, #[widget(ParsedWidgetOverride::Custom = "text_area")] headers: String, ) -> String { let mut header_map = parse_headers(&headers); header_map.insert("Content-Type", "application/octet-stream".parse().unwrap()); - let request = reqwest::Client::new().post(url).body(body).headers(header_map); + let body_bytes: Vec = body.iter_element_values().copied().collect(); + let request = reqwest::Client::new().post(url).body(body_bytes).headers(header_map); if discard_result { #[cfg(target_family = "wasm")] @@ -114,15 +115,15 @@ async fn post_request( /// Converts a text string to raw binary data. Useful for transmission over HTTP or writing to files. #[node_macro::node(category("Web Request"), name("String to Bytes"))] -fn string_to_bytes(_: impl Ctx, string: String) -> Vec { - string.into_bytes() +fn string_to_bytes(_: impl Ctx, string: String) -> Table { + string.into_bytes().into_iter().map(TableRow::new_from_element).collect() } /// Converts extracted raw RGBA pixel data from an input image. Each pixel becomes 4 sequential bytes. Useful for transmission over HTTP or writing to files. #[node_macro::node(category("Web Request"), name("Image to Bytes"))] -fn image_to_bytes(_: impl Ctx, image: Table>) -> Vec { - let Some(image) = image.element(0) else { return vec![] }; - image.data.iter().flat_map(|color| color.to_rgba8_srgb().into_iter()).collect::>() +fn image_to_bytes(_: impl Ctx, image: Table>) -> Table { + let Some(image) = image.element(0) else { return Table::new() }; + image.data.iter().flat_map(|color| color.to_rgba8_srgb()).map(TableRow::new_from_element).collect() } /// Loads binary from URLs and local asset paths. Returns a transparent placeholder if the resource fails to load, allowing rendering to continue. @@ -188,7 +189,6 @@ async fn rasterize( where Table: Render + Clone + graphic_types::IntoGraphicTable, { - use core_types::table::TableRow; use glam::{DAffine2, DVec2}; if footprint.transform.matrix2.determinant() == 0. { diff --git a/node-graph/nodes/text/src/json.rs b/node-graph/nodes/text/src/json.rs index c87a48c6a5..c1f1a7d3ce 100644 --- a/node-graph/nodes/text/src/json.rs +++ b/node-graph/nodes/text/src/json.rs @@ -1,4 +1,5 @@ use core_types::Ctx; +use core_types::table::{Table, TableRow}; use serde_json::Value; use crate::unescape_string; @@ -237,15 +238,15 @@ fn query_json_all( /// Strips the surrounding double quotes from string values, returning the raw text. Other types are never wrapped in quotes. #[default(true)] unquote_strings: bool, -) -> Vec { +) -> Table { let cleaned = strip_trailing_commas(&json); - let Ok(value): Result = serde_json::from_str(&cleaned) else { return Vec::new() }; - let Some(segments) = parse_json_path(path.trim()) else { return Vec::new() }; + let Ok(value): Result = serde_json::from_str(&cleaned) else { return Table::new() }; + let Some(segments) = parse_json_path(path.trim()) else { return Table::new() }; let mut results = Vec::new(); resolve_all(&value, &segments, !unquote_strings, &mut results); - results + results.into_iter().map(TableRow::new_from_element).collect() } /// A parsed segment of a JSON access path. diff --git a/node-graph/nodes/text/src/lib.rs b/node-graph/nodes/text/src/lib.rs index 50c039887b..eb57c9e5fe 100644 --- a/node-graph/nodes/text/src/lib.rs +++ b/node-graph/nodes/text/src/lib.rs @@ -9,7 +9,7 @@ use convert_case::{Boundary, Converter, pattern}; use core_types::Color; use core_types::graphene_hash::CacheHash; use core_types::registry::types::{SignedInteger, TextArea}; -use core_types::table::Table; +use core_types::table::{Table, TableRow}; use core_types::{CloneVarArgs, Context, Ctx, ExtractAll, ExtractVarArgs, OwnedContextImpl}; use dyn_any::DynAny; use glam::{DAffine2, DVec2}; @@ -700,10 +700,10 @@ fn string_split( /// "\n" (newline), "\r" (carriage return), "\t" (tab), "\0" (null), and "\\" (backslash). #[default(true)] delimiter_escaping: bool, -) -> Vec { +) -> Table { let delimiter = if delimiter_escaping { unescape_string(delimiter) } else { delimiter }; - string.split(&delimiter).map(str::to_string).collect() + string.split(&delimiter).map(str::to_string).map(TableRow::new_from_element).collect() } /// Joins a list of strings together with a separator between each pair. This is the inverse of the **String Split** node. @@ -713,7 +713,7 @@ fn string_split( fn string_join( _: impl Ctx, /// The list of strings to join together. - strings: Vec, + strings: Table, /// The text placed between each pair of strings. #[default(", ")] separator: String, @@ -724,26 +724,27 @@ fn string_join( ) -> String { let separator = if separator_escaping { unescape_string(separator) } else { separator }; - strings.join(&separator) + strings.iter_element_values().map(|s| s.as_str()).collect::>().join(&separator) } /// Iterates over a list of strings, evaluating the mapped operation for each one. Use the **Read String** node to access the current string inside the loop. #[node_macro::node(category("Text"))] async fn map_string( ctx: impl Ctx + CloneVarArgs + ExtractAll, - strings: Vec, + strings: Table, #[expose] #[implementations(Context -> String)] mapped: impl Node, Output = String>, -) -> Vec { - let mut result = Vec::new(); +) -> Table { + let mut result = Table::new(); - for (i, string) in strings.into_iter().enumerate() { + for (i, row) in strings.into_iter().enumerate() { + let string = row.into_element(); let owned_ctx = OwnedContextImpl::from(ctx.clone()); let owned_ctx = owned_ctx.with_vararg(Box::new(string)).with_index(i); - let mapped_strings = mapped.eval(owned_ctx.into_context()).await; + let mapped_string = mapped.eval(owned_ctx.into_context()).await; - result.push(mapped_strings); + result.push(TableRow::new_from_element(mapped_string)); } result diff --git a/node-graph/nodes/text/src/regex.rs b/node-graph/nodes/text/src/regex.rs index 4fe1373ade..72b3739f83 100644 --- a/node-graph/nodes/text/src/regex.rs +++ b/node-graph/nodes/text/src/regex.rs @@ -1,5 +1,6 @@ use core_types::Ctx; use core_types::registry::types::SignedInteger; +use core_types::table::{Table, TableRow}; /// Checks whether the string contains a match for the given regular expression pattern. Optionally restricts the match to only the start and/or end of the string. #[node_macro::node(category("Text: Regex"))] @@ -92,9 +93,9 @@ fn regex_find( case_insensitive: bool, /// Make `^` and `$` match the start and end of each line, not just the whole string. multiline: bool, -) -> Vec { +) -> Table { if pattern.is_empty() { - return Vec::new(); + return Table::new(); } let flags = match (case_insensitive, multiline) { @@ -107,7 +108,7 @@ fn regex_find( let Ok(regex) = fancy_regex::Regex::new(&full_pattern) else { log::error!("Invalid regex pattern: {pattern}"); - return Vec::new(); + return Table::new(); }; // Collect all matches since we need to support negative indexing @@ -117,7 +118,7 @@ fn regex_find( let resolved_index = if match_index < 0 { let from_end = (-match_index) as usize; if from_end > matches.len() { - return Vec::new(); + return Table::new(); } matches.len() - from_end } else { @@ -125,11 +126,14 @@ fn regex_find( }; let Some(captures) = matches.get(resolved_index) else { - return Vec::new(); + return Table::new(); }; // Index 0 is the whole match, 1+ are capture groups - (0..captures.len()).map(|i| captures.get(i).map_or(String::new(), |m| m.as_str().to_string())).collect() + (0..captures.len()) + .map(|i| captures.get(i).map_or(String::new(), |m| m.as_str().to_string())) + .map(TableRow::new_from_element) + .collect() } /// Finds all non-overlapping matches of a regular expression pattern in the string, returning a list of the matched substrings. @@ -144,9 +148,9 @@ fn regex_find_all( case_insensitive: bool, /// Make `^` and `$` match the start and end of each line, not just the whole string. multiline: bool, -) -> Vec { +) -> Table { if pattern.is_empty() { - return Vec::new(); + return Table::new(); } let flags = match (case_insensitive, multiline) { @@ -159,10 +163,15 @@ fn regex_find_all( let Ok(regex) = fancy_regex::Regex::new(&full_pattern) else { log::error!("Invalid regex pattern: {pattern}"); - return Vec::new(); + return Table::new(); }; - regex.find_iter(&string).filter_map(|m| m.ok()).map(|m| m.as_str().to_string()).collect() + regex + .find_iter(&string) + .filter_map(|m| m.ok()) + .map(|m| m.as_str().to_string()) + .map(TableRow::new_from_element) + .collect() } /// Splits a string into a list of substrings pulled from between separator characters as matched by a regular expression. @@ -179,9 +188,9 @@ fn regex_split( case_insensitive: bool, /// Make `^` and `$` match the start and end of each line, not just the whole string. multiline: bool, -) -> Vec { +) -> Table { if pattern.is_empty() { - return vec![string]; + return Table::new_from_element(string); } let flags = match (case_insensitive, multiline) { @@ -194,8 +203,8 @@ fn regex_split( let Ok(regex) = fancy_regex::Regex::new(&full_pattern) else { log::error!("Invalid regex pattern: {pattern}"); - return vec![string]; + return Table::new_from_element(string); }; - regex.split(&string).filter_map(|s| s.ok()).map(|s| s.to_string()).collect() + regex.split(&string).filter_map(|s| s.ok()).map(|s| s.to_string()).map(TableRow::new_from_element).collect() } diff --git a/node-graph/nodes/vector/src/generator_nodes.rs b/node-graph/nodes/vector/src/generator_nodes.rs index 37bcbf5f30..bb4f304559 100644 --- a/node-graph/nodes/vector/src/generator_nodes.rs +++ b/node-graph/nodes/vector/src/generator_nodes.rs @@ -18,22 +18,37 @@ impl CornerRadius for f64 { Table::new_from_element(Vector::from_subpath(subpath::Subpath::new_rounded_rectangle(size / -2., size / 2., [clamped_radius; 4]))) } } -impl CornerRadius for [f64; 4] { +impl CornerRadius for Table { fn generate(self, size: DVec2, clamped: bool) -> Table { + // Expand to four corners using the CSS `border-radius` shorthand rules. + // - `[a]` → `[a, a, a, a]` + // - `[a, b]` → `[a, b, a, b]` + // - `[a, b, c]` → `[a, b, c, b]` + // - `[a, b, c, d, …]` → `[a, b, c, d]` + // - `[]` → `[0, 0, 0, 0]` + let values: Vec = self.iter_element_values().copied().collect(); + let radii: [f64; 4] = match values.as_slice() { + [] => [0., 0., 0., 0.], + &[a] => [a, a, a, a], + &[a, b] => [a, b, a, b], + &[a, b, c] => [a, b, c, b], + &[a, b, c, d, ..] => [a, b, c, d], + }; + let clamped_radius = if clamped { // Algorithm follows the CSS spec: let mut scale_factor: f64 = 1.; for i in 0..4 { let side_length = if i % 2 == 0 { size.x } else { size.y }; - let adjacent_corner_radius_sum = self[i] + self[(i + 1) % 4]; + let adjacent_corner_radius_sum = radii[i] + radii[(i + 1) % 4]; if side_length < adjacent_corner_radius_sum { scale_factor = scale_factor.min(side_length / adjacent_corner_radius_sum); } } - self.map(|x| x * scale_factor) + radii.map(|x| x * scale_factor) } else { - self + radii }; Table::new_from_element(Vector::from_subpath(subpath::Subpath::new_rounded_rectangle(size / -2., size / 2., clamped_radius))) } @@ -140,7 +155,7 @@ fn rectangle( #[default(100)] height: f64, _individual_corner_radii: bool, // TODO: Move this to the bottom once we have a migration capability - #[implementations(f64, [f64; 4])] corner_radius: T, + #[implementations(f64, Table)] corner_radius: T, #[default(true)] clamped: bool, ) -> Table { corner_radius.generate(DVec2::new(width, height), clamped) diff --git a/node-graph/nodes/vector/src/vector_modification_nodes.rs b/node-graph/nodes/vector/src/vector_modification_nodes.rs index 1f0150c37f..f305d5066b 100644 --- a/node-graph/nodes/vector/src/vector_modification_nodes.rs +++ b/node-graph/nodes/vector/src/vector_modification_nodes.rs @@ -7,7 +7,7 @@ use vector_types::vector::VectorModification; /// Applies a differential modification to a vector path, associating changes made by the Pen and Path tools to indices of edited points and segments. #[node_macro::node(category(""))] -async fn path_modify(_ctx: impl Ctx, mut vector: Table, modification: Box, node_path: Vec) -> Table { +async fn path_modify(_ctx: impl Ctx, mut vector: Table, modification: Box, node_path: Table) -> Table { use core_types::table::TableRow; if vector.is_empty() { @@ -15,8 +15,11 @@ async fn path_modify(_ctx: impl Ctx, mut vector: Table, modification: Bo } modification.apply(vector.element_mut(0).expect("push should give one item")); - // Update the source node id - let this_node_path = node_path.iter().rev().nth(1).copied(); + // Update the source node id (penultimate element in the path, identifying the user-facing layer node) + let this_node_path = { + let index = node_path.len().wrapping_sub(2); + node_path.element(index).copied() + }; let existing: Option = vector.attribute_cloned_or_default("editor:layer", 0); vector.set_attribute("editor:layer", 0, existing.or(this_node_path)); diff --git a/node-graph/nodes/vector/src/vector_nodes.rs b/node-graph/nodes/vector/src/vector_nodes.rs index d6dc5bfa39..465a66105e 100644 --- a/node-graph/nodes/vector/src/vector_nodes.rs +++ b/node-graph/nodes/vector/src/vector_nodes.rs @@ -179,9 +179,9 @@ impl IntoF64Vec for f64 { vec![self] } } -impl IntoF64Vec for Vec { +impl IntoF64Vec for Table { fn into_vec(self) -> Vec { - self + self.into_iter().map(|row| row.into_element()).collect() } } impl IntoF64Vec for String { @@ -217,7 +217,7 @@ 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(Table, f64, String, Table, f64, String)] dash_lengths: L, /// The phase offset distance from the starting point of the dash pattern. #[unit(" px")] @@ -2850,11 +2850,6 @@ impl Count for Table { self.len() } } -impl Count for Vec { - fn count(&self) -> usize { - self.len() - } -} // TODO: Return u32, u64, or usize instead of f64 after #1621 is resolved and has allowed us to implement automatic type conversion in the node graph for nodes with generic type inputs. // TODO: (Currently automatic type conversion only works for concrete types, via the Graphene preprocessor and not the full Graphene type system.) @@ -2868,9 +2863,10 @@ async fn count_elements( Table>, Table, Table, - Vec, - Vec, - Vec, + Table, + Table, + Table, + Table, )] content: I, ) -> f64 {