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.
/// In addition, these messages do not change any state in the backend (aside from caches).
const SIDE_EFFECT_FREE_MESSAGES: &[MessageDiscriminant] = &[
MessageDiscriminant::Portfolio(PortfolioMessageDiscriminant::Document(DocumentMessageDiscriminant::NodeGraph(NodeGraphMessageDiscriminant::SendGraph))),
MessageDiscriminant::Portfolio(PortfolioMessageDiscriminant::Document(DocumentMessageDiscriminant::PropertiesPanel(
PropertiesPanelMessageDiscriminant::Refresh,
))),

View file

@ -1,6 +1,6 @@
use crate::consts::{VIEWPORT_ZOOM_WHEEL_RATE, VIEWPORT_ZOOM_WHEEL_RATE_CHANGE};
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::prelude::*;

View file

@ -1,9 +1,10 @@
use super::utility_types::{FrontendDocumentDetails, MouseCursorIcon};
use crate::messages::layout::utility_types::widget_prelude::*;
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::wires::{WirePath, WirePathUpdate};
use crate::messages::prelude::*;
use crate::messages::tool::utility_types::HintData;
use graph_craft::document::NodeId;
@ -250,12 +251,16 @@ pub enum FrontendMessage {
UpdateMouseCursor {
cursor: MouseCursorIcon,
},
UpdateNodeGraph {
UpdateNodeGraphNodes {
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 {
#[serde(rename = "layoutTarget")]
layout_target: LayoutTarget,

View file

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

View file

@ -5,8 +5,8 @@ use super::node_properties::{self, ParameterWidgetsInfo};
use super::utility_types::FrontendNodeType;
use crate::messages::layout::utility_types::widget_prelude::*;
use crate::messages::portfolio::document::utility_types::network_interface::{
DocumentNodeMetadata, DocumentNodePersistentMetadata, NodeNetworkInterface, NodeNetworkMetadata, NodeNetworkPersistentMetadata, NodeTemplate, NodeTypePersistentMetadata, NumberInputSettings,
PropertiesRow, Vec2InputSettings, WidgetOverride,
DocumentNodeMetadata, DocumentNodePersistentMetadata, InputMetadata, NodeNetworkInterface, NodeNetworkMetadata, NodeNetworkPersistentMetadata, NodeTemplate, NodeTypePersistentMetadata,
NumberInputSettings, Vec2InputSettings, WidgetOverride,
};
use crate::messages::portfolio::utility_types::PersistentData;
use crate::messages::prelude::Message;
@ -37,26 +37,14 @@ pub struct NodePropertiesContext<'a> {
impl NodePropertiesContext<'_> {
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 {
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");
return None;
};
widget_override_lambda(*node_id, index, self)
.map(|layout_group| {
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);
})
.map_err(|error| log::error!("Error in widget override lambda: {}", error))
.ok()
} else {
None
@ -105,7 +93,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
..Default::default()
},
persistent_node_metadata: DocumentNodePersistentMetadata {
input_properties: vec![("In", "TODO").into()],
input_metadata: vec![("In", "TODO").into()],
output_names: vec!["Out".to_string()],
..Default::default()
},
@ -126,7 +114,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
..Default::default()
},
persistent_node_metadata: DocumentNodePersistentMetadata {
input_properties: vec![("In", "TODO").into()],
input_metadata: vec![("In", "TODO").into()],
output_names: vec!["Out".to_string()],
..Default::default()
},
@ -223,7 +211,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
},
..Default::default()
}),
input_properties: vec![("Data", "TODO").into()],
input_metadata: vec![("Data", "TODO").into()],
output_names: vec!["Data".to_string()],
..Default::default()
},
@ -285,7 +273,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
..Default::default()
},
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()],
node_type_metadata: NodeTypePersistentMetadata::layer(IVec2::new(0, 0)),
network_metadata: Some(NodeNetworkMetadata {
@ -397,10 +385,10 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
..Default::default()
},
persistent_node_metadata: DocumentNodePersistentMetadata {
input_properties: vec![
input_metadata: vec![
("Artboards", "TODO").into(),
PropertiesRow::with_override("Contents", "TODO", WidgetOverride::Hidden),
PropertiesRow::with_override(
InputMetadata::with_name_description_override("Contents", "TODO", WidgetOverride::Hidden),
InputMetadata::with_name_description_override(
"Location",
"TODO",
WidgetOverride::Vec2(Vec2InputSettings {
@ -410,7 +398,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
..Default::default()
}),
),
PropertiesRow::with_override(
InputMetadata::with_name_description_override(
"Dimensions",
"TODO",
WidgetOverride::Vec2(Vec2InputSettings {
@ -420,7 +408,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
..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(),
],
output_names: vec!["Out".to_string()],
@ -498,7 +486,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
..Default::default()
},
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()],
network_metadata: Some(NodeNetworkMetadata {
persistent_metadata: NodeNetworkPersistentMetadata {
@ -640,7 +628,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
..Default::default()
},
persistent_node_metadata: DocumentNodePersistentMetadata {
input_properties: vec![("In", "TODO").into()],
input_metadata: vec![("In", "TODO").into()],
output_names: vec!["Canvas".to_string()],
network_metadata: Some(NodeNetworkMetadata {
persistent_metadata: NodeNetworkPersistentMetadata {
@ -740,7 +728,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
..Default::default()
},
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()],
network_metadata: Some(NodeNetworkMetadata {
persistent_metadata: NodeNetworkPersistentMetadata {
@ -812,23 +800,23 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
..Default::default()
},
persistent_node_metadata: DocumentNodePersistentMetadata {
input_properties: vec![
input_metadata: vec![
("Spacer", "TODO").into(),
("Clip", "TODO").into(),
("Seed", "TODO").into(),
PropertiesRow::with_override("Scale", "TODO", WidgetOverride::Custom("noise_properties_scale".to_string())),
PropertiesRow::with_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())),
PropertiesRow::with_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())),
PropertiesRow::with_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())),
PropertiesRow::with_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())),
PropertiesRow::with_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())),
PropertiesRow::with_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("Scale", "TODO", WidgetOverride::Custom("noise_properties_scale".to_string())),
InputMetadata::with_name_description_override("Noise Type", "TODO", WidgetOverride::Custom("noise_properties_noise_type".to_string())),
InputMetadata::with_name_description_override("Domain Warp Type", "TODO", WidgetOverride::Custom("noise_properties_domain_warp_type".to_string())),
InputMetadata::with_name_description_override("Domain Warp Amplitude", "TODO", WidgetOverride::Custom("noise_properties_domain_warp_amplitude".to_string())),
InputMetadata::with_name_description_override("Fractal Type", "TODO", WidgetOverride::Custom("noise_properties_fractal_type".to_string())),
InputMetadata::with_name_description_override("Fractal Octaves", "TODO", WidgetOverride::Custom("noise_properties_fractal_octaves".to_string())),
InputMetadata::with_name_description_override("Fractal Lacunarity", "TODO", WidgetOverride::Custom("noise_properties_fractal_lacunarity".to_string())),
InputMetadata::with_name_description_override("Fractal Gain", "TODO", WidgetOverride::Custom("noise_properties_fractal_gain".to_string())),
InputMetadata::with_name_description_override("Fractal Weighted Strength", "TODO", WidgetOverride::Custom("noise_properties_fractal_weighted_strength".to_string())),
InputMetadata::with_name_description_override("Fractal Ping Pong Strength", "TODO", WidgetOverride::Custom("noise_properties_ping_pong_strength".to_string())),
InputMetadata::with_name_description_override("Cellular Distance Function", "TODO", WidgetOverride::Custom("noise_properties_cellular_distance_function".to_string())),
InputMetadata::with_name_description_override("Cellular Return Type", "TODO", WidgetOverride::Custom("noise_properties_cellular_return_type".to_string())),
InputMetadata::with_name_description_override("Cellular Jitter", "TODO", WidgetOverride::Custom("noise_properties_cellular_jitter".to_string())),
],
output_names: vec!["Image".to_string()],
..Default::default()
@ -897,7 +885,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
..Default::default()
},
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()],
has_primary_output: false,
network_metadata: Some(NodeNetworkMetadata {
@ -982,7 +970,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
..Default::default()
},
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()],
has_primary_output: false,
network_metadata: Some(NodeNetworkMetadata {
@ -1053,7 +1041,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
..Default::default()
},
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()],
network_metadata: Some(NodeNetworkMetadata {
persistent_metadata: NodeNetworkPersistentMetadata {
@ -1090,7 +1078,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
..Default::default()
},
persistent_node_metadata: DocumentNodePersistentMetadata {
input_properties: vec![("Image", "TODO").into()],
input_metadata: vec![("Image", "TODO").into()],
output_names: vec!["Image".to_string()],
..Default::default()
},
@ -1109,7 +1097,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
..Default::default()
},
persistent_node_metadata: DocumentNodePersistentMetadata {
input_properties: vec![("Image", "TODO").into()],
input_metadata: vec![("Image", "TODO").into()],
output_names: vec!["Image".to_string()],
..Default::default()
},
@ -1152,7 +1140,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
..Default::default()
},
persistent_node_metadata: DocumentNodePersistentMetadata {
input_properties: vec![("In", "TODO").into()],
input_metadata: vec![("In", "TODO").into()],
output_names: vec!["Storage".to_string()],
network_metadata: Some(NodeNetworkMetadata {
persistent_metadata: NodeNetworkPersistentMetadata {
@ -1231,7 +1219,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
..Default::default()
},
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()],
network_metadata: Some(NodeNetworkMetadata {
persistent_metadata: NodeNetworkPersistentMetadata {
@ -1378,7 +1366,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
..Default::default()
},
persistent_node_metadata: DocumentNodePersistentMetadata {
input_properties: vec![("In", "TODO").into()],
input_metadata: vec![("In", "TODO").into()],
output_names: vec!["Texture".to_string()],
network_metadata: Some(NodeNetworkMetadata {
persistent_metadata: NodeNetworkPersistentMetadata {
@ -1432,7 +1420,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
..Default::default()
},
persistent_node_metadata: DocumentNodePersistentMetadata {
input_properties: vec![("Node", "TODO").into()],
input_metadata: vec![("Node", "TODO").into()],
output_names: vec!["Document Node".to_string()],
..Default::default()
},
@ -1503,7 +1491,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
..Default::default()
},
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()],
network_metadata: Some(NodeNetworkMetadata {
persistent_metadata: NodeNetworkPersistentMetadata {
@ -1563,11 +1551,11 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
..Default::default()
},
persistent_node_metadata: DocumentNodePersistentMetadata {
input_properties: vec![
input_metadata: vec![
("Editor API", "TODO").into(),
PropertiesRow::with_override("Text", "TODO", WidgetOverride::Custom("text_area".to_string())),
PropertiesRow::with_override("Font", "TODO", WidgetOverride::Custom("text_font".to_string())),
PropertiesRow::with_override(
InputMetadata::with_name_description_override("Text", "TODO", WidgetOverride::Custom("text_area".to_string())),
InputMetadata::with_name_description_override("Font", "TODO", WidgetOverride::Custom("text_font".to_string())),
InputMetadata::with_name_description_override(
"Size",
"TODO",
WidgetOverride::Number(NumberInputSettings {
@ -1576,7 +1564,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
..Default::default()
}),
),
PropertiesRow::with_override(
InputMetadata::with_name_description_override(
"Line Height",
"TODO",
WidgetOverride::Number(NumberInputSettings {
@ -1586,7 +1574,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
..Default::default()
}),
),
PropertiesRow::with_override(
InputMetadata::with_name_description_override(
"Character Spacing",
"TODO",
WidgetOverride::Number(NumberInputSettings {
@ -1596,7 +1584,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
..Default::default()
}),
),
PropertiesRow::with_override(
InputMetadata::with_name_description_override(
"Max Width",
"TODO",
WidgetOverride::Number(NumberInputSettings {
@ -1606,7 +1594,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
..Default::default()
}),
),
PropertiesRow::with_override(
InputMetadata::with_name_description_override(
"Max Height",
"TODO",
WidgetOverride::Number(NumberInputSettings {
@ -1616,7 +1604,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
..Default::default()
}),
),
PropertiesRow::with_override(
InputMetadata::with_name_description_override(
"Tilt",
"Faux italic",
WidgetOverride::Number(NumberInputSettings {
@ -1708,9 +1696,9 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
},
..Default::default()
}),
input_properties: vec![
input_metadata: vec![
("Vector Data", "TODO").into(),
PropertiesRow::with_override(
InputMetadata::with_name_description_override(
"Translation",
"TODO",
WidgetOverride::Vec2(Vec2InputSettings {
@ -1720,8 +1708,8 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
..Default::default()
}),
),
PropertiesRow::with_override("Rotation", "TODO", WidgetOverride::Custom("transform_rotation".to_string())),
PropertiesRow::with_override(
InputMetadata::with_name_description_override("Rotation", "TODO", WidgetOverride::Custom("transform_rotation".to_string())),
InputMetadata::with_name_description_override(
"Scale",
"TODO",
WidgetOverride::Vec2(Vec2InputSettings {
@ -1731,8 +1719,8 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
..Default::default()
}),
),
PropertiesRow::with_override("Skew", "TODO", WidgetOverride::Custom("transform_skew".to_string())),
PropertiesRow::with_override("Pivot", "TODO", WidgetOverride::Hidden),
InputMetadata::with_name_description_override("Skew", "TODO", WidgetOverride::Custom("transform_skew".to_string())),
InputMetadata::with_name_description_override("Pivot", "TODO", WidgetOverride::Hidden),
],
output_names: vec!["Data".to_string()],
..Default::default()
@ -1831,7 +1819,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
},
..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()],
..Default::default()
},
@ -1957,10 +1945,10 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
},
..Default::default()
}),
input_properties: vec![
input_metadata: vec![
("Vector Data", "The shape to be resampled and converted into a polyline.").into(),
Into::<PropertiesRow>::into(("Spacing", node_properties::SAMPLE_POLYLINE_TOOLTIP_SPACING)),
PropertiesRow::with_override(
("Spacing", node_properties::SAMPLE_POLYLINE_TOOLTIP_SPACING).into(),
InputMetadata::with_name_description_override(
"Separation",
node_properties::SAMPLE_POLYLINE_TOOLTIP_SEPARATION,
WidgetOverride::Number(NumberInputSettings {
@ -1969,7 +1957,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
..Default::default()
}),
),
PropertiesRow::with_override(
InputMetadata::with_name_description_override(
"Quantity",
node_properties::SAMPLE_POLYLINE_TOOLTIP_QUANTITY,
WidgetOverride::Number(NumberInputSettings {
@ -1978,7 +1966,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
..Default::default()
}),
),
PropertiesRow::with_override(
InputMetadata::with_name_description_override(
"Start Offset",
node_properties::SAMPLE_POLYLINE_TOOLTIP_START_OFFSET,
WidgetOverride::Number(NumberInputSettings {
@ -1987,7 +1975,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
..Default::default()
}),
),
PropertiesRow::with_override(
InputMetadata::with_name_description_override(
"Stop Offset",
node_properties::SAMPLE_POLYLINE_TOOLTIP_STOP_OFFSET,
WidgetOverride::Number(NumberInputSettings {
@ -1996,7 +1984,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
..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()],
..Default::default()
@ -2100,9 +2088,9 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
},
..Default::default()
}),
input_properties: vec![
input_metadata: vec![
("Vector Data", "TODO").into(),
PropertiesRow::with_override(
InputMetadata::with_name_description_override(
"Separation Disk Diameter",
"TODO",
WidgetOverride::Number(NumberInputSettings {
@ -2113,7 +2101,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
..Default::default()
}),
),
PropertiesRow::with_override(
InputMetadata::with_name_description_override(
"Seed",
"TODO",
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(
"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(
"monitor_properties".to_string(),
@ -2175,7 +2163,7 @@ fn static_input_properties() -> InputProperties {
map.insert(
"string".to_string(),
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));
};
let Some(string) = value.as_str() else {
@ -2187,37 +2175,36 @@ fn static_input_properties() -> InputProperties {
map.insert(
"number".to_string(),
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();
if let Some(unit) = context
.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())
{
number_input = number_input.unit(unit);
}
if let Some(min) = context
.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())
{
number_input = number_input.min(min);
}
if let Some(max) = context
.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())
{
number_input = number_input.max(max);
}
if let Some(step) = context
.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())
{
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();
mode
}) {
@ -2225,87 +2212,83 @@ fn static_input_properties() -> InputProperties {
}
if let Some(range_min) = context
.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())
{
number_input = number_input.range_min(Some(range_min));
}
if let Some(range_max) = context
.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())
{
number_input = number_input.range_max(Some(range_max));
}
if let Some(is_integer) = context
.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())
{
number_input = number_input.is_integer(is_integer);
}
let blank_assist = context
.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())
.unwrap_or_else(|| {
log::error!("Could not get blank assist when displaying number input for node {node_id}, index {index}");
true
});
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(
"vec2".to_string(),
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
.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())
.unwrap_or_else(|| {
log::error!("Could not get x for vec2 input");
""
});
})
.to_string();
let y = context
.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())
.unwrap_or_else(|| {
log::error!("Could not get y for vec2 input");
""
});
})
.to_string();
let unit = context
.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())
.unwrap_or_else(|| {
log::error!("Could not get unit for vec2 input");
""
});
})
.to_string();
let min = context
.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());
Ok(vec![node_properties::coordinate_widget(
ParameterWidgetsInfo::new(document_node, node_id, index, input_name, input_description, true),
x,
y,
unit,
min,
)])
Ok(vec![node_properties::coordinate_widget(ParameterWidgetsInfo::new(node_id, index, true, context), &x, &y, &unit, min)])
}),
);
map.insert(
"noise_properties_scale".to_string(),
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 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),
);
Ok(vec![scale.into()])
@ -2314,20 +2297,16 @@ fn static_input_properties() -> InputProperties {
map.insert(
"noise_properties_noise_type".to_string(),
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(document_node, node_id, index, input_name, input_description, true))
.property_row();
let noise_type_row = enum_choice::<NoiseType>().for_socket(ParameterWidgetsInfo::new(node_id, index, true, context)).property_row();
Ok(vec![noise_type_row, LayoutGroup::Row { widgets: Vec::new() }])
}),
);
map.insert(
"noise_properties_domain_warp_type".to_string(),
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 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)
.property_row();
Ok(vec![domain_warp_type])
@ -2336,10 +2315,9 @@ fn static_input_properties() -> InputProperties {
map.insert(
"noise_properties_domain_warp_amplitude".to_string(),
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 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),
);
Ok(vec![domain_warp_amplitude.into(), LayoutGroup::Row { widgets: Vec::new() }])
@ -2348,10 +2326,9 @@ fn static_input_properties() -> InputProperties {
map.insert(
"noise_properties_fractal_type".to_string(),
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 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)
.property_row();
Ok(vec![fractal_type_row])
@ -2360,10 +2337,9 @@ fn static_input_properties() -> InputProperties {
map.insert(
"noise_properties_fractal_octaves".to_string(),
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_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()
.mode_range()
.min(1.)
@ -2378,10 +2354,9 @@ fn static_input_properties() -> InputProperties {
map.insert(
"noise_properties_fractal_lacunarity".to_string(),
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_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()
.mode_range()
.min(0.)
@ -2394,10 +2369,9 @@ fn static_input_properties() -> InputProperties {
map.insert(
"noise_properties_fractal_gain".to_string(),
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_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()
.mode_range()
.min(0.)
@ -2410,10 +2384,9 @@ fn static_input_properties() -> InputProperties {
map.insert(
"noise_properties_fractal_weighted_strength".to_string(),
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_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()
.mode_range()
.min(0.)
@ -2426,10 +2399,9 @@ fn static_input_properties() -> InputProperties {
map.insert(
"noise_properties_ping_pong_strength".to_string(),
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_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()
.mode_range()
.min(0.)
@ -2442,10 +2414,9 @@ fn static_input_properties() -> InputProperties {
map.insert(
"noise_properties_cellular_distance_function".to_string(),
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 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)
.property_row();
Ok(vec![cellular_distance_function_row])
@ -2454,10 +2425,9 @@ fn static_input_properties() -> InputProperties {
map.insert(
"noise_properties_cellular_return_type".to_string(),
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 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)
.property_row();
Ok(vec![cellular_return_type])
@ -2466,10 +2436,9 @@ fn static_input_properties() -> InputProperties {
map.insert(
"noise_properties_cellular_jitter".to_string(),
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 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()
.mode_range()
.range_min(Some(0.))
@ -2482,7 +2451,7 @@ fn static_input_properties() -> InputProperties {
map.insert(
"brightness".to_string(),
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
.inputs
.iter()
@ -2493,7 +2462,7 @@ fn static_input_properties() -> InputProperties {
.unwrap_or(false);
let (b_min, b_max) = if is_use_classic { (-100., 100.) } else { (-100., 150.) };
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),
);
Ok(vec![brightness.into()])
@ -2502,7 +2471,8 @@ fn static_input_properties() -> InputProperties {
map.insert(
"contrast".to_string(),
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
.inputs
.iter()
@ -2513,7 +2483,7 @@ fn static_input_properties() -> InputProperties {
.unwrap_or(false);
let (c_min, c_max) = if is_use_classic { (-100., 100.) } else { (-50., 100.) };
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),
);
Ok(vec![contrast.into()])
@ -2522,21 +2492,16 @@ fn static_input_properties() -> InputProperties {
map.insert(
"assign_colors_gradient".to_string(),
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(document_node, node_id, index, input_name, input_description, true),
ColorInput::default().allow_none(false),
);
let gradient_row = node_properties::color_widget(ParameterWidgetsInfo::new(node_id, index, true, context), ColorInput::default().allow_none(false));
Ok(vec![gradient_row])
}),
);
map.insert(
"assign_colors_seed".to_string(),
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 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),
);
Ok(vec![seed_row.into()])
@ -2545,10 +2510,9 @@ fn static_input_properties() -> InputProperties {
map.insert(
"assign_colors_repeat_every".to_string(),
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 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),
);
Ok(vec![repeat_every_row.into()])
@ -2557,33 +2521,24 @@ fn static_input_properties() -> InputProperties {
map.insert(
"mask_stencil".to_string(),
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(document_node, node_id, index, input_name, input_description, true), ColorInput::default());
let mask = node_properties::color_widget(ParameterWidgetsInfo::new(node_id, index, true, context), ColorInput::default());
Ok(vec![mask])
}),
);
map.insert(
"spline_input".to_string(),
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 {
widgets: node_properties::array_of_coordinates_widget(
ParameterWidgetsInfo::new(document_node, node_id, index, input_name, input_description, true),
TextInput::default().centered(true),
),
widgets: node_properties::array_of_coordinates_widget(ParameterWidgetsInfo::new(node_id, index, true, context), TextInput::default().centered(true)),
}])
}),
);
map.insert(
"transform_rotation".to_string(),
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(document_node, node_id, index, input_name, input_description, true),
super::utility_types::FrontendGraphDataType::Number,
);
let mut widgets = node_properties::start_widgets(ParameterWidgetsInfo::new(node_id, index, true, context));
let document_node = node_properties::get_document_node(node_id, context)?;
let Some(input) = document_node.inputs.get(index) else {
return Err("Input not found in transform rotation input override".to_string());
};
@ -2612,13 +2567,9 @@ fn static_input_properties() -> InputProperties {
map.insert(
"transform_skew".to_string(),
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(document_node, node_id, index, input_name, input_description, true),
super::utility_types::FrontendGraphDataType::Number,
);
let mut widgets = node_properties::start_widgets(ParameterWidgetsInfo::new(node_id, index, true, context));
let document_node = node_properties::get_document_node(node_id, context)?;
let Some(input) = document_node.inputs.get(index) else {
return Err("Input not found in transform skew input override".to_string());
};
@ -2660,17 +2611,15 @@ fn static_input_properties() -> InputProperties {
map.insert(
"text_area".to_string(),
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 {
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(
"text_font".to_string(),
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(document_node, node_id, index, input_name, input_description, true));
let (font, style) = node_properties::font_inputs(ParameterWidgetsInfo::new(node_id, index, true, context));
let mut result = vec![LayoutGroup::Row { widgets: font }];
if let Some(style) = style {
result.push(LayoutGroup::Row { widgets: style });
@ -2681,9 +2630,8 @@ fn static_input_properties() -> InputProperties {
map.insert(
"artboard_background".to_string(),
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(
ParameterWidgetsInfo::new(document_node, node_id, index, input_name, input_description, true),
ParameterWidgetsInfo::new(node_id, index, true, context),
ColorInput::default().allow_none(false),
)])
}),
@ -2724,7 +2672,7 @@ pub fn collect_node_types() -> Vec<FrontendNodeType> {
// Extract input types (already creates owned Strings)
let input_types = implementations
.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>>()
.into_iter()
.collect::<Vec<String>>();
@ -2830,7 +2778,7 @@ impl DocumentNodeDefinition {
log::error!("Path is not valid for network");
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
let mut current_path = path.clone();
@ -2849,7 +2797,7 @@ impl DocumentNodeDefinition {
} else {
// Base case
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 {
for sub_node_id in node_template_network.nodes.keys().cloned().collect::<Vec<_>>() {
populate_input_properties(node_template, vec![sub_node_id]);
@ -2861,6 +2809,10 @@ impl DocumentNodeDefinition {
// Set the reference to the node definition
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
}

View file

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

View file

@ -33,6 +33,7 @@ pub enum NodeGraphMessage {
node_id: Option<NodeId>,
node_type: String,
xy: Option<(i32, i32)>,
add_transaction: bool,
},
CreateWire {
output_connector: OutputConnector,
@ -123,6 +124,9 @@ pub enum NodeGraphMessage {
},
SendClickTargets,
EndSendClickTargets,
UnloadWires,
SendWires,
UpdateVisibleNodes,
SendGraph,
SetGridAlignedEdges,
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 crate::consts::GRID_SIZE;
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,
};
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::tool::common_functionality::auto_panning::AutoPanning;
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>,
/// The start of the dragged line (cannot be moved), stored in node graph coordinates
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
pub wire_in_progress_to_connector: Option<DVec2>,
/// State for the context menu popups.
@ -77,12 +79,16 @@ pub struct NodeGraphMessageHandler {
auto_panning: AutoPanning,
/// The node to preview on mouse up if alt-clicked
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>,
// The index of the export that is being moved
/// The index of the export that is being moved
reordering_export: Option<usize>,
// The end index of the moved port
/// The end index of the moved port
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.
@ -175,7 +181,12 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
responses.add(PropertiesPanelMessage::Refresh);
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 {
(x, y)
} 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();
self.context_menu = None;
responses.add(DocumentMessage::AddTransaction);
if add_transaction {
responses.add(DocumentMessage::AddTransaction);
}
responses.add(NodeGraphMessage::InsertNode {
node_id,
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
if let Some((input_index, _)) = node_template
.document_node
.inputs
.iter()
.enumerate()
.find(|(_, input)| input.is_exposed_to_frontend(selection_network_path.is_empty()))
{
if let Some((input_index, _)) = node_template.document_node.inputs.iter().enumerate().find(|(_, input)| input.is_exposed()) {
responses.add(NodeGraphMessage::CreateWire {
output_connector: *output_connector,
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_type = FrontendGraphDataType::General;
self.wire_in_progress_to_connector = None;
}
responses.add(FrontendMessage::UpdateWirePathInProgress { wire_path: None });
@ -367,9 +376,14 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
responses.add(DocumentMessage::CommitTransaction);
// Update the graph UI and re-render
responses.add(PropertiesPanelMessage::Refresh);
responses.add(NodeGraphMessage::SendGraph);
responses.add(NodeGraphMessage::RunDocumentGraph);
if graph_view_overlay_open {
responses.add(PropertiesPanelMessage::Refresh);
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 } => {
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
if self.wire_in_progress_from_connector.is_some() {
self.wire_in_progress_from_connector = None;
self.wire_in_progress_type = FrontendGraphDataType::General;
self.wire_in_progress_to_connector = None;
responses.add(DocumentMessage::AbortTransaction);
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() {
self.context_menu = None;
self.wire_in_progress_from_connector = None;
self.wire_in_progress_type = FrontendGraphDataType::General;
self.wire_in_progress_to_connector = None;
responses.add(FrontendMessage::UpdateContextMenuInformation {
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 };
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;
}
@ -749,6 +766,15 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
self.initial_disconnecting = false;
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);
return;
}
@ -895,9 +921,18 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
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 {
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),
data_type: FrontendGraphDataType::General,
path_string,
data_type: self.wire_in_progress_type,
thick: false,
dashed: false,
};
@ -941,7 +976,7 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
self.update_node_graph_hints(responses);
} else if self.reordering_import.is_some() {
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;
};
// 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 });
} else if self.reordering_export.is_some() {
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;
};
// 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
let compatible_type = output_connector.and_then(|output_connector| {
output_connector.node_id().and_then(|node_id| {
let output_index = output_connector.index();
// 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
output_types.get(output_index).and_then(|type_option| type_option.as_ref()).map(|(output_type, _)| {
// Create a search term based on the type
format!("type:{}", output_type.clone().nested_type())
})
match type_source {
TypeSource::RandomProtonodeImplementation | TypeSource::Error(_) => None,
_ => Some(format!("type:{}", output_type.nested_type())),
}
})
});
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
.outward_wires(selection_network_path)
.is_some_and(|outward_wires| outward_wires.get(&OutputConnector::node(selected_node_id, 0)).is_some_and(|outward_wires| !outward_wires.is_empty()));
let 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 primary_input_is_value = selected_node.inputs.first().is_some_and(|first_input| first_input.as_value().is_some());
// Check that neither the primary input or output of the selected node are already connected.
if !has_primary_output_connection && primary_input_is_value {
if !has_primary_output_connection {
let Some(network) = network_interface.nested_network(selection_network_path) else {
return;
};
let Some(selected_node) = network.nodes.get(&selected_node_id) else {
return;
};
// 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 {
log::error!("Could not get bounding box for node: {selected_node_id}");
return;
};
// TODO: Cache all wire locations if this is a performance issue
let overlapping_wires = Self::collect_wires(network_interface, selection_network_path)
.into_iter()
.filter(|frontend_wire| {
// Prevent inserting on a link that is connected upstream to the selected node
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 mut wires_to_check = network_interface.node_graph_input_connectors(selection_network_path).into_iter().collect::<HashSet<_>>();
// Prevent inserting on a link that is connected upstream to the selected node
for upstream_node in network_interface.upstream_flow_back_from_nodes(vec![selected_node_id], selection_network_path, network_interface::FlowType::UpstreamFlow) {
for input_index in 0..network_interface.number_of_inputs(&upstream_node, selection_network_path) {
wires_to_check.remove(&InputConnector::node(upstream_node, input_index));
}
}
let overlapping_wires = wires_to_check
.into_iter()
.filter_map(|input| {
// Prevent inserting a layer into a chain
if network_interface.is_layer(&selected_node_id, selection_network_path)
&& frontend_wire
.wire_start
.node_id()
.is_some_and(|wire_start_id| network_interface.is_chain(&wire_start_id, selection_network_path))
&& input.node_id().is_some_and(|input_node_id| network_interface.is_chain(&input_node_id, selection_network_path))
{
return false;
return None;
}
let Some(input_position) = network_interface.input_position(&frontend_wire.wire_end, selection_network_path) else {
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)
let (wire, is_stack) = network_interface.vector_wire_from_input(&input, preferences.graph_wire_style, selection_network_path)?;
wire.rectangle_intersections_exist(bounding_box[0], bounding_box[1]).then_some((input, is_stack))
})
.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
let mut node_wires = Vec::new();
let mut stack_wires = Vec::new();
for wire in overlapping_wires {
if is_stack_wire(&wire) { stack_wires.push(wire) } else { node_wires.push(wire) }
for (overlapping_wire_input, is_stack) in overlapping_wires {
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) {
@ -1234,29 +1216,13 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
None
};
if let Some(overlapping_wire) = overlapping_wire {
let Some(network) = network_interface.nested_network(selection_network_path) else {
return;
};
// Ensure connection is to first visible input of selected node. If it does not have an input then do not connect
if let Some((selected_node_input_index, _)) = network
.nodes
.get(&selected_node_id)
.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);
}
responses.add(NodeGraphMessage::InsertNodeBetween {
node_id: selected_node_id,
input_connector: *overlapping_wire,
insert_node_input_index: selected_node_input_connect_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.box_selection_start = None;
self.wire_in_progress_from_connector = None;
self.wire_in_progress_type = FrontendGraphDataType::General;
self.wire_in_progress_to_connector = None;
self.reordering_export = 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)),
}),
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 => {
responses.add(NodeGraphMessage::UpdateLayerPanel);
responses.add(DocumentMessage::DocumentStructureChanged);
responses.add(PropertiesPanelMessage::Refresh);
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);
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 wires_direct_not_grid_aligned = preferences.graph_wire_style.is_direct();
responses.add(NodeGraphMessage::UpdateImportsExports);
responses.add(FrontendMessage::UpdateNodeGraph {
nodes,
wires,
wires_direct_not_grid_aligned,
});
responses.add(FrontendMessage::UpdateLayerWidths {
layer_widths,
chain_widths,
@ -1455,6 +1451,8 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
Ordering::Equal => {}
}
}
responses.add(NodeGraphMessage::SendWires);
}
NodeGraphMessage::ToggleSelectedAsLayersOrNodes => {
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 } => {
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 } => {
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::SendGraph);
responses.add(NodeGraphMessage::SendWires);
}
NodeGraphMessage::SetDisplayName {
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 {
log::error!("Could not get network metadata in PointerMove");
log::error!("Could not get network metadata in UpdateBoxSelection");
return;
};
@ -1689,7 +1690,8 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
)
.into_iter()
.next();
responses.add(NodeGraphMessage::UpdateVisibleNodes);
responses.add(NodeGraphMessage::SendWires);
responses.add(FrontendMessage::UpdateImportsExports {
imports,
exports,
@ -1835,6 +1837,7 @@ impl NodeGraphMessageHandler {
node_id: Some(node_id),
node_type: node_type.clone(),
xy: None,
add_transaction: true,
}
.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> {
let Some(network) = network_interface.nested_network(breadcrumb_network_path) else {
log::error!("Could not get network when collecting wires");
return Vec::new();
};
let mut wires = network
.nodes
fn collect_wires(&mut self, network_interface: &mut NodeNetworkInterface, graph_wire_style: GraphWireStyle, breadcrumb_network_path: &[NodeId]) -> Vec<WirePathUpdate> {
let mut added_wires = network_interface
.node_graph_input_connectors(breadcrumb_network_path)
.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(|(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,
}
})
.filter_map(|connector| network_interface.newly_loaded_input_wire(connector, graph_wire_style, breadcrumb_network_path))
.collect::<Vec<_>>();
// Connect primary export to root node, since previewing a node will change the primary export
if let Some(root_node) = network_interface.root_node(breadcrumb_network_path) {
wires.push(FrontendNodeWire {
wire_start: OutputConnector::node(root_node.node_id, root_node.output_index),
wire_end: InputConnector::Export(0),
dashed: false,
});
let changed_wire_inputs = added_wires.iter().map(|update| (update.id, update.input_index)).collect::<Vec<_>>();
self.frontend_wires.extend(changed_wire_inputs);
let mut orphaned_wire_inputs = self.frontend_wires.clone();
self.frontend_wires = network_interface
.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
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
added_wires
}
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}");
}
}
let mut frontend_inputs_lookup = frontend_inputs_lookup(breadcrumb_network_path, network_interface);
let Some(network) = network_interface.nested_network(breadcrumb_network_path) else {
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 inputs = frontend_inputs_lookup.remove(&node_id).unwrap_or_default();
let mut inputs = inputs.into_iter().map(|input| {
input.map(|input| FrontendGraphInput {
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(),
name: input.input_name.unwrap_or_else(|| input.ty.nested_type().to_string()),
description: input.input_description.unwrap_or_default(),
name: input.input_name,
description: input.input_description,
connected_to: input.output_connector,
})
});
@ -2266,20 +2241,16 @@ impl NodeGraphMessageHandler {
let primary_input = inputs.next().flatten();
let exposed_inputs = inputs.flatten().collect();
let output_types = network_interface.output_types(&node_id, breadcrumb_network_path);
let primary_output_type = output_types.first().cloned().flatten();
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 (output_type, type_source) = network_interface.output_type(&node_id, 0, breadcrumb_network_path);
let frontend_data_type = FrontendGraphDataType::displayed_type(&output_type, &type_source);
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 {
data_type: frontend_data_type,
name: "Output 1".to_string(),
description: String::new(),
resolved_type: primary_output_type.map(|(input, _)| format!("{input:?}")),
resolved_type: format!("{:?}", output_type),
connected_to,
})
} else {
@ -2287,15 +2258,13 @@ impl NodeGraphMessageHandler {
};
let mut exposed_outputs = Vec::new();
for (index, exposed_output) in output_types.iter().enumerate() {
if index == 0 && network_interface.has_primary_output(&node_id, breadcrumb_network_path) {
for output_index in 0..network_interface.number_of_outputs(&node_id, breadcrumb_network_path) {
if output_index == 0 && network_interface.has_primary_output(&node_id, breadcrumb_network_path) {
continue;
}
let frontend_data_type = if let Some((output_type, type_source)) = &exposed_output {
FrontendGraphDataType::displayed_type(output_type, type_source)
} else {
FrontendGraphDataType::General
};
let (output_type, type_source) = network_interface.output_type(&node_id, 0, breadcrumb_network_path);
let data_type = FrontendGraphDataType::displayed_type(&output_type, &type_source);
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}");
continue;
@ -2303,17 +2272,17 @@ impl NodeGraphMessageHandler {
let output_name = node_metadata
.persistent_metadata
.output_names
.get(index)
.map(|output_name| output_name.to_string())
.get(output_index)
.cloned()
.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 {
data_type: frontend_data_type,
data_type,
name: output_name,
description: String::new(),
resolved_type: exposed_output.clone().map(|(input, _)| format!("{input:?}")),
resolved_type: format!("{:?}", output_type),
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.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, &[]) {
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 {
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>) {
// A wire is in progress and its start and end connectors are set
let wiring = self.wire_in_progress_from_connector.is_some();
@ -2570,8 +2479,8 @@ impl NodeGraphMessageHandler {
#[derive(Default)]
struct InputLookup {
input_name: Option<String>,
input_description: Option<String>,
input_name: String,
input_description: String,
ty: Type,
type_source: TypeSource,
valid_types: Vec<Type>,
@ -2586,34 +2495,31 @@ fn frontend_inputs_lookup(breadcrumb_network_path: &[NodeId], network_interface:
return Default::default();
};
let mut frontend_inputs_lookup = HashMap::new();
for (&node_id, node) in network.nodes.iter() {
let mut inputs = Vec::with_capacity(node.inputs.len());
for (index, input) in node.inputs.iter().enumerate() {
let is_exposed = input.is_exposed_to_frontend(breadcrumb_network_path.is_empty());
// Skip not exposed inputs (they still get an entry to help with finding the primary input)
if !is_exposed {
inputs.push(None);
continue;
}
for (node_id, index, output_connector, is_exposed) in network
.nodes
.iter()
.flat_map(|(node_id, node)| {
node.inputs
.iter()
.enumerate()
.map(|(index, input)| (*node_id, index, OutputConnector::from_input(input), input.is_exposed()))
})
.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`)
let input_name = network_interface
.input_name(node_id, index, breadcrumb_network_path)
.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 {
let (input_name, input_description) = network_interface.displayed_input_name_and_description(&node_id, index, breadcrumb_network_path);
Some(InputLookup {
input_name,
input_description,
output_connector: connector,
output_connector,
..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() {
@ -2656,6 +2562,7 @@ impl Default for NodeGraphMessageHandler {
select_if_not_dragged: None,
wire_in_progress_from_connector: None,
wire_in_progress_to_connector: None,
wire_in_progress_type: FrontendGraphDataType::General,
context_menu: None,
deselect_on_pointer_up: None,
auto_panning: Default::default(),
@ -2663,6 +2570,8 @@ impl Default for NodeGraphMessageHandler {
reordering_export: None,
reordering_import: 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"
})
.on_update(move |_parameter| {
Message::Batched(Box::new([
NodeGraphMessage::ExposeInput {
input_connector: InputConnector::node(node_id, index),
set_to_exposed: !exposed,
start_transaction: true,
}
.into(),
DocumentMessage::GraphViewOverlay { open: true }.into(),
NavigationMessage::FitViewportToSelection.into(),
DocumentMessage::ZoomCanvasTo100Percent.into(),
]))
Message::Batched(Box::new([NodeGraphMessage::ExposeInput {
input_connector: InputConnector::node(node_id, index),
set_to_exposed: !exposed,
start_transaction: true,
}
.into()]))
})
.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> {
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> {
pub fn start_widgets(parameter_widgets_info: ParameterWidgetsInfo) -> Vec<WidgetHolder> {
let ParameterWidgetsInfo {
document_node,
node_id,
index,
name,
description,
input_type,
blank_assist,
exposeable,
} = 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 {
log::warn!("A widget failed to be built because its node's input index is invalid.");
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);
if exposable {
widgets.push(expose_widget(node_id, index, data_type, input.is_exposed()));
if exposeable {
widgets.push(expose_widget(node_id, index, input_type, input.is_exposed()));
}
widgets.push(TextLabel::new(name).tooltip(description).widget_holder());
if blank_assist {
@ -126,18 +124,6 @@ pub(crate) fn property_from_type(
step: Option<f64>,
context: &mut NodePropertiesContext,
) -> 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_input = NumberInput::default();
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 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 widgets = match ty {
@ -247,7 +233,7 @@ pub(crate) fn property_from_type(
// OTHER
// =====
_ => {
let mut widgets = start_widgets(default_info, FrontendGraphDataType::General);
let mut widgets = start_widgets(default_info);
widgets.extend_from_slice(&[
Separator::new(SeparatorType::Unrelated).widget_holder(),
TextLabel::new("-")
@ -277,8 +263,9 @@ pub(crate) fn property_from_type(
pub fn text_widget(parameter_widgets_info: ParameterWidgetsInfo) -> Vec<WidgetHolder> {
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 {
log::warn!("A widget failed to be built because its node's input index is invalid.");
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> {
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 {
log::warn!("A widget failed to be built because its node's input index is invalid.");
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> {
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 {
log::warn!("A widget failed to be built because its node's input index is invalid.");
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> {
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 {
log::warn!("A widget failed to be built because its node's input index is invalid.");
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 {
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());
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);
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 {
log::warn!("A widget failed to be built because its node's input index is invalid.");
return Vec::new().into();
};
if let Some(&TaggedValue::Footprint(footprint)) = input.as_non_exposed_value() {
let top_left = footprint.transform.transform_point2(DVec2::ZERO);
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 {
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 {
log::warn!("A widget failed to be built because its node's input index is invalid.");
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> {
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| {
string
@ -641,6 +634,7 @@ pub fn array_of_number_widget(parameter_widgets_info: ParameterWidgetsInfo, text
.map(TaggedValue::VecF64)
};
let Some(document_node) = document_node else { return Vec::new() };
let Some(input) = document_node.inputs.get(index) else {
log::warn!("A widget failed to be built because its node's input index is invalid.");
return vec![];
@ -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> {
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| {
string
@ -672,6 +666,7 @@ pub fn array_of_coordinates_widget(parameter_widgets_info: ParameterWidgetsInfo,
.map(TaggedValue::VecDVec2)
};
let Some(document_node) = document_node else { return Vec::new() };
let Some(input) = document_node.inputs.get(index) else {
log::warn!("A widget failed to be built because its node's input index is invalid.");
return vec![];
@ -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>>) {
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 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 {
log::warn!("A widget failed to be built because its node's input index is invalid.");
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> {
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(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> {
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(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> {
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(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> {
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 {
log::warn!("A widget failed to be built because its node's input index is invalid.");
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 {
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 {
log::warn!("A widget failed to be built because its node's input index is invalid.");
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 {
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
let NodeInput::Value { tagged_value, exposed: false } = &document_node.inputs[index] else {
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 {
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 {
log::warn!("A widget failed to be built because its node's input index is invalid.");
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"))
}
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 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");
""
});
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))
Ok((document_node, name, description))
}
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> {
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) {
Ok(document_node) => document_node,
Err(err) => {
@ -1002,12 +1002,6 @@ pub(crate) fn brightness_contrast_properties(node_id: NodeId, context: &mut Node
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() {
Some(TaggedValue::Bool(use_classic_choice)) => *use_classic_choice,
_ => false,
@ -1015,7 +1009,7 @@ pub(crate) fn brightness_contrast_properties(node_id: NodeId, context: &mut Node
// Brightness
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()
.unit("%")
.mode_range()
@ -1026,7 +1020,7 @@ pub(crate) fn brightness_contrast_properties(node_id: NodeId, context: &mut Node
// Contrast
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()
.unit("%")
.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> {
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) {
Ok(document_node) => document_node,
Err(err) => {
@ -1054,22 +1053,12 @@ pub(crate) fn channel_mixer_properties(node_id: NodeId, context: &mut NodeProper
return Vec::new();
}
};
// 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() {
Some(TaggedValue::Bool(monochrome_choice)) => *monochrome_choice,
_ => false,
};
// 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() {
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),
};
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 green = number_widget(ParameterWidgetsInfo::from_index(document_node, 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 constant = number_widget(ParameterWidgetsInfo::from_index(document_node, node_id, constant_output_index, true, context), number_input);
let red = number_widget(ParameterWidgetsInfo::new(node_id, red_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::new(node_id, blue_output_index, true, context), number_input.clone());
let constant = number_widget(ParameterWidgetsInfo::new(node_id, constant_output_index, true, context), number_input);
// 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> {
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) {
Ok(document_node) => document_node,
Err(err) => {
@ -1117,13 +1110,7 @@ pub(crate) fn selective_color_properties(node_id: NodeId, context: &mut NodeProp
return Vec::new();
}
};
// 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() {
Some(TaggedValue::SelectiveColorChoice(choice)) => choice,
_ => {
@ -1131,7 +1118,6 @@ pub(crate) fn selective_color_properties(node_id: NodeId, context: &mut NodeProp
return vec![];
}
};
// CMYK
let (c_index, m_index, y_index, k_index) = match colors_choice {
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),
};
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 magenta = number_widget(ParameterWidgetsInfo::from_index(document_node, 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 black = number_widget(ParameterWidgetsInfo::from_index(document_node, node_id, k_index, true, context), number_input);
let cyan = number_widget(ParameterWidgetsInfo::new(node_id, c_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::new(node_id, y_index, true, context), number_input.clone());
let black = number_widget(ParameterWidgetsInfo::new(node_id, k_index, true, context), number_input);
// Mode
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();
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> {
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>()
.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();
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 {
log::warn!("A widget failed to be built because its node's input index is invalid.");
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() {
match grid_type {
GridType::Rectangular => {
let spacing = coordinate_widget(
ParameterWidgetsInfo::from_index(document_node, node_id, SpacingInput::<f64>::INDEX, true, context),
"W",
"H",
" px",
Some(0.),
);
let spacing = coordinate_widget(ParameterWidgetsInfo::new(node_id, SpacingInput::<f64>::INDEX, true, context), "W", "H", " px", Some(0.));
widgets.push(spacing);
}
GridType::Isometric => {
let spacing = LayoutGroup::Row {
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"),
),
};
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]);
}
}
}
let columns = number_widget(
ParameterWidgetsInfo::from_index(document_node, node_id, ColumnsInput::INDEX, true, context),
NumberInput::default().min(1.),
);
let rows = number_widget(
ParameterWidgetsInfo::from_index(document_node, node_id, RowsInput::INDEX, true, context),
NumberInput::default().min(1.),
);
let columns = number_widget(ParameterWidgetsInfo::new(node_id, ColumnsInput::INDEX, true, context), NumberInput::default().min(1.));
let rows = number_widget(ParameterWidgetsInfo::new(node_id, RowsInput::INDEX, true, context), NumberInput::default().min(1.));
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 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();
let separation = number_widget(
ParameterWidgetsInfo::from_index(document_node, node_id, SeparationInput::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 separation = number_widget(ParameterWidgetsInfo::new(node_id, SeparationInput::INDEX, true, context), NumberInput::default().min(0.).unit(" px"));
let quantity = number_widget(ParameterWidgetsInfo::new(node_id, QuantityInput::INDEX, true, context), NumberInput::default().min(2.).int());
let start_offset = number_widget(ParameterWidgetsInfo::new(node_id, StartOffsetInput::INDEX, true, context), NumberInput::default().min(0.).unit(" px"));
let stop_offset = number_widget(ParameterWidgetsInfo::new(node_id, StopOffsetInput::INDEX, true, context), NumberInput::default().min(0.).unit(" px"));
let adaptive_spacing = bool_widget(
ParameterWidgetsInfo::from_index(document_node, node_id, AdaptiveSpacingInput::INDEX, true, context),
ParameterWidgetsInfo::new(node_id, AdaptiveSpacingInput::INDEX, true, context),
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> {
use graphene_std::raster::exposure::*;
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 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 exposure = number_widget(ParameterWidgetsInfo::new(node_id, ExposureInput::INDEX, true, context), NumberInput::default().min(-20.).max(20.));
let offset = number_widget(ParameterWidgetsInfo::new(node_id, OffsetInput::INDEX, true, context), NumberInput::default().min(-0.5).max(0.5));
let 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),
);
@ -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> {
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) {
Ok(document_node) => document_node,
Err(err) => {
@ -1325,23 +1281,6 @@ pub(crate) fn rectangle_properties(node_id: NodeId, context: &mut NodeProperties
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 {
log::warn!("A widget failed to be built because its node's input index is invalid.");
return vec![];
@ -1435,8 +1374,16 @@ pub(crate) fn rectangle_properties(node_id: NodeId, context: &mut NodeProperties
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
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![
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> {
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) {
Ok(document_node) => document_node,
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))) = (
&document_node.inputs[FillInput::<Color>::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();
}
};
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() {
Some(TaggedValue::VecF64(x)) => x,
_ => &vec![],
};
let dash_lengths = array_of_number_widget(
ParameterWidgetsInfo::from_index(document_node, node_id, DashLengthsInput::INDEX, true, context),
TextInput::default().centered(true),
let has_dash_lengths = dash_lengths_val.is_empty();
let miter_limit_disabled = join_value != &StrokeJoin::Miter;
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 dash_offset = number_widget(ParameterWidgetsInfo::from_index(document_node, node_id, DashOffsetInput::INDEX, true, context), number_input);
let weight = number_widget(ParameterWidgetsInfo::new(node_id, WeightInput::INDEX, true, context), NumberInput::default().unit(" px").min(0.));
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![
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> {
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) {
Ok(document_node) => document_node,
Err(err) => {
@ -1821,13 +1767,6 @@ pub fn offset_path_properties(node_id: NodeId, context: &mut NodePropertiesConte
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 join_val = match &document_node.inputs[JoinInput::INDEX].as_value() {
Some(TaggedValue::StrokeJoin(x)) => x,
@ -1835,7 +1774,7 @@ pub fn offset_path_properties(node_id: NodeId, context: &mut NodePropertiesConte
};
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 }]
}
@ -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> {
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 mut widgets = start_widgets(
ParameterWidgetsInfo::from_index(document_node, node_id, ExpressionInput::INDEX, true, context),
FrontendGraphDataType::General,
);
let mut widgets = start_widgets(ParameterWidgetsInfo::new(node_id, ExpressionInput::INDEX, true, context));
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 {
log::warn!("A widget failed to be built because its node's input index is invalid.");
return vec![];
@ -1889,10 +1824,7 @@ pub fn math_properties(node_id: NodeId, context: &mut NodePropertiesContext) ->
}
widgets
})();
let operand_b = number_widget(
ParameterWidgetsInfo::from_index(document_node, node_id, OperandBInput::<f64>::INDEX, true, context),
NumberInput::default(),
);
let operand_b = number_widget(ParameterWidgetsInfo::new(node_id, OperandBInput::<f64>::INDEX, true, context), NumberInput::default());
let operand_a_hint = vec![TextLabel::new("(Operand A is the primary input)").widget_holder()];
vec![
@ -1903,44 +1835,37 @@ pub fn math_properties(node_id: NodeId, context: &mut NodePropertiesContext) ->
}
pub struct ParameterWidgetsInfo<'a> {
document_node: &'a DocumentNode,
document_node: Option<&'a DocumentNode>,
node_id: NodeId,
index: usize,
name: &'a str,
description: &'a str,
name: String,
description: String,
input_type: FrontendGraphDataType,
blank_assist: bool,
exposeable: bool,
}
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 {
document_node,
node_id,
index,
name,
description,
input_type,
blank_assist,
}
}
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,
exposeable: true,
}
}
}
pub mod choice {
use super::ParameterWidgetsInfo;
use crate::messages::portfolio::document::node_graph::utility_types::FrontendGraphDataType;
use crate::messages::tool::tool_messages::tool_prelude::*;
use graph_craft::document::value::TaggedValue;
use graphene_std::registry::{ChoiceTypeStatic, ChoiceWidgetHint};
@ -1973,11 +1898,7 @@ pub mod choice {
impl<E: ChoiceTypeStatic + 'static> EnumChoice<E> {
pub fn for_socket(self, parameter_info: ParameterWidgetsInfo) -> ForSocket<Self> {
ForSocket {
widget_factory: self,
parameter_info,
exposable: true,
}
ForSocket { widget_factory: self, parameter_info }
}
/// Not yet implemented!
@ -2068,7 +1989,6 @@ pub mod choice {
pub struct ForSocket<'p, W> {
widget_factory: W,
parameter_info: ParameterWidgetsInfo<'p>,
exposable: bool,
}
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 {
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 {
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 {
fn with_type(input: &Type) -> Self {
pub fn from_type(input: &Type) -> Self {
match TaggedValue::from_type_or_none(input) {
TaggedValue::Image(_) | TaggedValue::RasterData(_) => Self::Raster,
TaggedValue::Subpaths(_) | TaggedValue::VectorData(_) => Self::VectorData,
@ -38,7 +38,7 @@ impl FrontendGraphDataType {
pub fn displayed_type(input: &Type, type_source: &TypeSource) -> Self {
match type_source {
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 description: String,
#[serde(rename = "resolvedType")]
pub resolved_type: Option<String>,
pub resolved_type: String,
#[serde(rename = "validTypes")]
pub valid_types: Vec<String>,
#[serde(rename = "connectedTo")]
@ -64,7 +64,7 @@ pub struct FrontendGraphOutput {
pub name: String,
pub description: String,
#[serde(rename = "resolvedType")]
pub resolved_type: Option<String>,
pub resolved_type: String,
#[serde(rename = "connectedTo")]
pub connected_to: Vec<InputConnector>,
}
@ -96,15 +96,6 @@ pub struct FrontendNode {
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)]
pub struct FrontendNodeType {
pub name: String,
@ -153,16 +144,6 @@ pub struct Transform {
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)]
pub struct BoxSelection {
#[serde(rename = "startX")]
@ -224,32 +205,3 @@ pub enum Direction {
Left,
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 nodes;
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
// 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::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 bezier_rs::Subpath;
use glam::IVec2;
use graph_craft::document::{DocumentNodeImplementation, NodeInput, value::TaggedValue};
use graphene_std::text::TypesettingConfig;
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 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) {
let mut network = document.network_interface.document_network().clone();
network.generate_node_paths(&[]);
let network = document.network_interface.document_network().clone();
// 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, path) in &node_ids {
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 (node_id, node, network_path) in network.recursive_nodes() {
if let DocumentNodeImplementation::ProtoNode(protonode_id) = &node.implementation {
for (old, new) in REPLACEMENTS {
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) {
document
.network_interface
.replace_implementation(node_id, &network_path, DocumentNodeImplementation::ProtoNode(new.to_string().into()));
document.network_interface.replace_implementation(node_id, &network_path, &mut default_template);
document.network_interface.set_manual_compostion(node_id, &network_path, Some(graph_craft::Type::Generic("T".into())));
}
}
}
}
if reset_node_definitions_on_open {
// 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.
// Used for upgrading old internal networks for demo artwork nodes. Will reset all node internals for any opened file
for node_id in &document
.network_interface
.document_network_metadata()
.persistent_metadata
.node_metadata
.keys()
.cloned()
.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())
{
// Apply upgrades to each unmodified node.
let nodes = document
.network_interface
.document_network()
.recursive_nodes()
.map(|(node_id, node, path)| (*node_id, node.clone(), path))
.collect::<Vec<(NodeId, graph_craft::document::DocumentNode, Vec<NodeId>)>>();
for (node_id, node, network_path) in &nodes {
if reset_node_definitions_on_open {
if let Some(Some(reference)) = document.network_interface.reference(node_id, network_path) {
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, &[], 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);
document.network_interface.replace_implementation(node_id, network_path, &mut node_definition.default_node_template());
}
}
}
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
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());
}
let Some(node_metadata) = document.network_interface.network_metadata(network_path).unwrap().persistent_metadata.node_metadata.get(node_id) else {
log::error!("could not get node metadata for node {node_id} in deserialize_document");
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");
let Some(Some(reference)) = document.network_interface.reference(node_id, network_path).cloned() else {
// Only nodes that have not been modified and still refer to a definition can be updated
continue;
};
let reference = &reference;
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)
if reference == "Stroke" && 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());
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 mut node_template = resolve_document_node_type(reference).unwrap().default_node_template();
let old_inputs = document.network_interface.replace_inputs(node_id, network_path, &mut node_template).unwrap();
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 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
if reference == "Text" && inputs_count != 9 {
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);
let mut template = resolve_document_node_type(reference).unwrap().default_node_template();
document.network_interface.replace_implementation(node_id, network_path, &mut template);
let old_inputs = document.network_interface.replace_inputs(node_id, network_path, &mut template).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, 1), old_inputs[1].clone(), network_path);
@ -500,21 +376,6 @@ pub fn document_migration_upgrades(document: &mut DocumentMessageHandler, reset_
},
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(
&InputConnector::node(*node_id, 8),
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
if (reference == "Sine" || reference == "Cosine" || reference == "Tangent") && inputs_count == 1 {
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 mut node_template = resolve_document_node_type(reference).unwrap().default_node_template();
document.network_interface.replace_implementation(node_id, network_path, &mut node_template);
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
@ -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
if reference == "Modulo" && inputs_count == 2 {
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 mut node_template = resolve_document_node_type(reference).unwrap().default_node_template();
document.network_interface.replace_implementation(node_id, network_path, &mut node_template);
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, 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
if reference == "Mirror" && inputs_count == 3 {
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 mut node_template = resolve_document_node_type(reference).unwrap().default_node_template();
document.network_interface.replace_implementation(node_id, network_path, &mut node_template);
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, 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`
if reference == "Mirror" && inputs_count == 4 {
let node_definition = resolve_document_node_type(reference).unwrap();
let new_node_template = node_definition.default_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 mut node_template = resolve_document_node_type(reference).unwrap().default_node_template();
document.network_interface.replace_implementation(node_id, network_path, &mut node_template);
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 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 {
let node_definition = crate::messages::portfolio::document::node_graph::document_node_definitions::resolve_document_node_type(reference).unwrap();
let new_image_node = node_definition.default_node_template();
document.network_interface.replace_implementation(node_id, network_path, new_image_node.document_node.implementation);
let mut node_template = resolve_document_node_type(reference).unwrap().default_node_template();
document.network_interface.replace_implementation(node_id, network_path, &mut node_template);
// Insert a new empty input for the image
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 {
let node_definition = crate::messages::portfolio::document::node_graph::document_node_definitions::resolve_document_node_type(reference).unwrap();
let new_noise_pattern_node = node_definition.default_node_template();
document
.network_interface
.replace_implementation(node_id, network_path, new_noise_pattern_node.document_node.implementation);
let mut node_template = resolve_document_node_type(reference).unwrap().default_node_template();
document.network_interface.replace_implementation(node_id, network_path, &mut node_template);
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
.network_interface
@ -635,30 +484,20 @@ pub fn document_migration_upgrades(document: &mut DocumentMessageHandler, reset_
}
if reference == "Instance on Points" && inputs_count == 2 {
let node_definition = resolve_document_node_type(reference).unwrap();
let new_node_template = node_definition.default_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 mut node_template = resolve_document_node_type(reference).unwrap().default_node_template();
document.network_interface.replace_implementation(node_id, network_path, &mut node_template);
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, 1), old_inputs[1].clone(), network_path);
}
if reference == "Morph" && inputs_count == 4 {
let node_definition = resolve_document_node_type(reference).unwrap();
let new_node_template = node_definition.default_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 mut node_template = resolve_document_node_type(reference).unwrap().default_node_template();
document.network_interface.replace_implementation(node_id, network_path, &mut node_template);
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, 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 {
let node_definition = resolve_document_node_type(reference).unwrap();
let new_node_template = node_definition.default_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 mut node_template = resolve_document_node_type(reference).unwrap().default_node_template();
document.network_interface.replace_implementation(node_id, network_path, &mut node_template);
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);
// 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" {
let node_definition = resolve_document_node_type("Flatten Path").unwrap();
let new_node_template = node_definition.default_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 mut node_template = resolve_document_node_type(reference).unwrap().default_node_template();
document.network_interface.replace_implementation(node_id, network_path, &mut node_template);
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);
@ -700,15 +529,10 @@ pub fn document_migration_upgrades(document: &mut DocumentMessageHandler, reset_
}
if reference == "Remove Handles" {
let node_definition = resolve_document_node_type("Auto-Tangents").unwrap();
let new_node_template = node_definition.default_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 mut node_template = resolve_document_node_type(reference).unwrap().default_node_template();
document.network_interface.replace_implementation(node_id, network_path, &mut node_template);
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
@ -722,15 +546,10 @@ pub fn document_migration_upgrades(document: &mut DocumentMessageHandler, reset_
}
if reference == "Generate Handles" {
let node_definition = resolve_document_node_type("Auto-Tangents").unwrap();
let new_node_template = node_definition.default_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 mut node_template = resolve_document_node_type("Auto-Tangents").unwrap().default_node_template();
document.network_interface.replace_implementation(node_id, network_path, &mut node_template);
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, 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 {
let node_definition = resolve_document_node_type("Merge by Distance").unwrap();
let new_node_template = node_definition.default_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 mut node_template = resolve_document_node_type(reference).unwrap().default_node_template();
document.network_interface.replace_implementation(node_id, network_path, &mut node_template);
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, 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" {
let node_definition = resolve_document_node_type("Merge by Distance").unwrap();
let new_node_template = node_definition.default_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 mut node_template = resolve_document_node_type("Merge by Distance").unwrap().default_node_template();
document.network_interface.replace_implementation(node_id, network_path, &mut node_template);
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, 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 {
let node_definition = resolve_document_node_type("Sample Polyline").unwrap();
let new_node_template = node_definition.default_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 mut node_template = resolve_document_node_type("Sample Polyline").unwrap().default_node_template();
document.network_interface.replace_implementation(node_id, network_path, &mut node_template);
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_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
if reference == "Sample Polyline" {
let node_definition = resolve_document_node_type("Sample Polyline").unwrap();
let new_node_template = node_definition.default_node_template();
let document_node = new_node_template.document_node;
let mut new_node_template = node_definition.default_node_template();
// 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 _ = document.network_interface.replace_inputs(node_id, old_inputs, network_path);
if let Some(NodeInput::Value { tagged_value, exposed }) = quantity_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")
if reference == "Grid" && inputs_count == 6 {
let node_definition = resolve_document_node_type(reference).unwrap();
let new_node_template = node_definition.default_node_template();
let document_node = new_node_template.document_node;
let mut new_node_template = node_definition.default_node_template();
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();
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);
} else {
// 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::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::network_interface::OutputConnector;
use crate::messages::portfolio::document::utility_types::nodes::SelectedNodes;
use crate::messages::portfolio::document_migration::*;
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
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
document.set_auto_save_state(document_is_auto_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 {
warn!("Tried to read non existant document");
warn!("Tried to read non existent document");
return;
};
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::prelude::*;

View file

@ -1,6 +1,6 @@
use crate::consts::VIEWPORT_ZOOM_WHEEL_RATE;
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::prelude::*;
use graph_craft::wasm_application_io::EditorPreferences;
@ -86,7 +86,8 @@ impl MessageHandler<PreferencesMessage, ()> for PreferencesMessageHandler {
}
PreferencesMessage::GraphWireStyle { style } => {
self.graph_wire_style = style;
responses.add(NodeGraphMessage::SendGraph);
responses.add(NodeGraphMessage::UnloadWires);
responses.add(NodeGraphMessage::SendWires);
}
PreferencesMessage::ViewportZoomWheelRate { 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 {
self.active_document()
.network_interface
.iter_recursive()
.inspect(|node| println!("{:#?}", node.1.implementation))
.filter_map(move |(_, document)| T::new_with_source(document))
.document_network()
.recursive_nodes()
.inspect(|(_, node, _)| println!("{:#?}", node.implementation))
.filter_map(move |(_, document, _)| T::new_with_source(document))
}
pub async fn move_mouse(&mut self, x: f64, y: f64, modifier_keys: ModifierKeys, mouse_keys: MouseKeys) {
@ -300,7 +301,7 @@ pub trait FrontendMessageTestUtils {
impl FrontendMessageTestUtils for FrontendMessage {
fn check_node_graph_error(&self) {
let FrontendMessage::UpdateNodeGraph { nodes, .. } = self else { return };
let FrontendMessage::UpdateNodeGraphNodes { nodes, .. } = self else { return };
for node in nodes {
if let Some(error) = &node.errors {

View file

@ -1,11 +1,11 @@
<script lang="ts">
import { getContext, onMount, tick } from "svelte";
import { getContext } from "svelte";
import { cubicInOut } from "svelte/easing";
import { fade } from "svelte/transition";
import type { Editor } from "@graphite/editor";
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 { IconName } from "@graphite/utility-functions/icons";
@ -27,26 +27,13 @@
const nodeGraph = getContext<NodeGraphState>("nodeGraph");
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
// let wireInProgressFromLayerTop: bigint | undefined = undefined;
// 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);
// Key value is node id + input/output index
// Imports/Export are stored at a key value of 0
$: gridSpacing = calculateGridSpacing($nodeGraph.transform.scale);
$: dotRadius = 1 + Math.floor($nodeGraph.transform.scale - 0.5 + 0.001) / 2;
$: wirePaths = createWirePaths($nodeGraph.wirePathInProgress, nodeWirePaths);
let inputElement: HTMLInputElement;
let hoveringImportIndex: number | undefined = undefined;
let hoveringExportIndex: number | undefined = undefined;
@ -121,80 +108,6 @@
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 {
if (!icon) return "NodeNodes";
const iconMap: Record<string, IconName> = {
@ -203,325 +116,6 @@
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) {
let node = $nodeGraph.nodes.get(toggleId);
if (node) editor.handle.setToNodeOrLayer(node.id, displayAsLayer);
@ -719,19 +313,21 @@
</div>
{/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})`}>
<svg>
{#each wirePaths as { pathString, dataType, thick, dashed }}
{#if thick}
<path
d={pathString}
style:--data-line-width={`${thick ? 8 : 2}px`}
style:--data-color={`var(--color-data-${dataType.toLowerCase()})`}
style:--data-color-dim={`var(--color-data-${dataType.toLowerCase()}-dim)`}
style:--data-dasharray={`3,${dashed ? 2 : 0}`}
/>
{/if}
{#each $nodeGraph.wires.values() as map}
{#each map.values() as { pathString, dataType, thick, dashed }}
{#if thick}
<path
d={pathString}
style:--data-line-width={"8px"}
style:--data-color={`var(--color-data-${dataType.toLowerCase()})`}
style:--data-color-dim={`var(--color-data-${dataType.toLowerCase()}-dim)`}
style:--data-dasharray={`3,${dashed ? 2 : 0}`}
/>
{/if}
{/each}
{/each}
</svg>
</div>
@ -749,7 +345,6 @@
style:--data-color-dim={`var(--color-data-${outputMetadata.dataType.toLowerCase()}-dim)`}
style:--offset-left={position.x / 24}
style:--offset-top={position.y / 24}
bind:this={outputs[0][index]}
>
<title>{`${dataTypeTooltip(outputMetadata)}\n\n${outputConnectedToText(outputMetadata)}`}</title>
{#if outputMetadata.connectedTo !== undefined}
@ -823,7 +418,6 @@
style:--data-color-dim={`var(--color-data-${inputMetadata.dataType.toLowerCase()}-dim)`}
style:--offset-left={position.x / 24}
style:--offset-top={position.y / 24}
bind:this={inputs[0][index]}
>
<title>{`${dataTypeTooltip(inputMetadata)}\n\n${inputConnectedToText(inputMetadata)}`}</title>
{#if inputMetadata.connectedTo !== undefined}
@ -887,14 +481,11 @@
</div>
<!-- Layers and nodes -->
<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})`}
bind:this={nodesContainer}
>
<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})`}>
<!-- 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 stackDataInput = node.exposedInputs[0]}
{@const layerAreaWidth = $nodeGraph.layerWidths.get(node.id) || 8}
@ -916,7 +507,6 @@
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}` : "")}
data-node={node.id}
bind:this={nodeElements[nodeIndex]}
>
{#if node.errors}
<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}
style:--data-color={`var(--color-data-${node.primaryOutput.dataType.toLowerCase()})`}
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>
{#if node.primaryOutput.connectedTo.length > 0}
@ -958,7 +547,6 @@
data-datatype={node.primaryInput?.dataType}
style:--data-color={`var(--color-data-${(node.primaryInput?.dataType || "General").toLowerCase()})`}
style:--data-color-dim={`var(--color-data-${(node.primaryInput?.dataType || "General").toLowerCase()}-dim)`}
bind:this={inputs[nodeIndex + 1][0]}
>
{#if node.primaryInput}
<title>{`${dataTypeTooltip(node.primaryInput)}\n\n${validTypesText(node.primaryInput)}\n\n${inputConnectedToText(node.primaryInput)}`}</title>
@ -984,7 +572,6 @@
data-datatype={stackDataInput.dataType}
style:--data-color={`var(--color-data-${stackDataInput.dataType.toLowerCase()})`}
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>
{#if stackDataInput.connectedTo !== undefined}
@ -1025,22 +612,35 @@
<!-- Node connection wires -->
<div class="wires">
<svg>
{#each wirePaths as { pathString, dataType, thick, dashed }}\
{#if !thick}
<path
d={pathString}
style:--data-line-width={`${thick ? 8 : 2}px`}
style:--data-color={`var(--color-data-${dataType.toLowerCase()})`}
style:--data-color-dim={`var(--color-data-${dataType.toLowerCase()}-dim)`}
style:--data-dasharray={dashed ? "4" : undefined}
/>
{/if}
{#each $nodeGraph.wires.values() as map}
{#each map.values() as { pathString, dataType, thick, dashed }}
{#if !thick}
<path
d={pathString}
style:--data-line-width={"2px"}
style:--data-color={`var(--color-data-${dataType.toLowerCase()})`}
style:--data-color-dim={`var(--color-data-${dataType.toLowerCase()}-dim)`}
style:--data-dasharray={`3,${dashed ? 2 : 0}`}
/>
{/if}
{/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>
</div>
<!-- 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 clipPathId = String(Math.random()).substring(2)}
{@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)`}
title={`${node.displayName}\n\n${description || ""}`.trim() + (editor.handle.inDevelopmentMode() ? `\n\nNode ID: ${node.id}` : "")}
data-node={node.id}
bind:this={nodeElements[nodeIndex]}
>
{#if node.errors}
<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}
style:--data-color={`var(--color-data-${node.primaryInput.dataType.toLowerCase()})`}
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>
{#if node.primaryInput.connectedTo !== undefined}
@ -1111,7 +709,6 @@
data-datatype={secondary.dataType}
style:--data-color={`var(--color-data-${secondary.dataType.toLowerCase()})`}
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>
{#if secondary.connectedTo !== undefined}
@ -1134,7 +731,6 @@
data-datatype={node.primaryOutput.dataType}
style:--data-color={`var(--color-data-${node.primaryOutput.dataType.toLowerCase()})`}
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>
{#if node.primaryOutput.connectedTo !== undefined}
@ -1144,7 +740,7 @@
{/if}
</svg>
{/if}
{#each node.exposedOutputs as secondary, outputIndex}
{#each node.exposedOutputs as secondary}
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 8 8"
@ -1153,7 +749,6 @@
data-datatype={secondary.dataType}
style:--data-color={`var(--color-data-${secondary.dataType.toLowerCase()})`}
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>
{#if secondary.connectedTo !== undefined}

View file

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

View file

@ -7,9 +7,9 @@ import {
type FrontendClickTargets,
type ContextMenuInformation,
type FrontendNode,
type FrontendNodeWire as FrontendNodeWire,
type FrontendNodeType,
type WirePath,
ClearAllNodeGraphWires,
SendUIMetadata,
UpdateBox,
UpdateClickTargets,
@ -19,7 +19,9 @@ import {
UpdateExportReorderIndex,
UpdateImportsExports,
UpdateLayerWidths,
UpdateNodeGraph,
UpdateNodeGraphNodes,
UpdateVisibleNodes,
UpdateNodeGraphWires,
UpdateNodeGraphSelection,
UpdateNodeGraphTransform,
UpdateNodeThumbnail,
@ -40,8 +42,9 @@ export function createNodeGraphState(editor: Editor) {
addImport: undefined as { x: number; y: number } | undefined,
addExport: undefined as { x: number; y: number } | undefined,
nodes: new Map<bigint, FrontendNode>(),
wires: [] as FrontendNodeWire[],
wiresDirectNotGridAligned: false,
visibleNodes: new Set<bigint>(),
/// 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,
nodeDescriptions: new Map<string, string>(),
nodeTypes: [] as FrontendNodeType[],
@ -114,15 +117,42 @@ export function createNodeGraphState(editor: Editor) {
return state;
});
});
// TODO: Add a way to only update the nodes that have changed
editor.subscriptions.subscribeJsMessage(UpdateNodeGraph, (updateNodeGraph) => {
editor.subscriptions.subscribeJsMessage(UpdateNodeGraphNodes, (updateNodeGraphNodes) => {
update((state) => {
state.nodes.clear();
updateNodeGraph.nodes.forEach((node) => {
updateNodeGraphNodes.nodes.forEach((node) => {
state.nodes.set(node.id, node);
});
state.wires = updateNodeGraph.wires;
state.wiresDirectNotGridAligned = updateNodeGraph.wiresDirectNotGridAligned;
return state;
});
});
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;
});
});

View file

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

View file

@ -67,6 +67,10 @@ impl ClickTarget {
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]> {
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,
}
}
/// 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 {
match self {
@ -1250,24 +1239,28 @@ impl NodeNetwork {
/// Create a [`RecursiveNodeIter`] that iterates over all [`DocumentNode`]s, including ones that are deeply nested.
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 }
}
}
/// An iterator over all [`DocumentNode`]s, including ones that are deeply nested.
pub struct RecursiveNodeIter<'a> {
nodes: Vec<(&'a NodeId, &'a DocumentNode)>,
nodes: Vec<(&'a NodeId, &'a DocumentNode, Vec<NodeId>)>,
}
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> {
let node = self.nodes.pop()?;
if let DocumentNodeImplementation::Network(network) = &node.1.implementation {
self.nodes.extend(network.nodes.iter());
let (current_id, node, path) = self.nodes.pop()?;
if let DocumentNodeImplementation::Network(network) = &node.implementation {
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))
}
}