From 8a0241f0fa1a87e07220f009ed83149371042c7b Mon Sep 17 00:00:00 2001 From: Firestar99 Date: Fri, 4 Jul 2025 11:56:22 +0200 Subject: [PATCH 1/4] Fix running tests locally; fix migrations for relocated nodes (#2805) * fix `BoundingBox for Raster` when `wgpu` feature is disabled * fix `tinyvec` crate not being `std` by default * fixup migration for `CoordinateValueNode` * fixup migration for raster nodes --- Cargo.toml | 2 +- .../messages/portfolio/document_migration.rs | 22 ++++++++++++++++--- node-graph/gcore/src/raster_types.rs | 10 +++++++++ 3 files changed, 30 insertions(+), 4 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index faeac043e..35c5e7040 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -150,7 +150,7 @@ petgraph = { version = "0.7.1", default-features = false, features = [ "graphmap", ] } half = { version = "2.4.1", default-features = false, features = ["bytemuck", "serde"] } -tinyvec = { version = "1" } +tinyvec = { version = "1", features = ["std"] } criterion = { version = "0.5", features = ["html_reports"] } iai-callgrind = { version = "0.12.3" } ndarray = "0.16.1" diff --git a/editor/src/messages/portfolio/document_migration.rs b/editor/src/messages/portfolio/document_migration.rs index fc8f42ff7..920d5fa68 100644 --- a/editor/src/messages/portfolio/document_migration.rs +++ b/editor/src/messages/portfolio/document_migration.rs @@ -62,6 +62,8 @@ const REPLACEMENTS: &[(&str, &str)] = &[ ("graphene_core::ops::NumberValueNode", "graphene_math_nodes::NumberValueNode"), ("graphene_core::ops::PercentageValueNode", "graphene_math_nodes::PercentageValueNode"), ("graphene_core::ops::CoordinateValueNode", "graphene_math_nodes::CoordinateValueNode"), + ("graphene_core::ops::ConstructVector2", "graphene_math_nodes::CoordinateValueNode"), + ("graphene_core::ops::Vector2ValueNode", "graphene_math_nodes::CoordinateValueNode"), ("graphene_core::ops::ColorValueNode", "graphene_math_nodes::ColorValueNode"), ("graphene_core::ops::GradientValueNode", "graphene_math_nodes::GradientValueNode"), ("graphene_core::ops::StringValueNode", "graphene_math_nodes::StringValueNode"), @@ -76,8 +78,6 @@ const REPLACEMENTS: &[(&str, &str)] = &[ ("graphene_core::logic::LogicAndNode", "graphene_core::ops::LogicAndNode"), ("graphene_core::logic::LogicNotNode", "graphene_core::ops::LogicNotNode"), ("graphene_core::logic::LogicOrNode", "graphene_core::ops::LogicOrNode"), - ("graphene_core::ops::ConstructVector2", "graphene_core::ops::CoordinateValueNode"), - ("graphene_core::ops::Vector2ValueNode", "graphene_core::ops::CoordinateValueNode"), ("graphene_core::raster::BlendModeNode", "graphene_core::blending_nodes::BlendModeNode"), ("graphene_core::raster::OpacityNode", "graphene_core::blending_nodes::OpacityNode"), ("graphene_core::raster::BlendingNode", "graphene_core::blending_nodes::BlendingNode"), @@ -121,8 +121,24 @@ const REPLACEMENTS: &[(&str, &str)] = &[ ("graphene_core::raster::PosterizeNode", "graphene_raster_nodes::adjustments::PosterizeNode"), ("graphene_core::raster::adjustments::ExposureNode", "graphene_raster_nodes::adjustments::ExposureNode"), ("graphene_core::raster::ExposureNode", "graphene_raster_nodes::adjustments::ExposureNode"), + ("graphene_core::raster::adjustments::ColorOverlayNode", "graphene_raster_nodes::adjustments::ColorOverlayNode"), + ("graphene_raster_nodes::generate_curves::ColorOverlayNode", "graphene_raster_nodes::adjustments::ColorOverlayNode"), + // raster ("graphene_core::raster::adjustments::GenerateCurvesNode", "graphene_raster_nodes::generate_curves::GenerateCurvesNode"), - ("graphene_core::raster::adjustments::ColorOverlayNode", "graphene_raster_nodes::generate_curves::ColorOverlayNode"), + ("graphene_std::dehaze::DehazeNode", "graphene_raster_nodes::dehaze::DehazeNode"), + ("graphene_std::filter::BlurNode", "graphene_raster_nodes::filter::BlurNode"), + ( + "graphene_std::image_color_palette::ImageColorPaletteNode", + "graphene_raster_nodes::image_color_palette::ImageColorPaletteNode", + ), + ("graphene_std::raster::SampleImageNode", "graphene_raster_nodes::std_nodes::SampleImageNode"), + ("graphene_std::raster::CombineChannelsNode", "graphene_raster_nodes::std_nodes::CombineChannelsNode"), + ("graphene_std::raster::MaskNode", "graphene_raster_nodes::std_nodes::MaskNode"), + ("graphene_std::raster::ExtendImageToBoundsNode", "graphene_raster_nodes::std_nodes::ExtendImageToBoundsNode"), + ("graphene_std::raster::EmptyImageNode", "graphene_raster_nodes::std_nodes::EmptyImageNode"), + ("graphene_std::raster::ImageValueNode", "graphene_raster_nodes::std_nodes::ImageValueNode"), + ("graphene_std::raster::NoisePatternNode", "graphene_raster_nodes::std_nodes::NoisePatternNode"), + ("graphene_std::raster::MandelbrotNode", "graphene_raster_nodes::std_nodes::MandelbrotNode"), // text ("graphene_core::text::TextGeneratorNode", "graphene_core::text::TextNode"), // transform diff --git a/node-graph/gcore/src/raster_types.rs b/node-graph/gcore/src/raster_types.rs index 4fb1fc4d5..937d89b2e 100644 --- a/node-graph/gcore/src/raster_types.rs +++ b/node-graph/gcore/src/raster_types.rs @@ -97,11 +97,20 @@ impl Raster { let RasterStorage::Gpu(gpu) = &self.data else { unreachable!() }; gpu.clone() } +} + +impl Raster { + #[cfg(feature = "wgpu")] pub fn is_empty(&self) -> bool { let data = self.data(); data.width() == 0 || data.height() == 0 } + #[cfg(not(feature = "wgpu"))] + pub fn is_empty(&self) -> bool { + true + } } + #[cfg(feature = "wgpu")] impl Deref for Raster { type Target = wgpu::Texture; @@ -110,6 +119,7 @@ impl Deref for Raster { self.data() } } + pub type RasterDataTable = Instances>; // TODO: Make this not dupliated From 354a68911e787a64233ad38498c8e572dcfd343d Mon Sep 17 00:00:00 2001 From: Keavon Chambers Date: Fri, 4 Jul 2025 04:58:52 -0700 Subject: [PATCH 2/4] Improve hotkeys with canonical flag, more industry-standard mappings, and fix GRS menu bar labels (#2827) --- editor/src/dispatcher.rs | 7 +- .../input_mapper_message_handler.rs | 97 +++++++++++-------- .../messages/input_mapper/input_mappings.rs | 28 +++--- .../key_mapping_message_handler.rs | 2 +- .../input_mapper/utility_types/macros.rs | 24 +++-- .../input_mapper/utility_types/misc.rs | 36 ++----- .../messages/layout/layout_message_handler.rs | 6 +- .../layout/utility_types/layout_widget.rs | 4 +- .../utility_types/widgets/menu_widgets.rs | 2 +- editor/src/messages/tool/tool_message.rs | 8 +- .../src/messages/tool/tool_message_handler.rs | 28 +++--- .../transform_layer_message.rs | 3 +- .../transform_layer_message_handler.rs | 48 ++++----- editor/src/test_utils.rs | 4 +- frontend/wasm/src/editor_api.rs | 6 +- 15 files changed, 152 insertions(+), 151 deletions(-) diff --git a/editor/src/dispatcher.rs b/editor/src/dispatcher.rs index 5259e9144..809bf4861 100644 --- a/editor/src/dispatcher.rs +++ b/editor/src/dispatcher.rs @@ -319,12 +319,11 @@ impl Dispatcher { })) } - /// Logs a message that is about to be executed, - /// either as a tree with a discriminant or the entire payload (depending on settings) + /// Logs a message that is about to be executed, either as a tree + /// with a discriminant or the entire payload (depending on settings) fn log_message(&self, message: &Message, queues: &[VecDeque], message_logging_verbosity: MessageLoggingVerbosity) { let discriminant = MessageDiscriminant::from(message); - let is_blocked = DEBUG_MESSAGE_BLOCK_LIST.iter().any(|&blocked_discriminant| discriminant == blocked_discriminant) - || DEBUG_MESSAGE_ENDING_BLOCK_LIST.iter().any(|blocked_name| discriminant.local_name().ends_with(blocked_name)); + let is_blocked = DEBUG_MESSAGE_BLOCK_LIST.contains(&discriminant) || DEBUG_MESSAGE_ENDING_BLOCK_LIST.iter().any(|blocked_name| discriminant.local_name().ends_with(blocked_name)); if !is_blocked { match message_logging_verbosity { diff --git a/editor/src/messages/input_mapper/input_mapper_message_handler.rs b/editor/src/messages/input_mapper/input_mapper_message_handler.rs index 744f86d97..eba386721 100644 --- a/editor/src/messages/input_mapper/input_mapper_message_handler.rs +++ b/editor/src/messages/input_mapper/input_mapper_message_handler.rs @@ -1,6 +1,7 @@ use super::utility_types::input_keyboard::KeysGroup; use super::utility_types::misc::Mapping; use crate::messages::input_mapper::utility_types::input_keyboard::{self, Key}; +use crate::messages::input_mapper::utility_types::misc::MappingEntry; use crate::messages::portfolio::utility_types::KeyboardPlatformLayout; use crate::messages::prelude::*; use std::fmt::Write; @@ -47,12 +48,12 @@ impl InputMapperMessageHandler { ma.map(|a| ((i as u8).try_into().unwrap(), a)) }) .for_each(|(k, a): (Key, _)| { - let _ = write!(output, "{}: {}, ", k.to_discriminant().local_name(), a.local_name().split('.').last().unwrap()); + let _ = write!(output, "{}: {}, ", k.to_discriminant().local_name(), a.local_name().split('.').next_back().unwrap()); }); output.replace("Key", "") } - pub fn action_input_mapping(&self, action_to_find: &MessageDiscriminant) -> Vec { + pub fn action_input_mapping(&self, action_to_find: &MessageDiscriminant) -> Option { let all_key_mapping_entries = std::iter::empty() .chain(self.mapping.key_up.iter()) .chain(self.mapping.key_down.iter()) @@ -66,55 +67,65 @@ impl InputMapperMessageHandler { // Filter for the desired message let found_actions = all_mapping_entries.filter(|entry| entry.action.to_discriminant() == *action_to_find); + // Get the `Key` for this platform's accelerator key let keyboard_layout = || GLOBAL_PLATFORM.get().copied().unwrap_or_default().as_keyboard_platform_layout(); let platform_accel_key = match keyboard_layout() { KeyboardPlatformLayout::Standard => Key::Control, KeyboardPlatformLayout::Mac => Key::Command, }; + let entry_to_key = |entry: &MappingEntry| { + // Get the modifier keys for the entry (and convert them to Key) + let mut keys = entry + .modifiers + .iter() + .map(|i| { + // TODO: Use a safe solution eventually + assert!( + i < input_keyboard::NUMBER_OF_KEYS, + "Attempting to convert a Key with enum index {i}, which is larger than the number of Key enums", + ); + (i as u8).try_into().unwrap() + }) + .collect::>(); + + // Append the key button for the entry + use InputMapperMessage as IMM; + match entry.input { + IMM::KeyDown(key) | IMM::KeyUp(key) | IMM::KeyDownNoRepeat(key) | IMM::KeyUpNoRepeat(key) => keys.push(key), + _ => (), + } + + keys.sort_by(|&a, &b| { + // Order according to platform guidelines mentioned at https://ux.stackexchange.com/questions/58185/normative-ordering-for-modifier-key-combinations + const ORDER: [Key; 4] = [Key::Control, Key::Alt, Key::Shift, Key::Command]; + + // Treat the `Accel` virtual key as the platform's accel key for sorting comparison purposes + let a = if a == Key::Accel { platform_accel_key } else { a }; + let b = if b == Key::Accel { platform_accel_key } else { b }; + + // Find where the keys are in the order, or put them at the end if they're not found + let a = ORDER.iter().position(|&key| key == a).unwrap_or(ORDER.len()); + let b = ORDER.iter().position(|&key| key == b).unwrap_or(ORDER.len()); + + // Compare the positions of both keys + a.cmp(&b) + }); + + KeysGroup(keys) + }; + + // If a canonical key combination is found, return it + if let Some(canonical) = found_actions.clone().find(|entry| entry.canonical).map(entry_to_key) { + return Some(canonical); + } + // Find the key combinations for all keymaps matching the desired action assert!(std::mem::size_of::() >= std::mem::size_of::()); - found_actions - .map(|entry| { - // Get the modifier keys for the entry (and convert them to Key) - let mut keys = entry - .modifiers - .iter() - .map(|i| { - // TODO: Use a safe solution eventually - assert!( - i < input_keyboard::NUMBER_OF_KEYS, - "Attempting to convert a Key with enum index {i}, which is larger than the number of Key enums", - ); - (i as u8).try_into().unwrap() - }) - .collect::>(); + let mut key_sequences = found_actions.map(entry_to_key).collect::>(); - // Append the key button for the entry - use InputMapperMessage as IMM; - match entry.input { - IMM::KeyDown(key) | IMM::KeyUp(key) | IMM::KeyDownNoRepeat(key) | IMM::KeyUpNoRepeat(key) => keys.push(key), - _ => (), - } - - keys.sort_by(|&a, &b| { - // Order according to platform guidelines mentioned at https://ux.stackexchange.com/questions/58185/normative-ordering-for-modifier-key-combinations - const ORDER: [Key; 4] = [Key::Control, Key::Alt, Key::Shift, Key::Command]; - - // Treat the `Accel` virtual key as the platform's accel key for sorting comparison purposes - let a = if a == Key::Accel { platform_accel_key } else { a }; - let b = if b == Key::Accel { platform_accel_key } else { b }; - - // Find where the keys are in the order, or put them at the end if they're not found - let a = ORDER.iter().position(|&key| key == a).unwrap_or(ORDER.len()); - let b = ORDER.iter().position(|&key| key == b).unwrap_or(ORDER.len()); - - // Compare the positions of both keys - a.cmp(&b) - }); - - KeysGroup(keys) - }) - .collect::>() + // Return the shortest key sequence, if any + key_sequences.sort_by_key(|keys| keys.0.len()); + key_sequences.first().cloned() } } diff --git a/editor/src/messages/input_mapper/input_mappings.rs b/editor/src/messages/input_mapper/input_mappings.rs index d24393031..6bb703cd0 100644 --- a/editor/src/messages/input_mapper/input_mappings.rs +++ b/editor/src/messages/input_mapper/input_mappings.rs @@ -8,7 +8,6 @@ use crate::messages::input_mapper::utility_types::misc::{KeyMappingEntries, Mapp use crate::messages::portfolio::document::node_graph::utility_types::Direction; use crate::messages::portfolio::document::utility_types::clipboards::Clipboard; use crate::messages::portfolio::document::utility_types::misc::GroupFolderType; -use crate::messages::portfolio::document::utility_types::transformation::TransformType; use crate::messages::prelude::*; use crate::messages::tool::tool_messages::brush_tool::BrushToolMessageOptionsUpdate; use crate::messages::tool::tool_messages::select_tool::SelectToolPointerKeys; @@ -221,7 +220,8 @@ pub fn input_mappings() -> Mapping { entry!(PointerMove; refresh_keys=[KeyC, Space, Control, Shift, Alt], action_dispatch=PathToolMessage::PointerMove { toggle_colinear: KeyC, equidistant: Alt, move_anchor_with_handles: Space, snap_angle: Shift, lock_angle: Control, delete_segment: Alt, break_colinear_molding: Alt }), entry!(KeyDown(Delete); action_dispatch=PathToolMessage::Delete), entry!(KeyDown(KeyA); modifiers=[Accel], action_dispatch=PathToolMessage::SelectAllAnchors), - entry!(KeyDown(KeyA); modifiers=[Accel, Shift], action_dispatch=PathToolMessage::DeselectAllPoints), + entry!(KeyDown(KeyA); modifiers=[Accel, Shift], canonical, action_dispatch=PathToolMessage::DeselectAllPoints), + entry!(KeyDown(KeyA); modifiers=[Alt], action_dispatch=PathToolMessage::DeselectAllPoints), entry!(KeyDown(Backspace); action_dispatch=PathToolMessage::Delete), entry!(KeyUp(MouseLeft); action_dispatch=PathToolMessage::DragStop { extend_selection: Shift, shrink_selection: Alt }), entry!(KeyDown(Enter); action_dispatch=PathToolMessage::Enter { extend_selection: Shift, shrink_selection: Alt }), @@ -313,9 +313,10 @@ pub fn input_mappings() -> Mapping { entry!(KeyDown(KeyE); action_dispatch=ToolMessage::ActivateToolShapeEllipse), entry!(KeyDown(KeyY); action_dispatch=ToolMessage::ActivateToolShape), entry!(KeyDown(KeyB); action_dispatch=ToolMessage::ActivateToolBrush), - entry!(KeyDown(KeyX); modifiers=[Accel, Shift], action_dispatch=ToolMessage::ResetColors), + entry!(KeyDown(KeyD); action_dispatch=ToolMessage::ResetColors), entry!(KeyDown(KeyX); modifiers=[Shift], action_dispatch=ToolMessage::SwapColors), - entry!(KeyDown(KeyC); modifiers=[Alt], action_dispatch=ToolMessage::SelectRandomPrimaryColor), + entry!(KeyDown(KeyC); modifiers=[Alt], action_dispatch=ToolMessage::SelectRandomWorkingColor { primary: true }), + entry!(KeyDown(KeyC); modifiers=[Alt, Shift], action_dispatch=ToolMessage::SelectRandomWorkingColor { primary: false }), // // DocumentMessage entry!(KeyDown(Space); modifiers=[Control], action_dispatch=DocumentMessage::GraphViewOverlayToggle), @@ -327,20 +328,21 @@ pub fn input_mappings() -> Mapping { entry!(KeyDown(KeyH); modifiers=[Accel], action_dispatch=DocumentMessage::ToggleSelectedVisibility), entry!(KeyDown(KeyL); modifiers=[Accel], action_dispatch=DocumentMessage::ToggleSelectedLocked), entry!(KeyDown(KeyG); modifiers=[Alt], action_dispatch=DocumentMessage::ToggleGridVisibility), - entry!(KeyDown(KeyZ); modifiers=[Accel, Shift], action_dispatch=DocumentMessage::Redo), + entry!(KeyDown(KeyZ); modifiers=[Accel, Shift], canonical, action_dispatch=DocumentMessage::Redo), entry!(KeyDown(KeyY); modifiers=[Accel], action_dispatch=DocumentMessage::Redo), entry!(KeyDown(KeyZ); modifiers=[Accel], action_dispatch=DocumentMessage::Undo), entry!(KeyDown(KeyA); modifiers=[Accel], action_dispatch=DocumentMessage::SelectAllLayers), - entry!(KeyDown(KeyA); modifiers=[Accel, Shift], action_dispatch=DocumentMessage::DeselectAllLayers), + entry!(KeyDown(KeyA); modifiers=[Accel, Shift], canonical, action_dispatch=DocumentMessage::DeselectAllLayers), + entry!(KeyDown(KeyA); modifiers=[Alt], action_dispatch=DocumentMessage::DeselectAllLayers), entry!(KeyDown(KeyS); modifiers=[Accel], action_dispatch=DocumentMessage::SaveDocument), - entry!(KeyDown(KeyD); modifiers=[Accel], action_dispatch=DocumentMessage::DuplicateSelectedLayers), + entry!(KeyDown(KeyD); modifiers=[Accel], canonical, action_dispatch=DocumentMessage::DuplicateSelectedLayers), entry!(KeyDown(KeyJ); modifiers=[Accel], action_dispatch=DocumentMessage::DuplicateSelectedLayers), entry!(KeyDown(KeyG); modifiers=[Accel], action_dispatch=DocumentMessage::GroupSelectedLayers { group_folder_type: GroupFolderType::Layer }), entry!(KeyDown(KeyG); modifiers=[Accel, Shift], action_dispatch=DocumentMessage::UngroupSelectedLayers), entry!(KeyDown(KeyN); modifiers=[Accel, Shift], action_dispatch=DocumentMessage::CreateEmptyFolder), - entry!(KeyDown(Backslash); modifiers=[Alt], action_dispatch=DocumentMessage::SelectParentLayer), - entry!(KeyDown(BracketLeft); modifiers=[Alt], action_dispatch=DocumentMessage::SelectionStepBack), - entry!(KeyDown(BracketRight); modifiers=[Alt], action_dispatch=DocumentMessage::SelectionStepForward), + entry!(KeyDown(Escape); modifiers=[Shift], action_dispatch=DocumentMessage::SelectParentLayer), + entry!(KeyDown(BracketLeft); modifiers=[Alt], canonical, action_dispatch=DocumentMessage::SelectionStepBack), + entry!(KeyDown(BracketRight); modifiers=[Alt], canonical, action_dispatch=DocumentMessage::SelectionStepForward), entry!(KeyDown(MouseBack); action_dispatch=DocumentMessage::SelectionStepBack), entry!(KeyDown(MouseForward); action_dispatch=DocumentMessage::SelectionStepForward), entry!(KeyDown(Digit0); modifiers=[Accel], action_dispatch=DocumentMessage::ZoomCanvasToFitAll), @@ -376,9 +378,9 @@ pub fn input_mappings() -> Mapping { entry!(KeyDown(ArrowRight); action_dispatch=DocumentMessage::NudgeSelectedLayers { delta_x: NUDGE_AMOUNT, delta_y: 0., resize: Alt, resize_opposite_corner: Control }), // // TransformLayerMessage - entry!(KeyDown(KeyG); action_dispatch=TransformLayerMessage::BeginGRS { transform_type: TransformType::Grab }), - entry!(KeyDown(KeyR); action_dispatch=TransformLayerMessage::BeginGRS { transform_type: TransformType::Rotate }), - entry!(KeyDown(KeyS); action_dispatch=TransformLayerMessage::BeginGRS { transform_type: TransformType::Scale }), + entry!(KeyDown(KeyG); action_dispatch=TransformLayerMessage::BeginGrab), + entry!(KeyDown(KeyR); action_dispatch=TransformLayerMessage::BeginRotate), + entry!(KeyDown(KeyS); action_dispatch=TransformLayerMessage::BeginScale), entry!(KeyDown(Digit0); action_dispatch=TransformLayerMessage::TypeDigit { digit: 0 }), entry!(KeyDown(Digit1); action_dispatch=TransformLayerMessage::TypeDigit { digit: 1 }), entry!(KeyDown(Digit2); action_dispatch=TransformLayerMessage::TypeDigit { digit: 2 }), diff --git a/editor/src/messages/input_mapper/key_mapping/key_mapping_message_handler.rs b/editor/src/messages/input_mapper/key_mapping/key_mapping_message_handler.rs index 0378bf0cc..53c626bf5 100644 --- a/editor/src/messages/input_mapper/key_mapping/key_mapping_message_handler.rs +++ b/editor/src/messages/input_mapper/key_mapping/key_mapping_message_handler.rs @@ -25,7 +25,7 @@ impl MessageHandler> for KeyMapping } impl KeyMappingMessageHandler { - pub fn action_input_mapping(&self, action_to_find: &MessageDiscriminant) -> Vec { + pub fn action_input_mapping(&self, action_to_find: &MessageDiscriminant) -> Option { self.mapping_handler.action_input_mapping(action_to_find) } } diff --git a/editor/src/messages/input_mapper/utility_types/macros.rs b/editor/src/messages/input_mapper/utility_types/macros.rs index 8371f2585..3fabbbeb2 100644 --- a/editor/src/messages/input_mapper/utility_types/macros.rs +++ b/editor/src/messages/input_mapper/utility_types/macros.rs @@ -24,44 +24,54 @@ macro_rules! modifiers { /// Each handler adds or removes actions in the form of message discriminants. Here, we tie an input condition (such as a hotkey) to an action's full message. /// When an action is currently available, and the user enters that input, the action's message is dispatched on the message bus. macro_rules! entry { + // Pattern with canonical parameter + ($input:expr_2021; $(modifiers=[$($modifier:ident),*],)? $(refresh_keys=[$($refresh:ident),* $(,)?],)? canonical, action_dispatch=$action_dispatch:expr_2021$(,)?) => { + entry!($input; $($($modifier),*)?; $($($refresh),*)?; $action_dispatch; true) + }; + + // Pattern without canonical parameter ($input:expr_2021; $(modifiers=[$($modifier:ident),*],)? $(refresh_keys=[$($refresh:ident),* $(,)?],)? action_dispatch=$action_dispatch:expr_2021$(,)?) => { + entry!($input; $($($modifier),*)?; $($($refresh),*)?; $action_dispatch; false) + }; + + // Implementation macro to avoid code duplication + ($input:expr; $($modifier:ident),*; $($refresh:ident),*; $action_dispatch:expr; $canonical:expr) => { &[&[ // Cause the `action_dispatch` message to be sent when the specified input occurs. MappingEntry { action: $action_dispatch.into(), input: $input, - modifiers: modifiers!($($($modifier),*)?), + modifiers: modifiers!($($modifier),*), + canonical: $canonical, }, // Also cause the `action_dispatch` message to be sent when any of the specified refresh keys change. - // - // For example, a snapping state bound to the Shift key may change if the user presses or releases that key. - // In that case, we want to dispatch the action's message even though the pointer didn't necessarily move so - // the input handler can update the snapping state without making the user move the mouse to see the change. - $( $( MappingEntry { action: $action_dispatch.into(), input: InputMapperMessage::KeyDown(Key::$refresh), modifiers: modifiers!(), + canonical: $canonical, }, MappingEntry { action: $action_dispatch.into(), input: InputMapperMessage::KeyUp(Key::$refresh), modifiers: modifiers!(), + canonical: $canonical, }, MappingEntry { action: $action_dispatch.into(), input: InputMapperMessage::KeyDownNoRepeat(Key::$refresh), modifiers: modifiers!(), + canonical: $canonical, }, MappingEntry { action: $action_dispatch.into(), input: InputMapperMessage::KeyUpNoRepeat(Key::$refresh), modifiers: modifiers!(), + canonical: $canonical, }, )* - )* ]] }; } diff --git a/editor/src/messages/input_mapper/utility_types/misc.rs b/editor/src/messages/input_mapper/utility_types/misc.rs index 461734987..0c9a22052 100644 --- a/editor/src/messages/input_mapper/utility_types/misc.rs +++ b/editor/src/messages/input_mapper/utility_types/misc.rs @@ -1,4 +1,4 @@ -use super::input_keyboard::{Key, KeysGroup, LayoutKeysGroup, all_required_modifiers_pressed}; +use super::input_keyboard::{KeysGroup, LayoutKeysGroup, all_required_modifiers_pressed}; use crate::messages::input_mapper::key_mapping::MappingVariant; use crate::messages::input_mapper::utility_types::input_keyboard::{KeyStates, NUMBER_OF_KEYS}; use crate::messages::input_mapper::utility_types::input_mouse::NUMBER_OF_MOUSE_BUTTONS; @@ -120,6 +120,8 @@ pub struct MappingEntry { pub input: InputMapperMessage, /// Any additional keys that must be also pressed for this input mapping to match pub modifiers: KeyStates, + /// True indicates that this takes priority as the labeled hotkey shown in UI menus and tooltips instead of an alternate binding for the same action + pub canonical: bool, } #[derive(Debug, Clone, Eq, PartialEq, serde::Serialize, serde::Deserialize, specta::Type)] @@ -130,36 +132,12 @@ pub enum ActionKeys { } impl ActionKeys { - pub fn to_keys(&mut self, action_input_mapping: &impl Fn(&MessageDiscriminant) -> Vec) -> String { + pub fn to_keys(&mut self, action_input_mapping: &impl Fn(&MessageDiscriminant) -> Option) -> String { match self { Self::Action(action) => { - // Take the shortest sequence of keys - let mut key_sequences = action_input_mapping(action); - key_sequences.sort_by_key(|keys| keys.0.len()); - let mut secondary_key_sequence = key_sequences.get(1).cloned(); - let mut key_sequence = key_sequences.get_mut(0); - - // TODO: Replace this exception with a per-action choice of canonical hotkey - if let Some(key_sequence) = &mut key_sequence { - if key_sequence.0.as_slice() == [Key::MouseBack] { - if let Some(replacement) = &mut secondary_key_sequence { - std::mem::swap(*key_sequence, replacement); - } - } - } - if let Some(key_sequence) = &mut key_sequence { - if key_sequence.0.as_slice() == [Key::MouseForward] { - if let Some(replacement) = &mut secondary_key_sequence { - std::mem::swap(*key_sequence, replacement); - } - } - } - - if let Some(keys) = key_sequence { - let mut taken_keys = KeysGroup::default(); - std::mem::swap(keys, &mut taken_keys); - let description = taken_keys.to_string(); - *self = Self::Keys(taken_keys.into()); + if let Some(keys) = action_input_mapping(action) { + let description = keys.to_string(); + *self = Self::Keys(keys.into()); description } else { *self = Self::Keys(KeysGroup::default().into()); diff --git a/editor/src/messages/layout/layout_message_handler.rs b/editor/src/messages/layout/layout_message_handler.rs index 9bb786d81..ee8e506ca 100644 --- a/editor/src/messages/layout/layout_message_handler.rs +++ b/editor/src/messages/layout/layout_message_handler.rs @@ -342,7 +342,7 @@ impl LayoutMessageHandler { } } -impl Vec> MessageHandler for LayoutMessageHandler { +impl Option> MessageHandler for LayoutMessageHandler { fn process_message(&mut self, message: LayoutMessage, responses: &mut std::collections::VecDeque, action_input_mapping: F) { match message { LayoutMessage::ResendActiveWidget { layout_target, widget_id } => { @@ -385,7 +385,7 @@ impl LayoutMessageHandler { layout_target: LayoutTarget, new_layout: Layout, responses: &mut VecDeque, - action_input_mapping: &impl Fn(&MessageDiscriminant) -> Vec, + action_input_mapping: &impl Fn(&MessageDiscriminant) -> Option, ) { match new_layout { Layout::WidgetLayout(_) => { @@ -419,7 +419,7 @@ impl LayoutMessageHandler { } /// Send a diff to the frontend based on the layout target. - fn send_diff(&self, mut diff: Vec, layout_target: LayoutTarget, responses: &mut VecDeque, action_input_mapping: &impl Fn(&MessageDiscriminant) -> Vec) { + fn send_diff(&self, mut diff: Vec, layout_target: LayoutTarget, responses: &mut VecDeque, action_input_mapping: &impl Fn(&MessageDiscriminant) -> Option) { diff.iter_mut().for_each(|diff| diff.new_value.apply_keyboard_shortcut(action_input_mapping)); let message = match layout_target { diff --git a/editor/src/messages/layout/utility_types/layout_widget.rs b/editor/src/messages/layout/utility_types/layout_widget.rs index 8b576c595..8802915e4 100644 --- a/editor/src/messages/layout/utility_types/layout_widget.rs +++ b/editor/src/messages/layout/utility_types/layout_widget.rs @@ -109,7 +109,7 @@ pub enum Layout { } impl Layout { - pub fn unwrap_menu_layout(self, action_input_mapping: &impl Fn(&MessageDiscriminant) -> Vec) -> MenuLayout { + pub fn unwrap_menu_layout(self, action_input_mapping: &impl Fn(&MessageDiscriminant) -> Option) -> MenuLayout { if let Self::MenuLayout(mut menu) = self { menu.layout .iter_mut() @@ -589,7 +589,7 @@ pub enum DiffUpdate { impl DiffUpdate { /// Append the keyboard shortcut to the tooltip where applicable - pub fn apply_keyboard_shortcut(&mut self, action_input_mapping: &impl Fn(&MessageDiscriminant) -> Vec) { + pub fn apply_keyboard_shortcut(&mut self, action_input_mapping: &impl Fn(&MessageDiscriminant) -> Option) { // Function used multiple times later in this code block to convert `ActionKeys::Action` to `ActionKeys::Keys` and append its shortcut to the tooltip let apply_shortcut_to_tooltip = |tooltip_shortcut: &mut ActionKeys, tooltip: &mut String| { let shortcut_text = tooltip_shortcut.to_keys(action_input_mapping); diff --git a/editor/src/messages/layout/utility_types/widgets/menu_widgets.rs b/editor/src/messages/layout/utility_types/widgets/menu_widgets.rs index db6de59e1..e96009d06 100644 --- a/editor/src/messages/layout/utility_types/widgets/menu_widgets.rs +++ b/editor/src/messages/layout/utility_types/widgets/menu_widgets.rs @@ -12,7 +12,7 @@ impl MenuBarEntryChildren { Self(Vec::new()) } - pub fn fill_in_shortcut_actions_with_keys(&mut self, action_input_mapping: &impl Fn(&MessageDiscriminant) -> Vec) { + pub fn fill_in_shortcut_actions_with_keys(&mut self, action_input_mapping: &impl Fn(&MessageDiscriminant) -> Option) { let entries = self.0.iter_mut().flatten(); for entry in entries { diff --git a/editor/src/messages/tool/tool_message.rs b/editor/src/messages/tool/tool_message.rs index 6c6178b46..c281149a4 100644 --- a/editor/src/messages/tool/tool_message.rs +++ b/editor/src/messages/tool/tool_message.rs @@ -80,12 +80,12 @@ pub enum ToolMessage { Redo, RefreshToolOptions, ResetColors, - SelectPrimaryColor { + SelectWorkingColor { color: Color, + primary: bool, }, - SelectRandomPrimaryColor, - SelectSecondaryColor { - color: Color, + SelectRandomWorkingColor { + primary: bool, }, SwapColors, Undo, diff --git a/editor/src/messages/tool/tool_message_handler.rs b/editor/src/messages/tool/tool_message_handler.rs index 6a1f8fef8..941349548 100644 --- a/editor/src/messages/tool/tool_message_handler.rs +++ b/editor/src/messages/tool/tool_message_handler.rs @@ -240,14 +240,8 @@ impl MessageHandler> for ToolMessageHandler { document_data.update_working_colors(responses); // TODO: Make this an event } - ToolMessage::SelectPrimaryColor { color } => { - let document_data = &mut self.tool_state.document_tool_data; - document_data.primary_color = color; - - document_data.update_working_colors(responses); // TODO: Make this an event - } - ToolMessage::SelectRandomPrimaryColor => { - // Select a random primary color (rgba) based on an UUID + ToolMessage::SelectRandomWorkingColor { primary } => { + // Select a random working color (RGBA) based on an UUID let document_data = &mut self.tool_state.document_tool_data; let random_number = generate_uuid(); @@ -255,13 +249,23 @@ impl MessageHandler> for ToolMessageHandler { let g = (random_number >> 8) as u8; let b = random_number as u8; let random_color = Color::from_rgba8_srgb(r, g, b, 255); - document_data.primary_color = random_color; + + if primary { + document_data.primary_color = random_color; + } else { + document_data.secondary_color = random_color; + } document_data.update_working_colors(responses); // TODO: Make this an event } - ToolMessage::SelectSecondaryColor { color } => { + ToolMessage::SelectWorkingColor { color, primary } => { let document_data = &mut self.tool_state.document_tool_data; - document_data.secondary_color = color; + + if primary { + document_data.primary_color = color; + } else { + document_data.secondary_color = color; + } document_data.update_working_colors(responses); // TODO: Make this an event } @@ -340,7 +344,7 @@ impl MessageHandler> for ToolMessageHandler { ActivateToolBrush, - SelectRandomPrimaryColor, + SelectRandomWorkingColor, ResetColors, SwapColors, Undo, diff --git a/editor/src/messages/tool/transform_layer/transform_layer_message.rs b/editor/src/messages/tool/transform_layer/transform_layer_message.rs index dfc45c1e0..e68d2702c 100644 --- a/editor/src/messages/tool/transform_layer/transform_layer_message.rs +++ b/editor/src/messages/tool/transform_layer/transform_layer_message.rs @@ -12,10 +12,11 @@ pub enum TransformLayerMessage { // Messages ApplyTransformOperation { final_transform: bool }, + BeginTransformOperation { operation: TransformType }, BeginGrab, BeginRotate, BeginScale, - BeginGRS { transform_type: TransformType }, + BeginGRS { operation: TransformType }, BeginGrabPen { last_point: DVec2, handle: DVec2 }, BeginRotatePen { last_point: DVec2, handle: DVec2 }, BeginScalePen { last_point: DVec2, handle: DVec2 }, diff --git a/editor/src/messages/tool/transform_layer/transform_layer_message_handler.rs b/editor/src/messages/tool/transform_layer/transform_layer_message_handler.rs index 104f1a66a..e449bbd3b 100644 --- a/editor/src/messages/tool/transform_layer/transform_layer_message_handler.rs +++ b/editor/src/messages/tool/transform_layer/transform_layer_message_handler.rs @@ -368,6 +368,15 @@ impl MessageHandler> for TransformLayer responses.add(OverlaysMessage::RemoveProvider(TRANSFORM_GRS_OVERLAY_PROVIDER)); } } + TransformLayerMessage::BeginTransformOperation { operation } => { + begin_operation(self.transform_operation, &mut self.typing, &mut self.mouse_position, &mut self.start_mouse, &mut self.initial_transform); + self.transform_operation = match operation { + TransformType::Grab => TransformOperation::Grabbing(Default::default()), + TransformType::Rotate => TransformOperation::Rotating(Default::default()), + TransformType::Scale => TransformOperation::Scaling(Default::default()), + }; + self.layer_bounding_box = selected.bounding_box(); + } TransformLayerMessage::BeginGrabPen { last_point, handle } | TransformLayerMessage::BeginRotatePen { last_point, handle } | TransformLayerMessage::BeginScalePen { last_point, handle } => { self.typing.clear(); @@ -402,9 +411,10 @@ impl MessageHandler> for TransformLayer increments_key: INCREMENTS_KEY, }); } - TransformLayerMessage::BeginGRS { transform_type } => { + TransformLayerMessage::BeginGRS { operation: transform_type } => { let selected_points: Vec<&ManipulatorPointId> = shape_editor.selected_points().collect(); let selected_segments = shape_editor.selected_segments().collect::>(); + if (using_path_tool && selected_points.is_empty() && selected_segments.is_empty()) || (!using_path_tool && !using_select_tool && !using_pen_tool && !using_shape_tool) || selected_layers.is_empty() @@ -439,42 +449,24 @@ impl MessageHandler> for TransformLayer } } + self.local = false; + self.operation_count += 1; + let chain_operation = self.transform_operation != TransformOperation::None; if chain_operation { responses.add(TransformLayerMessage::ApplyTransformOperation { final_transform: false }); } else { responses.add(OverlaysMessage::AddProvider(TRANSFORM_GRS_OVERLAY_PROVIDER)); } - - let response = match transform_type { - TransformType::Grab => TransformLayerMessage::BeginGrab, - TransformType::Rotate => TransformLayerMessage::BeginRotate, - TransformType::Scale => TransformLayerMessage::BeginScale, - }; - - self.local = false; - self.operation_count += 1; - responses.add(response); + responses.add(TransformLayerMessage::BeginTransformOperation { operation: transform_type }); responses.add(TransformLayerMessage::PointerMove { slow_key: SLOW_KEY, increments_key: INCREMENTS_KEY, }); } - TransformLayerMessage::BeginGrab => { - begin_operation(self.transform_operation, &mut self.typing, &mut self.mouse_position, &mut self.start_mouse, &mut self.initial_transform); - self.transform_operation = TransformOperation::Grabbing(Default::default()); - self.layer_bounding_box = selected.bounding_box(); - } - TransformLayerMessage::BeginRotate => { - begin_operation(self.transform_operation, &mut self.typing, &mut self.mouse_position, &mut self.start_mouse, &mut self.initial_transform); - self.transform_operation = TransformOperation::Rotating(Default::default()); - self.layer_bounding_box = selected.bounding_box(); - } - TransformLayerMessage::BeginScale => { - begin_operation(self.transform_operation, &mut self.typing, &mut self.mouse_position, &mut self.start_mouse, &mut self.initial_transform); - self.transform_operation = TransformOperation::Scaling(Default::default()); - self.layer_bounding_box = selected.bounding_box(); - } + TransformLayerMessage::BeginGrab => responses.add_front(TransformLayerMessage::BeginGRS { operation: TransformType::Grab }), + TransformLayerMessage::BeginRotate => responses.add_front(TransformLayerMessage::BeginGRS { operation: TransformType::Rotate }), + TransformLayerMessage::BeginScale => responses.add_front(TransformLayerMessage::BeginGRS { operation: TransformType::Scale }), TransformLayerMessage::CancelTransformOperation => { if using_pen_tool { self.typing.clear(); @@ -707,7 +699,9 @@ impl MessageHandler> for TransformLayer fn actions(&self) -> ActionList { let mut common = actions!(TransformLayerMessageDiscriminant; - BeginGRS, + BeginGrab, + BeginRotate, + BeginScale, ); if self.transform_operation != TransformOperation::None { diff --git a/editor/src/test_utils.rs b/editor/src/test_utils.rs index 683315c76..a8c9691c8 100644 --- a/editor/src/test_utils.rs +++ b/editor/src/test_utils.rs @@ -227,11 +227,11 @@ impl EditorTestUtils { } pub async fn select_primary_color(&mut self, color: Color) { - self.handle_message(Message::Tool(ToolMessage::SelectPrimaryColor { color })).await; + self.handle_message(Message::Tool(ToolMessage::SelectWorkingColor { color, primary: true })).await; } pub async fn select_secondary_color(&mut self, color: Color) { - self.handle_message(Message::Tool(ToolMessage::SelectSecondaryColor { color })).await; + self.handle_message(Message::Tool(ToolMessage::SelectWorkingColor { color, primary: false })).await; } pub async fn create_raster_image(&mut self, image: graphene_std::raster::Image, mouse: Option<(f64, f64)>) { diff --git a/frontend/wasm/src/editor_api.rs b/frontend/wasm/src/editor_api.rs index 80bfd7fce..1b7d3a0b2 100644 --- a/frontend/wasm/src/editor_api.rs +++ b/frontend/wasm/src/editor_api.rs @@ -467,8 +467,9 @@ impl EditorHandle { return Err(Error::new("Invalid color").into()); }; - let message = ToolMessage::SelectPrimaryColor { + let message = ToolMessage::SelectWorkingColor { color: primary_color.to_linear_srgb(), + primary: true, }; self.dispatch(message); @@ -482,8 +483,9 @@ impl EditorHandle { return Err(Error::new("Invalid color").into()); }; - let message = ToolMessage::SelectSecondaryColor { + let message = ToolMessage::SelectWorkingColor { color: secondary_color.to_linear_srgb(), + primary: false, }; self.dispatch(message); From f57163c79567c793a14f69c5da719078ec144a93 Mon Sep 17 00:00:00 2001 From: Adam Gerhant <116332429+adamgerhant@users.noreply.github.com> Date: Fri, 4 Jul 2025 22:53:37 -0700 Subject: [PATCH 3/4] Port node graph wires to the backend and improve graph UI performance (#2795) * 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 --- editor/src/dispatcher.rs | 1 - .../preferences_dialog_message_handler.rs | 2 +- .../src/messages/frontend/frontend_message.rs | 15 +- .../document/document_message_handler.rs | 14 +- .../node_graph/document_node_definitions.rs | 302 ++--- .../document_node_derive.rs | 10 +- .../document/node_graph/node_graph_message.rs | 4 + .../node_graph/node_graph_message_handler.rs | 545 ++++----- .../document/node_graph/node_properties.rs | 442 +++---- .../document/node_graph/utility_types.rs | 56 +- .../portfolio/document/utility_types/mod.rs | 1 + .../utility_types/network_interface.rs | 1026 +++++++++++------ .../portfolio/document/utility_types/wires.rs | 589 ++++++++++ .../messages/portfolio/document_migration.rs | 404 ++----- .../portfolio/portfolio_message_handler.rs | 39 +- .../preferences/preferences_message.rs | 2 +- .../preferences_message_handler.rs | 5 +- editor/src/test_utils.rs | 9 +- frontend/src/components/views/Graph.svelte | 497 +------- frontend/src/messages.ts | 72 +- frontend/src/state-providers/node-graph.ts | 48 +- frontend/wasm/src/editor_api.rs | 1 + node-graph/gcore/src/vector/click_target.rs | 4 + node-graph/graph-craft/src/document.rs | 29 +- 24 files changed, 2098 insertions(+), 2019 deletions(-) create mode 100644 editor/src/messages/portfolio/document/utility_types/wires.rs diff --git a/editor/src/dispatcher.rs b/editor/src/dispatcher.rs index 809bf4861..a8891033e 100644 --- a/editor/src/dispatcher.rs +++ b/editor/src/dispatcher.rs @@ -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, ))), diff --git a/editor/src/messages/dialog/preferences_dialog/preferences_dialog_message_handler.rs b/editor/src/messages/dialog/preferences_dialog/preferences_dialog_message_handler.rs index d4790a4d0..6e92d60bc 100644 --- a/editor/src/messages/dialog/preferences_dialog/preferences_dialog_message_handler.rs +++ b/editor/src/messages/dialog/preferences_dialog/preferences_dialog_message_handler.rs @@ -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::*; diff --git a/editor/src/messages/frontend/frontend_message.rs b/editor/src/messages/frontend/frontend_message.rs index 7c9b90c34..c24ebc405 100644 --- a/editor/src/messages/frontend/frontend_message.rs +++ b/editor/src/messages/frontend/frontend_message.rs @@ -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, - wires: Vec, - #[serde(rename = "wiresDirectNotGridAligned")] - wires_direct_not_grid_aligned: bool, }, + UpdateVisibleNodes { + nodes: Vec, + }, + UpdateNodeGraphWires { + wires: Vec, + }, + ClearAllNodeGraphWires, UpdateNodeGraphControlBarLayout { #[serde(rename = "layoutTarget")] layout_target: LayoutTarget, diff --git a/editor/src/messages/portfolio/document/document_message_handler.rs b/editor/src/messages/portfolio/document/document_message_handler.rs index 41938131c..81f631dfe 100644 --- a/editor/src/messages/portfolio/document/document_message_handler.rs +++ b/editor/src/messages/portfolio/document/document_message_handler.rs @@ -444,6 +444,7 @@ impl MessageHandler> 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> 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> 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> 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) { 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()); diff --git a/editor/src/messages/portfolio/document/node_graph/document_node_definitions.rs b/editor/src/messages/portfolio/document/node_graph/document_node_definitions.rs index 625c1a3e0..d6fa996f1 100644 --- a/editor/src/messages/portfolio/document/node_graph/document_node_definitions.rs +++ b/editor/src/messages/portfolio/document/node_graph/document_node_definitions.rs @@ -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> { - 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::>(), - _ => 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 { ..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 { ..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 { }, ..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 { ..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 { ..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 { ..Default::default() }), ), - PropertiesRow::with_override( + InputMetadata::with_name_description_override( "Dimensions", "TODO", WidgetOverride::Vec2(Vec2InputSettings { @@ -420,7 +408,7 @@ fn static_nodes() -> Vec { ..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 { ..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 { ..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 { ..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 { ..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 { ..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 { ..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 { ..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 { ..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 { ..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 { ..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 { ..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 { ..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 { ..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 { ..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 { ..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 { ..Default::default() }), ), - PropertiesRow::with_override( + InputMetadata::with_name_description_override( "Line Height", "TODO", WidgetOverride::Number(NumberInputSettings { @@ -1586,7 +1574,7 @@ fn static_nodes() -> Vec { ..Default::default() }), ), - PropertiesRow::with_override( + InputMetadata::with_name_description_override( "Character Spacing", "TODO", WidgetOverride::Number(NumberInputSettings { @@ -1596,7 +1584,7 @@ fn static_nodes() -> Vec { ..Default::default() }), ), - PropertiesRow::with_override( + InputMetadata::with_name_description_override( "Max Width", "TODO", WidgetOverride::Number(NumberInputSettings { @@ -1606,7 +1594,7 @@ fn static_nodes() -> Vec { ..Default::default() }), ), - PropertiesRow::with_override( + InputMetadata::with_name_description_override( "Max Height", "TODO", WidgetOverride::Number(NumberInputSettings { @@ -1616,7 +1604,7 @@ fn static_nodes() -> Vec { ..Default::default() }), ), - PropertiesRow::with_override( + InputMetadata::with_name_description_override( "Tilt", "Faux italic", WidgetOverride::Number(NumberInputSettings { @@ -1708,9 +1696,9 @@ fn static_nodes() -> Vec { }, ..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 { ..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 { ..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 { }, ..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 { }, ..Default::default() }), - input_properties: vec![ + input_metadata: vec![ ("Vector Data", "The shape to be resampled and converted into a polyline.").into(), - Into::::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 { ..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 { ..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 { ..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 { ..Default::default() }), ), - Into::::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 { }, ..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 { ..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::() - .for_socket(ParameterWidgetsInfo::new(document_node, node_id, index, input_name, input_description, true)) - .property_row(); + let noise_type_row = enum_choice::().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::() - .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::() - .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::() - .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::() - .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 { // 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::>() .into_iter() .collect::>(); @@ -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::>() { 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 } diff --git a/editor/src/messages/portfolio/document/node_graph/document_node_definitions/document_node_derive.rs b/editor/src/messages/portfolio/document/node_graph/document_node_definitions/document_node_derive.rs index 1339621dc..8fcec559a 100644 --- a/editor/src/messages/portfolio/document/node_graph/document_node_definitions/document_node_derive.rs +++ b/editor/src/messages/portfolio/document/node_graph/document_node_definitions/document_node_derive.rs @@ -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) -> 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()], diff --git a/editor/src/messages/portfolio/document/node_graph/node_graph_message.rs b/editor/src/messages/portfolio/document/node_graph/node_graph_message.rs index d6f9a6bed..a730f47ca 100644 --- a/editor/src/messages/portfolio/document/node_graph/node_graph_message.rs +++ b/editor/src/messages/portfolio/document/node_graph/node_graph_message.rs @@ -33,6 +33,7 @@ pub enum NodeGraphMessage { node_id: Option, 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 { diff --git a/editor/src/messages/portfolio/document/node_graph/node_graph_message_handler.rs b/editor/src/messages/portfolio/document/node_graph/node_graph_message_handler.rs index 49256220c..b3f1596cd 100644 --- a/editor/src/messages/portfolio/document/node_graph/node_graph_message_handler.rs +++ b/editor/src/messages/portfolio/document/node_graph/node_graph_message_handler.rs @@ -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, /// The start of the dragged line (cannot be moved), stored in node graph coordinates pub wire_in_progress_from_connector: Option, + 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, /// 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, - // The index of the import that is being moved + /// The index of the import that is being moved reordering_import: Option, - // The index of the export that is being moved + /// The index of the export that is being moved reordering_export: Option, - // The end index of the moved port + /// The end index of the moved port end_index: Option, + /// 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, + /// 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> 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> 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> 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> 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> 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> 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> 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> 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> 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> 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> 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> 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> 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> 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::>(); + // 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::>() - .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::>(); - - 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> 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> 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> 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> 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> 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> 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> 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> 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 { - 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 { + 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::>(); - // 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::>(); + 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::>(); + 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 { @@ -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 { - 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: - // // 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) { // 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, - input_description: Option, + input_name: String, + input_description: String, ty: Type, type_source: TypeSource, valid_types: Vec, @@ -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::>() + { + // 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(), } } } diff --git a/editor/src/messages/portfolio/document/node_graph/node_properties.rs b/editor/src/messages/portfolio/document/node_graph/node_properties.rs index 8c8df09a3..717bd2e31 100644 --- a/editor/src/messages/portfolio/document/node_graph/node_properties.rs +++ b/editor/src/messages/portfolio/document/node_graph/node_properties.rs @@ -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) { ]); } -pub fn start_widgets(parameter_widgets_info: ParameterWidgetsInfo, data_type: FrontendGraphDataType) -> Vec { - start_widgets_exposable(parameter_widgets_info, data_type, true) -} - -pub fn start_widgets_exposable(parameter_widgets_info: ParameterWidgetsInfo, data_type: FrontendGraphDataType, exposable: bool) -> Vec { +pub fn start_widgets(parameter_widgets_info: ParameterWidgetsInfo) -> Vec { 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, context: &mut NodePropertiesContext, ) -> Result, Vec> { - 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 { 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 Vec { 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 Vec { 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 { 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 { 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) -> 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 { 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 { 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, Option>) { 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 Vec { - 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 Vec { - 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 Vec { - 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 Vec { 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 { 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 { 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::().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::() - .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 { 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::().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::() - .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::() - .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 { 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::() - .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::::INDEX, true, context), - "W", - "H", - " px", - Some(0.), - ); + let spacing = coordinate_widget(ParameterWidgetsInfo::new(node_id, SpacingInput::::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::::INDEX, true, context), + ParameterWidgetsInfo::new(node_id, SpacingInput::::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::() - .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 { 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 { use graphene_std::vector::generator_nodes::rectangle::*; + // Corner Radius + let mut corner_radius_row_1 = start_widgets(ParameterWidgetsInfo::new(node_id, CornerRadiusInput::::INDEX, true, context)); + 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::::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 { use graphene_std::vector::fill::*; + let mut widgets_first_row = start_widgets(ParameterWidgetsInfo::new(node_id, FillInput::::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::::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::::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::>::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::() - .for_socket(ParameterWidgetsInfo::from_index(document_node, node_id, AlignInput::INDEX, true, context)) - .property_row(); - let cap = enum_choice::() - .for_socket(ParameterWidgetsInfo::from_index(document_node, node_id, CapInput::INDEX, true, context)) - .property_row(); - let join = enum_choice::() - .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::() - .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::>::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::() + .for_socket(ParameterWidgetsInfo::new(node_id, AlignInput::INDEX, true, context)) + .property_row(); + let cap = enum_choice::().for_socket(ParameterWidgetsInfo::new(node_id, CapInput::INDEX, true, context)).property_row(); + let join = enum_choice::() + .for_socket(ParameterWidgetsInfo::new(node_id, JoinInput::INDEX, true, context)) + .property_row(); + + let 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::() + .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 { 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::() + .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::() - .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 { 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::::INDEX, true, context), - NumberInput::default(), - ); + let operand_b = number_widget(ParameterWidgetsInfo::new(node_id, OperandBInput::::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 EnumChoice { pub fn for_socket(self, parameter_info: ParameterWidgetsInfo) -> ForSocket { - 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."); diff --git a/editor/src/messages/portfolio/document/node_graph/utility_types.rs b/editor/src/messages/portfolio/document/node_graph/utility_types.rs index f09b2690e..b743cb1fc 100644 --- a/editor/src/messages/portfolio/document/node_graph/utility_types.rs +++ b/editor/src/messages/portfolio/document/node_graph/utility_types.rs @@ -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, + pub resolved_type: String, #[serde(rename = "validTypes")] pub valid_types: Vec, #[serde(rename = "connectedTo")] @@ -64,7 +64,7 @@ pub struct FrontendGraphOutput { pub name: String, pub description: String, #[serde(rename = "resolvedType")] - pub resolved_type: Option, + pub resolved_type: String, #[serde(rename = "connectedTo")] pub connected_to: Vec, } @@ -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 - } -} diff --git a/editor/src/messages/portfolio/document/utility_types/mod.rs b/editor/src/messages/portfolio/document/utility_types/mod.rs index e9ad9ae11..8bed0dbb8 100644 --- a/editor/src/messages/portfolio/document/utility_types/mod.rs +++ b/editor/src/messages/portfolio/document/utility_types/mod.rs @@ -5,3 +5,4 @@ pub mod misc; pub mod network_interface; pub mod nodes; pub mod transformation; +pub mod wires; diff --git a/editor/src/messages/portfolio/document/utility_types/network_interface.rs b/editor/src/messages/portfolio/document/utility_types/network_interface.rs index 2566650e7..65e58693f 100644 --- a/editor/src/messages/portfolio/document/utility_types/network_interface.rs +++ b/editor/src/messages/portfolio/document/utility_types/network_interface.rs @@ -5,6 +5,7 @@ use crate::consts::{EXPORTS_TO_RIGHT_EDGE_PIXEL_GAP, EXPORTS_TO_TOP_EDGE_PIXEL_G use crate::messages::portfolio::document::graph_operation::utility_types::ModifyInputsContext; use crate::messages::portfolio::document::node_graph::document_node_definitions::{DocumentNodeDefinition, resolve_document_node_type}; use crate::messages::portfolio::document::node_graph::utility_types::{Direction, FrontendClickTargets, FrontendGraphDataType, FrontendGraphInput, FrontendGraphOutput}; +use crate::messages::portfolio::document::utility_types::wires::{GraphWireStyle, WirePath, WirePathUpdate, build_vector_wire}; use crate::messages::tool::common_functionality::graph_modification_utils; use crate::messages::tool::tool_messages::tool_prelude::NumberInputMode; use bezier_rs::Subpath; @@ -311,7 +312,7 @@ impl NodeNetworkInterface { log::error!("Could not get node {node_id} in number_of_displayed_inputs"); return 0; }; - node.inputs.iter().filter(|input| input.is_exposed_to_frontend(network_path.is_empty())).count() + node.inputs.iter().filter(|input| input.is_exposed()).count() } pub fn number_of_inputs(&self, node_id: &NodeId, network_path: &[NodeId]) -> usize { @@ -456,6 +457,12 @@ impl NodeNetworkInterface { node_template } + /// Try and get the [`DocumentNodeDefinition`] for a node + pub fn get_node_definition(&self, network_path: &[NodeId], node_id: NodeId) -> Option<&DocumentNodeDefinition> { + let metadata = self.node_metadata(&node_id, network_path)?; + resolve_document_node_type(metadata.persistent_metadata.reference.as_ref()?) + } + pub fn input_from_connector(&self, input_connector: &InputConnector, network_path: &[NodeId]) -> Option<&NodeInput> { let Some(network) = self.nested_network(network_path) else { log::error!("Could not get network in input_from_connector"); @@ -473,12 +480,6 @@ impl NodeNetworkInterface { } } - /// Try and get the [`DocumentNodeDefinition`] for a node - pub fn get_node_definition(&self, network_path: &[NodeId], node_id: NodeId) -> Option<&DocumentNodeDefinition> { - let metadata = self.node_metadata(&node_id, network_path)?; - resolve_document_node_type(metadata.persistent_metadata.reference.as_ref()?) - } - /// Try and get the [`Type`] for any [`InputConnector`] based on the `self.resolved_types`. fn node_type_from_compiled(&mut self, input_connector: &InputConnector, network_path: &[NodeId]) -> Option<(Type, TypeSource)> { let (node_id, input_index) = match *input_connector { @@ -489,11 +490,8 @@ impl NodeNetworkInterface { return Some((concrete!(graphene_std::ArtboardGroupTable), TypeSource::OuterMostExportDefault)); }; - let output_type = self.output_types(encapsulating_node_id, encapsulating_node_id_path).into_iter().nth(export_index).flatten(); - if output_type.is_none() { - warn!("Could not find output type for export node"); - } - return output_type; + let output_type = self.output_type(encapsulating_node_id, export_index, encapsulating_node_id_path); + return Some(output_type); } }; let Some(node) = self.document_node(&node_id, network_path) else { @@ -699,60 +697,51 @@ impl NodeNetworkInterface { /// /// This function assumes that export indices and node IDs always exist within their respective /// collections. It will panic if these assumptions are violated. - pub fn output_types(&self, node_id: &NodeId, network_path: &[NodeId]) -> Vec> { + /// + pub fn output_type(&self, node_id: &NodeId, output_index: usize, network_path: &[NodeId]) -> (Type, TypeSource) { let Some(implementation) = self.implementation(node_id, network_path) else { - log::error!("Could not get node {node_id} in output_types"); - return Vec::new(); + log::error!("Could not get output type for node {node_id} output index {output_index}. This node is no longer supported, and needs to be upgraded."); + return (concrete!(()), TypeSource::Error("Could not get implementation")); }; - let mut output_types = Vec::new(); - // If the node is not a protonode, get types by traversing across exports until a proto node is reached. match &implementation { graph_craft::document::DocumentNodeImplementation::Network(internal_network) => { - for export in internal_network.exports.iter() { - match export { - NodeInput::Node { - node_id: nested_node_id, - output_index, - .. - } => { - let nested_output_types = self.output_types(nested_node_id, &[network_path, &[*node_id]].concat()); - let Some(nested_nodes_output_types) = nested_output_types.get(*output_index) else { - log::error!("Could not get nested nodes output in output_types"); - return Vec::new(); - }; - output_types.push(nested_nodes_output_types.clone()); - } - NodeInput::Value { tagged_value, .. } => { - output_types.push(Some((tagged_value.ty(), TypeSource::TaggedValue))); - } - - NodeInput::Network { .. } => { - // https://github.com/GraphiteEditor/Graphite/issues/1762 - log::error!("Network input type cannot be connected to export"); - return Vec::new(); - } - NodeInput::Scope(_) => todo!(), - NodeInput::Inline(_) => todo!(), - NodeInput::Reflection(_) => todo!(), + let Some(export) = internal_network.exports.get(output_index) else { + return (concrete!(()), TypeSource::Error("Could not get export index")); + }; + match export { + NodeInput::Node { + node_id: nested_node_id, + output_index, + .. + } => self.output_type(nested_node_id, *output_index, &[network_path, &[*node_id]].concat()), + NodeInput::Value { tagged_value, .. } => (tagged_value.ty(), TypeSource::TaggedValue), + NodeInput::Network { .. } => { + // let mut encapsulating_path = network_path.to_vec(); + // let encapsulating_node = encapsulating_path.pop().expect("No imports exist in document network"); + // self.input_type(&InputConnector::node(encapsulating_node, *import_index), network_path) + (concrete!(()), TypeSource::Error("Could not type from network")) } + NodeInput::Scope(_) => todo!(), + NodeInput::Inline(_) => todo!(), + NodeInput::Reflection(_) => todo!(), } } graph_craft::document::DocumentNodeImplementation::ProtoNode(protonode) => { let node_id_path = &[network_path, &[*node_id]].concat(); - let primary_output_type = self.resolved_types.types.get(node_id_path).map(|ty| (ty.output.clone(), TypeSource::Compiled)).or_else(|| { - let node_types = random_protonode_implementation(protonode)?; - Some((node_types.return_value.clone(), TypeSource::RandomProtonodeImplementation)) - }); - - output_types.push(primary_output_type); - } - graph_craft::document::DocumentNodeImplementation::Extract => { - output_types.push(Some((concrete!(()), TypeSource::Error("extract node")))); + self.resolved_types + .types + .get(node_id_path) + .map(|ty| (ty.output.clone(), TypeSource::Compiled)) + .or_else(|| { + let node_types = random_protonode_implementation(protonode)?; + Some((node_types.return_value.clone(), TypeSource::RandomProtonodeImplementation)) + }) + .unwrap_or((concrete!(()), TypeSource::Error("Could not get protonode implementation"))) } + graph_craft::document::DocumentNodeImplementation::Extract => (concrete!(()), TypeSource::Error("extract node")), } - output_types } pub fn position(&mut self, node_id: &NodeId, network_path: &[NodeId]) -> Option { @@ -782,10 +771,6 @@ impl NodeNetworkInterface { .filter_map(|(import_index, click_target)| { // Get import name from parent node metadata input, which must match the number of imports. // Empty string means to use type, or "Import + index" if type can't be determined - let properties_row = self - .encapsulating_node_metadata(network_path) - .and_then(|encapsulating_metadata| encapsulating_metadata.persistent_metadata.input_properties.get(*import_index).cloned()) - .unwrap_or_default(); let mut import_metadata = None; @@ -796,12 +781,7 @@ impl NodeNetworkInterface { let (input_type, type_source) = self.input_type(&InputConnector::node(encapsulating_node_id, *import_index), &encapsulating_path); let data_type = FrontendGraphDataType::displayed_type(&input_type, &type_source); - let input_name = properties_row.input_name.as_str(); - let import_name = if input_name.is_empty() { - input_type.clone().nested_type().to_string() - } else { - input_name.to_string() - }; + let (name, description) = self.displayed_input_name_and_description(&encapsulating_node_id, *import_index, &encapsulating_path); let connected_to = self .outward_wires(network_path) @@ -815,9 +795,9 @@ impl NodeNetworkInterface { import_metadata = Some(( FrontendGraphOutput { data_type, - name: import_name, - description: String::new(), - resolved_type: Some(format!("{input_type:?}")), + name, + description, + resolved_type: format!("{:?}", input_type), connected_to, }, click_target, @@ -835,51 +815,14 @@ impl NodeNetworkInterface { import_export_ports .input_ports .iter() - .filter_map(|(export_index, click_target)| { - let Some(network) = self.nested_network(network_path) else { - log::error!("Could not get network in frontend_exports"); - return None; - }; + .map(|(export_index, click_target)| { + let export_type = self.input_type(&InputConnector::Export(*export_index), network_path); + let data_type = FrontendGraphDataType::displayed_type(&export_type.0, &TypeSource::TaggedValue); - let Some(export) = network.exports.get(*export_index) else { - log::error!("Could not get export {export_index} in frontend_exports"); - return None; - }; - - let (frontend_data_type, input_type) = if let NodeInput::Node { node_id, output_index, .. } = export { - let output_types = self.output_types(node_id, network_path); - - if let Some((output_type, type_source)) = output_types.get(*output_index).cloned().flatten() { - (FrontendGraphDataType::displayed_type(&output_type, &type_source), Some((output_type, type_source))) - } else { - (FrontendGraphDataType::General, None) - } - } else if let NodeInput::Value { tagged_value, .. } = export { - ( - FrontendGraphDataType::displayed_type(&tagged_value.ty(), &TypeSource::TaggedValue), - Some((tagged_value.ty(), TypeSource::TaggedValue)), - ) - // TODO: Get type from parent node input when is possible - // else if let NodeInput::Network { import_type, .. } = export { - // (FrontendGraphDataType::with_type(import_type), Some(import_type.clone())) - // } - } else { - (FrontendGraphDataType::General, None) - }; - - // First import index is visually connected to the root node instead of its actual export input so previewing does not change the connection - let connected_to = if *export_index == 0 { - self.root_node(network_path).map(|root_node| OutputConnector::node(root_node.node_id, root_node.output_index)) - } else if let NodeInput::Node { node_id, output_index, .. } = export { - Some(OutputConnector::node(*node_id, *output_index)) - } else if let NodeInput::Network { import_index, .. } = export { - Some(OutputConnector::Import(*import_index)) - } else { - None - }; + let connected_to = self.upstream_output_connector(&InputConnector::Export(*export_index), network_path); // Get export name from parent node metadata input, which must match the number of exports. - // Empty string means to use type, or "Export + index" if type can't be determined + // Empty string means to use type, or "Export + index" if type is empty determined let export_name = if network_path.is_empty() { "Canvas".to_string() } else { @@ -890,24 +833,23 @@ impl NodeNetworkInterface { let export_name = if !export_name.is_empty() { export_name + } else if *export_type.0.nested_type() != concrete!(()) { + export_type.0.nested_type().to_string() } else { - input_type - .clone() - .map(|(input_type, _)| input_type.nested_type().to_string()) - .unwrap_or(format!("Export {}", export_index + 1)) + format!("Export {}", *export_index + 1) }; - Some(( + ( FrontendGraphInput { - data_type: frontend_data_type, + data_type, name: export_name, description: String::new(), - resolved_type: input_type.map(|(export_type, _source)| format!("{export_type:?}")), + resolved_type: format!("{:?}", export_type.0), valid_types: self.valid_input_types(&InputConnector::Export(*export_index), network_path).iter().map(|ty| ty.to_string()).collect(), connected_to, }, click_target, - )) + ) }) .filter_map(|(export_metadata, output_port)| output_port.bounding_box().map(|bounding_box| (export_metadata, bounding_box[0].x as i32, bounding_box[0].y as i32))) .collect::>() @@ -956,14 +898,7 @@ impl NodeNetworkInterface { log::error!("Could not get node {node_id} in upstream_nodes_below_layer"); continue; }; - potential_upstream_nodes.extend( - chain_node - .inputs - .iter() - .filter(|input| input.is_exposed_to_frontend(network_path.is_empty())) - .skip(1) - .filter_map(|node_input| node_input.as_node()), - ) + potential_upstream_nodes.extend(chain_node.inputs.iter().filter(|input| input.is_exposed()).skip(1).filter_map(|node_input| node_input.as_node())) } // Get the node feeding into the left input of the chain @@ -976,7 +911,7 @@ impl NodeNetworkInterface { if let Some(primary_node_id) = current_node .inputs .iter() - .filter(|input| input.is_exposed_to_frontend(network_path.is_empty())) + .filter(|input| input.is_exposed()) .nth(if self.is_layer(¤t_node_id, network_path) { 1 } else { 0 }) .and_then(|left_input| left_input.as_node()) { @@ -1110,7 +1045,7 @@ impl NodeNetworkInterface { pub fn reference(&self, node_id: &NodeId, network_path: &[NodeId]) -> Option<&Option> { let Some(node_metadata) = self.node_metadata(node_id, network_path) else { - log::error!("Could not get reference"); + log::error!("Could not get reference for node: {:?}", node_id); return None; }; Some(&node_metadata.persistent_metadata.reference) @@ -1124,55 +1059,39 @@ impl NodeNetworkInterface { Some(&node.implementation) } - pub fn input_name<'a>(&'a self, node_id: NodeId, index: usize, network_path: &[NodeId]) -> Option<&'a str> { - let Some(input_row) = self.input_properties_row(&node_id, index, network_path) else { - log::error!("Could not get input_name for node {node_id} index {index}"); - return None; + pub fn input_data(&self, node_id: &NodeId, index: usize, key: &str, network_path: &[NodeId]) -> Option<&Value> { + let metadata = self + .node_metadata(node_id, network_path) + .and_then(|node_metadata| node_metadata.persistent_metadata.input_metadata.get(index))?; + metadata.persistent_metadata.input_data.get(key) + } + pub fn persistent_input_metadata(&self, node_id: &NodeId, index: usize, network_path: &[NodeId]) -> Option<&InputPersistentMetadata> { + let metadata = self + .node_metadata(node_id, network_path) + .and_then(|node_metadata| node_metadata.persistent_metadata.input_metadata.get(index))?; + Some(&metadata.persistent_metadata) + } + + fn transient_input_metadata(&self, node_id: &NodeId, index: usize, network_path: &[NodeId]) -> Option<&InputTransientMetadata> { + let metadata = self + .node_metadata(node_id, network_path) + .and_then(|node_metadata| node_metadata.persistent_metadata.input_metadata.get(index))?; + Some(&metadata.transient_metadata) + } + + /// Returns the input name to display in the properties panel. If the name is empty then the type is used. + pub fn displayed_input_name_and_description(&mut self, node_id: &NodeId, input_index: usize, network_path: &[NodeId]) -> (String, String) { + let Some(input_metadata) = self.persistent_input_metadata(node_id, input_index, network_path) else { + log::warn!("input metadata not found in displayed_input_name_and_description"); + return (String::new(), String::new()); }; - let name = input_row.input_name.as_str(); - if !name.is_empty() { - Some(name) + let description = input_metadata.input_description.to_string(); + let name = if input_metadata.input_name.is_empty() { + self.input_type(&InputConnector::node(*node_id, input_index), network_path).0.nested_type().to_string() } else { - let node_definition = resolve_document_node_type(self.reference(&node_id, network_path)?.as_ref()?)?; - let rows = &node_definition.node_template.persistent_node_metadata.input_properties; - - rows.get(index).map(|row| row.input_name.as_str()) - } - } - - pub fn input_description<'a>(&'a self, node_id: NodeId, index: usize, network_path: &[NodeId]) -> Option<&'a str> { - let Some(input_row) = self.input_properties_row(&node_id, index, network_path) else { - log::error!("Could not get input_row in input_description"); - return None; + input_metadata.input_name.to_string() }; - let description = input_row.input_description.as_str(); - if !description.is_empty() && description != "TODO" { - Some(description) - } else { - let node_definition = resolve_document_node_type(self.reference(&node_id, network_path)?.as_ref()?)?; - let rows = &node_definition.node_template.persistent_node_metadata.input_properties; - - rows.get(index).map(|row| row.input_description.as_str()) - } - } - - pub fn input_properties_row(&self, node_id: &NodeId, index: usize, network_path: &[NodeId]) -> Option<&PropertiesRow> { - self.node_metadata(node_id, network_path) - .and_then(|node_metadata| node_metadata.persistent_metadata.input_properties.get(index)) - } - - pub fn insert_input_properties_row(&mut self, node_id: &NodeId, index: usize, network_path: &[NodeId], row: PropertiesRow) { - let _ = self - .node_metadata_mut(node_id, network_path) - .map(|node_metadata| node_metadata.persistent_metadata.input_properties.insert(index - 1, row)); - } - - pub fn input_metadata(&self, node_id: &NodeId, index: usize, field: &str, network_path: &[NodeId]) -> Option<&Value> { - let Some(input_row) = self.input_properties_row(node_id, index, network_path) else { - log::error!("Could not get input_row in get_input_metadata"); - return None; - }; - input_row.input_data.get(field) + (name, description) } /// Returns the display name of the node. If the display name is empty, it will return "Untitled Node" or "Untitled Layer" depending on the node type. @@ -1182,6 +1101,7 @@ impl NodeNetworkInterface { .expect("Could not get persistent node metadata in untitled_layer_label") .persistent_metadata .is_layer(); + let Some(reference) = self.reference(node_id, network_path) else { log::error!("Could not get reference in untitled_layer_label"); return "".to_string(); @@ -1195,7 +1115,7 @@ impl NodeNetworkInterface { }; if display_name.is_empty() { - if is_layer && *reference == Some("Merge".to_string()) { + if is_layer { "Untitled Layer".to_string() } else { reference.clone().unwrap_or("Untitled Node".to_string()) @@ -1974,6 +1894,7 @@ impl NodeNetworkInterface { if !network_metadata.transient_metadata.import_export_ports.is_loaded() { self.load_import_export_ports(network_path); } + let Some(network_metadata) = self.network_metadata(network_path) else { log::error!("Could not get nested network_metadata in export_ports"); return None; @@ -2064,6 +1985,30 @@ impl NodeNetworkInterface { return; }; network_metadata.transient_metadata.import_export_ports.unload(); + + // Always unload all wires connected to them as well + let number_of_imports = self.number_of_imports(network_path); + let Some(outward_wires) = self.outward_wires(network_path) else { + log::error!("Could not get outward wires in remove_import"); + return; + }; + let mut input_connectors = Vec::new(); + for import_index in 0..number_of_imports { + let Some(outward_wires_for_import) = outward_wires.get(&OutputConnector::Import(import_index)).cloned() else { + log::error!("Could not get outward wires for import in remove_import"); + return; + }; + input_connectors.extend(outward_wires_for_import); + } + let Some(network) = self.nested_network(network_path) else { + return; + }; + for export_index in 0..network.exports.len() { + input_connectors.push(InputConnector::Export(export_index)); + } + for input in &input_connectors { + self.unload_wire(input, network_path); + } } pub fn modify_import_export(&mut self, network_path: &[NodeId]) -> Option<&ModifyImportExportClickTarget> { @@ -2334,7 +2279,7 @@ impl NodeNetworkInterface { return; }; network_metadata.transient_metadata.all_nodes_bounding_box.unload(); - network_metadata.transient_metadata.import_export_ports.unload(); + self.unload_import_export_ports(network_path); } pub fn outward_wires(&mut self, network_path: &[NodeId]) -> Option<&HashMap>> { @@ -2508,6 +2453,293 @@ impl NodeNetworkInterface { } } + pub fn get_input_center(&mut self, input: &InputConnector, network_path: &[NodeId]) -> Option { + let (ports, index) = match input { + InputConnector::Node { node_id, input_index } => { + let node_click_target = self.node_click_targets(node_id, network_path)?; + (&node_click_target.port_click_targets, input_index) + } + InputConnector::Export(export_index) => { + let ports = self.import_export_ports(network_path)?; + (ports, export_index) + } + }; + ports + .input_ports + .iter() + .find_map(|(input_index, click_target)| if index == input_index { click_target.bounding_box_center() } else { None }) + } + + pub fn get_output_center(&mut self, output: &OutputConnector, network_path: &[NodeId]) -> Option { + let (ports, index) = match output { + OutputConnector::Node { node_id, output_index } => { + let node_click_target = self.node_click_targets(node_id, network_path)?; + (&node_click_target.port_click_targets, output_index) + } + OutputConnector::Import(import_index) => { + let ports = self.import_export_ports(network_path)?; + (ports, import_index) + } + }; + ports + .output_ports + .iter() + .find_map(|(input_index, click_target)| if index == input_index { click_target.bounding_box_center() } else { None }) + } + + pub fn newly_loaded_input_wire(&mut self, input: &InputConnector, graph_wire_style: GraphWireStyle, network_path: &[NodeId]) -> Option { + if !self.wire_is_loaded(input, network_path) { + self.load_wire(input, graph_wire_style, network_path); + } else { + return None; + } + + let wire = match input { + InputConnector::Node { node_id, input_index } => { + let input_metadata = self.transient_input_metadata(node_id, *input_index, network_path)?; + let TransientMetadata::Loaded(wire) = &input_metadata.wire else { + log::error!("Could not load wire for input: {:?}", input); + return None; + }; + wire.clone() + } + InputConnector::Export(export_index) => { + let network_metadata = self.network_metadata(network_path)?; + let Some(TransientMetadata::Loaded(wire)) = network_metadata.transient_metadata.wires.get(*export_index) else { + log::error!("Could not load wire for input: {:?}", input); + return None; + }; + wire.clone() + } + }; + Some(wire) + } + + pub fn wire_is_loaded(&mut self, input: &InputConnector, network_path: &[NodeId]) -> bool { + match input { + InputConnector::Node { node_id, input_index } => { + let Some(input_metadata) = self.transient_input_metadata(node_id, *input_index, network_path) else { + log::error!("Input metadata should always exist for input"); + return false; + }; + input_metadata.wire.is_loaded() + } + InputConnector::Export(export_index) => { + let Some(network_metadata) = self.network_metadata(network_path) else { + return false; + }; + match network_metadata.transient_metadata.wires.get(*export_index) { + Some(wire) => wire.is_loaded(), + None => false, + } + } + } + } + + fn load_wire(&mut self, input: &InputConnector, graph_wire_style: GraphWireStyle, network_path: &[NodeId]) { + let dashed = match self.previewing(network_path) { + Previewing::Yes { .. } => match input { + InputConnector::Node { .. } => false, + InputConnector::Export(export_index) => *export_index == 0, + }, + Previewing::No => false, + }; + let Some(wire) = self.wire_path_from_input(input, graph_wire_style, dashed, network_path) else { + return; + }; + match input { + InputConnector::Node { node_id, input_index } => { + let Some(node_metadata) = self.node_metadata_mut(node_id, network_path) else { return }; + let Some(input_metadata) = node_metadata.persistent_metadata.input_metadata.get_mut(*input_index) else { + log::error!("Node metadata must exist on node: {input:?}"); + return; + }; + let wire_update = WirePathUpdate { + id: *node_id, + input_index: *input_index, + wire_path_update: Some(wire), + }; + input_metadata.transient_metadata.wire = TransientMetadata::Loaded(wire_update); + } + InputConnector::Export(export_index) => { + let Some(network_metadata) = self.network_metadata_mut(network_path) else { return }; + if *export_index >= network_metadata.transient_metadata.wires.len() { + network_metadata.transient_metadata.wires.resize(export_index + 1, TransientMetadata::Unloaded); + } + let Some(input_metadata) = network_metadata.transient_metadata.wires.get_mut(*export_index) else { + return; + }; + let wire_update = WirePathUpdate { + id: NodeId(u64::MAX), + input_index: *export_index, + wire_path_update: Some(wire), + }; + *input_metadata = TransientMetadata::Loaded(wire_update); + } + } + } + + pub fn all_input_connectors(&self, network_path: &[NodeId]) -> Vec { + let mut input_connectors = Vec::new(); + let Some(network) = self.nested_network(network_path) else { + log::error!("Could not get nested network in all_input_connectors"); + return Vec::new(); + }; + for export_index in 0..network.exports.len() { + input_connectors.push(InputConnector::Export(export_index)); + } + for (node_id, node) in &network.nodes { + for input_index in 0..node.inputs.len() { + input_connectors.push(InputConnector::node(*node_id, input_index)); + } + } + input_connectors + } + + pub fn node_graph_input_connectors(&self, network_path: &[NodeId]) -> Vec { + self.all_input_connectors(network_path) + .into_iter() + .filter(|input| self.input_from_connector(input, network_path).is_some_and(|input| input.is_exposed())) + .collect() + } + + /// Maps to the frontend representation of a wire start. Includes disconnected value wire inputs. + pub fn node_graph_wire_inputs(&self, network_path: &[NodeId]) -> Vec<(NodeId, usize)> { + self.node_graph_input_connectors(network_path) + .iter() + .map(|input| match input { + InputConnector::Node { node_id, input_index } => (*node_id, *input_index), + InputConnector::Export(export_index) => (NodeId(u64::MAX), *export_index), + }) + .chain(std::iter::once((NodeId(u64::MAX), usize::MAX))) + .collect() + } + + fn unload_wires_for_node(&mut self, node_id: &NodeId, network_path: &[NodeId]) { + let number_of_outputs = self.number_of_outputs(node_id, network_path); + let Some(outward_wires) = self.outward_wires(network_path) else { + log::error!("Could not get outward wires in reorder_export"); + return; + }; + let mut input_connectors = Vec::new(); + for output_index in 0..number_of_outputs { + let Some(inputs) = outward_wires.get(&OutputConnector::node(*node_id, output_index)) else { + continue; + }; + input_connectors.extend(inputs.clone()) + } + for input_index in 0..self.number_of_inputs(node_id, network_path) { + input_connectors.push(InputConnector::node(*node_id, input_index)); + } + for input in input_connectors { + self.unload_wire(&input, network_path); + } + } + + pub fn unload_wire(&mut self, input: &InputConnector, network_path: &[NodeId]) { + match input { + InputConnector::Node { node_id, input_index } => { + let Some(node_metadata) = self.node_metadata_mut(node_id, network_path) else { + return; + }; + let Some(input_metadata) = node_metadata.persistent_metadata.input_metadata.get_mut(*input_index) else { + log::error!("Node metadata must exist on node: {input:?}"); + return; + }; + input_metadata.transient_metadata.wire = TransientMetadata::Unloaded; + } + InputConnector::Export(export_index) => { + let Some(network_metadata) = self.network_metadata_mut(network_path) else { + return; + }; + if *export_index >= network_metadata.transient_metadata.wires.len() { + network_metadata.transient_metadata.wires.resize(export_index + 1, TransientMetadata::Unloaded); + } + let Some(input_metadata) = network_metadata.transient_metadata.wires.get_mut(*export_index) else { + return; + }; + *input_metadata = TransientMetadata::Unloaded; + } + } + } + + /// When previewing, there may be a second path to the root node. + pub fn wire_to_root(&mut self, graph_wire_style: GraphWireStyle, network_path: &[NodeId]) -> Option { + let input = InputConnector::Export(0); + let current_export = self.upstream_output_connector(&input, network_path)?; + + let root_node = match self.previewing(network_path) { + Previewing::Yes { root_node_to_restore } => root_node_to_restore, + Previewing::No => None, + }?; + + if Some(root_node.node_id) == current_export.node_id() { + return None; + } + let Some(input_position) = self.get_input_center(&input, network_path) else { + log::error!("Could not get dom rect for wire end in root node: {:?}", input); + return None; + }; + let upstream_output = OutputConnector::node(root_node.node_id, root_node.output_index); + let Some(output_position) = self.get_output_center(&upstream_output, network_path) else { + log::error!("Could not get dom rect for wire start in root node: {:?}", upstream_output); + return None; + }; + let vertical_end = input.node_id().is_some_and(|node_id| self.is_layer(&node_id, network_path) && input.input_index() == 0); + let vertical_start: bool = upstream_output.node_id().is_some_and(|node_id| self.is_layer(&node_id, network_path)); + let thick = vertical_end && vertical_start; + let vector_wire = build_vector_wire(output_position, input_position, vertical_start, vertical_end, graph_wire_style); + + let mut path_string = String::new(); + let _ = vector_wire.subpath_to_svg(&mut path_string, DAffine2::IDENTITY); + let data_type = FrontendGraphDataType::from_type(&self.input_type(&input, network_path).0); + let wire_path_update = Some(WirePath { + path_string, + data_type, + thick, + dashed: false, + }); + + Some(WirePathUpdate { + id: NodeId(u64::MAX), + input_index: usize::MAX, + wire_path_update, + }) + } + + /// Returns the vector subpath and a boolean of whether the wire should be thick. + pub fn vector_wire_from_input(&mut self, input: &InputConnector, wire_style: GraphWireStyle, network_path: &[NodeId]) -> Option<(Subpath, bool)> { + let Some(input_position) = self.get_input_center(input, network_path) else { + log::error!("Could not get dom rect for wire end: {:?}", input); + return None; + }; + // An upstream output could not be found, so the wire does not exist, but it should still be loaded as as empty vector + let Some(upstream_output) = self.upstream_output_connector(input, network_path) else { + return Some((Subpath::from_anchors(std::iter::empty(), false), false)); + }; + let Some(output_position) = self.get_output_center(&upstream_output, network_path) else { + log::error!("Could not get dom rect for wire start: {:?}", upstream_output); + return None; + }; + let vertical_end = input.node_id().is_some_and(|node_id| self.is_layer(&node_id, network_path) && input.input_index() == 0); + let vertical_start = upstream_output.node_id().is_some_and(|node_id| self.is_layer(&node_id, network_path)); + let thick = vertical_end && vertical_start; + Some((build_vector_wire(output_position, input_position, vertical_start, vertical_end, wire_style), thick)) + } + + pub fn wire_path_from_input(&mut self, input: &InputConnector, graph_wire_style: GraphWireStyle, dashed: bool, network_path: &[NodeId]) -> Option { + let (vector_wire, thick) = self.vector_wire_from_input(input, graph_wire_style, network_path)?; + let mut path_string = String::new(); + let _ = vector_wire.subpath_to_svg(&mut path_string, DAffine2::IDENTITY); + let data_type = FrontendGraphDataType::from_type(&self.input_type(input, network_path).0); + Some(WirePath { + path_string, + data_type, + thick, + dashed, + }) + } + pub fn node_click_targets(&mut self, node_id: &NodeId, network_path: &[NodeId]) -> Option<&DocumentNodeClickTargets> { self.try_load_node_click_targets(node_id, network_path); self.try_get_node_click_targets(node_id, network_path) @@ -2746,17 +2978,14 @@ impl NodeNetworkInterface { return; }; node_metadata.transient_metadata.click_targets.unload(); + self.unload_wires_for_node(node_id, network_path); } pub fn unload_upstream_node_click_targets(&mut self, node_ids: Vec, network_path: &[NodeId]) { let upstream_nodes = self.upstream_flow_back_from_nodes(node_ids, network_path, FlowType::UpstreamFlow).collect::>(); for upstream_id in &upstream_nodes { - let Some(node_metadata) = self.node_metadata_mut(upstream_id, network_path) else { - log::error!("Could not get node_metadata for node {upstream_id}"); - return; - }; - node_metadata.transient_metadata.click_targets.unload(); + self.unload_node_click_targets(upstream_id, network_path); } } @@ -2768,11 +2997,7 @@ impl NodeNetworkInterface { let upstream_nodes = network.nodes.keys().cloned().collect::>(); for upstream_id in &upstream_nodes { - let Some(node_metadata) = self.node_metadata_mut(upstream_id, network_path) else { - log::error!("Could not get node_metadata for node {upstream_id}"); - return; - }; - node_metadata.transient_metadata.click_targets.unload(); + self.unload_node_click_targets(upstream_id, network_path); } } } @@ -2915,8 +3140,8 @@ impl NodeNetworkInterface { log::error!("Could not get node {node_id} in is_eligible_to_be_layer"); return false; }; - let input_count = node.inputs.iter().take(2).filter(|input| input.is_exposed_to_frontend(network_path.is_empty())).count(); - let parameters_hidden = node.inputs.iter().skip(2).all(|input| !input.is_exposed_to_frontend(network_path.is_empty())); + let input_count = node.inputs.iter().take(2).filter(|input| input.is_exposed()).count(); + let parameters_hidden = node.inputs.iter().skip(2).all(|input| !input.is_exposed()); let output_count = self.number_of_outputs(node_id, network_path); self.node_metadata(node_id, network_path) @@ -3080,7 +3305,7 @@ impl NodeNetworkInterface { }; let mut displayed_index = 0; for i in 0..*input_index { - if node.inputs[i].is_exposed_to_frontend(network_path.is_empty()) { + if node.inputs[i].is_exposed() { displayed_index += 1; } } @@ -3417,9 +3642,10 @@ impl NodeNetworkInterface { } // Update the click targets for the encapsulating node, if it exists. There is no encapsulating node if the network is the document network - if let Some(encapsulating_node_metadata_mut) = self.encapsulating_node_metadata_mut(network_path) { - encapsulating_node_metadata_mut.transient_metadata.click_targets.unload(); - }; + let mut path = network_path.to_vec(); + if let Some(encapsulating_node) = path.pop() { + self.unload_node_click_targets(&encapsulating_node, &path); + } // If the export is inserted as the first input or second input, and the parent network is the document_network, then it may have affected the document metadata structure if network_path.len() == 1 && (insert_index == 0 || insert_index == 1) { @@ -3464,9 +3690,9 @@ impl NodeNetworkInterface { }; let new_input = (input_name, input_description).into(); if insert_index == -1 { - node_metadata.persistent_metadata.input_properties.push(new_input); + node_metadata.persistent_metadata.input_metadata.push(new_input); } else { - node_metadata.persistent_metadata.input_properties.insert(insert_index as usize, new_input); + node_metadata.persistent_metadata.input_metadata.insert(insert_index as usize, new_input); } // Clear the reference to the nodes definition @@ -3600,7 +3826,7 @@ impl NodeNetworkInterface { log::error!("Could not get encapsulating node metadata in remove_export"); return; }; - encapsulating_node_metadata.persistent_metadata.input_properties.remove(import_index); + encapsulating_node_metadata.persistent_metadata.input_metadata.remove(import_index); encapsulating_node_metadata.persistent_metadata.reference = None; // Update the metadata for the encapsulating node @@ -3737,8 +3963,8 @@ impl NodeNetworkInterface { return; }; - let properties_row = encapsulating_node_metadata.persistent_metadata.input_properties.remove(start_index); - encapsulating_node_metadata.persistent_metadata.input_properties.insert(end_index, properties_row); + let properties_row = encapsulating_node_metadata.persistent_metadata.input_metadata.remove(start_index); + encapsulating_node_metadata.persistent_metadata.input_metadata.insert(end_index, properties_row); encapsulating_node_metadata.persistent_metadata.reference = None; // Update the metadata for the outer network @@ -3799,9 +4025,8 @@ impl NodeNetworkInterface { self.unload_stack_dependents(network_path); } - // TODO: Eventually remove this document upgrade code - /// Keep metadata in sync with the new implementation if this is used by anything other than the upgrade scripts - pub fn replace_implementation(&mut self, node_id: &NodeId, network_path: &[NodeId], implementation: DocumentNodeImplementation) { + /// Replaces the implementation and corresponding metadata. + pub fn replace_implementation(&mut self, node_id: &NodeId, network_path: &[NodeId], new_template: &mut NodeTemplate) { let Some(network) = self.network_mut(network_path) else { log::error!("Could not get nested network in set_implementation"); return; @@ -3810,17 +4035,62 @@ impl NodeNetworkInterface { log::error!("Could not get node in set_implementation"); return; }; - node.implementation = implementation; - } - - // TODO: Eventually remove this document upgrade code - /// Keep metadata in sync with the new implementation if this is used by anything other than the upgrade scripts - pub fn replace_implementation_metadata(&mut self, node_id: &NodeId, network_path: &[NodeId], metadata: DocumentNodePersistentMetadata) { - let Some(node_metadata) = self.node_metadata_mut(node_id, network_path) else { - log::error!("Could not get network metadata in set implementation"); + let new_implementation = std::mem::take(&mut new_template.document_node.implementation); + let _ = std::mem::replace(&mut node.implementation, new_implementation); + let Some(metadata) = self.node_metadata_mut(node_id, network_path) else { + log::error!("Could not get metadata in set_implementation"); return; }; - node_metadata.persistent_metadata.network_metadata = metadata.network_metadata; + let new_metadata = std::mem::take(&mut new_template.persistent_node_metadata.network_metadata); + let _ = std::mem::replace(&mut metadata.persistent_metadata.network_metadata, new_metadata); + } + + /// Replaces the inputs and corresponding metadata. + pub fn replace_inputs(&mut self, node_id: &NodeId, network_path: &[NodeId], new_template: &mut NodeTemplate) -> Option> { + let Some(network) = self.network_mut(network_path) else { + log::error!("Could not get nested network in set_implementation"); + return None; + }; + let Some(node) = network.nodes.get_mut(node_id) else { + log::error!("Could not get node in set_implementation"); + return None; + }; + let new_inputs = std::mem::take(&mut new_template.document_node.inputs); + let old_inputs = std::mem::replace(&mut node.inputs, new_inputs); + let Some(metadata) = self.node_metadata_mut(node_id, network_path) else { + log::error!("Could not get metadata in set_implementation"); + return None; + }; + let new_metadata = std::mem::take(&mut new_template.persistent_node_metadata.input_metadata); + let _ = std::mem::replace(&mut metadata.persistent_metadata.input_metadata, new_metadata); + Some(old_inputs) + } + + /// Used when opening an old document to add the persistent metadata for each input if it doesnt exist, which is where the name/description are saved. + pub fn validate_input_metadata(&mut self, node_id: &NodeId, node: &DocumentNode, network_path: &[NodeId]) { + let number_of_inputs = node.inputs.len(); + let Some(metadata) = self.node_metadata_mut(node_id, network_path) else { return }; + for added_input_index in metadata.persistent_metadata.input_metadata.len()..number_of_inputs { + let reference = metadata.persistent_metadata.reference.as_ref(); + let definition = reference.and_then(|reference| resolve_document_node_type(reference)); + let input_metadata = definition + .and_then(|definition| definition.node_template.persistent_node_metadata.input_metadata.get(added_input_index)) + .cloned(); + metadata.persistent_metadata.input_metadata.push(input_metadata.unwrap_or_default()); + } + } + + /// Used to ensure the display name is the reference name in case it is empty. + pub fn validate_display_name_metadata(&mut self, node_id: &NodeId, network_path: &[NodeId]) { + let Some(metadata) = self.node_metadata_mut(node_id, network_path) else { return }; + if metadata.persistent_metadata.display_name.is_empty() { + if let Some(reference) = metadata.persistent_metadata.reference.clone() { + // Keep the name for merge nodes as empty + if reference != "Merge" { + metadata.persistent_metadata.display_name = reference; + } + } + } } /// Keep metadata in sync with the new implementation if this is used by anything other than the upgrade scripts @@ -3845,19 +4115,6 @@ impl NodeNetworkInterface { node.manual_composition = manual_composition; } - /// Keep metadata in sync with the new implementation if this is used by anything other than the upgrade scripts - pub fn replace_inputs(&mut self, node_id: &NodeId, inputs: Vec, network_path: &[NodeId]) -> Vec { - let Some(network) = self.network_mut(network_path) else { - log::error!("Could not get nested network in replace_inputs"); - return Vec::new(); - }; - let Some(node) = network.nodes.get_mut(node_id) else { - log::error!("Could not get node in replace_inputs"); - return Vec::new(); - }; - std::mem::replace(&mut node.inputs, inputs) - } - pub fn set_input(&mut self, input_connector: &InputConnector, new_input: NodeInput, network_path: &[NodeId]) { if matches!(input_connector, InputConnector::Export(_)) && matches!(new_input, NodeInput::Network { .. }) { // TODO: Add support for flattening NodeInput::Network exports in flatten_with_fns https://github.com/GraphiteEditor/Graphite/issues/1762 @@ -4013,20 +4270,22 @@ impl NodeNetworkInterface { } self.unload_upstream_node_click_targets(vec![*upstream_node_id], network_path); self.unload_stack_dependents(network_path); - self.try_set_upstream_to_chain(input_connector, network_path); } // If a connection is made to the imports (NodeInput::Value { .. } | NodeInput::Scope { .. } | NodeInput::Inline { .. }, NodeInput::Network { .. }) => { self.unload_outward_wires(network_path); + self.unload_wire(input_connector, network_path); } // If a connection to the imports is disconnected (NodeInput::Network { .. }, NodeInput::Value { .. } | NodeInput::Scope { .. } | NodeInput::Inline { .. }) => { self.unload_outward_wires(network_path); + self.unload_wire(input_connector, network_path); } // If a node is disconnected. (NodeInput::Node { .. }, NodeInput::Value { .. } | NodeInput::Scope { .. } | NodeInput::Inline { .. }) => { self.unload_outward_wires(network_path); + self.unload_wire(input_connector, network_path); if let Some((old_upstream_node_id, previous_position)) = previous_metadata { let old_upstream_node_is_layer = self.is_layer(&old_upstream_node_id, network_path); @@ -4170,8 +4429,8 @@ impl NodeNetworkInterface { self.unload_outward_wires(network_path); } - /// Used to insert a node template with no node/network inputs into the network. - pub fn insert_node(&mut self, node_id: NodeId, node_template: NodeTemplate, network_path: &[NodeId]) { + /// Used to insert a node template with no node/network inputs into the network and returns the a NodeTemplate with information from the previous node, if it existed. + pub fn insert_node(&mut self, node_id: NodeId, node_template: NodeTemplate, network_path: &[NodeId]) -> Option { let has_node_or_network_input = node_template .document_node .inputs @@ -4180,24 +4439,29 @@ impl NodeNetworkInterface { assert!(has_node_or_network_input, "Cannot insert node with node or network inputs. Use insert_node_group instead"); let Some(network) = self.network_mut(network_path) else { log::error!("Network not found in insert_node"); - return; + return None; }; - network.nodes.insert(node_id, node_template.document_node); + let previous_node = network.nodes.insert(node_id, node_template.document_node); self.transaction_modified(); let Some(network_metadata) = self.network_metadata_mut(network_path) else { log::error!("Network not found in insert_node"); - return; + return None; }; let node_metadata = DocumentNodeMetadata { persistent_metadata: node_template.persistent_node_metadata, transient_metadata: DocumentNodeTransientMetadata::default(), }; - network_metadata.persistent_metadata.node_metadata.insert(node_id, node_metadata); + let previous_metadata = network_metadata.persistent_metadata.node_metadata.insert(node_id, node_metadata); self.unload_all_nodes_bounding_box(network_path); - self.unload_node_click_targets(&node_id, network_path) + self.unload_node_click_targets(&node_id, network_path); + + previous_node.zip(previous_metadata).map(|(document_node, node_metadata)| NodeTemplate { + document_node, + persistent_node_metadata: node_metadata.persistent_metadata, + }) } /// Deletes all nodes in `node_ids` and any sole dependents in the horizontal chain if the node to delete is a layer node. @@ -4307,7 +4571,7 @@ impl NodeNetworkInterface { let reconnect_to_input = self.document_node(node_id, network_path).and_then(|node| { node.inputs .iter() - .find(|input| input.is_exposed_to_frontend(network_path.is_empty())) + .find(|input| input.is_exposed()) .filter(|input| matches!(input, NodeInput::Node { .. } | NodeInput::Network { .. })) .cloned() }); @@ -4463,25 +4727,21 @@ impl NodeNetworkInterface { let name_changed = match index { ImportOrExport::Import(import_index) => { - let Some(input_properties) = encapsulating_node.persistent_metadata.input_properties.get_mut(import_index) else { + let Some(input_properties) = encapsulating_node.persistent_metadata.input_metadata.get_mut(import_index) else { log::error!("Could not get input properties in set_import_export_name"); return; }; // Only return false if the previous value is the same as the current value - std::mem::swap(&mut input_properties.input_name, &mut name); - input_properties.input_name != name + std::mem::swap(&mut input_properties.persistent_metadata.input_name, &mut name); + input_properties.persistent_metadata.input_name != name } ImportOrExport::Export(export_index) => { let Some(export_name) = encapsulating_node.persistent_metadata.output_names.get_mut(export_index) else { log::error!("Could not get export_name in set_import_export_name"); return; }; - if *export_name == name { - false - } else { - *export_name = name; - true - } + std::mem::swap(export_name, &mut name); + *export_name != name } }; if name_changed { @@ -4752,6 +5012,7 @@ impl NodeNetworkInterface { log::error!("Could not set stack position for non layer node {node_id}"); } } + self.unload_upstream_node_click_targets(vec![*node_id], network_path); } /// Sets the position of a node to a stack position without changing its y offset @@ -5679,34 +5940,6 @@ impl NodeNetworkInterface { self.force_set_upstream_to_chain(node_id, network_path); } } - - pub fn iter_recursive(&self) -> NodesRecursiveIter<'_> { - NodesRecursiveIter { - stack: vec![&self.network], - current_slice: None, - } - } -} - -pub struct NodesRecursiveIter<'a> { - stack: Vec<&'a NodeNetwork>, - current_slice: Option>, -} - -impl<'a> Iterator for NodesRecursiveIter<'a> { - type Item = (NodeId, &'a DocumentNode); - fn next(&mut self) -> Option { - loop { - if let Some((id, node)) = self.current_slice.as_mut().and_then(|iter| iter.next()) { - if let DocumentNodeImplementation::Network(network) = &node.implementation { - self.stack.push(network); - } - return Some((*id, node)); - } - let network = self.stack.pop()?; - self.current_slice = Some(network.nodes.iter()); - } - } } #[derive(PartialEq)] @@ -5762,7 +5995,11 @@ impl Iterator for FlowIter<'_> { } } -/// Represents the source of a resolved type (for debugging) +// TODO: Refactor to be Unknown, Compiled(Type) for NodeInput::Node, or Value(Type) for NodeInput::Value +/// Represents the source of a resolved type (for debugging). +/// There will be two valid types list. One for the current valid types that will not cause a node graph error, +/// based on the other inputs to that node and returned during compilation. THe other list will be all potential +/// Valid types, based on the protonode implementation/downstream users. #[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize, specta::Type)] pub enum TypeSource { Compiled, @@ -5787,7 +6024,7 @@ pub enum ImportOrExport { } /// Represents an input connector with index based on the [`DocumentNode::inputs`] index, not the visible input index -#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize, specta::Type)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize, specta::Type)] pub enum InputConnector { #[serde(rename = "node")] Node { @@ -6099,14 +6336,14 @@ pub struct NodeNetworkTransientMetadata { // node_group_bounding_box: Vec<(Subpath, Vec)>, /// Cache for all outward wire connections pub outward_wires: TransientMetadata>>, - // TODO: Cache all wire paths instead of calculating in Graph.svelte - // pub wire_paths: Vec /// All export connector click targets pub import_export_ports: TransientMetadata, /// Click targets for adding, removing, and moving import/export ports pub modify_import_export: TransientMetadata, // Distance to the edges of the network, where the import/export ports are displayed. Rounded to nearest grid space when the panning ends. pub rounded_network_edge_distance: TransientMetadata, + // Wires from the exports + pub wires: Vec>, } #[derive(Debug, Clone)] @@ -6212,116 +6449,90 @@ pub enum WidgetOverride { } // TODO: Custom deserialization/serialization to ensure number of properties row matches number of node inputs -#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)] -pub struct PropertiesRow { +#[derive(Debug, Clone, Default, PartialEq, serde::Serialize, serde::Deserialize)] +pub struct InputPersistentMetadata { /// A general datastore than can store key value pairs of any types for any input - // TODO: This could be simplified to just Value, and key value pairs could be stored as the Value::Object variant + /// Each instance of the input node needs to store its own data, since it can lose the reference to its + /// node definition if the node signature is modified by the user. For example adding/removing/renaming an import/export of a network node. pub input_data: HashMap, // An input can override a widget, which would otherwise be automatically generated from the type // The string is the identifier to the widget override function stored in INPUT_OVERRIDES pub widget_override: Option, - #[serde(skip)] + /// An empty input name means to use the type as the name. pub input_name: String, - #[serde(skip)] + /// Displayed as the tooltip. pub input_description: String, } -impl Default for PropertiesRow { - fn default() -> Self { - ("", "TODO").into() +impl InputPersistentMetadata { + pub fn with_name(mut self, input_name: &str) -> Self { + self.input_name = input_name.to_string(); + self } -} - -impl From<(&str, &str)> for PropertiesRow { - fn from(input_name_and_description: (&str, &str)) -> Self { - PropertiesRow::with_override(input_name_and_description.0, input_name_and_description.1, WidgetOverride::None) - } -} - -impl PropertiesRow { - pub fn with_override(input_name: &str, input_description: &str, widget_override: WidgetOverride) -> Self { - let mut input_data = HashMap::new(); - let input_name = input_name.to_string(); - let input_description = input_description.to_string(); - + pub fn with_override(mut self, widget_override: WidgetOverride) -> Self { match widget_override { - WidgetOverride::None => PropertiesRow { - input_data, - widget_override: None, - input_name, - input_description, - }, - WidgetOverride::Hidden => PropertiesRow { - input_data, - widget_override: Some("hidden".to_string()), - input_name, - input_description, - }, + // Uses the default widget for the type + WidgetOverride::None => { + self.widget_override = None; + } + WidgetOverride::Hidden => { + self.widget_override = Some("hidden".to_string()); + } WidgetOverride::String(string_properties) => { - input_data.insert("string_properties".to_string(), Value::String(string_properties)); - PropertiesRow { - input_data, - widget_override: Some("string".to_string()), - input_name, - input_description, - } + self.input_data.insert("string_properties".to_string(), Value::String(string_properties)); + self.widget_override = Some("string".to_string()); } WidgetOverride::Number(mut number_properties) => { if let Some(unit) = number_properties.unit.take() { - input_data.insert("unit".to_string(), json!(unit)); + self.input_data.insert("unit".to_string(), json!(unit)); } if let Some(min) = number_properties.min.take() { - input_data.insert("min".to_string(), json!(min)); + self.input_data.insert("min".to_string(), json!(min)); } if let Some(max) = number_properties.max.take() { - input_data.insert("max".to_string(), json!(max)); + self.input_data.insert("max".to_string(), json!(max)); } if let Some(step) = number_properties.step.take() { - input_data.insert("step".to_string(), json!(step)); + self.input_data.insert("step".to_string(), json!(step)); } if let Some(range_min) = number_properties.range_min.take() { - input_data.insert("range_min".to_string(), json!(range_min)); + self.input_data.insert("range_min".to_string(), json!(range_min)); } if let Some(range_max) = number_properties.range_max.take() { - input_data.insert("range_max".to_string(), json!(range_max)); - } - input_data.insert("mode".to_string(), json!(number_properties.mode)); - input_data.insert("is_integer".to_string(), Value::Bool(number_properties.is_integer)); - input_data.insert("blank_assist".to_string(), Value::Bool(number_properties.blank_assist)); - PropertiesRow { - input_data, - widget_override: Some("number".to_string()), - input_name, - input_description, + self.input_data.insert("range_max".to_string(), json!(range_max)); } + self.input_data.insert("mode".to_string(), json!(number_properties.mode)); + self.input_data.insert("is_integer".to_string(), Value::Bool(number_properties.is_integer)); + self.input_data.insert("blank_assist".to_string(), Value::Bool(number_properties.blank_assist)); + self.widget_override = Some("number".to_string()); } WidgetOverride::Vec2(vec2_properties) => { - input_data.insert("x".to_string(), json!(vec2_properties.x)); - input_data.insert("y".to_string(), json!(vec2_properties.y)); - input_data.insert("unit".to_string(), json!(vec2_properties.unit)); + self.input_data.insert("x".to_string(), json!(vec2_properties.x)); + self.input_data.insert("y".to_string(), json!(vec2_properties.y)); + self.input_data.insert("unit".to_string(), json!(vec2_properties.unit)); if let Some(min) = vec2_properties.min { - input_data.insert("min".to_string(), json!(min)); - } - PropertiesRow { - input_data, - widget_override: Some("vec2".to_string()), - input_name, - input_description, + self.input_data.insert("min".to_string(), json!(min)); } + self.widget_override = Some("vec2".to_string()); } - WidgetOverride::Custom(lambda_name) => PropertiesRow { - input_data, - widget_override: Some(lambda_name), - input_name, - input_description, - }, - } - } - - pub fn with_tooltip(mut self, tooltip: &str) -> Self { - self.input_data.insert("tooltip".to_string(), json!(tooltip)); + WidgetOverride::Custom(lambda_name) => { + self.widget_override = Some(lambda_name); + } + }; self } + + pub fn with_description(mut self, tooltip: &str) -> Self { + self.input_description = tooltip.to_string(); + self + } +} + +#[derive(Debug, Clone, Default)] +struct InputTransientMetadata { + wire: TransientMetadata, + // downstream_protonode: populated for all inputs after each compile + // types: populated for each protonode after each } // TODO: Eventually remove this migration document upgrade code @@ -6361,7 +6572,7 @@ pub struct DocumentNodePersistentMetadata { pub display_name: String, /// Stores metadata to override the properties in the properties panel for each input. These can either be generated automatically based on the type, or with a custom function. /// Must match the length of node inputs - pub input_properties: Vec, + pub input_metadata: Vec, #[serde(deserialize_with = "migrate_output_names")] pub output_names: Vec, /// Indicates to the UI if a primary output should be drawn for this node. @@ -6386,7 +6597,7 @@ impl Default for DocumentNodePersistentMetadata { DocumentNodePersistentMetadata { reference: None, display_name: String::new(), - input_properties: Vec::new(), + input_metadata: Vec::new(), output_names: Vec::new(), has_primary_output: true, pinned: false, @@ -6403,6 +6614,48 @@ impl DocumentNodePersistentMetadata { } } +#[derive(Debug, Default, serde::Serialize, serde::Deserialize)] +pub struct InputMetadata { + pub persistent_metadata: InputPersistentMetadata, + #[serde(skip)] + transient_metadata: InputTransientMetadata, +} + +impl Clone for InputMetadata { + fn clone(&self) -> Self { + InputMetadata { + persistent_metadata: self.persistent_metadata.clone(), + transient_metadata: Default::default(), + } + } +} + +impl PartialEq for InputMetadata { + fn eq(&self, other: &Self) -> bool { + self.persistent_metadata == other.persistent_metadata + } +} + +impl From<(&str, &str)> for InputMetadata { + fn from(input_name_and_description: (&str, &str)) -> Self { + InputMetadata { + persistent_metadata: InputPersistentMetadata::default() + .with_name(input_name_and_description.0) + .with_description(input_name_and_description.1), + ..Default::default() + } + } +} + +impl InputMetadata { + pub fn with_name_description_override(input_name: &str, tooltip: &str, widget_override: WidgetOverride) -> Self { + InputMetadata { + persistent_metadata: InputPersistentMetadata::default().with_name(input_name).with_description(tooltip).with_override(widget_override), + ..Default::default() + } + } +} + /// Persistent metadata for each node in the network, which must be included when creating, serializing, and deserializing saving a node. #[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)] pub struct DocumentNodePersistentMetadataInputNames { @@ -6423,17 +6676,59 @@ pub struct DocumentNodePersistentMetadataInputNames { impl From for DocumentNodePersistentMetadata { fn from(old: DocumentNodePersistentMetadataInputNames) -> Self { - let input_properties = old - .reference - .as_ref() - .and_then(|reference| resolve_document_node_type(reference)) - .map(|definition| definition.node_template.persistent_node_metadata.input_properties.clone()) - .unwrap_or(old.input_names.into_iter().map(|name| (name.as_str(), "").into()).collect()); + DocumentNodePersistentMetadata { + input_metadata: Vec::new(), + ..old.into() + } + } +} +#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)] +pub struct DocumentNodePersistentMetadataPropertiesRow { + pub reference: Option, + #[serde(default)] + pub display_name: String, + pub input_properties: Vec, + #[serde(deserialize_with = "migrate_output_names")] + pub output_names: Vec, + #[serde(default = "return_true")] + pub has_primary_output: bool, + #[serde(default)] + pub locked: bool, + #[serde(default)] + pub pinned: bool, + pub node_type_metadata: NodeTypePersistentMetadata, + pub network_metadata: Option, +} + +#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)] +pub struct PropertiesRow { + pub input_data: HashMap, + pub widget_override: Option, + #[serde(skip)] + pub input_name: String, + #[serde(skip)] + pub input_description: String, +} + +impl From for DocumentNodePersistentMetadata { + fn from(old: DocumentNodePersistentMetadataPropertiesRow) -> Self { + let mut input_metadata = Vec::new(); + for properties_row in old.input_properties { + input_metadata.push(InputMetadata { + persistent_metadata: InputPersistentMetadata { + input_data: properties_row.input_data, + widget_override: properties_row.widget_override, + input_name: properties_row.input_name, + input_description: properties_row.input_description, + }, + ..Default::default() + }) + } DocumentNodePersistentMetadata { reference: old.reference, display_name: old.display_name, - input_properties, + input_metadata: Vec::new(), output_names: old.output_names, has_primary_output: old.has_primary_output, locked: old.locked, @@ -6446,6 +6741,7 @@ impl From for DocumentNodePersistentMe #[derive(serde::Serialize, serde::Deserialize)] enum NodePersistentMetadataVersions { + DocumentNodePersistentMetadataPropertiesRow(DocumentNodePersistentMetadataPropertiesRow), NodePersistentMetadataInputNames(DocumentNodePersistentMetadataInputNames), NodePersistentMetadata(DocumentNodePersistentMetadata), } @@ -6457,12 +6753,16 @@ where use serde::Deserialize; let value = Value::deserialize(deserializer)?; - - serde_json::from_value::(value.clone()).or_else(|_| { - serde_json::from_value::(value) - .map(DocumentNodePersistentMetadata::from) - .map_err(serde::de::Error::custom) - }) + if let Ok(document) = serde_json::from_value::(value.clone()) { + return Ok(document); + }; + if let Ok(document) = serde_json::from_value::(value.clone()) { + return Ok(document.into()); + }; + match serde_json::from_value::(value.clone()) { + Ok(document) => Ok(document.into()), + Err(e) => Err(serde::de::Error::custom(e)), + } } #[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)] @@ -6626,7 +6926,7 @@ impl Default for NavigationMetadata { // PartialEq required by message handlers /// All persistent editor and Graphene data for a node. Used to serialize and deserialize a node, pass it through the editor, and create definitions. -#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)] +#[derive(Debug, Clone, Default, PartialEq, serde::Serialize, serde::Deserialize)] pub struct NodeTemplate { pub document_node: DocumentNode, pub persistent_node_metadata: DocumentNodePersistentMetadata, diff --git a/editor/src/messages/portfolio/document/utility_types/wires.rs b/editor/src/messages/portfolio/document/utility_types/wires.rs new file mode 100644 index 000000000..9f85c8267 --- /dev/null +++ b/editor/src/messages/portfolio/document/utility_types/wires.rs @@ -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, +} + +#[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 { + 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 { + 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) -> Subpath { + 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) +} diff --git a/editor/src/messages/portfolio/document_migration.rs b/editor/src/messages/portfolio/document_migration.rs index 920d5fa68..7df0b3839 100644 --- a/editor/src/messages/portfolio/document_migration.rs +++ b/editor/src/messages/portfolio/document_migration.rs @@ -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::>() - { - 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::)>>(); + 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::>(); + 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::>(); - 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, &[]); - } - } - } } diff --git a/editor/src/messages/portfolio/portfolio_message_handler.rs b/editor/src/messages/portfolio/portfolio_message_handler.rs index 7dac84d34..35a1eb516 100644 --- a/editor/src/messages/portfolio/portfolio_message_handler.rs +++ b/editor/src/messages/portfolio/portfolio_message_handler.rs @@ -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> 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::>(); + 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> 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 { diff --git a/editor/src/messages/preferences/preferences_message.rs b/editor/src/messages/preferences/preferences_message.rs index 11b32dead..b8988fa3e 100644 --- a/editor/src/messages/preferences/preferences_message.rs +++ b/editor/src/messages/preferences/preferences_message.rs @@ -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::*; diff --git a/editor/src/messages/preferences/preferences_message_handler.rs b/editor/src/messages/preferences/preferences_message_handler.rs index d6ec4d937..b52e4e49a 100644 --- a/editor/src/messages/preferences/preferences_message_handler.rs +++ b/editor/src/messages/preferences/preferences_message_handler.rs @@ -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 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; diff --git a/editor/src/test_utils.rs b/editor/src/test_utils.rs index a8c9691c8..04ef698da 100644 --- a/editor/src/test_utils.rs +++ b/editor/src/test_utils.rs @@ -172,9 +172,10 @@ impl EditorTestUtils { pub fn get_node<'a, T: InputAccessor<'a, DocumentNode>>(&'a self) -> impl Iterator + '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 { diff --git a/frontend/src/components/views/Graph.svelte b/frontend/src/components/views/Graph.svelte index 59575d314..eaedc3a6d 100644 --- a/frontend/src/components/views/Graph.svelte +++ b/frontend/src/components/views/Graph.svelte @@ -1,11 +1,11 @@