Port node graph wires to the backend and improve graph UI performance (#2795)
Some checks are pending
Editor: Dev & CI / build (push) Waiting to run
Editor: Dev & CI / cargo-deny (push) Waiting to run

* Improve node graph performance

* Fix wire bugs, add type default for properties.

* Grid aligned wire paths

* remove type source

* node from exposed input

* Refresh wires on preference change

* merge fixes

* Code review

* Fix names

* Code review

* Fix wires on redo/undo

---------

Co-authored-by: Keavon Chambers <keavon@keavon.com>
This commit is contained in:
Adam Gerhant 2025-07-04 22:53:37 -07:00 committed by GitHub
parent 354a68911e
commit f57163c795
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
24 changed files with 2098 additions and 2019 deletions

View file

@ -40,7 +40,6 @@ impl DispatcherMessageHandlers {
/// The last occurrence of the message in the message queue is sufficient to ensure correct behavior. /// The last occurrence of the message in the message queue is sufficient to ensure correct behavior.
/// In addition, these messages do not change any state in the backend (aside from caches). /// In addition, these messages do not change any state in the backend (aside from caches).
const SIDE_EFFECT_FREE_MESSAGES: &[MessageDiscriminant] = &[ const SIDE_EFFECT_FREE_MESSAGES: &[MessageDiscriminant] = &[
MessageDiscriminant::Portfolio(PortfolioMessageDiscriminant::Document(DocumentMessageDiscriminant::NodeGraph(NodeGraphMessageDiscriminant::SendGraph))),
MessageDiscriminant::Portfolio(PortfolioMessageDiscriminant::Document(DocumentMessageDiscriminant::PropertiesPanel( MessageDiscriminant::Portfolio(PortfolioMessageDiscriminant::Document(DocumentMessageDiscriminant::PropertiesPanel(
PropertiesPanelMessageDiscriminant::Refresh, PropertiesPanelMessageDiscriminant::Refresh,
))), ))),

View file

@ -1,6 +1,6 @@
use crate::consts::{VIEWPORT_ZOOM_WHEEL_RATE, VIEWPORT_ZOOM_WHEEL_RATE_CHANGE}; use crate::consts::{VIEWPORT_ZOOM_WHEEL_RATE, VIEWPORT_ZOOM_WHEEL_RATE_CHANGE};
use crate::messages::layout::utility_types::widget_prelude::*; use crate::messages::layout::utility_types::widget_prelude::*;
use crate::messages::portfolio::document::node_graph::utility_types::GraphWireStyle; use crate::messages::portfolio::document::utility_types::wires::GraphWireStyle;
use crate::messages::preferences::SelectionMode; use crate::messages::preferences::SelectionMode;
use crate::messages::prelude::*; use crate::messages::prelude::*;

View file

@ -1,9 +1,10 @@
use super::utility_types::{FrontendDocumentDetails, MouseCursorIcon}; use super::utility_types::{FrontendDocumentDetails, MouseCursorIcon};
use crate::messages::layout::utility_types::widget_prelude::*; use crate::messages::layout::utility_types::widget_prelude::*;
use crate::messages::portfolio::document::node_graph::utility_types::{ use crate::messages::portfolio::document::node_graph::utility_types::{
BoxSelection, ContextMenuInformation, FrontendClickTargets, FrontendGraphInput, FrontendGraphOutput, FrontendNode, FrontendNodeType, FrontendNodeWire, Transform, WirePath, BoxSelection, ContextMenuInformation, FrontendClickTargets, FrontendGraphInput, FrontendGraphOutput, FrontendNode, FrontendNodeType, Transform,
}; };
use crate::messages::portfolio::document::utility_types::nodes::{JsRawBuffer, LayerPanelEntry, RawBuffer}; use crate::messages::portfolio::document::utility_types::nodes::{JsRawBuffer, LayerPanelEntry, RawBuffer};
use crate::messages::portfolio::document::utility_types::wires::{WirePath, WirePathUpdate};
use crate::messages::prelude::*; use crate::messages::prelude::*;
use crate::messages::tool::utility_types::HintData; use crate::messages::tool::utility_types::HintData;
use graph_craft::document::NodeId; use graph_craft::document::NodeId;
@ -250,12 +251,16 @@ pub enum FrontendMessage {
UpdateMouseCursor { UpdateMouseCursor {
cursor: MouseCursorIcon, cursor: MouseCursorIcon,
}, },
UpdateNodeGraph { UpdateNodeGraphNodes {
nodes: Vec<FrontendNode>, nodes: Vec<FrontendNode>,
wires: Vec<FrontendNodeWire>,
#[serde(rename = "wiresDirectNotGridAligned")]
wires_direct_not_grid_aligned: bool,
}, },
UpdateVisibleNodes {
nodes: Vec<NodeId>,
},
UpdateNodeGraphWires {
wires: Vec<WirePathUpdate>,
},
ClearAllNodeGraphWires,
UpdateNodeGraphControlBarLayout { UpdateNodeGraphControlBarLayout {
#[serde(rename = "layoutTarget")] #[serde(rename = "layoutTarget")]
layout_target: LayoutTarget, layout_target: LayoutTarget,

View file

@ -444,6 +444,7 @@ impl MessageHandler<DocumentMessage, DocumentMessageData<'_>> for DocumentMessag
DocumentMessage::EnterNestedNetwork { node_id } => { DocumentMessage::EnterNestedNetwork { node_id } => {
self.breadcrumb_network_path.push(node_id); self.breadcrumb_network_path.push(node_id);
self.selection_network_path.clone_from(&self.breadcrumb_network_path); self.selection_network_path.clone_from(&self.breadcrumb_network_path);
responses.add(NodeGraphMessage::UnloadWires);
responses.add(NodeGraphMessage::SendGraph); responses.add(NodeGraphMessage::SendGraph);
responses.add(DocumentMessage::ZoomCanvasToFitAll); responses.add(DocumentMessage::ZoomCanvasToFitAll);
responses.add(NodeGraphMessage::SetGridAlignedEdges); responses.add(NodeGraphMessage::SetGridAlignedEdges);
@ -473,9 +474,10 @@ impl MessageHandler<DocumentMessage, DocumentMessageData<'_>> for DocumentMessag
self.breadcrumb_network_path.pop(); self.breadcrumb_network_path.pop();
self.selection_network_path.clone_from(&self.breadcrumb_network_path); self.selection_network_path.clone_from(&self.breadcrumb_network_path);
} }
responses.add(NodeGraphMessage::UnloadWires);
responses.add(NodeGraphMessage::SendGraph);
responses.add(DocumentMessage::PTZUpdate); responses.add(DocumentMessage::PTZUpdate);
responses.add(NodeGraphMessage::SetGridAlignedEdges); responses.add(NodeGraphMessage::SetGridAlignedEdges);
responses.add(NodeGraphMessage::SendGraph);
} }
DocumentMessage::FlipSelectedLayers { flip_axis } => { DocumentMessage::FlipSelectedLayers { flip_axis } => {
let scale = match flip_axis { let scale = match flip_axis {
@ -525,6 +527,7 @@ impl MessageHandler<DocumentMessage, DocumentMessageData<'_>> for DocumentMessag
} }
} }
DocumentMessage::GraphViewOverlay { open } => { DocumentMessage::GraphViewOverlay { open } => {
let opened = !self.graph_view_overlay_open && open;
self.graph_view_overlay_open = open; self.graph_view_overlay_open = open;
responses.add(FrontendMessage::UpdateGraphViewOverlay { open }); responses.add(FrontendMessage::UpdateGraphViewOverlay { open });
@ -537,6 +540,9 @@ impl MessageHandler<DocumentMessage, DocumentMessageData<'_>> for DocumentMessag
responses.add(DocumentMessage::RenderRulers); responses.add(DocumentMessage::RenderRulers);
responses.add(DocumentMessage::RenderScrollbars); responses.add(DocumentMessage::RenderScrollbars);
if opened {
responses.add(NodeGraphMessage::UnloadWires);
}
if open { if open {
responses.add(ToolMessage::DeactivateTools); responses.add(ToolMessage::DeactivateTools);
responses.add(OverlaysMessage::Draw); // Clear the overlays responses.add(OverlaysMessage::Draw); // Clear the overlays
@ -1878,6 +1884,7 @@ impl DocumentMessageHandler {
responses.add(NodeGraphMessage::SelectedNodesUpdated); responses.add(NodeGraphMessage::SelectedNodesUpdated);
responses.add(NodeGraphMessage::ForceRunDocumentGraph); responses.add(NodeGraphMessage::ForceRunDocumentGraph);
// TODO: Remove once the footprint is used to load the imports/export distances from the edge // TODO: Remove once the footprint is used to load the imports/export distances from the edge
responses.add(NodeGraphMessage::UnloadWires);
responses.add(NodeGraphMessage::SetGridAlignedEdges); responses.add(NodeGraphMessage::SetGridAlignedEdges);
responses.add(Message::StartBuffer); responses.add(Message::StartBuffer);
Some(previous_network) Some(previous_network)
@ -1909,7 +1916,8 @@ impl DocumentMessageHandler {
responses.add(PortfolioMessage::UpdateOpenDocumentsList); responses.add(PortfolioMessage::UpdateOpenDocumentsList);
responses.add(NodeGraphMessage::SelectedNodesUpdated); responses.add(NodeGraphMessage::SelectedNodesUpdated);
responses.add(NodeGraphMessage::ForceRunDocumentGraph); responses.add(NodeGraphMessage::ForceRunDocumentGraph);
responses.add(NodeGraphMessage::UnloadWires);
responses.add(NodeGraphMessage::SendWires);
Some(previous_network) Some(previous_network)
} }
@ -2066,7 +2074,7 @@ impl DocumentMessageHandler {
/// Loads all of the fonts in the document. /// Loads all of the fonts in the document.
pub fn load_layer_resources(&self, responses: &mut VecDeque<Message>) { pub fn load_layer_resources(&self, responses: &mut VecDeque<Message>) {
let mut fonts = HashSet::new(); let mut fonts = HashSet::new();
for (_node_id, node) in self.document_network().recursive_nodes() { for (_node_id, node, _) in self.document_network().recursive_nodes() {
for input in &node.inputs { for input in &node.inputs {
if let Some(TaggedValue::Font(font)) = input.as_value() { if let Some(TaggedValue::Font(font)) = input.as_value() {
fonts.insert(font.clone()); fonts.insert(font.clone());

View file

@ -5,8 +5,8 @@ use super::node_properties::{self, ParameterWidgetsInfo};
use super::utility_types::FrontendNodeType; use super::utility_types::FrontendNodeType;
use crate::messages::layout::utility_types::widget_prelude::*; use crate::messages::layout::utility_types::widget_prelude::*;
use crate::messages::portfolio::document::utility_types::network_interface::{ use crate::messages::portfolio::document::utility_types::network_interface::{
DocumentNodeMetadata, DocumentNodePersistentMetadata, NodeNetworkInterface, NodeNetworkMetadata, NodeNetworkPersistentMetadata, NodeTemplate, NodeTypePersistentMetadata, NumberInputSettings, DocumentNodeMetadata, DocumentNodePersistentMetadata, InputMetadata, NodeNetworkInterface, NodeNetworkMetadata, NodeNetworkPersistentMetadata, NodeTemplate, NodeTypePersistentMetadata,
PropertiesRow, Vec2InputSettings, WidgetOverride, NumberInputSettings, Vec2InputSettings, WidgetOverride,
}; };
use crate::messages::portfolio::utility_types::PersistentData; use crate::messages::portfolio::utility_types::PersistentData;
use crate::messages::prelude::Message; use crate::messages::prelude::Message;
@ -37,26 +37,14 @@ pub struct NodePropertiesContext<'a> {
impl NodePropertiesContext<'_> { impl NodePropertiesContext<'_> {
pub fn call_widget_override(&mut self, node_id: &NodeId, index: usize) -> Option<Vec<LayoutGroup>> { pub fn call_widget_override(&mut self, node_id: &NodeId, index: usize) -> Option<Vec<LayoutGroup>> {
let input_properties_row = self.network_interface.input_properties_row(node_id, index, self.selection_network_path)?; let input_properties_row = self.network_interface.persistent_input_metadata(node_id, index, self.selection_network_path)?;
if let Some(widget_override) = &input_properties_row.widget_override { if let Some(widget_override) = &input_properties_row.widget_override {
let Some(widget_override_lambda) = INPUT_OVERRIDES.get(widget_override) else { let Some(widget_override_lambda) = INPUT_OVERRIDES.get(widget_override) else {
log::error!("Could not get widget override '{widget_override}' lambda in call_widget_override"); log::error!("Could not get widget override '{widget_override}' lambda in call_widget_override");
return None; return None;
}; };
widget_override_lambda(*node_id, index, self) widget_override_lambda(*node_id, index, self)
.map(|layout_group| { .map_err(|error| log::error!("Error in widget override lambda: {}", error))
let Some(input_properties_row) = self.network_interface.input_properties_row(node_id, index, self.selection_network_path) else {
log::error!("Could not get input properties row in call_widget_override");
return Vec::new();
};
match &input_properties_row.input_data.get("tooltip").and_then(|tooltip| tooltip.as_str()) {
Some(tooltip) => layout_group.into_iter().map(|widget| widget.with_tooltip(*tooltip)).collect::<Vec<_>>(),
_ => layout_group,
}
})
.map_err(|error| {
log::error!("Error in widget override lambda: {}", error);
})
.ok() .ok()
} else { } else {
None None
@ -105,7 +93,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
..Default::default() ..Default::default()
}, },
persistent_node_metadata: DocumentNodePersistentMetadata { persistent_node_metadata: DocumentNodePersistentMetadata {
input_properties: vec![("In", "TODO").into()], input_metadata: vec![("In", "TODO").into()],
output_names: vec!["Out".to_string()], output_names: vec!["Out".to_string()],
..Default::default() ..Default::default()
}, },
@ -126,7 +114,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
..Default::default() ..Default::default()
}, },
persistent_node_metadata: DocumentNodePersistentMetadata { persistent_node_metadata: DocumentNodePersistentMetadata {
input_properties: vec![("In", "TODO").into()], input_metadata: vec![("In", "TODO").into()],
output_names: vec!["Out".to_string()], output_names: vec!["Out".to_string()],
..Default::default() ..Default::default()
}, },
@ -223,7 +211,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
}, },
..Default::default() ..Default::default()
}), }),
input_properties: vec![("Data", "TODO").into()], input_metadata: vec![("Data", "TODO").into()],
output_names: vec!["Data".to_string()], output_names: vec!["Data".to_string()],
..Default::default() ..Default::default()
}, },
@ -285,7 +273,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
..Default::default() ..Default::default()
}, },
persistent_node_metadata: DocumentNodePersistentMetadata { persistent_node_metadata: DocumentNodePersistentMetadata {
input_properties: vec![("Graphical Data", "TODO").into(), ("Over", "TODO").into()], input_metadata: vec![("Graphical Data", "TODO").into(), ("Over", "TODO").into()],
output_names: vec!["Out".to_string()], output_names: vec!["Out".to_string()],
node_type_metadata: NodeTypePersistentMetadata::layer(IVec2::new(0, 0)), node_type_metadata: NodeTypePersistentMetadata::layer(IVec2::new(0, 0)),
network_metadata: Some(NodeNetworkMetadata { network_metadata: Some(NodeNetworkMetadata {
@ -397,10 +385,10 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
..Default::default() ..Default::default()
}, },
persistent_node_metadata: DocumentNodePersistentMetadata { persistent_node_metadata: DocumentNodePersistentMetadata {
input_properties: vec![ input_metadata: vec![
("Artboards", "TODO").into(), ("Artboards", "TODO").into(),
PropertiesRow::with_override("Contents", "TODO", WidgetOverride::Hidden), InputMetadata::with_name_description_override("Contents", "TODO", WidgetOverride::Hidden),
PropertiesRow::with_override( InputMetadata::with_name_description_override(
"Location", "Location",
"TODO", "TODO",
WidgetOverride::Vec2(Vec2InputSettings { WidgetOverride::Vec2(Vec2InputSettings {
@ -410,7 +398,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
..Default::default() ..Default::default()
}), }),
), ),
PropertiesRow::with_override( InputMetadata::with_name_description_override(
"Dimensions", "Dimensions",
"TODO", "TODO",
WidgetOverride::Vec2(Vec2InputSettings { WidgetOverride::Vec2(Vec2InputSettings {
@ -420,7 +408,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
..Default::default() ..Default::default()
}), }),
), ),
PropertiesRow::with_override("Background", "TODO", WidgetOverride::Custom("artboard_background".to_string())), InputMetadata::with_name_description_override("Background", "TODO", WidgetOverride::Custom("artboard_background".to_string())),
("Clip", "TODO").into(), ("Clip", "TODO").into(),
], ],
output_names: vec!["Out".to_string()], output_names: vec!["Out".to_string()],
@ -498,7 +486,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
..Default::default() ..Default::default()
}, },
persistent_node_metadata: DocumentNodePersistentMetadata { persistent_node_metadata: DocumentNodePersistentMetadata {
input_properties: vec![("Empty", "TODO").into(), ("URL", "TODO").into()], input_metadata: vec![("Empty", "TODO").into(), ("URL", "TODO").into()],
output_names: vec!["Image".to_string()], output_names: vec!["Image".to_string()],
network_metadata: Some(NodeNetworkMetadata { network_metadata: Some(NodeNetworkMetadata {
persistent_metadata: NodeNetworkPersistentMetadata { persistent_metadata: NodeNetworkPersistentMetadata {
@ -640,7 +628,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
..Default::default() ..Default::default()
}, },
persistent_node_metadata: DocumentNodePersistentMetadata { persistent_node_metadata: DocumentNodePersistentMetadata {
input_properties: vec![("In", "TODO").into()], input_metadata: vec![("In", "TODO").into()],
output_names: vec!["Canvas".to_string()], output_names: vec!["Canvas".to_string()],
network_metadata: Some(NodeNetworkMetadata { network_metadata: Some(NodeNetworkMetadata {
persistent_metadata: NodeNetworkPersistentMetadata { persistent_metadata: NodeNetworkPersistentMetadata {
@ -740,7 +728,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
..Default::default() ..Default::default()
}, },
persistent_node_metadata: DocumentNodePersistentMetadata { persistent_node_metadata: DocumentNodePersistentMetadata {
input_properties: vec![("Artwork", "TODO").into(), ("Footprint", "TODO").into()], input_metadata: vec![("Artwork", "TODO").into(), ("Footprint", "TODO").into()],
output_names: vec!["Canvas".to_string()], output_names: vec!["Canvas".to_string()],
network_metadata: Some(NodeNetworkMetadata { network_metadata: Some(NodeNetworkMetadata {
persistent_metadata: NodeNetworkPersistentMetadata { persistent_metadata: NodeNetworkPersistentMetadata {
@ -812,23 +800,23 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
..Default::default() ..Default::default()
}, },
persistent_node_metadata: DocumentNodePersistentMetadata { persistent_node_metadata: DocumentNodePersistentMetadata {
input_properties: vec![ input_metadata: vec![
("Spacer", "TODO").into(), ("Spacer", "TODO").into(),
("Clip", "TODO").into(), ("Clip", "TODO").into(),
("Seed", "TODO").into(), ("Seed", "TODO").into(),
PropertiesRow::with_override("Scale", "TODO", WidgetOverride::Custom("noise_properties_scale".to_string())), InputMetadata::with_name_description_override("Scale", "TODO", WidgetOverride::Custom("noise_properties_scale".to_string())),
PropertiesRow::with_override("Noise Type", "TODO", WidgetOverride::Custom("noise_properties_noise_type".to_string())), InputMetadata::with_name_description_override("Noise Type", "TODO", WidgetOverride::Custom("noise_properties_noise_type".to_string())),
PropertiesRow::with_override("Domain Warp Type", "TODO", WidgetOverride::Custom("noise_properties_domain_warp_type".to_string())), InputMetadata::with_name_description_override("Domain Warp Type", "TODO", WidgetOverride::Custom("noise_properties_domain_warp_type".to_string())),
PropertiesRow::with_override("Domain Warp Amplitude", "TODO", WidgetOverride::Custom("noise_properties_domain_warp_amplitude".to_string())), InputMetadata::with_name_description_override("Domain Warp Amplitude", "TODO", WidgetOverride::Custom("noise_properties_domain_warp_amplitude".to_string())),
PropertiesRow::with_override("Fractal Type", "TODO", WidgetOverride::Custom("noise_properties_fractal_type".to_string())), InputMetadata::with_name_description_override("Fractal Type", "TODO", WidgetOverride::Custom("noise_properties_fractal_type".to_string())),
PropertiesRow::with_override("Fractal Octaves", "TODO", WidgetOverride::Custom("noise_properties_fractal_octaves".to_string())), InputMetadata::with_name_description_override("Fractal Octaves", "TODO", WidgetOverride::Custom("noise_properties_fractal_octaves".to_string())),
PropertiesRow::with_override("Fractal Lacunarity", "TODO", WidgetOverride::Custom("noise_properties_fractal_lacunarity".to_string())), InputMetadata::with_name_description_override("Fractal Lacunarity", "TODO", WidgetOverride::Custom("noise_properties_fractal_lacunarity".to_string())),
PropertiesRow::with_override("Fractal Gain", "TODO", WidgetOverride::Custom("noise_properties_fractal_gain".to_string())), InputMetadata::with_name_description_override("Fractal Gain", "TODO", WidgetOverride::Custom("noise_properties_fractal_gain".to_string())),
PropertiesRow::with_override("Fractal Weighted Strength", "TODO", WidgetOverride::Custom("noise_properties_fractal_weighted_strength".to_string())), InputMetadata::with_name_description_override("Fractal Weighted Strength", "TODO", WidgetOverride::Custom("noise_properties_fractal_weighted_strength".to_string())),
PropertiesRow::with_override("Fractal Ping Pong Strength", "TODO", WidgetOverride::Custom("noise_properties_ping_pong_strength".to_string())), InputMetadata::with_name_description_override("Fractal Ping Pong Strength", "TODO", WidgetOverride::Custom("noise_properties_ping_pong_strength".to_string())),
PropertiesRow::with_override("Cellular Distance Function", "TODO", WidgetOverride::Custom("noise_properties_cellular_distance_function".to_string())), InputMetadata::with_name_description_override("Cellular Distance Function", "TODO", WidgetOverride::Custom("noise_properties_cellular_distance_function".to_string())),
PropertiesRow::with_override("Cellular Return Type", "TODO", WidgetOverride::Custom("noise_properties_cellular_return_type".to_string())), InputMetadata::with_name_description_override("Cellular Return Type", "TODO", WidgetOverride::Custom("noise_properties_cellular_return_type".to_string())),
PropertiesRow::with_override("Cellular Jitter", "TODO", WidgetOverride::Custom("noise_properties_cellular_jitter".to_string())), InputMetadata::with_name_description_override("Cellular Jitter", "TODO", WidgetOverride::Custom("noise_properties_cellular_jitter".to_string())),
], ],
output_names: vec!["Image".to_string()], output_names: vec!["Image".to_string()],
..Default::default() ..Default::default()
@ -897,7 +885,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
..Default::default() ..Default::default()
}, },
persistent_node_metadata: DocumentNodePersistentMetadata { persistent_node_metadata: DocumentNodePersistentMetadata {
input_properties: vec![("Image", "TODO").into()], input_metadata: vec![("Image", "TODO").into()],
output_names: vec!["Red".to_string(), "Green".to_string(), "Blue".to_string(), "Alpha".to_string()], output_names: vec!["Red".to_string(), "Green".to_string(), "Blue".to_string(), "Alpha".to_string()],
has_primary_output: false, has_primary_output: false,
network_metadata: Some(NodeNetworkMetadata { network_metadata: Some(NodeNetworkMetadata {
@ -982,7 +970,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
..Default::default() ..Default::default()
}, },
persistent_node_metadata: DocumentNodePersistentMetadata { persistent_node_metadata: DocumentNodePersistentMetadata {
input_properties: vec![("Coordinate", "TODO").into()], input_metadata: vec![("Coordinate", "TODO").into()],
output_names: vec!["X".to_string(), "Y".to_string()], output_names: vec!["X".to_string(), "Y".to_string()],
has_primary_output: false, has_primary_output: false,
network_metadata: Some(NodeNetworkMetadata { network_metadata: Some(NodeNetworkMetadata {
@ -1053,7 +1041,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
..Default::default() ..Default::default()
}, },
persistent_node_metadata: DocumentNodePersistentMetadata { persistent_node_metadata: DocumentNodePersistentMetadata {
input_properties: vec![("Background", "TODO").into(), ("Trace", "TODO").into(), ("Cache", "TODO").into()], input_metadata: vec![("Background", "TODO").into(), ("Trace", "TODO").into(), ("Cache", "TODO").into()],
output_names: vec!["Image".to_string()], output_names: vec!["Image".to_string()],
network_metadata: Some(NodeNetworkMetadata { network_metadata: Some(NodeNetworkMetadata {
persistent_metadata: NodeNetworkPersistentMetadata { persistent_metadata: NodeNetworkPersistentMetadata {
@ -1090,7 +1078,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
..Default::default() ..Default::default()
}, },
persistent_node_metadata: DocumentNodePersistentMetadata { persistent_node_metadata: DocumentNodePersistentMetadata {
input_properties: vec![("Image", "TODO").into()], input_metadata: vec![("Image", "TODO").into()],
output_names: vec!["Image".to_string()], output_names: vec!["Image".to_string()],
..Default::default() ..Default::default()
}, },
@ -1109,7 +1097,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
..Default::default() ..Default::default()
}, },
persistent_node_metadata: DocumentNodePersistentMetadata { persistent_node_metadata: DocumentNodePersistentMetadata {
input_properties: vec![("Image", "TODO").into()], input_metadata: vec![("Image", "TODO").into()],
output_names: vec!["Image".to_string()], output_names: vec!["Image".to_string()],
..Default::default() ..Default::default()
}, },
@ -1152,7 +1140,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
..Default::default() ..Default::default()
}, },
persistent_node_metadata: DocumentNodePersistentMetadata { persistent_node_metadata: DocumentNodePersistentMetadata {
input_properties: vec![("In", "TODO").into()], input_metadata: vec![("In", "TODO").into()],
output_names: vec!["Storage".to_string()], output_names: vec!["Storage".to_string()],
network_metadata: Some(NodeNetworkMetadata { network_metadata: Some(NodeNetworkMetadata {
persistent_metadata: NodeNetworkPersistentMetadata { persistent_metadata: NodeNetworkPersistentMetadata {
@ -1231,7 +1219,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
..Default::default() ..Default::default()
}, },
persistent_node_metadata: DocumentNodePersistentMetadata { persistent_node_metadata: DocumentNodePersistentMetadata {
input_properties: vec![("In", "TODO").into(), ("In", "TODO").into()], input_metadata: vec![("In", "TODO").into(), ("In", "TODO").into()],
output_names: vec!["Output Buffer".to_string()], output_names: vec!["Output Buffer".to_string()],
network_metadata: Some(NodeNetworkMetadata { network_metadata: Some(NodeNetworkMetadata {
persistent_metadata: NodeNetworkPersistentMetadata { persistent_metadata: NodeNetworkPersistentMetadata {
@ -1378,7 +1366,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
..Default::default() ..Default::default()
}, },
persistent_node_metadata: DocumentNodePersistentMetadata { persistent_node_metadata: DocumentNodePersistentMetadata {
input_properties: vec![("In", "TODO").into()], input_metadata: vec![("In", "TODO").into()],
output_names: vec!["Texture".to_string()], output_names: vec!["Texture".to_string()],
network_metadata: Some(NodeNetworkMetadata { network_metadata: Some(NodeNetworkMetadata {
persistent_metadata: NodeNetworkPersistentMetadata { persistent_metadata: NodeNetworkPersistentMetadata {
@ -1432,7 +1420,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
..Default::default() ..Default::default()
}, },
persistent_node_metadata: DocumentNodePersistentMetadata { persistent_node_metadata: DocumentNodePersistentMetadata {
input_properties: vec![("Node", "TODO").into()], input_metadata: vec![("Node", "TODO").into()],
output_names: vec!["Document Node".to_string()], output_names: vec!["Document Node".to_string()],
..Default::default() ..Default::default()
}, },
@ -1503,7 +1491,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
..Default::default() ..Default::default()
}, },
persistent_node_metadata: DocumentNodePersistentMetadata { persistent_node_metadata: DocumentNodePersistentMetadata {
input_properties: vec![("Vector Data", "TODO").into(), ("Modification", "TODO").into()], input_metadata: vec![("Vector Data", "TODO").into(), ("Modification", "TODO").into()],
output_names: vec!["Vector Data".to_string()], output_names: vec!["Vector Data".to_string()],
network_metadata: Some(NodeNetworkMetadata { network_metadata: Some(NodeNetworkMetadata {
persistent_metadata: NodeNetworkPersistentMetadata { persistent_metadata: NodeNetworkPersistentMetadata {
@ -1563,11 +1551,11 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
..Default::default() ..Default::default()
}, },
persistent_node_metadata: DocumentNodePersistentMetadata { persistent_node_metadata: DocumentNodePersistentMetadata {
input_properties: vec![ input_metadata: vec![
("Editor API", "TODO").into(), ("Editor API", "TODO").into(),
PropertiesRow::with_override("Text", "TODO", WidgetOverride::Custom("text_area".to_string())), InputMetadata::with_name_description_override("Text", "TODO", WidgetOverride::Custom("text_area".to_string())),
PropertiesRow::with_override("Font", "TODO", WidgetOverride::Custom("text_font".to_string())), InputMetadata::with_name_description_override("Font", "TODO", WidgetOverride::Custom("text_font".to_string())),
PropertiesRow::with_override( InputMetadata::with_name_description_override(
"Size", "Size",
"TODO", "TODO",
WidgetOverride::Number(NumberInputSettings { WidgetOverride::Number(NumberInputSettings {
@ -1576,7 +1564,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
..Default::default() ..Default::default()
}), }),
), ),
PropertiesRow::with_override( InputMetadata::with_name_description_override(
"Line Height", "Line Height",
"TODO", "TODO",
WidgetOverride::Number(NumberInputSettings { WidgetOverride::Number(NumberInputSettings {
@ -1586,7 +1574,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
..Default::default() ..Default::default()
}), }),
), ),
PropertiesRow::with_override( InputMetadata::with_name_description_override(
"Character Spacing", "Character Spacing",
"TODO", "TODO",
WidgetOverride::Number(NumberInputSettings { WidgetOverride::Number(NumberInputSettings {
@ -1596,7 +1584,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
..Default::default() ..Default::default()
}), }),
), ),
PropertiesRow::with_override( InputMetadata::with_name_description_override(
"Max Width", "Max Width",
"TODO", "TODO",
WidgetOverride::Number(NumberInputSettings { WidgetOverride::Number(NumberInputSettings {
@ -1606,7 +1594,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
..Default::default() ..Default::default()
}), }),
), ),
PropertiesRow::with_override( InputMetadata::with_name_description_override(
"Max Height", "Max Height",
"TODO", "TODO",
WidgetOverride::Number(NumberInputSettings { WidgetOverride::Number(NumberInputSettings {
@ -1616,7 +1604,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
..Default::default() ..Default::default()
}), }),
), ),
PropertiesRow::with_override( InputMetadata::with_name_description_override(
"Tilt", "Tilt",
"Faux italic", "Faux italic",
WidgetOverride::Number(NumberInputSettings { WidgetOverride::Number(NumberInputSettings {
@ -1708,9 +1696,9 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
}, },
..Default::default() ..Default::default()
}), }),
input_properties: vec![ input_metadata: vec![
("Vector Data", "TODO").into(), ("Vector Data", "TODO").into(),
PropertiesRow::with_override( InputMetadata::with_name_description_override(
"Translation", "Translation",
"TODO", "TODO",
WidgetOverride::Vec2(Vec2InputSettings { WidgetOverride::Vec2(Vec2InputSettings {
@ -1720,8 +1708,8 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
..Default::default() ..Default::default()
}), }),
), ),
PropertiesRow::with_override("Rotation", "TODO", WidgetOverride::Custom("transform_rotation".to_string())), InputMetadata::with_name_description_override("Rotation", "TODO", WidgetOverride::Custom("transform_rotation".to_string())),
PropertiesRow::with_override( InputMetadata::with_name_description_override(
"Scale", "Scale",
"TODO", "TODO",
WidgetOverride::Vec2(Vec2InputSettings { WidgetOverride::Vec2(Vec2InputSettings {
@ -1731,8 +1719,8 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
..Default::default() ..Default::default()
}), }),
), ),
PropertiesRow::with_override("Skew", "TODO", WidgetOverride::Custom("transform_skew".to_string())), InputMetadata::with_name_description_override("Skew", "TODO", WidgetOverride::Custom("transform_skew".to_string())),
PropertiesRow::with_override("Pivot", "TODO", WidgetOverride::Hidden), InputMetadata::with_name_description_override("Pivot", "TODO", WidgetOverride::Hidden),
], ],
output_names: vec!["Data".to_string()], output_names: vec!["Data".to_string()],
..Default::default() ..Default::default()
@ -1831,7 +1819,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
}, },
..Default::default() ..Default::default()
}), }),
input_properties: vec![("Group of Paths", "TODO").into(), ("Operation", "TODO").into()], input_metadata: vec![("Group of Paths", "TODO").into(), ("Operation", "TODO").into()],
output_names: vec!["Vector".to_string()], output_names: vec!["Vector".to_string()],
..Default::default() ..Default::default()
}, },
@ -1957,10 +1945,10 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
}, },
..Default::default() ..Default::default()
}), }),
input_properties: vec![ input_metadata: vec![
("Vector Data", "The shape to be resampled and converted into a polyline.").into(), ("Vector Data", "The shape to be resampled and converted into a polyline.").into(),
Into::<PropertiesRow>::into(("Spacing", node_properties::SAMPLE_POLYLINE_TOOLTIP_SPACING)), ("Spacing", node_properties::SAMPLE_POLYLINE_TOOLTIP_SPACING).into(),
PropertiesRow::with_override( InputMetadata::with_name_description_override(
"Separation", "Separation",
node_properties::SAMPLE_POLYLINE_TOOLTIP_SEPARATION, node_properties::SAMPLE_POLYLINE_TOOLTIP_SEPARATION,
WidgetOverride::Number(NumberInputSettings { WidgetOverride::Number(NumberInputSettings {
@ -1969,7 +1957,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
..Default::default() ..Default::default()
}), }),
), ),
PropertiesRow::with_override( InputMetadata::with_name_description_override(
"Quantity", "Quantity",
node_properties::SAMPLE_POLYLINE_TOOLTIP_QUANTITY, node_properties::SAMPLE_POLYLINE_TOOLTIP_QUANTITY,
WidgetOverride::Number(NumberInputSettings { WidgetOverride::Number(NumberInputSettings {
@ -1978,7 +1966,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
..Default::default() ..Default::default()
}), }),
), ),
PropertiesRow::with_override( InputMetadata::with_name_description_override(
"Start Offset", "Start Offset",
node_properties::SAMPLE_POLYLINE_TOOLTIP_START_OFFSET, node_properties::SAMPLE_POLYLINE_TOOLTIP_START_OFFSET,
WidgetOverride::Number(NumberInputSettings { WidgetOverride::Number(NumberInputSettings {
@ -1987,7 +1975,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
..Default::default() ..Default::default()
}), }),
), ),
PropertiesRow::with_override( InputMetadata::with_name_description_override(
"Stop Offset", "Stop Offset",
node_properties::SAMPLE_POLYLINE_TOOLTIP_STOP_OFFSET, node_properties::SAMPLE_POLYLINE_TOOLTIP_STOP_OFFSET,
WidgetOverride::Number(NumberInputSettings { WidgetOverride::Number(NumberInputSettings {
@ -1996,7 +1984,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
..Default::default() ..Default::default()
}), }),
), ),
Into::<PropertiesRow>::into(("Adaptive Spacing", node_properties::SAMPLE_POLYLINE_TOOLTIP_ADAPTIVE_SPACING)), ("Adaptive Spacing", node_properties::SAMPLE_POLYLINE_TOOLTIP_ADAPTIVE_SPACING).into(),
], ],
output_names: vec!["Vector".to_string()], output_names: vec!["Vector".to_string()],
..Default::default() ..Default::default()
@ -2100,9 +2088,9 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
}, },
..Default::default() ..Default::default()
}), }),
input_properties: vec![ input_metadata: vec![
("Vector Data", "TODO").into(), ("Vector Data", "TODO").into(),
PropertiesRow::with_override( InputMetadata::with_name_description_override(
"Separation Disk Diameter", "Separation Disk Diameter",
"TODO", "TODO",
WidgetOverride::Number(NumberInputSettings { WidgetOverride::Number(NumberInputSettings {
@ -2113,7 +2101,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
..Default::default() ..Default::default()
}), }),
), ),
PropertiesRow::with_override( InputMetadata::with_name_description_override(
"Seed", "Seed",
"TODO", "TODO",
WidgetOverride::Number(NumberInputSettings { WidgetOverride::Number(NumberInputSettings {
@ -2155,7 +2143,7 @@ fn static_node_properties() -> NodeProperties {
map.insert("sample_polyline_properties".to_string(), Box::new(node_properties::sample_polyline_properties)); map.insert("sample_polyline_properties".to_string(), Box::new(node_properties::sample_polyline_properties));
map.insert( map.insert(
"identity_properties".to_string(), "identity_properties".to_string(),
Box::new(|_node_id, _context| node_properties::string_properties("The identity node simply passes its data through.")), Box::new(|_node_id, _context| node_properties::string_properties("The identity node passes its data through.")),
); );
map.insert( map.insert(
"monitor_properties".to_string(), "monitor_properties".to_string(),
@ -2175,7 +2163,7 @@ fn static_input_properties() -> InputProperties {
map.insert( map.insert(
"string".to_string(), "string".to_string(),
Box::new(|node_id, index, context| { Box::new(|node_id, index, context| {
let Some(value) = context.network_interface.input_metadata(&node_id, index, "string_properties", context.selection_network_path) else { let Some(value) = context.network_interface.input_data(&node_id, index, "string_properties", context.selection_network_path) else {
return Err(format!("Could not get string properties for node {}", node_id)); return Err(format!("Could not get string properties for node {}", node_id));
}; };
let Some(string) = value.as_str() else { let Some(string) = value.as_str() else {
@ -2187,37 +2175,36 @@ fn static_input_properties() -> InputProperties {
map.insert( map.insert(
"number".to_string(), "number".to_string(),
Box::new(|node_id, index, context| { Box::new(|node_id, index, context| {
let (document_node, input_name, input_description) = node_properties::query_node_and_input_info(node_id, index, context)?;
let mut number_input = NumberInput::default(); let mut number_input = NumberInput::default();
if let Some(unit) = context if let Some(unit) = context
.network_interface .network_interface
.input_metadata(&node_id, index, "unit", context.selection_network_path) .input_data(&node_id, index, "unit", context.selection_network_path)
.and_then(|value| value.as_str()) .and_then(|value| value.as_str())
{ {
number_input = number_input.unit(unit); number_input = number_input.unit(unit);
} }
if let Some(min) = context if let Some(min) = context
.network_interface .network_interface
.input_metadata(&node_id, index, "min", context.selection_network_path) .input_data(&node_id, index, "min", context.selection_network_path)
.and_then(|value| value.as_f64()) .and_then(|value| value.as_f64())
{ {
number_input = number_input.min(min); number_input = number_input.min(min);
} }
if let Some(max) = context if let Some(max) = context
.network_interface .network_interface
.input_metadata(&node_id, index, "max", context.selection_network_path) .input_data(&node_id, index, "max", context.selection_network_path)
.and_then(|value| value.as_f64()) .and_then(|value| value.as_f64())
{ {
number_input = number_input.max(max); number_input = number_input.max(max);
} }
if let Some(step) = context if let Some(step) = context
.network_interface .network_interface
.input_metadata(&node_id, index, "step", context.selection_network_path) .input_data(&node_id, index, "step", context.selection_network_path)
.and_then(|value| value.as_f64()) .and_then(|value| value.as_f64())
{ {
number_input = number_input.step(step); number_input = number_input.step(step);
} }
if let Some(mode) = context.network_interface.input_metadata(&node_id, index, "mode", context.selection_network_path).map(|value| { if let Some(mode) = context.network_interface.input_data(&node_id, index, "mode", context.selection_network_path).map(|value| {
let mode: NumberInputMode = serde_json::from_value(value.clone()).unwrap(); let mode: NumberInputMode = serde_json::from_value(value.clone()).unwrap();
mode mode
}) { }) {
@ -2225,87 +2212,83 @@ fn static_input_properties() -> InputProperties {
} }
if let Some(range_min) = context if let Some(range_min) = context
.network_interface .network_interface
.input_metadata(&node_id, index, "range_min", context.selection_network_path) .input_data(&node_id, index, "range_min", context.selection_network_path)
.and_then(|value| value.as_f64()) .and_then(|value| value.as_f64())
{ {
number_input = number_input.range_min(Some(range_min)); number_input = number_input.range_min(Some(range_min));
} }
if let Some(range_max) = context if let Some(range_max) = context
.network_interface .network_interface
.input_metadata(&node_id, index, "range_max", context.selection_network_path) .input_data(&node_id, index, "range_max", context.selection_network_path)
.and_then(|value| value.as_f64()) .and_then(|value| value.as_f64())
{ {
number_input = number_input.range_max(Some(range_max)); number_input = number_input.range_max(Some(range_max));
} }
if let Some(is_integer) = context if let Some(is_integer) = context
.network_interface .network_interface
.input_metadata(&node_id, index, "is_integer", context.selection_network_path) .input_data(&node_id, index, "is_integer", context.selection_network_path)
.and_then(|value| value.as_bool()) .and_then(|value| value.as_bool())
{ {
number_input = number_input.is_integer(is_integer); number_input = number_input.is_integer(is_integer);
} }
let blank_assist = context let blank_assist = context
.network_interface .network_interface
.input_metadata(&node_id, index, "blank_assist", context.selection_network_path) .input_data(&node_id, index, "blank_assist", context.selection_network_path)
.and_then(|value| value.as_bool()) .and_then(|value| value.as_bool())
.unwrap_or_else(|| { .unwrap_or_else(|| {
log::error!("Could not get blank assist when displaying number input for node {node_id}, index {index}"); log::error!("Could not get blank assist when displaying number input for node {node_id}, index {index}");
true true
}); });
Ok(vec![LayoutGroup::Row { Ok(vec![LayoutGroup::Row {
widgets: node_properties::number_widget(ParameterWidgetsInfo::new(document_node, node_id, index, input_name, input_description, blank_assist), number_input), widgets: node_properties::number_widget(ParameterWidgetsInfo::new(node_id, index, blank_assist, context), number_input),
}]) }])
}), }),
); );
map.insert( map.insert(
"vec2".to_string(), "vec2".to_string(),
Box::new(|node_id, index, context| { Box::new(|node_id, index, context| {
let (document_node, input_name, input_description) = node_properties::query_node_and_input_info(node_id, index, context)?;
let x = context let x = context
.network_interface .network_interface
.input_metadata(&node_id, index, "x", context.selection_network_path) .input_data(&node_id, index, "x", context.selection_network_path)
.and_then(|value| value.as_str()) .and_then(|value| value.as_str())
.unwrap_or_else(|| { .unwrap_or_else(|| {
log::error!("Could not get x for vec2 input"); log::error!("Could not get x for vec2 input");
"" ""
}); })
.to_string();
let y = context let y = context
.network_interface .network_interface
.input_metadata(&node_id, index, "y", context.selection_network_path) .input_data(&node_id, index, "y", context.selection_network_path)
.and_then(|value| value.as_str()) .and_then(|value| value.as_str())
.unwrap_or_else(|| { .unwrap_or_else(|| {
log::error!("Could not get y for vec2 input"); log::error!("Could not get y for vec2 input");
"" ""
}); })
.to_string();
let unit = context let unit = context
.network_interface .network_interface
.input_metadata(&node_id, index, "unit", context.selection_network_path) .input_data(&node_id, index, "unit", context.selection_network_path)
.and_then(|value| value.as_str()) .and_then(|value| value.as_str())
.unwrap_or_else(|| { .unwrap_or_else(|| {
log::error!("Could not get unit for vec2 input"); log::error!("Could not get unit for vec2 input");
"" ""
}); })
.to_string();
let min = context let min = context
.network_interface .network_interface
.input_metadata(&node_id, index, "min", context.selection_network_path) .input_data(&node_id, index, "min", context.selection_network_path)
.and_then(|value| value.as_f64()); .and_then(|value| value.as_f64());
Ok(vec![node_properties::coordinate_widget( Ok(vec![node_properties::coordinate_widget(ParameterWidgetsInfo::new(node_id, index, true, context), &x, &y, &unit, min)])
ParameterWidgetsInfo::new(document_node, node_id, index, input_name, input_description, true),
x,
y,
unit,
min,
)])
}), }),
); );
map.insert( map.insert(
"noise_properties_scale".to_string(), "noise_properties_scale".to_string(),
Box::new(|node_id, index, context| { Box::new(|node_id, index, context| {
let (document_node, input_name, input_description) = node_properties::query_node_and_input_info(node_id, index, context)?;
let (_, coherent_noise_active, _, _, _, _) = node_properties::query_noise_pattern_state(node_id, context)?; let (_, coherent_noise_active, _, _, _, _) = node_properties::query_noise_pattern_state(node_id, context)?;
let scale = node_properties::number_widget( let scale = node_properties::number_widget(
ParameterWidgetsInfo::new(document_node, node_id, index, input_name, input_description, true), ParameterWidgetsInfo::new(node_id, index, true, context),
NumberInput::default().min(0.).disabled(!coherent_noise_active), NumberInput::default().min(0.).disabled(!coherent_noise_active),
); );
Ok(vec![scale.into()]) Ok(vec![scale.into()])
@ -2314,20 +2297,16 @@ fn static_input_properties() -> InputProperties {
map.insert( map.insert(
"noise_properties_noise_type".to_string(), "noise_properties_noise_type".to_string(),
Box::new(|node_id, index, context| { Box::new(|node_id, index, context| {
let (document_node, input_name, input_description) = node_properties::query_node_and_input_info(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::new(document_node, node_id, index, input_name, input_description, true))
.property_row();
Ok(vec![noise_type_row, LayoutGroup::Row { widgets: Vec::new() }]) Ok(vec![noise_type_row, LayoutGroup::Row { widgets: Vec::new() }])
}), }),
); );
map.insert( map.insert(
"noise_properties_domain_warp_type".to_string(), "noise_properties_domain_warp_type".to_string(),
Box::new(|node_id, index, context| { Box::new(|node_id, index, context| {
let (document_node, input_name, input_description) = node_properties::query_node_and_input_info(node_id, index, context)?;
let (_, coherent_noise_active, _, _, _, _) = node_properties::query_noise_pattern_state(node_id, context)?; let (_, coherent_noise_active, _, _, _, _) = node_properties::query_noise_pattern_state(node_id, context)?;
let domain_warp_type = enum_choice::<DomainWarpType>() let domain_warp_type = enum_choice::<DomainWarpType>()
.for_socket(ParameterWidgetsInfo::new(document_node, node_id, index, input_name, input_description, true)) .for_socket(ParameterWidgetsInfo::new(node_id, index, true, context))
.disabled(!coherent_noise_active) .disabled(!coherent_noise_active)
.property_row(); .property_row();
Ok(vec![domain_warp_type]) Ok(vec![domain_warp_type])
@ -2336,10 +2315,9 @@ fn static_input_properties() -> InputProperties {
map.insert( map.insert(
"noise_properties_domain_warp_amplitude".to_string(), "noise_properties_domain_warp_amplitude".to_string(),
Box::new(|node_id, index, context| { Box::new(|node_id, index, context| {
let (document_node, input_name, input_description) = node_properties::query_node_and_input_info(node_id, index, context)?;
let (_, coherent_noise_active, _, _, domain_warp_active, _) = node_properties::query_noise_pattern_state(node_id, 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( let domain_warp_amplitude = node_properties::number_widget(
ParameterWidgetsInfo::new(document_node, node_id, index, input_name, input_description, true), ParameterWidgetsInfo::new(node_id, index, true, context),
NumberInput::default().min(0.).disabled(!coherent_noise_active || !domain_warp_active), NumberInput::default().min(0.).disabled(!coherent_noise_active || !domain_warp_active),
); );
Ok(vec![domain_warp_amplitude.into(), LayoutGroup::Row { widgets: Vec::new() }]) Ok(vec![domain_warp_amplitude.into(), LayoutGroup::Row { widgets: Vec::new() }])
@ -2348,10 +2326,9 @@ fn static_input_properties() -> InputProperties {
map.insert( map.insert(
"noise_properties_fractal_type".to_string(), "noise_properties_fractal_type".to_string(),
Box::new(|node_id, index, context| { Box::new(|node_id, index, context| {
let (document_node, input_name, input_description) = node_properties::query_node_and_input_info(node_id, index, context)?;
let (_, coherent_noise_active, _, _, _, _) = node_properties::query_noise_pattern_state(node_id, context)?; let (_, coherent_noise_active, _, _, _, _) = node_properties::query_noise_pattern_state(node_id, context)?;
let fractal_type_row = enum_choice::<FractalType>() let fractal_type_row = enum_choice::<FractalType>()
.for_socket(ParameterWidgetsInfo::new(document_node, node_id, index, input_name, input_description, true)) .for_socket(ParameterWidgetsInfo::new(node_id, index, true, context))
.disabled(!coherent_noise_active) .disabled(!coherent_noise_active)
.property_row(); .property_row();
Ok(vec![fractal_type_row]) Ok(vec![fractal_type_row])
@ -2360,10 +2337,9 @@ fn static_input_properties() -> InputProperties {
map.insert( map.insert(
"noise_properties_fractal_octaves".to_string(), "noise_properties_fractal_octaves".to_string(),
Box::new(|node_id, index, context| { Box::new(|node_id, index, context| {
let (document_node, input_name, input_description) = node_properties::query_node_and_input_info(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_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( let fractal_octaves = node_properties::number_widget(
ParameterWidgetsInfo::new(document_node, node_id, index, input_name, input_description, true), ParameterWidgetsInfo::new(node_id, index, true, context),
NumberInput::default() NumberInput::default()
.mode_range() .mode_range()
.min(1.) .min(1.)
@ -2378,10 +2354,9 @@ fn static_input_properties() -> InputProperties {
map.insert( map.insert(
"noise_properties_fractal_lacunarity".to_string(), "noise_properties_fractal_lacunarity".to_string(),
Box::new(|node_id, index, context| { Box::new(|node_id, index, context| {
let (document_node, input_name, input_description) = node_properties::query_node_and_input_info(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_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( let fractal_lacunarity = node_properties::number_widget(
ParameterWidgetsInfo::new(document_node, node_id, index, input_name, input_description, true), ParameterWidgetsInfo::new(node_id, index, true, context),
NumberInput::default() NumberInput::default()
.mode_range() .mode_range()
.min(0.) .min(0.)
@ -2394,10 +2369,9 @@ fn static_input_properties() -> InputProperties {
map.insert( map.insert(
"noise_properties_fractal_gain".to_string(), "noise_properties_fractal_gain".to_string(),
Box::new(|node_id, index, context| { Box::new(|node_id, index, context| {
let (document_node, input_name, input_description) = node_properties::query_node_and_input_info(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_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( let fractal_gain = node_properties::number_widget(
ParameterWidgetsInfo::new(document_node, node_id, index, input_name, input_description, true), ParameterWidgetsInfo::new(node_id, index, true, context),
NumberInput::default() NumberInput::default()
.mode_range() .mode_range()
.min(0.) .min(0.)
@ -2410,10 +2384,9 @@ fn static_input_properties() -> InputProperties {
map.insert( map.insert(
"noise_properties_fractal_weighted_strength".to_string(), "noise_properties_fractal_weighted_strength".to_string(),
Box::new(|node_id, index, context| { Box::new(|node_id, index, context| {
let (document_node, input_name, input_description) = node_properties::query_node_and_input_info(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_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( let fractal_weighted_strength = node_properties::number_widget(
ParameterWidgetsInfo::new(document_node, node_id, index, input_name, input_description, true), ParameterWidgetsInfo::new(node_id, index, true, context),
NumberInput::default() NumberInput::default()
.mode_range() .mode_range()
.min(0.) .min(0.)
@ -2426,10 +2399,9 @@ fn static_input_properties() -> InputProperties {
map.insert( map.insert(
"noise_properties_ping_pong_strength".to_string(), "noise_properties_ping_pong_strength".to_string(),
Box::new(|node_id, index, context| { Box::new(|node_id, index, context| {
let (document_node, input_name, input_description) = node_properties::query_node_and_input_info(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_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( let fractal_ping_pong_strength = node_properties::number_widget(
ParameterWidgetsInfo::new(document_node, node_id, index, input_name, input_description, true), ParameterWidgetsInfo::new(node_id, index, true, context),
NumberInput::default() NumberInput::default()
.mode_range() .mode_range()
.min(0.) .min(0.)
@ -2442,10 +2414,9 @@ fn static_input_properties() -> InputProperties {
map.insert( map.insert(
"noise_properties_cellular_distance_function".to_string(), "noise_properties_cellular_distance_function".to_string(),
Box::new(|node_id, index, context| { Box::new(|node_id, index, context| {
let (document_node, input_name, input_description) = node_properties::query_node_and_input_info(node_id, index, context)?;
let (_, coherent_noise_active, cellular_noise_active, _, _, _) = node_properties::query_noise_pattern_state(node_id, 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>() let cellular_distance_function_row = enum_choice::<CellularDistanceFunction>()
.for_socket(ParameterWidgetsInfo::new(document_node, node_id, index, input_name, input_description, true)) .for_socket(ParameterWidgetsInfo::new(node_id, index, true, context))
.disabled(!coherent_noise_active || !cellular_noise_active) .disabled(!coherent_noise_active || !cellular_noise_active)
.property_row(); .property_row();
Ok(vec![cellular_distance_function_row]) Ok(vec![cellular_distance_function_row])
@ -2454,10 +2425,9 @@ fn static_input_properties() -> InputProperties {
map.insert( map.insert(
"noise_properties_cellular_return_type".to_string(), "noise_properties_cellular_return_type".to_string(),
Box::new(|node_id, index, context| { Box::new(|node_id, index, context| {
let (document_node, input_name, input_description) = node_properties::query_node_and_input_info(node_id, index, context)?;
let (_, coherent_noise_active, cellular_noise_active, _, _, _) = node_properties::query_noise_pattern_state(node_id, context)?; let (_, coherent_noise_active, cellular_noise_active, _, _, _) = node_properties::query_noise_pattern_state(node_id, context)?;
let cellular_return_type = enum_choice::<CellularReturnType>() let cellular_return_type = enum_choice::<CellularReturnType>()
.for_socket(ParameterWidgetsInfo::new(document_node, node_id, index, input_name, input_description, true)) .for_socket(ParameterWidgetsInfo::new(node_id, index, true, context))
.disabled(!coherent_noise_active || !cellular_noise_active) .disabled(!coherent_noise_active || !cellular_noise_active)
.property_row(); .property_row();
Ok(vec![cellular_return_type]) Ok(vec![cellular_return_type])
@ -2466,10 +2436,9 @@ fn static_input_properties() -> InputProperties {
map.insert( map.insert(
"noise_properties_cellular_jitter".to_string(), "noise_properties_cellular_jitter".to_string(),
Box::new(|node_id, index, context| { Box::new(|node_id, index, context| {
let (document_node, input_name, input_description) = node_properties::query_node_and_input_info(node_id, index, context)?;
let (_, coherent_noise_active, cellular_noise_active, _, _, _) = node_properties::query_noise_pattern_state(node_id, context)?; let (_, coherent_noise_active, cellular_noise_active, _, _, _) = node_properties::query_noise_pattern_state(node_id, context)?;
let cellular_jitter = node_properties::number_widget( let cellular_jitter = node_properties::number_widget(
ParameterWidgetsInfo::new(document_node, node_id, index, input_name, input_description, true), ParameterWidgetsInfo::new(node_id, index, true, context),
NumberInput::default() NumberInput::default()
.mode_range() .mode_range()
.range_min(Some(0.)) .range_min(Some(0.))
@ -2482,7 +2451,7 @@ fn static_input_properties() -> InputProperties {
map.insert( map.insert(
"brightness".to_string(), "brightness".to_string(),
Box::new(|node_id, index, context| { Box::new(|node_id, index, context| {
let (document_node, input_name, input_description) = node_properties::query_node_and_input_info(node_id, index, context)?; let document_node = node_properties::get_document_node(node_id, context)?;
let is_use_classic = document_node let is_use_classic = document_node
.inputs .inputs
.iter() .iter()
@ -2493,7 +2462,7 @@ fn static_input_properties() -> InputProperties {
.unwrap_or(false); .unwrap_or(false);
let (b_min, b_max) = if is_use_classic { (-100., 100.) } else { (-100., 150.) }; let (b_min, b_max) = if is_use_classic { (-100., 100.) } else { (-100., 150.) };
let brightness = node_properties::number_widget( let brightness = node_properties::number_widget(
ParameterWidgetsInfo::new(document_node, node_id, index, input_name, input_description, true), ParameterWidgetsInfo::new(node_id, index, true, context),
NumberInput::default().mode_range().range_min(Some(b_min)).range_max(Some(b_max)).unit("%").display_decimal_places(2), NumberInput::default().mode_range().range_min(Some(b_min)).range_max(Some(b_max)).unit("%").display_decimal_places(2),
); );
Ok(vec![brightness.into()]) Ok(vec![brightness.into()])
@ -2502,7 +2471,8 @@ fn static_input_properties() -> InputProperties {
map.insert( map.insert(
"contrast".to_string(), "contrast".to_string(),
Box::new(|node_id, index, context| { Box::new(|node_id, index, context| {
let (document_node, input_name, input_description) = node_properties::query_node_and_input_info(node_id, index, context)?; let document_node = node_properties::get_document_node(node_id, context)?;
let is_use_classic = document_node let is_use_classic = document_node
.inputs .inputs
.iter() .iter()
@ -2513,7 +2483,7 @@ fn static_input_properties() -> InputProperties {
.unwrap_or(false); .unwrap_or(false);
let (c_min, c_max) = if is_use_classic { (-100., 100.) } else { (-50., 100.) }; let (c_min, c_max) = if is_use_classic { (-100., 100.) } else { (-50., 100.) };
let contrast = node_properties::number_widget( let contrast = node_properties::number_widget(
ParameterWidgetsInfo::new(document_node, node_id, index, input_name, input_description, true), ParameterWidgetsInfo::new(node_id, index, true, context),
NumberInput::default().mode_range().range_min(Some(c_min)).range_max(Some(c_max)).unit("%").display_decimal_places(2), NumberInput::default().mode_range().range_min(Some(c_min)).range_max(Some(c_max)).unit("%").display_decimal_places(2),
); );
Ok(vec![contrast.into()]) Ok(vec![contrast.into()])
@ -2522,21 +2492,16 @@ fn static_input_properties() -> InputProperties {
map.insert( map.insert(
"assign_colors_gradient".to_string(), "assign_colors_gradient".to_string(),
Box::new(|node_id, index, context| { Box::new(|node_id, index, context| {
let (document_node, input_name, input_description) = node_properties::query_node_and_input_info(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::new(document_node, node_id, index, input_name, input_description, true),
ColorInput::default().allow_none(false),
);
Ok(vec![gradient_row]) Ok(vec![gradient_row])
}), }),
); );
map.insert( map.insert(
"assign_colors_seed".to_string(), "assign_colors_seed".to_string(),
Box::new(|node_id, index, context| { Box::new(|node_id, index, context| {
let (document_node, input_name, input_description) = node_properties::query_node_and_input_info(node_id, index, context)?;
let randomize_enabled = node_properties::query_assign_colors_randomize(node_id, context)?; let randomize_enabled = node_properties::query_assign_colors_randomize(node_id, context)?;
let seed_row = node_properties::number_widget( let seed_row = node_properties::number_widget(
ParameterWidgetsInfo::new(document_node, node_id, index, input_name, input_description, true), ParameterWidgetsInfo::new(node_id, index, true, context),
NumberInput::default().min(0.).int().disabled(!randomize_enabled), NumberInput::default().min(0.).int().disabled(!randomize_enabled),
); );
Ok(vec![seed_row.into()]) Ok(vec![seed_row.into()])
@ -2545,10 +2510,9 @@ fn static_input_properties() -> InputProperties {
map.insert( map.insert(
"assign_colors_repeat_every".to_string(), "assign_colors_repeat_every".to_string(),
Box::new(|node_id, index, context| { Box::new(|node_id, index, context| {
let (document_node, input_name, input_description) = node_properties::query_node_and_input_info(node_id, index, context)?;
let randomize_enabled = node_properties::query_assign_colors_randomize(node_id, context)?; let randomize_enabled = node_properties::query_assign_colors_randomize(node_id, context)?;
let repeat_every_row = node_properties::number_widget( let repeat_every_row = node_properties::number_widget(
ParameterWidgetsInfo::new(document_node, node_id, index, input_name, input_description, true), ParameterWidgetsInfo::new(node_id, index, true, context),
NumberInput::default().min(0.).int().disabled(randomize_enabled), NumberInput::default().min(0.).int().disabled(randomize_enabled),
); );
Ok(vec![repeat_every_row.into()]) Ok(vec![repeat_every_row.into()])
@ -2557,33 +2521,24 @@ fn static_input_properties() -> InputProperties {
map.insert( map.insert(
"mask_stencil".to_string(), "mask_stencil".to_string(),
Box::new(|node_id, index, context| { Box::new(|node_id, index, context| {
let (document_node, input_name, input_description) = node_properties::query_node_and_input_info(node_id, index, context)?; let mask = node_properties::color_widget(ParameterWidgetsInfo::new(node_id, index, true, context), ColorInput::default());
let mask = node_properties::color_widget(ParameterWidgetsInfo::new(document_node, node_id, index, input_name, input_description, true), ColorInput::default());
Ok(vec![mask]) Ok(vec![mask])
}), }),
); );
map.insert( map.insert(
"spline_input".to_string(), "spline_input".to_string(),
Box::new(|node_id, index, context| { Box::new(|node_id, index, context| {
let (document_node, input_name, input_description) = node_properties::query_node_and_input_info(node_id, index, context)?;
Ok(vec![LayoutGroup::Row { Ok(vec![LayoutGroup::Row {
widgets: node_properties::array_of_coordinates_widget( widgets: node_properties::array_of_coordinates_widget(ParameterWidgetsInfo::new(node_id, index, true, context), TextInput::default().centered(true)),
ParameterWidgetsInfo::new(document_node, node_id, index, input_name, input_description, true),
TextInput::default().centered(true),
),
}]) }])
}), }),
); );
map.insert( map.insert(
"transform_rotation".to_string(), "transform_rotation".to_string(),
Box::new(|node_id, index, context| { Box::new(|node_id, index, context| {
let (document_node, input_name, input_description) = node_properties::query_node_and_input_info(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::new(document_node, node_id, index, input_name, input_description, true),
super::utility_types::FrontendGraphDataType::Number,
);
let document_node = node_properties::get_document_node(node_id, context)?;
let Some(input) = document_node.inputs.get(index) else { let Some(input) = document_node.inputs.get(index) else {
return Err("Input not found in transform rotation input override".to_string()); return Err("Input not found in transform rotation input override".to_string());
}; };
@ -2612,13 +2567,9 @@ fn static_input_properties() -> InputProperties {
map.insert( map.insert(
"transform_skew".to_string(), "transform_skew".to_string(),
Box::new(|node_id, index, context| { Box::new(|node_id, index, context| {
let (document_node, input_name, input_description) = node_properties::query_node_and_input_info(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::new(document_node, node_id, index, input_name, input_description, true),
super::utility_types::FrontendGraphDataType::Number,
);
let document_node = node_properties::get_document_node(node_id, context)?;
let Some(input) = document_node.inputs.get(index) else { let Some(input) = document_node.inputs.get(index) else {
return Err("Input not found in transform skew input override".to_string()); return Err("Input not found in transform skew input override".to_string());
}; };
@ -2660,17 +2611,15 @@ fn static_input_properties() -> InputProperties {
map.insert( map.insert(
"text_area".to_string(), "text_area".to_string(),
Box::new(|node_id, index, context| { Box::new(|node_id, index, context| {
let (document_node, input_name, input_description) = node_properties::query_node_and_input_info(node_id, index, context)?;
Ok(vec![LayoutGroup::Row { Ok(vec![LayoutGroup::Row {
widgets: node_properties::text_area_widget(ParameterWidgetsInfo::new(document_node, node_id, index, input_name, input_description, true)), widgets: node_properties::text_area_widget(ParameterWidgetsInfo::new(node_id, index, true, context)),
}]) }])
}), }),
); );
map.insert( map.insert(
"text_font".to_string(), "text_font".to_string(),
Box::new(|node_id, index, context| { Box::new(|node_id, index, context| {
let (document_node, input_name, input_description) = node_properties::query_node_and_input_info(node_id, index, context)?; let (font, style) = node_properties::font_inputs(ParameterWidgetsInfo::new(node_id, index, true, context));
let (font, style) = node_properties::font_inputs(ParameterWidgetsInfo::new(document_node, node_id, index, input_name, input_description, true));
let mut result = vec![LayoutGroup::Row { widgets: font }]; let mut result = vec![LayoutGroup::Row { widgets: font }];
if let Some(style) = style { if let Some(style) = style {
result.push(LayoutGroup::Row { widgets: style }); result.push(LayoutGroup::Row { widgets: style });
@ -2681,9 +2630,8 @@ fn static_input_properties() -> InputProperties {
map.insert( map.insert(
"artboard_background".to_string(), "artboard_background".to_string(),
Box::new(|node_id, index, context| { Box::new(|node_id, index, context| {
let (document_node, input_name, input_description) = node_properties::query_node_and_input_info(node_id, index, context)?;
Ok(vec![node_properties::color_widget( Ok(vec![node_properties::color_widget(
ParameterWidgetsInfo::new(document_node, node_id, index, input_name, input_description, true), ParameterWidgetsInfo::new(node_id, index, true, context),
ColorInput::default().allow_none(false), ColorInput::default().allow_none(false),
)]) )])
}), }),
@ -2724,7 +2672,7 @@ pub fn collect_node_types() -> Vec<FrontendNodeType> {
// Extract input types (already creates owned Strings) // Extract input types (already creates owned Strings)
let input_types = implementations let input_types = implementations
.iter() .iter()
.flat_map(|(_, node_io)| node_io.inputs.iter().map(|ty| ty.clone().nested_type().to_string())) .flat_map(|(_, node_io)| node_io.inputs.iter().map(|ty| ty.nested_type().to_string()))
.collect::<HashSet<String>>() .collect::<HashSet<String>>()
.into_iter() .into_iter()
.collect::<Vec<String>>(); .collect::<Vec<String>>();
@ -2830,7 +2778,7 @@ impl DocumentNodeDefinition {
log::error!("Path is not valid for network"); log::error!("Path is not valid for network");
return; return;
}; };
nested_node_metadata.persistent_metadata.input_properties.resize_with(input_length, PropertiesRow::default); nested_node_metadata.persistent_metadata.input_metadata.resize_with(input_length, InputMetadata::default);
// Recurse over all sub-nodes if the current node is a network implementation // Recurse over all sub-nodes if the current node is a network implementation
let mut current_path = path.clone(); let mut current_path = path.clone();
@ -2849,7 +2797,7 @@ impl DocumentNodeDefinition {
} else { } else {
// Base case // Base case
let input_len = node_template.document_node.inputs.len(); let input_len = node_template.document_node.inputs.len();
node_template.persistent_node_metadata.input_properties.resize_with(input_len, PropertiesRow::default); node_template.persistent_node_metadata.input_metadata.resize_with(input_len, InputMetadata::default);
if let DocumentNodeImplementation::Network(node_template_network) = &node_template.document_node.implementation { if let DocumentNodeImplementation::Network(node_template_network) = &node_template.document_node.implementation {
for sub_node_id in node_template_network.nodes.keys().cloned().collect::<Vec<_>>() { for sub_node_id in node_template_network.nodes.keys().cloned().collect::<Vec<_>>() {
populate_input_properties(node_template, vec![sub_node_id]); populate_input_properties(node_template, vec![sub_node_id]);
@ -2861,6 +2809,10 @@ impl DocumentNodeDefinition {
// Set the reference to the node definition // Set the reference to the node definition
template.persistent_node_metadata.reference = Some(self.identifier.to_string()); template.persistent_node_metadata.reference = Some(self.identifier.to_string());
// If the display name is empty and it is not a merge node, then set it to the reference
if template.persistent_node_metadata.display_name.is_empty() && self.identifier != "Merge" {
template.persistent_node_metadata.display_name = self.identifier.to_string();
}
template template
} }

View file

@ -1,5 +1,5 @@
use super::DocumentNodeDefinition; use super::DocumentNodeDefinition;
use crate::messages::portfolio::document::utility_types::network_interface::{DocumentNodePersistentMetadata, NodeTemplate, PropertiesRow, WidgetOverride}; use crate::messages::portfolio::document::utility_types::network_interface::{DocumentNodePersistentMetadata, InputMetadata, NodeTemplate, WidgetOverride};
use graph_craft::ProtoNodeIdentifier; use graph_craft::ProtoNodeIdentifier;
use graph_craft::document::*; use graph_craft::document::*;
use graphene_std::registry::*; use graphene_std::registry::*;
@ -67,13 +67,13 @@ pub(super) fn post_process_nodes(mut custom: Vec<DocumentNodeDefinition>) -> Vec
}, },
persistent_node_metadata: DocumentNodePersistentMetadata { persistent_node_metadata: DocumentNodePersistentMetadata {
// TODO: Store information for input overrides in the node macro // TODO: Store information for input overrides in the node macro
input_properties: fields input_metadata: fields
.iter() .iter()
.map(|f| match f.widget_override { .map(|f| match f.widget_override {
RegistryWidgetOverride::None => (f.name, f.description).into(), RegistryWidgetOverride::None => (f.name, f.description).into(),
RegistryWidgetOverride::Hidden => PropertiesRow::with_override(f.name, f.description, WidgetOverride::Hidden), RegistryWidgetOverride::Hidden => InputMetadata::with_name_description_override(f.name, f.description, WidgetOverride::Hidden),
RegistryWidgetOverride::String(str) => PropertiesRow::with_override(f.name, f.description, WidgetOverride::String(str.to_string())), RegistryWidgetOverride::String(str) => InputMetadata::with_name_description_override(f.name, f.description, WidgetOverride::String(str.to_string())),
RegistryWidgetOverride::Custom(str) => PropertiesRow::with_override(f.name, f.description, WidgetOverride::Custom(str.to_string())), RegistryWidgetOverride::Custom(str) => InputMetadata::with_name_description_override(f.name, f.description, WidgetOverride::Custom(str.to_string())),
}) })
.collect(), .collect(),
output_names: vec![output_type.to_string()], output_names: vec![output_type.to_string()],

View file

@ -33,6 +33,7 @@ pub enum NodeGraphMessage {
node_id: Option<NodeId>, node_id: Option<NodeId>,
node_type: String, node_type: String,
xy: Option<(i32, i32)>, xy: Option<(i32, i32)>,
add_transaction: bool,
}, },
CreateWire { CreateWire {
output_connector: OutputConnector, output_connector: OutputConnector,
@ -123,6 +124,9 @@ pub enum NodeGraphMessage {
}, },
SendClickTargets, SendClickTargets,
EndSendClickTargets, EndSendClickTargets,
UnloadWires,
SendWires,
UpdateVisibleNodes,
SendGraph, SendGraph,
SetGridAlignedEdges, SetGridAlignedEdges,
SetInputValue { SetInputValue {

View file

@ -1,4 +1,4 @@
use super::utility_types::{BoxSelection, ContextMenuInformation, DragStart, FrontendGraphInput, FrontendGraphOutput, FrontendNode, FrontendNodeWire, WirePath}; use super::utility_types::{BoxSelection, ContextMenuInformation, DragStart, FrontendGraphInput, FrontendGraphOutput, FrontendNode};
use super::{document_node_definitions, node_properties}; use super::{document_node_definitions, node_properties};
use crate::consts::GRID_SIZE; use crate::consts::GRID_SIZE;
use crate::messages::input_mapper::utility_types::macros::action_keys; use crate::messages::input_mapper::utility_types::macros::action_keys;
@ -13,6 +13,7 @@ use crate::messages::portfolio::document::utility_types::network_interface::{
self, InputConnector, NodeNetworkInterface, NodeTemplate, NodeTypePersistentMetadata, OutputConnector, Previewing, TypeSource, self, InputConnector, NodeNetworkInterface, NodeTemplate, NodeTypePersistentMetadata, OutputConnector, Previewing, TypeSource,
}; };
use crate::messages::portfolio::document::utility_types::nodes::{CollapsedLayers, LayerPanelEntry}; use crate::messages::portfolio::document::utility_types::nodes::{CollapsedLayers, LayerPanelEntry};
use crate::messages::portfolio::document::utility_types::wires::{GraphWireStyle, WirePath, WirePathUpdate, build_vector_wire};
use crate::messages::prelude::*; use crate::messages::prelude::*;
use crate::messages::tool::common_functionality::auto_panning::AutoPanning; use crate::messages::tool::common_functionality::auto_panning::AutoPanning;
use crate::messages::tool::common_functionality::graph_modification_utils::get_clip_mode; use crate::messages::tool::common_functionality::graph_modification_utils::get_clip_mode;
@ -67,6 +68,7 @@ pub struct NodeGraphMessageHandler {
select_if_not_dragged: Option<NodeId>, select_if_not_dragged: Option<NodeId>,
/// The start of the dragged line (cannot be moved), stored in node graph coordinates /// The start of the dragged line (cannot be moved), stored in node graph coordinates
pub wire_in_progress_from_connector: Option<DVec2>, pub wire_in_progress_from_connector: Option<DVec2>,
wire_in_progress_type: FrontendGraphDataType,
/// The end point of the dragged line (cannot be moved), stored in node graph coordinates /// The end point of the dragged line (cannot be moved), stored in node graph coordinates
pub wire_in_progress_to_connector: Option<DVec2>, pub wire_in_progress_to_connector: Option<DVec2>,
/// State for the context menu popups. /// State for the context menu popups.
@ -77,12 +79,16 @@ pub struct NodeGraphMessageHandler {
auto_panning: AutoPanning, auto_panning: AutoPanning,
/// The node to preview on mouse up if alt-clicked /// The node to preview on mouse up if alt-clicked
preview_on_mouse_up: Option<NodeId>, preview_on_mouse_up: Option<NodeId>,
// The index of the import that is being moved /// The index of the import that is being moved
reordering_import: Option<usize>, reordering_import: Option<usize>,
// The index of the export that is being moved /// The index of the export that is being moved
reordering_export: Option<usize>, reordering_export: Option<usize>,
// The end index of the moved port /// The end index of the moved port
end_index: Option<usize>, end_index: Option<usize>,
/// Used to keep track of what nodes are sent to the front end so that only visible ones are sent to the frontend
frontend_nodes: Vec<NodeId>,
/// Used to keep track of what wires are sent to the front end so the old ones can be removed
frontend_wires: HashSet<(NodeId, usize)>,
} }
/// NodeGraphMessageHandler always modifies the network which the selected nodes are in. No GraphOperationMessages should be added here, since those messages will always affect the document network. /// NodeGraphMessageHandler always modifies the network which the selected nodes are in. No GraphOperationMessages should be added here, since those messages will always affect the document network.
@ -175,7 +181,12 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
responses.add(PropertiesPanelMessage::Refresh); responses.add(PropertiesPanelMessage::Refresh);
responses.add(NodeGraphMessage::RunDocumentGraph); responses.add(NodeGraphMessage::RunDocumentGraph);
} }
NodeGraphMessage::CreateNodeFromContextMenu { node_id, node_type, xy } => { NodeGraphMessage::CreateNodeFromContextMenu {
node_id,
node_type,
xy,
add_transaction,
} => {
let (x, y) = if let Some((x, y)) = xy { let (x, y) = if let Some((x, y)) = xy {
(x, y) (x, y)
} else if let Some(node_graph_ptz) = network_interface.node_graph_ptz(breadcrumb_network_path) { } else if let Some(node_graph_ptz) = network_interface.node_graph_ptz(breadcrumb_network_path) {
@ -197,7 +208,10 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
let node_template = document_node_type.default_node_template(); let node_template = document_node_type.default_node_template();
self.context_menu = None; self.context_menu = None;
responses.add(DocumentMessage::AddTransaction); if add_transaction {
responses.add(DocumentMessage::AddTransaction);
}
responses.add(NodeGraphMessage::InsertNode { responses.add(NodeGraphMessage::InsertNode {
node_id, node_id,
node_template: node_template.clone(), node_template: node_template.clone(),
@ -220,13 +234,7 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
}; };
// Ensure connection is to correct input of new node. If it does not have an input then do not connect // Ensure connection is to correct input of new node. If it does not have an input then do not connect
if let Some((input_index, _)) = node_template if let Some((input_index, _)) = node_template.document_node.inputs.iter().enumerate().find(|(_, input)| input.is_exposed()) {
.document_node
.inputs
.iter()
.enumerate()
.find(|(_, input)| input.is_exposed_to_frontend(selection_network_path.is_empty()))
{
responses.add(NodeGraphMessage::CreateWire { responses.add(NodeGraphMessage::CreateWire {
output_connector: *output_connector, output_connector: *output_connector,
input_connector: InputConnector::node(node_id, input_index), input_connector: InputConnector::node(node_id, input_index),
@ -236,6 +244,7 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
} }
self.wire_in_progress_from_connector = None; self.wire_in_progress_from_connector = None;
self.wire_in_progress_type = FrontendGraphDataType::General;
self.wire_in_progress_to_connector = None; self.wire_in_progress_to_connector = None;
} }
responses.add(FrontendMessage::UpdateWirePathInProgress { wire_path: None }); responses.add(FrontendMessage::UpdateWirePathInProgress { wire_path: None });
@ -367,9 +376,14 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
responses.add(DocumentMessage::CommitTransaction); responses.add(DocumentMessage::CommitTransaction);
// Update the graph UI and re-render // Update the graph UI and re-render
responses.add(PropertiesPanelMessage::Refresh); if graph_view_overlay_open {
responses.add(NodeGraphMessage::SendGraph); responses.add(PropertiesPanelMessage::Refresh);
responses.add(NodeGraphMessage::RunDocumentGraph); responses.add(NodeGraphMessage::SendGraph);
} else {
responses.add(DocumentMessage::GraphViewOverlay { open: true });
responses.add(NavigationMessage::FitViewportToSelection);
responses.add(DocumentMessage::ZoomCanvasTo100Percent);
}
} }
NodeGraphMessage::InsertNode { node_id, node_template } => { NodeGraphMessage::InsertNode { node_id, node_template } => {
network_interface.insert_node(node_id, node_template, selection_network_path); network_interface.insert_node(node_id, node_template, selection_network_path);
@ -629,6 +643,7 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
// Abort dragging a wire // Abort dragging a wire
if self.wire_in_progress_from_connector.is_some() { if self.wire_in_progress_from_connector.is_some() {
self.wire_in_progress_from_connector = None; self.wire_in_progress_from_connector = None;
self.wire_in_progress_type = FrontendGraphDataType::General;
self.wire_in_progress_to_connector = None; self.wire_in_progress_to_connector = None;
responses.add(DocumentMessage::AbortTransaction); responses.add(DocumentMessage::AbortTransaction);
responses.add(FrontendMessage::UpdateWirePathInProgress { wire_path: None }); responses.add(FrontendMessage::UpdateWirePathInProgress { wire_path: None });
@ -707,6 +722,7 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
if self.context_menu.is_some() { if self.context_menu.is_some() {
self.context_menu = None; self.context_menu = None;
self.wire_in_progress_from_connector = None; self.wire_in_progress_from_connector = None;
self.wire_in_progress_type = FrontendGraphDataType::General;
self.wire_in_progress_to_connector = None; self.wire_in_progress_to_connector = None;
responses.add(FrontendMessage::UpdateContextMenuInformation { responses.add(FrontendMessage::UpdateContextMenuInformation {
context_menu_information: self.context_menu.clone(), context_menu_information: self.context_menu.clone(),
@ -740,6 +756,7 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
}; };
let Some(output_connector) = output_connector else { return }; let Some(output_connector) = output_connector else { return };
self.wire_in_progress_from_connector = network_interface.output_position(&output_connector, selection_network_path); self.wire_in_progress_from_connector = network_interface.output_position(&output_connector, selection_network_path);
self.wire_in_progress_type = FrontendGraphDataType::from_type(&network_interface.input_type(clicked_input, breadcrumb_network_path).0);
return; return;
} }
@ -749,6 +766,15 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
self.initial_disconnecting = false; self.initial_disconnecting = false;
self.wire_in_progress_from_connector = network_interface.output_position(&clicked_output, selection_network_path); self.wire_in_progress_from_connector = network_interface.output_position(&clicked_output, selection_network_path);
if let Some((output_type, source)) = clicked_output
.node_id()
.map(|node_id| network_interface.output_type(&node_id, clicked_output.index(), breadcrumb_network_path))
{
self.wire_in_progress_type = FrontendGraphDataType::displayed_type(&output_type, &source);
} else {
self.wire_in_progress_type = FrontendGraphDataType::General;
}
self.update_node_graph_hints(responses); self.update_node_graph_hints(responses);
return; return;
} }
@ -895,9 +921,18 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
false false
} }
}); });
let vector_wire = build_vector_wire(
wire_in_progress_from_connector,
wire_in_progress_to_connector,
from_connector_is_layer,
to_connector_is_layer,
GraphWireStyle::Direct,
);
let mut path_string = String::new();
let _ = vector_wire.subpath_to_svg(&mut path_string, DAffine2::IDENTITY);
let wire_path = WirePath { let wire_path = WirePath {
path_string: Self::build_wire_path_string(wire_in_progress_from_connector, wire_in_progress_to_connector, from_connector_is_layer, to_connector_is_layer), path_string,
data_type: FrontendGraphDataType::General, data_type: self.wire_in_progress_type,
thick: false, thick: false,
dashed: false, dashed: false,
}; };
@ -941,7 +976,7 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
self.update_node_graph_hints(responses); self.update_node_graph_hints(responses);
} else if self.reordering_import.is_some() { } else if self.reordering_import.is_some() {
let Some(modify_import_export) = network_interface.modify_import_export(selection_network_path) else { let Some(modify_import_export) = network_interface.modify_import_export(selection_network_path) else {
log::error!("Could not get modify import export in PointerUp"); log::error!("Could not get modify import export in PointerMove");
return; return;
}; };
// Find the first import that is below the mouse position // Find the first import that is below the mouse position
@ -961,7 +996,7 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
responses.add(FrontendMessage::UpdateImportReorderIndex { index: self.end_index }); responses.add(FrontendMessage::UpdateImportReorderIndex { index: self.end_index });
} else if self.reordering_export.is_some() { } else if self.reordering_export.is_some() {
let Some(modify_import_export) = network_interface.modify_import_export(selection_network_path) else { let Some(modify_import_export) = network_interface.modify_import_export(selection_network_path) else {
log::error!("Could not get modify import export in PointerUp"); log::error!("Could not get modify import export in PointerMove");
return; return;
}; };
// Find the first export that is below the mouse position // Find the first export that is below the mouse position
@ -1043,15 +1078,13 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
// Get the compatible type from the output connector // Get the compatible type from the output connector
let compatible_type = output_connector.and_then(|output_connector| { let compatible_type = output_connector.and_then(|output_connector| {
output_connector.node_id().and_then(|node_id| { output_connector.node_id().and_then(|node_id| {
let output_index = output_connector.index();
// Get the output types from the network interface // Get the output types from the network interface
let output_types = network_interface.output_types(&node_id, selection_network_path); let (output_type, type_source) = network_interface.output_type(&node_id, output_connector.index(), selection_network_path);
// Extract the type if available match type_source {
output_types.get(output_index).and_then(|type_option| type_option.as_ref()).map(|(output_type, _)| { TypeSource::RandomProtonodeImplementation | TypeSource::Error(_) => None,
// Create a search term based on the type _ => Some(format!("type:{}", output_type.nested_type())),
format!("type:{}", output_type.clone().nested_type()) }
})
}) })
}); });
let appear_right_of_mouse = if ipp.mouse.position.x > ipp.viewport_bounds.size().x - 173. { -173. } else { 0. }; let appear_right_of_mouse = if ipp.mouse.position.x > ipp.viewport_bounds.size().x - 173. { -173. } else { 0. };
@ -1117,107 +1150,56 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
let has_primary_output_connection = network_interface let has_primary_output_connection = network_interface
.outward_wires(selection_network_path) .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())); .is_some_and(|outward_wires| outward_wires.get(&OutputConnector::node(selected_node_id, 0)).is_some_and(|outward_wires| !outward_wires.is_empty()));
let Some(network) = network_interface.nested_network(selection_network_path) else { if !has_primary_output_connection {
return; let Some(network) = network_interface.nested_network(selection_network_path) else {
}; return;
if let Some(selected_node) = network.nodes.get(&selected_node_id) { };
// Check if any downstream node has any input that feeds into the primary export of the selected node let Some(selected_node) = network.nodes.get(&selected_node_id) else {
let primary_input_is_value = selected_node.inputs.first().is_some_and(|first_input| first_input.as_value().is_some()); return;
// Check that neither the primary input or output of the selected node are already connected. };
if !has_primary_output_connection && primary_input_is_value { // Check that the first visible input is disconnected
let selected_node_input_connect_index = selected_node
.inputs
.iter()
.enumerate()
.find(|input| input.1.is_exposed())
.filter(|input| input.1.as_value().is_some())
.map(|input| input.0);
if let Some(selected_node_input_connect_index) = selected_node_input_connect_index {
let Some(bounding_box) = network_interface.node_bounding_box(&selected_node_id, selection_network_path) else { let Some(bounding_box) = network_interface.node_bounding_box(&selected_node_id, selection_network_path) else {
log::error!("Could not get bounding box for node: {selected_node_id}"); log::error!("Could not get bounding box for node: {selected_node_id}");
return; return;
}; };
// TODO: Cache all wire locations if this is a performance issue let mut wires_to_check = network_interface.node_graph_input_connectors(selection_network_path).into_iter().collect::<HashSet<_>>();
let overlapping_wires = Self::collect_wires(network_interface, selection_network_path) // Prevent inserting on a link that is connected upstream to the selected node
.into_iter() for upstream_node in network_interface.upstream_flow_back_from_nodes(vec![selected_node_id], selection_network_path, network_interface::FlowType::UpstreamFlow) {
.filter(|frontend_wire| { for input_index in 0..network_interface.number_of_inputs(&upstream_node, selection_network_path) {
// Prevent inserting on a link that is connected upstream to the selected node wires_to_check.remove(&InputConnector::node(upstream_node, input_index));
if network_interface }
.upstream_flow_back_from_nodes(vec![selected_node_id], selection_network_path, network_interface::FlowType::UpstreamFlow) }
.any(|upstream_id| {
frontend_wire.wire_end.node_id().is_some_and(|wire_end_id| wire_end_id == upstream_id)
|| frontend_wire.wire_start.node_id().is_some_and(|wire_start_id| wire_start_id == upstream_id)
}) {
return false;
}
let overlapping_wires = wires_to_check
.into_iter()
.filter_map(|input| {
// Prevent inserting a layer into a chain // Prevent inserting a layer into a chain
if network_interface.is_layer(&selected_node_id, selection_network_path) if network_interface.is_layer(&selected_node_id, selection_network_path)
&& frontend_wire && input.node_id().is_some_and(|input_node_id| network_interface.is_chain(&input_node_id, selection_network_path))
.wire_start
.node_id()
.is_some_and(|wire_start_id| network_interface.is_chain(&wire_start_id, selection_network_path))
{ {
return false; return None;
} }
let (wire, is_stack) = network_interface.vector_wire_from_input(&input, preferences.graph_wire_style, selection_network_path)?;
let Some(input_position) = network_interface.input_position(&frontend_wire.wire_end, selection_network_path) else { wire.rectangle_intersections_exist(bounding_box[0], bounding_box[1]).then_some((input, is_stack))
log::error!("Could not get input port position for {:?}", frontend_wire.wire_end);
return false;
};
let Some(output_position) = network_interface.output_position(&frontend_wire.wire_start, selection_network_path) else {
log::error!("Could not get output port position for {:?}", frontend_wire.wire_start);
return false;
};
let start_node_is_layer = frontend_wire
.wire_end
.node_id()
.is_some_and(|wire_start_id| network_interface.is_layer(&wire_start_id, selection_network_path));
let end_node_is_layer = frontend_wire
.wire_end
.node_id()
.is_some_and(|wire_end_id| network_interface.is_layer(&wire_end_id, selection_network_path));
let locations = Self::build_wire_path_locations(output_position, input_position, start_node_is_layer, end_node_is_layer);
let bezier = bezier_rs::Bezier::from_cubic_dvec2(
(locations[0].x, locations[0].y).into(),
(locations[1].x, locations[1].y).into(),
(locations[2].x, locations[2].y).into(),
(locations[3].x, locations[3].y).into(),
);
!bezier.rectangle_intersections(bounding_box[0], bounding_box[1]).is_empty() || bezier.is_contained_within(bounding_box[0], bounding_box[1])
})
.collect::<Vec<_>>()
.into_iter()
.filter_map(|mut wire| {
if let Some(end_node_id) = wire.wire_end.node_id() {
let Some(actual_index_from_exposed) = (0..network_interface.number_of_inputs(&end_node_id, selection_network_path))
.filter(|&input_index| {
network_interface
.input_from_connector(&InputConnector::Node { node_id: end_node_id, input_index }, selection_network_path)
.is_some_and(|input| input.is_exposed_to_frontend(selection_network_path.is_empty()))
})
.nth(wire.wire_end.input_index())
else {
log::error!("Could not get exposed input index for {:?}", wire.wire_end);
return None;
};
wire.wire_end = InputConnector::Node {
node_id: end_node_id,
input_index: actual_index_from_exposed,
};
}
Some(wire)
}) })
.collect::<Vec<_>>(); .collect::<Vec<_>>();
let is_stack_wire = |wire: &FrontendNodeWire| match (wire.wire_start.node_id(), wire.wire_end.node_id(), wire.wire_end.input_index()) {
(Some(start_id), Some(end_id), input_index) => {
input_index == 0 && network_interface.is_layer(&start_id, selection_network_path) && network_interface.is_layer(&end_id, selection_network_path)
}
_ => false,
};
// Prioritize vertical thick lines and cancel if there are multiple potential wires // Prioritize vertical thick lines and cancel if there are multiple potential wires
let mut node_wires = Vec::new(); let mut node_wires = Vec::new();
let mut stack_wires = Vec::new(); let mut stack_wires = Vec::new();
for wire in overlapping_wires { for (overlapping_wire_input, is_stack) in overlapping_wires {
if is_stack_wire(&wire) { stack_wires.push(wire) } else { node_wires.push(wire) } if is_stack {
stack_wires.push(overlapping_wire_input)
} else {
node_wires.push(overlapping_wire_input)
}
} }
let overlapping_wire = if network_interface.is_layer(&selected_node_id, selection_network_path) { let overlapping_wire = if network_interface.is_layer(&selected_node_id, selection_network_path) {
@ -1234,29 +1216,13 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
None None
}; };
if let Some(overlapping_wire) = overlapping_wire { if let Some(overlapping_wire) = overlapping_wire {
let Some(network) = network_interface.nested_network(selection_network_path) else { responses.add(NodeGraphMessage::InsertNodeBetween {
return; node_id: selected_node_id,
}; input_connector: *overlapping_wire,
// Ensure connection is to first visible input of selected node. If it does not have an input then do not connect insert_node_input_index: selected_node_input_connect_index,
if let Some((selected_node_input_index, _)) = network });
.nodes responses.add(NodeGraphMessage::RunDocumentGraph);
.get(&selected_node_id) responses.add(NodeGraphMessage::SendGraph);
.unwrap()
.inputs
.iter()
.enumerate()
.find(|(_, input)| input.is_exposed_to_frontend(selection_network_path.is_empty()))
{
responses.add(NodeGraphMessage::InsertNodeBetween {
node_id: selected_node_id,
input_connector: overlapping_wire.wire_end,
insert_node_input_index: selected_node_input_index,
});
responses.add(NodeGraphMessage::RunDocumentGraph);
responses.add(NodeGraphMessage::SendGraph);
}
} }
} }
} }
@ -1283,6 +1249,7 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
self.begin_dragging = false; self.begin_dragging = false;
self.box_selection_start = None; self.box_selection_start = None;
self.wire_in_progress_from_connector = None; self.wire_in_progress_from_connector = None;
self.wire_in_progress_type = FrontendGraphDataType::General;
self.wire_in_progress_to_connector = None; self.wire_in_progress_to_connector = None;
self.reordering_export = None; self.reordering_export = None;
self.reordering_import = None; self.reordering_import = None;
@ -1357,23 +1324,52 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
click_targets: Some(network_interface.collect_frontend_click_targets(breadcrumb_network_path)), click_targets: Some(network_interface.collect_frontend_click_targets(breadcrumb_network_path)),
}), }),
NodeGraphMessage::EndSendClickTargets => responses.add(FrontendMessage::UpdateClickTargets { click_targets: None }), NodeGraphMessage::EndSendClickTargets => responses.add(FrontendMessage::UpdateClickTargets { click_targets: None }),
NodeGraphMessage::UnloadWires => {
for input in network_interface.node_graph_input_connectors(breadcrumb_network_path) {
network_interface.unload_wire(&input, breadcrumb_network_path);
}
responses.add(FrontendMessage::ClearAllNodeGraphWires);
}
NodeGraphMessage::SendWires => {
let wires = self.collect_wires(network_interface, preferences.graph_wire_style, breadcrumb_network_path);
responses.add(FrontendMessage::UpdateNodeGraphWires { wires });
}
NodeGraphMessage::UpdateVisibleNodes => {
let Some(network_metadata) = network_interface.network_metadata(breadcrumb_network_path) else {
return;
};
let viewport_bbox = ipp.document_bounds();
let document_bbox: [DVec2; 2] = viewport_bbox.map(|p| network_metadata.persistent_metadata.navigation_metadata.node_graph_to_viewport.inverse().transform_point2(p));
let mut nodes = Vec::new();
for node_id in &self.frontend_nodes {
let Some(node_bbox) = network_interface.node_bounding_box(node_id, breadcrumb_network_path) else {
log::error!("Could not get bbox for node: {:?}", node_id);
continue;
};
if node_bbox[1].x >= document_bbox[0].x && node_bbox[0].x <= document_bbox[1].x && node_bbox[1].y >= document_bbox[0].y && node_bbox[0].y <= document_bbox[1].y {
nodes.push(*node_id);
}
}
responses.add(FrontendMessage::UpdateVisibleNodes { nodes });
}
NodeGraphMessage::SendGraph => { NodeGraphMessage::SendGraph => {
responses.add(NodeGraphMessage::UpdateLayerPanel); responses.add(NodeGraphMessage::UpdateLayerPanel);
responses.add(DocumentMessage::DocumentStructureChanged); responses.add(DocumentMessage::DocumentStructureChanged);
responses.add(PropertiesPanelMessage::Refresh); responses.add(PropertiesPanelMessage::Refresh);
if breadcrumb_network_path == selection_network_path && graph_view_overlay_open { if breadcrumb_network_path == selection_network_path && graph_view_overlay_open {
// TODO: Implement culling of nodes and wires whose bounding boxes are outside of the viewport
let wires = Self::collect_wires(network_interface, breadcrumb_network_path);
let nodes = self.collect_nodes(network_interface, breadcrumb_network_path); let nodes = self.collect_nodes(network_interface, breadcrumb_network_path);
self.frontend_nodes = nodes.iter().map(|node| node.id).collect();
responses.add(FrontendMessage::UpdateNodeGraphNodes { nodes });
responses.add(NodeGraphMessage::UpdateVisibleNodes);
let (layer_widths, chain_widths, has_left_input_wire) = network_interface.collect_layer_widths(breadcrumb_network_path); let (layer_widths, chain_widths, has_left_input_wire) = network_interface.collect_layer_widths(breadcrumb_network_path);
let wires_direct_not_grid_aligned = preferences.graph_wire_style.is_direct();
responses.add(NodeGraphMessage::UpdateImportsExports); responses.add(NodeGraphMessage::UpdateImportsExports);
responses.add(FrontendMessage::UpdateNodeGraph {
nodes,
wires,
wires_direct_not_grid_aligned,
});
responses.add(FrontendMessage::UpdateLayerWidths { responses.add(FrontendMessage::UpdateLayerWidths {
layer_widths, layer_widths,
chain_widths, chain_widths,
@ -1455,6 +1451,8 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
Ordering::Equal => {} Ordering::Equal => {}
} }
} }
responses.add(NodeGraphMessage::SendWires);
} }
NodeGraphMessage::ToggleSelectedAsLayersOrNodes => { NodeGraphMessage::ToggleSelectedAsLayersOrNodes => {
let Some(selected_nodes) = network_interface.selected_nodes_in_nested_network(selection_network_path) else { let Some(selected_nodes) = network_interface.selected_nodes_in_nested_network(selection_network_path) else {
@ -1474,6 +1472,8 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
} }
NodeGraphMessage::ShiftNodePosition { node_id, x, y } => { NodeGraphMessage::ShiftNodePosition { node_id, x, y } => {
network_interface.shift_absolute_node_position(&node_id, IVec2::new(x, y), selection_network_path); network_interface.shift_absolute_node_position(&node_id, IVec2::new(x, y), selection_network_path);
responses.add(NodeGraphMessage::SendWires);
} }
NodeGraphMessage::SetToNodeOrLayer { node_id, is_layer } => { NodeGraphMessage::SetToNodeOrLayer { node_id, is_layer } => {
if is_layer && !network_interface.is_eligible_to_be_layer(&node_id, selection_network_path) { if is_layer && !network_interface.is_eligible_to_be_layer(&node_id, selection_network_path) {
@ -1487,6 +1487,7 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
}); });
responses.add(NodeGraphMessage::RunDocumentGraph); responses.add(NodeGraphMessage::RunDocumentGraph);
responses.add(NodeGraphMessage::SendGraph); responses.add(NodeGraphMessage::SendGraph);
responses.add(NodeGraphMessage::SendWires);
} }
NodeGraphMessage::SetDisplayName { NodeGraphMessage::SetDisplayName {
node_id, node_id,
@ -1623,7 +1624,7 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
// } // }
let Some(network_metadata) = network_interface.network_metadata(selection_network_path) else { let Some(network_metadata) = network_interface.network_metadata(selection_network_path) else {
log::error!("Could not get network metadata in PointerMove"); log::error!("Could not get network metadata in UpdateBoxSelection");
return; return;
}; };
@ -1689,7 +1690,8 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
) )
.into_iter() .into_iter()
.next(); .next();
responses.add(NodeGraphMessage::UpdateVisibleNodes);
responses.add(NodeGraphMessage::SendWires);
responses.add(FrontendMessage::UpdateImportsExports { responses.add(FrontendMessage::UpdateImportsExports {
imports, imports,
exports, exports,
@ -1835,6 +1837,7 @@ impl NodeGraphMessageHandler {
node_id: Some(node_id), node_id: Some(node_id),
node_type: node_type.clone(), node_type: node_type.clone(),
xy: None, xy: None,
add_transaction: true,
} }
.into(), .into(),
NodeGraphMessage::SelectedNodesSet { nodes: vec![node_id] }.into(), NodeGraphMessage::SelectedNodesSet { nodes: vec![node_id] }.into(),
@ -2151,69 +2154,39 @@ impl NodeGraphMessageHandler {
} }
} }
fn collect_wires(network_interface: &NodeNetworkInterface, breadcrumb_network_path: &[NodeId]) -> Vec<FrontendNodeWire> { fn collect_wires(&mut self, network_interface: &mut NodeNetworkInterface, graph_wire_style: GraphWireStyle, breadcrumb_network_path: &[NodeId]) -> Vec<WirePathUpdate> {
let Some(network) = network_interface.nested_network(breadcrumb_network_path) else { let mut added_wires = network_interface
log::error!("Could not get network when collecting wires"); .node_graph_input_connectors(breadcrumb_network_path)
return Vec::new();
};
let mut wires = network
.nodes
.iter() .iter()
.flat_map(|(wire_end, node)| node.inputs.iter().filter(|input| input.is_exposed()).enumerate().map(move |(index, input)| (input, wire_end, index))) .filter_map(|connector| network_interface.newly_loaded_input_wire(connector, graph_wire_style, breadcrumb_network_path))
.filter_map(|(input, &wire_end, wire_end_input_index)| {
match *input {
NodeInput::Node {
node_id: wire_start,
output_index: wire_start_output_index,
// TODO: add ui for lambdas
lambda: _,
} => Some(FrontendNodeWire {
wire_start: OutputConnector::node(wire_start, wire_start_output_index),
wire_end: InputConnector::node(wire_end, wire_end_input_index),
dashed: false,
}),
NodeInput::Network { import_index, .. } => Some(FrontendNodeWire {
wire_start: OutputConnector::Import(import_index),
wire_end: InputConnector::node(wire_end, wire_end_input_index),
dashed: false,
}),
_ => None,
}
})
.collect::<Vec<_>>(); .collect::<Vec<_>>();
// Connect primary export to root node, since previewing a node will change the primary export let changed_wire_inputs = added_wires.iter().map(|update| (update.id, update.input_index)).collect::<Vec<_>>();
if let Some(root_node) = network_interface.root_node(breadcrumb_network_path) { self.frontend_wires.extend(changed_wire_inputs);
wires.push(FrontendNodeWire {
wire_start: OutputConnector::node(root_node.node_id, root_node.output_index), let mut orphaned_wire_inputs = self.frontend_wires.clone();
wire_end: InputConnector::Export(0), self.frontend_wires = network_interface
dashed: false, .node_graph_wire_inputs(breadcrumb_network_path)
}); .iter()
.filter_map(|visible_wire_input| orphaned_wire_inputs.take(visible_wire_input))
.collect::<HashSet<_>>();
added_wires.extend(orphaned_wire_inputs.into_iter().map(|(id, input_index)| WirePathUpdate {
id,
input_index,
wire_path_update: None,
}));
if let Some(wire_to_root) = network_interface.wire_to_root(graph_wire_style, breadcrumb_network_path) {
added_wires.push(wire_to_root);
} else {
added_wires.push(WirePathUpdate {
id: NodeId(u64::MAX),
input_index: usize::MAX,
wire_path_update: None,
})
} }
// Connect rest of exports to their actual export field since they are not affected by previewing. Only connect the primary export if it is dashed added_wires
for (i, export) in network.exports.iter().enumerate() {
let dashed = matches!(network_interface.previewing(breadcrumb_network_path), Previewing::Yes { .. }) && i == 0;
if dashed || i != 0 {
if let NodeInput::Node { node_id, output_index, .. } = export {
wires.push(FrontendNodeWire {
wire_start: OutputConnector::Node {
node_id: *node_id,
output_index: *output_index,
},
wire_end: InputConnector::Export(i),
dashed,
});
} else if let NodeInput::Network { import_index, .. } = *export {
wires.push(FrontendNodeWire {
wire_start: OutputConnector::Import(import_index),
wire_end: InputConnector::Export(i),
dashed,
})
}
}
}
wires
} }
fn collect_nodes(&self, network_interface: &mut NodeNetworkInterface, breadcrumb_network_path: &[NodeId]) -> Vec<FrontendNode> { fn collect_nodes(&self, network_interface: &mut NodeNetworkInterface, breadcrumb_network_path: &[NodeId]) -> Vec<FrontendNode> {
@ -2237,6 +2210,7 @@ impl NodeGraphMessageHandler {
log::error!("Could not get position for node {node_id}"); log::error!("Could not get position for node {node_id}");
} }
} }
let mut frontend_inputs_lookup = frontend_inputs_lookup(breadcrumb_network_path, network_interface); let mut frontend_inputs_lookup = frontend_inputs_lookup(breadcrumb_network_path, network_interface);
let Some(network) = network_interface.nested_network(breadcrumb_network_path) else { let Some(network) = network_interface.nested_network(breadcrumb_network_path) else {
log::error!("Could not get nested network when collecting nodes"); log::error!("Could not get nested network when collecting nodes");
@ -2252,13 +2226,14 @@ impl NodeGraphMessageHandler {
let node_id_path = [breadcrumb_network_path, (&[node_id])].concat(); let node_id_path = [breadcrumb_network_path, (&[node_id])].concat();
let inputs = frontend_inputs_lookup.remove(&node_id).unwrap_or_default(); let inputs = frontend_inputs_lookup.remove(&node_id).unwrap_or_default();
let mut inputs = inputs.into_iter().map(|input| { let mut inputs = inputs.into_iter().map(|input| {
input.map(|input| FrontendGraphInput { input.map(|input| FrontendGraphInput {
data_type: FrontendGraphDataType::displayed_type(&input.ty, &input.type_source), data_type: FrontendGraphDataType::displayed_type(&input.ty, &input.type_source),
resolved_type: Some(format!("{:?}", &input.ty)), resolved_type: format!("{:?}", &input.ty),
valid_types: input.valid_types.iter().map(|ty| ty.to_string()).collect(), valid_types: input.valid_types.iter().map(|ty| ty.to_string()).collect(),
name: input.input_name.unwrap_or_else(|| input.ty.nested_type().to_string()), name: input.input_name,
description: input.input_description.unwrap_or_default(), description: input.input_description,
connected_to: input.output_connector, connected_to: input.output_connector,
}) })
}); });
@ -2266,20 +2241,16 @@ impl NodeGraphMessageHandler {
let primary_input = inputs.next().flatten(); let primary_input = inputs.next().flatten();
let exposed_inputs = inputs.flatten().collect(); let exposed_inputs = inputs.flatten().collect();
let output_types = network_interface.output_types(&node_id, breadcrumb_network_path); let (output_type, type_source) = network_interface.output_type(&node_id, 0, breadcrumb_network_path);
let primary_output_type = output_types.first().cloned().flatten(); let frontend_data_type = FrontendGraphDataType::displayed_type(&output_type, &type_source);
let frontend_data_type = if let Some((output_type, type_source)) = &primary_output_type {
FrontendGraphDataType::displayed_type(output_type, type_source)
} else {
FrontendGraphDataType::General
};
let connected_to = outward_wires.get(&OutputConnector::node(node_id, 0)).cloned().unwrap_or_default(); let connected_to = outward_wires.get(&OutputConnector::node(node_id, 0)).cloned().unwrap_or_default();
let primary_output = if network_interface.has_primary_output(&node_id, breadcrumb_network_path) && !output_types.is_empty() { let primary_output = if network_interface.has_primary_output(&node_id, breadcrumb_network_path) {
Some(FrontendGraphOutput { Some(FrontendGraphOutput {
data_type: frontend_data_type, data_type: frontend_data_type,
name: "Output 1".to_string(), name: "Output 1".to_string(),
description: String::new(), description: String::new(),
resolved_type: primary_output_type.map(|(input, _)| format!("{input:?}")), resolved_type: format!("{:?}", output_type),
connected_to, connected_to,
}) })
} else { } else {
@ -2287,15 +2258,13 @@ impl NodeGraphMessageHandler {
}; };
let mut exposed_outputs = Vec::new(); let mut exposed_outputs = Vec::new();
for (index, exposed_output) in output_types.iter().enumerate() { for output_index in 0..network_interface.number_of_outputs(&node_id, breadcrumb_network_path) {
if index == 0 && network_interface.has_primary_output(&node_id, breadcrumb_network_path) { if output_index == 0 && network_interface.has_primary_output(&node_id, breadcrumb_network_path) {
continue; continue;
} }
let frontend_data_type = if let Some((output_type, type_source)) = &exposed_output { let (output_type, type_source) = network_interface.output_type(&node_id, 0, breadcrumb_network_path);
FrontendGraphDataType::displayed_type(output_type, type_source) let data_type = FrontendGraphDataType::displayed_type(&output_type, &type_source);
} else {
FrontendGraphDataType::General
};
let Some(node_metadata) = network_metadata.persistent_metadata.node_metadata.get(&node_id) else { let Some(node_metadata) = network_metadata.persistent_metadata.node_metadata.get(&node_id) else {
log::error!("Could not get node_metadata when getting output for {node_id}"); log::error!("Could not get node_metadata when getting output for {node_id}");
continue; continue;
@ -2303,17 +2272,17 @@ impl NodeGraphMessageHandler {
let output_name = node_metadata let output_name = node_metadata
.persistent_metadata .persistent_metadata
.output_names .output_names
.get(index) .get(output_index)
.map(|output_name| output_name.to_string()) .cloned()
.filter(|output_name| !output_name.is_empty()) .filter(|output_name| !output_name.is_empty())
.unwrap_or_else(|| exposed_output.clone().map(|(output_type, _)| output_type.nested_type().to_string()).unwrap_or_default()); .unwrap_or_else(|| output_type.nested_type().to_string());
let connected_to = outward_wires.get(&OutputConnector::node(node_id, index)).cloned().unwrap_or_default(); let connected_to = outward_wires.get(&OutputConnector::node(node_id, output_index)).cloned().unwrap_or_default();
exposed_outputs.push(FrontendGraphOutput { exposed_outputs.push(FrontendGraphOutput {
data_type: frontend_data_type, data_type,
name: output_name, name: output_name,
description: String::new(), description: String::new(),
resolved_type: exposed_output.clone().map(|(input, _)| format!("{input:?}")), resolved_type: format!("{:?}", output_type),
connected_to, connected_to,
}); });
} }
@ -2416,9 +2385,9 @@ impl NodeGraphMessageHandler {
network_interface.upstream_flow_back_from_nodes(vec![node_id], &[], network_interface::FlowType::HorizontalFlow).last().is_some_and(|node_id| network_interface.upstream_flow_back_from_nodes(vec![node_id], &[], network_interface::FlowType::HorizontalFlow).last().is_some_and(|node_id|
network_interface.document_node(&node_id, &[]).map_or_else(||{log::error!("Could not get node {node_id} in update_layer_panel"); false}, |node| { network_interface.document_node(&node_id, &[]).map_or_else(||{log::error!("Could not get node {node_id} in update_layer_panel"); false}, |node| {
if network_interface.is_layer(&node_id, &[]) { if network_interface.is_layer(&node_id, &[]) {
node.inputs.iter().filter(|input| input.is_exposed_to_frontend(true)).nth(1).is_some_and(|input| input.as_value().is_some()) node.inputs.iter().filter(|input| input.is_exposed()).nth(1).is_some_and(|input| input.as_value().is_some())
} else { } else {
node.inputs.iter().filter(|input| input.is_exposed_to_frontend(true)).nth(0).is_some_and(|input| input.as_value().is_some()) node.inputs.iter().filter(|input| input.is_exposed()).nth(0).is_some_and(|input| input.as_value().is_some())
} }
})) }))
); );
@ -2467,66 +2436,6 @@ impl NodeGraphMessageHandler {
} }
} }
fn build_wire_path_string(output_position: DVec2, input_position: DVec2, vertical_out: bool, vertical_in: bool) -> String {
let locations = Self::build_wire_path_locations(output_position, input_position, vertical_out, vertical_in);
let smoothing = 0.5;
let delta01 = DVec2::new((locations[1].x - locations[0].x) * smoothing, (locations[1].y - locations[0].y) * smoothing);
let delta23 = DVec2::new((locations[3].x - locations[2].x) * smoothing, (locations[3].y - locations[2].y) * smoothing);
format!(
"M{},{} L{},{} C{},{} {},{} {},{} L{},{}",
locations[0].x,
locations[0].y,
locations[1].x,
locations[1].y,
locations[1].x + delta01.x,
locations[1].y + delta01.y,
locations[2].x - delta23.x,
locations[2].y - delta23.y,
locations[2].x,
locations[2].y,
locations[3].x,
locations[3].y
)
}
fn build_wire_path_locations(output_position: DVec2, input_position: DVec2, vertical_out: bool, vertical_in: bool) -> Vec<DVec2> {
let horizontal_gap = (output_position.x - input_position.x).abs();
let vertical_gap = (output_position.y - input_position.y).abs();
// TODO: Finish this commented out code replacement for the code below it based on this diagram: <https://files.keavon.com/-/SuperbWideFoxterrier/capture.png>
// // Straight: stacking lines which are always straight, or a straight horizontal wire between two aligned nodes
// if ((verticalOut && vertical_in) || (!verticalOut && !vertical_in && vertical_gap === 0)) {
// return [
// { x: output_position.x, y: output_position.y },
// { x: input_position.x, y: input_position.y },
// ];
// }
// // L-shape bend
// if (verticalOut !== vertical_in) {
// }
let curve_length = 24.;
let curve_falloff_rate = curve_length * std::f64::consts::PI * 2.;
let horizontal_curve_amount = -(2_f64.powf((-10. * horizontal_gap) / curve_falloff_rate)) + 1.;
let vertical_curve_amount = -(2_f64.powf((-10. * vertical_gap) / curve_falloff_rate)) + 1.;
let horizontal_curve = horizontal_curve_amount * curve_length;
let vertical_curve = vertical_curve_amount * curve_length;
vec![
output_position,
DVec2::new(
if vertical_out { output_position.x } else { output_position.x + horizontal_curve },
if vertical_out { output_position.y - vertical_curve } else { output_position.y },
),
DVec2::new(
if vertical_in { input_position.x } else { input_position.x - horizontal_curve },
if vertical_in { input_position.y + vertical_curve } else { input_position.y },
),
DVec2::new(input_position.x, input_position.y),
]
}
pub fn update_node_graph_hints(&self, responses: &mut VecDeque<Message>) { pub fn update_node_graph_hints(&self, responses: &mut VecDeque<Message>) {
// A wire is in progress and its start and end connectors are set // A wire is in progress and its start and end connectors are set
let wiring = self.wire_in_progress_from_connector.is_some(); let wiring = self.wire_in_progress_from_connector.is_some();
@ -2570,8 +2479,8 @@ impl NodeGraphMessageHandler {
#[derive(Default)] #[derive(Default)]
struct InputLookup { struct InputLookup {
input_name: Option<String>, input_name: String,
input_description: Option<String>, input_description: String,
ty: Type, ty: Type,
type_source: TypeSource, type_source: TypeSource,
valid_types: Vec<Type>, valid_types: Vec<Type>,
@ -2586,34 +2495,31 @@ fn frontend_inputs_lookup(breadcrumb_network_path: &[NodeId], network_interface:
return Default::default(); return Default::default();
}; };
let mut frontend_inputs_lookup = HashMap::new(); let mut frontend_inputs_lookup = HashMap::new();
for (&node_id, node) in network.nodes.iter() { for (node_id, index, output_connector, is_exposed) in network
let mut inputs = Vec::with_capacity(node.inputs.len()); .nodes
for (index, input) in node.inputs.iter().enumerate() { .iter()
let is_exposed = input.is_exposed_to_frontend(breadcrumb_network_path.is_empty()); .flat_map(|(node_id, node)| {
node.inputs
// Skip not exposed inputs (they still get an entry to help with finding the primary input) .iter()
if !is_exposed { .enumerate()
inputs.push(None); .map(|(index, input)| (*node_id, index, OutputConnector::from_input(input), input.is_exposed()))
continue; })
} .collect::<Vec<_>>()
{
// Skip not exposed inputs (they still get an entry to help with finding the primary input)
let lookup = if !is_exposed {
None
} else {
// Get the name from the metadata here (since it also requires a reference to the `network_interface`) // Get the name from the metadata here (since it also requires a reference to the `network_interface`)
let input_name = network_interface let (input_name, input_description) = network_interface.displayed_input_name_and_description(&node_id, index, breadcrumb_network_path);
.input_name(node_id, index, breadcrumb_network_path) Some(InputLookup {
.filter(|s| !s.is_empty())
.map(|name| name.to_string());
let input_description = network_interface.input_description(node_id, index, breadcrumb_network_path).map(|description| description.to_string());
// Get the output connector that feeds into this input (done here as well for simplicity)
let connector = OutputConnector::from_input(input);
inputs.push(Some(InputLookup {
input_name, input_name,
input_description, input_description,
output_connector: connector, output_connector,
..Default::default() ..Default::default()
})); })
} };
frontend_inputs_lookup.insert(node_id, inputs); frontend_inputs_lookup.entry(node_id).or_insert_with(Vec::new).push(lookup);
} }
for (&node_id, value) in frontend_inputs_lookup.iter_mut() { for (&node_id, value) in frontend_inputs_lookup.iter_mut() {
@ -2656,6 +2562,7 @@ impl Default for NodeGraphMessageHandler {
select_if_not_dragged: None, select_if_not_dragged: None,
wire_in_progress_from_connector: None, wire_in_progress_from_connector: None,
wire_in_progress_to_connector: None, wire_in_progress_to_connector: None,
wire_in_progress_type: FrontendGraphDataType::General,
context_menu: None, context_menu: None,
deselect_on_pointer_up: None, deselect_on_pointer_up: None,
auto_panning: Default::default(), auto_panning: Default::default(),
@ -2663,6 +2570,8 @@ impl Default for NodeGraphMessageHandler {
reordering_export: None, reordering_export: None,
reordering_import: None, reordering_import: None,
end_index: None, end_index: None,
frontend_nodes: Vec::new(),
frontend_wires: HashSet::new(),
} }
} }
} }

View file

@ -60,17 +60,12 @@ pub fn expose_widget(node_id: NodeId, index: usize, data_type: FrontendGraphData
"Expose this parameter as a node input in the graph" "Expose this parameter as a node input in the graph"
}) })
.on_update(move |_parameter| { .on_update(move |_parameter| {
Message::Batched(Box::new([ Message::Batched(Box::new([NodeGraphMessage::ExposeInput {
NodeGraphMessage::ExposeInput { input_connector: InputConnector::node(node_id, index),
input_connector: InputConnector::node(node_id, index), set_to_exposed: !exposed,
set_to_exposed: !exposed, start_transaction: true,
start_transaction: true, }
} .into()]))
.into(),
DocumentMessage::GraphViewOverlay { open: true }.into(),
NavigationMessage::FitViewportToSelection.into(),
DocumentMessage::ZoomCanvasTo100Percent.into(),
]))
}) })
.widget_holder() .widget_holder()
} }
@ -85,28 +80,31 @@ pub fn add_blank_assist(widgets: &mut Vec<WidgetHolder>) {
]); ]);
} }
pub fn start_widgets(parameter_widgets_info: ParameterWidgetsInfo, data_type: FrontendGraphDataType) -> Vec<WidgetHolder> { pub fn start_widgets(parameter_widgets_info: ParameterWidgetsInfo) -> Vec<WidgetHolder> {
start_widgets_exposable(parameter_widgets_info, data_type, true)
}
pub fn start_widgets_exposable(parameter_widgets_info: ParameterWidgetsInfo, data_type: FrontendGraphDataType, exposable: bool) -> Vec<WidgetHolder> {
let ParameterWidgetsInfo { let ParameterWidgetsInfo {
document_node, document_node,
node_id, node_id,
index, index,
name, name,
description, description,
input_type,
blank_assist, blank_assist,
exposeable,
} = parameter_widgets_info; } = parameter_widgets_info;
let Some(document_node) = document_node else {
log::warn!("A widget failed to be built because its document node is invalid.");
return vec![];
};
let Some(input) = document_node.inputs.get(index) else { let Some(input) = document_node.inputs.get(index) else {
log::warn!("A widget failed to be built because its node's input index is invalid."); log::warn!("A widget failed to be built because its node's input index is invalid.");
return vec![]; return vec![];
}; };
let description = if description != "TODO" { description } else { "" }; let description = if description != "TODO" { description } else { String::new() };
let mut widgets = Vec::with_capacity(6); let mut widgets = Vec::with_capacity(6);
if exposable { if exposeable {
widgets.push(expose_widget(node_id, index, data_type, input.is_exposed())); widgets.push(expose_widget(node_id, index, input_type, input.is_exposed()));
} }
widgets.push(TextLabel::new(name).tooltip(description).widget_holder()); widgets.push(TextLabel::new(name).tooltip(description).widget_holder());
if blank_assist { if blank_assist {
@ -126,18 +124,6 @@ pub(crate) fn property_from_type(
step: Option<f64>, step: Option<f64>,
context: &mut NodePropertiesContext, context: &mut NodePropertiesContext,
) -> Result<Vec<LayoutGroup>, Vec<LayoutGroup>> { ) -> Result<Vec<LayoutGroup>, Vec<LayoutGroup>> {
let Some(network) = context.network_interface.nested_network(context.selection_network_path) else {
log::warn!("A widget failed to be built for node {node_id}, index {index} because the network could not be determined");
return Err(vec![]);
};
let Some(document_node) = network.nodes.get(&node_id) else {
log::warn!("A widget failed to be built for node {node_id}, index {index} because the document node does not exist");
return Err(vec![]);
};
let name = context.network_interface.input_name(node_id, index, context.selection_network_path).unwrap_or_default();
let description = context.network_interface.input_description(node_id, index, context.selection_network_path).unwrap_or_default();
let (mut number_min, mut number_max, range) = number_options; let (mut number_min, mut number_max, range) = number_options;
let mut number_input = NumberInput::default(); let mut number_input = NumberInput::default();
if let Some((range_start, range_end)) = range { if let Some((range_start, range_end)) = range {
@ -158,7 +144,7 @@ pub(crate) fn property_from_type(
let min = |x: f64| number_min.unwrap_or(x); let min = |x: f64| number_min.unwrap_or(x);
let max = |x: f64| number_max.unwrap_or(x); let max = |x: f64| number_max.unwrap_or(x);
let default_info = ParameterWidgetsInfo::new(document_node, node_id, index, name, description, true); let default_info = ParameterWidgetsInfo::new(node_id, index, true, context);
let mut extra_widgets = vec![]; let mut extra_widgets = vec![];
let widgets = match ty { let widgets = match ty {
@ -247,7 +233,7 @@ pub(crate) fn property_from_type(
// OTHER // OTHER
// ===== // =====
_ => { _ => {
let mut widgets = start_widgets(default_info, FrontendGraphDataType::General); let mut widgets = start_widgets(default_info);
widgets.extend_from_slice(&[ widgets.extend_from_slice(&[
Separator::new(SeparatorType::Unrelated).widget_holder(), Separator::new(SeparatorType::Unrelated).widget_holder(),
TextLabel::new("-") TextLabel::new("-")
@ -277,8 +263,9 @@ pub(crate) fn property_from_type(
pub fn text_widget(parameter_widgets_info: ParameterWidgetsInfo) -> Vec<WidgetHolder> { pub fn text_widget(parameter_widgets_info: ParameterWidgetsInfo) -> Vec<WidgetHolder> {
let ParameterWidgetsInfo { document_node, node_id, index, .. } = parameter_widgets_info; let ParameterWidgetsInfo { document_node, node_id, index, .. } = parameter_widgets_info;
let mut widgets = start_widgets(parameter_widgets_info, FrontendGraphDataType::General); let mut widgets = start_widgets(parameter_widgets_info);
let Some(document_node) = document_node else { return Vec::new() };
let Some(input) = document_node.inputs.get(index) else { let Some(input) = document_node.inputs.get(index) else {
log::warn!("A widget failed to be built because its node's input index is invalid."); log::warn!("A widget failed to be built because its node's input index is invalid.");
return vec![]; return vec![];
@ -298,8 +285,9 @@ pub fn text_widget(parameter_widgets_info: ParameterWidgetsInfo) -> Vec<WidgetHo
pub fn text_area_widget(parameter_widgets_info: ParameterWidgetsInfo) -> Vec<WidgetHolder> { pub fn text_area_widget(parameter_widgets_info: ParameterWidgetsInfo) -> Vec<WidgetHolder> {
let ParameterWidgetsInfo { document_node, node_id, index, .. } = parameter_widgets_info; let ParameterWidgetsInfo { document_node, node_id, index, .. } = parameter_widgets_info;
let mut widgets = start_widgets(parameter_widgets_info, FrontendGraphDataType::General); let mut widgets = start_widgets(parameter_widgets_info);
let Some(document_node) = document_node else { return Vec::new() };
let Some(input) = document_node.inputs.get(index) else { let Some(input) = document_node.inputs.get(index) else {
log::warn!("A widget failed to be built because its node's input index is invalid."); log::warn!("A widget failed to be built because its node's input index is invalid.");
return vec![]; return vec![];
@ -319,8 +307,9 @@ pub fn text_area_widget(parameter_widgets_info: ParameterWidgetsInfo) -> Vec<Wid
pub fn bool_widget(parameter_widgets_info: ParameterWidgetsInfo, checkbox_input: CheckboxInput) -> Vec<WidgetHolder> { pub fn bool_widget(parameter_widgets_info: ParameterWidgetsInfo, checkbox_input: CheckboxInput) -> Vec<WidgetHolder> {
let ParameterWidgetsInfo { document_node, node_id, index, .. } = parameter_widgets_info; let ParameterWidgetsInfo { document_node, node_id, index, .. } = parameter_widgets_info;
let mut widgets = start_widgets(parameter_widgets_info, FrontendGraphDataType::General); let mut widgets = start_widgets(parameter_widgets_info);
let Some(document_node) = document_node else { return Vec::new() };
let Some(input) = document_node.inputs.get(index) else { let Some(input) = document_node.inputs.get(index) else {
log::warn!("A widget failed to be built because its node's input index is invalid."); log::warn!("A widget failed to be built because its node's input index is invalid.");
return vec![]; return vec![];
@ -341,8 +330,9 @@ pub fn bool_widget(parameter_widgets_info: ParameterWidgetsInfo, checkbox_input:
pub fn reference_point_widget(parameter_widgets_info: ParameterWidgetsInfo, disabled: bool) -> Vec<WidgetHolder> { pub fn reference_point_widget(parameter_widgets_info: ParameterWidgetsInfo, disabled: bool) -> Vec<WidgetHolder> {
let ParameterWidgetsInfo { document_node, node_id, index, .. } = parameter_widgets_info; let ParameterWidgetsInfo { document_node, node_id, index, .. } = parameter_widgets_info;
let mut widgets = start_widgets(parameter_widgets_info, FrontendGraphDataType::General); let mut widgets = start_widgets(parameter_widgets_info);
let Some(document_node) = document_node else { return Vec::new() };
let Some(input) = document_node.inputs.get(index) else { let Some(input) = document_node.inputs.get(index) else {
log::warn!("A widget failed to be built because its node's input index is invalid."); log::warn!("A widget failed to be built because its node's input index is invalid.");
return vec![]; return vec![];
@ -371,7 +361,7 @@ pub fn reference_point_widget(parameter_widgets_info: ParameterWidgetsInfo, disa
pub fn footprint_widget(parameter_widgets_info: ParameterWidgetsInfo, extra_widgets: &mut Vec<LayoutGroup>) -> LayoutGroup { pub fn footprint_widget(parameter_widgets_info: ParameterWidgetsInfo, extra_widgets: &mut Vec<LayoutGroup>) -> LayoutGroup {
let ParameterWidgetsInfo { document_node, node_id, index, .. } = parameter_widgets_info; let ParameterWidgetsInfo { document_node, node_id, index, .. } = parameter_widgets_info;
let mut location_widgets = start_widgets(parameter_widgets_info, FrontendGraphDataType::General); let mut location_widgets = start_widgets(parameter_widgets_info);
location_widgets.push(Separator::new(SeparatorType::Unrelated).widget_holder()); location_widgets.push(Separator::new(SeparatorType::Unrelated).widget_holder());
let mut scale_widgets = vec![TextLabel::new("").widget_holder()]; let mut scale_widgets = vec![TextLabel::new("").widget_holder()];
@ -382,10 +372,12 @@ pub fn footprint_widget(parameter_widgets_info: ParameterWidgetsInfo, extra_widg
add_blank_assist(&mut resolution_widgets); add_blank_assist(&mut resolution_widgets);
resolution_widgets.push(Separator::new(SeparatorType::Unrelated).widget_holder()); resolution_widgets.push(Separator::new(SeparatorType::Unrelated).widget_holder());
let Some(document_node) = document_node else { return LayoutGroup::default() };
let Some(input) = document_node.inputs.get(index) else { let Some(input) = document_node.inputs.get(index) else {
log::warn!("A widget failed to be built because its node's input index is invalid."); log::warn!("A widget failed to be built because its node's input index is invalid.");
return Vec::new().into(); return Vec::new().into();
}; };
if let Some(&TaggedValue::Footprint(footprint)) = input.as_non_exposed_value() { if let Some(&TaggedValue::Footprint(footprint)) = input.as_non_exposed_value() {
let top_left = footprint.transform.transform_point2(DVec2::ZERO); let top_left = footprint.transform.transform_point2(DVec2::ZERO);
let bounds = footprint.scale(); let bounds = footprint.scale();
@ -517,8 +509,9 @@ pub fn footprint_widget(parameter_widgets_info: ParameterWidgetsInfo, extra_widg
pub fn coordinate_widget(parameter_widgets_info: ParameterWidgetsInfo, x: &str, y: &str, unit: &str, min: Option<f64>) -> LayoutGroup { pub fn coordinate_widget(parameter_widgets_info: ParameterWidgetsInfo, x: &str, y: &str, unit: &str, min: Option<f64>) -> LayoutGroup {
let ParameterWidgetsInfo { document_node, node_id, index, .. } = parameter_widgets_info; let ParameterWidgetsInfo { document_node, node_id, index, .. } = parameter_widgets_info;
let mut widgets = start_widgets(parameter_widgets_info, FrontendGraphDataType::Number); let mut widgets = start_widgets(parameter_widgets_info);
let Some(document_node) = document_node else { return LayoutGroup::default() };
let Some(input) = document_node.inputs.get(index) else { let Some(input) = document_node.inputs.get(index) else {
log::warn!("A widget failed to be built because its node's input index is invalid."); log::warn!("A widget failed to be built because its node's input index is invalid.");
return LayoutGroup::Row { widgets: vec![] }; return LayoutGroup::Row { widgets: vec![] };
@ -629,7 +622,7 @@ pub fn coordinate_widget(parameter_widgets_info: ParameterWidgetsInfo, x: &str,
pub fn array_of_number_widget(parameter_widgets_info: ParameterWidgetsInfo, text_input: TextInput) -> Vec<WidgetHolder> { pub fn array_of_number_widget(parameter_widgets_info: ParameterWidgetsInfo, text_input: TextInput) -> Vec<WidgetHolder> {
let ParameterWidgetsInfo { document_node, node_id, index, .. } = parameter_widgets_info; let ParameterWidgetsInfo { document_node, node_id, index, .. } = parameter_widgets_info;
let mut widgets = start_widgets(parameter_widgets_info, FrontendGraphDataType::Number); let mut widgets = start_widgets(parameter_widgets_info);
let from_string = |string: &str| { let from_string = |string: &str| {
string string
@ -641,6 +634,7 @@ pub fn array_of_number_widget(parameter_widgets_info: ParameterWidgetsInfo, text
.map(TaggedValue::VecF64) .map(TaggedValue::VecF64)
}; };
let Some(document_node) = document_node else { return Vec::new() };
let Some(input) = document_node.inputs.get(index) else { let Some(input) = document_node.inputs.get(index) else {
log::warn!("A widget failed to be built because its node's input index is invalid."); log::warn!("A widget failed to be built because its node's input index is invalid.");
return vec![]; return vec![];
@ -660,7 +654,7 @@ pub fn array_of_number_widget(parameter_widgets_info: ParameterWidgetsInfo, text
pub fn array_of_coordinates_widget(parameter_widgets_info: ParameterWidgetsInfo, text_props: TextInput) -> Vec<WidgetHolder> { pub fn array_of_coordinates_widget(parameter_widgets_info: ParameterWidgetsInfo, text_props: TextInput) -> Vec<WidgetHolder> {
let ParameterWidgetsInfo { document_node, node_id, index, .. } = parameter_widgets_info; let ParameterWidgetsInfo { document_node, node_id, index, .. } = parameter_widgets_info;
let mut widgets = start_widgets(parameter_widgets_info, FrontendGraphDataType::Number); let mut widgets = start_widgets(parameter_widgets_info);
let from_string = |string: &str| { let from_string = |string: &str| {
string string
@ -672,6 +666,7 @@ pub fn array_of_coordinates_widget(parameter_widgets_info: ParameterWidgetsInfo,
.map(TaggedValue::VecDVec2) .map(TaggedValue::VecDVec2)
}; };
let Some(document_node) = document_node else { return Vec::new() };
let Some(input) = document_node.inputs.get(index) else { let Some(input) = document_node.inputs.get(index) else {
log::warn!("A widget failed to be built because its node's input index is invalid."); log::warn!("A widget failed to be built because its node's input index is invalid.");
return vec![]; return vec![];
@ -691,11 +686,12 @@ pub fn array_of_coordinates_widget(parameter_widgets_info: ParameterWidgetsInfo,
pub fn font_inputs(parameter_widgets_info: ParameterWidgetsInfo) -> (Vec<WidgetHolder>, Option<Vec<WidgetHolder>>) { pub fn font_inputs(parameter_widgets_info: ParameterWidgetsInfo) -> (Vec<WidgetHolder>, Option<Vec<WidgetHolder>>) {
let ParameterWidgetsInfo { document_node, node_id, index, .. } = parameter_widgets_info; let ParameterWidgetsInfo { document_node, node_id, index, .. } = parameter_widgets_info;
let mut first_widgets = start_widgets(parameter_widgets_info, FrontendGraphDataType::General); let mut first_widgets = start_widgets(parameter_widgets_info);
let mut second_widgets = None; let mut second_widgets = None;
let from_font_input = |font: &FontInput| TaggedValue::Font(Font::new(font.font_family.clone(), font.font_style.clone())); let from_font_input = |font: &FontInput| TaggedValue::Font(Font::new(font.font_family.clone(), font.font_style.clone()));
let Some(document_node) = document_node else { return (Vec::new(), None) };
let Some(input) = document_node.inputs.get(index) else { let Some(input) = document_node.inputs.get(index) else {
log::warn!("A widget failed to be built because its node's input index is invalid."); log::warn!("A widget failed to be built because its node's input index is invalid.");
return (vec![], None); return (vec![], None);
@ -725,7 +721,7 @@ pub fn font_inputs(parameter_widgets_info: ParameterWidgetsInfo) -> (Vec<WidgetH
} }
pub fn vector_data_widget(parameter_widgets_info: ParameterWidgetsInfo) -> Vec<WidgetHolder> { pub fn vector_data_widget(parameter_widgets_info: ParameterWidgetsInfo) -> Vec<WidgetHolder> {
let mut widgets = start_widgets(parameter_widgets_info, FrontendGraphDataType::VectorData); let mut widgets = start_widgets(parameter_widgets_info);
widgets.push(Separator::new(SeparatorType::Unrelated).widget_holder()); widgets.push(Separator::new(SeparatorType::Unrelated).widget_holder());
widgets.push(TextLabel::new("Vector data is supplied through the node graph").widget_holder()); widgets.push(TextLabel::new("Vector data is supplied through the node graph").widget_holder());
@ -734,7 +730,7 @@ pub fn vector_data_widget(parameter_widgets_info: ParameterWidgetsInfo) -> Vec<W
} }
pub fn raster_widget(parameter_widgets_info: ParameterWidgetsInfo) -> Vec<WidgetHolder> { pub fn raster_widget(parameter_widgets_info: ParameterWidgetsInfo) -> Vec<WidgetHolder> {
let mut widgets = start_widgets(parameter_widgets_info, FrontendGraphDataType::Raster); let mut widgets = start_widgets(parameter_widgets_info);
widgets.push(Separator::new(SeparatorType::Unrelated).widget_holder()); widgets.push(Separator::new(SeparatorType::Unrelated).widget_holder());
widgets.push(TextLabel::new("Raster data is supplied through the node graph").widget_holder()); widgets.push(TextLabel::new("Raster data is supplied through the node graph").widget_holder());
@ -743,7 +739,7 @@ pub fn raster_widget(parameter_widgets_info: ParameterWidgetsInfo) -> Vec<Widget
} }
pub fn group_widget(parameter_widgets_info: ParameterWidgetsInfo) -> Vec<WidgetHolder> { pub fn group_widget(parameter_widgets_info: ParameterWidgetsInfo) -> Vec<WidgetHolder> {
let mut widgets = start_widgets(parameter_widgets_info, FrontendGraphDataType::Group); let mut widgets = start_widgets(parameter_widgets_info);
widgets.push(Separator::new(SeparatorType::Unrelated).widget_holder()); widgets.push(Separator::new(SeparatorType::Unrelated).widget_holder());
widgets.push(TextLabel::new("Group data is supplied through the node graph").widget_holder()); widgets.push(TextLabel::new("Group data is supplied through the node graph").widget_holder());
@ -754,8 +750,9 @@ pub fn group_widget(parameter_widgets_info: ParameterWidgetsInfo) -> Vec<WidgetH
pub fn number_widget(parameter_widgets_info: ParameterWidgetsInfo, number_props: NumberInput) -> Vec<WidgetHolder> { pub fn number_widget(parameter_widgets_info: ParameterWidgetsInfo, number_props: NumberInput) -> Vec<WidgetHolder> {
let ParameterWidgetsInfo { document_node, node_id, index, .. } = parameter_widgets_info; let ParameterWidgetsInfo { document_node, node_id, index, .. } = parameter_widgets_info;
let mut widgets = start_widgets(parameter_widgets_info, FrontendGraphDataType::Number); let mut widgets = start_widgets(parameter_widgets_info);
let Some(document_node) = document_node else { return Vec::new() };
let Some(input) = document_node.inputs.get(index) else { let Some(input) = document_node.inputs.get(index) else {
log::warn!("A widget failed to be built because its node's input index is invalid."); log::warn!("A widget failed to be built because its node's input index is invalid.");
return vec![]; return vec![];
@ -825,7 +822,8 @@ pub fn number_widget(parameter_widgets_info: ParameterWidgetsInfo, number_props:
pub fn blend_mode_widget(parameter_widgets_info: ParameterWidgetsInfo) -> LayoutGroup { pub fn blend_mode_widget(parameter_widgets_info: ParameterWidgetsInfo) -> LayoutGroup {
let ParameterWidgetsInfo { document_node, node_id, index, .. } = parameter_widgets_info; let ParameterWidgetsInfo { document_node, node_id, index, .. } = parameter_widgets_info;
let mut widgets = start_widgets(parameter_widgets_info, FrontendGraphDataType::General); let mut widgets = start_widgets(parameter_widgets_info);
let Some(document_node) = document_node else { return LayoutGroup::default() };
let Some(input) = document_node.inputs.get(index) else { let Some(input) = document_node.inputs.get(index) else {
log::warn!("A widget failed to be built because its node's input index is invalid."); log::warn!("A widget failed to be built because its node's input index is invalid.");
return LayoutGroup::Row { widgets: vec![] }; return LayoutGroup::Row { widgets: vec![] };
@ -859,8 +857,9 @@ pub fn blend_mode_widget(parameter_widgets_info: ParameterWidgetsInfo) -> Layout
pub fn color_widget(parameter_widgets_info: ParameterWidgetsInfo, color_button: ColorInput) -> LayoutGroup { pub fn color_widget(parameter_widgets_info: ParameterWidgetsInfo, color_button: ColorInput) -> LayoutGroup {
let ParameterWidgetsInfo { document_node, node_id, index, .. } = parameter_widgets_info; let ParameterWidgetsInfo { document_node, node_id, index, .. } = parameter_widgets_info;
let mut widgets = start_widgets(parameter_widgets_info, FrontendGraphDataType::General); let mut widgets = start_widgets(parameter_widgets_info);
let Some(document_node) = document_node else { return LayoutGroup::default() };
// Return early with just the label if the input is exposed to the graph, meaning we don't want to show the color picker widget in the Properties panel // Return early with just the label if the input is exposed to the graph, meaning we don't want to show the color picker widget in the Properties panel
let NodeInput::Value { tagged_value, exposed: false } = &document_node.inputs[index] else { let NodeInput::Value { tagged_value, exposed: false } = &document_node.inputs[index] else {
return LayoutGroup::Row { widgets }; return LayoutGroup::Row { widgets };
@ -913,8 +912,9 @@ pub fn font_widget(parameter_widgets_info: ParameterWidgetsInfo) -> LayoutGroup
pub fn curve_widget(parameter_widgets_info: ParameterWidgetsInfo) -> LayoutGroup { pub fn curve_widget(parameter_widgets_info: ParameterWidgetsInfo) -> LayoutGroup {
let ParameterWidgetsInfo { document_node, node_id, index, .. } = parameter_widgets_info; let ParameterWidgetsInfo { document_node, node_id, index, .. } = parameter_widgets_info;
let mut widgets = start_widgets(parameter_widgets_info, FrontendGraphDataType::General); let mut widgets = start_widgets(parameter_widgets_info);
let Some(document_node) = document_node else { return LayoutGroup::default() };
let Some(input) = document_node.inputs.get(index) else { let Some(input) = document_node.inputs.get(index) else {
log::warn!("A widget failed to be built because its node's input index is invalid."); log::warn!("A widget failed to be built because its node's input index is invalid.");
return LayoutGroup::Row { widgets: vec![] }; return LayoutGroup::Row { widgets: vec![] };
@ -939,14 +939,11 @@ pub fn get_document_node<'a>(node_id: NodeId, context: &'a NodePropertiesContext
network.nodes.get(&node_id).ok_or(format!("node {node_id} not found in get_document_node")) network.nodes.get(&node_id).ok_or(format!("node {node_id} not found in get_document_node"))
} }
pub fn query_node_and_input_info<'a>(node_id: NodeId, input_index: usize, context: &'a NodePropertiesContext<'a>) -> Result<(&'a DocumentNode, &'a str, &'a str), String> { pub fn query_node_and_input_info<'a>(node_id: NodeId, input_index: usize, context: &'a mut NodePropertiesContext<'a>) -> Result<(&'a DocumentNode, String, String), String> {
let (name, description) = context.network_interface.displayed_input_name_and_description(&node_id, input_index, context.selection_network_path);
let document_node = get_document_node(node_id, context)?; let document_node = get_document_node(node_id, context)?;
let input_name = context.network_interface.input_name(node_id, input_index, context.selection_network_path).unwrap_or_else(|| {
log::warn!("input name not found in query_node_and_input_info"); Ok((document_node, name, description))
""
});
let input_description = context.network_interface.input_description(node_id, input_index, context.selection_network_path).unwrap_or_default();
Ok((document_node, input_name, input_description))
} }
pub fn query_noise_pattern_state(node_id: NodeId, context: &NodePropertiesContext) -> Result<(bool, bool, bool, bool, bool, bool), String> { pub fn query_noise_pattern_state(node_id: NodeId, context: &NodePropertiesContext) -> Result<(bool, bool, bool, bool, bool, bool), String> {
@ -995,6 +992,9 @@ pub fn query_assign_colors_randomize(node_id: NodeId, context: &NodePropertiesCo
pub(crate) fn brightness_contrast_properties(node_id: NodeId, context: &mut NodePropertiesContext) -> Vec<LayoutGroup> { pub(crate) fn brightness_contrast_properties(node_id: NodeId, context: &mut NodePropertiesContext) -> Vec<LayoutGroup> {
use graphene_std::raster::brightness_contrast::*; use graphene_std::raster::brightness_contrast::*;
// Use Classic
let use_classic = bool_widget(ParameterWidgetsInfo::new(node_id, UseClassicInput::INDEX, true, context), CheckboxInput::default());
let document_node = match get_document_node(node_id, context) { let document_node = match get_document_node(node_id, context) {
Ok(document_node) => document_node, Ok(document_node) => document_node,
Err(err) => { Err(err) => {
@ -1002,12 +1002,6 @@ pub(crate) fn brightness_contrast_properties(node_id: NodeId, context: &mut Node
return Vec::new(); return Vec::new();
} }
}; };
// Use Classic
let use_classic = bool_widget(
ParameterWidgetsInfo::from_index(document_node, node_id, UseClassicInput::INDEX, true, context),
CheckboxInput::default(),
);
let use_classic_value = match document_node.inputs[UseClassicInput::INDEX].as_value() { let use_classic_value = match document_node.inputs[UseClassicInput::INDEX].as_value() {
Some(TaggedValue::Bool(use_classic_choice)) => *use_classic_choice, Some(TaggedValue::Bool(use_classic_choice)) => *use_classic_choice,
_ => false, _ => false,
@ -1015,7 +1009,7 @@ pub(crate) fn brightness_contrast_properties(node_id: NodeId, context: &mut Node
// Brightness // Brightness
let brightness = number_widget( let brightness = number_widget(
ParameterWidgetsInfo::from_index(document_node, node_id, BrightnessInput::INDEX, true, context), ParameterWidgetsInfo::new(node_id, BrightnessInput::INDEX, true, context),
NumberInput::default() NumberInput::default()
.unit("%") .unit("%")
.mode_range() .mode_range()
@ -1026,7 +1020,7 @@ pub(crate) fn brightness_contrast_properties(node_id: NodeId, context: &mut Node
// Contrast // Contrast
let contrast = number_widget( let contrast = number_widget(
ParameterWidgetsInfo::from_index(document_node, node_id, ContrastInput::INDEX, true, context), ParameterWidgetsInfo::new(node_id, ContrastInput::INDEX, true, context),
NumberInput::default() NumberInput::default()
.unit("%") .unit("%")
.mode_range() .mode_range()
@ -1047,6 +1041,11 @@ pub(crate) fn brightness_contrast_properties(node_id: NodeId, context: &mut Node
pub(crate) fn channel_mixer_properties(node_id: NodeId, context: &mut NodePropertiesContext) -> Vec<LayoutGroup> { pub(crate) fn channel_mixer_properties(node_id: NodeId, context: &mut NodePropertiesContext) -> Vec<LayoutGroup> {
use graphene_std::raster::channel_mixer::*; use graphene_std::raster::channel_mixer::*;
let is_monochrome = bool_widget(ParameterWidgetsInfo::new(node_id, MonochromeInput::INDEX, true, context), CheckboxInput::default());
let mut parameter_info = ParameterWidgetsInfo::new(node_id, OutputChannelInput::INDEX, true, context);
parameter_info.exposeable = false;
let output_channel = enum_choice::<RedGreenBlue>().for_socket(parameter_info).property_row();
let document_node = match get_document_node(node_id, context) { let document_node = match get_document_node(node_id, context) {
Ok(document_node) => document_node, Ok(document_node) => document_node,
Err(err) => { Err(err) => {
@ -1054,22 +1053,12 @@ pub(crate) fn channel_mixer_properties(node_id: NodeId, context: &mut NodeProper
return Vec::new(); return Vec::new();
} }
}; };
// Monochrome // Monochrome
let is_monochrome = bool_widget(
ParameterWidgetsInfo::from_index(document_node, node_id, MonochromeInput::INDEX, true, context),
CheckboxInput::default(),
);
let is_monochrome_value = match document_node.inputs[MonochromeInput::INDEX].as_value() { let is_monochrome_value = match document_node.inputs[MonochromeInput::INDEX].as_value() {
Some(TaggedValue::Bool(monochrome_choice)) => *monochrome_choice, Some(TaggedValue::Bool(monochrome_choice)) => *monochrome_choice,
_ => false, _ => false,
}; };
// Output channel choice // Output channel choice
let output_channel = enum_choice::<RedGreenBlue>()
.for_socket(ParameterWidgetsInfo::from_index(document_node, node_id, OutputChannelInput::INDEX, true, context))
.exposable(false)
.property_row();
let output_channel_value = match &document_node.inputs[OutputChannelInput::INDEX].as_value() { let output_channel_value = match &document_node.inputs[OutputChannelInput::INDEX].as_value() {
Some(TaggedValue::RedGreenBlue(choice)) => choice, Some(TaggedValue::RedGreenBlue(choice)) => choice,
_ => { _ => {
@ -1086,10 +1075,10 @@ pub(crate) fn channel_mixer_properties(node_id: NodeId, context: &mut NodeProper
(false, RedGreenBlue::Blue) => (BlueRInput::INDEX, BlueGInput::INDEX, BlueBInput::INDEX, BlueCInput::INDEX), (false, RedGreenBlue::Blue) => (BlueRInput::INDEX, BlueGInput::INDEX, BlueBInput::INDEX, BlueCInput::INDEX),
}; };
let number_input = NumberInput::default().mode_range().min(-200.).max(200.).unit("%"); let number_input = NumberInput::default().mode_range().min(-200.).max(200.).unit("%");
let red = number_widget(ParameterWidgetsInfo::from_index(document_node, node_id, red_output_index, true, context), number_input.clone()); let red = number_widget(ParameterWidgetsInfo::new(node_id, red_output_index, true, context), number_input.clone());
let green = number_widget(ParameterWidgetsInfo::from_index(document_node, node_id, green_output_index, true, context), number_input.clone()); let green = number_widget(ParameterWidgetsInfo::new(node_id, green_output_index, true, context), number_input.clone());
let blue = number_widget(ParameterWidgetsInfo::from_index(document_node, node_id, blue_output_index, true, context), number_input.clone()); let blue = number_widget(ParameterWidgetsInfo::new(node_id, blue_output_index, true, context), number_input.clone());
let constant = number_widget(ParameterWidgetsInfo::from_index(document_node, node_id, constant_output_index, true, context), number_input); let constant = number_widget(ParameterWidgetsInfo::new(node_id, constant_output_index, true, context), number_input);
// Monochrome // Monochrome
let mut layout = vec![LayoutGroup::Row { widgets: is_monochrome }]; let mut layout = vec![LayoutGroup::Row { widgets: is_monochrome }];
@ -1110,6 +1099,10 @@ pub(crate) fn channel_mixer_properties(node_id: NodeId, context: &mut NodeProper
pub(crate) fn selective_color_properties(node_id: NodeId, context: &mut NodePropertiesContext) -> Vec<LayoutGroup> { pub(crate) fn selective_color_properties(node_id: NodeId, context: &mut NodePropertiesContext) -> Vec<LayoutGroup> {
use graphene_std::raster::selective_color::*; use graphene_std::raster::selective_color::*;
let mut default_info = ParameterWidgetsInfo::new(node_id, ColorsInput::INDEX, true, context);
default_info.exposeable = false;
let colors = enum_choice::<SelectiveColorChoice>().for_socket(default_info).property_row();
let document_node = match get_document_node(node_id, context) { let document_node = match get_document_node(node_id, context) {
Ok(document_node) => document_node, Ok(document_node) => document_node,
Err(err) => { Err(err) => {
@ -1117,13 +1110,7 @@ pub(crate) fn selective_color_properties(node_id: NodeId, context: &mut NodeProp
return Vec::new(); return Vec::new();
} }
}; };
// Colors choice // Colors choice
let colors = enum_choice::<SelectiveColorChoice>()
.for_socket(ParameterWidgetsInfo::from_index(document_node, node_id, ColorsInput::INDEX, true, context))
.exposable(false)
.property_row();
let colors_choice = match &document_node.inputs[ColorsInput::INDEX].as_value() { let colors_choice = match &document_node.inputs[ColorsInput::INDEX].as_value() {
Some(TaggedValue::SelectiveColorChoice(choice)) => choice, Some(TaggedValue::SelectiveColorChoice(choice)) => choice,
_ => { _ => {
@ -1131,7 +1118,6 @@ pub(crate) fn selective_color_properties(node_id: NodeId, context: &mut NodeProp
return vec![]; return vec![];
} }
}; };
// CMYK // CMYK
let (c_index, m_index, y_index, k_index) = match colors_choice { let (c_index, m_index, y_index, k_index) = match colors_choice {
SelectiveColorChoice::Reds => (RCInput::INDEX, RMInput::INDEX, RYInput::INDEX, RKInput::INDEX), SelectiveColorChoice::Reds => (RCInput::INDEX, RMInput::INDEX, RYInput::INDEX, RKInput::INDEX),
@ -1145,14 +1131,14 @@ pub(crate) fn selective_color_properties(node_id: NodeId, context: &mut NodeProp
SelectiveColorChoice::Blacks => (KCInput::INDEX, KMInput::INDEX, KYInput::INDEX, KKInput::INDEX), SelectiveColorChoice::Blacks => (KCInput::INDEX, KMInput::INDEX, KYInput::INDEX, KKInput::INDEX),
}; };
let number_input = NumberInput::default().mode_range().min(-100.).max(100.).unit("%"); let number_input = NumberInput::default().mode_range().min(-100.).max(100.).unit("%");
let cyan = number_widget(ParameterWidgetsInfo::from_index(document_node, node_id, c_index, true, context), number_input.clone()); let cyan = number_widget(ParameterWidgetsInfo::new(node_id, c_index, true, context), number_input.clone());
let magenta = number_widget(ParameterWidgetsInfo::from_index(document_node, node_id, m_index, true, context), number_input.clone()); let magenta = number_widget(ParameterWidgetsInfo::new(node_id, m_index, true, context), number_input.clone());
let yellow = number_widget(ParameterWidgetsInfo::from_index(document_node, node_id, y_index, true, context), number_input.clone()); let yellow = number_widget(ParameterWidgetsInfo::new(node_id, y_index, true, context), number_input.clone());
let black = number_widget(ParameterWidgetsInfo::from_index(document_node, node_id, k_index, true, context), number_input); let black = number_widget(ParameterWidgetsInfo::new(node_id, k_index, true, context), number_input);
// Mode // Mode
let mode = enum_choice::<RelativeAbsolute>() let mode = enum_choice::<RelativeAbsolute>()
.for_socket(ParameterWidgetsInfo::from_index(document_node, node_id, ModeInput::INDEX, true, context)) .for_socket(ParameterWidgetsInfo::new(node_id, ModeInput::INDEX, true, context))
.property_row(); .property_row();
vec![ vec![
@ -1171,19 +1157,19 @@ pub(crate) fn selective_color_properties(node_id: NodeId, context: &mut NodeProp
pub(crate) fn grid_properties(node_id: NodeId, context: &mut NodePropertiesContext) -> Vec<LayoutGroup> { pub(crate) fn grid_properties(node_id: NodeId, context: &mut NodePropertiesContext) -> Vec<LayoutGroup> {
use graphene_std::vector::generator_nodes::grid::*; use graphene_std::vector::generator_nodes::grid::*;
let document_node = match get_document_node(node_id, context) {
Ok(document_node) => document_node,
Err(err) => {
log::error!("Could not get document node in exposure_properties: {err}");
return Vec::new();
}
};
let grid_type = enum_choice::<GridType>() let grid_type = enum_choice::<GridType>()
.for_socket(ParameterWidgetsInfo::from_index(document_node, node_id, GridTypeInput::INDEX, true, context)) .for_socket(ParameterWidgetsInfo::new(node_id, GridTypeInput::INDEX, true, context))
.property_row(); .property_row();
let mut widgets = vec![grid_type]; let mut widgets = vec![grid_type];
let document_node = match get_document_node(node_id, context) {
Ok(document_node) => document_node,
Err(err) => {
log::error!("Could not get document node in grid_properties: {err}");
return Vec::new();
}
};
let Some(grid_type_input) = document_node.inputs.get(GridTypeInput::INDEX) else { let Some(grid_type_input) = document_node.inputs.get(GridTypeInput::INDEX) else {
log::warn!("A widget failed to be built because its node's input index is invalid."); log::warn!("A widget failed to be built because its node's input index is invalid.");
return vec![]; return vec![];
@ -1191,36 +1177,24 @@ pub(crate) fn grid_properties(node_id: NodeId, context: &mut NodePropertiesConte
if let Some(&TaggedValue::GridType(grid_type)) = grid_type_input.as_non_exposed_value() { if let Some(&TaggedValue::GridType(grid_type)) = grid_type_input.as_non_exposed_value() {
match grid_type { match grid_type {
GridType::Rectangular => { GridType::Rectangular => {
let spacing = coordinate_widget( let spacing = coordinate_widget(ParameterWidgetsInfo::new(node_id, SpacingInput::<f64>::INDEX, true, context), "W", "H", " px", Some(0.));
ParameterWidgetsInfo::from_index(document_node, node_id, SpacingInput::<f64>::INDEX, true, context),
"W",
"H",
" px",
Some(0.),
);
widgets.push(spacing); widgets.push(spacing);
} }
GridType::Isometric => { GridType::Isometric => {
let spacing = LayoutGroup::Row { let spacing = LayoutGroup::Row {
widgets: number_widget( widgets: number_widget(
ParameterWidgetsInfo::from_index(document_node, node_id, SpacingInput::<f64>::INDEX, true, context), ParameterWidgetsInfo::new(node_id, SpacingInput::<f64>::INDEX, true, context),
NumberInput::default().label("H").min(0.).unit(" px"), NumberInput::default().label("H").min(0.).unit(" px"),
), ),
}; };
let angles = coordinate_widget(ParameterWidgetsInfo::from_index(document_node, node_id, AnglesInput::INDEX, true, context), "", "", "°", None); let angles = coordinate_widget(ParameterWidgetsInfo::new(node_id, AnglesInput::INDEX, true, context), "", "", "°", None);
widgets.extend([spacing, angles]); widgets.extend([spacing, angles]);
} }
} }
} }
let columns = number_widget( let columns = number_widget(ParameterWidgetsInfo::new(node_id, ColumnsInput::INDEX, true, context), NumberInput::default().min(1.));
ParameterWidgetsInfo::from_index(document_node, node_id, ColumnsInput::INDEX, true, context), let rows = number_widget(ParameterWidgetsInfo::new(node_id, RowsInput::INDEX, true, context), NumberInput::default().min(1.));
NumberInput::default().min(1.),
);
let rows = number_widget(
ParameterWidgetsInfo::from_index(document_node, node_id, RowsInput::INDEX, true, context),
NumberInput::default().min(1.),
);
widgets.extend([LayoutGroup::Row { widgets: columns }, LayoutGroup::Row { widgets: rows }]); widgets.extend([LayoutGroup::Row { widgets: columns }, LayoutGroup::Row { widgets: rows }]);
@ -1249,26 +1223,14 @@ pub(crate) fn sample_polyline_properties(node_id: NodeId, context: &mut NodeProp
let is_quantity = matches!(current_spacing, Some(TaggedValue::PointSpacingType(PointSpacingType::Quantity))); let is_quantity = matches!(current_spacing, Some(TaggedValue::PointSpacingType(PointSpacingType::Quantity)));
let spacing = enum_choice::<PointSpacingType>() let spacing = enum_choice::<PointSpacingType>()
.for_socket(ParameterWidgetsInfo::from_index(document_node, node_id, SpacingInput::INDEX, true, context)) .for_socket(ParameterWidgetsInfo::new(node_id, SpacingInput::INDEX, true, context))
.property_row(); .property_row();
let separation = number_widget( let separation = number_widget(ParameterWidgetsInfo::new(node_id, SeparationInput::INDEX, true, context), NumberInput::default().min(0.).unit(" px"));
ParameterWidgetsInfo::from_index(document_node, node_id, SeparationInput::INDEX, true, context), let quantity = number_widget(ParameterWidgetsInfo::new(node_id, QuantityInput::INDEX, true, context), NumberInput::default().min(2.).int());
NumberInput::default().min(0.).unit(" px"), let start_offset = number_widget(ParameterWidgetsInfo::new(node_id, StartOffsetInput::INDEX, true, context), NumberInput::default().min(0.).unit(" px"));
); let stop_offset = number_widget(ParameterWidgetsInfo::new(node_id, StopOffsetInput::INDEX, true, context), NumberInput::default().min(0.).unit(" px"));
let quantity = number_widget(
ParameterWidgetsInfo::from_index(document_node, node_id, QuantityInput::INDEX, true, context),
NumberInput::default().min(2.).int(),
);
let start_offset = number_widget(
ParameterWidgetsInfo::from_index(document_node, node_id, StartOffsetInput::INDEX, true, context),
NumberInput::default().min(0.).unit(" px"),
);
let stop_offset = number_widget(
ParameterWidgetsInfo::from_index(document_node, node_id, StopOffsetInput::INDEX, true, context),
NumberInput::default().min(0.).unit(" px"),
);
let adaptive_spacing = bool_widget( let adaptive_spacing = bool_widget(
ParameterWidgetsInfo::from_index(document_node, node_id, AdaptiveSpacingInput::INDEX, true, context), ParameterWidgetsInfo::new(node_id, AdaptiveSpacingInput::INDEX, true, context),
CheckboxInput::default().disabled(is_quantity), CheckboxInput::default().disabled(is_quantity),
); );
@ -1288,23 +1250,10 @@ pub(crate) fn sample_polyline_properties(node_id: NodeId, context: &mut NodeProp
pub(crate) fn exposure_properties(node_id: NodeId, context: &mut NodePropertiesContext) -> Vec<LayoutGroup> { pub(crate) fn exposure_properties(node_id: NodeId, context: &mut NodePropertiesContext) -> Vec<LayoutGroup> {
use graphene_std::raster::exposure::*; use graphene_std::raster::exposure::*;
let document_node = match get_document_node(node_id, context) { let exposure = number_widget(ParameterWidgetsInfo::new(node_id, ExposureInput::INDEX, true, context), NumberInput::default().min(-20.).max(20.));
Ok(document_node) => document_node, let offset = number_widget(ParameterWidgetsInfo::new(node_id, OffsetInput::INDEX, true, context), NumberInput::default().min(-0.5).max(0.5));
Err(err) => {
log::error!("Could not get document node in exposure_properties: {err}");
return Vec::new();
}
};
let exposure = number_widget(
ParameterWidgetsInfo::from_index(document_node, node_id, ExposureInput::INDEX, true, context),
NumberInput::default().min(-20.).max(20.),
);
let offset = number_widget(
ParameterWidgetsInfo::from_index(document_node, node_id, OffsetInput::INDEX, true, context),
NumberInput::default().min(-0.5).max(0.5),
);
let gamma_correction = number_widget( let gamma_correction = number_widget(
ParameterWidgetsInfo::from_index(document_node, node_id, GammaCorrectionInput::INDEX, true, context), ParameterWidgetsInfo::new(node_id, GammaCorrectionInput::INDEX, true, context),
NumberInput::default().min(0.01).max(9.99).increment_step(0.1), NumberInput::default().min(0.01).max(9.99).increment_step(0.1),
); );
@ -1318,6 +1267,13 @@ pub(crate) fn exposure_properties(node_id: NodeId, context: &mut NodePropertiesC
pub(crate) fn rectangle_properties(node_id: NodeId, context: &mut NodePropertiesContext) -> Vec<LayoutGroup> { pub(crate) fn rectangle_properties(node_id: NodeId, context: &mut NodePropertiesContext) -> Vec<LayoutGroup> {
use graphene_std::vector::generator_nodes::rectangle::*; use graphene_std::vector::generator_nodes::rectangle::*;
// Corner Radius
let mut corner_radius_row_1 = start_widgets(ParameterWidgetsInfo::new(node_id, CornerRadiusInput::<f64>::INDEX, true, context));
corner_radius_row_1.push(Separator::new(SeparatorType::Unrelated).widget_holder());
let mut corner_radius_row_2 = vec![Separator::new(SeparatorType::Unrelated).widget_holder()];
corner_radius_row_2.push(TextLabel::new("").widget_holder());
let document_node = match get_document_node(node_id, context) { let document_node = match get_document_node(node_id, context) {
Ok(document_node) => document_node, Ok(document_node) => document_node,
Err(err) => { Err(err) => {
@ -1325,23 +1281,6 @@ pub(crate) fn rectangle_properties(node_id: NodeId, context: &mut NodeProperties
return Vec::new(); return Vec::new();
} }
}; };
// Size X
let size_x = number_widget(ParameterWidgetsInfo::from_index(document_node, node_id, WidthInput::INDEX, true, context), NumberInput::default());
// Size Y
let size_y = number_widget(ParameterWidgetsInfo::from_index(document_node, node_id, HeightInput::INDEX, true, context), NumberInput::default());
// Corner Radius
let mut corner_radius_row_1 = start_widgets(
ParameterWidgetsInfo::from_index(document_node, node_id, CornerRadiusInput::<f64>::INDEX, true, context),
FrontendGraphDataType::Number,
);
corner_radius_row_1.push(Separator::new(SeparatorType::Unrelated).widget_holder());
let mut corner_radius_row_2 = vec![Separator::new(SeparatorType::Unrelated).widget_holder()];
corner_radius_row_2.push(TextLabel::new("").widget_holder());
add_blank_assist(&mut corner_radius_row_2);
let Some(input) = document_node.inputs.get(IndividualCornerRadiiInput::INDEX) else { let Some(input) = document_node.inputs.get(IndividualCornerRadiiInput::INDEX) else {
log::warn!("A widget failed to be built because its node's input index is invalid."); log::warn!("A widget failed to be built because its node's input index is invalid.");
return vec![]; return vec![];
@ -1435,8 +1374,16 @@ pub(crate) fn rectangle_properties(node_id: NodeId, context: &mut NodeProperties
corner_radius_row_2.push(input_widget); corner_radius_row_2.push(input_widget);
} }
// Size X
let size_x = number_widget(ParameterWidgetsInfo::new(node_id, WidthInput::INDEX, true, context), NumberInput::default());
// Size Y
let size_y = number_widget(ParameterWidgetsInfo::new(node_id, HeightInput::INDEX, true, context), NumberInput::default());
add_blank_assist(&mut corner_radius_row_2);
// Clamped // Clamped
let clamped = bool_widget(ParameterWidgetsInfo::from_index(document_node, node_id, ClampedInput::INDEX, true, context), CheckboxInput::default()); let clamped = bool_widget(ParameterWidgetsInfo::new(node_id, ClampedInput::INDEX, true, context), CheckboxInput::default());
vec![ vec![
LayoutGroup::Row { widgets: size_x }, LayoutGroup::Row { widgets: size_x },
@ -1565,6 +1512,8 @@ pub(crate) fn generate_node_properties(node_id: NodeId, context: &mut NodeProper
pub(crate) fn fill_properties(node_id: NodeId, context: &mut NodePropertiesContext) -> Vec<LayoutGroup> { pub(crate) fn fill_properties(node_id: NodeId, context: &mut NodePropertiesContext) -> Vec<LayoutGroup> {
use graphene_std::vector::fill::*; use graphene_std::vector::fill::*;
let mut widgets_first_row = start_widgets(ParameterWidgetsInfo::new(node_id, FillInput::<Color>::INDEX, true, context));
let document_node = match get_document_node(node_id, context) { let document_node = match get_document_node(node_id, context) {
Ok(document_node) => document_node, Ok(document_node) => document_node,
Err(err) => { Err(err) => {
@ -1573,11 +1522,6 @@ pub(crate) fn fill_properties(node_id: NodeId, context: &mut NodePropertiesConte
} }
}; };
let mut widgets_first_row = start_widgets(
ParameterWidgetsInfo::from_index(document_node, node_id, FillInput::<Color>::INDEX, true, context),
FrontendGraphDataType::General,
);
let (fill, backup_color, backup_gradient) = if let (Some(TaggedValue::Fill(fill)), &Some(&TaggedValue::OptionalColor(backup_color)), Some(TaggedValue::Gradient(backup_gradient))) = ( let (fill, backup_color, backup_gradient) = if let (Some(TaggedValue::Fill(fill)), &Some(&TaggedValue::OptionalColor(backup_color)), Some(TaggedValue::Gradient(backup_gradient))) = (
&document_node.inputs[FillInput::<Color>::INDEX].as_value(), &document_node.inputs[FillInput::<Color>::INDEX].as_value(),
&document_node.inputs[BackupColorInput::INDEX].as_value(), &document_node.inputs[BackupColorInput::INDEX].as_value(),
@ -1756,47 +1700,42 @@ pub fn stroke_properties(node_id: NodeId, context: &mut NodePropertiesContext) -
return Vec::new(); return Vec::new();
} }
}; };
let join_value = match &document_node.inputs[JoinInput::INDEX].as_value() {
Some(TaggedValue::StrokeJoin(x)) => x,
_ => &StrokeJoin::Miter,
};
let color = color_widget(
ParameterWidgetsInfo::from_index(document_node, node_id, ColorInput::<Option<Color>>::INDEX, true, context),
crate::messages::layout::utility_types::widgets::button_widgets::ColorInput::default(),
);
let weight = number_widget(
ParameterWidgetsInfo::from_index(document_node, node_id, WeightInput::INDEX, true, context),
NumberInput::default().unit(" px").min(0.),
);
let align = enum_choice::<StrokeAlign>()
.for_socket(ParameterWidgetsInfo::from_index(document_node, node_id, AlignInput::INDEX, true, context))
.property_row();
let cap = enum_choice::<StrokeCap>()
.for_socket(ParameterWidgetsInfo::from_index(document_node, node_id, CapInput::INDEX, true, context))
.property_row();
let join = enum_choice::<StrokeJoin>()
.for_socket(ParameterWidgetsInfo::from_index(document_node, node_id, JoinInput::INDEX, true, context))
.property_row();
let miter_limit = number_widget(
ParameterWidgetsInfo::from_index(document_node, node_id, MiterLimitInput::INDEX, true, context),
NumberInput::default().min(0.).disabled({
let join_value = match &document_node.inputs[JoinInput::INDEX].as_value() {
Some(TaggedValue::StrokeJoin(x)) => x,
_ => &StrokeJoin::Miter,
};
join_value != &StrokeJoin::Miter
}),
);
let paint_order = enum_choice::<PaintOrder>()
.for_socket(ParameterWidgetsInfo::from_index(document_node, node_id, PaintOrderInput::INDEX, true, context))
.property_row();
let dash_lengths_val = match &document_node.inputs[DashLengthsInput::INDEX].as_value() { let dash_lengths_val = match &document_node.inputs[DashLengthsInput::INDEX].as_value() {
Some(TaggedValue::VecF64(x)) => x, Some(TaggedValue::VecF64(x)) => x,
_ => &vec![], _ => &vec![],
}; };
let dash_lengths = array_of_number_widget( let has_dash_lengths = dash_lengths_val.is_empty();
ParameterWidgetsInfo::from_index(document_node, node_id, DashLengthsInput::INDEX, true, context), let miter_limit_disabled = join_value != &StrokeJoin::Miter;
TextInput::default().centered(true),
let color = color_widget(
ParameterWidgetsInfo::new(node_id, ColorInput::<Option<Color>>::INDEX, true, context),
crate::messages::layout::utility_types::widgets::button_widgets::ColorInput::default(),
); );
let number_input = NumberInput::default().unit(" px").disabled(dash_lengths_val.is_empty()); let weight = number_widget(ParameterWidgetsInfo::new(node_id, WeightInput::INDEX, true, context), NumberInput::default().unit(" px").min(0.));
let dash_offset = number_widget(ParameterWidgetsInfo::from_index(document_node, node_id, DashOffsetInput::INDEX, true, context), number_input); let align = enum_choice::<StrokeAlign>()
.for_socket(ParameterWidgetsInfo::new(node_id, AlignInput::INDEX, true, context))
.property_row();
let cap = enum_choice::<StrokeCap>().for_socket(ParameterWidgetsInfo::new(node_id, CapInput::INDEX, true, context)).property_row();
let join = enum_choice::<StrokeJoin>()
.for_socket(ParameterWidgetsInfo::new(node_id, JoinInput::INDEX, true, context))
.property_row();
let miter_limit = number_widget(
ParameterWidgetsInfo::new(node_id, MiterLimitInput::INDEX, true, context),
NumberInput::default().min(0.).disabled(miter_limit_disabled),
);
let paint_order = enum_choice::<PaintOrder>()
.for_socket(ParameterWidgetsInfo::new(node_id, PaintOrderInput::INDEX, true, context))
.property_row();
let disabled_number_input = NumberInput::default().unit(" px").disabled(has_dash_lengths);
let dash_lengths = array_of_number_widget(ParameterWidgetsInfo::new(node_id, DashLengthsInput::INDEX, true, context), TextInput::default().centered(true));
let number_input = disabled_number_input;
let dash_offset = number_widget(ParameterWidgetsInfo::new(node_id, DashOffsetInput::INDEX, true, context), number_input);
vec![ vec![
color, color,
@ -1814,6 +1753,13 @@ pub fn stroke_properties(node_id: NodeId, context: &mut NodePropertiesContext) -
pub fn offset_path_properties(node_id: NodeId, context: &mut NodePropertiesContext) -> Vec<LayoutGroup> { pub fn offset_path_properties(node_id: NodeId, context: &mut NodePropertiesContext) -> Vec<LayoutGroup> {
use graphene_std::vector::offset_path::*; use graphene_std::vector::offset_path::*;
let number_input = NumberInput::default().unit(" px");
let distance = number_widget(ParameterWidgetsInfo::new(node_id, DistanceInput::INDEX, true, context), number_input);
let join = enum_choice::<StrokeJoin>()
.for_socket(ParameterWidgetsInfo::new(node_id, JoinInput::INDEX, true, context))
.property_row();
let document_node = match get_document_node(node_id, context) { let document_node = match get_document_node(node_id, context) {
Ok(document_node) => document_node, Ok(document_node) => document_node,
Err(err) => { Err(err) => {
@ -1821,13 +1767,6 @@ pub fn offset_path_properties(node_id: NodeId, context: &mut NodePropertiesConte
return Vec::new(); return Vec::new();
} }
}; };
let number_input = NumberInput::default().unit(" px");
let distance = number_widget(ParameterWidgetsInfo::from_index(document_node, node_id, DistanceInput::INDEX, true, context), number_input);
let join = enum_choice::<StrokeJoin>()
.for_socket(ParameterWidgetsInfo::from_index(document_node, node_id, JoinInput::INDEX, true, context))
.property_row();
let number_input = NumberInput::default().min(0.).disabled({ let number_input = NumberInput::default().min(0.).disabled({
let join_val = match &document_node.inputs[JoinInput::INDEX].as_value() { let join_val = match &document_node.inputs[JoinInput::INDEX].as_value() {
Some(TaggedValue::StrokeJoin(x)) => x, Some(TaggedValue::StrokeJoin(x)) => x,
@ -1835,7 +1774,7 @@ pub fn offset_path_properties(node_id: NodeId, context: &mut NodePropertiesConte
}; };
join_val != &StrokeJoin::Miter join_val != &StrokeJoin::Miter
}); });
let miter_limit = number_widget(ParameterWidgetsInfo::from_index(document_node, node_id, MiterLimitInput::INDEX, true, context), number_input); let miter_limit = number_widget(ParameterWidgetsInfo::new(node_id, MiterLimitInput::INDEX, true, context), number_input);
vec![LayoutGroup::Row { widgets: distance }, join, LayoutGroup::Row { widgets: miter_limit }] vec![LayoutGroup::Row { widgets: distance }, join, LayoutGroup::Row { widgets: miter_limit }]
} }
@ -1843,20 +1782,16 @@ pub fn offset_path_properties(node_id: NodeId, context: &mut NodePropertiesConte
pub fn math_properties(node_id: NodeId, context: &mut NodePropertiesContext) -> Vec<LayoutGroup> { pub fn math_properties(node_id: NodeId, context: &mut NodePropertiesContext) -> Vec<LayoutGroup> {
use graphene_std::math_nodes::math::*; use graphene_std::math_nodes::math::*;
let document_node = match get_document_node(node_id, context) {
Ok(document_node) => document_node,
Err(err) => {
log::error!("Could not get document node in offset_path_properties: {err}");
return Vec::new();
}
};
let expression = (|| { let expression = (|| {
let mut widgets = start_widgets( let mut widgets = start_widgets(ParameterWidgetsInfo::new(node_id, ExpressionInput::INDEX, true, context));
ParameterWidgetsInfo::from_index(document_node, node_id, ExpressionInput::INDEX, true, context),
FrontendGraphDataType::General,
);
let document_node = match get_document_node(node_id, context) {
Ok(document_node) => document_node,
Err(err) => {
log::error!("Could not get document node in offset_path_properties: {err}");
return Vec::new();
}
};
let Some(input) = document_node.inputs.get(ExpressionInput::INDEX) else { let Some(input) = document_node.inputs.get(ExpressionInput::INDEX) else {
log::warn!("A widget failed to be built because its node's input index is invalid."); log::warn!("A widget failed to be built because its node's input index is invalid.");
return vec![]; return vec![];
@ -1889,10 +1824,7 @@ pub fn math_properties(node_id: NodeId, context: &mut NodePropertiesContext) ->
} }
widgets widgets
})(); })();
let operand_b = number_widget( let operand_b = number_widget(ParameterWidgetsInfo::new(node_id, OperandBInput::<f64>::INDEX, true, context), NumberInput::default());
ParameterWidgetsInfo::from_index(document_node, node_id, OperandBInput::<f64>::INDEX, true, context),
NumberInput::default(),
);
let operand_a_hint = vec![TextLabel::new("(Operand A is the primary input)").widget_holder()]; let operand_a_hint = vec![TextLabel::new("(Operand A is the primary input)").widget_holder()];
vec![ vec![
@ -1903,44 +1835,37 @@ pub fn math_properties(node_id: NodeId, context: &mut NodePropertiesContext) ->
} }
pub struct ParameterWidgetsInfo<'a> { pub struct ParameterWidgetsInfo<'a> {
document_node: &'a DocumentNode, document_node: Option<&'a DocumentNode>,
node_id: NodeId, node_id: NodeId,
index: usize, index: usize,
name: &'a str, name: String,
description: &'a str, description: String,
input_type: FrontendGraphDataType,
blank_assist: bool, blank_assist: bool,
exposeable: bool,
} }
impl<'a> ParameterWidgetsInfo<'a> { impl<'a> ParameterWidgetsInfo<'a> {
pub fn new(document_node: &'a DocumentNode, node_id: NodeId, index: usize, name: &'a str, description: &'a str, blank_assist: bool) -> ParameterWidgetsInfo<'a> { pub fn new(node_id: NodeId, index: usize, blank_assist: bool, context: &'a mut NodePropertiesContext) -> ParameterWidgetsInfo<'a> {
let (name, description) = context.network_interface.displayed_input_name_and_description(&node_id, index, context.selection_network_path);
let input_type = FrontendGraphDataType::from_type(&context.network_interface.input_type(&InputConnector::node(node_id, index), context.selection_network_path).0);
let document_node = context.network_interface.document_node(&node_id, context.selection_network_path);
ParameterWidgetsInfo { ParameterWidgetsInfo {
document_node, document_node,
node_id, node_id,
index, index,
name, name,
description, description,
input_type,
blank_assist, blank_assist,
} exposeable: true,
}
pub fn from_index(document_node: &'a DocumentNode, node_id: NodeId, index: usize, blank_assist: bool, context: &'a NodePropertiesContext) -> ParameterWidgetsInfo<'a> {
let name = context.network_interface.input_name(node_id, index, context.selection_network_path).unwrap_or_default();
let description = context.network_interface.input_description(node_id, index, context.selection_network_path).unwrap_or_default();
Self {
document_node,
node_id,
index,
name,
description,
blank_assist,
} }
} }
} }
pub mod choice { pub mod choice {
use super::ParameterWidgetsInfo; use super::ParameterWidgetsInfo;
use crate::messages::portfolio::document::node_graph::utility_types::FrontendGraphDataType;
use crate::messages::tool::tool_messages::tool_prelude::*; use crate::messages::tool::tool_messages::tool_prelude::*;
use graph_craft::document::value::TaggedValue; use graph_craft::document::value::TaggedValue;
use graphene_std::registry::{ChoiceTypeStatic, ChoiceWidgetHint}; use graphene_std::registry::{ChoiceTypeStatic, ChoiceWidgetHint};
@ -1973,11 +1898,7 @@ pub mod choice {
impl<E: ChoiceTypeStatic + 'static> EnumChoice<E> { impl<E: ChoiceTypeStatic + 'static> EnumChoice<E> {
pub fn for_socket(self, parameter_info: ParameterWidgetsInfo) -> ForSocket<Self> { pub fn for_socket(self, parameter_info: ParameterWidgetsInfo) -> ForSocket<Self> {
ForSocket { ForSocket { widget_factory: self, parameter_info }
widget_factory: self,
parameter_info,
exposable: true,
}
} }
/// Not yet implemented! /// Not yet implemented!
@ -2068,7 +1989,6 @@ pub mod choice {
pub struct ForSocket<'p, W> { pub struct ForSocket<'p, W> {
widget_factory: W, widget_factory: W,
parameter_info: ParameterWidgetsInfo<'p>, parameter_info: ParameterWidgetsInfo<'p>,
exposable: bool,
} }
impl<'p, W> ForSocket<'p, W> impl<'p, W> ForSocket<'p, W>
@ -2085,14 +2005,14 @@ pub mod choice {
} }
} }
pub fn exposable(self, exposable: bool) -> Self {
Self { exposable, ..self }
}
pub fn property_row(self) -> LayoutGroup { pub fn property_row(self) -> LayoutGroup {
let ParameterWidgetsInfo { document_node, node_id, index, .. } = self.parameter_info; let ParameterWidgetsInfo { document_node, node_id, index, .. } = self.parameter_info;
let Some(document_node) = document_node else {
log::error!("Could not get document node when building property row for node {:?}", node_id);
return LayoutGroup::Row { widgets: Vec::new() };
};
let mut widgets = super::start_widgets_exposable(self.parameter_info, FrontendGraphDataType::General, self.exposable); let mut widgets = super::start_widgets(self.parameter_info);
let Some(input) = document_node.inputs.get(index) else { let Some(input) = document_node.inputs.get(index) else {
log::warn!("A widget failed to be built because its node's input index is invalid."); log::warn!("A widget failed to be built because its node's input index is invalid.");

View file

@ -15,7 +15,7 @@ pub enum FrontendGraphDataType {
} }
impl FrontendGraphDataType { impl FrontendGraphDataType {
fn with_type(input: &Type) -> Self { pub fn from_type(input: &Type) -> Self {
match TaggedValue::from_type_or_none(input) { match TaggedValue::from_type_or_none(input) {
TaggedValue::Image(_) | TaggedValue::RasterData(_) => Self::Raster, TaggedValue::Image(_) | TaggedValue::RasterData(_) => Self::Raster,
TaggedValue::Subpaths(_) | TaggedValue::VectorData(_) => Self::VectorData, TaggedValue::Subpaths(_) | TaggedValue::VectorData(_) => Self::VectorData,
@ -38,7 +38,7 @@ impl FrontendGraphDataType {
pub fn displayed_type(input: &Type, type_source: &TypeSource) -> Self { pub fn displayed_type(input: &Type, type_source: &TypeSource) -> Self {
match type_source { match type_source {
TypeSource::Error(_) | TypeSource::RandomProtonodeImplementation => Self::General, TypeSource::Error(_) | TypeSource::RandomProtonodeImplementation => Self::General,
_ => Self::with_type(input), _ => Self::from_type(input),
} }
} }
} }
@ -50,7 +50,7 @@ pub struct FrontendGraphInput {
pub name: String, pub name: String,
pub description: String, pub description: String,
#[serde(rename = "resolvedType")] #[serde(rename = "resolvedType")]
pub resolved_type: Option<String>, pub resolved_type: String,
#[serde(rename = "validTypes")] #[serde(rename = "validTypes")]
pub valid_types: Vec<String>, pub valid_types: Vec<String>,
#[serde(rename = "connectedTo")] #[serde(rename = "connectedTo")]
@ -64,7 +64,7 @@ pub struct FrontendGraphOutput {
pub name: String, pub name: String,
pub description: String, pub description: String,
#[serde(rename = "resolvedType")] #[serde(rename = "resolvedType")]
pub resolved_type: Option<String>, pub resolved_type: String,
#[serde(rename = "connectedTo")] #[serde(rename = "connectedTo")]
pub connected_to: Vec<InputConnector>, pub connected_to: Vec<InputConnector>,
} }
@ -96,15 +96,6 @@ pub struct FrontendNode {
pub ui_only: bool, pub ui_only: bool,
} }
#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize, specta::Type)]
pub struct FrontendNodeWire {
#[serde(rename = "wireStart")]
pub wire_start: OutputConnector,
#[serde(rename = "wireEnd")]
pub wire_end: InputConnector,
pub dashed: bool,
}
#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize, specta::Type)] #[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize, specta::Type)]
pub struct FrontendNodeType { pub struct FrontendNodeType {
pub name: String, pub name: String,
@ -153,16 +144,6 @@ pub struct Transform {
pub y: f64, pub y: f64,
} }
#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize, specta::Type)]
pub struct WirePath {
#[serde(rename = "pathString")]
pub path_string: String,
#[serde(rename = "dataType")]
pub data_type: FrontendGraphDataType,
pub thick: bool,
pub dashed: bool,
}
#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize, specta::Type)] #[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize, specta::Type)]
pub struct BoxSelection { pub struct BoxSelection {
#[serde(rename = "startX")] #[serde(rename = "startX")]
@ -224,32 +205,3 @@ pub enum Direction {
Left, Left,
Right, Right,
} }
#[derive(Copy, Clone, Debug, PartialEq, Default, serde::Serialize, serde::Deserialize, specta::Type)]
pub enum GraphWireStyle {
#[default]
Direct = 0,
GridAligned = 1,
}
impl std::fmt::Display for GraphWireStyle {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
GraphWireStyle::GridAligned => write!(f, "Grid-Aligned"),
GraphWireStyle::Direct => write!(f, "Direct"),
}
}
}
impl GraphWireStyle {
pub fn tooltip_description(&self) -> &'static str {
match self {
GraphWireStyle::GridAligned => "Wires follow the grid, running in straight lines between nodes",
GraphWireStyle::Direct => "Wires bend to run at an angle directly between nodes",
}
}
pub fn is_direct(&self) -> bool {
*self == GraphWireStyle::Direct
}
}

View file

@ -5,3 +5,4 @@ pub mod misc;
pub mod network_interface; pub mod network_interface;
pub mod nodes; pub mod nodes;
pub mod transformation; pub mod transformation;
pub mod wires;

View file

@ -0,0 +1,589 @@
use crate::messages::portfolio::document::node_graph::utility_types::FrontendGraphDataType;
use bezier_rs::{ManipulatorGroup, Subpath};
use glam::{DVec2, IVec2};
use graphene_std::uuid::NodeId;
use graphene_std::vector::PointId;
#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize, specta::Type)]
pub struct WirePath {
#[serde(rename = "pathString")]
pub path_string: String,
#[serde(rename = "dataType")]
pub data_type: FrontendGraphDataType,
pub thick: bool,
pub dashed: bool,
}
#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize, specta::Type)]
pub struct WirePathUpdate {
pub id: NodeId,
#[serde(rename = "inputIndex")]
pub input_index: usize,
// If none, then remove the wire from the map
#[serde(rename = "wirePathUpdate")]
pub wire_path_update: Option<WirePath>,
}
#[derive(Copy, Clone, Debug, PartialEq, Default, serde::Serialize, serde::Deserialize, specta::Type)]
pub enum GraphWireStyle {
#[default]
Direct = 0,
GridAligned = 1,
}
impl std::fmt::Display for GraphWireStyle {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
GraphWireStyle::GridAligned => write!(f, "Grid-Aligned"),
GraphWireStyle::Direct => write!(f, "Direct"),
}
}
}
impl GraphWireStyle {
pub fn tooltip_description(&self) -> &'static str {
match self {
GraphWireStyle::GridAligned => "Wires follow the grid, running in straight lines between nodes",
GraphWireStyle::Direct => "Wires bend to run at an angle directly between nodes",
}
}
pub fn is_direct(&self) -> bool {
*self == GraphWireStyle::Direct
}
}
pub fn build_vector_wire(output_position: DVec2, input_position: DVec2, vertical_out: bool, vertical_in: bool, graph_wire_style: GraphWireStyle) -> Subpath<PointId> {
let grid_spacing = 24.;
match graph_wire_style {
GraphWireStyle::Direct => {
let horizontal_gap = (output_position.x - input_position.x).abs();
let vertical_gap = (output_position.y - input_position.y).abs();
let curve_length = grid_spacing;
let curve_falloff_rate = curve_length * std::f64::consts::TAU;
let horizontal_curve_amount = -(2_f64.powf((-10. * horizontal_gap) / curve_falloff_rate)) + 1.;
let vertical_curve_amount = -(2_f64.powf((-10. * vertical_gap) / curve_falloff_rate)) + 1.;
let horizontal_curve = horizontal_curve_amount * curve_length;
let vertical_curve = vertical_curve_amount * curve_length;
let locations = [
output_position,
DVec2::new(
if vertical_out { output_position.x } else { output_position.x + horizontal_curve },
if vertical_out { output_position.y - vertical_curve } else { output_position.y },
),
DVec2::new(
if vertical_in { input_position.x } else { input_position.x - horizontal_curve },
if vertical_in { input_position.y + vertical_curve } else { input_position.y },
),
DVec2::new(input_position.x, input_position.y),
];
let smoothing = 0.5;
let delta01 = DVec2::new((locations[1].x - locations[0].x) * smoothing, (locations[1].y - locations[0].y) * smoothing);
let delta23 = DVec2::new((locations[3].x - locations[2].x) * smoothing, (locations[3].y - locations[2].y) * smoothing);
Subpath::new(
vec![
ManipulatorGroup {
anchor: locations[0],
in_handle: None,
out_handle: None,
id: PointId::generate(),
},
ManipulatorGroup {
anchor: locations[1],
in_handle: None,
out_handle: Some(locations[1] + delta01),
id: PointId::generate(),
},
ManipulatorGroup {
anchor: locations[2],
in_handle: Some(locations[2] - delta23),
out_handle: None,
id: PointId::generate(),
},
ManipulatorGroup {
anchor: locations[3],
in_handle: None,
out_handle: None,
id: PointId::generate(),
},
],
false,
)
}
GraphWireStyle::GridAligned => {
let locations = straight_wire_paths(output_position, input_position, vertical_out, vertical_in);
straight_wire_subpath(locations)
}
}
}
fn straight_wire_paths(output_position: DVec2, input_position: DVec2, vertical_out: bool, vertical_in: bool) -> Vec<IVec2> {
let grid_spacing = 24;
let line_width = 2;
let in_x = input_position.x as i32;
let in_y = input_position.y as i32;
let out_x = output_position.x as i32;
let out_y = output_position.y as i32;
let mid_x = (in_x + out_x) / 2 + (((in_x + out_x) / 2) % grid_spacing);
let mid_y = (in_y + out_y) / 2 + (((in_y + out_y) / 2) % grid_spacing);
let mid_y_alternate = (in_y + in_y) / 2 - (((in_y + in_y) / 2) % grid_spacing);
let x1 = out_x;
let x2 = out_x + grid_spacing;
let x3 = in_x - 2 * grid_spacing;
let x4 = in_x;
let x5 = in_x - 2 * grid_spacing + line_width;
let x6 = out_x + grid_spacing + line_width;
let x7 = out_x + 2 * grid_spacing + line_width;
let x8 = in_x + line_width;
let x9 = out_x + 2 * grid_spacing;
let x10 = mid_x + line_width;
let x11 = out_x - grid_spacing;
let x12 = out_x - 4 * grid_spacing;
let x13 = mid_x;
let x14 = in_x + grid_spacing;
let x15 = in_x - 4 * grid_spacing;
let x16 = in_x + 8 * grid_spacing;
let x17 = mid_x - 2 * line_width;
let x18 = out_x + grid_spacing - 2 * line_width;
let x19 = out_x - 2 * line_width;
let x20 = mid_x - line_width;
let y1 = out_y;
let y2 = out_y - grid_spacing;
let y3 = in_y;
let y4 = out_y - grid_spacing + 5 * line_width + 1;
let y5 = in_y - 2 * grid_spacing;
let y6 = out_y + 4 * line_width;
let y7 = out_y + 5 * line_width;
let y8 = out_y - 2 * grid_spacing + 5 * line_width + 1;
let y9 = out_y + 6 * line_width;
let y10 = in_y + 2 * grid_spacing;
let y111 = in_y + grid_spacing + 6 * line_width + 1;
let y12 = in_y + grid_spacing - 5 * line_width + 1;
let y13 = in_y - grid_spacing;
let y14 = in_y + grid_spacing;
let y15 = mid_y;
let y16 = mid_y_alternate;
let wire1 = vec![IVec2::new(x1, y1), IVec2::new(x1, y4), IVec2::new(x5, y4), IVec2::new(x5, y3), IVec2::new(x4, y3)];
let wire2 = vec![IVec2::new(x1, y1), IVec2::new(x1, y16), IVec2::new(x3, y16), IVec2::new(x3, y3), IVec2::new(x4, y3)];
let wire3 = vec![
IVec2::new(x1, y1),
IVec2::new(x1, y4),
IVec2::new(x12, y4),
IVec2::new(x12, y10),
IVec2::new(x3, y10),
IVec2::new(x3, y3),
IVec2::new(x4, y3),
];
let wire4 = vec![
IVec2::new(x1, y1),
IVec2::new(x1, y4),
IVec2::new(x13, y4),
IVec2::new(x13, y10),
IVec2::new(x3, y10),
IVec2::new(x3, y3),
IVec2::new(x4, y3),
];
if out_y == in_y && out_x > in_x && (vertical_out || !vertical_in) {
return vec![IVec2::new(x1, y1), IVec2::new(x2, y1), IVec2::new(x2, y2), IVec2::new(x3, y2), IVec2::new(x3, y3), IVec2::new(x4, y3)];
}
// `outConnector` point and `inConnector` point lying on the same horizontal grid line and `outConnector` point lies to the right of `inConnector` point
if out_y == in_y && out_x > in_x && (vertical_out || !vertical_in) {
return vec![IVec2::new(x1, y1), IVec2::new(x2, y1), IVec2::new(x2, y2), IVec2::new(x3, y2), IVec2::new(x3, y3), IVec2::new(x4, y3)];
};
// Handle straight lines
if out_y == in_y || (out_x == in_x && vertical_out) {
return vec![IVec2::new(x1, y1), IVec2::new(x4, y3)];
};
// Handle standard right-angle paths
// Start vertical, then horizontal
// `outConnector` point lies to the left of `inConnector` point
if vertical_out && in_x > out_x {
// `outConnector` point lies above `inConnector` point
if out_y < in_y {
// `outConnector` point lies on the vertical grid line 4 units to the left of `inConnector` point point
if -4 * grid_spacing <= out_x - in_x && out_x - in_x < -3 * grid_spacing {
return wire1;
};
// `outConnector` point lying on vertical grid lines 3 and 2 units to the left of `inConnector` point
if -3 * grid_spacing <= out_x - in_x && out_x - in_x <= -grid_spacing {
if -2 * grid_spacing <= out_y - in_y && out_y - in_y <= -grid_spacing {
return vec![IVec2::new(x1, y1), IVec2::new(x1, y2), IVec2::new(x2, y2), IVec2::new(x2, y3), IVec2::new(x4, y3)];
};
if -grid_spacing <= out_y - in_y && out_y - in_y <= 0 {
return vec![IVec2::new(x1, y1), IVec2::new(x1, y4), IVec2::new(x6, y4), IVec2::new(x6, y3), IVec2::new(x4, y3)];
};
return vec![
IVec2::new(x1, y1),
IVec2::new(x1, y4),
IVec2::new(x7, y4),
IVec2::new(x7, y5),
IVec2::new(x3, y5),
IVec2::new(x3, y3),
IVec2::new(x4, y3),
];
}
// `outConnector` point lying on vertical grid line 1 units to the left of `inConnector` point
if -grid_spacing < out_x - in_x && out_x - in_x <= 0 {
// `outConnector` point lying on horizontal grid line 1 unit above `inConnector` point
if -2 * grid_spacing <= out_y - in_y && out_y - in_y <= -grid_spacing {
return vec![IVec2::new(x1, y6), IVec2::new(x2, y6), IVec2::new(x8, y3)];
};
// `outConnector` point lying on the same horizontal grid line as `inConnector` point
if -grid_spacing <= out_y - in_y && out_y - in_y <= 0 {
return vec![IVec2::new(x1, y7), IVec2::new(x4, y3)];
};
return vec![
IVec2::new(x1, y1),
IVec2::new(x1, y2),
IVec2::new(x9, y2),
IVec2::new(x9, y5),
IVec2::new(x3, y5),
IVec2::new(x3, y3),
IVec2::new(x4, y3),
];
}
return vec![IVec2::new(x1, y1), IVec2::new(x1, y4), IVec2::new(x10, y4), IVec2::new(x10, y3), IVec2::new(x4, y3)];
}
// `outConnector` point lies below `inConnector` point
// `outConnector` point lying on vertical grid line 1 unit to the left of `inConnector` point
if -grid_spacing <= out_x - in_x && out_x - in_x <= 0 {
// `outConnector` point lying on the horizontal grid lines 1 and 2 units below the `inConnector` point
if 0 <= out_y - in_y && out_y - in_y <= 2 * grid_spacing {
return vec![IVec2::new(x1, y6), IVec2::new(x11, y6), IVec2::new(x11, y3), IVec2::new(x4, y3)];
};
return wire2;
}
return vec![IVec2::new(x1, y1), IVec2::new(x1, y3), IVec2::new(x4, y3)];
}
// `outConnector` point lies to the right of `inConnector` point
if vertical_out && in_x <= out_x {
// `outConnector` point lying on any horizontal grid line above `inConnector` point
if out_y < in_y {
// `outConnector` point lying on horizontal grid line 1 unit above `inConnector` point
if -2 * grid_spacing < out_y - in_y && out_y - in_y <= -grid_spacing {
return wire1;
};
// `outConnector` point lying on the same horizontal grid line as `inConnector` point
if -grid_spacing < out_y - in_y && out_y - in_y <= 0 {
return vec![IVec2::new(x1, y1), IVec2::new(x1, y8), IVec2::new(x5, y8), IVec2::new(x5, y3), IVec2::new(x4, y3)];
};
// `outConnector` point lying on vertical grid lines 1 and 2 units to the right of `inConnector` point
if grid_spacing <= out_x - in_x && out_x - in_x <= 3 * grid_spacing {
return vec![
IVec2::new(x1, y1),
IVec2::new(x1, y4),
IVec2::new(x9, y4),
IVec2::new(x9, y5),
IVec2::new(x3, y5),
IVec2::new(x3, y3),
IVec2::new(x4, y3),
];
}
return vec![
IVec2::new(x1, y1),
IVec2::new(x1, y4),
IVec2::new(x10, y4),
IVec2::new(x10, y5),
IVec2::new(x5, y5),
IVec2::new(x5, y3),
IVec2::new(x4, y3),
];
}
// `outConnector` point lies below `inConnector` point
if out_y - in_y <= grid_spacing {
// `outConnector` point lies on the horizontal grid line 1 unit below the `inConnector` Point
if 0 <= out_x - in_x && out_x - in_x <= 13 * grid_spacing {
return vec![IVec2::new(x1, y9), IVec2::new(x3, y9), IVec2::new(x3, y3), IVec2::new(x4, y3)];
};
if 13 < out_x - in_x && out_x - in_x <= 18 * grid_spacing {
return wire3;
};
return wire4;
}
// `outConnector` point lies on the horizontal grid line 2 units below `outConnector` point
if grid_spacing <= out_y - in_y && out_y - in_y <= 2 * grid_spacing {
if 0 <= out_x - in_x && out_x - in_x <= 13 * grid_spacing {
return vec![IVec2::new(x1, y7), IVec2::new(x5, y7), IVec2::new(x5, y3), IVec2::new(x4, y3)];
};
if 13 < out_x - in_x && out_x - in_x <= 18 * grid_spacing {
return wire3;
};
return wire4;
}
// 0 to 4 units below the `outConnector` Point
if out_y - in_y <= 4 * grid_spacing {
return wire1;
};
return wire2;
}
// Start horizontal, then vertical
if vertical_in {
// when `outConnector` lies below `inConnector`
if out_y > in_y {
// `out_x` lies to the left of `in_x`
if out_x < in_x {
return vec![IVec2::new(x1, y1), IVec2::new(x4, y1), IVec2::new(x4, y3)];
};
// `out_x` lies to the right of `in_x`
if out_y - in_y <= grid_spacing {
// `outConnector` point directly below `inConnector` point
if 0 <= out_x - in_x && out_x - in_x <= grid_spacing {
return vec![IVec2::new(x1, y1), IVec2::new(x14, y1), IVec2::new(x14, y2), IVec2::new(x4, y2), IVec2::new(x4, y3)];
};
// `outConnector` point lies below `inConnector` point and strictly to the right of `inConnector` point
return vec![IVec2::new(x1, y1), IVec2::new(x2, y1), IVec2::new(x2, y111), IVec2::new(x4, y111), IVec2::new(x4, y3)];
}
return vec![IVec2::new(x1, y1), IVec2::new(x2, y1), IVec2::new(x2, y2), IVec2::new(x4, y2), IVec2::new(x4, y3)];
}
// `out_y` lies on or above the `in_y` point
if -6 * grid_spacing < in_x - out_x && in_x - out_x < 4 * grid_spacing {
// edge case: `outConnector` point lying on vertical grid lines ranging from 4 units to left to 5 units to right of `inConnector` point
if -grid_spacing < in_x - out_x && in_x - out_x < 4 * grid_spacing {
return vec![
IVec2::new(x1, y1),
IVec2::new(x2, y1),
IVec2::new(x2, y2),
IVec2::new(x15, y2),
IVec2::new(x15, y12),
IVec2::new(x4, y12),
IVec2::new(x4, y3),
];
}
return vec![IVec2::new(x1, y1), IVec2::new(x16, y1), IVec2::new(x16, y12), IVec2::new(x4, y12), IVec2::new(x4, y3)];
}
// left of edge case: `outConnector` point lying on vertical grid lines more than 4 units to left of `inConnector` point
if 4 * grid_spacing < in_x - out_x {
return vec![IVec2::new(x1, y1), IVec2::new(x17, y1), IVec2::new(x17, y12), IVec2::new(x4, y12), IVec2::new(x4, y3)];
};
// right of edge case: `outConnector` point lying on the vertical grid lines more than 5 units to right of `inConnector` point
if 6 * grid_spacing > in_x - out_x {
return vec![IVec2::new(x1, y1), IVec2::new(x18, y1), IVec2::new(x18, y12), IVec2::new(x4, y12), IVec2::new(x4, y3)];
};
}
// Both horizontal - use horizontal middle point
// When `inConnector` point is one of the two closest diagonally opposite points
if 0 <= in_x - out_x && in_x - out_x <= grid_spacing && in_y - out_y >= -grid_spacing && in_y - out_y <= grid_spacing {
return vec![IVec2::new(x19, y1), IVec2::new(x19, y3), IVec2::new(x4, y3)];
}
// When `inConnector` point lies on the horizontal line 1 unit above and below the `outConnector` point
if -grid_spacing <= out_y - in_y && out_y - in_y <= grid_spacing && out_x > in_x {
// Horizontal line above `out_y`
if in_y < out_y {
return vec![IVec2::new(x1, y1), IVec2::new(x2, y1), IVec2::new(x2, y13), IVec2::new(x3, y13), IVec2::new(x3, y3), IVec2::new(x4, y3)];
};
// Horizontal line below `out_y`
return vec![IVec2::new(x1, y1), IVec2::new(x2, y1), IVec2::new(x2, y14), IVec2::new(x3, y14), IVec2::new(x3, y3), IVec2::new(x4, y3)];
}
// `outConnector` point to the right of `inConnector` point
if out_x > in_x - grid_spacing {
return vec![
IVec2::new(x1, y1),
IVec2::new(x18, y1),
IVec2::new(x18, y15),
IVec2::new(x5, y15),
IVec2::new(x5, y3),
IVec2::new(x4, y3),
];
};
// When `inConnector` point lies on the vertical grid line two units to the right of `outConnector` point
if grid_spacing <= in_x - out_x && in_x - out_x <= 2 * grid_spacing {
return vec![IVec2::new(x1, y1), IVec2::new(x18, y1), IVec2::new(x18, y3), IVec2::new(x4, y3)];
};
vec![IVec2::new(x1, y1), IVec2::new(x20, y1), IVec2::new(x20, y3), IVec2::new(x4, y3)]
}
fn straight_wire_subpath(locations: Vec<IVec2>) -> Subpath<PointId> {
if locations.is_empty() {
return Subpath::new(Vec::new(), false);
}
if locations.len() == 2 {
return Subpath::new(
vec![
ManipulatorGroup {
anchor: locations[0].into(),
in_handle: None,
out_handle: None,
id: PointId::generate(),
},
ManipulatorGroup {
anchor: locations[1].into(),
in_handle: None,
out_handle: None,
id: PointId::generate(),
},
],
false,
);
}
let corner_radius = 10;
// Create path with rounded corners
let mut path = vec![ManipulatorGroup {
anchor: locations[0].into(),
in_handle: None,
out_handle: None,
id: PointId::generate(),
}];
for i in 1..(locations.len() - 1) {
let prev = locations[i - 1];
let curr = locations[i];
let next = locations[i + 1];
let corner_start = IVec2::new(
curr.x
+ if curr.x == prev.x {
0
} else if prev.x > curr.x {
corner_radius
} else {
-corner_radius
},
curr.y
+ if curr.y == prev.y {
0
} else if prev.y > curr.y {
corner_radius
} else {
-corner_radius
},
);
let corner_start_mid = IVec2::new(
curr.x
+ if curr.x == prev.x {
0
} else if prev.x > curr.x {
corner_radius / 2
} else {
-corner_radius / 2
},
curr.y
+ if curr.y == prev.y {
0
} else {
match prev.y > curr.y {
true => corner_radius / 2,
false => -corner_radius / 2,
}
},
);
let corner_end = IVec2::new(
curr.x
+ if curr.x == next.x {
0
} else if next.x > curr.x {
corner_radius
} else {
-corner_radius
},
curr.y
+ if curr.y == next.y {
0
} else if next.y > curr.y {
corner_radius
} else {
-corner_radius
},
);
let corner_end_mid = IVec2::new(
curr.x
+ if curr.x == next.x {
0
} else if next.x > curr.x {
corner_radius / 2
} else {
-corner_radius / 2
},
curr.y
+ if curr.y == next.y {
0
} else if next.y > curr.y {
10 / 2
} else {
-corner_radius / 2
},
);
path.extend(vec![
ManipulatorGroup {
anchor: corner_start.into(),
in_handle: None,
out_handle: Some(corner_start_mid.into()),
id: PointId::generate(),
},
ManipulatorGroup {
anchor: corner_end.into(),
in_handle: Some(corner_end_mid.into()),
out_handle: None,
id: PointId::generate(),
},
])
}
path.push(ManipulatorGroup {
anchor: (*locations.last().unwrap()).into(),
in_handle: None,
out_handle: None,
id: PointId::generate(),
});
Subpath::new(path, false)
}

View file

@ -1,17 +1,16 @@
// TODO: Eventually remove this document upgrade code // TODO: Eventually remove this document upgrade code
// This file contains lots of hacky code for upgrading old documents to the new format // This file contains lots of hacky code for upgrading old documents to the new format
use super::document::utility_types::network_interface::{NumberInputSettings, PropertiesRow, WidgetOverride};
use crate::messages::portfolio::document::node_graph::document_node_definitions::resolve_document_node_type; use crate::messages::portfolio::document::node_graph::document_node_definitions::resolve_document_node_type;
use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier; use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier;
use crate::messages::portfolio::document::utility_types::network_interface::{InputConnector, OutputConnector}; use crate::messages::portfolio::document::utility_types::network_interface::{InputConnector, NodeTemplate, OutputConnector};
use crate::messages::prelude::DocumentMessageHandler; use crate::messages::prelude::DocumentMessageHandler;
use bezier_rs::Subpath; use bezier_rs::Subpath;
use glam::IVec2; use glam::IVec2;
use graph_craft::document::{DocumentNodeImplementation, NodeInput, value::TaggedValue}; use graph_craft::document::{DocumentNodeImplementation, NodeInput, value::TaggedValue};
use graphene_std::text::TypesettingConfig; use graphene_std::text::TypesettingConfig;
use graphene_std::uuid::NodeId; use graphene_std::uuid::NodeId;
use graphene_std::vector::style::{Fill, FillType, Gradient, PaintOrder, StrokeAlign}; use graphene_std::vector::style::{PaintOrder, StrokeAlign};
use graphene_std::vector::{VectorData, VectorDataTable}; use graphene_std::vector::{VectorData, VectorDataTable};
use std::collections::HashMap; use std::collections::HashMap;
@ -190,90 +189,37 @@ pub fn document_migration_reset_node_definition(document_serialized_content: &st
} }
pub fn document_migration_upgrades(document: &mut DocumentMessageHandler, reset_node_definitions_on_open: bool) { pub fn document_migration_upgrades(document: &mut DocumentMessageHandler, reset_node_definitions_on_open: bool) {
let mut network = document.network_interface.document_network().clone(); let network = document.network_interface.document_network().clone();
network.generate_node_paths(&[]);
// Apply string replacements to each node // Apply string replacements to each node
let node_ids: Vec<_> = network.recursive_nodes().map(|(&id, node)| (id, node.original_location.path.clone().unwrap())).collect(); for (node_id, node, network_path) in network.recursive_nodes() {
for (node_id, path) in &node_ids { if let DocumentNodeImplementation::ProtoNode(protonode_id) = &node.implementation {
let network_path: Vec<_> = path.iter().copied().take(path.len() - 1).collect();
if let Some(DocumentNodeImplementation::ProtoNode(protonode_id)) = document
.network_interface
.nested_network(&network_path)
.unwrap()
.nodes
.get(node_id)
.map(|node| node.implementation.clone())
{
for (old, new) in REPLACEMENTS { for (old, new) in REPLACEMENTS {
let node_path_without_type_args = protonode_id.name.split('<').next(); let node_path_without_type_args = protonode_id.name.split('<').next();
let mut default_template = NodeTemplate::default();
default_template.document_node.implementation = DocumentNodeImplementation::ProtoNode(new.to_string().into());
if node_path_without_type_args == Some(old) { if node_path_without_type_args == Some(old) {
document document.network_interface.replace_implementation(node_id, &network_path, &mut default_template);
.network_interface
.replace_implementation(node_id, &network_path, DocumentNodeImplementation::ProtoNode(new.to_string().into()));
document.network_interface.set_manual_compostion(node_id, &network_path, Some(graph_craft::Type::Generic("T".into()))); document.network_interface.set_manual_compostion(node_id, &network_path, Some(graph_craft::Type::Generic("T".into())));
} }
} }
} }
} }
if reset_node_definitions_on_open { // Apply upgrades to each unmodified node.
// This can be used, if uncommented, to upgrade demo artwork with outdated document node internals from their definitions. Delete when it's no longer needed. let nodes = document
// Used for upgrading old internal networks for demo artwork nodes. Will reset all node internals for any opened file .network_interface
for node_id in &document .document_network()
.network_interface .recursive_nodes()
.document_network_metadata() .map(|(node_id, node, path)| (*node_id, node.clone(), path))
.persistent_metadata .collect::<Vec<(NodeId, graph_craft::document::DocumentNode, Vec<NodeId>)>>();
.node_metadata for (node_id, node, network_path) in &nodes {
.keys() if reset_node_definitions_on_open {
.cloned() if let Some(Some(reference)) = document.network_interface.reference(node_id, network_path) {
.collect::<Vec<NodeId>>()
{
if let Some(reference) = document
.network_interface
.document_network_metadata()
.persistent_metadata
.node_metadata
.get(node_id)
.and_then(|node| node.persistent_metadata.reference.as_ref())
{
let Some(node_definition) = resolve_document_node_type(reference) else { continue }; let Some(node_definition) = resolve_document_node_type(reference) else { continue };
let default_definition_node = node_definition.default_node_template(); document.network_interface.replace_implementation(node_id, network_path, &mut node_definition.default_node_template());
document.network_interface.replace_implementation(node_id, &[], default_definition_node.document_node.implementation);
document
.network_interface
.replace_implementation_metadata(node_id, &[], default_definition_node.persistent_node_metadata);
document.network_interface.set_manual_compostion(node_id, &[], default_definition_node.document_node.manual_composition);
} }
} }
}
if document
.network_interface
.document_network_metadata()
.persistent_metadata
.node_metadata
.iter()
.any(|(node_id, node)| node.persistent_metadata.reference.as_ref().is_some_and(|reference| reference == "Output") && *node_id == NodeId(0))
{
document.network_interface.delete_nodes(vec![NodeId(0)], true, &[]);
}
let mut network = document.network_interface.document_network().clone();
network.generate_node_paths(&[]);
let node_ids: Vec<_> = network.recursive_nodes().map(|(&id, node)| (id, node.original_location.path.clone().unwrap())).collect();
// Apply upgrades to each node
for (node_id, path) in &node_ids {
let network_path: Vec<_> = path.iter().copied().take(path.len() - 1).collect();
let network_path = &network_path;
let Some(node) = document.network_interface.nested_network(network_path).unwrap().nodes.get(node_id).cloned() else {
log::error!("could not get node in deserialize_document");
continue;
};
// Upgrade old nodes to use `Context` instead of `()` or `Footprint` for manual composition // Upgrade old nodes to use `Context` instead of `()` or `Footprint` for manual composition
if node.manual_composition == Some(graph_craft::concrete!(())) || node.manual_composition == Some(graph_craft::concrete!(graphene_std::transform::Footprint)) { if node.manual_composition == Some(graph_craft::concrete!(())) || node.manual_composition == Some(graph_craft::concrete!(graphene_std::transform::Footprint)) {
@ -282,87 +228,19 @@ pub fn document_migration_upgrades(document: &mut DocumentMessageHandler, reset_
.set_manual_compostion(node_id, network_path, graph_craft::concrete!(graphene_std::Context).into()); .set_manual_compostion(node_id, network_path, graph_craft::concrete!(graphene_std::Context).into());
} }
let Some(node_metadata) = document.network_interface.network_metadata(network_path).unwrap().persistent_metadata.node_metadata.get(node_id) else { let Some(Some(reference)) = document.network_interface.reference(node_id, network_path).cloned() else {
log::error!("could not get node metadata for node {node_id} in deserialize_document"); // Only nodes that have not been modified and still refer to a definition can be updated
continue;
};
let Some(ref reference) = node_metadata.persistent_metadata.reference.clone() else {
// TODO: Investigate if this should be an expected case, because currently it runs hundreds of times normally.
// TODO: Either delete the commented out error below if this is normal, or fix the underlying issue if this is not expected.
// log::error!("could not get reference in deserialize_document");
continue; continue;
}; };
let reference = &reference;
let inputs_count = node.inputs.len(); let inputs_count = node.inputs.len();
// Upgrade Fill nodes to the format change in #1778
if reference == "Fill" && inputs_count == 8 {
let node_definition = resolve_document_node_type(reference).unwrap();
let document_node = node_definition.default_node_template().document_node;
document.network_interface.replace_implementation(node_id, network_path, document_node.implementation.clone());
let old_inputs = document.network_interface.replace_inputs(node_id, document_node.inputs.clone(), network_path);
document.network_interface.set_input(&InputConnector::node(*node_id, 0), old_inputs[0].clone(), network_path);
let Some(fill_type) = old_inputs[1].as_value().cloned() else { continue };
let TaggedValue::FillType(fill_type) = fill_type else { continue };
let Some(solid_color) = old_inputs[2].as_value().cloned() else { continue };
let TaggedValue::OptionalColor(solid_color) = solid_color else { continue };
let Some(gradient_type) = old_inputs[3].as_value().cloned() else { continue };
let TaggedValue::GradientType(gradient_type) = gradient_type else { continue };
let Some(start) = old_inputs[4].as_value().cloned() else { continue };
let TaggedValue::DVec2(start) = start else { continue };
let Some(end) = old_inputs[5].as_value().cloned() else { continue };
let TaggedValue::DVec2(end) = end else { continue };
let Some(transform) = old_inputs[6].as_value().cloned() else { continue };
let TaggedValue::DAffine2(transform) = transform else { continue };
let Some(positions) = old_inputs[7].as_value().cloned() else { continue };
let TaggedValue::GradientStops(positions) = positions else { continue };
let fill = match (fill_type, solid_color) {
(FillType::Solid, None) => Fill::None,
(FillType::Solid, Some(color)) => Fill::Solid(color),
(FillType::Gradient, _) => Fill::Gradient(Gradient {
stops: positions,
gradient_type,
start,
end,
transform,
}),
};
document
.network_interface
.set_input(&InputConnector::node(*node_id, 1), NodeInput::value(TaggedValue::Fill(fill.clone()), false), network_path);
match fill {
Fill::None => {
document
.network_interface
.set_input(&InputConnector::node(*node_id, 2), NodeInput::value(TaggedValue::OptionalColor(None), false), network_path);
}
Fill::Solid(color) => {
document
.network_interface
.set_input(&InputConnector::node(*node_id, 2), NodeInput::value(TaggedValue::OptionalColor(Some(color)), false), network_path);
}
Fill::Gradient(gradient) => {
document
.network_interface
.set_input(&InputConnector::node(*node_id, 3), NodeInput::value(TaggedValue::Gradient(gradient), false), network_path);
}
}
}
// Upgrade Stroke node to reorder parameters and add "Align" and "Paint Order" (#2644) // Upgrade Stroke node to reorder parameters and add "Align" and "Paint Order" (#2644)
if reference == "Stroke" && inputs_count == 8 { if reference == "Stroke" && inputs_count == 8 {
let node_definition = resolve_document_node_type(reference).unwrap(); let mut node_template = resolve_document_node_type(reference).unwrap().default_node_template();
let document_node = node_definition.default_node_template().document_node; let old_inputs = document.network_interface.replace_inputs(node_id, network_path, &mut node_template).unwrap();
document.network_interface.replace_implementation(node_id, network_path, document_node.implementation.clone());
document.network_interface.insert_input_properties_row(node_id, 8, network_path, ("", "TODO").into());
document.network_interface.insert_input_properties_row(node_id, 9, network_path, ("", "TODO").into());
let old_inputs = document.network_interface.replace_inputs(node_id, document_node.inputs.clone(), network_path);
let align_input = NodeInput::value(TaggedValue::StrokeAlign(StrokeAlign::Center), false); let align_input = NodeInput::value(TaggedValue::StrokeAlign(StrokeAlign::Center), false);
let paint_order_input = NodeInput::value(TaggedValue::PaintOrder(PaintOrder::StrokeAbove), false); let paint_order_input = NodeInput::value(TaggedValue::PaintOrder(PaintOrder::StrokeAbove), false);
@ -454,11 +332,9 @@ pub fn document_migration_upgrades(document: &mut DocumentMessageHandler, reset_
// Upgrade Text node to include line height and character spacing, which were previously hardcoded to 1, from https://github.com/GraphiteEditor/Graphite/pull/2016 // Upgrade Text node to include line height and character spacing, which were previously hardcoded to 1, from https://github.com/GraphiteEditor/Graphite/pull/2016
if reference == "Text" && inputs_count != 9 { if reference == "Text" && inputs_count != 9 {
let node_definition = resolve_document_node_type(reference).unwrap(); let mut template = resolve_document_node_type(reference).unwrap().default_node_template();
let document_node = node_definition.default_node_template().document_node; document.network_interface.replace_implementation(node_id, network_path, &mut template);
document.network_interface.replace_implementation(node_id, network_path, document_node.implementation.clone()); let old_inputs = document.network_interface.replace_inputs(node_id, network_path, &mut template).unwrap();
let old_inputs = document.network_interface.replace_inputs(node_id, document_node.inputs.clone(), network_path);
document.network_interface.set_input(&InputConnector::node(*node_id, 0), old_inputs[0].clone(), network_path); document.network_interface.set_input(&InputConnector::node(*node_id, 0), old_inputs[0].clone(), network_path);
document.network_interface.set_input(&InputConnector::node(*node_id, 1), old_inputs[1].clone(), network_path); document.network_interface.set_input(&InputConnector::node(*node_id, 1), old_inputs[1].clone(), network_path);
@ -500,21 +376,6 @@ pub fn document_migration_upgrades(document: &mut DocumentMessageHandler, reset_
}, },
network_path, network_path,
); );
document.network_interface.insert_input_properties_row(
node_id,
9,
network_path,
PropertiesRow::with_override(
"Tilt",
"Faux italic",
WidgetOverride::Number(NumberInputSettings {
min: Some(-85.),
max: Some(85.),
unit: Some("°".to_string()),
..Default::default()
}),
),
);
document.network_interface.set_input( document.network_interface.set_input(
&InputConnector::node(*node_id, 8), &InputConnector::node(*node_id, 8),
if inputs_count >= 9 { if inputs_count >= 9 {
@ -528,11 +389,10 @@ pub fn document_migration_upgrades(document: &mut DocumentMessageHandler, reset_
// Upgrade Sine, Cosine, and Tangent nodes to include a boolean input for whether the output should be in radians, which was previously the only option but is now not the default // Upgrade Sine, Cosine, and Tangent nodes to include a boolean input for whether the output should be in radians, which was previously the only option but is now not the default
if (reference == "Sine" || reference == "Cosine" || reference == "Tangent") && inputs_count == 1 { if (reference == "Sine" || reference == "Cosine" || reference == "Tangent") && inputs_count == 1 {
let node_definition = resolve_document_node_type(reference).unwrap(); let mut node_template = resolve_document_node_type(reference).unwrap().default_node_template();
let document_node = node_definition.default_node_template().document_node; document.network_interface.replace_implementation(node_id, network_path, &mut node_template);
document.network_interface.replace_implementation(node_id, network_path, document_node.implementation.clone());
let old_inputs = document.network_interface.replace_inputs(node_id, document_node.inputs.clone(), network_path); let old_inputs = document.network_interface.replace_inputs(node_id, network_path, &mut node_template).unwrap();
document.network_interface.set_input(&InputConnector::node(*node_id, 0), old_inputs[0].clone(), network_path); document.network_interface.set_input(&InputConnector::node(*node_id, 0), old_inputs[0].clone(), network_path);
document document
@ -542,11 +402,10 @@ pub fn document_migration_upgrades(document: &mut DocumentMessageHandler, reset_
// Upgrade the Modulo node to include a boolean input for whether the output should be always positive, which was previously not an option // Upgrade the Modulo node to include a boolean input for whether the output should be always positive, which was previously not an option
if reference == "Modulo" && inputs_count == 2 { if reference == "Modulo" && inputs_count == 2 {
let node_definition = resolve_document_node_type(reference).unwrap(); let mut node_template = resolve_document_node_type(reference).unwrap().default_node_template();
let document_node = node_definition.default_node_template().document_node; document.network_interface.replace_implementation(node_id, network_path, &mut node_template);
document.network_interface.replace_implementation(node_id, network_path, document_node.implementation.clone());
let old_inputs = document.network_interface.replace_inputs(node_id, document_node.inputs.clone(), network_path); let old_inputs = document.network_interface.replace_inputs(node_id, network_path, &mut node_template).unwrap();
document.network_interface.set_input(&InputConnector::node(*node_id, 0), old_inputs[0].clone(), network_path); document.network_interface.set_input(&InputConnector::node(*node_id, 0), old_inputs[0].clone(), network_path);
document.network_interface.set_input(&InputConnector::node(*node_id, 1), old_inputs[1].clone(), network_path); document.network_interface.set_input(&InputConnector::node(*node_id, 1), old_inputs[1].clone(), network_path);
@ -557,11 +416,10 @@ pub fn document_migration_upgrades(document: &mut DocumentMessageHandler, reset_
// Upgrade the Mirror node to add the `keep_original` boolean input // Upgrade the Mirror node to add the `keep_original` boolean input
if reference == "Mirror" && inputs_count == 3 { if reference == "Mirror" && inputs_count == 3 {
let node_definition = resolve_document_node_type(reference).unwrap(); let mut node_template = resolve_document_node_type(reference).unwrap().default_node_template();
let document_node = node_definition.default_node_template().document_node; document.network_interface.replace_implementation(node_id, network_path, &mut node_template);
document.network_interface.replace_implementation(node_id, network_path, document_node.implementation.clone());
let old_inputs = document.network_interface.replace_inputs(node_id, document_node.inputs.clone(), network_path); let old_inputs = document.network_interface.replace_inputs(node_id, network_path, &mut node_template).unwrap();
document.network_interface.set_input(&InputConnector::node(*node_id, 0), old_inputs[0].clone(), network_path); document.network_interface.set_input(&InputConnector::node(*node_id, 0), old_inputs[0].clone(), network_path);
document.network_interface.set_input(&InputConnector::node(*node_id, 1), old_inputs[1].clone(), network_path); document.network_interface.set_input(&InputConnector::node(*node_id, 1), old_inputs[1].clone(), network_path);
@ -573,15 +431,10 @@ pub fn document_migration_upgrades(document: &mut DocumentMessageHandler, reset_
// Upgrade the Mirror node to add the `reference_point` input and change `offset` from `DVec2` to `f64` // Upgrade the Mirror node to add the `reference_point` input and change `offset` from `DVec2` to `f64`
if reference == "Mirror" && inputs_count == 4 { if reference == "Mirror" && inputs_count == 4 {
let node_definition = resolve_document_node_type(reference).unwrap(); let mut node_template = resolve_document_node_type(reference).unwrap().default_node_template();
let new_node_template = node_definition.default_node_template(); document.network_interface.replace_implementation(node_id, network_path, &mut node_template);
let document_node = new_node_template.document_node;
document.network_interface.replace_implementation(node_id, network_path, document_node.implementation.clone());
document
.network_interface
.replace_implementation_metadata(node_id, network_path, new_node_template.persistent_node_metadata);
let old_inputs = document.network_interface.replace_inputs(node_id, document_node.inputs.clone(), network_path); let old_inputs = document.network_interface.replace_inputs(node_id, network_path, &mut node_template).unwrap();
let Some(&TaggedValue::DVec2(old_offset)) = old_inputs[1].as_value() else { return }; let Some(&TaggedValue::DVec2(old_offset)) = old_inputs[1].as_value() else { return };
let old_offset = if old_offset.x.abs() > old_offset.y.abs() { old_offset.x } else { old_offset.y }; let old_offset = if old_offset.x.abs() > old_offset.y.abs() { old_offset.x } else { old_offset.y };
@ -608,9 +461,8 @@ pub fn document_migration_upgrades(document: &mut DocumentMessageHandler, reset_
} }
if reference == "Image" && inputs_count == 1 { if reference == "Image" && inputs_count == 1 {
let node_definition = crate::messages::portfolio::document::node_graph::document_node_definitions::resolve_document_node_type(reference).unwrap(); let mut node_template = resolve_document_node_type(reference).unwrap().default_node_template();
let new_image_node = node_definition.default_node_template(); document.network_interface.replace_implementation(node_id, network_path, &mut node_template);
document.network_interface.replace_implementation(node_id, network_path, new_image_node.document_node.implementation);
// Insert a new empty input for the image // Insert a new empty input for the image
document.network_interface.add_import(TaggedValue::None, false, 0, "Empty", "", &[*node_id]); document.network_interface.add_import(TaggedValue::None, false, 0, "Empty", "", &[*node_id]);
@ -618,13 +470,10 @@ pub fn document_migration_upgrades(document: &mut DocumentMessageHandler, reset_
} }
if reference == "Noise Pattern" && inputs_count == 15 { if reference == "Noise Pattern" && inputs_count == 15 {
let node_definition = crate::messages::portfolio::document::node_graph::document_node_definitions::resolve_document_node_type(reference).unwrap(); let mut node_template = resolve_document_node_type(reference).unwrap().default_node_template();
let new_noise_pattern_node = node_definition.default_node_template(); document.network_interface.replace_implementation(node_id, network_path, &mut node_template);
document
.network_interface
.replace_implementation(node_id, network_path, new_noise_pattern_node.document_node.implementation);
let old_inputs = document.network_interface.replace_inputs(node_id, new_noise_pattern_node.document_node.inputs.clone(), network_path); let old_inputs = document.network_interface.replace_inputs(node_id, network_path, &mut node_template).unwrap();
document document
.network_interface .network_interface
@ -635,30 +484,20 @@ pub fn document_migration_upgrades(document: &mut DocumentMessageHandler, reset_
} }
if reference == "Instance on Points" && inputs_count == 2 { if reference == "Instance on Points" && inputs_count == 2 {
let node_definition = resolve_document_node_type(reference).unwrap(); let mut node_template = resolve_document_node_type(reference).unwrap().default_node_template();
let new_node_template = node_definition.default_node_template(); document.network_interface.replace_implementation(node_id, network_path, &mut node_template);
let document_node = new_node_template.document_node;
document.network_interface.replace_implementation(node_id, network_path, document_node.implementation.clone());
document
.network_interface
.replace_implementation_metadata(node_id, network_path, new_node_template.persistent_node_metadata);
let old_inputs = document.network_interface.replace_inputs(node_id, document_node.inputs.clone(), network_path); let old_inputs = document.network_interface.replace_inputs(node_id, network_path, &mut node_template).unwrap();
document.network_interface.set_input(&InputConnector::node(*node_id, 0), old_inputs[0].clone(), network_path); document.network_interface.set_input(&InputConnector::node(*node_id, 0), old_inputs[0].clone(), network_path);
document.network_interface.set_input(&InputConnector::node(*node_id, 1), old_inputs[1].clone(), network_path); document.network_interface.set_input(&InputConnector::node(*node_id, 1), old_inputs[1].clone(), network_path);
} }
if reference == "Morph" && inputs_count == 4 { if reference == "Morph" && inputs_count == 4 {
let node_definition = resolve_document_node_type(reference).unwrap(); let mut node_template = resolve_document_node_type(reference).unwrap().default_node_template();
let new_node_template = node_definition.default_node_template(); document.network_interface.replace_implementation(node_id, network_path, &mut node_template);
let document_node = new_node_template.document_node;
document.network_interface.replace_implementation(node_id, network_path, document_node.implementation.clone());
document
.network_interface
.replace_implementation_metadata(node_id, network_path, new_node_template.persistent_node_metadata);
let old_inputs = document.network_interface.replace_inputs(node_id, document_node.inputs.clone(), network_path); let old_inputs = document.network_interface.replace_inputs(node_id, network_path, &mut node_template).unwrap();
document.network_interface.set_input(&InputConnector::node(*node_id, 0), old_inputs[0].clone(), network_path); document.network_interface.set_input(&InputConnector::node(*node_id, 0), old_inputs[0].clone(), network_path);
document.network_interface.set_input(&InputConnector::node(*node_id, 1), old_inputs[1].clone(), network_path); document.network_interface.set_input(&InputConnector::node(*node_id, 1), old_inputs[1].clone(), network_path);
@ -667,15 +506,10 @@ pub fn document_migration_upgrades(document: &mut DocumentMessageHandler, reset_
} }
if reference == "Brush" && inputs_count == 4 { if reference == "Brush" && inputs_count == 4 {
let node_definition = resolve_document_node_type(reference).unwrap(); let mut node_template = resolve_document_node_type(reference).unwrap().default_node_template();
let new_node_template = node_definition.default_node_template(); document.network_interface.replace_implementation(node_id, network_path, &mut node_template);
let document_node = new_node_template.document_node;
document.network_interface.replace_implementation(node_id, network_path, document_node.implementation.clone());
document
.network_interface
.replace_implementation_metadata(node_id, network_path, new_node_template.persistent_node_metadata);
let old_inputs = document.network_interface.replace_inputs(node_id, document_node.inputs.clone(), network_path); let old_inputs = document.network_interface.replace_inputs(node_id, network_path, &mut node_template).unwrap();
document.network_interface.set_input(&InputConnector::node(*node_id, 0), old_inputs[0].clone(), network_path); document.network_interface.set_input(&InputConnector::node(*node_id, 0), old_inputs[0].clone(), network_path);
// We have removed the second input ("bounds"), so we don't add index 1 and we shift the rest of the inputs down by one // We have removed the second input ("bounds"), so we don't add index 1 and we shift the rest of the inputs down by one
@ -684,15 +518,10 @@ pub fn document_migration_upgrades(document: &mut DocumentMessageHandler, reset_
} }
if reference == "Flatten Vector Elements" { if reference == "Flatten Vector Elements" {
let node_definition = resolve_document_node_type("Flatten Path").unwrap(); let mut node_template = resolve_document_node_type(reference).unwrap().default_node_template();
let new_node_template = node_definition.default_node_template(); document.network_interface.replace_implementation(node_id, network_path, &mut node_template);
let document_node = new_node_template.document_node;
document.network_interface.replace_implementation(node_id, network_path, document_node.implementation.clone());
document
.network_interface
.replace_implementation_metadata(node_id, network_path, new_node_template.persistent_node_metadata);
let old_inputs = document.network_interface.replace_inputs(node_id, document_node.inputs.clone(), network_path); let old_inputs = document.network_interface.replace_inputs(node_id, network_path, &mut node_template).unwrap();
document.network_interface.set_input(&InputConnector::node(*node_id, 0), old_inputs[0].clone(), network_path); document.network_interface.set_input(&InputConnector::node(*node_id, 0), old_inputs[0].clone(), network_path);
@ -700,15 +529,10 @@ pub fn document_migration_upgrades(document: &mut DocumentMessageHandler, reset_
} }
if reference == "Remove Handles" { if reference == "Remove Handles" {
let node_definition = resolve_document_node_type("Auto-Tangents").unwrap(); let mut node_template = resolve_document_node_type(reference).unwrap().default_node_template();
let new_node_template = node_definition.default_node_template(); document.network_interface.replace_implementation(node_id, network_path, &mut node_template);
let document_node = new_node_template.document_node;
document.network_interface.replace_implementation(node_id, network_path, document_node.implementation.clone());
document
.network_interface
.replace_implementation_metadata(node_id, network_path, new_node_template.persistent_node_metadata);
let old_inputs = document.network_interface.replace_inputs(node_id, document_node.inputs.clone(), network_path); let old_inputs = document.network_interface.replace_inputs(node_id, network_path, &mut node_template).unwrap();
document.network_interface.set_input(&InputConnector::node(*node_id, 0), old_inputs[0].clone(), network_path); document.network_interface.set_input(&InputConnector::node(*node_id, 0), old_inputs[0].clone(), network_path);
document document
@ -722,15 +546,10 @@ pub fn document_migration_upgrades(document: &mut DocumentMessageHandler, reset_
} }
if reference == "Generate Handles" { if reference == "Generate Handles" {
let node_definition = resolve_document_node_type("Auto-Tangents").unwrap(); let mut node_template = resolve_document_node_type("Auto-Tangents").unwrap().default_node_template();
let new_node_template = node_definition.default_node_template(); document.network_interface.replace_implementation(node_id, network_path, &mut node_template);
let document_node = new_node_template.document_node;
document.network_interface.replace_implementation(node_id, network_path, document_node.implementation.clone());
document
.network_interface
.replace_implementation_metadata(node_id, network_path, new_node_template.persistent_node_metadata);
let old_inputs = document.network_interface.replace_inputs(node_id, document_node.inputs.clone(), network_path); let old_inputs = document.network_interface.replace_inputs(node_id, network_path, &mut node_template).unwrap();
document.network_interface.set_input(&InputConnector::node(*node_id, 0), old_inputs[0].clone(), network_path); document.network_interface.set_input(&InputConnector::node(*node_id, 0), old_inputs[0].clone(), network_path);
document.network_interface.set_input(&InputConnector::node(*node_id, 1), old_inputs[1].clone(), network_path); document.network_interface.set_input(&InputConnector::node(*node_id, 1), old_inputs[1].clone(), network_path);
@ -742,15 +561,10 @@ pub fn document_migration_upgrades(document: &mut DocumentMessageHandler, reset_
} }
if reference == "Merge by Distance" && inputs_count == 2 { if reference == "Merge by Distance" && inputs_count == 2 {
let node_definition = resolve_document_node_type("Merge by Distance").unwrap(); let mut node_template = resolve_document_node_type(reference).unwrap().default_node_template();
let new_node_template = node_definition.default_node_template(); document.network_interface.replace_implementation(node_id, network_path, &mut node_template);
let document_node = new_node_template.document_node;
document.network_interface.replace_implementation(node_id, network_path, document_node.implementation.clone());
document
.network_interface
.replace_implementation_metadata(node_id, network_path, new_node_template.persistent_node_metadata);
let old_inputs = document.network_interface.replace_inputs(node_id, document_node.inputs.clone(), network_path); let old_inputs = document.network_interface.replace_inputs(node_id, network_path, &mut node_template).unwrap();
document.network_interface.set_input(&InputConnector::node(*node_id, 0), old_inputs[0].clone(), network_path); document.network_interface.set_input(&InputConnector::node(*node_id, 0), old_inputs[0].clone(), network_path);
document.network_interface.set_input(&InputConnector::node(*node_id, 1), old_inputs[1].clone(), network_path); document.network_interface.set_input(&InputConnector::node(*node_id, 1), old_inputs[1].clone(), network_path);
@ -762,15 +576,10 @@ pub fn document_migration_upgrades(document: &mut DocumentMessageHandler, reset_
} }
if reference == "Spatial Merge by Distance" { if reference == "Spatial Merge by Distance" {
let node_definition = resolve_document_node_type("Merge by Distance").unwrap(); let mut node_template = resolve_document_node_type("Merge by Distance").unwrap().default_node_template();
let new_node_template = node_definition.default_node_template(); document.network_interface.replace_implementation(node_id, network_path, &mut node_template);
let document_node = new_node_template.document_node;
document.network_interface.replace_implementation(node_id, network_path, document_node.implementation.clone());
document
.network_interface
.replace_implementation_metadata(node_id, network_path, new_node_template.persistent_node_metadata);
let old_inputs = document.network_interface.replace_inputs(node_id, document_node.inputs.clone(), network_path); let old_inputs = document.network_interface.replace_inputs(node_id, network_path, &mut node_template).unwrap();
document.network_interface.set_input(&InputConnector::node(*node_id, 0), old_inputs[0].clone(), network_path); document.network_interface.set_input(&InputConnector::node(*node_id, 0), old_inputs[0].clone(), network_path);
document.network_interface.set_input(&InputConnector::node(*node_id, 1), old_inputs[1].clone(), network_path); document.network_interface.set_input(&InputConnector::node(*node_id, 1), old_inputs[1].clone(), network_path);
@ -784,15 +593,10 @@ pub fn document_migration_upgrades(document: &mut DocumentMessageHandler, reset_
} }
if reference == "Sample Points" && inputs_count == 5 { if reference == "Sample Points" && inputs_count == 5 {
let node_definition = resolve_document_node_type("Sample Polyline").unwrap(); let mut node_template = resolve_document_node_type("Sample Polyline").unwrap().default_node_template();
let new_node_template = node_definition.default_node_template(); document.network_interface.replace_implementation(node_id, network_path, &mut node_template);
let document_node = new_node_template.document_node;
document.network_interface.replace_implementation(node_id, network_path, document_node.implementation.clone());
document
.network_interface
.replace_implementation_metadata(node_id, network_path, new_node_template.persistent_node_metadata);
let old_inputs = document.network_interface.replace_inputs(node_id, document_node.inputs.clone(), network_path); let old_inputs = document.network_interface.replace_inputs(node_id, network_path, &mut node_template).unwrap();
let new_spacing_value = NodeInput::value(TaggedValue::PointSpacingType(graphene_std::vector::misc::PointSpacingType::Separation), false); let new_spacing_value = NodeInput::value(TaggedValue::PointSpacingType(graphene_std::vector::misc::PointSpacingType::Separation), false);
let new_quantity_value = NodeInput::value(TaggedValue::U32(100), false); let new_quantity_value = NodeInput::value(TaggedValue::U32(100), false);
@ -810,13 +614,11 @@ pub fn document_migration_upgrades(document: &mut DocumentMessageHandler, reset_
// Make the "Quantity" parameter a u32 instead of f64 // Make the "Quantity" parameter a u32 instead of f64
if reference == "Sample Polyline" { if reference == "Sample Polyline" {
let node_definition = resolve_document_node_type("Sample Polyline").unwrap(); let node_definition = resolve_document_node_type("Sample Polyline").unwrap();
let new_node_template = node_definition.default_node_template(); let mut new_node_template = node_definition.default_node_template();
let document_node = new_node_template.document_node;
// Get the inputs, obtain the quantity value, and put the inputs back // Get the inputs, obtain the quantity value, and put the inputs back
let old_inputs = document.network_interface.replace_inputs(node_id, document_node.inputs.clone(), network_path); let old_inputs = document.network_interface.replace_inputs(node_id, network_path, &mut new_node_template).unwrap();
let quantity_value = old_inputs.get(3).cloned(); let quantity_value = old_inputs.get(3).cloned();
let _ = document.network_interface.replace_inputs(node_id, old_inputs, network_path);
if let Some(NodeInput::Value { tagged_value, exposed }) = quantity_value { if let Some(NodeInput::Value { tagged_value, exposed }) = quantity_value {
if let TaggedValue::F64(value) = *tagged_value { if let TaggedValue::F64(value) = *tagged_value {
@ -829,10 +631,10 @@ pub fn document_migration_upgrades(document: &mut DocumentMessageHandler, reset_
// Make the "Grid" node, if its input of index 3 is a DVec2 for "angles" instead of a u32 for the "columns" input that now succeeds "angles", move the angle to index 5 (after "columns" and "rows") // Make the "Grid" node, if its input of index 3 is a DVec2 for "angles" instead of a u32 for the "columns" input that now succeeds "angles", move the angle to index 5 (after "columns" and "rows")
if reference == "Grid" && inputs_count == 6 { if reference == "Grid" && inputs_count == 6 {
let node_definition = resolve_document_node_type(reference).unwrap(); let node_definition = resolve_document_node_type(reference).unwrap();
let new_node_template = node_definition.default_node_template(); let mut new_node_template = node_definition.default_node_template();
let document_node = new_node_template.document_node;
let old_inputs = document.network_interface.replace_inputs(node_id, document_node.inputs.clone(), network_path); let mut current_node_template = document.network_interface.create_node_template(node_id, network_path).unwrap();
let old_inputs = document.network_interface.replace_inputs(node_id, network_path, &mut new_node_template).unwrap();
let index_3_value = old_inputs.get(3).cloned(); let index_3_value = old_inputs.get(3).cloned();
if let Some(NodeInput::Value { tagged_value, exposed: _ }) = index_3_value { if let Some(NodeInput::Value { tagged_value, exposed: _ }) = index_3_value {
@ -846,35 +648,35 @@ pub fn document_migration_upgrades(document: &mut DocumentMessageHandler, reset_
document.network_interface.set_input(&InputConnector::node(*node_id, 5), old_inputs[3].clone(), network_path); document.network_interface.set_input(&InputConnector::node(*node_id, 5), old_inputs[3].clone(), network_path);
} else { } else {
// Swap it back if we're not changing anything // Swap it back if we're not changing anything
let _ = document.network_interface.replace_inputs(node_id, old_inputs, network_path); let _ = document.network_interface.replace_inputs(node_id, network_path, &mut current_node_template);
}
}
}
// Ensure layers are positioned as stacks if they are upstream siblings of another layer
document.network_interface.load_structure();
let all_layers = LayerNodeIdentifier::ROOT_PARENT.descendants(document.network_interface.document_metadata()).collect::<Vec<_>>();
for layer in all_layers {
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.first())
.and_then(|input_connector| input_connector.node_id().map(|node_id| (node_id, input_connector.input_index())))
else {
continue;
};
// If the downstream node is a layer and the input is the first input and the current layer is not in a stack
if input_index == 0 && document.network_interface.is_layer(&downstream_node, &[]) && !document.network_interface.is_stack(&layer.to_node(), &[]) {
// Ensure the layer is horizontally aligned with the downstream layer to prevent changing the layout of old files
let (Some(layer_position), Some(downstream_position)) = (document.network_interface.position(&layer.to_node(), &[]), document.network_interface.position(&downstream_node, &[])) else {
log::error!("Could not get position for layer {:?} or downstream node {} when opening file", layer.to_node(), downstream_node);
continue;
};
if layer_position.x == downstream_position.x {
document.network_interface.set_stack_position_calculated_offset(&layer.to_node(), &downstream_node, &[]);
} }
} }
} }
} }
// Ensure layers are positioned as stacks if they are upstream siblings of another layer
document.network_interface.load_structure();
let all_layers = LayerNodeIdentifier::ROOT_PARENT.descendants(document.network_interface.document_metadata()).collect::<Vec<_>>();
for layer in all_layers {
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.first())
.and_then(|input_connector| input_connector.node_id().map(|node_id| (node_id, input_connector.input_index())))
else {
continue;
};
// If the downstream node is a layer and the input is the first input and the current layer is not in a stack
if input_index == 0 && document.network_interface.is_layer(&downstream_node, &[]) && !document.network_interface.is_stack(&layer.to_node(), &[]) {
// Ensure the layer is horizontally aligned with the downstream layer to prevent changing the layout of old files
let (Some(layer_position), Some(downstream_position)) = (document.network_interface.position(&layer.to_node(), &[]), document.network_interface.position(&downstream_node, &[])) else {
log::error!("Could not get position for layer {:?} or downstream node {} when opening file", layer.to_node(), downstream_node);
continue;
};
if layer_position.x == downstream_position.x {
document.network_interface.set_stack_position_calculated_offset(&layer.to_node(), &downstream_node, &[]);
}
}
}
} }

View file

@ -12,6 +12,7 @@ use crate::messages::layout::utility_types::widget_prelude::*;
use crate::messages::portfolio::document::DocumentMessageData; use crate::messages::portfolio::document::DocumentMessageData;
use crate::messages::portfolio::document::graph_operation::utility_types::TransformIn; use crate::messages::portfolio::document::graph_operation::utility_types::TransformIn;
use crate::messages::portfolio::document::utility_types::clipboards::{Clipboard, CopyBufferEntry, INTERNAL_CLIPBOARD_COUNT}; use crate::messages::portfolio::document::utility_types::clipboards::{Clipboard, CopyBufferEntry, INTERNAL_CLIPBOARD_COUNT};
use crate::messages::portfolio::document::utility_types::network_interface::OutputConnector;
use crate::messages::portfolio::document::utility_types::nodes::SelectedNodes; use crate::messages::portfolio::document::utility_types::nodes::SelectedNodes;
use crate::messages::portfolio::document_migration::*; use crate::messages::portfolio::document_migration::*;
use crate::messages::preferences::SelectionMode; use crate::messages::preferences::SelectionMode;
@ -426,6 +427,42 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageData<'_>> for PortfolioMes
// Upgrade the document's nodes to be compatible with the latest version // Upgrade the document's nodes to be compatible with the latest version
document_migration_upgrades(&mut document, reset_node_definitions_on_open); document_migration_upgrades(&mut document, reset_node_definitions_on_open);
// Ensure each node has the metadata for its inputs
for (node_id, node, path) in document.network_interface.document_network().clone().recursive_nodes() {
document.network_interface.validate_input_metadata(node_id, node, &path);
document.network_interface.validate_display_name_metadata(node_id, &path);
}
// Ensure layers are positioned as stacks if they are upstream siblings of another layer
document.network_interface.load_structure();
let all_layers = LayerNodeIdentifier::ROOT_PARENT.descendants(document.network_interface.document_metadata()).collect::<Vec<_>>();
for layer in all_layers {
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.first())
.and_then(|input_connector| input_connector.node_id().map(|node_id| (node_id, input_connector.input_index())))
else {
continue;
};
// If the downstream node is a layer and the input is the first input and the current layer is not in a stack
if input_index == 0 && document.network_interface.is_layer(&downstream_node, &[]) && !document.network_interface.is_stack(&layer.to_node(), &[]) {
// Ensure the layer is horizontally aligned with the downstream layer to prevent changing the layout of old files
let (Some(layer_position), Some(downstream_position)) =
(document.network_interface.position(&layer.to_node(), &[]), document.network_interface.position(&downstream_node, &[]))
else {
log::error!("Could not get position for layer {:?} or downstream node {} when opening file", layer.to_node(), downstream_node);
continue;
};
if layer_position.x == downstream_position.x {
document.network_interface.set_stack_position_calculated_offset(&layer.to_node(), &downstream_node, &[]);
}
}
}
// Set the save state of the document based on what's given to us by the caller to this message // Set the save state of the document based on what's given to us by the caller to this message
document.set_auto_save_state(document_is_auto_saved); document.set_auto_save_state(document_is_auto_saved);
document.set_save_state(document_is_saved); document.set_save_state(document_is_saved);
@ -709,7 +746,7 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageData<'_>> for PortfolioMes
} }
let Some(document) = self.documents.get_mut(&document_id) else { let Some(document) = self.documents.get_mut(&document_id) else {
warn!("Tried to read non existant document"); warn!("Tried to read non existent document");
return; return;
}; };
if !document.is_loaded { if !document.is_loaded {

View file

@ -1,4 +1,4 @@
use crate::messages::portfolio::document::node_graph::utility_types::GraphWireStyle; use crate::messages::portfolio::document::utility_types::wires::GraphWireStyle;
use crate::messages::preferences::SelectionMode; use crate::messages::preferences::SelectionMode;
use crate::messages::prelude::*; use crate::messages::prelude::*;

View file

@ -1,6 +1,6 @@
use crate::consts::VIEWPORT_ZOOM_WHEEL_RATE; use crate::consts::VIEWPORT_ZOOM_WHEEL_RATE;
use crate::messages::input_mapper::key_mapping::MappingVariant; use crate::messages::input_mapper::key_mapping::MappingVariant;
use crate::messages::portfolio::document::node_graph::utility_types::GraphWireStyle; use crate::messages::portfolio::document::utility_types::wires::GraphWireStyle;
use crate::messages::preferences::SelectionMode; use crate::messages::preferences::SelectionMode;
use crate::messages::prelude::*; use crate::messages::prelude::*;
use graph_craft::wasm_application_io::EditorPreferences; use graph_craft::wasm_application_io::EditorPreferences;
@ -86,7 +86,8 @@ impl MessageHandler<PreferencesMessage, ()> for PreferencesMessageHandler {
} }
PreferencesMessage::GraphWireStyle { style } => { PreferencesMessage::GraphWireStyle { style } => {
self.graph_wire_style = style; self.graph_wire_style = style;
responses.add(NodeGraphMessage::SendGraph); responses.add(NodeGraphMessage::UnloadWires);
responses.add(NodeGraphMessage::SendWires);
} }
PreferencesMessage::ViewportZoomWheelRate { rate } => { PreferencesMessage::ViewportZoomWheelRate { rate } => {
self.viewport_zoom_wheel_rate = rate; self.viewport_zoom_wheel_rate = rate;

View file

@ -172,9 +172,10 @@ impl EditorTestUtils {
pub fn get_node<'a, T: InputAccessor<'a, DocumentNode>>(&'a self) -> impl Iterator<Item = T> + 'a { pub fn get_node<'a, T: InputAccessor<'a, DocumentNode>>(&'a self) -> impl Iterator<Item = T> + 'a {
self.active_document() self.active_document()
.network_interface .network_interface
.iter_recursive() .document_network()
.inspect(|node| println!("{:#?}", node.1.implementation)) .recursive_nodes()
.filter_map(move |(_, document)| T::new_with_source(document)) .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) { pub async fn move_mouse(&mut self, x: f64, y: f64, modifier_keys: ModifierKeys, mouse_keys: MouseKeys) {
@ -300,7 +301,7 @@ pub trait FrontendMessageTestUtils {
impl FrontendMessageTestUtils for FrontendMessage { impl FrontendMessageTestUtils for FrontendMessage {
fn check_node_graph_error(&self) { fn check_node_graph_error(&self) {
let FrontendMessage::UpdateNodeGraph { nodes, .. } = self else { return }; let FrontendMessage::UpdateNodeGraphNodes { nodes, .. } = self else { return };
for node in nodes { for node in nodes {
if let Some(error) = &node.errors { if let Some(error) = &node.errors {

View file

@ -1,11 +1,11 @@
<script lang="ts"> <script lang="ts">
import { getContext, onMount, tick } from "svelte"; import { getContext } from "svelte";
import { cubicInOut } from "svelte/easing"; import { cubicInOut } from "svelte/easing";
import { fade } from "svelte/transition"; import { fade } from "svelte/transition";
import type { Editor } from "@graphite/editor"; import type { Editor } from "@graphite/editor";
import type { Node } from "@graphite/messages"; import type { Node } from "@graphite/messages";
import type { FrontendNodeWire, FrontendNode, FrontendGraphInput, FrontendGraphOutput, FrontendGraphDataType, WirePath } from "@graphite/messages"; import type { FrontendNode, FrontendGraphInput, FrontendGraphOutput } from "@graphite/messages";
import type { NodeGraphState } from "@graphite/state-providers/node-graph"; import type { NodeGraphState } from "@graphite/state-providers/node-graph";
import type { IconName } from "@graphite/utility-functions/icons"; import type { IconName } from "@graphite/utility-functions/icons";
@ -27,26 +27,13 @@
const nodeGraph = getContext<NodeGraphState>("nodeGraph"); const nodeGraph = getContext<NodeGraphState>("nodeGraph");
let graph: HTMLDivElement | undefined; let graph: HTMLDivElement | undefined;
let nodesContainer: HTMLDivElement | undefined;
// TODO: Using this not-complete code, or another better approach, make it so the dragged in-progress connector correctly handles showing/hiding the SVG shape of the connector caps // Key value is node id + input/output index
// let wireInProgressFromLayerTop: bigint | undefined = undefined; // Imports/Export are stored at a key value of 0
// let wireInProgressFromLayerBottom: bigint | undefined = undefined;
let nodeWirePaths: WirePath[] = [];
// TODO: Convert these arrays-of-arrays to a Map?
let inputs: SVGSVGElement[][] = [];
let outputs: SVGSVGElement[][] = [];
let nodeElements: HTMLDivElement[] = [];
$: watchNodes($nodeGraph.nodes);
$: gridSpacing = calculateGridSpacing($nodeGraph.transform.scale); $: gridSpacing = calculateGridSpacing($nodeGraph.transform.scale);
$: dotRadius = 1 + Math.floor($nodeGraph.transform.scale - 0.5 + 0.001) / 2; $: dotRadius = 1 + Math.floor($nodeGraph.transform.scale - 0.5 + 0.001) / 2;
$: wirePaths = createWirePaths($nodeGraph.wirePathInProgress, nodeWirePaths);
let inputElement: HTMLInputElement; let inputElement: HTMLInputElement;
let hoveringImportIndex: number | undefined = undefined; let hoveringImportIndex: number | undefined = undefined;
let hoveringExportIndex: number | undefined = undefined; let hoveringExportIndex: number | undefined = undefined;
@ -121,80 +108,6 @@
return sparse; return sparse;
} }
function createWirePaths(wirePathInProgress: WirePath | undefined, nodeWirePaths: WirePath[]): WirePath[] {
const maybeWirePathInProgress = wirePathInProgress ? [wirePathInProgress] : [];
return [...maybeWirePathInProgress, ...nodeWirePaths];
}
async function watchNodes(nodes: Map<bigint, FrontendNode>) {
Array.from(nodes.keys()).forEach((_, index) => {
if (!inputs[index + 1]) inputs[index + 1] = [];
if (!outputs[index + 1]) outputs[index + 1] = [];
});
if (!inputs[0]) inputs[0] = [];
if (!outputs[0]) outputs[0] = [];
await refreshWires();
}
function resolveWire(wire: FrontendNodeWire): { nodeOutput: SVGSVGElement; nodeInput: SVGSVGElement } | undefined {
// TODO: Avoid the linear search
const wireStartNodeIdIndex = Array.from($nodeGraph.nodes.keys()).findIndex((nodeId) => nodeId === (wire.wireStart as Node).nodeId);
let nodeOutputConnectors = outputs[wireStartNodeIdIndex + 1];
if (nodeOutputConnectors === undefined && (wire.wireStart as Node).nodeId === undefined) {
nodeOutputConnectors = outputs[0];
}
const indexOutput = Number(wire.wireStart.index);
const nodeOutput = nodeOutputConnectors?.[indexOutput] as SVGSVGElement | undefined;
if (nodeOutput === undefined) return undefined;
// TODO: Avoid the linear search
const wireEndNodeIdIndex = Array.from($nodeGraph.nodes.keys()).findIndex((nodeId) => nodeId === (wire.wireEnd as Node).nodeId);
let nodeInputConnectors = inputs[wireEndNodeIdIndex + 1] || undefined;
if (nodeInputConnectors === undefined && (wire.wireEnd as Node).nodeId === undefined) {
nodeInputConnectors = inputs[0];
}
const indexInput = Number(wire.wireEnd.index);
const nodeInput = nodeInputConnectors?.[indexInput] as SVGSVGElement | undefined;
if (nodeInput === undefined) return undefined;
return { nodeOutput, nodeInput };
}
function createWirePath(outputPort: SVGSVGElement, inputPort: SVGSVGElement, verticalOut: boolean, verticalIn: boolean, dashed: boolean, directNotGridAligned: boolean): WirePath {
const inputPortRect = inputPort.getBoundingClientRect();
const outputPortRect = outputPort.getBoundingClientRect();
const pathString = directNotGridAligned
? buildCurvedWirePathString(outputPortRect, inputPortRect, verticalOut, verticalIn)
: buildStraightWirePathString(outputPortRect, inputPortRect, verticalOut, verticalIn);
const dataType = (outputPort.getAttribute("data-datatype") as FrontendGraphDataType) || "General";
const thick = verticalIn && verticalOut;
return { pathString, dataType, thick, dashed };
}
async function refreshWires() {
await tick();
nodeWirePaths = $nodeGraph.wires.flatMap((wire) => {
// TODO: This call contains linear searches, which combined with the loop we're in, causes O(n^2) complexity as the graph grows
const resolvedWires = resolveWire(wire);
if (!resolvedWires) return [];
const { nodeOutput, nodeInput } = resolvedWires;
const wireStartNode = wire.wireStart.nodeId !== undefined ? $nodeGraph.nodes.get(wire.wireStart.nodeId) : undefined;
const wireStart = wireStartNode?.isLayer || false;
const wireEndNode = wire.wireEnd.nodeId !== undefined ? $nodeGraph.nodes.get(wire.wireEnd.nodeId) : undefined;
const wireEnd = (wireEndNode?.isLayer && Number(wire.wireEnd.index) === 0) || false;
return [createWirePath(nodeOutput, nodeInput, wireStart, wireEnd, wire.dashed, $nodeGraph.wiresDirectNotGridAligned)];
});
}
onMount(refreshWires);
function nodeIcon(icon?: string): IconName { function nodeIcon(icon?: string): IconName {
if (!icon) return "NodeNodes"; if (!icon) return "NodeNodes";
const iconMap: Record<string, IconName> = { const iconMap: Record<string, IconName> = {
@ -203,325 +116,6 @@
return iconMap[icon] || "NodeNodes"; return iconMap[icon] || "NodeNodes";
} }
function buildStraightWirePathLocations(outputBounds: DOMRect, inputBounds: DOMRect, verticalOut: boolean, verticalIn: boolean): { x: number; y: number }[] {
if (!nodesContainer) return [];
const VERTICAL_WIRE_OVERLAP_ON_SHAPED_CAP = 1;
const LINE_WIDTH = 2;
// Calculate coordinates for input and output connectors
const inX = verticalIn ? inputBounds.x + inputBounds.width / 2 : inputBounds.x;
const inY = verticalIn ? inputBounds.y + inputBounds.height - VERTICAL_WIRE_OVERLAP_ON_SHAPED_CAP : inputBounds.y + inputBounds.height / 2;
const outX = verticalOut ? outputBounds.x + outputBounds.width / 2 : outputBounds.x + outputBounds.width - 1;
const outY = verticalOut ? outputBounds.y + VERTICAL_WIRE_OVERLAP_ON_SHAPED_CAP : outputBounds.y + outputBounds.height / 2;
// Adjust for scale
const containerBounds = nodesContainer.getBoundingClientRect();
const scale = $nodeGraph.transform.scale;
const inConnectorX = Math.round((inX - containerBounds.x) / scale);
const inConnectorY = Math.round((inY - containerBounds.y) / scale);
const outConnectorX = Math.round((outX - containerBounds.x) / scale);
const outConnectorY = Math.round((outY - containerBounds.y) / scale);
// Helper functions for calculating coordinates
const calculateMidX = () => (inConnectorX + outConnectorX) / 2 + (((inConnectorX + outConnectorX) / 2) % gridSpacing);
const calculateMidY = () => (inConnectorY + outConnectorY) / 2 + (((inConnectorY + outConnectorY) / 2) % gridSpacing);
const calculateMidYAlternate = () => (inConnectorY + outConnectorY) / 2 - (((inConnectorY + outConnectorY) / 2) % gridSpacing);
// Define X coordinate calculations
const x1 = () => outConnectorX;
const x2 = () => outConnectorX + gridSpacing;
const x3 = () => inConnectorX - 2 * gridSpacing;
const x4 = () => inConnectorX;
const x5 = () => inConnectorX - 2 * gridSpacing + LINE_WIDTH;
const x6 = () => outConnectorX + gridSpacing + LINE_WIDTH;
const x7 = () => outConnectorX + 2 * gridSpacing + LINE_WIDTH;
const x8 = () => inConnectorX + LINE_WIDTH;
const x9 = () => outConnectorX + 2 * gridSpacing;
const x10 = () => calculateMidX() + LINE_WIDTH;
const x11 = () => outConnectorX - gridSpacing;
const x12 = () => outConnectorX - 4 * gridSpacing;
const x13 = () => calculateMidX();
const x14 = () => inConnectorX + gridSpacing;
const x15 = () => inConnectorX - 4 * gridSpacing;
const x16 = () => inConnectorX + 8 * gridSpacing;
const x17 = () => calculateMidX() - 2 * LINE_WIDTH;
const x18 = () => outConnectorX + gridSpacing - 2 * LINE_WIDTH;
const x19 = () => outConnectorX - 2 * LINE_WIDTH;
const x20 = () => calculateMidX() - LINE_WIDTH;
// Define Y coordinate calculations
const y1 = () => outConnectorY;
const y2 = () => outConnectorY - gridSpacing;
const y3 = () => inConnectorY;
const y4 = () => outConnectorY - gridSpacing + 5.5 * LINE_WIDTH;
const y5 = () => inConnectorY - 2 * gridSpacing;
const y6 = () => outConnectorY + 4 * LINE_WIDTH;
const y7 = () => outConnectorY + 5 * LINE_WIDTH;
const y8 = () => outConnectorY - 2 * gridSpacing + 5.5 * LINE_WIDTH;
const y9 = () => outConnectorY + 6 * LINE_WIDTH;
const y10 = () => inConnectorY + 2 * gridSpacing;
const y111 = () => inConnectorY + gridSpacing + 6.5 * LINE_WIDTH;
const y12 = () => inConnectorY + gridSpacing - 5.5 * LINE_WIDTH;
const y13 = () => inConnectorY - gridSpacing;
const y14 = () => inConnectorY + gridSpacing;
const y15 = () => calculateMidY();
const y16 = () => calculateMidYAlternate();
// Helper function for constructing coordinate pairs
const construct = (...coords: [() => number, () => number][]) => coords.map(([x, y]) => ({ x: x(), y: y() }));
// Define wire path shapes that get used more than once
const wire1 = () => construct([x1, y1], [x1, y4], [x5, y4], [x5, y3], [x4, y3]);
const wire2 = () => construct([x1, y1], [x1, y16], [x3, y16], [x3, y3], [x4, y3]);
const wire3 = () => construct([x1, y1], [x1, y4], [x12, y4], [x12, y10], [x3, y10], [x3, y3], [x4, y3]);
const wire4 = () => construct([x1, y1], [x1, y4], [x13, y4], [x13, y10], [x3, y10], [x3, y3], [x4, y3]);
// `outConnector` point and `inConnector` point lying on the same horizontal grid line and `outConnector` point lies to the right of `inConnector` point
if (outConnectorY === inConnectorY && outConnectorX > inConnectorX && (verticalOut || !verticalIn)) return construct([x1, y1], [x2, y1], [x2, y2], [x3, y2], [x3, y3], [x4, y3]);
// Handle straight lines
if (outConnectorY === inConnectorY || (outConnectorX === inConnectorX && verticalOut)) return construct([x1, y1], [x4, y3]);
// Handle standard right-angle paths
// Start vertical, then horizontal
// `outConnector` point lies to the left of `inConnector` point
if (verticalOut && inConnectorX > outConnectorX) {
// `outConnector` point lies above `inConnector` point
if (outConnectorY < inConnectorY) {
// `outConnector` point lies on the vertical grid line 4 units to the left of `inConnector` point point
if (-4 * gridSpacing <= outConnectorX - inConnectorX && outConnectorX - inConnectorX < -3 * gridSpacing) return wire1();
// `outConnector` point lying on vertical grid lines 3 and 2 units to the left of `inConnector` point
if (-3 * gridSpacing <= outConnectorX - inConnectorX && outConnectorX - inConnectorX <= -1 * gridSpacing) {
if (-2 * gridSpacing <= outConnectorY - inConnectorY && outConnectorY - inConnectorY <= -1 * gridSpacing) return construct([x1, y1], [x1, y2], [x2, y2], [x2, y3], [x4, y3]);
if (-1 * gridSpacing <= outConnectorY - inConnectorY && outConnectorY - inConnectorY <= 0 * gridSpacing) return construct([x1, y1], [x1, y4], [x6, y4], [x6, y3], [x4, y3]);
return construct([x1, y1], [x1, y4], [x7, y4], [x7, y5], [x3, y5], [x3, y3], [x4, y3]);
}
// `outConnector` point lying on vertical grid line 1 units to the left of `inConnector` point
if (-1 * gridSpacing < outConnectorX - inConnectorX && outConnectorX - inConnectorX <= 0 * gridSpacing) {
// `outConnector` point lying on horizontal grid line 1 unit above `inConnector` point
if (-2 * gridSpacing <= outConnectorY - inConnectorY && outConnectorY - inConnectorY <= -1 * gridSpacing) return construct([x1, y6], [x2, y6], [x8, y3]);
// `outConnector` point lying on the same horizontal grid line as `inConnector` point
if (-1 * gridSpacing <= outConnectorY - inConnectorY && outConnectorY - inConnectorY <= 0 * gridSpacing) return construct([x1, y7], [x4, y3]);
return construct([x1, y1], [x1, y2], [x9, y2], [x9, y5], [x3, y5], [x3, y3], [x4, y3]);
}
return construct([x1, y1], [x1, y4], [x10, y4], [x10, y3], [x4, y3]);
}
// `outConnector` point lies below `inConnector` point
// `outConnector` point lying on vertical grid line 1 unit to the left of `inConnector` point
if (-1 * gridSpacing <= outConnectorX - inConnectorX && outConnectorX - inConnectorX <= 0 * gridSpacing) {
// `outConnector` point lying on the horizontal grid lines 1 and 2 units below the `inConnector` point
if (0 * gridSpacing <= outConnectorY - inConnectorY && outConnectorY - inConnectorY <= 2 * gridSpacing) construct([x1, y6], [x11, y6], [x11, y3], [x4, y3]);
return wire2();
}
return construct([x1, y1], [x1, y3], [x4, y3]);
}
// `outConnector` point lies to the right of `inConnector` point
if (verticalOut && inConnectorX <= outConnectorX) {
// `outConnector` point lying on any horizontal grid line above `inConnector` point
if (outConnectorY < inConnectorY) {
// `outConnector` point lying on horizontal grid line 1 unit above `inConnector` point
if (-2 * gridSpacing < outConnectorY - inConnectorY && outConnectorY - inConnectorY <= -1 * gridSpacing) return wire1();
// `outConnector` point lying on the same horizontal grid line as `inConnector` point
if (-1 * gridSpacing < outConnectorY - inConnectorY && outConnectorY - inConnectorY <= 0 * gridSpacing) return construct([x1, y1], [x1, y8], [x5, y8], [x5, y3], [x4, y3]);
// `outConnector` point lying on vertical grid lines 1 and 2 units to the right of `inConnector` point
if (gridSpacing <= outConnectorX - inConnectorX && outConnectorX - inConnectorX <= 3 * gridSpacing) {
return construct([x1, y1], [x1, y4], [x9, y4], [x9, y5], [x3, y5], [x3, y3], [x4, y3]);
}
return construct([x1, y1], [x1, y4], [x10, y4], [x10, y5], [x5, y5], [x5, y3], [x4, y3]);
}
// `outConnector` point lies below `inConnector` point
if (outConnectorY - inConnectorY <= gridSpacing) {
// `outConnector` point lies on the horizontal grid line 1 unit below the `inConnector` Point
if (0 <= outConnectorX - inConnectorX && outConnectorX - inConnectorX <= 13 * gridSpacing) return construct([x1, y9], [x3, y9], [x3, y3], [x4, y3]);
if (13 < outConnectorX - inConnectorX && outConnectorX - inConnectorX <= 18 * gridSpacing) return wire3();
return wire4();
}
// `outConnector` point lies on the horizontal grid line 2 units below `outConnector` point
if (gridSpacing <= outConnectorY - inConnectorY && outConnectorY - inConnectorY <= 2 * gridSpacing) {
if (0 <= outConnectorX - inConnectorX && outConnectorX - inConnectorX <= 13 * gridSpacing) return construct([x1, y7], [x5, y7], [x5, y3], [x4, y3]);
if (13 < outConnectorX - inConnectorX && outConnectorX - inConnectorX <= 18 * gridSpacing) return wire3();
return wire4();
}
// 0 to 4 units below the `outConnector` Point
if (outConnectorY - inConnectorY <= 4 * gridSpacing) return wire1();
return wire2();
}
// Start horizontal, then vertical
if (verticalIn) {
// when `outConnector` lies below `inConnector`
if (outConnectorY > inConnectorY) {
// `outConnectorX` lies to the left of `inConnectorX`
if (outConnectorX < inConnectorX) return construct([x1, y1], [x4, y1], [x4, y3]);
// `outConnectorX` lies to the right of `inConnectorX`
if (outConnectorY - inConnectorY <= gridSpacing) {
// `outConnector` point directly below `inConnector` point
if (0 <= outConnectorX - inConnectorX && outConnectorX - inConnectorX <= gridSpacing) return construct([x1, y1], [x14, y1], [x14, y2], [x4, y2], [x4, y3]);
// `outConnector` point lies below `inConnector` point and strictly to the right of `inConnector` point
return construct([x1, y1], [x2, y1], [x2, y111], [x4, y111], [x4, y3]);
}
return construct([x1, y1], [x2, y1], [x2, y2], [x4, y2], [x4, y3]);
}
// `outConnectorY` lies on or above the `inConnectorY` point
if (-6 * gridSpacing < inConnectorX - outConnectorX && inConnectorX - outConnectorX < 4 * gridSpacing) {
// edge case: `outConnector` point lying on vertical grid lines ranging from 4 units to left to 5 units to right of `inConnector` point
if (-1 * gridSpacing < inConnectorX - outConnectorX && inConnectorX - outConnectorX < 4 * gridSpacing) {
return construct([x1, y1], [x2, y1], [x2, y2], [x15, y2], [x15, y12], [x4, y12], [x4, y3]);
}
return construct([x1, y1], [x16, y1], [x16, y12], [x4, y12], [x4, y3]);
}
// left of edge case: `outConnector` point lying on vertical grid lines more than 4 units to left of `inConnector` point
if (4 * gridSpacing < inConnectorX - outConnectorX) return construct([x1, y1], [x17, y1], [x17, y12], [x4, y12], [x4, y3]);
// right of edge case: `outConnector` point lying on the vertical grid lines more than 5 units to right of `inConnector` point
if (6 * gridSpacing > inConnectorX - outConnectorX) return construct([x1, y1], [x18, y1], [x18, y12], [x4, y12], [x4, y3]);
}
// Both horizontal - use horizontal middle point
// When `inConnector` point is one of the two closest diagonally opposite points
if (0 <= inConnectorX - outConnectorX && inConnectorX - outConnectorX <= gridSpacing && inConnectorY - outConnectorY >= -1 * gridSpacing && inConnectorY - outConnectorY <= gridSpacing) {
return construct([x19, y1], [x19, y3], [x4, y3]);
}
// When `inConnector` point lies on the horizontal line 1 unit above and below the `outConnector` point
if (-1 * gridSpacing <= outConnectorY - inConnectorY && outConnectorY - inConnectorY <= gridSpacing && outConnectorX > inConnectorX) {
// Horizontal line above `outConnectorY`
if (inConnectorY < outConnectorY) return construct([x1, y1], [x2, y1], [x2, y13], [x3, y13], [x3, y3], [x4, y3]);
// Horizontal line below `outConnectorY`
return construct([x1, y1], [x2, y1], [x2, y14], [x3, y14], [x3, y3], [x4, y3]);
}
// `outConnector` point to the right of `inConnector` point
if (outConnectorX > inConnectorX - gridSpacing) return construct([x1, y1], [x18, y1], [x18, y15], [x5, y15], [x5, y3], [x4, y3]);
// When `inConnector` point lies on the vertical grid line two units to the right of `outConnector` point
if (gridSpacing <= inConnectorX - outConnectorX && inConnectorX - outConnectorX <= 2 * gridSpacing) return construct([x1, y1], [x18, y1], [x18, y3], [x4, y3]);
return construct([x1, y1], [x20, y1], [x20, y3], [x4, y3]);
}
function buildStraightWirePathString(outputBounds: DOMRect, inputBounds: DOMRect, verticalOut: boolean, verticalIn: boolean): string {
const locations = buildStraightWirePathLocations(outputBounds, inputBounds, verticalOut, verticalIn);
if (locations.length === 0) return "[error]";
if (locations.length === 2) return `M${locations[0].x},${locations[0].y} L${locations[1].x},${locations[1].y}`;
const CORNER_RADIUS = 10;
// Create path with rounded corners
let path = `M${locations[0].x},${locations[0].y}`;
for (let i = 1; i < locations.length - 1; i++) {
const prev = locations[i - 1];
const curr = locations[i];
const next = locations[i + 1];
// Calculate corner points
const isVertical = curr.x === prev.x;
const cornerStart = {
x: curr.x + (isVertical ? 0 : prev.x < curr.x ? -CORNER_RADIUS : CORNER_RADIUS),
y: curr.y + (isVertical ? (prev.y < curr.y ? -CORNER_RADIUS : CORNER_RADIUS) : 0),
};
const cornerEnd = {
x: curr.x + (isVertical ? (next.x < curr.x ? -CORNER_RADIUS : CORNER_RADIUS) : 0),
y: curr.y + (isVertical ? 0 : next.y < curr.y ? -CORNER_RADIUS : CORNER_RADIUS),
};
// Add line to corner start, quadratic curve for corner, then continue to next point
path += ` L${cornerStart.x},${cornerStart.y}`;
path += ` Q${curr.x},${curr.y} ${cornerEnd.x},${cornerEnd.y}`;
}
path += ` L${locations[locations.length - 1].x},${locations[locations.length - 1].y}`;
return path;
}
function buildCurvedWirePathLocations(outputBounds: DOMRect, inputBounds: DOMRect, verticalOut: boolean, verticalIn: boolean): { x: number; y: number }[] {
if (!nodesContainer) return [];
const VERTICAL_WIRE_OVERLAP_ON_SHAPED_CAP = 1;
const containerBounds = nodesContainer.getBoundingClientRect();
const outX = verticalOut ? outputBounds.x + outputBounds.width / 2 : outputBounds.x + outputBounds.width - 1;
const outY = verticalOut ? outputBounds.y + VERTICAL_WIRE_OVERLAP_ON_SHAPED_CAP : outputBounds.y + outputBounds.height / 2;
const outConnectorX = (outX - containerBounds.x) / $nodeGraph.transform.scale;
const outConnectorY = (outY - containerBounds.y) / $nodeGraph.transform.scale;
const inX = verticalIn ? inputBounds.x + inputBounds.width / 2 : inputBounds.x;
const inY = verticalIn ? inputBounds.y + inputBounds.height - VERTICAL_WIRE_OVERLAP_ON_SHAPED_CAP : inputBounds.y + inputBounds.height / 2;
const inConnectorX = (inX - containerBounds.x) / $nodeGraph.transform.scale;
const inConnectorY = (inY - containerBounds.y) / $nodeGraph.transform.scale;
const horizontalGap = Math.abs(outConnectorX - inConnectorX);
const verticalGap = Math.abs(outConnectorY - inConnectorY);
const curveLength = 24;
const curveFalloffRate = curveLength * Math.PI * 2;
const horizontalCurveAmount = -(2 ** ((-10 * horizontalGap) / curveFalloffRate)) + 1;
const verticalCurveAmount = -(2 ** ((-10 * verticalGap) / curveFalloffRate)) + 1;
const horizontalCurve = horizontalCurveAmount * curveLength;
const verticalCurve = verticalCurveAmount * curveLength;
return [
{ x: outConnectorX, y: outConnectorY },
{ x: verticalOut ? outConnectorX : outConnectorX + horizontalCurve, y: verticalOut ? outConnectorY - verticalCurve : outConnectorY },
{ x: verticalIn ? inConnectorX : inConnectorX - horizontalCurve, y: verticalIn ? inConnectorY + verticalCurve : inConnectorY },
{ x: inConnectorX, y: inConnectorY },
];
}
function buildCurvedWirePathString(outputBounds: DOMRect, inputBounds: DOMRect, verticalOut: boolean, verticalIn: boolean): string {
const locations = buildCurvedWirePathLocations(outputBounds, inputBounds, verticalOut, verticalIn);
if (locations.length === 0) return "[error]";
const SMOOTHING = 0.5;
const delta01 = { x: (locations[1].x - locations[0].x) * SMOOTHING, y: (locations[1].y - locations[0].y) * SMOOTHING };
const delta23 = { x: (locations[3].x - locations[2].x) * SMOOTHING, y: (locations[3].y - locations[2].y) * SMOOTHING };
return `
M${locations[0].x},${locations[0].y}
L${locations[1].x},${locations[1].y}
C${locations[1].x + delta01.x},${locations[1].y + delta01.y}
${locations[2].x - delta23.x},${locations[2].y - delta23.y}
${locations[2].x},${locations[2].y}
L${locations[3].x},${locations[3].y}
`
.split("\n")
.map((line) => line.trim())
.join(" ");
}
function toggleLayerDisplay(displayAsLayer: boolean, toggleId: bigint) { function toggleLayerDisplay(displayAsLayer: boolean, toggleId: bigint) {
let node = $nodeGraph.nodes.get(toggleId); let node = $nodeGraph.nodes.get(toggleId);
if (node) editor.handle.setToNodeOrLayer(node.id, displayAsLayer); if (node) editor.handle.setToNodeOrLayer(node.id, displayAsLayer);
@ -719,19 +313,21 @@
</div> </div>
{/if} {/if}
<!-- Node connection wires --> <!-- Thick vertical layer connection wires -->
<div class="wires" style:transform-origin={`0 0`} style:transform={`translate(${$nodeGraph.transform.x}px, ${$nodeGraph.transform.y}px) scale(${$nodeGraph.transform.scale})`}> <div class="wires" style:transform-origin={`0 0`} style:transform={`translate(${$nodeGraph.transform.x}px, ${$nodeGraph.transform.y}px) scale(${$nodeGraph.transform.scale})`}>
<svg> <svg>
{#each wirePaths as { pathString, dataType, thick, dashed }} {#each $nodeGraph.wires.values() as map}
{#if thick} {#each map.values() as { pathString, dataType, thick, dashed }}
<path {#if thick}
d={pathString} <path
style:--data-line-width={`${thick ? 8 : 2}px`} d={pathString}
style:--data-color={`var(--color-data-${dataType.toLowerCase()})`} style:--data-line-width={"8px"}
style:--data-color-dim={`var(--color-data-${dataType.toLowerCase()}-dim)`} style:--data-color={`var(--color-data-${dataType.toLowerCase()})`}
style:--data-dasharray={`3,${dashed ? 2 : 0}`} style:--data-color-dim={`var(--color-data-${dataType.toLowerCase()}-dim)`}
/> style:--data-dasharray={`3,${dashed ? 2 : 0}`}
{/if} />
{/if}
{/each}
{/each} {/each}
</svg> </svg>
</div> </div>
@ -749,7 +345,6 @@
style:--data-color-dim={`var(--color-data-${outputMetadata.dataType.toLowerCase()}-dim)`} style:--data-color-dim={`var(--color-data-${outputMetadata.dataType.toLowerCase()}-dim)`}
style:--offset-left={position.x / 24} style:--offset-left={position.x / 24}
style:--offset-top={position.y / 24} style:--offset-top={position.y / 24}
bind:this={outputs[0][index]}
> >
<title>{`${dataTypeTooltip(outputMetadata)}\n\n${outputConnectedToText(outputMetadata)}`}</title> <title>{`${dataTypeTooltip(outputMetadata)}\n\n${outputConnectedToText(outputMetadata)}`}</title>
{#if outputMetadata.connectedTo !== undefined} {#if outputMetadata.connectedTo !== undefined}
@ -823,7 +418,6 @@
style:--data-color-dim={`var(--color-data-${inputMetadata.dataType.toLowerCase()}-dim)`} style:--data-color-dim={`var(--color-data-${inputMetadata.dataType.toLowerCase()}-dim)`}
style:--offset-left={position.x / 24} style:--offset-left={position.x / 24}
style:--offset-top={position.y / 24} style:--offset-top={position.y / 24}
bind:this={inputs[0][index]}
> >
<title>{`${dataTypeTooltip(inputMetadata)}\n\n${inputConnectedToText(inputMetadata)}`}</title> <title>{`${dataTypeTooltip(inputMetadata)}\n\n${inputConnectedToText(inputMetadata)}`}</title>
{#if inputMetadata.connectedTo !== undefined} {#if inputMetadata.connectedTo !== undefined}
@ -887,14 +481,11 @@
</div> </div>
<!-- Layers and nodes --> <!-- Layers and nodes -->
<div <div class="layers-and-nodes" style:transform-origin={`0 0`} style:transform={`translate(${$nodeGraph.transform.x}px, ${$nodeGraph.transform.y}px) scale(${$nodeGraph.transform.scale})`}>
class="layers-and-nodes"
style:transform-origin={`0 0`}
style:transform={`translate(${$nodeGraph.transform.x}px, ${$nodeGraph.transform.y}px) scale(${$nodeGraph.transform.scale})`}
bind:this={nodesContainer}
>
<!-- Layers --> <!-- Layers -->
{#each Array.from($nodeGraph.nodes.values()).flatMap((node, nodeIndex) => (node.isLayer ? [{ node, nodeIndex }] : [])) as { node, nodeIndex } (nodeIndex)} {#each Array.from($nodeGraph.nodes)
.filter(([nodeId, node]) => node.isLayer && $nodeGraph.visibleNodes.has(nodeId))
.map(([_, node], nodeIndex) => ({ node, nodeIndex })) as { node, nodeIndex } (nodeIndex)}
{@const clipPathId = String(Math.random()).substring(2)} {@const clipPathId = String(Math.random()).substring(2)}
{@const stackDataInput = node.exposedInputs[0]} {@const stackDataInput = node.exposedInputs[0]}
{@const layerAreaWidth = $nodeGraph.layerWidths.get(node.id) || 8} {@const layerAreaWidth = $nodeGraph.layerWidths.get(node.id) || 8}
@ -916,7 +507,6 @@
style:--node-chain-area-left-extension={layerChainWidth !== 0 ? layerChainWidth + 0.5 : 0} style:--node-chain-area-left-extension={layerChainWidth !== 0 ? layerChainWidth + 0.5 : 0}
title={`${node.displayName}\n\n${description || ""}`.trim() + (editor.handle.inDevelopmentMode() ? `\n\nNode ID: ${node.id}` : "")} title={`${node.displayName}\n\n${description || ""}`.trim() + (editor.handle.inDevelopmentMode() ? `\n\nNode ID: ${node.id}` : "")}
data-node={node.id} data-node={node.id}
bind:this={nodeElements[nodeIndex]}
> >
{#if node.errors} {#if node.errors}
<span class="node-error faded" transition:fade={FADE_TRANSITION} title="" data-node-error>{node.errors}</span> <span class="node-error faded" transition:fade={FADE_TRANSITION} title="" data-node-error>{node.errors}</span>
@ -936,7 +526,6 @@
data-datatype={node.primaryOutput.dataType} data-datatype={node.primaryOutput.dataType}
style:--data-color={`var(--color-data-${node.primaryOutput.dataType.toLowerCase()})`} style:--data-color={`var(--color-data-${node.primaryOutput.dataType.toLowerCase()})`}
style:--data-color-dim={`var(--color-data-${node.primaryOutput.dataType.toLowerCase()}-dim)`} style:--data-color-dim={`var(--color-data-${node.primaryOutput.dataType.toLowerCase()}-dim)`}
bind:this={outputs[nodeIndex + 1][0]}
> >
<title>{`${dataTypeTooltip(node.primaryOutput)}\n\n${outputConnectedToText(node.primaryOutput)}`}</title> <title>{`${dataTypeTooltip(node.primaryOutput)}\n\n${outputConnectedToText(node.primaryOutput)}`}</title>
{#if node.primaryOutput.connectedTo.length > 0} {#if node.primaryOutput.connectedTo.length > 0}
@ -958,7 +547,6 @@
data-datatype={node.primaryInput?.dataType} data-datatype={node.primaryInput?.dataType}
style:--data-color={`var(--color-data-${(node.primaryInput?.dataType || "General").toLowerCase()})`} style:--data-color={`var(--color-data-${(node.primaryInput?.dataType || "General").toLowerCase()})`}
style:--data-color-dim={`var(--color-data-${(node.primaryInput?.dataType || "General").toLowerCase()}-dim)`} style:--data-color-dim={`var(--color-data-${(node.primaryInput?.dataType || "General").toLowerCase()}-dim)`}
bind:this={inputs[nodeIndex + 1][0]}
> >
{#if node.primaryInput} {#if node.primaryInput}
<title>{`${dataTypeTooltip(node.primaryInput)}\n\n${validTypesText(node.primaryInput)}\n\n${inputConnectedToText(node.primaryInput)}`}</title> <title>{`${dataTypeTooltip(node.primaryInput)}\n\n${validTypesText(node.primaryInput)}\n\n${inputConnectedToText(node.primaryInput)}`}</title>
@ -984,7 +572,6 @@
data-datatype={stackDataInput.dataType} data-datatype={stackDataInput.dataType}
style:--data-color={`var(--color-data-${stackDataInput.dataType.toLowerCase()})`} style:--data-color={`var(--color-data-${stackDataInput.dataType.toLowerCase()})`}
style:--data-color-dim={`var(--color-data-${stackDataInput.dataType.toLowerCase()}-dim)`} style:--data-color-dim={`var(--color-data-${stackDataInput.dataType.toLowerCase()}-dim)`}
bind:this={inputs[nodeIndex + 1][1]}
> >
<title>{`${dataTypeTooltip(stackDataInput)}\n\n${validTypesText(stackDataInput)}\n\n${inputConnectedToText(stackDataInput)}`}</title> <title>{`${dataTypeTooltip(stackDataInput)}\n\n${validTypesText(stackDataInput)}\n\n${inputConnectedToText(stackDataInput)}`}</title>
{#if stackDataInput.connectedTo !== undefined} {#if stackDataInput.connectedTo !== undefined}
@ -1025,22 +612,35 @@
<!-- Node connection wires --> <!-- Node connection wires -->
<div class="wires"> <div class="wires">
<svg> <svg>
{#each wirePaths as { pathString, dataType, thick, dashed }}\ {#each $nodeGraph.wires.values() as map}
{#if !thick} {#each map.values() as { pathString, dataType, thick, dashed }}
<path {#if !thick}
d={pathString} <path
style:--data-line-width={`${thick ? 8 : 2}px`} d={pathString}
style:--data-color={`var(--color-data-${dataType.toLowerCase()})`} style:--data-line-width={"2px"}
style:--data-color-dim={`var(--color-data-${dataType.toLowerCase()}-dim)`} style:--data-color={`var(--color-data-${dataType.toLowerCase()})`}
style:--data-dasharray={dashed ? "4" : undefined} style:--data-color-dim={`var(--color-data-${dataType.toLowerCase()}-dim)`}
/> style:--data-dasharray={`3,${dashed ? 2 : 0}`}
{/if} />
{/if}
{/each}
{/each} {/each}
{#if $nodeGraph.wirePathInProgress}
<path
d={$nodeGraph.wirePathInProgress?.pathString}
style:--data-line-width={`${$nodeGraph.wirePathInProgress.thick ? 8 : 2}px`}
style:--data-color={`var(--color-data-${$nodeGraph.wirePathInProgress.dataType.toLowerCase()})`}
style:--data-color-dim={`var(--color-data-${$nodeGraph.wirePathInProgress.dataType.toLowerCase()}-dim)`}
style:--data-dasharray={`3,${$nodeGraph.wirePathInProgress.dashed ? 2 : 0}`}
/>
{/if}
</svg> </svg>
</div> </div>
<!-- Nodes --> <!-- Nodes -->
{#each Array.from($nodeGraph.nodes.values()).flatMap((node, nodeIndex) => (node.isLayer ? [] : [{ node, nodeIndex }])) as { node, nodeIndex } (nodeIndex)} {#each Array.from($nodeGraph.nodes)
.filter(([nodeId, node]) => !node.isLayer && $nodeGraph.visibleNodes.has(nodeId))
.map(([_, node], nodeIndex) => ({ node, nodeIndex })) as { node, nodeIndex } (nodeIndex)}
{@const exposedInputsOutputs = zipWithUndefined(node.exposedInputs, node.exposedOutputs)} {@const exposedInputsOutputs = zipWithUndefined(node.exposedInputs, node.exposedOutputs)}
{@const clipPathId = String(Math.random()).substring(2)} {@const clipPathId = String(Math.random()).substring(2)}
{@const description = (node.reference && $nodeGraph.nodeDescriptions.get(node.reference)) || undefined} {@const description = (node.reference && $nodeGraph.nodeDescriptions.get(node.reference)) || undefined}
@ -1056,7 +656,6 @@
style:--data-color-dim={`var(--color-data-${(node.primaryOutput?.dataType || "General").toLowerCase()}-dim)`} style:--data-color-dim={`var(--color-data-${(node.primaryOutput?.dataType || "General").toLowerCase()}-dim)`}
title={`${node.displayName}\n\n${description || ""}`.trim() + (editor.handle.inDevelopmentMode() ? `\n\nNode ID: ${node.id}` : "")} title={`${node.displayName}\n\n${description || ""}`.trim() + (editor.handle.inDevelopmentMode() ? `\n\nNode ID: ${node.id}` : "")}
data-node={node.id} data-node={node.id}
bind:this={nodeElements[nodeIndex]}
> >
{#if node.errors} {#if node.errors}
<span class="node-error faded" transition:fade={FADE_TRANSITION} title="" data-node-error>{node.errors}</span> <span class="node-error faded" transition:fade={FADE_TRANSITION} title="" data-node-error>{node.errors}</span>
@ -1091,7 +690,6 @@
data-datatype={node.primaryInput?.dataType} data-datatype={node.primaryInput?.dataType}
style:--data-color={`var(--color-data-${node.primaryInput.dataType.toLowerCase()})`} style:--data-color={`var(--color-data-${node.primaryInput.dataType.toLowerCase()})`}
style:--data-color-dim={`var(--color-data-${node.primaryInput.dataType.toLowerCase()}-dim)`} style:--data-color-dim={`var(--color-data-${node.primaryInput.dataType.toLowerCase()}-dim)`}
bind:this={inputs[nodeIndex + 1][0]}
> >
<title>{`${dataTypeTooltip(node.primaryInput)}\n\n${validTypesText(node.primaryInput)}\n\n${inputConnectedToText(node.primaryInput)}`}</title> <title>{`${dataTypeTooltip(node.primaryInput)}\n\n${validTypesText(node.primaryInput)}\n\n${inputConnectedToText(node.primaryInput)}`}</title>
{#if node.primaryInput.connectedTo !== undefined} {#if node.primaryInput.connectedTo !== undefined}
@ -1111,7 +709,6 @@
data-datatype={secondary.dataType} data-datatype={secondary.dataType}
style:--data-color={`var(--color-data-${secondary.dataType.toLowerCase()})`} style:--data-color={`var(--color-data-${secondary.dataType.toLowerCase()})`}
style:--data-color-dim={`var(--color-data-${secondary.dataType.toLowerCase()}-dim)`} style:--data-color-dim={`var(--color-data-${secondary.dataType.toLowerCase()}-dim)`}
bind:this={inputs[nodeIndex + 1][index + (node.primaryInput ? 1 : 0)]}
> >
<title>{`${dataTypeTooltip(secondary)}\n\n${validTypesText(secondary)}\n\n${inputConnectedToText(secondary)}`}</title> <title>{`${dataTypeTooltip(secondary)}\n\n${validTypesText(secondary)}\n\n${inputConnectedToText(secondary)}`}</title>
{#if secondary.connectedTo !== undefined} {#if secondary.connectedTo !== undefined}
@ -1134,7 +731,6 @@
data-datatype={node.primaryOutput.dataType} data-datatype={node.primaryOutput.dataType}
style:--data-color={`var(--color-data-${node.primaryOutput.dataType.toLowerCase()})`} style:--data-color={`var(--color-data-${node.primaryOutput.dataType.toLowerCase()})`}
style:--data-color-dim={`var(--color-data-${node.primaryOutput.dataType.toLowerCase()}-dim)`} style:--data-color-dim={`var(--color-data-${node.primaryOutput.dataType.toLowerCase()}-dim)`}
bind:this={outputs[nodeIndex + 1][0]}
> >
<title>{`${dataTypeTooltip(node.primaryOutput)}\n\n${outputConnectedToText(node.primaryOutput)}`}</title> <title>{`${dataTypeTooltip(node.primaryOutput)}\n\n${outputConnectedToText(node.primaryOutput)}`}</title>
{#if node.primaryOutput.connectedTo !== undefined} {#if node.primaryOutput.connectedTo !== undefined}
@ -1144,7 +740,7 @@
{/if} {/if}
</svg> </svg>
{/if} {/if}
{#each node.exposedOutputs as secondary, outputIndex} {#each node.exposedOutputs as secondary}
<svg <svg
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 8 8" viewBox="0 0 8 8"
@ -1153,7 +749,6 @@
data-datatype={secondary.dataType} data-datatype={secondary.dataType}
style:--data-color={`var(--color-data-${secondary.dataType.toLowerCase()})`} style:--data-color={`var(--color-data-${secondary.dataType.toLowerCase()})`}
style:--data-color-dim={`var(--color-data-${secondary.dataType.toLowerCase()}-dim)`} style:--data-color-dim={`var(--color-data-${secondary.dataType.toLowerCase()}-dim)`}
bind:this={outputs[nodeIndex + 1][outputIndex + (node.primaryOutput ? 1 : 0)]}
> >
<title>{`${dataTypeTooltip(secondary)}\n\n${outputConnectedToText(secondary)}`}</title> <title>{`${dataTypeTooltip(secondary)}\n\n${outputConnectedToText(secondary)}`}</title>
{#if secondary.connectedTo !== undefined} {#if secondary.connectedTo !== undefined}

View file

@ -96,16 +96,21 @@ export class UpdateLayerWidths extends JsMessage {
readonly hasLeftInputWire!: Map<bigint, boolean>; readonly hasLeftInputWire!: Map<bigint, boolean>;
} }
export class UpdateNodeGraph extends JsMessage { export class UpdateNodeGraphNodes extends JsMessage {
@Type(() => FrontendNode) @Type(() => FrontendNode)
readonly nodes!: FrontendNode[]; readonly nodes!: FrontendNode[];
@Type(() => FrontendNodeWire)
readonly wires!: FrontendNodeWire[];
readonly wiresDirectNotGridAligned!: boolean;
} }
export class UpdateVisibleNodes extends JsMessage {
readonly nodes!: bigint[];
}
export class UpdateNodeGraphWires extends JsMessage {
readonly wires!: WireUpdate[];
}
export class ClearAllNodeGraphWires extends JsMessage {}
export class UpdateNodeGraphTransform extends JsMessage { export class UpdateNodeGraphTransform extends JsMessage {
readonly transform!: NodeGraphTransform; readonly transform!: NodeGraphTransform;
} }
@ -219,7 +224,7 @@ export class FrontendGraphInput {
readonly description!: string; readonly description!: string;
readonly resolvedType!: string | undefined; readonly resolvedType!: string;
readonly validTypes!: string[]; readonly validTypes!: string[];
@ -252,7 +257,7 @@ export class FrontendGraphOutput {
readonly description!: string; readonly description!: string;
readonly resolvedType!: string | undefined; readonly resolvedType!: string;
@CreateInputConnectorArray @CreateInputConnectorArray
connectedTo!: Node[]; connectedTo!: Node[];
@ -297,44 +302,6 @@ export class FrontendNode {
readonly uiOnly!: boolean; readonly uiOnly!: boolean;
} }
const CreateOutputConnector = Transform(({ obj }) => {
if (obj.wireStart.export !== undefined) {
return { index: obj.wireStart.export };
} else if (obj.wireStart.import !== undefined) {
return { index: obj.wireStart.import };
} else {
if (obj.wireStart.node.inputIndex !== undefined) {
return { nodeId: obj.wireStart.node.nodeId, index: obj.wireStart.node.inputIndex };
} else {
return { nodeId: obj.wireStart.node.nodeId, index: obj.wireStart.node.outputIndex };
}
}
});
const CreateInputConnector = Transform(({ obj }) => {
if (obj.wireEnd.export !== undefined) {
return { index: obj.wireEnd.export };
} else if (obj.wireEnd.import !== undefined) {
return { index: obj.wireEnd.import };
} else {
if (obj.wireEnd.node.inputIndex !== undefined) {
return { nodeId: obj.wireEnd.node.nodeId, index: obj.wireEnd.node.inputIndex };
} else {
return { nodeId: obj.wireEnd.node.nodeId, index: obj.wireEnd.node.outputIndex };
}
}
});
export class FrontendNodeWire {
@CreateOutputConnector
readonly wireStart!: Node;
@CreateInputConnector
readonly wireEnd!: Node;
readonly dashed!: boolean;
}
export class FrontendNodeType { export class FrontendNodeType {
readonly name!: string; readonly name!: string;
@ -356,6 +323,12 @@ export class WirePath {
readonly dashed!: boolean; readonly dashed!: boolean;
} }
export class WireUpdate {
readonly id!: bigint;
readonly inputIndex!: number;
readonly wirePathUpdate!: WirePath | undefined;
}
export class IndexedDbDocumentDetails extends DocumentDetails { export class IndexedDbDocumentDetails extends DocumentDetails {
@Transform(({ value }: { value: bigint }) => value.toString()) @Transform(({ value }: { value: bigint }) => value.toString())
id!: string; id!: string;
@ -1645,6 +1618,7 @@ type JSMessageFactory = (data: any, wasm: WebAssembly.Memory, handle: EditorHand
type MessageMaker = typeof JsMessage | JSMessageFactory; type MessageMaker = typeof JsMessage | JSMessageFactory;
export const messageMakers: Record<string, MessageMaker> = { export const messageMakers: Record<string, MessageMaker> = {
ClearAllNodeGraphWires,
DisplayDialog, DisplayDialog,
DisplayDialogDismiss, DisplayDialogDismiss,
DisplayDialogPanic, DisplayDialogPanic,
@ -1700,10 +1674,12 @@ export const messageMakers: Record<string, MessageMaker> = {
UpdateLayerWidths, UpdateLayerWidths,
UpdateMenuBarLayout, UpdateMenuBarLayout,
UpdateMouseCursor, UpdateMouseCursor,
UpdateNodeGraph, UpdateNodeGraphNodes,
UpdateVisibleNodes,
UpdateNodeGraphWires,
UpdateNodeGraphTransform,
UpdateNodeGraphControlBarLayout, UpdateNodeGraphControlBarLayout,
UpdateNodeGraphSelection, UpdateNodeGraphSelection,
UpdateNodeGraphTransform,
UpdateNodeThumbnail, UpdateNodeThumbnail,
UpdateOpenDocumentsList, UpdateOpenDocumentsList,
UpdatePropertyPanelSectionsLayout, UpdatePropertyPanelSectionsLayout,

View file

@ -7,9 +7,9 @@ import {
type FrontendClickTargets, type FrontendClickTargets,
type ContextMenuInformation, type ContextMenuInformation,
type FrontendNode, type FrontendNode,
type FrontendNodeWire as FrontendNodeWire,
type FrontendNodeType, type FrontendNodeType,
type WirePath, type WirePath,
ClearAllNodeGraphWires,
SendUIMetadata, SendUIMetadata,
UpdateBox, UpdateBox,
UpdateClickTargets, UpdateClickTargets,
@ -19,7 +19,9 @@ import {
UpdateExportReorderIndex, UpdateExportReorderIndex,
UpdateImportsExports, UpdateImportsExports,
UpdateLayerWidths, UpdateLayerWidths,
UpdateNodeGraph, UpdateNodeGraphNodes,
UpdateVisibleNodes,
UpdateNodeGraphWires,
UpdateNodeGraphSelection, UpdateNodeGraphSelection,
UpdateNodeGraphTransform, UpdateNodeGraphTransform,
UpdateNodeThumbnail, UpdateNodeThumbnail,
@ -40,8 +42,9 @@ export function createNodeGraphState(editor: Editor) {
addImport: undefined as { x: number; y: number } | undefined, addImport: undefined as { x: number; y: number } | undefined,
addExport: undefined as { x: number; y: number } | undefined, addExport: undefined as { x: number; y: number } | undefined,
nodes: new Map<bigint, FrontendNode>(), nodes: new Map<bigint, FrontendNode>(),
wires: [] as FrontendNodeWire[], visibleNodes: new Set<bigint>(),
wiresDirectNotGridAligned: false, /// The index is the exposed input index. The exports have a first key value of u32::MAX.
wires: new Map<bigint, Map<number, WirePath>>(),
wirePathInProgress: undefined as WirePath | undefined, wirePathInProgress: undefined as WirePath | undefined,
nodeDescriptions: new Map<string, string>(), nodeDescriptions: new Map<string, string>(),
nodeTypes: [] as FrontendNodeType[], nodeTypes: [] as FrontendNodeType[],
@ -114,15 +117,42 @@ export function createNodeGraphState(editor: Editor) {
return state; return state;
}); });
}); });
// TODO: Add a way to only update the nodes that have changed editor.subscriptions.subscribeJsMessage(UpdateNodeGraphNodes, (updateNodeGraphNodes) => {
editor.subscriptions.subscribeJsMessage(UpdateNodeGraph, (updateNodeGraph) => {
update((state) => { update((state) => {
state.nodes.clear(); state.nodes.clear();
updateNodeGraph.nodes.forEach((node) => { updateNodeGraphNodes.nodes.forEach((node) => {
state.nodes.set(node.id, node); state.nodes.set(node.id, node);
}); });
state.wires = updateNodeGraph.wires; return state;
state.wiresDirectNotGridAligned = updateNodeGraph.wiresDirectNotGridAligned; });
});
editor.subscriptions.subscribeJsMessage(UpdateVisibleNodes, (updateVisibleNodes) => {
update((state) => {
state.visibleNodes = new Set<bigint>(updateVisibleNodes.nodes);
return state;
});
});
editor.subscriptions.subscribeJsMessage(UpdateNodeGraphWires, (updateNodeWires) => {
update((state) => {
updateNodeWires.wires.forEach((wireUpdate) => {
let inputMap = state.wires.get(wireUpdate.id);
// If it doesn't exist, create it and set it in the outer map
if (!inputMap) {
inputMap = new Map();
state.wires.set(wireUpdate.id, inputMap);
}
if (wireUpdate.wirePathUpdate !== undefined) {
inputMap.set(wireUpdate.inputIndex, wireUpdate.wirePathUpdate);
} else {
inputMap.delete(wireUpdate.inputIndex);
}
});
return state;
});
});
editor.subscriptions.subscribeJsMessage(ClearAllNodeGraphWires, (_) => {
update((state) => {
state.wires.clear();
return state; return state;
}); });
}); });

View file

@ -604,6 +604,7 @@ impl EditorHandle {
node_id: Some(id), node_id: Some(id),
node_type, node_type,
xy: Some((x / 24, y / 24)), xy: Some((x / 24, y / 24)),
add_transaction: true,
}; };
self.dispatch(message); self.dispatch(message);
} }

View file

@ -67,6 +67,10 @@ impl ClickTarget {
self.bounding_box self.bounding_box
} }
pub fn bounding_box_center(&self) -> Option<DVec2> {
self.bounding_box.map(|bbox| bbox[0] + (bbox[1] - bbox[0]) / 2.)
}
pub fn bounding_box_with_transform(&self, transform: DAffine2) -> Option<[DVec2; 2]> { pub fn bounding_box_with_transform(&self, transform: DAffine2) -> Option<[DVec2; 2]> {
self.bounding_box.map(|[a, b]| [transform.transform_point2(a), transform.transform_point2(b)]) self.bounding_box.map(|[a, b]| [transform.transform_point2(a), transform.transform_point2(b)])
} }

View file

@ -363,17 +363,6 @@ impl NodeInput {
NodeInput::Reflection(_) => false, NodeInput::Reflection(_) => false,
} }
} }
/// Network node inputs in the document network are not displayed, but still exist in the compiled network
pub fn is_exposed_to_frontend(&self, is_document_network: bool) -> bool {
match self {
NodeInput::Node { .. } => true,
NodeInput::Value { exposed, .. } => *exposed,
NodeInput::Network { .. } => !is_document_network,
NodeInput::Inline(_) => false,
NodeInput::Scope(_) => false,
NodeInput::Reflection(_) => false,
}
}
pub fn ty(&self) -> Type { pub fn ty(&self) -> Type {
match self { match self {
@ -1250,24 +1239,28 @@ impl NodeNetwork {
/// Create a [`RecursiveNodeIter`] that iterates over all [`DocumentNode`]s, including ones that are deeply nested. /// Create a [`RecursiveNodeIter`] that iterates over all [`DocumentNode`]s, including ones that are deeply nested.
pub fn recursive_nodes(&self) -> RecursiveNodeIter<'_> { pub fn recursive_nodes(&self) -> RecursiveNodeIter<'_> {
let nodes = self.nodes.iter().collect(); let nodes = self.nodes.iter().map(|(id, node)| (id, node, Vec::new())).collect();
RecursiveNodeIter { nodes } RecursiveNodeIter { nodes }
} }
} }
/// An iterator over all [`DocumentNode`]s, including ones that are deeply nested. /// An iterator over all [`DocumentNode`]s, including ones that are deeply nested.
pub struct RecursiveNodeIter<'a> { pub struct RecursiveNodeIter<'a> {
nodes: Vec<(&'a NodeId, &'a DocumentNode)>, nodes: Vec<(&'a NodeId, &'a DocumentNode, Vec<NodeId>)>,
} }
impl<'a> Iterator for RecursiveNodeIter<'a> { impl<'a> Iterator for RecursiveNodeIter<'a> {
type Item = (&'a NodeId, &'a DocumentNode); type Item = (&'a NodeId, &'a DocumentNode, Vec<NodeId>);
fn next(&mut self) -> Option<Self::Item> { fn next(&mut self) -> Option<Self::Item> {
let node = self.nodes.pop()?; let (current_id, node, path) = self.nodes.pop()?;
if let DocumentNodeImplementation::Network(network) = &node.1.implementation { if let DocumentNodeImplementation::Network(network) = &node.implementation {
self.nodes.extend(network.nodes.iter()); self.nodes.extend(network.nodes.iter().map(|(id, node)| {
let mut nested_path = path.clone();
nested_path.push(*current_id);
(id, node, nested_path)
}));
} }
Some(node) Some((current_id, node, path))
} }
} }