From d0e1d8982fee975d6b07471ba3b737c3f6c43b4d Mon Sep 17 00:00:00 2001 From: HWienhold Date: Wed, 9 Jul 2025 03:47:56 +0200 Subject: [PATCH 1/8] Allow tooling to treat near-zero-length handles as zero-length when converting to colinear (#2747) fix colinear handles on connected path Co-authored-by: Keavon Chambers --- editor/src/messages/tool/common_functionality/shape_editor.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/editor/src/messages/tool/common_functionality/shape_editor.rs b/editor/src/messages/tool/common_functionality/shape_editor.rs index 581c89908f..f98e50f079 100644 --- a/editor/src/messages/tool/common_functionality/shape_editor.rs +++ b/editor/src/messages/tool/common_functionality/shape_editor.rs @@ -1079,7 +1079,7 @@ impl ShapeState { let mut normalized = handle_directions[0].and_then(|a| handle_directions[1].and_then(|b| (a - b).try_normalize())); - if normalized.is_none() { + if normalized.is_none() || handle_directions.iter().any(|&d| d.is_some_and(|d| d.length_squared() < f64::EPSILON * 1e5)) { handle_directions = anchor_positions.map(|relative_anchor| relative_anchor.map(|relative_anchor| (relative_anchor - anchor) / 3.)); normalized = handle_directions[0].and_then(|a| handle_directions[1].and_then(|b| (a - b).try_normalize())) } From 00236c8136bc318d0b639d79a9a64cc903d1c123 Mon Sep 17 00:00:00 2001 From: Mohd Mohsin <136162998+MohdMohsin97@users.noreply.github.com> Date: Wed, 9 Jul 2025 07:43:51 +0530 Subject: [PATCH 2/8] Generate a visualization of the editor's hierarchical message system tree (#2499) * Feat: implement the hierarchical tree for visualization * rename HierarchicalTree trait function * feat: change the HierarchicalTree from String to DebugMessageTree struct * Nits * feat: impliment proc macro to extract field from messagedata structs * update the hierarchical-tree for hanlder data * feat: added message handler struct to hierarchical tree * feat: add the line number to message handler struct * feat: added handler path to tree and NITS * clean the white spaces in type string * fixes some white spaces * feat: added path to message enum in hierarchical tree * feat: add file creation of hierarchical message system tree * cleanup * Code review * Add todo comment for deferred change --------- Co-authored-by: Keavon Chambers --- .gitignore | 1 + editor/src/lib.rs | 1 + .../animation/animation_message_handler.rs | 3 +- .../broadcast/broadcast_message_handler.rs | 3 +- .../messages/debug/debug_message_handler.rs | 3 +- .../messages/dialog/dialog_message_handler.rs | 4 +- .../export_dialog_message_handler.rs | 4 +- .../new_document_dialog_message_handler.rs | 3 +- .../preferences_dialog_message_handler.rs | 4 +- .../globals/globals_message_handler.rs | 3 +- .../input_mapper_message_handler.rs | 4 +- .../key_mapping_message_handler.rs | 4 +- .../input_preprocessor_message_handler.rs | 4 +- .../messages/layout/layout_message_handler.rs | 11 +- editor/src/messages/message.rs | 83 ++++++++++++ .../document/document_message_handler.rs | 4 +- .../graph_operation_message_handler.rs | 4 +- .../navigation/navigation_message_handler.rs | 4 +- .../node_graph/node_graph_message_handler.rs | 5 +- .../overlays/overlays_message_handler.rs | 4 +- .../properties_panel_message_handler.rs | 3 +- .../menu_bar/menu_bar_message_handler.rs | 3 +- .../portfolio/portfolio_message_handler.rs | 4 +- .../spreadsheet_message_handler.rs | 3 +- .../preferences_message_handler.rs | 3 +- editor/src/messages/prelude.rs | 4 +- .../src/messages/tool/tool_message_handler.rs | 4 +- .../tool/tool_messages/artboard_tool.rs | 3 +- .../messages/tool/tool_messages/brush_tool.rs | 3 +- .../tool/tool_messages/eyedropper_tool.rs | 3 +- .../messages/tool/tool_messages/fill_tool.rs | 3 +- .../tool/tool_messages/freehand_tool.rs | 3 +- .../tool/tool_messages/gradient_tool.rs | 3 +- .../tool/tool_messages/navigate_tool.rs | 3 +- .../messages/tool/tool_messages/path_tool.rs | 3 +- .../messages/tool/tool_messages/pen_tool.rs | 3 +- .../tool/tool_messages/select_tool.rs | 3 +- .../tool/tool_messages/spline_tool.rs | 3 +- .../messages/tool/tool_messages/text_tool.rs | 3 +- .../transform_layer_message_handler.rs | 22 +++- editor/src/messages/tool/utility_types.rs | 1 + .../workspace/workspace_message_handler.rs | 3 +- editor/src/utility_traits.rs | 16 +++ editor/src/utility_types.rs | 99 +++++++++++++++ proc-macros/src/combined_message_attrs.rs | 4 +- proc-macros/src/extract_fields.rs | 57 +++++++++ proc-macros/src/helpers.rs | 52 ++++++++ proc-macros/src/hierarchical_tree.rs | 73 +++++++++++ proc-macros/src/lib.rs | 21 ++++ proc-macros/src/message_handler_data_attr.rs | 118 ++++++++++++++++++ 50 files changed, 640 insertions(+), 42 deletions(-) create mode 100644 editor/src/utility_types.rs create mode 100644 proc-macros/src/extract_fields.rs create mode 100644 proc-macros/src/hierarchical_tree.rs create mode 100644 proc-macros/src/message_handler_data_attr.rs diff --git a/.gitignore b/.gitignore index 5700f4e89b..bdfa416126 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,4 @@ profile.json flamegraph.svg .idea/ .direnv +hierarchical_message_system_tree.txt diff --git a/editor/src/lib.rs b/editor/src/lib.rs index 80d80b5305..2c59945136 100644 --- a/editor/src/lib.rs +++ b/editor/src/lib.rs @@ -15,3 +15,4 @@ pub mod node_graph_executor; #[cfg(test)] pub mod test_utils; pub mod utility_traits; +pub mod utility_types; diff --git a/editor/src/messages/animation/animation_message_handler.rs b/editor/src/messages/animation/animation_message_handler.rs index eb7ceba2e0..211d22c87c 100644 --- a/editor/src/messages/animation/animation_message_handler.rs +++ b/editor/src/messages/animation/animation_message_handler.rs @@ -24,7 +24,7 @@ enum AnimationState { }, } -#[derive(Default, Debug, Clone, PartialEq)] +#[derive(Default, Debug, Clone, PartialEq, ExtractField)] pub struct AnimationMessageHandler { /// Used to re-send the UI on the next frame after playback starts live_preview_recently_zero: bool, @@ -57,6 +57,7 @@ impl AnimationMessageHandler { } } +#[message_handler_data] impl MessageHandler for AnimationMessageHandler { fn process_message(&mut self, message: AnimationMessage, responses: &mut VecDeque, _data: ()) { match message { diff --git a/editor/src/messages/broadcast/broadcast_message_handler.rs b/editor/src/messages/broadcast/broadcast_message_handler.rs index 51d64b5852..489df47ab7 100644 --- a/editor/src/messages/broadcast/broadcast_message_handler.rs +++ b/editor/src/messages/broadcast/broadcast_message_handler.rs @@ -1,10 +1,11 @@ use crate::messages::prelude::*; -#[derive(Debug, Clone, Default)] +#[derive(Debug, Clone, Default, ExtractField)] pub struct BroadcastMessageHandler { listeners: HashMap>, } +#[message_handler_data] impl MessageHandler for BroadcastMessageHandler { fn process_message(&mut self, message: BroadcastMessage, responses: &mut VecDeque, _data: ()) { match message { diff --git a/editor/src/messages/debug/debug_message_handler.rs b/editor/src/messages/debug/debug_message_handler.rs index 6044ce9108..064ad2510c 100644 --- a/editor/src/messages/debug/debug_message_handler.rs +++ b/editor/src/messages/debug/debug_message_handler.rs @@ -1,11 +1,12 @@ use super::utility_types::MessageLoggingVerbosity; use crate::messages::prelude::*; -#[derive(Debug, Default)] +#[derive(Debug, Default, ExtractField)] pub struct DebugMessageHandler { pub message_logging_verbosity: MessageLoggingVerbosity, } +#[message_handler_data] impl MessageHandler for DebugMessageHandler { fn process_message(&mut self, message: DebugMessage, responses: &mut VecDeque, _data: ()) { match message { diff --git a/editor/src/messages/dialog/dialog_message_handler.rs b/editor/src/messages/dialog/dialog_message_handler.rs index 3e9c80e46a..8e6bbde2f8 100644 --- a/editor/src/messages/dialog/dialog_message_handler.rs +++ b/editor/src/messages/dialog/dialog_message_handler.rs @@ -2,19 +2,21 @@ use super::simple_dialogs::{self, AboutGraphiteDialog, ComingSoonDialog, DemoArt use crate::messages::layout::utility_types::widget_prelude::*; use crate::messages::prelude::*; +#[derive(ExtractField)] pub struct DialogMessageData<'a> { pub portfolio: &'a PortfolioMessageHandler, pub preferences: &'a PreferencesMessageHandler, } /// Stores the dialogs which require state. These are the ones that have their own message handlers, and are not the ones defined in `simple_dialogs`. -#[derive(Debug, Default, Clone)] +#[derive(Debug, Default, Clone, ExtractField)] pub struct DialogMessageHandler { export_dialog: ExportDialogMessageHandler, new_document_dialog: NewDocumentDialogMessageHandler, preferences_dialog: PreferencesDialogMessageHandler, } +#[message_handler_data] impl MessageHandler> for DialogMessageHandler { fn process_message(&mut self, message: DialogMessage, responses: &mut VecDeque, data: DialogMessageData) { let DialogMessageData { portfolio, preferences } = data; diff --git a/editor/src/messages/dialog/export_dialog/export_dialog_message_handler.rs b/editor/src/messages/dialog/export_dialog/export_dialog_message_handler.rs index 1bcf4b7003..179aab4f96 100644 --- a/editor/src/messages/dialog/export_dialog/export_dialog_message_handler.rs +++ b/editor/src/messages/dialog/export_dialog/export_dialog_message_handler.rs @@ -3,12 +3,13 @@ use crate::messages::layout::utility_types::widget_prelude::*; use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier; use crate::messages::prelude::*; +#[derive(ExtractField)] pub struct ExportDialogMessageData<'a> { pub portfolio: &'a PortfolioMessageHandler, } /// A dialog to allow users to customize their file export. -#[derive(Debug, Clone)] +#[derive(Debug, Clone, ExtractField)] pub struct ExportDialogMessageHandler { pub file_type: FileType, pub scale_factor: f64, @@ -31,6 +32,7 @@ impl Default for ExportDialogMessageHandler { } } +#[message_handler_data] impl MessageHandler> for ExportDialogMessageHandler { fn process_message(&mut self, message: ExportDialogMessage, responses: &mut VecDeque, data: ExportDialogMessageData) { let ExportDialogMessageData { portfolio } = data; diff --git a/editor/src/messages/dialog/new_document_dialog/new_document_dialog_message_handler.rs b/editor/src/messages/dialog/new_document_dialog/new_document_dialog_message_handler.rs index 2e94f876c7..539180117c 100644 --- a/editor/src/messages/dialog/new_document_dialog/new_document_dialog_message_handler.rs +++ b/editor/src/messages/dialog/new_document_dialog/new_document_dialog_message_handler.rs @@ -4,13 +4,14 @@ use glam::{IVec2, UVec2}; use graph_craft::document::NodeId; /// A dialog to allow users to set some initial options about a new document. -#[derive(Debug, Clone, Default)] +#[derive(Debug, Clone, Default, ExtractField)] pub struct NewDocumentDialogMessageHandler { pub name: String, pub infinite: bool, pub dimensions: UVec2, } +#[message_handler_data] impl MessageHandler for NewDocumentDialogMessageHandler { fn process_message(&mut self, message: NewDocumentDialogMessage, responses: &mut VecDeque, _data: ()) { match message { 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 6e92d60bcf..882fc6db5c 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 @@ -4,14 +4,16 @@ use crate::messages::portfolio::document::utility_types::wires::GraphWireStyle; use crate::messages::preferences::SelectionMode; use crate::messages::prelude::*; +#[derive(ExtractField)] pub struct PreferencesDialogMessageData<'a> { pub preferences: &'a PreferencesMessageHandler, } /// A dialog to allow users to customize Graphite editor options -#[derive(Debug, Clone, Default)] +#[derive(Debug, Clone, Default, ExtractField)] pub struct PreferencesDialogMessageHandler {} +#[message_handler_data] impl MessageHandler> for PreferencesDialogMessageHandler { fn process_message(&mut self, message: PreferencesDialogMessage, responses: &mut VecDeque, data: PreferencesDialogMessageData) { let PreferencesDialogMessageData { preferences } = data; diff --git a/editor/src/messages/globals/globals_message_handler.rs b/editor/src/messages/globals/globals_message_handler.rs index 5651555f60..9a72ffa6dc 100644 --- a/editor/src/messages/globals/globals_message_handler.rs +++ b/editor/src/messages/globals/globals_message_handler.rs @@ -1,8 +1,9 @@ use crate::messages::prelude::*; -#[derive(Debug, Default)] +#[derive(Debug, Default, ExtractField)] pub struct GlobalsMessageHandler {} +#[message_handler_data] impl MessageHandler for GlobalsMessageHandler { fn process_message(&mut self, message: GlobalsMessage, _responses: &mut VecDeque, _data: ()) { match message { 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 eba3867213..b26a1b4c40 100644 --- a/editor/src/messages/input_mapper/input_mapper_message_handler.rs +++ b/editor/src/messages/input_mapper/input_mapper_message_handler.rs @@ -6,16 +6,18 @@ use crate::messages::portfolio::utility_types::KeyboardPlatformLayout; use crate::messages::prelude::*; use std::fmt::Write; +#[derive(ExtractField)] pub struct InputMapperMessageData<'a> { pub input: &'a InputPreprocessorMessageHandler, pub actions: ActionList, } -#[derive(Debug, Default)] +#[derive(Debug, Default, ExtractField)] pub struct InputMapperMessageHandler { mapping: Mapping, } +#[message_handler_data] impl MessageHandler> for InputMapperMessageHandler { fn process_message(&mut self, message: InputMapperMessage, responses: &mut VecDeque, data: InputMapperMessageData) { let InputMapperMessageData { input, actions } = data; 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 53c626bf54..81f249ec5e 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 @@ -2,16 +2,18 @@ use crate::messages::input_mapper::input_mapper_message_handler::InputMapperMess use crate::messages::input_mapper::utility_types::input_keyboard::KeysGroup; use crate::messages::prelude::*; +#[derive(ExtractField)] pub struct KeyMappingMessageData<'a> { pub input: &'a InputPreprocessorMessageHandler, pub actions: ActionList, } -#[derive(Debug, Default)] +#[derive(Debug, Default, ExtractField)] pub struct KeyMappingMessageHandler { mapping_handler: InputMapperMessageHandler, } +#[message_handler_data] impl MessageHandler> for KeyMappingMessageHandler { fn process_message(&mut self, message: KeyMappingMessage, responses: &mut VecDeque, data: KeyMappingMessageData) { let KeyMappingMessageData { input, actions } = data; diff --git a/editor/src/messages/input_preprocessor/input_preprocessor_message_handler.rs b/editor/src/messages/input_preprocessor/input_preprocessor_message_handler.rs index bf90e5881f..144652fdc5 100644 --- a/editor/src/messages/input_preprocessor/input_preprocessor_message_handler.rs +++ b/editor/src/messages/input_preprocessor/input_preprocessor_message_handler.rs @@ -6,11 +6,12 @@ use crate::messages::prelude::*; use glam::DVec2; use std::time::Duration; +#[derive(ExtractField)] pub struct InputPreprocessorMessageData { pub keyboard_platform: KeyboardPlatformLayout, } -#[derive(Debug, Default)] +#[derive(Debug, Default, ExtractField)] pub struct InputPreprocessorMessageHandler { pub frame_time: FrameTimeInfo, pub time: u64, @@ -19,6 +20,7 @@ pub struct InputPreprocessorMessageHandler { pub viewport_bounds: ViewportBounds, } +#[message_handler_data] impl MessageHandler for InputPreprocessorMessageHandler { fn process_message(&mut self, message: InputPreprocessorMessage, responses: &mut VecDeque, data: InputPreprocessorMessageData) { let InputPreprocessorMessageData { keyboard_platform } = data; diff --git a/editor/src/messages/layout/layout_message_handler.rs b/editor/src/messages/layout/layout_message_handler.rs index ee8e506ca8..7beec22fe9 100644 --- a/editor/src/messages/layout/layout_message_handler.rs +++ b/editor/src/messages/layout/layout_message_handler.rs @@ -6,7 +6,7 @@ use graphene_std::text::Font; use graphene_std::vector::style::{FillChoice, GradientStops}; use serde_json::Value; -#[derive(Debug, Clone, Default)] +#[derive(Debug, Clone, Default, ExtractField)] pub struct LayoutMessageHandler { layouts: [Layout; LayoutTarget::LayoutTargetLength as usize], } @@ -342,6 +342,15 @@ impl LayoutMessageHandler { } } +pub fn custom_data() -> MessageData { + // TODO: When is resolved and released, + // TODO: use to get + // TODO: the line number instead of hardcoding it to the magic number on the following line. + // TODO: Also, utilize the line number in the actual output, since it is currently unused. + MessageData::new(String::from("Function"), vec![(String::from("Fn(&MessageDiscriminant) -> Option"), 350)], file!()) +} + +#[message_handler_data(CustomData)] impl Option> MessageHandler for LayoutMessageHandler { fn process_message(&mut self, message: LayoutMessage, responses: &mut std::collections::VecDeque, action_input_mapping: F) { match message { diff --git a/editor/src/messages/message.rs b/editor/src/messages/message.rs index 80323d9309..fbe4170023 100644 --- a/editor/src/messages/message.rs +++ b/editor/src/messages/message.rs @@ -45,3 +45,86 @@ impl specta::Type for MessageDiscriminant { specta::DataType::Any } } + +#[cfg(test)] +mod test { + use super::*; + use std::io::Write; + + #[test] + fn generate_message_tree() { + let result = Message::build_message_tree(); + let mut file = std::fs::File::create("../hierarchical_message_system_tree.txt").unwrap(); + file.write_all(format!("{} `{}`\n", result.name(), result.path()).as_bytes()).unwrap(); + if let Some(variants) = result.variants() { + for (i, variant) in variants.iter().enumerate() { + let is_last = i == variants.len() - 1; + print_tree_node(variant, "", is_last, &mut file); + } + } + } + + fn print_tree_node(tree: &DebugMessageTree, prefix: &str, is_last: bool, file: &mut std::fs::File) { + // Print the current node + let (branch, child_prefix) = if tree.has_message_handler_data_fields() || tree.has_message_handler_fields() { + ("├── ", format!("{}│ ", prefix)) + } else { + if is_last { + ("└── ", format!("{} ", prefix)) + } else { + ("├── ", format!("{}│ ", prefix)) + } + }; + + if tree.path().is_empty() { + file.write_all(format!("{}{}{}\n", prefix, branch, tree.name()).as_bytes()).unwrap(); + } else { + file.write_all(format!("{}{}{} `{}`\n", prefix, branch, tree.name(), tree.path()).as_bytes()).unwrap(); + } + + // Print children if any + if let Some(variants) = tree.variants() { + let len = variants.len(); + for (i, variant) in variants.iter().enumerate() { + let is_last_child = i == len - 1; + print_tree_node(variant, &child_prefix, is_last_child, file); + } + } + + // Print handler field if any + if let Some(data) = tree.message_handler_fields() { + let len = data.fields().len(); + let (branch, child_prefix) = if tree.has_message_handler_data_fields() { + ("├── ", format!("{}│ ", prefix)) + } else { + ("└── ", format!("{} ", prefix)) + }; + if data.path().is_empty() { + file.write_all(format!("{}{}{}\n", prefix, branch, data.name()).as_bytes()).unwrap(); + } else { + file.write_all(format!("{}{}{} `{}`\n", prefix, branch, data.name(), data.path()).as_bytes()).unwrap(); + } + for (i, field) in data.fields().iter().enumerate() { + let is_last_field = i == len - 1; + let branch = if is_last_field { "└── " } else { "├── " }; + + file.write_all(format!("{}{}{}\n", child_prefix, branch, field.0).as_bytes()).unwrap(); + } + } + + // Print data field if any + if let Some(data) = tree.message_handler_data_fields() { + let len = data.fields().len(); + if data.path().is_empty() { + file.write_all(format!("{}{}{}\n", prefix, "└── ", data.name()).as_bytes()).unwrap(); + } else { + file.write_all(format!("{}{}{} `{}`\n", prefix, "└── ", data.name(), data.path()).as_bytes()).unwrap(); + } + for (i, field) in data.fields().iter().enumerate() { + let is_last_field = i == len - 1; + let branch = if is_last_field { "└── " } else { "├── " }; + file.write_all(format!("{}{}{}\n", format!("{} ", prefix), branch, field.0).as_bytes()).unwrap(); + } + } + } +} diff --git a/editor/src/messages/portfolio/document/document_message_handler.rs b/editor/src/messages/portfolio/document/document_message_handler.rs index 89e0aaac92..784c3ba3ef 100644 --- a/editor/src/messages/portfolio/document/document_message_handler.rs +++ b/editor/src/messages/portfolio/document/document_message_handler.rs @@ -38,6 +38,7 @@ use graphene_std::vector::click_target::{ClickTarget, ClickTargetType}; use graphene_std::vector::style::ViewMode; use std::time::Duration; +#[derive(ExtractField)] pub struct DocumentMessageData<'a> { pub document_id: DocumentId, pub ipp: &'a InputPreprocessorMessageHandler, @@ -48,7 +49,7 @@ pub struct DocumentMessageData<'a> { pub device_pixel_ratio: f64, } -#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)] +#[derive(Clone, Debug, serde::Serialize, serde::Deserialize, ExtractField)] #[serde(default)] pub struct DocumentMessageHandler { // ====================== @@ -168,6 +169,7 @@ impl Default for DocumentMessageHandler { } } +#[message_handler_data] impl MessageHandler> for DocumentMessageHandler { fn process_message(&mut self, message: DocumentMessage, responses: &mut VecDeque, data: DocumentMessageData) { let DocumentMessageData { diff --git a/editor/src/messages/portfolio/document/graph_operation/graph_operation_message_handler.rs b/editor/src/messages/portfolio/document/graph_operation/graph_operation_message_handler.rs index 40653e4af4..931a15e3bf 100644 --- a/editor/src/messages/portfolio/document/graph_operation/graph_operation_message_handler.rs +++ b/editor/src/messages/portfolio/document/graph_operation/graph_operation_message_handler.rs @@ -21,17 +21,19 @@ struct ArtboardInfo { merge_node: NodeId, } +#[derive(ExtractField)] pub struct GraphOperationMessageData<'a> { pub network_interface: &'a mut NodeNetworkInterface, pub collapsed: &'a mut CollapsedLayers, pub node_graph: &'a mut NodeGraphMessageHandler, } -#[derive(Debug, Clone, PartialEq, Default, serde::Serialize, serde::Deserialize)] +#[derive(Debug, Clone, PartialEq, Default, serde::Serialize, serde::Deserialize, ExtractField)] pub struct GraphOperationMessageHandler {} // GraphOperationMessageHandler always modified the document network. This is so changes to the layers panel will only affect the document network. // For changes to the selected network, use NodeGraphMessageHandler. No NodeGraphMessage's should be added here, since they will affect the selected nested network. +#[message_handler_data] impl MessageHandler> for GraphOperationMessageHandler { fn process_message(&mut self, message: GraphOperationMessage, responses: &mut VecDeque, data: GraphOperationMessageData) { let network_interface = data.network_interface; diff --git a/editor/src/messages/portfolio/document/navigation/navigation_message_handler.rs b/editor/src/messages/portfolio/document/navigation/navigation_message_handler.rs index 929c850160..0d732edd04 100644 --- a/editor/src/messages/portfolio/document/navigation/navigation_message_handler.rs +++ b/editor/src/messages/portfolio/document/navigation/navigation_message_handler.rs @@ -13,6 +13,7 @@ use crate::messages::tool::utility_types::{HintData, HintGroup, HintInfo}; use glam::{DAffine2, DVec2}; use graph_craft::document::NodeId; +#[derive(ExtractField)] pub struct NavigationMessageData<'a> { pub network_interface: &'a mut NodeNetworkInterface, pub breadcrumb_network_path: &'a [NodeId], @@ -23,7 +24,7 @@ pub struct NavigationMessageData<'a> { pub preferences: &'a PreferencesMessageHandler, } -#[derive(Debug, Clone, PartialEq, Default)] +#[derive(Debug, Clone, PartialEq, Default, ExtractField)] pub struct NavigationMessageHandler { navigation_operation: NavigationOperation, mouse_position: ViewportPosition, @@ -31,6 +32,7 @@ pub struct NavigationMessageHandler { abortable_pan_start: Option, } +#[message_handler_data] impl MessageHandler> for NavigationMessageHandler { fn process_message(&mut self, message: NavigationMessage, responses: &mut VecDeque, data: NavigationMessageData) { let NavigationMessageData { 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 b3f1596cd2..828fb4a8ae 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 @@ -27,7 +27,7 @@ use graphene_std::*; use renderer::Quad; use std::cmp::Ordering; -#[derive(Debug)] +#[derive(Debug, ExtractField)] pub struct NodeGraphHandlerData<'a> { pub network_interface: &'a mut NodeNetworkInterface, pub selection_network_path: &'a [NodeId], @@ -41,7 +41,7 @@ pub struct NodeGraphHandlerData<'a> { pub preferences: &'a PreferencesMessageHandler, } -#[derive(Debug, Clone)] +#[derive(Debug, Clone, ExtractField)] pub struct NodeGraphMessageHandler { // TODO: Remove network and move to NodeNetworkInterface pub network: Vec, @@ -92,6 +92,7 @@ pub struct NodeGraphMessageHandler { } /// 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. +#[message_handler_data] impl<'a> MessageHandler> for NodeGraphMessageHandler { fn process_message(&mut self, message: NodeGraphMessage, responses: &mut VecDeque, data: NodeGraphHandlerData<'a>) { let NodeGraphHandlerData { diff --git a/editor/src/messages/portfolio/document/overlays/overlays_message_handler.rs b/editor/src/messages/portfolio/document/overlays/overlays_message_handler.rs index 0db542da75..d4bb518e68 100644 --- a/editor/src/messages/portfolio/document/overlays/overlays_message_handler.rs +++ b/editor/src/messages/portfolio/document/overlays/overlays_message_handler.rs @@ -1,13 +1,14 @@ use super::utility_types::{OverlayProvider, OverlaysVisibilitySettings}; use crate::messages::prelude::*; +#[derive(ExtractField)] pub struct OverlaysMessageData<'a> { pub visibility_settings: OverlaysVisibilitySettings, pub ipp: &'a InputPreprocessorMessageHandler, pub device_pixel_ratio: f64, } -#[derive(Debug, Clone, Default)] +#[derive(Debug, Clone, Default, ExtractField)] pub struct OverlaysMessageHandler { pub overlay_providers: HashSet, #[cfg(target_arch = "wasm32")] @@ -16,6 +17,7 @@ pub struct OverlaysMessageHandler { context: Option, } +#[message_handler_data] impl MessageHandler> for OverlaysMessageHandler { fn process_message(&mut self, message: OverlaysMessage, responses: &mut VecDeque, data: OverlaysMessageData) { let OverlaysMessageData { visibility_settings, ipp, .. } = data; diff --git a/editor/src/messages/portfolio/document/properties_panel/properties_panel_message_handler.rs b/editor/src/messages/portfolio/document/properties_panel/properties_panel_message_handler.rs index a783b24aae..3ca9f350f3 100644 --- a/editor/src/messages/portfolio/document/properties_panel/properties_panel_message_handler.rs +++ b/editor/src/messages/portfolio/document/properties_panel/properties_panel_message_handler.rs @@ -4,9 +4,10 @@ use crate::messages::portfolio::document::node_graph::document_node_definitions: use crate::messages::portfolio::utility_types::PersistentData; use crate::messages::prelude::*; -#[derive(Debug, Clone, Default)] +#[derive(Debug, Clone, Default, ExtractField)] pub struct PropertiesPanelMessageHandler {} +#[message_handler_data] impl MessageHandler)> for PropertiesPanelMessageHandler { fn process_message(&mut self, message: PropertiesPanelMessage, responses: &mut VecDeque, (persistent_data, data): (&PersistentData, PropertiesPanelMessageHandlerData)) { let PropertiesPanelMessageHandlerData { diff --git a/editor/src/messages/portfolio/menu_bar/menu_bar_message_handler.rs b/editor/src/messages/portfolio/menu_bar/menu_bar_message_handler.rs index 50cfa632ac..26a9a739f3 100644 --- a/editor/src/messages/portfolio/menu_bar/menu_bar_message_handler.rs +++ b/editor/src/messages/portfolio/menu_bar/menu_bar_message_handler.rs @@ -6,7 +6,7 @@ use crate::messages::portfolio::document::utility_types::misc::{AlignAggregate, use crate::messages::prelude::*; use graphene_std::path_bool::BooleanOperation; -#[derive(Debug, Clone, Default)] +#[derive(Debug, Clone, Default, ExtractField)] pub struct MenuBarMessageHandler { pub has_active_document: bool, pub canvas_tilted: bool, @@ -21,6 +21,7 @@ pub struct MenuBarMessageHandler { pub reset_node_definitions_on_open: bool, } +#[message_handler_data] impl MessageHandler for MenuBarMessageHandler { fn process_message(&mut self, message: MenuBarMessage, responses: &mut VecDeque, _data: ()) { match message { diff --git a/editor/src/messages/portfolio/portfolio_message_handler.rs b/editor/src/messages/portfolio/portfolio_message_handler.rs index a6d9da338e..195768bd4b 100644 --- a/editor/src/messages/portfolio/portfolio_message_handler.rs +++ b/editor/src/messages/portfolio/portfolio_message_handler.rs @@ -25,6 +25,7 @@ use graphene_std::renderer::Quad; use graphene_std::text::Font; use std::vec; +#[derive(ExtractField)] pub struct PortfolioMessageData<'a> { pub ipp: &'a InputPreprocessorMessageHandler, pub preferences: &'a PreferencesMessageHandler, @@ -35,7 +36,7 @@ pub struct PortfolioMessageData<'a> { pub animation: &'a AnimationMessageHandler, } -#[derive(Debug, Default)] +#[derive(Debug, Default, ExtractField)] pub struct PortfolioMessageHandler { menu_bar_message_handler: MenuBarMessageHandler, pub documents: HashMap, @@ -52,6 +53,7 @@ pub struct PortfolioMessageHandler { pub reset_node_definitions_on_open: bool, } +#[message_handler_data] impl MessageHandler> for PortfolioMessageHandler { fn process_message(&mut self, message: PortfolioMessage, responses: &mut VecDeque, data: PortfolioMessageData) { let PortfolioMessageData { diff --git a/editor/src/messages/portfolio/spreadsheet/spreadsheet_message_handler.rs b/editor/src/messages/portfolio/spreadsheet/spreadsheet_message_handler.rs index 378c8b1b52..72c44c5975 100644 --- a/editor/src/messages/portfolio/spreadsheet/spreadsheet_message_handler.rs +++ b/editor/src/messages/portfolio/spreadsheet/spreadsheet_message_handler.rs @@ -15,7 +15,7 @@ use std::any::Any; use std::sync::Arc; /// The spreadsheet UI allows for instance data to be previewed. -#[derive(Default, Debug, Clone)] +#[derive(Default, Debug, Clone, ExtractField)] pub struct SpreadsheetMessageHandler { /// Sets whether or not the spreadsheet is drawn. pub spreadsheet_view_open: bool, @@ -25,6 +25,7 @@ pub struct SpreadsheetMessageHandler { viewing_vector_data_domain: VectorDataDomain, } +#[message_handler_data] impl MessageHandler for SpreadsheetMessageHandler { fn process_message(&mut self, message: SpreadsheetMessage, responses: &mut VecDeque, _data: ()) { match message { diff --git a/editor/src/messages/preferences/preferences_message_handler.rs b/editor/src/messages/preferences/preferences_message_handler.rs index b52e4e49a8..1e52233fe3 100644 --- a/editor/src/messages/preferences/preferences_message_handler.rs +++ b/editor/src/messages/preferences/preferences_message_handler.rs @@ -5,7 +5,7 @@ use crate::messages::preferences::SelectionMode; use crate::messages::prelude::*; use graph_craft::wasm_application_io::EditorPreferences; -#[derive(Debug, PartialEq, Clone, serde::Serialize, serde::Deserialize, specta::Type)] +#[derive(Debug, PartialEq, Clone, serde::Serialize, serde::Deserialize, specta::Type, ExtractField)] pub struct PreferencesMessageHandler { pub selection_mode: SelectionMode, pub zoom_with_scroll: bool, @@ -44,6 +44,7 @@ impl Default for PreferencesMessageHandler { } } +#[message_handler_data] impl MessageHandler for PreferencesMessageHandler { fn process_message(&mut self, message: PreferencesMessage, responses: &mut VecDeque, _data: ()) { match message { diff --git a/editor/src/messages/prelude.rs b/editor/src/messages/prelude.rs index d33b7c235c..7517c80f48 100644 --- a/editor/src/messages/prelude.rs +++ b/editor/src/messages/prelude.rs @@ -1,6 +1,6 @@ // Root -pub use crate::utility_traits::{ActionList, AsMessage, MessageHandler, ToDiscriminant, TransitiveChild}; - +pub use crate::utility_traits::{ActionList, AsMessage, HierarchicalTree, MessageHandler, ToDiscriminant, TransitiveChild}; +pub use crate::utility_types::{DebugMessageTree, MessageData}; // Message, MessageData, MessageDiscriminant, MessageHandler pub use crate::messages::animation::{AnimationMessage, AnimationMessageDiscriminant, AnimationMessageHandler}; pub use crate::messages::broadcast::{BroadcastMessage, BroadcastMessageDiscriminant, BroadcastMessageHandler}; diff --git a/editor/src/messages/tool/tool_message_handler.rs b/editor/src/messages/tool/tool_message_handler.rs index e20d0989b1..6000301a18 100644 --- a/editor/src/messages/tool/tool_message_handler.rs +++ b/editor/src/messages/tool/tool_message_handler.rs @@ -12,6 +12,7 @@ use graphene_std::raster::color::Color; const ARTBOARD_OVERLAY_PROVIDER: OverlayProvider = |context| DocumentMessage::DrawArtboardOverlays(context).into(); +#[derive(ExtractField)] pub struct ToolMessageData<'a> { pub document_id: DocumentId, pub document: &'a mut DocumentMessageHandler, @@ -21,7 +22,7 @@ pub struct ToolMessageData<'a> { pub preferences: &'a PreferencesMessageHandler, } -#[derive(Debug, Default)] +#[derive(Debug, Default, ExtractField)] pub struct ToolMessageHandler { pub tool_state: ToolFsmState, pub transform_layer_handler: TransformLayerMessageHandler, @@ -29,6 +30,7 @@ pub struct ToolMessageHandler { pub tool_is_active: bool, } +#[message_handler_data] impl MessageHandler> for ToolMessageHandler { fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque, data: ToolMessageData) { let ToolMessageData { diff --git a/editor/src/messages/tool/tool_messages/artboard_tool.rs b/editor/src/messages/tool/tool_messages/artboard_tool.rs index 0362b7f148..e14058c32c 100644 --- a/editor/src/messages/tool/tool_messages/artboard_tool.rs +++ b/editor/src/messages/tool/tool_messages/artboard_tool.rs @@ -13,7 +13,7 @@ use crate::messages::tool::common_functionality::transformation_cage::*; use graph_craft::document::NodeId; use graphene_std::renderer::Quad; -#[derive(Default)] +#[derive(Default, ExtractField)] pub struct ArtboardTool { fsm_state: ArtboardToolFsmState, data: ArtboardToolData, @@ -48,6 +48,7 @@ impl ToolMetadata for ArtboardTool { } } +#[message_handler_data] impl<'a> MessageHandler> for ArtboardTool { fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque, tool_data: &mut ToolActionHandlerData<'a>) { self.fsm_state.process_event(message, &mut self.data, tool_data, &(), responses, false); diff --git a/editor/src/messages/tool/tool_messages/brush_tool.rs b/editor/src/messages/tool/tool_messages/brush_tool.rs index b468603989..9bba3711cf 100644 --- a/editor/src/messages/tool/tool_messages/brush_tool.rs +++ b/editor/src/messages/tool/tool_messages/brush_tool.rs @@ -20,7 +20,7 @@ pub enum DrawMode { Restore, } -#[derive(Default)] +#[derive(Default, ExtractField)] pub struct BrushTool { fsm_state: BrushToolFsmState, data: BrushToolData, @@ -185,6 +185,7 @@ impl LayoutHolder for BrushTool { } } +#[message_handler_data] impl<'a> MessageHandler> for BrushTool { fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque, tool_data: &mut ToolActionHandlerData<'a>) { let ToolMessage::Brush(BrushToolMessage::UpdateOptions(action)) = message else { diff --git a/editor/src/messages/tool/tool_messages/eyedropper_tool.rs b/editor/src/messages/tool/tool_messages/eyedropper_tool.rs index 3b3f324972..ce4b09d8e9 100644 --- a/editor/src/messages/tool/tool_messages/eyedropper_tool.rs +++ b/editor/src/messages/tool/tool_messages/eyedropper_tool.rs @@ -1,7 +1,7 @@ use super::tool_prelude::*; use crate::messages::tool::utility_types::DocumentToolData; -#[derive(Default)] +#[derive(Default, ExtractField)] pub struct EyedropperTool { fsm_state: EyedropperToolFsmState, data: EyedropperToolData, @@ -39,6 +39,7 @@ impl LayoutHolder for EyedropperTool { } } +#[message_handler_data] impl<'a> MessageHandler> for EyedropperTool { fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque, tool_data: &mut ToolActionHandlerData<'a>) { self.fsm_state.process_event(message, &mut self.data, tool_data, &(), responses, true); diff --git a/editor/src/messages/tool/tool_messages/fill_tool.rs b/editor/src/messages/tool/tool_messages/fill_tool.rs index 5aeb063d87..94ad2b8c21 100644 --- a/editor/src/messages/tool/tool_messages/fill_tool.rs +++ b/editor/src/messages/tool/tool_messages/fill_tool.rs @@ -3,7 +3,7 @@ use crate::messages::portfolio::document::overlays::utility_types::OverlayContex use crate::messages::tool::common_functionality::graph_modification_utils::NodeGraphLayer; use graphene_std::vector::style::Fill; -#[derive(Default)] +#[derive(Default, ExtractField)] pub struct FillTool { fsm_state: FillToolFsmState, } @@ -41,6 +41,7 @@ impl LayoutHolder for FillTool { } } +#[message_handler_data] impl<'a> MessageHandler> for FillTool { fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque, tool_data: &mut ToolActionHandlerData<'a>) { self.fsm_state.process_event(message, &mut (), tool_data, &(), responses, true); diff --git a/editor/src/messages/tool/tool_messages/freehand_tool.rs b/editor/src/messages/tool/tool_messages/freehand_tool.rs index e2abddef2a..fb1539a818 100644 --- a/editor/src/messages/tool/tool_messages/freehand_tool.rs +++ b/editor/src/messages/tool/tool_messages/freehand_tool.rs @@ -13,7 +13,7 @@ use graphene_std::Color; use graphene_std::vector::VectorModificationType; use graphene_std::vector::{PointId, SegmentId}; -#[derive(Default)] +#[derive(Default, ExtractField)] pub struct FreehandTool { fsm_state: FreehandToolFsmState, data: FreehandToolData, @@ -116,6 +116,7 @@ impl LayoutHolder for FreehandTool { } } +#[message_handler_data] impl<'a> MessageHandler> for FreehandTool { fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque, tool_data: &mut ToolActionHandlerData<'a>) { let ToolMessage::Freehand(FreehandToolMessage::UpdateOptions(action)) = message else { diff --git a/editor/src/messages/tool/tool_messages/gradient_tool.rs b/editor/src/messages/tool/tool_messages/gradient_tool.rs index 030da8ec1d..0e84f9585f 100644 --- a/editor/src/messages/tool/tool_messages/gradient_tool.rs +++ b/editor/src/messages/tool/tool_messages/gradient_tool.rs @@ -7,7 +7,7 @@ use crate::messages::tool::common_functionality::graph_modification_utils::{Node use crate::messages::tool::common_functionality::snapping::SnapManager; use graphene_std::vector::style::{Fill, Gradient, GradientType}; -#[derive(Default)] +#[derive(Default, ExtractField)] pub struct GradientTool { fsm_state: GradientToolFsmState, data: GradientToolData, @@ -53,6 +53,7 @@ impl ToolMetadata for GradientTool { } } +#[message_handler_data] impl<'a> MessageHandler> for GradientTool { fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque, tool_data: &mut ToolActionHandlerData<'a>) { let ToolMessage::Gradient(GradientToolMessage::UpdateOptions(action)) = message else { diff --git a/editor/src/messages/tool/tool_messages/navigate_tool.rs b/editor/src/messages/tool/tool_messages/navigate_tool.rs index 0a76a331dc..9f9ef0307b 100644 --- a/editor/src/messages/tool/tool_messages/navigate_tool.rs +++ b/editor/src/messages/tool/tool_messages/navigate_tool.rs @@ -1,6 +1,6 @@ use super::tool_prelude::*; -#[derive(Default)] +#[derive(Default, ExtractField)] pub struct NavigateTool { fsm_state: NavigateToolFsmState, tool_data: NavigateToolData, @@ -38,6 +38,7 @@ impl LayoutHolder for NavigateTool { } } +#[message_handler_data] impl<'a> MessageHandler> for NavigateTool { fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque, tool_data: &mut ToolActionHandlerData<'a>) { self.fsm_state.process_event(message, &mut self.tool_data, tool_data, &(), responses, true); diff --git a/editor/src/messages/tool/tool_messages/path_tool.rs b/editor/src/messages/tool/tool_messages/path_tool.rs index 0642613cf0..d54ce54274 100644 --- a/editor/src/messages/tool/tool_messages/path_tool.rs +++ b/editor/src/messages/tool/tool_messages/path_tool.rs @@ -24,7 +24,7 @@ use graphene_std::vector::{HandleExt, HandleId, NoHashBuilder, SegmentId, Vector use graphene_std::vector::{ManipulatorPointId, PointId, VectorModificationType}; use std::vec; -#[derive(Default)] +#[derive(Default, ExtractField)] pub struct PathTool { fsm_state: PathToolFsmState, tool_data: PathToolData, @@ -305,6 +305,7 @@ impl LayoutHolder for PathTool { } } +#[message_handler_data] impl<'a> MessageHandler> for PathTool { fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque, tool_data: &mut ToolActionHandlerData<'a>) { let updating_point = message == ToolMessage::Path(PathToolMessage::SelectedPointUpdated); diff --git a/editor/src/messages/tool/tool_messages/pen_tool.rs b/editor/src/messages/tool/tool_messages/pen_tool.rs index 14f95a48f7..53a9e4d17c 100644 --- a/editor/src/messages/tool/tool_messages/pen_tool.rs +++ b/editor/src/messages/tool/tool_messages/pen_tool.rs @@ -17,7 +17,7 @@ use graphene_std::Color; use graphene_std::vector::{HandleId, ManipulatorPointId, NoHashBuilder, SegmentId, StrokeId, VectorData}; use graphene_std::vector::{PointId, VectorModificationType}; -#[derive(Default)] +#[derive(Default, ExtractField)] pub struct PenTool { fsm_state: PenToolFsmState, tool_data: PenToolData, @@ -186,6 +186,7 @@ impl LayoutHolder for PenTool { } } +#[message_handler_data] impl<'a> MessageHandler> for PenTool { fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque, tool_data: &mut ToolActionHandlerData<'a>) { let ToolMessage::Pen(PenToolMessage::UpdateOptions(action)) = message else { diff --git a/editor/src/messages/tool/tool_messages/select_tool.rs b/editor/src/messages/tool/tool_messages/select_tool.rs index 9ae3459a7d..6c8498317f 100644 --- a/editor/src/messages/tool/tool_messages/select_tool.rs +++ b/editor/src/messages/tool/tool_messages/select_tool.rs @@ -29,7 +29,7 @@ use graphene_std::renderer::Rect; use graphene_std::transform::ReferencePoint; use std::fmt; -#[derive(Default)] +#[derive(Default, ExtractField)] pub struct SelectTool { fsm_state: SelectToolFsmState, tool_data: SelectToolData, @@ -272,6 +272,7 @@ impl LayoutHolder for SelectTool { } } +#[message_handler_data] impl<'a> MessageHandler> for SelectTool { fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque, tool_data: &mut ToolActionHandlerData<'a>) { let mut redraw_reference_pivot = false; diff --git a/editor/src/messages/tool/tool_messages/spline_tool.rs b/editor/src/messages/tool/tool_messages/spline_tool.rs index 2ac541d702..2de6747f01 100644 --- a/editor/src/messages/tool/tool_messages/spline_tool.rs +++ b/editor/src/messages/tool/tool_messages/spline_tool.rs @@ -14,7 +14,7 @@ use graph_craft::document::{NodeId, NodeInput}; use graphene_std::Color; use graphene_std::vector::{PointId, SegmentId, VectorModificationType}; -#[derive(Default)] +#[derive(Default, ExtractField)] pub struct SplineTool { fsm_state: SplineToolFsmState, tool_data: SplineToolData, @@ -123,6 +123,7 @@ impl LayoutHolder for SplineTool { } } +#[message_handler_data] impl<'a> MessageHandler> for SplineTool { fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque, tool_data: &mut ToolActionHandlerData<'a>) { let ToolMessage::Spline(SplineToolMessage::UpdateOptions(action)) = message else { diff --git a/editor/src/messages/tool/tool_messages/text_tool.rs b/editor/src/messages/tool/tool_messages/text_tool.rs index 6be22719cb..52430f09ca 100644 --- a/editor/src/messages/tool/tool_messages/text_tool.rs +++ b/editor/src/messages/tool/tool_messages/text_tool.rs @@ -20,7 +20,7 @@ use graphene_std::renderer::Quad; use graphene_std::text::{Font, FontCache, TypesettingConfig, lines_clipping, load_font}; use graphene_std::vector::style::Fill; -#[derive(Default)] +#[derive(Default, ExtractField)] pub struct TextTool { fsm_state: TextToolFsmState, tool_data: TextToolData, @@ -170,6 +170,7 @@ impl LayoutHolder for TextTool { } } +#[message_handler_data] impl<'a> MessageHandler> for TextTool { fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque, tool_data: &mut ToolActionHandlerData<'a>) { let ToolMessage::Text(TextToolMessage::UpdateOptions(action)) = message else { 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 7258163772..a6221f729d 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 @@ -21,7 +21,7 @@ const TRANSFORM_GRS_OVERLAY_PROVIDER: OverlayProvider = |context| TransformLayer const SLOW_KEY: Key = Key::Shift; const INCREMENTS_KEY: Key = Key::Control; -#[derive(Debug, Clone, Default)] +#[derive(Debug, Clone, Default, ExtractField)] pub struct TransformLayerMessageHandler { pub transform_operation: TransformOperation, @@ -175,6 +175,26 @@ fn update_colinear_handles(selected_layers: &[LayerNodeIdentifier], document: &D } type TransformData<'a> = (&'a DocumentMessageHandler, &'a InputPreprocessorMessageHandler, &'a ToolData, &'a mut ShapeState); + +pub fn custom_data() -> MessageData { + MessageData::new( + String::from("TransformData<'a>"), + // TODO: When is resolved and released, + // TODO: use to get + // TODO: the line number instead of hardcoding it to the magic number on the following lines + // TODO: which points to the line of the `type TransformData<'a> = ...` definition above. + // TODO: Also, utilize the line number in the actual output, since it is currently unused. + vec![ + (String::from("&'a DocumentMessageHandler"), 177), + (String::from("&'a InputPreprocessorMessageHandler"), 177), + (String::from("&'a ToolData"), 177), + (String::from("&'a mut ShapeState"), 177), + ], + file!(), + ) +} + +#[message_handler_data(CustomData)] impl MessageHandler> for TransformLayerMessageHandler { fn process_message(&mut self, message: TransformLayerMessage, responses: &mut VecDeque, (document, input, tool_data, shape_editor): TransformData) { let using_path_tool = tool_data.active_tool_type == ToolType::Path; diff --git a/editor/src/messages/tool/utility_types.rs b/editor/src/messages/tool/utility_types.rs index fbbec768e4..fc06385f43 100644 --- a/editor/src/messages/tool/utility_types.rs +++ b/editor/src/messages/tool/utility_types.rs @@ -18,6 +18,7 @@ use graphene_std::text::FontCache; use std::borrow::Cow; use std::fmt::{self, Debug}; +#[derive(ExtractField)] pub struct ToolActionHandlerData<'a> { pub document: &'a mut DocumentMessageHandler, pub document_id: DocumentId, diff --git a/editor/src/messages/workspace/workspace_message_handler.rs b/editor/src/messages/workspace/workspace_message_handler.rs index 397e7cf00b..47c81aab0e 100644 --- a/editor/src/messages/workspace/workspace_message_handler.rs +++ b/editor/src/messages/workspace/workspace_message_handler.rs @@ -1,10 +1,11 @@ use crate::messages::prelude::*; -#[derive(Debug, Clone, Default)] +#[derive(Debug, Clone, Default, ExtractField)] pub struct WorkspaceMessageHandler { node_graph_visible: bool, } +#[message_handler_data] impl MessageHandler for WorkspaceMessageHandler { fn process_message(&mut self, message: WorkspaceMessage, _responses: &mut VecDeque, _data: ()) { match message { diff --git a/editor/src/utility_traits.rs b/editor/src/utility_traits.rs index 711a8cc34b..850d730b13 100644 --- a/editor/src/utility_traits.rs +++ b/editor/src/utility_traits.rs @@ -45,3 +45,19 @@ pub trait TransitiveChild: Into + Into { pub trait Hint { fn hints(&self) -> HashMap; } + +pub trait HierarchicalTree { + fn build_message_tree() -> DebugMessageTree; + + fn message_handler_data_str() -> MessageData { + MessageData::new(String::new(), Vec::new(), "") + } + + fn message_handler_str() -> MessageData { + MessageData::new(String::new(), Vec::new(), "") + } + + fn path() -> &'static str { + "" + } +} diff --git a/editor/src/utility_types.rs b/editor/src/utility_types.rs new file mode 100644 index 0000000000..6b5dc6de6b --- /dev/null +++ b/editor/src/utility_types.rs @@ -0,0 +1,99 @@ +#[derive(Debug)] +pub struct MessageData { + name: String, + fields: Vec<(String, usize)>, + path: &'static str, +} + +impl MessageData { + pub fn new(name: String, fields: Vec<(String, usize)>, path: &'static str) -> MessageData { + MessageData { name, fields, path } + } + + pub fn name(&self) -> &str { + &self.name + } + + pub fn fields(&self) -> &Vec<(String, usize)> { + &self.fields + } + + pub fn path(&self) -> &'static str { + self.path + } +} + +#[derive(Debug)] +pub struct DebugMessageTree { + name: String, + variants: Option>, + message_handler: Option, + message_handler_data: Option, + path: &'static str, +} + +impl DebugMessageTree { + pub fn new(name: &str) -> DebugMessageTree { + DebugMessageTree { + name: name.to_string(), + variants: None, + message_handler: None, + message_handler_data: None, + path: "", + } + } + + pub fn set_path(&mut self, path: &'static str) { + self.path = path; + } + + pub fn add_variant(&mut self, variant: DebugMessageTree) { + if let Some(variants) = &mut self.variants { + variants.push(variant); + } else { + self.variants = Some(vec![variant]); + } + } + + pub fn add_message_handler_data_field(&mut self, message_handler_data: MessageData) { + self.message_handler_data = Some(message_handler_data); + } + + pub fn add_message_handler_field(&mut self, message_handler: MessageData) { + self.message_handler = Some(message_handler); + } + + pub fn name(&self) -> &str { + &self.name + } + + pub fn path(&self) -> &'static str { + self.path + } + + pub fn variants(&self) -> Option<&Vec> { + self.variants.as_ref() + } + + pub fn message_handler_data_fields(&self) -> Option<&MessageData> { + self.message_handler_data.as_ref() + } + + pub fn message_handler_fields(&self) -> Option<&MessageData> { + self.message_handler.as_ref() + } + + pub fn has_message_handler_data_fields(&self) -> bool { + match self.message_handler_data_fields() { + Some(_) => true, + None => false, + } + } + + pub fn has_message_handler_fields(&self) -> bool { + match self.message_handler_fields() { + Some(_) => true, + None => false, + } + } +} diff --git a/proc-macros/src/combined_message_attrs.rs b/proc-macros/src/combined_message_attrs.rs index aadca6384e..943457cd1c 100644 --- a/proc-macros/src/combined_message_attrs.rs +++ b/proc-macros/src/combined_message_attrs.rs @@ -61,7 +61,7 @@ pub fn combined_message_attrs_impl(attr: TokenStream, input_item: TokenStream) - <#parent as ToDiscriminant>::Discriminant }; - input.attrs.push(syn::parse_quote! { #[derive(ToDiscriminant, TransitiveChild)] }); + input.attrs.push(syn::parse_quote! { #[derive(ToDiscriminant, TransitiveChild, HierarchicalTree)] }); input.attrs.push(syn::parse_quote! { #[parent(#parent, #parent::#variant)] }); if parent_is_top { input.attrs.push(syn::parse_quote! { #[parent_is_top] }); @@ -97,7 +97,7 @@ pub fn combined_message_attrs_impl(attr: TokenStream, input_item: TokenStream) - fn top_level_impl(input_item: TokenStream) -> syn::Result { let mut input = syn::parse2::(input_item)?; - input.attrs.push(syn::parse_quote! { #[derive(ToDiscriminant)] }); + input.attrs.push(syn::parse_quote! { #[derive(ToDiscriminant, HierarchicalTree)] }); input.attrs.push(syn::parse_quote! { #[discriminant_attr(derive(Debug, Copy, Clone, PartialEq, Eq, Hash, AsMessage))] }); for var in &mut input.variants { diff --git a/proc-macros/src/extract_fields.rs b/proc-macros/src/extract_fields.rs new file mode 100644 index 0000000000..606b3b8a37 --- /dev/null +++ b/proc-macros/src/extract_fields.rs @@ -0,0 +1,57 @@ +use crate::helpers::clean_rust_type_syntax; +use proc_macro2::{Span, TokenStream}; +use quote::{ToTokens, format_ident, quote}; +use syn::{Data, DeriveInput, Fields, Type, parse2}; + +pub fn derive_extract_field_impl(input: TokenStream) -> syn::Result { + let input = parse2::(input)?; + let struct_name = &input.ident; + let generics = &input.generics; + let (impl_generics, ty_generics, where_clause) = generics.split_for_impl(); + + let fields = match &input.data { + Data::Struct(data) => match &data.fields { + Fields::Named(fields) => &fields.named, + _ => return Err(syn::Error::new(Span::call_site(), "ExtractField only works on structs with named fields")), + }, + _ => return Err(syn::Error::new(Span::call_site(), "ExtractField only works on structs")), + }; + + let mut field_line = Vec::new(); + // Extract field names and types as strings at compile time + let field_info = fields + .iter() + .map(|field| { + let ident = field.ident.as_ref().unwrap(); + let name = ident.to_string(); + let ty = clean_rust_type_syntax(field.ty.to_token_stream().to_string()); + let line = ident.span().start().line; + field_line.push(line); + (name, ty) + }) + .collect::>(); + + let field_str = field_info.into_iter().map(|(name, ty)| (format!("{}: {}", name, ty))); + + let res = quote! { + impl #impl_generics #struct_name #ty_generics #where_clause { + pub fn field_types() -> Vec<(String, usize)> { + vec![ + #((String::from(#field_str), #field_line)),* + ] + } + + pub fn print_field_types() { + for (field, line) in Self::field_types() { + println!("{} at line {}", field, line); + } + } + + pub fn path() -> &'static str { + file!() + } + } + }; + + Ok(res) +} diff --git a/proc-macros/src/helpers.rs b/proc-macros/src/helpers.rs index 11c18c8b41..230f792c0f 100644 --- a/proc-macros/src/helpers.rs +++ b/proc-macros/src/helpers.rs @@ -42,6 +42,58 @@ pub fn two_segment_path(left_ident: Ident, right_ident: Ident) -> Path { Path { leading_colon: None, segments } } +pub fn clean_rust_type_syntax(input: String) -> String { + let mut result = String::new(); + let mut chars = input.chars().peekable(); + + while let Some(c) = chars.next() { + match c { + '&' => { + result.push('&'); + while let Some(' ') = chars.peek() { + chars.next(); + } + } + '<' => { + while let Some(' ') = result.chars().rev().next() { + result.pop(); + } + result.push('<'); + while let Some(' ') = chars.peek() { + chars.next(); + } + } + '>' => { + while let Some(' ') = result.chars().rev().next() { + result.pop(); + } + result.push('>'); + while let Some(' ') = chars.peek() { + chars.next(); + } + } + ':' => { + if let Some(':') = chars.peek() { + while let Some(' ') = result.chars().rev().next() { + result.pop(); + } + } + result.push(':'); + chars.next(); + result.push(':'); + while let Some(' ') = chars.peek() { + chars.next(); + } + } + _ => { + result.push(c); + } + } + } + + result +} + #[cfg(test)] mod tests { use super::*; diff --git a/proc-macros/src/hierarchical_tree.rs b/proc-macros/src/hierarchical_tree.rs new file mode 100644 index 0000000000..e0d6fb6a71 --- /dev/null +++ b/proc-macros/src/hierarchical_tree.rs @@ -0,0 +1,73 @@ +use proc_macro2::{Span, TokenStream}; +use quote::{ToTokens, quote}; +use syn::{Data, DeriveInput, Fields, Type, parse2}; + +pub fn generate_hierarchical_tree(input: TokenStream) -> syn::Result { + let input = parse2::(input)?; + let input_type = &input.ident; + + let data = match &input.data { + Data::Enum(data) => data, + _ => return Err(syn::Error::new(Span::call_site(), "Tried to derive HierarchicalTree for non-enum")), + }; + + let build_message_tree = data.variants.iter().map(|variant| { + let variant_type = &variant.ident; + + let has_child = variant + .attrs + .iter() + .any(|attr| attr.path().get_ident().is_some_and(|ident| ident == "sub_discriminant" || ident == "child")); + + if has_child { + if let Fields::Unnamed(fields) = &variant.fields { + let field_type = &fields.unnamed.first().unwrap().ty; + quote! { + { + let mut variant_tree = DebugMessageTree::new(stringify!(#variant_type)); + let field_name = stringify!(#field_type); + const message_string: &str = "Message"; + if message_string == &field_name[field_name.len().saturating_sub(message_string.len())..] { + // The field is a Message type, recursively build its tree + let sub_tree = #field_type::build_message_tree(); + variant_tree.add_variant(sub_tree); + } + message_tree.add_variant(variant_tree); + } + } + } else { + quote! { + message_tree.add_variant(DebugMessageTree::new(stringify!(#variant_type))); + } + } + } else { + quote! { + message_tree.add_variant(DebugMessageTree::new(stringify!(#variant_type))); + } + } + }); + + let res = quote! { + impl HierarchicalTree for #input_type { + fn build_message_tree() -> DebugMessageTree { + let mut message_tree = DebugMessageTree::new(stringify!(#input_type)); + #(#build_message_tree)* + let message_handler_str = #input_type::message_handler_str(); + if message_handler_str.fields().len() > 0 { + message_tree.add_message_handler_field(message_handler_str); + } + + let message_handler_data_str = #input_type::message_handler_data_str(); + if message_handler_data_str.fields().len() > 0 { + message_tree.add_message_handler_data_field(message_handler_data_str); + } + + message_tree.set_path(file!()); + + message_tree + } + } + }; + + Ok(res) +} diff --git a/proc-macros/src/lib.rs b/proc-macros/src/lib.rs index 02cd1caaf4..8d68df75c8 100644 --- a/proc-macros/src/lib.rs +++ b/proc-macros/src/lib.rs @@ -3,17 +3,23 @@ mod as_message; mod combined_message_attrs; mod discriminant; +mod extract_fields; mod helper_structs; mod helpers; +mod hierarchical_tree; mod hint; +mod message_handler_data_attr; mod transitive_child; mod widget_builder; use crate::as_message::derive_as_message_impl; use crate::combined_message_attrs::combined_message_attrs_impl; use crate::discriminant::derive_discriminant_impl; +use crate::extract_fields::derive_extract_field_impl; use crate::helper_structs::AttrInnerSingleString; +use crate::hierarchical_tree::generate_hierarchical_tree; use crate::hint::derive_hint_impl; +use crate::message_handler_data_attr::message_handler_data_attr_impl; use crate::transitive_child::derive_transitive_child_impl; use crate::widget_builder::derive_widget_builder_impl; use proc_macro::TokenStream; @@ -281,6 +287,21 @@ pub fn derive_widget_builder(input_item: TokenStream) -> TokenStream { TokenStream::from(derive_widget_builder_impl(input_item.into()).unwrap_or_else(|err| err.to_compile_error())) } +#[proc_macro_derive(HierarchicalTree)] +pub fn derive_hierarchical_tree(input_item: TokenStream) -> TokenStream { + TokenStream::from(generate_hierarchical_tree(input_item.into()).unwrap_or_else(|err| err.to_compile_error())) +} + +#[proc_macro_derive(ExtractField)] +pub fn derive_extract_field(input_item: TokenStream) -> TokenStream { + TokenStream::from(derive_extract_field_impl(input_item.into()).unwrap_or_else(|err| err.to_compile_error())) +} + +#[proc_macro_attribute] +pub fn message_handler_data(attr: TokenStream, input_item: TokenStream) -> TokenStream { + TokenStream::from(message_handler_data_attr_impl(attr.into(), input_item.into()).unwrap_or_else(|err| err.to_compile_error())) +} + #[cfg(test)] mod tests { use super::*; diff --git a/proc-macros/src/message_handler_data_attr.rs b/proc-macros/src/message_handler_data_attr.rs new file mode 100644 index 0000000000..b2170beac4 --- /dev/null +++ b/proc-macros/src/message_handler_data_attr.rs @@ -0,0 +1,118 @@ +use crate::helpers::{call_site_ident, clean_rust_type_syntax}; +use proc_macro2::{Span, TokenStream}; +use quote::{ToTokens, quote}; +use syn::{ItemImpl, Type, parse2, spanned::Spanned}; + +pub fn message_handler_data_attr_impl(attr: TokenStream, input_item: TokenStream) -> syn::Result { + // Parse the input as an impl block + let impl_block = parse2::(input_item.clone())?; + + let self_ty = &impl_block.self_ty; + + let path = match &**self_ty { + Type::Path(path) => &path.path, + _ => return Err(syn::Error::new(Span::call_site(), "Expected impl implementation")), + }; + + let input_type = path.segments.last().map(|s| &s.ident).unwrap(); + + // Extract the message type from the trait path + let trait_path = match &impl_block.trait_ { + Some((_, path, _)) => path, + None => return Err(syn::Error::new(Span::call_site(), "Expected trait implementation")), + }; + + // Get the trait generics (should be MessageHandler) + if let Some(segment) = trait_path.segments.last() { + if segment.ident != "MessageHandler" { + return Err(syn::Error::new(segment.ident.span(), "Expected MessageHandler trait")); + } + if let syn::PathArguments::AngleBracketed(args) = &segment.arguments { + if args.args.len() >= 2 { + // Extract the message type (M) and data type (D) from the trait params + let message_type = &args.args[0]; + let data_type = &args.args[1]; + + // Check if the attribute is "CustomData" + let is_custom_data = attr.to_string().contains("CustomData"); + + let impl_item = match data_type { + syn::GenericArgument::Type(t) => { + match t { + syn::Type::Path(type_path) if !type_path.path.segments.is_empty() => { + // Get just the base identifier (ToolMessageData) without generics + let type_name = &type_path.path.segments.first().unwrap().ident; + + if is_custom_data { + quote! { + #input_item + impl #message_type { + pub fn message_handler_data_str() -> MessageData { + custom_data() + } + pub fn message_handler_str() -> MessageData { + MessageData::new(format!("{}",stringify!(#input_type)), #input_type::field_types(), #input_type::path()) + + } + } + } + } else { + quote! { + #input_item + impl #message_type { + pub fn message_handler_data_str() -> MessageData + { + MessageData::new(format!("{}",stringify!(#type_name)), #type_name::field_types(), #type_name::path()) + + } + pub fn message_handler_str() -> MessageData { + MessageData::new(format!("{}",stringify!(#input_type)), #input_type::field_types(), #input_type::path()) + + } + } + } + } + } + syn::Type::Tuple(_) => quote! { + #input_item + impl #message_type { + pub fn message_handler_str() -> MessageData { + MessageData::new(format!("{}",stringify!(#input_type)), #input_type::field_types(), #input_type::path()) + } + } + }, + syn::Type::Reference(type_reference) => { + let message_type = call_site_ident(format!("{input_type}Message")); + let type_ident = match &*type_reference.elem { + syn::Type::Path(type_path) => &type_path.path.segments.first().unwrap().ident, + _ => return Err(syn::Error::new(type_reference.elem.span(), "Expected type path")), + }; + let tr = clean_rust_type_syntax(type_reference.to_token_stream().to_string()); + quote! { + #input_item + impl #message_type { + pub fn message_handler_data_str() -> MessageData { + MessageData::new(format!("{}", #tr),#type_ident::field_types(), #type_ident::path()) + } + + pub fn message_handler_str() -> MessageData { + MessageData::new(format!("{}",stringify!(#input_type)), #input_type::field_types(), #input_type::path()) + + } + } + } + } + _ => return Err(syn::Error::new(t.span(), "Unsupported type format")), + } + } + + _ => quote! { + #input_item + }, + }; + return Ok(impl_item); + } + } + } + Ok(input_item) +} From a40de58c7c3ef3cf568476dea5b9e4549a04506a Mon Sep 17 00:00:00 2001 From: mTvare Date: Wed, 9 Jul 2025 12:15:41 +0530 Subject: [PATCH 3/8] Fix brush bounding boxes by making BrushCacheImpl's hash not shared between different instances (#2845) * fix: add cache to each layer * fix: warning * fix: tests * Clean up code --------- Co-authored-by: Keavon Chambers --- .../node_graph/document_node_definitions.rs | 2 +- .../messages/tool/tool_messages/brush_tool.rs | 6 +- node-graph/gbrush/src/brush.rs | 2 +- node-graph/gbrush/src/brush_cache.rs | 84 +++++++++---------- 4 files changed, 45 insertions(+), 49 deletions(-) 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 594f8125ee..3c85102098 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 @@ -946,7 +946,7 @@ fn static_nodes() -> Vec { inputs: vec![ NodeInput::value(TaggedValue::RasterData(RasterDataTable::default()), true), NodeInput::value(TaggedValue::BrushStrokes(Vec::new()), false), - NodeInput::value(TaggedValue::BrushCache(BrushCache::new_proto()), false), + NodeInput::value(TaggedValue::BrushCache(BrushCache::default()), false), ], ..Default::default() }, diff --git a/editor/src/messages/tool/tool_messages/brush_tool.rs b/editor/src/messages/tool/tool_messages/brush_tool.rs index 9bba3711cf..a625bdb9e6 100644 --- a/editor/src/messages/tool/tool_messages/brush_tool.rs +++ b/editor/src/messages/tool/tool_messages/brush_tool.rs @@ -1,6 +1,6 @@ use super::tool_prelude::*; use crate::consts::DEFAULT_BRUSH_SIZE; -use crate::messages::portfolio::document::graph_operation::transform_utils::{get_current_normalized_pivot, get_current_transform}; +use crate::messages::portfolio::document::graph_operation::transform_utils::get_current_transform; 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::FlowType; @@ -287,9 +287,7 @@ impl BrushToolData { } if *reference == Some("Transform".to_string()) { - let upstream = document.metadata().upstream_transform(node_id); - let pivot = DAffine2::from_translation(upstream.transform_point2(get_current_normalized_pivot(&node.inputs))); - self.transform = pivot * get_current_transform(&node.inputs) * pivot.inverse() * self.transform; + self.transform = get_current_transform(&node.inputs) * self.transform; } } diff --git a/node-graph/gbrush/src/brush.rs b/node-graph/gbrush/src/brush.rs index b2782adc8a..3a085c0abe 100644 --- a/node-graph/gbrush/src/brush.rs +++ b/node-graph/gbrush/src/brush.rs @@ -403,7 +403,7 @@ mod test { blend_mode: BlendMode::Normal, }, }], - BrushCache::new_proto(), + BrushCache::default(), ) .await; assert_eq!(image.instance_ref_iter().next().unwrap().instance.width, 20); diff --git a/node-graph/gbrush/src/brush_cache.rs b/node-graph/gbrush/src/brush_cache.rs index 853a13ca34..c5495534c1 100644 --- a/node-graph/gbrush/src/brush_cache.rs +++ b/node-graph/gbrush/src/brush_cache.rs @@ -6,11 +6,16 @@ use graphene_core::raster_types::CPU; use graphene_core::raster_types::Raster; use std::collections::HashMap; use std::hash::Hash; -use std::sync::Arc; -use std::sync::Mutex; +use std::hash::Hasher; +use std::sync::atomic::{AtomicU64, Ordering}; +use std::sync::{Arc, Mutex}; -#[derive(Clone, Debug, PartialEq, DynAny, Default, serde::Serialize, serde::Deserialize)] +// TODO: This is a temporary hack, be sure to not reuse this when the brush is being rewritten. +static NEXT_BRUSH_CACHE_IMPL_ID: AtomicU64 = AtomicU64::new(0); + +#[derive(Clone, Debug, DynAny, serde::Serialize, serde::Deserialize)] struct BrushCacheImpl { + unique_id: u64, // The full previous input that was cached. prev_input: Vec, @@ -90,9 +95,29 @@ impl BrushCacheImpl { } } +impl Default for BrushCacheImpl { + fn default() -> Self { + Self { + unique_id: NEXT_BRUSH_CACHE_IMPL_ID.fetch_add(1, Ordering::SeqCst), + prev_input: Vec::new(), + background: Default::default(), + blended_image: Default::default(), + last_stroke_texture: Default::default(), + brush_texture_cache: HashMap::new(), + } + } +} + +impl PartialEq for BrushCacheImpl { + fn eq(&self, other: &Self) -> bool { + self.unique_id == other.unique_id + } +} + impl Hash for BrushCacheImpl { - // Zero hash. - fn hash(&self, _state: &mut H) {} + fn hash(&self, state: &mut H) { + self.unique_id.hash(state); + } } #[derive(Clone, Debug, Default)] @@ -103,46 +128,26 @@ pub struct BrushPlan { pub first_stroke_point_skip: usize, } -#[derive(Debug, DynAny, serde::Serialize, serde::Deserialize)] -pub struct BrushCache { - inner: Arc>, - proto: bool, -} - -impl Default for BrushCache { - fn default() -> Self { - Self::new_proto() - } -} +#[derive(Debug, Default, DynAny, serde::Serialize, serde::Deserialize)] +pub struct BrushCache(Arc>); // A bit of a cursed implementation to work around the current node system. // The original object is a 'prototype' that when cloned gives you a independent // new object. Any further clones however are all the same underlying cache object. impl Clone for BrushCache { fn clone(&self) -> Self { - if self.proto { - let inner_val = self.inner.lock().unwrap(); - Self { - inner: Arc::new(Mutex::new(inner_val.clone())), - proto: false, - } - } else { - Self { - inner: Arc::clone(&self.inner), - proto: false, - } - } + Self(Arc::new(Mutex::new(self.0.lock().unwrap().clone()))) } } impl PartialEq for BrushCache { fn eq(&self, other: &Self) -> bool { - if Arc::ptr_eq(&self.inner, &other.inner) { + if Arc::ptr_eq(&self.0, &other.0) { return true; } - let s = self.inner.lock().unwrap(); - let o = other.inner.lock().unwrap(); + let s = self.0.lock().unwrap(); + let o = other.0.lock().unwrap(); *s == *o } @@ -150,35 +155,28 @@ impl PartialEq for BrushCache { impl Hash for BrushCache { fn hash(&self, state: &mut H) { - self.inner.lock().unwrap().hash(state); + self.0.lock().unwrap().hash(state); } } impl BrushCache { - pub fn new_proto() -> Self { - Self { - inner: Default::default(), - proto: true, - } - } - pub fn compute_brush_plan(&self, background: Instance>, input: &[BrushStroke]) -> BrushPlan { - let mut inner = self.inner.lock().unwrap(); + let mut inner = self.0.lock().unwrap(); inner.compute_brush_plan(background, input) } pub fn cache_results(&self, input: Vec, blended_image: Instance>, last_stroke_texture: Instance>) { - let mut inner = self.inner.lock().unwrap(); + let mut inner = self.0.lock().unwrap(); inner.cache_results(input, blended_image, last_stroke_texture) } pub fn get_cached_brush(&self, style: &BrushStyle) -> Option> { - let inner = self.inner.lock().unwrap(); + let inner = self.0.lock().unwrap(); inner.brush_texture_cache.get(style).cloned() } pub fn store_brush(&self, style: BrushStyle, brush: Raster) { - let mut inner = self.inner.lock().unwrap(); + let mut inner = self.0.lock().unwrap(); inner.brush_texture_cache.insert(style, brush); } } From e02510303eb23fe9e17718979462734dabed5bb3 Mon Sep 17 00:00:00 2001 From: Kavin <40298945+Kavin006@users.noreply.github.com> Date: Wed, 9 Jul 2025 16:19:39 +0530 Subject: [PATCH 4/8] Add X and Y offset distance overlay while dragging with the Select tool (#2814) * Add offset overlay to select tool * Add overlay to Dragging * Add axis align behavior * Style changes * Add trim to string * reduce code duplication * Code review --------- Co-authored-by: Keavon Chambers --- .../document/node_graph/node_properties.rs | 5 ++-- .../document/overlays/utility_types.rs | 30 +++++++++++++++++++ .../tool/tool_messages/select_tool.rs | 8 +++++ .../transform_layer_message_handler.rs | 28 ++--------------- 4 files changed, 43 insertions(+), 28 deletions(-) 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 18a7f82909..9551ed2094 100644 --- a/editor/src/messages/portfolio/document/node_graph/node_properties.rs +++ b/editor/src/messages/portfolio/document/node_graph/node_properties.rs @@ -1274,6 +1274,7 @@ pub(crate) fn rectangle_properties(node_id: NodeId, context: &mut NodeProperties 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 document_node = match get_document_node(node_id, context) { Ok(document_node) => document_node, @@ -1381,8 +1382,6 @@ pub(crate) fn rectangle_properties(node_id: NodeId, context: &mut NodeProperties // 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::new(node_id, ClampedInput::INDEX, true, context), CheckboxInput::default()); @@ -1436,7 +1435,7 @@ pub(crate) fn generate_node_properties(node_id: NodeId, context: &mut NodeProper if let Some(field) = graphene_std::registry::NODE_METADATA .lock() .unwrap() - .get(&proto_node_identifier) + .get(proto_node_identifier) .and_then(|metadata| metadata.fields.get(input_index)) { number_options = (field.number_min, field.number_max, field.number_mode_range); diff --git a/editor/src/messages/portfolio/document/overlays/utility_types.rs b/editor/src/messages/portfolio/document/overlays/utility_types.rs index 6e25bb77b3..3d12ba4961 100644 --- a/editor/src/messages/portfolio/document/overlays/utility_types.rs +++ b/editor/src/messages/portfolio/document/overlays/utility_types.rs @@ -814,6 +814,36 @@ impl OverlayContext { self.render_context.fill_text(text, 0., 0.).expect("Failed to draw the text at the calculated position"); self.render_context.reset_transform().expect("Failed to reset the render context transform"); } + + pub fn translation_box(&mut self, translation: DVec2, quad: Quad, typed_string: Option) { + if translation.x.abs() > 1e-3 { + self.dashed_line(quad.top_left(), quad.top_right(), None, None, Some(2.), Some(2.), Some(0.5)); + + let width = match typed_string { + Some(ref typed_string) => typed_string, + None => &format!("{:.2}", translation.x).trim_end_matches('0').trim_end_matches('.').to_string(), + }; + let x_transform = DAffine2::from_translation((quad.top_left() + quad.top_right()) / 2.); + self.text(width, COLOR_OVERLAY_BLUE, None, x_transform, 4., [Pivot::Middle, Pivot::End]); + } + + if translation.y.abs() > 1e-3 { + self.dashed_line(quad.top_left(), quad.bottom_left(), None, None, Some(2.), Some(2.), Some(0.5)); + + let height = match typed_string { + Some(ref typed_string) => typed_string, + None => &format!("{:.2}", translation.y).trim_end_matches('0').trim_end_matches('.').to_string(), + }; + let y_transform = DAffine2::from_translation((quad.top_left() + quad.bottom_left()) / 2.); + let height_pivot = if translation.x > -1e-3 { Pivot::Start } else { Pivot::End }; + self.text(height, COLOR_OVERLAY_BLUE, None, y_transform, 3., [height_pivot, Pivot::Middle]); + } + + if translation.x.abs() > 1e-3 && translation.y.abs() > 1e-3 { + self.line(quad.top_right(), quad.bottom_right(), None, None); + self.line(quad.bottom_left(), quad.bottom_right(), None, None); + } + } } pub enum Pivot { diff --git a/editor/src/messages/tool/tool_messages/select_tool.rs b/editor/src/messages/tool/tool_messages/select_tool.rs index 6c8498317f..e6f4a5d7a3 100644 --- a/editor/src/messages/tool/tool_messages/select_tool.rs +++ b/editor/src/messages/tool/tool_messages/select_tool.rs @@ -974,6 +974,14 @@ impl Fsm for SelectToolFsmState { } } + if let Self::Dragging { .. } = self { + let quad = Quad::from_box([tool_data.drag_start, tool_data.drag_current]); + let document_start = document.metadata().document_to_viewport.inverse().transform_point2(quad.top_left()); + let document_current = document.metadata().document_to_viewport.inverse().transform_point2(quad.bottom_right()); + + overlay_context.translation_box(document_current - document_start, quad, None); + } + self } (_, SelectToolMessage::EditLayer) => { 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 a6221f729d..b9b9880e98 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 @@ -319,37 +319,15 @@ impl MessageHandler> for TransformLayer let translation = translation.to_dvec(self.initial_transform, self.increments); let viewport_translate = document_to_viewport.transform_vector2(translation); let pivot = document_to_viewport.transform_point2(self.grab_target); - let quad = Quad::from_box([pivot, pivot + viewport_translate]).0; - let e1 = (self.layer_bounding_box.0[1] - self.layer_bounding_box.0[0]).normalize_or(DVec2::X); + let quad = Quad::from_box([pivot, pivot + viewport_translate]); responses.add(SelectToolMessage::PivotShift { offset: Some(viewport_translate), flush: false, }); - if matches!(axis_constraint, Axis::Both | Axis::X) && translation.x != 0. { - let end = if self.local { (quad[1] - quad[0]).rotate(e1) + quad[0] } else { quad[1] }; - overlay_context.dashed_line(quad[0], end, None, None, Some(2.), Some(2.), Some(0.5)); - - let x_transform = DAffine2::from_translation((quad[0] + end) / 2.); - overlay_context.text(&format_rounded(translation.x, 3), COLOR_OVERLAY_BLUE, None, x_transform, 4., [Pivot::Middle, Pivot::End]); - } - - if matches!(axis_constraint, Axis::Both | Axis::Y) && translation.y != 0. { - let end = if self.local { (quad[3] - quad[0]).rotate(e1) + quad[0] } else { quad[3] }; - overlay_context.dashed_line(quad[0], end, None, None, Some(2.), Some(2.), Some(0.5)); - let x_parameter = viewport_translate.x.clamp(-1., 1.); - let y_transform = DAffine2::from_translation((quad[0] + end) / 2. + x_parameter * DVec2::X * 0.); - let pivot_selection = if x_parameter >= -1e-3 { Pivot::Start } else { Pivot::End }; - if axis_constraint != Axis::Both || self.typing.digits.is_empty() || !self.transform_operation.can_begin_typing() { - overlay_context.text(&format_rounded(translation.y, 2), COLOR_OVERLAY_BLUE, None, y_transform, 3., [pivot_selection, Pivot::Middle]); - } - } - - if matches!(axis_constraint, Axis::Both) && translation.x != 0. && translation.y != 0. { - overlay_context.line(quad[1], quad[2], None, None); - overlay_context.line(quad[3], quad[2], None, None); - } + let typed_string = (!self.typing.digits.is_empty() && self.transform_operation.can_begin_typing()).then(|| self.typing.string.clone()); + overlay_context.translation_box(translation, quad, typed_string); } TransformOperation::Scaling(scale) => { let scale = scale.to_f64(self.increments); From ceffcfd8df644b53b0853c38afa1edae555cd229 Mon Sep 17 00:00:00 2001 From: Keavon Chambers Date: Wed, 9 Jul 2025 23:51:25 -0700 Subject: [PATCH 5/8] Update website deployment CI script to output auto-generated code docs artifacts (#2849) --- .github/workflows/build-dev-and-ci.yml | 67 ++++++++++++++++++- .github/workflows/comment-!build-commands.yml | 3 +- .github/workflows/website.yml | 43 +++++++++--- 3 files changed, 100 insertions(+), 13 deletions(-) diff --git a/.github/workflows/build-dev-and-ci.yml b/.github/workflows/build-dev-and-ci.yml index ff3f25151e..e62ea3bca0 100644 --- a/.github/workflows/build-dev-and-ci.yml +++ b/.github/workflows/build-dev-and-ci.yml @@ -4,7 +4,7 @@ on: push: branches: - master - pull_request: + pull_request: {} env: CARGO_TERM_COLOR: always INDEX_HTML_HEAD_REPLACEMENT: @@ -13,9 +13,10 @@ jobs: build: runs-on: self-hosted permissions: - contents: read + contents: write deployments: write pull-requests: write + actions: write env: RUSTC_WRAPPER: /usr/bin/sccache CARGO_INCREMENTAL: 0 @@ -47,9 +48,11 @@ jobs: rustc --version - name: ✂ Replace template in of index.html + if: github.ref != 'refs/heads/master' + env: + INDEX_HTML_HEAD_REPLACEMENT: "" run: | # Remove the INDEX_HTML_HEAD_REPLACEMENT environment variable for build links (not master deploys) - git rev-parse --abbrev-ref HEAD | grep master > /dev/null || export INDEX_HTML_HEAD_REPLACEMENT="" sed -i "s||$INDEX_HTML_HEAD_REPLACEMENT|" frontend/index.html - name: 🌐 Build Graphite web code @@ -70,6 +73,19 @@ jobs: projectName: graphite-dev directory: frontend/dist + - name: 💬 Comment build link URL to commit hash page on GitHub + if: github.ref == 'refs/heads/master' + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + gh api \ + -X POST \ + -H "Accept: application/vnd.github+json" \ + /repos/${{ github.repository }}/commits/$(git rev-parse HEAD)/comments \ + -f body="| 📦 **Build Complete for** $(git rev-parse HEAD) | + |-| + | ${{ steps.cloudflare.outputs.url }} |" + - name: 👕 Lint Graphite web formatting env: NODE_ENV: production @@ -91,6 +107,51 @@ jobs: run: | mold -run cargo test --all-features --workspace + - name: 📃 Generate code documentation info for website + if: github.ref == 'refs/heads/master' + run: | + cargo test --package graphite-editor --lib -- messages::message::test::generate_message_tree + mkdir -p artifacts-generated + mv hierarchical_message_system_tree.txt artifacts-generated/hierarchical_message_system_tree.txt + + - name: 💿 Obtain cache of auto-generated code docs artifacts, to check if they've changed + if: github.ref == 'refs/heads/master' + id: cache-website-code-docs + uses: actions/cache/restore@v3 + with: + path: artifacts + key: website-code-docs + + - name: 🔍 Check if auto-generated code docs artifacts changed + if: github.ref == 'refs/heads/master' + id: website-code-docs-changed + run: | + if ! diff --brief --recursive artifacts-generated artifacts; then + echo "Auto-generated code docs artifacts have changed." + rm -rf artifacts + mv artifacts-generated artifacts + echo "changed=true" >> $GITHUB_OUTPUT + else + echo "Auto-generated code docs artifacts have not changed." + rm -rf artifacts + rm -rf artifacts-generated + fi + + - name: 💾 Save cache of auto-generated code docs artifacts + if: steps.website-code-docs-changed.outputs.changed == 'true' + uses: actions/cache/save@v3 + with: + path: artifacts + key: ${{ steps.cache-website-code-docs.outputs.cache-primary-key }} + + - name: ♻️ Trigger website rebuild if the auto-generated code docs artifacts have changed + if: steps.website-code-docs-changed.outputs.changed == 'true' + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + rm -rf artifacts + gh workflow run website.yml --ref master + # miri: # runs-on: self-hosted diff --git a/.github/workflows/comment-!build-commands.yml b/.github/workflows/comment-!build-commands.yml index a8fa55e275..8d8a0ae096 100644 --- a/.github/workflows/comment-!build-commands.yml +++ b/.github/workflows/comment-!build-commands.yml @@ -73,9 +73,10 @@ jobs: rustc --version - name: ✂ Replace template in of index.html + env: + INDEX_HTML_HEAD_REPLACEMENT: "" run: | # Remove the INDEX_HTML_HEAD_REPLACEMENT environment variable for build links (not master deploys) - export INDEX_HTML_HEAD_REPLACEMENT="" sed -i "s||$INDEX_HTML_HEAD_REPLACEMENT|" frontend/index.html - name: ⌨ Set build command based on comment diff --git a/.github/workflows/website.yml b/.github/workflows/website.yml index 0451d4226e..4b9a6ed409 100644 --- a/.github/workflows/website.yml +++ b/.github/workflows/website.yml @@ -9,6 +9,7 @@ on: pull_request: paths: - website/** + workflow_dispatch: {} env: CARGO_TERM_COLOR: always INDEX_HTML_HEAD_INCLUSION: @@ -30,6 +31,14 @@ jobs: with: tool: zola@0.20.0 + - name: 🔍 Check if `website/other` directory changed + uses: dorny/paths-filter@v3 + id: changes + with: + filters: | + website-other: + - "website/other/**" + - name: ✂ Replace template in of index.html run: | # Remove the INDEX_HTML_HEAD_INCLUSION environment variable for build links (not master deploys) @@ -43,16 +52,8 @@ jobs: npm run install-fonts zola --config config.toml build --minify - - name: 🔍 Check if `website/other` directory changed - uses: dorny/paths-filter@v3 - id: changes - with: - filters: | - other: - - "website/other/**" - - name: 💿 Restore cache of `website/other/dist` directory, if available and `website/other` didn't change - if: steps.changes.outputs.other != 'true' + if: steps.changes.outputs.website-other != 'true' id: cache-website-other-dist uses: actions/cache/restore@v3 with: @@ -80,8 +81,32 @@ jobs: - name: 🚚 Move `website/other/dist` contents to `website/public` run: | + mkdir -p website/public mv website/other/dist/* website/public + - name: 💿 Obtain cache of auto-generated code docs artifacts + id: cache-website-code-docs + uses: actions/cache/restore@v3 + with: + path: artifacts + key: website-code-docs + + - name: 📁 Fallback in case auto-generated code docs artifacts weren't cached + if: steps.cache-website-code-docs.outputs.cache-hit != 'true' + run: | + echo "🦀 Initial system version of Rust:" + rustc --version + rustup update stable + echo "🦀 Latest updated version of Rust:" + rustc --version + cargo test --package graphite-editor --lib -- messages::message::test::generate_message_tree + mkdir artifacts + mv hierarchical_message_system_tree.txt artifacts/hierarchical_message_system_tree.txt + + - name: 🚚 Move `artifacts` contents to `website/public` + run: | + mv artifacts/* website/public + - name: 📤 Publish to Cloudflare Pages id: cloudflare uses: cloudflare/pages-action@1 From 13ad81463945b4f45608085b49f4bb6e3baaf0b5 Mon Sep 17 00:00:00 2001 From: James Lindsay <78500760+0HyperCube@users.noreply.github.com> Date: Thu, 10 Jul 2025 08:57:45 +0100 Subject: [PATCH 6/8] Add a complexity limit on displaying layer thumbnails to improve performance (#2828) * Skip complex layer thumbnails * Set Raster to have usize::MAX render complexity * Code review --------- Co-authored-by: Keavon Chambers --- editor/src/node_graph_executor/runtime.rs | 12 +++++ node-graph/gcore/src/lib.rs | 1 + node-graph/gcore/src/render_complexity.rs | 61 +++++++++++++++++++++++ node-graph/gsvg-renderer/src/renderer.rs | 5 +- 4 files changed, 77 insertions(+), 2 deletions(-) create mode 100644 node-graph/gcore/src/render_complexity.rs diff --git a/editor/src/node_graph_executor/runtime.rs b/editor/src/node_graph_executor/runtime.rs index 227999143c..da92ad313b 100644 --- a/editor/src/node_graph_executor/runtime.rs +++ b/editor/src/node_graph_executor/runtime.rs @@ -328,6 +328,18 @@ impl NodeRuntime { return; } + // Skip thumbnails if the layer is too complex (for performance) + if graphic_element.render_complexity() > 1000 { + let old = thumbnail_renders.insert(parent_network_node_id, Vec::new()); + if old.is_none_or(|v| !v.is_empty()) { + responses.push_back(FrontendMessage::UpdateNodeThumbnail { + id: parent_network_node_id, + value: "Dense thumbnail omitted for performance".to_string(), + }); + } + return; + } + let bounds = graphic_element.bounding_box(DAffine2::IDENTITY, true); // Render the thumbnail from a `GraphicElement` into an SVG string diff --git a/node-graph/gcore/src/lib.rs b/node-graph/gcore/src/lib.rs index 1333ec7433..4f1f0aaf0b 100644 --- a/node-graph/gcore/src/lib.rs +++ b/node-graph/gcore/src/lib.rs @@ -22,6 +22,7 @@ pub mod ops; pub mod raster; pub mod raster_types; pub mod registry; +pub mod render_complexity; pub mod structural; pub mod text; pub mod transform; diff --git a/node-graph/gcore/src/render_complexity.rs b/node-graph/gcore/src/render_complexity.rs new file mode 100644 index 0000000000..3c137c87e8 --- /dev/null +++ b/node-graph/gcore/src/render_complexity.rs @@ -0,0 +1,61 @@ +use crate::instances::Instances; +use crate::raster_types::{CPU, GPU, Raster}; +use crate::vector::VectorData; +use crate::{Artboard, Color, GraphicElement}; +use glam::DVec2; + +pub trait RenderComplexity { + fn render_complexity(&self) -> usize { + 0 + } +} + +impl RenderComplexity for Instances { + fn render_complexity(&self) -> usize { + self.instance_ref_iter().map(|instance| instance.instance.render_complexity()).fold(0, usize::saturating_add) + } +} + +impl RenderComplexity for Artboard { + fn render_complexity(&self) -> usize { + self.graphic_group.render_complexity() + } +} + +impl RenderComplexity for GraphicElement { + fn render_complexity(&self) -> usize { + match self { + Self::GraphicGroup(instances) => instances.render_complexity(), + Self::VectorData(instances) => instances.render_complexity(), + Self::RasterDataCPU(instances) => instances.render_complexity(), + Self::RasterDataGPU(instances) => instances.render_complexity(), + } + } +} + +impl RenderComplexity for VectorData { + fn render_complexity(&self) -> usize { + self.segment_domain.ids().len() + } +} + +impl RenderComplexity for Raster { + fn render_complexity(&self) -> usize { + (self.width * self.height / 500) as usize + } +} + +impl RenderComplexity for Raster { + fn render_complexity(&self) -> usize { + // GPU textures currently can't have a thumbnail + usize::MAX + } +} + +impl RenderComplexity for String {} +impl RenderComplexity for bool {} +impl RenderComplexity for f32 {} +impl RenderComplexity for f64 {} +impl RenderComplexity for DVec2 {} +impl RenderComplexity for Option {} +impl RenderComplexity for Vec {} diff --git a/node-graph/gsvg-renderer/src/renderer.rs b/node-graph/gsvg-renderer/src/renderer.rs index 70f7c18206..a71cdf1b56 100644 --- a/node-graph/gsvg-renderer/src/renderer.rs +++ b/node-graph/gsvg-renderer/src/renderer.rs @@ -10,6 +10,7 @@ use graphene_core::instances::Instance; use graphene_core::math::quad::Quad; use graphene_core::raster::Image; use graphene_core::raster_types::{CPU, GPU, RasterDataTable}; +use graphene_core::render_complexity::RenderComplexity; use graphene_core::transform::{Footprint, Transform}; use graphene_core::uuid::{NodeId, generate_uuid}; use graphene_core::vector::VectorDataTable; @@ -204,7 +205,7 @@ pub struct RenderMetadata { } // TODO: Rename to "Graphical" -pub trait GraphicElementRendered: BoundingBox { +pub trait GraphicElementRendered: BoundingBox + RenderComplexity { fn render_svg(&self, render: &mut SvgRender, render_params: &RenderParams); #[cfg(feature = "vello")] @@ -1151,7 +1152,7 @@ impl GraphicElementRendered for GraphicElement { } /// Used to stop rust complaining about upstream traits adding display implementations to `Option`. This would not be an issue as we control that crate. -trait Primitive: std::fmt::Display + BoundingBox {} +trait Primitive: std::fmt::Display + BoundingBox + RenderComplexity {} impl Primitive for String {} impl Primitive for bool {} impl Primitive for f32 {} From 912e6f61a973c3a7a0d4e3930088c90123776cf6 Mon Sep 17 00:00:00 2001 From: Adesh Gupta <148623820+4adex@users.noreply.github.com> Date: Thu, 10 Jul 2025 14:21:25 +0530 Subject: [PATCH 7/8] Fix regression with inability to drag segments using the Path tool's segment mode (#2850) Fix segment dragging --- editor/src/messages/tool/tool_messages/path_tool.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/editor/src/messages/tool/tool_messages/path_tool.rs b/editor/src/messages/tool/tool_messages/path_tool.rs index d54ce54274..8701ca91cd 100644 --- a/editor/src/messages/tool/tool_messages/path_tool.rs +++ b/editor/src/messages/tool/tool_messages/path_tool.rs @@ -1701,9 +1701,9 @@ impl Fsm for PathToolFsmState { break_molding, tool_data.temporary_adjacent_handles_while_molding, ); - } - return PathToolFsmState::Dragging(tool_data.dragging_state); + return PathToolFsmState::Dragging(tool_data.dragging_state); + } } let anchor_and_handle_toggled = input.keyboard.get(move_anchor_with_handles as usize); From 4c75ddf936e264d3dd2f595e0b4813ffdeaa6ece Mon Sep 17 00:00:00 2001 From: Firestar99 Date: Fri, 11 Jul 2025 02:40:54 +0200 Subject: [PATCH 8/8] Refactor node migration to be less susceptible to forgetting outdated strings (#2853) * migration: make document migration use ProtoNodeIdentifier * migration: NodeReplacement struct for better formatting * migration: test for duplicates and fix them --- .../messages/portfolio/document_migration.rs | 605 +++++++++++++----- node-graph/gcore/src/graphic_element.rs | 2 +- 2 files changed, 459 insertions(+), 148 deletions(-) diff --git a/editor/src/messages/portfolio/document_migration.rs b/editor/src/messages/portfolio/document_migration.rs index e4db26d2ec..822801f04e 100644 --- a/editor/src/messages/portfolio/document_migration.rs +++ b/editor/src/messages/portfolio/document_migration.rs @@ -9,6 +9,7 @@ use bezier_rs::Subpath; use glam::IVec2; use graph_craft::document::DocumentNode; use graph_craft::document::{DocumentNodeImplementation, NodeInput, value::TaggedValue}; +use graphene_std::ProtoNodeIdentifier; use graphene_std::text::TypesettingConfig; use graphene_std::uuid::NodeId; use graphene_std::vector::style::{PaintOrder, StrokeAlign}; @@ -20,156 +21,440 @@ const TEXT_REPLACEMENTS: &[(&str, &str)] = &[ ("graphene_core::vector::vector_nodes::SubpathSegmentLengthsNode", "graphene_core::vector::SubpathSegmentLengthsNode"), ]; -const REPLACEMENTS: &[(&str, &str)] = &[ - ("graphene_core::AddArtboardNode", "graphene_core::graphic_element::AppendArtboardNode"), - ("graphene_core::ConstructArtboardNode", "graphene_core::graphic_element::ToArtboardNode"), - ("graphene_core::ToGraphicElementNode", "graphene_core::graphic_element::ToElementNode"), - ("graphene_core::ToGraphicGroupNode", "graphene_core::graphic_element::ToGroupNode"), +pub struct NodeReplacement<'a> { + node: ProtoNodeIdentifier, + aliases: &'a [&'a str], +} + +const NODE_REPLACEMENTS: &[NodeReplacement<'static>] = &[ + // graphic element + NodeReplacement { + node: graphene_std::graphic_element::append_artboard::IDENTIFIER, + aliases: &["graphene_core::AddArtboardNode"], + }, + NodeReplacement { + node: graphene_std::graphic_element::to_artboard::IDENTIFIER, + aliases: &["graphene_core::ConstructArtboardNode"], + }, + NodeReplacement { + node: graphene_std::graphic_element::to_element::IDENTIFIER, + aliases: &["graphene_core::ToGraphicElementNode"], + }, + NodeReplacement { + node: graphene_std::graphic_element::to_group::IDENTIFIER, + aliases: &["graphene_core::ToGraphicGroupNode"], + }, // math_nodes - ("graphene_core::ops::MathNode", "graphene_math_nodes::MathNode"), - ("graphene_core::ops::AddNode", "graphene_math_nodes::AddNode"), - ("graphene_core::ops::SubtractNode", "graphene_math_nodes::SubtractNode"), - ("graphene_core::ops::MultiplyNode", "graphene_math_nodes::MultiplyNode"), - ("graphene_core::ops::DivideNode", "graphene_math_nodes::DivideNode"), - ("graphene_core::ops::ModuloNode", "graphene_math_nodes::ModuloNode"), - ("graphene_core::ops::ExponentNode", "graphene_math_nodes::ExponentNode"), - ("graphene_core::ops::RootNode", "graphene_math_nodes::RootNode"), - ("graphene_core::ops::LogarithmNode", "graphene_math_nodes::LogarithmNode"), - ("graphene_core::ops::SineNode", "graphene_math_nodes::SineNode"), - ("graphene_core::ops::CosineNode", "graphene_math_nodes::CosineNode"), - ("graphene_core::ops::TangentNode", "graphene_math_nodes::TangentNode"), - ("graphene_core::ops::SineInverseNode", "graphene_math_nodes::SineInverseNode"), - ("graphene_core::ops::CosineInverseNode", "graphene_math_nodes::CosineInverseNode"), - ("graphene_core::ops::TangentInverseNode", "graphene_math_nodes::TangentInverseNode"), - ("graphene_core::ops::RandomNode", "graphene_math_nodes::RandomNode"), - ("graphene_core::ops::ToU32Node", "graphene_math_nodes::ToU32Node"), - ("graphene_core::ops::ToU64Node", "graphene_math_nodes::ToU64Node"), - ("graphene_core::ops::ToF64Node", "graphene_math_nodes::ToF64Node"), - ("graphene_core::ops::RoundNode", "graphene_math_nodes::RoundNode"), - ("graphene_core::ops::FloorNode", "graphene_math_nodes::FloorNode"), - ("graphene_core::ops::CeilingNode", "graphene_math_nodes::CeilingNode"), - ("graphene_core::ops::MinNode", "graphene_math_nodes::MinNode"), - ("graphene_core::ops::MaxNode", "graphene_math_nodes::MaxNode"), - ("graphene_core::ops::ClampNode", "graphene_math_nodes::ClampNode"), - ("graphene_core::ops::EqualsNode", "graphene_math_nodes::EqualsNode"), - ("graphene_core::ops::NotEqualsNode", "graphene_math_nodes::NotEqualsNode"), - ("graphene_core::ops::LessThanNode", "graphene_math_nodes::LessThanNode"), - ("graphene_core::ops::GreaterThanNode", "graphene_math_nodes::GreaterThanNode"), - ("graphene_core::ops::LogicalOrNode", "graphene_math_nodes::LogicalOrNode"), - ("graphene_core::ops::LogicalAndNode", "graphene_math_nodes::LogicalAndNode"), - ("graphene_core::ops::LogicalNotNode", "graphene_math_nodes::LogicalNotNode"), - ("graphene_core::ops::BoolValueNode", "graphene_math_nodes::BoolValueNode"), - ("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::SampleGradientNode", "graphene_math_nodes::SampleGradientNode"), - ("graphene_core::ops::StringValueNode", "graphene_math_nodes::StringValueNode"), - ("graphene_core::ops::DotProductNode", "graphene_math_nodes::DotProductNode"), + NodeReplacement { + node: graphene_std::math_nodes::math::IDENTIFIER, + aliases: &["graphene_core::ops::MathNode"], + }, + NodeReplacement { + node: graphene_std::math_nodes::add::IDENTIFIER, + aliases: &["graphene_core::ops::AddNode"], + }, + NodeReplacement { + node: graphene_std::math_nodes::subtract::IDENTIFIER, + aliases: &["graphene_core::ops::SubtractNode"], + }, + NodeReplacement { + node: graphene_std::math_nodes::multiply::IDENTIFIER, + aliases: &["graphene_core::ops::MultiplyNode"], + }, + NodeReplacement { + node: graphene_std::math_nodes::divide::IDENTIFIER, + aliases: &["graphene_core::ops::DivideNode"], + }, + NodeReplacement { + node: graphene_std::math_nodes::modulo::IDENTIFIER, + aliases: &["graphene_core::ops::ModuloNode"], + }, + NodeReplacement { + node: graphene_std::math_nodes::exponent::IDENTIFIER, + aliases: &["graphene_core::ops::ExponentNode"], + }, + NodeReplacement { + node: graphene_std::math_nodes::root::IDENTIFIER, + aliases: &["graphene_core::ops::RootNode"], + }, + NodeReplacement { + node: graphene_std::math_nodes::logarithm::IDENTIFIER, + aliases: &["graphene_core::ops::LogarithmNode"], + }, + NodeReplacement { + node: graphene_std::math_nodes::sine::IDENTIFIER, + aliases: &["graphene_core::ops::SineNode"], + }, + NodeReplacement { + node: graphene_std::math_nodes::cosine::IDENTIFIER, + aliases: &["graphene_core::ops::CosineNode"], + }, + NodeReplacement { + node: graphene_std::math_nodes::tangent::IDENTIFIER, + aliases: &["graphene_core::ops::TangentNode"], + }, + NodeReplacement { + node: graphene_std::math_nodes::sine_inverse::IDENTIFIER, + aliases: &["graphene_core::ops::SineInverseNode"], + }, + NodeReplacement { + node: graphene_std::math_nodes::cosine_inverse::IDENTIFIER, + aliases: &["graphene_core::ops::CosineInverseNode"], + }, + NodeReplacement { + node: graphene_std::math_nodes::tangent_inverse::IDENTIFIER, + aliases: &["graphene_core::ops::TangentInverseNode"], + }, + NodeReplacement { + node: graphene_std::math_nodes::random::IDENTIFIER, + aliases: &["graphene_core::ops::RandomNode"], + }, + NodeReplacement { + node: graphene_std::math_nodes::to_u_32::IDENTIFIER, + aliases: &["graphene_core::ops::ToU32Node"], + }, + NodeReplacement { + node: graphene_std::math_nodes::to_u_64::IDENTIFIER, + aliases: &["graphene_core::ops::ToU64Node"], + }, + NodeReplacement { + node: graphene_std::math_nodes::to_f_64::IDENTIFIER, + aliases: &["graphene_core::ops::ToF64Node"], + }, + NodeReplacement { + node: graphene_std::math_nodes::round::IDENTIFIER, + aliases: &["graphene_core::ops::RoundNode"], + }, + NodeReplacement { + node: graphene_std::math_nodes::floor::IDENTIFIER, + aliases: &["graphene_core::ops::FloorNode"], + }, + NodeReplacement { + node: graphene_std::math_nodes::ceiling::IDENTIFIER, + aliases: &["graphene_core::ops::CeilingNode"], + }, + NodeReplacement { + node: graphene_std::math_nodes::min::IDENTIFIER, + aliases: &["graphene_core::ops::MinNode"], + }, + NodeReplacement { + node: graphene_std::math_nodes::max::IDENTIFIER, + aliases: &["graphene_core::ops::MaxNode"], + }, + NodeReplacement { + node: graphene_std::math_nodes::clamp::IDENTIFIER, + aliases: &["graphene_core::ops::ClampNode"], + }, + NodeReplacement { + node: graphene_std::math_nodes::equals::IDENTIFIER, + aliases: &["graphene_core::ops::EqualsNode"], + }, + NodeReplacement { + node: graphene_std::math_nodes::not_equals::IDENTIFIER, + aliases: &["graphene_core::ops::NotEqualsNode"], + }, + NodeReplacement { + node: graphene_std::math_nodes::less_than::IDENTIFIER, + aliases: &["graphene_core::ops::LessThanNode"], + }, + NodeReplacement { + node: graphene_std::math_nodes::greater_than::IDENTIFIER, + aliases: &["graphene_core::ops::GreaterThanNode"], + }, + NodeReplacement { + node: graphene_std::math_nodes::logical_or::IDENTIFIER, + aliases: &["graphene_core::ops::LogicalOrNode", "graphene_core::ops::LogicAndNode", "graphene_core::logic::LogicAndNode"], + }, + NodeReplacement { + node: graphene_std::math_nodes::logical_and::IDENTIFIER, + aliases: &["graphene_core::ops::LogicalAndNode", "graphene_core::ops::LogicNotNode", "graphene_core::logic::LogicNotNode"], + }, + NodeReplacement { + node: graphene_std::math_nodes::logical_not::IDENTIFIER, + aliases: &["graphene_core::ops::LogicalNotNode", "graphene_core::ops::LogicOrNode", "graphene_core::logic::LogicOrNode"], + }, + NodeReplacement { + node: graphene_std::math_nodes::bool_value::IDENTIFIER, + aliases: &["graphene_core::ops::BoolValueNode"], + }, + NodeReplacement { + node: graphene_std::math_nodes::number_value::IDENTIFIER, + aliases: &["graphene_core::ops::NumberValueNode"], + }, + NodeReplacement { + node: graphene_std::math_nodes::percentage_value::IDENTIFIER, + aliases: &["graphene_core::ops::PercentageValueNode"], + }, + NodeReplacement { + node: graphene_std::math_nodes::coordinate_value::IDENTIFIER, + aliases: &[ + "graphene_core::ops::CoordinateValueNode", + "graphene_core::ops::ConstructVector2", + "graphene_core::ops::Vector2ValueNode", + ], + }, + NodeReplacement { + node: graphene_std::math_nodes::color_value::IDENTIFIER, + aliases: &["graphene_core::ops::ColorValueNode"], + }, + NodeReplacement { + node: graphene_std::math_nodes::gradient_value::IDENTIFIER, + aliases: &["graphene_core::ops::GradientValueNode"], + }, + NodeReplacement { + node: graphene_std::math_nodes::sample_gradient::IDENTIFIER, + aliases: &["graphene_core::ops::SampleGradientNode"], + }, + NodeReplacement { + node: graphene_std::math_nodes::string_value::IDENTIFIER, + aliases: &["graphene_core::ops::StringValueNode"], + }, + NodeReplacement { + node: graphene_std::math_nodes::dot_product::IDENTIFIER, + aliases: &["graphene_core::ops::DotProductNode"], + }, // debug - ("graphene_core::ops::SizeOfNode", "graphene_core::debug::SizeOfNode"), - ("graphene_core::ops::SomeNode", "graphene_core::debug::SomeNode"), - ("graphene_core::ops::UnwrapNode", "graphene_core::debug::UnwrapNode"), - ("graphene_core::ops::CloneNode", "graphene_core::debug::CloneNode"), + NodeReplacement { + node: graphene_std::debug::size_of::IDENTIFIER, + aliases: &["graphene_core::ops::SizeOfNode"], + }, + NodeReplacement { + node: graphene_std::debug::some::IDENTIFIER, + aliases: &["graphene_core::ops::SomeNode"], + }, + NodeReplacement { + node: graphene_std::debug::unwrap::IDENTIFIER, + aliases: &["graphene_core::ops::UnwrapNode"], + }, + NodeReplacement { + node: graphene_std::debug::clone::IDENTIFIER, + aliases: &["graphene_core::ops::CloneNode"], + }, // ??? - ("graphene_core::ops::ExtractXyNode", "graphene_core::extract_xy::ExtractXyNode"), - ("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::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"), - ("graphene_core::vector::GenerateHandlesNode", "graphene_core::vector::AutoTangentsNode"), - ("graphene_core::vector::RemoveHandlesNode", "graphene_core::vector::AutoTangentsNode"), + NodeReplacement { + node: graphene_std::extract_xy::extract_xy::IDENTIFIER, + aliases: &["graphene_core::ops::ExtractXyNode"], + }, + NodeReplacement { + node: graphene_std::blending_nodes::blend_mode::IDENTIFIER, + aliases: &["graphene_core::raster::BlendModeNode"], + }, + NodeReplacement { + node: graphene_std::blending_nodes::opacity::IDENTIFIER, + aliases: &["graphene_core::raster::OpacityNode"], + }, + NodeReplacement { + node: graphene_std::blending_nodes::blending::IDENTIFIER, + aliases: &["graphene_core::raster::BlendingNode"], + }, + NodeReplacement { + node: graphene_std::vector::auto_tangents::IDENTIFIER, + aliases: &["graphene_core::vector::GenerateHandlesNode", "graphene_core::vector::RemoveHandlesNode"], + }, // raster::adjustments - ("graphene_core::raster::adjustments::LuminanceNode", "graphene_raster_nodes::adjustments::LuminanceNode"), - ("graphene_core::raster::LuminanceNode", "graphene_raster_nodes::adjustments::LuminanceNode"), - ("graphene_core::raster::adjustments::ExtractChannelNode", "graphene_raster_nodes::adjustments::ExtractChannelNode"), - ("graphene_core::raster::ExtractChannelNode", "graphene_raster_nodes::adjustments::ExtractChannelNode"), - ("graphene_core::raster::adjustments::MakeOpaqueNode", "graphene_raster_nodes::adjustments::MakeOpaqueNode"), - ("graphene_core::raster::ExtractOpaqueNode", "graphene_raster_nodes::adjustments::MakeOpaqueNode"), - ( - "graphene_core::raster::adjustments::BrightnessContrastNode", - "graphene_raster_nodes::adjustments::BrightnessContrastNode", - ), - ("graphene_core::raster::adjustments::LevelsNode", "graphene_raster_nodes::adjustments::LevelsNode"), - ("graphene_core::raster::LevelsNode", "graphene_raster_nodes::adjustments::LevelsNode"), - ("graphene_core::raster::adjustments::BlackAndWhiteNode", "graphene_raster_nodes::adjustments::BlackAndWhiteNode"), - ("graphene_core::raster::BlackAndWhiteNode", "graphene_raster_nodes::adjustments::BlackAndWhiteNode"), - ("graphene_core::raster::adjustments::HueSaturationNode", "graphene_raster_nodes::adjustments::HueSaturationNode"), - ("graphene_core::raster::HueSaturationNode", "graphene_raster_nodes::adjustments::HueSaturationNode"), - ("graphene_core::raster::adjustments::InvertNode", "graphene_raster_nodes::adjustments::InvertNode"), - ("graphene_core::raster::InvertNode", "graphene_raster_nodes::adjustments::InvertNode"), - ("graphene_core::raster::InvertRGBNode", "graphene_raster_nodes::adjustments::InvertNode"), - ("graphene_core::raster::adjustments::ThresholdNode", "graphene_raster_nodes::adjustments::ThresholdNode"), - ("graphene_core::raster::ThresholdNode", "graphene_raster_nodes::adjustments::ThresholdNode"), - ("graphene_core::raster::adjustments::BlendNode", "graphene_raster_nodes::adjustments::BlendNode"), - ("graphene_core::raster::BlendNode", "graphene_raster_nodes::adjustments::BlendNode"), - ("graphene_core::raster::BlendColorPairNode", "graphene_raster_nodes::adjustments::BlendColorPairNode"), - ("graphene_core::raster::adjustments::BlendColorsNode", "graphene_raster_nodes::adjustments::BlendColorsNode"), - ("graphene_core::raster::BlendColorsNode", "graphene_raster_nodes::adjustments::BlendColorsNode"), - ("graphene_core::raster::adjustments::GradientMapNode", "graphene_raster_nodes::adjustments::GradientMapNode"), - ("graphene_core::raster::GradientMapNode", "graphene_raster_nodes::adjustments::GradientMapNode"), - ("graphene_core::raster::adjustments::VibranceNode", "graphene_raster_nodes::adjustments::VibranceNode"), - ("graphene_core::raster::VibranceNode", "graphene_raster_nodes::adjustments::VibranceNode"), - ("graphene_core::raster::adjustments::ChannelMixerNode", "graphene_raster_nodes::adjustments::ChannelMixerNode"), - ("graphene_core::raster::ChannelMixerNode", "graphene_raster_nodes::adjustments::ChannelMixerNode"), - ("graphene_core::raster::adjustments::SelectiveColorNode", "graphene_raster_nodes::adjustments::SelectiveColorNode"), - ("graphene_core::raster::adjustments::PosterizeNode", "graphene_raster_nodes::adjustments::PosterizeNode"), - ("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"), + NodeReplacement { + node: graphene_std::raster_nodes::adjustments::luminance::IDENTIFIER, + aliases: &["graphene_core::raster::adjustments::LuminanceNode", "graphene_core::raster::LuminanceNode"], + }, + NodeReplacement { + node: graphene_std::raster_nodes::adjustments::extract_channel::IDENTIFIER, + aliases: &["graphene_core::raster::adjustments::ExtractChannelNode", "graphene_core::raster::ExtractChannelNode"], + }, + NodeReplacement { + node: graphene_std::raster_nodes::adjustments::make_opaque::IDENTIFIER, + aliases: &["graphene_core::raster::adjustments::MakeOpaqueNode", "graphene_core::raster::ExtractOpaqueNode"], + }, + NodeReplacement { + node: graphene_std::raster_nodes::adjustments::brightness_contrast::IDENTIFIER, + aliases: &["graphene_core::raster::adjustments::BrightnessContrastNode"], + }, + NodeReplacement { + node: graphene_std::raster_nodes::adjustments::levels::IDENTIFIER, + aliases: &["graphene_core::raster::adjustments::LevelsNode", "graphene_core::raster::LevelsNode"], + }, + NodeReplacement { + node: graphene_std::raster_nodes::adjustments::black_and_white::IDENTIFIER, + aliases: &["graphene_core::raster::adjustments::BlackAndWhiteNode", "graphene_core::raster::BlackAndWhiteNode"], + }, + NodeReplacement { + node: graphene_std::raster_nodes::adjustments::hue_saturation::IDENTIFIER, + aliases: &["graphene_core::raster::adjustments::HueSaturationNode", "graphene_core::raster::HueSaturationNode"], + }, + NodeReplacement { + node: graphene_std::raster_nodes::adjustments::invert::IDENTIFIER, + aliases: &[ + "graphene_core::raster::adjustments::InvertNode", + "graphene_core::raster::InvertNode", + "graphene_core::raster::InvertRGBNode", + ], + }, + NodeReplacement { + node: graphene_std::raster_nodes::adjustments::threshold::IDENTIFIER, + aliases: &["graphene_core::raster::adjustments::ThresholdNode", "graphene_core::raster::ThresholdNode"], + }, + NodeReplacement { + node: graphene_std::raster_nodes::adjustments::blend::IDENTIFIER, + aliases: &["graphene_core::raster::adjustments::BlendNode", "graphene_core::raster::BlendNode"], + }, + NodeReplacement { + node: graphene_std::raster_nodes::adjustments::blend_color_pair::IDENTIFIER, + aliases: &["graphene_core::raster::BlendColorPairNode"], + }, + // this node doesn't seem to exist? + // (graphene_std::raster_nodes::adjustments::blend_color::IDENTIFIER, &["graphene_core::raster::adjustments::BlendColorsNode","graphene_core::raster::BlendColorsNode"]), + NodeReplacement { + node: graphene_std::raster_nodes::adjustments::gradient_map::IDENTIFIER, + aliases: &["graphene_core::raster::adjustments::GradientMapNode", "graphene_core::raster::GradientMapNode"], + }, + NodeReplacement { + node: graphene_std::raster_nodes::adjustments::vibrance::IDENTIFIER, + aliases: &["graphene_core::raster::adjustments::VibranceNode", "graphene_core::raster::VibranceNode"], + }, + NodeReplacement { + node: graphene_std::raster_nodes::adjustments::channel_mixer::IDENTIFIER, + aliases: &["graphene_core::raster::adjustments::ChannelMixerNode", "graphene_core::raster::ChannelMixerNode"], + }, + NodeReplacement { + node: graphene_std::raster_nodes::adjustments::selective_color::IDENTIFIER, + aliases: &["graphene_core::raster::adjustments::SelectiveColorNode"], + }, + NodeReplacement { + node: graphene_std::raster_nodes::adjustments::posterize::IDENTIFIER, + aliases: &["graphene_core::raster::adjustments::PosterizeNode", "graphene_core::raster::PosterizeNode"], + }, + NodeReplacement { + node: graphene_std::raster_nodes::adjustments::exposure::IDENTIFIER, + aliases: &["graphene_core::raster::adjustments::ExposureNode", "graphene_core::raster::ExposureNode"], + }, + NodeReplacement { + node: graphene_std::raster_nodes::adjustments::color_overlay::IDENTIFIER, + aliases: &["graphene_core::raster::adjustments::ColorOverlayNode", "graphene_raster_nodes::generate_curves::ColorOverlayNode"], + }, // raster - ("graphene_core::raster::adjustments::GenerateCurvesNode", "graphene_raster_nodes::generate_curves::GenerateCurvesNode"), - ("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"), + NodeReplacement { + node: graphene_std::raster_nodes::generate_curves::generate_curves::IDENTIFIER, + aliases: &["graphene_core::raster::adjustments::GenerateCurvesNode"], + }, + NodeReplacement { + node: graphene_std::raster_nodes::dehaze::dehaze::IDENTIFIER, + aliases: &["graphene_std::dehaze::DehazeNode"], + }, + NodeReplacement { + node: graphene_std::raster_nodes::filter::blur::IDENTIFIER, + aliases: &["graphene_std::filter::BlurNode"], + }, + NodeReplacement { + node: graphene_std::raster_nodes::image_color_palette::image_color_palette::IDENTIFIER, + aliases: &["graphene_std::image_color_palette::ImageColorPaletteNode"], + }, + NodeReplacement { + node: graphene_std::raster_nodes::std_nodes::sample_image::IDENTIFIER, + aliases: &["graphene_std::raster::SampleImageNode", "graphene_std::raster::SampleNode"], + }, + NodeReplacement { + node: graphene_std::raster_nodes::std_nodes::combine_channels::IDENTIFIER, + aliases: &["graphene_std::raster::CombineChannelsNode"], + }, + NodeReplacement { + node: graphene_std::raster_nodes::std_nodes::mask::IDENTIFIER, + aliases: &["graphene_std::raster::MaskNode", "graphene_std::raster::MaskImageNode"], + }, + NodeReplacement { + node: graphene_std::raster_nodes::std_nodes::extend_image_to_bounds::IDENTIFIER, + aliases: &["graphene_std::raster::ExtendImageToBoundsNode"], + }, + NodeReplacement { + node: graphene_std::raster_nodes::std_nodes::empty_image::IDENTIFIER, + aliases: &["graphene_std::raster::EmptyImageNode"], + }, + NodeReplacement { + node: graphene_std::raster_nodes::std_nodes::image_value::IDENTIFIER, + aliases: &["graphene_std::raster::ImageValueNode"], + }, + NodeReplacement { + node: graphene_std::raster_nodes::std_nodes::noise_pattern::IDENTIFIER, + aliases: &["graphene_std::raster::NoisePatternNode"], + }, + NodeReplacement { + node: graphene_std::raster_nodes::std_nodes::mandelbrot::IDENTIFIER, + aliases: &["graphene_std::raster::MandelbrotNode"], + }, // text - ("graphene_core::text::TextGeneratorNode", "graphene_core::text::TextNode"), + NodeReplacement { + node: graphene_std::text::text::IDENTIFIER, + aliases: &["graphene_core::text::TextGeneratorNode"], + }, // transform - ("graphene_core::transform::SetTransformNode", "graphene_core::transform_nodes::ReplaceTransformNode"), - ("graphene_core::transform::ReplaceTransformNode", "graphene_core::transform_nodes::ReplaceTransformNode"), - ("graphene_core::transform::TransformNode", "graphene_core::transform_nodes::TransformNode"), - ("graphene_core::transform::BoundlessFootprintNode", "graphene_core::transform_nodes::BoundlessFootprintNode"), - ("graphene_core::transform::FreezeRealTimeNode", "graphene_core::transform_nodes::FreezeRealTimeNode"), + NodeReplacement { + node: graphene_std::transform_nodes::replace_transform::IDENTIFIER, + aliases: &["graphene_core::transform::SetTransformNode", "graphene_core::transform::ReplaceTransformNode"], + }, + NodeReplacement { + node: graphene_std::transform_nodes::transform::IDENTIFIER, + aliases: &["graphene_core::transform::TransformNode"], + }, + NodeReplacement { + node: graphene_std::transform_nodes::boundless_footprint::IDENTIFIER, + aliases: &["graphene_core::transform::BoundlessFootprintNode"], + }, + NodeReplacement { + node: graphene_std::transform_nodes::freeze_real_time::IDENTIFIER, + aliases: &["graphene_core::transform::FreezeRealTimeNode"], + }, // ??? - ("graphene_core::vector::SplinesFromPointsNode", "graphene_core::vector::SplineNode"), - ("graphene_core::vector::generator_nodes::EllipseGenerator", "graphene_core::vector::generator_nodes::EllipseNode"), - ("graphene_core::vector::generator_nodes::LineGenerator", "graphene_core::vector::generator_nodes::LineNode"), - ("graphene_core::vector::generator_nodes::RectangleGenerator", "graphene_core::vector::generator_nodes::RectangleNode"), - ( - "graphene_core::vector::generator_nodes::RegularPolygonGenerator", - "graphene_core::vector::generator_nodes::RegularPolygonNode", - ), - ("graphene_core::vector::generator_nodes::StarGenerator", "graphene_core::vector::generator_nodes::StarNode"), - ("graphene_std::executor::BlendGpuImageNode", "graphene_std::gpu_nodes::BlendGpuImageNode"), - ("graphene_std::raster::SampleNode", "graphene_std::raster::SampleImageNode"), - ("graphene_core::transform::CullNode", "graphene_core::ops::IdentityNode"), - ("graphene_std::raster::MaskImageNode", "graphene_std::raster::MaskNode"), - ("graphene_core::vector::FlattenVectorElementsNode", "graphene_core::vector::FlattenPathNode"), - ("graphene_std::vector::BooleanOperationNode", "graphene_path_bool::BooleanOperationNode"), + NodeReplacement { + node: graphene_std::vector::spline::IDENTIFIER, + aliases: &["graphene_core::vector::SplinesFromPointsNode"], + }, + NodeReplacement { + node: graphene_std::vector::generator_nodes::ellipse::IDENTIFIER, + aliases: &["graphene_core::vector::generator_nodes::EllipseGenerator"], + }, + NodeReplacement { + node: graphene_std::vector::generator_nodes::line::IDENTIFIER, + aliases: &["graphene_core::vector::generator_nodes::LineGenerator"], + }, + NodeReplacement { + node: graphene_std::vector::generator_nodes::rectangle::IDENTIFIER, + aliases: &["graphene_core::vector::generator_nodes::RectangleGenerator"], + }, + NodeReplacement { + node: graphene_std::vector::generator_nodes::regular_polygon::IDENTIFIER, + aliases: &["graphene_core::vector::generator_nodes::RegularPolygonGenerator"], + }, + NodeReplacement { + node: graphene_std::vector::generator_nodes::star::IDENTIFIER, + aliases: &["graphene_core::vector::generator_nodes::StarGenerator"], + }, + NodeReplacement { + node: graphene_std::ops::identity::IDENTIFIER, + aliases: &["graphene_core::transform::CullNode"], + }, + NodeReplacement { + node: graphene_std::vector::flatten_path::IDENTIFIER, + aliases: &["graphene_core::vector::FlattenVectorElementsNode"], + }, + NodeReplacement { + node: graphene_std::path_bool::boolean_operation::IDENTIFIER, + aliases: &["graphene_std::vector::BooleanOperationNode"], + }, // brush - ("graphene_std::brush::BrushStampGeneratorNode", "graphene_brush::brush::BrushStampGeneratorNode"), - ("graphene_std::brush::BlitNode", "graphene_brush::brush::BlitNode"), - ("graphene_std::brush::BrushNode", "graphene_brush::brush::BrushNode"), + NodeReplacement { + node: graphene_std::brush::brush::brush_stamp_generator::IDENTIFIER, + aliases: &["graphene_std::brush::BrushStampGeneratorNode"], + }, + NodeReplacement { + node: graphene_std::brush::brush::blit::IDENTIFIER, + aliases: &["graphene_std::brush::BlitNode"], + }, + NodeReplacement { + node: graphene_std::brush::brush::brush::IDENTIFIER, + aliases: &["graphene_std::brush::BrushNode"], + }, ]; +const REPLACEMENTS: &[(&str, &str)] = &[]; + pub fn document_migration_string_preprocessing(document_serialized_content: String) -> String { TEXT_REPLACEMENTS .iter() @@ -195,17 +480,26 @@ pub fn document_migration_upgrades(document: &mut DocumentMessageHandler, reset_ let network = document.network_interface.document_network().clone(); - // Apply string replacements to each node + // Apply string and node replacements to each node + let mut replacements = HashMap::<&str, ProtoNodeIdentifier>::new(); + Iterator::chain( + NODE_REPLACEMENTS.iter().flat_map(|NodeReplacement { node, aliases }| aliases.iter().map(|old| (*old, node.clone()))), + REPLACEMENTS.iter().map(|(old, new)| (*old, ProtoNodeIdentifier::new(new))), + ) + .for_each(|(old, new)| { + if replacements.insert(old, new).is_some() { + panic!("Duplicate old name `{old}`"); + } + }); + 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 node_path_without_type_args = protonode_id.name.split('<').next(); + if let Some(new) = node_path_without_type_args.and_then(|node_path| replacements.get(node_path)) { 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, &mut default_template); - document.network_interface.set_manual_compostion(node_id, &network_path, Some(graph_craft::Type::Generic("T".into()))); - } + default_template.document_node.implementation = DocumentNodeImplementation::ProtoNode(new.clone()); + 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()))); } } } @@ -691,3 +985,20 @@ fn migrate_node(node_id: &NodeId, node: &DocumentNode, network_path: &[NodeId], Some(()) } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_no_duplicate_node_replacements() { + let mut hashmap = HashMap::::new(); + NODE_REPLACEMENTS.iter().for_each(|node| { + *hashmap.entry(node.node.clone()).or_default() += 1; + }); + let duplicates = hashmap.iter().filter(|(_, count)| **count > 1).map(|(node, _)| &node.name).collect::>(); + if duplicates.len() > 0 { + panic!("Duplicate entries in `NODE_REPLACEMENTS`: {:?}", duplicates); + } + } +} diff --git a/node-graph/gcore/src/graphic_element.rs b/node-graph/gcore/src/graphic_element.rs index 131c6c04b0..af8ae31230 100644 --- a/node-graph/gcore/src/graphic_element.rs +++ b/node-graph/gcore/src/graphic_element.rs @@ -490,7 +490,7 @@ async fn to_artboard + 'n>( } #[node_macro::node(category(""))] -async fn append_artboard(_ctx: impl Ctx, mut artboards: ArtboardGroupTable, artboard: Artboard, node_path: Vec) -> ArtboardGroupTable { +pub async fn append_artboard(_ctx: impl Ctx, mut artboards: ArtboardGroupTable, artboard: Artboard, node_path: Vec) -> ArtboardGroupTable { // Get the penultimate element of the node path, or None if the path is too short. // This is used to get the ID of the user-facing "Artboard" node (which encapsulates this internal "Append Artboard" node). let encapsulating_node_id = node_path.get(node_path.len().wrapping_sub(2)).copied();