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
This commit is contained in:
Keavon Chambers
2026-09-14 12:58:14 +02:00
committed by Dennis Kobert
parent e39782c957
commit 69148d1b36
49 changed files with 1248 additions and 1131 deletions
@@ -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())
});
@@ -4335,13 +4340,13 @@ mod document_message_handler_tests {
// A base that wrongly carried a phantom element would therefore show up as a recorded row, which this catches.
// The `news` guard below is what keeps both assertions honest, since a wrong `Output` type empties every record.
let base_lengths: Vec<usize> = instrumented
.grab_all_input_as::<graphene_std::list::extend::BaseInput<graphene_std::Graphic>, graphene_std::list::List<graphene_std::Graphic>>(&editor.runtime)
.grab_all_input::<graphene_std::list::extend::BaseInput, graphene_std::list::List<graphene_std::Graphic>>(&editor.runtime)
.map(|base| base.len())
.collect();
assert!(base_lengths.iter().all(|&len| len == 0), "Every stack base should be empty, found lengths {base_lengths:?}");
let news: Vec<graphene_std::list::List<graphene_std::Graphic>> = instrumented
.grab_all_input_as::<graphene_std::list::extend::NewInput<graphene_std::Graphic>, graphene_std::list::List<graphene_std::Graphic>>(&editor.runtime)
.grab_all_input::<graphene_std::list::extend::NewInput, graphene_std::list::List<graphene_std::Graphic>>(&editor.runtime)
.collect();
assert!(!news.is_empty(), "Instrumentation should have recorded at least one stacked element list");
let phantom_count = news
@@ -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;
@@ -132,7 +133,7 @@ impl MessageHandler<GraphOperationMessage, GraphOperationMessageContext<'_>> 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;
};
@@ -181,15 +182,15 @@ impl MessageHandler<GraphOperationMessage, GraphOperationMessageContext<'_>> for
// Set the bottom input of the artboard back to artboard
let bottom_input = NodeInput::type_default(descriptor!(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(descriptor!(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] });
@@ -212,11 +213,14 @@ impl MessageHandler<GraphOperationMessage, GraphOperationMessageContext<'_>> 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)
@@ -226,9 +230,9 @@ impl MessageHandler<GraphOperationMessage, GraphOperationMessageContext<'_>> 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,
@@ -246,29 +250,29 @@ impl MessageHandler<GraphOperationMessage, GraphOperationMessageContext<'_>> 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(&current_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), &[]);
@@ -299,7 +303,7 @@ impl MessageHandler<GraphOperationMessage, GraphOperationMessageContext<'_>> 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),
});
}
@@ -368,7 +372,7 @@ impl MessageHandler<GraphOperationMessage, GraphOperationMessageContext<'_>> 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,
@@ -394,7 +398,7 @@ impl MessageHandler<GraphOperationMessage, GraphOperationMessageContext<'_>> 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),
});
@@ -402,7 +406,7 @@ impl MessageHandler<GraphOperationMessage, GraphOperationMessageContext<'_>> 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 });
@@ -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.
@@ -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::*;
@@ -17,7 +19,7 @@ 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 +372,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_nested_type() == Some(&concrete!(List<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 +393,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 +408,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 +425,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 +457,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 +467,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 +489,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 +515,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 +548,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 +626,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 +642,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 +651,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 +660,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 +777,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 +800,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
@@ -53,6 +53,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.
@@ -1050,7 +1057,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,
))])
}),
@@ -1091,7 +1098,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,
))])
@@ -1104,7 +1111,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,
))])
@@ -1151,7 +1158,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,
@@ -1165,7 +1172,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()])
@@ -1174,7 +1181,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::<NoiseType>().for_socket(ParameterWidgetsInfo::new(node_id, index, true, context)).property_row();
let noise_type_row = enum_choice::<NoiseType>().for_socket(ParameterWidgetsInfo::at_index(node_id, index, true, context)).property_row();
Ok(vec![noise_type_row, LayoutGroup::row(Vec::new())])
}),
);
@@ -1183,7 +1190,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::<DomainWarpType>()
.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])
@@ -1194,7 +1201,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())])
@@ -1205,7 +1212,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::<FractalType>()
.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])
@@ -1216,7 +1223,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.)
@@ -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_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.)
@@ -1248,7 +1255,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.)
@@ -1263,7 +1270,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.)
@@ -1278,7 +1285,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.)
@@ -1293,7 +1300,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::<CellularDistanceFunction>()
.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])
@@ -1304,7 +1311,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::<CellularReturnType>()
.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])
@@ -1315,7 +1322,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.))
@@ -1328,7 +1335,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])
}),
);
@@ -1337,7 +1344,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()])
@@ -1348,7 +1355,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()])
@@ -1357,7 +1364,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 {
@@ -1371,7 +1378,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,
@@ -1389,7 +1396,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",
@@ -1401,13 +1408,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 {
@@ -1421,7 +1437,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,
@@ -1434,7 +1450,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,
@@ -1449,7 +1465,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(),
@@ -1458,7 +1474,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));
@@ -1470,7 +1486,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),
)])
}),
@@ -1478,7 +1494,9 @@ fn static_input_properties() -> InputProperties {
map.insert(
"text_align".to_string(),
Box::new(|node_id, index, context| {
let choices = enum_choice::<text::TextAlign>().for_socket(ParameterWidgetsInfo::new(node_id, index, true, context)).property_row();
let choices = enum_choice::<text::TextAlign>()
.for_socket(ParameterWidgetsInfo::at_index(node_id, index, true, context))
.property_row();
Ok(vec![choices])
}),
);
@@ -234,8 +234,8 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphMessageContext<'a>> 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<NodeGraphMessage, NodeGraphMessageContext<'a>> 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<NodeGraphMessage, NodeGraphMessageContext<'a>> 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<NodeGraphMessage, NodeGraphMessageContext<'a>> 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<NodeGraphMessage, NodeGraphMessageContext<'a>> 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<NodeGraphMessage, NodeGraphMessageContext<'a>> 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<NodeGraphMessage, NodeGraphMessageContext<'a>> 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<NodeGraphMessage, NodeGraphMessageContext<'a>> 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(&current_input_connector, breadcrumb_network_path) else {
continue;
};
@@ -715,7 +715,7 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphMessageContext<'a>> 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<NodeGraphMessage, NodeGraphMessageContext<'a>> for NodeG
.cloned()
.collect::<Vec<_>>()
{
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<NodeGraphMessage, NodeGraphMessageContext<'a>> 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<NodeGraphMessage, NodeGraphMessageContext<'a>> 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<NodeGraphMessage, NodeGraphMessageContext<'a>> 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<NodeGraphMessage, NodeGraphMessageContext<'a>> 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<NodeGraphMessage, NodeGraphMessageContext<'a>> 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;
};
@@ -1765,7 +1767,7 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphMessageContext<'a>> 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);
@@ -2608,7 +2610,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();
@@ -2705,7 +2707,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::<Vec<_>>() {
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)
@@ -2716,10 +2718,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))
File diff suppressed because it is too large Load Diff
@@ -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::docume
/// The stored paint value of the document's single Fill node.
fn fill_paint_value(document: &DocumentMessageHandler) -> 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;
@@ -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<alloc::sync::Arc<graphene_core::context::OwnedContextImpl>>","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<alloc::sync::Arc<graphene_core::context::OwnedContextImpl>>","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<Instances<VectorData>>"],"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<Color>"],"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:?}"
@@ -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");
@@ -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));
@@ -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()
})
@@ -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);
@@ -875,7 +875,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);
@@ -920,7 +920,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;
@@ -1120,11 +1120,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::<Vec<_>>();
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);
}
@@ -1162,7 +1165,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 {
@@ -1245,7 +1248,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);
}
}
@@ -1451,7 +1454,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);
@@ -1472,7 +1475,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 })
})
@@ -1497,7 +1500,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);
}
@@ -1557,7 +1560,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) {
@@ -1565,7 +1568,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
@@ -1581,7 +1584,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,
@@ -1593,7 +1596,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 };
}
}
@@ -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::<Vec<_>>() {
// 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;
};
@@ -129,7 +129,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,
})
}
@@ -166,7 +166,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()),
@@ -259,7 +259,7 @@ impl NodeNetworkInterface {
let node_io = &entry.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()
@@ -292,7 +292,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
.iter()
@@ -303,7 +303,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,
@@ -338,7 +338,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
@@ -348,7 +348,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,
@@ -360,7 +362,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
}
@@ -39,6 +39,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<P: graphene_std::NodeParameter>(&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<P: graphene_std::NodeParameter>(&mut self, _parameter: P) -> Option<&mut NodeInput> {
self.inputs.get_mut(P::INDEX)
}
}
impl Default for NodeTemplate {
fn default() -> Self {
Self {
@@ -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<ParameterRef>) -> 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,
@@ -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<bool, NetworkError> {
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<bool, NetworkError> {
@@ -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)
}
File diff suppressed because it is too large Load Diff
@@ -912,7 +912,7 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageContext<'_>> 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 {