From 5927eff0597b0caea7a1d808c909920ff9f90f9d Mon Sep 17 00:00:00 2001 From: Keavon Chambers Date: Tue, 28 Jul 2026 11:17:30 -0700 Subject: [PATCH] Replace raw node input indices with compile-time parameter symbols (#4387) * Remove the dead InputAccessor traits and the uncallable test helper built on them * Replace raw node input indices with macro-generated parameter symbols across the editor * Audit dynamic input index usage, converting to parameter symbols and named input position constants * Abstract the remaining input index plumbing behind ParameterRef APIs and accessors * Build SetInputValue messages as struct literals to keep message enums impl-free * Delete the typed parameter markers in favor of explicit-output test introspection * Wire the interpolation control path input per chain node type * Return no input when a parameter symbol is read against the wrong node's parameter view --- .../document/document_message_handler.rs | 20 +- .../graph_operation_message_handler.rs | 46 +- .../graph_operation/transform_utils.rs | 24 +- .../document/graph_operation/utility_types.rs | 85 ++- .../node_graph/document_node_definitions.rs | 80 +- .../node_graph/node_graph_message_handler.rs | 52 +- .../document/node_graph/node_properties.rs | 721 ++++++++---------- .../storage_tests/round_trip_tests.rs | 25 +- .../utility_types/network_interface/caches.rs | 18 +- .../characterization_tests.rs | 22 +- .../network_interface/hit_tests.rs | 2 +- .../utility_types/network_interface/layout.rs | 48 +- .../network_interface/mutations.rs | 29 +- .../network_interface/queries.rs | 22 +- .../network_interface/resolved_types.rs | 18 +- .../network_interface/template.rs | 12 + .../utility_types/network_interface/types.rs | 43 +- .../utility_types/network_interface/view.rs | 6 +- .../messages/portfolio/document_migration.rs | 478 ++++++------ .../portfolio/portfolio_message_handler.rs | 2 +- .../shape_gizmos/circle_arc_radius_handle.rs | 11 +- .../shape_gizmos/grid_rows_columns_gizmo.rs | 12 +- .../shape_gizmos/number_of_points_dial.rs | 11 +- .../shape_gizmos/point_radius_handle.rs | 50 +- .../shape_gizmos/spiral_turns_handle.rs | 11 +- .../gizmos/shape_gizmos/sweep_angle_gizmo.rs | 4 +- .../graph_modification_utils.rs | 202 ++--- .../common_functionality/shapes/arc_shape.rs | 2 +- .../shapes/arrow_shape.rs | 2 +- .../shapes/circle_shape.rs | 2 +- .../shapes/ellipse_shape.rs | 8 +- .../common_functionality/shapes/grid_shape.rs | 5 +- .../common_functionality/shapes/line_shape.rs | 2 +- .../shapes/polygon_shape.rs | 4 +- .../shapes/rectangle_shape.rs | 4 +- .../shapes/shape_utility.rs | 37 +- .../shapes/spiral_shape.rs | 15 +- .../common_functionality/shapes/star_shape.rs | 4 +- .../common_functionality/stroke_options.rs | 19 +- .../common_functionality/utility_functions.rs | 2 +- .../tool/tool_messages/artboard_tool.rs | 5 +- .../messages/tool/tool_messages/fill_tool.rs | 5 +- .../tool/tool_messages/gradient_tool.rs | 22 +- .../messages/tool/tool_messages/shape_tool.rs | 55 +- .../messages/tool/tool_messages/text_tool.rs | 20 +- editor/src/node_graph_executor.rs | 82 +- editor/src/test_utils.rs | 12 - node-graph/graph-craft/src/document.rs | 17 +- node-graph/libraries/core-types/src/lib.rs | 43 +- node-graph/node-macro/src/codegen.rs | 164 +--- node-graph/node-macro/src/lib.rs | 1 - 51 files changed, 1259 insertions(+), 1327 deletions(-) diff --git a/editor/src/messages/portfolio/document/document_message_handler.rs b/editor/src/messages/portfolio/document/document_message_handler.rs index 1bc20e191c..bb1c0113d7 100644 --- a/editor/src/messages/portfolio/document/document_message_handler.rs +++ b/editor/src/messages/portfolio/document/document_message_handler.rs @@ -2613,7 +2613,11 @@ impl DocumentMessageHandler { // If there's already a boolean operation on the selected layer, update it with the new operation if let (Some(upstream_boolean_op), Some(only_selected_layer)) = (upstream_boolean_op, only_selected_layer) { - network_interface.set_input(&InputConnector::node(upstream_boolean_op, 1), NodeInput::value(TaggedValue::BooleanOperation(operation), false), &[]); + network_interface.set_input( + &InputConnector::node(upstream_boolean_op, graphene_std::path_bool_nodes::boolean_operation::OperationInput), + NodeInput::value(TaggedValue::BooleanOperation(operation), false), + &[], + ); responses.add(NodeGraphMessage::RunDocumentGraph); @@ -2814,7 +2818,8 @@ impl DocumentMessageHandler { self.network_interface.insert_node(new_index_id, new_index_template, &[]); self.network_interface.move_node_to_chain_start(&new_index_id, new_layer, &[], false); - self.network_interface.create_wire(&OutputConnector::node(solidify_id, 0), &InputConnector::node(new_index_id, 0), &[]); + self.network_interface + .create_wire(&OutputConnector::primary_output(solidify_id), &InputConnector::primary_input(new_index_id), &[]); resulting_layers.push(layer.to_node()); resulting_layers.push(new_layer.to_node()); @@ -3579,7 +3584,7 @@ impl DocumentMessageHandler { // Showing only compatible types for the layer based on the output type of the node upstream from its horizontal input let compatible_type = selected_layer.and_then(|layer| { self.network_interface - .upstream_output_connector(&InputConnector::node(layer.to_node(), 1), &[]) + .upstream_output_connector(&InputConnector::layer_secondary_input(layer.to_node()), &[]) .and_then(|upstream_output| self.network_interface.output_type(&upstream_output, &[]).add_node_string()) }); @@ -4331,13 +4336,18 @@ mod document_message_handler_tests { let instrumented = editor.eval_graph().await.unwrap(); + // The emptiness guards keep these assertions honest: a wrong `Output` type on `grab_all_input` yields no records at all, which would otherwise pass vacuously let base_lengths: Vec = instrumented - .grab_all_input::>(&editor.runtime) + .grab_all_input::>(&editor.runtime) .map(|base| base.len()) .collect(); + assert!(!base_lengths.is_empty(), "Instrumentation should have recorded at least one stack base"); assert!(base_lengths.iter().all(|&len| len == 0), "Every stack base should be empty, found lengths {base_lengths:?}"); - let news: Vec> = instrumented.grab_all_input::>(&editor.runtime).collect(); + let news: Vec> = instrumented + .grab_all_input::>(&editor.runtime) + .collect(); + assert!(!news.is_empty(), "Instrumentation should have recorded at least one stacked element list"); let phantom_count = news .iter() .flat_map(|new| new.iter_element_values()) diff --git a/editor/src/messages/portfolio/document/graph_operation/graph_operation_message_handler.rs b/editor/src/messages/portfolio/document/graph_operation/graph_operation_message_handler.rs index 5fd3c16b26..102dbafe9c 100644 --- a/editor/src/messages/portfolio/document/graph_operation/graph_operation_message_handler.rs +++ b/editor/src/messages/portfolio/document/graph_operation/graph_operation_message_handler.rs @@ -2,6 +2,7 @@ use super::transform_utils; use super::utility_types::ModifyInputsContext; use crate::consts::{LAYER_INDENT_OFFSET, STACK_VERTICAL_GAP}; use crate::messages::portfolio::document::graph_operation::utility_types::TransformIn; +use crate::messages::portfolio::document::node_graph::document_node_definitions::BLEND_PATH_INPUT_INDEX; use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier; use crate::messages::portfolio::document::utility_types::network_interface::{InputConnector, NodeNetworkInterface, OutputConnector}; use crate::messages::portfolio::document::utility_types::nodes::CollapsedLayers; @@ -131,7 +132,7 @@ impl MessageHandler> for } } GraphOperationMessage::SetUpstreamToChain { layer } => { - let Some(OutputConnector::Node { node_id: first_chain_node, .. }) = network_interface.upstream_output_connector(&InputConnector::node(layer.to_node(), 1), &[]) else { + let Some(OutputConnector::Node { node_id: first_chain_node, .. }) = network_interface.upstream_output_connector(&InputConnector::layer_secondary_input(layer.to_node()), &[]) else { return; }; @@ -180,15 +181,15 @@ impl MessageHandler> for // Set the bottom input of the artboard back to artboard let bottom_input = NodeInput::type_default(list!(Artboard), true); - network_interface.set_input(&InputConnector::node(artboard_layer.to_node(), 0), bottom_input, &[]); + network_interface.set_input(&InputConnector::primary_input(artboard_layer.to_node()), bottom_input, &[]); } else { // We have some non layers (e.g. just a rectangle node). We disconnect the bottom input and connect it to the left input. - network_interface.disconnect_input(&InputConnector::node(artboard_layer.to_node(), 0), &[]); - network_interface.set_input(&InputConnector::node(artboard_layer.to_node(), 1), primary_input, &[]); + network_interface.disconnect_input(&InputConnector::primary_input(artboard_layer.to_node()), &[]); + network_interface.set_input(&InputConnector::layer_secondary_input(artboard_layer.to_node()), primary_input, &[]); // Set the bottom input of the artboard back to artboard let bottom_input = NodeInput::type_default(list!(Artboard), true); - network_interface.set_input(&InputConnector::node(artboard_layer.to_node(), 0), bottom_input, &[]); + network_interface.set_input(&InputConnector::primary_input(artboard_layer.to_node()), bottom_input, &[]); } } responses.add_front(NodeGraphMessage::SelectedNodesSet { nodes: vec![id] }); @@ -211,11 +212,14 @@ impl MessageHandler> for let mut modify_inputs = ModifyInputsContext::new(network_interface, responses); let layer = modify_inputs.create_layer(id); - // Insert the main chain node (Blend or Morph) depending on whether a blend count is provided - let (chain_node_id, layer_alias, path_alias) = if let Some(count) = blend_count { - (modify_inputs.insert_blend_data(layer, count as f64), "Blend", "Blend Path") + // Insert the main chain node (Blend or Morph) depending on whether a blend count is provided, referencing + // its control path input by the Blend template's named position or the Morph proto node's parameter symbol + let (path_input_connector, layer_alias, path_alias) = if let Some(count) = blend_count { + let blend_node_id = modify_inputs.insert_blend_data(layer, count as f64); + (InputConnector::node_at_index(blend_node_id, BLEND_PATH_INPUT_INDEX), "Blend", "Blend Path") } else { - (modify_inputs.insert_morph_data(layer), "Morph", "Morph Path") + let morph_node_id = modify_inputs.insert_morph_data(layer); + (InputConnector::node(morph_node_id, graphene_std::vector::morph::PathInput), "Morph", "Morph Path") }; // Create the control path layer (Path → Auto-Tangents → Origins to Polyline) @@ -225,9 +229,9 @@ impl MessageHandler> for network_interface.move_layer_to_stack(control_path_layer, parent, insert_index, &[]); network_interface.move_layer_to_stack(layer, parent, insert_index + 1, &[]); - // Connect the Path node's output to the chain node's path parameter input (input 4 for both Morph and Blend). + // Connect the Path node's output to the chain node's control path input. // Done after move_layer_to_stack so chain nodes have correct positions when converted to absolute. - network_interface.set_input(&InputConnector::node(chain_node_id, 4), NodeInput::node(path_node_id, 0), &[]); + network_interface.set_input(&path_input_connector, NodeInput::node(path_node_id, 0), &[]); responses.add(NodeGraphMessage::SetDisplayNameImpl { node_id: id, @@ -245,29 +249,29 @@ impl MessageHandler> for control_path_id, } => { // Find the chain node (Blend or Morph, first in chain of the layer) - let Some(OutputConnector::Node { node_id: chain_node, .. }) = network_interface.upstream_output_connector(&InputConnector::node(interpolation_layer_id, 1), &[]) else { + let Some(OutputConnector::Node { node_id: chain_node, .. }) = network_interface.upstream_output_connector(&InputConnector::layer_secondary_input(interpolation_layer_id), &[]) else { log::error!("Could not find chain node for layer {interpolation_layer_id}"); return; }; // Get what feeds into the chain node's primary input (the children stack) - let Some(OutputConnector::Node { node_id: children_id, output_index }) = network_interface.upstream_output_connector(&InputConnector::node(chain_node, 0), &[]) else { + let Some(OutputConnector::Node { node_id: children_id, output_index }) = network_interface.upstream_output_connector(&InputConnector::primary_input(chain_node), &[]) else { log::error!("Could not find children stack feeding chain node {chain_node}"); return; }; // Find the deepest node in the control path layer's chain (Origins to Polyline) let mut deepest_chain_node = None; - let mut current_connector = InputConnector::node(control_path_id, 1); + let mut current_connector = InputConnector::layer_secondary_input(control_path_id); while let Some(OutputConnector::Node { node_id, .. }) = network_interface.upstream_output_connector(¤t_connector, &[]) { deepest_chain_node = Some(node_id); - current_connector = InputConnector::node(node_id, 0); + current_connector = InputConnector::primary_input(node_id); } // Connect children to the deepest chain node's input 0 (or the layer's input 1 if no chain) let target_connector = match deepest_chain_node { - Some(node_id) => InputConnector::node(node_id, 0), - None => InputConnector::node(control_path_id, 1), + Some(node_id) => InputConnector::primary_input(node_id), + None => InputConnector::layer_secondary_input(control_path_id), }; network_interface.set_input(&target_connector, NodeInput::node(children_id, output_index), &[]); @@ -298,7 +302,7 @@ impl MessageHandler> for responses.add(NodeGraphMessage::AddNodes { nodes, new_ids }); responses.add(NodeGraphMessage::SetInput { - input_connector: InputConnector::node(layer.to_node(), 1), + input_connector: InputConnector::layer_secondary_input(layer.to_node()), input: NodeInput::node(first_new_node_id, 0), }); } @@ -367,7 +371,7 @@ impl MessageHandler> for input_node: NodeInput::node(document_node.inputs[1].as_node().unwrap_or_default(), 0), output_nodes: network_interface .outward_wires(&[]) - .and_then(|outward_wires| outward_wires.get(&OutputConnector::node(artboard.to_node(), 0))) + .and_then(|outward_wires| outward_wires.get(&OutputConnector::primary_output(artboard.to_node()))) .cloned() .unwrap_or_default(), merge_node: node_id, @@ -393,7 +397,7 @@ impl MessageHandler> for for artboard in &artboard_data { // Modify downstream connections responses.add(NodeGraphMessage::SetInput { - input_connector: InputConnector::node(artboard.1.merge_node, 1), + input_connector: InputConnector::layer_secondary_input(artboard.1.merge_node), input: NodeInput::node(artboard.1.input_node.as_node().unwrap_or_default(), 0), }); @@ -401,7 +405,7 @@ impl MessageHandler> for for outward_wire in &artboard.1.output_nodes { let input = NodeInput::node(artboard_data[artboard.0].merge_node, 0); let input_connector = match artboard_data.get(&outward_wire.node_id().unwrap_or_default()) { - Some(artboard_info) => InputConnector::node(artboard_info.merge_node, outward_wire.input_index()), + Some(artboard_info) => InputConnector::node_at_index(artboard_info.merge_node, outward_wire.input_index()), _ => *outward_wire, }; responses.add(NodeGraphMessage::SetInput { input_connector, input }); diff --git a/editor/src/messages/portfolio/document/graph_operation/transform_utils.rs b/editor/src/messages/portfolio/document/graph_operation/transform_utils.rs index e132402e71..eb2716609c 100644 --- a/editor/src/messages/portfolio/document/graph_operation/transform_utils.rs +++ b/editor/src/messages/portfolio/document/graph_operation/transform_utils.rs @@ -14,10 +14,26 @@ pub fn update_transform(network_interface: &mut NodeNetworkInterface, node_id: & let rotation = rotation.to_degrees(); let skew = DVec2::new(skew.atan().to_degrees(), 0.); - network_interface.set_input(&InputConnector::node(*node_id, 1), NodeInput::value(TaggedValue::DVec2(translation), false), &[]); - network_interface.set_input(&InputConnector::node(*node_id, 2), NodeInput::value(TaggedValue::F64(rotation), false), &[]); - network_interface.set_input(&InputConnector::node(*node_id, 3), NodeInput::value(TaggedValue::DVec2(scale), false), &[]); - network_interface.set_input(&InputConnector::node(*node_id, 4), NodeInput::value(TaggedValue::DVec2(skew), false), &[]); + network_interface.set_input( + &InputConnector::node(*node_id, graphene_std::transform_nodes::transform::TranslationInput), + NodeInput::value(TaggedValue::DVec2(translation), false), + &[], + ); + network_interface.set_input( + &InputConnector::node(*node_id, graphene_std::transform_nodes::transform::RotationInput), + NodeInput::value(TaggedValue::F64(rotation), false), + &[], + ); + network_interface.set_input( + &InputConnector::node(*node_id, graphene_std::transform_nodes::transform::ScaleInput), + NodeInput::value(TaggedValue::DVec2(scale), false), + &[], + ); + network_interface.set_input( + &InputConnector::node(*node_id, graphene_std::transform_nodes::transform::SkewInput), + NodeInput::value(TaggedValue::DVec2(skew), false), + &[], + ); } // TODO: This should be extracted from the graph at the location of the transform node. 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 df3f276dd9..6d6b62191a 100644 --- a/editor/src/messages/portfolio/document/graph_operation/utility_types.rs +++ b/editor/src/messages/portfolio/document/graph_operation/utility_types.rs @@ -1,5 +1,7 @@ use super::transform_utils; -use crate::messages::portfolio::document::node_graph::document_node_definitions::{DefinitionIdentifier, resolve_document_node_type, resolve_network_node_type, resolve_proto_node_type}; +use crate::messages::portfolio::document::node_graph::document_node_definitions::{ + ARTBOARD_DIMENSIONS_INPUT_INDEX, ARTBOARD_LOCATION_INPUT_INDEX, DefinitionIdentifier, resolve_document_node_type, resolve_network_node_type, resolve_proto_node_type, +}; use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier; use crate::messages::portfolio::document::utility_types::network_interface::{self, FlowType, InputConnector, NodeNetworkInterface}; use crate::messages::prelude::*; @@ -10,14 +12,13 @@ use graph_craft::document::value::TaggedValue; use graph_craft::document::{NodeId, NodeInput}; use graph_craft::{ProtoNodeIdentifier, list}; use graphene_std::brush::brush_stroke::BrushStroke; -use graphene_std::list::List; use graphene_std::raster::BlendMode; use graphene_std::raster_types::Image; use graphene_std::subpath::Subpath; use graphene_std::text::{Font, TypesettingConfig}; use graphene_std::vector::style::{GradientSpreadMethod, GradientType, Stroke}; use graphene_std::vector::{Gradient, PointId, Vector, VectorModification, VectorModificationType}; -use graphene_std::{Artboard, Color, Graphic, NodeInputDecleration}; +use graphene_std::{Artboard, Color, Graphic}; #[derive(PartialEq, Clone, Copy, Debug, serde::Serialize, serde::Deserialize)] pub enum TransformIn { @@ -370,7 +371,7 @@ impl<'a> ModifyInputsContext<'a> { // If inserting a 'Path' node, insert a 'Combine Paths' node if the type is `Graphic`. // TODO: Allow the 'Path' node to operate on `List` data by utilizing the reference (index or ID?) for each item. if node_definition.identifier == "Path" { - let layer_input_type = self.network_interface.input_type(&InputConnector::node(output_layer.to_node(), 1), &[]); + let layer_input_type = self.network_interface.input_type(&InputConnector::layer_secondary_input(output_layer.to_node()), &[]); if layer_input_type.compiled_element_name().as_deref() == Some("Graphic") { let Some(combine_paths_definition) = resolve_proto_node_type(graphene_std::vector_nodes::combine_paths::IDENTIFIER) else { log::error!("Combine Paths does not exist in ModifyInputsContext::existing_node_id"); @@ -391,8 +392,8 @@ impl<'a> ModifyInputsContext<'a> { let Some(fill_node_id) = self.existing_proto_node_id(graphene_std::vector_nodes::fill::IDENTIFIER, true) else { return; }; - let input_connector = InputConnector::node(fill_node_id, graphene_std::vector::fill::FillInput::>::INDEX); - let backup_input_connector = InputConnector::node(fill_node_id, graphene_std::vector::fill::BackupColorInput::INDEX); + let input_connector = InputConnector::node(fill_node_id, graphene_std::vector::fill::FillInput); + let backup_input_connector = InputConnector::node(fill_node_id, graphene_std::vector::fill::BackupColorInput); // The backup remembers the last solid color, so the red-slash "none" choice leaves it untouched if let Some(color) = color { @@ -406,13 +407,13 @@ impl<'a> ModifyInputsContext<'a> { let Some(fill_node_id) = self.existing_proto_node_id(graphene_std::vector_nodes::fill::IDENTIFIER, true) else { return; }; - let backup_input_connector = InputConnector::node(fill_node_id, graphene_std::vector::fill::BackupGradientInput::INDEX); + let backup_input_connector = InputConnector::node(fill_node_id, graphene_std::vector::fill::BackupGradientInput); self.set_input_with_refresh(backup_input_connector, NodeInput::value(TaggedValue::Gradient(gradient.clone()), false), true); // Skip the rerender on all but the last input so the whole update triggers a single graph run self.set_input_with_refresh( - InputConnector::node(fill_node_id, graphene_std::vector::fill::FillInput::>::INDEX), + InputConnector::node(fill_node_id, graphene_std::vector::fill::FillInput), NodeInput::value(TaggedValue::Gradient(gradient), false), true, ); @@ -423,29 +424,29 @@ impl<'a> ModifyInputsContext<'a> { .document_network() .nodes .get(&fill_node_id) - .and_then(|node| node.inputs.get(graphene_std::vector::fill::TransformInput::INDEX)) + .and_then(|node| node.input(graphene_std::vector::fill::TransformInput)) .is_some_and(|input| input.as_value().is_some()); if transform_is_value { self.set_input_with_refresh( - InputConnector::node(fill_node_id, graphene_std::vector::fill::HasTransformInput::INDEX), + InputConnector::node(fill_node_id, graphene_std::vector::fill::HasTransformInput), NodeInput::value(TaggedValue::Bool(true), false), true, ); self.set_input_with_refresh( - InputConnector::node(fill_node_id, graphene_std::vector::fill::TransformInput::INDEX), + InputConnector::node(fill_node_id, graphene_std::vector::fill::TransformInput), NodeInput::value(TaggedValue::DAffine2(transform), false), true, ); } self.set_input_with_refresh( - InputConnector::node(fill_node_id, graphene_std::vector::fill::GradientTypeInput::INDEX), + InputConnector::node(fill_node_id, graphene_std::vector::fill::GradientTypeInput), NodeInput::value(TaggedValue::GradientType(gradient_type), false), true, ); self.set_input_with_refresh( - InputConnector::node(fill_node_id, graphene_std::vector::fill::SpreadMethodInput::INDEX), + InputConnector::node(fill_node_id, graphene_std::vector::fill::SpreadMethodInput), NodeInput::value(TaggedValue::GradientSpreadMethod(spread_method), false), false, ); @@ -455,7 +456,7 @@ impl<'a> ModifyInputsContext<'a> { let Some(blend_node_id) = self.existing_proto_node_id(graphene_std::blending_nodes::blend_mode::IDENTIFIER, true) else { return; }; - let input_connector = InputConnector::node(blend_node_id, graphene_std::blending_nodes::blend_mode::BlendModeInput::INDEX); + let input_connector = InputConnector::node(blend_node_id, graphene_std::blending_nodes::blend_mode::BlendModeInput); self.set_input_with_refresh(input_connector, NodeInput::value(TaggedValue::BlendMode(blend_mode), false), false); } @@ -465,12 +466,12 @@ impl<'a> ModifyInputsContext<'a> { }; // Enable the `has_opacity` checkbox so the value is applied self.set_input_with_refresh( - InputConnector::node(opacity_node_id, graphene_std::blending_nodes::opacity::HasOpacityInput::INDEX), + InputConnector::node(opacity_node_id, graphene_std::blending_nodes::opacity::HasOpacityInput), NodeInput::value(TaggedValue::Bool(true), false), false, ); self.set_input_with_refresh( - InputConnector::node(opacity_node_id, graphene_std::blending_nodes::opacity::OpacityInput::INDEX), + InputConnector::node(opacity_node_id, graphene_std::blending_nodes::opacity::OpacityInput), NodeInput::value(TaggedValue::F64(opacity * 100.), false), false, ); @@ -487,19 +488,19 @@ impl<'a> ModifyInputsContext<'a> { // Freshly-created node defaults to opacity enabled; disable it so the fill slider works independently if !existed { self.set_input_with_refresh( - InputConnector::node(opacity_node_id, graphene_std::blending_nodes::opacity::HasOpacityInput::INDEX), + InputConnector::node(opacity_node_id, graphene_std::blending_nodes::opacity::HasOpacityInput), NodeInput::value(TaggedValue::Bool(false), false), false, ); } // Enable the `has_fill` checkbox so the value is applied self.set_input_with_refresh( - InputConnector::node(opacity_node_id, graphene_std::blending_nodes::opacity::HasFillInput::INDEX), + InputConnector::node(opacity_node_id, graphene_std::blending_nodes::opacity::HasFillInput), NodeInput::value(TaggedValue::Bool(true), false), false, ); self.set_input_with_refresh( - InputConnector::node(opacity_node_id, graphene_std::blending_nodes::opacity::FillInput::INDEX), + InputConnector::node(opacity_node_id, graphene_std::blending_nodes::opacity::FillInput), NodeInput::value(TaggedValue::F64(fill * 100.), false), false, ); @@ -513,7 +514,7 @@ impl<'a> ModifyInputsContext<'a> { Some(id) => id, None => { let target = gradient_chain_target_input(output_layer, self.network_interface); - let starts_layer_chain = target == InputConnector::node(output_layer.to_node(), 1); + let starts_layer_chain = target == InputConnector::layer_secondary_input(output_layer.to_node()); // The Gradient Value node discards its primary input, so starting a chain ahead of existing layer content would drop that content; refuse instead if starts_layer_chain && self.network_interface.upstream_output_connector(&target, &[]).is_some() { @@ -546,7 +547,7 @@ impl<'a> ModifyInputsContext<'a> { } }; - let input_connector = InputConnector::node(gradient_value_id, graphene_std::math_nodes::gradient_value::GradientInput::INDEX); + let input_connector = InputConnector::node(gradient_value_id, graphene_std::math_nodes::gradient_value::GradientInput); self.set_input_with_refresh(input_connector, NodeInput::value(TaggedValue::Gradient(stops), false), false); } @@ -624,7 +625,7 @@ impl<'a> ModifyInputsContext<'a> { return; }; - let input_connector = InputConnector::node(node_id, graphene_std::math_nodes::gradient_type::GradientTypeInput::INDEX); + let input_connector = InputConnector::node(node_id, graphene_std::math_nodes::gradient_type::GradientTypeInput); self.set_input_with_refresh(input_connector, NodeInput::value(TaggedValue::GradientType(gradient_type), false), false); } @@ -640,7 +641,7 @@ impl<'a> ModifyInputsContext<'a> { return; }; - let input_connector = InputConnector::node(node_id, graphene_std::math_nodes::spread_method::SpreadMethodInput::INDEX); + let input_connector = InputConnector::node(node_id, graphene_std::math_nodes::spread_method::SpreadMethodInput); self.set_input_with_refresh(input_connector, NodeInput::value(TaggedValue::GradientSpreadMethod(spread_method), false), false); } @@ -649,7 +650,7 @@ impl<'a> ModifyInputsContext<'a> { let Some(clip_node_id) = self.existing_proto_node_id(graphene_std::blending_nodes::clipping_mask::IDENTIFIER, true) else { return; }; - let input_connector = InputConnector::node(clip_node_id, graphene_std::blending_nodes::clipping_mask::ClipInput::INDEX); + let input_connector = InputConnector::node(clip_node_id, graphene_std::blending_nodes::clipping_mask::ClipInput); self.set_input_with_refresh(input_connector, NodeInput::value(TaggedValue::Bool(clip), false), false); } @@ -658,23 +659,23 @@ impl<'a> ModifyInputsContext<'a> { return; }; - let input_connector = InputConnector::node(stroke_node_id, graphene_std::vector::stroke::PaintInput::>::INDEX); + let input_connector = InputConnector::node(stroke_node_id, graphene_std::vector::stroke::PaintInput); self.set_input_with_refresh(input_connector, NodeInput::value(color.map_or_else(TaggedValue::no_paint, TaggedValue::Color), false), true); - let input_connector = InputConnector::node(stroke_node_id, graphene_std::vector::stroke::WeightInput::INDEX); + let input_connector = InputConnector::node(stroke_node_id, graphene_std::vector::stroke::WeightInput); 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); + let input_connector = InputConnector::node(stroke_node_id, graphene_std::vector::stroke::AlignInput); self.set_input_with_refresh(input_connector, NodeInput::value(TaggedValue::StrokeAlign(stroke.align), false), false); - let input_connector = InputConnector::node(stroke_node_id, graphene_std::vector::stroke::CapInput::INDEX); + let input_connector = InputConnector::node(stroke_node_id, graphene_std::vector::stroke::CapInput); self.set_input_with_refresh(input_connector, NodeInput::value(TaggedValue::StrokeCap(stroke.cap), false), true); - let input_connector = InputConnector::node(stroke_node_id, graphene_std::vector::stroke::JoinInput::INDEX); + let input_connector = InputConnector::node(stroke_node_id, graphene_std::vector::stroke::JoinInput); self.set_input_with_refresh(input_connector, NodeInput::value(TaggedValue::StrokeJoin(stroke.join), false), true); - let input_connector = InputConnector::node(stroke_node_id, graphene_std::vector::stroke::MiterLimitInput::INDEX); + let input_connector = InputConnector::node(stroke_node_id, graphene_std::vector::stroke::MiterLimitInput); 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); + let input_connector = InputConnector::node(stroke_node_id, graphene_std::vector::stroke::PaintOrderInput); self.set_input_with_refresh(input_connector, NodeInput::value(TaggedValue::PaintOrder(stroke.paint_order), false), false); - let input_connector = InputConnector::node(stroke_node_id, graphene_std::vector::stroke::DashPatternInput::INDEX); + let input_connector = InputConnector::node(stroke_node_id, graphene_std::vector::stroke::DashPatternInput); self.set_input_with_refresh(input_connector, NodeInput::value(TaggedValue::DashPattern(stroke.dash_lengths.into()), false), true); - let input_connector = InputConnector::node(stroke_node_id, graphene_std::vector::stroke::DashOffsetInput::INDEX); + let input_connector = InputConnector::node(stroke_node_id, graphene_std::vector::stroke::DashOffsetInput); self.set_input_with_refresh(input_connector, NodeInput::value(TaggedValue::F64(stroke.dash_offset), false), true); } @@ -775,7 +776,11 @@ impl<'a> ModifyInputsContext<'a> { let Some(brush_node_id) = self.existing_proto_node_id(graphene_std::brush::brush::brush::IDENTIFIER, true) else { return; }; - self.set_input_with_refresh(InputConnector::node(brush_node_id, 1), NodeInput::value(TaggedValue::BrushStrokes(strokes), false), false); + self.set_input_with_refresh( + InputConnector::node(brush_node_id, graphene_std::brush::brush::brush::TraceInput), + NodeInput::value(TaggedValue::BrushStrokes(strokes), false), + false, + ); } pub fn resize_artboard(&mut self, location: DVec2, dimensions: DVec2) { @@ -794,8 +799,16 @@ impl<'a> ModifyInputsContext<'a> { dimensions.y = -dimensions.y; location.y -= dimensions.y; } - self.set_input_with_refresh(InputConnector::node(artboard_node_id, 2), NodeInput::value(TaggedValue::DVec2(location), false), false); - self.set_input_with_refresh(InputConnector::node(artboard_node_id, 3), NodeInput::value(TaggedValue::DVec2(dimensions), false), false); + self.set_input_with_refresh( + InputConnector::node_at_index(artboard_node_id, ARTBOARD_LOCATION_INPUT_INDEX), + NodeInput::value(TaggedValue::DVec2(location), false), + false, + ); + self.set_input_with_refresh( + InputConnector::node_at_index(artboard_node_id, ARTBOARD_DIMENSIONS_INPUT_INDEX), + NodeInput::value(TaggedValue::DVec2(dimensions), false), + false, + ); } /// Set the input, refresh the Properties panel, and run the document graph if skip_rerender is false 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 8c3ac9637d..3ab0dcc0f1 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 @@ -52,6 +52,13 @@ impl NodePropertiesContext<'_> { } } +/// Input position of the location parameter on the "Artboard" layer template, which follows the two layer-shaped inputs (stack and content) and maps through to the inner Create Artboard proto node. +pub const ARTBOARD_LOCATION_INPUT_INDEX: usize = 2; +/// Input position of the dimensions parameter on the "Artboard" layer template, which follows the two layer-shaped inputs (stack and content) and maps through to the inner Create Artboard proto node. +pub const ARTBOARD_DIMENSIONS_INPUT_INDEX: usize = 3; +/// Input position of the control path parameter on the "Blend" network node template. +pub const BLEND_PATH_INPUT_INDEX: usize = 4; + /// The key used to access definitions for a network node or proto node. /// For proto nodes, this is their [`ProtoNodeIdentifier`]. /// For network nodes, it doesn't necessarily have to be the same as the network's display name, but it often is. @@ -1020,7 +1027,7 @@ fn static_input_properties() -> InputProperties { }); Ok(vec![LayoutGroup::row(node_properties::number_widget( - ParameterWidgetsInfo::new(node_id, index, blank_assist, context), + ParameterWidgetsInfo::at_index(node_id, index, blank_assist, context), number_input, ))]) }), @@ -1061,7 +1068,7 @@ fn static_input_properties() -> InputProperties { }; // NOTE: The bool input MUST be at the input index directly before the f64 input! Ok(vec![LayoutGroup::row(node_properties::optional_f64_widget( - ParameterWidgetsInfo::new(node_id, index, false, context), + ParameterWidgetsInfo::at_index(node_id, index, false, context), index - 1, number_input, ))]) @@ -1074,7 +1081,7 @@ fn static_input_properties() -> InputProperties { Box::new(|node_id, index, context| { let number_input = NumberInput::default().percentage().min(0.).max(100.); Ok(vec![LayoutGroup::row(node_properties::optional_f64_widget( - ParameterWidgetsInfo::new(node_id, index, false, context), + ParameterWidgetsInfo::at_index(node_id, index, false, context), index - 1, number_input, ))]) @@ -1121,7 +1128,7 @@ fn static_input_properties() -> InputProperties { .unwrap_or_default(); Ok(vec![node_properties::vec2_widget( - ParameterWidgetsInfo::new(node_id, index, true, context), + ParameterWidgetsInfo::at_index(node_id, index, true, context), &x, &y, &unit, @@ -1135,7 +1142,7 @@ fn static_input_properties() -> InputProperties { Box::new(|node_id, index, context| { let (_, coherent_noise_active, _, _, _, _) = node_properties::query_noise_pattern_state(node_id, context)?; let scale = node_properties::number_widget( - ParameterWidgetsInfo::new(node_id, index, true, context), + ParameterWidgetsInfo::at_index(node_id, index, true, context), NumberInput::default().min(0.).disabled(!coherent_noise_active), ); Ok(vec![scale.into()]) @@ -1144,7 +1151,7 @@ fn static_input_properties() -> InputProperties { map.insert( "noise_properties_noise_type".to_string(), Box::new(|node_id, index, context| { - let noise_type_row = enum_choice::().for_socket(ParameterWidgetsInfo::new(node_id, index, true, context)).property_row(); + let noise_type_row = enum_choice::().for_socket(ParameterWidgetsInfo::at_index(node_id, index, true, context)).property_row(); Ok(vec![noise_type_row, LayoutGroup::row(Vec::new())]) }), ); @@ -1153,7 +1160,7 @@ fn static_input_properties() -> InputProperties { Box::new(|node_id, index, context| { let (_, coherent_noise_active, _, _, _, _) = node_properties::query_noise_pattern_state(node_id, context)?; let domain_warp_type = enum_choice::() - .for_socket(ParameterWidgetsInfo::new(node_id, index, true, context)) + .for_socket(ParameterWidgetsInfo::at_index(node_id, index, true, context)) .disabled(!coherent_noise_active) .property_row(); Ok(vec![domain_warp_type]) @@ -1164,7 +1171,7 @@ fn static_input_properties() -> InputProperties { Box::new(|node_id, index, context| { let (_, coherent_noise_active, _, _, domain_warp_active, _) = node_properties::query_noise_pattern_state(node_id, context)?; let domain_warp_amplitude = node_properties::number_widget( - ParameterWidgetsInfo::new(node_id, index, true, context), + ParameterWidgetsInfo::at_index(node_id, index, true, context), NumberInput::default().min(0.).disabled(!coherent_noise_active || !domain_warp_active), ); Ok(vec![domain_warp_amplitude.into(), LayoutGroup::row(Vec::new())]) @@ -1175,7 +1182,7 @@ fn static_input_properties() -> InputProperties { Box::new(|node_id, index, context| { let (_, coherent_noise_active, _, _, _, _) = node_properties::query_noise_pattern_state(node_id, context)?; let fractal_type_row = enum_choice::() - .for_socket(ParameterWidgetsInfo::new(node_id, index, true, context)) + .for_socket(ParameterWidgetsInfo::at_index(node_id, index, true, context)) .disabled(!coherent_noise_active) .property_row(); Ok(vec![fractal_type_row]) @@ -1186,7 +1193,7 @@ fn static_input_properties() -> InputProperties { Box::new(|node_id, index, context| { let (fractal_active, coherent_noise_active, _, _, _, domain_warp_only_fractal_type_wrongly_active) = node_properties::query_noise_pattern_state(node_id, context)?; let fractal_octaves = node_properties::number_widget( - ParameterWidgetsInfo::new(node_id, index, true, context), + ParameterWidgetsInfo::at_index(node_id, index, true, context), NumberInput::default() .mode_range() .min(1.) @@ -1203,7 +1210,7 @@ fn static_input_properties() -> InputProperties { Box::new(|node_id, index, context| { let (fractal_active, coherent_noise_active, _, _, _, domain_warp_only_fractal_type_wrongly_active) = node_properties::query_noise_pattern_state(node_id, context)?; let fractal_lacunarity = node_properties::number_widget( - ParameterWidgetsInfo::new(node_id, index, true, context), + ParameterWidgetsInfo::at_index(node_id, index, true, context), NumberInput::default() .mode_range() .min(0.) @@ -1218,7 +1225,7 @@ fn static_input_properties() -> InputProperties { Box::new(|node_id, index, context| { let (fractal_active, coherent_noise_active, _, _, _, domain_warp_only_fractal_type_wrongly_active) = node_properties::query_noise_pattern_state(node_id, context)?; let fractal_gain = node_properties::number_widget( - ParameterWidgetsInfo::new(node_id, index, true, context), + ParameterWidgetsInfo::at_index(node_id, index, true, context), NumberInput::default() .mode_range() .min(0.) @@ -1233,7 +1240,7 @@ fn static_input_properties() -> InputProperties { Box::new(|node_id, index, context| { let (fractal_active, coherent_noise_active, _, _, _, domain_warp_only_fractal_type_wrongly_active) = node_properties::query_noise_pattern_state(node_id, context)?; let fractal_weighted_strength = node_properties::number_widget( - ParameterWidgetsInfo::new(node_id, index, true, context), + ParameterWidgetsInfo::at_index(node_id, index, true, context), NumberInput::default() .mode_range() .min(0.) @@ -1248,7 +1255,7 @@ fn static_input_properties() -> InputProperties { Box::new(|node_id, index, context| { let (fractal_active, coherent_noise_active, _, ping_pong_active, _, domain_warp_only_fractal_type_wrongly_active) = node_properties::query_noise_pattern_state(node_id, context)?; let fractal_ping_pong_strength = node_properties::number_widget( - ParameterWidgetsInfo::new(node_id, index, true, context), + ParameterWidgetsInfo::at_index(node_id, index, true, context), NumberInput::default() .mode_range() .min(0.) @@ -1263,7 +1270,7 @@ fn static_input_properties() -> InputProperties { Box::new(|node_id, index, context| { let (_, coherent_noise_active, cellular_noise_active, _, _, _) = node_properties::query_noise_pattern_state(node_id, context)?; let cellular_distance_function_row = enum_choice::() - .for_socket(ParameterWidgetsInfo::new(node_id, index, true, context)) + .for_socket(ParameterWidgetsInfo::at_index(node_id, index, true, context)) .disabled(!coherent_noise_active || !cellular_noise_active) .property_row(); Ok(vec![cellular_distance_function_row]) @@ -1274,7 +1281,7 @@ fn static_input_properties() -> InputProperties { Box::new(|node_id, index, context| { let (_, coherent_noise_active, cellular_noise_active, _, _, _) = node_properties::query_noise_pattern_state(node_id, context)?; let cellular_return_type = enum_choice::() - .for_socket(ParameterWidgetsInfo::new(node_id, index, true, context)) + .for_socket(ParameterWidgetsInfo::at_index(node_id, index, true, context)) .disabled(!coherent_noise_active || !cellular_noise_active) .property_row(); Ok(vec![cellular_return_type]) @@ -1285,7 +1292,7 @@ fn static_input_properties() -> InputProperties { Box::new(|node_id, index, context| { let (_, coherent_noise_active, cellular_noise_active, _, _, _) = node_properties::query_noise_pattern_state(node_id, context)?; let cellular_jitter = node_properties::number_widget( - ParameterWidgetsInfo::new(node_id, index, true, context), + ParameterWidgetsInfo::at_index(node_id, index, true, context), NumberInput::default() .mode_range() .range_min(Some(0.)) @@ -1298,7 +1305,7 @@ fn static_input_properties() -> InputProperties { map.insert( "assign_colors_gradient".to_string(), Box::new(|node_id, index, context| { - let gradient_row = node_properties::color_widget(ParameterWidgetsInfo::new(node_id, index, true, context), ColorInput::default().allow_none(false)); + let gradient_row = node_properties::color_widget(ParameterWidgetsInfo::at_index(node_id, index, true, context), ColorInput::default().allow_none(false)); Ok(vec![gradient_row]) }), ); @@ -1307,7 +1314,7 @@ fn static_input_properties() -> InputProperties { Box::new(|node_id, index, context| { let randomize_enabled = node_properties::query_assign_colors_randomize(node_id, context)?; let seed_row = node_properties::number_widget( - ParameterWidgetsInfo::new(node_id, index, true, context), + ParameterWidgetsInfo::at_index(node_id, index, true, context), NumberInput::default().min(0.).int().disabled(!randomize_enabled), ); Ok(vec![seed_row.into()]) @@ -1318,7 +1325,7 @@ fn static_input_properties() -> InputProperties { Box::new(|node_id, index, context| { let randomize_enabled = node_properties::query_assign_colors_randomize(node_id, context)?; let repeat_every_row = node_properties::number_widget( - ParameterWidgetsInfo::new(node_id, index, true, context), + ParameterWidgetsInfo::at_index(node_id, index, true, context), NumberInput::default().min(0.).int().disabled(randomize_enabled), ); Ok(vec![repeat_every_row.into()]) @@ -1327,7 +1334,7 @@ fn static_input_properties() -> InputProperties { map.insert( "transform_rotation".to_string(), Box::new(|node_id, index, context| { - let mut widgets = node_properties::start_widgets(ParameterWidgetsInfo::new(node_id, index, true, context)); + let mut widgets = node_properties::start_widgets(&ParameterWidgetsInfo::at_index(node_id, index, true, context)); let document_node = node_properties::get_document_node(node_id, context)?; let Some(input) = document_node.inputs.get(index) else { @@ -1341,7 +1348,7 @@ fn static_input_properties() -> InputProperties { .mode(NumberInputMode::Range) .range_min(Some(-180.)) .range_max(Some(180.)) - .on_update(node_properties::update_value( + .on_update(node_properties::update_value_at_index( |number_input: &NumberInput| TaggedValue::F64(number_input.value.unwrap()), node_id, index, @@ -1359,7 +1366,7 @@ fn static_input_properties() -> InputProperties { "transform_translation".to_string(), Box::new(|node_id, index, context| { Ok(vec![node_properties::vec2_widget( - ParameterWidgetsInfo::new(node_id, index, true, context), + ParameterWidgetsInfo::at_index(node_id, index, true, context), "X", "Y", " px", @@ -1371,13 +1378,22 @@ fn static_input_properties() -> InputProperties { // Scale uses a Vec2 widget with W/H labels and an "x" unit suffix map.insert( "transform_scale".to_string(), - Box::new(|node_id, index, context| Ok(vec![node_properties::vec2_widget(ParameterWidgetsInfo::new(node_id, index, true, context), "W", "H", "x", None, false)])), + Box::new(|node_id, index, context| { + Ok(vec![node_properties::vec2_widget( + ParameterWidgetsInfo::at_index(node_id, index, true, context), + "W", + "H", + "x", + None, + false, + )]) + }), ); // Skew has a custom override that maps to degrees map.insert( "transform_skew".to_string(), Box::new(|node_id, index, context| { - let mut widgets = node_properties::start_widgets(ParameterWidgetsInfo::new(node_id, index, true, context)); + let mut widgets = node_properties::start_widgets(&ParameterWidgetsInfo::at_index(node_id, index, true, context)); let document_node = node_properties::get_document_node(node_id, context)?; let Some(input) = document_node.inputs.get(index) else { @@ -1391,7 +1407,7 @@ fn static_input_properties() -> InputProperties { .unit("°") .min(-89.9) .max(89.9) - .on_update(node_properties::update_value( + .on_update(node_properties::update_value_at_index( move |input: &NumberInput| TaggedValue::DVec2(DVec2::new(input.value.unwrap(), val.y)), node_id, index, @@ -1404,7 +1420,7 @@ fn static_input_properties() -> InputProperties { .unit("°") .min(-89.9) .max(89.9) - .on_update(node_properties::update_value( + .on_update(node_properties::update_value_at_index( move |input: &NumberInput| TaggedValue::DVec2(DVec2::new(val.x, input.value.unwrap())), node_id, index, @@ -1419,7 +1435,7 @@ fn static_input_properties() -> InputProperties { ); map.insert( "text_area".to_string(), - Box::new(|node_id, index, context| Ok(vec![LayoutGroup::row(node_properties::text_area_widget(ParameterWidgetsInfo::new(node_id, index, true, context)))])), + Box::new(|node_id, index, context| Ok(vec![LayoutGroup::row(node_properties::text_area_widget(ParameterWidgetsInfo::at_index(node_id, index, true, context)))])), ); map.insert( "text_font".to_string(), @@ -1428,7 +1444,7 @@ fn static_input_properties() -> InputProperties { if context.fonts.font_catalog.is_empty() { context.responses.add(FontsMessage::LoadCatalog); } - let (font, style) = node_properties::font_inputs(ParameterWidgetsInfo::new(node_id, index, true, context)); + let (font, style) = node_properties::font_inputs(ParameterWidgetsInfo::at_index(node_id, index, true, context)); let mut result = vec![LayoutGroup::row(font)]; if let Some(style) = style { result.push(LayoutGroup::row(style)); @@ -1440,7 +1456,7 @@ fn static_input_properties() -> InputProperties { "artboard_background".to_string(), Box::new(|node_id, index, context| { Ok(vec![node_properties::color_widget( - ParameterWidgetsInfo::new(node_id, index, true, context), + ParameterWidgetsInfo::at_index(node_id, index, true, context), ColorInput::default().allow_none(false), )]) }), @@ -1448,7 +1464,9 @@ fn static_input_properties() -> InputProperties { map.insert( "text_align".to_string(), Box::new(|node_id, index, context| { - let choices = enum_choice::().for_socket(ParameterWidgetsInfo::new(node_id, index, true, context)).property_row(); + let choices = enum_choice::() + .for_socket(ParameterWidgetsInfo::at_index(node_id, index, true, context)) + .property_row(); Ok(vec![choices]) }), ); diff --git a/editor/src/messages/portfolio/document/node_graph/node_graph_message_handler.rs b/editor/src/messages/portfolio/document/node_graph/node_graph_message_handler.rs index 5ebfb798de..4b97b61316 100644 --- a/editor/src/messages/portfolio/document/node_graph/node_graph_message_handler.rs +++ b/editor/src/messages/portfolio/document/node_graph/node_graph_message_handler.rs @@ -234,8 +234,8 @@ impl<'a> MessageHandler> for NodeG x: (mid_point.x / 24.) as i32, y: (mid_point.y / 24.) as i32, }); - let node_input_connector = InputConnector::node(node_id, 0); - let node_output_connector = OutputConnector::node(node_id, 0); + let node_input_connector = InputConnector::primary_input(node_id); + let node_output_connector = OutputConnector::primary_output(node_id); responses.add(NodeGraphMessage::CreateWire { output_connector, input_connector: node_input_connector, @@ -301,7 +301,7 @@ impl<'a> MessageHandler> for NodeG // A freshly added Text node carries no font, so give it the default font (registered like the Text tool does) if node_type == DefinitionIdentifier::ProtoNode(graphene_std::text::text::IDENTIFIER) { let font_resource_id = graph_craft::application_io::resource::ResourceId::new(); - if let Some(font_input) = node_template.inputs.get_mut(graphene_std::text::text::FontInput::INDEX) { + if let Some(font_input) = node_template.input_mut(graphene_std::text::text::FontInput) { *font_input = NodeInput::value(TaggedValue::Resource(font_resource_id), false); } responses.add(DocumentMessage::Resource(ResourceMessage::AddFont { @@ -339,7 +339,7 @@ impl<'a> MessageHandler> for NodeG if let Some((input_index, _)) = node_template.inputs.iter().enumerate().find(|(_, input)| input.is_exposed()) { responses.add(NodeGraphMessage::CreateWire { output_connector: *output_connector, - input_connector: InputConnector::node(node_id, input_index), + input_connector: InputConnector::node_at_index(node_id, input_index), }); responses.add(NodeGraphMessage::RunDocumentGraph); @@ -493,7 +493,7 @@ impl<'a> MessageHandler> for NodeG *exposed = set_to_exposed; } responses.add(NodeGraphMessage::SetInput { - input_connector: InputConnector::node(node_id, input_index), + input_connector: InputConnector::node_at_index(node_id, input_index), input: node_input, }); @@ -515,7 +515,7 @@ impl<'a> MessageHandler> for NodeG return; }; - let encapsulating_connector = InputConnector::node(*node_id, 0); + let encapsulating_connector = InputConnector::primary_input(*node_id); if !exposed { network_interface.disconnect_input(&encapsulating_connector, network_path); } @@ -554,7 +554,7 @@ impl<'a> MessageHandler> for NodeG // Disconnect all connections in the encapsulating network if let Some((encapsulating_node, encapsulating_path)) = breadcrumb_network_path.split_last() { - network_interface.disconnect_output_wires(&OutputConnector::node(*encapsulating_node, 0), encapsulating_path); + network_interface.disconnect_output_wires(&OutputConnector::primary_output(*encapsulating_node), encapsulating_path); } responses.add(NodeGraphMessage::UpdateImportsExports); @@ -596,7 +596,7 @@ impl<'a> MessageHandler> for NodeG // Ensure that nodes can be grouped by checking if there is an unselected node between selected nodes for selected_node_id in &selected_node_ids { for input_index in 0..network_interface.number_of_inputs(selected_node_id, breadcrumb_network_path) { - let input_connector = InputConnector::node(*selected_node_id, input_index); + let input_connector = InputConnector::node_at_index(*selected_node_id, input_index); if let Some(upstream_deselected_node_id) = network_interface .upstream_output_connector(&input_connector, breadcrumb_network_path) .and_then(|output_connector| output_connector.node_id()) @@ -618,7 +618,7 @@ impl<'a> MessageHandler> for NodeG } for node_id in nodes_sorted_top_to_bottom { for input_index in 0..network_interface.number_of_inputs(&node_id, breadcrumb_network_path) { - let current_input_connector = InputConnector::node(node_id, input_index); + let current_input_connector = InputConnector::node_at_index(node_id, input_index); let Some(upstream_connector) = network_interface.upstream_output_connector(¤t_input_connector, breadcrumb_network_path) else { continue; }; @@ -715,7 +715,7 @@ impl<'a> MessageHandler> for NodeG for (input_index, output_connector) in input_connections.into_iter().enumerate() { responses.add(NodeGraphMessage::CreateWire { output_connector, - input_connector: InputConnector::node(encapsulating_node_id, input_index), + input_connector: InputConnector::node_at_index(encapsulating_node_id, input_index), }); } for (output_index, input_connectors) in output_connections.into_iter().enumerate() { @@ -1359,7 +1359,7 @@ impl<'a> MessageHandler> for NodeG .cloned() .collect::>() { - network_interface.try_set_upstream_to_chain(&InputConnector::node(layer, 1), selection_network_path); + network_interface.try_set_upstream_to_chain(&InputConnector::layer_secondary_input(layer), selection_network_path); } responses.add(NodeGraphMessage::SendGraph); @@ -1370,9 +1370,11 @@ impl<'a> MessageHandler> for NodeG // Check if a single node was dragged onto a wire and that the node was dragged onto the wire if selected_nodes.selected_nodes_ref().len() == 1 && !self.begin_dragging { let selected_node_id = selected_nodes.selected_nodes_ref()[0]; - let has_primary_output_connection = network_interface - .outward_wires(selection_network_path) - .is_some_and(|outward_wires| outward_wires.get(&OutputConnector::node(selected_node_id, 0)).is_some_and(|outward_wires| !outward_wires.is_empty())); + let has_primary_output_connection = network_interface.outward_wires(selection_network_path).is_some_and(|outward_wires| { + outward_wires + .get(&OutputConnector::primary_output(selected_node_id)) + .is_some_and(|outward_wires| !outward_wires.is_empty()) + }); if !has_primary_output_connection { let Some(network) = network_interface.nested_network(selection_network_path) else { return; @@ -1397,7 +1399,7 @@ impl<'a> MessageHandler> for NodeG // Prevent inserting on a link that is connected upstream to the selected node for upstream_node in network_interface.upstream_flow_back_from_nodes(vec![selected_node_id], selection_network_path, network_interface::FlowType::UpstreamFlow) { for input_index in 0..network_interface.number_of_inputs(&upstream_node, selection_network_path) { - wires_to_check.remove(&InputConnector::node(upstream_node, input_index)); + wires_to_check.remove(&InputConnector::node_at_index(upstream_node, input_index)); } } @@ -1554,7 +1556,7 @@ impl<'a> MessageHandler> for NodeG for selected_node in &all_selected_nodes { // Handle inputs of selected node for input_index in 0..network_interface.number_of_inputs(selected_node, selection_network_path) { - let input_connector = InputConnector::node(*selected_node, input_index); + let input_connector = InputConnector::node_at_index(*selected_node, input_index); // Only disconnect inputs to non selected nodes if network_interface .upstream_output_connector(&input_connector, selection_network_path) @@ -1565,13 +1567,13 @@ impl<'a> MessageHandler> for NodeG } let number_of_outputs = network_interface.number_of_outputs(selected_node, selection_network_path); - let mut first_deselected_upstream_output = network_interface.upstream_output_connector(&InputConnector::node(*selected_node, 0), selection_network_path); + let mut first_deselected_upstream_output = network_interface.upstream_output_connector(&InputConnector::primary_input(*selected_node), selection_network_path); while let Some(OutputConnector::Node { node_id, .. }) = &first_deselected_upstream_output { if !all_selected_nodes.contains(node_id) { break; } - first_deselected_upstream_output = network_interface.upstream_output_connector(&InputConnector::node(*node_id, 0), selection_network_path); + first_deselected_upstream_output = network_interface.upstream_output_connector(&InputConnector::primary_input(*node_id), selection_network_path); } let Some(outward_wires) = network_interface.outward_wires(selection_network_path) else { @@ -1594,7 +1596,7 @@ impl<'a> MessageHandler> for NodeG // Handle reconnection // Find first non selected upstream node by primary flow if let Some(first_deselected_upstream_output) = first_deselected_upstream_output { - let Some(downstream_connections_to_first_output) = outward_wires.get(&OutputConnector::node(*selected_node, 0)).cloned() else { + let Some(downstream_connections_to_first_output) = outward_wires.get(&OutputConnector::primary_output(*selected_node)).cloned() else { log::error!("Could not get downstream_connections_to_first_output in shake node"); return; }; @@ -1755,7 +1757,7 @@ impl<'a> MessageHandler> for NodeG let is_text_node = reference.as_ref().is_some_and(|r| *r == DefinitionIdentifier::ProtoNode(graphene_std::text::text::IDENTIFIER)); let is_stroke_node = reference.as_ref().is_some_and(|r| *r == DefinitionIdentifier::ProtoNode(graphene_std::vector::stroke::IDENTIFIER)); let is_fill_node = reference.as_ref().is_some_and(|r| *r == DefinitionIdentifier::ProtoNode(graphene_std::vector::fill::IDENTIFIER)); - let is_fill_input = is_fill_node && input_index == graphene_std::vector::fill::FillInput::>::INDEX; + let is_fill_input = is_fill_node && input_index == graphene_std::vector::fill::FillInput::INDEX; let is_shape_generator_node = reference.as_ref().is_some_and(|r| { [regular_polygon::IDENTIFIER, star::IDENTIFIER, arc::IDENTIFIER, spiral::IDENTIFIER, grid::IDENTIFIER, arrow::IDENTIFIER] .into_iter() @@ -1764,7 +1766,7 @@ impl<'a> MessageHandler> for NodeG let input = NodeInput::value(*value, false); responses.add(NodeGraphMessage::SetInput { - input_connector: InputConnector::node(node_id, input_index), + input_connector: InputConnector::node_at_index(node_id, input_index), input, }); responses.add(PropertiesPanelMessage::Refresh); @@ -2607,7 +2609,7 @@ impl NodeGraphMessageHandler { .popover_layout({ let compatible_type = context .network_interface - .upstream_output_connector(&InputConnector::node(layer, 1), &[]) + .upstream_output_connector(&InputConnector::layer_secondary_input(layer), &[]) .and_then(|upstream_output| context.network_interface.output_type(&upstream_output, &[]).add_node_string()); let mut node_chooser = NodeCatalog::new(); @@ -2704,7 +2706,7 @@ impl NodeGraphMessageHandler { }; let mut nodes = Vec::new(); for (node_id, visible) in network.nodes.iter().map(|(node_id, node)| (*node_id, node.visible)).collect::>() { - let primary_input_connector = InputConnector::node(node_id, 0); + let primary_input_connector = InputConnector::primary_input(node_id); let primary_input = if network_interface .input_from_connector(&primary_input_connector, breadcrumb_network_path) @@ -2715,10 +2717,10 @@ impl NodeGraphMessageHandler { None }; let exposed_inputs = (1..network_interface.number_of_inputs(&node_id, breadcrumb_network_path)) - .filter_map(|input_index| network_interface.frontend_input_from_connector(&InputConnector::node(node_id, input_index), breadcrumb_network_path)) + .filter_map(|input_index| network_interface.frontend_input_from_connector(&InputConnector::node_at_index(node_id, input_index), breadcrumb_network_path)) .collect(); - let primary_output = network_interface.frontend_output_from_connector(&OutputConnector::node(node_id, 0), breadcrumb_network_path); + let primary_output = network_interface.frontend_output_from_connector(&OutputConnector::primary_output(node_id), breadcrumb_network_path); let exposed_outputs = (1..network_interface.number_of_outputs(&node_id, breadcrumb_network_path)) .filter_map(|output_index| network_interface.frontend_output_from_connector(&OutputConnector::node(node_id, output_index), breadcrumb_network_path)) diff --git a/editor/src/messages/portfolio/document/node_graph/node_properties.rs b/editor/src/messages/portfolio/document/node_graph/node_properties.rs index df8872fcd2..3d20747b69 100644 --- a/editor/src/messages/portfolio/document/node_graph/node_properties.rs +++ b/editor/src/messages/portfolio/document/node_graph/node_properties.rs @@ -16,13 +16,10 @@ use graph_craft::application_io::resource::ResourceId; use graph_craft::document::value::TaggedValue; use graph_craft::document::{DocumentNode, DocumentNodeImplementation, NodeId, NodeInput}; use graph_craft::{Type, concrete}; -use graphene_std::Graphic; -use graphene_std::NodeInputDecleration; use graphene_std::animation::RealTimeMode; use graphene_std::brush::brush_stroke::BrushTrace; use graphene_std::color::SRGBA8; use graphene_std::extract_xy::XY; -use graphene_std::list::List; use graphene_std::raster::{ BlendMode, CellularDistanceFunction, CellularReturnType, Color, DomainWarpType, FractalType, LuminanceCalculation, NoiseType, RedGreenBlue, RedGreenBlueAlpha, RelativeAbsolute, SelectiveColorChoice, @@ -39,13 +36,22 @@ use graphene_std::vector::style::{ DashPattern, FillChoiceUI, Gradient, GradientSpreadMethod, GradientType, GradientUI, PaintOrder, StrokeAlign, StrokeCap, StrokeJoin, build_transform_with_y_preservation, }; use graphene_std::vector::{QRCodeErrorCorrectionLevel, VectorModification}; +use graphene_std::{NodeParameter, ParameterRef}; pub(crate) fn string_properties(text: &str) -> Vec { let widget = TextLabel::new(text).widget_instance(); vec![LayoutGroup::row(vec![widget])] } -fn optionally_update_value(value: impl Fn(&T) -> Option + 'static + Send + Sync, node_id: NodeId, input_index: usize) -> impl Fn(&T) -> Message + 'static + Send + Sync { +fn optionally_update_value( + value: impl Fn(&T) -> Option + 'static + Send + Sync, + node_id: NodeId, + parameter: impl Into, +) -> impl Fn(&T) -> Message + 'static + Send + Sync { + optionally_update_value_at_index(value, node_id, parameter.into().input_index) +} + +fn optionally_update_value_at_index(value: impl Fn(&T) -> Option + 'static + Send + Sync, node_id: NodeId, input_index: usize) -> impl Fn(&T) -> Message + 'static + Send + Sync { move |input_value: &T| match value(input_value) { Some(value) => NodeGraphMessage::SetInputValue { node_id, @@ -57,8 +63,13 @@ fn optionally_update_value(value: impl Fn(&T) -> Option + 'stati } } -pub fn update_value(value: impl Fn(&T) -> TaggedValue + 'static + Send + Sync, node_id: NodeId, input_index: usize) -> impl Fn(&T) -> Message + 'static + Send + Sync { - optionally_update_value(move |v| Some(value(v)), node_id, input_index) +pub fn update_value(value: impl Fn(&T) -> TaggedValue + 'static + Send + Sync, node_id: NodeId, parameter: impl Into) -> impl Fn(&T) -> Message + 'static + Send + Sync { + optionally_update_value_at_index(move |v| Some(value(v)), node_id, parameter.into().input_index) +} + +/// Like [`update_value`], for callers that receive the input index dynamically (e.g. widget overrides). +pub fn update_value_at_index(value: impl Fn(&T) -> TaggedValue + 'static + Send + Sync, node_id: NodeId, input_index: usize) -> impl Fn(&T) -> Message + 'static + Send + Sync { + optionally_update_value_at_index(move |v| Some(value(v)), node_id, input_index) } pub fn commit_value(_: &T) -> Message { @@ -76,7 +87,7 @@ pub fn expose_widget(node_id: NodeId, index: usize, data_type: FrontendGraphData }) .on_update(move |_parameter| Message::Batched { messages: Box::new([NodeGraphMessage::ExposeInput { - input_connector: InputConnector::node(node_id, index), + input_connector: InputConnector::node_at_index(node_id, index), set_to_exposed: !exposed, start_transaction: true, } @@ -118,43 +129,39 @@ pub fn jump_to_source_widget(input: &NodeInput, network_interface: &NodeNetworkI } } -pub fn start_widgets(parameter_widgets_info: ParameterWidgetsInfo) -> Vec { - let ParameterWidgetsInfo { - document_node, - node_id, - index, - name, - description, - input_type, - blank_assist, - exposable, - network_interface, - selection_network_path, - .. - } = parameter_widgets_info; - - let Some(document_node) = document_node else { +pub fn start_widgets(parameter_widgets_info: &ParameterWidgetsInfo) -> Vec { + if parameter_widgets_info.document_node.is_none() { log::warn!("A widget failed to be built because its document node is invalid."); return vec![]; - }; + } - let Some(input) = document_node.inputs.get(index) else { + let Some(input) = parameter_widgets_info.input() else { log::warn!("A widget failed to be built because its node's input index is invalid."); return vec![]; }; - let mut widgets = Vec::with_capacity(6); - if exposable { - widgets.push(expose_widget(node_id, index, input_type, input.is_exposed())); - } - widgets.push(TextLabel::new(name).tooltip_description(description).widget_instance()); - if blank_assist || input.is_exposed() { + let mut widgets = Vec::with_capacity(6); + if parameter_widgets_info.exposable { + widgets.push(expose_widget( + parameter_widgets_info.node_id, + parameter_widgets_info.index, + parameter_widgets_info.input_type, + input.is_exposed(), + )); + } + widgets.push( + TextLabel::new(parameter_widgets_info.name.clone()) + .tooltip_description(parameter_widgets_info.description.clone()) + .widget_instance(), + ); + + if parameter_widgets_info.blank_assist || input.is_exposed() { add_blank_assist(&mut widgets); } if input.is_exposed() { widgets.push(Separator::new(SeparatorStyle::Unrelated).widget_instance()); - widgets.push(jump_to_source_widget(input, network_interface, selection_network_path)); + widgets.push(jump_to_source_widget(input, parameter_widgets_info.network_interface, parameter_widgets_info.selection_network_path)); } widgets @@ -218,13 +225,13 @@ pub(crate) fn property_from_type( .range_max(Some(extent_max).filter(|bound| bound.is_finite())) }; - let default_info = ParameterWidgetsInfo::new(node_id, index, true, context); + let default_info = ParameterWidgetsInfo::at_index(node_id, index, true, context); // A type with no widget can only be supplied through the graph, labeled with a placeholder row let unsupported_widgets = |default_info: ParameterWidgetsInfo, type_label: String| { let is_exposed = default_info.is_exposed(); - let mut widgets = start_widgets(default_info); + let mut widgets = start_widgets(&default_info); if !is_exposed { widgets.extend_from_slice(&[ Separator::new(SeparatorStyle::Unrelated).widget_instance(), @@ -348,12 +355,9 @@ pub(crate) fn property_from_type( } pub fn text_widget(parameter_widgets_info: ParameterWidgetsInfo) -> Vec { - let ParameterWidgetsInfo { document_node, node_id, index, .. } = parameter_widgets_info; + let mut widgets = start_widgets(¶meter_widgets_info); - let mut widgets = start_widgets(parameter_widgets_info); - - let Some(document_node) = document_node else { return Vec::new() }; - let Some(input) = document_node.inputs.get(index) else { + let Some(input) = parameter_widgets_info.input() else { log::warn!("A widget failed to be built because its node's input index is invalid."); return vec![]; }; @@ -361,7 +365,7 @@ pub fn text_widget(parameter_widgets_info: ParameterWidgetsInfo) -> Vec Vec Vec { - let ParameterWidgetsInfo { document_node, node_id, index, .. } = parameter_widgets_info; + let mut widgets = start_widgets(¶meter_widgets_info); - let mut widgets = start_widgets(parameter_widgets_info); - - let Some(document_node) = document_node else { return Vec::new() }; - let Some(input) = document_node.inputs.get(index) else { + let Some(input) = parameter_widgets_info.input() else { log::warn!("A widget failed to be built because its node's input index is invalid."); return vec![]; }; @@ -383,7 +384,7 @@ pub fn text_area_widget(parameter_widgets_info: ParameterWidgetsInfo) -> Vec Vec Vec { - let ParameterWidgetsInfo { document_node, node_id, index, .. } = parameter_widgets_info; + let mut widgets = start_widgets(¶meter_widgets_info); - let mut widgets = start_widgets(parameter_widgets_info); - - let Some(document_node) = document_node else { return Vec::new() }; - let Some(input) = document_node.inputs.get(index) else { + let Some(input) = parameter_widgets_info.input() else { log::warn!("A widget failed to be built because its node's input index is invalid."); return vec![]; }; @@ -406,7 +404,7 @@ pub fn bool_widget(parameter_widgets_info: ParameterWidgetsInfo, checkbox_input: Separator::new(SeparatorStyle::Unrelated).widget_instance(), checkbox_input .checked(x) - .on_update(update_value(|x: &CheckboxInput| TaggedValue::Bool(x.checked), node_id, index)) + .on_update(parameter_widgets_info.update_value(|x: &CheckboxInput| TaggedValue::Bool(x.checked))) .on_commit(commit_value) .widget_instance(), ]) @@ -415,12 +413,9 @@ pub fn bool_widget(parameter_widgets_info: ParameterWidgetsInfo, checkbox_input: } pub fn reference_point_widget(parameter_widgets_info: ParameterWidgetsInfo, disabled: bool) -> Vec { - let ParameterWidgetsInfo { document_node, node_id, index, .. } = parameter_widgets_info; + let mut widgets = start_widgets(¶meter_widgets_info); - let mut widgets = start_widgets(parameter_widgets_info); - - let Some(document_node) = document_node else { return Vec::new() }; - let Some(input) = document_node.inputs.get(index) else { + let Some(input) = parameter_widgets_info.input() else { log::warn!("A widget failed to be built because its node's input index is invalid."); return vec![]; }; @@ -428,16 +423,12 @@ pub fn reference_point_widget(parameter_widgets_info: ParameterWidgetsInfo, disa widgets.extend_from_slice(&[ Separator::new(SeparatorStyle::Unrelated).widget_instance(), CheckboxInput::new(reference_point != ReferencePoint::None) - .on_update(update_value( - move |x: &CheckboxInput| TaggedValue::ReferencePoint(if x.checked { ReferencePoint::Center } else { ReferencePoint::None }), - node_id, - index, - )) + .on_update(parameter_widgets_info.update_value(move |x: &CheckboxInput| TaggedValue::ReferencePoint(if x.checked { ReferencePoint::Center } else { ReferencePoint::None }))) .disabled(disabled) .widget_instance(), Separator::new(SeparatorStyle::Related).widget_instance(), ReferencePointInput::new(reference_point) - .on_update(update_value(move |x: &ReferencePointInput| TaggedValue::ReferencePoint(x.value), node_id, index)) + .on_update(parameter_widgets_info.update_value(move |x: &ReferencePointInput| TaggedValue::ReferencePoint(x.value))) .disabled(disabled) .widget_instance(), ]) @@ -448,7 +439,7 @@ pub fn reference_point_widget(parameter_widgets_info: ParameterWidgetsInfo, disa pub fn vector_modification_widget(parameter_widgets_info: ParameterWidgetsInfo) -> Vec { let ParameterWidgetsInfo { document_node, node_id: _, index, .. } = parameter_widgets_info; - let mut widgets = start_widgets(parameter_widgets_info); + let mut widgets = start_widgets(¶meter_widgets_info); let Some(document_node) = document_node else { return widgets }; let Some(input) = document_node.inputs.get(index) else { return widgets }; @@ -469,7 +460,7 @@ pub fn vector_modification_widget(parameter_widgets_info: ParameterWidgetsInfo) pub fn brush_strokes_widget(parameter_widgets_info: ParameterWidgetsInfo) -> Vec { let ParameterWidgetsInfo { document_node, node_id: _, index, .. } = parameter_widgets_info; - let mut widgets = start_widgets(parameter_widgets_info); + let mut widgets = start_widgets(¶meter_widgets_info); let Some(document_node) = document_node else { return widgets }; let Some(input) = document_node.inputs.get(index) else { return widgets }; @@ -496,7 +487,7 @@ pub fn brush_strokes_widget(parameter_widgets_info: ParameterWidgetsInfo) -> Vec pub fn image_data_widget(parameter_widgets_info: ParameterWidgetsInfo) -> Vec { let ParameterWidgetsInfo { document_node, node_id: _, index, .. } = parameter_widgets_info; - let mut widgets = start_widgets(parameter_widgets_info); + let mut widgets = start_widgets(¶meter_widgets_info); let Some(document_node) = document_node else { return widgets }; let Some(input) = document_node.inputs.get(index) else { return widgets }; @@ -513,7 +504,7 @@ pub fn image_data_widget(parameter_widgets_info: ParameterWidgetsInfo) -> Vec) -> LayoutGroup { let ParameterWidgetsInfo { document_node, node_id, index, .. } = parameter_widgets_info; - let mut location_widgets = start_widgets(parameter_widgets_info); + let mut location_widgets = start_widgets(¶meter_widgets_info); location_widgets.push(Separator::new(SeparatorStyle::Unrelated).widget_instance()); let mut scale_widgets = vec![TextLabel::new("").widget_instance()]; @@ -539,48 +530,40 @@ pub fn footprint_widget(parameter_widgets_info: ParameterWidgetsInfo, extra_widg NumberInput::new(Some(top_left.x)) .label("X") .unit(" px") - .on_update(update_value( - move |x: &NumberInput| { - let (offset, scale) = { - let diff = DVec2::new(top_left.x - x.value.unwrap_or_default(), 0.); - (top_left - diff, bounds) - }; + .on_update(parameter_widgets_info.update_value(move |x: &NumberInput| { + let (offset, scale) = { + let diff = DVec2::new(top_left.x - x.value.unwrap_or_default(), 0.); + (top_left - diff, bounds) + }; - let footprint = Footprint { - transform: DAffine2::from_scale_angle_translation(scale, 0., offset), - resolution: (oversample * scale).as_uvec2(), - ..footprint - }; + let footprint = Footprint { + transform: DAffine2::from_scale_angle_translation(scale, 0., offset), + resolution: (oversample * scale).as_uvec2(), + ..footprint + }; - TaggedValue::Footprint(footprint) - }, - node_id, - index, - )) + TaggedValue::Footprint(footprint) + })) .on_commit(commit_value) .widget_instance(), Separator::new(SeparatorStyle::Related).widget_instance(), NumberInput::new(Some(top_left.y)) .label("Y") .unit(" px") - .on_update(update_value( - move |x: &NumberInput| { - let (offset, scale) = { - let diff = DVec2::new(0., top_left.y - x.value.unwrap_or_default()); - (top_left - diff, bounds) - }; + .on_update(parameter_widgets_info.update_value(move |x: &NumberInput| { + let (offset, scale) = { + let diff = DVec2::new(0., top_left.y - x.value.unwrap_or_default()); + (top_left - diff, bounds) + }; - let footprint = Footprint { - transform: DAffine2::from_scale_angle_translation(scale, 0., offset), - resolution: (oversample * scale).as_uvec2(), - ..footprint - }; + let footprint = Footprint { + transform: DAffine2::from_scale_angle_translation(scale, 0., offset), + resolution: (oversample * scale).as_uvec2(), + ..footprint + }; - TaggedValue::Footprint(footprint) - }, - node_id, - index, - )) + TaggedValue::Footprint(footprint) + })) .on_commit(commit_value) .widget_instance(), ]); @@ -589,7 +572,7 @@ pub fn footprint_widget(parameter_widgets_info: ParameterWidgetsInfo, extra_widg NumberInput::new(Some(bounds.x)) .label("W") .unit(" px") - .on_update(update_value( + .on_update(update_value_at_index( move |x: &NumberInput| { let (offset, scale) = (top_left, DVec2::new(x.value.unwrap_or_default(), bounds.y)); @@ -610,7 +593,7 @@ pub fn footprint_widget(parameter_widgets_info: ParameterWidgetsInfo, extra_widg NumberInput::new(Some(bounds.y)) .label("H") .unit(" px") - .on_update(update_value( + .on_update(update_value_at_index( move |x: &NumberInput| { let (offset, scale) = (top_left, DVec2::new(bounds.x, x.value.unwrap_or_default())); @@ -637,16 +620,12 @@ pub fn footprint_widget(parameter_widgets_info: ParameterWidgetsInfo, extra_widg .range_min(Some(1.)) .range_max(Some(100.)) .unit("%") - .on_update(update_value( - move |x: &NumberInput| { - let resolution = (bounds * x.value.unwrap_or(100.) / 100.).as_uvec2().max((1, 1).into()).min((4000, 4000).into()); + .on_update(parameter_widgets_info.update_value(move |x: &NumberInput| { + let resolution = (bounds * x.value.unwrap_or(100.) / 100.).as_uvec2().max((1, 1).into()).min((4000, 4000).into()); - let footprint = Footprint { resolution, ..footprint }; - TaggedValue::Footprint(footprint) - }, - node_id, - index, - )) + let footprint = Footprint { resolution, ..footprint }; + TaggedValue::Footprint(footprint) + })) .on_commit(commit_value) .widget_instance(), ); @@ -661,7 +640,7 @@ pub fn footprint_widget(parameter_widgets_info: ParameterWidgetsInfo, extra_widg pub fn transform_widget(parameter_widgets_info: ParameterWidgetsInfo, extra_widgets: &mut Vec) -> LayoutGroup { let ParameterWidgetsInfo { document_node, node_id, index, .. } = parameter_widgets_info; - let mut location_widgets = start_widgets(parameter_widgets_info); + let mut location_widgets = start_widgets(¶meter_widgets_info); location_widgets.push(Separator::new(SeparatorStyle::Unrelated).widget_instance()); let mut rotation_widgets = vec![TextLabel::new("").widget_instance()]; @@ -687,30 +666,22 @@ pub fn transform_widget(parameter_widgets_info: ParameterWidgetsInfo, extra_widg NumberInput::new(Some(translation.x)) .label("X") .unit(" px") - .on_update(update_value( - move |x: &NumberInput| { - let mut transform = transform; - transform.translation.x = x.value.unwrap_or(transform.translation.x); - TaggedValue::DAffine2(transform) - }, - node_id, - index, - )) + .on_update(parameter_widgets_info.update_value(move |x: &NumberInput| { + let mut transform = transform; + transform.translation.x = x.value.unwrap_or(transform.translation.x); + TaggedValue::DAffine2(transform) + })) .on_commit(commit_value) .widget_instance(), Separator::new(SeparatorStyle::Related).widget_instance(), NumberInput::new(Some(translation.y)) .label("Y") .unit(" px") - .on_update(update_value( - move |y: &NumberInput| { - let mut transform = transform; - transform.translation.y = y.value.unwrap_or(transform.translation.y); - TaggedValue::DAffine2(transform) - }, - node_id, - index, - )) + .on_update(parameter_widgets_info.update_value(move |y: &NumberInput| { + let mut transform = transform; + transform.translation.y = y.value.unwrap_or(transform.translation.y); + TaggedValue::DAffine2(transform) + })) .on_commit(commit_value) .widget_instance(), ]); @@ -720,7 +691,7 @@ pub fn transform_widget(parameter_widgets_info: ParameterWidgetsInfo, extra_widg .mode(NumberInputMode::Range) .range_min(Some(-180.)) .range_max(Some(180.)) - .on_update(update_value( + .on_update(update_value_at_index( move |r: &NumberInput| { let transform = DAffine2::from_scale_angle_translation(scale, r.value.map(|r| r.to_radians()).unwrap_or(rotation), translation) * skew_matrix; TaggedValue::DAffine2(transform) @@ -735,7 +706,7 @@ pub fn transform_widget(parameter_widgets_info: ParameterWidgetsInfo, extra_widg NumberInput::new(Some(scale.x)) .label("W") .unit("x") - .on_update(update_value( + .on_update(update_value_at_index( move |w: &NumberInput| { let transform = DAffine2::from_scale_angle_translation(DVec2::new(w.value.unwrap_or(scale.x), scale.y), rotation, translation) * skew_matrix; TaggedValue::DAffine2(transform) @@ -749,7 +720,7 @@ pub fn transform_widget(parameter_widgets_info: ParameterWidgetsInfo, extra_widg NumberInput::new(Some(scale.y)) .label("H") .unit("x") - .on_update(update_value( + .on_update(update_value_at_index( move |h: &NumberInput| { let transform = DAffine2::from_scale_angle_translation(DVec2::new(scale.x, h.value.unwrap_or(scale.y)), rotation, translation) * skew_matrix; TaggedValue::DAffine2(transform) @@ -775,12 +746,9 @@ pub fn transform_widget(parameter_widgets_info: ParameterWidgetsInfo, extra_widg } pub fn vec2_widget(parameter_widgets_info: ParameterWidgetsInfo, x: &str, y: &str, unit: &str, min: Option, is_integer: bool) -> LayoutGroup { - let ParameterWidgetsInfo { document_node, node_id, index, .. } = parameter_widgets_info; + let mut widgets = start_widgets(¶meter_widgets_info); - let mut widgets = start_widgets(parameter_widgets_info); - - let Some(document_node) = document_node else { return LayoutGroup::default() }; - let Some(input) = document_node.inputs.get(index) else { + let Some(input) = parameter_widgets_info.input() else { log::warn!("A widget failed to be built because its node's input index is invalid."); return LayoutGroup::row(vec![]); }; @@ -794,7 +762,7 @@ pub fn vec2_widget(parameter_widgets_info: ParameterWidgetsInfo, x: &str, y: &st .min(min.unwrap_or(-((1_u64 << f64::MANTISSA_DIGITS) as f64))) .max((1_u64 << f64::MANTISSA_DIGITS) as f64) .is_integer(is_integer) - .on_update(update_value(move |input: &NumberInput| TaggedValue::DVec2(DVec2::new(input.value.unwrap(), dvec2.y)), node_id, index)) + .on_update(parameter_widgets_info.update_value(move |input: &NumberInput| TaggedValue::DVec2(DVec2::new(input.value.unwrap(), dvec2.y)))) .on_commit(commit_value) .widget_instance(), Separator::new(SeparatorStyle::Related).widget_instance(), @@ -804,7 +772,7 @@ pub fn vec2_widget(parameter_widgets_info: ParameterWidgetsInfo, x: &str, y: &st .min(min.unwrap_or(-((1_u64 << f64::MANTISSA_DIGITS) as f64))) .max((1_u64 << f64::MANTISSA_DIGITS) as f64) .is_integer(is_integer) - .on_update(update_value(move |input: &NumberInput| TaggedValue::DVec2(DVec2::new(dvec2.x, input.value.unwrap())), node_id, index)) + .on_update(parameter_widgets_info.update_value(move |input: &NumberInput| TaggedValue::DVec2(DVec2::new(dvec2.x, input.value.unwrap())))) .on_commit(commit_value) .widget_instance(), ]); @@ -818,7 +786,7 @@ pub fn vec2_widget(parameter_widgets_info: ParameterWidgetsInfo, x: &str, y: &st .min(min.unwrap_or(-((1_u64 << f64::MANTISSA_DIGITS) as f64))) .max((1_u64 << f64::MANTISSA_DIGITS) as f64) .is_integer(is_integer) - .on_update(update_value(move |input: &NumberInput| TaggedValue::DVec2(DVec2::new(input.value.unwrap(), value)), node_id, index)) + .on_update(parameter_widgets_info.update_value(move |input: &NumberInput| TaggedValue::DVec2(DVec2::new(input.value.unwrap(), value)))) .on_commit(commit_value) .widget_instance(), Separator::new(SeparatorStyle::Related).widget_instance(), @@ -828,7 +796,7 @@ pub fn vec2_widget(parameter_widgets_info: ParameterWidgetsInfo, x: &str, y: &st .min(min.unwrap_or(-((1_u64 << f64::MANTISSA_DIGITS) as f64))) .max((1_u64 << f64::MANTISSA_DIGITS) as f64) .is_integer(is_integer) - .on_update(update_value(move |input: &NumberInput| TaggedValue::DVec2(DVec2::new(value, input.value.unwrap())), node_id, index)) + .on_update(parameter_widgets_info.update_value(move |input: &NumberInput| TaggedValue::DVec2(DVec2::new(value, input.value.unwrap())))) .on_commit(commit_value) .widget_instance(), ]); @@ -840,9 +808,7 @@ pub fn vec2_widget(parameter_widgets_info: ParameterWidgetsInfo, x: &str, y: &st } pub fn array_of_number_widget(parameter_widgets_info: ParameterWidgetsInfo, text_input: TextInput) -> Vec { - let ParameterWidgetsInfo { document_node, node_id, index, .. } = parameter_widgets_info; - - let mut widgets = start_widgets(parameter_widgets_info); + let mut widgets = start_widgets(¶meter_widgets_info); let from_string = |string: &str| { string @@ -854,8 +820,7 @@ pub fn array_of_number_widget(parameter_widgets_info: ParameterWidgetsInfo, text .map(TaggedValue::F64Array) }; - let Some(document_node) = document_node else { return Vec::new() }; - let Some(input) = document_node.inputs.get(index) else { + let Some(input) = parameter_widgets_info.input() else { log::warn!("A widget failed to be built because its node's input index is invalid."); return vec![]; }; @@ -864,7 +829,7 @@ pub fn array_of_number_widget(parameter_widgets_info: ParameterWidgetsInfo, text Separator::new(SeparatorStyle::Unrelated).widget_instance(), text_input .value(values.iter().map(|v| v.to_string()).collect::>().join(", ")) - .on_update(optionally_update_value(move |x: &TextInput| from_string(&x.value), node_id, index)) + .on_update(parameter_widgets_info.optionally_update_value(move |x: &TextInput| from_string(&x.value))) .widget_instance(), ]) } @@ -872,12 +837,9 @@ pub fn array_of_number_widget(parameter_widgets_info: ParameterWidgetsInfo, text } pub fn dash_pattern_widget(parameter_widgets_info: ParameterWidgetsInfo, text_input: TextInput) -> Vec { - let ParameterWidgetsInfo { document_node, node_id, index, .. } = parameter_widgets_info; + let mut widgets = start_widgets(¶meter_widgets_info); - let mut widgets = start_widgets(parameter_widgets_info); - - let Some(document_node) = document_node else { return Vec::new() }; - let Some(input) = document_node.inputs.get(index) else { + let Some(input) = parameter_widgets_info.input() else { log::warn!("A widget failed to be built because its node's input index is invalid."); return vec![]; }; @@ -886,11 +848,7 @@ pub fn dash_pattern_widget(parameter_widgets_info: ParameterWidgetsInfo, text_in Separator::new(SeparatorStyle::Unrelated).widget_instance(), text_input .value(pattern.0.iter_element_values().map(|length| length.to_string()).collect::>().join(", ")) - .on_update(optionally_update_value( - move |input: &TextInput| Some(TaggedValue::DashPattern(DashPattern::from(input.value.as_str()))), - node_id, - index, - )) + .on_update(parameter_widgets_info.optionally_update_value(move |input: &TextInput| Some(TaggedValue::DashPattern(DashPattern::from(input.value.as_str()))))) .widget_instance(), ]) } @@ -922,7 +880,7 @@ pub fn font_inputs(parameter_widgets_info: ParameterWidgetsInfo) -> (Vec (Vec Vec { - let ParameterWidgetsInfo { document_node, node_id, index, .. } = parameter_widgets_info; + let mut widgets = start_widgets(¶meter_widgets_info); - let mut widgets = start_widgets(parameter_widgets_info); - - let Some(document_node) = document_node else { return Vec::new() }; - let Some(input) = document_node.inputs.get(index) else { + let Some(input) = parameter_widgets_info.input() else { log::warn!("A widget failed to be built because its node's input index is invalid."); return vec![]; }; @@ -1037,7 +992,7 @@ pub fn progression_widget(parameter_widgets_info: ParameterWidgetsInfo, number_p .min(0.) .max(0.99999) .value(Some(fractional_part)) - .on_update(update_value(move |input: &NumberInput| TaggedValue::F64(whole_part + input.value.unwrap()), node_id, index)) + .on_update(parameter_widgets_info.update_value(move |input: &NumberInput| TaggedValue::F64(whole_part + input.value.unwrap()))) .on_commit(commit_value) .widget_instance(), Separator::new(SeparatorStyle::Related).widget_instance(), @@ -1049,7 +1004,7 @@ pub fn progression_widget(parameter_widgets_info: ParameterWidgetsInfo, number_p .min(0.) .is_integer(true) .value(Some(whole_part)) - .on_update(update_value(move |input: &NumberInput| TaggedValue::F64(input.value.unwrap() + fractional_part), node_id, index)) + .on_update(parameter_widgets_info.update_value(move |input: &NumberInput| TaggedValue::F64(input.value.unwrap() + fractional_part))) .on_commit(commit_value) .widget_instance(), ]) @@ -1066,7 +1021,7 @@ pub fn optional_f64_widget(parameter_widgets_info: ParameterWidgetsInfo, bool_in .. } = parameter_widgets_info; - let mut widgets = start_widgets(parameter_widgets_info); + let mut widgets = start_widgets(¶meter_widgets_info); let Some(document_node) = document_node else { return Vec::new() }; let Some(number_input) = document_node.inputs.get(number_input_index) else { @@ -1083,14 +1038,14 @@ pub fn optional_f64_widget(parameter_widgets_info: ParameterWidgetsInfo, bool_in Separator::new(SeparatorStyle::Related).widget_instance(), // The checkbox toggles if the value is Some or None CheckboxInput::new(enabled) - .on_update(update_value(|x: &CheckboxInput| TaggedValue::Bool(x.checked), node_id, bool_input_index)) + .on_update(update_value_at_index(|x: &CheckboxInput| TaggedValue::Bool(x.checked), node_id, bool_input_index)) .on_commit(commit_value) .widget_instance(), Separator::new(SeparatorStyle::Related).widget_instance(), Separator::new(SeparatorStyle::Unrelated).widget_instance(), number_props .value(Some(number)) - .on_update(update_value(move |x: &NumberInput| TaggedValue::F64(x.value.unwrap_or_default()), node_id, number_input_index)) + .on_update(update_value_at_index(move |x: &NumberInput| TaggedValue::F64(x.value.unwrap_or_default()), node_id, number_input_index)) .disabled(!enabled) .on_commit(commit_value) .widget_instance(), @@ -1101,12 +1056,9 @@ pub fn optional_f64_widget(parameter_widgets_info: ParameterWidgetsInfo, bool_in } pub fn number_widget(parameter_widgets_info: ParameterWidgetsInfo, number_props: NumberInput) -> Vec { - let ParameterWidgetsInfo { document_node, node_id, index, .. } = parameter_widgets_info; + let mut widgets = start_widgets(¶meter_widgets_info); - let mut widgets = start_widgets(parameter_widgets_info); - - let Some(document_node) = document_node else { return Vec::new() }; - let Some(input) = document_node.inputs.get(index) else { + let Some(input) = parameter_widgets_info.input() else { log::warn!("A widget failed to be built because its node's input index is invalid."); return vec![]; }; @@ -1115,7 +1067,7 @@ pub fn number_widget(parameter_widgets_info: ParameterWidgetsInfo, number_props: Separator::new(SeparatorStyle::Unrelated).widget_instance(), number_props .value(Some(x)) - .on_update(update_value(move |x: &NumberInput| TaggedValue::F64(x.value.unwrap()), node_id, index)) + .on_update(parameter_widgets_info.update_value(move |x: &NumberInput| TaggedValue::F64(x.value.unwrap()))) .on_commit(commit_value) .widget_instance(), ]), @@ -1123,7 +1075,7 @@ pub fn number_widget(parameter_widgets_info: ParameterWidgetsInfo, number_props: Separator::new(SeparatorStyle::Unrelated).widget_instance(), number_props .value(Some(x as f64)) - .on_update(update_value(move |x: &NumberInput| TaggedValue::F32(x.value.unwrap() as f32), node_id, index)) + .on_update(parameter_widgets_info.update_value(move |x: &NumberInput| TaggedValue::F32(x.value.unwrap() as f32))) .on_commit(commit_value) .widget_instance(), ]), @@ -1131,7 +1083,7 @@ pub fn number_widget(parameter_widgets_info: ParameterWidgetsInfo, number_props: Separator::new(SeparatorStyle::Unrelated).widget_instance(), number_props .value(Some(x as f64)) - .on_update(update_value(move |x: &NumberInput| TaggedValue::U32((x.value.unwrap()) as u32), node_id, index)) + .on_update(parameter_widgets_info.update_value(move |x: &NumberInput| TaggedValue::U32((x.value.unwrap()) as u32))) .on_commit(commit_value) .widget_instance(), ]), @@ -1139,7 +1091,7 @@ pub fn number_widget(parameter_widgets_info: ParameterWidgetsInfo, number_props: Separator::new(SeparatorStyle::Unrelated).widget_instance(), number_props .value(Some(x as f64)) - .on_update(update_value(move |x: &NumberInput| TaggedValue::U64((x.value.unwrap()) as u64), node_id, index)) + .on_update(parameter_widgets_info.update_value(move |x: &NumberInput| TaggedValue::U64((x.value.unwrap()) as u64))) .on_commit(commit_value) .widget_instance(), ]), @@ -1148,7 +1100,7 @@ pub fn number_widget(parameter_widgets_info: ParameterWidgetsInfo, number_props: number_props // We use an arbitrary `y` instead of an arbitrary `x` here because the "Grid" node's "Spacing" value's height should be used from rectangular mode when transferred to "Y Spacing" in isometric mode .value(Some(dvec2.y)) - .on_update(update_value(move |x: &NumberInput| TaggedValue::F64(x.value.unwrap()), node_id, index)) + .on_update(parameter_widgets_info.update_value(move |x: &NumberInput| TaggedValue::F64(x.value.unwrap()))) .on_commit(commit_value) .widget_instance(), ]), @@ -1160,11 +1112,9 @@ pub fn number_widget(parameter_widgets_info: ParameterWidgetsInfo, number_props: // TODO: Auto-generate this enum dropdown menu widget pub fn blend_mode_widget(parameter_widgets_info: ParameterWidgetsInfo) -> LayoutGroup { - let ParameterWidgetsInfo { document_node, node_id, index, .. } = parameter_widgets_info; + let mut widgets = start_widgets(¶meter_widgets_info); - let mut widgets = start_widgets(parameter_widgets_info); - let Some(document_node) = document_node else { return LayoutGroup::default() }; - let Some(input) = document_node.inputs.get(index) else { + let Some(input) = parameter_widgets_info.input() else { log::warn!("A widget failed to be built because its node's input index is invalid."); return LayoutGroup::row(vec![]); }; @@ -1177,7 +1127,7 @@ pub fn blend_mode_widget(parameter_widgets_info: ParameterWidgetsInfo) -> Layout .map(|blend_mode| { MenuListEntry::new(format!("{blend_mode:?}")) .label(blend_mode.to_string()) - .on_update(update_value(move |_| TaggedValue::BlendMode(*blend_mode), node_id, index)) + .on_update(parameter_widgets_info.update_value(move |_| TaggedValue::BlendMode(*blend_mode))) .on_commit(commit_value) }) .collect() @@ -1195,13 +1145,10 @@ pub fn blend_mode_widget(parameter_widgets_info: ParameterWidgetsInfo) -> Layout } pub fn color_widget(parameter_widgets_info: ParameterWidgetsInfo, color_button: ColorInput) -> LayoutGroup { - let ParameterWidgetsInfo { document_node, node_id, index, .. } = parameter_widgets_info; + let mut widgets = start_widgets(¶meter_widgets_info); - let mut widgets = start_widgets(parameter_widgets_info); - - let Some(document_node) = document_node else { return LayoutGroup::default() }; // Return early with just the label if the input is exposed to the graph, meaning we don't want to show the color picker widget in the Properties panel - let NodeInput::Value { tagged_value, exposed: false } = &document_node.inputs[index] else { + let Some(NodeInput::Value { tagged_value, exposed: false }) = parameter_widgets_info.input() else { return LayoutGroup::row(widgets); }; @@ -1236,7 +1183,7 @@ pub fn color_widget(parameter_widgets_info: ParameterWidgetsInfo, color_button: widgets.push( color_button .value(widget_value) - .on_update(update_value(on_update, node_id, index)) + .on_update(parameter_widgets_info.update_value(on_update)) .on_commit(commit_value) .widget_instance(), ); @@ -1301,7 +1248,7 @@ pub fn query_assign_colors_randomize(node_id: NodeId, context: &NodePropertiesCo let document_node = get_document_node(node_id, context)?; // This is safe since the node is a proto node and the implementation cannot be changed. - Ok(match document_node.inputs.get(RandomizeInput::INDEX).and_then(|input| input.as_value()) { + Ok(match document_node.input(RandomizeInput).and_then(|input| input.as_value()) { Some(TaggedValue::Bool(randomize_enabled)) => *randomize_enabled, _ => false, }) @@ -1331,7 +1278,7 @@ pub(crate) fn brightness_contrast_properties(node_id: NodeId, context: &mut Node // Use Classic toggle changes the brightness range let use_classic_value = get_document_node(node_id, context) .ok() - .and_then(|document_node| document_node.inputs.get(UseClassicInput::INDEX).and_then(|input| input.as_value())) + .and_then(|document_node| document_node.input(UseClassicInput).and_then(|input| input.as_value())) .and_then(|tagged| if let TaggedValue::Bool(value) = tagged { Some(*value) } else { None }); let includes_use_classic = use_classic_value.is_some(); let use_classic_value = use_classic_value.unwrap_or(false); @@ -1342,7 +1289,7 @@ pub(crate) fn brightness_contrast_properties(node_id: NodeId, context: &mut Node let brightness = spectrum_slider_row( node_id, context, - BrightnessInput::INDEX, + BrightnessInput, bw_track(), Color::WHITE, brightness_min, @@ -1361,7 +1308,7 @@ pub(crate) fn brightness_contrast_properties(node_id: NodeId, context: &mut Node let contrast = spectrum_slider_row( node_id, context, - ContrastInput::INDEX, + ContrastInput, contrast_track, Color::WHITE, contrast_min, @@ -1373,7 +1320,7 @@ pub(crate) fn brightness_contrast_properties(node_id: NodeId, context: &mut Node let mut layout = vec![brightness, contrast]; if includes_use_classic { // TODO: When we no longer use this function in the temporary "Brightness/Contrast Classic" node, remove this conditional pushing and just always include this - let use_classic = bool_widget(ParameterWidgetsInfo::new(node_id, UseClassicInput::INDEX, true, context), CheckboxInput::default()); + let use_classic = bool_widget(ParameterWidgetsInfo::new(node_id, UseClassicInput, true, context), CheckboxInput::default()); layout.push(LayoutGroup::row(use_classic)); } @@ -1383,13 +1330,13 @@ pub(crate) fn brightness_contrast_properties(node_id: NodeId, context: &mut Node pub(crate) fn levels_properties(node_id: NodeId, context: &mut NodePropertiesContext) -> Vec { use graphene_std::raster::levels::*; - // (input index, marker handle color, default percentage for double-click reset) + // (parameter, marker handle color, default percentage for double-click reset) let input_range_params = [ - (ShadowsInput::INDEX, Color::BLACK, 0.), - (MidtonesInput::INDEX, Color::from_rgbf32_unchecked(0.5, 0.5, 0.5), 50.), - (HighlightsInput::INDEX, Color::WHITE, 100.), + (ShadowsInput.into(), Color::BLACK, 0.), + (MidtonesInput.into(), Color::from_rgbf32_unchecked(0.5, 0.5, 0.5), 50.), + (HighlightsInput.into(), Color::WHITE, 100.), ]; - let output_range_params = [(OutputMinimumsInput::INDEX, Color::BLACK, 0.), (OutputMaximumsInput::INDEX, Color::WHITE, 100.)]; + let output_range_params = [(OutputMinimumsInput.into(), Color::BLACK, 0.), (OutputMaximumsInput.into(), Color::WHITE, 100.)]; let mut layout = Vec::with_capacity(5); build_shared_spectrum_section(node_id, context, &input_range_params, &mut layout); @@ -1400,13 +1347,13 @@ pub(crate) fn levels_properties(node_id: NodeId, context: &mut NodePropertiesCon /// Append a section of related percentage parameters as rows: a shared black-to-white spectrum (with one marker per non-exposed parameter) sits on the first non-exposed row /// alongside its 60px number input, and the remaining non-exposed rows show only their 60px number input. Exposed parameters render as the standard exposed-row display. /// Marker positions are clamped to non-decreasing display order so they never visually cross even if the underlying values do. -fn build_shared_spectrum_section(node_id: NodeId, context: &mut NodePropertiesContext, params: &[(usize, Color, f64)], layout: &mut Vec) { +fn build_shared_spectrum_section(node_id: NodeId, context: &mut NodePropertiesContext, params: &[(ParameterRef, Color, f64)], layout: &mut Vec) { // Snapshot exposure and values before the mutable-borrow loop let exposure_and_value: Vec<(bool, f64)> = match get_document_node(node_id, context) { Ok(document_node) => params .iter() - .map(|&(input_index, _, _)| { - let input = document_node.inputs.get(input_index); + .map(|(parameter, _, _)| { + let input = document_node.inputs.get(parameter.input_index); let exposed = input.is_some_and(|input| input.is_exposed()); let percent = input .and_then(|input| input.as_value()) @@ -1426,13 +1373,13 @@ fn build_shared_spectrum_section(node_id: NodeId, context: &mut NodePropertiesCo let mut marker_default_percents = Vec::new(); let mut marker_positions = Vec::new(); let mut handle_colors = Vec::new(); - for (i, &(input_index, handle_color, default_percent)) in params.iter().enumerate() { + for (i, &(ref parameter, handle_color, default_percent)) in params.iter().enumerate() { let (exposed, percent) = exposure_and_value[i]; if exposed { continue; } marker_positions.push((percent / 100.).clamp(0., 1.)); - marker_input_indices.push(input_index); + marker_input_indices.push(parameter.input_index); marker_default_percents.push(default_percent); handle_colors.push(handle_color); } @@ -1496,14 +1443,15 @@ fn build_shared_spectrum_section(node_id: NodeId, context: &mut NodePropertiesCo let number_input = NumberInput::default().mode_increment().unit("%").min(0.).max(100.); // One row per parameter: first non-exposed carries the shared spectrum, others get just a number input - for (i, &(input_index, _, _)) in params.iter().enumerate() { + for (i, (parameter, _, _)) in params.iter().enumerate() { let (exposed, current) = exposure_and_value[i]; + let input_index = parameter.input_index; if exposed { - let row = number_widget(ParameterWidgetsInfo::new(node_id, input_index, true, context), number_input.clone()); + let row = number_widget(ParameterWidgetsInfo::at_index(node_id, input_index, true, context), number_input.clone()); layout.push(LayoutGroup::row(row)); } else { - let mut row = start_widgets(ParameterWidgetsInfo::new(node_id, input_index, true, context)); + let mut row = start_widgets(&ParameterWidgetsInfo::at_index(node_id, input_index, true, context)); row.push(Separator::new(SeparatorStyle::Unrelated).widget_instance()); if Some(input_index) == spectrum_owner @@ -1520,7 +1468,11 @@ fn build_shared_spectrum_section(node_id: NodeId, context: &mut NodePropertiesCo .min_width(60) .max_width(60) .display_decimal_places(0) - .on_update(update_value(move |widget: &NumberInput| TaggedValue::F32(widget.value.unwrap_or(0.) as f32), node_id, input_index)) + .on_update(update_value_at_index( + move |widget: &NumberInput| TaggedValue::F32(widget.value.unwrap_or(0.) as f32), + node_id, + input_index, + )) .on_commit(commit_value) .widget_instance(), ); @@ -1535,7 +1487,7 @@ pub(crate) fn hue_saturation_properties(node_id: NodeId, context: &mut NodePrope // Current hue position on the rainbow track, used for the saturation track's right-end color let current_hue_shift = get_document_node(node_id, context) .ok() - .and_then(|document_node| document_node.inputs.get(HueShiftInput::INDEX).and_then(|input| input.as_value())) + .and_then(|document_node| document_node.input(HueShiftInput).and_then(|input| input.as_value())) .and_then(|tagged| if let TaggedValue::F32(value) = tagged { Some(*value) } else { None }) .unwrap_or(0.); // The rainbow has cyan at position 0.5 (hue_shift=0), so offset by +180 to align @@ -1561,7 +1513,7 @@ pub(crate) fn hue_saturation_properties(node_id: NodeId, context: &mut NodePrope spectrum_slider_row( node_id, context, - HueShiftInput::INDEX, + HueShiftInput, hue_track, Color::WHITE, -180., @@ -1572,7 +1524,7 @@ pub(crate) fn hue_saturation_properties(node_id: NodeId, context: &mut NodePrope spectrum_slider_row( node_id, context, - SaturationShiftInput::INDEX, + SaturationShiftInput, saturation_track, Color::WHITE, -100., @@ -1583,7 +1535,7 @@ pub(crate) fn hue_saturation_properties(node_id: NodeId, context: &mut NodePrope spectrum_slider_row( node_id, context, - LightnessShiftInput::INDEX, + LightnessShiftInput, lightness_track, Color::WHITE, -100., @@ -1598,7 +1550,7 @@ pub(crate) fn hue_saturation_properties(node_id: NodeId, context: &mut NodePrope fn spectrum_slider_row( node_id: NodeId, context: &mut NodePropertiesContext, - input_index: usize, + parameter: impl Into, track: Gradient, handle_color: Color, value_min: f64, @@ -1606,7 +1558,8 @@ fn spectrum_slider_row( default_value: f64, number_input: NumberInput, ) -> LayoutGroup { - let mut row = start_widgets(ParameterWidgetsInfo::new(node_id, input_index, true, context)); + let input_index = parameter.into().input_index; + let mut row = start_widgets(&ParameterWidgetsInfo::at_index(node_id, input_index, true, context)); let current = get_document_node(node_id, context) .ok() @@ -1654,7 +1607,11 @@ fn spectrum_slider_row( .min_width(60) .max_width(60) .display_decimal_places(0) - .on_update(update_value(move |widget: &NumberInput| TaggedValue::F32(widget.value.unwrap_or(0.) as f32), node_id, input_index)) + .on_update(update_value_at_index( + move |widget: &NumberInput| TaggedValue::F32(widget.value.unwrap_or(0.) as f32), + node_id, + input_index, + )) .on_commit(commit_value) .widget_instance(), ); @@ -1666,13 +1623,13 @@ fn spectrum_slider_row( pub(crate) fn threshold_properties(node_id: NodeId, context: &mut NodePropertiesContext) -> Vec { use graphene_std::raster::threshold::*; - let params: &[(usize, Color, f64)] = &[(MinLuminanceInput::INDEX, Color::BLACK, 50.), (MaxLuminanceInput::INDEX, Color::WHITE, 100.)]; + let params: &[(ParameterRef, Color, f64)] = &[(MinLuminanceInput.into(), Color::BLACK, 50.), (MaxLuminanceInput.into(), Color::WHITE, 100.)]; let mut layout = Vec::with_capacity(3); build_shared_spectrum_section(node_id, context, params, &mut layout); let luminance_calc = { - let mut info = ParameterWidgetsInfo::new(node_id, LuminanceCalcInput::INDEX, true, context); + let mut info = ParameterWidgetsInfo::new(node_id, LuminanceCalcInput, true, context); info.exposable = false; enum_choice::().for_socket(info).property_row() }; @@ -1692,7 +1649,7 @@ pub(crate) fn vibrance_properties(node_id: NodeId, context: &mut NodePropertiesC vec![spectrum_slider_row( node_id, context, - VibranceInput::INDEX, + VibranceInput, track, Color::WHITE, -100., @@ -1707,27 +1664,27 @@ pub(crate) fn black_and_white_properties(node_id: NodeId, context: &mut NodeProp let number_input = NumberInput::default().mode_increment().unit("%").min(-200.).max(300.); - let tint = color_widget(ParameterWidgetsInfo::new(node_id, TintInput::INDEX, true, context), ColorInput::default()); + let tint = color_widget(ParameterWidgetsInfo::new(node_id, TintInput, true, context), ColorInput::default()); let mut layout = vec![tint]; - let params: &[(usize, Color, f64)] = &[ - (RedsInput::INDEX, Color::RED, 40.), - (YellowsInput::INDEX, Color::YELLOW, 60.), - (GreensInput::INDEX, Color::GREEN, 40.), - (CyansInput::INDEX, Color::CYAN, 60.), - (BluesInput::INDEX, Color::BLUE, 20.), - (MagentasInput::INDEX, Color::MAGENTA, 80.), + let params: &[(ParameterRef, Color, f64)] = &[ + (RedsInput.into(), Color::RED, 40.), + (YellowsInput.into(), Color::YELLOW, 60.), + (GreensInput.into(), Color::GREEN, 40.), + (CyansInput.into(), Color::CYAN, 60.), + (BluesInput.into(), Color::BLUE, 20.), + (MagentasInput.into(), Color::MAGENTA, 80.), ]; - for &(input_index, color, default) in params { + for (parameter, color, default) in params { layout.push(spectrum_slider_row( node_id, context, - input_index, - color_track(color), + parameter.clone(), + color_track(*color), Color::WHITE, -200., 300., - default, + *default, number_input.clone(), )); } @@ -1738,8 +1695,8 @@ pub(crate) fn black_and_white_properties(node_id: NodeId, context: &mut NodeProp pub(crate) fn channel_mixer_properties(node_id: NodeId, context: &mut NodePropertiesContext) -> Vec { use graphene_std::raster::channel_mixer::*; - let is_monochrome = bool_widget(ParameterWidgetsInfo::new(node_id, MonochromeInput::INDEX, true, context), CheckboxInput::default()); - let mut parameter_info = ParameterWidgetsInfo::new(node_id, OutputChannelInput::INDEX, true, context); + let is_monochrome = bool_widget(ParameterWidgetsInfo::new(node_id, MonochromeInput, true, context), CheckboxInput::default()); + let mut parameter_info = ParameterWidgetsInfo::new(node_id, OutputChannelInput, true, context); parameter_info.exposable = false; let output_channel = enum_choice::().for_socket(parameter_info).property_row(); @@ -1751,12 +1708,12 @@ pub(crate) fn channel_mixer_properties(node_id: NodeId, context: &mut NodeProper } }; // Monochrome - let is_monochrome_value = match document_node.inputs[MonochromeInput::INDEX].as_value() { + let is_monochrome_value = match document_node.input_value(MonochromeInput) { Some(TaggedValue::Bool(monochrome_choice)) => *monochrome_choice, _ => false, }; // Output channel choice - let output_channel_value = match &document_node.inputs[OutputChannelInput::INDEX].as_value() { + let output_channel_value = match &document_node.input_value(OutputChannelInput) { Some(TaggedValue::RedGreenBlue(choice)) => choice, _ => { warn!("Channel Mixer node properties panel could not be displayed."); @@ -1764,15 +1721,15 @@ pub(crate) fn channel_mixer_properties(node_id: NodeId, context: &mut NodeProper } }; - // Input indices and defaults depend on monochrome toggle and output channel selection - let (indices, defaults) = match (is_monochrome_value, output_channel_value) { + // The edited parameters and their defaults depend on the monochrome toggle and output channel selection + let (parameters, defaults): ([ParameterRef; 4], [f64; 4]) = match (is_monochrome_value, output_channel_value) { (true, _) => ( - [MonochromeRInput::INDEX, MonochromeGInput::INDEX, MonochromeBInput::INDEX, MonochromeCInput::INDEX], + [MonochromeRInput.into(), MonochromeGInput.into(), MonochromeBInput.into(), MonochromeCInput.into()], [40., 40., 20., 0.], ), - (false, RedGreenBlue::Red) => ([RedRInput::INDEX, RedGInput::INDEX, RedBInput::INDEX, RedCInput::INDEX], [100., 0., 0., 0.]), - (false, RedGreenBlue::Green) => ([GreenRInput::INDEX, GreenGInput::INDEX, GreenBInput::INDEX, GreenCInput::INDEX], [0., 100., 0., 0.]), - (false, RedGreenBlue::Blue) => ([BlueRInput::INDEX, BlueGInput::INDEX, BlueBInput::INDEX, BlueCInput::INDEX], [0., 0., 100., 0.]), + (false, RedGreenBlue::Red) => ([RedRInput.into(), RedGInput.into(), RedBInput.into(), RedCInput.into()], [100., 0., 0., 0.]), + (false, RedGreenBlue::Green) => ([GreenRInput.into(), GreenGInput.into(), GreenBInput.into(), GreenCInput.into()], [0., 100., 0., 0.]), + (false, RedGreenBlue::Blue) => ([BlueRInput.into(), BlueGInput.into(), BlueBInput.into(), BlueCInput.into()], [0., 0., 100., 0.]), }; let number_input = NumberInput::default().mode_increment().unit("%").min(-200.).max(200.); @@ -1782,11 +1739,11 @@ pub(crate) fn channel_mixer_properties(node_id: NodeId, context: &mut NodeProper if !is_monochrome_value { layout.push(output_channel); } - for (i, (&input_index, &default)) in indices.iter().zip(defaults.iter()).enumerate() { + for (i, (parameter, &default)) in parameters.into_iter().zip(defaults.iter()).enumerate() { layout.push(spectrum_slider_row( node_id, context, - input_index, + parameter, tracks[i].clone(), Color::WHITE, -200., @@ -1802,7 +1759,7 @@ pub(crate) fn channel_mixer_properties(node_id: NodeId, context: &mut NodeProper pub(crate) fn selective_color_properties(node_id: NodeId, context: &mut NodePropertiesContext) -> Vec { use graphene_std::raster::selective_color::*; - let mut default_info = ParameterWidgetsInfo::new(node_id, ColorsInput::INDEX, true, context); + let mut default_info = ParameterWidgetsInfo::new(node_id, ColorsInput, true, context); default_info.exposable = false; let colors = enum_choice::().for_socket(default_info).property_row(); @@ -1814,7 +1771,7 @@ pub(crate) fn selective_color_properties(node_id: NodeId, context: &mut NodeProp } }; // Colors choice - let colors_choice = match &document_node.inputs[ColorsInput::INDEX].as_value() { + let colors_choice = match &document_node.input_value(ColorsInput) { Some(TaggedValue::SelectiveColorChoice(choice)) => choice, _ => { warn!("Selective Color node properties panel could not be displayed."); @@ -1822,16 +1779,16 @@ pub(crate) fn selective_color_properties(node_id: NodeId, context: &mut NodeProp } }; // CMYK - let indices = match colors_choice { - SelectiveColorChoice::Reds => [RCInput::INDEX, RMInput::INDEX, RYInput::INDEX, RKInput::INDEX], - SelectiveColorChoice::Yellows => [YCInput::INDEX, YMInput::INDEX, YYInput::INDEX, YKInput::INDEX], - SelectiveColorChoice::Greens => [GCInput::INDEX, GMInput::INDEX, GYInput::INDEX, GKInput::INDEX], - SelectiveColorChoice::Cyans => [CCInput::INDEX, CMInput::INDEX, CYInput::INDEX, CKInput::INDEX], - SelectiveColorChoice::Blues => [BCInput::INDEX, BMInput::INDEX, BYInput::INDEX, BKInput::INDEX], - SelectiveColorChoice::Magentas => [MCInput::INDEX, MMInput::INDEX, MYInput::INDEX, MKInput::INDEX], - SelectiveColorChoice::Whites => [WCInput::INDEX, WMInput::INDEX, WYInput::INDEX, WKInput::INDEX], - SelectiveColorChoice::Neutrals => [NCInput::INDEX, NMInput::INDEX, NYInput::INDEX, NKInput::INDEX], - SelectiveColorChoice::Blacks => [KCInput::INDEX, KMInput::INDEX, KYInput::INDEX, KKInput::INDEX], + let parameters: [ParameterRef; 4] = match colors_choice { + SelectiveColorChoice::Reds => [RCInput.into(), RMInput.into(), RYInput.into(), RKInput.into()], + SelectiveColorChoice::Yellows => [YCInput.into(), YMInput.into(), YYInput.into(), YKInput.into()], + SelectiveColorChoice::Greens => [GCInput.into(), GMInput.into(), GYInput.into(), GKInput.into()], + SelectiveColorChoice::Cyans => [CCInput.into(), CMInput.into(), CYInput.into(), CKInput.into()], + SelectiveColorChoice::Blues => [BCInput.into(), BMInput.into(), BYInput.into(), BKInput.into()], + SelectiveColorChoice::Magentas => [MCInput.into(), MMInput.into(), MYInput.into(), MKInput.into()], + SelectiveColorChoice::Whites => [WCInput.into(), WMInput.into(), WYInput.into(), WKInput.into()], + SelectiveColorChoice::Neutrals => [NCInput.into(), NMInput.into(), NYInput.into(), NKInput.into()], + SelectiveColorChoice::Blacks => [KCInput.into(), KMInput.into(), KYInput.into(), KKInput.into()], }; let tracks = [color_track(Color::CYAN), color_track(Color::MAGENTA), color_track(Color::YELLOW), bw_track()]; @@ -1839,22 +1796,12 @@ pub(crate) fn selective_color_properties(node_id: NodeId, context: &mut NodeProp // Mode let mode = enum_choice::() - .for_socket(ParameterWidgetsInfo::new(node_id, ModeInput::INDEX, true, context)) + .for_socket(ParameterWidgetsInfo::new(node_id, ModeInput, true, context)) .property_row(); let mut layout = vec![colors]; - for (i, &input_index) in indices.iter().enumerate() { - layout.push(spectrum_slider_row( - node_id, - context, - input_index, - tracks[i].clone(), - Color::WHITE, - -100., - 100., - 0., - number_input.clone(), - )); + for (i, parameter) in parameters.into_iter().enumerate() { + layout.push(spectrum_slider_row(node_id, context, parameter, tracks[i].clone(), Color::WHITE, -100., 100., 0., number_input.clone())); } layout.push(mode); @@ -1864,9 +1811,7 @@ pub(crate) fn selective_color_properties(node_id: NodeId, context: &mut NodeProp pub(crate) fn grid_properties(node_id: NodeId, context: &mut NodePropertiesContext) -> Vec { use graphene_std::vector::generator_nodes::grid::*; - let grid_type = enum_choice::() - .for_socket(ParameterWidgetsInfo::new(node_id, GridTypeInput::INDEX, true, context)) - .property_row(); + let grid_type = enum_choice::().for_socket(ParameterWidgetsInfo::new(node_id, GridTypeInput, true, context)).property_row(); let mut widgets = vec![grid_type]; @@ -1877,31 +1822,31 @@ pub(crate) fn grid_properties(node_id: NodeId, context: &mut NodePropertiesConte return Vec::new(); } }; - let Some(grid_type_input) = document_node.inputs.get(GridTypeInput::INDEX) else { + let Some(grid_type_input) = document_node.input(GridTypeInput) else { log::warn!("A widget failed to be built because its node's input index is invalid."); return vec![]; }; if let Some(&TaggedValue::GridType(grid_type)) = grid_type_input.as_non_exposed_value() { match grid_type { GridType::Rectangular => { - let spacing = vec2_widget(ParameterWidgetsInfo::new(node_id, SpacingInput::::INDEX, true, context), "W", "H", " px", Some(0.), false); + let spacing = vec2_widget(ParameterWidgetsInfo::new(node_id, SpacingInput, true, context), "W", "H", " px", Some(0.), false); widgets.push(spacing); } GridType::Isometric => { let spacing = LayoutGroup::row(number_widget( - ParameterWidgetsInfo::new(node_id, SpacingInput::::INDEX, true, context), + ParameterWidgetsInfo::new(node_id, SpacingInput, true, context), NumberInput::default().label("H").min(0.).unit(" px"), )); - let angles = vec2_widget(ParameterWidgetsInfo::new(node_id, AnglesInput::INDEX, true, context), "", "", "°", None, false); + let angles = vec2_widget(ParameterWidgetsInfo::new(node_id, AnglesInput, true, context), "", "", "°", None, false); widgets.extend([spacing, angles]); } } } - let columns = number_widget(ParameterWidgetsInfo::new(node_id, ColumnsInput::INDEX, true, context), NumberInput::default().min(1.)); - let rows = number_widget(ParameterWidgetsInfo::new(node_id, RowsInput::INDEX, true, context), NumberInput::default().min(1.)); + let columns = number_widget(ParameterWidgetsInfo::new(node_id, ColumnsInput, true, context), NumberInput::default().min(1.)); + let rows = number_widget(ParameterWidgetsInfo::new(node_id, RowsInput, true, context), NumberInput::default().min(1.)); - let connect_cells = bool_widget(ParameterWidgetsInfo::new(node_id, ConnectCellsInput::INDEX, true, context), CheckboxInput::default()); + let connect_cells = bool_widget(ParameterWidgetsInfo::new(node_id, ConnectCellsInput, true, context), CheckboxInput::default()); widgets.extend([LayoutGroup::row(columns), LayoutGroup::row(rows), LayoutGroup::row(connect_cells)]); @@ -1912,10 +1857,10 @@ pub(crate) fn spiral_properties(node_id: NodeId, context: &mut NodePropertiesCon use graphene_std::vector::generator_nodes::spiral::*; let spiral_type = enum_choice::() - .for_socket(ParameterWidgetsInfo::new(node_id, SpiralTypeInput::INDEX, true, context)) + .for_socket(ParameterWidgetsInfo::new(node_id, SpiralTypeInput, true, context)) .property_row(); - let turns = number_widget(ParameterWidgetsInfo::new(node_id, TurnsInput::INDEX, true, context), NumberInput::default().min(0.1)); - let start_angle = number_widget(ParameterWidgetsInfo::new(node_id, StartAngleInput::INDEX, true, context), NumberInput::default().unit("°")); + let turns = number_widget(ParameterWidgetsInfo::new(node_id, TurnsInput, true, context), NumberInput::default().min(0.1)); + let start_angle = number_widget(ParameterWidgetsInfo::new(node_id, StartAngleInput, true, context), NumberInput::default().unit("°")); let mut widgets = vec![spiral_type, LayoutGroup::row(turns), LayoutGroup::row(start_angle)]; @@ -1927,7 +1872,7 @@ pub(crate) fn spiral_properties(node_id: NodeId, context: &mut NodePropertiesCon } }; - let Some(spiral_type_input) = document_node.inputs.get(SpiralTypeInput::INDEX) else { + let Some(spiral_type_input) = document_node.input(SpiralTypeInput) else { log::warn!("A widget failed to be built because its node's input index is invalid."); return vec![]; }; @@ -1935,25 +1880,22 @@ pub(crate) fn spiral_properties(node_id: NodeId, context: &mut NodePropertiesCon match spiral_type { SpiralType::Archimedean => { let inner_radius = LayoutGroup::row(number_widget( - ParameterWidgetsInfo::new(node_id, InnerRadiusInput::INDEX, true, context), + ParameterWidgetsInfo::new(node_id, InnerRadiusInput, true, context), NumberInput::default().min(0.).unit(" px"), )); - let outer_radius = LayoutGroup::row(number_widget( - ParameterWidgetsInfo::new(node_id, OuterRadiusInput::INDEX, true, context), - NumberInput::default().unit(" px"), - )); + let outer_radius = LayoutGroup::row(number_widget(ParameterWidgetsInfo::new(node_id, OuterRadiusInput, true, context), NumberInput::default().unit(" px"))); widgets.extend([inner_radius, outer_radius]); } SpiralType::Logarithmic => { let inner_radius = LayoutGroup::row(number_widget( - ParameterWidgetsInfo::new(node_id, InnerRadiusInput::INDEX, true, context), + ParameterWidgetsInfo::new(node_id, InnerRadiusInput, true, context), NumberInput::default().min(0.).unit(" px"), )); let outer_radius = LayoutGroup::row(number_widget( - ParameterWidgetsInfo::new(node_id, OuterRadiusInput::INDEX, true, context), + ParameterWidgetsInfo::new(node_id, OuterRadiusInput, true, context), NumberInput::default().min(0.1).unit(" px"), )); @@ -1963,7 +1905,7 @@ pub(crate) fn spiral_properties(node_id: NodeId, context: &mut NodePropertiesCon } let angular_resolution = number_widget( - ParameterWidgetsInfo::new(node_id, AngularResolutionInput::INDEX, true, context), + ParameterWidgetsInfo::new(node_id, AngularResolutionInput, true, context), NumberInput::default().min(1.).max(180.).unit("°"), ); @@ -1990,20 +1932,17 @@ pub(crate) fn sample_polyline_properties(node_id: NodeId, context: &mut NodeProp } }; - let current_spacing = document_node.inputs.get(SpacingInput::INDEX).and_then(|input| input.as_value()).cloned(); + let current_spacing = document_node.input(SpacingInput).and_then(|input| input.as_value()).cloned(); let is_quantity = matches!(current_spacing, Some(TaggedValue::PointSpacingType(PointSpacingType::Quantity))); let spacing = enum_choice::() - .for_socket(ParameterWidgetsInfo::new(node_id, SpacingInput::INDEX, true, context)) + .for_socket(ParameterWidgetsInfo::new(node_id, SpacingInput, true, context)) .property_row(); - let separation = number_widget(ParameterWidgetsInfo::new(node_id, SeparationInput::INDEX, true, context), NumberInput::default().min(0.).unit(" px")); - let quantity = number_widget(ParameterWidgetsInfo::new(node_id, QuantityInput::INDEX, true, context), NumberInput::default().min(2.).int()); - let start_offset = number_widget(ParameterWidgetsInfo::new(node_id, StartOffsetInput::INDEX, true, context), NumberInput::default().min(0.).unit(" px")); - let stop_offset = number_widget(ParameterWidgetsInfo::new(node_id, StopOffsetInput::INDEX, true, context), NumberInput::default().min(0.).unit(" px")); - let adaptive_spacing = bool_widget( - ParameterWidgetsInfo::new(node_id, AdaptiveSpacingInput::INDEX, true, context), - CheckboxInput::default().disabled(is_quantity), - ); + let separation = number_widget(ParameterWidgetsInfo::new(node_id, SeparationInput, true, context), NumberInput::default().min(0.).unit(" px")); + let quantity = number_widget(ParameterWidgetsInfo::new(node_id, QuantityInput, true, context), NumberInput::default().min(2.).int()); + let start_offset = number_widget(ParameterWidgetsInfo::new(node_id, StartOffsetInput, true, context), NumberInput::default().min(0.).unit(" px")); + let stop_offset = number_widget(ParameterWidgetsInfo::new(node_id, StopOffsetInput, true, context), NumberInput::default().min(0.).unit(" px")); + let adaptive_spacing = bool_widget(ParameterWidgetsInfo::new(node_id, AdaptiveSpacingInput, true, context), CheckboxInput::default().disabled(is_quantity)); vec![ spacing.with_tooltip_description(SAMPLE_POLYLINE_DESCRIPTION_SPACING), @@ -2021,10 +1960,10 @@ pub(crate) fn sample_polyline_properties(node_id: NodeId, context: &mut NodeProp pub(crate) fn exposure_properties(node_id: NodeId, context: &mut NodePropertiesContext) -> Vec { use graphene_std::raster::exposure::*; - let exposure = number_widget(ParameterWidgetsInfo::new(node_id, ExposureInput::INDEX, true, context), NumberInput::default().min(-20.).max(20.)); - let offset = number_widget(ParameterWidgetsInfo::new(node_id, OffsetInput::INDEX, true, context), NumberInput::default().min(-0.5).max(0.5)); + let exposure = number_widget(ParameterWidgetsInfo::new(node_id, ExposureInput, true, context), NumberInput::default().min(-20.).max(20.)); + let offset = number_widget(ParameterWidgetsInfo::new(node_id, OffsetInput, true, context), NumberInput::default().min(-0.5).max(0.5)); let gamma_correction = number_widget( - ParameterWidgetsInfo::new(node_id, GammaCorrectionInput::INDEX, true, context), + ParameterWidgetsInfo::new(node_id, GammaCorrectionInput, true, context), NumberInput::default().min(0.01).max(9.99).increment_step(0.1), ); @@ -2037,20 +1976,20 @@ pub(crate) fn format_number_properties(node_id: NodeId, context: &mut NodeProper // Read current values before borrowing context mutably for widgets let (no_decimals, decimal_sep_value, use_thousands, thousands_sep_value) = match get_document_node(node_id, context) { Ok(document_node) => { - let decimal_places = match document_node.inputs.get(DecimalPlacesInput::INDEX).and_then(|input| input.as_value()) { + let decimal_places = match document_node.input(DecimalPlacesInput).and_then(|input| input.as_value()) { Some(&TaggedValue::U32(x)) => x, _ => 2, }; - let decimal_sep = match document_node.inputs.get(DecimalSeparatorInput::INDEX).and_then(|input| input.as_non_exposed_value()) { + let decimal_sep = match document_node.input(DecimalSeparatorInput).and_then(|input| input.as_non_exposed_value()) { Some(TaggedValue::String(x)) => Some(x.clone()), _ => None, }; - let use_thousands = match document_node.inputs.get(UseThousandsSeparatorInput::INDEX).and_then(|input| input.as_value()) { + let use_thousands = match document_node.input(UseThousandsSeparatorInput).and_then(|input| input.as_value()) { Some(&TaggedValue::Bool(x)) => x, _ => false, }; - let use_thousands = use_thousands || document_node.inputs.get(ThousandsSeparatorInput::INDEX).is_some_and(|input| input.is_exposed()); - let thousands_sep = match document_node.inputs.get(ThousandsSeparatorInput::INDEX).and_then(|input| input.as_non_exposed_value()) { + let use_thousands = use_thousands || document_node.input(ThousandsSeparatorInput).is_some_and(|input| input.is_exposed()); + let thousands_sep = match document_node.input(ThousandsSeparatorInput).and_then(|input| input.as_non_exposed_value()) { Some(TaggedValue::String(x)) => Some(x.clone()), _ => None, }; @@ -2062,50 +2001,44 @@ pub(crate) fn format_number_properties(node_id: NodeId, context: &mut NodeProper } }; - let decimal_places = number_widget(ParameterWidgetsInfo::new(node_id, DecimalPlacesInput::INDEX, true, context), NumberInput::default().min(0.).int()); + let decimal_places = number_widget(ParameterWidgetsInfo::new(node_id, DecimalPlacesInput, true, context), NumberInput::default().min(0.).int()); // Fixed decimals and decimal separator are disabled when decimal places is 0 - let fixed_decimals = bool_widget( - ParameterWidgetsInfo::new(node_id, FixedDecimalsInput::INDEX, true, context), - CheckboxInput::default().disabled(no_decimals), - ); - let mut decimal_sep_widgets = start_widgets(ParameterWidgetsInfo::new(node_id, DecimalSeparatorInput::INDEX, true, context)); + let fixed_decimals = bool_widget(ParameterWidgetsInfo::new(node_id, FixedDecimalsInput, true, context), CheckboxInput::default().disabled(no_decimals)); + let mut decimal_sep_widgets = start_widgets(&ParameterWidgetsInfo::new(node_id, DecimalSeparatorInput, true, context)); if let Some(sep) = decimal_sep_value { decimal_sep_widgets.extend_from_slice(&[ Separator::new(SeparatorStyle::Unrelated).widget_instance(), TextInput::new(sep) .disabled(no_decimals) - .on_update(update_value(|x: &TextInput| TaggedValue::String(x.value.clone()), node_id, DecimalSeparatorInput::INDEX)) + .on_update(update_value(|x: &TextInput| TaggedValue::String(x.value.clone()), node_id, DecimalSeparatorInput)) .on_commit(commit_value) .widget_instance(), ]); } // Thousands separator: checkbox in assist area - let mut thousands_sep_widgets = start_widgets(ParameterWidgetsInfo::new(node_id, ThousandsSeparatorInput::INDEX, false, context)); + let mut thousands_sep_widgets = start_widgets(&ParameterWidgetsInfo::new(node_id, ThousandsSeparatorInput, false, context)); if let Some(sep) = thousands_sep_value { thousands_sep_widgets.extend_from_slice(&[ Separator::new(SeparatorStyle::Unrelated).widget_instance(), Separator::new(SeparatorStyle::Related).widget_instance(), CheckboxInput::new(use_thousands) - .on_update(update_value(|x: &CheckboxInput| TaggedValue::Bool(x.checked), node_id, UseThousandsSeparatorInput::INDEX)) + .on_update(update_value(|x: &CheckboxInput| TaggedValue::Bool(x.checked), node_id, UseThousandsSeparatorInput)) .on_commit(commit_value) .widget_instance(), Separator::new(SeparatorStyle::Related).widget_instance(), Separator::new(SeparatorStyle::Unrelated).widget_instance(), TextInput::new(sep) .disabled(!use_thousands) - .on_update(update_value(|x: &TextInput| TaggedValue::String(x.value.clone()), node_id, ThousandsSeparatorInput::INDEX)) + .on_update(update_value(|x: &TextInput| TaggedValue::String(x.value.clone()), node_id, ThousandsSeparatorInput)) .on_commit(commit_value) .widget_instance(), ]); } // Start at 10,000: disabled when thousands separator is off - let start_at_10000 = bool_widget( - ParameterWidgetsInfo::new(node_id, StartAt10000Input::INDEX, true, context), - CheckboxInput::default().disabled(!use_thousands), - ); + let start_at_10000 = bool_widget(ParameterWidgetsInfo::new(node_id, StartAt10000Input, true, context), CheckboxInput::default().disabled(!use_thousands)); vec![ LayoutGroup::row(decimal_places), @@ -2122,7 +2055,7 @@ pub(crate) fn string_capitalization_properties(node_id: NodeId, context: &mut No // Read the current values before borrowing context mutably for widgets let (is_simple_case, use_joiner_enabled, joiner_value) = match get_document_node(node_id, context) { Ok(document_node) => { - let capitalization_input = document_node.inputs.get(CapitalizationInput::INDEX); + let capitalization_input = document_node.input(CapitalizationInput); let capitalization_exposed = capitalization_input.is_some_and(|input| input.is_exposed()); // When exposed, the capitalization mode may change dynamically, so we can't assume it's a simple (joiner-inapplicable) mode let is_simple = !capitalization_exposed @@ -2130,11 +2063,11 @@ pub(crate) fn string_capitalization_properties(node_id: NodeId, context: &mut No capitalization_input.and_then(|input| input.as_value()), Some(TaggedValue::StringCapitalization(StringCapitalization::LowerCase | StringCapitalization::UpperCase)) ); - let use_joiner = match document_node.inputs.get(UseJoinerInput::INDEX).and_then(|input| input.as_value()) { + let use_joiner = match document_node.input(UseJoinerInput).and_then(|input| input.as_value()) { Some(&TaggedValue::Bool(x)) => x, _ => true, }; - let joiner = match document_node.inputs.get(JoinerInput::INDEX).and_then(|input| input.as_non_exposed_value()) { + let joiner = match document_node.input(JoinerInput).and_then(|input| input.as_non_exposed_value()) { Some(TaggedValue::String(x)) => Some(x.clone()), _ => None, }; @@ -2150,11 +2083,11 @@ pub(crate) fn string_capitalization_properties(node_id: NodeId, context: &mut No let joiner_disabled = is_simple_case || !use_joiner_enabled; let capitalization = enum_choice::() - .for_socket(ParameterWidgetsInfo::new(node_id, CapitalizationInput::INDEX, true, context)) + .for_socket(ParameterWidgetsInfo::new(node_id, CapitalizationInput, true, context)) .property_row(); // Joiner row: the UseJoiner checkbox is drawn in the assist area, followed by the Joiner text input - let mut joiner_widgets = start_widgets(ParameterWidgetsInfo::new(node_id, JoinerInput::INDEX, false, context)); + let mut joiner_widgets = start_widgets(&ParameterWidgetsInfo::new(node_id, JoinerInput, false, context)); if let Some(joiner) = joiner_value { let joiner_is_empty = joiner.is_empty(); joiner_widgets.extend_from_slice(&[ @@ -2162,7 +2095,7 @@ pub(crate) fn string_capitalization_properties(node_id: NodeId, context: &mut No Separator::new(SeparatorStyle::Related).widget_instance(), CheckboxInput::new(use_joiner_enabled) .disabled(is_simple_case) - .on_update(update_value(|x: &CheckboxInput| TaggedValue::Bool(x.checked), node_id, UseJoinerInput::INDEX)) + .on_update(update_value(|x: &CheckboxInput| TaggedValue::Bool(x.checked), node_id, UseJoinerInput)) .on_commit(commit_value) .widget_instance(), Separator::new(SeparatorStyle::Related).widget_instance(), @@ -2170,7 +2103,7 @@ pub(crate) fn string_capitalization_properties(node_id: NodeId, context: &mut No TextInput::new(joiner) .placeholder(if joiner_is_empty { "Empty" } else { "" }) .disabled(joiner_disabled) - .on_update(update_value(|x: &TextInput| TaggedValue::String(x.value.clone()), node_id, JoinerInput::INDEX)) + .on_update(update_value(|x: &TextInput| TaggedValue::String(x.value.clone()), node_id, JoinerInput)) .on_commit(commit_value) .widget_instance(), ]); @@ -2219,7 +2152,7 @@ pub(crate) fn rectangle_properties(node_id: NodeId, context: &mut NodeProperties use graphene_std::vector::generator_nodes::rectangle::*; // Corner Radius - let mut corner_radius_row_1 = start_widgets(ParameterWidgetsInfo::new(node_id, CornerRadiusInput::INDEX, true, context)); + let mut corner_radius_row_1 = start_widgets(&ParameterWidgetsInfo::new(node_id, CornerRadiusInput, true, context)); corner_radius_row_1.push(Separator::new(SeparatorStyle::Unrelated).widget_instance()); let mut corner_radius_row_2 = vec![Separator::new(SeparatorStyle::Unrelated).widget_instance()]; @@ -2233,13 +2166,13 @@ pub(crate) fn rectangle_properties(node_id: NodeId, context: &mut NodeProperties return Vec::new(); } }; - let Some(input) = document_node.inputs.get(IndividualCornerRadiiInput::INDEX) else { + let Some(input) = document_node.input(IndividualCornerRadiiInput) else { log::warn!("A widget failed to be built because its node's input index is invalid."); return vec![]; }; if let Some(&TaggedValue::Bool(is_individual)) = input.as_non_exposed_value() { // Values - let Some(input) = document_node.inputs.get(CornerRadiusInput::INDEX) else { + let Some(input) = document_node.input(CornerRadiusInput) else { log::warn!("A widget failed to be built because its node's input index is invalid."); return vec![]; }; @@ -2298,7 +2231,7 @@ pub(crate) fn rectangle_properties(node_id: NodeId, context: &mut NodeProperties .on_update(optionally_update_value( move |x: &TextInput| Some(TaggedValue::BoxCorners(BoxCorners::from(x.value.as_str()))), node_id, - CornerRadiusInput::INDEX, + CornerRadiusInput, )) .widget_instance() } else { @@ -2308,7 +2241,7 @@ pub(crate) fn rectangle_properties(node_id: NodeId, context: &mut NodeProperties .on_update(update_value( move |x: &NumberInput| TaggedValue::BoxCorners(BoxCorners::from(x.value.unwrap())), node_id, - CornerRadiusInput::INDEX, + CornerRadiusInput, )) .on_commit(commit_value) .widget_instance() @@ -2317,13 +2250,13 @@ pub(crate) fn rectangle_properties(node_id: NodeId, context: &mut NodeProperties } // Size X - let size_x = number_widget(ParameterWidgetsInfo::new(node_id, WidthInput::INDEX, true, context), NumberInput::default()); + let size_x = number_widget(ParameterWidgetsInfo::new(node_id, WidthInput, true, context), NumberInput::default()); // Size Y - let size_y = number_widget(ParameterWidgetsInfo::new(node_id, HeightInput::INDEX, true, context), NumberInput::default()); + let size_y = number_widget(ParameterWidgetsInfo::new(node_id, HeightInput, true, context), NumberInput::default()); // Clamped - let clamped = bool_widget(ParameterWidgetsInfo::new(node_id, ClampedInput::INDEX, true, context), CheckboxInput::default()); + let clamped = bool_widget(ParameterWidgetsInfo::new(node_id, ClampedInput, true, context), CheckboxInput::default()); vec![ LayoutGroup::row(size_x), @@ -2361,7 +2294,7 @@ pub(crate) fn generate_node_properties(node_id: NodeId, context: &mut NodeProper // Hide inputs that are connected to a scope if let Some(NodeInput::Scope(_)) = context .network_interface - .input_from_connector(&InputConnector::node(node_id, input_index), context.selection_network_path) + .input_from_connector(&InputConnector::node_at_index(node_id, input_index), context.selection_network_path) { continue; } @@ -2419,7 +2352,7 @@ pub(crate) fn generate_node_properties(node_id: NodeId, context: &mut NodeProper } _ => context .network_interface - .input_type(&InputConnector::node(node_id, input_index), context.selection_network_path) + .input_type(&InputConnector::node_at_index(node_id, input_index), context.selection_network_path) .compiled_nested_type() .cloned() .unwrap_or(concrete!(())), @@ -2504,9 +2437,9 @@ pub(crate) fn fill_properties(node_id: NodeId, context: &mut NodePropertiesConte } // Pass blank_assist=false because the assist slot is filled below ("Reverse Stops" button when in gradient mode) - let mut widgets_first_row = start_widgets(ParameterWidgetsInfo::new(node_id, FillInput::>::INDEX, false, context)); + let mut widgets_first_row = start_widgets(&ParameterWidgetsInfo::new(node_id, FillInput, false, context)); - if get_document_node(node_id, context).is_ok_and(|node| node.inputs.get(FillInput::>::INDEX).is_some_and(|input| input.is_exposed())) { + if get_document_node(node_id, context).is_ok_and(|node| node.input(FillInput).is_some_and(|input| input.is_exposed())) { return vec![LayoutGroup::row(widgets_first_row)]; } @@ -2515,7 +2448,7 @@ pub(crate) fn fill_properties(node_id: NodeId, context: &mut NodePropertiesConte let layer = root_layer_for_chain_node(node_id, context); let fill = match get_document_node(node_id, context) { - Ok(document_node) => match document_node.inputs[FillInput::>::INDEX].as_value() { + Ok(document_node) => match document_node.input_value(FillInput) { Some(TaggedValue::Color(color)) => ResolvedFill::Solid(Some(*color)), Some(value) if value.is_no_paint() => ResolvedFill::Solid(None), Some(TaggedValue::Gradient(_)) => { @@ -2539,11 +2472,11 @@ pub(crate) fn fill_properties(node_id: NodeId, context: &mut NodePropertiesConte let (backup_color, backup_gradient) = match get_document_node(node_id, context) { Ok(document_node) => { - let backup_color = match document_node.inputs[BackupColorInput::INDEX].as_value() { + let backup_color = match document_node.input_value(BackupColorInput) { Some(&TaggedValue::Color(color)) => Some(color), _ => None, }; - let backup_stops = match document_node.inputs[BackupGradientInput::INDEX].as_value() { + let backup_stops = match document_node.input_value(BackupGradientInput) { Some(TaggedValue::Gradient(stops)) => stops.clone(), _ => Gradient::default(), }; @@ -2559,7 +2492,7 @@ pub(crate) fn fill_properties(node_id: NodeId, context: &mut NodePropertiesConte let reverse_button = IconButton::new("Reverse", 24) .tooltip_label("Reverse Stops") .tooltip_description("Reverse the gradient color stops.") - .on_update(update_value(move |_| TaggedValue::Gradient(stops.reversed()), node_id, FillInput::>::INDEX)) + .on_update(update_value(move |_| TaggedValue::Gradient(stops.reversed()), node_id, FillInput)) .widget_instance(); widgets_first_row.push(Separator::new(SeparatorStyle::Unrelated).widget_instance()); widgets_first_row.push(reverse_button); @@ -2583,7 +2516,7 @@ pub(crate) fn fill_properties(node_id: NodeId, context: &mut NodePropertiesConte let mut messages = vec![ NodeGraphMessage::SetInputValue { node_id, - input_index: FillInput::>::INDEX, + input_index: FillInput::INDEX, value: color.map_or_else(TaggedValue::no_paint, TaggedValue::Color).into(), } .into(), @@ -2605,7 +2538,7 @@ pub(crate) fn fill_properties(node_id: NodeId, context: &mut NodePropertiesConte messages: Box::new([ NodeGraphMessage::SetInputValue { node_id, - input_index: FillInput::>::INDEX, + input_index: FillInput::INDEX, value: TaggedValue::Gradient(gradient.clone()).into(), } .into(), @@ -2646,15 +2579,11 @@ pub(crate) fn fill_properties(node_id: NodeId, context: &mut NodePropertiesConte let entries = vec![ RadioEntryData::new("solid") .label("Solid") - .on_update(update_value( - move |_| backup_color.map_or_else(TaggedValue::no_paint, TaggedValue::Color), - node_id, - FillInput::>::INDEX, - )) + .on_update(update_value(move |_| backup_color.map_or_else(TaggedValue::no_paint, TaggedValue::Color), node_id, FillInput)) .on_commit(commit_value), RadioEntryData::new("gradient") .label("Gradient") - .on_update(update_value(move |_| TaggedValue::Gradient(backup_gradient.clone()), node_id, FillInput::>::INDEX)) + .on_update(update_value(move |_| TaggedValue::Gradient(backup_gradient.clone()), node_id, FillInput)) .on_commit(commit_value), ]; @@ -2686,7 +2615,7 @@ pub(crate) fn fill_properties(node_id: NodeId, context: &mut NodePropertiesConte .map(|&grad_type| { RadioEntryData::new(format!("{:?}", grad_type)) .label(format!("{:?}", grad_type)) - .on_update(update_value(move |_| TaggedValue::GradientType(grad_type), node_id, GradientTypeInput::INDEX)) + .on_update(update_value(move |_| TaggedValue::GradientType(grad_type), node_id, GradientTypeInput)) .on_commit(commit_value) }) .collect(); @@ -2744,7 +2673,7 @@ pub(crate) fn fill_properties(node_id: NodeId, context: &mut NodePropertiesConte .map(|&spread_method| { RadioEntryData::new(format!("{:?}", spread_method)) .label(format!("{:?}", spread_method)) - .on_update(update_value(move |_| TaggedValue::GradientSpreadMethod(spread_method), node_id, SpreadMethodInput::INDEX)) + .on_update(update_value(move |_| TaggedValue::GradientSpreadMethod(spread_method), node_id, SpreadMethodInput)) .on_commit(commit_value) }) .collect(); @@ -2770,41 +2699,37 @@ pub fn stroke_properties(node_id: NodeId, context: &mut NodePropertiesContext) - return Vec::new(); } }; - let join_value = match &document_node.inputs[JoinInput::INDEX].as_value() { + let join_value = match &document_node.input_value(JoinInput) { Some(TaggedValue::StrokeJoin(x)) => x, _ => &StrokeJoin::Miter, }; - let has_dash_lengths = match &document_node.inputs[DashPatternInput::INDEX].as_value() { + let has_dash_lengths = match &document_node.input_value(DashPatternInput) { Some(TaggedValue::DashPattern(pattern)) => pattern.0.is_empty(), _ => true, }; let miter_limit_disabled = join_value != &StrokeJoin::Miter; let color = color_widget( - ParameterWidgetsInfo::new(node_id, PaintInput::>::INDEX, true, context), + ParameterWidgetsInfo::new(node_id, PaintInput, 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.)); - let align = enum_choice::() - .for_socket(ParameterWidgetsInfo::new(node_id, AlignInput::INDEX, true, context)) - .property_row(); - let cap = enum_choice::().for_socket(ParameterWidgetsInfo::new(node_id, CapInput::INDEX, true, context)).property_row(); - let join = enum_choice::() - .for_socket(ParameterWidgetsInfo::new(node_id, JoinInput::INDEX, true, context)) - .property_row(); + let weight = number_widget(ParameterWidgetsInfo::new(node_id, WeightInput, true, context), NumberInput::default().unit(" px").min(0.)); + let align = enum_choice::().for_socket(ParameterWidgetsInfo::new(node_id, AlignInput, true, context)).property_row(); + let cap = enum_choice::().for_socket(ParameterWidgetsInfo::new(node_id, CapInput, true, context)).property_row(); + let join = enum_choice::().for_socket(ParameterWidgetsInfo::new(node_id, JoinInput, true, context)).property_row(); let miter_limit = number_widget( - ParameterWidgetsInfo::new(node_id, MiterLimitInput::INDEX, true, context), + ParameterWidgetsInfo::new(node_id, MiterLimitInput, true, context), NumberInput::default().min(0.).disabled(miter_limit_disabled), ); let paint_order = enum_choice::() - .for_socket(ParameterWidgetsInfo::new(node_id, PaintOrderInput::INDEX, true, context)) + .for_socket(ParameterWidgetsInfo::new(node_id, PaintOrderInput, true, context)) .property_row(); let disabled_number_input = NumberInput::default().unit(" px").disabled(has_dash_lengths); - let dash_lengths = dash_pattern_widget(ParameterWidgetsInfo::new(node_id, DashPatternInput::INDEX, true, context), TextInput::default().centered(true)); + let dash_lengths = dash_pattern_widget(ParameterWidgetsInfo::new(node_id, DashPatternInput, true, context), TextInput::default().centered(true)); let number_input = disabled_number_input; - let dash_offset = number_widget(ParameterWidgetsInfo::new(node_id, DashOffsetInput::INDEX, true, context), number_input); + let dash_offset = number_widget(ParameterWidgetsInfo::new(node_id, DashOffsetInput, true, context), number_input); vec![ color, @@ -2823,11 +2748,9 @@ pub fn offset_path_properties(node_id: NodeId, context: &mut NodePropertiesConte use graphene_std::vector::offset_path::*; let number_input = NumberInput::default().unit(" px"); - let distance = number_widget(ParameterWidgetsInfo::new(node_id, DistanceInput::INDEX, true, context), number_input); + let distance = number_widget(ParameterWidgetsInfo::new(node_id, DistanceInput, true, context), number_input); - let join = enum_choice::() - .for_socket(ParameterWidgetsInfo::new(node_id, JoinInput::INDEX, true, context)) - .property_row(); + let join = enum_choice::().for_socket(ParameterWidgetsInfo::new(node_id, JoinInput, true, context)).property_row(); let document_node = match get_document_node(node_id, context) { Ok(document_node) => document_node, @@ -2837,13 +2760,13 @@ pub fn offset_path_properties(node_id: NodeId, context: &mut NodePropertiesConte } }; let number_input = NumberInput::default().min(0.).disabled({ - let join_val = match &document_node.inputs[JoinInput::INDEX].as_value() { + let join_value = match &document_node.input_value(JoinInput) { Some(TaggedValue::StrokeJoin(x)) => x, _ => &StrokeJoin::Miter, }; - join_val != &StrokeJoin::Miter + join_value != &StrokeJoin::Miter }); - let miter_limit = number_widget(ParameterWidgetsInfo::new(node_id, MiterLimitInput::INDEX, true, context), number_input); + let miter_limit = number_widget(ParameterWidgetsInfo::new(node_id, MiterLimitInput, true, context), number_input); vec![LayoutGroup::row(distance), join, LayoutGroup::row(miter_limit)] } @@ -2852,7 +2775,7 @@ pub fn math_properties(node_id: NodeId, context: &mut NodePropertiesContext) -> use graphene_std::math_nodes::math::*; let expression = (|| { - let mut widgets = start_widgets(ParameterWidgetsInfo::new(node_id, ExpressionInput::INDEX, true, context)); + let mut widgets = start_widgets(&ParameterWidgetsInfo::new(node_id, ExpressionInput, true, context)); let document_node = match get_document_node(node_id, context) { Ok(document_node) => document_node, @@ -2861,7 +2784,7 @@ pub fn math_properties(node_id: NodeId, context: &mut NodePropertiesContext) -> return Vec::new(); } }; - let Some(input) = document_node.inputs.get(ExpressionInput::INDEX) else { + let Some(input) = document_node.input(ExpressionInput) else { log::warn!("A widget failed to be built because its node's input index is invalid."); return vec![]; }; @@ -2885,7 +2808,7 @@ pub fn math_properties(node_id: NodeId, context: &mut NodePropertiesContext) -> }) }, node_id, - ExpressionInput::INDEX, + ExpressionInput, )) .on_commit(commit_value) .widget_instance(), @@ -2893,7 +2816,7 @@ pub fn math_properties(node_id: NodeId, context: &mut NodePropertiesContext) -> } widgets })(); - let operand_b = number_widget(ParameterWidgetsInfo::new(node_id, OperandBInput::::INDEX, true, context), NumberInput::default()); + let operand_b = number_widget(ParameterWidgetsInfo::new(node_id, OperandBInput, true, context), NumberInput::default()); let operand_a_hint = vec![TextLabel::new("(Operand A is the primary input)").widget_instance()]; vec![ @@ -2919,11 +2842,32 @@ pub struct ParameterWidgetsInfo<'a> { } impl<'a> ParameterWidgetsInfo<'a> { - pub fn new(node_id: NodeId, index: usize, blank_assist: bool, context: &'a mut NodePropertiesContext) -> ParameterWidgetsInfo<'a> { + /// Reference the parameter by its symbol, e.g. `ParameterWidgetsInfo::new(node_id, brightness_contrast::BrightnessInput, true, context)`, or by an erased [`ParameterRef`] chosen at runtime. + pub fn new(node_id: NodeId, parameter: impl Into, blank_assist: bool, context: &'a mut NodePropertiesContext) -> ParameterWidgetsInfo<'a> { + Self::at_index(node_id, parameter.into().input_index, blank_assist, context) + } + + /// The input slot this parameter row edits. + pub fn input(&self) -> Option<&'a NodeInput> { + self.document_node?.inputs.get(self.index) + } + + /// A widget callback that writes the callback-produced value to this parameter. + pub fn update_value(&self, value: impl Fn(&T) -> TaggedValue + 'static + Send + Sync) -> impl Fn(&T) -> Message + 'static + Send + Sync { + update_value_at_index(value, self.node_id, self.index) + } + + /// Like [`Self::update_value`], for callbacks that sometimes produce no value. + pub fn optionally_update_value(&self, value: impl Fn(&T) -> Option + 'static + Send + Sync) -> impl Fn(&T) -> Message + 'static + Send + Sync { + optionally_update_value_at_index(value, self.node_id, self.index) + } + + /// Reference the parameter by a runtime input index, for callers that receive the index dynamically (e.g. widget overrides). + pub fn at_index(node_id: NodeId, index: usize, blank_assist: bool, context: &'a mut NodePropertiesContext) -> ParameterWidgetsInfo<'a> { let (name, description) = context.network_interface.displayed_input_name_and_description(&node_id, index, context.selection_network_path); let input_type = context .network_interface - .input_type_not_invalid(&InputConnector::node(node_id, index), context.selection_network_path) + .input_type_not_invalid(&InputConnector::node_at_index(node_id, index), context.selection_network_path) .displayed_type(); let document_node = context.network_interface.document_node(&node_id, context.selection_network_path); @@ -3091,15 +3035,14 @@ pub mod choice { } pub fn property_row(self) -> LayoutGroup { - let ParameterWidgetsInfo { document_node, node_id, index, .. } = self.parameter_info; - let Some(document_node) = document_node else { - log::error!("Could not get document node when building property row for node {node_id:?}"); + if self.parameter_info.document_node.is_none() { + log::error!("Could not get document node when building property row for node {:?}", self.parameter_info.node_id); return LayoutGroup::row(Vec::new()); - }; + } - let mut widgets = super::start_widgets(self.parameter_info); + let mut widgets = super::start_widgets(&self.parameter_info); - let Some(input) = document_node.inputs.get(index) else { + let Some(input) = self.parameter_info.input() else { log::warn!("A widget failed to be built because its node's input index is invalid."); return LayoutGroup::row(vec![]); }; @@ -3108,7 +3051,7 @@ pub mod choice { if let Some(current) = input { let committer = || super::commit_value; - let updater = || super::update_value(move |v: &W::Value| TaggedValue::from(v.clone()), node_id, index); + let updater = || self.parameter_info.update_value(move |v: &W::Value| TaggedValue::from(v.clone())); let widget = self.widget_factory.build(current, updater, committer); widgets.extend_from_slice(&[Separator::new(SeparatorStyle::Unrelated).widget_instance(), widget]); } diff --git a/editor/src/messages/portfolio/document/storage_tests/round_trip_tests.rs b/editor/src/messages/portfolio/document/storage_tests/round_trip_tests.rs index ed23c3a8bb..f7f9f4c0ac 100644 --- a/editor/src/messages/portfolio/document/storage_tests/round_trip_tests.rs +++ b/editor/src/messages/portfolio/document/storage_tests/round_trip_tests.rs @@ -16,6 +16,7 @@ use crate::messages::portfolio::document::utility_types::misc::GroupFolderType; use crate::messages::portfolio::document::utility_types::network_interface::NodeNetworkInterface; use crate::messages::portfolio::document::utility_types::network_interface::storage_metadata::{StorageMetadataView, build_interface_from_storage}; use crate::test_utils::test_prelude::*; +use graphene_std::NodeParameter; use graphene_std::vector::style::RenderMode; /// Every node addressable in `original` resolves identically through the round-tripped interface: @@ -722,21 +723,14 @@ fn find_fill_node(document: &DocumentMessageHandler) -> (Vec graph_craft::document::value::TaggedValue { - use graphene_std::NodeInputDecleration as _; - let (network_path, node_id) = find_fill_node(document); let network = document.network_interface.nested_network(&network_path).expect("the found network path should resolve"); - let input = network.nodes[&node_id] - .inputs - .get(graphene_std::vector::fill::FillInput::>::INDEX) - .expect("Fill should have a paint input"); + let input = network.nodes[&node_id].input(graphene_std::vector::fill::FillInput).expect("Fill should have a paint input"); input.as_value().expect("the paint input should hold a value").clone() } #[tokio::test] async fn none_fill_survives_document_reopen() { - use graphene_std::NodeInputDecleration as _; - let mut editor = EditorTestUtils::create(); editor.new_document().await; editor.drag_tool(ToolType::Rectangle, 0., 0., 100., 100., ModifierKeys::empty()).await; @@ -746,7 +740,7 @@ async fn none_fill_survives_document_reopen() { editor .handle_message(NodeGraphMessage::SetInputValue { node_id: fill_node_id, - input_index: graphene_std::vector::fill::FillInput::>::INDEX, + input_index: graphene_std::vector::fill::FillInput::INDEX, value: graph_craft::document::value::TaggedValue::no_paint().into(), }) .await; @@ -769,7 +763,6 @@ async fn none_fill_survives_document_reopen() { #[tokio::test] async fn legacy_four_input_fill_migrates_to_the_split_transform_shape() { use graph_craft::document::value::TaggedValue; - use graphene_std::NodeInputDecleration as _; // A minimal master-era document: a 4-input Fill (content, fill: wired, backup color, backup gradient) fed by another node const LEGACY_DOCUMENT: &str = r#"{"network_interface":{"network":{"exports":[{"Node":{"node_id":1,"output_index":0,"lambda":false}}],"nodes":[[1,{"inputs":[{"Value":{"tagged_value":{"GraphicGroup":{"instance":[],"transform":[],"alpha_blending":[],"source_node_id":[]}},"exposed":true}},{"Node":{"node_id":2,"output_index":0,"lambda":false}},{"Value":{"tagged_value":{"OptionalColor":null},"exposed":false}},{"Value":{"tagged_value":{"Gradient":{"stops":[[0.0,{"red":0.0,"green":0.0,"blue":0.0,"alpha":1.0}],[1.0,{"red":1.0,"green":1.0,"blue":1.0,"alpha":1.0}]],"gradient_type":"Linear","start":[0.0,0.5],"end":[1.0,0.5],"transform":[1.0,0.0,0.0,1.0,0.0,0.0]}},"exposed":false}}],"manual_composition":{"Concrete":{"name":"core::option::Option>","alias":null}},"implementation":{"ProtoNode":{"name":"graphene_core::vector::FillNode"}},"visible":true,"skip_deduplication":false}],[2,{"inputs":[{"Value":{"tagged_value":"None","exposed":false}},{"Value":{"tagged_value":{"GradientStops":[[0.0,{"red":0.0,"green":0.0,"blue":0.0,"alpha":1.0}],[1.0,{"red":1.0,"green":1.0,"blue":1.0,"alpha":1.0}]]},"exposed":false}},{"Value":{"tagged_value":{"F64":0.5},"exposed":false}}],"manual_composition":{"Concrete":{"name":"core::option::Option>","alias":null}},"implementation":{"ProtoNode":{"name":"graphene_core::ops::SampleGradientNode"}},"visible":true,"skip_deduplication":false}]],"scope_injections":[]},"network_metadata":{"persistent_metadata":{"node_metadata":[[1,{"persistent_metadata":{"reference":"Fill","display_name":"","input_properties":[{"input_data":{"input_name":"Vector Data"},"widget_override":null},{"input_data":{"input_name":"Fill"},"widget_override":null},{"input_data":{"input_name":"Backup Color"},"widget_override":null},{"input_data":{"input_name":"Backup Gradient"},"widget_override":null}],"output_names":["Future>"],"has_primary_output":true,"locked":false,"pinned":false,"node_type_metadata":{"Node":{"position":{"Absolute":[0,0]}}},"network_metadata":null}}],[2,{"persistent_metadata":{"reference":"Sample Gradient","display_name":"","input_properties":[{"input_data":{"input_name":"Primary"},"widget_override":null},{"input_data":{"input_name":"Gradient"},"widget_override":null},{"input_data":{"input_name":"Position"},"widget_override":null}],"output_names":["Future"],"has_primary_output":true,"locked":false,"pinned":false,"node_type_metadata":{"Node":{"position":{"Absolute":[-20,0]}}},"network_metadata":null}}]],"previewing":"No","navigation_metadata":{"node_graph_ptz":{"pan":[0.0,0.0],"tilt":0.0,"zoom":1.0,"flip":false},"node_graph_to_viewport":[1.0,0.0,0.0,1.0,0.0,0.0],"node_graph_top_right":[0.0,0.0]},"selection_undo_history":[],"selection_redo_history":[]}}},"collapsed":[],"name":"legacy_fill.graphite","commit_hash":"0000000000000000000000000000000000000000","document_ptz":{"pan":[0.0,0.0],"tilt":0.0,"zoom":1.0,"flip":false},"document_mode":"DesignMode","view_mode":"Normal","overlays_visibility_settings":{"all":true,"artboard_name":true,"compass_rose":true,"quick_measurement":true,"transform_measurement":true,"transform_cage":true,"hover_outline":true,"selection_outline":true,"pivot":true,"path":true,"anchors":true,"handles":true},"rulers_visible":true,"snapping_state":{"snapping_enabled":true,"grid_snapping":false,"artboards":true,"tolerance":8.0,"bounding_box":{"center_point":true,"corner_point":true,"edge_midpoint":true,"align_with_edges":true,"distribute_evenly":true},"path":{"anchor_point":true,"line_midpoint":true,"along_path":true,"normal_to_path":true,"tangent_to_path":true,"path_intersection_point":true,"align_with_anchor_point":true,"perpendicular_from_endpoint":true},"grid":{"origin":[0.0,0.0],"grid_type":{"Rectangular":{"spacing":[1.0,1.0]}},"grid_color":{"red":0.6,"green":0.6,"blue":0.6,"alpha":1.0},"dot_display":false}},"graph_view_overlay_open":false,"graph_fade_artwork_percentage":80.0}"#; @@ -789,20 +782,20 @@ async fn legacy_four_input_fill_migrates_to_the_split_transform_shape() { let document = editor.active_document(); let (network_path, node_id) = find_fill_node(document); let network = document.network_interface.nested_network(&network_path).expect("the found network path should resolve"); - let inputs = &network.nodes[&node_id].inputs; + let fill_node = &network.nodes[&node_id]; - assert_eq!(inputs.len(), 8, "the legacy Fill should upgrade to the 8-input shape"); - let paint = &inputs[graphene_std::vector::fill::FillInput::>::INDEX]; + assert_eq!(fill_node.inputs.len(), 8, "the legacy Fill should upgrade to the 8-input shape"); + let paint = fill_node.input(graphene_std::vector::fill::FillInput); assert!( - matches!(paint, graph_craft::document::NodeInput::Node { .. }), + matches!(paint, Some(graph_craft::document::NodeInput::Node { .. })), "the wired legacy fill should keep its connection, but became {paint:?}" ); - let has_transform = inputs[graphene_std::vector::fill::HasTransformInput::INDEX].as_value(); + let has_transform = fill_node.input_value(graphene_std::vector::fill::HasTransformInput); assert!( matches!(has_transform, Some(TaggedValue::Bool(_))), "the has-transform input should hold a bool, but became {has_transform:?}" ); - let transform = inputs[graphene_std::vector::fill::TransformInput::INDEX].as_value(); + let transform = fill_node.input_value(graphene_std::vector::fill::TransformInput); assert!( matches!(transform, Some(TaggedValue::DAffine2(_))), "the transform input should hold a matrix, but became {transform:?}" diff --git a/editor/src/messages/portfolio/document/utility_types/network_interface/caches.rs b/editor/src/messages/portfolio/document/utility_types/network_interface/caches.rs index eb12f2dcf5..79fd124104 100644 --- a/editor/src/messages/portfolio/document/utility_types/network_interface/caches.rs +++ b/editor/src/messages/portfolio/document/utility_types/network_interface/caches.rs @@ -145,7 +145,7 @@ impl NodeNetworkInterface { }; let Some(first_downstream_input) = self.with_outward_wires(network_path, |outward_wires| { outward_wires - .get(&OutputConnector::node(current_node, 0)) + .get(&OutputConnector::primary_output(current_node)) .map(|layer_outward_wires| layer_outward_wires.first().copied()) }) else { log::error!("Cannot load outward wires in load_stack_dependents"); @@ -565,9 +565,13 @@ impl NodeNetworkInterface { for (current_node_id, node) in network.nodes.iter() { for (input_index, input) in node.inputs.iter().enumerate() { if let NodeInput::Node { node_id, output_index, .. } = input { - push_outward_wire(&mut outward_wires, OutputConnector::node(*node_id, *output_index), InputConnector::node(*current_node_id, input_index)); + push_outward_wire( + &mut outward_wires, + OutputConnector::node(*node_id, *output_index), + InputConnector::node_at_index(*current_node_id, input_index), + ); } else if let NodeInput::Import { import_index, .. } = input { - push_outward_wire(&mut outward_wires, OutputConnector::Import(*import_index), InputConnector::node(*current_node_id, input_index)); + push_outward_wire(&mut outward_wires, OutputConnector::Import(*import_index), InputConnector::node_at_index(*current_node_id, input_index)); } } } @@ -753,7 +757,7 @@ impl NodeNetworkInterface { } for (node_id, node) in &network.nodes { for input_index in 0..node.inputs.len() { - input_connectors.push(InputConnector::node(*node_id, input_index)); + input_connectors.push(InputConnector::node_at_index(*node_id, input_index)); } } input_connectors @@ -792,7 +796,7 @@ impl NodeNetworkInterface { input_connectors.extend(inputs.clone()) } for input_index in 0..self.number_of_inputs(node_id, network_path) { - input_connectors.push(InputConnector::node(*node_id, input_index)); + input_connectors.push(InputConnector::node_at_index(*node_id, input_index)); } for input in input_connectors { self.unload_wire(&input, network_path); @@ -1154,7 +1158,7 @@ impl NodeNetworkInterface { LayerPosition::Absolute(position) => Some(position), LayerPosition::Stack(y_offset) => { let Some(downstream_node_connectors) = self - .with_outward_wires(network_path, |outward_wires| outward_wires.get(&OutputConnector::node(*node_id, 0)).cloned()) + .with_outward_wires(network_path, |outward_wires| outward_wires.get(&OutputConnector::primary_output(*node_id)).cloned()) .flatten() else { log::error!("Could not get downstream node in position_from_downstream_node"); @@ -1188,7 +1192,7 @@ impl NodeNetworkInterface { loop { // TODO: Use root node to restore if previewing let Some(downstream_node_connectors) = self - .with_outward_wires(network_path, |outward_wires| outward_wires.get(&OutputConnector::node(current_node_id, 0)).cloned()) + .with_outward_wires(network_path, |outward_wires| outward_wires.get(&OutputConnector::primary_output(current_node_id)).cloned()) .flatten() else { log::error!("Could not get downstream node for node {node_id} with Position::Chain"); diff --git a/editor/src/messages/portfolio/document/utility_types/network_interface/characterization_tests.rs b/editor/src/messages/portfolio/document/utility_types/network_interface/characterization_tests.rs index 29c4775004..6059583ff8 100644 --- a/editor/src/messages/portfolio/document/utility_types/network_interface/characterization_tests.rs +++ b/editor/src/messages/portfolio/document/utility_types/network_interface/characterization_tests.rs @@ -63,7 +63,7 @@ async fn deleting_a_node_with_children_prunes_them_from_the_selection() { assert!(network_interface.number_of_inputs(&parent, &[]) >= 2, "Test needs a secondary input to wire the child into"); // Wire the child into the parent's secondary input so it is a sole dependent, then select both and delete only the parent - network_interface.set_input(&InputConnector::node(parent, 1), NodeInput::node(child, 0), &[]); + network_interface.set_input(&InputConnector::node_at_index(parent, 1), NodeInput::node(child, 0), &[]); network_interface.selected_nodes_mut(&[]).unwrap().set_selected_nodes(vec![parent, child]); network_interface.delete_nodes(vec![parent], true, &[]); @@ -86,8 +86,8 @@ async fn deleting_a_node_keeps_children_shared_with_other_nodes() { let network_interface = &mut editor.active_document_mut().network_interface; // Wire the same child into the secondary inputs of both nodes, then delete only the parent along with its children - network_interface.set_input(&InputConnector::node(parent, 1), NodeInput::node(shared_child, 0), &[]); - network_interface.set_input(&InputConnector::node(sibling, 1), NodeInput::node(shared_child, 0), &[]); + network_interface.set_input(&InputConnector::node_at_index(parent, 1), NodeInput::node(shared_child, 0), &[]); + network_interface.set_input(&InputConnector::node_at_index(sibling, 1), NodeInput::node(shared_child, 0), &[]); network_interface.delete_nodes(vec![parent], true, &[]); let nodes = &network_interface.document_network().nodes; @@ -107,14 +107,14 @@ async fn cyclic_connection_is_rejected_without_side_effects() { let b = editor.create_node_by_name(rectangle_definition()).await; let network_interface = &mut editor.active_document_mut().network_interface; - network_interface.set_input(&InputConnector::node(a, 1), NodeInput::node(b, 0), &[]); + network_interface.set_input(&InputConnector::node_at_index(a, 1), NodeInput::node(b, 0), &[]); // Attempt to complete a cycle inside a transaction: the edit must be rejected without marking the transaction as modified network_interface.start_transaction(); - let input_before = network_interface.input_from_connector(&InputConnector::node(b, 1), &[]).cloned(); - network_interface.set_input(&InputConnector::node(b, 1), NodeInput::node(a, 0), &[]); + let input_before = network_interface.input_from_connector(&InputConnector::node_at_index(b, 1), &[]).cloned(); + network_interface.set_input(&InputConnector::node_at_index(b, 1), NodeInput::node(a, 0), &[]); - let input_after = network_interface.input_from_connector(&InputConnector::node(b, 1), &[]).cloned(); + let input_after = network_interface.input_from_connector(&InputConnector::node_at_index(b, 1), &[]).cloned(); assert_eq!(input_before, input_after, "A rejected cyclic connection should leave the input unchanged"); assert_eq!( network_interface.transaction_status(), @@ -204,13 +204,13 @@ async fn layer_stacking_follows_wiring() { // Wiring a layer into the bottom input of another layer converts it to stack positioning at its current visual spot let lower_position_before = network_interface.position(&lower, &[]).expect("Lower layer should have a position"); - network_interface.create_wire(&OutputConnector::node(lower, 0), &InputConnector::node(upper, 0), &[]); + network_interface.create_wire(&OutputConnector::primary_output(lower), &InputConnector::primary_input(upper), &[]); assert!(network_interface.is_stack(&lower, &[]), "A layer feeding the bottom of a layer should be stack positioned"); let stacked_position = network_interface.position(&lower, &[]).expect("Stacked layer should have a position"); assert_eq!(stacked_position.y, lower_position_before.y, "Stacking should preserve the layer's vertical position"); // Disconnecting converts the layer back to absolute positioning without moving it - network_interface.disconnect_input(&InputConnector::node(upper, 0), &[]); + network_interface.disconnect_input(&InputConnector::primary_input(upper), &[]); assert!(network_interface.is_absolute(&lower, &[]), "A disconnected stack layer should return to absolute positioning"); assert_eq!(network_interface.position(&lower, &[]), Some(stacked_position), "Unstacking should not move the layer"); @@ -229,7 +229,7 @@ async fn chain_membership_follows_wiring() { network_interface.set_to_node_or_layer(&layer, &[], true); // A node wired into a layer's secondary input from the same row, within chain distance, joins the chain - network_interface.create_wire(&OutputConnector::node(node, 0), &InputConnector::node(layer, 1), &[]); + network_interface.create_wire(&OutputConnector::primary_output(node), &InputConnector::layer_secondary_input(layer), &[]); assert!( network_interface.is_chain(&node, &[]), "A node feeding a layer's secondary input from chain range should become a chain node" @@ -237,7 +237,7 @@ async fn chain_membership_follows_wiring() { // Disconnecting breaks the chain and the node becomes absolute at its chain spot let chained_y = network_interface.position(&node, &[]).expect("Chained node should have a position").y; - network_interface.disconnect_input(&InputConnector::node(layer, 1), &[]); + network_interface.disconnect_input(&InputConnector::layer_secondary_input(layer), &[]); assert!(!network_interface.is_chain(&node, &[]), "Disconnecting should break the chain"); assert!(network_interface.is_absolute(&node, &[])); assert_eq!(network_interface.position(&node, &[]).map(|position| position.y), Some(chained_y)); diff --git a/editor/src/messages/portfolio/document/utility_types/network_interface/hit_tests.rs b/editor/src/messages/portfolio/document/utility_types/network_interface/hit_tests.rs index 14879febb5..4c93827d6b 100644 --- a/editor/src/messages/portfolio/document/utility_types/network_interface/hit_tests.rs +++ b/editor/src/messages/portfolio/document/utility_types/network_interface/hit_tests.rs @@ -220,7 +220,7 @@ impl NodeNetworkInterface { transient_node_metadata .port_click_targets .clicked_input_port_from_point(point) - .map(|port| InputConnector::node(*node_id, port)) + .map(|port| InputConnector::node_at_index(*node_id, port)) }) .flatten() }) diff --git a/editor/src/messages/portfolio/document/utility_types/network_interface/layout.rs b/editor/src/messages/portfolio/document/utility_types/network_interface/layout.rs index 249c074745..58c2313c24 100644 --- a/editor/src/messages/portfolio/document/utility_types/network_interface/layout.rs +++ b/editor/src/messages/portfolio/document/utility_types/network_interface/layout.rs @@ -105,7 +105,7 @@ impl NodeNetworkInterface { } let downstream_connection_count = self .with_outward_wires(network_path, |outward_wires| { - outward_wires.get(&OutputConnector::node(upstream_node, 0)).map(|connections| connections.len()) + outward_wires.get(&OutputConnector::primary_output(upstream_node)).map(|connections| connections.len()) }) .flatten(); let Some(downstream_connection_count) = downstream_connection_count else { @@ -155,7 +155,7 @@ impl NodeNetworkInterface { break; }; downstream_layer = outward_wires - .get(&OutputConnector::node(downstream_layer_id, 0)) + .get(&OutputConnector::primary_output(downstream_layer_id)) .and_then(|outward_wires| if outward_wires.len() == 1 { outward_wires[0].node_id() } else { None }); } else { break; @@ -170,7 +170,7 @@ impl NodeNetworkInterface { pub(crate) fn try_set_node_to_chain(&mut self, node_id: &NodeId, network_path: &[NodeId]) { if let Some(outward_wires) = self .outward_wires(network_path) - .and_then(|outward_wires| outward_wires.get(&OutputConnector::node(*node_id, 0))) + .and_then(|outward_wires| outward_wires.get(&OutputConnector::primary_output(*node_id))) .cloned() && outward_wires.len() == 1 { self.try_set_upstream_to_chain(&outward_wires[0], network_path) @@ -182,7 +182,7 @@ impl NodeNetworkInterface { if !self.is_layer(upstream_id, network_path) && self .outward_wires(network_path) - .is_some_and(|outward_wires| outward_wires.get(&OutputConnector::node(*upstream_id, 0)).is_some_and(|outward_wires| outward_wires.len() == 1)) + .is_some_and(|outward_wires| outward_wires.get(&OutputConnector::primary_output(*upstream_id)).is_some_and(|outward_wires| outward_wires.len() == 1)) { self.set_chain_position(upstream_id, network_path); } @@ -312,7 +312,7 @@ impl NodeNetworkInterface { && let LayerPosition::Stack(offset) = layer_metadata.position { // If the upstream layer is selected, then skip - let Some(outward_wires) = self.outward_wires(network_path).and_then(|outward_wires| outward_wires.get(&OutputConnector::node(*node_id, 0))) else { + let Some(outward_wires) = self.outward_wires(network_path).and_then(|outward_wires| outward_wires.get(&OutputConnector::primary_output(*node_id))) else { log::error!("Could not get outward wires in shift_selected_nodes"); return; }; @@ -365,7 +365,7 @@ impl NodeNetworkInterface { } let Some(downstream_node) = self .outward_wires(network_path) - .and_then(|outward_wires| outward_wires.get(&OutputConnector::node(downstream_absolute_layer, 0))) + .and_then(|outward_wires| outward_wires.get(&OutputConnector::primary_output(downstream_absolute_layer))) .and_then(|downstream_nodes| downstream_nodes.first()) .and_then(|downstream_node| downstream_node.node_id()) else { @@ -692,7 +692,7 @@ impl NodeNetworkInterface { // 1. Disconnect old upstream from post_node, wire layer output to post_node self.set_input_for_import(&post_node, layer_output, network_path); // 2. Wire old upstream into layer's primary (stack) input - self.set_input_for_import(&InputConnector::node(layer.to_node(), 0), post_node_input, network_path); + self.set_input_for_import(&InputConnector::primary_input(layer.to_node()), post_node_input, network_path); } NodeInput::Import { .. } => { log::error!("Cannot insert import layer into a parent that connects to the imports"); @@ -856,7 +856,7 @@ impl NodeNetworkInterface { match post_node_input { // Create a new stack NodeInput::Value { .. } | NodeInput::Scope(_) | NodeInput::Inline(_) | NodeInput::Reflection(_) => { - self.create_wire(&OutputConnector::node(layer.to_node(), 0), &post_node, network_path); + self.create_wire(&OutputConnector::primary_output(layer.to_node()), &post_node, network_path); let final_layer_position = after_move_post_layer_position + IVec2::new(-LAYER_INDENT_OFFSET, STACK_VERTICAL_GAP); let shift = final_layer_position - previous_layer_position; @@ -884,7 +884,7 @@ impl NodeNetworkInterface { NodeInput::Value { .. } | NodeInput::Scope(_) | NodeInput::Inline(_) | NodeInput::Reflection(_) => { let offset = after_move_post_layer_position - previous_layer_position + IVec2::new(0, STACK_VERTICAL_GAP + height_above_layer); self.shift_absolute_node_position(&layer.to_node(), offset, network_path); - self.create_wire(&OutputConnector::node(layer.to_node(), 0), &post_node, network_path); + self.create_wire(&OutputConnector::primary_output(layer.to_node()), &post_node, network_path); } // Insert into the stack NodeInput::Node { .. } => { @@ -903,7 +903,7 @@ impl NodeNetworkInterface { self.insert_node_between(&layer.to_node(), &post_node, 0, network_path); // Get the other wires which need to be moved to the output of the moved layer - let layer_input_connector = InputConnector::node(layer.to_node(), 0); + let layer_input_connector = InputConnector::primary_input(layer.to_node()); let other_outward_wires = self .upstream_output_connector(&layer_input_connector, network_path) .and_then(|pre_node_output| self.outward_wires(network_path).and_then(|wires| wires.get(&pre_node_output))) @@ -919,7 +919,7 @@ impl NodeNetworkInterface { // Disconnect and reconnect for other_outward_wire in &other_outward_wires { self.disconnect_input(other_outward_wire, network_path); - self.create_wire(&OutputConnector::node(layer.to_node(), 0), other_outward_wire, network_path); + self.create_wire(&OutputConnector::primary_output(layer.to_node()), other_outward_wire, network_path); } } self.unload_upstream_node_click_targets(vec![layer.to_node()], network_path); @@ -941,10 +941,10 @@ impl NodeNetworkInterface { self.disconnect_input(input_connector, network_path); // Connect the input connector to the new node - self.create_wire(&OutputConnector::node(*node_id, 0), input_connector, network_path); + self.create_wire(&OutputConnector::primary_output(*node_id), input_connector, network_path); // Connect the new node to the previous node - self.create_wire(&upstream_output, &InputConnector::node(*node_id, insert_node_input_index), network_path); + self.create_wire(&upstream_output, &InputConnector::node_at_index(*node_id, insert_node_input_index), network_path); } /// Inserts the freshly-created `node_id` onto the wire feeding `input_connector`: the previous upstream becomes the @@ -962,11 +962,11 @@ impl NodeNetworkInterface { return; }; - if self.input_from_connector(&InputConnector::node(*node_id, 0), network_path).is_none() { + if self.input_from_connector(&InputConnector::primary_input(*node_id), network_path).is_none() { return; } - self.set_input(&InputConnector::node(*node_id, 0), current_input, network_path); + self.set_input(&InputConnector::primary_input(*node_id), current_input, network_path); self.set_input(input_connector, NodeInput::node(*node_id, 0), network_path); // If `set_input` chain-positioned the node (it joined a layer chain), there's nothing more to do. @@ -990,7 +990,7 @@ impl NodeNetworkInterface { /// Moves a node to the start of a layer chain (feeding into the secondary input of the layer). /// When `import` is true, uses lightweight wiring that skips `is_acyclic` checks and per-node cache invalidation. pub fn move_node_to_chain_start(&mut self, node_id: &NodeId, parent: LayerNodeIdentifier, network_path: &[NodeId], import: bool) { - let parent_input = InputConnector::node(parent.to_node(), 1); + let parent_input = InputConnector::layer_secondary_input(parent.to_node()); let Some(current_input) = self.input_from_connector(&parent_input, network_path).cloned() else { log::error!("Could not get input for node {node_id}"); return; @@ -999,7 +999,7 @@ impl NodeNetworkInterface { // Chain is empty: wire the node as the first (and only) entry in the chain if matches!(current_input, NodeInput::Value { .. }) { // A node whose exposed primary defaults to no value inherits the layer's content value, so the chain keeps producing the layer's content type - let node_primary = InputConnector::node(*node_id, 0); + let node_primary = InputConnector::primary_input(*node_id); let default_is_valueless = self .input_from_connector(&node_primary, network_path) .is_some_and(|input| matches!(input, NodeInput::Value { tagged_value, exposed: true } if matches!(**tagged_value, TaggedValue::None))); @@ -1015,7 +1015,7 @@ impl NodeNetworkInterface { if import { self.set_input_for_import(&parent_input, NodeInput::node(*node_id, 0), network_path); } else { - self.create_wire(&OutputConnector::node(*node_id, 0), &parent_input, network_path); + self.create_wire(&OutputConnector::primary_output(*node_id), &parent_input, network_path); } // Mark this lone node as chain-positioned @@ -1026,7 +1026,7 @@ impl NodeNetworkInterface { // Wire: [parent] -> [new node] -> [existing node] if import { self.set_input_for_import(&parent_input, NodeInput::node(*node_id, 0), network_path); - self.set_input_for_import(&InputConnector::node(*node_id, 0), current_input, network_path); + self.set_input_for_import(&InputConnector::primary_input(*node_id), current_input, network_path); } else { self.insert_node_between(node_id, &parent_input, 0, network_path); } @@ -1075,7 +1075,7 @@ impl NodeNetworkInterface { let tail_input = if let Some(source) = pinned_source { NodeInput::node(source, 0) } else { - let Some(input) = self.input_from_connector(&InputConnector::node(*chain.last().unwrap(), 0), network_path).cloned() else { + let Some(input) = self.input_from_connector(&InputConnector::primary_input(*chain.last().unwrap()), network_path).cloned() else { log::error!("Could not get the upstream input of the chain in reorder_chain_node"); return; }; @@ -1084,15 +1084,15 @@ impl NodeNetworkInterface { // Disconnect first so the rewiring can't transiently form a cycle (the pinned source keeps its wiring) for &chain_node in reorderable { - self.disconnect_input(&InputConnector::node(chain_node, 0), network_path); + self.disconnect_input(&InputConnector::primary_input(chain_node), network_path); } // Rewire in the new order: layer's secondary input -> new_order[0] -> ... -> new_order[last] -> tail input - self.set_input(&InputConnector::node(layer, 1), NodeInput::node(new_order[0], 0), network_path); + self.set_input(&InputConnector::layer_secondary_input(layer), NodeInput::node(new_order[0], 0), network_path); for pair in new_order.windows(2) { - self.set_input(&InputConnector::node(pair[0], 0), NodeInput::node(pair[1], 0), network_path); + self.set_input(&InputConnector::primary_input(pair[0]), NodeInput::node(pair[1], 0), network_path); } - self.set_input(&InputConnector::node(*new_order.last().unwrap(), 0), tail_input, network_path); + self.set_input(&InputConnector::primary_input(*new_order.last().unwrap()), tail_input, network_path); // Re-establish chain positioning for the reordered nodes self.force_set_upstream_to_chain(&new_order[0], network_path); diff --git a/editor/src/messages/portfolio/document/utility_types/network_interface/mutations.rs b/editor/src/messages/portfolio/document/utility_types/network_interface/mutations.rs index f4a1b598d5..1b2b873985 100644 --- a/editor/src/messages/portfolio/document/utility_types/network_interface/mutations.rs +++ b/editor/src/messages/portfolio/document/utility_types/network_interface/mutations.rs @@ -887,7 +887,7 @@ impl NodeNetworkInterface { // If the layer feeds into the bottom input of layer, and has no other outputs, set its position to stack at its previous y position let multiple_outward_wires = self .outward_wires(network_path) - .and_then(|all_outward_wires| all_outward_wires.get(&OutputConnector::node(*upstream_node_id, 0))) + .and_then(|all_outward_wires| all_outward_wires.get(&OutputConnector::primary_output(*upstream_node_id))) .is_some_and(|outward_wires| outward_wires.len() > 1); if *input_index == 0 && !multiple_outward_wires { self.set_stack_position_calculated_offset(upstream_node_id, downstream_node_id, network_path); @@ -932,7 +932,7 @@ impl NodeNetworkInterface { let old_upstream_node_is_layer = self.is_layer(&old_upstream_node_id, network_path); let Some(outward_wires) = self .outward_wires(network_path) - .and_then(|outward_wires| outward_wires.get(&OutputConnector::node(old_upstream_node_id, 0))) + .and_then(|outward_wires| outward_wires.get(&OutputConnector::primary_output(old_upstream_node_id))) else { log::error!("Could not get outward wires in set_input"); return; @@ -1132,11 +1132,14 @@ impl NodeNetworkInterface { // Perform an upstream traversal to try delete children for secondary inputs let mut upstream_nodes = (1..self.number_of_inputs(node_id, network_path)) - .filter_map(|input_index| self.upstream_output_connector(&InputConnector::node(*node_id, input_index), network_path).and_then(|oc| oc.node_id())) + .filter_map(|input_index| { + self.upstream_output_connector(&InputConnector::node_at_index(*node_id, input_index), network_path) + .and_then(|oc| oc.node_id()) + }) .collect::>(); while let Some(upstream_node) = upstream_nodes.pop() { // Add the upstream nodes to the traversal - for input_connector in (0..self.number_of_inputs(&upstream_node, network_path)).map(|input_index| InputConnector::node(upstream_node, input_index)) { + for input_connector in (0..self.number_of_inputs(&upstream_node, network_path)).map(|input_index| InputConnector::node_at_index(upstream_node, input_index)) { if let Some(upstream_node) = self.upstream_output_connector(&input_connector, network_path).and_then(|oc| oc.node_id()) { upstream_nodes.push(upstream_node); } @@ -1174,7 +1177,7 @@ impl NodeNetworkInterface { // Disconnect every input by position, since hidden inputs make the displayed count undershoot the index of a later exposed wire for input_index in 0..self.number_of_inputs(delete_node_id, network_path) { - self.disconnect_input(&InputConnector::node(*delete_node_id, input_index), network_path); + self.disconnect_input(&InputConnector::node_at_index(*delete_node_id, input_index), network_path); } let Some(network) = self.network_mut(network_path) else { @@ -1257,7 +1260,7 @@ impl NodeNetworkInterface { && let Some(reconnect_input) = &reconnect_to_input { reconnect_node = reconnect_input.as_node().and_then(|node_id| if self.is_stack(&node_id, network_path) { Some(node_id) } else { None }); - self.disconnect_input(&InputConnector::node(*node_id, 0), network_path); + self.disconnect_input(&InputConnector::primary_input(*node_id), network_path); self.set_input(downstream_input, reconnect_input.clone(), network_path); } } @@ -1463,7 +1466,7 @@ impl NodeNetworkInterface { if self.is_layer(&upstream_sibling_id, network_path) && self .outward_wires(network_path) - .and_then(|outward_wires| outward_wires.get(&OutputConnector::node(upstream_sibling_id, 0))) + .and_then(|outward_wires| outward_wires.get(&OutputConnector::primary_output(upstream_sibling_id))) .is_some_and(|outward_wires| outward_wires.len() == 1) { self.set_stack_position_calculated_offset(&upstream_sibling_id, node_id, network_path); @@ -1484,7 +1487,7 @@ impl NodeNetworkInterface { .outward_wires(network_path) .and_then(|outward_wires| { outward_wires - .get(&OutputConnector::node(*node_id, 0)) + .get(&OutputConnector::primary_output(*node_id)) .and_then(|outward_wires| (outward_wires.len() == 1).then(|| outward_wires[0])) .and_then(|downstream_connector| if downstream_connector.input_index() == 0 { downstream_connector.node_id() } else { None }) }) @@ -1509,7 +1512,7 @@ impl NodeNetworkInterface { // Try build the chain if is_layer { - self.try_set_upstream_to_chain(&InputConnector::node(*node_id, 1), network_path); + self.try_set_upstream_to_chain(&InputConnector::layer_secondary_input(*node_id), network_path); } else { self.try_set_node_to_chain(node_id, network_path); } @@ -1569,7 +1572,7 @@ impl NodeNetworkInterface { } // The export is not clicked else { - new_export = Some(OutputConnector::node(toggle_id, 0)); + new_export = Some(OutputConnector::primary_output(toggle_id)); // There is currently a dashed line being drawn if let Previewing::Yes { root_node_to_restore } = self.previewing(network_path) { @@ -1577,7 +1580,7 @@ impl NodeNetworkInterface { if let Some(root_node_to_restore) = root_node_to_restore { // If the node with the solid line is clicked, then start previewing that node without restore if root_node_to_restore.node_id == toggle_id { - new_export = Some(OutputConnector::node(toggle_id, 0)); + new_export = Some(OutputConnector::primary_output(toggle_id)); new_previewing_state = Previewing::Yes { root_node_to_restore: None }; } else { // Root node to restore does not change @@ -1593,7 +1596,7 @@ impl NodeNetworkInterface { } // Not previewing, there is no dashed line being drawn else { - new_export = Some(OutputConnector::node(toggle_id, 0)); + new_export = Some(OutputConnector::primary_output(toggle_id)); new_previewing_state = Previewing::Yes { root_node_to_restore: Some(RootNode { node_id: previous_export_id, @@ -1605,7 +1608,7 @@ impl NodeNetworkInterface { } // The primary export is disconnected, so preview the node with nothing to restore, which disconnects the export again when the preview ends else { - new_export = Some(OutputConnector::node(toggle_id, 0)); + new_export = Some(OutputConnector::primary_output(toggle_id)); new_previewing_state = Previewing::Yes { root_node_to_restore: None }; } } diff --git a/editor/src/messages/portfolio/document/utility_types/network_interface/queries.rs b/editor/src/messages/portfolio/document/utility_types/network_interface/queries.rs index 2ea19dfb74..98698effef 100644 --- a/editor/src/messages/portfolio/document/utility_types/network_interface/queries.rs +++ b/editor/src/messages/portfolio/document/utility_types/network_interface/queries.rs @@ -108,7 +108,7 @@ impl NodeNetworkInterface { while !self.is_layer(&id, network_path) { id = self.with_outward_wires(network_path, |outward_wires| { outward_wires - .get(&OutputConnector::node(id, 0)) + .get(&OutputConnector::primary_output(id)) .and_then(|connections| connections.first()) .and_then(|connector| connector.node_id()) })??; @@ -125,7 +125,7 @@ impl NodeNetworkInterface { layers.push(current_node); } else { let downstream_found = self.with_outward_wires(network_path, |outward_wires| { - let Some(connections) = outward_wires.get(&OutputConnector::node(current_node, 0)) else { + let Some(connections) = outward_wires.get(&OutputConnector::primary_output(current_node)) else { return false; }; stack.extend(connections.iter().filter_map(|input_connector| input_connector.node_id())); @@ -185,7 +185,7 @@ impl NodeNetworkInterface { self.create_node_template(node_id, network_path).and_then(|mut node_template| { // TODO: Get downstream connections from all outputs let Some(has_selected_node_downstream) = self.with_outward_wires(network_path, |outward_wires| { - outward_wires.get(&OutputConnector::node(*node_id, 0)).is_some_and(|outputs| { + outward_wires.get(&OutputConnector::primary_output(*node_id)).is_some_and(|outputs| { outputs .iter() .any(|input_connector| input_connector.node_id().is_some_and(|upstream_id| new_ids.keys().any(|key| *key == upstream_id))) @@ -241,7 +241,7 @@ impl NodeNetworkInterface { for old_id in new_nodes.iter().map(|(_, old_id, _)| *old_id).collect::>() { // Try set all selected nodes upstream of a layer to be chain nodes if self.is_layer(&old_id, network_path) { - for valid_upstream_chain_node in self.valid_upstream_chain_nodes(&InputConnector::node(old_id, 1), network_path) { + for valid_upstream_chain_node in self.valid_upstream_chain_nodes(&InputConnector::layer_secondary_input(old_id), network_path) { if let Some(node_template) = new_nodes.iter_mut().find_map(|(_, old_id, template)| (*old_id == valid_upstream_chain_node).then_some(template)) { match &mut node_template.node_type_metadata { NodeTypePersistentMetadata::Node(node_metadata) => node_metadata.position = NodePosition::Chain, @@ -269,12 +269,12 @@ impl NodeNetworkInterface { *input = NodeInput::Node { node_id: new_id, output_index }; } else { // Disconnect node input if it is not connected to another node in new_ids - let tagged_value = self.tagged_value_from_input(&InputConnector::node(*node_id, input_index), network_path); + let tagged_value = self.tagged_value_from_input(&InputConnector::node_at_index(*node_id, input_index), network_path); *input = NodeInput::value(tagged_value, true); } } else if let &mut NodeInput::Import { .. } = input { // Always disconnect network node input - let tagged_value = self.tagged_value_from_input(&InputConnector::node(*node_id, input_index), network_path); + let tagged_value = self.tagged_value_from_input(&InputConnector::node_at_index(*node_id, input_index), network_path); *input = NodeInput::value(tagged_value, true); } } @@ -624,7 +624,7 @@ impl NodeNetworkInterface { let mut post_node_input_connector = if parent == LayerNodeIdentifier::ROOT_PARENT { InputConnector::Export(0) } else { - InputConnector::node(parent.to_node(), 1) + InputConnector::layer_secondary_input(parent.to_node()) }; // Skip layers based on skip_layer_nodes, which inserts the new layer at a certain index of the layer stack. let mut current_index = 0; @@ -644,7 +644,7 @@ impl NodeNetworkInterface { current_index += 1; } // Input as a sibling to the Layer node above - post_node_input_connector = InputConnector::node(*next_node_in_stack_id, 0); + post_node_input_connector = InputConnector::primary_input(*next_node_in_stack_id); } else { log::error!("Error getting post node: insert_index out of bounds"); break; @@ -660,7 +660,7 @@ impl NodeNetworkInterface { match pre_node_output_connector { Some(OutputConnector::Node { node_id: pre_node_id, .. }) if !self.is_layer(&pre_node_id, network_path) => { // Update post_node_input_connector for the next iteration - post_node_input_connector = InputConnector::node(pre_node_id, 0); + post_node_input_connector = InputConnector::primary_input(pre_node_id); // Insert directly under layer if moving to the end of a layer stack that ends with a non layer node that does not have an exposed primary input let primary_is_exposed = self.input_from_connector(&post_node_input_connector, network_path).is_some_and(|input| input.is_exposed()); if !primary_is_exposed { @@ -805,7 +805,7 @@ impl NodeNetworkInterface { }; let description = input_metadata.input_description.to_string(); let name = if input_metadata.input_name.is_empty() { - self.input_type(&InputConnector::node(*node_id, input_index), network_path).resolved_type_node_string() + self.input_type(&InputConnector::node_at_index(*node_id, input_index), network_path).resolved_type_node_string() } else { input_metadata.input_name.to_string() }; @@ -846,7 +846,7 @@ impl NodeNetworkInterface { } pub fn primary_output_connected_to_layer(&self, node_id: &NodeId, network_path: &[NodeId]) -> bool { - let Some(downstream_connectors) = self.with_outward_wires(network_path, |outward_wires| outward_wires.get(&OutputConnector::node(*node_id, 0)).cloned()) else { + let Some(downstream_connectors) = self.with_outward_wires(network_path, |outward_wires| outward_wires.get(&OutputConnector::primary_output(*node_id)).cloned()) else { log::error!("Could not get outward_wires in primary_output_connected_to_layer"); return false; }; 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 15f9f4c250..4127baee83 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 @@ -124,7 +124,7 @@ impl NodeNetworkInterface { let outward_wires = self.with_outward_wires(&node_path, |map| map.get(&OutputConnector::Import(*input_index)).cloned()).flatten(); let Some(outward_wires) = outward_wires else { return false }; outward_wires.iter().any(|connector| match connector { - InputConnector::Node { node_id, input_index } => self.input_has_error(&InputConnector::node(*node_id, *input_index), &node_path), + InputConnector::Node { node_id, input_index } => self.input_has_error(&InputConnector::node_at_index(*node_id, *input_index), &node_path), InputConnector::Export(_) => false, }) } @@ -161,7 +161,7 @@ impl NodeNetworkInterface { let Some((encapsulating_node, encapsulating_path)) = network_path.split_last() else { return TypeSource::Error("Could not get type of import in document network since it has no imports"); }; - self.input_type(&InputConnector::node(*encapsulating_node, *import_index), encapsulating_path) + self.input_type(&InputConnector::node_at_index(*encapsulating_node, *import_index), encapsulating_path) } NodeInput::Scope(_) => TypeSource::Compiled(concrete!(())), NodeInput::Reflection(document_node_metadata) => TypeSource::Compiled(document_node_metadata.ty()), @@ -264,7 +264,7 @@ impl NodeNetworkInterface { .filter_map(|node_io| { // Check if this NodeIOTypes implementation is valid for the other inputs let valid_implementation = (0..number_of_inputs).filter(|iterator_index| iterator_index != input_index).all(|iterator_index| { - let input_type = self.input_type_not_invalid(&InputConnector::node(*node_id, iterator_index), network_path); + let input_type = self.input_type_not_invalid(&InputConnector::node_at_index(*node_id, iterator_index), network_path); // TODO: Fix type checking for different call arguments // For example a node input of (Footprint) -> Vector would not be compatible with a node that is called with () and returns Vector node_io.inputs.get(iterator_index).map(|ty| ty.nested_type()) == input_type.compiled_nested_type() @@ -297,7 +297,7 @@ impl NodeNetworkInterface { log::error!("Protonode {proto_node_identifier:?} not found in registry in complete_valid_input_types"); return Vec::new(); }; - let valid_output_types = self.valid_output_types(&OutputConnector::node(*node_id, 0), network_path); + let valid_output_types = self.valid_output_types(&OutputConnector::primary_output(*node_id), network_path); implementations .keys() @@ -307,7 +307,7 @@ impl NodeNetworkInterface { } let valid_inputs = (0..node_io.inputs.len()).filter(|iterator_index| iterator_index != input_index).all(|iterator_index| { - let input_type = self.input_type_not_invalid(&InputConnector::node(*node_id, iterator_index), network_path); + let input_type = self.input_type_not_invalid(&InputConnector::node_at_index(*node_id, iterator_index), network_path); match input_type.compiled_nested_type() { Some(input_type) => node_io.inputs.get(iterator_index).is_some_and(|node_io_input_type| node_io_input_type.nested_type() == input_type), None => true, @@ -342,7 +342,7 @@ impl NodeNetworkInterface { OutputConnector::Node { node_id, output_index } => { // A hidden node is replaced by a passthrough during flattening, so its output carries its primary input's type if *output_index == 0 && !self.is_visible(node_id, network_path) { - return self.input_type(&InputConnector::node(*node_id, 0), network_path); + return self.input_type(&InputConnector::primary_input(*node_id), network_path); } // First try iterating upstream to the first protonode and try get its compiled type @@ -352,7 +352,9 @@ impl NodeNetworkInterface { match implementation { DocumentNodeImplementation::Network(_) => self.input_type(&InputConnector::Export(*output_index), &[network_path, &[*node_id]].concat()), // The compiler removes passthrough nodes so they resolve no type of their own, but their output carries their primary input's type - DocumentNodeImplementation::ProtoNode(identifier) if *identifier == graphene_std::ops::passthrough::IDENTIFIER => self.input_type(&InputConnector::node(*node_id, 0), network_path), + DocumentNodeImplementation::ProtoNode(identifier) if *identifier == graphene_std::ops::passthrough::IDENTIFIER => { + self.input_type(&InputConnector::primary_input(*node_id), network_path) + } DocumentNodeImplementation::ProtoNode(_) => match self.resolved_types.types.get(&[network_path, &[*node_id]].concat()) { Some(resolved_type) => TypeSource::Compiled(resolved_type.output.clone()), None => TypeSource::Unknown, @@ -364,7 +366,7 @@ impl NodeNetworkInterface { let Some((encapsulating_node, encapsulating_path)) = network_path.split_last() else { return TypeSource::Error("Cannot get import type in document network since it has no imports"); }; - let mut input_type = self.input_type(&InputConnector::node(*encapsulating_node, *import_index), encapsulating_path); + let mut input_type = self.input_type(&InputConnector::node_at_index(*encapsulating_node, *import_index), encapsulating_path); if matches!(input_type, TypeSource::Invalid) { input_type = TypeSource::Unknown } diff --git a/editor/src/messages/portfolio/document/utility_types/network_interface/template.rs b/editor/src/messages/portfolio/document/utility_types/network_interface/template.rs index cac31868c0..3fccaba631 100644 --- a/editor/src/messages/portfolio/document/utility_types/network_interface/template.rs +++ b/editor/src/messages/portfolio/document/utility_types/network_interface/template.rs @@ -38,6 +38,18 @@ pub struct NodeTemplate { pub node_type_metadata: NodeTypePersistentMetadata, } +impl NodeTemplate { + /// The input slot named by the given parameter symbol, mirroring [`DocumentNode::input`]. + pub fn input(&self, _parameter: P) -> Option<&NodeInput> { + self.inputs.get(P::INDEX) + } + + /// Mutable access to the input slot named by the given parameter symbol, mirroring [`DocumentNode::input_mut`]. + pub fn input_mut(&mut self, _parameter: P) -> Option<&mut NodeInput> { + self.inputs.get_mut(P::INDEX) + } +} + impl Default for NodeTemplate { fn default() -> Self { Self { diff --git a/editor/src/messages/portfolio/document/utility_types/network_interface/types.rs b/editor/src/messages/portfolio/document/utility_types/network_interface/types.rs index c04e7a52cc..74361697b7 100644 --- a/editor/src/messages/portfolio/document/utility_types/network_interface/types.rs +++ b/editor/src/messages/portfolio/document/utility_types/network_interface/types.rs @@ -1,4 +1,5 @@ use super::*; +use graphene_std::ParameterRef; #[derive(PartialEq)] pub enum FlowType { @@ -60,6 +61,13 @@ pub enum ImportOrExport { Export(usize), } +/// The primary input (index 0) of any node: a chain node's horizontal wire continuing from the left, or a layer's vertical stack wire from the sibling rendered below it. +pub const PRIMARY_INPUT_INDEX: usize = 0; +/// The secondary input (index 1) of a layer-shaped node: the horizontal wire from the left, carrying the node chain or child stack that the layer renders. +pub const LAYER_SECONDARY_INPUT_INDEX: usize = 1; +/// The primary output (index 0) of a node, which most nodes expose as their only output. +pub const PRIMARY_OUTPUT_INDEX: usize = 0; + /// Represents an input connector with index based on the [`DocumentNode::inputs`] index, not the visible input index #[cfg_attr(feature = "wasm", derive(tsify::Tsify))] #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)] @@ -82,7 +90,32 @@ impl Default for InputConnector { } impl InputConnector { - pub fn node(node_id: NodeId, input_index: usize) -> Self { + /// Reference a node's input by its parameter symbol, e.g. `InputConnector::node(node_id, stroke::WeightInput)`, or by an erased [`ParameterRef`] chosen at runtime. + pub fn node(node_id: NodeId, parameter: impl Into) -> Self { + InputConnector::Node { + node_id, + input_index: parameter.into().input_index, + } + } + + /// Reference a node's primary input: a chain node's continuation from the left, or a layer's stack wire from the bottom. + pub fn primary_input(node_id: NodeId) -> Self { + InputConnector::Node { + node_id, + input_index: PRIMARY_INPUT_INDEX, + } + } + + /// Reference a layer-shaped node's secondary input, the wire from the left carrying the content that the layer renders. + pub fn layer_secondary_input(node_id: NodeId) -> Self { + InputConnector::Node { + node_id, + input_index: LAYER_SECONDARY_INPUT_INDEX, + } + } + + /// Reference a node's input by a runtime index, for genuinely dynamic cases like clicked ports, input enumeration, and document upgrades. + pub fn node_at_index(node_id: NodeId, input_index: usize) -> Self { InputConnector::Node { node_id, input_index } } @@ -127,6 +160,14 @@ impl OutputConnector { OutputConnector::Node { node_id, output_index } } + /// Reference a node's primary (first) output. + pub fn primary_output(node_id: NodeId) -> Self { + OutputConnector::Node { + node_id, + output_index: PRIMARY_OUTPUT_INDEX, + } + } + pub fn index(&self) -> usize { match self { OutputConnector::Node { output_index, .. } => *output_index, diff --git a/editor/src/messages/portfolio/document/utility_types/network_interface/view.rs b/editor/src/messages/portfolio/document/utility_types/network_interface/view.rs index 737878f8f9..fafbeb35b5 100644 --- a/editor/src/messages/portfolio/document/utility_types/network_interface/view.rs +++ b/editor/src/messages/portfolio/document/utility_types/network_interface/view.rs @@ -160,7 +160,7 @@ impl<'a, 'p> NetworkView<'a, 'p> { } pub fn primary_input_connected_to_layer(&self, node_id: &NodeId) -> bool { - self.input(&InputConnector::node(*node_id, 0)) + self.input(&InputConnector::primary_input(*node_id)) .ok() .and_then(|input| input.as_node()) .is_some_and(|upstream_id| self.is_layer(&upstream_id).unwrap_or_default()) @@ -234,7 +234,7 @@ impl<'a, 'p> NetworkView<'a, 'p> { } pub fn has_primary_input(&self, node_id: &NodeId) -> Result { - Ok(self.input(&InputConnector::node(*node_id, 0)).is_ok_and(|input| input.is_exposed())) + Ok(self.input(&InputConnector::primary_input(*node_id)).is_ok_and(|input| input.is_exposed())) } pub fn hidden_primary_output(&self, node_id: &NodeId) -> Result { @@ -281,7 +281,7 @@ impl<'a, 'p> NetworkView<'a, 'p> { pub fn persistent_input_metadata(&self, node_id: &NodeId, index: usize) -> Result<&'a InputPersistentMetadata, NetworkError> { let metadata = self.node_metadata(node_id)?; let input_metadata = metadata.persistent_metadata.input_metadata.get(index).ok_or(NetworkError::InputNotFound { - connector: InputConnector::node(*node_id, index), + connector: InputConnector::node_at_index(*node_id, index), })?; Ok(&input_metadata.persistent_metadata) } diff --git a/editor/src/messages/portfolio/document_migration.rs b/editor/src/messages/portfolio/document_migration.rs index 4de2a44b0a..eba90c3b84 100644 --- a/editor/src/messages/portfolio/document_migration.rs +++ b/editor/src/messages/portfolio/document_migration.rs @@ -11,7 +11,7 @@ use graph_craft::document::DocumentNode; use graph_craft::document::{DocumentNodeImplementation, NodeInput, value::TaggedValue}; use graph_craft::{Type, item}; use graphene_std::Color; -use graphene_std::NodeInputDecleration; +use graphene_std::ParameterRef; use graphene_std::ProtoNodeIdentifier; use graphene_std::text::{TextAlign, TypesettingConfig}; use graphene_std::transform::ScaleType; @@ -1181,7 +1181,7 @@ pub fn document_migration_upgrades(document: &mut DocumentMessageHandler, reset_ continue; }; for (index, input) in old_inputs.iter().take(3).enumerate() { - document.network_interface.set_input(&InputConnector::node(*node_id, index), input.clone(), network_path); + document.network_interface.set_input(&InputConnector::node_at_index(*node_id, index), input.clone(), network_path); } } @@ -1208,7 +1208,7 @@ pub fn document_migration_upgrades(document: &mut DocumentMessageHandler, reset_ }; // Forward the first 5 inputs (Value, Translation, Rotation, Scale, Skew); drop indices 5 and 6 if present. for (index, input) in old_inputs.iter().take(5).enumerate() { - document.network_interface.set_input(&InputConnector::node(*node_id, index), input.clone(), network_path); + document.network_interface.set_input(&InputConnector::node_at_index(*node_id, index), input.clone(), network_path); } // Pre-2024 documents stored Transform with 6 inputs and used radians for Rotation and `tan(radians)` for Skew. Detect that legacy @@ -1218,7 +1218,7 @@ pub fn document_migration_upgrades(document: &mut DocumentMessageHandler, reset_ Some(NodeInput::Value { tagged_value, exposed }) => { if let TaggedValue::F64(radians) = *tagged_value.clone().into_inner() { let degrees = NodeInput::value(TaggedValue::F64(radians.to_degrees()), *exposed); - document.network_interface.set_input(&InputConnector::node(*node_id, 2), degrees, network_path); + document.network_interface.set_input(&InputConnector::node_at_index(*node_id, 2), degrees, network_path); } } Some(NodeInput::Node { .. }) => { @@ -1232,7 +1232,9 @@ pub fn document_migration_upgrades(document: &mut DocumentMessageHandler, reset_ let multiply_position = transform_position + IVec2::new(-7, 1); document.network_interface.insert_node(multiply_node_id, multiply_template, network_path); document.network_interface.shift_absolute_node_position(&multiply_node_id, multiply_position, network_path); - document.network_interface.insert_node_between(&multiply_node_id, &InputConnector::node(*node_id, 2), 0, network_path); + document + .network_interface + .insert_node_between(&multiply_node_id, &InputConnector::node_at_index(*node_id, 2), 0, network_path); } } } @@ -1245,7 +1247,7 @@ pub fn document_migration_upgrades(document: &mut DocumentMessageHandler, reset_ // The previous skew value stored `tan(radians)`, now it stores degrees directly. let new_value = DVec2::new(old_value.x.atan().to_degrees(), old_value.y.atan().to_degrees()); let new_input = NodeInput::value(TaggedValue::DVec2(new_value), *exposed); - document.network_interface.set_input(&InputConnector::node(*node_id, 4), new_input, network_path); + document.network_interface.set_input(&InputConnector::node_at_index(*node_id, 4), new_input, network_path); } } } @@ -1270,7 +1272,7 @@ pub fn document_migration_upgrades(document: &mut DocumentMessageHandler, reset_ // Forward the embedded image data into input 0, where the `migrate_node` `image` pass finds it and converts it to a resource. if let Some(image_input) = old_inputs.into_iter().find(|input| matches!(input.as_value(), Some(TaggedValue::ImageData(_)))) { - document.network_interface.set_input(&InputConnector::node(*node_id, 0), image_input, network_path); + document.network_interface.set_input(&InputConnector::node_at_index(*node_id, 0), image_input, network_path); } } @@ -1331,7 +1333,7 @@ pub fn document_migration_upgrades(document: &mut DocumentMessageHandler, reset_ const LEGACY_INPUT_FOR_NEW: [usize; 12] = [0, 1, 2, 3, 4, 5, 10, 6, 7, 8, 9, 11]; for (new_index, &legacy_index) in LEGACY_INPUT_FOR_NEW.iter().enumerate() { if let Some(input) = old_inputs.get(legacy_index) { - document.network_interface.set_input(&InputConnector::node(*node_id, new_index), input.clone(), network_path); + document.network_interface.set_input(&InputConnector::node_at_index(*node_id, new_index), input.clone(), network_path); } } // A `true` toggle at index 12 chose per-glyph geometry, which is now the dedicated "Text to Vector Glyphs" node @@ -1366,7 +1368,9 @@ pub fn document_migration_upgrades(document: &mut DocumentMessageHandler, reset_ document.network_interface.set_input(consumer, NodeInput::node(converter_id, 0), network_path); } } else { - document.network_interface.set_input(&InputConnector::node(converter_id, 0), NodeInput::node(*node_id, 0), network_path); + document + .network_interface + .set_input(&InputConnector::node_at_index(converter_id, 0), NodeInput::node(*node_id, 0), network_path); } // If `text` was in a layer chain, re-chain the converter and its upstream so both lay out by distance from the layer (the splice @@ -1499,23 +1503,25 @@ fn migrate_node(node_id: &NodeId, node: &DocumentNode, network_path: &[NodeId], let _ = document.network_interface.replace_inputs(node_id, network_path, &mut first_template); // Wire the existing node's inputs based on its new role (input 0 is always `content`) - document.network_interface.set_input(&InputConnector::node(*node_id, 0), content_input, network_path); + document.network_interface.set_input(&InputConnector::node_at_index(*node_id, 0), content_input, network_path); match first_kind { SubNode::BlendMode => { - document.network_interface.set_input(&InputConnector::node(*node_id, 1), blend_mode_input.clone(), network_path); + document + .network_interface + .set_input(&InputConnector::node_at_index(*node_id, 1), blend_mode_input.clone(), network_path); } SubNode::Opacity => { document .network_interface - .set_input(&InputConnector::node(*node_id, 1), NodeInput::value(TaggedValue::Bool(keep_opacity), false), network_path); - document.network_interface.set_input(&InputConnector::node(*node_id, 2), opacity_input.clone(), network_path); + .set_input(&InputConnector::node_at_index(*node_id, 1), NodeInput::value(TaggedValue::Bool(keep_opacity), false), network_path); + document.network_interface.set_input(&InputConnector::node_at_index(*node_id, 2), opacity_input.clone(), network_path); document .network_interface - .set_input(&InputConnector::node(*node_id, 3), NodeInput::value(TaggedValue::Bool(keep_fill), false), network_path); - document.network_interface.set_input(&InputConnector::node(*node_id, 4), fill_input.clone(), network_path); + .set_input(&InputConnector::node_at_index(*node_id, 3), NodeInput::value(TaggedValue::Bool(keep_fill), false), network_path); + document.network_interface.set_input(&InputConnector::node_at_index(*node_id, 4), fill_input.clone(), network_path); } SubNode::Clip => { - document.network_interface.set_input(&InputConnector::node(*node_id, 1), clip_input.clone(), network_path); + document.network_interface.set_input(&InputConnector::node_at_index(*node_id, 1), clip_input.clone(), network_path); } } @@ -1556,20 +1562,20 @@ fn migrate_node(node_id: &NodeId, node: &DocumentNode, network_path: &[NodeId], // Wire the parameter inputs for the inserted node match sub { SubNode::BlendMode => { - document.network_interface.set_input(&InputConnector::node(new_id, 1), blend_mode_input.clone(), network_path); + document.network_interface.set_input(&InputConnector::node_at_index(new_id, 1), blend_mode_input.clone(), network_path); } SubNode::Opacity => { document .network_interface - .set_input(&InputConnector::node(new_id, 1), NodeInput::value(TaggedValue::Bool(keep_opacity), false), network_path); - document.network_interface.set_input(&InputConnector::node(new_id, 2), opacity_input.clone(), network_path); + .set_input(&InputConnector::node_at_index(new_id, 1), NodeInput::value(TaggedValue::Bool(keep_opacity), false), network_path); + document.network_interface.set_input(&InputConnector::node_at_index(new_id, 2), opacity_input.clone(), network_path); document .network_interface - .set_input(&InputConnector::node(new_id, 3), NodeInput::value(TaggedValue::Bool(keep_fill), false), network_path); - document.network_interface.set_input(&InputConnector::node(new_id, 4), fill_input.clone(), network_path); + .set_input(&InputConnector::node_at_index(new_id, 3), NodeInput::value(TaggedValue::Bool(keep_fill), false), network_path); + document.network_interface.set_input(&InputConnector::node_at_index(new_id, 4), fill_input.clone(), network_path); } SubNode::Clip => { - document.network_interface.set_input(&InputConnector::node(new_id, 1), clip_input.clone(), network_path); + document.network_interface.set_input(&InputConnector::node_at_index(new_id, 1), clip_input.clone(), network_path); } } } @@ -1589,17 +1595,17 @@ fn migrate_node(node_id: &NodeId, node: &DocumentNode, network_path: &[NodeId], document.network_interface.replace_implementation(node_id, network_path, &mut node_template); let old_inputs = document.network_interface.replace_inputs(node_id, network_path, &mut node_template)?; - document.network_interface.set_input(&InputConnector::node(*node_id, 0), old_inputs[0].clone(), network_path); + document.network_interface.set_input(&InputConnector::node_at_index(*node_id, 0), old_inputs[0].clone(), network_path); document .network_interface - .set_input(&InputConnector::node(*node_id, 1), NodeInput::value(TaggedValue::Bool(true), false), network_path); - document.network_interface.set_input(&InputConnector::node(*node_id, 2), old_inputs[1].clone(), network_path); + .set_input(&InputConnector::node_at_index(*node_id, 1), NodeInput::value(TaggedValue::Bool(true), false), network_path); + document.network_interface.set_input(&InputConnector::node_at_index(*node_id, 2), old_inputs[1].clone(), network_path); document .network_interface - .set_input(&InputConnector::node(*node_id, 3), NodeInput::value(TaggedValue::Bool(false), false), network_path); + .set_input(&InputConnector::node_at_index(*node_id, 3), NodeInput::value(TaggedValue::Bool(false), false), network_path); document .network_interface - .set_input(&InputConnector::node(*node_id, 4), NodeInput::value(TaggedValue::F64(100.), false), network_path); + .set_input(&InputConnector::node_at_index(*node_id, 4), NodeInput::value(TaggedValue::F64(100.), false), network_path); inputs_count = 5; } @@ -1612,7 +1618,7 @@ fn migrate_node(node_id: &NodeId, node: &DocumentNode, network_path: &[NodeId], let old_inputs = document.network_interface.replace_inputs(node_id, network_path, &mut node_template)?; // Content: no change - document.network_interface.set_input(&InputConnector::node(*node_id, 0), old_inputs[0].clone(), network_path); + document.network_interface.set_input(&InputConnector::node_at_index(*node_id, 0), old_inputs[0].clone(), network_path); // Fill: a literal Fill value is decomposed, and a wired input (`List / List`) is kept as-is match old_inputs[1].as_value() { @@ -1625,17 +1631,17 @@ fn migrate_node(node_id: &NodeId, node: &DocumentNode, network_path: &[NodeId], }; document .network_interface - .set_input(&InputConnector::node(*node_id, 1), NodeInput::value(fill_value, exposed), network_path); + .set_input(&InputConnector::node_at_index(*node_id, 1), NodeInput::value(fill_value, exposed), network_path); // Gradient metadata (4, 5, 6, 7): applies only to a literal gradient, solids/none keep the template defaults if let graphic_types::migrations::legacy::LegacyFill::Gradient(gradient) = old_fill { document.network_interface.set_input( - &InputConnector::node(*node_id, 4), + &InputConnector::node_at_index(*node_id, 4), NodeInput::value(TaggedValue::GradientType(gradient.gradient_type), false), network_path, ); document.network_interface.set_input( - &InputConnector::node(*node_id, 5), + &InputConnector::node_at_index(*node_id, 5), NodeInput::value(TaggedValue::GradientSpreadMethod(gradient.spread_method), false), network_path, ); @@ -1644,10 +1650,10 @@ fn migrate_node(node_id: &NodeId, node: &DocumentNode, network_path: &[NodeId], let transform = gradient.transform * gradient.to_transform(); document .network_interface - .set_input(&InputConnector::node(*node_id, 6), NodeInput::value(TaggedValue::Bool(true), false), network_path); + .set_input(&InputConnector::node_at_index(*node_id, 6), NodeInput::value(TaggedValue::Bool(true), false), network_path); document .network_interface - .set_input(&InputConnector::node(*node_id, 7), NodeInput::value(TaggedValue::DAffine2(transform), false), network_path); + .set_input(&InputConnector::node_at_index(*node_id, 7), NodeInput::value(TaggedValue::DAffine2(transform), false), network_path); } else { // Baking a legacy bounding-box-relative gradient is deferred until the measurement pre-pass can supply the paint // target's bounds, so the template's unbaked `_has_transform = false` stands until the bake lands @@ -1658,18 +1664,20 @@ fn migrate_node(node_id: &NodeId, node: &DocumentNode, network_path: &[NodeId], // Wired/exposed fill keeps the connection. // The generic paint connector accepts the existing `List`/`List` paint sources directly. _ => { - document.network_interface.set_input(&InputConnector::node(*node_id, 1), old_inputs[1].clone(), network_path); + document.network_interface.set_input(&InputConnector::node_at_index(*node_id, 1), old_inputs[1].clone(), network_path); } } // Color backup: no change - document.network_interface.set_input(&InputConnector::node(*node_id, 2), old_inputs[2].clone(), network_path); + document.network_interface.set_input(&InputConnector::node_at_index(*node_id, 2), old_inputs[2].clone(), network_path); // Gradient backup: extract stops if let Some(TaggedValue::LegacyGradient(g)) = old_inputs[3].as_value() { - document - .network_interface - .set_input(&InputConnector::node(*node_id, 3), NodeInput::value(TaggedValue::Gradient(g.stops.clone()), false), network_path); + document.network_interface.set_input( + &InputConnector::node_at_index(*node_id, 3), + NodeInput::value(TaggedValue::Gradient(g.stops.clone()), false), + network_path, + ); // A solid/no-fill node leaves the gradient metadata inputs unused, so seed them from the backup gradient for a later Solid -> Gradient toggle to restore if matches!( @@ -1678,11 +1686,13 @@ fn migrate_node(node_id: &NodeId, node: &DocumentNode, network_path: &[NodeId], graphic_types::migrations::legacy::LegacyFill::None | graphic_types::migrations::legacy::LegacyFill::Solid(_) )) ) { - document - .network_interface - .set_input(&InputConnector::node(*node_id, 4), NodeInput::value(TaggedValue::GradientType(g.gradient_type), false), network_path); document.network_interface.set_input( - &InputConnector::node(*node_id, 5), + &InputConnector::node_at_index(*node_id, 4), + NodeInput::value(TaggedValue::GradientType(g.gradient_type), false), + network_path, + ); + document.network_interface.set_input( + &InputConnector::node_at_index(*node_id, 5), NodeInput::value(TaggedValue::GradientSpreadMethod(g.spread_method), false), network_path, ); @@ -1691,10 +1701,10 @@ fn migrate_node(node_id: &NodeId, node: &DocumentNode, network_path: &[NodeId], let transform = g.transform * g.to_transform(); document .network_interface - .set_input(&InputConnector::node(*node_id, 6), NodeInput::value(TaggedValue::Bool(true), false), network_path); + .set_input(&InputConnector::node_at_index(*node_id, 6), NodeInput::value(TaggedValue::Bool(true), false), network_path); document .network_interface - .set_input(&InputConnector::node(*node_id, 7), NodeInput::value(TaggedValue::DAffine2(transform), false), network_path); + .set_input(&InputConnector::node_at_index(*node_id, 7), NodeInput::value(TaggedValue::DAffine2(transform), false), network_path); } else { document.pending_gradient_bbox_bake.push((network_path.to_vec(), *node_id, g.clone())); } @@ -1710,7 +1720,7 @@ fn migrate_node(node_id: &NodeId, node: &DocumentNode, network_path: &[NodeId], let old_inputs = document.network_interface.replace_inputs(node_id, network_path, &mut node_template)?; for (index, input) in old_inputs.iter().enumerate().take(6) { - document.network_interface.set_input(&InputConnector::node(*node_id, index), input.clone(), network_path); + document.network_interface.set_input(&InputConnector::node_at_index(*node_id, index), input.clone(), network_path); } match old_inputs.get(6).and_then(|input| input.as_value()) { @@ -1719,18 +1729,18 @@ fn migrate_node(node_id: &NodeId, node: &DocumentNode, network_path: &[NodeId], let transform = value.unwrap_or(glam::DAffine2::IDENTITY); document .network_interface - .set_input(&InputConnector::node(*node_id, 6), NodeInput::value(TaggedValue::Bool(has_transform), false), network_path); + .set_input(&InputConnector::node_at_index(*node_id, 6), NodeInput::value(TaggedValue::Bool(has_transform), false), network_path); document .network_interface - .set_input(&InputConnector::node(*node_id, 7), NodeInput::value(TaggedValue::DAffine2(transform), false), network_path); + .set_input(&InputConnector::node_at_index(*node_id, 7), NodeInput::value(TaggedValue::DAffine2(transform), false), network_path); } // A wired (or otherwise non-value) transform keeps its connection and is treated as present _ => { document .network_interface - .set_input(&InputConnector::node(*node_id, 6), NodeInput::value(TaggedValue::Bool(true), false), network_path); + .set_input(&InputConnector::node_at_index(*node_id, 6), NodeInput::value(TaggedValue::Bool(true), false), network_path); let transform_input = old_inputs.get(6).cloned().unwrap_or_else(|| NodeInput::value(TaggedValue::DAffine2(glam::DAffine2::IDENTITY), false)); - document.network_interface.set_input(&InputConnector::node(*node_id, 7), transform_input, network_path); + document.network_interface.set_input(&InputConnector::node_at_index(*node_id, 7), transform_input, network_path); } } @@ -1745,17 +1755,17 @@ fn migrate_node(node_id: &NodeId, node: &DocumentNode, network_path: &[NodeId], let align_input = NodeInput::value(TaggedValue::StrokeAlign(StrokeAlign::Center), false); let paint_order_input = NodeInput::value(TaggedValue::PaintOrder(PaintOrder::StrokeAbove), false); - document.network_interface.set_input(&InputConnector::node(*node_id, 0), old_inputs[0].clone(), network_path); - document.network_interface.set_input(&InputConnector::node(*node_id, 1), old_inputs[1].clone(), network_path); - document.network_interface.set_input(&InputConnector::node(*node_id, 2), old_inputs[2].clone(), network_path); - document.network_interface.set_input(&InputConnector::node(*node_id, 3), align_input, network_path); - document.network_interface.set_input(&InputConnector::node(*node_id, 4), old_inputs[5].clone(), network_path); - document.network_interface.set_input(&InputConnector::node(*node_id, 5), old_inputs[6].clone(), network_path); - document.network_interface.set_input(&InputConnector::node(*node_id, 6), old_inputs[7].clone(), network_path); - document.network_interface.set_input(&InputConnector::node(*node_id, 7), paint_order_input, network_path); + document.network_interface.set_input(&InputConnector::node_at_index(*node_id, 0), old_inputs[0].clone(), network_path); + document.network_interface.set_input(&InputConnector::node_at_index(*node_id, 1), old_inputs[1].clone(), network_path); + document.network_interface.set_input(&InputConnector::node_at_index(*node_id, 2), old_inputs[2].clone(), network_path); + document.network_interface.set_input(&InputConnector::node_at_index(*node_id, 3), align_input, network_path); + document.network_interface.set_input(&InputConnector::node_at_index(*node_id, 4), old_inputs[5].clone(), network_path); + document.network_interface.set_input(&InputConnector::node_at_index(*node_id, 5), old_inputs[6].clone(), network_path); + document.network_interface.set_input(&InputConnector::node_at_index(*node_id, 6), old_inputs[7].clone(), network_path); + document.network_interface.set_input(&InputConnector::node_at_index(*node_id, 7), paint_order_input, network_path); let dash_input = migrate_dash_input(&old_inputs[3]).unwrap_or_else(|| old_inputs[3].clone()); - document.network_interface.set_input(&InputConnector::node(*node_id, 8), dash_input, network_path); - document.network_interface.set_input(&InputConnector::node(*node_id, 9), old_inputs[4].clone(), network_path); + document.network_interface.set_input(&InputConnector::node_at_index(*node_id, 8), dash_input, network_path); + document.network_interface.set_input(&InputConnector::node_at_index(*node_id, 9), old_inputs[4].clone(), network_path); } // TODO: Eventually remove this migration document upgrade code @@ -1770,53 +1780,35 @@ fn migrate_node(node_id: &NodeId, node: &DocumentNode, network_path: &[NodeId], Some(NodeInput::value(TaggedValue::Color(fallback), *exposed)) }; - let conversions: &[(ProtoNodeIdentifier, usize, Color)] = &[ - (graphene_std::vector::fill::IDENTIFIER, graphene_std::vector::fill::BackupColorInput::INDEX, Color::BLACK), - ( - graphene_std::artboard::create_artboard::IDENTIFIER, - graphene_std::artboard::create_artboard::BackgroundInput::INDEX, - Color::WHITE, - ), - ( - graphene_std::math_nodes::color_value::IDENTIFIER, - graphene_std::math_nodes::color_value::ColorInput::INDEX, - Color::TRANSPARENT, - ), - ( - graphene_std::raster_nodes::adjustments::black_and_white::IDENTIFIER, - graphene_std::raster_nodes::adjustments::black_and_white::TintInput::INDEX, - Color::BLACK, - ), - ( - graphene_std::raster_nodes::blending_nodes::color_overlay::IDENTIFIER, - graphene_std::raster_nodes::blending_nodes::color_overlay::ColorInput::INDEX, - Color::BLACK, - ), - ( - graphene_std::raster_nodes::std_nodes::empty_image::IDENTIFIER, - graphene_std::raster_nodes::std_nodes::empty_image::ColorInput::INDEX, - Color::WHITE, - ), + let conversions: &[(ParameterRef, Color)] = &[ + (graphene_std::vector::fill::BackupColorInput.into(), Color::BLACK), + (graphene_std::artboard::create_artboard::BackgroundInput.into(), Color::WHITE), + (graphene_std::math_nodes::color_value::ColorInput.into(), Color::TRANSPARENT), + (graphene_std::raster_nodes::adjustments::black_and_white::TintInput.into(), Color::BLACK), + (graphene_std::raster_nodes::blending_nodes::color_overlay::ColorInput.into(), Color::BLACK), + (graphene_std::raster_nodes::std_nodes::empty_image::ColorInput.into(), Color::WHITE), ]; - for &(ref identifier, index, fallback) in conversions { - if reference != DefinitionIdentifier::ProtoNode(identifier.clone()) { + for (parameter, fallback) in conversions { + if reference != DefinitionIdentifier::ProtoNode(parameter.node_identifier.clone()) { continue; } - let Some(input) = node.inputs.get(index) else { continue }; - if let Some(migrated) = migrate_color_input(input, fallback) { - document.network_interface.set_input(&InputConnector::node(*node_id, index), migrated, network_path); + let Some(input) = node.inputs.get(parameter.input_index) else { continue }; + if let Some(migrated) = migrate_color_input(input, *fallback) { + document + .network_interface + .set_input(&InputConnector::node_at_index(*node_id, parameter.input_index), migrated, network_path); } } } // The stroke dash sequence became the `DashPattern` value type; convert any already-shaped stroke that still stores a legacy dash input if reference == DefinitionIdentifier::ProtoNode(graphene_std::vector::stroke::IDENTIFIER) - && let Some(dash_input) = node.inputs.get(graphene_std::vector::stroke::DashPatternInput::INDEX) + && let Some(dash_input) = node.input(graphene_std::vector::stroke::DashPatternInput) && let Some(migrated) = migrate_dash_input(dash_input) { document .network_interface - .set_input(&InputConnector::node(*node_id, graphene_std::vector::stroke::DashPatternInput::INDEX), migrated, network_path); + .set_input(&InputConnector::node(*node_id, graphene_std::vector::stroke::DashPatternInput), migrated, network_path); } // The rectangle's corner radius became the `BoxCorners` value type and its hidden individual-radii toggle moved after the @@ -1831,12 +1823,12 @@ fn migrate_node(node_id: &NodeId, node: &DocumentNode, network_path: &[NodeId], let corner_radius = migrate_corner_radius_input(&old_inputs[4]).unwrap_or_else(|| old_inputs[4].clone()); - document.network_interface.set_input(&InputConnector::node(*node_id, 0), old_inputs[0].clone(), network_path); - document.network_interface.set_input(&InputConnector::node(*node_id, 1), old_inputs[1].clone(), network_path); - document.network_interface.set_input(&InputConnector::node(*node_id, 2), old_inputs[2].clone(), network_path); - document.network_interface.set_input(&InputConnector::node(*node_id, 3), corner_radius, network_path); - document.network_interface.set_input(&InputConnector::node(*node_id, 4), old_inputs[5].clone(), network_path); - document.network_interface.set_input(&InputConnector::node(*node_id, 5), old_inputs[3].clone(), network_path); + document.network_interface.set_input(&InputConnector::node_at_index(*node_id, 0), old_inputs[0].clone(), network_path); + document.network_interface.set_input(&InputConnector::node_at_index(*node_id, 1), old_inputs[1].clone(), network_path); + document.network_interface.set_input(&InputConnector::node_at_index(*node_id, 2), old_inputs[2].clone(), network_path); + document.network_interface.set_input(&InputConnector::node_at_index(*node_id, 3), corner_radius, network_path); + document.network_interface.set_input(&InputConnector::node_at_index(*node_id, 4), old_inputs[5].clone(), network_path); + document.network_interface.set_input(&InputConnector::node_at_index(*node_id, 5), old_inputs[3].clone(), network_path); } // The Text to Vector node's runtime `separate_glyphs` toggle became the dedicated "Text to Vector Glyphs" node, leaving Text to Vector as a plain @@ -1854,7 +1846,7 @@ fn migrate_node(node_id: &NodeId, node: &DocumentNode, network_path: &[NodeId], document.network_interface.replace_implementation(node_id, network_path, &mut node_template); document.network_interface.replace_inputs(node_id, network_path, &mut node_template)?; if let Some(string_input) = node.inputs.first() { - document.network_interface.set_input(&InputConnector::node(*node_id, 0), string_input.clone(), network_path); + document.network_interface.set_input(&InputConnector::node_at_index(*node_id, 0), string_input.clone(), network_path); } } @@ -1864,12 +1856,12 @@ fn migrate_node(node_id: &NodeId, node: &DocumentNode, network_path: &[NodeId], document.network_interface.replace_implementation(node_id, network_path, &mut template); let old_inputs = document.network_interface.replace_inputs(node_id, network_path, &mut template)?; - document.network_interface.set_input(&InputConnector::node(*node_id, 0), old_inputs[0].clone(), network_path); - document.network_interface.set_input(&InputConnector::node(*node_id, 1), old_inputs[1].clone(), network_path); - document.network_interface.set_input(&InputConnector::node(*node_id, 2), old_inputs[2].clone(), network_path); - document.network_interface.set_input(&InputConnector::node(*node_id, 3), old_inputs[3].clone(), network_path); + document.network_interface.set_input(&InputConnector::node_at_index(*node_id, 0), old_inputs[0].clone(), network_path); + document.network_interface.set_input(&InputConnector::node_at_index(*node_id, 1), old_inputs[1].clone(), network_path); + document.network_interface.set_input(&InputConnector::node_at_index(*node_id, 2), old_inputs[2].clone(), network_path); + document.network_interface.set_input(&InputConnector::node_at_index(*node_id, 3), old_inputs[3].clone(), network_path); document.network_interface.set_input( - &InputConnector::node(*node_id, 4), + &InputConnector::node_at_index(*node_id, 4), if inputs_count == 6 { old_inputs[4].clone() } else { @@ -1878,7 +1870,7 @@ fn migrate_node(node_id: &NodeId, node: &DocumentNode, network_path: &[NodeId], network_path, ); document.network_interface.set_input( - &InputConnector::node(*node_id, 5), + &InputConnector::node_at_index(*node_id, 5), if inputs_count == 6 { old_inputs[5].clone() } else { @@ -1887,7 +1879,7 @@ fn migrate_node(node_id: &NodeId, node: &DocumentNode, network_path: &[NodeId], network_path, ); document.network_interface.set_input( - &InputConnector::node(*node_id, 6), + &InputConnector::node_at_index(*node_id, 6), if inputs_count >= 7 { old_inputs[6].clone() } else { @@ -1896,7 +1888,7 @@ fn migrate_node(node_id: &NodeId, node: &DocumentNode, network_path: &[NodeId], network_path, ); document.network_interface.set_input( - &InputConnector::node(*node_id, 7), + &InputConnector::node_at_index(*node_id, 7), if inputs_count >= 8 { old_inputs[7].clone() } else { @@ -1905,7 +1897,7 @@ fn migrate_node(node_id: &NodeId, node: &DocumentNode, network_path: &[NodeId], network_path, ); document.network_interface.set_input( - &InputConnector::node(*node_id, 8), + &InputConnector::node_at_index(*node_id, 8), if inputs_count >= 9 { old_inputs[8].clone() } else { @@ -1914,7 +1906,7 @@ fn migrate_node(node_id: &NodeId, node: &DocumentNode, network_path: &[NodeId], network_path, ); document.network_interface.set_input( - &InputConnector::node(*node_id, 9), + &InputConnector::node_at_index(*node_id, 9), if inputs_count >= 10 { old_inputs[9].clone() } else { @@ -1923,7 +1915,7 @@ fn migrate_node(node_id: &NodeId, node: &DocumentNode, network_path: &[NodeId], network_path, ); document.network_interface.set_input( - &InputConnector::node(*node_id, 10), + &InputConnector::node_at_index(*node_id, 10), if inputs_count >= 11 { old_inputs[10].clone() } else { @@ -1944,27 +1936,31 @@ fn migrate_node(node_id: &NodeId, node: &DocumentNode, network_path: &[NodeId], // Copy over old inputs #[allow(clippy::needless_range_loop)] for i in 0..=5 { - document.network_interface.set_input(&InputConnector::node(*node_id, i), old_inputs[i].clone(), network_path); + document.network_interface.set_input(&InputConnector::node_at_index(*node_id, i), old_inputs[i].clone(), network_path); } // Max Width let Some(&TaggedValue::F64(old_max_width)) = old_inputs[6].as_value() else { return None }; - document - .network_interface - .set_input(&InputConnector::node(*node_id, 6), NodeInput::value(TaggedValue::Bool(old_max_width != 0.), false), network_path); document.network_interface.set_input( - &InputConnector::node(*node_id, 7), + &InputConnector::node_at_index(*node_id, 6), + NodeInput::value(TaggedValue::Bool(old_max_width != 0.), false), + network_path, + ); + document.network_interface.set_input( + &InputConnector::node_at_index(*node_id, 7), NodeInput::value(TaggedValue::F64(if old_max_width == 0. { 100. } else { old_max_width }), false), network_path, ); // Max Height let Some(&TaggedValue::F64(old_max_height)) = old_inputs[7].as_value() else { return None }; - document - .network_interface - .set_input(&InputConnector::node(*node_id, 8), NodeInput::value(TaggedValue::Bool(old_max_height != 0.), false), network_path); document.network_interface.set_input( - &InputConnector::node(*node_id, 9), + &InputConnector::node_at_index(*node_id, 8), + NodeInput::value(TaggedValue::Bool(old_max_height != 0.), false), + network_path, + ); + document.network_interface.set_input( + &InputConnector::node_at_index(*node_id, 9), NodeInput::value(TaggedValue::F64(if old_max_height == 0. { 100. } else { old_max_height }), false), network_path, ); @@ -1972,7 +1968,9 @@ fn migrate_node(node_id: &NodeId, node: &DocumentNode, network_path: &[NodeId], // Copy over old inputs #[allow(clippy::needless_range_loop)] for i in 10..=12 { - document.network_interface.set_input(&InputConnector::node(*node_id, i), old_inputs[i - 2].clone(), network_path); + document + .network_interface + .set_input(&InputConnector::node_at_index(*node_id, i), old_inputs[i - 2].clone(), network_path); } inputs_count = 13; @@ -1989,10 +1987,10 @@ fn migrate_node(node_id: &NodeId, node: &DocumentNode, network_path: &[NodeId], let old_inputs = document.network_interface.replace_inputs(node_id, network_path, &mut node_template)?; - document.network_interface.set_input(&InputConnector::node(*node_id, 0), old_inputs[0].clone(), network_path); + document.network_interface.set_input(&InputConnector::node_at_index(*node_id, 0), old_inputs[0].clone(), network_path); document .network_interface - .set_input(&InputConnector::node(*node_id, 1), NodeInput::value(TaggedValue::Bool(true), false), network_path); + .set_input(&InputConnector::node_at_index(*node_id, 1), NodeInput::value(TaggedValue::Bool(true), false), network_path); } // Upgrade the 'Tangent on Path' node to include a boolean input for whether the output should be in radians, which was previously the only option but is now not the default @@ -2002,13 +2000,13 @@ fn migrate_node(node_id: &NodeId, node: &DocumentNode, network_path: &[NodeId], let old_inputs = document.network_interface.replace_inputs(node_id, network_path, &mut node_template)?; - document.network_interface.set_input(&InputConnector::node(*node_id, 0), old_inputs[0].clone(), network_path); - document.network_interface.set_input(&InputConnector::node(*node_id, 1), old_inputs[1].clone(), network_path); - document.network_interface.set_input(&InputConnector::node(*node_id, 2), old_inputs[2].clone(), network_path); - document.network_interface.set_input(&InputConnector::node(*node_id, 3), old_inputs[3].clone(), network_path); + document.network_interface.set_input(&InputConnector::node_at_index(*node_id, 0), old_inputs[0].clone(), network_path); + document.network_interface.set_input(&InputConnector::node_at_index(*node_id, 1), old_inputs[1].clone(), network_path); + document.network_interface.set_input(&InputConnector::node_at_index(*node_id, 2), old_inputs[2].clone(), network_path); + document.network_interface.set_input(&InputConnector::node_at_index(*node_id, 3), old_inputs[3].clone(), network_path); document .network_interface - .set_input(&InputConnector::node(*node_id, 4), NodeInput::value(TaggedValue::Bool(true), false), network_path); + .set_input(&InputConnector::node_at_index(*node_id, 4), NodeInput::value(TaggedValue::Bool(true), false), network_path); } // Upgrade the Modulo node to include a boolean input for whether the output should be always positive, which was previously not an option @@ -2018,11 +2016,11 @@ fn migrate_node(node_id: &NodeId, node: &DocumentNode, network_path: &[NodeId], let old_inputs = document.network_interface.replace_inputs(node_id, network_path, &mut node_template)?; - document.network_interface.set_input(&InputConnector::node(*node_id, 0), old_inputs[0].clone(), network_path); - document.network_interface.set_input(&InputConnector::node(*node_id, 1), old_inputs[1].clone(), network_path); + document.network_interface.set_input(&InputConnector::node_at_index(*node_id, 0), old_inputs[0].clone(), network_path); + document.network_interface.set_input(&InputConnector::node_at_index(*node_id, 1), old_inputs[1].clone(), network_path); document .network_interface - .set_input(&InputConnector::node(*node_id, 2), NodeInput::value(TaggedValue::Bool(false), false), network_path); + .set_input(&InputConnector::node_at_index(*node_id, 2), NodeInput::value(TaggedValue::Bool(false), false), network_path); } // Convert the old 'Vec2 Value' node, identified by its leftover 3-input shape with separate X and Y inputs, @@ -2034,9 +2032,9 @@ fn migrate_node(node_id: &NodeId, node: &DocumentNode, network_path: &[NodeId], let old_inputs = document.network_interface.replace_inputs(node_id, network_path, &mut node_template)?; - document.network_interface.set_input(&InputConnector::node(*node_id, 0), old_inputs[0].clone(), network_path); - document.network_interface.set_input(&InputConnector::node(*node_id, 1), old_inputs[1].clone(), network_path); - document.network_interface.set_input(&InputConnector::node(*node_id, 2), old_inputs[2].clone(), network_path); + document.network_interface.set_input(&InputConnector::node_at_index(*node_id, 0), old_inputs[0].clone(), network_path); + document.network_interface.set_input(&InputConnector::node_at_index(*node_id, 1), old_inputs[1].clone(), network_path); + document.network_interface.set_input(&InputConnector::node_at_index(*node_id, 2), old_inputs[2].clone(), network_path); } // Upgrade the Mirror node to add the `keep_original` boolean input @@ -2046,12 +2044,12 @@ fn migrate_node(node_id: &NodeId, node: &DocumentNode, network_path: &[NodeId], let old_inputs = document.network_interface.replace_inputs(node_id, network_path, &mut node_template)?; - document.network_interface.set_input(&InputConnector::node(*node_id, 0), old_inputs[0].clone(), network_path); - document.network_interface.set_input(&InputConnector::node(*node_id, 1), old_inputs[1].clone(), network_path); - document.network_interface.set_input(&InputConnector::node(*node_id, 2), old_inputs[2].clone(), network_path); + document.network_interface.set_input(&InputConnector::node_at_index(*node_id, 0), old_inputs[0].clone(), network_path); + document.network_interface.set_input(&InputConnector::node_at_index(*node_id, 1), old_inputs[1].clone(), network_path); + document.network_interface.set_input(&InputConnector::node_at_index(*node_id, 2), old_inputs[2].clone(), network_path); document .network_interface - .set_input(&InputConnector::node(*node_id, 3), NodeInput::value(TaggedValue::Bool(true), false), network_path); + .set_input(&InputConnector::node_at_index(*node_id, 3), NodeInput::value(TaggedValue::Bool(true), false), network_path); } // Upgrade the Mirror node to add the `reference_point` input and change `offset` from `DVec2` to `f64` @@ -2064,17 +2062,17 @@ fn migrate_node(node_id: &NodeId, node: &DocumentNode, network_path: &[NodeId], let Some(&TaggedValue::DVec2(old_offset)) = old_inputs[1].as_value() else { return None }; let old_offset = if old_offset.x.abs() > old_offset.y.abs() { old_offset.x } else { old_offset.y }; - document.network_interface.set_input(&InputConnector::node(*node_id, 0), old_inputs[0].clone(), network_path); + document.network_interface.set_input(&InputConnector::node_at_index(*node_id, 0), old_inputs[0].clone(), network_path); document.network_interface.set_input( - &InputConnector::node(*node_id, 1), + &InputConnector::node_at_index(*node_id, 1), NodeInput::value(TaggedValue::ReferencePoint(graphene_std::transform::ReferencePoint::Center), false), network_path, ); document .network_interface - .set_input(&InputConnector::node(*node_id, 2), NodeInput::value(TaggedValue::F64(old_offset), false), network_path); - document.network_interface.set_input(&InputConnector::node(*node_id, 3), old_inputs[2].clone(), network_path); - document.network_interface.set_input(&InputConnector::node(*node_id, 4), old_inputs[3].clone(), network_path); + .set_input(&InputConnector::node_at_index(*node_id, 2), NodeInput::value(TaggedValue::F64(old_offset), false), network_path); + document.network_interface.set_input(&InputConnector::node_at_index(*node_id, 3), old_inputs[2].clone(), network_path); + document.network_interface.set_input(&InputConnector::node_at_index(*node_id, 4), old_inputs[3].clone(), network_path); } // Upgrade `image` nodes that stored image data as `Image`` to `image` nodes that store a reference to the image data as a resource. @@ -2097,7 +2095,7 @@ fn migrate_node(node_id: &NodeId, node: &DocumentNode, network_path: &[NodeId], let _ = document.network_interface.replace_inputs(node_id, network_path, &mut node_template); document .network_interface - .set_input(&InputConnector::node(*node_id, 0), NodeInput::value(TaggedValue::Resource(resource_id), false), network_path); + .set_input(&InputConnector::node_at_index(*node_id, 0), NodeInput::value(TaggedValue::Resource(resource_id), false), network_path); } } @@ -2106,7 +2104,7 @@ fn migrate_node(node_id: &NodeId, node: &DocumentNode, network_path: &[NodeId], if reference == DefinitionIdentifier::ProtoNode(ProtoNodeIdentifier::new("graphene_std::text::TextNode")) && inputs_count == 13 && matches!(node.inputs.first(), Some(NodeInput::Scope(_))) { document .network_interface - .set_input(&InputConnector::node(*node_id, 0), NodeInput::value(TaggedValue::None, false), network_path); + .set_input(&InputConnector::node_at_index(*node_id, 0), NodeInput::value(TaggedValue::None, false), network_path); if let Some(TaggedValue::Font(font)) = node.inputs.get(2).and_then(|input| input.as_value()) { let resource_id = ResourceId::new(); @@ -2120,7 +2118,7 @@ fn migrate_node(node_id: &NodeId, node: &DocumentNode, network_path: &[NodeId], ); document .network_interface - .set_input(&InputConnector::node(*node_id, 2), NodeInput::value(TaggedValue::Resource(resource_id), false), network_path); + .set_input(&InputConnector::node_at_index(*node_id, 2), NodeInput::value(TaggedValue::Resource(resource_id), false), network_path); } } @@ -2132,9 +2130,9 @@ fn migrate_node(node_id: &NodeId, node: &DocumentNode, network_path: &[NodeId], document .network_interface - .set_input(&InputConnector::node(*node_id, 0), NodeInput::value(TaggedValue::None, false), network_path); + .set_input(&InputConnector::node_at_index(*node_id, 0), NodeInput::value(TaggedValue::None, false), network_path); for (i, input) in old_inputs.iter().enumerate() { - document.network_interface.set_input(&InputConnector::node(*node_id, i + 1), input.clone(), network_path); + document.network_interface.set_input(&InputConnector::node_at_index(*node_id, i + 1), input.clone(), network_path); } } @@ -2144,8 +2142,8 @@ fn migrate_node(node_id: &NodeId, node: &DocumentNode, network_path: &[NodeId], let old_inputs = document.network_interface.replace_inputs(node_id, network_path, &mut node_template)?; - document.network_interface.set_input(&InputConnector::node(*node_id, 0), old_inputs[0].clone(), network_path); - document.network_interface.set_input(&InputConnector::node(*node_id, 1), old_inputs[1].clone(), network_path); + document.network_interface.set_input(&InputConnector::node_at_index(*node_id, 0), old_inputs[0].clone(), network_path); + document.network_interface.set_input(&InputConnector::node_at_index(*node_id, 1), old_inputs[1].clone(), network_path); } // Old shape: [background, bounds, trace, cache]. Both "bounds" (input 1) and "cache" (input 3) are dropped, and "cache" is now stored as @@ -2156,8 +2154,8 @@ fn migrate_node(node_id: &NodeId, node: &DocumentNode, network_path: &[NodeId], let old_inputs = document.network_interface.replace_inputs(node_id, network_path, &mut node_template)?; - document.network_interface.set_input(&InputConnector::node(*node_id, 0), old_inputs[0].clone(), network_path); - document.network_interface.set_input(&InputConnector::node(*node_id, 1), old_inputs[2].clone(), network_path); + document.network_interface.set_input(&InputConnector::node_at_index(*node_id, 0), old_inputs[0].clone(), network_path); + document.network_interface.set_input(&InputConnector::node_at_index(*node_id, 1), old_inputs[2].clone(), network_path); } // Old shape: [background, trace, cache]. The "cache" input is dropped because the brush node now stores its cache as internal `#[data]` state. @@ -2167,15 +2165,15 @@ fn migrate_node(node_id: &NodeId, node: &DocumentNode, network_path: &[NodeId], let old_inputs = document.network_interface.replace_inputs(node_id, network_path, &mut node_template)?; - document.network_interface.set_input(&InputConnector::node(*node_id, 0), old_inputs[0].clone(), network_path); - document.network_interface.set_input(&InputConnector::node(*node_id, 1), old_inputs[1].clone(), network_path); + document.network_interface.set_input(&InputConnector::node_at_index(*node_id, 0), old_inputs[0].clone(), network_path); + document.network_interface.set_input(&InputConnector::node_at_index(*node_id, 1), old_inputs[1].clone(), network_path); } // A brush node saved before `Item>` had a default stored its unconnected background as the invalid `()`, // which fails type resolution against the raster primary; adopt the definition's empty-raster default instead. if reference == DefinitionIdentifier::ProtoNode(graphene_std::brush::brush::brush::IDENTIFIER) && matches!(node.inputs.first().and_then(|input| input.as_value()), Some(TaggedValue::None)) { let default_background = resolve_document_node_type(&reference)?.node_template.inputs.first()?.clone(); - document.network_interface.set_input(&InputConnector::node(*node_id, 0), default_background, network_path); + document.network_interface.set_input(&InputConnector::node_at_index(*node_id, 0), default_background, network_path); } if reference == DefinitionIdentifier::ProtoNode(ProtoNodeIdentifier::new("graphene_core::vector::RemoveHandlesNode")) { @@ -2184,13 +2182,13 @@ fn migrate_node(node_id: &NodeId, node: &DocumentNode, network_path: &[NodeId], let old_inputs = document.network_interface.replace_inputs(node_id, network_path, &mut node_template)?; - document.network_interface.set_input(&InputConnector::node(*node_id, 0), old_inputs[0].clone(), network_path); + document.network_interface.set_input(&InputConnector::node_at_index(*node_id, 0), old_inputs[0].clone(), network_path); document .network_interface - .set_input(&InputConnector::node(*node_id, 1), NodeInput::value(TaggedValue::F64(0.), false), network_path); + .set_input(&InputConnector::node_at_index(*node_id, 1), NodeInput::value(TaggedValue::F64(0.), false), network_path); document .network_interface - .set_input(&InputConnector::node(*node_id, 2), NodeInput::value(TaggedValue::Bool(false), false), network_path); + .set_input(&InputConnector::node_at_index(*node_id, 2), NodeInput::value(TaggedValue::Bool(false), false), network_path); } if reference == DefinitionIdentifier::ProtoNode(ProtoNodeIdentifier::new("graphene_core::vector::GenerateHandlesNode")) { @@ -2199,11 +2197,11 @@ fn migrate_node(node_id: &NodeId, node: &DocumentNode, network_path: &[NodeId], let old_inputs = document.network_interface.replace_inputs(node_id, network_path, &mut node_template)?; - document.network_interface.set_input(&InputConnector::node(*node_id, 0), old_inputs[0].clone(), network_path); - document.network_interface.set_input(&InputConnector::node(*node_id, 1), old_inputs[1].clone(), network_path); + document.network_interface.set_input(&InputConnector::node_at_index(*node_id, 0), old_inputs[0].clone(), network_path); + document.network_interface.set_input(&InputConnector::node_at_index(*node_id, 1), old_inputs[1].clone(), network_path); document .network_interface - .set_input(&InputConnector::node(*node_id, 2), NodeInput::value(TaggedValue::Bool(true), false), network_path); + .set_input(&InputConnector::node_at_index(*node_id, 2), NodeInput::value(TaggedValue::Bool(true), false), network_path); } if reference == DefinitionIdentifier::ProtoNode(graphene_std::vector::merge_by_distance::IDENTIFIER) && inputs_count == 2 { @@ -2212,10 +2210,10 @@ fn migrate_node(node_id: &NodeId, node: &DocumentNode, network_path: &[NodeId], let old_inputs = document.network_interface.replace_inputs(node_id, network_path, &mut node_template)?; - document.network_interface.set_input(&InputConnector::node(*node_id, 0), old_inputs[0].clone(), network_path); - document.network_interface.set_input(&InputConnector::node(*node_id, 1), old_inputs[1].clone(), network_path); + document.network_interface.set_input(&InputConnector::node_at_index(*node_id, 0), old_inputs[0].clone(), network_path); + document.network_interface.set_input(&InputConnector::node_at_index(*node_id, 1), old_inputs[1].clone(), network_path); document.network_interface.set_input( - &InputConnector::node(*node_id, 2), + &InputConnector::node_at_index(*node_id, 2), NodeInput::value(TaggedValue::MergeByDistanceAlgorithm(graphene_std::vector::misc::MergeByDistanceAlgorithm::Topological), false), network_path, ); @@ -2227,10 +2225,10 @@ fn migrate_node(node_id: &NodeId, node: &DocumentNode, network_path: &[NodeId], let old_inputs = document.network_interface.replace_inputs(node_id, network_path, &mut node_template)?; - document.network_interface.set_input(&InputConnector::node(*node_id, 0), old_inputs[0].clone(), network_path); - document.network_interface.set_input(&InputConnector::node(*node_id, 1), old_inputs[1].clone(), network_path); + document.network_interface.set_input(&InputConnector::node_at_index(*node_id, 0), old_inputs[0].clone(), network_path); + document.network_interface.set_input(&InputConnector::node_at_index(*node_id, 1), old_inputs[1].clone(), network_path); document.network_interface.set_input( - &InputConnector::node(*node_id, 2), + &InputConnector::node_at_index(*node_id, 2), NodeInput::value(TaggedValue::MergeByDistanceAlgorithm(graphene_std::vector::misc::MergeByDistanceAlgorithm::Spatial), false), network_path, ); @@ -2244,13 +2242,13 @@ fn migrate_node(node_id: &NodeId, node: &DocumentNode, network_path: &[NodeId], let new_spacing_value = NodeInput::value(TaggedValue::PointSpacingType(graphene_std::vector::misc::PointSpacingType::Separation), false); let new_quantity_value = NodeInput::value(TaggedValue::U32(100), false); - document.network_interface.set_input(&InputConnector::node(*node_id, 0), old_inputs[0].clone(), network_path); - document.network_interface.set_input(&InputConnector::node(*node_id, 1), new_spacing_value, network_path); - document.network_interface.set_input(&InputConnector::node(*node_id, 2), old_inputs[1].clone(), network_path); - document.network_interface.set_input(&InputConnector::node(*node_id, 3), new_quantity_value, network_path); - document.network_interface.set_input(&InputConnector::node(*node_id, 4), old_inputs[2].clone(), network_path); - document.network_interface.set_input(&InputConnector::node(*node_id, 5), old_inputs[3].clone(), network_path); - document.network_interface.set_input(&InputConnector::node(*node_id, 6), old_inputs[4].clone(), network_path); + document.network_interface.set_input(&InputConnector::node_at_index(*node_id, 0), old_inputs[0].clone(), network_path); + document.network_interface.set_input(&InputConnector::node_at_index(*node_id, 1), new_spacing_value, network_path); + document.network_interface.set_input(&InputConnector::node_at_index(*node_id, 2), old_inputs[1].clone(), network_path); + document.network_interface.set_input(&InputConnector::node_at_index(*node_id, 3), new_quantity_value, network_path); + document.network_interface.set_input(&InputConnector::node_at_index(*node_id, 4), old_inputs[2].clone(), network_path); + document.network_interface.set_input(&InputConnector::node_at_index(*node_id, 5), old_inputs[3].clone(), network_path); + document.network_interface.set_input(&InputConnector::node_at_index(*node_id, 6), old_inputs[4].clone(), network_path); } // Make the "Quantity" parameter a u32 instead of f64 @@ -2264,7 +2262,7 @@ fn migrate_node(node_id: &NodeId, node: &DocumentNode, network_path: &[NodeId], && let TaggedValue::F64(value) = **tagged_value { let new_quantity_value = NodeInput::value(TaggedValue::U32(value as u32), *exposed); - document.network_interface.set_input(&InputConnector::node(*node_id, 3), new_quantity_value, network_path); + document.network_interface.set_input(&InputConnector::node_at_index(*node_id, 3), new_quantity_value, network_path); } } @@ -2287,16 +2285,16 @@ fn migrate_node(node_id: &NodeId, node: &DocumentNode, network_path: &[NodeId], if legacy_angles_layout { // Old order: [primary, grid_type, spacing, angles, columns, rows]. Move "angles" from index 3 to index 5. - document.network_interface.set_input(&InputConnector::node(*node_id, 0), old_inputs[0].clone(), network_path); - document.network_interface.set_input(&InputConnector::node(*node_id, 1), old_inputs[1].clone(), network_path); - document.network_interface.set_input(&InputConnector::node(*node_id, 2), old_inputs[2].clone(), network_path); - document.network_interface.set_input(&InputConnector::node(*node_id, 3), old_inputs[4].clone(), network_path); - document.network_interface.set_input(&InputConnector::node(*node_id, 4), old_inputs[5].clone(), network_path); - document.network_interface.set_input(&InputConnector::node(*node_id, 5), old_inputs[3].clone(), network_path); + document.network_interface.set_input(&InputConnector::node_at_index(*node_id, 0), old_inputs[0].clone(), network_path); + document.network_interface.set_input(&InputConnector::node_at_index(*node_id, 1), old_inputs[1].clone(), network_path); + document.network_interface.set_input(&InputConnector::node_at_index(*node_id, 2), old_inputs[2].clone(), network_path); + document.network_interface.set_input(&InputConnector::node_at_index(*node_id, 3), old_inputs[4].clone(), network_path); + document.network_interface.set_input(&InputConnector::node_at_index(*node_id, 4), old_inputs[5].clone(), network_path); + document.network_interface.set_input(&InputConnector::node_at_index(*node_id, 5), old_inputs[3].clone(), network_path); } else { // Modern six-input order. Carry each input over to the same index. for (index, input) in old_inputs.iter().take(6).enumerate() { - document.network_interface.set_input(&InputConnector::node(*node_id, index), input.clone(), network_path); + document.network_interface.set_input(&InputConnector::node_at_index(*node_id, index), input.clone(), network_path); } } } @@ -2322,10 +2320,10 @@ fn migrate_node(node_id: &NodeId, node: &DocumentNode, network_path: &[NodeId], document .network_interface - .set_input(&InputConnector::node(*node_id, 0), NodeInput::value(TaggedValue::None, false), network_path); + .set_input(&InputConnector::node_at_index(*node_id, 0), NodeInput::value(TaggedValue::None, false), network_path); document .network_interface - .set_input(&InputConnector::node(*node_id, 1), NodeInput::value(TaggedValue::F64(1.), false), network_path); + .set_input(&InputConnector::node_at_index(*node_id, 1), NodeInput::value(TaggedValue::F64(1.), false), network_path); } // Upgrade the "Read Position" node to add the "Loop Level" input @@ -2336,10 +2334,10 @@ fn migrate_node(node_id: &NodeId, node: &DocumentNode, network_path: &[NodeId], document .network_interface - .set_input(&InputConnector::node(*node_id, 0), NodeInput::value(TaggedValue::None, false), network_path); + .set_input(&InputConnector::node_at_index(*node_id, 0), NodeInput::value(TaggedValue::None, false), network_path); document .network_interface - .set_input(&InputConnector::node(*node_id, 1), NodeInput::value(TaggedValue::U32(0), false), network_path); + .set_input(&InputConnector::node_at_index(*node_id, 1), NodeInput::value(TaggedValue::U32(0), false), network_path); } // Migrate from the old source/target v1 "Morph" node to the new `List`-based v2 "Morph" node. @@ -2381,15 +2379,19 @@ fn migrate_node(node_id: &NodeId, node: &DocumentNode, network_path: &[NodeId], document.network_interface.shift_absolute_node_position(&merge_node_id, merge_position, network_path); // Connect the old 'source' and 'target' inputs to the new Merge node - document.network_interface.set_input(&InputConnector::node(merge_node_id, 0), old_inputs[0].clone(), network_path); - document.network_interface.set_input(&InputConnector::node(merge_node_id, 1), old_inputs[1].clone(), network_path); + document + .network_interface + .set_input(&InputConnector::node_at_index(merge_node_id, 0), old_inputs[0].clone(), network_path); + document + .network_interface + .set_input(&InputConnector::node_at_index(merge_node_id, 1), old_inputs[1].clone(), network_path); // Connect the new Merge node to the 'content' input of the Morph node document .network_interface - .set_input(&InputConnector::node(*node_id, 0), NodeInput::node(merge_node_id, 0), network_path); + .set_input(&InputConnector::node_at_index(*node_id, 0), NodeInput::node(merge_node_id, 0), network_path); // Connect the old 'progression' input to the new 'progression' input of the Morph node - document.network_interface.set_input(&InputConnector::node(*node_id, 1), old_inputs[2].clone(), network_path); + document.network_interface.set_input(&InputConnector::node_at_index(*node_id, 1), old_inputs[2].clone(), network_path); inputs_count = 2; } @@ -2404,18 +2406,18 @@ fn migrate_node(node_id: &NodeId, node: &DocumentNode, network_path: &[NodeId], let old_inputs = document.network_interface.replace_inputs(node_id, network_path, &mut node_template)?; // Reconnect content (input 0) and leave path (input 4) as default - document.network_interface.set_input(&InputConnector::node(*node_id, 0), old_inputs[0].clone(), network_path); + document.network_interface.set_input(&InputConnector::node_at_index(*node_id, 0), old_inputs[0].clone(), network_path); let Some(morph_position) = document.network_interface.position_from_downstream_node(node_id, network_path) else { log::error!("Could not get position for morph node {node_id}"); - document.network_interface.set_input(&InputConnector::node(*node_id, 1), old_inputs[1].clone(), network_path); + document.network_interface.set_input(&InputConnector::node_at_index(*node_id, 1), old_inputs[1].clone(), network_path); return None; }; // Create List Length node: counts content `List` items → N let Some(list_length_def) = resolve_document_node_type(&DefinitionIdentifier::ProtoNode(graphene_std::vector::list_length::IDENTIFIER)) else { log::error!("Could not get list_length node from definition when upgrading morph"); - document.network_interface.set_input(&InputConnector::node(*node_id, 1), old_inputs[1].clone(), network_path); + document.network_interface.set_input(&InputConnector::node_at_index(*node_id, 1), old_inputs[1].clone(), network_path); return None; }; let list_length_template = list_length_def.default_node_template(); @@ -2424,7 +2426,7 @@ fn migrate_node(node_id: &NodeId, node: &DocumentNode, network_path: &[NodeId], // Create Subtract node: N → N-1 let Some(subtract_def) = resolve_document_node_type(&DefinitionIdentifier::ProtoNode(graphene_std::math_nodes::subtract::IDENTIFIER)) else { log::error!("Could not get subtract node from definition when upgrading morph"); - document.network_interface.set_input(&InputConnector::node(*node_id, 1), old_inputs[1].clone(), network_path); + document.network_interface.set_input(&InputConnector::node_at_index(*node_id, 1), old_inputs[1].clone(), network_path); return None; }; let mut subtract_template = subtract_def.default_node_template(); @@ -2434,7 +2436,7 @@ fn migrate_node(node_id: &NodeId, node: &DocumentNode, network_path: &[NodeId], // Create Divide node: old_progression / (N-1) → new progression let Some(divide_def) = resolve_document_node_type(&DefinitionIdentifier::ProtoNode(graphene_std::math_nodes::divide::IDENTIFIER)) else { log::error!("Could not get divide node from definition when upgrading morph"); - document.network_interface.set_input(&InputConnector::node(*node_id, 1), old_inputs[1].clone(), network_path); + document.network_interface.set_input(&InputConnector::node_at_index(*node_id, 1), old_inputs[1].clone(), network_path); return None; }; let divide_template = divide_def.default_node_template(); @@ -2453,21 +2455,27 @@ fn migrate_node(node_id: &NodeId, node: &DocumentNode, network_path: &[NodeId], document.network_interface.shift_absolute_node_position(÷_id, morph_position + IVec2::new(-7, 1), network_path); // Wire: content source → List Length input 0 - document.network_interface.set_input(&InputConnector::node(list_length_id, 0), old_inputs[0].clone(), network_path); + document + .network_interface + .set_input(&InputConnector::node_at_index(list_length_id, 0), old_inputs[0].clone(), network_path); // Wire: List Length output → Subtract input 0 (minuend) document .network_interface - .set_input(&InputConnector::node(subtract_id, 0), NodeInput::node(list_length_id, 0), network_path); + .set_input(&InputConnector::node_at_index(subtract_id, 0), NodeInput::node(list_length_id, 0), network_path); // Wire: old progression → Divide input 0 (numerator) - document.network_interface.set_input(&InputConnector::node(divide_id, 0), old_inputs[1].clone(), network_path); + document.network_interface.set_input(&InputConnector::node_at_index(divide_id, 0), old_inputs[1].clone(), network_path); // Wire: Subtract output → Divide input 1 (denominator) - document.network_interface.set_input(&InputConnector::node(divide_id, 1), NodeInput::node(subtract_id, 0), network_path); + document + .network_interface + .set_input(&InputConnector::node_at_index(divide_id, 1), NodeInput::node(subtract_id, 0), network_path); // Wire: Divide output → Morph progression input - document.network_interface.set_input(&InputConnector::node(*node_id, 1), NodeInput::node(divide_id, 0), network_path); + document + .network_interface + .set_input(&InputConnector::node_at_index(*node_id, 1), NodeInput::node(divide_id, 0), network_path); } // Migrate old Arrow node from (start, end, shaft_width, head_width, head_length) to (arrow_to, shaft_width, head_width, head_length) with a Transform node for positioning @@ -2499,15 +2507,15 @@ fn migrate_node(node_id: &NodeId, node: &DocumentNode, network_path: &[NodeId], let old_inputs = document.network_interface.replace_inputs(node_id, network_path, &mut node_template)?; // Preserve primary input connection - document.network_interface.set_input(&InputConnector::node(*node_id, 0), old_inputs[0].clone(), network_path); + document.network_interface.set_input(&InputConnector::node_at_index(*node_id, 0), old_inputs[0].clone(), network_path); // Set arrow_to = end - start document .network_interface - .set_input(&InputConnector::node(*node_id, 1), NodeInput::value(TaggedValue::DVec2(end - start), false), network_path); + .set_input(&InputConnector::node_at_index(*node_id, 1), NodeInput::value(TaggedValue::DVec2(end - start), false), network_path); // Preserve shaft_width, head_width, head_length (shifted from indices 3,4,5 to 2,3,4) - document.network_interface.set_input(&InputConnector::node(*node_id, 2), old_inputs[3].clone(), network_path); - document.network_interface.set_input(&InputConnector::node(*node_id, 3), old_inputs[4].clone(), network_path); - document.network_interface.set_input(&InputConnector::node(*node_id, 4), old_inputs[5].clone(), network_path); + document.network_interface.set_input(&InputConnector::node_at_index(*node_id, 2), old_inputs[3].clone(), network_path); + document.network_interface.set_input(&InputConnector::node_at_index(*node_id, 3), old_inputs[4].clone(), network_path); + document.network_interface.set_input(&InputConnector::node_at_index(*node_id, 4), old_inputs[5].clone(), network_path); // Find downstream connection to insert Transform node let downstream = document @@ -2564,11 +2572,11 @@ fn migrate_node(node_id: &NodeId, node: &DocumentNode, network_path: &[NodeId], let old_inputs = document.network_interface.replace_inputs(node_id, network_path, &mut node_template)?; // Preserve primary input connection - document.network_interface.set_input(&InputConnector::node(*node_id, 0), old_inputs[0].clone(), network_path); + document.network_interface.set_input(&InputConnector::node_at_index(*node_id, 0), old_inputs[0].clone(), network_path); // Set line_to = end - start document .network_interface - .set_input(&InputConnector::node(*node_id, 1), NodeInput::value(TaggedValue::DVec2(end - start), false), network_path); + .set_input(&InputConnector::node_at_index(*node_id, 1), NodeInput::value(TaggedValue::DVec2(end - start), false), network_path); // Find downstream connection to insert Transform node let downstream = document @@ -2610,10 +2618,12 @@ fn migrate_node(node_id: &NodeId, node: &DocumentNode, network_path: &[NodeId], let mut node_template = resolve_document_node_type(&reference)?.default_node_template(); let old_inputs = document.network_interface.replace_inputs(node_id, network_path, &mut node_template)?; - document.network_interface.set_input(&InputConnector::node(*node_id, 0), old_inputs[0].clone(), network_path); - document - .network_interface - .set_input(&InputConnector::node(*node_id, 1), NodeInput::value(TaggedValue::ScaleType(ScaleType::Magnitude), false), network_path); + document.network_interface.set_input(&InputConnector::node_at_index(*node_id, 0), old_inputs[0].clone(), network_path); + document.network_interface.set_input( + &InputConnector::node_at_index(*node_id, 1), + NodeInput::value(TaggedValue::ScaleType(ScaleType::Magnitude), false), + network_path, + ); } // Add the "Along Normals" parameter to the "Jitter Points" node @@ -2621,12 +2631,12 @@ fn migrate_node(node_id: &NodeId, node: &DocumentNode, network_path: &[NodeId], let mut node_template = resolve_document_node_type(&reference)?.default_node_template(); let old_inputs = document.network_interface.replace_inputs(node_id, network_path, &mut node_template)?; - document.network_interface.set_input(&InputConnector::node(*node_id, 0), old_inputs[0].clone(), network_path); - document.network_interface.set_input(&InputConnector::node(*node_id, 1), old_inputs[1].clone(), network_path); - document.network_interface.set_input(&InputConnector::node(*node_id, 2), old_inputs[2].clone(), network_path); + document.network_interface.set_input(&InputConnector::node_at_index(*node_id, 0), old_inputs[0].clone(), network_path); + document.network_interface.set_input(&InputConnector::node_at_index(*node_id, 1), old_inputs[1].clone(), network_path); + document.network_interface.set_input(&InputConnector::node_at_index(*node_id, 2), old_inputs[2].clone(), network_path); document .network_interface - .set_input(&InputConnector::node(*node_id, 3), NodeInput::value(TaggedValue::Bool(false), false), network_path); + .set_input(&InputConnector::node_at_index(*node_id, 3), NodeInput::value(TaggedValue::Bool(false), false), network_path); } // SVG-import legacy Path nodes baked their geometry at non-exposed input 0; move it to input 1 (the modern slot for VectorModification). @@ -2639,14 +2649,18 @@ fn migrate_node(node_id: &NodeId, node: &DocumentNode, network_path: &[NodeId], let modification = modification.clone(); let was_exposed = *exposed; - document - .network_interface - .set_input(&InputConnector::node(*node_id, 0), NodeInput::type_default(item!(graphene_std::vector::Vector), true), network_path); + document.network_interface.set_input( + &InputConnector::node_at_index(*node_id, 0), + NodeInput::type_default(item!(graphene_std::vector::Vector), true), + network_path, + ); if !was_exposed { - document - .network_interface - .set_input(&InputConnector::node(*node_id, 1), NodeInput::value(TaggedValue::VectorModification(modification), false), network_path); + document.network_interface.set_input( + &InputConnector::node_at_index(*node_id, 1), + NodeInput::value(TaggedValue::VectorModification(modification), false), + network_path, + ); } } } @@ -2656,8 +2670,8 @@ fn migrate_node(node_id: &NodeId, node: &DocumentNode, network_path: &[NodeId], let mut node_template = resolve_document_node_type(&reference)?.default_node_template(); let old_inputs = document.network_interface.replace_inputs(node_id, network_path, &mut node_template)?; - document.network_interface.set_input(&InputConnector::node(*node_id, 0), old_inputs[0].clone(), network_path); - document.network_interface.set_input(&InputConnector::node(*node_id, 1), old_inputs[2].clone(), network_path); + document.network_interface.set_input(&InputConnector::node_at_index(*node_id, 0), old_inputs[0].clone(), network_path); + document.network_interface.set_input(&InputConnector::node_at_index(*node_id, 1), old_inputs[2].clone(), network_path); } // `wgpu-executor` scope was removed, change to the auto injected scope node `graphene_std::platform_application_io::wgpu_executor` @@ -2666,7 +2680,7 @@ fn migrate_node(node_id: &NodeId, node: &DocumentNode, network_path: &[NodeId], && *name == "wgpu-executor" { document.network_interface.set_input( - &InputConnector::node(*node_id, i), + &InputConnector::node_at_index(*node_id, i), NodeInput::Scope("graphene_std::platform_application_io::WgpuExecutorNode".into()), network_path, ); @@ -2684,7 +2698,7 @@ fn migrate_node(node_id: &NodeId, node: &DocumentNode, network_path: &[NodeId], let stale_list_default = document .network_interface - .input_from_connector(&InputConnector::node(*node_id, index), network_path) + .input_from_connector(&InputConnector::node_at_index(*node_id, index), network_path) .is_some_and(|stored_input| match stored_input { NodeInput::Value { tagged_value, .. } => match &**tagged_value { TaggedValue::TypeDefault(stored_type) if matches!(stored_type, Type::List(_)) && !tagged_value.is_no_paint() => { @@ -2696,7 +2710,9 @@ fn migrate_node(node_id: &NodeId, node: &DocumentNode, network_path: &[NodeId], }); if stale_list_default { - document.network_interface.set_input(&InputConnector::node(*node_id, index), definition_input.clone(), network_path); + document + .network_interface + .set_input(&InputConnector::node_at_index(*node_id, index), definition_input.clone(), network_path); } } } @@ -2750,7 +2766,7 @@ fn migrate_removed_catalog_definitions(node_id: &NodeId, node: &DocumentNode, ne document.network_interface.replace_implementation(node_id, network_path, &mut node_template); let old_inputs = document.network_interface.replace_inputs(node_id, network_path, &mut node_template)?; for (index, input) in old_inputs.iter().take(7).enumerate() { - document.network_interface.set_input(&InputConnector::node(*node_id, index), input.clone(), network_path); + document.network_interface.set_input(&InputConnector::node_at_index(*node_id, index), input.clone(), network_path); } } @@ -2766,7 +2782,7 @@ fn migrate_removed_catalog_definitions(node_id: &NodeId, node: &DocumentNode, ne document.network_interface.replace_implementation(node_id, network_path, &mut node_template); let old_inputs = document.network_interface.replace_inputs(node_id, network_path, &mut node_template)?; for (index, input) in old_inputs.iter().take(3).enumerate() { - document.network_interface.set_input(&InputConnector::node(*node_id, index), input.clone(), network_path); + document.network_interface.set_input(&InputConnector::node_at_index(*node_id, index), input.clone(), network_path); } } @@ -2782,7 +2798,7 @@ fn migrate_removed_catalog_definitions(node_id: &NodeId, node: &DocumentNode, ne document.network_interface.replace_implementation(node_id, network_path, &mut node_template); let old_inputs = document.network_interface.replace_inputs(node_id, network_path, &mut node_template)?; for (index, input) in old_inputs.iter().take(2).enumerate() { - document.network_interface.set_input(&InputConnector::node(*node_id, index), input.clone(), network_path); + document.network_interface.set_input(&InputConnector::node_at_index(*node_id, index), input.clone(), network_path); } } @@ -2795,7 +2811,7 @@ fn migrate_removed_catalog_definitions(node_id: &NodeId, node: &DocumentNode, ne document.network_interface.replace_implementation(node_id, network_path, &mut node_template); let old_inputs = document.network_interface.replace_inputs(node_id, network_path, &mut node_template)?; if let Some(content) = old_inputs.first() { - document.network_interface.set_input(&InputConnector::node(*node_id, 0), content.clone(), network_path); + document.network_interface.set_input(&InputConnector::node_at_index(*node_id, 0), content.clone(), network_path); } } @@ -2807,7 +2823,7 @@ fn migrate_removed_catalog_definitions(node_id: &NodeId, node: &DocumentNode, ne document.network_interface.replace_implementation(node_id, network_path, &mut node_template); let old_inputs = document.network_interface.replace_inputs(node_id, network_path, &mut node_template)?; if let Some(content) = old_inputs.first() { - document.network_interface.set_input(&InputConnector::node(*node_id, 0), content.clone(), network_path); + document.network_interface.set_input(&InputConnector::node_at_index(*node_id, 0), content.clone(), network_path); } } diff --git a/editor/src/messages/portfolio/portfolio_message_handler.rs b/editor/src/messages/portfolio/portfolio_message_handler.rs index 5dbd6fb55c..b9ffc69a5a 100644 --- a/editor/src/messages/portfolio/portfolio_message_handler.rs +++ b/editor/src/messages/portfolio/portfolio_message_handler.rs @@ -912,7 +912,7 @@ impl MessageHandler> for Portfolio let Some((downstream_node, input_index)) = document .network_interface .outward_wires(&[]) - .and_then(|outward_wires| outward_wires.get(&OutputConnector::node(layer.to_node(), 0))) + .and_then(|outward_wires| outward_wires.get(&OutputConnector::primary_output(layer.to_node()))) .and_then(|outward_wires| outward_wires.first()) .and_then(|input_connector| input_connector.node_id().map(|node_id| (node_id, input_connector.input_index()))) else { diff --git a/editor/src/messages/tool/common_functionality/gizmos/shape_gizmos/circle_arc_radius_handle.rs b/editor/src/messages/tool/common_functionality/gizmos/shape_gizmos/circle_arc_radius_handle.rs index 6152a6a5dd..837a1100aa 100644 --- a/editor/src/messages/tool/common_functionality/gizmos/shape_gizmos/circle_arc_radius_handle.rs +++ b/editor/src/messages/tool/common_functionality/gizmos/shape_gizmos/circle_arc_radius_handle.rs @@ -11,6 +11,7 @@ use crate::messages::tool::common_functionality::shapes::shape_utility::{extract use glam::{DAffine2, DVec2}; use graph_craft::document::NodeInput; use graph_craft::document::value::TaggedValue; +use graphene_std::ParameterRef; use std::collections::VecDeque; use std::f64::consts::FRAC_PI_2; @@ -147,7 +148,13 @@ impl RadiusHandle { pub fn update_inner_radius(&mut self, document: &DocumentMessageHandler, input: &InputPreprocessorMessageHandler, responses: &mut VecDeque, drag_start: DVec2) { let Some(layer) = self.layer else { return }; - let Some(node_id) = graph_modification_utils::get_circle_id(layer, &document.network_interface).or(get_arc_id(layer, &document.network_interface)) else { + + // This gizmo serves both Circle and Arc layers, so resolve which node is present to know whose radius parameter to write + let (node_id, radius_parameter) = if let Some(node_id) = graph_modification_utils::get_circle_id(layer, &document.network_interface) { + (node_id, ParameterRef::from(graphene_std::vector::generator_nodes::circle::RadiusInput)) + } else if let Some(node_id) = get_arc_id(layer, &document.network_interface) { + (node_id, ParameterRef::from(graphene_std::vector::generator_nodes::arc::RadiusInput)) + } else { return; }; let Some(current_radius) = extract_circle_radius(layer, document).or(extract_arc_parameters(Some(layer), document).map(|(r, _, _, _)| r)) else { @@ -165,7 +172,7 @@ impl RadiusHandle { self.previous_mouse_position = input.mouse.position; responses.add(NodeGraphMessage::SetInput { - input_connector: InputConnector::node(node_id, 1), + input_connector: InputConnector::node(node_id, radius_parameter), input: NodeInput::value(TaggedValue::F64(current_radius + net_delta), false), }); responses.add(NodeGraphMessage::RunDocumentGraph); diff --git a/editor/src/messages/tool/common_functionality/gizmos/shape_gizmos/grid_rows_columns_gizmo.rs b/editor/src/messages/tool/common_functionality/gizmos/shape_gizmos/grid_rows_columns_gizmo.rs index 308dbbdb48..a5b2a39712 100644 --- a/editor/src/messages/tool/common_functionality/gizmos/shape_gizmos/grid_rows_columns_gizmo.rs +++ b/editor/src/messages/tool/common_functionality/gizmos/shape_gizmos/grid_rows_columns_gizmo.rs @@ -13,7 +13,7 @@ use crate::messages::tool::common_functionality::shapes::shape_utility::extract_ use glam::{DAffine2, DVec2}; use graph_craft::document::NodeInput; use graph_craft::document::value::TaggedValue; -use graphene_std::NodeInputDecleration; +use graphene_std::ParameterRef; use graphene_std::vector::misc::{GridType, dvec2_to_point, get_line_endpoints}; use kurbo::{Line, ParamCurveNearest, Rect}; use std::collections::VecDeque; @@ -123,7 +123,7 @@ impl RowColumnGizmo { let transform = self.transform_grid(dimensions_delta, self.spacing, grid_type, angles, viewport); responses.add(NodeGraphMessage::SetInput { - input_connector: InputConnector::node(node_id, self.gizmo_type.index()), + input_connector: InputConnector::node(node_id, self.gizmo_type.parameter()), input: NodeInput::value(TaggedValue::U32((self.initial_dimension() as i32 + dimensions_to_add).max(1) as u32), false), }); @@ -411,13 +411,13 @@ impl RowColumnGizmoType { } } - fn index(&self) -> usize { + fn parameter(&self) -> ParameterRef { use graphene_std::vector::generator_nodes::grid::*; match self { - RowColumnGizmoType::Top | RowColumnGizmoType::Bottom => RowsInput::INDEX, - RowColumnGizmoType::Left | RowColumnGizmoType::Right => ColumnsInput::INDEX, - RowColumnGizmoType::None => panic!("RowColumnGizmoType::None does not have a mouse_icon"), + RowColumnGizmoType::Top | RowColumnGizmoType::Bottom => RowsInput.into(), + RowColumnGizmoType::Left | RowColumnGizmoType::Right => ColumnsInput.into(), + RowColumnGizmoType::None => panic!("RowColumnGizmoType::None does not reference a grid input"), } } diff --git a/editor/src/messages/tool/common_functionality/gizmos/shape_gizmos/number_of_points_dial.rs b/editor/src/messages/tool/common_functionality/gizmos/shape_gizmos/number_of_points_dial.rs index c6fef1fbda..1badf26484 100644 --- a/editor/src/messages/tool/common_functionality/gizmos/shape_gizmos/number_of_points_dial.rs +++ b/editor/src/messages/tool/common_functionality/gizmos/shape_gizmos/number_of_points_dial.rs @@ -13,6 +13,7 @@ use crate::messages::tool::common_functionality::shapes::shape_utility::{extract use glam::{DAffine2, DVec2}; use graph_craft::document::NodeInput; use graph_craft::document::value::TaggedValue; +use graphene_std::ParameterRef; use std::collections::VecDeque; use std::f64::consts::TAU; @@ -194,14 +195,20 @@ impl NumberOfPointsDial { let net_delta = (delta.length() / 25.).round() * sign; let Some(layer) = self.layer else { return }; - let Some(node_id) = graph_modification_utils::get_star_id(layer, &document.network_interface).or(graph_modification_utils::get_polygon_id(layer, &document.network_interface)) else { + + // This dial serves both Star and Polygon layers, so resolve which node is present to know whose sides parameter to write + let (node_id, sides_parameter) = if let Some(node_id) = graph_modification_utils::get_star_id(layer, &document.network_interface) { + (node_id, ParameterRef::from(graphene_std::vector::generator_nodes::star::SidesInput)) + } else if let Some(node_id) = graph_modification_utils::get_polygon_id(layer, &document.network_interface) { + (node_id, ParameterRef::from(graphene_std::vector::generator_nodes::regular_polygon::SidesInput)) + } else { return; }; let new_point_count = ((self.initial_points as i32) + (net_delta as i32)).max(3); responses.add(NodeGraphMessage::SetInput { - input_connector: InputConnector::node(node_id, 1), + input_connector: InputConnector::node(node_id, sides_parameter), input: NodeInput::value(TaggedValue::U32(new_point_count as u32), false), }); responses.add(NodeGraphMessage::RunDocumentGraph); diff --git a/editor/src/messages/tool/common_functionality/gizmos/shape_gizmos/point_radius_handle.rs b/editor/src/messages/tool/common_functionality/gizmos/shape_gizmos/point_radius_handle.rs index d72414f48a..23b399f1b9 100644 --- a/editor/src/messages/tool/common_functionality/gizmos/shape_gizmos/point_radius_handle.rs +++ b/editor/src/messages/tool/common_functionality/gizmos/shape_gizmos/point_radius_handle.rs @@ -8,12 +8,14 @@ use crate::messages::portfolio::document::{overlays::utility_types::OverlayConte use crate::messages::prelude::FrontendMessage; use crate::messages::prelude::Responses; use crate::messages::prelude::{DocumentMessageHandler, InputPreprocessorMessageHandler, NodeGraphMessage}; -use crate::messages::tool::common_functionality::graph_modification_utils::{self, NodeGraphLayer}; +use crate::messages::tool::common_functionality::graph_modification_utils::NodeGraphLayer; use crate::messages::tool::common_functionality::shapes::shape_utility::{draw_snapping_ticks, extract_polygon_parameters, polygon_outline, polygon_vertex_position, star_outline}; use crate::messages::tool::common_functionality::shapes::shape_utility::{extract_star_parameters, star_vertex_position}; use glam::DVec2; use graph_craft::document::NodeInput; use graph_craft::document::value::TaggedValue; +use graphene_std::ParameterRef; +use graphene_std::vector::generator_nodes::{regular_polygon, star}; use std::collections::VecDeque; use std::f64::consts::{FRAC_1_SQRT_2, FRAC_PI_4, PI, SQRT_2}; @@ -30,7 +32,8 @@ pub enum PointRadiusHandleState { pub struct PointRadiusHandle { pub layer: Option, point: u32, - radius_index: usize, + /// The radius parameter the hovered or dragged handle writes to: a star's first or second radius, or a polygon's radius. + radius_parameter: Option, snap_radii: Vec, initial_radius: f64, handle_state: PointRadiusHandleState, @@ -63,7 +66,11 @@ impl PointRadiusHandle { let viewport = document.metadata().transform_to_viewport(layer); for i in 0..2 * sides { - let (radius, radius_index) = if i % 2 == 0 { (radius1, 2) } else { (radius2, 3) }; + let (radius, radius_parameter) = if i % 2 == 0 { + (radius1, ParameterRef::from(star::Radius1Input)) + } else { + (radius2, ParameterRef::from(star::Radius2Input)) + }; let point = star_vertex_position(viewport, i as i32, sides, radius1, radius2); let center = viewport.transform_point2(DVec2::ZERO); @@ -73,10 +80,10 @@ impl PointRadiusHandle { } if point.distance(mouse_position) < 5. { - self.radius_index = radius_index; + self.snap_radii = Self::calculate_snap_radii(document, layer, &radius_parameter); + self.radius_parameter = Some(radius_parameter); self.layer = Some(layer); self.point = i; - self.snap_radii = Self::calculate_snap_radii(document, layer, radius_index); self.initial_radius = radius; responses.add(FrontendMessage::UpdateMouseCursor { cursor: MouseCursorIcon::Default }); self.update_state(PointRadiusHandleState::Hover); @@ -100,7 +107,7 @@ impl PointRadiusHandle { } if point.distance(mouse_position) < 5. { - self.radius_index = 2; + self.radius_parameter = Some(regular_polygon::RadiusInput.into()); self.layer = Some(layer); self.point = i; self.snap_radii.clear(); @@ -329,21 +336,20 @@ impl PointRadiusHandle { } } - fn calculate_snap_radii(document: &DocumentMessageHandler, layer: LayerNodeIdentifier, radius_index: usize) -> Vec { + fn calculate_snap_radii(document: &DocumentMessageHandler, layer: LayerNodeIdentifier, radius_parameter: &ParameterRef) -> Vec { let mut snap_radii = Vec::new(); - let Some(node_inputs) = NodeGraphLayer::new(layer, &document.network_interface).find_node_inputs(&DefinitionIdentifier::ProtoNode(graphene_std::vector::generator_nodes::star::IDENTIFIER)) - else { + let Some(parameters) = NodeGraphLayer::new(layer, &document.network_interface).find_node_parameters(star::IDENTIFIER) else { return snap_radii; }; - let (Some(&TaggedValue::F64(radius_1)), Some(&TaggedValue::F64(radius_2))) = (node_inputs[2].as_value(), node_inputs[3].as_value()) else { + let (Some(&TaggedValue::F64(radius_1)), Some(&TaggedValue::F64(radius_2))) = (parameters.value(star::Radius1Input), parameters.value(star::Radius2Input)) else { return snap_radii; }; - let other_radius = if radius_index == 3 { radius_1 } else { radius_2 }; + let other_radius = if *radius_parameter == ParameterRef::from(star::Radius2Input) { radius_1 } else { radius_2 }; - let Some(&TaggedValue::U32(sides)) = node_inputs[1].as_value() else { + let Some(&TaggedValue::U32(sides)) = parameters.value(star::SidesInput) else { return snap_radii; }; @@ -415,14 +421,15 @@ impl PointRadiusHandle { pub fn update_inner_radius(&mut self, document: &DocumentMessageHandler, input: &InputPreprocessorMessageHandler, responses: &mut VecDeque, drag_start: DVec2) { let Some(layer) = self.layer else { return }; + let Some(radius_parameter) = self.radius_parameter.clone() else { return }; - let Some(node_id) = graph_modification_utils::get_star_id(layer, &document.network_interface).or(graph_modification_utils::get_polygon_id(layer, &document.network_interface)) else { + // The stored parameter names the node it belongs to (Star or Polygon), so locate that same node on the layer + let Some(node_id) = NodeGraphLayer::new(layer, &document.network_interface).upstream_node_id_from_name(&DefinitionIdentifier::ProtoNode(radius_parameter.node_identifier.clone())) else { return; }; let viewport_transform = document.network_interface.document_metadata().transform_to_viewport(layer); let center = viewport_transform.transform_point2(DVec2::ZERO); - let radius_index = self.radius_index; let original_radius = self.initial_radius; @@ -436,7 +443,7 @@ impl PointRadiusHandle { self.update_state(PointRadiusHandleState::Dragging); - self.check_if_radius_flipped(original_radius, new_radius, document, layer, radius_index); + self.check_if_radius_flipped(original_radius, new_radius, document, layer, &radius_parameter); if let Some((index, snapped_delta)) = self.check_snapping(new_radius, original_radius) { net_delta = snapped_delta; @@ -444,29 +451,28 @@ impl PointRadiusHandle { } responses.add(NodeGraphMessage::SetInput { - input_connector: InputConnector::node(node_id, radius_index), + input_connector: InputConnector::node(node_id, radius_parameter), input: NodeInput::value(TaggedValue::F64(original_radius + net_delta), false), }); responses.add(NodeGraphMessage::RunDocumentGraph); } - fn check_if_radius_flipped(&mut self, original_radius: f64, new_radius: f64, document: &DocumentMessageHandler, layer: LayerNodeIdentifier, radius_index: usize) { - let Some(node_inputs) = NodeGraphLayer::new(layer, &document.network_interface).find_node_inputs(&DefinitionIdentifier::ProtoNode(graphene_std::vector::generator_nodes::star::IDENTIFIER)) - else { + fn check_if_radius_flipped(&mut self, original_radius: f64, new_radius: f64, document: &DocumentMessageHandler, layer: LayerNodeIdentifier, radius_parameter: &ParameterRef) { + let Some(parameters) = NodeGraphLayer::new(layer, &document.network_interface).find_node_parameters(star::IDENTIFIER) else { return; }; - let (Some(&TaggedValue::F64(radius_1)), Some(&TaggedValue::F64(radius_2))) = (node_inputs[2].as_value(), node_inputs[3].as_value()) else { + let (Some(&TaggedValue::F64(radius_1)), Some(&TaggedValue::F64(radius_2))) = (parameters.value(star::Radius1Input), parameters.value(star::Radius2Input)) else { return; }; - let other_radius = if radius_index == 3 { radius_1 } else { radius_2 }; + let other_radius = if *radius_parameter == ParameterRef::from(star::Radius2Input) { radius_1 } else { radius_2 }; let flipped = (other_radius.is_sign_positive() && original_radius.is_sign_negative() && new_radius.is_sign_positive()) || (other_radius.is_sign_negative() && original_radius.is_sign_positive() && new_radius.is_sign_negative()); if flipped { - self.snap_radii = Self::calculate_snap_radii(document, layer, radius_index); + self.snap_radii = Self::calculate_snap_radii(document, layer, radius_parameter); } } } diff --git a/editor/src/messages/tool/common_functionality/gizmos/shape_gizmos/spiral_turns_handle.rs b/editor/src/messages/tool/common_functionality/gizmos/shape_gizmos/spiral_turns_handle.rs index 95825c2691..d41fda2c18 100644 --- a/editor/src/messages/tool/common_functionality/gizmos/shape_gizmos/spiral_turns_handle.rs +++ b/editor/src/messages/tool/common_functionality/gizmos/shape_gizmos/spiral_turns_handle.rs @@ -12,7 +12,6 @@ use crate::messages::tool::common_functionality::shapes::spiral_shape::calculate use glam::DVec2; use graph_craft::document::NodeInput; use graph_craft::document::value::TaggedValue; -use graphene_std::NodeInputDecleration; use graphene_std::subpath::{calculate_growth_factor, spiral_point}; use graphene_std::vector::misc::SpiralType; use std::collections::VecDeque; @@ -190,15 +189,15 @@ impl SpiralTurns { let new_outer_radius = (self.initial_outer_radius + outer_radius_change * sign).max(0.1); responses.add(NodeGraphMessage::SetInput { - input_connector: InputConnector::node(node_id, StartAngleInput::INDEX), + input_connector: InputConnector::node(node_id, StartAngleInput), input: NodeInput::value(TaggedValue::F64(self.initial_start_angle + total_delta), false), }); responses.add(NodeGraphMessage::SetInput { - input_connector: InputConnector::node(node_id, TurnsInput::INDEX), + input_connector: InputConnector::node(node_id, TurnsInput), input: NodeInput::value(TaggedValue::F64(new_turns), false), }); responses.add(NodeGraphMessage::SetInput { - input_connector: InputConnector::node(node_id, OuterRadiusInput::INDEX), + input_connector: InputConnector::node(node_id, OuterRadiusInput), input: NodeInput::value(TaggedValue::F64(new_outer_radius), false), }); } @@ -207,11 +206,11 @@ impl SpiralTurns { let new_outer_radius = (self.initial_outer_radius + outer_radius_change).max(0.1); responses.add(NodeGraphMessage::SetInput { - input_connector: InputConnector::node(node_id, TurnsInput::INDEX), + input_connector: InputConnector::node(node_id, TurnsInput), input: NodeInput::value(TaggedValue::F64(new_turns), false), }); responses.add(NodeGraphMessage::SetInput { - input_connector: InputConnector::node(node_id, OuterRadiusInput::INDEX), + input_connector: InputConnector::node(node_id, OuterRadiusInput), input: NodeInput::value(TaggedValue::F64(new_outer_radius), false), }); } diff --git a/editor/src/messages/tool/common_functionality/gizmos/shape_gizmos/sweep_angle_gizmo.rs b/editor/src/messages/tool/common_functionality/gizmos/shape_gizmos/sweep_angle_gizmo.rs index efa4b18351..c65b912aba 100644 --- a/editor/src/messages/tool/common_functionality/gizmos/shape_gizmos/sweep_angle_gizmo.rs +++ b/editor/src/messages/tool/common_functionality/gizmos/shape_gizmos/sweep_angle_gizmo.rs @@ -332,11 +332,11 @@ impl SweepAngleGizmo { self.snap_angles = Self::calculate_snap_angles(); responses.add(NodeGraphMessage::SetInput { - input_connector: InputConnector::node(node_id, 2), + input_connector: InputConnector::node(node_id, graphene_std::vector::generator_nodes::arc::StartAngleInput), input: NodeInput::value(TaggedValue::F64(start_angle), false), }); responses.add(NodeGraphMessage::SetInput { - input_connector: InputConnector::node(node_id, 3), + input_connector: InputConnector::node(node_id, graphene_std::vector::generator_nodes::arc::SweepAngleInput), input: NodeInput::value(TaggedValue::F64(sweep_angle), false), }); diff --git a/editor/src/messages/tool/common_functionality/graph_modification_utils.rs b/editor/src/messages/tool/common_functionality/graph_modification_utils.rs index 7184cc5151..89041ab8c8 100644 --- a/editor/src/messages/tool/common_functionality/graph_modification_utils.rs +++ b/editor/src/messages/tool/common_functionality/graph_modification_utils.rs @@ -7,8 +7,7 @@ use glam::{DAffine2, DVec2}; use graph_craft::ProtoNodeIdentifier; use graph_craft::document::value::TaggedValue; use graph_craft::document::{DocumentNode, NodeId, NodeInput}; -use graphene_std::NodeInputDecleration; -use graphene_std::list::List; +use graphene_std::Color; use graphene_std::raster::BlendMode; use graphene_std::raster_types::Image; use graphene_std::subpath::Subpath; @@ -16,7 +15,7 @@ use graphene_std::text::{Font, TypesettingConfig}; use graphene_std::vector::misc::ManipulatorPointId; use graphene_std::vector::style::{FillChoice, PaintOrder, StrokeAlign, StrokeCap, StrokeJoin, initial_gradient_transform_for_bounding_box}; use graphene_std::vector::{Gradient, GradientSpreadMethod, GradientType, PointId, SegmentId, VectorModificationType}; -use graphene_std::{Color, Graphic}; +use graphene_std::{NodeParameter, ParameterRef}; use std::collections::VecDeque; /// Returns the ID of the first Spline node in the horizontal flow which is not followed by a `Path` node, or `None` if none exists. @@ -93,8 +92,8 @@ pub fn merge_layers(document: &DocumentMessageHandler, first_layer: LayerNodeIde parent: first_layer, }); responses.add(NodeGraphMessage::ConnectUpstreamOutputToInput { - downstream_input: InputConnector::node(second_layer.to_node(), 1), - input_connector: InputConnector::node(merge_node_id, 1), + downstream_input: InputConnector::layer_secondary_input(second_layer.to_node()), + input_connector: InputConnector::layer_secondary_input(merge_node_id), }); responses.add(NodeGraphMessage::DeleteNodes { node_ids: vec![second_layer.to_node()], @@ -251,9 +250,7 @@ pub fn new_custom(id: NodeId, nodes: Vec<(NodeId, NodeTemplate)>, parent: LayerN pub fn get_origin(layer: LayerNodeIdentifier, network_interface: &NodeNetworkInterface) -> Option { use graphene_std::transform_nodes::transform::*; - if let TaggedValue::DVec2(origin) = - NodeGraphLayer::new(layer, network_interface).find_input(&DefinitionIdentifier::ProtoNode(graphene_std::transform_nodes::transform::IDENTIFIER), TranslationInput::INDEX)? - { + if let TaggedValue::DVec2(origin) = NodeGraphLayer::new(layer, network_interface).parameter_value(TranslationInput)? { Some(*origin) } else { None @@ -275,16 +272,16 @@ pub fn get_viewport_center(layer: LayerNodeIdentifier, network_interface: &NodeN pub fn get_fill_node_id_with_direct_fill_input(layer: LayerNodeIdentifier, network_interface: &NodeNetworkInterface) -> Option { let fill_node_id = NodeGraphLayer::new(layer, network_interface).upstream_node_id_from_name(&DefinitionIdentifier::ProtoNode(graphene_std::vector::fill::IDENTIFIER))?; let fill_node = network_interface.document_network().nodes.get(&fill_node_id)?; - matches!(fill_node.inputs.get(graphene_std::vector::fill::FillInput::>::INDEX)?, NodeInput::Value { .. }).then_some(fill_node_id) + matches!(fill_node.input(graphene_std::vector::fill::FillInput)?, NodeInput::Value { .. }).then_some(fill_node_id) } /// Determine the input connector where the gradient chain enters the layer. /// Returns Fill's fill input if the layer has a "Fill" node, otherwise returns the layer's content input. pub fn gradient_chain_target_input(layer: LayerNodeIdentifier, network_interface: &NodeNetworkInterface) -> InputConnector { if let Some(fill_node_id) = NodeGraphLayer::new(layer, network_interface).upstream_node_id_from_name(&DefinitionIdentifier::ProtoNode(graphene_std::vector::fill::IDENTIFIER)) { - InputConnector::node(fill_node_id, graphene_std::vector::fill::FillInput::>::INDEX) + InputConnector::node(fill_node_id, graphene_std::vector::fill::FillInput) } else { - InputConnector::node(layer.to_node(), 1) + InputConnector::layer_secondary_input(layer.to_node()) } } @@ -303,7 +300,7 @@ pub fn get_upstream_gradient_value_node_id(layer: LayerNodeIdentifier, network_i pub fn get_fill_input_node_id(layer: LayerNodeIdentifier, network_interface: &NodeNetworkInterface) -> Option { let fill_node_id = NodeGraphLayer::new(layer, network_interface).upstream_node_id_from_name(&DefinitionIdentifier::ProtoNode(graphene_std::vector::fill::IDENTIFIER))?; let fill_node = network_interface.document_network().nodes.get(&fill_node_id)?; - let NodeInput::Node { node_id, .. } = fill_node.inputs.get(graphene_std::vector::fill::FillInput::>::INDEX)? else { + let NodeInput::Node { node_id, .. } = fill_node.input(graphene_std::vector::fill::FillInput)? else { return None; }; Some(*node_id) @@ -317,13 +314,13 @@ pub fn get_gradient_stops(layer: LayerNodeIdentifier, network_interface: &NodeNe .document_network() .nodes .get(&fill_node_id) - .and_then(|node| node.inputs.get(graphene_std::vector::fill::FillInput::>::INDEX)) + .and_then(|node| node.input(graphene_std::vector::fill::FillInput)) .and_then(|input| input.as_value()) .and_then(|value| if let TaggedValue::Gradient(gradient) = value { Some(gradient.clone()) } else { None }); } let gradient_value_node = network_interface.document_network().nodes.get(&get_upstream_gradient_value_node_id(layer, network_interface)?)?; - let TaggedValue::Gradient(stops) = gradient_value_node.inputs.get(graphene_std::math_nodes::gradient_value::GradientInput::INDEX)?.as_value()? else { + let TaggedValue::Gradient(stops) = gradient_value_node.input(graphene_std::math_nodes::gradient_value::GradientInput)?.as_value()? else { return None; }; Some(stops.clone()) @@ -362,8 +359,7 @@ pub fn gradient_orientation_rightward(transform: glam::DAffine2) -> bool { /// Get the current fill of a layer from the closest "Fill" node. pub fn get_fill_color(layer: LayerNodeIdentifier, network_interface: &NodeNetworkInterface) -> Option { - let inputs = NodeGraphLayer::new(layer, network_interface).find_node_inputs(&DefinitionIdentifier::ProtoNode(graphene_std::vector::fill::IDENTIFIER))?; - let TaggedValue::Color(color) = inputs.get(graphene_std::vector::fill::FillInput::>::INDEX)?.as_value()? else { + let TaggedValue::Color(color) = NodeGraphLayer::new(layer, network_interface).parameter_value(graphene_std::vector::fill::FillInput)? else { return None; }; Some(*color) @@ -472,42 +468,35 @@ pub fn get_text<'a>( fonts: &FontsMessageHandler, resources: &ResourceMessageHandler, ) -> Option<(&'a String, Font, TypesettingConfig)> { - let inputs = NodeGraphLayer::new(layer, network_interface).find_node_inputs(&DefinitionIdentifier::ProtoNode(graphene_std::text::text::IDENTIFIER))?; + use graphene_std::text::text; + let parameters = NodeGraphLayer::new(layer, network_interface).find_node_parameters(text::IDENTIFIER)?; - let Some(TaggedValue::String(text)) = inputs.get(graphene_std::text::text::TextInput::INDEX)?.as_value() else { - return None; - }; - let font = match inputs.get(graphene_std::text::text::FontInput::INDEX)?.as_value() { + let Some(TaggedValue::String(text)) = parameters.value(text::TextInput) else { return None }; + let font = match parameters.value(text::FontInput) { Some(TaggedValue::Resource(resource_id)) => fonts.id_font(resources, *resource_id).unwrap_or_default(), _ => Font::default(), }; - let Some(&TaggedValue::F64(font_size)) = inputs.get(graphene_std::text::text::SizeInput::INDEX)?.as_value() else { + let Some(&TaggedValue::F64(font_size)) = parameters.value(text::SizeInput) else { return None }; + let Some(&TaggedValue::F64(line_height_ratio)) = parameters.value(text::LineHeightInput) else { return None; }; - let Some(&TaggedValue::F64(line_height_ratio)) = inputs.get(graphene_std::text::text::LineHeightInput::INDEX)?.as_value() else { + let Some(&TaggedValue::F64(letter_spacing)) = parameters.value(text::LetterSpacingInput) else { return None; }; - let Some(&TaggedValue::F64(letter_spacing)) = inputs.get(graphene_std::text::text::LetterSpacingInput::INDEX)?.as_value() else { + let Some(&TaggedValue::Bool(has_max_width)) = parameters.value(text::HasMaxWidthInput) else { return None; }; - let Some(&TaggedValue::Bool(has_max_width)) = inputs.get(graphene_std::text::text::HasMaxWidthInput::INDEX)?.as_value() else { + let Some(&TaggedValue::F64(max_width)) = parameters.value(text::MaxWidthInput) else { return None }; + let Some(&TaggedValue::Bool(has_max_height)) = parameters.value(text::HasMaxHeightInput) else { return None; }; - let Some(&TaggedValue::F64(max_width)) = inputs.get(graphene_std::text::text::MaxWidthInput::INDEX)?.as_value() else { + let Some(&TaggedValue::F64(max_height)) = parameters.value(text::MaxHeightInput) else { return None; }; - let Some(&TaggedValue::Bool(has_max_height)) = inputs.get(graphene_std::text::text::HasMaxHeightInput::INDEX)?.as_value() else { - return None; - }; - let Some(&TaggedValue::F64(max_height)) = inputs.get(graphene_std::text::text::MaxHeightInput::INDEX)?.as_value() else { - return None; - }; - let Some(&TaggedValue::F64(letter_tilt)) = inputs.get(graphene_std::text::text::LetterTiltInput::INDEX)?.as_value() else { - return None; - }; - let Some(&TaggedValue::TextAlign(align)) = inputs.get(graphene_std::text::text::AlignInput::INDEX)?.as_value() else { + let Some(&TaggedValue::F64(letter_tilt)) = parameters.value(text::LetterTiltInput) else { return None; }; + let Some(&TaggedValue::TextAlign(align)) = parameters.value(text::AlignInput) else { return None }; let typesetting = TypesettingConfig { font_size, @@ -522,8 +511,7 @@ pub fn get_text<'a>( } pub fn get_stroke_width(layer: LayerNodeIdentifier, network_interface: &NodeNetworkInterface) -> Option { - let weight_node_input_index = graphene_std::vector::stroke::WeightInput::INDEX; - if let TaggedValue::F64(width) = NodeGraphLayer::new(layer, network_interface).find_input(&DefinitionIdentifier::ProtoNode(graphene_std::vector::stroke::IDENTIFIER), weight_node_input_index)? { + if let TaggedValue::F64(width) = NodeGraphLayer::new(layer, network_interface).parameter_value(graphene_std::vector::stroke::WeightInput)? { Some(*width) } else { None @@ -545,36 +533,34 @@ pub struct StrokeOptionsState { /// Reads the non-color stroke option inputs from a layer's Stroke proto node. Returns `None` when the layer has no Stroke node. /// Inputs that aren't a static value (e.g. wired to another node) fall back to per-field defaults so the layer still participates in the sync. pub fn get_stroke_options(layer: LayerNodeIdentifier, network_interface: &NodeNetworkInterface) -> Option { - let stroke = &DefinitionIdentifier::ProtoNode(graphene_std::vector::stroke::IDENTIFIER); - let layer_view = NodeGraphLayer::new(layer, network_interface); - layer_view.upstream_node_id_from_name(stroke)?; - let read = |index: usize| layer_view.find_input(stroke, index); + use graphene_std::vector::stroke; + let parameters = NodeGraphLayer::new(layer, network_interface).find_node_parameters(stroke::IDENTIFIER)?; - let align = match read(graphene_std::vector::stroke::AlignInput::INDEX) { + let align = match parameters.value(stroke::AlignInput) { Some(TaggedValue::StrokeAlign(value)) => *value, _ => StrokeAlign::default(), }; - let cap = match read(graphene_std::vector::stroke::CapInput::INDEX) { + let cap = match parameters.value(stroke::CapInput) { Some(TaggedValue::StrokeCap(value)) => *value, _ => StrokeCap::default(), }; - let join = match read(graphene_std::vector::stroke::JoinInput::INDEX) { + let join = match parameters.value(stroke::JoinInput) { Some(TaggedValue::StrokeJoin(value)) => *value, _ => StrokeJoin::default(), }; - let miter_limit = match read(graphene_std::vector::stroke::MiterLimitInput::INDEX) { + let miter_limit = match parameters.value(stroke::MiterLimitInput) { Some(TaggedValue::F64(value)) => *value, _ => 4., }; - let paint_order = match read(graphene_std::vector::stroke::PaintOrderInput::INDEX) { + let paint_order = match parameters.value(stroke::PaintOrderInput) { Some(TaggedValue::PaintOrder(value)) => *value, _ => PaintOrder::default(), }; - let dash_lengths = match read(graphene_std::vector::stroke::DashPatternInput::INDEX) { + let dash_lengths = match parameters.value(stroke::DashPatternInput) { Some(TaggedValue::DashPattern(value)) => value.0.iter_element_values().copied().collect(), _ => Vec::new(), }; - let dash_offset = match read(graphene_std::vector::stroke::DashOffsetInput::INDEX) { + let dash_offset = match parameters.value(stroke::DashOffsetInput) { Some(TaggedValue::F64(value)) => *value, _ => 0., }; @@ -613,12 +599,10 @@ pub fn set_stroke_weight_for_selected_layers(weight: f64, document: &DocumentMes let layers: Vec<_> = document.network_interface.selected_nodes().selected_layers_except_artboards(&document.network_interface).collect(); for layer in layers { if let Some(node_id) = get_stroke_id(layer, &document.network_interface) { - let input_index = graphene_std::vector::stroke::WeightInput::INDEX; - let value = TaggedValue::F64(weight); responses.add(NodeGraphMessage::SetInputValue { node_id, - input_index, - value: value.into(), + input_index: graphene_std::vector::stroke::WeightInput::INDEX, + value: TaggedValue::F64(weight).into(), }); } else if weight > 0. { let color = Some(Color::BLACK); @@ -642,19 +626,19 @@ pub struct FillNodeGradient { pub fn read_fill_node_gradient(fill_node: &DocumentNode, bounding_box: impl FnOnce() -> [DVec2; 2]) -> Option { use graphene_std::vector::fill; - let TaggedValue::Gradient(stops) = fill_node.inputs.get(fill::FillInput::>::INDEX)?.as_value()? else { + let TaggedValue::Gradient(stops) = fill_node.input(fill::FillInput)?.as_value()? else { return None; }; - let gradient_type = match fill_node.inputs.get(fill::GradientTypeInput::INDEX).and_then(|input| input.as_value()) { + let gradient_type = match fill_node.input(fill::GradientTypeInput).and_then(|input| input.as_value()) { Some(&TaggedValue::GradientType(value)) => value, _ => GradientType::default(), }; - let spread_method = match fill_node.inputs.get(fill::SpreadMethodInput::INDEX).and_then(|input| input.as_value()) { + let spread_method = match fill_node.input(fill::SpreadMethodInput).and_then(|input| input.as_value()) { Some(&TaggedValue::GradientSpreadMethod(value)) => value, _ => GradientSpreadMethod::default(), }; - let has_transform = matches!(fill_node.inputs.get(fill::HasTransformInput::INDEX).and_then(|input| input.as_value()), Some(&TaggedValue::Bool(true))); - let transform_input = fill_node.inputs.get(fill::TransformInput::INDEX).and_then(|input| input.as_value()); + let has_transform = matches!(fill_node.input(fill::HasTransformInput).and_then(|input| input.as_value()), Some(&TaggedValue::Bool(true))); + let transform_input = fill_node.input(fill::TransformInput).and_then(|input| input.as_value()); let transform = match (has_transform, transform_input) { (true, Some(&TaggedValue::DAffine2(value))) => value, (false, _) => initial_gradient_transform_for_bounding_box(bounding_box()), @@ -671,8 +655,7 @@ pub fn read_fill_node_gradient(fill_node: &DocumentNode, bounding_box: impl FnOn } /// Returns the stroke color from a layer's upstream Stroke node. pub fn get_stroke_color(layer: LayerNodeIdentifier, network_interface: &NodeNetworkInterface) -> Option> { - let color_index = graphene_std::vector::stroke::PaintInput::>::INDEX; - let tagged = NodeGraphLayer::new(layer, network_interface).find_input(&DefinitionIdentifier::ProtoNode(graphene_std::vector::stroke::IDENTIFIER), color_index)?; + let tagged = NodeGraphLayer::new(layer, network_interface).parameter_value(graphene_std::vector::stroke::PaintInput)?; match tagged { TaggedValue::Color(color) => Some(Some(*color)), value if value.is_no_paint() => Some(None), @@ -709,7 +692,7 @@ pub fn selected_fill_state(document: &DocumentMessageHandler) -> Option>::INDEX)?.as_value()? { + match fill_node.input(graphene_std::vector::fill::FillInput)?.as_value()? { TaggedValue::Color(color) => Some(FillChoice::Solid(*color)), TaggedValue::Gradient(stops) => Some(FillChoice::Gradient(stops.clone())), value if value.is_no_paint() => Some(FillChoice::None), @@ -796,22 +779,19 @@ pub fn set_fill_for_selected_layers(fill_choice: FillChoice, document: &Document FillChoice::None => responses.add(GraphOperationMessage::FillColorSet { layer, color: None }), FillChoice::Solid(color) => responses.add(GraphOperationMessage::FillColorSet { layer, color: Some(*color) }), FillChoice::Gradient(stops) => { - let fill_node = NodeGraphLayer::new(layer, &document.network_interface) - .upstream_node_id_from_name(&DefinitionIdentifier::ProtoNode(graphene_std::vector::fill::IDENTIFIER)) - .and_then(|id| document.network_interface.document_network().nodes.get(&id)); + use graphene_std::vector::fill; + let fill_parameters = NodeGraphLayer::new(layer, &document.network_interface).find_node_parameters(fill::IDENTIFIER); - let read = |index: usize| fill_node.and_then(|node| node.inputs.get(index)).and_then(|input| input.as_value()); - - let gradient_type = match read(graphene_std::vector::fill::GradientTypeInput::INDEX) { + let gradient_type = match fill_parameters.as_ref().and_then(|parameters| parameters.value(fill::GradientTypeInput)) { Some(TaggedValue::GradientType(value)) => *value, _ => GradientType::default(), }; - let spread_method = match read(graphene_std::vector::fill::SpreadMethodInput::INDEX) { + let spread_method = match fill_parameters.as_ref().and_then(|parameters| parameters.value(fill::SpreadMethodInput)) { Some(TaggedValue::GradientSpreadMethod(value)) => *value, _ => GradientSpreadMethod::default(), }; - let has_transform = matches!(read(graphene_std::vector::fill::HasTransformInput::INDEX), Some(TaggedValue::Bool(true))); - let transform = match (has_transform, read(graphene_std::vector::fill::TransformInput::INDEX)) { + let has_transform = matches!(fill_parameters.as_ref().and_then(|parameters| parameters.value(fill::HasTransformInput)), Some(TaggedValue::Bool(true))); + let transform = match (has_transform, fill_parameters.as_ref().and_then(|parameters| parameters.value(fill::TransformInput))) { (true, Some(TaggedValue::DAffine2(value))) => *value, (false, _) => initial_gradient_transform_for_bounding_box(document.network_interface.document_metadata().nonzero_bounding_box(layer)), _ => DAffine2::IDENTITY, @@ -836,12 +816,10 @@ pub fn set_stroke_color_for_selected_layers(color: Option, weight: f64, d let layers: Vec<_> = document.network_interface.selected_nodes().selected_layers_except_artboards(&document.network_interface).collect(); for layer in layers { if let Some(node_id) = get_stroke_id(layer, &document.network_interface) { - let input_index = graphene_std::vector::stroke::PaintInput::>::INDEX; - let value = color.map_or_else(TaggedValue::no_paint, TaggedValue::Color); responses.add(NodeGraphMessage::SetInputValue { node_id, - input_index, - value: value.into(), + input_index: graphene_std::vector::stroke::PaintInput::INDEX, + value: color.map_or_else(TaggedValue::no_paint, TaggedValue::Color).into(), }); } else { let stroke = graphene_std::vector::style::Stroke::new(weight); @@ -880,28 +858,26 @@ pub fn remove_stroke_for_selected_layers(document: &DocumentMessageHandler, resp responses.add(NodeGraphMessage::SendGraph); } -/// Reads a specific input from the matching proto node on the first selected non-artboard layer that has one. +/// Reads the given parameter from its proto node on the first selected non-artboard layer that has one. /// Used by tool control bars to mirror per-shape parameters (sides, arc type, turns, etc.) from the selection /// into the control bar's input widget state without each call site re-implementing the layer iteration. -pub fn first_selected_proto_node_input(document: &DocumentMessageHandler, identifier: graph_craft::ProtoNodeIdentifier, input_index: usize) -> Option<&TaggedValue> { - let identifier = DefinitionIdentifier::ProtoNode(identifier); +pub fn first_selected_parameter(document: &DocumentMessageHandler, parameter: impl Into) -> Option<&TaggedValue> { + let parameter = parameter.into(); + let identifier = DefinitionIdentifier::ProtoNode(parameter.node_identifier); + document .network_interface .selected_nodes() .selected_layers_except_artboards(&document.network_interface) - .find_map(|layer| NodeGraphLayer::new(layer, &document.network_interface).find_input(&identifier, input_index)) + .find_map(|layer| NodeGraphLayer::new(layer, &document.network_interface).find_input(&identifier, parameter.input_index)) } -/// Writes a value to a specific input on the matching proto node of every selected non-artboard layer that has one. +/// Writes a value to the given parameter on its proto node for every selected non-artboard layer that has one. /// Used by tool control bars to push per-shape parameter changes back onto all selected layers of that shape. -pub fn set_proto_node_input_for_selected_layers( - document: &DocumentMessageHandler, - identifier: graph_craft::ProtoNodeIdentifier, - input_index: usize, - value: TaggedValue, - responses: &mut VecDeque, -) { - let identifier = DefinitionIdentifier::ProtoNode(identifier); +/// The parameter symbol locates the node, so the value can never land on the right index of the wrong node. +pub fn set_parameter_for_selected_layers(document: &DocumentMessageHandler, parameter: impl Into, value: TaggedValue, responses: &mut VecDeque) { + let parameter = parameter.into(); + let identifier = DefinitionIdentifier::ProtoNode(parameter.node_identifier.clone()); let layers: Vec<_> = document.network_interface.selected_nodes().selected_layers_except_artboards(&document.network_interface).collect(); @@ -911,7 +887,7 @@ pub fn set_proto_node_input_for_selected_layers( }; responses.add(NodeGraphMessage::SetInputValue { node_id, - input_index, + input_index: parameter.input_index, value: value.clone().into(), }); } @@ -982,16 +958,60 @@ impl<'a> NodeGraphLayer<'a> { .and_then(|node_id| self.network_interface.document_network().nodes.get(&node_id).map(|node| &node.inputs)) } - /// Find a specific input of a node within the layer's primary flow + /// Find a specific input of a node within the layer's primary flow. + /// Prefer [`Self::parameter_value`] where the input is statically known; this remains for network nodes and runtime-chosen indices. pub fn find_input(&self, identifier: &DefinitionIdentifier, index: usize) -> Option<&'a TaggedValue> { - // TODO: Find a better way to accept a node input rather than using its index (which is quite unclear and fragile) self.find_node_inputs(identifier)?.get(index)?.as_value() } + /// The stored value of the given parameter on the matching proto node in the layer's primary flow, if that node exists and the input holds a value. + /// The parameter symbol names both the node and the input, so the two can never disagree. + /// For reading several parameters of the same node, prefer [`Self::find_node_parameters`] so the upstream flow is walked only once. + pub fn parameter_value(&self, parameter: impl Into) -> Option<&'a TaggedValue> { + let parameter = parameter.into(); + self.find_input(&DefinitionIdentifier::ProtoNode(parameter.node_identifier), parameter.input_index) + } + + /// Find a proto node in the layer's primary flow and return its inputs for reading by parameter symbol, walking upstream only once. + pub fn find_node_parameters(&self, identifier: ProtoNodeIdentifier) -> Option> { + let inputs = self.find_node_inputs(&DefinitionIdentifier::ProtoNode(identifier.clone()))?; + Some(ProtoNodeParameters { identifier, inputs }) + } + /// Check if a layer is a raster layer pub fn is_raster_layer(layer: LayerNodeIdentifier, network_interface: &mut NodeNetworkInterface) -> bool { - let layer_input_type = network_interface.input_type(&InputConnector::node(layer.to_node(), 1), &[]); + let layer_input_type = network_interface.input_type(&InputConnector::layer_secondary_input(layer.to_node()), &[]); matches!(layer_input_type.compiled_element_name().as_deref(), Some("Raster" | "Raster")) } } + +/// The inputs of a proto node located by [`NodeGraphLayer::find_node_parameters`], read by parameter symbol without re-walking the layer's upstream flow per read. +pub struct ProtoNodeParameters<'a> { + identifier: ProtoNodeIdentifier, + inputs: &'a [NodeInput], +} + +impl<'a> ProtoNodeParameters<'a> { + /// The input slot of the given parameter, or `None` if the parameter belongs to a different node than this view was built from. + pub fn input(&self, parameter: impl Into) -> Option<&'a NodeInput> { + let parameter = parameter.into(); + + // A mismatched symbol would otherwise read whatever sits at that index on the wrong node, so crash under tests but only log and read nothing in the running app + if parameter.node_identifier != self.identifier { + let message = format!("A parameter of {} was read from the inputs of {}", parameter.node_identifier, self.identifier); + if cfg!(test) { + panic!("{message}") + } + log::error!("{message}"); + return None; + } + + self.inputs.get(parameter.input_index) + } + + /// The stored value of the given parameter, if that input currently holds a value rather than a wire. + pub fn value(&self, parameter: impl Into) -> Option<&'a TaggedValue> { + self.input(parameter)?.as_value() + } +} diff --git a/editor/src/messages/tool/common_functionality/shapes/arc_shape.rs b/editor/src/messages/tool/common_functionality/shapes/arc_shape.rs index da10dffe56..e1c62a659c 100644 --- a/editor/src/messages/tool/common_functionality/shapes/arc_shape.rs +++ b/editor/src/messages/tool/common_functionality/shapes/arc_shape.rs @@ -172,7 +172,7 @@ impl Arc { let radius = radius / viewport_zoom(document); responses.add(NodeGraphMessage::SetInput { - input_connector: InputConnector::node(node_id, 1), + input_connector: InputConnector::node(node_id, graphene_std::vector::generator_nodes::arc::RadiusInput), input: NodeInput::value(TaggedValue::F64(radius), false), }); diff --git a/editor/src/messages/tool/common_functionality/shapes/arrow_shape.rs b/editor/src/messages/tool/common_functionality/shapes/arrow_shape.rs index df04b578ff..772f4d4dc9 100644 --- a/editor/src/messages/tool/common_functionality/shapes/arrow_shape.rs +++ b/editor/src/messages/tool/common_functionality/shapes/arrow_shape.rs @@ -67,7 +67,7 @@ impl Arrow { let document_to_viewport = document.metadata().document_to_viewport; responses.add(NodeGraphMessage::SetInput { - input_connector: InputConnector::node(node_id, 1), + input_connector: InputConnector::node(node_id, graphene_std::vector::generator_nodes::arrow::ArrowToInput), input: NodeInput::value(TaggedValue::DVec2(arrow_to), false), }); let downstream = document.metadata().downstream_transform_to_viewport(layer); diff --git a/editor/src/messages/tool/common_functionality/shapes/circle_shape.rs b/editor/src/messages/tool/common_functionality/shapes/circle_shape.rs index 5f25ac8d23..c30e54af12 100644 --- a/editor/src/messages/tool/common_functionality/shapes/circle_shape.rs +++ b/editor/src/messages/tool/common_functionality/shapes/circle_shape.rs @@ -106,7 +106,7 @@ impl Circle { let radius: f64 = if dimensions.x > dimensions.y { dimensions.y / 2. } else { dimensions.x / 2. }; responses.add(NodeGraphMessage::SetInput { - input_connector: InputConnector::node(node_id, 1), + input_connector: InputConnector::node(node_id, graphene_std::vector::generator_nodes::circle::RadiusInput), input: NodeInput::value(TaggedValue::F64(radius), false), }); diff --git a/editor/src/messages/tool/common_functionality/shapes/ellipse_shape.rs b/editor/src/messages/tool/common_functionality/shapes/ellipse_shape.rs index ed837eaddb..5bc4745c87 100644 --- a/editor/src/messages/tool/common_functionality/shapes/ellipse_shape.rs +++ b/editor/src/messages/tool/common_functionality/shapes/ellipse_shape.rs @@ -37,11 +37,11 @@ impl Ellipse { let radius = ((start - end) / 2. / viewport_zoom(document)).abs(); responses.add(NodeGraphMessage::SetInput { - input_connector: InputConnector::node(node_id, 1), + input_connector: InputConnector::node(node_id, graphene_std::vector::generator_nodes::ellipse::RadiusXInput), input: NodeInput::value(TaggedValue::F64(radius.x), false), }); responses.add(NodeGraphMessage::SetInput { - input_connector: InputConnector::node(node_id, 2), + input_connector: InputConnector::node(node_id, graphene_std::vector::generator_nodes::ellipse::RadiusYInput), input: NodeInput::value(TaggedValue::F64(radius.y), false), }); responses.add(window_aligned_transform_set(document, layer, start.midpoint(end), DVec2::ONE)); @@ -75,8 +75,8 @@ mod test_ellipse { let node_graph_layer = NodeGraphLayer::new(layer, &document.network_interface); let ellipse_node = node_graph_layer.upstream_node_id_from_protonode(ellipse::IDENTIFIER)?; Some(ResolvedEllipse { - radius_x: instrumented.grab_ranked_input::(&vec![ellipse_node], &editor.runtime).unwrap(), - radius_y: instrumented.grab_ranked_input::(&vec![ellipse_node], &editor.runtime).unwrap(), + radius_x: instrumented.grab_ranked_input::(&vec![ellipse_node], &editor.runtime).unwrap(), + radius_y: instrumented.grab_ranked_input::(&vec![ellipse_node], &editor.runtime).unwrap(), transform: document.metadata().transform_to_document(layer), }) }) diff --git a/editor/src/messages/tool/common_functionality/shapes/grid_shape.rs b/editor/src/messages/tool/common_functionality/shapes/grid_shape.rs index 199ecfaddf..2548461107 100644 --- a/editor/src/messages/tool/common_functionality/shapes/grid_shape.rs +++ b/editor/src/messages/tool/common_functionality/shapes/grid_shape.rs @@ -11,7 +11,6 @@ use crate::messages::tool::common_functionality::shapes::shape_utility::ShapeGiz use crate::messages::tool::tool_messages::tool_prelude::*; use graph_craft::document::NodeInput; use graph_craft::document::value::TaggedValue; -use graphene_std::NodeInputDecleration; use graphene_std::vector::misc::GridType; use std::collections::VecDeque; @@ -119,14 +118,14 @@ impl Grid { // Set dimensions/spacing responses.add(NodeGraphMessage::SetInput { - input_connector: InputConnector::node(node_id, SpacingInput::::INDEX), + input_connector: InputConnector::node(node_id, SpacingInput), input: NodeInput::value(TaggedValue::DVec2(dimensions), false), }); // Set angle for isometric grids if let Some(angle_deg) = angle { responses.add(NodeGraphMessage::SetInput { - input_connector: InputConnector::node(node_id, AnglesInput::INDEX), + input_connector: InputConnector::node(node_id, AnglesInput), input: NodeInput::value(TaggedValue::DVec2(DVec2::splat(angle_deg)), false), }); } diff --git a/editor/src/messages/tool/common_functionality/shapes/line_shape.rs b/editor/src/messages/tool/common_functionality/shapes/line_shape.rs index 397dc02a5e..d15ab3c8b0 100644 --- a/editor/src/messages/tool/common_functionality/shapes/line_shape.rs +++ b/editor/src/messages/tool/common_functionality/shapes/line_shape.rs @@ -73,7 +73,7 @@ impl Line { let line_to = document_points[1] - document_points[0]; responses.add(NodeGraphMessage::SetInput { - input_connector: InputConnector::node(node_id, 1), + input_connector: InputConnector::node(node_id, graphene_std::vector::generator_nodes::line::LineToInput), input: NodeInput::value(TaggedValue::DVec2(line_to), false), }); let document_to_viewport = document.metadata().document_to_viewport; diff --git a/editor/src/messages/tool/common_functionality/shapes/polygon_shape.rs b/editor/src/messages/tool/common_functionality/shapes/polygon_shape.rs index 5195d6d849..221109defa 100644 --- a/editor/src/messages/tool/common_functionality/shapes/polygon_shape.rs +++ b/editor/src/messages/tool/common_functionality/shapes/polygon_shape.rs @@ -147,7 +147,7 @@ impl Polygon { }; responses.add(NodeGraphMessage::SetInput { - input_connector: InputConnector::node(node_id, 2), + input_connector: InputConnector::node(node_id, graphene_std::vector::generator_nodes::regular_polygon::RadiusInput), input: NodeInput::value(TaggedValue::F64(radius), false), }); @@ -180,7 +180,7 @@ impl Polygon { }); responses.add(NodeGraphMessage::SetInput { - input_connector: InputConnector::node(node_id, 1), + input_connector: InputConnector::node(node_id, graphene_std::vector::generator_nodes::regular_polygon::SidesInput), input: NodeInput::value(TaggedValue::U32(new_dimension), false), }); responses.add(NodeGraphMessage::RunDocumentGraph); diff --git a/editor/src/messages/tool/common_functionality/shapes/rectangle_shape.rs b/editor/src/messages/tool/common_functionality/shapes/rectangle_shape.rs index 59591b18ee..afc68180e9 100644 --- a/editor/src/messages/tool/common_functionality/shapes/rectangle_shape.rs +++ b/editor/src/messages/tool/common_functionality/shapes/rectangle_shape.rs @@ -37,11 +37,11 @@ impl Rectangle { let size = ((start - end) / viewport_zoom(document)).abs(); responses.add(NodeGraphMessage::SetInput { - input_connector: InputConnector::node(node_id, 1), + input_connector: InputConnector::node(node_id, graphene_std::vector::generator_nodes::rectangle::WidthInput), input: NodeInput::value(TaggedValue::F64(size.x), false), }); responses.add(NodeGraphMessage::SetInput { - input_connector: InputConnector::node(node_id, 2), + input_connector: InputConnector::node(node_id, graphene_std::vector::generator_nodes::rectangle::HeightInput), input: NodeInput::value(TaggedValue::F64(size.y), false), }); responses.add(window_aligned_transform_set(document, layer, start.midpoint(end), DVec2::ONE)); diff --git a/editor/src/messages/tool/common_functionality/shapes/shape_utility.rs b/editor/src/messages/tool/common_functionality/shapes/shape_utility.rs index acc48ff37a..c2413ef35d 100644 --- a/editor/src/messages/tool/common_functionality/shapes/shape_utility.rs +++ b/editor/src/messages/tool/common_functionality/shapes/shape_utility.rs @@ -16,7 +16,6 @@ use crate::messages::tool::utility_types::*; use glam::{DAffine2, DMat2, DVec2}; use graph_craft::document::NodeInput; use graph_craft::document::value::TaggedValue; -use graphene_std::NodeInputDecleration; use graphene_std::subpath::{self, Subpath}; use graphene_std::vector::click_target::ClickTargetType; use graphene_std::vector::misc::{ArcType, GridType, SpiralType, dvec2_to_point}; @@ -214,7 +213,7 @@ pub fn update_radius_sign(end: DVec2, start: DVec2, layer: LayerNodeIdentifier, let new_layer = NodeGraphLayer::new(layer, &document.network_interface); if new_layer - .find_input(&DefinitionIdentifier::ProtoNode(graphene_std::vector::generator_nodes::regular_polygon::IDENTIFIER), 1) + .parameter_value(graphene_std::vector::generator_nodes::regular_polygon::SidesInput) .unwrap_or(&TaggedValue::U32(0)) .to_u32() % 2 == 1 @@ -224,14 +223,14 @@ pub fn update_radius_sign(end: DVec2, start: DVec2, layer: LayerNodeIdentifier, }; responses.add(NodeGraphMessage::SetInput { - input_connector: InputConnector::node(polygon_node_id, 2), + input_connector: InputConnector::node(polygon_node_id, graphene_std::vector::generator_nodes::regular_polygon::RadiusInput), input: NodeInput::value(TaggedValue::F64(sign_num * 0.5), false), }); return; } if new_layer - .find_input(&DefinitionIdentifier::ProtoNode(graphene_std::vector::generator_nodes::star::IDENTIFIER), 1) + .parameter_value(graphene_std::vector::generator_nodes::star::SidesInput) .unwrap_or(&TaggedValue::U32(0)) .to_u32() % 2 == 1 @@ -241,11 +240,11 @@ pub fn update_radius_sign(end: DVec2, start: DVec2, layer: LayerNodeIdentifier, }; responses.add(NodeGraphMessage::SetInput { - input_connector: InputConnector::node(star_node_id, 2), + input_connector: InputConnector::node(star_node_id, graphene_std::vector::generator_nodes::star::Radius1Input), input: NodeInput::value(TaggedValue::F64(sign_num * 0.5), false), }); responses.add(NodeGraphMessage::SetInput { - input_connector: InputConnector::node(star_node_id, 3), + input_connector: InputConnector::node(star_node_id, graphene_std::vector::generator_nodes::star::Radius2Input), input: NodeInput::value(TaggedValue::F64(sign_num * 0.25), false), }); } @@ -353,7 +352,7 @@ pub fn extract_arc_parameters(layer: Option, document: &Doc pub fn extract_spiral_parameters(layer: LayerNodeIdentifier, document: &DocumentMessageHandler) -> Option<(SpiralType, f64, f64, f64, f64, f64)> { use graphene_std::vector::generator_nodes::spiral::*; - let node_inputs = NodeGraphLayer::new(layer, &document.network_interface).find_node_inputs(&DefinitionIdentifier::ProtoNode(graphene_std::vector::generator_nodes::spiral::IDENTIFIER))?; + let parameters = NodeGraphLayer::new(layer, &document.network_interface).find_node_parameters(IDENTIFIER)?; let ( Some(&TaggedValue::SpiralType(spiral_type)), @@ -363,12 +362,12 @@ pub fn extract_spiral_parameters(layer: LayerNodeIdentifier, document: &Document Some(&TaggedValue::F64(turns)), Some(&TaggedValue::F64(angle_resolution)), ) = ( - node_inputs.get(SpiralTypeInput::INDEX)?.as_value(), - node_inputs.get(StartAngleInput::INDEX)?.as_value(), - node_inputs.get(InnerRadiusInput::INDEX)?.as_value(), - node_inputs.get(OuterRadiusInput::INDEX)?.as_value(), - node_inputs.get(TurnsInput::INDEX)?.as_value(), - node_inputs.get(AngularResolutionInput::INDEX)?.as_value(), + parameters.value(SpiralTypeInput), + parameters.value(StartAngleInput), + parameters.value(InnerRadiusInput), + parameters.value(OuterRadiusInput), + parameters.value(TurnsInput), + parameters.value(AngularResolutionInput), ) else { return None; @@ -629,14 +628,14 @@ pub fn calculate_arc_text_transform(angle: f64, offset_angle: f64, center: DVec2 pub fn extract_grid_parameters(layer: LayerNodeIdentifier, document: &DocumentMessageHandler) -> Option<(GridType, DVec2, u32, u32, DVec2)> { use graphene_std::vector::generator_nodes::grid::*; - let node_inputs = NodeGraphLayer::new(layer, &document.network_interface).find_node_inputs(&DefinitionIdentifier::ProtoNode(graphene_std::vector::generator_nodes::grid::IDENTIFIER))?; + let parameters = NodeGraphLayer::new(layer, &document.network_interface).find_node_parameters(IDENTIFIER)?; let (Some(&TaggedValue::GridType(grid_type)), Some(&TaggedValue::DVec2(spacing)), Some(&TaggedValue::U32(columns)), Some(&TaggedValue::U32(rows)), Some(&TaggedValue::DVec2(angles))) = ( - node_inputs.get(GridTypeInput::INDEX)?.as_value(), - node_inputs.get(SpacingInput::::INDEX)?.as_value(), - node_inputs.get(ColumnsInput::INDEX)?.as_value(), - node_inputs.get(RowsInput::INDEX)?.as_value(), - node_inputs.get(AnglesInput::INDEX)?.as_value(), + parameters.value(GridTypeInput), + parameters.value(SpacingInput), + parameters.value(ColumnsInput), + parameters.value(RowsInput), + parameters.value(AnglesInput), ) else { return None; }; diff --git a/editor/src/messages/tool/common_functionality/shapes/spiral_shape.rs b/editor/src/messages/tool/common_functionality/shapes/spiral_shape.rs index ed41c86f02..5216661e71 100644 --- a/editor/src/messages/tool/common_functionality/shapes/spiral_shape.rs +++ b/editor/src/messages/tool/common_functionality/shapes/spiral_shape.rs @@ -14,7 +14,6 @@ use crate::messages::tool::tool_messages::tool_prelude::*; use glam::DAffine2; use graph_craft::document::NodeInput; use graph_craft::document::value::TaggedValue; -use graphene_std::NodeInputDecleration; use graphene_std::subpath::{calculate_growth_factor, spiral_point}; use graphene_std::vector::misc::SpiralType; use std::collections::VecDeque; @@ -140,12 +139,11 @@ impl Spiral { return; }; - let Some(node_inputs) = NodeGraphLayer::new(layer, &document.network_interface).find_node_inputs(&DefinitionIdentifier::ProtoNode(graphene_std::vector::generator_nodes::spiral::IDENTIFIER)) - else { + let Some(parameters) = NodeGraphLayer::new(layer, &document.network_interface).find_node_parameters(graphene_std::vector::generator_nodes::spiral::IDENTIFIER) else { return; }; - let Some(&TaggedValue::SpiralType(spiral_type)) = node_inputs.get(SpiralTypeInput::INDEX).unwrap().as_value() else { + let Some(&TaggedValue::SpiralType(spiral_type)) = parameters.value(SpiralTypeInput) else { return; }; @@ -157,7 +155,7 @@ impl Spiral { responses.add(window_aligned_transform_set(document, layer, viewport_drag_start, DVec2::ONE)); responses.add(NodeGraphMessage::SetInput { - input_connector: InputConnector::node(node_id, OuterRadiusInput::INDEX), + input_connector: InputConnector::node(node_id, OuterRadiusInput), input: NodeInput::value(TaggedValue::F64(new_radius), false), }); } @@ -167,8 +165,7 @@ impl Spiral { pub fn update_turns(decrease: bool, layer: LayerNodeIdentifier, document: &DocumentMessageHandler, responses: &mut VecDeque) { use graphene_std::vector::generator_nodes::spiral::*; - let Some(node_inputs) = NodeGraphLayer::new(layer, &document.network_interface).find_node_inputs(&DefinitionIdentifier::ProtoNode(graphene_std::vector::generator_nodes::spiral::IDENTIFIER)) - else { + let Some(parameters) = NodeGraphLayer::new(layer, &document.network_interface).find_node_parameters(graphene_std::vector::generator_nodes::spiral::IDENTIFIER) else { return; }; @@ -176,7 +173,7 @@ impl Spiral { return; }; - let Some(&TaggedValue::F64(mut turns)) = node_inputs.get(TurnsInput::INDEX).unwrap().as_value() else { + let Some(&TaggedValue::F64(mut turns)) = parameters.value(TurnsInput) else { return; }; @@ -191,7 +188,7 @@ impl Spiral { }); responses.add(NodeGraphMessage::SetInput { - input_connector: InputConnector::node(node_id, TurnsInput::INDEX), + input_connector: InputConnector::node(node_id, TurnsInput), input: NodeInput::value(TaggedValue::F64(turns), false), }); } diff --git a/editor/src/messages/tool/common_functionality/shapes/star_shape.rs b/editor/src/messages/tool/common_functionality/shapes/star_shape.rs index 423c675ebd..049f8b27c8 100644 --- a/editor/src/messages/tool/common_functionality/shapes/star_shape.rs +++ b/editor/src/messages/tool/common_functionality/shapes/star_shape.rs @@ -152,12 +152,12 @@ impl Star { }; responses.add(NodeGraphMessage::SetInput { - input_connector: InputConnector::node(node_id, 2), + input_connector: InputConnector::node(node_id, graphene_std::vector::generator_nodes::star::Radius1Input), input: NodeInput::value(TaggedValue::F64(radius), false), }); responses.add(NodeGraphMessage::SetInput { - input_connector: InputConnector::node(node_id, 3), + input_connector: InputConnector::node(node_id, graphene_std::vector::generator_nodes::star::Radius2Input), input: NodeInput::value(TaggedValue::F64(radius / 2.), false), }); diff --git a/editor/src/messages/tool/common_functionality/stroke_options.rs b/editor/src/messages/tool/common_functionality/stroke_options.rs index cc7572bbad..5069f7627e 100644 --- a/editor/src/messages/tool/common_functionality/stroke_options.rs +++ b/editor/src/messages/tool/common_functionality/stroke_options.rs @@ -3,7 +3,6 @@ use crate::messages::prelude::*; use crate::messages::tool::common_functionality::color_selector::{DrawingToolState, apply_line_weight}; use crate::messages::tool::common_functionality::graph_modification_utils; use graph_craft::document::value::TaggedValue; -use graphene_std::NodeInputDecleration; use graphene_std::choice_type::ChoiceTypeStatic; use graphene_std::vector::style::{PaintOrder, StrokeAlign, StrokeCap, StrokeJoin}; @@ -189,39 +188,35 @@ where pub fn apply_stroke_align(drawing: &mut DrawingToolState, align: StrokeAlign, document: &DocumentMessageHandler, responses: &mut VecDeque) { drawing.stroke_align = Some(align); - set_stroke_input_for_selected(document, graphene_std::vector::stroke::AlignInput::INDEX, TaggedValue::StrokeAlign(align), responses); + graph_modification_utils::set_parameter_for_selected_layers(document, graphene_std::vector::stroke::AlignInput, TaggedValue::StrokeAlign(align), responses); } pub fn apply_stroke_cap(drawing: &mut DrawingToolState, cap: StrokeCap, document: &DocumentMessageHandler, responses: &mut VecDeque) { drawing.stroke_cap = Some(cap); - set_stroke_input_for_selected(document, graphene_std::vector::stroke::CapInput::INDEX, TaggedValue::StrokeCap(cap), responses); + graph_modification_utils::set_parameter_for_selected_layers(document, graphene_std::vector::stroke::CapInput, TaggedValue::StrokeCap(cap), responses); } pub fn apply_stroke_join(drawing: &mut DrawingToolState, join: StrokeJoin, document: &DocumentMessageHandler, responses: &mut VecDeque) { drawing.stroke_join = Some(join); - set_stroke_input_for_selected(document, graphene_std::vector::stroke::JoinInput::INDEX, TaggedValue::StrokeJoin(join), responses); + graph_modification_utils::set_parameter_for_selected_layers(document, graphene_std::vector::stroke::JoinInput, TaggedValue::StrokeJoin(join), responses); } pub fn apply_miter_limit(drawing: &mut DrawingToolState, limit: f64, document: &DocumentMessageHandler, responses: &mut VecDeque) { drawing.miter_limit = Some(limit); - set_stroke_input_for_selected(document, graphene_std::vector::stroke::MiterLimitInput::INDEX, TaggedValue::F64(limit), responses); + graph_modification_utils::set_parameter_for_selected_layers(document, graphene_std::vector::stroke::MiterLimitInput, TaggedValue::F64(limit), responses); } pub fn apply_paint_order(drawing: &mut DrawingToolState, order: PaintOrder, document: &DocumentMessageHandler, responses: &mut VecDeque) { drawing.paint_order = Some(order); - set_stroke_input_for_selected(document, graphene_std::vector::stroke::PaintOrderInput::INDEX, TaggedValue::PaintOrder(order), responses); + graph_modification_utils::set_parameter_for_selected_layers(document, graphene_std::vector::stroke::PaintOrderInput, TaggedValue::PaintOrder(order), responses); } pub fn apply_dash_lengths(drawing: &mut DrawingToolState, lengths: Vec, document: &DocumentMessageHandler, responses: &mut VecDeque) { drawing.dash_lengths = Some(lengths.clone()); - set_stroke_input_for_selected(document, graphene_std::vector::stroke::DashPatternInput::INDEX, TaggedValue::DashPattern(lengths.into()), responses); + graph_modification_utils::set_parameter_for_selected_layers(document, graphene_std::vector::stroke::DashPatternInput, TaggedValue::DashPattern(lengths.into()), responses); } pub fn apply_dash_offset(drawing: &mut DrawingToolState, offset: f64, document: &DocumentMessageHandler, responses: &mut VecDeque) { drawing.dash_offset = Some(offset); - set_stroke_input_for_selected(document, graphene_std::vector::stroke::DashOffsetInput::INDEX, TaggedValue::F64(offset), responses); -} - -fn set_stroke_input_for_selected(document: &DocumentMessageHandler, input_index: usize, value: TaggedValue, responses: &mut VecDeque) { - graph_modification_utils::set_proto_node_input_for_selected_layers(document, graphene_std::vector::stroke::IDENTIFIER, input_index, value, responses); + graph_modification_utils::set_parameter_for_selected_layers(document, graphene_std::vector::stroke::DashOffsetInput, TaggedValue::F64(offset), responses); } diff --git a/editor/src/messages/tool/common_functionality/utility_functions.rs b/editor/src/messages/tool/common_functionality/utility_functions.rs index cbab966061..8deeb60218 100644 --- a/editor/src/messages/tool/common_functionality/utility_functions.rs +++ b/editor/src/messages/tool/common_functionality/utility_functions.rs @@ -569,7 +569,7 @@ pub fn make_path_editable_is_allowed(network_interface: &mut NodeNetworkInterfac // Must be a vector layer, at either rank let node_id = NodeGraphLayer::new(first_layer, network_interface).horizontal_layer_flow().nth(1)?; - let output_type = network_interface.output_type(&OutputConnector::node(node_id, 0), &[]); + let output_type = network_interface.output_type(&OutputConnector::primary_output(node_id), &[]); if output_type.compiled_element_name().as_deref() != Some("Vector") { return None; } diff --git a/editor/src/messages/tool/tool_messages/artboard_tool.rs b/editor/src/messages/tool/tool_messages/artboard_tool.rs index 37cf819c0c..5ba52683c5 100644 --- a/editor/src/messages/tool/tool_messages/artboard_tool.rs +++ b/editor/src/messages/tool/tool_messages/artboard_tool.rs @@ -579,7 +579,10 @@ mod test_artboard { Ok(instrumented) => instrumented, Err(e) => panic!("Failed to evaluate graph: {e}"), }; - instrumented.grab_all_input::>(&editor.runtime).flatten().collect() + instrumented + .grab_all_input::>(&editor.runtime) + .flatten() + .collect() } #[derive(Debug, PartialEq)] diff --git a/editor/src/messages/tool/tool_messages/fill_tool.rs b/editor/src/messages/tool/tool_messages/fill_tool.rs index bbd3e6cd57..118707d44a 100644 --- a/editor/src/messages/tool/tool_messages/fill_tool.rs +++ b/editor/src/messages/tool/tool_messages/fill_tool.rs @@ -205,9 +205,8 @@ impl Fsm for FillToolFsmState { #[cfg(test)] mod test_fill { pub use crate::test_utils::test_prelude::*; - use graphene_std::Graphic; use graphene_std::color::SRGBA8; - use graphene_std::list::{Item, List}; + use graphene_std::list::Item; use graphene_std::vector::fill; // The Fill tool writes solid colors, whose stored values the input monitor records as `Item` wires @@ -217,7 +216,7 @@ mod test_fill { Err(e) => panic!("Failed to evaluate graph: {e}"), }; - instrumented.grab_all_input_as::>, Item>(&editor.runtime).collect() + instrumented.grab_all_input::>(&editor.runtime).collect() } #[tokio::test] diff --git a/editor/src/messages/tool/tool_messages/gradient_tool.rs b/editor/src/messages/tool/tool_messages/gradient_tool.rs index ebcb54a0a6..670f3e3704 100644 --- a/editor/src/messages/tool/tool_messages/gradient_tool.rs +++ b/editor/src/messages/tool/tool_messages/gradient_tool.rs @@ -2013,10 +2013,8 @@ mod test_gradient { use glam::DAffine2; use graph_craft::document::value::TaggedValue; use graphene_std::color::SRGBA8; - use graphene_std::list::List; use graphene_std::vector::style::{GradientSpreadMethod, build_transform_with_y_preservation}; use graphene_std::vector::{Gradient, GradientStop, fill}; - use graphene_std::{Graphic, NodeInputDecleration}; use super::gradient_space_transform; @@ -2058,18 +2056,18 @@ mod test_gradient { let fill_node_id = get_fill_node_id_with_direct_fill_input(layer, &document.network_interface)?; let fill_node = document.network_interface.document_network().nodes.get(&fill_node_id)?; - let stops = match fill_node.inputs.get(fill::FillInput::>::INDEX)?.as_value()? { + let stops = match fill_node.input(fill::FillInput)?.as_value()? { TaggedValue::Gradient(stops) => stops.clone(), _ => return None, }; - let spread_method = match fill_node.inputs.get(fill::SpreadMethodInput::INDEX).and_then(|input| input.as_value()) { + let spread_method = match fill_node.input(fill::SpreadMethodInput).and_then(|input| input.as_value()) { Some(&TaggedValue::GradientSpreadMethod(value)) => value, _ => GradientSpreadMethod::default(), }; - let has_transform = matches!(fill_node.inputs.get(fill::HasTransformInput::INDEX).and_then(|input| input.as_value()), Some(&TaggedValue::Bool(true))); - let local_transform = match fill_node.inputs.get(fill::TransformInput::INDEX).and_then(|input| input.as_value()) { + let has_transform = matches!(fill_node.input(fill::HasTransformInput).and_then(|input| input.as_value()), Some(&TaggedValue::Bool(true))); + let local_transform = match fill_node.input(fill::TransformInput).and_then(|input| input.as_value()) { Some(&TaggedValue::DAffine2(value)) if has_transform => value, _ => DAffine2::IDENTITY, }; @@ -2139,8 +2137,8 @@ mod test_gradient { editor .handle_message(NodeGraphMessage::CreateWire { - output_connector: OutputConnector::node(gradient_node_id, 0), - input_connector: InputConnector::node(layer.to_node(), 1), + output_connector: OutputConnector::primary_output(gradient_node_id), + input_connector: InputConnector::layer_secondary_input(layer.to_node()), }) .await; @@ -2177,8 +2175,8 @@ mod test_gradient { editor .handle_message(NodeGraphMessage::CreateWire { - output_connector: OutputConnector::node(gradient_node_id, 0), - input_connector: InputConnector::node(fill_node_id, fill::FillInput::>::INDEX), + output_connector: OutputConnector::primary_output(gradient_node_id), + input_connector: InputConnector::node(fill_node_id, fill::FillInput), }) .await; @@ -2822,8 +2820,8 @@ mod test_gradient { let gradient_value_id = editor.create_node_by_name(DefinitionIdentifier::ProtoNode(graphene_std::math_nodes::gradient_value::IDENTIFIER)).await; editor .handle_message(NodeGraphMessage::CreateWire { - output_connector: OutputConnector::node(gradient_value_id, 0), - input_connector: InputConnector::node(fill_node_id, 1), + output_connector: OutputConnector::primary_output(gradient_value_id), + input_connector: InputConnector::node(fill_node_id, graphene_std::vector::fill::FillInput), }) .await; editor diff --git a/editor/src/messages/tool/tool_messages/shape_tool.rs b/editor/src/messages/tool/tool_messages/shape_tool.rs index 710b0def8d..7348dcf387 100644 --- a/editor/src/messages/tool/tool_messages/shape_tool.rs +++ b/editor/src/messages/tool/tool_messages/shape_tool.rs @@ -32,7 +32,7 @@ use graph_craft::document::value::TaggedValue; use graphene_std::renderer::Quad; use graphene_std::vector::misc::{ArcType, GridType, SpiralType}; use graphene_std::vector::style::FillChoice; -use graphene_std::{Color, NodeInputDecleration}; +use graphene_std::{Color, ParameterRef}; use std::vec; #[derive(Default, ExtractField)] @@ -365,15 +365,14 @@ fn sync_shape_options_from_selection(options: &mut ShapeToolOptions, tool_data: // The rest (Ellipse, Rectangle, Line) just keep `shape_type` in step and rely on the shared Stroke/Fill controls. match shape_type { ShapeType::Polygon | ShapeType::Star => { - let id = if shape_type == ShapeType::Polygon { regular_polygon::IDENTIFIER } else { star::IDENTIFIER }; // Both `regular_polygon` and `star` are generic over `T: AsU64`, but the control bar widget always writes `u32`, // and existing call sites (e.g. `polygon_shape.rs`) read it back as `TaggedValue::U32`. - let index = if shape_type == ShapeType::Polygon { - regular_polygon::SidesInput::::INDEX + let sides_parameter = if shape_type == ShapeType::Polygon { + ParameterRef::from(regular_polygon::SidesInput) } else { - star::SidesInput::::INDEX + ParameterRef::from(star::SidesInput) }; - if let Some(&TaggedValue::U32(sides)) = layer_view.find_input(&proto(id), index) + if let Some(&TaggedValue::U32(sides)) = layer_view.parameter_value(sides_parameter) && options.vertices != sides { options.vertices = sides; @@ -381,7 +380,7 @@ fn sync_shape_options_from_selection(options: &mut ShapeToolOptions, tool_data: } } ShapeType::Arc => { - if let Some(&TaggedValue::ArcType(arc_type)) = layer_view.find_input(&proto(arc::IDENTIFIER), arc::ArcTypeInput::INDEX) + if let Some(&TaggedValue::ArcType(arc_type)) = layer_view.parameter_value(arc::ArcTypeInput) && options.arc_type != arc_type { options.arc_type = arc_type; @@ -389,13 +388,13 @@ fn sync_shape_options_from_selection(options: &mut ShapeToolOptions, tool_data: } } ShapeType::Spiral => { - if let Some(&TaggedValue::SpiralType(spiral_type)) = layer_view.find_input(&proto(spiral::IDENTIFIER), spiral::SpiralTypeInput::INDEX) + if let Some(&TaggedValue::SpiralType(spiral_type)) = layer_view.parameter_value(spiral::SpiralTypeInput) && options.spiral_type != spiral_type { options.spiral_type = spiral_type; changed = true; } - if let Some(&TaggedValue::F64(turns)) = layer_view.find_input(&proto(spiral::IDENTIFIER), spiral::TurnsInput::INDEX) + if let Some(&TaggedValue::F64(turns)) = layer_view.parameter_value(spiral::TurnsInput) && options.turns != turns { options.turns = turns; @@ -403,7 +402,7 @@ fn sync_shape_options_from_selection(options: &mut ShapeToolOptions, tool_data: } } ShapeType::Grid => { - if let Some(&TaggedValue::GridType(grid_type)) = layer_view.find_input(&proto(grid::IDENTIFIER), grid::GridTypeInput::INDEX) + if let Some(&TaggedValue::GridType(grid_type)) = layer_view.parameter_value(grid::GridTypeInput) && options.grid_type != grid_type { options.grid_type = grid_type; @@ -411,19 +410,19 @@ fn sync_shape_options_from_selection(options: &mut ShapeToolOptions, tool_data: } } ShapeType::Arrow => { - if let Some(&TaggedValue::F64(shaft)) = layer_view.find_input(&proto(arrow::IDENTIFIER), arrow::ShaftWidthInput::INDEX) + if let Some(&TaggedValue::F64(shaft)) = layer_view.parameter_value(arrow::ShaftWidthInput) && options.arrow_shaft_width != shaft { options.arrow_shaft_width = shaft; changed = true; } - if let Some(&TaggedValue::F64(head_w)) = layer_view.find_input(&proto(arrow::IDENTIFIER), arrow::HeadWidthInput::INDEX) + if let Some(&TaggedValue::F64(head_w)) = layer_view.parameter_value(arrow::HeadWidthInput) && options.arrow_head_width != head_w { options.arrow_head_width = head_w; changed = true; } - if let Some(&TaggedValue::F64(head_l)) = layer_view.find_input(&proto(arrow::IDENTIFIER), arrow::HeadLengthInput::INDEX) + if let Some(&TaggedValue::F64(head_l)) = layer_view.parameter_value(arrow::HeadLengthInput) && options.arrow_head_length != head_l { options.arrow_head_length = head_l; @@ -640,47 +639,41 @@ impl<'a> MessageHandler> for Shap ShapeOptionsUpdate::Vertices(vertices) => { self.options.vertices = vertices; // Push to whichever sides-bearing shape (Polygon or Star) the control bar's `shape_type` currently targets. - // `set_proto_node_input_for_selected_layers` skips selected layers without that proto node, making it a no-op. - let (id, index) = match self.options.shape_type { - ShapeType::Polygon => (regular_polygon::IDENTIFIER, regular_polygon::SidesInput::::INDEX), - ShapeType::Star => (star::IDENTIFIER, star::SidesInput::::INDEX), + // `set_parameter_for_selected_layers` skips selected layers without that proto node, making it a no-op. + let sides_parameter = match self.options.shape_type { + ShapeType::Polygon => ParameterRef::from(regular_polygon::SidesInput), + ShapeType::Star => ParameterRef::from(star::SidesInput), _ => return, }; - graph_modification_utils::set_proto_node_input_for_selected_layers(context.document, id, index, TaggedValue::U32(vertices), responses); + graph_modification_utils::set_parameter_for_selected_layers(context.document, sides_parameter, TaggedValue::U32(vertices), responses); } ShapeOptionsUpdate::ArcType(arc_type) => { self.options.arc_type = arc_type; - graph_modification_utils::set_proto_node_input_for_selected_layers(context.document, arc::IDENTIFIER, arc::ArcTypeInput::INDEX, TaggedValue::ArcType(arc_type), responses); + graph_modification_utils::set_parameter_for_selected_layers(context.document, arc::ArcTypeInput, TaggedValue::ArcType(arc_type), responses); } ShapeOptionsUpdate::SpiralType(spiral_type) => { self.options.spiral_type = spiral_type; - graph_modification_utils::set_proto_node_input_for_selected_layers( - context.document, - spiral::IDENTIFIER, - spiral::SpiralTypeInput::INDEX, - TaggedValue::SpiralType(spiral_type), - responses, - ); + graph_modification_utils::set_parameter_for_selected_layers(context.document, spiral::SpiralTypeInput, TaggedValue::SpiralType(spiral_type), responses); } ShapeOptionsUpdate::Turns(turns) => { self.options.turns = turns; - graph_modification_utils::set_proto_node_input_for_selected_layers(context.document, spiral::IDENTIFIER, spiral::TurnsInput::INDEX, TaggedValue::F64(turns), responses); + graph_modification_utils::set_parameter_for_selected_layers(context.document, spiral::TurnsInput, TaggedValue::F64(turns), responses); } ShapeOptionsUpdate::GridType(grid_type) => { self.options.grid_type = grid_type; - graph_modification_utils::set_proto_node_input_for_selected_layers(context.document, grid::IDENTIFIER, grid::GridTypeInput::INDEX, TaggedValue::GridType(grid_type), responses); + graph_modification_utils::set_parameter_for_selected_layers(context.document, grid::GridTypeInput, TaggedValue::GridType(grid_type), responses); } ShapeOptionsUpdate::ArrowShaftWidth(shaft_width) => { self.options.arrow_shaft_width = shaft_width; - graph_modification_utils::set_proto_node_input_for_selected_layers(context.document, arrow::IDENTIFIER, arrow::ShaftWidthInput::INDEX, TaggedValue::F64(shaft_width), responses); + graph_modification_utils::set_parameter_for_selected_layers(context.document, arrow::ShaftWidthInput, TaggedValue::F64(shaft_width), responses); } ShapeOptionsUpdate::ArrowHeadWidth(head_width) => { self.options.arrow_head_width = head_width; - graph_modification_utils::set_proto_node_input_for_selected_layers(context.document, arrow::IDENTIFIER, arrow::HeadWidthInput::INDEX, TaggedValue::F64(head_width), responses); + graph_modification_utils::set_parameter_for_selected_layers(context.document, arrow::HeadWidthInput, TaggedValue::F64(head_width), responses); } ShapeOptionsUpdate::ArrowHeadLength(head_length) => { self.options.arrow_head_length = head_length; - graph_modification_utils::set_proto_node_input_for_selected_layers(context.document, arrow::IDENTIFIER, arrow::HeadLengthInput::INDEX, TaggedValue::F64(head_length), responses); + graph_modification_utils::set_parameter_for_selected_layers(context.document, arrow::HeadLengthInput, TaggedValue::F64(head_length), responses); } } diff --git a/editor/src/messages/tool/tool_messages/text_tool.rs b/editor/src/messages/tool/tool_messages/text_tool.rs index 7642ae9442..5af744ed6f 100644 --- a/editor/src/messages/tool/tool_messages/text_tool.rs +++ b/editor/src/messages/tool/tool_messages/text_tool.rs @@ -25,7 +25,7 @@ use graphene_std::color::SRGBA8; use graphene_std::renderer::Quad; use graphene_std::text::{Font, TextAlign, TypesettingConfig, lines_clipping}; use graphene_std::vector::style::{FillChoice, FillChoiceUI}; -use graphene_std::{Color, NodeInputDecleration}; +use graphene_std::{Color, NodeParameter}; #[derive(Default, ExtractField)] pub struct TextTool { @@ -546,7 +546,10 @@ impl TextToolData { responses.add(NodeGraphMessage::SelectedNodesSet { nodes: vec![self.layer.to_node()] }); // Make the rendered text invisible while editing responses.add(NodeGraphMessage::SetInput { - input_connector: InputConnector::node(graph_modification_utils::get_text_id(self.layer, &document.network_interface).unwrap(), 1), + input_connector: InputConnector::node( + graph_modification_utils::get_text_id(self.layer, &document.network_interface).unwrap(), + graphene_std::text::text::TextInput, + ), input: NodeInput::value(TaggedValue::String("".to_string()), false), }); responses.add(NodeGraphMessage::RunDocumentGraph); @@ -883,19 +886,19 @@ impl Fsm for TextToolFsmState { // TODO: Don't set both max_width and max_height to true at the same time, only do one based on which edge is being dragged (or both if a corner is being dragged) responses.add(NodeGraphMessage::SetInput { - input_connector: InputConnector::node(node_id, graphene_std::text::text::HasMaxWidthInput::INDEX), + input_connector: InputConnector::node(node_id, graphene_std::text::text::HasMaxWidthInput), input: NodeInput::value(TaggedValue::Bool(true), false), }); responses.add(NodeGraphMessage::SetInput { - input_connector: InputConnector::node(node_id, graphene_std::text::text::MaxWidthInput::INDEX), + input_connector: InputConnector::node(node_id, graphene_std::text::text::MaxWidthInput), input: NodeInput::value(TaggedValue::F64(size_layer.x), false), }); responses.add(NodeGraphMessage::SetInput { - input_connector: InputConnector::node(node_id, graphene_std::text::text::HasMaxHeightInput::INDEX), + input_connector: InputConnector::node(node_id, graphene_std::text::text::HasMaxHeightInput), input: NodeInput::value(TaggedValue::Bool(true), false), }); responses.add(NodeGraphMessage::SetInput { - input_connector: InputConnector::node(node_id, graphene_std::text::text::MaxHeightInput::INDEX), + input_connector: InputConnector::node(node_id, graphene_std::text::text::MaxHeightInput), input: NodeInput::value(TaggedValue::F64(size_layer.y), false), }); responses.add(GraphOperationMessage::TransformSet { @@ -1026,7 +1029,10 @@ impl Fsm for TextToolFsmState { tool_data.set_editing(false, fonts, responses); responses.add(NodeGraphMessage::SetInput { - input_connector: InputConnector::node(graph_modification_utils::get_text_id(tool_data.layer, &document.network_interface).unwrap(), 1), + input_connector: InputConnector::node( + graph_modification_utils::get_text_id(tool_data.layer, &document.network_interface).unwrap(), + graphene_std::text::text::TextInput, + ), input: NodeInput::value(TaggedValue::String(tool_data.new_text.clone()), false), }); responses.add(NodeGraphMessage::RunDocumentGraph); diff --git a/editor/src/node_graph_executor.rs b/editor/src/node_graph_executor.rs index d7f341d1f6..6e9d835460 100644 --- a/editor/src/node_graph_executor.rs +++ b/editor/src/node_graph_executor.rs @@ -15,7 +15,7 @@ use graphene_std::raster::{CPU, Raster}; use graphene_std::renderer::{RenderMetadata, graphic_list_bounding_box}; use graphene_std::transform::Footprint; use graphene_std::vector::{Vector, graphic_types}; -use graphene_std::{ATTR_TRANSFORM, Context, Graphic, NodeInputDecleration}; +use graphene_std::{ATTR_TRANSFORM, Context, Graphic}; use interpreted_executor::dynamic_executor::ResolvedDocumentNodeTypesDelta; use std::any::Any; use std::sync::Arc; @@ -608,8 +608,8 @@ impl NodeGraphExecutor { if fill_transform_unbaked(document, &network_path, fill_node_id) { let absolute_gradient = gradient.to_absolute(bounding_box, item_transform); let gradient_transform = absolute_gradient.transform * absolute_gradient.to_transform(); - let has_transform_input = InputConnector::node(fill_node_id, graphene_std::vector::fill::HasTransformInput::INDEX); - let transform_input = InputConnector::node(fill_node_id, graphene_std::vector::fill::TransformInput::INDEX); + let has_transform_input = InputConnector::node(fill_node_id, graphene_std::vector::fill::HasTransformInput); + let transform_input = InputConnector::node(fill_node_id, graphene_std::vector::fill::TransformInput); document .network_interface .set_input(&has_transform_input, NodeInput::value(TaggedValue::Bool(true), false), &network_path); @@ -827,7 +827,7 @@ fn fill_transform_unbaked(document: &DocumentMessageHandler, network_path: &[Nod }; let Some(node) = network.nodes.get(&fill_node_id) else { return false }; matches!( - node.inputs.get(graphene_std::vector::fill::HasTransformInput::INDEX).and_then(|input| input.as_value()), + node.input(graphene_std::vector::fill::HasTransformInput).and_then(|input| input.as_value()), Some(TaggedValue::Bool(false)) ) } @@ -914,19 +914,11 @@ mod test { use graph_craft::ProtoNodeIdentifier; use graph_craft::document::NodeNetwork; use graphene_std::Context; - use graphene_std::NodeInputDecleration; + use graphene_std::NodeParameter; use graphene_std::list::Item; use graphene_std::memo::IORecord; use test_prelude::LayerNodeIdentifier; - /// A ranked input whose `Item` Result carries an element `E`, recovered by `grab_ranked_input`. - pub trait RankedResult { - type Element: Send + Sync + Clone + 'static; - } - impl RankedResult for Item { - type Element = E; - } - /// Stores all of the monitor nodes that have been attached to a graph #[derive(Default)] pub struct Instrumented { @@ -987,16 +979,6 @@ mod test { instrumented } - fn downcast(dynamic: Arc) -> Option - where - Input::Result: Send + Sync + Clone + 'static, - { - Self::downcast_record::(dynamic).or_else(|| { - warn!("cannot downcast type for introspection"); - None - }) - } - /// Pulls a concrete output type out of a monitor record, tolerating the three context shapes the executor records against. fn downcast_record(dynamic: Arc) -> Option { if let Some(x) = dynamic.downcast_ref::>() { @@ -1010,25 +992,11 @@ mod test { } } - /// Grab all of the values of the input every time it occurs in the graph. - pub fn grab_all_input<'a, Input: NodeInputDecleration + 'a>(&'a self, runtime: &'a NodeRuntime) -> impl Iterator + 'a - where - Input::Result: Send + Sync + Clone + 'static, - { + /// Grab all of the values of the input every time it occurs in the graph, downcast to the recorded `Output` type. + /// A record whose type does not match `Output` is skipped, so a wrong `Output` yields an empty iterator rather than an error. + pub fn grab_all_input<'a, Input: NodeParameter + 'a, Output: Send + Sync + Clone + 'static>(&'a self, runtime: &'a NodeRuntime) -> impl Iterator + 'a { self.protonodes_by_name - .get(&Input::identifier()) - .map_or([].as_slice(), |x| x.as_slice()) - .iter() - .filter_map(|inputs| inputs.get(Input::INDEX)) - .filter_map(|input_monitor_node| runtime.executor.introspect(input_monitor_node).ok()) - .filter_map(Instrumented::downcast::) // Some might not resolve (e.g. generics that don't work properly) - } - - /// Like [`Self::grab_all_input`], but downcasting the recorded values to `Output` instead of the marker's `Result`. - /// Useful when a stored value's wire form (e.g. `Item`) differs from the declared row types the marker's generic accepts. - pub fn grab_all_input_as<'a, Input: NodeInputDecleration + 'a, Output: Send + Sync + Clone + 'static>(&'a self, runtime: &'a NodeRuntime) -> impl Iterator + 'a { - self.protonodes_by_name - .get(&Input::identifier()) + .get(&Input::NODE_IDENTIFIER) .map_or([].as_slice(), |x| x.as_slice()) .iter() .filter_map(|inputs| inputs.get(Input::INDEX)) @@ -1036,35 +1004,31 @@ mod test { .filter_map(Instrumented::downcast_record::) } - pub fn grab_protonode_input(&self, path: &Vec, runtime: &NodeRuntime) -> Option - where - Input::Result: Send + Sync + Clone + 'static, - { + pub fn grab_protonode_input(&self, path: &Vec, runtime: &NodeRuntime) -> Option { let input_monitor_node = self.protonodes_by_path.get(path)?.get(Input::INDEX)?; let dynamic = runtime.executor.introspect(input_monitor_node).ok()?; - Self::downcast::(dynamic) + Self::downcast_record::(dynamic) } - /// Grabs a ranked (`Item`) input's recorded value as its bare element `E`. - /// A stored value materializes as an `Item` wire, so the monitor records the whole cell and this unwraps its element. - pub fn grab_ranked_input(&self, path: &Vec, runtime: &NodeRuntime) -> Option<::Element> - where - Input::Result: RankedResult, - { + /// Grabs a ranked input's recorded value as its bare element `Element`. + /// A stored value materializes as an `Item` wire, so the monitor records the whole cell and this unwraps its element. + pub fn grab_ranked_input(&self, path: &Vec, runtime: &NodeRuntime) -> Option { let input_monitor_node = self.protonodes_by_path.get(path)?.get(Input::INDEX)?; let dynamic = runtime.executor.introspect(input_monitor_node).ok()?; - Self::downcast_record::::Element>>(dynamic).map(|item| item.into_element()) + Self::downcast_record::>(dynamic).map(|item| item.into_element()) } - pub fn grab_input_from_layer(&self, layer: LayerNodeIdentifier, network_interface: &NodeNetworkInterface, runtime: &NodeRuntime) -> Option - where - Input::Result: Send + Sync + Clone + 'static, - { + pub fn grab_input_from_layer( + &self, + layer: LayerNodeIdentifier, + network_interface: &NodeNetworkInterface, + runtime: &NodeRuntime, + ) -> Option { let node_graph_layer = NodeGraphLayer::new(layer, network_interface); - let node = node_graph_layer.upstream_node_id_from_protonode(Input::identifier())?; - self.grab_protonode_input::(&vec![node], runtime) + let node = node_graph_layer.upstream_node_id_from_protonode(Input::NODE_IDENTIFIER)?; + self.grab_protonode_input::(&vec![node], runtime) } } } diff --git a/editor/src/test_utils.rs b/editor/src/test_utils.rs index 946276c0e0..844550ee73 100644 --- a/editor/src/test_utils.rs +++ b/editor/src/test_utils.rs @@ -9,8 +9,6 @@ use crate::node_graph_executor::Instrumented; use crate::node_graph_executor::NodeRuntime; use crate::test_utils::test_prelude::LayerNodeIdentifier; use glam::{DVec2, UVec2}; -use graph_craft::document::DocumentNode; -use graphene_std::InputAccessor; use graphene_std::raster::color::Color; use graphene_std::uuid::NodeId; @@ -174,15 +172,6 @@ impl EditorTestUtils { self.editor.dispatcher.message_handlers.portfolio_message_handler.active_document_mut().unwrap() } - pub fn get_node<'a, T: InputAccessor<'a, DocumentNode>>(&'a self) -> impl Iterator + 'a { - self.active_document() - .network_interface - .document_network() - .recursive_nodes() - .inspect(|(_, node, _)| println!("{:#?}", node.implementation)) - .filter_map(move |(_, document, _)| T::new_with_source(document)) - } - pub async fn move_mouse(&mut self, x: f64, y: f64, modifier_keys: ModifierKeys, mouse_keys: MouseKeys) { let editor_mouse_state = EditorMouseState { editor_position: ViewportPosition::new(x, y), @@ -377,7 +366,6 @@ pub mod test_prelude { pub use graph_craft::document::DocumentNode; pub use graphene_std::raster::{Color, Image}; pub use graphene_std::transform::Footprint; - pub use graphene_std::{InputAccessor, InputAccessorSource}; #[macro_export] macro_rules! float_eq { diff --git a/node-graph/graph-craft/src/document.rs b/node-graph/graph-craft/src/document.rs index 1650910306..8e237af341 100644 --- a/node-graph/graph-craft/src/document.rs +++ b/node-graph/graph-craft/src/document.rs @@ -5,7 +5,7 @@ use crate::proto::{ConstructionArgs, ProtoNetwork, ProtoNode}; use core_types::memo::MemoHashGuard; pub use core_types::uuid::NodeId; pub use core_types::uuid::generate_uuid; -use core_types::{Context, ContextDependencies, Cow, MemoHash, ProtoNodeIdentifier, Type}; +use core_types::{Context, ContextDependencies, Cow, MemoHash, NodeParameter, ProtoNodeIdentifier, Type}; use dyn_any::DynAny; use glam::IVec2; use log::Metadata; @@ -121,6 +121,21 @@ impl OriginalLocation { } } impl DocumentNode { + /// The input slot named by the given parameter symbol, e.g. `node.input(stroke::WeightInput)`. + pub fn input(&self, _parameter: P) -> Option<&NodeInput> { + self.inputs.get(P::INDEX) + } + + /// Mutable access to the input slot named by the given parameter symbol. + pub fn input_mut(&mut self, _parameter: P) -> Option<&mut NodeInput> { + self.inputs.get_mut(P::INDEX) + } + + /// The stored value of the given parameter, if that input currently holds a value rather than a wire. + pub fn input_value(&self, parameter: P) -> Option<&TaggedValue> { + self.input(parameter)?.as_value() + } + /// Normalizes this node's stored types (call argument, `Import` input types, `TypeDefault` value payloads, and any nested network) to their structural form. /// Applied once at ingestion (document migration and clipboard paste) so no name-encoded ranked type enters a live document. pub fn normalize_stored_types(&mut self) { diff --git a/node-graph/libraries/core-types/src/lib.rs b/node-graph/libraries/core-types/src/lib.rs index 878e7f5360..a596f4736d 100644 --- a/node-graph/libraries/core-types/src/lib.rs +++ b/node-graph/libraries/core-types/src/lib.rs @@ -141,24 +141,29 @@ impl<'i, I, O: 'i> Node<'i, I> for Pin<&'i (dyn NodeIO<'i, I, Output = O> + 'i)> } } -pub trait InputAccessorSource<'a, T>: InputAccessorSourceIdentifier + std::fmt::Debug { - fn get_input(&'a self, index: usize) -> Option<&'a T>; - fn set_input(&'a mut self, index: usize, value: T); -} - -pub trait InputAccessorSourceIdentifier { - fn has_identifier(&self, identifier: &str) -> bool; -} - -pub trait InputAccessor<'n, Source: 'n> -where - Self: Sized, -{ - fn new_with_source(source: &'n Source) -> Option; -} - -pub trait NodeInputDecleration { +/// A compile-time symbol naming one parameter of one proto node. +/// The node macro generates a unit struct implementing this for every parameter, so code can pass the type itself (e.g. `stroke::WeightInput`) instead of a raw input index. +pub trait NodeParameter { + /// The proto node this parameter belongs to. + const NODE_IDENTIFIER: ProtoNodeIdentifier; + /// Position of this parameter among the node's inputs. + /// Prefer passing the symbol to an API that accepts it; reach for this only at genuinely index-based boundaries. const INDEX: usize; - fn identifier() -> ProtoNodeIdentifier; - type Result; +} + +/// A runtime reference to one parameter of one proto node, for heterogeneous tables and runtime-chosen parameters. +/// Convert a symbol with `.into()`; unlike a raw index, the node identifier and index always stay paired. +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct ParameterRef { + pub node_identifier: ProtoNodeIdentifier, + pub input_index: usize, +} + +impl From

for ParameterRef { + fn from(_: P) -> Self { + ParameterRef { + node_identifier: P::NODE_IDENTIFIER, + input_index: P::INDEX, + } + } } diff --git a/node-graph/node-macro/src/codegen.rs b/node-graph/node-macro/src/codegen.rs index 8d31963295..88f28e08eb 100644 --- a/node-graph/node-macro/src/codegen.rs +++ b/node-graph/node-macro/src/codegen.rs @@ -658,7 +658,7 @@ pub(crate) fn generate_node_code(crate_ident: &CrateIdent, parsed: &ParsedNodeFn let inject_scope_flag = attributes.inject_scope; let cfg = crate::shader_nodes::modify_cfg(attributes); - let node_input_accessor = generate_node_input_references(parsed, fn_generics, &field_idents, core_types, &identifier, &cfg); + let node_input_accessor = generate_node_input_references(parsed, &field_idents, core_types, &identifier, &cfg); let ShaderTokens { shader_entry_point, gpu_node } = attributes.shader_node.as_ref().map(|n| n.codegen(crate_ident, parsed)).unwrap_or(Ok(ShaderTokens::default()))?; let mapped_node_impl = match (&mapped_struct_where_clause, &mapped_eval_impl) { @@ -881,61 +881,24 @@ pub(crate) fn generate_node_code(crate_ident: &CrateIdent, parsed: &ParsedNodeFn }) } -/// Generates strongly typed utilites to access inputs -fn generate_node_input_references( - parsed: &ParsedNodeFn, - fn_generics: &[crate::GenericParam], - field_idents: &[&PatIdent], - core_types: &TokenStream2, - identifier: &Ident, - cfg: &TokenStream2, -) -> TokenStream2 { +/// Generates the per-parameter symbol types used to reference this node's inputs. +fn generate_node_input_references(parsed: &ParsedNodeFn, field_idents: &[&PatIdent], core_types: &TokenStream2, identifier: &Ident, cfg: &TokenStream2) -> TokenStream2 { let inputs_module_name = format_ident!("{}", parsed.struct_name.to_string().to_case(Case::Snake)); let mut generated_input_accessor = Vec::new(); if !parsed.attributes.skip_impl { - let (mut modified, mut generic_collector) = FilterUsedGenerics::new(fn_generics); - - for (input_index, (parsed_input, input_ident)) in parsed.fields.iter().zip(field_idents).enumerate() { - let mut ty = match &parsed_input.ty { - ParsedFieldType::Node(NodeParsedField { output_type, .. }) => output_type.clone(), - value => value.regular().expect("a non-node field is a value field").ty.clone(), - }; - - // The element-wise primary input's document wire carries the mapped List form - if Some(input_index) == parsed.primary_input_field().map(|(primary_index, _)| primary_index) - && let Some(element_ty) = primary_item_element(parsed) - { - ty = parse_quote!(#core_types::list::List<#element_ty>); - } - - // We only want the necessary generics. - let used = generic_collector.filter_unnecessary_generics(&mut modified, &mut ty); + for (input_index, input_ident) in field_idents.iter().enumerate() { // TODO: figure out a better name that doesn't conflict with so many types let struct_name = format_ident!("{}Input", input_ident.ident.to_string().to_case(Case::Pascal)); - let (fn_generic_params, phantom_data_declerations) = generate_phantom_data(used.iter()); - // Only create structs with phantom data where necessary. - generated_input_accessor.push(if phantom_data_declerations.is_empty() { - quote! { - pub struct #struct_name; - } - } else { - quote! { - pub struct #struct_name <#(#used),*>{ - #(#phantom_data_declerations,)* - } + // Every parameter gets a plain unit struct: the symbol used across the codebase to name this input + generated_input_accessor.push(quote! { + pub struct #struct_name; + impl #core_types::NodeParameter for #struct_name { + const NODE_IDENTIFIER: #core_types::ProtoNodeIdentifier = #inputs_module_name::IDENTIFIER; + const INDEX: usize = #input_index; } }); - generated_input_accessor.push(quote! { - impl <#(#used),*> #core_types::NodeInputDecleration for #struct_name <#(#fn_generic_params),*> { - const INDEX: usize = #input_index; - fn identifier() -> #core_types::ProtoNodeIdentifier { - #inputs_module_name::IDENTIFIER.clone() - } - type Result = #ty; - } - }) } } @@ -951,33 +914,6 @@ fn generate_node_input_references( } } -/// It is necessary to generate PhantomData for each fn generic to avoid compiler errors. -fn generate_phantom_data<'a>(fn_generics: impl Iterator) -> (Vec, Vec) { - let mut phantom_data_declerations = Vec::new(); - let mut fn_generic_params = Vec::new(); - - for fn_generic_param in fn_generics { - let field_name = format_ident!("phantom_{}", phantom_data_declerations.len()); - - match fn_generic_param { - crate::GenericParam::Lifetime(lifetime_param) => { - let lifetime = &lifetime_param.lifetime; - - fn_generic_params.push(quote! {#lifetime}); - phantom_data_declerations.push(quote! {#field_name: core::marker::PhantomData<&#lifetime ()>}) - } - crate::GenericParam::Type(type_param) => { - let generic_name = &type_param.ident; - - fn_generic_params.push(quote! {#generic_name}); - phantom_data_declerations.push(quote! {#field_name: core::marker::PhantomData<#generic_name>}); - } - _ => {} - } - } - (fn_generic_params, phantom_data_declerations) -} - /// The wire container a generated node variant is registered with, wrapping the kernel's primary input element type. #[derive(Clone, Copy, PartialEq)] enum WireWrapper { @@ -1297,86 +1233,6 @@ fn substitute_lifetimes(mut ty: Type, lifetime: &'static str) -> Type { ty } -/// Get only the necessary generics. -struct FilterUsedGenerics { - all: Vec, - used: Vec, -} - -impl VisitMut for FilterUsedGenerics { - fn visit_lifetime_mut(&mut self, used_lifetime: &mut Lifetime) { - for (generic, used) in self.all.iter().zip(self.used.iter_mut()) { - let crate::GenericParam::Lifetime(lifetime_param) = generic else { continue }; - if used_lifetime == &lifetime_param.lifetime { - *used = true; - } - } - } - - fn visit_path_mut(&mut self, path: &mut syn::Path) { - for (index, (generic, used)) in self.all.iter().zip(self.used.iter_mut()).enumerate() { - let crate::GenericParam::Type(type_param) = generic else { continue }; - if path.leading_colon.is_none() && !path.segments.is_empty() && path.segments[0].arguments.is_none() && path.segments[0].ident == type_param.ident { - *used = true; - // Sometimes the generics conflict with the type name so we rename the generics. - path.segments[0].ident = format_ident!("G{index}"); - } - } - for mut el in Punctuated::pairs_mut(&mut path.segments) { - self.visit_path_segment_mut(el.value_mut()); - } - } -} - -impl FilterUsedGenerics { - fn new(fn_generics: &[crate::GenericParam]) -> (Vec, Self) { - let mut all_possible_generics = fn_generics.to_vec(); - // The 'n lifetime may also be needed; we must add it in - all_possible_generics.insert(0, syn::GenericParam::Lifetime(syn::LifetimeParam::new(Lifetime::new("'n", proc_macro2::Span::call_site())))); - - let modified = all_possible_generics - .iter() - .cloned() - .enumerate() - .map(|(index, mut generic)| { - let crate::GenericParam::Type(type_param) = &mut generic else { return generic }; - // Sometimes the generics conflict with the type name so we rename the generics. - type_param.ident = format_ident!("G{index}"); - generic - }) - .collect::>(); - - let generic_collector = Self { - used: vec![false; all_possible_generics.len()], - all: all_possible_generics, - }; - - (modified, generic_collector) - } - - fn used<'a>(&'a self, modified: &'a [crate::GenericParam]) -> impl Iterator { - modified.iter().zip(&self.used).filter(|(_, used)| **used).map(move |(value, _)| value) - } - - fn filter_unnecessary_generics(&mut self, modified: &mut Vec, ty: &mut Type) -> Vec { - self.used.fill(false); - - // Find out which generics are necessary to support the node input - self.visit_type_mut(ty); - - // Sometimes generics may reference other generics. This is a non-optimal way of dealing with that. - for _ in 0..=self.all.len() { - for (index, item) in modified.iter_mut().enumerate() { - if self.used[index] { - self.visit_generic_param_mut(item); - } - } - } - - self.used(&*modified).cloned().collect() - } -} - /// Check if a type contains a reference to a specific identifier (e.g., a generic type parameter) fn type_contains_ident(ty: &Type, ident: &Ident) -> bool { struct IdentChecker<'a> { diff --git a/node-graph/node-macro/src/lib.rs b/node-graph/node-macro/src/lib.rs index 35fe604a01..5752122136 100644 --- a/node-graph/node-macro/src/lib.rs +++ b/node-graph/node-macro/src/lib.rs @@ -1,7 +1,6 @@ use crate::crate_ident::CrateIdent; use proc_macro::TokenStream; use proc_macro_error2::proc_macro_error; -use syn::GenericParam; mod buffer_struct; mod codegen;