From 765092fbe9aa5d7727589082b4267939429ab607 Mon Sep 17 00:00:00 2001 From: Keavon Chambers Date: Sat, 12 Jul 2025 22:50:59 -0700 Subject: [PATCH 01/62] Rename the message system's 'data' argument to 'context' (#2872) --- editor/src/dispatcher.rs | 228 +++++++-------- .../messages/animation/animation_message.rs | 6 +- .../animation/animation_message_handler.rs | 8 +- .../broadcast/broadcast_message_handler.rs | 2 +- .../messages/debug/debug_message_handler.rs | 2 +- .../messages/dialog/dialog_message_handler.rs | 12 +- .../export_dialog_message_handler.rs | 8 +- .../src/messages/dialog/export_dialog/mod.rs | 2 +- editor/src/messages/dialog/mod.rs | 2 +- .../new_document_dialog_message_handler.rs | 2 +- .../messages/dialog/preferences_dialog/mod.rs | 2 +- .../preferences_dialog_message_handler.rs | 8 +- .../globals/globals_message_handler.rs | 2 +- .../input_mapper_message_handler.rs | 8 +- .../key_mapping_message_handler.rs | 12 +- .../messages/input_mapper/key_mapping/mod.rs | 2 +- editor/src/messages/input_mapper/mod.rs | 2 +- .../input_preprocessor_message_handler.rs | 30 +- editor/src/messages/input_preprocessor/mod.rs | 2 +- .../messages/layout/layout_message_handler.rs | 97 +++--- editor/src/messages/layout/mod.rs | 2 +- editor/src/messages/message.rs | 18 +- .../document/document_message_handler.rs | 32 +- .../graph_operation_message_handler.rs | 22 +- editor/src/messages/portfolio/document/mod.rs | 2 +- .../portfolio/document/navigation/mod.rs | 2 +- .../navigation/navigation_message_handler.rs | 10 +- .../node_graph/node_graph_message_handler.rs | 32 +- .../document/node_graph/node_properties.rs | 24 +- .../portfolio/document/overlays/mod.rs | 2 +- .../overlays/overlays_message_handler.rs | 28 +- .../document/properties_panel/mod.rs | 4 +- .../properties_panel_message_handler.rs | 29 +- .../properties_panel/utility_types.rs | 10 - .../menu_bar/menu_bar_message_handler.rs | 2 +- editor/src/messages/portfolio/mod.rs | 2 +- .../messages/portfolio/portfolio_message.rs | 1 + .../portfolio/portfolio_message_handler.rs | 38 ++- .../spreadsheet_message_handler.rs | 2 +- .../preferences_message_handler.rs | 2 +- editor/src/messages/prelude.rs | 24 +- editor/src/messages/tool/mod.rs | 2 +- .../src/messages/tool/tool_message_handler.rs | 34 ++- .../tool/tool_messages/artboard_tool.rs | 10 +- .../messages/tool/tool_messages/brush_tool.rs | 17 +- .../tool/tool_messages/eyedropper_tool.rs | 10 +- .../messages/tool/tool_messages/fill_tool.rs | 17 +- .../tool/tool_messages/freehand_tool.rs | 17 +- .../tool/tool_messages/gradient_tool.rs | 19 +- editor/src/messages/tool/tool_messages/mod.rs | 2 +- .../tool/tool_messages/navigate_tool.rs | 8 +- .../messages/tool/tool_messages/path_tool.rs | 23 +- .../messages/tool/tool_messages/pen_tool.rs | 17 +- .../tool/tool_messages/select_tool.rs | 10 +- .../messages/tool/tool_messages/shape_tool.rs | 10 +- .../tool/tool_messages/spline_tool.rs | 17 +- .../messages/tool/tool_messages/text_tool.rs | 17 +- .../src/messages/tool/transform_layer/mod.rs | 4 +- .../transform_layer_message_handler.rs | 276 +++++++++--------- editor/src/messages/tool/utility_types.rs | 10 +- .../workspace/workspace_message_handler.rs | 2 +- editor/src/node_graph_executor.rs | 17 +- editor/src/test_utils.rs | 2 +- editor/src/utility_traits.rs | 9 +- frontend/wasm/src/editor_api.rs | 2 +- node-graph/gcore/src/logic.rs | 4 +- node-graph/gmath-nodes/src/lib.rs | 4 +- proc-macros/src/message_handler_data_attr.rs | 4 +- 68 files changed, 674 insertions(+), 615 deletions(-) delete mode 100644 editor/src/messages/portfolio/document/properties_panel/utility_types.rs diff --git a/editor/src/dispatcher.rs b/editor/src/dispatcher.rs index 9408d7d92..ee098193c 100644 --- a/editor/src/dispatcher.rs +++ b/editor/src/dispatcher.rs @@ -1,6 +1,6 @@ use crate::messages::debug::utility_types::MessageLoggingVerbosity; -use crate::messages::dialog::DialogMessageData; -use crate::messages::portfolio::document::node_graph::document_node_definitions; +use crate::messages::dialog::DialogMessageContext; +use crate::messages::layout::layout_message_handler::LayoutMessageContext; use crate::messages::prelude::*; #[derive(Debug, Default)] @@ -91,7 +91,7 @@ impl Dispatcher { pub fn handle_message>(&mut self, message: T, process_after_all_current: bool) { let message = message.into(); // Add all additional messages to the buffer if it exists (except from the end buffer message) - if !matches!(message, Message::EndBuffer(_)) { + if !matches!(message, Message::EndBuffer { .. }) { if let Some(buffered_queue) = &mut self.buffered_queue { Self::schedule_execution(buffered_queue, true, [message]); @@ -126,10 +126,112 @@ impl Dispatcher { // Process the action by forwarding it to the relevant message handler, or saving the FrontendMessage to be sent to the frontend match message { + Message::Animation(message) => { + self.message_handlers.animation_message_handler.process_message(message, &mut queue, ()); + } + Message::Broadcast(message) => self.message_handlers.broadcast_message_handler.process_message(message, &mut queue, ()), + Message::Debug(message) => { + self.message_handlers.debug_message_handler.process_message(message, &mut queue, ()); + } + Message::Dialog(message) => { + let context = DialogMessageContext { + portfolio: &self.message_handlers.portfolio_message_handler, + preferences: &self.message_handlers.preferences_message_handler, + }; + self.message_handlers.dialog_message_handler.process_message(message, &mut queue, context); + } + Message::Frontend(message) => { + // Handle these messages immediately by returning early + if let FrontendMessage::TriggerFontLoad { .. } = message { + self.responses.push(message); + self.cleanup_queues(false); + + // Return early to avoid running the code after the match block + return; + } else { + // `FrontendMessage`s are saved and will be sent to the frontend after the message queue is done being processed + self.responses.push(message); + } + } + Message::Globals(message) => { + self.message_handlers.globals_message_handler.process_message(message, &mut queue, ()); + } + Message::InputPreprocessor(message) => { + let keyboard_platform = GLOBAL_PLATFORM.get().copied().unwrap_or_default().as_keyboard_platform_layout(); + + self.message_handlers + .input_preprocessor_message_handler + .process_message(message, &mut queue, InputPreprocessorMessageContext { keyboard_platform }); + } + Message::KeyMapping(message) => { + let input = &self.message_handlers.input_preprocessor_message_handler; + let actions = self.collect_actions(); + + self.message_handlers + .key_mapping_message_handler + .process_message(message, &mut queue, KeyMappingMessageContext { input, actions }); + } + Message::Layout(message) => { + let action_input_mapping = &|action_to_find: &MessageDiscriminant| self.message_handlers.key_mapping_message_handler.action_input_mapping(action_to_find); + let context = LayoutMessageContext { action_input_mapping }; + + self.message_handlers.layout_message_handler.process_message(message, &mut queue, context); + } + Message::Portfolio(message) => { + let ipp = &self.message_handlers.input_preprocessor_message_handler; + let preferences = &self.message_handlers.preferences_message_handler; + let current_tool = &self.message_handlers.tool_message_handler.tool_state.tool_data.active_tool_type; + let message_logging_verbosity = self.message_handlers.debug_message_handler.message_logging_verbosity; + let reset_node_definitions_on_open = self.message_handlers.portfolio_message_handler.reset_node_definitions_on_open; + let timing_information = self.message_handlers.animation_message_handler.timing_information(); + let animation = &self.message_handlers.animation_message_handler; + + self.message_handlers.portfolio_message_handler.process_message( + message, + &mut queue, + PortfolioMessageContext { + ipp, + preferences, + current_tool, + message_logging_verbosity, + reset_node_definitions_on_open, + timing_information, + animation, + }, + ); + } + Message::Preferences(message) => { + self.message_handlers.preferences_message_handler.process_message(message, &mut queue, ()); + } + Message::Tool(message) => { + let document_id = self.message_handlers.portfolio_message_handler.active_document_id().unwrap(); + let Some(document) = self.message_handlers.portfolio_message_handler.documents.get_mut(&document_id) else { + warn!("Called ToolMessage without an active document.\nGot {message:?}"); + return; + }; + + let context = ToolMessageContext { + document_id, + document, + input: &self.message_handlers.input_preprocessor_message_handler, + persistent_data: &self.message_handlers.portfolio_message_handler.persistent_data, + node_graph: &self.message_handlers.portfolio_message_handler.executor, + preferences: &self.message_handlers.preferences_message_handler, + }; + + self.message_handlers.tool_message_handler.process_message(message, &mut queue, context); + } + Message::Workspace(message) => { + self.message_handlers.workspace_message_handler.process_message(message, &mut queue, ()); + } + Message::NoOp => {} + Message::Batched { messages } => { + messages.iter().for_each(|message| self.handle_message(message.to_owned(), false)); + } Message::StartBuffer => { self.buffered_queue = Some(std::mem::take(&mut self.message_queues)); } - Message::EndBuffer(render_metadata) => { + Message::EndBuffer { render_metadata } => { // Assign the message queue to the currently buffered queue if let Some(buffered_queue) = self.buffered_queue.take() { self.cleanup_queues(false); @@ -157,124 +259,6 @@ impl Dispatcher { ]; Self::schedule_execution(&mut self.message_queues, false, messages.map(Message::from)); } - Message::NoOp => {} - Message::Init => { - // Load persistent data from the browser database - queue.add(FrontendMessage::TriggerLoadFirstAutoSaveDocument); - queue.add(FrontendMessage::TriggerLoadPreferences); - - // Display the menu bar at the top of the window - queue.add(MenuBarMessage::SendLayout); - - // Send the information for tooltips and categories for each node/input. - queue.add(FrontendMessage::SendUIMetadata { - node_descriptions: document_node_definitions::collect_node_descriptions(), - node_types: document_node_definitions::collect_node_types(), - }); - - // Finish loading persistent data from the browser database - queue.add(FrontendMessage::TriggerLoadRestAutoSaveDocuments); - } - Message::Animation(message) => { - self.message_handlers.animation_message_handler.process_message(message, &mut queue, ()); - } - Message::Batched(messages) => { - messages.iter().for_each(|message| self.handle_message(message.to_owned(), false)); - } - Message::Broadcast(message) => self.message_handlers.broadcast_message_handler.process_message(message, &mut queue, ()), - Message::Debug(message) => { - self.message_handlers.debug_message_handler.process_message(message, &mut queue, ()); - } - Message::Dialog(message) => { - let data = DialogMessageData { - portfolio: &self.message_handlers.portfolio_message_handler, - preferences: &self.message_handlers.preferences_message_handler, - }; - self.message_handlers.dialog_message_handler.process_message(message, &mut queue, data); - } - Message::Frontend(message) => { - // Handle these messages immediately by returning early - if let FrontendMessage::TriggerFontLoad { .. } = message { - self.responses.push(message); - self.cleanup_queues(false); - - // Return early to avoid running the code after the match block - return; - } else { - // `FrontendMessage`s are saved and will be sent to the frontend after the message queue is done being processed - self.responses.push(message); - } - } - Message::Globals(message) => { - self.message_handlers.globals_message_handler.process_message(message, &mut queue, ()); - } - Message::InputPreprocessor(message) => { - let keyboard_platform = GLOBAL_PLATFORM.get().copied().unwrap_or_default().as_keyboard_platform_layout(); - - self.message_handlers - .input_preprocessor_message_handler - .process_message(message, &mut queue, InputPreprocessorMessageData { keyboard_platform }); - } - Message::KeyMapping(message) => { - let input = &self.message_handlers.input_preprocessor_message_handler; - let actions = self.collect_actions(); - - self.message_handlers - .key_mapping_message_handler - .process_message(message, &mut queue, KeyMappingMessageData { input, actions }); - } - Message::Layout(message) => { - let action_input_mapping = &|action_to_find: &MessageDiscriminant| self.message_handlers.key_mapping_message_handler.action_input_mapping(action_to_find); - - self.message_handlers.layout_message_handler.process_message(message, &mut queue, action_input_mapping); - } - Message::Portfolio(message) => { - let ipp = &self.message_handlers.input_preprocessor_message_handler; - let preferences = &self.message_handlers.preferences_message_handler; - let current_tool = &self.message_handlers.tool_message_handler.tool_state.tool_data.active_tool_type; - let message_logging_verbosity = self.message_handlers.debug_message_handler.message_logging_verbosity; - let reset_node_definitions_on_open = self.message_handlers.portfolio_message_handler.reset_node_definitions_on_open; - let timing_information = self.message_handlers.animation_message_handler.timing_information(); - let animation = &self.message_handlers.animation_message_handler; - - self.message_handlers.portfolio_message_handler.process_message( - message, - &mut queue, - PortfolioMessageData { - ipp, - preferences, - current_tool, - message_logging_verbosity, - reset_node_definitions_on_open, - timing_information, - animation, - }, - ); - } - Message::Preferences(message) => { - self.message_handlers.preferences_message_handler.process_message(message, &mut queue, ()); - } - Message::Tool(message) => { - let document_id = self.message_handlers.portfolio_message_handler.active_document_id().unwrap(); - let Some(document) = self.message_handlers.portfolio_message_handler.documents.get_mut(&document_id) else { - warn!("Called ToolMessage without an active document.\nGot {message:?}"); - return; - }; - - let data = ToolMessageData { - document_id, - document, - input: &self.message_handlers.input_preprocessor_message_handler, - persistent_data: &self.message_handlers.portfolio_message_handler.persistent_data, - node_graph: &self.message_handlers.portfolio_message_handler.executor, - preferences: &self.message_handlers.preferences_message_handler, - }; - - self.message_handlers.tool_message_handler.process_message(message, &mut queue, data); - } - Message::Workspace(message) => { - self.message_handlers.workspace_message_handler.process_message(message, &mut queue, ()); - } } // If there are child messages, append the queue to the list of queues diff --git a/editor/src/messages/animation/animation_message.rs b/editor/src/messages/animation/animation_message.rs index 131668009..128cc70ea 100644 --- a/editor/src/messages/animation/animation_message.rs +++ b/editor/src/messages/animation/animation_message.rs @@ -9,9 +9,9 @@ pub enum AnimationMessage { EnableLivePreview, DisableLivePreview, RestartAnimation, - SetFrameIndex(f64), - SetTime(f64), + SetFrameIndex { frame: f64 }, + SetTime { time: f64 }, UpdateTime, IncrementFrameCounter, - SetAnimationTimeMode(AnimationTimeMode), + SetAnimationTimeMode { animation_time_mode: AnimationTimeMode }, } diff --git a/editor/src/messages/animation/animation_message_handler.rs b/editor/src/messages/animation/animation_message_handler.rs index 211d22c87..32a7979ab 100644 --- a/editor/src/messages/animation/animation_message_handler.rs +++ b/editor/src/messages/animation/animation_message_handler.rs @@ -59,7 +59,7 @@ impl AnimationMessageHandler { #[message_handler_data] impl MessageHandler for AnimationMessageHandler { - fn process_message(&mut self, message: AnimationMessage, responses: &mut VecDeque, _data: ()) { + fn process_message(&mut self, message: AnimationMessage, responses: &mut VecDeque, _: ()) { match message { AnimationMessage::ToggleLivePreview => match self.animation_state { AnimationState::Stopped => responses.add(AnimationMessage::EnableLivePreview), @@ -82,13 +82,13 @@ impl MessageHandler for AnimationMessageHandler { // Update the restart and pause/play buttons responses.add(PortfolioMessage::UpdateDocumentWidgets); } - AnimationMessage::SetFrameIndex(frame) => { + AnimationMessage::SetFrameIndex { frame } => { self.frame_index = frame; responses.add(PortfolioMessage::SubmitActiveGraphRender); // Update the restart and pause/play buttons responses.add(PortfolioMessage::UpdateDocumentWidgets); } - AnimationMessage::SetTime(time) => { + AnimationMessage::SetTime { time } => { self.timestamp = time; responses.add(AnimationMessage::UpdateTime); } @@ -120,7 +120,7 @@ impl MessageHandler for AnimationMessageHandler { // Update the restart and pause/play buttons responses.add(PortfolioMessage::UpdateDocumentWidgets); } - AnimationMessage::SetAnimationTimeMode(animation_time_mode) => { + AnimationMessage::SetAnimationTimeMode { animation_time_mode } => { self.animation_time_mode = animation_time_mode; } } diff --git a/editor/src/messages/broadcast/broadcast_message_handler.rs b/editor/src/messages/broadcast/broadcast_message_handler.rs index 489df47ab..29d75f5cb 100644 --- a/editor/src/messages/broadcast/broadcast_message_handler.rs +++ b/editor/src/messages/broadcast/broadcast_message_handler.rs @@ -7,7 +7,7 @@ pub struct BroadcastMessageHandler { #[message_handler_data] impl MessageHandler for BroadcastMessageHandler { - fn process_message(&mut self, message: BroadcastMessage, responses: &mut VecDeque, _data: ()) { + fn process_message(&mut self, message: BroadcastMessage, responses: &mut VecDeque, _: ()) { match message { // Sub-messages BroadcastMessage::TriggerEvent(event) => { diff --git a/editor/src/messages/debug/debug_message_handler.rs b/editor/src/messages/debug/debug_message_handler.rs index 064ad2510..24fc69558 100644 --- a/editor/src/messages/debug/debug_message_handler.rs +++ b/editor/src/messages/debug/debug_message_handler.rs @@ -8,7 +8,7 @@ pub struct DebugMessageHandler { #[message_handler_data] impl MessageHandler for DebugMessageHandler { - fn process_message(&mut self, message: DebugMessage, responses: &mut VecDeque, _data: ()) { + fn process_message(&mut self, message: DebugMessage, responses: &mut VecDeque, _: ()) { match message { DebugMessage::ToggleTraceLogs => { if log::max_level() == log::LevelFilter::Debug { diff --git a/editor/src/messages/dialog/dialog_message_handler.rs b/editor/src/messages/dialog/dialog_message_handler.rs index 8e6bbde2f..0853abc04 100644 --- a/editor/src/messages/dialog/dialog_message_handler.rs +++ b/editor/src/messages/dialog/dialog_message_handler.rs @@ -3,7 +3,7 @@ use crate::messages::layout::utility_types::widget_prelude::*; use crate::messages::prelude::*; #[derive(ExtractField)] -pub struct DialogMessageData<'a> { +pub struct DialogMessageContext<'a> { pub portfolio: &'a PortfolioMessageHandler, pub preferences: &'a PreferencesMessageHandler, } @@ -17,14 +17,14 @@ pub struct DialogMessageHandler { } #[message_handler_data] -impl MessageHandler> for DialogMessageHandler { - fn process_message(&mut self, message: DialogMessage, responses: &mut VecDeque, data: DialogMessageData) { - let DialogMessageData { portfolio, preferences } = data; +impl MessageHandler> for DialogMessageHandler { + fn process_message(&mut self, message: DialogMessage, responses: &mut VecDeque, context: DialogMessageContext) { + let DialogMessageContext { portfolio, preferences } = context; match message { - DialogMessage::ExportDialog(message) => self.export_dialog.process_message(message, responses, ExportDialogMessageData { portfolio }), + DialogMessage::ExportDialog(message) => self.export_dialog.process_message(message, responses, ExportDialogMessageContext { portfolio }), DialogMessage::NewDocumentDialog(message) => self.new_document_dialog.process_message(message, responses, ()), - DialogMessage::PreferencesDialog(message) => self.preferences_dialog.process_message(message, responses, PreferencesDialogMessageData { preferences }), + DialogMessage::PreferencesDialog(message) => self.preferences_dialog.process_message(message, responses, PreferencesDialogMessageContext { preferences }), DialogMessage::CloseAllDocumentsWithConfirmation => { let dialog = simple_dialogs::CloseAllDocumentsDialog { 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 179aab4f9..980f3e3e2 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 @@ -4,7 +4,7 @@ use crate::messages::portfolio::document::utility_types::document_metadata::Laye use crate::messages::prelude::*; #[derive(ExtractField)] -pub struct ExportDialogMessageData<'a> { +pub struct ExportDialogMessageContext<'a> { pub portfolio: &'a PortfolioMessageHandler, } @@ -33,9 +33,9 @@ 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; +impl MessageHandler> for ExportDialogMessageHandler { + fn process_message(&mut self, message: ExportDialogMessage, responses: &mut VecDeque, context: ExportDialogMessageContext) { + let ExportDialogMessageContext { portfolio } = context; match message { ExportDialogMessage::FileType(export_type) => self.file_type = export_type, diff --git a/editor/src/messages/dialog/export_dialog/mod.rs b/editor/src/messages/dialog/export_dialog/mod.rs index 440684ef8..379480e31 100644 --- a/editor/src/messages/dialog/export_dialog/mod.rs +++ b/editor/src/messages/dialog/export_dialog/mod.rs @@ -4,4 +4,4 @@ mod export_dialog_message_handler; #[doc(inline)] pub use export_dialog_message::{ExportDialogMessage, ExportDialogMessageDiscriminant}; #[doc(inline)] -pub use export_dialog_message_handler::{ExportDialogMessageData, ExportDialogMessageHandler}; +pub use export_dialog_message_handler::{ExportDialogMessageContext, ExportDialogMessageHandler}; diff --git a/editor/src/messages/dialog/mod.rs b/editor/src/messages/dialog/mod.rs index 692991a30..67a186f99 100644 --- a/editor/src/messages/dialog/mod.rs +++ b/editor/src/messages/dialog/mod.rs @@ -16,4 +16,4 @@ pub mod simple_dialogs; #[doc(inline)] pub use dialog_message::{DialogMessage, DialogMessageDiscriminant}; #[doc(inline)] -pub use dialog_message_handler::{DialogMessageData, DialogMessageHandler}; +pub use dialog_message_handler::{DialogMessageContext, DialogMessageHandler}; 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 539180117..6121424de 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 @@ -13,7 +13,7 @@ pub struct NewDocumentDialogMessageHandler { #[message_handler_data] impl MessageHandler for NewDocumentDialogMessageHandler { - fn process_message(&mut self, message: NewDocumentDialogMessage, responses: &mut VecDeque, _data: ()) { + fn process_message(&mut self, message: NewDocumentDialogMessage, responses: &mut VecDeque, _: ()) { match message { NewDocumentDialogMessage::Name(name) => self.name = name, NewDocumentDialogMessage::Infinite(infinite) => self.infinite = infinite, diff --git a/editor/src/messages/dialog/preferences_dialog/mod.rs b/editor/src/messages/dialog/preferences_dialog/mod.rs index c7dd00bb0..eb5ce0384 100644 --- a/editor/src/messages/dialog/preferences_dialog/mod.rs +++ b/editor/src/messages/dialog/preferences_dialog/mod.rs @@ -4,4 +4,4 @@ mod preferences_dialog_message_handler; #[doc(inline)] pub use preferences_dialog_message::{PreferencesDialogMessage, PreferencesDialogMessageDiscriminant}; #[doc(inline)] -pub use preferences_dialog_message_handler::{PreferencesDialogMessageData, PreferencesDialogMessageHandler}; +pub use preferences_dialog_message_handler::{PreferencesDialogMessageContext, PreferencesDialogMessageHandler}; 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 882fc6db5..66173d889 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 @@ -5,7 +5,7 @@ use crate::messages::preferences::SelectionMode; use crate::messages::prelude::*; #[derive(ExtractField)] -pub struct PreferencesDialogMessageData<'a> { +pub struct PreferencesDialogMessageContext<'a> { pub preferences: &'a PreferencesMessageHandler, } @@ -14,9 +14,9 @@ pub struct PreferencesDialogMessageData<'a> { 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; +impl MessageHandler> for PreferencesDialogMessageHandler { + fn process_message(&mut self, message: PreferencesDialogMessage, responses: &mut VecDeque, context: PreferencesDialogMessageContext) { + let PreferencesDialogMessageContext { preferences } = context; match message { PreferencesDialogMessage::Confirm => {} diff --git a/editor/src/messages/globals/globals_message_handler.rs b/editor/src/messages/globals/globals_message_handler.rs index 9a72ffa6d..8974bd16c 100644 --- a/editor/src/messages/globals/globals_message_handler.rs +++ b/editor/src/messages/globals/globals_message_handler.rs @@ -5,7 +5,7 @@ pub struct GlobalsMessageHandler {} #[message_handler_data] impl MessageHandler for GlobalsMessageHandler { - fn process_message(&mut self, message: GlobalsMessage, _responses: &mut VecDeque, _data: ()) { + fn process_message(&mut self, message: GlobalsMessage, _responses: &mut VecDeque, _: ()) { match message { GlobalsMessage::SetPlatform { platform } => { if GLOBAL_PLATFORM.get() != Some(&platform) { 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 b26a1b4c4..166889d8b 100644 --- a/editor/src/messages/input_mapper/input_mapper_message_handler.rs +++ b/editor/src/messages/input_mapper/input_mapper_message_handler.rs @@ -7,7 +7,7 @@ use crate::messages::prelude::*; use std::fmt::Write; #[derive(ExtractField)] -pub struct InputMapperMessageData<'a> { +pub struct InputMapperMessageContext<'a> { pub input: &'a InputPreprocessorMessageHandler, pub actions: ActionList, } @@ -18,9 +18,9 @@ pub struct InputMapperMessageHandler { } #[message_handler_data] -impl MessageHandler> for InputMapperMessageHandler { - fn process_message(&mut self, message: InputMapperMessage, responses: &mut VecDeque, data: InputMapperMessageData) { - let InputMapperMessageData { input, actions } = data; +impl MessageHandler> for InputMapperMessageHandler { + fn process_message(&mut self, message: InputMapperMessage, responses: &mut VecDeque, context: InputMapperMessageContext) { + let InputMapperMessageContext { input, actions } = context; if let Some(message) = self.mapping.match_input_message(message, &input.keyboard, actions) { responses.add(message); 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 81f249ec5..605f2587f 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 @@ -1,9 +1,9 @@ -use crate::messages::input_mapper::input_mapper_message_handler::InputMapperMessageData; +use crate::messages::input_mapper::input_mapper_message_handler::InputMapperMessageContext; use crate::messages::input_mapper::utility_types::input_keyboard::KeysGroup; use crate::messages::prelude::*; #[derive(ExtractField)] -pub struct KeyMappingMessageData<'a> { +pub struct KeyMappingMessageContext<'a> { pub input: &'a InputPreprocessorMessageHandler, pub actions: ActionList, } @@ -14,12 +14,12 @@ pub struct KeyMappingMessageHandler { } #[message_handler_data] -impl MessageHandler> for KeyMappingMessageHandler { - fn process_message(&mut self, message: KeyMappingMessage, responses: &mut VecDeque, data: KeyMappingMessageData) { - let KeyMappingMessageData { input, actions } = data; +impl MessageHandler> for KeyMappingMessageHandler { + fn process_message(&mut self, message: KeyMappingMessage, responses: &mut VecDeque, context: KeyMappingMessageContext) { + let KeyMappingMessageContext { input, actions } = context; match message { - KeyMappingMessage::Lookup(input_message) => self.mapping_handler.process_message(input_message, responses, InputMapperMessageData { input, actions }), + KeyMappingMessage::Lookup(input_message) => self.mapping_handler.process_message(input_message, responses, InputMapperMessageContext { input, actions }), KeyMappingMessage::ModifyMapping(new_layout) => self.mapping_handler.set_mapping(new_layout.into()), } } diff --git a/editor/src/messages/input_mapper/key_mapping/mod.rs b/editor/src/messages/input_mapper/key_mapping/mod.rs index 5b629044a..064b58509 100644 --- a/editor/src/messages/input_mapper/key_mapping/mod.rs +++ b/editor/src/messages/input_mapper/key_mapping/mod.rs @@ -4,4 +4,4 @@ mod key_mapping_message_handler; #[doc(inline)] pub use key_mapping_message::{KeyMappingMessage, KeyMappingMessageDiscriminant, MappingVariant, MappingVariantDiscriminant}; #[doc(inline)] -pub use key_mapping_message_handler::{KeyMappingMessageData, KeyMappingMessageHandler}; +pub use key_mapping_message_handler::{KeyMappingMessageContext, KeyMappingMessageHandler}; diff --git a/editor/src/messages/input_mapper/mod.rs b/editor/src/messages/input_mapper/mod.rs index 14aff3e3f..a5a194afe 100644 --- a/editor/src/messages/input_mapper/mod.rs +++ b/editor/src/messages/input_mapper/mod.rs @@ -8,4 +8,4 @@ pub mod utility_types; #[doc(inline)] pub use input_mapper_message::{InputMapperMessage, InputMapperMessageDiscriminant}; #[doc(inline)] -pub use input_mapper_message_handler::{InputMapperMessageData, InputMapperMessageHandler}; +pub use input_mapper_message_handler::{InputMapperMessageContext, InputMapperMessageHandler}; 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 144652fdc..16f9dca9c 100644 --- a/editor/src/messages/input_preprocessor/input_preprocessor_message_handler.rs +++ b/editor/src/messages/input_preprocessor/input_preprocessor_message_handler.rs @@ -7,7 +7,7 @@ use glam::DVec2; use std::time::Duration; #[derive(ExtractField)] -pub struct InputPreprocessorMessageData { +pub struct InputPreprocessorMessageContext { pub keyboard_platform: KeyboardPlatformLayout, } @@ -21,9 +21,9 @@ pub struct InputPreprocessorMessageHandler { } #[message_handler_data] -impl MessageHandler for InputPreprocessorMessageHandler { - fn process_message(&mut self, message: InputPreprocessorMessage, responses: &mut VecDeque, data: InputPreprocessorMessageData) { - let InputPreprocessorMessageData { keyboard_platform } = data; +impl MessageHandler for InputPreprocessorMessageHandler { + fn process_message(&mut self, message: InputPreprocessorMessage, responses: &mut VecDeque, context: InputPreprocessorMessageContext) { + let InputPreprocessorMessageContext { keyboard_platform } = context; match message { InputPreprocessorMessage::BoundsOfViewports { bounds_of_viewports } => { @@ -98,7 +98,7 @@ impl MessageHandler for self.translate_mouse_event(mouse_state, false, responses); } InputPreprocessorMessage::CurrentTime { timestamp } => { - responses.add(AnimationMessage::SetTime(timestamp as f64)); + responses.add(AnimationMessage::SetTime { time: timestamp as f64 }); self.time = timestamp; self.frame_time.advance_timestamp(Duration::from_millis(timestamp)); } @@ -214,10 +214,10 @@ mod test { let mut responses = VecDeque::new(); - let data = InputPreprocessorMessageData { + let context = InputPreprocessorMessageContext { keyboard_platform: KeyboardPlatformLayout::Standard, }; - input_preprocessor.process_message(message, &mut responses, data); + input_preprocessor.process_message(message, &mut responses, context); assert!(input_preprocessor.keyboard.get(Key::Alt as usize)); assert_eq!(responses.pop_front(), Some(InputMapperMessage::KeyDown(Key::Alt).into())); @@ -233,10 +233,10 @@ mod test { let mut responses = VecDeque::new(); - let data = InputPreprocessorMessageData { + let context = InputPreprocessorMessageContext { keyboard_platform: KeyboardPlatformLayout::Standard, }; - input_preprocessor.process_message(message, &mut responses, data); + input_preprocessor.process_message(message, &mut responses, context); assert!(input_preprocessor.keyboard.get(Key::Control as usize)); assert_eq!(responses.pop_front(), Some(InputMapperMessage::KeyDown(Key::Control).into())); @@ -252,10 +252,10 @@ mod test { let mut responses = VecDeque::new(); - let data = InputPreprocessorMessageData { + let context = InputPreprocessorMessageContext { keyboard_platform: KeyboardPlatformLayout::Standard, }; - input_preprocessor.process_message(message, &mut responses, data); + input_preprocessor.process_message(message, &mut responses, context); assert!(input_preprocessor.keyboard.get(Key::Shift as usize)); assert_eq!(responses.pop_front(), Some(InputMapperMessage::KeyDown(Key::Shift).into())); @@ -273,10 +273,10 @@ mod test { let mut responses = VecDeque::new(); - let data = InputPreprocessorMessageData { + let context = InputPreprocessorMessageContext { keyboard_platform: KeyboardPlatformLayout::Standard, }; - input_preprocessor.process_message(message, &mut responses, data); + input_preprocessor.process_message(message, &mut responses, context); assert!(!input_preprocessor.keyboard.get(Key::Control as usize)); assert_eq!(responses.pop_front(), Some(InputMapperMessage::KeyUp(Key::Control).into())); @@ -293,10 +293,10 @@ mod test { let mut responses = VecDeque::new(); - let data = InputPreprocessorMessageData { + let context = InputPreprocessorMessageContext { keyboard_platform: KeyboardPlatformLayout::Standard, }; - input_preprocessor.process_message(message, &mut responses, data); + input_preprocessor.process_message(message, &mut responses, context); assert!(input_preprocessor.keyboard.get(Key::Control as usize)); assert!(input_preprocessor.keyboard.get(Key::Shift as usize)); diff --git a/editor/src/messages/input_preprocessor/mod.rs b/editor/src/messages/input_preprocessor/mod.rs index 99f445849..10785a7fa 100644 --- a/editor/src/messages/input_preprocessor/mod.rs +++ b/editor/src/messages/input_preprocessor/mod.rs @@ -4,4 +4,4 @@ mod input_preprocessor_message_handler; #[doc(inline)] pub use input_preprocessor_message::{InputPreprocessorMessage, InputPreprocessorMessageDiscriminant}; #[doc(inline)] -pub use input_preprocessor_message_handler::{InputPreprocessorMessageData, InputPreprocessorMessageHandler}; +pub use input_preprocessor_message_handler::{InputPreprocessorMessageContext, InputPreprocessorMessageHandler}; diff --git a/editor/src/messages/layout/layout_message_handler.rs b/editor/src/messages/layout/layout_message_handler.rs index 7beec22fe..f478e7091 100644 --- a/editor/src/messages/layout/layout_message_handler.rs +++ b/editor/src/messages/layout/layout_message_handler.rs @@ -6,14 +6,53 @@ use graphene_std::text::Font; use graphene_std::vector::style::{FillChoice, GradientStops}; use serde_json::Value; +#[derive(ExtractField)] +pub struct LayoutMessageContext<'a> { + pub action_input_mapping: &'a dyn Fn(&MessageDiscriminant) -> Option, +} + #[derive(Debug, Clone, Default, ExtractField)] pub struct LayoutMessageHandler { layouts: [Layout; LayoutTarget::LayoutTargetLength as usize], } -enum WidgetValueAction { - Commit, - Update, +#[message_handler_data] +impl MessageHandler> for LayoutMessageHandler { + fn process_message(&mut self, message: LayoutMessage, responses: &mut std::collections::VecDeque, context: LayoutMessageContext) { + let action_input_mapping = &context.action_input_mapping; + + match message { + LayoutMessage::ResendActiveWidget { layout_target, widget_id } => { + // Find the updated diff based on the specified layout target + let Some(diff) = (match &self.layouts[layout_target as usize] { + Layout::MenuLayout(_) => return, + Layout::WidgetLayout(layout) => Self::get_widget_path(layout, widget_id).map(|(widget, widget_path)| { + // Create a widget update diff for the relevant id + let new_value = DiffUpdate::Widget(widget.clone()); + WidgetDiff { widget_path, new_value } + }), + }) else { + return; + }; + // Resend that diff + self.send_diff(vec![diff], layout_target, responses, action_input_mapping); + } + LayoutMessage::SendLayout { layout, layout_target } => { + self.diff_and_send_layout_to_frontend(layout_target, layout, responses, action_input_mapping); + } + LayoutMessage::WidgetValueCommit { layout_target, widget_id, value } => { + self.handle_widget_callback(layout_target, widget_id, value, WidgetValueAction::Commit, responses); + } + LayoutMessage::WidgetValueUpdate { layout_target, widget_id, value } => { + self.handle_widget_callback(layout_target, widget_id, value, WidgetValueAction::Update, responses); + responses.add(LayoutMessage::ResendActiveWidget { layout_target, widget_id }); + } + } + } + + fn actions(&self) -> ActionList { + actions!(LayoutMessageDiscriminant;) + } } impl LayoutMessageHandler { @@ -340,54 +379,7 @@ impl LayoutMessageHandler { Widget::WorkingColorsInput(_) => {} }; } -} -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 { - LayoutMessage::ResendActiveWidget { layout_target, widget_id } => { - // Find the updated diff based on the specified layout target - let Some(diff) = (match &self.layouts[layout_target as usize] { - Layout::MenuLayout(_) => return, - Layout::WidgetLayout(layout) => Self::get_widget_path(layout, widget_id).map(|(widget, widget_path)| { - // Create a widget update diff for the relevant id - let new_value = DiffUpdate::Widget(widget.clone()); - WidgetDiff { widget_path, new_value } - }), - }) else { - return; - }; - // Resend that diff - self.send_diff(vec![diff], layout_target, responses, &action_input_mapping); - } - LayoutMessage::SendLayout { layout, layout_target } => { - self.diff_and_send_layout_to_frontend(layout_target, layout, responses, &action_input_mapping); - } - LayoutMessage::WidgetValueCommit { layout_target, widget_id, value } => { - self.handle_widget_callback(layout_target, widget_id, value, WidgetValueAction::Commit, responses); - } - LayoutMessage::WidgetValueUpdate { layout_target, widget_id, value } => { - self.handle_widget_callback(layout_target, widget_id, value, WidgetValueAction::Update, responses); - responses.add(LayoutMessage::ResendActiveWidget { layout_target, widget_id }); - } - } - } - - fn actions(&self) -> ActionList { - actions!(LayoutMessageDiscriminant;) - } -} - -impl LayoutMessageHandler { /// Diff the update and send to the frontend where necessary fn diff_and_send_layout_to_frontend( &mut self, @@ -453,3 +445,8 @@ impl LayoutMessageHandler { responses.add(message); } } + +enum WidgetValueAction { + Commit, + Update, +} diff --git a/editor/src/messages/layout/mod.rs b/editor/src/messages/layout/mod.rs index bd148eb76..beda5756c 100644 --- a/editor/src/messages/layout/mod.rs +++ b/editor/src/messages/layout/mod.rs @@ -1,5 +1,5 @@ mod layout_message; -mod layout_message_handler; +pub mod layout_message_handler; pub mod utility_types; diff --git a/editor/src/messages/message.rs b/editor/src/messages/message.rs index fbe417002..18023c1b6 100644 --- a/editor/src/messages/message.rs +++ b/editor/src/messages/message.rs @@ -1,15 +1,11 @@ use crate::messages::prelude::*; +use graphene_std::renderer::RenderMetadata; use graphite_proc_macros::*; #[impl_message] #[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)] pub enum Message { - NoOp, - Init, - Batched(Box<[Message]>), - StartBuffer, - EndBuffer(graphene_std::renderer::RenderMetadata), - + // Sub-messages #[child] Animation(AnimationMessage), #[child] @@ -36,6 +32,16 @@ pub enum Message { Tool(ToolMessage), #[child] Workspace(WorkspaceMessage), + + // Messages + NoOp, + Batched { + messages: Box<[Message]>, + }, + StartBuffer, + EndBuffer { + render_metadata: RenderMetadata, + }, } /// Provides an impl of `specta::Type` for `MessageDiscriminant`, the struct created by `impl_message`. diff --git a/editor/src/messages/portfolio/document/document_message_handler.rs b/editor/src/messages/portfolio/document/document_message_handler.rs index 784c3ba3e..1d1c6aa93 100644 --- a/editor/src/messages/portfolio/document/document_message_handler.rs +++ b/editor/src/messages/portfolio/document/document_message_handler.rs @@ -10,10 +10,10 @@ use crate::consts::{ASYMPTOTIC_EFFECT, COLOR_OVERLAY_GRAY, DEFAULT_DOCUMENT_NAME use crate::messages::input_mapper::utility_types::macros::action_keys; use crate::messages::layout::utility_types::widget_prelude::*; use crate::messages::portfolio::document::graph_operation::utility_types::TransformIn; -use crate::messages::portfolio::document::node_graph::NodeGraphHandlerData; +use crate::messages::portfolio::document::node_graph::NodeGraphMessageContext; use crate::messages::portfolio::document::overlays::grid_overlays::{grid_overlay, overlay_options}; use crate::messages::portfolio::document::overlays::utility_types::{OverlaysType, OverlaysVisibilitySettings}; -use crate::messages::portfolio::document::properties_panel::utility_types::PropertiesPanelMessageHandlerData; +use crate::messages::portfolio::document::properties_panel::properties_panel_message_handler::PropertiesPanelMessageContext; use crate::messages::portfolio::document::utility_types::document_metadata::{DocumentMetadata, LayerNodeIdentifier}; use crate::messages::portfolio::document::utility_types::misc::{AlignAggregate, AlignAxis, DocumentMode, FlipAxis, PTZ}; use crate::messages::portfolio::document::utility_types::network_interface::{FlowType, InputConnector, NodeTemplate}; @@ -39,7 +39,7 @@ use graphene_std::vector::style::ViewMode; use std::time::Duration; #[derive(ExtractField)] -pub struct DocumentMessageData<'a> { +pub struct DocumentMessageContext<'a> { pub document_id: DocumentId, pub ipp: &'a InputPreprocessorMessageHandler, pub persistent_data: &'a PersistentData, @@ -170,9 +170,9 @@ 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 { +impl MessageHandler> for DocumentMessageHandler { + fn process_message(&mut self, message: DocumentMessage, responses: &mut VecDeque, context: DocumentMessageContext) { + let DocumentMessageContext { document_id, ipp, persistent_data, @@ -180,14 +180,14 @@ impl MessageHandler> for DocumentMessag current_tool, preferences, device_pixel_ratio, - } = data; + } = context; let selected_nodes_bounding_box_viewport = self.network_interface.selected_nodes_bounding_box_viewport(&self.breadcrumb_network_path); let selected_visible_layers_bounding_box_viewport = self.selected_visible_layers_bounding_box_viewport(); match message { // Sub-messages DocumentMessage::Navigation(message) => { - let data = NavigationMessageData { + let context = NavigationMessageContext { network_interface: &mut self.network_interface, breadcrumb_network_path: &self.breadcrumb_network_path, ipp, @@ -201,7 +201,7 @@ impl MessageHandler> for DocumentMessag preferences, }; - self.navigation_handler.process_message(message, responses, data); + self.navigation_handler.process_message(message, responses, context); } DocumentMessage::Overlays(message) => { let visibility_settings = self.overlays_visibility_settings; @@ -210,7 +210,7 @@ impl MessageHandler> for DocumentMessag self.overlays_message_handler.process_message( message, responses, - OverlaysMessageData { + OverlaysMessageContext { visibility_settings, ipp, device_pixel_ratio, @@ -218,20 +218,20 @@ impl MessageHandler> for DocumentMessag ); } DocumentMessage::PropertiesPanel(message) => { - let properties_panel_message_handler_data = PropertiesPanelMessageHandlerData { + let context = PropertiesPanelMessageContext { network_interface: &mut self.network_interface, selection_network_path: &self.selection_network_path, document_name: self.name.as_str(), executor, + persistent_data, }; - self.properties_panel_message_handler - .process_message(message, responses, (persistent_data, properties_panel_message_handler_data)); + self.properties_panel_message_handler.process_message(message, responses, context); } DocumentMessage::NodeGraph(message) => { self.node_graph_handler.process_message( message, responses, - NodeGraphHandlerData { + NodeGraphMessageContext { network_interface: &mut self.network_interface, selection_network_path: &self.selection_network_path, breadcrumb_network_path: &self.breadcrumb_network_path, @@ -246,13 +246,13 @@ impl MessageHandler> for DocumentMessag ); } DocumentMessage::GraphOperation(message) => { - let data = GraphOperationMessageData { + let context = GraphOperationMessageContext { network_interface: &mut self.network_interface, collapsed: &mut self.collapsed, node_graph: &mut self.node_graph_handler, }; let mut graph_operation_message_handler = GraphOperationMessageHandler {}; - graph_operation_message_handler.process_message(message, responses, data); + graph_operation_message_handler.process_message(message, responses, context); } DocumentMessage::AlignSelectedLayers { axis, aggregate } => { let axis = match axis { 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 931a15e3b..c7f6fc12c 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 @@ -14,15 +14,8 @@ use graphene_std::renderer::convert_usvg_path::convert_usvg_path; use graphene_std::text::{Font, TypesettingConfig}; use graphene_std::vector::style::{Fill, Gradient, GradientStops, GradientType, PaintOrder, Stroke, StrokeAlign, StrokeCap, StrokeJoin}; -#[derive(Debug, Clone)] -struct ArtboardInfo { - input_node: NodeInput, - output_nodes: Vec, - merge_node: NodeId, -} - #[derive(ExtractField)] -pub struct GraphOperationMessageData<'a> { +pub struct GraphOperationMessageContext<'a> { pub network_interface: &'a mut NodeNetworkInterface, pub collapsed: &'a mut CollapsedLayers, pub node_graph: &'a mut NodeGraphMessageHandler, @@ -34,9 +27,9 @@ 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; +impl MessageHandler> for GraphOperationMessageHandler { + fn process_message(&mut self, message: GraphOperationMessage, responses: &mut VecDeque, context: GraphOperationMessageContext) { + let network_interface = context.network_interface; match message { GraphOperationMessage::FillSet { layer, fill } => { @@ -323,6 +316,13 @@ impl MessageHandler> for Gr } } +#[derive(Debug, Clone)] +struct ArtboardInfo { + input_node: NodeInput, + output_nodes: Vec, + merge_node: NodeId, +} + fn usvg_color(c: usvg::Color, a: f32) -> Color { Color::from_rgbaf32_unchecked(c.red as f32 / 255., c.green as f32 / 255., c.blue as f32 / 255., a) } diff --git a/editor/src/messages/portfolio/document/mod.rs b/editor/src/messages/portfolio/document/mod.rs index b9b7172e5..4099a584f 100644 --- a/editor/src/messages/portfolio/document/mod.rs +++ b/editor/src/messages/portfolio/document/mod.rs @@ -11,4 +11,4 @@ pub mod utility_types; #[doc(inline)] pub use document_message::{DocumentMessage, DocumentMessageDiscriminant}; #[doc(inline)] -pub use document_message_handler::{DocumentMessageData, DocumentMessageHandler}; +pub use document_message_handler::{DocumentMessageContext, DocumentMessageHandler}; diff --git a/editor/src/messages/portfolio/document/navigation/mod.rs b/editor/src/messages/portfolio/document/navigation/mod.rs index 0ee1512ed..882a0f907 100644 --- a/editor/src/messages/portfolio/document/navigation/mod.rs +++ b/editor/src/messages/portfolio/document/navigation/mod.rs @@ -5,4 +5,4 @@ pub mod utility_types; #[doc(inline)] pub use navigation_message::{NavigationMessage, NavigationMessageDiscriminant}; #[doc(inline)] -pub use navigation_message_handler::{NavigationMessageData, NavigationMessageHandler}; +pub use navigation_message_handler::{NavigationMessageContext, NavigationMessageHandler}; 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 0d732edd0..c1128f008 100644 --- a/editor/src/messages/portfolio/document/navigation/navigation_message_handler.rs +++ b/editor/src/messages/portfolio/document/navigation/navigation_message_handler.rs @@ -14,7 +14,7 @@ use glam::{DAffine2, DVec2}; use graph_craft::document::NodeId; #[derive(ExtractField)] -pub struct NavigationMessageData<'a> { +pub struct NavigationMessageContext<'a> { pub network_interface: &'a mut NodeNetworkInterface, pub breadcrumb_network_path: &'a [NodeId], pub ipp: &'a InputPreprocessorMessageHandler, @@ -33,9 +33,9 @@ pub struct NavigationMessageHandler { } #[message_handler_data] -impl MessageHandler> for NavigationMessageHandler { - fn process_message(&mut self, message: NavigationMessage, responses: &mut VecDeque, data: NavigationMessageData) { - let NavigationMessageData { +impl MessageHandler> for NavigationMessageHandler { + fn process_message(&mut self, message: NavigationMessage, responses: &mut VecDeque, context: NavigationMessageContext) { + let NavigationMessageContext { network_interface, breadcrumb_network_path, ipp, @@ -43,7 +43,7 @@ impl MessageHandler> for Navigation document_ptz, graph_view_overlay_open, preferences, - } = data; + } = context; fn get_ptz<'a>(document_ptz: &'a PTZ, network_interface: &'a NodeNetworkInterface, graph_view_overlay_open: bool, breadcrumb_network_path: &[NodeId]) -> Option<&'a PTZ> { if !graph_view_overlay_open { 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 828fb4a8a..d7391227c 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 @@ -28,7 +28,7 @@ use renderer::Quad; use std::cmp::Ordering; #[derive(Debug, ExtractField)] -pub struct NodeGraphHandlerData<'a> { +pub struct NodeGraphMessageContext<'a> { pub network_interface: &'a mut NodeNetworkInterface, pub selection_network_path: &'a [NodeId], pub breadcrumb_network_path: &'a [NodeId], @@ -93,9 +93,9 @@ 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 { +impl<'a> MessageHandler> for NodeGraphMessageHandler { + fn process_message(&mut self, message: NodeGraphMessage, responses: &mut VecDeque, context: NodeGraphMessageContext<'a>) { + let NodeGraphMessageContext { network_interface, selection_network_path, breadcrumb_network_path, @@ -106,7 +106,7 @@ impl<'a> MessageHandler> for NodeGrap graph_fade_artwork_percentage, navigation_handler, preferences, - } = data; + } = context; match message { // TODO: automatically remove broadcast messages. @@ -1833,16 +1833,18 @@ impl NodeGraphMessageHandler { .on_update(move |node_type| { let node_id = NodeId::new(); - Message::Batched(Box::new([ - NodeGraphMessage::CreateNodeFromContextMenu { - node_id: Some(node_id), - node_type: node_type.clone(), - xy: None, - add_transaction: true, - } - .into(), - NodeGraphMessage::SelectedNodesSet { nodes: vec![node_id] }.into(), - ])) + Message::Batched { + messages: Box::new([ + NodeGraphMessage::CreateNodeFromContextMenu { + node_id: Some(node_id), + node_type: node_type.clone(), + xy: None, + add_transaction: true, + } + .into(), + NodeGraphMessage::SelectedNodesSet { nodes: vec![node_id] }.into(), + ]), + } }) .widget_holder(); vec![LayoutGroup::Row { widgets: vec![node_chooser] }] 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 9551ed209..c69574b9a 100644 --- a/editor/src/messages/portfolio/document/node_graph/node_properties.rs +++ b/editor/src/messages/portfolio/document/node_graph/node_properties.rs @@ -59,13 +59,13 @@ pub fn expose_widget(node_id: NodeId, index: usize, data_type: FrontendGraphData } else { "Expose this parameter as a node input in the graph" }) - .on_update(move |_parameter| { - Message::Batched(Box::new([NodeGraphMessage::ExposeInput { + .on_update(move |_parameter| Message::Batched { + messages: Box::new([NodeGraphMessage::ExposeInput { input_connector: InputConnector::node(node_id, index), set_to_exposed: !exposed, start_transaction: true, } - .into()])) + .into()]), }) .widget_holder() } @@ -1307,8 +1307,8 @@ pub(crate) fn rectangle_properties(node_id: NodeId, context: &mut NodeProperties // Uniform/individual radio input widget let uniform = RadioEntryData::new("Uniform") .label("Uniform") - .on_update(move |_| { - Message::Batched(Box::new([ + .on_update(move |_| Message::Batched { + messages: Box::new([ NodeGraphMessage::SetInputValue { node_id, input_index: IndividualCornerRadiiInput::INDEX, @@ -1321,13 +1321,13 @@ pub(crate) fn rectangle_properties(node_id: NodeId, context: &mut NodeProperties value: TaggedValue::F64(uniform_val), } .into(), - ])) + ]), }) .on_commit(commit_value); let individual = RadioEntryData::new("Individual") .label("Individual") - .on_update(move |_| { - Message::Batched(Box::new([ + .on_update(move |_| Message::Batched { + messages: Box::new([ NodeGraphMessage::SetInputValue { node_id, input_index: IndividualCornerRadiiInput::INDEX, @@ -1340,7 +1340,7 @@ pub(crate) fn rectangle_properties(node_id: NodeId, context: &mut NodeProperties value: TaggedValue::F64Array4(individual_val), } .into(), - ])) + ]), }) .on_commit(commit_value); let radio_input = RadioInput::new(vec![uniform, individual]).selected_index(Some(is_individual as u32)).widget_holder(); @@ -1539,8 +1539,8 @@ pub(crate) fn fill_properties(node_id: NodeId, context: &mut NodePropertiesConte widgets_first_row.push( ColorInput::default() .value(fill.clone().into()) - .on_update(move |x: &ColorInput| { - Message::Batched(Box::new([ + .on_update(move |x: &ColorInput| Message::Batched { + messages: Box::new([ match &fill2 { Fill::None => NodeGraphMessage::SetInputValue { node_id, @@ -1567,7 +1567,7 @@ pub(crate) fn fill_properties(node_id: NodeId, context: &mut NodePropertiesConte value: TaggedValue::Fill(x.value.to_fill(fill2.as_gradient())), } .into(), - ])) + ]), }) .on_commit(commit_value) .widget_holder(), diff --git a/editor/src/messages/portfolio/document/overlays/mod.rs b/editor/src/messages/portfolio/document/overlays/mod.rs index 2102b3a32..253bec496 100644 --- a/editor/src/messages/portfolio/document/overlays/mod.rs +++ b/editor/src/messages/portfolio/document/overlays/mod.rs @@ -7,4 +7,4 @@ pub mod utility_types; #[doc(inline)] pub use overlays_message::{OverlaysMessage, OverlaysMessageDiscriminant}; #[doc(inline)] -pub use overlays_message_handler::{OverlaysMessageData, OverlaysMessageHandler}; +pub use overlays_message_handler::{OverlaysMessageContext, OverlaysMessageHandler}; 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 d4bb518e6..a70a3bfa5 100644 --- a/editor/src/messages/portfolio/document/overlays/overlays_message_handler.rs +++ b/editor/src/messages/portfolio/document/overlays/overlays_message_handler.rs @@ -2,7 +2,7 @@ use super::utility_types::{OverlayProvider, OverlaysVisibilitySettings}; use crate::messages::prelude::*; #[derive(ExtractField)] -pub struct OverlaysMessageData<'a> { +pub struct OverlaysMessageContext<'a> { pub visibility_settings: OverlaysVisibilitySettings, pub ipp: &'a InputPreprocessorMessageHandler, pub device_pixel_ratio: f64, @@ -18,9 +18,11 @@ pub struct OverlaysMessageHandler { } #[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; +impl MessageHandler> for OverlaysMessageHandler { + fn process_message(&mut self, message: OverlaysMessage, responses: &mut VecDeque, context: OverlaysMessageContext) { + let OverlaysMessageContext { visibility_settings, ipp, .. } = context; + #[cfg(target_arch = "wasm32")] + let device_pixel_ratio = context.device_pixel_ratio; match message { #[cfg(target_arch = "wasm32")] @@ -30,8 +32,6 @@ impl MessageHandler> for OverlaysMessag use glam::{DAffine2, DVec2}; use wasm_bindgen::JsCast; - let device_pixel_ratio = data.device_pixel_ratio; - let canvas = match &self.canvas { Some(canvas) => canvas, None => { @@ -40,28 +40,28 @@ impl MessageHandler> for OverlaysMessag } }; - let context = self.context.get_or_insert_with(|| { - let context = canvas.get_context("2d").ok().flatten().expect("Failed to get canvas context"); - context.dyn_into().expect("Context should be a canvas 2d context") + let canvas_context = self.context.get_or_insert_with(|| { + let canvas_context = canvas.get_context("2d").ok().flatten().expect("Failed to get canvas context"); + canvas_context.dyn_into().expect("Context should be a canvas 2d context") }); let size = ipp.viewport_bounds.size().as_uvec2(); let [a, b, c, d, e, f] = DAffine2::from_scale(DVec2::splat(device_pixel_ratio)).to_cols_array(); - let _ = context.set_transform(a, b, c, d, e, f); - context.clear_rect(0., 0., ipp.viewport_bounds.size().x, ipp.viewport_bounds.size().y); - let _ = context.reset_transform(); + let _ = canvas_context.set_transform(a, b, c, d, e, f); + canvas_context.clear_rect(0., 0., ipp.viewport_bounds.size().x, ipp.viewport_bounds.size().y); + let _ = canvas_context.reset_transform(); if visibility_settings.all() { responses.add(DocumentMessage::GridOverlays(OverlayContext { - render_context: context.clone(), + render_context: canvas_context.clone(), size: size.as_dvec2(), device_pixel_ratio, visibility_settings: visibility_settings.clone(), })); for provider in &self.overlay_providers { responses.add(provider(OverlayContext { - render_context: context.clone(), + render_context: canvas_context.clone(), size: size.as_dvec2(), device_pixel_ratio, visibility_settings: visibility_settings.clone(), diff --git a/editor/src/messages/portfolio/document/properties_panel/mod.rs b/editor/src/messages/portfolio/document/properties_panel/mod.rs index 7508ecada..cc8c29167 100644 --- a/editor/src/messages/portfolio/document/properties_panel/mod.rs +++ b/editor/src/messages/portfolio/document/properties_panel/mod.rs @@ -1,7 +1,5 @@ mod properties_panel_message; -mod properties_panel_message_handler; - -pub mod utility_types; +pub mod properties_panel_message_handler; #[doc(inline)] pub use properties_panel_message::{PropertiesPanelMessage, PropertiesPanelMessageDiscriminant}; 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 3ca9f350f..4c659f754 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 @@ -1,21 +1,34 @@ -use super::utility_types::PropertiesPanelMessageHandlerData; +use graphene_std::uuid::NodeId; + use crate::messages::layout::utility_types::widget_prelude::*; use crate::messages::portfolio::document::node_graph::document_node_definitions::NodePropertiesContext; +use crate::messages::portfolio::document::utility_types::network_interface::NodeNetworkInterface; use crate::messages::portfolio::utility_types::PersistentData; use crate::messages::prelude::*; +use crate::node_graph_executor::NodeGraphExecutor; + +#[derive(ExtractField)] +pub struct PropertiesPanelMessageContext<'a> { + pub network_interface: &'a mut NodeNetworkInterface, + pub selection_network_path: &'a [NodeId], + pub document_name: &'a str, + pub executor: &'a mut NodeGraphExecutor, + pub persistent_data: &'a PersistentData, +} #[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 { +impl MessageHandler> for PropertiesPanelMessageHandler { + fn process_message(&mut self, message: PropertiesPanelMessage, responses: &mut VecDeque, context: PropertiesPanelMessageContext) { + let PropertiesPanelMessageContext { network_interface, selection_network_path, document_name, executor, - } = data; + persistent_data, + } = context; match message { PropertiesPanelMessage::Clear => { @@ -25,7 +38,7 @@ impl MessageHandler { - let mut context = NodePropertiesContext { + let mut node_properties_context = NodePropertiesContext { persistent_data, responses, network_interface, @@ -33,9 +46,9 @@ impl MessageHandler { - pub network_interface: &'a mut NodeNetworkInterface, - pub selection_network_path: &'a [NodeId], - pub document_name: &'a str, - pub executor: &'a mut NodeGraphExecutor, -} 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 26a9a739f..50fb7b8ec 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 @@ -23,7 +23,7 @@ pub struct MenuBarMessageHandler { #[message_handler_data] impl MessageHandler for MenuBarMessageHandler { - fn process_message(&mut self, message: MenuBarMessage, responses: &mut VecDeque, _data: ()) { + fn process_message(&mut self, message: MenuBarMessage, responses: &mut VecDeque, _: ()) { match message { MenuBarMessage::SendLayout => self.send_layout(responses, LayoutTarget::MenuBar), } diff --git a/editor/src/messages/portfolio/mod.rs b/editor/src/messages/portfolio/mod.rs index 2203ac940..364dcdfdd 100644 --- a/editor/src/messages/portfolio/mod.rs +++ b/editor/src/messages/portfolio/mod.rs @@ -10,4 +10,4 @@ pub mod utility_types; #[doc(inline)] pub use portfolio_message::{PortfolioMessage, PortfolioMessageDiscriminant}; #[doc(inline)] -pub use portfolio_message_handler::{PortfolioMessageData, PortfolioMessageHandler}; +pub use portfolio_message_handler::{PortfolioMessageContext, PortfolioMessageHandler}; diff --git a/editor/src/messages/portfolio/portfolio_message.rs b/editor/src/messages/portfolio/portfolio_message.rs index cb2fa34e3..4fb756fc4 100644 --- a/editor/src/messages/portfolio/portfolio_message.rs +++ b/editor/src/messages/portfolio/portfolio_message.rs @@ -19,6 +19,7 @@ pub enum PortfolioMessage { Spreadsheet(SpreadsheetMessage), // Messages + Init, DocumentPassMessage { document_id: DocumentId, message: DocumentMessage, diff --git a/editor/src/messages/portfolio/portfolio_message_handler.rs b/editor/src/messages/portfolio/portfolio_message_handler.rs index 195768bd4..daa81274a 100644 --- a/editor/src/messages/portfolio/portfolio_message_handler.rs +++ b/editor/src/messages/portfolio/portfolio_message_handler.rs @@ -9,8 +9,9 @@ use crate::messages::debug::utility_types::MessageLoggingVerbosity; use crate::messages::dialog::simple_dialogs; use crate::messages::frontend::utility_types::FrontendDocumentDetails; use crate::messages::layout::utility_types::widget_prelude::*; -use crate::messages::portfolio::document::DocumentMessageData; +use crate::messages::portfolio::document::DocumentMessageContext; use crate::messages::portfolio::document::graph_operation::utility_types::TransformIn; +use crate::messages::portfolio::document::node_graph::document_node_definitions; use crate::messages::portfolio::document::utility_types::clipboards::{Clipboard, CopyBufferEntry, INTERNAL_CLIPBOARD_COUNT}; use crate::messages::portfolio::document::utility_types::network_interface::OutputConnector; use crate::messages::portfolio::document::utility_types::nodes::SelectedNodes; @@ -26,7 +27,7 @@ use graphene_std::text::Font; use std::vec; #[derive(ExtractField)] -pub struct PortfolioMessageData<'a> { +pub struct PortfolioMessageContext<'a> { pub ipp: &'a InputPreprocessorMessageHandler, pub preferences: &'a PreferencesMessageHandler, pub current_tool: &'a ToolType, @@ -54,9 +55,9 @@ pub struct PortfolioMessageHandler { } #[message_handler_data] -impl MessageHandler> for PortfolioMessageHandler { - fn process_message(&mut self, message: PortfolioMessage, responses: &mut VecDeque, data: PortfolioMessageData) { - let PortfolioMessageData { +impl MessageHandler> for PortfolioMessageHandler { + fn process_message(&mut self, message: PortfolioMessage, responses: &mut VecDeque, context: PortfolioMessageContext) { + let PortfolioMessageContext { ipp, preferences, current_tool, @@ -64,7 +65,7 @@ impl MessageHandler> for PortfolioMes reset_node_definitions_on_open, timing_information, animation, - } = data; + } = context; match message { // Sub-messages @@ -104,7 +105,7 @@ impl MessageHandler> for PortfolioMes PortfolioMessage::Document(message) => { if let Some(document_id) = self.active_document_id { if let Some(document) = self.documents.get_mut(&document_id) { - let document_inputs = DocumentMessageData { + let document_inputs = DocumentMessageContext { document_id, ipp, persistent_data: &self.persistent_data, @@ -119,9 +120,26 @@ impl MessageHandler> for PortfolioMes } // Messages + PortfolioMessage::Init => { + // Load persistent data from the browser database + responses.add(FrontendMessage::TriggerLoadFirstAutoSaveDocument); + responses.add(FrontendMessage::TriggerLoadPreferences); + + // Display the menu bar at the top of the window + responses.add(MenuBarMessage::SendLayout); + + // Send the information for tooltips and categories for each node/input. + responses.add(FrontendMessage::SendUIMetadata { + node_descriptions: document_node_definitions::collect_node_descriptions(), + node_types: document_node_definitions::collect_node_types(), + }); + + // Finish loading persistent data from the browser database + responses.add(FrontendMessage::TriggerLoadRestAutoSaveDocuments); + } PortfolioMessage::DocumentPassMessage { document_id, message } => { if let Some(document) = self.documents.get_mut(&document_id) { - let document_inputs = DocumentMessageData { + let document_inputs = DocumentMessageContext { document_id, ipp, persistent_data: &self.persistent_data, @@ -972,7 +990,9 @@ impl PortfolioMessageHandler { /text>"# // It's a mystery why the `/text>` tag above needs to be missing its `<`, but when it exists it prints the `<` character in the text. However this works with it removed. .to_string(); - responses.add(Message::EndBuffer(graphene_std::renderer::RenderMetadata::default())); + responses.add(Message::EndBuffer { + render_metadata: graphene_std::renderer::RenderMetadata::default(), + }); responses.add(FrontendMessage::UpdateDocumentArtwork { svg: error }); } result diff --git a/editor/src/messages/portfolio/spreadsheet/spreadsheet_message_handler.rs b/editor/src/messages/portfolio/spreadsheet/spreadsheet_message_handler.rs index 72c44c597..52e8a7679 100644 --- a/editor/src/messages/portfolio/spreadsheet/spreadsheet_message_handler.rs +++ b/editor/src/messages/portfolio/spreadsheet/spreadsheet_message_handler.rs @@ -27,7 +27,7 @@ pub struct SpreadsheetMessageHandler { #[message_handler_data] impl MessageHandler for SpreadsheetMessageHandler { - fn process_message(&mut self, message: SpreadsheetMessage, responses: &mut VecDeque, _data: ()) { + fn process_message(&mut self, message: SpreadsheetMessage, responses: &mut VecDeque, _: ()) { match message { SpreadsheetMessage::ToggleOpen => { self.spreadsheet_view_open = !self.spreadsheet_view_open; diff --git a/editor/src/messages/preferences/preferences_message_handler.rs b/editor/src/messages/preferences/preferences_message_handler.rs index 1e52233fe..a79ad379b 100644 --- a/editor/src/messages/preferences/preferences_message_handler.rs +++ b/editor/src/messages/preferences/preferences_message_handler.rs @@ -46,7 +46,7 @@ impl Default for PreferencesMessageHandler { #[message_handler_data] impl MessageHandler for PreferencesMessageHandler { - fn process_message(&mut self, message: PreferencesMessage, responses: &mut VecDeque, _data: ()) { + fn process_message(&mut self, message: PreferencesMessage, responses: &mut VecDeque, _: ()) { match message { // Management messages PreferencesMessage::Load { preferences } => { diff --git a/editor/src/messages/prelude.rs b/editor/src/messages/prelude.rs index 7517c80f4..72a6cb9ba 100644 --- a/editor/src/messages/prelude.rs +++ b/editor/src/messages/prelude.rs @@ -5,28 +5,28 @@ pub use crate::utility_types::{DebugMessageTree, MessageData}; pub use crate::messages::animation::{AnimationMessage, AnimationMessageDiscriminant, AnimationMessageHandler}; pub use crate::messages::broadcast::{BroadcastMessage, BroadcastMessageDiscriminant, BroadcastMessageHandler}; pub use crate::messages::debug::{DebugMessage, DebugMessageDiscriminant, DebugMessageHandler}; -pub use crate::messages::dialog::export_dialog::{ExportDialogMessage, ExportDialogMessageData, ExportDialogMessageDiscriminant, ExportDialogMessageHandler}; +pub use crate::messages::dialog::export_dialog::{ExportDialogMessage, ExportDialogMessageContext, ExportDialogMessageDiscriminant, ExportDialogMessageHandler}; pub use crate::messages::dialog::new_document_dialog::{NewDocumentDialogMessage, NewDocumentDialogMessageDiscriminant, NewDocumentDialogMessageHandler}; -pub use crate::messages::dialog::preferences_dialog::{PreferencesDialogMessage, PreferencesDialogMessageData, PreferencesDialogMessageDiscriminant, PreferencesDialogMessageHandler}; -pub use crate::messages::dialog::{DialogMessage, DialogMessageData, DialogMessageDiscriminant, DialogMessageHandler}; +pub use crate::messages::dialog::preferences_dialog::{PreferencesDialogMessage, PreferencesDialogMessageContext, PreferencesDialogMessageDiscriminant, PreferencesDialogMessageHandler}; +pub use crate::messages::dialog::{DialogMessage, DialogMessageContext, DialogMessageDiscriminant, DialogMessageHandler}; pub use crate::messages::frontend::{FrontendMessage, FrontendMessageDiscriminant}; pub use crate::messages::globals::{GlobalsMessage, GlobalsMessageDiscriminant, GlobalsMessageHandler}; -pub use crate::messages::input_mapper::key_mapping::{KeyMappingMessage, KeyMappingMessageData, KeyMappingMessageDiscriminant, KeyMappingMessageHandler}; -pub use crate::messages::input_mapper::{InputMapperMessage, InputMapperMessageData, InputMapperMessageDiscriminant, InputMapperMessageHandler}; -pub use crate::messages::input_preprocessor::{InputPreprocessorMessage, InputPreprocessorMessageData, InputPreprocessorMessageDiscriminant, InputPreprocessorMessageHandler}; +pub use crate::messages::input_mapper::key_mapping::{KeyMappingMessage, KeyMappingMessageContext, KeyMappingMessageDiscriminant, KeyMappingMessageHandler}; +pub use crate::messages::input_mapper::{InputMapperMessage, InputMapperMessageContext, InputMapperMessageDiscriminant, InputMapperMessageHandler}; +pub use crate::messages::input_preprocessor::{InputPreprocessorMessage, InputPreprocessorMessageContext, InputPreprocessorMessageDiscriminant, InputPreprocessorMessageHandler}; pub use crate::messages::layout::{LayoutMessage, LayoutMessageDiscriminant, LayoutMessageHandler}; -pub use crate::messages::portfolio::document::graph_operation::{GraphOperationMessage, GraphOperationMessageData, GraphOperationMessageDiscriminant, GraphOperationMessageHandler}; -pub use crate::messages::portfolio::document::navigation::{NavigationMessage, NavigationMessageData, NavigationMessageDiscriminant, NavigationMessageHandler}; +pub use crate::messages::portfolio::document::graph_operation::{GraphOperationMessage, GraphOperationMessageContext, GraphOperationMessageDiscriminant, GraphOperationMessageHandler}; +pub use crate::messages::portfolio::document::navigation::{NavigationMessage, NavigationMessageContext, NavigationMessageDiscriminant, NavigationMessageHandler}; pub use crate::messages::portfolio::document::node_graph::{NodeGraphMessage, NodeGraphMessageDiscriminant, NodeGraphMessageHandler}; -pub use crate::messages::portfolio::document::overlays::{OverlaysMessage, OverlaysMessageData, OverlaysMessageDiscriminant, OverlaysMessageHandler}; +pub use crate::messages::portfolio::document::overlays::{OverlaysMessage, OverlaysMessageContext, OverlaysMessageDiscriminant, OverlaysMessageHandler}; pub use crate::messages::portfolio::document::properties_panel::{PropertiesPanelMessage, PropertiesPanelMessageDiscriminant, PropertiesPanelMessageHandler}; -pub use crate::messages::portfolio::document::{DocumentMessage, DocumentMessageData, DocumentMessageDiscriminant, DocumentMessageHandler}; +pub use crate::messages::portfolio::document::{DocumentMessage, DocumentMessageContext, DocumentMessageDiscriminant, DocumentMessageHandler}; pub use crate::messages::portfolio::menu_bar::{MenuBarMessage, MenuBarMessageDiscriminant, MenuBarMessageHandler}; pub use crate::messages::portfolio::spreadsheet::{SpreadsheetMessage, SpreadsheetMessageDiscriminant}; -pub use crate::messages::portfolio::{PortfolioMessage, PortfolioMessageData, PortfolioMessageDiscriminant, PortfolioMessageHandler}; +pub use crate::messages::portfolio::{PortfolioMessage, PortfolioMessageContext, PortfolioMessageDiscriminant, PortfolioMessageHandler}; pub use crate::messages::preferences::{PreferencesMessage, PreferencesMessageDiscriminant, PreferencesMessageHandler}; pub use crate::messages::tool::transform_layer::{TransformLayerMessage, TransformLayerMessageDiscriminant, TransformLayerMessageHandler}; -pub use crate::messages::tool::{ToolMessage, ToolMessageData, ToolMessageDiscriminant, ToolMessageHandler}; +pub use crate::messages::tool::{ToolMessage, ToolMessageContext, ToolMessageDiscriminant, ToolMessageHandler}; pub use crate::messages::workspace::{WorkspaceMessage, WorkspaceMessageDiscriminant, WorkspaceMessageHandler}; // Message, MessageDiscriminant diff --git a/editor/src/messages/tool/mod.rs b/editor/src/messages/tool/mod.rs index ca03f01e8..e0f9be3ee 100644 --- a/editor/src/messages/tool/mod.rs +++ b/editor/src/messages/tool/mod.rs @@ -9,6 +9,6 @@ pub mod utility_types; #[doc(inline)] pub use tool_message::{ToolMessage, ToolMessageDiscriminant}; #[doc(inline)] -pub use tool_message_handler::{ToolMessageData, ToolMessageHandler}; +pub use tool_message_handler::{ToolMessageContext, ToolMessageHandler}; #[doc(inline)] pub use transform_layer::{TransformLayerMessage, TransformLayerMessageDiscriminant}; diff --git a/editor/src/messages/tool/tool_message_handler.rs b/editor/src/messages/tool/tool_message_handler.rs index 6000301a1..df1d7faaf 100644 --- a/editor/src/messages/tool/tool_message_handler.rs +++ b/editor/src/messages/tool/tool_message_handler.rs @@ -1,19 +1,20 @@ use super::common_functionality::shape_editor::ShapeState; use super::common_functionality::shapes::shape_utility::ShapeType::{self, Ellipse, Line, Rectangle}; -use super::utility_types::{ToolActionHandlerData, ToolFsmState, tool_message_to_tool_type}; +use super::utility_types::{ToolActionMessageContext, ToolFsmState, tool_message_to_tool_type}; use crate::application::generate_uuid; use crate::messages::layout::utility_types::widget_prelude::*; use crate::messages::portfolio::document::overlays::utility_types::OverlayProvider; use crate::messages::portfolio::utility_types::PersistentData; use crate::messages::prelude::*; +use crate::messages::tool::transform_layer::transform_layer_message_handler::TransformLayerMessageContext; use crate::messages::tool::utility_types::ToolType; use crate::node_graph_executor::NodeGraphExecutor; use graphene_std::raster::color::Color; -const ARTBOARD_OVERLAY_PROVIDER: OverlayProvider = |context| DocumentMessage::DrawArtboardOverlays(context).into(); +const ARTBOARD_OVERLAY_PROVIDER: OverlayProvider = |overlay_context| DocumentMessage::DrawArtboardOverlays(overlay_context).into(); #[derive(ExtractField)] -pub struct ToolMessageData<'a> { +pub struct ToolMessageContext<'a> { pub document_id: DocumentId, pub document: &'a mut DocumentMessageHandler, pub input: &'a InputPreprocessorMessageHandler, @@ -31,23 +32,30 @@ pub struct ToolMessageHandler { } #[message_handler_data] -impl MessageHandler> for ToolMessageHandler { - fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque, data: ToolMessageData) { - let ToolMessageData { +impl MessageHandler> for ToolMessageHandler { + fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque, context: ToolMessageContext) { + let ToolMessageContext { document_id, document, input, persistent_data, node_graph, preferences, - } = data; + } = context; let font_cache = &persistent_data.font_cache; match message { // Messages - ToolMessage::TransformLayer(message) => self - .transform_layer_handler - .process_message(message, responses, (document, input, &self.tool_state.tool_data, &mut self.shape_editor)), + ToolMessage::TransformLayer(message) => self.transform_layer_handler.process_message( + message, + responses, + TransformLayerMessageContext { + document, + input, + tool_data: &self.tool_state.tool_data, + shape_editor: &mut self.shape_editor, + }, + ), ToolMessage::ActivateToolSelect => responses.add_front(ToolMessage::ActivateTool { tool_type: ToolType::Select }), ToolMessage::ActivateToolArtboard => responses.add_front(ToolMessage::ActivateTool { tool_type: ToolType::Artboard }), @@ -106,7 +114,7 @@ impl MessageHandler> for ToolMessageHandler { // Send the old and new tools a transition to their FSM Abort states let mut send_abort_to_tool = |old_tool: ToolType, new_tool: ToolType, update_hints_and_cursor: bool| { if let Some(tool) = tool_data.tools.get_mut(&new_tool) { - let mut data = ToolActionHandlerData { + let mut data = ToolActionMessageContext { document, document_id, global_tool_data: &self.tool_state.document_tool_data, @@ -206,7 +214,7 @@ impl MessageHandler> for ToolMessageHandler { // Notify the frontend about the initial working colors document_data.update_working_colors(responses); - let mut data = ToolActionHandlerData { + let mut data = ToolActionMessageContext { document, document_id, global_tool_data: &self.tool_state.document_tool_data, @@ -302,7 +310,7 @@ impl MessageHandler> for ToolMessageHandler { let graph_view_overlay_open = document.graph_view_overlay_open(); if tool_type == tool_data.active_tool_type { - let mut data = ToolActionHandlerData { + let mut data = ToolActionMessageContext { document, document_id, global_tool_data: &self.tool_state.document_tool_data, diff --git a/editor/src/messages/tool/tool_messages/artboard_tool.rs b/editor/src/messages/tool/tool_messages/artboard_tool.rs index e14058c32..810cb4636 100644 --- a/editor/src/messages/tool/tool_messages/artboard_tool.rs +++ b/editor/src/messages/tool/tool_messages/artboard_tool.rs @@ -49,9 +49,9 @@ 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); +impl<'a> MessageHandler> for ArtboardTool { + fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque, context: &mut ToolActionMessageContext<'a>) { + self.fsm_state.process_event(message, &mut self.data, context, &(), responses, false); } fn actions(&self) -> ActionList { @@ -218,8 +218,8 @@ impl Fsm for ArtboardToolFsmState { type ToolData = ArtboardToolData; type ToolOptions = (); - fn transition(self, event: ToolMessage, tool_data: &mut Self::ToolData, tool_action_data: &mut ToolActionHandlerData, _tool_options: &(), responses: &mut VecDeque) -> Self { - let ToolActionHandlerData { document, input, .. } = tool_action_data; + fn transition(self, event: ToolMessage, tool_data: &mut Self::ToolData, tool_action_data: &mut ToolActionMessageContext, _tool_options: &(), responses: &mut VecDeque) -> Self { + let ToolActionMessageContext { document, input, .. } = tool_action_data; let hovered = ArtboardToolData::hovered_artboard(document, input).is_some(); diff --git a/editor/src/messages/tool/tool_messages/brush_tool.rs b/editor/src/messages/tool/tool_messages/brush_tool.rs index a625bdb9e..202d026e0 100644 --- a/editor/src/messages/tool/tool_messages/brush_tool.rs +++ b/editor/src/messages/tool/tool_messages/brush_tool.rs @@ -186,10 +186,10 @@ 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>) { +impl<'a> MessageHandler> for BrushTool { + fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque, context: &mut ToolActionMessageContext<'a>) { let ToolMessage::Brush(BrushToolMessage::UpdateOptions(action)) = message else { - self.fsm_state.process_event(message, &mut self.data, tool_data, &self.options, responses, true); + self.fsm_state.process_event(message, &mut self.data, context, &self.options, responses, true); return; }; match action { @@ -306,8 +306,15 @@ impl Fsm for BrushToolFsmState { type ToolData = BrushToolData; type ToolOptions = BrushOptions; - fn transition(self, event: ToolMessage, tool_data: &mut Self::ToolData, tool_action_data: &mut ToolActionHandlerData, tool_options: &Self::ToolOptions, responses: &mut VecDeque) -> Self { - let ToolActionHandlerData { + fn transition( + self, + event: ToolMessage, + tool_data: &mut Self::ToolData, + tool_action_data: &mut ToolActionMessageContext, + tool_options: &Self::ToolOptions, + responses: &mut VecDeque, + ) -> Self { + let ToolActionMessageContext { document, global_tool_data, input, .. } = tool_action_data; diff --git a/editor/src/messages/tool/tool_messages/eyedropper_tool.rs b/editor/src/messages/tool/tool_messages/eyedropper_tool.rs index ce4b09d8e..d5a082cc2 100644 --- a/editor/src/messages/tool/tool_messages/eyedropper_tool.rs +++ b/editor/src/messages/tool/tool_messages/eyedropper_tool.rs @@ -40,9 +40,9 @@ 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); +impl<'a> MessageHandler> for EyedropperTool { + fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque, context: &mut ToolActionMessageContext<'a>) { + self.fsm_state.process_event(message, &mut self.data, context, &(), responses, true); } advertise_actions!(EyedropperToolMessageDiscriminant; @@ -80,8 +80,8 @@ impl Fsm for EyedropperToolFsmState { type ToolData = EyedropperToolData; type ToolOptions = (); - fn transition(self, event: ToolMessage, _tool_data: &mut Self::ToolData, tool_action_data: &mut ToolActionHandlerData, _tool_options: &(), responses: &mut VecDeque) -> Self { - let ToolActionHandlerData { global_tool_data, input, .. } = tool_action_data; + fn transition(self, event: ToolMessage, _tool_data: &mut Self::ToolData, tool_action_data: &mut ToolActionMessageContext, _tool_options: &(), responses: &mut VecDeque) -> Self { + let ToolActionMessageContext { global_tool_data, input, .. } = tool_action_data; let ToolMessage::Eyedropper(event) = event else { return self }; match (self, event) { diff --git a/editor/src/messages/tool/tool_messages/fill_tool.rs b/editor/src/messages/tool/tool_messages/fill_tool.rs index 94ad2b8c2..6a9429e3e 100644 --- a/editor/src/messages/tool/tool_messages/fill_tool.rs +++ b/editor/src/messages/tool/tool_messages/fill_tool.rs @@ -42,9 +42,9 @@ 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); +impl<'a> MessageHandler> for FillTool { + fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque, context: &mut ToolActionMessageContext<'a>) { + self.fsm_state.process_event(message, &mut (), context, &(), responses, true); } fn actions(&self) -> ActionList { match self.fsm_state { @@ -85,8 +85,15 @@ impl Fsm for FillToolFsmState { type ToolData = (); type ToolOptions = (); - fn transition(self, event: ToolMessage, _tool_data: &mut Self::ToolData, handler_data: &mut ToolActionHandlerData, _tool_options: &Self::ToolOptions, responses: &mut VecDeque) -> Self { - let ToolActionHandlerData { + fn transition( + self, + event: ToolMessage, + _tool_data: &mut Self::ToolData, + handler_data: &mut ToolActionMessageContext, + _tool_options: &Self::ToolOptions, + responses: &mut VecDeque, + ) -> Self { + let ToolActionMessageContext { document, global_tool_data, input, .. } = handler_data; diff --git a/editor/src/messages/tool/tool_messages/freehand_tool.rs b/editor/src/messages/tool/tool_messages/freehand_tool.rs index fb1539a81..8b0dbb73f 100644 --- a/editor/src/messages/tool/tool_messages/freehand_tool.rs +++ b/editor/src/messages/tool/tool_messages/freehand_tool.rs @@ -117,10 +117,10 @@ 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>) { +impl<'a> MessageHandler> for FreehandTool { + fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque, context: &mut ToolActionMessageContext<'a>) { let ToolMessage::Freehand(FreehandToolMessage::UpdateOptions(action)) = message else { - self.fsm_state.process_event(message, &mut self.data, tool_data, &self.options, responses, true); + self.fsm_state.process_event(message, &mut self.data, context, &self.options, responses, true); return; }; match action { @@ -184,8 +184,15 @@ impl Fsm for FreehandToolFsmState { type ToolData = FreehandToolData; type ToolOptions = FreehandOptions; - fn transition(self, event: ToolMessage, tool_data: &mut Self::ToolData, tool_action_data: &mut ToolActionHandlerData, tool_options: &Self::ToolOptions, responses: &mut VecDeque) -> Self { - let ToolActionHandlerData { + fn transition( + self, + event: ToolMessage, + tool_data: &mut Self::ToolData, + tool_action_data: &mut ToolActionMessageContext, + tool_options: &Self::ToolOptions, + responses: &mut VecDeque, + ) -> Self { + let ToolActionMessageContext { document, global_tool_data, input, diff --git a/editor/src/messages/tool/tool_messages/gradient_tool.rs b/editor/src/messages/tool/tool_messages/gradient_tool.rs index 0e84f9585..de6d5753d 100644 --- a/editor/src/messages/tool/tool_messages/gradient_tool.rs +++ b/editor/src/messages/tool/tool_messages/gradient_tool.rs @@ -54,10 +54,10 @@ 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>) { +impl<'a> MessageHandler> for GradientTool { + fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque, context: &mut ToolActionMessageContext<'a>) { let ToolMessage::Gradient(GradientToolMessage::UpdateOptions(action)) = message else { - self.fsm_state.process_event(message, &mut self.data, tool_data, &self.options, responses, false); + self.fsm_state.process_event(message, &mut self.data, context, &self.options, responses, false); return; }; match action { @@ -67,7 +67,7 @@ impl<'a> MessageHandler> for Gradien if let Some(selected_gradient) = &mut self.data.selected_gradient { // Check if the current layer is a raster layer if let Some(layer) = selected_gradient.layer { - if NodeGraphLayer::is_raster_layer(layer, &mut tool_data.document.network_interface) { + if NodeGraphLayer::is_raster_layer(layer, &mut context.document.network_interface) { return; // Don't proceed if it's a raster layer } selected_gradient.gradient.gradient_type = gradient_type; @@ -243,8 +243,15 @@ impl Fsm for GradientToolFsmState { type ToolData = GradientToolData; type ToolOptions = GradientOptions; - fn transition(self, event: ToolMessage, tool_data: &mut Self::ToolData, tool_action_data: &mut ToolActionHandlerData, tool_options: &Self::ToolOptions, responses: &mut VecDeque) -> Self { - let ToolActionHandlerData { + fn transition( + self, + event: ToolMessage, + tool_data: &mut Self::ToolData, + tool_action_data: &mut ToolActionMessageContext, + tool_options: &Self::ToolOptions, + responses: &mut VecDeque, + ) -> Self { + let ToolActionMessageContext { document, global_tool_data, input, .. } = tool_action_data; diff --git a/editor/src/messages/tool/tool_messages/mod.rs b/editor/src/messages/tool/tool_messages/mod.rs index b95d9c393..6d29ad81a 100644 --- a/editor/src/messages/tool/tool_messages/mod.rs +++ b/editor/src/messages/tool/tool_messages/mod.rs @@ -17,7 +17,7 @@ pub mod tool_prelude { pub use crate::messages::input_mapper::utility_types::input_keyboard::{Key, MouseMotion}; pub use crate::messages::layout::utility_types::widget_prelude::*; pub use crate::messages::prelude::*; - pub use crate::messages::tool::utility_types::{EventToMessageMap, Fsm, ToolActionHandlerData, ToolMetadata, ToolTransition, ToolType}; + pub use crate::messages::tool::utility_types::{EventToMessageMap, Fsm, ToolActionMessageContext, ToolMetadata, ToolTransition, ToolType}; pub use crate::messages::tool::utility_types::{HintData, HintGroup, HintInfo}; pub use glam::{DAffine2, DVec2}; } diff --git a/editor/src/messages/tool/tool_messages/navigate_tool.rs b/editor/src/messages/tool/tool_messages/navigate_tool.rs index 9f9ef0307..7bbec9d20 100644 --- a/editor/src/messages/tool/tool_messages/navigate_tool.rs +++ b/editor/src/messages/tool/tool_messages/navigate_tool.rs @@ -39,9 +39,9 @@ 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); +impl<'a> MessageHandler> for NavigateTool { + fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque, context: &mut ToolActionMessageContext<'a>) { + self.fsm_state.process_event(message, &mut self.tool_data, context, &(), responses, true); } fn actions(&self) -> ActionList { @@ -92,7 +92,7 @@ impl Fsm for NavigateToolFsmState { self, message: ToolMessage, tool_data: &mut Self::ToolData, - ToolActionHandlerData { input, .. }: &mut ToolActionHandlerData, + ToolActionMessageContext { input, .. }: &mut ToolActionMessageContext, _tool_options: &Self::ToolOptions, responses: &mut VecDeque, ) -> Self { diff --git a/editor/src/messages/tool/tool_messages/path_tool.rs b/editor/src/messages/tool/tool_messages/path_tool.rs index 8701ca91c..b8683626d 100644 --- a/editor/src/messages/tool/tool_messages/path_tool.rs +++ b/editor/src/messages/tool/tool_messages/path_tool.rs @@ -306,8 +306,8 @@ 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>) { +impl<'a> MessageHandler> for PathTool { + fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque, context: &mut ToolActionMessageContext<'a>) { let updating_point = message == ToolMessage::Path(PathToolMessage::SelectedPointUpdated); match message { @@ -350,20 +350,20 @@ impl<'a> MessageHandler> for PathToo }, ToolMessage::Path(PathToolMessage::ClosePath) => { responses.add(DocumentMessage::AddTransaction); - tool_data.shape_editor.close_selected_path(tool_data.document, responses); + context.shape_editor.close_selected_path(context.document, responses); responses.add(DocumentMessage::EndTransaction); responses.add(OverlaysMessage::Draw); } ToolMessage::Path(PathToolMessage::SwapSelectedHandles) => { - if tool_data.shape_editor.handle_with_pair_selected(&tool_data.document.network_interface) { - tool_data.shape_editor.alternate_selected_handles(&tool_data.document.network_interface); + if context.shape_editor.handle_with_pair_selected(&context.document.network_interface) { + context.shape_editor.alternate_selected_handles(&context.document.network_interface); responses.add(PathToolMessage::SelectedPointUpdated); responses.add(FrontendMessage::UpdateMouseCursor { cursor: MouseCursorIcon::None }); responses.add(OverlaysMessage::Draw); } } _ => { - self.fsm_state.process_event(message, &mut self.tool_data, tool_data, &self.options, responses, true); + self.fsm_state.process_event(message, &mut self.tool_data, context, &self.options, responses, true); } } @@ -1387,8 +1387,15 @@ impl Fsm for PathToolFsmState { type ToolData = PathToolData; type ToolOptions = PathToolOptions; - fn transition(self, event: ToolMessage, tool_data: &mut Self::ToolData, tool_action_data: &mut ToolActionHandlerData, tool_options: &Self::ToolOptions, responses: &mut VecDeque) -> Self { - let ToolActionHandlerData { document, input, shape_editor, .. } = tool_action_data; + fn transition( + self, + event: ToolMessage, + tool_data: &mut Self::ToolData, + tool_action_data: &mut ToolActionMessageContext, + tool_options: &Self::ToolOptions, + responses: &mut VecDeque, + ) -> Self { + let ToolActionMessageContext { document, input, shape_editor, .. } = tool_action_data; update_dynamic_hints(self, responses, shape_editor, document, tool_data, tool_options); diff --git a/editor/src/messages/tool/tool_messages/pen_tool.rs b/editor/src/messages/tool/tool_messages/pen_tool.rs index 53a9e4d17..215c90bdc 100644 --- a/editor/src/messages/tool/tool_messages/pen_tool.rs +++ b/editor/src/messages/tool/tool_messages/pen_tool.rs @@ -187,10 +187,10 @@ 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>) { +impl<'a> MessageHandler> for PenTool { + fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque, context: &mut ToolActionMessageContext<'a>) { let ToolMessage::Pen(PenToolMessage::UpdateOptions(action)) = message else { - self.fsm_state.process_event(message, &mut self.tool_data, tool_data, &self.options, responses, true); + self.fsm_state.process_event(message, &mut self.tool_data, context, &self.options, responses, true); return; }; @@ -1403,8 +1403,15 @@ impl Fsm for PenToolFsmState { type ToolData = PenToolData; type ToolOptions = PenOptions; - fn transition(self, event: ToolMessage, tool_data: &mut Self::ToolData, tool_action_data: &mut ToolActionHandlerData, tool_options: &Self::ToolOptions, responses: &mut VecDeque) -> Self { - let ToolActionHandlerData { + fn transition( + self, + event: ToolMessage, + tool_data: &mut Self::ToolData, + tool_action_data: &mut ToolActionMessageContext, + tool_options: &Self::ToolOptions, + responses: &mut VecDeque, + ) -> Self { + let ToolActionMessageContext { document, global_tool_data, input, diff --git a/editor/src/messages/tool/tool_messages/select_tool.rs b/editor/src/messages/tool/tool_messages/select_tool.rs index e6f4a5d7a..f792ee2bb 100644 --- a/editor/src/messages/tool/tool_messages/select_tool.rs +++ b/editor/src/messages/tool/tool_messages/select_tool.rs @@ -273,8 +273,8 @@ 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>) { +impl<'a> MessageHandler> for SelectTool { + fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque, context: &mut ToolActionMessageContext<'a>) { let mut redraw_reference_pivot = false; if let ToolMessage::Select(SelectToolMessage::SelectOptions(ref option_update)) = message { @@ -309,7 +309,7 @@ impl<'a> MessageHandler> for SelectT } } - self.fsm_state.process_event(message, &mut self.tool_data, tool_data, &(), responses, false); + self.fsm_state.process_event(message, &mut self.tool_data, context, &(), responses, false); if self.tool_data.pivot_gizmo.pivot.should_refresh_pivot_position() || self.tool_data.selected_layers_changed || redraw_reference_pivot { // Send the layout containing the updated pivot position (a bit ugly to do it here not in the fsm but that doesn't have SelectTool) @@ -584,8 +584,8 @@ impl Fsm for SelectToolFsmState { type ToolData = SelectToolData; type ToolOptions = (); - fn transition(self, event: ToolMessage, tool_data: &mut Self::ToolData, tool_action_data: &mut ToolActionHandlerData, _tool_options: &(), responses: &mut VecDeque) -> Self { - let ToolActionHandlerData { document, input, font_cache, .. } = tool_action_data; + fn transition(self, event: ToolMessage, tool_data: &mut Self::ToolData, tool_action_data: &mut ToolActionMessageContext, _tool_options: &(), responses: &mut VecDeque) -> Self { + let ToolActionMessageContext { document, input, font_cache, .. } = tool_action_data; let ToolMessage::Select(event) = event else { return self }; match (self, event) { diff --git a/editor/src/messages/tool/tool_messages/shape_tool.rs b/editor/src/messages/tool/tool_messages/shape_tool.rs index e4ba44b04..0f43adf28 100644 --- a/editor/src/messages/tool/tool_messages/shape_tool.rs +++ b/editor/src/messages/tool/tool_messages/shape_tool.rs @@ -163,10 +163,10 @@ impl LayoutHolder for ShapeTool { } } -impl<'a> MessageHandler> for ShapeTool { - fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque, tool_data: &mut ToolActionHandlerData<'a>) { +impl<'a> MessageHandler> for ShapeTool { + fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque, context: &mut ToolActionMessageContext<'a>) { let ToolMessage::Shape(ShapeToolMessage::UpdateOptions(action)) = message else { - self.fsm_state.process_event(message, &mut self.tool_data, tool_data, &self.options, responses, true); + self.fsm_state.process_event(message, &mut self.tool_data, context, &self.options, responses, true); return; }; match action { @@ -337,14 +337,14 @@ impl Fsm for ShapeToolFsmState { self, event: ToolMessage, tool_data: &mut Self::ToolData, - ToolActionHandlerData { + ToolActionMessageContext { document, global_tool_data, input, preferences, shape_editor, .. - }: &mut ToolActionHandlerData, + }: &mut ToolActionMessageContext, tool_options: &Self::ToolOptions, responses: &mut VecDeque, ) -> Self { diff --git a/editor/src/messages/tool/tool_messages/spline_tool.rs b/editor/src/messages/tool/tool_messages/spline_tool.rs index 2de6747f0..0a96443ee 100644 --- a/editor/src/messages/tool/tool_messages/spline_tool.rs +++ b/editor/src/messages/tool/tool_messages/spline_tool.rs @@ -124,10 +124,10 @@ 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>) { +impl<'a> MessageHandler> for SplineTool { + fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque, context: &mut ToolActionMessageContext<'a>) { let ToolMessage::Spline(SplineToolMessage::UpdateOptions(action)) = message else { - self.fsm_state.process_event(message, &mut self.tool_data, tool_data, &self.options, responses, true); + self.fsm_state.process_event(message, &mut self.tool_data, context, &self.options, responses, true); return; }; match action { @@ -242,8 +242,15 @@ impl Fsm for SplineToolFsmState { type ToolData = SplineToolData; type ToolOptions = SplineOptions; - fn transition(self, event: ToolMessage, tool_data: &mut Self::ToolData, tool_action_data: &mut ToolActionHandlerData, tool_options: &Self::ToolOptions, responses: &mut VecDeque) -> Self { - let ToolActionHandlerData { + fn transition( + self, + event: ToolMessage, + tool_data: &mut Self::ToolData, + tool_action_data: &mut ToolActionMessageContext, + tool_options: &Self::ToolOptions, + responses: &mut VecDeque, + ) -> Self { + let ToolActionMessageContext { document, global_tool_data, input, diff --git a/editor/src/messages/tool/tool_messages/text_tool.rs b/editor/src/messages/tool/tool_messages/text_tool.rs index 52430f09c..d719c7879 100644 --- a/editor/src/messages/tool/tool_messages/text_tool.rs +++ b/editor/src/messages/tool/tool_messages/text_tool.rs @@ -171,10 +171,10 @@ 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>) { +impl<'a> MessageHandler> for TextTool { + fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque, context: &mut ToolActionMessageContext<'a>) { let ToolMessage::Text(TextToolMessage::UpdateOptions(action)) = message else { - self.fsm_state.process_event(message, &mut self.tool_data, tool_data, &self.options, responses, true); + self.fsm_state.process_event(message, &mut self.tool_data, context, &self.options, responses, true); return; }; match action { @@ -449,8 +449,15 @@ impl Fsm for TextToolFsmState { type ToolData = TextToolData; type ToolOptions = TextOptions; - fn transition(self, event: ToolMessage, tool_data: &mut Self::ToolData, transition_data: &mut ToolActionHandlerData, tool_options: &Self::ToolOptions, responses: &mut VecDeque) -> Self { - let ToolActionHandlerData { + fn transition( + self, + event: ToolMessage, + tool_data: &mut Self::ToolData, + transition_data: &mut ToolActionMessageContext, + tool_options: &Self::ToolOptions, + responses: &mut VecDeque, + ) -> Self { + let ToolActionMessageContext { document, global_tool_data, input, diff --git a/editor/src/messages/tool/transform_layer/mod.rs b/editor/src/messages/tool/transform_layer/mod.rs index 823edf1a4..ce40f76f6 100644 --- a/editor/src/messages/tool/transform_layer/mod.rs +++ b/editor/src/messages/tool/transform_layer/mod.rs @@ -1,4 +1,4 @@ -//! Handles Blender inspired layer transformation with the G R and S keys for grabbing, rotating and scaling. +//! Handles Blender inspired layer transformation with the G, R, and S keys for grabbing, rotating, and scaling. //! //! Other features include //! - Typing a number for a precise transformation @@ -7,7 +7,7 @@ //! - Escape or right click to cancel mod transform_layer_message; -mod transform_layer_message_handler; +pub mod transform_layer_message_handler; #[doc(inline)] pub use transform_layer_message::{TransformLayerMessage, TransformLayerMessageDiscriminant}; 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 b9b9880e9..603cda067 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,6 +21,14 @@ const TRANSFORM_GRS_OVERLAY_PROVIDER: OverlayProvider = |context| TransformLayer const SLOW_KEY: Key = Key::Shift; const INCREMENTS_KEY: Key = Key::Control; +#[derive(ExtractField)] +pub struct TransformLayerMessageContext<'a> { + pub document: &'a DocumentMessageHandler, + pub input: &'a InputPreprocessorMessageHandler, + pub tool_data: &'a ToolData, + pub shape_editor: &'a mut ShapeState, +} + #[derive(Debug, Clone, Default, ExtractField)] pub struct TransformLayerMessageHandler { pub transform_operation: TransformOperation, @@ -55,148 +63,15 @@ pub struct TransformLayerMessageHandler { grs_pen_handle: bool, } -impl TransformLayerMessageHandler { - pub fn is_transforming(&self) -> bool { - self.transform_operation != TransformOperation::None - } +impl MessageHandler> for TransformLayerMessageHandler { + fn process_message(&mut self, message: TransformLayerMessage, responses: &mut VecDeque, context: TransformLayerMessageContext) { + let TransformLayerMessageContext { + document, + input, + tool_data, + shape_editor, + } = context; - pub fn hints(&self, responses: &mut VecDeque) { - self.transform_operation.hints(responses, self.local); - } -} - -fn calculate_pivot( - document: &DocumentMessageHandler, - selected_points: &Vec<&ManipulatorPointId>, - vector_data: &VectorData, - viewspace: DAffine2, - get_location: impl Fn(&ManipulatorPointId) -> Option, - gizmo: &mut PivotGizmo, -) -> (Option<(DVec2, DVec2)>, Option<[DVec2; 2]>) { - let average_position = || { - let mut point_count = 0_usize; - selected_points.iter().filter_map(|p| get_location(p)).inspect(|_| point_count += 1).sum::() / point_count as f64 - }; - let bounds = selected_points.iter().filter_map(|p| get_location(p)).fold(None, |acc: Option<[DVec2; 2]>, point| { - if let Some([mut min, mut max]) = acc { - min.x = min.x.min(point.x); - min.y = min.y.min(point.y); - max.x = max.x.max(point.x); - max.y = max.y.max(point.y); - Some([min, max]) - } else { - Some([point, point]) - } - }); - gizmo.pivot.recalculate_pivot_for_layer(document, bounds); - let position = || { - (if !gizmo.state.disabled { - match gizmo.state.gizmo_type { - PivotGizmoType::Average => None, - PivotGizmoType::Active => gizmo.point.and_then(|p| get_location(&p)), - PivotGizmoType::Pivot => gizmo.pivot.pivot, - } - } else { - None - }) - .unwrap_or_else(average_position) - }; - let [point] = selected_points.as_slice() else { - // Handle the case where there are multiple points - let position = position(); - return (Some((position, position)), bounds); - }; - - match point { - ManipulatorPointId::PrimaryHandle(_) | ManipulatorPointId::EndHandle(_) => { - // Get the anchor position and transform it to the pivot - let (Some(pivot_position), Some(position)) = ( - point.get_anchor_position(vector_data).map(|anchor_position| viewspace.transform_point2(anchor_position)), - point.get_position(vector_data), - ) else { - return (None, None); - }; - let target = viewspace.transform_point2(position); - (Some((pivot_position, target)), None) - } - _ => { - // Calculate the average position of all selected points - let position = position(); - (Some((position, position)), bounds) - } - } -} - -fn project_edge_to_quad(edge: DVec2, quad: &Quad, local: bool, axis_constraint: Axis) -> DVec2 { - match axis_constraint { - Axis::X => { - if local { - edge.project_onto(quad.top_right() - quad.top_left()) - } else { - edge.with_y(0.) - } - } - Axis::Y => { - if local { - edge.project_onto(quad.bottom_left() - quad.top_left()) - } else { - edge.with_x(0.) - } - } - _ => edge, - } -} - -fn update_colinear_handles(selected_layers: &[LayerNodeIdentifier], document: &DocumentMessageHandler, responses: &mut VecDeque) { - for &layer in selected_layers { - let Some(vector_data) = document.network_interface.compute_modified_vector(layer) else { continue }; - - for [handle1, handle2] in &vector_data.colinear_manipulators { - let manipulator1 = handle1.to_manipulator_point(); - let manipulator2 = handle2.to_manipulator_point(); - - let Some(anchor) = manipulator1.get_anchor_position(&vector_data) else { continue }; - let Some(pos1) = manipulator1.get_position(&vector_data).map(|pos| pos - anchor) else { continue }; - let Some(pos2) = manipulator2.get_position(&vector_data).map(|pos| pos - anchor) else { continue }; - - let angle = pos1.angle_to(pos2); - - // Check if handles are not colinear (not approximately equal to +/- PI) - if (angle - PI).abs() > 1e-6 && (angle + PI).abs() > 1e-6 { - let modification_type = VectorModificationType::SetG1Continuous { - handles: [*handle1, *handle2], - enabled: false, - }; - - responses.add(GraphOperationMessage::Vector { layer, modification_type }); - } - } - } -} - -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; let using_select_tool = tool_data.active_tool_type == ToolType::Select; let using_pen_tool = tool_data.active_tool_type == ToolType::Pen; @@ -774,6 +649,125 @@ impl MessageHandler> for TransformLayer } } +impl TransformLayerMessageHandler { + pub fn is_transforming(&self) -> bool { + self.transform_operation != TransformOperation::None + } + + pub fn hints(&self, responses: &mut VecDeque) { + self.transform_operation.hints(responses, self.local); + } +} + +fn calculate_pivot( + document: &DocumentMessageHandler, + selected_points: &Vec<&ManipulatorPointId>, + vector_data: &VectorData, + viewspace: DAffine2, + get_location: impl Fn(&ManipulatorPointId) -> Option, + gizmo: &mut PivotGizmo, +) -> (Option<(DVec2, DVec2)>, Option<[DVec2; 2]>) { + let average_position = || { + let mut point_count = 0_usize; + selected_points.iter().filter_map(|p| get_location(p)).inspect(|_| point_count += 1).sum::() / point_count as f64 + }; + let bounds = selected_points.iter().filter_map(|p| get_location(p)).fold(None, |acc: Option<[DVec2; 2]>, point| { + if let Some([mut min, mut max]) = acc { + min.x = min.x.min(point.x); + min.y = min.y.min(point.y); + max.x = max.x.max(point.x); + max.y = max.y.max(point.y); + Some([min, max]) + } else { + Some([point, point]) + } + }); + gizmo.pivot.recalculate_pivot_for_layer(document, bounds); + let position = || { + (if !gizmo.state.disabled { + match gizmo.state.gizmo_type { + PivotGizmoType::Average => None, + PivotGizmoType::Active => gizmo.point.and_then(|p| get_location(&p)), + PivotGizmoType::Pivot => gizmo.pivot.pivot, + } + } else { + None + }) + .unwrap_or_else(average_position) + }; + let [point] = selected_points.as_slice() else { + // Handle the case where there are multiple points + let position = position(); + return (Some((position, position)), bounds); + }; + + match point { + ManipulatorPointId::PrimaryHandle(_) | ManipulatorPointId::EndHandle(_) => { + // Get the anchor position and transform it to the pivot + let (Some(pivot_position), Some(position)) = ( + point.get_anchor_position(vector_data).map(|anchor_position| viewspace.transform_point2(anchor_position)), + point.get_position(vector_data), + ) else { + return (None, None); + }; + let target = viewspace.transform_point2(position); + (Some((pivot_position, target)), None) + } + _ => { + // Calculate the average position of all selected points + let position = position(); + (Some((position, position)), bounds) + } + } +} + +fn project_edge_to_quad(edge: DVec2, quad: &Quad, local: bool, axis_constraint: Axis) -> DVec2 { + match axis_constraint { + Axis::X => { + if local { + edge.project_onto(quad.top_right() - quad.top_left()) + } else { + edge.with_y(0.) + } + } + Axis::Y => { + if local { + edge.project_onto(quad.bottom_left() - quad.top_left()) + } else { + edge.with_x(0.) + } + } + _ => edge, + } +} + +fn update_colinear_handles(selected_layers: &[LayerNodeIdentifier], document: &DocumentMessageHandler, responses: &mut VecDeque) { + for &layer in selected_layers { + let Some(vector_data) = document.network_interface.compute_modified_vector(layer) else { continue }; + + for [handle1, handle2] in &vector_data.colinear_manipulators { + let manipulator1 = handle1.to_manipulator_point(); + let manipulator2 = handle2.to_manipulator_point(); + + let Some(anchor) = manipulator1.get_anchor_position(&vector_data) else { continue }; + let Some(pos1) = manipulator1.get_position(&vector_data).map(|pos| pos - anchor) else { continue }; + let Some(pos2) = manipulator2.get_position(&vector_data).map(|pos| pos - anchor) else { continue }; + + let angle = pos1.angle_to(pos2); + + // Check if handles are not colinear (not approximately equal to +/- PI) + if (angle - PI).abs() > 1e-6 && (angle + PI).abs() > 1e-6 { + let modification_type = VectorModificationType::SetG1Continuous { + handles: [*handle1, *handle2], + enabled: false, + }; + + responses.add(GraphOperationMessage::Vector { layer, modification_type }); + } + } + } +} + #[cfg(test)] mod test_transform_layer { use crate::messages::portfolio::document::graph_operation::transform_utils; diff --git a/editor/src/messages/tool/utility_types.rs b/editor/src/messages/tool/utility_types.rs index fc06385f4..740b09c97 100644 --- a/editor/src/messages/tool/utility_types.rs +++ b/editor/src/messages/tool/utility_types.rs @@ -19,7 +19,7 @@ use std::borrow::Cow; use std::fmt::{self, Debug}; #[derive(ExtractField)] -pub struct ToolActionHandlerData<'a> { +pub struct ToolActionMessageContext<'a> { pub document: &'a mut DocumentMessageHandler, pub document_id: DocumentId, pub global_tool_data: &'a DocumentToolData, @@ -30,8 +30,8 @@ pub struct ToolActionHandlerData<'a> { pub preferences: &'a PreferencesMessageHandler, } -pub trait ToolCommon: for<'a, 'b> MessageHandler> + LayoutHolder + ToolTransition + ToolMetadata {} -impl ToolCommon for T where T: for<'a, 'b> MessageHandler> + LayoutHolder + ToolTransition + ToolMetadata {} +pub trait ToolCommon: for<'a, 'b> MessageHandler> + LayoutHolder + ToolTransition + ToolMetadata {} +impl ToolCommon for T where T: for<'a, 'b> MessageHandler> + LayoutHolder + ToolTransition + ToolMetadata {} type Tool = dyn ToolCommon + Send + Sync; @@ -53,7 +53,7 @@ pub trait Fsm { /// For example, if the tool's FSM is in a `Ready` state and receives a `DragStart` message as its event, it may decide to send some messages, /// update some internal tool variables, and end by transitioning to a `Drawing` state. #[must_use] - fn transition(self, message: ToolMessage, tool_data: &mut Self::ToolData, transition_data: &mut ToolActionHandlerData, options: &Self::ToolOptions, responses: &mut VecDeque) -> Self; + fn transition(self, message: ToolMessage, tool_data: &mut Self::ToolData, transition_data: &mut ToolActionMessageContext, options: &Self::ToolOptions, responses: &mut VecDeque) -> Self; /// Implementing this trait function lets a specific tool provide a list of hints (user input actions presently available) to draw in the footer bar. fn update_hints(&self, responses: &mut VecDeque); @@ -82,7 +82,7 @@ pub trait Fsm { &mut self, message: ToolMessage, tool_data: &mut Self::ToolData, - transition_data: &mut ToolActionHandlerData, + transition_data: &mut ToolActionMessageContext, options: &Self::ToolOptions, responses: &mut VecDeque, update_cursor_on_transition: bool, diff --git a/editor/src/messages/workspace/workspace_message_handler.rs b/editor/src/messages/workspace/workspace_message_handler.rs index 47c81aab0..d71c7a42c 100644 --- a/editor/src/messages/workspace/workspace_message_handler.rs +++ b/editor/src/messages/workspace/workspace_message_handler.rs @@ -7,7 +7,7 @@ pub struct WorkspaceMessageHandler { #[message_handler_data] impl MessageHandler for WorkspaceMessageHandler { - fn process_message(&mut self, message: WorkspaceMessage, _responses: &mut VecDeque, _data: ()) { + fn process_message(&mut self, message: WorkspaceMessage, _responses: &mut VecDeque, _: ()) { match message { // Messages WorkspaceMessage::NodeGraphToggleVisibility => { diff --git a/editor/src/node_graph_executor.rs b/editor/src/node_graph_executor.rs index ba2f52d7b..8c6a7d13a 100644 --- a/editor/src/node_graph_executor.rs +++ b/editor/src/node_graph_executor.rs @@ -53,7 +53,7 @@ pub enum NodeGraphUpdate { NodeGraphUpdateMessage(NodeGraphUpdateMessage), } -#[derive(Debug)] +#[derive(Debug, Default)] pub struct NodeGraphExecutor { runtime_io: NodeRuntimeIO, futures: HashMap, @@ -66,17 +66,6 @@ struct ExecutionContext { export_config: Option, } -impl Default for NodeGraphExecutor { - fn default() -> Self { - Self { - futures: Default::default(), - runtime_io: NodeRuntimeIO::new(), - node_graph_hash: 0, - old_inspect_node: None, - } - } -} - impl NodeGraphExecutor { /// A local runtime is useful on threads since having global state causes flakes #[cfg(test)] @@ -394,7 +383,9 @@ impl NodeGraphExecutor { return Err(format!("Invalid node graph output type: {node_graph_output:#?}")); } }; - responses.add(Message::EndBuffer(render_output_metadata)); + responses.add(Message::EndBuffer { + render_metadata: render_output_metadata, + }); responses.add(DocumentMessage::RenderScrollbars); responses.add(DocumentMessage::RenderRulers); responses.add(OverlaysMessage::Draw); diff --git a/editor/src/test_utils.rs b/editor/src/test_utils.rs index 04ef698da..0bb7c7f4f 100644 --- a/editor/src/test_utils.rs +++ b/editor/src/test_utils.rs @@ -31,7 +31,7 @@ impl EditorTestUtils { // It isn't sufficient to guard the message dispatch here with a check if the once_cell is empty, because that isn't atomic and the time between checking and handling the dispatch can let multiple through. let _ = GLOBAL_PLATFORM.set(Platform::Windows).is_ok(); - editor.handle_message(Message::Init); + editor.handle_message(PortfolioMessage::Init); Self { editor, runtime } } diff --git a/editor/src/utility_traits.rs b/editor/src/utility_traits.rs index 850d730b1..aaf977d1d 100644 --- a/editor/src/utility_traits.rs +++ b/editor/src/utility_traits.rs @@ -2,15 +2,14 @@ pub use crate::dispatcher::*; use crate::messages::prelude::*; /// Implements a message handler struct for a separate message struct. -/// - The first generic argument (`M`) is that message struct type, representing a message enum variant to be matched and handled in `process_message()`. -/// - The second generic argument (`D`) is the type of data that can be passed along by the caller to `process_message()`. -pub trait MessageHandler +/// - The first type argument (`M`) is that message struct type, representing a message enum variant to be matched and handled in `process_message()`. +/// - The second type argument (`C`) is the type of the context struct that can be passed along by the caller to `process_message()`. +pub trait MessageHandler where M::Discriminant: AsMessage, ::TopParent: TransitiveChild::TopParent, TopParent = ::TopParent> + AsMessage, { - /// Return true if the Action is consumed. - fn process_message(&mut self, message: M, responses: &mut VecDeque, data: D); + fn process_message(&mut self, message: M, responses: &mut VecDeque, context: C); fn actions(&self) -> ActionList; } diff --git a/frontend/wasm/src/editor_api.rs b/frontend/wasm/src/editor_api.rs index 20f2f1ae9..c8056e4ef 100644 --- a/frontend/wasm/src/editor_api.rs +++ b/frontend/wasm/src/editor_api.rs @@ -123,7 +123,7 @@ impl EditorHandle { _ => Platform::Unknown, }; self.dispatch(GlobalsMessage::SetPlatform { platform }); - self.dispatch(Message::Init); + self.dispatch(PortfolioMessage::Init); // Poll node graph evaluation on `requestAnimationFrame` { diff --git a/node-graph/gcore/src/logic.rs b/node-graph/gcore/src/logic.rs index 8ccd9c506..3abd7c493 100644 --- a/node-graph/gcore/src/logic.rs +++ b/node-graph/gcore/src/logic.rs @@ -33,8 +33,8 @@ fn string_slice(_: impl Ctx, #[implementations(String)] string: String, start: f } #[node_macro::node(category("Text"))] -fn string_length(_: impl Ctx, #[implementations(String)] string: String) -> usize { - string.len() +fn string_length(_: impl Ctx, #[implementations(String)] string: String) -> u32 { + string.len() as u32 } #[node_macro::node(category("Math: Logic"))] diff --git a/node-graph/gmath-nodes/src/lib.rs b/node-graph/gmath-nodes/src/lib.rs index 1019506bd..e392af4a7 100644 --- a/node-graph/gmath-nodes/src/lib.rs +++ b/node-graph/gmath-nodes/src/lib.rs @@ -467,10 +467,10 @@ fn clamp( fn equals, T>( _: impl Ctx, /// One of the two numbers to compare for equality. - #[implementations(f64, f32, u32, DVec2, &str)] + #[implementations(f64, f32, u32, DVec2, &str, String)] value: T, /// The other of the two numbers to compare for equality. - #[implementations(f64, f32, u32, DVec2, &str)] + #[implementations(f64, f32, u32, DVec2, &str, String)] other_value: U, ) -> bool { other_value == value diff --git a/proc-macros/src/message_handler_data_attr.rs b/proc-macros/src/message_handler_data_attr.rs index b2170beac..ea3e634c2 100644 --- a/proc-macros/src/message_handler_data_attr.rs +++ b/proc-macros/src/message_handler_data_attr.rs @@ -22,14 +22,14 @@ pub fn message_handler_data_attr_impl(attr: TokenStream, input_item: TokenStream None => return Err(syn::Error::new(Span::call_site(), "Expected trait implementation")), }; - // Get the trait generics (should be MessageHandler) + // 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 + // Extract the message type (M) and context struct type (C) from the trait params let message_type = &args.args[0]; let data_type = &args.args[1]; From d6d1bbb1e63655a09274c1cff56ecb8a54d80c14 Mon Sep 17 00:00:00 2001 From: Keavon Chambers Date: Sat, 12 Jul 2025 23:09:29 -0700 Subject: [PATCH 02/62] Add editor structure outline to the developer guide on the website --- .github/workflows/website.yml | 51 ++--- proc-macros/src/helpers.rs | 14 ++ proc-macros/src/message_handler_data_attr.rs | 40 ++-- website/.gitignore | 1 + .../_unpublished/product-design/_index.md | 18 -- .../product-design/glossary-of-terminology.md | 68 ------- .../product-design/product-outline.md | 108 ----------- .../product-design/uses-and-workflows.md | 115 ----------- .../project-setup/knowing-your-tooling.md | 20 -- .../guide/codebase-overview/_index.md | 178 +----------------- .../guide/codebase-overview/debugging-tips.md | 2 +- .../codebase-overview/editor-structure.md | 98 ++++++++++ website/other/editor-structure/generate.js | 120 ++++++++++++ website/sass/base.scss | 7 +- .../developer-guide-editor-structure.scss | 106 +++++++++++ .../js/developer-guide-editor-structure.js | 17 ++ website/templates/base.html | 6 +- website/templates/macros/replacements.html | 15 ++ 18 files changed, 426 insertions(+), 558 deletions(-) delete mode 100644 website/content/volunteer/guide/_unpublished/product-design/_index.md delete mode 100644 website/content/volunteer/guide/_unpublished/product-design/glossary-of-terminology.md delete mode 100644 website/content/volunteer/guide/_unpublished/product-design/product-outline.md delete mode 100644 website/content/volunteer/guide/_unpublished/product-design/uses-and-workflows.md delete mode 100644 website/content/volunteer/guide/_unpublished/project-setup/knowing-your-tooling.md create mode 100644 website/content/volunteer/guide/codebase-overview/editor-structure.md create mode 100644 website/other/editor-structure/generate.js create mode 100644 website/sass/page/developer-guide-editor-structure.scss create mode 100644 website/static/js/developer-guide-editor-structure.js diff --git a/.github/workflows/website.yml b/.github/workflows/website.yml index 4b9a6ed40..5e5c27ffb 100644 --- a/.github/workflows/website.yml +++ b/.github/workflows/website.yml @@ -44,6 +44,34 @@ jobs: # Remove the INDEX_HTML_HEAD_INCLUSION environment variable for build links (not master deploys) git rev-parse --abbrev-ref HEAD | grep master > /dev/null || export INDEX_HTML_HEAD_INCLUSION="" + - 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/other/editor-structure` + run: | + mv artifacts/* website/other/editor-structure + + - name: 🔧 Build auto-generated code docs artifacts into HTML + run: | + cd website/other/editor-structure + node generate.js hierarchical_message_system_tree.txt replacement.html + - name: 🌐 Build Graphite website with Zola env: MODE: prod @@ -84,29 +112,6 @@ jobs: 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 diff --git a/proc-macros/src/helpers.rs b/proc-macros/src/helpers.rs index 230f792c0..4beb44f42 100644 --- a/proc-macros/src/helpers.rs +++ b/proc-macros/src/helpers.rs @@ -72,6 +72,20 @@ pub fn clean_rust_type_syntax(input: String) -> String { chars.next(); } } + '-' => { + if let Some('>') = chars.peek() { + while let Some(' ') = result.chars().rev().next() { + result.pop(); + } + result.push_str(" -> "); + chars.next(); + while let Some(' ') = chars.peek() { + chars.next(); + } + } else { + result.push(c); + } + } ':' => { if let Some(':') = chars.peek() { while let Some(' ') = result.chars().rev().next() { diff --git a/proc-macros/src/message_handler_data_attr.rs b/proc-macros/src/message_handler_data_attr.rs index ea3e634c2..6c1c88b88 100644 --- a/proc-macros/src/message_handler_data_attr.rs +++ b/proc-macros/src/message_handler_data_attr.rs @@ -33,9 +33,6 @@ pub fn message_handler_data_attr_impl(attr: TokenStream, input_item: TokenStream 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 { @@ -43,32 +40,17 @@ pub fn message_handler_data_attr_impl(attr: TokenStream, input_item: TokenStream // 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()) + 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()) - } } - } - } 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()) - } - pub fn message_handler_str() -> MessageData { - MessageData::new(format!("{}",stringify!(#input_type)), #input_type::field_types(), #input_type::path()) - - } } } } @@ -77,7 +59,7 @@ pub fn message_handler_data_attr_impl(attr: TokenStream, input_item: TokenStream #input_item impl #message_type { pub fn message_handler_str() -> MessageData { - MessageData::new(format!("{}",stringify!(#input_type)), #input_type::field_types(), #input_type::path()) + MessageData::new(format!("{}", stringify!(#input_type)), #input_type::field_types(), #input_type::path()) } } }, @@ -92,11 +74,11 @@ pub fn message_handler_data_attr_impl(attr: TokenStream, input_item: TokenStream #input_item impl #message_type { pub fn message_handler_data_str() -> MessageData { - MessageData::new(format!("{}", #tr),#type_ident::field_types(), #type_ident::path()) + 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()) + MessageData::new(format!("{}", stringify!(#input_type)), #input_type::field_types(), #input_type::path()) } } diff --git a/website/.gitignore b/website/.gitignore index 0fc0af5dd..c9dbb6337 100644 --- a/website/.gitignore +++ b/website/.gitignore @@ -3,3 +3,4 @@ public/ static/fonts/ static/syntax-highlighting.css static/text-balancer.js +other/editor-structure/replacement.html diff --git a/website/content/volunteer/guide/_unpublished/product-design/_index.md b/website/content/volunteer/guide/_unpublished/product-design/_index.md deleted file mode 100644 index 48efda62f..000000000 --- a/website/content/volunteer/guide/_unpublished/product-design/_index.md +++ /dev/null @@ -1,18 +0,0 @@ -+++ -title = "Product design" -template = "book.html" -page_template = "book.html" - -[extra] -order = 5 # Chapter number -+++ - -**NOTE: Developers probably don't need to read this chapter.** - -A vital part in the success of open source software development is having a clear vision and direction for the product design. Failure to pull that off is a leading factor in the stagnation and mediocrity of many software projects. - -The Graphite project's founder and its principal product designer, Keavon, is in charge of maintaining and evolving the product design to ensure cohesion within the development process. This ranges from building the user interface design system and content layout as a mockup before it's replicated in HTML/CSS, to determining user workflows, to translating high-level goals into the constraints and requirements of specific features for the development team. - -The hardest part is translating an always-evolving network of ideas and thoughts comprehensively into written form in order to communicate the big (and little) ideas to collaborators. As the product develops, the design grows alongside the code and real usage of the developed features helps bring clarity to what's planned next. Since the design process never stops evolving, any attempt to write it down is inevitably doomed to become out of date. And the multitudes of mental state can't possibly all be written down. - -Despite the challenges with documenting design, this chapter aims to provide a living catalog of ideas surrounding some topics in the product design process. It won't be organized, nor complete, nor will it be always current. The best way to seek clarity about a topic is asking Keavon about it on Discord. **This chapter is optional reading** and is likely of minimal relevance to most code contributors. diff --git a/website/content/volunteer/guide/_unpublished/product-design/glossary-of-terminology.md b/website/content/volunteer/guide/_unpublished/product-design/glossary-of-terminology.md deleted file mode 100644 index 0923a6547..000000000 --- a/website/content/volunteer/guide/_unpublished/product-design/glossary-of-terminology.md +++ /dev/null @@ -1,68 +0,0 @@ -+++ -title = "Glossary of terminology" - -[extra] -order = 3 # Page number after chapter intro -+++ - -**NOTE: This is old. Some parts may not match current usage.** - -### Document -A design source file created and edited in the Graphite editor. Saved to disk as a Graphite Design Document in a _GDD file_. Documents can be included as _layers_ inside other documents, and in doing so they take the form of _groups_. The _layer graph_ contents of a _group_ actually belong to the _embedded_ document's _subgraph_. Because a document is a _group_ which is a _layer_ in the _layer graph_, documents have _properties_ such as the _frames_ in the _canvas_. Documents are composed of a layer graph, a defined set of properties of set _data types_ that are _imported_ and _exported_, and the _properties_ of the _root layer_. -### Asset -A portable mechanism for distributing a "compiled" Graphite _document_ in a format that is immediately ready for rendering. Saved to disk as a Graphite Digital Asset in a _GDA file_. Assets are created by "flattening" a _document's_ complex, nested _layer graph_ structure into a single, simple directed acyclic graph (DAG). The Graphite editor internally maintains an asset version of any open _document_ in order to draw the _canvas_ live in the _viewport_. An asset also includes certain exposed _properties_ of specified _data types_ that are _imported_ and _exported_, as defined by the asset's author in the source _document's_ _layer graph_. They can be shared and _embedded_ in another _layer graph_ as a black box (meaning it can't be expanded to reveal or edit its interior graph), as compared to _embedded_ _documents_ from _GDD files_ which are white boxes (they can be expanded to reveal their _subgraph_ which can be edited). Assets are helpful for defining custom _nodes_ that perform some useful functionality. Tangible examples include custom procedural effects, shape generators, and image filters. Many of the Graphite editor's own built-in _nodes_ are actually assets rather than being implemented directly in code. The _Asset Manager_ panel helps maintain these assets from various sources. The _Asset Store_ can be used to share and sell assets for easy inclusion in other projects. -### GDD file -Graphite Design Document. A binary serialization of a _document_ source file. The format includes a chain of _operations_ that describe changes to the _layer graph_ and the _properties_ of _layers_ throughout the history of the document since its creation. It also stores certain metadata and the raw data of _embedded_ files. Because GDD files are editable (unlike _GDA files_), the _layers_ of GDD files imported into another _document_ may be expanded in its _layer graph_ to reveal and modify their contents using a copy-on-write scheme stored to the _asset's_ _layer_. -### GDA file -Graphite Digital Asset. A binary serialization of an _asset_ file. Because GDA files are read-only and can't be edited (unlike _GDD files_), the _layers_ created from _assets_ do not offer an ability to be expanded in the _layer graph_ of a _document_ that _embeds_ them. GDA files are useful for sharing _assets_ when their authors do not wish to provide the source _documents_ to author them. _DGA files_ are also the input format included in games that utilize the _Graphite Renderer Core Library_ to render graphical content at runtime, as well as similar applications like headless renderers on web servers and image processing pipelines. -### Window -### Main window -### Popout window -### Title bar -### Status bar -### Workspace -The part of the Graphite editor's UI that houses the _panels_ in a _window_. The workspace occupies the large space below the _title bar_ and above the _status bar_ of the _main window_. It occupies the entirety of _popout windows_ (window buttons are added in the _tab bar_). -### Workspace layout -The specific configuration of panels in the _main window_ and any _popout windows_. Workspace layout presets are provided by the Graphite editor and users may customize and save their own. -### Tab bar -The bar at the top of a _panel group_ which includes a clickable tab for each panel that is docked there. Each tab bar has at least one tab and one active tab. -### Active tab -The one tab in a _tab bar_ that is currently active. The user can click any inactive tab to make it become the active tab. The active tab shows the _panel content_ beneath it unless it is a _folded panel_. -### Folded panel -A shrunken _panel_ showing only the _tab bar_. A _panel_ consists of the _tab bar_ and _panel content_ except when the latter is folded away. The user may click the _active tab_ to fold and restore a panel, however a panel cannot be folded if there are no other unfolded panels in its column. -### Panel -### Panel content -### Control bar -The bar that spans horizontally across the top of a _panel_ (located under the _tab bar_) which displays controls related to the _panel_. -### Viewport -The area that takes up the main space in a _panel_ (located beneath the _control bar_) which displays the primary content of the _panel_. -### Shelf -The bar that spans vertically along the left side of some _panels_ (located left of the _viewport_) which displays a catalog of available items, such as document editing _tools_ or common _nodes_. -### Tool -An instrument for interactively editing _documents_ through a collection of related behavior. Each tool puts the editor into a mode that provides the ability to perform certain _operations_ on the document interactively. Each _operation_ is run based on the current context of mouse and modifier buttons, key presses, tool options, selected layers, editor state, and document state. The _operations_ that get run are appended to the document history and update the underlying _layer graph_ in real time. -### Canvas -The infinite coordinate system that shows the visual output of an open _document_ at the current zoom level and pan position. It is drawn in the document panel's _viewport_ within the area inside the scroll bars on the bottom/right edges and the _rulers_ on the top/left edges. The canvas can be panned and zoomed in order to display all or part of the artwork in any _frames_. A canvas has a coordinate system spanning infinitely in all directions with an origin always located at the top left of the primary _artboard_. The purpose of an infinite canvas is to offer a convenient editing experience when there is no logical edge to the artwork, for example a loosely-arranged board of logo design concepts, a mood board, or whiteboard-style notes. -### Artboard -An area inside a _canvas_ that provides rectangular bounds to the artwork contained within, as well as default bounds for an exported image. The _Artboard tool_ adjusts the bounds and placement of frames in the _document_ and each artboard is stored in a "artboard list" property of the _root layer_. When there is at least one artboard, the infinite _canvas_ area outside any artboard displays a configurable background color. Artwork can be placed outside of a artboard but it will appear mostly transparent. The purpose of using one artboard is to provide convenient cropping to the edges of the artwork, such as a single digital painting or photograph. The purpose of using multiple frames is to work on related artwork with separate bounds, such as the layout for a book. -### Layer graph -A (directed acyclic) graph structure composed of _layers_ with _connections_ between their input and output _ports_. This is commonly referred to as a "node graph" in other software, but Graphite's layer graph is more suited towards layer-based compositing compared to traditional compositor node graphs. -### Node -A definition of a _layer_. A node is a graph "operation" or "function" that receives input and generates deterministic output. -### Layer -Any instance of a _node_ that lives in the _layer graph_. Layers (usually) take input data, then they transform it or synthesize new data, then they provide it as output. Layers have _properties_ as well as exposed input and output _ports_ for sending and receiving data. -### Root layer -### Group -### Raster -### Vector -### Mask -### Data type -### Subgraph -### Port -### Connection -### Core Libraries -### Graphite Editor (Frontend) -### Graphite Editor (Backend) -### Graphene (Node Graph Engine) -### Trace -### Path -### Shape diff --git a/website/content/volunteer/guide/_unpublished/product-design/product-outline.md b/website/content/volunteer/guide/_unpublished/product-design/product-outline.md deleted file mode 100644 index 7e1e8365e..000000000 --- a/website/content/volunteer/guide/_unpublished/product-design/product-outline.md +++ /dev/null @@ -1,108 +0,0 @@ -+++ -title = "Product outline" - -[extra] -order = 1 # Page number after chapter intro -+++ - -**NOTE: This is old. Some parts may not match current usage.** - -- Interface - - Title bar - - Menu bar - - Document title - - Window buttons - - Workspace - - Panel interface (tab, pin, control bar, left menu) - - Arrangement and docking - - Status bar - - Multiple windows -- Panels - - Document - - Canvas and frames - - Rulers - - Tool menu - - Control bar - - Properties - - Blending - - Origin - - Transform - - Node-specific properties - - Layers - - Interface - - Compositing flow - - Groups - - Masks - - Isolation - - Graph - - Interface - - Layer/node equivalence - - Compositing flow - - Groups - - Masks - - Isolation - - Connection Matrix - - Spreadsheet - - Node Catalog - - Asset Manager - - Embedded assets - - Linked assets - - Local assets - - Remote assets - - Store assets - - Out-of-the-box assets - - Extension-provided assets - - Palettes - - Each color palette is an asset - - Minimap - - Histogram - - Timeline -- Documents and assets - - Node groups, layer groups, and document tabs are equivalent - - Groups/documents are assets - - Assets are embedded or linked - - The document canvas is a group output - - Art boards and dimensions - - Reusable assets -- Tools - - Overview - - Tools add and update assets - - General tool group - - Vector tool group - - Raster tool group -- Masking - - Mask mode -- Vector editing - - Data types - - Data flow - - Rasterization -- Raster editing - - Adaptive resolution sampling - - Compositing and data flow - - Caching - - Predictive caching - - Progressive enhancement - - Generator nodes - - Rasterizer nodes - - Adjustment nodes - - Filter nodes - - Sample transformer nodes - - Historical sampling -- Text editing -- Data types -- Node types -- Node library -- File format interoperability - - Import/export: JPG, PNG, APNG, GIF, TIFF, TGA, BMP, JPEG 2000, WebP, HEIF, ICO - - Import/export: EXR - - Import: digital camera raw formats - - Import/export: SVG - - Import/export: EPS - - Import/export: PDF - - Import: PSD/PSB, AI, INDD? - - Export: G-code formats for laser and vinyl cutters? -- Extensions -- Color management -- Units, measurement, and scale -- Headless and integrations -- Animation diff --git a/website/content/volunteer/guide/_unpublished/product-design/uses-and-workflows.md b/website/content/volunteer/guide/_unpublished/product-design/uses-and-workflows.md deleted file mode 100644 index c05356a95..000000000 --- a/website/content/volunteer/guide/_unpublished/product-design/uses-and-workflows.md +++ /dev/null @@ -1,115 +0,0 @@ -+++ -title = "Uses and workflows" - -[extra] -order = 2 # Page number after chapter intro -+++ - -**NOTE: This is old. Some parts may not match current usage.** - -This list describes some long-term aspirational goals and ideas. It represents an incomplete brainstorm idea dump, not a roadmap. - -## Use cases - -General goals for product capabilities are categorized by discipline below. - -### Photography -- RAW photo editing/processing -- Batch processing pipeline - -### Motion graphics -- Title sequences/kinetic typography/etc. - -### Live broadcast/streaming -- Live video compositing/overlays -- Interactive exhibits (rendering live content for museum/art/festival exhibits) - -### Web -- SVG design -- Animated or interactive SVG design - -### Automation -- Batch multimedia editing/conversion - -### Graphic design -- Print design -- Web/digital-focused graphics (marketing, branding, infographics, ads, etc.) -- Templates filled with file/spreadsheet data (e.g. prototyping/iterating components of a board/card game) - -### Illustration -- Digital painting -- Logo and icon design - -### Desktop publishing -- Templates filled with Markdown/HTML content with export to PDF - -### Video compositing - -### Data Visualization -- Data-powered graphs/charts/etc. -- Automated rendering with live/often-updated data - -### 3D/Gamedev -- PBR procedural material authorship -- 3D model UV map texturing - -### AI-assisted tools - -### HDR processing - -### 360° and panoramic stitching and spherical editing - -## User stories - -Example user workflows are categorized by discipline below. - -### Photography -- Using a face detection node to sort photos into the correct folders upon export -- Using a face detection node to place a watermark near every face to prevent customers from cropping out watermarks placed in less important areas of some photos -- Shadow removal from part of an image by masking the location of a shadow and using the texture details of the darker area and the lighting and color context of the illuminated area -- Lightening or contextually infilling all the areas where a camera sensor has dust specks -- Isolating clipping highlights (that have expanded into neighboring pixels) in one or more color channels from point light sources like city lights and rendering smoother bright gradient point lights in place of them -- Advanced, intuitive blending with a node that lets you create a "custom blend mode" by specifying something like "Anything that's white: display as is. Anything else further from white by luminosity: fade out the saturation and opacity." - -### Image editing -- Removing translucent watermarks that were applied in the same location to a batch of photos by finding their shared similarities and differences and using that as a subtraction diff - -### Game development -- Design a GUI for the game and use Graphene to render the in-game GUI textures at runtime at the desired resolution without scaling problems, or even render it live as data updates its state -- Authoring procedural noise-based textures and PBR materials - -### Data visualization -- Creating a chart from a CSV -- Rendering an always-up-to-date chart powered by real-time updates from a database -- Data-driven infographics like an org chart that can be updated with text instead of manual design work -- Rendering a timelapse video of every operation done in the history of a document - -### Digital painting -- Creating a digital acrylic or oil painting using various brushes -- Preventing mixing/smearing of previous wet paint layers by drying it with a hair dryer tool -- Smearing wet paint colors together on a simulated paint palette and then sampling paint colors from that palette to paint with - -### Graphic design -- Prototyping cards for board games fed with data in a spreadsheet which generates the cards from a template -- Creating an image that has been shredded but pieced back together, where the image can be updated then return to the shredded one without having to redo the editing steps to shred it - -### Broadcast, interactive exhibits, and digital signage -- Rendering overlays for live streams or television broadcasts based on live input data, for example somebody donates and leaves a comment on a live stream and this web hook could trigger an animated display containing the user and their comment, or live telemetry for a rocket launch streams in and gets rendered as graphical overlays for a webcast. -- Rendering a custom live clockface with hour/minute/second hands based on an input of the current time, then showing them fullscreen on a display -- Request the weather from an API and render live visualizations which gets displayed on a monitor in your house or a museum (export to a Windows screen saver?) -- Data from sensors can render interactive 2D graphics at a museum art installation -- A storefront can have a monitor set up showing daily or hourly sales items based on web hooks or polling from the company’s website -- Polling an API for content like Twitter or an RSS feed and displaying the tweet or headline when it arrives on screen, styled as desired - -### Print and publishing -- Formatting Markdown documents into PDF print layouts -- Laying out book covers for proper PDF export to a printer -- Typesetting and formatting all the interior pages of a book with manual control where needed - -### Automation -- Laser cutter artwork processing for automating custom Etsy orders -- Running on a server to let users upload images for a custom T-shirt printing website, and it renders their graphic on the model’s shirt (or other custom printing online stores) -- Generating a PDF invoice based on data in a pipeline on a server - -### Computer vision and industrial control -- Factory line is examining its fruit for defects. In order to verify the quality, they need to enhance the contrast, automatically orient the image and correct the lighting. They then pass the results into a machine learning algorithm to classify, and sometimes need to snoop on the stream of data to manually do quality control (ImageMagick or custom Python scripts are often used for this right now) diff --git a/website/content/volunteer/guide/_unpublished/project-setup/knowing-your-tooling.md b/website/content/volunteer/guide/_unpublished/project-setup/knowing-your-tooling.md deleted file mode 100644 index 97869a2cd..000000000 --- a/website/content/volunteer/guide/_unpublished/project-setup/knowing-your-tooling.md +++ /dev/null @@ -1,20 +0,0 @@ -+++ -title = "Knowing your tooling" - -[extra] -order = 2 # Page number after chapter intro -+++ - -## First time builds - -Slower. - -## Troubleshooting - -Delete the `target`, `pkg`, `node_modules`, and `dist` directories. - -## Slow builds - -If you're seeing the terminal spend several seconds installing wasm-opt every recompilation, reinstall the exact version of `wasm-bindgen-cli` that matches the `wasm-bindgen` dependency in [`Cargo.toml`](https://github.com/GraphiteEditor/Graphite/blob/master/Cargo.toml). - -## Rust Analyzer speed tips diff --git a/website/content/volunteer/guide/codebase-overview/_index.md b/website/content/volunteer/guide/codebase-overview/_index.md index c28ac7821..c3b8cd6da 100644 --- a/website/content/volunteer/guide/codebase-overview/_index.md +++ b/website/content/volunteer/guide/codebase-overview/_index.md @@ -32,190 +32,28 @@ Graphite is built from several main software components. New developers may choo ### Frontend -*Location: `/frontend/src`* +*Location: [`/frontend/src`](https://github.com/GraphiteEditor/Graphite/tree/master/frontend/src)* -The frontend is the interface for Graphite which users see and interact with. It is built using web technologies with TypeScript and Svelte (HTML and SCSS). The frontend's philosophy is to be as lightweight and minimal as possible. It acts as the entry point for user input and then quickly hands off its work to the WebAssembly editor backend via its Wasm wrapper API. That API is written in Rust but has TypeScript bindings generated by the [wasm-bindgen](https://github.com/rustwasm/wasm-bindgen) tooling that is part of the Vite-based build chain. The frontend is built of many components that recursively form the window, panels, and widgets that make up the user interface. +The frontend is the GUI for Graphite which users see and interact with. It is built using web technologies with TypeScript and Svelte (HTML and SCSS). The frontend's philosophy is to be as lightweight and minimal as possible. It acts as the entry point for user input and then quickly hands off its work to the WebAssembly editor backend via its [Wasm wrapper API](https://github.com/GraphiteEditor/Graphite/tree/master/frontend/wasm). That API is written in Rust but has TypeScript bindings generated by the [wasm-bindgen](https://github.com/rustwasm/wasm-bindgen) tooling that is part of the Vite-based build toolchain. The frontend is built of many [components](https://github.com/GraphiteEditor/Graphite/tree/master/frontend/src/components) that recursively form the window, panels, and widgets that make up the user interface. ### Editor -*Location: `/editor`* +*Location: [`/editor`](https://github.com/GraphiteEditor/Graphite/tree/master/editor)* -The editor is the core of the Graphite application, and it's where all the business logic occurs for the tooling and user interaction. It is written in Rust and compiled to WebAssembly. At its heart is the message system described below. It is responsible for communicating with Graphene as well as handling the actual logic, state, tooling, and responsibilities of the interactive application. +[The editor](./editor-structure) is the core of the Graphite application, and it's where all the business logic occurs for the tooling and user interaction. It is written in Rust and compiled to WebAssembly. At its heart is the message system. It is responsible for communicating with Graphene as well as handling the actual logic, state, tooling, and responsibilities of the interactive application. ### Graphene -*Location: `/node-graph`* +*Location: [`/node-graph`](https://github.com/GraphiteEditor/Graphite/tree/master/node-graph)* [Graphene](../graphene/) is the node graph engine which manages and renders the documents. It is itself a programming language, where Graphene programs are compiled while being edited live by the user, and where executing the program renders the document. ## Frontend/backend communication -Frontend-to-backend communication is achieved through a thin Rust translation layer in `/frontend/wasm/src/editor_api.rs` which wraps the editor backend's Rust-based message system API and provides the TypeScript-compatible API of callable functions. These wrapper functions are compiled by [wasm-bindgen](https://github.com/rustwasm/wasm-bindgen) into autogenerated TS functions that serve as an entry point from TS into the Wasm binary. +Frontend-to-backend communication is achieved through a thin Rust translation layer in [`/frontend/wasm/src/editor_api.rs`](https://github.com/GraphiteEditor/Graphite/tree/master/frontend/wasm/src/editor_api.rs) which wraps the editor backend's Rust-based message system API and provides the TypeScript-compatible API of callable functions. These wrapper functions are compiled by [wasm-bindgen](https://github.com/rustwasm/wasm-bindgen) into autogenerated TS functions that serve as an entry point from TS into the Wasm binary. -Backend-to-frontend communication happens by sending a queue of messages to the frontend message dispatcher. After the TS has called any wrapper API function to get into backend code execution, the editor's business logic runs and queues up `FrontendMessage`s (defined in `/editor/src/messages/frontend/frontend_message.rs`) which get mapped from Rust to TS-friendly data types in `/frontend/src/messages.ts`. Various TS code subscribes to these messages by calling `subscribeJsMessage(MessageName, (messageData) => { /* callback code */ });`. - -## The message system - -The Graphite editor backend is organized into a hierarchy of subsystems, called *message handlers*, which talk to one another through message passing. Messages are pushed to the front or back of a queue and each one is processed sequentially by the backend's dispatcher. - -The dispatcher lives at the root of the application hierarchy and it owns its message handlers. Thus, Rust's restrictions on mutable borrowing are satisfied because only the dispatcher mutably borrows its message handlers, one at a time, while each message is processed. - -### Messages - -Messages are enum variants that are dispatched to perform some intended activity within their respective message handlers. Here are two `DocumentMessage` definitions: -```rs -pub enum DocumentMessage { - ... - // A message that carries one named data field - DeleteLayer { - id: NodeId, - } - // A message that carries no data - DeleteSelectedLayers, - ... -} -``` - -As shown above, additional data fields can be included with each message. But as a special case denoted by the `#[child]` attribute, that data can also be a sub-message enum, which enables hierarchical nesting of message handler subsystems. - -
-To view the hierarchical subsystem file structure: click here - - - -``` -messages -├── broadcast -│   ├── broadcast_message.rs -│   └── broadcast_message_handler.rs -├── debug -│   ├── debug_message.rs -│   └── debug_message_handler.rs -├── dialog -│   ├── dialog_message.rs -│   ├── dialog_message_handler.rs -│   ├── export_dialog -│   │   ├── export_dialog_message.rs -│   │   └── export_dialog_message_handler.rs -│   ├── new_document_dialog -│   │   ├── new_document_dialog_message.rs -│   │   └── new_document_dialog_message_handler.rs -│   └── preferences_dialog -│   ├── preferences_dialog_message.rs -│   └── preferences_dialog_message_handler.rs -├── frontend -│   └── frontend_message.rs -├── globals -│   ├── globals_message.rs -│   └── globals_message_handler.rs -├── input_mapper -│   ├── input_mapper_message.rs -│   ├── input_mapper_message_handler.rs -│   └── key_mapping -│   ├── key_mapping_message.rs -│   └── key_mapping_message_handler.rs -├── input_preprocessor -│   ├── input_preprocessor_message.rs -│   └── input_preprocessor_message_handler.rs -├── layout -│   ├── layout_message.rs -│   └── layout_message_handler.rs -├── portfolio -│   ├── document -│   │   ├── document_message.rs -│   │   ├── document_message_handler.rs -│   │   ├── graph_operation -│   │   │   ├── graph_operation_message.rs -│   │   │   └── graph_operation_message_handler.rs -│   │   ├── navigation -│   │   │   ├── navigation_message.rs -│   │   │   └── navigation_message_handler.rs -│   │   ├── node_graph -│   │   │   ├── node_graph_message.rs -│   │   │   └── node_graph_message_handler.rs -│   │   ├── overlays -│   │   │   ├── overlays_message.rs -│   │   │   └── overlays_message_handler.rs -│   │   └── properties_panel -│   │   ├── properties_panel_message.rs -│   │   └── properties_panel_message_handler.rs -│   ├── menu_bar -│   │   ├── menu_bar_message.rs -│   │   └── menu_bar_message_handler.rs -│   ├── portfolio_message.rs -│   └── portfolio_message_handler.rs -├── preferences -│   ├── preferences_message.rs -│   └── preferences_message_handler.rs -├── tool -│   ├── tool_message.rs -│   ├── tool_message_handler.rs -│   ├── tool_messages -│   │   ├── artboard_tool.rs -│   │   ├── brush_tool.rs -│   │   ├── ellipse_tool.rs -│   │   ├── eyedropper_tool.rs -│   │   ├── fill_tool.rs -│   │   ├── freehand_tool.rs -│   │   ├── gradient_tool.rs -│   │   ├── line_tool.rs -│   │   ├── navigate_tool.rs -│   │   ├── path_tool.rs -│   │   ├── pen_tool.rs -│   │   ├── polygon_tool.rs -│   │   ├── rectangle_tool.rs -│   │   ├── select_tool.rs -│   │   ├── spline_tool.rs -│   │   └── text_tool.rs -│   └── transform_layer -│   ├── transform_layer_message.rs -│   └── transform_layer_message_handler.rs -└── workspace - ├── workspace_message.rs - └── workspace_message_handler.rs -``` - -
- -By convention, regular data must be written as struct-style named fields (shown above), while a sub-message enum must be written as a tuple/newtype-style field (shown below). The `DocumentMessage` enum of the previous example is defined as a child of `PortfolioMessage` which wraps it like this: +Backend-to-frontend communication happens by sending a queue of messages to the frontend message dispatcher. After the TS has called any wrapper API function to get into backend code execution, the editor's business logic runs and queues up each [`FrontendMessage`](https://github.com/GraphiteEditor/Graphite/tree/master/editor/src/messages/frontend/frontend_message.rs) which get mapped from Rust to JavaScript data structures in [`/frontend/src/messages.ts`](https://github.com/GraphiteEditor/Graphite/tree/master/frontend/src/messages.ts). Various TS code subscribes to these messages by calling: ```rs -pub enum PortfolioMessage { - ... - // A message that carries the `DocumentMessage` child enum as data - #[child] - Document(DocumentMessage), - ... -} +subscribeJsMessage(MessageName, (messageData) => { /* callback code */ }); ``` - -Likewise, the `PortfolioMessage` enum is wrapped by the top-level `Message` enum. The dispatcher operates on the queue of these base-level `Message` types. - -So for example, the `DeleteSelectedLayers` message mentioned previously will look like this as a `Message` data type: - -```rs -Message::Portfolio( - PortfolioMessage::Document( - DocumentMessage::DeleteSelectedLayers - ) -) -``` - -Writing out these nested message enum variants would be cumbersome, so that `#[child]` attribute shown earlier invokes a proc macro that automatically implements the `From` trait, letting you write this instead to get a `Message` data type: - -```rs -DocumentMessage::DeleteSelectedLayers.into() -``` - -Most often, this is simplified even further because the `.into()` is called for you when pushing a message to the queue with `.add()` or `.add_front()`. So this becomes as simple as: - -```rs -responses.add(DocumentMessage::DeleteSelectedLayers); -``` - -The `responses` message queue is composed of `Message` data types, and thanks to this system, child messages like `DocumentMessage::DeleteSelectedLayers` are automatically wrapped in their ancestor enum variants to become a `Message`, saving you from writing the verbose nested form. diff --git a/website/content/volunteer/guide/codebase-overview/debugging-tips.md b/website/content/volunteer/guide/codebase-overview/debugging-tips.md index 2e7933261..581f9ea15 100644 --- a/website/content/volunteer/guide/codebase-overview/debugging-tips.md +++ b/website/content/volunteer/guide/codebase-overview/debugging-tips.md @@ -2,7 +2,7 @@ title = "Debugging tips" [extra] -order = 4 # Page number after chapter intro +order = 2 # Page number after chapter intro +++ The Wasm-based editor has some unique limitations about how you are able to debug it. This page offers tips and best practices to get the most out of your problem-solving efforts. diff --git a/website/content/volunteer/guide/codebase-overview/editor-structure.md b/website/content/volunteer/guide/codebase-overview/editor-structure.md new file mode 100644 index 000000000..d07d7e23e --- /dev/null +++ b/website/content/volunteer/guide/codebase-overview/editor-structure.md @@ -0,0 +1,98 @@ ++++ +title = "Editor structure" + +[extra] +order = 1 # Page number after chapter intro +css = ["/page/developer-guide-editor-structure.css"] +js = ["/js/developer-guide-editor-structure.js"] ++++ + +The Graphite editor is the application users interact with to create documents. Its code is one Rust crate sandwiched between the frontend and Graphene, the node-based graphics engine. The main business logic of all visual editing is handled by the editor backend. When running in the browser, it is compiled to WebAssembly and passes messages to the frontend. + +## Message system + +The Graphite editor backend is organized into a hierarchy of subsystems which talk to one another through message passing. Messages are pushed to the front or back of a queue and each one is processed sequentially by the editor's dispatcher. + +The dispatcher lives at the root of the editor hierarchy and acts as the owner of all its top-level message handlers. This satisfies Rust's restrictions on mutable borrows because only the dispatcher may mutate its message handlers, one at a time, while each message is processed. + +## Editor outline + +Click to explore the outline of the editor subsystem hierarchy which forms the structure of the editor's subsystems, state, and interactions. Bookmark this page to reference it later. + +
+ +
+ +### Parts of the hierarchy + +Subsystem components + +- A *Message enum is the component of an editor subsystem that defines its message interfaces as enum variants. Messages are used for passing a request from anywhere in the application, optionally with some included data, to have a particular block of code be run by its respective message handler. + +- A *MessageHandler struct is the component of an editor subsystem that has ownership over its persistent editor state and its child message handlers for the lifetime of the application. It also defines the logic for handling each of its messages that it receives from the dispatcher. Those blocks of logic may further enqueue additional messages to be processed by itself or other message handlers during the same dispatch cycle. + +- A *MessageContext struct is the component of an editor subsystem that defines what data is made available from other subsystems when running the logic to handle a dispatched message. It is a struct that is passed to the message handler when processing a message, and it gets filled in with data (owned, borrowed, or mutably borrowed) from its parent message handler. Intermediate subsystem layers may forward data from their parent to their child contexts to make state available from further up the hierarchy. + +Sub-messages + +- A #[child] * attribute-decorated message enum variant is a special kind of message that encapsulates a nested subsystem. As with all messages, its handler has a manually written code block. But that code must call its corresponding child message handler's `process_message` method. The child message handler is a field of this parent message handler's state struct. + +`Messages` + +- A `*` message enum variant is used throughout the editor to request that a certain subsystem performs some action, potentially given some data. In that sense, it resembles a function call, but a key difference is that messages are queued up and processed sequentially in a flat order, always invoked by the dispatcher. + +## How messages work + +Messages are enum variants that are dispatched to perform some intended activity within their respective message handlers. Here are two DocumentMessage definitions: +```rs +pub enum DocumentMessage { + ... + // A message that carries one named data field + DeleteLayer { + id: NodeId, + } + // A message that carries no data + DeleteSelectedLayers, + ... +} +``` + +As shown above, additional data fields can be included with each message. But as a special case denoted by the #[child] attribute, that data can also be a sub-message enum, which enables hierarchical nesting of message handler subsystems. + +By convention, regular data must be written as struct-style named fields (shown above), while a sub-message enum must be written as a tuple/newtype-style field (shown below). The DocumentMessage enum of the previous example is defined as a child of PortfolioMessage which wraps it like this: + +```rs +pub enum PortfolioMessage { + ... + // A message that carries the `DocumentMessage` child enum as data + #[child] + Document(DocumentMessage), + ... +} +``` + +Likewise, the PortfolioMessage enum is wrapped by the top-level Message enum. The dispatcher operates on the queue of these base-level Message types. + +So for example, the `DeleteSelectedLayers` message mentioned previously will look like this as a Message data type: + +```rs +Message::Portfolio( + PortfolioMessage::Document( + DocumentMessage::DeleteSelectedLayers + ) +) +``` + +Writing out these nested message enum variants would be cumbersome, so that #[child] attribute shown earlier invokes a proc macro that automatically implements the `From` trait, letting you write this instead to get a Message data type: + +```rs +DocumentMessage::DeleteSelectedLayers.into() +``` + +Most often, this is simplified even further because the `.into()` is called for you when pushing a message to the queue with `.add()` or `.add_front()`. So this becomes as simple as: + +```rs +responses.add(DocumentMessage::DeleteSelectedLayers); +``` + +The `responses` message queue is composed of Message data types, and thanks to this system, child messages like `DocumentMessage::DeleteSelectedLayers` are automatically wrapped in their ancestor enum variants to become a Message, saving you from writing the verbose nested form. diff --git a/website/other/editor-structure/generate.js b/website/other/editor-structure/generate.js new file mode 100644 index 000000000..b9ce6d996 --- /dev/null +++ b/website/other/editor-structure/generate.js @@ -0,0 +1,120 @@ +const fs = require("fs"); +const path = require("path"); + +/** + * Escapes characters that have special meaning in HTML. + * @param {string} text The text to escape. + * @returns {string} The escaped text. + */ +function escapeHtml(text) { + return text.replace(//g, ">"); +} + +/** + * Parses a single line of the input text. + * @param {string} line The line to parse. + * @returns {{ level: number, text: string, link: string | undefined }} + */ +function parseLine(line) { + const linkRegex = /`([^`]+)`$/; + const linkMatch = line.match(linkRegex); + let link = undefined; + + if (linkMatch) { + const filePath = linkMatch[1].replace(/\\/g, "/"); + link = `https://github.com/GraphiteEditor/Graphite/blob/master/${filePath}`; + } + + const textContent = line.replace(/^[\s│├└─]*/, "").replace(linkRegex, "").trim(); + const indentation = line.indexOf(textContent); + // Each level of indentation is 4 characters. + const level = Math.floor(indentation / 4); + + return { level, text: textContent, link }; +} + +/** + * Recursively builds the HTML list from the parsed nodes. + * @param {Array} nodes The array of parsed node objects. + * @param {number} currentIndex The current index in the nodes array. + * @param {number} currentLevel The current indentation level. + * @returns {{html: string, nextIndex: number}} + */ +function buildHtmlList(nodes, currentIndex, currentLevel) { + if (currentIndex >= nodes.length) { + return { html: "", nextIndex: currentIndex }; + } + + let html = "
    \n"; + let i = currentIndex; + + while (i < nodes.length && nodes[i].level >= currentLevel) { + const node = nodes[i]; + + if (node.level > currentLevel) { + // This case handles malformed input, skip to next valid line + i++; + continue; + } + + const hasChildren = (i + 1 < nodes.length) && (nodes[i + 1].level > node.level); + const linkHtml = node.link ? `${path.basename(node.link)}` : ""; + const fieldPieces = node.text.match(/([^:]*):(.*)/); + const partOfMessageFromNamingConvention = ["Message", "MessageHandler", "MessageContext"].some((suffix) => node.text.replace(/(.*)<.*>/g, "$1").endsWith(suffix)); + const partOfMessageViolatesNamingConvention = node.link && !partOfMessageFromNamingConvention; + const partOfMessage = node.link ? "subsystem" : ""; + const messageParent = (hasChildren && !node.link) ? " submessage": ""; + const violatesNamingConvention = partOfMessageViolatesNamingConvention ? "(violates naming convention — should end with 'Message', 'MessageHandler', or 'MessageContext')" : ""; + let escapedText; + if (fieldPieces && fieldPieces.length === 3) { + escapedText = [escapeHtml(fieldPieces[1].trim()), escapeHtml(fieldPieces[2].trim())]; + } else { + escapedText = [escapeHtml(node.text)]; + } + + if (hasChildren) { + html += `
  • ${escapedText}${linkHtml}${violatesNamingConvention}`; + const childResult = buildHtmlList(nodes, i + 1, node.level + 1); + html += `
    ${childResult.html}
  • \n`; + i = childResult.nextIndex; + } else if (escapedText.length === 2) { + html += `
  • ${escapedText[0]}: ${escapedText[1]}${linkHtml}
  • \n`; + i++; + } else { + html += `
  • ${escapedText[0]}${linkHtml}${violatesNamingConvention}
  • \n`; + i++; + } + } + + html += "
\n"; + return { html, nextIndex: i }; +} + +const inputFile = process.argv[2]; +const outputFile = process.argv[3]; + +if (!inputFile || !outputFile) { + console.error("Error: Please provide the input text and output HTML file paths as arguments."); + console.log("Usage: node generate.js "); + process.exit(1); +} + +if (!fs.existsSync(inputFile)) { + console.error(`Error: File not found at "${inputFile}"`); + process.exit(1); +} + +try { + const fileContent = fs.readFileSync(inputFile, "utf-8"); + const lines = fileContent.split(/\r?\n/).filter(line => line.trim() !== "" && !line.startsWith("// filepath:")); + const parsedNodes = lines.map(parseLine); + + const { html } = buildHtmlList(parsedNodes, 0, 0); + + fs.writeFileSync(outputFile, html, "utf-8"); + + console.log(`Successfully generated HTML outline at: ${outputFile}`); +} catch (error) { + console.error("An error occurred during processing:", error); + process.exit(1); +} diff --git a/website/sass/base.scss b/website/sass/base.scss index d938d26a9..786f22d37 100644 --- a/website/sass/base.scss +++ b/website/sass/base.scss @@ -11,17 +11,17 @@ --color-walnut: #473a3a; --color-slate: #3a4047; --color-crimson: #803847; - --color-lilac: #cabdc8; + --color-lilac: #e5e0eb; // --color-lime: #c5e0af; --color-lemon: #efe2b2; --color-peach: #ebb29f; --color-ale: #cd8f7a; - // --color-flamingo: #d2697c; + --color-flamingo: #d2697c; --color-seaside: #b0d6cb; --color-seaside-rgb: 176, 214, 203; // --color-cove: #83c0b9; // --color-sage: #91b99a; - // --color-storm: #627088; + --color-storm: #495875; --max-width: 1200px; --max-width-plus-padding: calc(var(--max-width) + 40px * 2); @@ -313,7 +313,6 @@ body > .page { } details[open] summary::before { - // content: "▼"; transform: rotate(90deg); } } diff --git a/website/sass/page/developer-guide-editor-structure.scss b/website/sass/page/developer-guide-editor-structure.scss new file mode 100644 index 000000000..3e649aa69 --- /dev/null +++ b/website/sass/page/developer-guide-editor-structure.scss @@ -0,0 +1,106 @@ +.structure-outline { + font-family: monospace; + font-size: 18px; + line-height: 1.5; + margin-top: 20px; + + ul { + list-style-type: none; + padding-left: 20px; + + li { + margin-top: 0; + + span { + line-height: 1.5; + display: inline-block; + } + } + } + + .tree-node { + padding-left: calc(10px + 8px); + position: relative; + user-select: none; + cursor: pointer; + + &::before { + content: ""; + background: url('data:image/svg+xml;utf8,\ + \ + '); + position: absolute; + margin: auto; + top: 0; + bottom: 0; + left: 0; + width: 10px; + height: 10px; + } + + &.expanded::before { + transform: rotate(90deg); + } + + a { + margin-left: 12px; + color: var(--color-crimson); + font-size: 12px; + font-family: Arial, sans-serif; + position: relative; + + &:hover::after { + content: "↗"; + margin-left: 4px; + position: absolute; + } + } + } + + .tree-leaf { + margin-left: calc(10px + 8px); + + &.field { + padding-left: 4px; + color: var(--color-storm); + } + + &:not(.field) { + padding: 0 4px; + background: var(--color-fog); + } + } + + .nested { + display: none; + } + + .active { + display: block; + } + + .warn { + margin-left: 12px; + color: var(--color-flamingo); + font-family: Arial, sans-serif; + font-size: 12px; + text-decoration: none; + font-style: italic; + } +} + +.subsystem, +.submessage { + font-family: monospace; + line-height: 1.5; + padding: 0 4px; +} + +.subsystem { + color: #ffffff; + background: var(--color-storm); +} + +.submessage { + background: var(--color-lilac); +} diff --git a/website/static/js/developer-guide-editor-structure.js b/website/static/js/developer-guide-editor-structure.js new file mode 100644 index 000000000..917685665 --- /dev/null +++ b/website/static/js/developer-guide-editor-structure.js @@ -0,0 +1,17 @@ +document.addEventListener("DOMContentLoaded", () => { + document.querySelectorAll(".tree-node").forEach((toggle) => { + toggle.addEventListener("click", (event) => { + // Prevent link click from also toggling parent + if (event.target.tagName === "A") return; + + const nestedList = toggle.parentElement.querySelector(".nested"); + if (nestedList) { + toggle.classList.toggle("expanded"); + nestedList.classList.toggle("active"); + } + }); + }); + + // Expand the first level by default + document.querySelector(".structure-outline > ul > li > .tree-node")?.click(); +}); diff --git a/website/templates/base.html b/website/templates/base.html index d3ed68c49..29c6c1fa9 100644 --- a/website/templates/base.html +++ b/website/templates/base.html @@ -36,9 +36,9 @@ {%- set global_linked_css = [] -%} {%- set global_js = ["/js/text-justification.js", "/js/navbar.js"] -%} {%- set global_css = ["/base.css", "/fonts/common.css"] -%} - {%- set fonts_loaded = load_data(path="static/fonts/common.css", format="plain", required=false) -%} + {%- set fonts_loaded = load_data(path = "static/fonts/common.css", format = "plain", required = false) -%} {%- if not fonts_loaded -%} - {{ throw(message="------------------------------------------------------------> FONTS ARE NOT INSTALLED! Before running Zola, execute `npm run install-fonts` from the `/website` directory.") }} + {{ throw(message = "------------------------------------------------------------> FONTS ARE NOT INSTALLED! Before running Zola, execute `npm run install-fonts` from the `/website` directory.") }} {%- endif -%} {#- RETRIEVE FROM TEMPLATES AND PAGES: CSS AND JS TO LOAD EITHER AS A LINK OR INLINE -#} @@ -125,9 +125,11 @@
{%- filter replace(from = "", to = replacements::blog_posts(count = 2)) -%} {%- filter replace(from = "", to = replacements::text_balancer()) -%} + {%- filter replace(from = "", to = replacements::hierarchical_message_system_tree()) -%} {%- block content -%}{%- endblock -%} {%- endfilter -%} {%- endfilter -%} + {%- endfilter -%}