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-15 14:38:16 +02:00
committed by Dennis Kobert
parent 5af465ce36
commit bf217169f6
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 {
@@ -11,6 +11,7 @@ use crate::messages::tool::common_functionality::shapes::shape_utility::{extract
use glam::{DAffine2, DVec2};
use graph_craft::document::NodeInput;
use graph_craft::document::value::TaggedValue;
use graphene_std::ParameterRef;
use std::collections::VecDeque;
use std::f64::consts::FRAC_PI_2;
@@ -147,7 +148,13 @@ impl RadiusHandle {
pub fn update_inner_radius(&mut self, document: &DocumentMessageHandler, input: &InputPreprocessorMessageHandler, responses: &mut VecDeque<Message>, drag_start: DVec2) {
let Some(layer) = self.layer else { return };
let Some(node_id) = graph_modification_utils::get_circle_id(layer, &document.network_interface).or(get_arc_id(layer, &document.network_interface)) else {
// This gizmo serves both Circle and Arc layers, so resolve which node is present to know whose radius parameter to write
let (node_id, radius_parameter) = if let Some(node_id) = graph_modification_utils::get_circle_id(layer, &document.network_interface) {
(node_id, ParameterRef::from(graphene_std::vector::generator_nodes::circle::RadiusInput))
} else if let Some(node_id) = get_arc_id(layer, &document.network_interface) {
(node_id, ParameterRef::from(graphene_std::vector::generator_nodes::arc::RadiusInput))
} else {
return;
};
let Some(current_radius) = extract_circle_radius(layer, document).or(extract_arc_parameters(Some(layer), document).map(|(r, _, _, _)| r)) else {
@@ -165,7 +172,7 @@ impl RadiusHandle {
self.previous_mouse_position = input.mouse.position;
responses.add(NodeGraphMessage::SetInput {
input_connector: InputConnector::node(node_id, 1),
input_connector: InputConnector::node(node_id, radius_parameter),
input: NodeInput::value(TaggedValue::F64(current_radius + net_delta), false),
});
responses.add(NodeGraphMessage::RunDocumentGraph);
@@ -13,7 +13,7 @@ use crate::messages::tool::common_functionality::shapes::shape_utility::extract_
use glam::{DAffine2, DVec2};
use graph_craft::document::NodeInput;
use graph_craft::document::value::TaggedValue;
use graphene_std::NodeInputDecleration;
use graphene_std::ParameterRef;
use graphene_std::vector::misc::{GridType, dvec2_to_point, get_line_endpoints};
use kurbo::{Line, ParamCurveNearest, Rect};
use std::collections::VecDeque;
@@ -123,7 +123,7 @@ impl RowColumnGizmo {
let transform = self.transform_grid(dimensions_delta, self.spacing, grid_type, angles, viewport);
responses.add(NodeGraphMessage::SetInput {
input_connector: InputConnector::node(node_id, self.gizmo_type.index()),
input_connector: InputConnector::node(node_id, self.gizmo_type.parameter()),
input: NodeInput::value(TaggedValue::U32((self.initial_dimension() as i32 + dimensions_to_add).max(1) as u32), false),
});
@@ -411,13 +411,13 @@ impl RowColumnGizmoType {
}
}
fn index(&self) -> usize {
fn parameter(&self) -> ParameterRef {
use graphene_std::vector::generator_nodes::grid::*;
match self {
RowColumnGizmoType::Top | RowColumnGizmoType::Bottom => RowsInput::INDEX,
RowColumnGizmoType::Left | RowColumnGizmoType::Right => ColumnsInput::INDEX,
RowColumnGizmoType::None => panic!("RowColumnGizmoType::None does not have a mouse_icon"),
RowColumnGizmoType::Top | RowColumnGizmoType::Bottom => RowsInput.into(),
RowColumnGizmoType::Left | RowColumnGizmoType::Right => ColumnsInput.into(),
RowColumnGizmoType::None => panic!("RowColumnGizmoType::None does not reference a grid input"),
}
}
@@ -13,6 +13,7 @@ use crate::messages::tool::common_functionality::shapes::shape_utility::{extract
use glam::{DAffine2, DVec2};
use graph_craft::document::NodeInput;
use graph_craft::document::value::TaggedValue;
use graphene_std::ParameterRef;
use std::collections::VecDeque;
use std::f64::consts::TAU;
@@ -194,14 +195,20 @@ impl NumberOfPointsDial {
let net_delta = (delta.length() / 25.).round() * sign;
let Some(layer) = self.layer else { return };
let Some(node_id) = graph_modification_utils::get_star_id(layer, &document.network_interface).or(graph_modification_utils::get_polygon_id(layer, &document.network_interface)) else {
// This dial serves both Star and Polygon layers, so resolve which node is present to know whose sides parameter to write
let (node_id, sides_parameter) = if let Some(node_id) = graph_modification_utils::get_star_id(layer, &document.network_interface) {
(node_id, ParameterRef::from(graphene_std::vector::generator_nodes::star::SidesInput))
} else if let Some(node_id) = graph_modification_utils::get_polygon_id(layer, &document.network_interface) {
(node_id, ParameterRef::from(graphene_std::vector::generator_nodes::regular_polygon::SidesInput))
} else {
return;
};
let new_point_count = ((self.initial_points as i32) + (net_delta as i32)).max(3);
responses.add(NodeGraphMessage::SetInput {
input_connector: InputConnector::node(node_id, 1),
input_connector: InputConnector::node(node_id, sides_parameter),
input: NodeInput::value(TaggedValue::U32(new_point_count as u32), false),
});
responses.add(NodeGraphMessage::RunDocumentGraph);
@@ -8,12 +8,14 @@ use crate::messages::portfolio::document::{overlays::utility_types::OverlayConte
use crate::messages::prelude::FrontendMessage;
use crate::messages::prelude::Responses;
use crate::messages::prelude::{DocumentMessageHandler, InputPreprocessorMessageHandler, NodeGraphMessage};
use crate::messages::tool::common_functionality::graph_modification_utils::{self, NodeGraphLayer};
use crate::messages::tool::common_functionality::graph_modification_utils::NodeGraphLayer;
use crate::messages::tool::common_functionality::shapes::shape_utility::{draw_snapping_ticks, extract_polygon_parameters, polygon_outline, polygon_vertex_position, star_outline};
use crate::messages::tool::common_functionality::shapes::shape_utility::{extract_star_parameters, star_vertex_position};
use glam::DVec2;
use graph_craft::document::NodeInput;
use graph_craft::document::value::TaggedValue;
use graphene_std::ParameterRef;
use graphene_std::vector::generator_nodes::{regular_polygon, star};
use std::collections::VecDeque;
use std::f64::consts::{FRAC_1_SQRT_2, FRAC_PI_4, PI, SQRT_2};
@@ -30,7 +32,8 @@ pub enum PointRadiusHandleState {
pub struct PointRadiusHandle {
pub layer: Option<LayerNodeIdentifier>,
point: u32,
radius_index: usize,
/// The radius parameter the hovered or dragged handle writes to: a star's first or second radius, or a polygon's radius.
radius_parameter: Option<ParameterRef>,
snap_radii: Vec<f64>,
initial_radius: f64,
handle_state: PointRadiusHandleState,
@@ -63,7 +66,11 @@ impl PointRadiusHandle {
let viewport = document.metadata().transform_to_viewport(layer);
for i in 0..2 * sides {
let (radius, radius_index) = if i % 2 == 0 { (radius1, 2) } else { (radius2, 3) };
let (radius, radius_parameter) = if i % 2 == 0 {
(radius1, ParameterRef::from(star::Radius1Input))
} else {
(radius2, ParameterRef::from(star::Radius2Input))
};
let point = star_vertex_position(viewport, i as i32, sides, radius1, radius2);
let center = viewport.transform_point2(DVec2::ZERO);
@@ -73,10 +80,10 @@ impl PointRadiusHandle {
}
if point.distance(mouse_position) < 5. {
self.radius_index = radius_index;
self.snap_radii = Self::calculate_snap_radii(document, layer, &radius_parameter);
self.radius_parameter = Some(radius_parameter);
self.layer = Some(layer);
self.point = i;
self.snap_radii = Self::calculate_snap_radii(document, layer, radius_index);
self.initial_radius = radius;
responses.add(FrontendMessage::UpdateMouseCursor { cursor: MouseCursorIcon::Default });
self.update_state(PointRadiusHandleState::Hover);
@@ -100,7 +107,7 @@ impl PointRadiusHandle {
}
if point.distance(mouse_position) < 5. {
self.radius_index = 2;
self.radius_parameter = Some(regular_polygon::RadiusInput.into());
self.layer = Some(layer);
self.point = i;
self.snap_radii.clear();
@@ -329,21 +336,20 @@ impl PointRadiusHandle {
}
}
fn calculate_snap_radii(document: &DocumentMessageHandler, layer: LayerNodeIdentifier, radius_index: usize) -> Vec<f64> {
fn calculate_snap_radii(document: &DocumentMessageHandler, layer: LayerNodeIdentifier, radius_parameter: &ParameterRef) -> Vec<f64> {
let mut snap_radii = Vec::new();
let Some(node_inputs) = NodeGraphLayer::new(layer, &document.network_interface).find_node_inputs(&DefinitionIdentifier::ProtoNode(graphene_std::vector::generator_nodes::star::IDENTIFIER))
else {
let Some(parameters) = NodeGraphLayer::new(layer, &document.network_interface).find_node_parameters(star::IDENTIFIER) else {
return snap_radii;
};
let (Some(&TaggedValue::F64(radius_1)), Some(&TaggedValue::F64(radius_2))) = (node_inputs[2].as_value(), node_inputs[3].as_value()) else {
let (Some(&TaggedValue::F64(radius_1)), Some(&TaggedValue::F64(radius_2))) = (parameters.value(star::Radius1Input), parameters.value(star::Radius2Input)) else {
return snap_radii;
};
let other_radius = if radius_index == 3 { radius_1 } else { radius_2 };
let other_radius = if *radius_parameter == ParameterRef::from(star::Radius2Input) { radius_1 } else { radius_2 };
let Some(&TaggedValue::U32(sides)) = node_inputs[1].as_value() else {
let Some(&TaggedValue::U32(sides)) = parameters.value(star::SidesInput) else {
return snap_radii;
};
@@ -415,14 +421,15 @@ impl PointRadiusHandle {
pub fn update_inner_radius(&mut self, document: &DocumentMessageHandler, input: &InputPreprocessorMessageHandler, responses: &mut VecDeque<Message>, drag_start: DVec2) {
let Some(layer) = self.layer else { return };
let Some(radius_parameter) = self.radius_parameter.clone() else { return };
let Some(node_id) = graph_modification_utils::get_star_id(layer, &document.network_interface).or(graph_modification_utils::get_polygon_id(layer, &document.network_interface)) else {
// The stored parameter names the node it belongs to (Star or Polygon), so locate that same node on the layer
let Some(node_id) = NodeGraphLayer::new(layer, &document.network_interface).upstream_node_id_from_name(&DefinitionIdentifier::ProtoNode(radius_parameter.node_identifier.clone())) else {
return;
};
let viewport_transform = document.network_interface.document_metadata().transform_to_viewport(layer);
let center = viewport_transform.transform_point2(DVec2::ZERO);
let radius_index = self.radius_index;
let original_radius = self.initial_radius;
@@ -436,7 +443,7 @@ impl PointRadiusHandle {
self.update_state(PointRadiusHandleState::Dragging);
self.check_if_radius_flipped(original_radius, new_radius, document, layer, radius_index);
self.check_if_radius_flipped(original_radius, new_radius, document, layer, &radius_parameter);
if let Some((index, snapped_delta)) = self.check_snapping(new_radius, original_radius) {
net_delta = snapped_delta;
@@ -444,29 +451,28 @@ impl PointRadiusHandle {
}
responses.add(NodeGraphMessage::SetInput {
input_connector: InputConnector::node(node_id, radius_index),
input_connector: InputConnector::node(node_id, radius_parameter),
input: NodeInput::value(TaggedValue::F64(original_radius + net_delta), false),
});
responses.add(NodeGraphMessage::RunDocumentGraph);
}
fn check_if_radius_flipped(&mut self, original_radius: f64, new_radius: f64, document: &DocumentMessageHandler, layer: LayerNodeIdentifier, radius_index: usize) {
let Some(node_inputs) = NodeGraphLayer::new(layer, &document.network_interface).find_node_inputs(&DefinitionIdentifier::ProtoNode(graphene_std::vector::generator_nodes::star::IDENTIFIER))
else {
fn check_if_radius_flipped(&mut self, original_radius: f64, new_radius: f64, document: &DocumentMessageHandler, layer: LayerNodeIdentifier, radius_parameter: &ParameterRef) {
let Some(parameters) = NodeGraphLayer::new(layer, &document.network_interface).find_node_parameters(star::IDENTIFIER) else {
return;
};
let (Some(&TaggedValue::F64(radius_1)), Some(&TaggedValue::F64(radius_2))) = (node_inputs[2].as_value(), node_inputs[3].as_value()) else {
let (Some(&TaggedValue::F64(radius_1)), Some(&TaggedValue::F64(radius_2))) = (parameters.value(star::Radius1Input), parameters.value(star::Radius2Input)) else {
return;
};
let other_radius = if radius_index == 3 { radius_1 } else { radius_2 };
let other_radius = if *radius_parameter == ParameterRef::from(star::Radius2Input) { radius_1 } else { radius_2 };
let flipped = (other_radius.is_sign_positive() && original_radius.is_sign_negative() && new_radius.is_sign_positive())
|| (other_radius.is_sign_negative() && original_radius.is_sign_positive() && new_radius.is_sign_negative());
if flipped {
self.snap_radii = Self::calculate_snap_radii(document, layer, radius_index);
self.snap_radii = Self::calculate_snap_radii(document, layer, radius_parameter);
}
}
}
@@ -12,7 +12,6 @@ use crate::messages::tool::common_functionality::shapes::spiral_shape::calculate
use glam::DVec2;
use graph_craft::document::NodeInput;
use graph_craft::document::value::TaggedValue;
use graphene_std::NodeInputDecleration;
use graphene_std::subpath::{calculate_growth_factor, spiral_point};
use graphene_std::vector::misc::SpiralType;
use std::collections::VecDeque;
@@ -192,15 +191,15 @@ impl SpiralTurns {
let new_outer_radius = (self.initial_outer_radius + outer_radius_change * sign).max(0.1);
responses.add(NodeGraphMessage::SetInput {
input_connector: InputConnector::node(node_id, StartAngleInput::INDEX),
input_connector: InputConnector::node(node_id, StartAngleInput),
input: NodeInput::value(TaggedValue::F64(self.initial_start_angle + total_delta), false),
});
responses.add(NodeGraphMessage::SetInput {
input_connector: InputConnector::node(node_id, TurnsInput::INDEX),
input_connector: InputConnector::node(node_id, TurnsInput),
input: NodeInput::value(TaggedValue::F64(new_turns), false),
});
responses.add(NodeGraphMessage::SetInput {
input_connector: InputConnector::node(node_id, OuterRadiusInput::INDEX),
input_connector: InputConnector::node(node_id, OuterRadiusInput),
input: NodeInput::value(TaggedValue::F64(new_outer_radius), false),
});
}
@@ -209,11 +208,11 @@ impl SpiralTurns {
let new_outer_radius = (self.initial_outer_radius + outer_radius_change).max(0.1);
responses.add(NodeGraphMessage::SetInput {
input_connector: InputConnector::node(node_id, TurnsInput::INDEX),
input_connector: InputConnector::node(node_id, TurnsInput),
input: NodeInput::value(TaggedValue::F64(new_turns), false),
});
responses.add(NodeGraphMessage::SetInput {
input_connector: InputConnector::node(node_id, OuterRadiusInput::INDEX),
input_connector: InputConnector::node(node_id, OuterRadiusInput),
input: NodeInput::value(TaggedValue::F64(new_outer_radius), false),
});
}
@@ -332,11 +332,11 @@ impl SweepAngleGizmo {
self.snap_angles = Self::calculate_snap_angles();
responses.add(NodeGraphMessage::SetInput {
input_connector: InputConnector::node(node_id, 2),
input_connector: InputConnector::node(node_id, graphene_std::vector::generator_nodes::arc::StartAngleInput),
input: NodeInput::value(TaggedValue::F64(start_angle), false),
});
responses.add(NodeGraphMessage::SetInput {
input_connector: InputConnector::node(node_id, 3),
input_connector: InputConnector::node(node_id, graphene_std::vector::generator_nodes::arc::SweepAngleInput),
input: NodeInput::value(TaggedValue::F64(sweep_angle), false),
});
@@ -8,7 +8,6 @@ use graph_craft::document::value::TaggedValue;
use graph_craft::document::{DocumentNode, NodeId, NodeInput};
use graph_craft::{ProtoNodeIdentifier, concrete};
use graphene_std::Color;
use graphene_std::NodeInputDecleration;
use graphene_std::raster::BlendMode;
use graphene_std::raster_types::{CPU, GPU, Image, Raster};
use graphene_std::subpath::Subpath;
@@ -16,6 +15,7 @@ use graphene_std::text::{Font, TypesettingConfig};
use graphene_std::vector::misc::ManipulatorPointId;
use graphene_std::vector::style::{FillChoice, PaintOrder, StrokeAlign, StrokeCap, StrokeJoin, initial_gradient_transform_for_bounding_box};
use graphene_std::vector::{Gradient, GradientSpreadMethod, GradientType, PointId, SegmentId, VectorModificationType};
use graphene_std::{NodeParameter, ParameterRef};
use std::collections::VecDeque;
/// Returns the ID of the first Spline node in the horizontal flow which is not followed by a `Path` node, or `None` if none exists.
@@ -92,8 +92,8 @@ pub fn merge_layers(document: &DocumentMessageHandler, first_layer: LayerNodeIde
parent: first_layer,
});
responses.add(NodeGraphMessage::ConnectUpstreamOutputToInput {
downstream_input: InputConnector::node(second_layer.to_node(), 1),
input_connector: InputConnector::node(merge_node_id, 1),
downstream_input: InputConnector::layer_secondary_input(second_layer.to_node()),
input_connector: InputConnector::layer_secondary_input(merge_node_id),
});
responses.add(NodeGraphMessage::DeleteNodes {
node_ids: vec![second_layer.to_node()],
@@ -250,9 +250,7 @@ pub fn new_custom(id: NodeId, nodes: Vec<(NodeId, NodeTemplate)>, parent: LayerN
pub fn get_origin(layer: LayerNodeIdentifier, network_interface: &NodeNetworkInterface) -> Option<DVec2> {
use graphene_std::transform_nodes::transform::*;
if let TaggedValue::DVec2(origin) =
NodeGraphLayer::new(layer, network_interface).find_input(&DefinitionIdentifier::ProtoNode(graphene_std::transform_nodes::transform::IDENTIFIER), TranslationInput::INDEX)?
{
if let TaggedValue::DVec2(origin) = NodeGraphLayer::new(layer, network_interface).parameter_value(TranslationInput)? {
Some(*origin)
} else {
None
@@ -274,16 +272,16 @@ pub fn get_viewport_center(layer: LayerNodeIdentifier, network_interface: &NodeN
pub fn get_fill_node_id_with_direct_fill_input(layer: LayerNodeIdentifier, network_interface: &NodeNetworkInterface) -> Option<NodeId> {
let fill_node_id = NodeGraphLayer::new(layer, network_interface).upstream_node_id_from_name(&DefinitionIdentifier::ProtoNode(graphene_std::vector::fill::IDENTIFIER))?;
let fill_node = network_interface.document_network().nodes.get(&fill_node_id)?;
matches!(fill_node.inputs.get(graphene_std::vector::fill::FillInput::INDEX)?, NodeInput::Value { .. }).then_some(fill_node_id)
matches!(fill_node.input(graphene_std::vector::fill::FillInput)?, NodeInput::Value { .. }).then_some(fill_node_id)
}
/// Determine the input connector where the gradient chain enters the layer.
/// Returns Fill's fill input if the layer has a "Fill" node, otherwise returns the layer's content input.
pub fn gradient_chain_target_input(layer: LayerNodeIdentifier, network_interface: &NodeNetworkInterface) -> InputConnector {
if let Some(fill_node_id) = NodeGraphLayer::new(layer, network_interface).upstream_node_id_from_name(&DefinitionIdentifier::ProtoNode(graphene_std::vector::fill::IDENTIFIER)) {
InputConnector::node(fill_node_id, graphene_std::vector::fill::FillInput::INDEX)
InputConnector::node(fill_node_id, graphene_std::vector::fill::FillInput)
} else {
InputConnector::node(layer.to_node(), 1)
InputConnector::layer_secondary_input(layer.to_node())
}
}
@@ -302,7 +300,7 @@ pub fn get_upstream_gradient_value_node_id(layer: LayerNodeIdentifier, network_i
pub fn get_fill_input_node_id(layer: LayerNodeIdentifier, network_interface: &NodeNetworkInterface) -> Option<NodeId> {
let fill_node_id = NodeGraphLayer::new(layer, network_interface).upstream_node_id_from_name(&DefinitionIdentifier::ProtoNode(graphene_std::vector::fill::IDENTIFIER))?;
let fill_node = network_interface.document_network().nodes.get(&fill_node_id)?;
let NodeInput::Node { node_id, .. } = fill_node.inputs.get(graphene_std::vector::fill::FillInput::INDEX)? else {
let NodeInput::Node { node_id, .. } = fill_node.input(graphene_std::vector::fill::FillInput)? else {
return None;
};
Some(*node_id)
@@ -316,13 +314,13 @@ pub fn get_gradient_stops(layer: LayerNodeIdentifier, network_interface: &NodeNe
.document_network()
.nodes
.get(&fill_node_id)
.and_then(|node| node.inputs.get(graphene_std::vector::fill::FillInput::INDEX))
.and_then(|node| node.input(graphene_std::vector::fill::FillInput))
.and_then(|input| input.as_value())
.and_then(|value| if let TaggedValue::Gradient(gradient) = value { Some(gradient.clone()) } else { None });
}
let gradient_value_node = network_interface.document_network().nodes.get(&get_upstream_gradient_value_node_id(layer, network_interface)?)?;
let TaggedValue::Gradient(stops) = gradient_value_node.inputs.get(graphene_std::math_nodes::gradient_value::GradientInput::INDEX)?.as_value()? else {
let TaggedValue::Gradient(stops) = gradient_value_node.input(graphene_std::math_nodes::gradient_value::GradientInput)?.as_value()? else {
return None;
};
Some(stops.clone())
@@ -361,8 +359,7 @@ pub fn gradient_orientation_rightward(transform: glam::DAffine2) -> bool {
/// Get the current fill of a layer from the closest "Fill" node.
pub fn get_fill_color(layer: LayerNodeIdentifier, network_interface: &NodeNetworkInterface) -> Option<Color> {
let inputs = NodeGraphLayer::new(layer, network_interface).find_node_inputs(&DefinitionIdentifier::ProtoNode(graphene_std::vector::fill::IDENTIFIER))?;
let TaggedValue::Color(color) = inputs.get(graphene_std::vector::fill::FillInput::INDEX)?.as_value()? else {
let TaggedValue::Color(color) = NodeGraphLayer::new(layer, network_interface).parameter_value(graphene_std::vector::fill::FillInput)? else {
return None;
};
Some(*color)
@@ -471,42 +468,35 @@ pub fn get_text<'a>(
fonts: &FontsMessageHandler,
resources: &ResourceMessageHandler,
) -> Option<(&'a String, Font, TypesettingConfig)> {
let inputs = NodeGraphLayer::new(layer, network_interface).find_node_inputs(&DefinitionIdentifier::ProtoNode(graphene_std::text::text::IDENTIFIER))?;
use graphene_std::text::text;
let parameters = NodeGraphLayer::new(layer, network_interface).find_node_parameters(text::IDENTIFIER)?;
let Some(TaggedValue::String(text)) = inputs.get(graphene_std::text::text::TextInput::INDEX)?.as_value() else {
return None;
};
let font = match inputs.get(graphene_std::text::text::FontInput::INDEX)?.as_value() {
let Some(TaggedValue::String(text)) = parameters.value(text::TextInput) else { return None };
let font = match parameters.value(text::FontInput) {
Some(TaggedValue::Resource(resource_id)) => fonts.id_font(resources, *resource_id).unwrap_or_default(),
_ => Font::default(),
};
let Some(&TaggedValue::F64(font_size)) = inputs.get(graphene_std::text::text::SizeInput::INDEX)?.as_value() else {
let Some(&TaggedValue::F64(font_size)) = parameters.value(text::SizeInput) else { return None };
let Some(&TaggedValue::F64(line_height_ratio)) = parameters.value(text::LineHeightInput) else {
return None;
};
let Some(&TaggedValue::F64(line_height_ratio)) = inputs.get(graphene_std::text::text::LineHeightInput::INDEX)?.as_value() else {
let Some(&TaggedValue::F64(letter_spacing)) = parameters.value(text::LetterSpacingInput) else {
return None;
};
let Some(&TaggedValue::F64(letter_spacing)) = inputs.get(graphene_std::text::text::LetterSpacingInput::INDEX)?.as_value() else {
let Some(&TaggedValue::Bool(has_max_width)) = parameters.value(text::HasMaxWidthInput) else {
return None;
};
let Some(&TaggedValue::Bool(has_max_width)) = inputs.get(graphene_std::text::text::HasMaxWidthInput::INDEX)?.as_value() else {
let Some(&TaggedValue::F64(max_width)) = parameters.value(text::MaxWidthInput) else { return None };
let Some(&TaggedValue::Bool(has_max_height)) = parameters.value(text::HasMaxHeightInput) else {
return None;
};
let Some(&TaggedValue::F64(max_width)) = inputs.get(graphene_std::text::text::MaxWidthInput::INDEX)?.as_value() else {
let Some(&TaggedValue::F64(max_height)) = parameters.value(text::MaxHeightInput) else {
return None;
};
let Some(&TaggedValue::Bool(has_max_height)) = inputs.get(graphene_std::text::text::HasMaxHeightInput::INDEX)?.as_value() else {
return None;
};
let Some(&TaggedValue::F64(max_height)) = inputs.get(graphene_std::text::text::MaxHeightInput::INDEX)?.as_value() else {
return None;
};
let Some(&TaggedValue::F64(letter_tilt)) = inputs.get(graphene_std::text::text::LetterTiltInput::INDEX)?.as_value() else {
return None;
};
let Some(&TaggedValue::TextAlign(align)) = inputs.get(graphene_std::text::text::AlignInput::INDEX)?.as_value() else {
let Some(&TaggedValue::F64(letter_tilt)) = parameters.value(text::LetterTiltInput) else {
return None;
};
let Some(&TaggedValue::TextAlign(align)) = parameters.value(text::AlignInput) else { return None };
let typesetting = TypesettingConfig {
font_size,
@@ -521,8 +511,7 @@ pub fn get_text<'a>(
}
pub fn get_stroke_width(layer: LayerNodeIdentifier, network_interface: &NodeNetworkInterface) -> Option<f64> {
let weight_node_input_index = graphene_std::vector::stroke::WeightInput::INDEX;
if let TaggedValue::F64(width) = NodeGraphLayer::new(layer, network_interface).find_input(&DefinitionIdentifier::ProtoNode(graphene_std::vector::stroke::IDENTIFIER), weight_node_input_index)? {
if let TaggedValue::F64(width) = NodeGraphLayer::new(layer, network_interface).parameter_value(graphene_std::vector::stroke::WeightInput)? {
Some(*width)
} else {
None
@@ -544,36 +533,34 @@ pub struct StrokeOptionsState {
/// Reads the non-color stroke option inputs from a layer's Stroke proto node. Returns `None` when the layer has no Stroke node.
/// Inputs that aren't a static value (e.g. wired to another node) fall back to per-field defaults so the layer still participates in the sync.
pub fn get_stroke_options(layer: LayerNodeIdentifier, network_interface: &NodeNetworkInterface) -> Option<StrokeOptionsState> {
let stroke = &DefinitionIdentifier::ProtoNode(graphene_std::vector::stroke::IDENTIFIER);
let layer_view = NodeGraphLayer::new(layer, network_interface);
layer_view.upstream_node_id_from_name(stroke)?;
let read = |index: usize| layer_view.find_input(stroke, index);
use graphene_std::vector::stroke;
let parameters = NodeGraphLayer::new(layer, network_interface).find_node_parameters(stroke::IDENTIFIER)?;
let align = match read(graphene_std::vector::stroke::AlignInput::INDEX) {
let align = match parameters.value(stroke::AlignInput) {
Some(TaggedValue::StrokeAlign(value)) => *value,
_ => StrokeAlign::default(),
};
let cap = match read(graphene_std::vector::stroke::CapInput::INDEX) {
let cap = match parameters.value(stroke::CapInput) {
Some(TaggedValue::StrokeCap(value)) => *value,
_ => StrokeCap::default(),
};
let join = match read(graphene_std::vector::stroke::JoinInput::INDEX) {
let join = match parameters.value(stroke::JoinInput) {
Some(TaggedValue::StrokeJoin(value)) => *value,
_ => StrokeJoin::default(),
};
let miter_limit = match read(graphene_std::vector::stroke::MiterLimitInput::INDEX) {
let miter_limit = match parameters.value(stroke::MiterLimitInput) {
Some(TaggedValue::F64(value)) => *value,
_ => 4.,
};
let paint_order = match read(graphene_std::vector::stroke::PaintOrderInput::INDEX) {
let paint_order = match parameters.value(stroke::PaintOrderInput) {
Some(TaggedValue::PaintOrder(value)) => *value,
_ => PaintOrder::default(),
};
let dash_lengths = match read(graphene_std::vector::stroke::DashPatternInput::INDEX) {
let dash_lengths = match parameters.value(stroke::DashPatternInput) {
Some(TaggedValue::DashPattern(value)) => value.0.iter_element_values().copied().collect(),
_ => Vec::new(),
};
let dash_offset = match read(graphene_std::vector::stroke::DashOffsetInput::INDEX) {
let dash_offset = match parameters.value(stroke::DashOffsetInput) {
Some(TaggedValue::F64(value)) => *value,
_ => 0.,
};
@@ -612,9 +599,11 @@ pub fn set_stroke_weight_for_selected_layers(weight: f64, document: &DocumentMes
let layers: Vec<_> = document.network_interface.selected_nodes().selected_layers_except_artboards(&document.network_interface).collect();
for layer in layers {
if let Some(node_id) = get_stroke_id(layer, &document.network_interface) {
let input_index = graphene_std::vector::stroke::WeightInput::INDEX;
let value = Box::new(TaggedValue::F64(weight));
responses.add(NodeGraphMessage::SetInputValue { node_id, input_index, value });
responses.add(NodeGraphMessage::SetInputValue {
node_id,
input_index: graphene_std::vector::stroke::WeightInput::INDEX,
value: TaggedValue::F64(weight).into(),
});
} else if weight > 0. {
let color = Some(Color::BLACK);
let stroke = graphene_std::vector::style::Stroke::default().with_weight(weight);
@@ -637,19 +626,19 @@ pub struct FillNodeGradient {
pub fn read_fill_node_gradient(fill_node: &DocumentNode, bounding_box: impl FnOnce() -> [DVec2; 2]) -> Option<FillNodeGradient> {
use graphene_std::vector::fill;
let TaggedValue::Gradient(stops) = fill_node.inputs.get(fill::FillInput::INDEX)?.as_value()? else {
let TaggedValue::Gradient(stops) = fill_node.input(fill::FillInput)?.as_value()? else {
return None;
};
let gradient_type = match fill_node.inputs.get(fill::GradientTypeInput::INDEX).and_then(|input| input.as_value()) {
let gradient_type = match fill_node.input(fill::GradientTypeInput).and_then(|input| input.as_value()) {
Some(&TaggedValue::GradientType(value)) => value,
_ => GradientType::default(),
};
let spread_method = match fill_node.inputs.get(fill::SpreadMethodInput::INDEX).and_then(|input| input.as_value()) {
let spread_method = match fill_node.input(fill::SpreadMethodInput).and_then(|input| input.as_value()) {
Some(&TaggedValue::GradientSpreadMethod(value)) => value,
_ => GradientSpreadMethod::default(),
};
let has_transform = matches!(fill_node.inputs.get(fill::HasTransformInput::INDEX).and_then(|input| input.as_value()), Some(&TaggedValue::Bool(true)));
let transform_input = fill_node.inputs.get(fill::TransformInput::INDEX).and_then(|input| input.as_value());
let has_transform = matches!(fill_node.input(fill::HasTransformInput).and_then(|input| input.as_value()), Some(&TaggedValue::Bool(true)));
let transform_input = fill_node.input(fill::TransformInput).and_then(|input| input.as_value());
let transform = match (has_transform, transform_input) {
(true, Some(&TaggedValue::DAffine2(value))) => value,
(false, _) => initial_gradient_transform_for_bounding_box(bounding_box()),
@@ -666,8 +655,7 @@ pub fn read_fill_node_gradient(fill_node: &DocumentNode, bounding_box: impl FnOn
}
/// Returns the stroke color from a layer's upstream Stroke node.
pub fn get_stroke_color(layer: LayerNodeIdentifier, network_interface: &NodeNetworkInterface) -> Option<Option<Color>> {
let color_index = graphene_std::vector::stroke::PaintInput::INDEX;
let tagged = NodeGraphLayer::new(layer, network_interface).find_input(&DefinitionIdentifier::ProtoNode(graphene_std::vector::stroke::IDENTIFIER), color_index)?;
let tagged = NodeGraphLayer::new(layer, network_interface).parameter_value(graphene_std::vector::stroke::PaintInput)?;
match tagged {
TaggedValue::Color(color) => Some(Some(*color)),
value if value.is_no_paint() => Some(None),
@@ -704,8 +692,8 @@ pub fn selected_fill_state(document: &DocumentMessageHandler) -> Option<Selected
let fill_choice = (|| {
let fill_node = document.network_interface.document_network().nodes.get(&fill_node_id)?;
match fill_node.inputs.get(graphene_std::vector::fill::FillInput::INDEX)?.as_value()? {
&TaggedValue::Color(color) => Some(FillChoice::Solid(color)),
match fill_node.input(graphene_std::vector::fill::FillInput)?.as_value()? {
TaggedValue::Color(color) => Some(FillChoice::Solid(*color)),
TaggedValue::Gradient(stops) => Some(FillChoice::Gradient(stops.clone())),
value if value.is_no_paint() => Some(FillChoice::None),
_ => None,
@@ -791,22 +779,19 @@ pub fn set_fill_for_selected_layers(fill_choice: FillChoice, document: &Document
FillChoice::None => responses.add(GraphOperationMessage::FillColorSet { layer, color: None }),
FillChoice::Solid(color) => responses.add(GraphOperationMessage::FillColorSet { layer, color: Some(*color) }),
FillChoice::Gradient(stops) => {
let fill_node = NodeGraphLayer::new(layer, &document.network_interface)
.upstream_node_id_from_name(&DefinitionIdentifier::ProtoNode(graphene_std::vector::fill::IDENTIFIER))
.and_then(|id| document.network_interface.document_network().nodes.get(&id));
use graphene_std::vector::fill;
let fill_parameters = NodeGraphLayer::new(layer, &document.network_interface).find_node_parameters(fill::IDENTIFIER);
let read = |index: usize| fill_node.and_then(|node| node.inputs.get(index)).and_then(|input| input.as_value());
let gradient_type = match read(graphene_std::vector::fill::GradientTypeInput::INDEX) {
let gradient_type = match fill_parameters.as_ref().and_then(|parameters| parameters.value(fill::GradientTypeInput)) {
Some(TaggedValue::GradientType(value)) => *value,
_ => GradientType::default(),
};
let spread_method = match read(graphene_std::vector::fill::SpreadMethodInput::INDEX) {
let spread_method = match fill_parameters.as_ref().and_then(|parameters| parameters.value(fill::SpreadMethodInput)) {
Some(TaggedValue::GradientSpreadMethod(value)) => *value,
_ => GradientSpreadMethod::default(),
};
let has_transform = matches!(read(graphene_std::vector::fill::HasTransformInput::INDEX), Some(TaggedValue::Bool(true)));
let transform = match (has_transform, read(graphene_std::vector::fill::TransformInput::INDEX)) {
let has_transform = matches!(fill_parameters.as_ref().and_then(|parameters| parameters.value(fill::HasTransformInput)), Some(TaggedValue::Bool(true)));
let transform = match (has_transform, fill_parameters.as_ref().and_then(|parameters| parameters.value(fill::TransformInput))) {
(true, Some(TaggedValue::DAffine2(value))) => *value,
(false, _) => initial_gradient_transform_for_bounding_box(document.network_interface.document_metadata().nonzero_bounding_box(layer)),
_ => DAffine2::IDENTITY,
@@ -831,9 +816,11 @@ pub fn set_stroke_color_for_selected_layers(color: Option<Color>, weight: f64, d
let layers: Vec<_> = document.network_interface.selected_nodes().selected_layers_except_artboards(&document.network_interface).collect();
for layer in layers {
if let Some(node_id) = get_stroke_id(layer, &document.network_interface) {
let input_index = graphene_std::vector::stroke::PaintInput::INDEX;
let value = Box::new(color.map_or_else(TaggedValue::no_paint, TaggedValue::Color));
responses.add(NodeGraphMessage::SetInputValue { node_id, input_index, value });
responses.add(NodeGraphMessage::SetInputValue {
node_id,
input_index: graphene_std::vector::stroke::PaintInput::INDEX,
value: color.map_or_else(TaggedValue::no_paint, TaggedValue::Color).into(),
});
} else {
let stroke = graphene_std::vector::style::Stroke::new(weight);
responses.add(GraphOperationMessage::StrokeSet { layer, color, stroke });
@@ -871,28 +858,26 @@ pub fn remove_stroke_for_selected_layers(document: &DocumentMessageHandler, resp
responses.add(NodeGraphMessage::SendGraph);
}
/// Reads a specific input from the matching proto node on the first selected non-artboard layer that has one.
/// Reads the given parameter from its proto node on the first selected non-artboard layer that has one.
/// Used by tool control bars to mirror per-shape parameters (sides, arc type, turns, etc.) from the selection
/// into the control bar's input widget state without each call site re-implementing the layer iteration.
pub fn first_selected_proto_node_input(document: &DocumentMessageHandler, identifier: graph_craft::ProtoNodeIdentifier, input_index: usize) -> Option<&TaggedValue> {
let identifier = DefinitionIdentifier::ProtoNode(identifier);
pub fn first_selected_parameter(document: &DocumentMessageHandler, parameter: impl Into<ParameterRef>) -> Option<&TaggedValue> {
let parameter = parameter.into();
let identifier = DefinitionIdentifier::ProtoNode(parameter.node_identifier);
document
.network_interface
.selected_nodes()
.selected_layers_except_artboards(&document.network_interface)
.find_map(|layer| NodeGraphLayer::new(layer, &document.network_interface).find_input(&identifier, input_index))
.find_map(|layer| NodeGraphLayer::new(layer, &document.network_interface).find_input(&identifier, parameter.input_index))
}
/// Writes a value to a specific input on the matching proto node of every selected non-artboard layer that has one.
/// Writes a value to the given parameter on its proto node for every selected non-artboard layer that has one.
/// Used by tool control bars to push per-shape parameter changes back onto all selected layers of that shape.
pub fn set_proto_node_input_for_selected_layers(
document: &DocumentMessageHandler,
identifier: graph_craft::ProtoNodeIdentifier,
input_index: usize,
value: TaggedValue,
responses: &mut VecDeque<Message>,
) {
let identifier = DefinitionIdentifier::ProtoNode(identifier);
/// The parameter symbol locates the node, so the value can never land on the right index of the wrong node.
pub fn set_parameter_for_selected_layers(document: &DocumentMessageHandler, parameter: impl Into<ParameterRef>, value: TaggedValue, responses: &mut VecDeque<Message>) {
let parameter = parameter.into();
let identifier = DefinitionIdentifier::ProtoNode(parameter.node_identifier.clone());
let layers: Vec<_> = document.network_interface.selected_nodes().selected_layers_except_artboards(&document.network_interface).collect();
@@ -902,8 +887,8 @@ pub fn set_proto_node_input_for_selected_layers(
};
responses.add(NodeGraphMessage::SetInputValue {
node_id,
input_index,
value: Box::new(value.clone()),
input_index: parameter.input_index,
value: value.clone().into(),
});
}
}
@@ -973,18 +958,62 @@ impl<'a> NodeGraphLayer<'a> {
.and_then(|node_id| self.network_interface.document_network().nodes.get(&node_id).map(|node| &node.inputs))
}
/// Find a specific input of a node within the layer's primary flow
/// Find a specific input of a node within the layer's primary flow.
/// Prefer [`Self::parameter_value`] where the input is statically known; this remains for network nodes and runtime-chosen indices.
pub fn find_input(&self, identifier: &DefinitionIdentifier, index: usize) -> Option<&'a TaggedValue> {
// TODO: Find a better way to accept a node input rather than using its index (which is quite unclear and fragile)
self.find_node_inputs(identifier)?.get(index)?.as_value()
}
/// The stored value of the given parameter on the matching proto node in the layer's primary flow, if that node exists and the input holds a value.
/// The parameter symbol names both the node and the input, so the two can never disagree.
/// For reading several parameters of the same node, prefer [`Self::find_node_parameters`] so the upstream flow is walked only once.
pub fn parameter_value(&self, parameter: impl Into<ParameterRef>) -> Option<&'a TaggedValue> {
let parameter = parameter.into();
self.find_input(&DefinitionIdentifier::ProtoNode(parameter.node_identifier), parameter.input_index)
}
/// Find a proto node in the layer's primary flow and return its inputs for reading by parameter symbol, walking upstream only once.
pub fn find_node_parameters(&self, identifier: ProtoNodeIdentifier) -> Option<ProtoNodeParameters<'a>> {
let inputs = self.find_node_inputs(&DefinitionIdentifier::ProtoNode(identifier.clone()))?;
Some(ProtoNodeParameters { identifier, inputs })
}
/// Check if a layer is a raster layer
pub fn is_raster_layer(layer: LayerNodeIdentifier, network_interface: &mut NodeNetworkInterface) -> bool {
let layer_input_type = network_interface.input_type(&InputConnector::node(layer.to_node(), 1), &[]);
let layer_input_type = network_interface.input_type(&InputConnector::layer_secondary_input(layer.to_node()), &[]);
// A leveled wire is typed by its element; depth rides the layout.
let compiled = layer_input_type.compiled_nested_type();
compiled == Some(&concrete!(Raster<CPU>)) || compiled == Some(&concrete!(Raster<GPU>))
}
}
/// The inputs of a proto node located by [`NodeGraphLayer::find_node_parameters`], read by parameter symbol without re-walking the layer's upstream flow per read.
pub struct ProtoNodeParameters<'a> {
identifier: ProtoNodeIdentifier,
inputs: &'a [NodeInput],
}
impl<'a> ProtoNodeParameters<'a> {
/// The input slot of the given parameter, or `None` if the parameter belongs to a different node than this view was built from.
pub fn input(&self, parameter: impl Into<ParameterRef>) -> Option<&'a NodeInput> {
let parameter = parameter.into();
// A mismatched symbol would otherwise read whatever sits at that index on the wrong node, so crash under tests but only log and read nothing in the running app
if parameter.node_identifier != self.identifier {
let message = format!("A parameter of {} was read from the inputs of {}", parameter.node_identifier, self.identifier);
if cfg!(test) {
panic!("{message}")
}
log::error!("{message}");
return None;
}
self.inputs.get(parameter.input_index)
}
/// The stored value of the given parameter, if that input currently holds a value rather than a wire.
pub fn value(&self, parameter: impl Into<ParameterRef>) -> Option<&'a TaggedValue> {
self.input(parameter)?.as_value()
}
}
@@ -172,7 +172,7 @@ impl Arc {
let radius = radius / viewport_zoom(document);
responses.add(NodeGraphMessage::SetInput {
input_connector: InputConnector::node(node_id, 1),
input_connector: InputConnector::node(node_id, graphene_std::vector::generator_nodes::arc::RadiusInput),
input: NodeInput::value(TaggedValue::F64(radius), false),
});
@@ -67,7 +67,7 @@ impl Arrow {
let document_to_viewport = document.metadata().document_to_viewport;
responses.add(NodeGraphMessage::SetInput {
input_connector: InputConnector::node(node_id, 1),
input_connector: InputConnector::node(node_id, graphene_std::vector::generator_nodes::arrow::ArrowToInput),
input: NodeInput::value(TaggedValue::DVec2(arrow_to), false),
});
let downstream = document.metadata().downstream_transform_to_viewport(layer);
@@ -106,7 +106,7 @@ impl Circle {
let radius: f64 = if dimensions.x > dimensions.y { dimensions.y / 2. } else { dimensions.x / 2. };
responses.add(NodeGraphMessage::SetInput {
input_connector: InputConnector::node(node_id, 1),
input_connector: InputConnector::node(node_id, graphene_std::vector::generator_nodes::circle::RadiusInput),
input: NodeInput::value(TaggedValue::F64(radius), false),
});
@@ -37,11 +37,11 @@ impl Ellipse {
let radius = ((start - end) / 2. / viewport_zoom(document)).abs();
responses.add(NodeGraphMessage::SetInput {
input_connector: InputConnector::node(node_id, 1),
input_connector: InputConnector::node(node_id, graphene_std::vector::generator_nodes::ellipse::RadiusXInput),
input: NodeInput::value(TaggedValue::F64(radius.x), false),
});
responses.add(NodeGraphMessage::SetInput {
input_connector: InputConnector::node(node_id, 2),
input_connector: InputConnector::node(node_id, graphene_std::vector::generator_nodes::ellipse::RadiusYInput),
input: NodeInput::value(TaggedValue::F64(radius.y), false),
});
responses.add(window_aligned_transform_set(document, layer, start.midpoint(end), DVec2::ONE));
@@ -75,8 +75,8 @@ mod test_ellipse {
let node_graph_layer = NodeGraphLayer::new(layer, &document.network_interface);
let ellipse_node = node_graph_layer.upstream_node_id_from_protonode(ellipse::IDENTIFIER)?;
Some(ResolvedEllipse {
radius_x: instrumented.grab_ranked_input::<ellipse::RadiusXInput>(&vec![ellipse_node], &editor.runtime).unwrap(),
radius_y: instrumented.grab_ranked_input::<ellipse::RadiusYInput>(&vec![ellipse_node], &editor.runtime).unwrap(),
radius_x: instrumented.grab_ranked_input::<ellipse::RadiusXInput, f64>(&vec![ellipse_node], &editor.runtime).unwrap(),
radius_y: instrumented.grab_ranked_input::<ellipse::RadiusYInput, f64>(&vec![ellipse_node], &editor.runtime).unwrap(),
transform: document.metadata().transform_to_document(layer),
})
})
@@ -11,7 +11,6 @@ use crate::messages::tool::common_functionality::shapes::shape_utility::ShapeGiz
use crate::messages::tool::tool_messages::tool_prelude::*;
use graph_craft::document::NodeInput;
use graph_craft::document::value::TaggedValue;
use graphene_std::NodeInputDecleration;
use graphene_std::vector::misc::GridType;
use std::collections::VecDeque;
@@ -119,14 +118,14 @@ impl Grid {
// Set dimensions/spacing
responses.add(NodeGraphMessage::SetInput {
input_connector: InputConnector::node(node_id, SpacingInput::<f64>::INDEX),
input_connector: InputConnector::node(node_id, SpacingInput),
input: NodeInput::value(TaggedValue::DVec2(dimensions), false),
});
// Set angle for isometric grids
if let Some(angle_deg) = angle {
responses.add(NodeGraphMessage::SetInput {
input_connector: InputConnector::node(node_id, AnglesInput::INDEX),
input_connector: InputConnector::node(node_id, AnglesInput),
input: NodeInput::value(TaggedValue::DVec2(DVec2::splat(angle_deg)), false),
});
}
@@ -73,7 +73,7 @@ impl Line {
let line_to = document_points[1] - document_points[0];
responses.add(NodeGraphMessage::SetInput {
input_connector: InputConnector::node(node_id, 1),
input_connector: InputConnector::node(node_id, graphene_std::vector::generator_nodes::line::LineToInput),
input: NodeInput::value(TaggedValue::DVec2(line_to), false),
});
let document_to_viewport = document.metadata().document_to_viewport;
@@ -147,7 +147,7 @@ impl Polygon {
};
responses.add(NodeGraphMessage::SetInput {
input_connector: InputConnector::node(node_id, 2),
input_connector: InputConnector::node(node_id, graphene_std::vector::generator_nodes::regular_polygon::RadiusInput),
input: NodeInput::value(TaggedValue::F64(radius), false),
});
@@ -180,7 +180,7 @@ impl Polygon {
});
responses.add(NodeGraphMessage::SetInput {
input_connector: InputConnector::node(node_id, 1),
input_connector: InputConnector::node(node_id, graphene_std::vector::generator_nodes::regular_polygon::SidesInput),
input: NodeInput::value(TaggedValue::U32(new_dimension), false),
});
responses.add(NodeGraphMessage::RunDocumentGraph);
@@ -37,11 +37,11 @@ impl Rectangle {
let size = ((start - end) / viewport_zoom(document)).abs();
responses.add(NodeGraphMessage::SetInput {
input_connector: InputConnector::node(node_id, 1),
input_connector: InputConnector::node(node_id, graphene_std::vector::generator_nodes::rectangle::WidthInput),
input: NodeInput::value(TaggedValue::F64(size.x), false),
});
responses.add(NodeGraphMessage::SetInput {
input_connector: InputConnector::node(node_id, 2),
input_connector: InputConnector::node(node_id, graphene_std::vector::generator_nodes::rectangle::HeightInput),
input: NodeInput::value(TaggedValue::F64(size.y), false),
});
responses.add(window_aligned_transform_set(document, layer, start.midpoint(end), DVec2::ONE));
@@ -16,7 +16,6 @@ use crate::messages::tool::utility_types::*;
use glam::{DAffine2, DMat2, DVec2};
use graph_craft::document::NodeInput;
use graph_craft::document::value::TaggedValue;
use graphene_std::NodeInputDecleration;
use graphene_std::subpath::{self, Subpath};
use graphene_std::vector::click_target::ClickTargetType;
use graphene_std::vector::misc::{ArcType, GridType, SpiralType, dvec2_to_point};
@@ -211,7 +210,7 @@ pub fn update_radius_sign(end: DVec2, start: DVec2, layer: LayerNodeIdentifier,
let new_layer = NodeGraphLayer::new(layer, &document.network_interface);
if new_layer
.find_input(&DefinitionIdentifier::ProtoNode(graphene_std::vector::generator_nodes::regular_polygon::IDENTIFIER), 1)
.parameter_value(graphene_std::vector::generator_nodes::regular_polygon::SidesInput)
.unwrap_or(&TaggedValue::U32(0))
.to_u32()
% 2 == 1
@@ -221,14 +220,14 @@ pub fn update_radius_sign(end: DVec2, start: DVec2, layer: LayerNodeIdentifier,
};
responses.add(NodeGraphMessage::SetInput {
input_connector: InputConnector::node(polygon_node_id, 2),
input_connector: InputConnector::node(polygon_node_id, graphene_std::vector::generator_nodes::regular_polygon::RadiusInput),
input: NodeInput::value(TaggedValue::F64(sign_num * 0.5), false),
});
return;
}
if new_layer
.find_input(&DefinitionIdentifier::ProtoNode(graphene_std::vector::generator_nodes::star::IDENTIFIER), 1)
.parameter_value(graphene_std::vector::generator_nodes::star::SidesInput)
.unwrap_or(&TaggedValue::U32(0))
.to_u32()
% 2 == 1
@@ -238,11 +237,11 @@ pub fn update_radius_sign(end: DVec2, start: DVec2, layer: LayerNodeIdentifier,
};
responses.add(NodeGraphMessage::SetInput {
input_connector: InputConnector::node(star_node_id, 2),
input_connector: InputConnector::node(star_node_id, graphene_std::vector::generator_nodes::star::Radius1Input),
input: NodeInput::value(TaggedValue::F64(sign_num * 0.5), false),
});
responses.add(NodeGraphMessage::SetInput {
input_connector: InputConnector::node(star_node_id, 3),
input_connector: InputConnector::node(star_node_id, graphene_std::vector::generator_nodes::star::Radius2Input),
input: NodeInput::value(TaggedValue::F64(sign_num * 0.25), false),
});
}
@@ -350,7 +349,7 @@ pub fn extract_arc_parameters(layer: Option<LayerNodeIdentifier>, document: &Doc
pub fn extract_spiral_parameters(layer: LayerNodeIdentifier, document: &DocumentMessageHandler) -> Option<(SpiralType, f64, f64, f64, f64, f64)> {
use graphene_std::vector::generator_nodes::spiral::*;
let node_inputs = NodeGraphLayer::new(layer, &document.network_interface).find_node_inputs(&DefinitionIdentifier::ProtoNode(graphene_std::vector::generator_nodes::spiral::IDENTIFIER))?;
let parameters = NodeGraphLayer::new(layer, &document.network_interface).find_node_parameters(IDENTIFIER)?;
let (
Some(&TaggedValue::SpiralType(spiral_type)),
@@ -360,12 +359,12 @@ pub fn extract_spiral_parameters(layer: LayerNodeIdentifier, document: &Document
Some(&TaggedValue::F64(turns)),
Some(&TaggedValue::F64(angle_resolution)),
) = (
node_inputs.get(SpiralTypeInput::INDEX)?.as_value(),
node_inputs.get(StartAngleInput::INDEX)?.as_value(),
node_inputs.get(InnerRadiusInput::INDEX)?.as_value(),
node_inputs.get(OuterRadiusInput::INDEX)?.as_value(),
node_inputs.get(TurnsInput::INDEX)?.as_value(),
node_inputs.get(AngularResolutionInput::INDEX)?.as_value(),
parameters.value(SpiralTypeInput),
parameters.value(StartAngleInput),
parameters.value(InnerRadiusInput),
parameters.value(OuterRadiusInput),
parameters.value(TurnsInput),
parameters.value(AngularResolutionInput),
)
else {
return None;
@@ -626,14 +625,14 @@ pub fn calculate_arc_text_transform(angle: f64, offset_angle: f64, center: DVec2
pub fn extract_grid_parameters(layer: LayerNodeIdentifier, document: &DocumentMessageHandler) -> Option<(GridType, DVec2, u32, u32, DVec2)> {
use graphene_std::vector::generator_nodes::grid::*;
let node_inputs = NodeGraphLayer::new(layer, &document.network_interface).find_node_inputs(&DefinitionIdentifier::ProtoNode(graphene_std::vector::generator_nodes::grid::IDENTIFIER))?;
let parameters = NodeGraphLayer::new(layer, &document.network_interface).find_node_parameters(IDENTIFIER)?;
let (Some(&TaggedValue::GridType(grid_type)), Some(&TaggedValue::DVec2(spacing)), Some(&TaggedValue::U32(columns)), Some(&TaggedValue::U32(rows)), Some(&TaggedValue::DVec2(angles))) = (
node_inputs.get(GridTypeInput::INDEX)?.as_value(),
node_inputs.get(SpacingInput::<f64>::INDEX)?.as_value(),
node_inputs.get(ColumnsInput::INDEX)?.as_value(),
node_inputs.get(RowsInput::INDEX)?.as_value(),
node_inputs.get(AnglesInput::INDEX)?.as_value(),
parameters.value(GridTypeInput),
parameters.value(SpacingInput),
parameters.value(ColumnsInput),
parameters.value(RowsInput),
parameters.value(AnglesInput),
) else {
return None;
};
@@ -14,7 +14,6 @@ use crate::messages::tool::tool_messages::tool_prelude::*;
use glam::DAffine2;
use graph_craft::document::NodeInput;
use graph_craft::document::value::TaggedValue;
use graphene_std::NodeInputDecleration;
use graphene_std::subpath::{calculate_growth_factor, spiral_point};
use graphene_std::vector::misc::SpiralType;
use std::collections::VecDeque;
@@ -140,12 +139,11 @@ impl Spiral {
return;
};
let Some(node_inputs) = NodeGraphLayer::new(layer, &document.network_interface).find_node_inputs(&DefinitionIdentifier::ProtoNode(graphene_std::vector::generator_nodes::spiral::IDENTIFIER))
else {
let Some(parameters) = NodeGraphLayer::new(layer, &document.network_interface).find_node_parameters(graphene_std::vector::generator_nodes::spiral::IDENTIFIER) else {
return;
};
let Some(&TaggedValue::SpiralType(spiral_type)) = node_inputs.get(SpiralTypeInput::INDEX).unwrap().as_value() else {
let Some(&TaggedValue::SpiralType(spiral_type)) = parameters.value(SpiralTypeInput) else {
return;
};
@@ -157,7 +155,7 @@ impl Spiral {
responses.add(window_aligned_transform_set(document, layer, viewport_drag_start, DVec2::ONE));
responses.add(NodeGraphMessage::SetInput {
input_connector: InputConnector::node(node_id, OuterRadiusInput::INDEX),
input_connector: InputConnector::node(node_id, OuterRadiusInput),
input: NodeInput::value(TaggedValue::F64(new_radius), false),
});
}
@@ -167,8 +165,7 @@ impl Spiral {
pub fn update_turns(decrease: bool, layer: LayerNodeIdentifier, document: &DocumentMessageHandler, responses: &mut VecDeque<Message>) {
use graphene_std::vector::generator_nodes::spiral::*;
let Some(node_inputs) = NodeGraphLayer::new(layer, &document.network_interface).find_node_inputs(&DefinitionIdentifier::ProtoNode(graphene_std::vector::generator_nodes::spiral::IDENTIFIER))
else {
let Some(parameters) = NodeGraphLayer::new(layer, &document.network_interface).find_node_parameters(graphene_std::vector::generator_nodes::spiral::IDENTIFIER) else {
return;
};
@@ -176,7 +173,7 @@ impl Spiral {
return;
};
let Some(&TaggedValue::F64(mut turns)) = node_inputs.get(TurnsInput::INDEX).unwrap().as_value() else {
let Some(&TaggedValue::F64(mut turns)) = parameters.value(TurnsInput) else {
return;
};
@@ -191,7 +188,7 @@ impl Spiral {
});
responses.add(NodeGraphMessage::SetInput {
input_connector: InputConnector::node(node_id, TurnsInput::INDEX),
input_connector: InputConnector::node(node_id, TurnsInput),
input: NodeInput::value(TaggedValue::F64(turns), false),
});
}
@@ -152,12 +152,12 @@ impl Star {
};
responses.add(NodeGraphMessage::SetInput {
input_connector: InputConnector::node(node_id, 2),
input_connector: InputConnector::node(node_id, graphene_std::vector::generator_nodes::star::Radius1Input),
input: NodeInput::value(TaggedValue::F64(radius), false),
});
responses.add(NodeGraphMessage::SetInput {
input_connector: InputConnector::node(node_id, 3),
input_connector: InputConnector::node(node_id, graphene_std::vector::generator_nodes::star::Radius2Input),
input: NodeInput::value(TaggedValue::F64(radius / 2.), false),
});
@@ -3,7 +3,6 @@ use crate::messages::prelude::*;
use crate::messages::tool::common_functionality::color_selector::{DrawingToolState, apply_line_weight};
use crate::messages::tool::common_functionality::graph_modification_utils;
use graph_craft::document::value::TaggedValue;
use graphene_std::NodeInputDecleration;
use graphene_std::choice_type::ChoiceTypeStatic;
use graphene_std::vector::style::{PaintOrder, StrokeAlign, StrokeCap, StrokeJoin};
@@ -189,39 +188,35 @@ where
pub fn apply_stroke_align(drawing: &mut DrawingToolState, align: StrokeAlign, document: &DocumentMessageHandler, responses: &mut VecDeque<Message>) {
drawing.stroke_align = Some(align);
set_stroke_input_for_selected(document, graphene_std::vector::stroke::AlignInput::INDEX, TaggedValue::StrokeAlign(align), responses);
graph_modification_utils::set_parameter_for_selected_layers(document, graphene_std::vector::stroke::AlignInput, TaggedValue::StrokeAlign(align), responses);
}
pub fn apply_stroke_cap(drawing: &mut DrawingToolState, cap: StrokeCap, document: &DocumentMessageHandler, responses: &mut VecDeque<Message>) {
drawing.stroke_cap = Some(cap);
set_stroke_input_for_selected(document, graphene_std::vector::stroke::CapInput::INDEX, TaggedValue::StrokeCap(cap), responses);
graph_modification_utils::set_parameter_for_selected_layers(document, graphene_std::vector::stroke::CapInput, TaggedValue::StrokeCap(cap), responses);
}
pub fn apply_stroke_join(drawing: &mut DrawingToolState, join: StrokeJoin, document: &DocumentMessageHandler, responses: &mut VecDeque<Message>) {
drawing.stroke_join = Some(join);
set_stroke_input_for_selected(document, graphene_std::vector::stroke::JoinInput::INDEX, TaggedValue::StrokeJoin(join), responses);
graph_modification_utils::set_parameter_for_selected_layers(document, graphene_std::vector::stroke::JoinInput, TaggedValue::StrokeJoin(join), responses);
}
pub fn apply_miter_limit(drawing: &mut DrawingToolState, limit: f64, document: &DocumentMessageHandler, responses: &mut VecDeque<Message>) {
drawing.miter_limit = Some(limit);
set_stroke_input_for_selected(document, graphene_std::vector::stroke::MiterLimitInput::INDEX, TaggedValue::F64(limit), responses);
graph_modification_utils::set_parameter_for_selected_layers(document, graphene_std::vector::stroke::MiterLimitInput, TaggedValue::F64(limit), responses);
}
pub fn apply_paint_order(drawing: &mut DrawingToolState, order: PaintOrder, document: &DocumentMessageHandler, responses: &mut VecDeque<Message>) {
drawing.paint_order = Some(order);
set_stroke_input_for_selected(document, graphene_std::vector::stroke::PaintOrderInput::INDEX, TaggedValue::PaintOrder(order), responses);
graph_modification_utils::set_parameter_for_selected_layers(document, graphene_std::vector::stroke::PaintOrderInput, TaggedValue::PaintOrder(order), responses);
}
pub fn apply_dash_lengths(drawing: &mut DrawingToolState, lengths: Vec<f64>, document: &DocumentMessageHandler, responses: &mut VecDeque<Message>) {
drawing.dash_lengths = Some(lengths.clone());
set_stroke_input_for_selected(document, graphene_std::vector::stroke::DashPatternInput::INDEX, TaggedValue::DashPattern(lengths.into()), responses);
graph_modification_utils::set_parameter_for_selected_layers(document, graphene_std::vector::stroke::DashPatternInput, TaggedValue::DashPattern(lengths.into()), responses);
}
pub fn apply_dash_offset(drawing: &mut DrawingToolState, offset: f64, document: &DocumentMessageHandler, responses: &mut VecDeque<Message>) {
drawing.dash_offset = Some(offset);
set_stroke_input_for_selected(document, graphene_std::vector::stroke::DashOffsetInput::INDEX, TaggedValue::F64(offset), responses);
}
fn set_stroke_input_for_selected(document: &DocumentMessageHandler, input_index: usize, value: TaggedValue, responses: &mut VecDeque<Message>) {
graph_modification_utils::set_proto_node_input_for_selected_layers(document, graphene_std::vector::stroke::IDENTIFIER, input_index, value, responses);
graph_modification_utils::set_parameter_for_selected_layers(document, graphene_std::vector::stroke::DashOffsetInput, TaggedValue::F64(offset), responses);
}
@@ -571,7 +571,7 @@ pub fn make_path_editable_is_allowed(network_interface: &mut NodeNetworkInterfac
// Must be a vector layer, at either rank
let node_id = NodeGraphLayer::new(first_layer, network_interface).horizontal_layer_flow().nth(1)?;
let output_type = network_interface.output_type(&OutputConnector::node(node_id, 0), &[]);
let output_type = network_interface.output_type(&OutputConnector::primary_output(node_id), &[]);
if output_type.compiled_nested_type() != Some(&concrete!(List<Vector>)) {
return None;
}
@@ -582,7 +582,7 @@ mod test_artboard {
Err(e) => panic!("Failed to evaluate graph: {e}"),
};
let mut artboards = List::new();
for list in instrumented.grab_all_input_level::<graphene_std::list::extend::NewInput<Artboard>, Artboard>(&editor.runtime) {
for list in instrumented.grab_all_input_level::<graphene_std::list::extend::NewInput, Artboard>(&editor.runtime) {
for index in 0..list.len() {
if let Some(item) = list.clone_item(index) {
artboards.push(item);
@@ -2012,7 +2012,6 @@ mod test_gradient {
pub use crate::test_utils::test_prelude::*;
use glam::DAffine2;
use graph_craft::document::value::TaggedValue;
use graphene_std::NodeInputDecleration;
use graphene_std::color::SRGBA8;
use graphene_std::vector::style::{GradientSpreadMethod, build_transform_with_y_preservation};
use graphene_std::vector::{Gradient, GradientStop, fill};
@@ -2057,18 +2056,18 @@ mod test_gradient {
let fill_node_id = get_fill_node_id_with_direct_fill_input(layer, &document.network_interface)?;
let fill_node = document.network_interface.document_network().nodes.get(&fill_node_id)?;
let stops = match fill_node.inputs.get(fill::FillInput::INDEX)?.as_value()? {
let stops = match fill_node.input(fill::FillInput)?.as_value()? {
TaggedValue::Gradient(stops) => stops.clone(),
_ => return None,
};
let spread_method = match fill_node.inputs.get(fill::SpreadMethodInput::INDEX).and_then(|input| input.as_value()) {
let spread_method = match fill_node.input(fill::SpreadMethodInput).and_then(|input| input.as_value()) {
Some(&TaggedValue::GradientSpreadMethod(value)) => value,
_ => GradientSpreadMethod::default(),
};
let has_transform = matches!(fill_node.inputs.get(fill::HasTransformInput::INDEX).and_then(|input| input.as_value()), Some(&TaggedValue::Bool(true)));
let local_transform = match fill_node.inputs.get(fill::TransformInput::INDEX).and_then(|input| input.as_value()) {
let has_transform = matches!(fill_node.input(fill::HasTransformInput).and_then(|input| input.as_value()), Some(&TaggedValue::Bool(true)));
let local_transform = match fill_node.input(fill::TransformInput).and_then(|input| input.as_value()) {
Some(&TaggedValue::DAffine2(value)) if has_transform => value,
_ => DAffine2::IDENTITY,
};
@@ -2138,8 +2137,8 @@ mod test_gradient {
editor
.handle_message(NodeGraphMessage::CreateWire {
output_connector: OutputConnector::node(gradient_node_id, 0),
input_connector: InputConnector::node(layer.to_node(), 1),
output_connector: OutputConnector::primary_output(gradient_node_id),
input_connector: InputConnector::layer_secondary_input(layer.to_node()),
})
.await;
@@ -2175,8 +2174,8 @@ mod test_gradient {
editor
.handle_message(NodeGraphMessage::CreateWire {
output_connector: OutputConnector::node(gradient_node_id, 0),
input_connector: InputConnector::node(fill_node_id, fill::FillInput::INDEX),
output_connector: OutputConnector::primary_output(gradient_node_id),
input_connector: InputConnector::node(fill_node_id, fill::FillInput),
})
.await;
@@ -2819,8 +2818,8 @@ mod test_gradient {
let gradient_value_id = editor.create_node_by_name(DefinitionIdentifier::ProtoNode(graphene_std::math_nodes::gradient_value::IDENTIFIER)).await;
editor
.handle_message(NodeGraphMessage::CreateWire {
output_connector: OutputConnector::node(gradient_value_id, 0),
input_connector: InputConnector::node(fill_node_id, 1),
output_connector: OutputConnector::primary_output(gradient_value_id),
input_connector: InputConnector::node(fill_node_id, graphene_std::vector::fill::FillInput),
})
.await;
editor
@@ -32,7 +32,7 @@ use graph_craft::document::value::TaggedValue;
use graphene_std::renderer::Quad;
use graphene_std::vector::misc::{ArcType, GridType, SpiralType};
use graphene_std::vector::style::FillChoice;
use graphene_std::{Color, NodeInputDecleration};
use graphene_std::{Color, ParameterRef};
use std::vec;
#[derive(Default, ExtractField)]
@@ -365,15 +365,14 @@ fn sync_shape_options_from_selection(options: &mut ShapeToolOptions, tool_data:
// The rest (Ellipse, Rectangle, Line) just keep `shape_type` in step and rely on the shared Stroke/Fill controls.
match shape_type {
ShapeType::Polygon | ShapeType::Star => {
let id = if shape_type == ShapeType::Polygon { regular_polygon::IDENTIFIER } else { star::IDENTIFIER };
// Both `regular_polygon` and `star` are generic over `T: AsU64`, but the control bar widget always writes `u32`,
// and existing call sites (e.g. `polygon_shape.rs`) read it back as `TaggedValue::U32`.
let index = if shape_type == ShapeType::Polygon {
regular_polygon::SidesInput::<u32>::INDEX
let sides_parameter = if shape_type == ShapeType::Polygon {
ParameterRef::from(regular_polygon::SidesInput)
} else {
star::SidesInput::<u32>::INDEX
ParameterRef::from(star::SidesInput)
};
if let Some(&TaggedValue::U32(sides)) = layer_view.find_input(&proto(id), index)
if let Some(&TaggedValue::U32(sides)) = layer_view.parameter_value(sides_parameter)
&& options.vertices != sides
{
options.vertices = sides;
@@ -381,7 +380,7 @@ fn sync_shape_options_from_selection(options: &mut ShapeToolOptions, tool_data:
}
}
ShapeType::Arc => {
if let Some(&TaggedValue::ArcType(arc_type)) = layer_view.find_input(&proto(arc::IDENTIFIER), arc::ArcTypeInput::INDEX)
if let Some(&TaggedValue::ArcType(arc_type)) = layer_view.parameter_value(arc::ArcTypeInput)
&& options.arc_type != arc_type
{
options.arc_type = arc_type;
@@ -389,13 +388,13 @@ fn sync_shape_options_from_selection(options: &mut ShapeToolOptions, tool_data:
}
}
ShapeType::Spiral => {
if let Some(&TaggedValue::SpiralType(spiral_type)) = layer_view.find_input(&proto(spiral::IDENTIFIER), spiral::SpiralTypeInput::INDEX)
if let Some(&TaggedValue::SpiralType(spiral_type)) = layer_view.parameter_value(spiral::SpiralTypeInput)
&& options.spiral_type != spiral_type
{
options.spiral_type = spiral_type;
changed = true;
}
if let Some(&TaggedValue::F64(turns)) = layer_view.find_input(&proto(spiral::IDENTIFIER), spiral::TurnsInput::INDEX)
if let Some(&TaggedValue::F64(turns)) = layer_view.parameter_value(spiral::TurnsInput)
&& options.turns != turns
{
options.turns = turns;
@@ -403,7 +402,7 @@ fn sync_shape_options_from_selection(options: &mut ShapeToolOptions, tool_data:
}
}
ShapeType::Grid => {
if let Some(&TaggedValue::GridType(grid_type)) = layer_view.find_input(&proto(grid::IDENTIFIER), grid::GridTypeInput::INDEX)
if let Some(&TaggedValue::GridType(grid_type)) = layer_view.parameter_value(grid::GridTypeInput)
&& options.grid_type != grid_type
{
options.grid_type = grid_type;
@@ -411,19 +410,19 @@ fn sync_shape_options_from_selection(options: &mut ShapeToolOptions, tool_data:
}
}
ShapeType::Arrow => {
if let Some(&TaggedValue::F64(shaft)) = layer_view.find_input(&proto(arrow::IDENTIFIER), arrow::ShaftWidthInput::INDEX)
if let Some(&TaggedValue::F64(shaft)) = layer_view.parameter_value(arrow::ShaftWidthInput)
&& options.arrow_shaft_width != shaft
{
options.arrow_shaft_width = shaft;
changed = true;
}
if let Some(&TaggedValue::F64(head_w)) = layer_view.find_input(&proto(arrow::IDENTIFIER), arrow::HeadWidthInput::INDEX)
if let Some(&TaggedValue::F64(head_w)) = layer_view.parameter_value(arrow::HeadWidthInput)
&& options.arrow_head_width != head_w
{
options.arrow_head_width = head_w;
changed = true;
}
if let Some(&TaggedValue::F64(head_l)) = layer_view.find_input(&proto(arrow::IDENTIFIER), arrow::HeadLengthInput::INDEX)
if let Some(&TaggedValue::F64(head_l)) = layer_view.parameter_value(arrow::HeadLengthInput)
&& options.arrow_head_length != head_l
{
options.arrow_head_length = head_l;
@@ -640,47 +639,41 @@ impl<'a> MessageHandler<ToolMessage, &mut ToolActionMessageContext<'a>> for Shap
ShapeOptionsUpdate::Vertices(vertices) => {
self.options.vertices = vertices;
// Push to whichever sides-bearing shape (Polygon or Star) the control bar's `shape_type` currently targets.
// `set_proto_node_input_for_selected_layers` skips selected layers without that proto node, making it a no-op.
let (id, index) = match self.options.shape_type {
ShapeType::Polygon => (regular_polygon::IDENTIFIER, regular_polygon::SidesInput::<u32>::INDEX),
ShapeType::Star => (star::IDENTIFIER, star::SidesInput::<u32>::INDEX),
// `set_parameter_for_selected_layers` skips selected layers without that proto node, making it a no-op.
let sides_parameter = match self.options.shape_type {
ShapeType::Polygon => ParameterRef::from(regular_polygon::SidesInput),
ShapeType::Star => ParameterRef::from(star::SidesInput),
_ => return,
};
graph_modification_utils::set_proto_node_input_for_selected_layers(context.document, id, index, TaggedValue::U32(vertices), responses);
graph_modification_utils::set_parameter_for_selected_layers(context.document, sides_parameter, TaggedValue::U32(vertices), responses);
}
ShapeOptionsUpdate::ArcType(arc_type) => {
self.options.arc_type = arc_type;
graph_modification_utils::set_proto_node_input_for_selected_layers(context.document, arc::IDENTIFIER, arc::ArcTypeInput::INDEX, TaggedValue::ArcType(arc_type), responses);
graph_modification_utils::set_parameter_for_selected_layers(context.document, arc::ArcTypeInput, TaggedValue::ArcType(arc_type), responses);
}
ShapeOptionsUpdate::SpiralType(spiral_type) => {
self.options.spiral_type = spiral_type;
graph_modification_utils::set_proto_node_input_for_selected_layers(
context.document,
spiral::IDENTIFIER,
spiral::SpiralTypeInput::INDEX,
TaggedValue::SpiralType(spiral_type),
responses,
);
graph_modification_utils::set_parameter_for_selected_layers(context.document, spiral::SpiralTypeInput, TaggedValue::SpiralType(spiral_type), responses);
}
ShapeOptionsUpdate::Turns(turns) => {
self.options.turns = turns;
graph_modification_utils::set_proto_node_input_for_selected_layers(context.document, spiral::IDENTIFIER, spiral::TurnsInput::INDEX, TaggedValue::F64(turns), responses);
graph_modification_utils::set_parameter_for_selected_layers(context.document, spiral::TurnsInput, TaggedValue::F64(turns), responses);
}
ShapeOptionsUpdate::GridType(grid_type) => {
self.options.grid_type = grid_type;
graph_modification_utils::set_proto_node_input_for_selected_layers(context.document, grid::IDENTIFIER, grid::GridTypeInput::INDEX, TaggedValue::GridType(grid_type), responses);
graph_modification_utils::set_parameter_for_selected_layers(context.document, grid::GridTypeInput, TaggedValue::GridType(grid_type), responses);
}
ShapeOptionsUpdate::ArrowShaftWidth(shaft_width) => {
self.options.arrow_shaft_width = shaft_width;
graph_modification_utils::set_proto_node_input_for_selected_layers(context.document, arrow::IDENTIFIER, arrow::ShaftWidthInput::INDEX, TaggedValue::F64(shaft_width), responses);
graph_modification_utils::set_parameter_for_selected_layers(context.document, arrow::ShaftWidthInput, TaggedValue::F64(shaft_width), responses);
}
ShapeOptionsUpdate::ArrowHeadWidth(head_width) => {
self.options.arrow_head_width = head_width;
graph_modification_utils::set_proto_node_input_for_selected_layers(context.document, arrow::IDENTIFIER, arrow::HeadWidthInput::INDEX, TaggedValue::F64(head_width), responses);
graph_modification_utils::set_parameter_for_selected_layers(context.document, arrow::HeadWidthInput, TaggedValue::F64(head_width), responses);
}
ShapeOptionsUpdate::ArrowHeadLength(head_length) => {
self.options.arrow_head_length = head_length;
graph_modification_utils::set_proto_node_input_for_selected_layers(context.document, arrow::IDENTIFIER, arrow::HeadLengthInput::INDEX, TaggedValue::F64(head_length), responses);
graph_modification_utils::set_parameter_for_selected_layers(context.document, arrow::HeadLengthInput, TaggedValue::F64(head_length), responses);
}
}
@@ -25,7 +25,7 @@ use graphene_std::color::SRGBA8;
use graphene_std::renderer::Quad;
use graphene_std::text::{Font, TextAlign, TypesettingConfig, lines_clipping};
use graphene_std::vector::style::{FillChoice, FillChoiceUI};
use graphene_std::{Color, NodeInputDecleration};
use graphene_std::{Color, NodeParameter};
#[derive(Default, ExtractField)]
pub struct TextTool {
@@ -546,7 +546,10 @@ impl TextToolData {
responses.add(NodeGraphMessage::SelectedNodesSet { nodes: vec![self.layer.to_node()] });
// Make the rendered text invisible while editing
responses.add(NodeGraphMessage::SetInput {
input_connector: InputConnector::node(graph_modification_utils::get_text_id(self.layer, &document.network_interface).unwrap(), 1),
input_connector: InputConnector::node(
graph_modification_utils::get_text_id(self.layer, &document.network_interface).unwrap(),
graphene_std::text::text::TextInput,
),
input: NodeInput::value(TaggedValue::String("".to_string()), false),
});
responses.add(NodeGraphMessage::RunDocumentGraph);
@@ -883,19 +886,19 @@ impl Fsm for TextToolFsmState {
// TODO: Don't set both max_width and max_height to true at the same time, only do one based on which edge is being dragged (or both if a corner is being dragged)
responses.add(NodeGraphMessage::SetInput {
input_connector: InputConnector::node(node_id, graphene_std::text::text::HasMaxWidthInput::INDEX),
input_connector: InputConnector::node(node_id, graphene_std::text::text::HasMaxWidthInput),
input: NodeInput::value(TaggedValue::Bool(true), false),
});
responses.add(NodeGraphMessage::SetInput {
input_connector: InputConnector::node(node_id, graphene_std::text::text::MaxWidthInput::INDEX),
input_connector: InputConnector::node(node_id, graphene_std::text::text::MaxWidthInput),
input: NodeInput::value(TaggedValue::F64(size_layer.x), false),
});
responses.add(NodeGraphMessage::SetInput {
input_connector: InputConnector::node(node_id, graphene_std::text::text::HasMaxHeightInput::INDEX),
input_connector: InputConnector::node(node_id, graphene_std::text::text::HasMaxHeightInput),
input: NodeInput::value(TaggedValue::Bool(true), false),
});
responses.add(NodeGraphMessage::SetInput {
input_connector: InputConnector::node(node_id, graphene_std::text::text::MaxHeightInput::INDEX),
input_connector: InputConnector::node(node_id, graphene_std::text::text::MaxHeightInput),
input: NodeInput::value(TaggedValue::F64(size_layer.y), false),
});
responses.add(GraphOperationMessage::TransformSet {
@@ -1026,7 +1029,10 @@ impl Fsm for TextToolFsmState {
tool_data.set_editing(false, fonts, responses);
responses.add(NodeGraphMessage::SetInput {
input_connector: InputConnector::node(graph_modification_utils::get_text_id(tool_data.layer, &document.network_interface).unwrap(), 1),
input_connector: InputConnector::node(
graph_modification_utils::get_text_id(tool_data.layer, &document.network_interface).unwrap(),
graphene_std::text::text::TextInput,
),
input: NodeInput::value(TaggedValue::String(tool_data.new_text.clone()), false),
});
responses.add(NodeGraphMessage::RunDocumentGraph);
+19 -34
View File
@@ -14,7 +14,7 @@ use graphene_std::raster::{CPU, Raster};
use graphene_std::renderer::{RenderMetadata, graphic_list_bounding_box};
use graphene_std::transform::Footprint;
use graphene_std::vector::{Vector, graphic_types};
use graphene_std::{ATTR_TRANSFORM, Graphic, NodeInputDecleration};
use graphene_std::{ATTR_TRANSFORM, Graphic};
use interpreted_executor::dynamic_executor::ResolvedDocumentNodeTypesDelta;
use std::any::Any;
use std::sync::Arc;
@@ -623,8 +623,8 @@ impl NodeGraphExecutor {
if fill_transform_unbaked(document, &network_path, fill_node_id) {
let absolute_gradient = gradient.to_absolute(bounding_box, item_transform);
let gradient_transform = absolute_gradient.transform * absolute_gradient.to_transform();
let has_transform_input = InputConnector::node(fill_node_id, graphene_std::vector::fill::HasTransformInput::INDEX);
let transform_input = InputConnector::node(fill_node_id, graphene_std::vector::fill::TransformInput::INDEX);
let has_transform_input = InputConnector::node(fill_node_id, graphene_std::vector::fill::HasTransformInput);
let transform_input = InputConnector::node(fill_node_id, graphene_std::vector::fill::TransformInput);
document
.network_interface
.set_input(&has_transform_input, NodeInput::value(TaggedValue::Bool(true), false), &network_path);
@@ -842,7 +842,7 @@ fn fill_transform_unbaked(document: &DocumentMessageHandler, network_path: &[Nod
};
let Some(node) = network.nodes.get(&fill_node_id) else { return false };
matches!(
node.inputs.get(graphene_std::vector::fill::HasTransformInput::INDEX).and_then(|input| input.as_value()),
node.input(graphene_std::vector::fill::HasTransformInput).and_then(|input| input.as_value()),
Some(TaggedValue::Bool(false))
)
}
@@ -919,7 +919,7 @@ mod test {
use crate::test_utils::test_prelude::{self, NodeGraphLayer};
use graph_craft::ProtoNodeIdentifier;
use graph_craft::document::NodeNetwork;
use graphene_std::NodeInputDecleration;
use graphene_std::{NodeInputDecleration, NodeParameter};
use test_prelude::LayerNodeIdentifier;
/// Stores all of the monitor nodes that have been attached to a graph
@@ -982,17 +982,6 @@ mod test {
instrumented
}
fn downcast<Input: NodeInputDecleration>(dynamic: Arc<dyn std::any::Any + Send + Sync>) -> Option<Input::Result>
where
Input::Result: Send + Sync + Clone + 'static,
{
let element = Self::downcast_record::<Input::Result>(dynamic);
if element.is_none() {
warn!("cannot downcast type for introspection");
}
element
}
/// Our monitor introspects as the recorded value itself, not as an `IORecord` wrapper.
fn downcast_record<Output: Send + Sync + Clone + 'static>(dynamic: Arc<dyn std::any::Any + Send + Sync>) -> Option<Output> {
dynamic.downcast_ref::<Output>().cloned()
@@ -1012,9 +1001,9 @@ mod test {
.filter_map(|dynamic| dynamic.downcast_ref::<List<T>>().cloned())
}
/// Like [`Self::grab_all_input_level`], but downcasting each record to `Output` instead of to the marker's `Result`.
/// Useful when a stored value's recorded form differs from the declared row types the marker's generic accepts.
pub fn grab_all_input_as<'a, Input: NodeInputDecleration + 'a, Output: Send + Sync + Clone + 'static>(&'a self, runtime: &'a NodeRuntime) -> impl Iterator<Item = Output> + 'a {
/// Grab all of the values of the input every time it occurs in the graph, downcast to the recorded `Output` type.
/// A record whose type does not match `Output` is skipped, so a wrong `Output` yields an empty iterator rather than an error.
pub fn grab_all_input<'a, Input: NodeInputDecleration + 'a, Output: Send + Sync + Clone + 'static>(&'a self, runtime: &'a NodeRuntime) -> impl Iterator<Item = Output> + 'a {
self.protonodes_by_name
.get(&Input::identifier())
.map_or([].as_slice(), |x| x.as_slice())
@@ -1024,32 +1013,28 @@ mod test {
.filter_map(Instrumented::downcast_record::<Output>)
}
pub fn grab_protonode_input<Input: NodeInputDecleration>(&self, path: &Vec<NodeId>, runtime: &NodeRuntime) -> Option<Input::Result>
where
Input::Result: Send + Sync + Clone + 'static,
{
pub fn grab_protonode_input<Input: NodeParameter, Output: Send + Sync + Clone + 'static>(&self, path: &Vec<NodeId>, runtime: &NodeRuntime) -> Option<Output> {
let input_monitor_node = self.protonodes_by_path.get(path)?.get(Input::INDEX)?;
let dynamic = runtime.executor.introspect(input_monitor_node).ok()?;
Self::downcast::<Input>(dynamic)
Self::downcast_record::<Output>(dynamic)
}
/// Grabs a ranked input's recorded value as its bare element; our monitor serves a rank-0 input as the element itself.
pub fn grab_ranked_input<Input: NodeInputDecleration>(&self, path: &Vec<NodeId>, runtime: &NodeRuntime) -> Option<Input::Result>
where
Input::Result: Send + Sync + Clone + 'static,
{
self.grab_protonode_input::<Input>(path, runtime)
pub fn grab_ranked_input<Input: NodeParameter, Element: Send + Sync + Clone + 'static>(&self, path: &Vec<NodeId>, runtime: &NodeRuntime) -> Option<Element> {
self.grab_protonode_input::<Input, Element>(path, runtime)
}
pub fn grab_input_from_layer<Input: NodeInputDecleration>(&self, layer: LayerNodeIdentifier, network_interface: &NodeNetworkInterface, runtime: &NodeRuntime) -> Option<Input::Result>
where
Input::Result: Send + Sync + Clone + 'static,
{
pub fn grab_input_from_layer<Input: NodeParameter, Output: Send + Sync + Clone + 'static>(
&self,
layer: LayerNodeIdentifier,
network_interface: &NodeNetworkInterface,
runtime: &NodeRuntime,
) -> Option<Output> {
let node_graph_layer = NodeGraphLayer::new(layer, network_interface);
let node = node_graph_layer.upstream_node_id_from_protonode(Input::identifier())?;
self.grab_protonode_input::<Input>(&vec![node], runtime)
self.grab_protonode_input::<Input, Output>(&vec![node], runtime)
}
}
}
-12
View File
@@ -9,8 +9,6 @@ use crate::node_graph_executor::Instrumented;
use crate::node_graph_executor::NodeRuntime;
use crate::test_utils::test_prelude::LayerNodeIdentifier;
use glam::{DVec2, UVec2};
use graph_craft::document::DocumentNode;
use graphene_std::InputAccessor;
use graphene_std::raster::color::Color;
use graphene_std::uuid::NodeId;
@@ -195,15 +193,6 @@ impl EditorTestUtils {
self.editor.dispatcher.message_handlers.portfolio_message_handler.active_document_mut().unwrap()
}
pub fn get_node<'a, T: InputAccessor<'a, DocumentNode>>(&'a self) -> impl Iterator<Item = T> + 'a {
self.active_document()
.network_interface
.document_network()
.recursive_nodes()
.inspect(|(_, node, _)| println!("{:#?}", node.implementation))
.filter_map(move |(_, document, _)| T::new_with_source(document))
}
pub async fn move_mouse(&mut self, x: f64, y: f64, modifier_keys: ModifierKeys, mouse_keys: MouseKeys) {
let editor_mouse_state = EditorMouseState {
editor_position: ViewportPosition::new(x, y),
@@ -398,7 +387,6 @@ pub mod test_prelude {
pub use graph_craft::document::DocumentNode;
pub use graphene_std::raster::{Color, Image};
pub use graphene_std::transform::Footprint;
pub use graphene_std::{InputAccessor, InputAccessorSource};
#[macro_export]
macro_rules! float_eq {