From ddfd7db0a0b6bc51448573bf8e2896ef42874e21 Mon Sep 17 00:00:00 2001 From: Keavon Chambers Date: Fri, 5 Aug 2022 05:38:11 -0700 Subject: [PATCH] Restructure the entire editor codebase to consistently match the message hierarchy Closes #744 --- bezier-rs/lib/src/subpath/core.rs | 4 +- .../graphite-test-document.graphite | 0 editor/src/application.rs | 83 + editor/src/communication/broadcast_message.rs | 26 - .../broadcast_message_handler.rs | 27 - editor/src/communication/message_handler.rs | 22 - editor/src/communication/mod.rs | 34 - editor/src/consts.rs | 4 +- editor/src/dialog/dialogs/mod.rs | 15 - editor/src/dialog/mod.rs | 17 - editor/src/{communication => }/dispatcher.rs | 69 +- editor/src/document/mod.rs | 62 - editor/src/input/input_mapper.rs | 511 ------ editor/src/input/input_preprocessor.rs | 116 -- editor/src/input/mod.rs | 20 - editor/src/layout/mod.rs | 5 - editor/src/layout/widgets.rs | 776 --------- editor/src/lib.rs | 103 +- editor/src/{misc => }/macros.rs | 4 +- .../src/messages/broadcast/broadcast_event.rs | 11 + .../messages/broadcast/broadcast_message.rs | 23 + .../broadcast/broadcast_message_handler.rs | 32 + editor/src/messages/broadcast/mod.rs | 9 + .../src/{ => messages}/debug/debug_message.rs | 10 +- .../debug/debug_message_handler.rs | 14 +- editor/src/{ => messages}/debug/mod.rs | 5 +- editor/src/messages/debug/utility_types.rs | 7 + .../{ => messages}/dialog/dialog_message.rs | 9 +- .../dialog/dialog_message_handler.rs | 31 +- .../export_dialog/export_dialog_message.rs | 15 + .../export_dialog_message_handler.rs} | 93 +- .../src/messages/dialog/export_dialog/mod.rs | 7 + editor/src/messages/dialog/mod.rs | 18 + .../dialog/new_document_dialog/mod.rs | 7 + .../new_document_dialog_message.rs | 14 + .../new_document_dialog_message_handler.rs} | 98 +- .../simple_dialogs/about_graphite_dialog.rs} | 12 +- .../close_all_documents_dialog.rs | 10 +- .../simple_dialogs}/close_document_dialog.rs | 14 +- .../simple_dialogs}/coming_soon_dialog.rs | 10 +- .../dialog/simple_dialogs}/error_dialog.rs | 10 +- .../src/messages/dialog/simple_dialogs/mod.rs | 11 + .../frontend/frontend_message.rs | 13 +- editor/src/{ => messages}/frontend/mod.rs | 4 +- .../{ => messages}/frontend/utility_types.rs | 27 +- .../messages/input_mapper/default_mapping.rs | 387 +++++ .../input_mapper}/input_mapper_message.rs | 4 +- .../input_mapper_message_handler.rs | 33 +- editor/src/messages/input_mapper/mod.rs | 10 + .../utility_types/input_keyboard.rs} | 25 +- .../utility_types/input_mouse.rs} | 4 +- .../input_mapper/utility_types/macros.rs} | 8 + .../input_mapper/utility_types/misc.rs | 148 ++ .../input_mapper/utility_types/mod.rs | 4 + .../input_preprocessor_message.rs | 7 +- .../input_preprocessor_message_handler.rs | 105 +- editor/src/messages/input_preprocessor/mod.rs | 7 + editor/src/messages/layout/layout_message.rs | 14 + .../layout/layout_message_handler.rs | 140 +- editor/src/messages/layout/mod.rs | 9 + .../layout/utility_types/layout_widget.rs | 292 ++++ .../layout/utility_types/misc.rs} | 12 - .../src/messages/layout/utility_types/mod.rs | 3 + .../utility_types/widgets/button_widgets.rs | 55 + .../utility_types/widgets/input_widgets.rs | 268 +++ .../utility_types/widgets/label_widgets.rs | 56 + .../utility_types/widgets/menu_widgets.rs | 134 ++ .../layout/utility_types/widgets/mod.rs | 4 + .../{communication => messages}/message.rs | 13 +- editor/src/messages/mod.rs | 14 + .../document/artboard}/artboard_message.rs | 3 +- .../artboard}/artboard_message_handler.rs | 20 +- .../portfolio/document/artboard/mod.rs | 7 + .../portfolio}/document/document_message.rs | 10 +- .../document/document_message_handler.rs | 1485 ++++++++--------- editor/src/messages/portfolio/document/mod.rs | 14 + .../portfolio/document/movement/mod.rs | 7 + .../document/movement}/movement_message.rs | 5 +- .../movement}/movement_message_handler.rs | 151 +- .../portfolio/document/overlays/mod.rs | 7 + .../document/overlays}/overlays_message.rs | 2 +- .../overlays}/overlays_message_handler.rs | 5 +- .../document/properties_panel/mod.rs | 10 + .../properties_panel_message.rs | 20 +- .../properties_panel_message_handler.rs | 166 ++ .../properties_panel/utility_functions.rs} | 350 ++-- .../properties_panel/utility_types.rs | 23 + .../portfolio/document/transform_layer/mod.rs | 7 + .../transform_layer_message.rs | 7 +- .../transform_layer_message_handler.rs | 22 +- .../document/utility_types}/clipboards.rs | 0 .../document/utility_types}/error.rs | 0 .../document/utility_types}/layer_panel.rs | 102 +- .../portfolio/document/utility_types/misc.rs} | 3 +- .../portfolio/document/utility_types/mod.rs | 6 + .../document/utility_types}/transformation.rs | 5 +- .../vectorize_layer_metadata.rs | 0 .../portfolio/menu_bar}/menu_bar_message.rs | 3 +- .../menu_bar}/menu_bar_message_handler.rs | 58 +- editor/src/messages/portfolio/menu_bar/mod.rs | 7 + editor/src/messages/portfolio/mod.rs | 10 + .../portfolio}/portfolio_message.rs | 6 +- .../portfolio}/portfolio_message_handler.rs | 191 ++- editor/src/messages/prelude.rs | 46 + .../tool/common_functionality}/mod.rs | 3 + .../common_functionality}/overlay_renderer.rs | 17 +- .../common_functionality}/path_outline.rs | 5 +- .../tool/common_functionality}/resize.rs | 23 +- .../common_functionality}/shape_editor.rs | 10 +- .../tool/common_functionality}/snapping.rs | 52 +- .../transformation_cage.rs | 9 +- editor/src/messages/tool/mod.rs | 11 + .../tool}/tool_message.rs | 4 +- .../tool}/tool_message_handler.rs | 136 +- .../tool/tool_messages}/artboard_tool.rs | 75 +- .../tool/tool_messages}/ellipse_tool.rs | 28 +- .../tool/tool_messages}/eyedropper_tool.rs | 26 +- .../tool/tool_messages}/fill_tool.rs | 26 +- .../tool/tool_messages}/freehand_tool.rs | 30 +- .../tool/tool_messages}/gradient_tool.rs | 56 +- .../tool/tool_messages}/line_tool.rs | 50 +- .../tool/tool_messages}/mod.rs | 1 - .../tool/tool_messages}/navigate_tool.rs | 26 +- .../tool/tool_messages}/path_tool.rs | 44 +- .../tool/tool_messages}/pen_tool.rs | 52 +- .../tool/tool_messages}/rectangle_tool.rs | 28 +- .../tool/tool_messages}/select_tool.rs | 65 +- .../tool/tool_messages}/shape_tool.rs | 31 +- .../tool/tool_messages}/spline_tool.rs | 46 +- .../tool/tool_messages}/text_tool.rs | 35 +- .../tool/utility_types.rs} | 195 ++- editor/src/{ => messages}/workspace/mod.rs | 0 .../workspace/workspace_message.rs | 2 +- .../workspace/workspace_message_handler.rs | 5 +- editor/src/misc/build_metadata.rs | 31 - editor/src/misc/derivable_custom_traits.rs | 18 - editor/src/misc/hints.rs | 31 - editor/src/misc/mod.rs | 12 - editor/src/{misc => }/test_utils.rs | 10 +- editor/src/utility_traits.rs | 51 + editor/src/viewport_tools/mod.rs | 35 - .../viewport_tools/vector_editor/constants.rs | 4 - .../src/viewport_tools/vector_editor/mod.rs | 6 - frontend/wasm/src/editor_api.rs | 17 +- frontend/wasm/src/helpers.rs | 4 +- frontend/wasm/src/lib.rs | 2 +- graphene/src/layers/text_layer/to_path.rs | 2 +- .../layers/vector/{constants.rs => consts.rs} | 1 + .../src/layers/vector/manipulator_group.rs | 4 +- .../src/layers/vector/manipulator_point.rs | 2 +- graphene/src/layers/vector/mod.rs | 2 +- graphene/src/layers/vector/subpath.rs | 2 +- graphene/src/operation.rs | 2 +- proc-macros/src/lib.rs | 10 +- 154 files changed, 4199 insertions(+), 4064 deletions(-) rename editor/{src/communication => }/graphite-test-document.graphite (100%) create mode 100644 editor/src/application.rs delete mode 100644 editor/src/communication/broadcast_message.rs delete mode 100644 editor/src/communication/broadcast_message_handler.rs delete mode 100644 editor/src/communication/message_handler.rs delete mode 100644 editor/src/communication/mod.rs delete mode 100644 editor/src/dialog/dialogs/mod.rs delete mode 100644 editor/src/dialog/mod.rs rename editor/src/{communication => }/dispatcher.rs (90%) delete mode 100644 editor/src/document/mod.rs delete mode 100644 editor/src/input/input_mapper.rs delete mode 100644 editor/src/input/input_preprocessor.rs delete mode 100644 editor/src/input/mod.rs delete mode 100644 editor/src/layout/mod.rs delete mode 100644 editor/src/layout/widgets.rs rename editor/src/{misc => }/macros.rs (86%) create mode 100644 editor/src/messages/broadcast/broadcast_event.rs create mode 100644 editor/src/messages/broadcast/broadcast_message.rs create mode 100644 editor/src/messages/broadcast/broadcast_message_handler.rs create mode 100644 editor/src/messages/broadcast/mod.rs rename editor/src/{ => messages}/debug/debug_message.rs (62%) rename editor/src/{ => messages}/debug/debug_message_handler.rs (71%) rename editor/src/{ => messages}/debug/mod.rs (80%) create mode 100644 editor/src/messages/debug/utility_types.rs rename editor/src/{ => messages}/dialog/dialog_message.rs (74%) rename editor/src/{ => messages}/dialog/dialog_message_handler.rs (78%) create mode 100644 editor/src/messages/dialog/export_dialog/export_dialog_message.rs rename editor/src/{dialog/dialogs/export_dialog.rs => messages/dialog/export_dialog/export_dialog_message_handler.rs} (72%) create mode 100644 editor/src/messages/dialog/export_dialog/mod.rs create mode 100644 editor/src/messages/dialog/mod.rs create mode 100644 editor/src/messages/dialog/new_document_dialog/mod.rs create mode 100644 editor/src/messages/dialog/new_document_dialog/new_document_dialog_message.rs rename editor/src/{dialog/dialogs/new_document_dialog.rs => messages/dialog/new_document_dialog/new_document_dialog_message_handler.rs} (70%) rename editor/src/{dialog/dialogs/about_dialog.rs => messages/dialog/simple_dialogs/about_graphite_dialog.rs} (75%) rename editor/src/{dialog/dialogs => messages/dialog/simple_dialogs}/close_all_documents_dialog.rs (74%) rename editor/src/{dialog/dialogs => messages/dialog/simple_dialogs}/close_document_dialog.rs (70%) rename editor/src/{dialog/dialogs => messages/dialog/simple_dialogs}/coming_soon_dialog.rs (77%) rename editor/src/{dialog/dialogs => messages/dialog/simple_dialogs}/error_dialog.rs (67%) create mode 100644 editor/src/messages/dialog/simple_dialogs/mod.rs rename editor/src/{ => messages}/frontend/frontend_message.rs (87%) rename editor/src/{ => messages}/frontend/mod.rs (100%) rename editor/src/{ => messages}/frontend/utility_types.rs (63%) create mode 100644 editor/src/messages/input_mapper/default_mapping.rs rename editor/src/{input => messages/input_mapper}/input_mapper_message.rs (77%) rename editor/src/{input => messages/input_mapper}/input_mapper_message_handler.rs (88%) create mode 100644 editor/src/messages/input_mapper/mod.rs rename editor/src/{input/keyboard.rs => messages/input_mapper/utility_types/input_keyboard.rs} (90%) rename editor/src/{input/mouse.rs => messages/input_mapper/utility_types/input_mouse.rs} (97%) rename editor/src/{input/input_mapper_macros.rs => messages/input_mapper/utility_types/macros.rs} (95%) create mode 100644 editor/src/messages/input_mapper/utility_types/misc.rs create mode 100644 editor/src/messages/input_mapper/utility_types/mod.rs rename editor/src/{input => messages/input_preprocessor}/input_preprocessor_message.rs (79%) rename editor/src/{input => messages/input_preprocessor}/input_preprocessor_message_handler.rs (60%) create mode 100644 editor/src/messages/input_preprocessor/mod.rs create mode 100644 editor/src/messages/layout/layout_message.rs rename editor/src/{ => messages}/layout/layout_message_handler.rs (93%) create mode 100644 editor/src/messages/layout/mod.rs create mode 100644 editor/src/messages/layout/utility_types/layout_widget.rs rename editor/src/{layout/layout_message.rs => messages/layout/utility_types/misc.rs} (54%) create mode 100644 editor/src/messages/layout/utility_types/mod.rs create mode 100644 editor/src/messages/layout/utility_types/widgets/button_widgets.rs create mode 100644 editor/src/messages/layout/utility_types/widgets/input_widgets.rs create mode 100644 editor/src/messages/layout/utility_types/widgets/label_widgets.rs create mode 100644 editor/src/messages/layout/utility_types/widgets/menu_widgets.rs create mode 100644 editor/src/messages/layout/utility_types/widgets/mod.rs rename editor/src/{communication => messages}/message.rs (82%) create mode 100644 editor/src/messages/mod.rs rename editor/src/{document => messages/portfolio/document/artboard}/artboard_message.rs (92%) rename editor/src/{document => messages/portfolio/document/artboard}/artboard_message_handler.rs (92%) create mode 100644 editor/src/messages/portfolio/document/artboard/mod.rs rename editor/src/{ => messages/portfolio}/document/document_message.rs (91%) rename editor/src/{ => messages/portfolio}/document/document_message_handler.rs (94%) create mode 100644 editor/src/messages/portfolio/document/mod.rs create mode 100644 editor/src/messages/portfolio/document/movement/mod.rs rename editor/src/{document => messages/portfolio/document/movement}/movement_message.rs (88%) rename editor/src/{document => messages/portfolio/document/movement}/movement_message_handler.rs (92%) create mode 100644 editor/src/messages/portfolio/document/overlays/mod.rs rename editor/src/{document => messages/portfolio/document/overlays}/overlays_message.rs (94%) rename editor/src/{document => messages/portfolio/document/overlays}/overlays_message_handler.rs (83%) create mode 100644 editor/src/messages/portfolio/document/properties_panel/mod.rs rename editor/src/{document => messages/portfolio/document/properties_panel}/properties_panel_message.rs (77%) create mode 100644 editor/src/messages/portfolio/document/properties_panel/properties_panel_message_handler.rs rename editor/src/{document/properties_panel_message_handler.rs => messages/portfolio/document/properties_panel/utility_functions.rs} (80%) create mode 100644 editor/src/messages/portfolio/document/properties_panel/utility_types.rs create mode 100644 editor/src/messages/portfolio/document/transform_layer/mod.rs rename editor/src/{document => messages/portfolio/document/transform_layer}/transform_layer_message.rs (69%) rename editor/src/{document => messages/portfolio/document/transform_layer}/transform_layer_message_handler.rs (87%) rename editor/src/{document => messages/portfolio/document/utility_types}/clipboards.rs (100%) rename editor/src/{misc => messages/portfolio/document/utility_types}/error.rs (100%) rename editor/src/{document => messages/portfolio/document/utility_types}/layer_panel.rs (52%) rename editor/src/{document/utility_types.rs => messages/portfolio/document/utility_types/misc.rs} (96%) create mode 100644 editor/src/messages/portfolio/document/utility_types/mod.rs rename editor/src/{document => messages/portfolio/document/utility_types}/transformation.rs (98%) rename editor/src/{document => messages/portfolio/document/utility_types}/vectorize_layer_metadata.rs (100%) rename editor/src/{document => messages/portfolio/menu_bar}/menu_bar_message.rs (82%) rename editor/src/{document => messages/portfolio/menu_bar}/menu_bar_message_handler.rs (79%) create mode 100644 editor/src/messages/portfolio/menu_bar/mod.rs create mode 100644 editor/src/messages/portfolio/mod.rs rename editor/src/{document => messages/portfolio}/portfolio_message.rs (89%) rename editor/src/{document => messages/portfolio}/portfolio_message_handler.rs (93%) create mode 100644 editor/src/messages/prelude.rs rename editor/src/{viewport_tools/tools/shared => messages/tool/common_functionality}/mod.rs (50%) rename editor/src/{viewport_tools/vector_editor => messages/tool/common_functionality}/overlay_renderer.rs (96%) rename editor/src/{viewport_tools/tools/shared => messages/tool/common_functionality}/path_outline.rs (97%) rename editor/src/{viewport_tools/tools/shared => messages/tool/common_functionality}/resize.rs (69%) rename editor/src/{viewport_tools/vector_editor => messages/tool/common_functionality}/shape_editor.rs (97%) rename editor/src/{viewport_tools => messages/tool/common_functionality}/snapping.rs (98%) rename editor/src/{viewport_tools/tools/shared => messages/tool/common_functionality}/transformation_cage.rs (97%) create mode 100644 editor/src/messages/tool/mod.rs rename editor/src/{viewport_tools => messages/tool}/tool_message.rs (97%) rename editor/src/{viewport_tools => messages/tool}/tool_message_handler.rs (51%) rename editor/src/{viewport_tools/tools => messages/tool/tool_messages}/artboard_tool.rs (84%) rename editor/src/{viewport_tools/tools => messages/tool/tool_messages}/ellipse_tool.rs (83%) rename editor/src/{viewport_tools/tools => messages/tool/tool_messages}/eyedropper_tool.rs (81%) rename editor/src/{viewport_tools/tools => messages/tool/tool_messages}/fill_tool.rs (80%) rename editor/src/{viewport_tools/tools => messages/tool/tool_messages}/freehand_tool.rs (84%) rename editor/src/{viewport_tools/tools => messages/tool/tool_messages}/gradient_tool.rs (88%) rename editor/src/{viewport_tools/tools => messages/tool/tool_messages}/line_tool.rs (81%) rename editor/src/{viewport_tools/tools => messages/tool/tool_messages}/mod.rs (95%) rename editor/src/{viewport_tools/tools => messages/tool/tool_messages}/navigate_tool.rs (87%) rename editor/src/{viewport_tools/tools => messages/tool/tool_messages}/path_tool.rs (88%) rename editor/src/{viewport_tools/tools => messages/tool/tool_messages}/pen_tool.rs (89%) rename editor/src/{viewport_tools/tools => messages/tool/tool_messages}/rectangle_tool.rs (83%) rename editor/src/{viewport_tools/tools => messages/tool/tool_messages}/select_tool.rs (92%) rename editor/src/{viewport_tools/tools => messages/tool/tool_messages}/shape_tool.rs (83%) rename editor/src/{viewport_tools/tools => messages/tool/tool_messages}/spline_tool.rs (81%) rename editor/src/{viewport_tools/tools => messages/tool/tool_messages}/text_tool.rs (90%) rename editor/src/{viewport_tools/tool.rs => messages/tool/utility_types.rs} (63%) rename editor/src/{ => messages}/workspace/mod.rs (100%) rename editor/src/{ => messages}/workspace/workspace_message.rs (87%) rename editor/src/{ => messages}/workspace/workspace_message_handler.rs (74%) delete mode 100644 editor/src/misc/build_metadata.rs delete mode 100644 editor/src/misc/derivable_custom_traits.rs delete mode 100644 editor/src/misc/hints.rs delete mode 100644 editor/src/misc/mod.rs rename editor/src/{misc => }/test_utils.rs (90%) create mode 100644 editor/src/utility_traits.rs delete mode 100644 editor/src/viewport_tools/mod.rs delete mode 100644 editor/src/viewport_tools/vector_editor/constants.rs delete mode 100644 editor/src/viewport_tools/vector_editor/mod.rs rename graphene/src/layers/vector/{constants.rs => consts.rs} (99%) diff --git a/bezier-rs/lib/src/subpath/core.rs b/bezier-rs/lib/src/subpath/core.rs index 359c010469..7dc43f3588 100644 --- a/bezier-rs/lib/src/subpath/core.rs +++ b/bezier-rs/lib/src/subpath/core.rs @@ -5,9 +5,9 @@ use crate::consts::*; impl Subpath { /// Create a new `Subpath` using a list of [ManipulatorGroup]s. /// A `Subpath` with less than 2 [ManipulatorGroup]s may not be closed. - pub fn new(manipulator_groups: Vec, closed: bool) -> Subpath { + pub fn new(manipulator_groups: Vec, closed: bool) -> Self { assert!(!closed || manipulator_groups.len() > 1, "A closed Subpath must contain more than 1 ManipulatorGroup."); - Subpath { manipulator_groups, closed } + Self { manipulator_groups, closed } } /// Create a `Subpath` consisting of 2 manipulator groups from a `Bezier`. diff --git a/editor/src/communication/graphite-test-document.graphite b/editor/graphite-test-document.graphite similarity index 100% rename from editor/src/communication/graphite-test-document.graphite rename to editor/graphite-test-document.graphite diff --git a/editor/src/application.rs b/editor/src/application.rs new file mode 100644 index 0000000000..86621321b8 --- /dev/null +++ b/editor/src/application.rs @@ -0,0 +1,83 @@ +use crate::dispatcher::Dispatcher; +use crate::messages::prelude::*; + +use rand_chacha::rand_core::{RngCore, SeedableRng}; +use rand_chacha::ChaCha20Rng; +use spin::Mutex; +use std::cell::Cell; + +static RNG: Mutex> = Mutex::new(None); +thread_local! { + pub static UUID_SEED: Cell> = Cell::new(None); +} + +// TODO: serialize with serde to save the current editor state +pub struct Editor { + pub dispatcher: Dispatcher, +} + +impl Editor { + /// Construct a new editor instance. + /// Remember to provide a random seed with `editor::set_uuid_seed(seed)` before any editors can be used. + pub fn new() -> Self { + Self { dispatcher: Dispatcher::new() } + } + + pub fn handle_message>(&mut self, message: T) -> Vec { + self.dispatcher.handle_message(message); + + let mut responses = Vec::new(); + std::mem::swap(&mut responses, &mut self.dispatcher.responses); + + responses + } +} + +impl Default for Editor { + fn default() -> Self { + Self::new() + } +} + +pub fn set_uuid_seed(random_seed: u64) { + UUID_SEED.with(|seed| seed.set(Some(random_seed))) +} + +pub fn generate_uuid() -> u64 { + let mut lock = RNG.lock(); + if lock.is_none() { + UUID_SEED.with(|seed| { + let random_seed = seed.get().expect("Random seed not set before editor was initialized"); + *lock = Some(ChaCha20Rng::seed_from_u64(random_seed)); + }) + } + lock.as_mut().map(ChaCha20Rng::next_u64).unwrap() +} + +pub fn release_series() -> String { + format!("Release Series: {}", env!("GRAPHITE_RELEASE_SERIES")) +} + +pub fn commit_info() -> String { + format!("{}\n{}\n{}", commit_timestamp(), commit_hash(), commit_branch()) +} + +pub fn commit_info_localized(localized_commit_date: &str) -> String { + format!("{}\n{}\n{}", commit_timestamp_localized(localized_commit_date), commit_hash(), commit_branch()) +} + +pub fn commit_timestamp() -> String { + format!("Date: {}", env!("GRAPHITE_GIT_COMMIT_DATE")) +} + +pub fn commit_timestamp_localized(localized_commit_date: &str) -> String { + format!("Date: {}", localized_commit_date) +} + +pub fn commit_hash() -> String { + format!("Hash: {}", &env!("GRAPHITE_GIT_COMMIT_HASH")[..8]) +} + +pub fn commit_branch() -> String { + format!("Branch: {}", env!("GRAPHITE_GIT_COMMIT_BRANCH")) +} diff --git a/editor/src/communication/broadcast_message.rs b/editor/src/communication/broadcast_message.rs deleted file mode 100644 index ac542fdf7e..0000000000 --- a/editor/src/communication/broadcast_message.rs +++ /dev/null @@ -1,26 +0,0 @@ -use crate::message_prelude::*; - -use serde::{Deserialize, Serialize}; - -#[impl_message(Message, Broadcast)] -#[derive(PartialEq, Clone, Debug, Serialize, Deserialize)] -pub enum BroadcastMessage { - SubscribeSignal { - on: BroadcastSignal, - send: Box, - }, - UnsubscribeSignal { - on: BroadcastSignal, - message: Box, - }, - #[child] - TriggerSignal(BroadcastSignal), -} - -#[derive(PartialEq, Eq, Clone, Debug, Serialize, Deserialize, Hash)] -#[impl_message(Message, BroadcastMessage, TriggerSignal)] -pub enum BroadcastSignal { - DocumentIsDirty, - ToolAbort, - SelectionChanged, -} diff --git a/editor/src/communication/broadcast_message_handler.rs b/editor/src/communication/broadcast_message_handler.rs deleted file mode 100644 index 278c931a89..0000000000 --- a/editor/src/communication/broadcast_message_handler.rs +++ /dev/null @@ -1,27 +0,0 @@ -use crate::message_prelude::*; - -use std::collections::HashMap; - -#[derive(Debug, Clone, Default)] -pub struct BroadcastMessageHandler { - listeners: HashMap>, -} - -impl MessageHandler for BroadcastMessageHandler { - fn process_action(&mut self, action: BroadcastMessage, _data: (), responses: &mut VecDeque) { - use BroadcastMessage::*; - match action { - SubscribeSignal { on, send } => self.listeners.entry(on).or_default().push(*send), - UnsubscribeSignal { on, message } => self.listeners.entry(on).or_default().retain(|msg| *msg != *message), - TriggerSignal(signal) => { - for message in self.listeners.entry(signal).or_default() { - responses.push_front(message.clone()) - } - } - } - } - - fn actions(&self) -> ActionList { - vec![] - } -} diff --git a/editor/src/communication/message_handler.rs b/editor/src/communication/message_handler.rs deleted file mode 100644 index a45805d7c8..0000000000 --- a/editor/src/communication/message_handler.rs +++ /dev/null @@ -1,22 +0,0 @@ -pub use crate::communication::dispatcher::*; -pub use crate::input::InputPreprocessorMessageHandler; -use crate::message_prelude::*; - -use std::collections::VecDeque; - -pub type ActionList = Vec>; - -// TODO: Add Send + Sync requirement -// Use something like rw locks for synchronization -pub trait MessageHandlerData {} - -pub trait MessageHandler -where - A::Discriminant: AsMessage, - ::TopParent: TransitiveChild::TopParent, TopParent = ::TopParent> + AsMessage, -{ - /// Return true if the Action is consumed. - fn process_action(&mut self, action: A, data: T, responses: &mut VecDeque); - - fn actions(&self) -> ActionList; -} diff --git a/editor/src/communication/mod.rs b/editor/src/communication/mod.rs deleted file mode 100644 index 1009ff89b4..0000000000 --- a/editor/src/communication/mod.rs +++ /dev/null @@ -1,34 +0,0 @@ -pub mod broadcast_message; -pub mod broadcast_message_handler; -pub mod dispatcher; -pub mod message; -pub mod message_handler; - -pub use crate::communication::dispatcher::*; -pub use crate::input::InputPreprocessorMessageHandler; - -use rand_chacha::rand_core::{RngCore, SeedableRng}; -use rand_chacha::ChaCha20Rng; -use spin::Mutex; -use std::cell::Cell; - -static RNG: Mutex> = Mutex::new(None); - -thread_local! { - pub static UUID_SEED: Cell> = Cell::new(None); -} - -pub fn set_uuid_seed(random_seed: u64) { - UUID_SEED.with(|seed| seed.set(Some(random_seed))) -} - -pub fn generate_uuid() -> u64 { - let mut lock = RNG.lock(); - if lock.is_none() { - UUID_SEED.with(|seed| { - let random_seed = seed.get().expect("random seed not set before editor was initialized"); - *lock = Some(ChaCha20Rng::seed_from_u64(random_seed)); - }) - } - lock.as_mut().map(ChaCha20Rng::next_u64).unwrap() -} diff --git a/editor/src/consts.rs b/editor/src/consts.rs index 484aa5c213..2185ec1518 100644 --- a/editor/src/consts.rs +++ b/editor/src/consts.rs @@ -11,6 +11,8 @@ pub const VIEWPORT_ZOOM_LEVELS: [f64; 74] = [ 128., 160., 200., 256., 320., 400., 512., 640., 800., 1024., 1280., 1600., 2048., 2560., ]; +pub const VIEWPORT_GRID_ROUNDING_BIAS: f64 = 0.002; // Helps push values that end in approximately half, plus or minus some floating point imprecision, towards the same side of the round() function + pub const VIEWPORT_SCROLL_RATE: f64 = 0.6; pub const VIEWPORT_ROTATE_SNAP_INTERVAL: f64 = 15.; @@ -72,5 +74,5 @@ pub const DEFAULT_FONT_FAMILY: &str = "Merriweather"; pub const DEFAULT_FONT_STYLE: &str = "Normal (400)"; // Document -pub const GRAPHITE_DOCUMENT_VERSION: &str = "0.0.10"; // Remember to save a simple document and replace the test file at: editor\src\communication\graphite-test-document.graphite +pub const GRAPHITE_DOCUMENT_VERSION: &str = "0.0.10"; // Remember to save a simple document and replace the test file `graphite-test-document.graphite` pub const VIEWPORT_ZOOM_TO_FIT_PADDING_SCALE_FACTOR: f32 = 1.05; diff --git a/editor/src/dialog/dialogs/mod.rs b/editor/src/dialog/dialogs/mod.rs deleted file mode 100644 index ad15ea554b..0000000000 --- a/editor/src/dialog/dialogs/mod.rs +++ /dev/null @@ -1,15 +0,0 @@ -mod about_dialog; -mod close_all_documents_dialog; -mod close_document_dialog; -mod coming_soon_dialog; -mod error_dialog; -mod export_dialog; -mod new_document_dialog; - -pub use about_dialog::AboutGraphite; -pub use close_all_documents_dialog::CloseAllDocuments; -pub use close_document_dialog::CloseDocument; -pub use coming_soon_dialog::ComingSoon; -pub use error_dialog::Error; -pub use export_dialog::{Export, ExportDialogUpdate, ExportDialogUpdateDiscriminant}; -pub use new_document_dialog::{NewDocument, NewDocumentDialogUpdate, NewDocumentDialogUpdateDiscriminant}; diff --git a/editor/src/dialog/mod.rs b/editor/src/dialog/mod.rs deleted file mode 100644 index 91a7b2981d..0000000000 --- a/editor/src/dialog/mod.rs +++ /dev/null @@ -1,17 +0,0 @@ -//! Handles dialogs/modals/popups that appear as boxes in the centre of the editor. -//! -//! Dialogs are represented as structs that implement the [`crate::layout::widgets::PropertyHolder`] trait. -//! -//! To open a dialog, call the function `register_properties` on the dialog struct with `responses` and the `LayoutTarget::DialogDetails` -//! and then you can open the dialog with [`crate::message_prelude::FrontendMessage::DisplayDialog`] - -mod dialog_message; -mod dialog_message_handler; -mod dialogs; - -pub mod messages { - pub use super::dialog_message::{DialogMessage, DialogMessageDiscriminant}; - pub use super::dialog_message_handler::DialogMessageHandler; -} - -pub use dialogs::*; diff --git a/editor/src/communication/dispatcher.rs b/editor/src/dispatcher.rs similarity index 90% rename from editor/src/communication/dispatcher.rs rename to editor/src/dispatcher.rs index c0cf995195..6bc96e29ec 100644 --- a/editor/src/communication/dispatcher.rs +++ b/editor/src/dispatcher.rs @@ -1,13 +1,6 @@ -use super::broadcast_message_handler::BroadcastMessageHandler; use crate::consts::{DEFAULT_FONT_FAMILY, DEFAULT_FONT_STYLE}; -use crate::debug::debug_message::LoggingMessages; -use crate::debug::DebugMessageHandler; -use crate::document::PortfolioMessageHandler; -use crate::input::{InputMapperMessageHandler, InputPreprocessorMessageHandler}; -use crate::layout::layout_message_handler::LayoutMessageHandler; -use crate::message_prelude::*; -use crate::viewport_tools::tool_message_handler::ToolMessageHandler; -use crate::workspace::WorkspaceMessageHandler; +use crate::messages::debug::utility_types::MessageLoggingVerbosity; +use crate::messages::prelude::*; use graphene::layers::text_layer::Font; @@ -47,7 +40,7 @@ const SIDE_EFFECT_FREE_MESSAGES: &[MessageDiscriminant] = &[ MessageDiscriminant::Portfolio(PortfolioMessageDiscriminant::Document(DocumentMessageDiscriminant::DocumentStructureChanged)), MessageDiscriminant::Frontend(FrontendMessageDiscriminant::UpdateDocumentLayerTreeStructure), MessageDiscriminant::Frontend(FrontendMessageDiscriminant::TriggerFontLoad), - MessageDiscriminant::Broadcast(BroadcastMessageDiscriminant::TriggerSignal(BroadcastSignalDiscriminant::DocumentIsDirty)), + MessageDiscriminant::Broadcast(BroadcastMessageDiscriminant::TriggerEvent(BroadcastEventDiscriminant::DocumentIsDirty)), ]; impl Dispatcher { @@ -80,11 +73,11 @@ impl Dispatcher { if SIDE_EFFECT_FREE_MESSAGES.contains(&message.to_discriminant()) { let already_in_queue = self.message_queues.first().filter(|queue| queue.contains(&message)).is_some(); if already_in_queue { - self.log_deferred_message(&message, &self.message_queues, self.message_handlers.debug_message_handler.logging_messages_mode); + self.log_deferred_message(&message, &self.message_queues, self.message_handlers.debug_message_handler.message_logging_verbosity); self.cleanup_queues(false); continue; } else if self.message_queues.len() > 1 { - self.log_deferred_message(&message, &self.message_queues, self.message_handlers.debug_message_handler.logging_messages_mode); + self.log_deferred_message(&message, &self.message_queues, self.message_handlers.debug_message_handler.message_logging_verbosity); self.cleanup_queues(true); self.message_queues[0].push_back(message); continue; @@ -92,7 +85,7 @@ impl Dispatcher { } // Print the message at a verbosity level of `log` - self.log_message(&message, &self.message_queues, self.message_handlers.debug_message_handler.logging_messages_mode); + self.log_message(&message, &self.message_queues, self.message_handlers.debug_message_handler.message_logging_verbosity); // Create a new queue for the child messages let mut queue = VecDeque::new(); @@ -114,14 +107,14 @@ impl Dispatcher { queue.push_back(message); } - Broadcast(message) => self.message_handlers.broadcast_message_handler.process_action(message, (), &mut queue), + Broadcast(message) => self.message_handlers.broadcast_message_handler.process_message(message, (), &mut queue), Debug(message) => { - self.message_handlers.debug_message_handler.process_action(message, (), &mut queue); + self.message_handlers.debug_message_handler.process_message(message, (), &mut queue); } Dialog(message) => { self.message_handlers .dialog_message_handler - .process_action(message, &self.message_handlers.portfolio_message_handler, &mut queue); + .process_message(message, &self.message_handlers.portfolio_message_handler, &mut queue); } Frontend(message) => { // Handle these messages immediately by returning early @@ -142,12 +135,12 @@ impl Dispatcher { self.message_handlers .input_mapper_message_handler - .process_action(message, (&self.message_handlers.input_preprocessor_message_handler, keyboard_platform, actions), &mut queue); + .process_message(message, (&self.message_handlers.input_preprocessor_message_handler, keyboard_platform, actions), &mut queue); } InputPreprocessor(message) => { let keyboard_platform = self.message_handlers.portfolio_message_handler.platform.as_keyboard_platform_layout(); - self.message_handlers.input_preprocessor_message_handler.process_action(message, keyboard_platform, &mut queue); + self.message_handlers.input_preprocessor_message_handler.process_message(message, keyboard_platform, &mut queue); } Layout(message) => { let keyboard_platform = self.message_handlers.portfolio_message_handler.platform.as_keyboard_platform_layout(); @@ -155,16 +148,16 @@ impl Dispatcher { self.message_handlers .layout_message_handler - .process_action(message, (action_input_mapping, keyboard_platform), &mut queue); + .process_message(message, (action_input_mapping, keyboard_platform), &mut queue); } Portfolio(message) => { self.message_handlers .portfolio_message_handler - .process_action(message, &self.message_handlers.input_preprocessor_message_handler, &mut queue); + .process_message(message, &self.message_handlers.input_preprocessor_message_handler, &mut queue); } Tool(message) => { if let Some(document) = self.message_handlers.portfolio_message_handler.active_document() { - self.message_handlers.tool_message_handler.process_action( + self.message_handlers.tool_message_handler.process_message( message, ( document, @@ -180,7 +173,7 @@ impl Dispatcher { Workspace(message) => { self.message_handlers .workspace_message_handler - .process_action(message, &self.message_handlers.input_preprocessor_message_handler, &mut queue); + .process_message(message, &self.message_handlers.input_preprocessor_message_handler, &mut queue); } } @@ -226,14 +219,14 @@ impl Dispatcher { /// Logs a message that is about to be executed, /// either as a tree with a discriminant or the entire payload (depending on settings) - fn log_message(&self, message: &Message, queues: &[VecDeque], log_tree_contents: LoggingMessages) { + fn log_message(&self, message: &Message, queues: &[VecDeque], message_logging_verbosity: MessageLoggingVerbosity) { if !MessageDiscriminant::from(message).local_name().ends_with("PointerMove") { - match log_tree_contents { - LoggingMessages::Off => {} - LoggingMessages::Names => { + match message_logging_verbosity { + MessageLoggingVerbosity::Off => {} + MessageLoggingVerbosity::Names => { log::info!("{}{:?}", Self::create_indents(queues), message.to_discriminant()); } - LoggingMessages::Contents => { + MessageLoggingVerbosity::Contents => { if !(matches!(message, Message::InputPreprocessor(_))) { log::info!("Message: {}{:?}", Self::create_indents(queues), message); } @@ -243,8 +236,8 @@ impl Dispatcher { } /// Logs into the tree that the message is in the side effect free messages and its execution will be deferred - fn log_deferred_message(&self, message: &Message, queues: &[VecDeque], log_tree_contents: LoggingMessages) { - if let LoggingMessages::Names = log_tree_contents { + fn log_deferred_message(&self, message: &Message, queues: &[VecDeque], message_logging_verbosity: MessageLoggingVerbosity) { + if let MessageLoggingVerbosity::Names = message_logging_verbosity { log::info!("{}Deferred \"{:?}\" because it's a SIDE_EFFECT_FREE_MESSAGE", Self::create_indents(queues), message.to_discriminant()); } } @@ -252,14 +245,14 @@ impl Dispatcher { #[cfg(test)] mod test { - use crate::communication::set_uuid_seed; - use crate::document::clipboards::Clipboard; - use crate::document::DocumentMessageHandler; - use crate::message_prelude::*; - use crate::misc::test_utils::EditorTestUtils; - use crate::Editor; + use crate::application::set_uuid_seed; + use crate::application::Editor; + use crate::messages::portfolio::document::utility_types::clipboards::Clipboard; + use crate::messages::prelude::*; + use crate::test_utils::EditorTestUtils; use graphene::color::Color; + use graphene::LayerId; use graphene::Operation; fn init_logger() { @@ -546,7 +539,9 @@ mod test { /// If this test is failing take a look at `GRAPHITE_DOCUMENT_VERSION` in `editor/src/consts.rs`, it may need to be updated. /// This test will fail when you make changes to the underlying serialization format for a document. fn check_if_graphite_file_version_upgrade_is_needed() { - use crate::layout::widgets::{LayoutGroup, TextLabel, Widget}; + use crate::messages::layout::utility_types::layout_widget::{LayoutGroup, Widget}; + use crate::messages::layout::utility_types::widgets::label_widgets::TextLabel; + let print_problem_to_terminal_on_failure = |value: &String| { println!(); println!("-------------------------------------------------"); @@ -564,7 +559,7 @@ mod test { init_logger(); set_uuid_seed(0); let mut editor = Editor::new(); - let test_file = include_str!("./graphite-test-document.graphite"); + let test_file = include_str!("../graphite-test-document.graphite"); let responses = editor.handle_message(PortfolioMessage::OpenDocumentFile { document_name: "Graphite Version Test".into(), document_serialized_content: test_file.into(), diff --git a/editor/src/document/mod.rs b/editor/src/document/mod.rs deleted file mode 100644 index e2056df7fc..0000000000 --- a/editor/src/document/mod.rs +++ /dev/null @@ -1,62 +0,0 @@ -pub mod clipboards; -pub mod layer_panel; -pub mod transformation; -pub mod utility_types; -pub mod vectorize_layer_metadata; - -mod artboard_message; -mod artboard_message_handler; -mod document_message; -mod document_message_handler; -mod menu_bar_message; -mod menu_bar_message_handler; -mod movement_message; -mod movement_message_handler; -mod overlays_message; -mod overlays_message_handler; -mod portfolio_message; -mod portfolio_message_handler; -mod properties_panel_message; -mod properties_panel_message_handler; -mod transform_layer_message; -mod transform_layer_message_handler; - -#[doc(inline)] -pub use artboard_message::{ArtboardMessage, ArtboardMessageDiscriminant}; -#[doc(inline)] -pub use artboard_message_handler::ArtboardMessageHandler; - -#[doc(inline)] -pub use document_message::{DocumentMessage, DocumentMessageDiscriminant}; -#[doc(inline)] -pub use document_message_handler::DocumentMessageHandler; - -#[doc(inline)] -pub use movement_message::{MovementMessage, MovementMessageDiscriminant}; -#[doc(inline)] -pub use movement_message_handler::MovementMessageHandler; - -#[doc(inline)] -pub use menu_bar_message::{MenuBarMessage, MenuBarMessageDiscriminant}; -#[doc(inline)] -pub use menu_bar_message_handler::MenuBarMessageHandler; - -#[doc(inline)] -pub use overlays_message::{OverlaysMessage, OverlaysMessageDiscriminant}; -#[doc(inline)] -pub use overlays_message_handler::OverlaysMessageHandler; - -#[doc(inline)] -pub use portfolio_message::{PortfolioMessage, PortfolioMessageDiscriminant}; -#[doc(inline)] -pub use portfolio_message_handler::PortfolioMessageHandler; - -#[doc(inline)] -pub use properties_panel_message::{PropertiesPanelMessage, PropertiesPanelMessageDiscriminant}; -#[doc(inline)] -pub use properties_panel_message_handler::PropertiesPanelMessageHandler; - -#[doc(inline)] -pub use transform_layer_message::{TransformLayerMessage, TransformLayerMessageDiscriminant}; -#[doc(inline)] -pub use transform_layer_message_handler::TransformLayerMessageHandler; diff --git a/editor/src/input/input_mapper.rs b/editor/src/input/input_mapper.rs deleted file mode 100644 index 0320502a9e..0000000000 --- a/editor/src/input/input_mapper.rs +++ /dev/null @@ -1,511 +0,0 @@ -use super::input_mapper_macros::*; -use super::keyboard::{Key, KeyStates, NUMBER_OF_KEYS}; -use crate::consts::{BIG_NUDGE_AMOUNT, NUDGE_AMOUNT}; -use crate::document::clipboards::Clipboard; -use crate::document::utility_types::KeyboardPlatformLayout; -use crate::message_prelude::*; - -use glam::DVec2; -use serde::{Deserialize, Serialize}; - -#[derive(Debug, Clone)] -pub struct Mapping { - pub key_up: [KeyMappingEntries; NUMBER_OF_KEYS], - pub key_down: [KeyMappingEntries; NUMBER_OF_KEYS], - pub double_click: KeyMappingEntries, - pub wheel_scroll: KeyMappingEntries, - pub pointer_move: KeyMappingEntries, -} - -impl Default for Mapping { - fn default() -> Self { - use InputMapperMessage::*; - use Key::*; - - // WARNING! - // If a new mapping you added here isn't working (and perhaps another lower-precedence one is instead), make sure to advertise - // it as an available action in the respective message handler file (such as the bottom of `document_message_handler.rs`). - - let mappings = mapping![ - // HIGHER PRIORITY: - // - // MovementMessage - entry!( - PointerMove; - refresh_keys=[KeyControl], - action_dispatch=MovementMessage::PointerMove { snap_angle: KeyControl, wait_for_snap_angle_release: true, snap_zoom: KeyControl, zoom_from_viewport: None }, - ), - // NORMAL PRIORITY: - // - // TransformLayerMessage - entry!(KeyDown(KeyEnter); action_dispatch=TransformLayerMessage::ApplyTransformOperation), - entry!(KeyDown(Lmb); action_dispatch=TransformLayerMessage::ApplyTransformOperation), - entry!(KeyDown(KeyEscape); action_dispatch=TransformLayerMessage::CancelTransformOperation), - entry!(KeyDown(Rmb); action_dispatch=TransformLayerMessage::CancelTransformOperation), - entry!(KeyDown(KeyX); action_dispatch=TransformLayerMessage::ConstrainX), - entry!(KeyDown(KeyY); action_dispatch=TransformLayerMessage::ConstrainY), - entry!(KeyDown(KeyBackspace); action_dispatch=TransformLayerMessage::TypeBackspace), - entry!(KeyDown(KeyMinus); action_dispatch=TransformLayerMessage::TypeNegate), - entry!(KeyDown(KeyComma); action_dispatch=TransformLayerMessage::TypeDecimalPoint), - entry!(KeyDown(KeyPeriod); action_dispatch=TransformLayerMessage::TypeDecimalPoint), - entry!(PointerMove; refresh_keys=[KeyShift, KeyControl], action_dispatch=TransformLayerMessage::PointerMove { slow_key: KeyShift, snap_key: KeyControl }), - // SelectToolMessage - entry!(PointerMove; refresh_keys=[KeyControl, KeyShift, KeyAlt], action_dispatch=SelectToolMessage::PointerMove { axis_align: KeyShift, snap_angle: KeyControl, center: KeyAlt }), - entry!(KeyDown(Lmb); action_dispatch=SelectToolMessage::DragStart { add_to_selection: KeyShift }), - entry!(KeyUp(Lmb); action_dispatch=SelectToolMessage::DragStop), - entry!(KeyDown(KeyEnter); action_dispatch=SelectToolMessage::DragStop), - entry!(DoubleClick; action_dispatch=SelectToolMessage::EditLayer), - entry!(KeyDown(Rmb); action_dispatch=SelectToolMessage::Abort), - entry!(KeyDown(KeyEscape); action_dispatch=SelectToolMessage::Abort), - // ArtboardToolMessage - entry!(KeyDown(Lmb); action_dispatch=ArtboardToolMessage::PointerDown), - entry!(PointerMove; refresh_keys=[KeyShift, KeyAlt], action_dispatch=ArtboardToolMessage::PointerMove { constrain_axis_or_aspect: KeyShift, center: KeyAlt }), - entry!(KeyUp(Lmb); action_dispatch=ArtboardToolMessage::PointerUp), - entry!(KeyDown(KeyDelete); action_dispatch=ArtboardToolMessage::DeleteSelected), - entry!(KeyDown(KeyBackspace); action_dispatch=ArtboardToolMessage::DeleteSelected), - // NavigateToolMessage - entry!(KeyUp(Lmb); modifiers=[KeyShift], action_dispatch=NavigateToolMessage::ClickZoom { zoom_in: false }), - entry!(KeyUp(Lmb); action_dispatch=NavigateToolMessage::ClickZoom { zoom_in: true }), - entry!(PointerMove; refresh_keys=[KeyControl], action_dispatch=NavigateToolMessage::PointerMove { snap_angle: KeyControl, snap_zoom: KeyControl }), - entry!(KeyDown(Mmb); action_dispatch=NavigateToolMessage::TranslateCanvasBegin), - entry!(KeyDown(Rmb); action_dispatch=NavigateToolMessage::RotateCanvasBegin), - entry!(KeyDown(Lmb); action_dispatch=NavigateToolMessage::ZoomCanvasBegin), - entry!(KeyUp(Rmb); action_dispatch=NavigateToolMessage::TransformCanvasEnd), - entry!(KeyUp(Lmb); action_dispatch=NavigateToolMessage::TransformCanvasEnd), - entry!(KeyUp(Mmb); action_dispatch=NavigateToolMessage::TransformCanvasEnd), - // EyedropperToolMessage - entry!(KeyDown(Lmb); action_dispatch=EyedropperToolMessage::LeftMouseDown), - entry!(KeyDown(Rmb); action_dispatch=EyedropperToolMessage::RightMouseDown), - // TextToolMessage - entry!(KeyUp(Lmb); action_dispatch=TextToolMessage::Interact), - entry!(KeyDown(KeyEscape); action_dispatch=TextToolMessage::Abort), - entry_multiplatform!( - standard!(KeyDown(KeyEnter); modifiers=[KeyControl], action_dispatch=TextToolMessage::CommitText), - mac_only!(KeyDown(KeyEnter); modifiers=[KeyCommand], action_dispatch=TextToolMessage::CommitText), - ), - // GradientToolMessage - entry!(KeyDown(Lmb); action_dispatch=GradientToolMessage::PointerDown), - entry!(PointerMove; refresh_keys=[KeyShift], action_dispatch=GradientToolMessage::PointerMove { constrain_axis: KeyShift }), - entry!(KeyUp(Lmb); action_dispatch=GradientToolMessage::PointerUp), - // RectangleToolMessage - entry!(KeyDown(Lmb); action_dispatch=RectangleToolMessage::DragStart), - entry!(KeyUp(Lmb); action_dispatch=RectangleToolMessage::DragStop), - entry!(KeyDown(Rmb); action_dispatch=RectangleToolMessage::Abort), - entry!(KeyDown(KeyEscape); action_dispatch=RectangleToolMessage::Abort), - entry!(PointerMove; refresh_keys=[KeyAlt, KeyShift], action_dispatch=RectangleToolMessage::Resize { center: KeyAlt, lock_ratio: KeyShift }), - // EllipseToolMessage - entry!(KeyDown(Lmb); action_dispatch=EllipseToolMessage::DragStart), - entry!(KeyUp(Lmb); action_dispatch=EllipseToolMessage::DragStop), - entry!(KeyDown(Rmb); action_dispatch=EllipseToolMessage::Abort), - entry!(KeyDown(KeyEscape); action_dispatch=EllipseToolMessage::Abort), - entry!(PointerMove; refresh_keys=[KeyAlt, KeyShift], action_dispatch=EllipseToolMessage::Resize { center: KeyAlt, lock_ratio: KeyShift }), - // ShapeToolMessage - entry!(KeyDown(Lmb); action_dispatch=ShapeToolMessage::DragStart), - entry!(KeyUp(Lmb); action_dispatch=ShapeToolMessage::DragStop), - entry!(KeyDown(Rmb); action_dispatch=ShapeToolMessage::Abort), - entry!(KeyDown(KeyEscape); action_dispatch=ShapeToolMessage::Abort), - entry!(PointerMove; refresh_keys=[KeyAlt, KeyShift], action_dispatch=ShapeToolMessage::Resize { center: KeyAlt, lock_ratio: KeyShift }), - // LineToolMessage - entry!(KeyDown(Lmb); action_dispatch=LineToolMessage::DragStart), - entry!(KeyUp(Lmb); action_dispatch=LineToolMessage::DragStop), - entry!(KeyDown(Rmb); action_dispatch=LineToolMessage::Abort), - entry!(KeyDown(KeyEscape); action_dispatch=LineToolMessage::Abort), - entry!(PointerMove; refresh_keys=[KeyAlt, KeyShift, KeyControl], action_dispatch=LineToolMessage::Redraw { center: KeyAlt, lock_angle: KeyControl, snap_angle: KeyShift }), - // PathToolMessage - entry!(KeyDown(Lmb); action_dispatch=PathToolMessage::DragStart { add_to_selection: KeyShift }), - entry!(PointerMove; refresh_keys=[KeyAlt, KeyShift], action_dispatch=PathToolMessage::PointerMove { alt_mirror_angle: KeyAlt, shift_mirror_distance: KeyShift }), - entry!(KeyDown(KeyDelete); action_dispatch=PathToolMessage::Delete), - entry!(KeyDown(KeyBackspace); action_dispatch=PathToolMessage::Delete), - entry!(KeyUp(Lmb); action_dispatch=PathToolMessage::DragStop), - // PenToolMessage - entry!(PointerMove; refresh_keys=[KeyShift, KeyControl], action_dispatch=PenToolMessage::PointerMove { snap_angle: KeyControl, break_handle: KeyShift }), - entry!(KeyDown(Lmb); action_dispatch=PenToolMessage::DragStart), - entry!(KeyUp(Lmb); action_dispatch=PenToolMessage::DragStop), - entry!(KeyDown(Rmb); action_dispatch=PenToolMessage::Confirm), - entry!(KeyDown(KeyEscape); action_dispatch=PenToolMessage::Confirm), - entry!(KeyDown(KeyEnter); action_dispatch=PenToolMessage::Confirm), - // FreehandToolMessage - entry!(PointerMove; action_dispatch=FreehandToolMessage::PointerMove), - entry!(KeyDown(Lmb); action_dispatch=FreehandToolMessage::DragStart), - entry!(KeyUp(Lmb); action_dispatch=FreehandToolMessage::DragStop), - // SplineToolMessage - entry!(PointerMove; action_dispatch=SplineToolMessage::PointerMove), - entry!(KeyDown(Lmb); action_dispatch=SplineToolMessage::DragStart), - entry!(KeyUp(Lmb); action_dispatch=SplineToolMessage::DragStop), - entry!(KeyDown(Rmb); action_dispatch=SplineToolMessage::Confirm), - entry!(KeyDown(KeyEscape); action_dispatch=SplineToolMessage::Confirm), - entry!(KeyDown(KeyEnter); action_dispatch=SplineToolMessage::Confirm), - // FillToolMessage - entry!(KeyDown(Lmb); action_dispatch=FillToolMessage::LeftMouseDown), - entry!(KeyDown(Rmb); action_dispatch=FillToolMessage::RightMouseDown), - // ToolMessage - entry!(KeyDown(KeyV); action_dispatch=ToolMessage::ActivateToolSelect), - entry!(KeyDown(KeyZ); action_dispatch=ToolMessage::ActivateToolNavigate), - entry!(KeyDown(KeyI); action_dispatch=ToolMessage::ActivateToolEyedropper), - entry!(KeyDown(KeyT); action_dispatch=ToolMessage::ActivateToolText), - entry!(KeyDown(KeyF); action_dispatch=ToolMessage::ActivateToolFill), - entry!(KeyDown(KeyH); action_dispatch=ToolMessage::ActivateToolGradient), - entry!(KeyDown(KeyA); action_dispatch=ToolMessage::ActivateToolPath), - entry!(KeyDown(KeyP); action_dispatch=ToolMessage::ActivateToolPen), - entry!(KeyDown(KeyN); action_dispatch=ToolMessage::ActivateToolFreehand), - entry!(KeyDown(KeyL); action_dispatch=ToolMessage::ActivateToolLine), - entry!(KeyDown(KeyM); action_dispatch=ToolMessage::ActivateToolRectangle), - entry!(KeyDown(KeyE); action_dispatch=ToolMessage::ActivateToolEllipse), - entry!(KeyDown(KeyY); action_dispatch=ToolMessage::ActivateToolShape), - entry_multiplatform!( - standard!(KeyDown(KeyX); modifiers=[KeyShift, KeyControl], action_dispatch=ToolMessage::ResetColors), - mac_only!(KeyDown(KeyX); modifiers=[KeyShift, KeyCommand], action_dispatch=ToolMessage::ResetColors), - ), - entry!(KeyDown(KeyX); modifiers=[KeyShift], action_dispatch=ToolMessage::SwapColors), - entry!(KeyDown(KeyC); modifiers=[KeyAlt], action_dispatch=ToolMessage::SelectRandomPrimaryColor), - // DocumentMessage - entry!(KeyDown(KeyDelete); action_dispatch=DocumentMessage::DeleteSelectedLayers), - entry!(KeyDown(KeyBackspace); action_dispatch=DocumentMessage::DeleteSelectedLayers), - entry!(KeyDown(KeyP); modifiers=[KeyAlt], action_dispatch=DocumentMessage::DebugPrintDocument), - entry_multiplatform!( - standard!(KeyDown(KeyZ); modifiers=[KeyControl, KeyShift], action_dispatch=DocumentMessage::Redo), - mac_only!(KeyDown(KeyZ); modifiers=[KeyCommand, KeyShift], action_dispatch=DocumentMessage::Redo), - ), - entry_multiplatform!( - standard!(KeyDown(KeyZ); modifiers=[KeyControl], action_dispatch=DocumentMessage::Undo), - mac_only!(KeyDown(KeyZ); modifiers=[KeyCommand], action_dispatch=DocumentMessage::Undo), - ), - entry_multiplatform!( - standard!(KeyDown(KeyA); modifiers=[KeyControl, KeyAlt], action_dispatch=DocumentMessage::DeselectAllLayers), - mac_only!(KeyDown(KeyA); modifiers=[KeyCommand, KeyAlt], action_dispatch=DocumentMessage::DeselectAllLayers), - ), - entry_multiplatform!( - standard!(KeyDown(KeyA); modifiers=[KeyControl], action_dispatch=DocumentMessage::SelectAllLayers), - mac_only!(KeyDown(KeyA); modifiers=[KeyCommand], action_dispatch=DocumentMessage::SelectAllLayers), - ), - entry_multiplatform!( - standard!(KeyDown(KeyS); modifiers=[KeyControl], action_dispatch=DocumentMessage::SaveDocument), - mac_only!(KeyDown(KeyS); modifiers=[KeyCommand], action_dispatch=DocumentMessage::SaveDocument), - ), - entry_multiplatform!( - standard!(KeyDown(Key0); modifiers=[KeyControl], action_dispatch=DocumentMessage::ZoomCanvasToFitAll), - mac_only!(KeyDown(Key0); modifiers=[KeyCommand], action_dispatch=DocumentMessage::ZoomCanvasToFitAll), - ), - entry_multiplatform!( - standard!(KeyDown(KeyD); modifiers=[KeyControl], action_dispatch=DocumentMessage::DuplicateSelectedLayers), - mac_only!(KeyDown(KeyD); modifiers=[KeyCommand], action_dispatch=DocumentMessage::DuplicateSelectedLayers), - ), - entry_multiplatform!( - standard!(KeyDown(KeyG); modifiers=[KeyControl], action_dispatch=DocumentMessage::GroupSelectedLayers), - mac_only!(KeyDown(KeyG); modifiers=[KeyCommand], action_dispatch=DocumentMessage::GroupSelectedLayers), - ), - entry_multiplatform!( - standard!(KeyDown(KeyG); modifiers=[KeyControl, KeyShift], action_dispatch=DocumentMessage::UngroupSelectedLayers), - mac_only!(KeyDown(KeyG); modifiers=[KeyCommand, KeyShift], action_dispatch=DocumentMessage::UngroupSelectedLayers), - ), - entry_multiplatform!( - standard!(KeyDown(KeyN); modifiers=[KeyControl, KeyShift], action_dispatch=DocumentMessage::CreateEmptyFolder { container_path: vec![] }), - mac_only!(KeyDown(KeyN); modifiers=[KeyCommand, KeyShift], action_dispatch=DocumentMessage::CreateEmptyFolder { container_path: vec![] }), - ), - entry_multiplatform!( - standard!(KeyDown(KeyLeftBracket); modifiers=[KeyControl, KeyShift], action_dispatch=DocumentMessage::SelectedLayersLowerToBack), - mac_only!(KeyDown(KeyLeftBracket); modifiers=[KeyCommand, KeyShift], action_dispatch=DocumentMessage::SelectedLayersLowerToBack), - ), - entry_multiplatform!( - // TODO: Delete this in favor of the KeyLeftBracket (non-shifted version of this key) mapping above once the input system can distinguish between the non-shifted and shifted keys (important for other language keyboards) - standard!(KeyDown(KeyLeftCurlyBracket); modifiers=[KeyControl, KeyShift], action_dispatch=DocumentMessage::SelectedLayersLowerToBack), - mac_only!(KeyDown(KeyLeftCurlyBracket); modifiers=[KeyCommand, KeyShift], action_dispatch=DocumentMessage::SelectedLayersLowerToBack), - ), - entry_multiplatform!( - standard!(KeyDown(KeyRightBracket); modifiers=[KeyControl, KeyShift], action_dispatch=DocumentMessage::SelectedLayersRaiseToFront), - mac_only!(KeyDown(KeyRightBracket); modifiers=[KeyCommand, KeyShift], action_dispatch=DocumentMessage::SelectedLayersRaiseToFront), - ), - entry_multiplatform!( - // TODO: Delete this in favor of the KeyRightBracket (non-shifted version of this key) mapping above once the input system can distinguish between the non-shifted and shifted keys (important for other language keyboards) - standard!(KeyDown(KeyRightCurlyBracket); modifiers=[KeyControl, KeyShift], action_dispatch=DocumentMessage::SelectedLayersRaiseToFront), - mac_only!(KeyDown(KeyRightCurlyBracket); modifiers=[KeyCommand, KeyShift], action_dispatch=DocumentMessage::SelectedLayersRaiseToFront), - ), - entry_multiplatform!( - standard!(KeyDown(KeyLeftBracket); modifiers=[KeyControl], action_dispatch=DocumentMessage::SelectedLayersLower), - mac_only!(KeyDown(KeyLeftBracket); modifiers=[KeyCommand], action_dispatch=DocumentMessage::SelectedLayersLower), - ), - entry_multiplatform!( - standard!(KeyDown(KeyRightBracket); modifiers=[KeyControl], action_dispatch=DocumentMessage::SelectedLayersRaise), - mac_only!(KeyDown(KeyRightBracket); modifiers=[KeyCommand], action_dispatch=DocumentMessage::SelectedLayersRaise), - ), - entry!(KeyDown(KeyArrowUp); modifiers=[KeyShift, KeyArrowLeft], action_dispatch=DocumentMessage::NudgeSelectedLayers { delta_x: -BIG_NUDGE_AMOUNT, delta_y: -BIG_NUDGE_AMOUNT }), - entry!(KeyDown(KeyArrowUp); modifiers=[KeyShift, KeyArrowRight], action_dispatch=DocumentMessage::NudgeSelectedLayers { delta_x: BIG_NUDGE_AMOUNT, delta_y: -BIG_NUDGE_AMOUNT }), - entry!(KeyDown(KeyArrowUp); modifiers=[KeyShift], action_dispatch=DocumentMessage::NudgeSelectedLayers { delta_x: 0., delta_y: -BIG_NUDGE_AMOUNT }), - entry!(KeyDown(KeyArrowDown); modifiers=[KeyShift, KeyArrowLeft], action_dispatch=DocumentMessage::NudgeSelectedLayers { delta_x: -BIG_NUDGE_AMOUNT, delta_y: BIG_NUDGE_AMOUNT }), - entry!(KeyDown(KeyArrowDown); modifiers=[KeyShift, KeyArrowRight], action_dispatch=DocumentMessage::NudgeSelectedLayers { delta_x: BIG_NUDGE_AMOUNT, delta_y: BIG_NUDGE_AMOUNT }), - entry!(KeyDown(KeyArrowDown); modifiers=[KeyShift], action_dispatch=DocumentMessage::NudgeSelectedLayers { delta_x: 0., delta_y: BIG_NUDGE_AMOUNT }), - entry!(KeyDown(KeyArrowLeft); modifiers=[KeyShift, KeyArrowUp], action_dispatch=DocumentMessage::NudgeSelectedLayers { delta_x: -BIG_NUDGE_AMOUNT, delta_y: -BIG_NUDGE_AMOUNT }), - entry!(KeyDown(KeyArrowLeft); modifiers=[KeyShift, KeyArrowDown], action_dispatch=DocumentMessage::NudgeSelectedLayers { delta_x: -BIG_NUDGE_AMOUNT, delta_y: BIG_NUDGE_AMOUNT }), - entry!(KeyDown(KeyArrowLeft); modifiers=[KeyShift], action_dispatch=DocumentMessage::NudgeSelectedLayers { delta_x: -BIG_NUDGE_AMOUNT, delta_y: 0. }), - entry!(KeyDown(KeyArrowRight); modifiers=[KeyShift, KeyArrowUp], action_dispatch=DocumentMessage::NudgeSelectedLayers { delta_x: BIG_NUDGE_AMOUNT, delta_y: -BIG_NUDGE_AMOUNT }), - entry!(KeyDown(KeyArrowRight); modifiers=[KeyShift, KeyArrowDown], action_dispatch=DocumentMessage::NudgeSelectedLayers { delta_x: BIG_NUDGE_AMOUNT, delta_y: BIG_NUDGE_AMOUNT }), - entry!(KeyDown(KeyArrowRight); modifiers=[KeyShift], action_dispatch=DocumentMessage::NudgeSelectedLayers { delta_x: BIG_NUDGE_AMOUNT, delta_y: 0. }), - entry!(KeyDown(KeyArrowUp); modifiers=[KeyArrowLeft], action_dispatch=DocumentMessage::NudgeSelectedLayers { delta_x: -NUDGE_AMOUNT, delta_y: -NUDGE_AMOUNT }), - entry!(KeyDown(KeyArrowUp); modifiers=[KeyArrowRight], action_dispatch=DocumentMessage::NudgeSelectedLayers { delta_x: NUDGE_AMOUNT, delta_y: -NUDGE_AMOUNT }), - entry!(KeyDown(KeyArrowUp); action_dispatch=DocumentMessage::NudgeSelectedLayers { delta_x: 0., delta_y: -NUDGE_AMOUNT }), - entry!(KeyDown(KeyArrowDown); modifiers=[KeyArrowLeft], action_dispatch=DocumentMessage::NudgeSelectedLayers { delta_x: -NUDGE_AMOUNT, delta_y: NUDGE_AMOUNT }), - entry!(KeyDown(KeyArrowDown); modifiers=[KeyArrowRight], action_dispatch=DocumentMessage::NudgeSelectedLayers { delta_x: NUDGE_AMOUNT, delta_y: NUDGE_AMOUNT }), - entry!(KeyDown(KeyArrowDown); action_dispatch=DocumentMessage::NudgeSelectedLayers { delta_x: 0., delta_y: NUDGE_AMOUNT }), - entry!(KeyDown(KeyArrowLeft); modifiers=[KeyArrowUp], action_dispatch=DocumentMessage::NudgeSelectedLayers { delta_x: -NUDGE_AMOUNT, delta_y: -NUDGE_AMOUNT }), - entry!(KeyDown(KeyArrowLeft); modifiers=[KeyArrowDown], action_dispatch=DocumentMessage::NudgeSelectedLayers { delta_x: -NUDGE_AMOUNT, delta_y: NUDGE_AMOUNT }), - entry!(KeyDown(KeyArrowLeft); action_dispatch=DocumentMessage::NudgeSelectedLayers { delta_x: -NUDGE_AMOUNT, delta_y: 0. }), - entry!(KeyDown(KeyArrowRight); modifiers=[KeyArrowUp], action_dispatch=DocumentMessage::NudgeSelectedLayers { delta_x: NUDGE_AMOUNT, delta_y: -NUDGE_AMOUNT }), - entry!(KeyDown(KeyArrowRight); modifiers=[KeyArrowDown], action_dispatch=DocumentMessage::NudgeSelectedLayers { delta_x: NUDGE_AMOUNT, delta_y: NUDGE_AMOUNT }), - entry!(KeyDown(KeyArrowRight); action_dispatch=DocumentMessage::NudgeSelectedLayers { delta_x: NUDGE_AMOUNT, delta_y: 0. }), - // TransformLayerMessage - entry!(KeyDown(KeyG); action_dispatch=TransformLayerMessage::BeginGrab), - entry!(KeyDown(KeyR); action_dispatch=TransformLayerMessage::BeginRotate), - entry!(KeyDown(KeyS); action_dispatch=TransformLayerMessage::BeginScale), - // MovementMessage - entry!(KeyDown(Mmb); modifiers=[KeyControl], action_dispatch=MovementMessage::RotateCanvasBegin), - entry!(KeyDown(Mmb); modifiers=[KeyShift], action_dispatch=MovementMessage::ZoomCanvasBegin), - entry!(KeyDown(Mmb); action_dispatch=MovementMessage::TranslateCanvasBegin), - entry!(KeyUp(Mmb); action_dispatch=MovementMessage::TransformCanvasEnd), - entry!(KeyDown(Lmb); modifiers=[KeySpace], action_dispatch=MovementMessage::TranslateCanvasBegin), - entry!(KeyUp(Lmb); modifiers=[KeySpace], action_dispatch=MovementMessage::TransformCanvasEnd), - entry_multiplatform!( - standard!(KeyDown(KeyPlus); modifiers=[KeyControl], action_dispatch=MovementMessage::IncreaseCanvasZoom { center_on_mouse: false }), - mac_only!(KeyDown(KeyPlus); modifiers=[KeyCommand], action_dispatch=MovementMessage::IncreaseCanvasZoom { center_on_mouse: false }), - ), - entry_multiplatform!( - standard!(KeyDown(KeyEquals); modifiers=[KeyControl], action_dispatch=MovementMessage::IncreaseCanvasZoom { center_on_mouse: false }), - mac_only!(KeyDown(KeyEquals); modifiers=[KeyCommand], action_dispatch=MovementMessage::IncreaseCanvasZoom { center_on_mouse: false }), - ), - entry_multiplatform!( - standard!(KeyDown(KeyMinus); modifiers=[KeyControl], action_dispatch=MovementMessage::DecreaseCanvasZoom { center_on_mouse: false }), - mac_only!(KeyDown(KeyMinus); modifiers=[KeyCommand], action_dispatch=MovementMessage::DecreaseCanvasZoom { center_on_mouse: false }), - ), - entry_multiplatform!( - standard!(KeyDown(Key1); modifiers=[KeyControl], action_dispatch=MovementMessage::SetCanvasZoom { zoom_factor: 1. }), - mac_only!(KeyDown(Key1); modifiers=[KeyCommand], action_dispatch=MovementMessage::SetCanvasZoom { zoom_factor: 1. }), - ), - entry_multiplatform!( - standard!(KeyDown(Key2); modifiers=[KeyControl], action_dispatch=MovementMessage::SetCanvasZoom { zoom_factor: 2. }), - mac_only!(KeyDown(Key2); modifiers=[KeyCommand], action_dispatch=MovementMessage::SetCanvasZoom { zoom_factor: 2. }), - ), - entry!(WheelScroll; modifiers=[KeyControl], action_dispatch=MovementMessage::WheelCanvasZoom), - entry!(WheelScroll; modifiers=[KeyShift], action_dispatch=MovementMessage::WheelCanvasTranslate { use_y_as_x: true }), - entry!(WheelScroll; action_dispatch=MovementMessage::WheelCanvasTranslate { use_y_as_x: false }), - entry!(KeyDown(KeyPageUp); modifiers=[KeyShift], action_dispatch=MovementMessage::TranslateCanvasByViewportFraction { delta: DVec2::new(1., 0.) }), - entry!(KeyDown(KeyPageDown); modifiers=[KeyShift], action_dispatch=MovementMessage::TranslateCanvasByViewportFraction { delta: DVec2::new(-1., 0.) }), - entry!(KeyDown(KeyPageUp); action_dispatch=MovementMessage::TranslateCanvasByViewportFraction { delta: DVec2::new(0., 1.) }), - entry!(KeyDown(KeyPageDown); action_dispatch=MovementMessage::TranslateCanvasByViewportFraction { delta: DVec2::new(0., -1.) }), - // PortfolioMessage - entry_multiplatform!( - standard!(KeyDown(KeyO); modifiers=[KeyControl], action_dispatch=PortfolioMessage::OpenDocument), - mac_only!(KeyDown(KeyO); modifiers=[KeyCommand], action_dispatch=PortfolioMessage::OpenDocument), - ), - entry_multiplatform!( - standard!(KeyDown(KeyI); modifiers=[KeyControl], action_dispatch=PortfolioMessage::Import), - mac_only!(KeyDown(KeyI); modifiers=[KeyCommand], action_dispatch=PortfolioMessage::Import), - ), - entry!(KeyDown(KeyTab); modifiers=[KeyControl], action_dispatch=PortfolioMessage::NextDocument), - entry!(KeyDown(KeyTab); modifiers=[KeyControl, KeyShift], action_dispatch=PortfolioMessage::PrevDocument), - entry_multiplatform!( - standard!(KeyDown(KeyW); modifiers=[KeyControl], action_dispatch=PortfolioMessage::CloseActiveDocumentWithConfirmation), - mac_only!(KeyDown(KeyW); modifiers=[KeyCommand], action_dispatch=PortfolioMessage::CloseActiveDocumentWithConfirmation), - ), - entry_multiplatform!( - standard!(KeyDown(KeyX); modifiers=[KeyControl], action_dispatch=PortfolioMessage::Cut { clipboard: Clipboard::Device }), - mac_only!(KeyDown(KeyX); modifiers=[KeyCommand], action_dispatch=PortfolioMessage::Cut { clipboard: Clipboard::Device }), - ), - entry_multiplatform!( - standard!(KeyDown(KeyC); modifiers=[KeyControl], action_dispatch=PortfolioMessage::Copy { clipboard: Clipboard::Device }), - mac_only!(KeyDown(KeyC); modifiers=[KeyCommand], action_dispatch=PortfolioMessage::Copy { clipboard: Clipboard::Device }), - ), - entry_multiplatform!( - // This shortcut is intercepted in the frontend; it exists here only as a shortcut mapping source - standard!(KeyDown(KeyV); modifiers=[KeyControl], action_dispatch=FrontendMessage::TriggerPaste), - mac_only!(KeyDown(KeyV); modifiers=[KeyCommand], action_dispatch=FrontendMessage::TriggerPaste), - ), - // DialogMessage - entry_multiplatform!( - standard!(KeyDown(KeyN); modifiers=[KeyControl], action_dispatch=DialogMessage::RequestNewDocumentDialog), - mac_only!(KeyDown(KeyN); modifiers=[KeyCommand], action_dispatch=DialogMessage::RequestNewDocumentDialog), - ), - entry_multiplatform!( - standard!(KeyDown(KeyW); modifiers=[KeyControl, KeyAlt], action_dispatch=DialogMessage::CloseAllDocumentsWithConfirmation), - mac_only!(KeyDown(KeyW); modifiers=[KeyCommand, KeyAlt], action_dispatch=DialogMessage::CloseAllDocumentsWithConfirmation), - ), - entry_multiplatform!( - standard!(KeyDown(KeyE); modifiers=[KeyControl], action_dispatch=DialogMessage::RequestExportDialog), - mac_only!(KeyDown(KeyE); modifiers=[KeyCommand], action_dispatch=DialogMessage::RequestExportDialog), - ), - // DebugMessage - entry!(KeyDown(KeyT); modifiers=[KeyAlt], action_dispatch=DebugMessage::ToggleTraceLogs), - entry!(KeyDown(Key0); modifiers=[KeyAlt], action_dispatch=DebugMessage::MessageOff), - entry!(KeyDown(Key1); modifiers=[KeyAlt], action_dispatch=DebugMessage::MessageNames), - entry!(KeyDown(Key2); modifiers=[KeyAlt], action_dispatch=DebugMessage::MessageContents), - ]; - let (mut key_up, mut key_down, mut double_click, mut wheel_scroll, mut pointer_move) = mappings; - - // TODO: Hardcode these 10 lines into 10 lines of declarations, or make this use a macro to do all 10 in one line - const NUMBER_KEYS: [Key; 10] = [Key0, Key1, Key2, Key3, Key4, Key5, Key6, Key7, Key8, Key9]; - for (i, key) in NUMBER_KEYS.iter().enumerate() { - key_down[*key as usize].0.insert( - 0, - MappingEntry { - action: TransformLayerMessage::TypeDigit { digit: i as u8 }.into(), - input: InputMapperMessage::KeyDown(*key), - platform_layout: None, - modifiers: modifiers!(), - }, - ); - } - - let sort = |list: &mut KeyMappingEntries| list.0.sort_by(|u, v| v.modifiers.ones().cmp(&u.modifiers.ones())); - for list in [&mut key_up, &mut key_down] { - for sublist in list { - sort(sublist); - } - } - sort(&mut double_click); - sort(&mut wheel_scroll); - sort(&mut pointer_move); - - Self { - key_up, - key_down, - double_click, - wheel_scroll, - pointer_move, - } - } -} - -impl Mapping { - pub fn match_input_message(&self, message: InputMapperMessage, keyboard_state: &KeyStates, actions: ActionList, keyboard_platform: KeyboardPlatformLayout) -> Option { - let list = match message { - InputMapperMessage::KeyDown(key) => &self.key_down[key as usize], - InputMapperMessage::KeyUp(key) => &self.key_up[key as usize], - InputMapperMessage::DoubleClick => &self.double_click, - InputMapperMessage::WheelScroll => &self.wheel_scroll, - InputMapperMessage::PointerMove => &self.pointer_move, - }; - list.match_mapping(keyboard_state, actions, keyboard_platform) - } -} - -#[derive(PartialEq, Clone, Debug)] -pub struct MappingEntry { - /// Serves two purposes: - /// - This is the message that gets dispatched when the hotkey is matched - /// - This message's discriminant is the action; it must be a currently active action to be considered as a shortcut - pub action: Message, - /// The user input event from an input device which this input mapping matches on - pub input: InputMapperMessage, - /// Any additional keys that must be also pressed for this input mapping to match - pub modifiers: KeyStates, - /// The keyboard platform layout which this mapping is exclusive to, or `None` if it's platform-agnostic - pub platform_layout: Option, -} - -#[derive(Debug, Clone)] -pub struct KeyMappingEntries(pub Vec); - -impl KeyMappingEntries { - fn match_mapping(&self, keyboard_state: &KeyStates, actions: ActionList, keyboard_platform: KeyboardPlatformLayout) -> Option { - for entry in self.0.iter() { - // Skip this entry if it is platform-specific, and for a layout that does not match the user's keyboard platform layout - if let Some(entry_platform_layout) = entry.platform_layout { - if entry_platform_layout != keyboard_platform { - continue; - } - } - - // Find which currently pressed keys are also the modifiers in this hotkey entry, then compare those against the required modifiers to see if there are zero missing - let pressed_modifiers = *keyboard_state & entry.modifiers; - let all_modifiers_without_pressed_modifiers = entry.modifiers ^ pressed_modifiers; - let all_required_modifiers_pressed = all_modifiers_without_pressed_modifiers.is_empty(); - // Skip this entry if any of the required modifiers are missing - if !all_required_modifiers_pressed { - continue; - } - - if actions.iter().flatten().any(|action| entry.action.to_discriminant() == *action) { - return Some(entry.action.clone()); - } - } - None - } - - fn push(&mut self, entry: MappingEntry) { - self.0.push(entry) - } - - const fn new() -> Self { - Self(Vec::new()) - } - - fn key_array() -> [Self; NUMBER_OF_KEYS] { - const DEFAULT: KeyMappingEntries = KeyMappingEntries::new(); - [DEFAULT; NUMBER_OF_KEYS] - } -} - -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] -pub enum ActionKeys { - Action(MessageDiscriminant), - #[serde(rename = "keys")] - Keys(Vec), -} - -impl ActionKeys { - pub fn to_keys(&mut self, action_input_mapping: &impl Fn(&MessageDiscriminant) -> Vec>) { - match self { - ActionKeys::Action(action) => { - if let Some(keys) = action_input_mapping(action).get_mut(0) { - let mut taken_keys = Vec::new(); - std::mem::swap(keys, &mut taken_keys); - - *self = ActionKeys::Keys(taken_keys); - } else { - *self = ActionKeys::Keys(Vec::new()); - } - } - ActionKeys::Keys(keys) => { - log::warn!("Calling `.to_keys()` on a `ActionKeys::Keys` is a mistake/bug. Keys are: {:?}.", keys); - } - } - } -} - -pub fn keys_text_shortcut(keys: &[Key], keyboard_platform: KeyboardPlatformLayout) -> String { - const JOINER_MARK: &str = "+"; - - let mut joined = keys - .iter() - .map(|key| { - let key_string = key.to_string(); - - if keyboard_platform == KeyboardPlatformLayout::Mac { - match key_string.as_str() { - "Command" => "⌘".to_string(), - "Control" => "⌃".to_string(), - "Alt" => "⌥".to_string(), - "Shift" => "⇧".to_string(), - _ => key_string + JOINER_MARK, - } - } else { - key_string + JOINER_MARK - } - }) - .collect::(); - - // Truncate to cut the joining character off the end if it's present - if joined.ends_with(JOINER_MARK) { - joined.truncate(joined.len() - JOINER_MARK.len()); - } - - joined -} - -pub mod action_keys { - macro_rules! action_shortcut { - ($action:expr) => { - Some(crate::input::input_mapper::ActionKeys::Action($action.into())) - }; - } - - pub(crate) use action_shortcut; -} diff --git a/editor/src/input/input_preprocessor.rs b/editor/src/input/input_preprocessor.rs deleted file mode 100644 index 70694bdda4..0000000000 --- a/editor/src/input/input_preprocessor.rs +++ /dev/null @@ -1,116 +0,0 @@ -#[doc(inline)] -pub use graphene::DocumentResponse; - -use bitflags::bitflags; -use serde::{Deserialize, Serialize}; - -pub enum KeyPosition { - Pressed, - Released, -} - -bitflags! { - #[derive(Default, Serialize, Deserialize)] - #[repr(transparent)] - pub struct ModifierKeys: u8 { - const SHIFT = 0b0000_0001; - const ALT = 0b0000_0010; - const CONTROL = 0b0000_0100; - const META_OR_COMMAND = 0b0000_1000; - } -} - -#[cfg(test)] -mod test { - use crate::document::utility_types::KeyboardPlatformLayout; - use crate::input::input_preprocessor::ModifierKeys; - use crate::input::keyboard::Key; - use crate::input::mouse::EditorMouseState; - use crate::input::{InputMapperMessage, InputPreprocessorMessage, InputPreprocessorMessageHandler}; - use crate::message_prelude::MessageHandler; - - use std::collections::VecDeque; - - #[test] - fn process_action_mouse_move_handle_modifier_keys() { - let mut input_preprocessor = InputPreprocessorMessageHandler::default(); - - let editor_mouse_state = EditorMouseState::from_editor_position(4., 809.); - let modifier_keys = ModifierKeys::ALT; - let message = InputPreprocessorMessage::PointerMove { editor_mouse_state, modifier_keys }; - - let mut responses = VecDeque::new(); - - input_preprocessor.process_action(message, KeyboardPlatformLayout::Standard, &mut responses); - - assert!(input_preprocessor.keyboard.get(Key::KeyAlt as usize)); - assert_eq!(responses.pop_front(), Some(InputMapperMessage::KeyDown(Key::KeyAlt).into())); - } - - #[test] - fn process_action_mouse_down_handle_modifier_keys() { - let mut input_preprocessor = InputPreprocessorMessageHandler::default(); - - let editor_mouse_state = EditorMouseState::new(); - let modifier_keys = ModifierKeys::CONTROL; - let message = InputPreprocessorMessage::PointerDown { editor_mouse_state, modifier_keys }; - - let mut responses = VecDeque::new(); - - input_preprocessor.process_action(message, KeyboardPlatformLayout::Standard, &mut responses); - - assert!(input_preprocessor.keyboard.get(Key::KeyControl as usize)); - assert_eq!(responses.pop_front(), Some(InputMapperMessage::KeyDown(Key::KeyControl).into())); - } - - #[test] - fn process_action_mouse_up_handle_modifier_keys() { - let mut input_preprocessor = InputPreprocessorMessageHandler::default(); - - let editor_mouse_state = EditorMouseState::new(); - let modifier_keys = ModifierKeys::SHIFT; - let message = InputPreprocessorMessage::PointerUp { editor_mouse_state, modifier_keys }; - - let mut responses = VecDeque::new(); - - input_preprocessor.process_action(message, KeyboardPlatformLayout::Standard, &mut responses); - - assert!(input_preprocessor.keyboard.get(Key::KeyShift as usize)); - assert_eq!(responses.pop_front(), Some(InputMapperMessage::KeyDown(Key::KeyShift).into())); - } - - #[test] - fn process_action_key_down_handle_modifier_keys() { - let mut input_preprocessor = InputPreprocessorMessageHandler::default(); - input_preprocessor.keyboard.set(Key::KeyControl as usize); - - let key = Key::KeyA; - let modifier_keys = ModifierKeys::empty(); - let message = InputPreprocessorMessage::KeyDown { key, modifier_keys }; - - let mut responses = VecDeque::new(); - - input_preprocessor.process_action(message, KeyboardPlatformLayout::Standard, &mut responses); - - assert!(!input_preprocessor.keyboard.get(Key::KeyControl as usize)); - assert_eq!(responses.pop_front(), Some(InputMapperMessage::KeyUp(Key::KeyControl).into())); - } - - #[test] - fn process_action_key_up_handle_modifier_keys() { - let mut input_preprocessor = InputPreprocessorMessageHandler::default(); - - let key = Key::KeyS; - let modifier_keys = ModifierKeys::CONTROL | ModifierKeys::SHIFT; - let message = InputPreprocessorMessage::KeyUp { key, modifier_keys }; - - let mut responses = VecDeque::new(); - - input_preprocessor.process_action(message, KeyboardPlatformLayout::Standard, &mut responses); - - assert!(input_preprocessor.keyboard.get(Key::KeyControl as usize)); - assert!(input_preprocessor.keyboard.get(Key::KeyShift as usize)); - assert!(responses.contains(&InputMapperMessage::KeyDown(Key::KeyControl).into())); - assert!(responses.contains(&InputMapperMessage::KeyDown(Key::KeyControl).into())); - } -} diff --git a/editor/src/input/mod.rs b/editor/src/input/mod.rs deleted file mode 100644 index 23b7bd4a2c..0000000000 --- a/editor/src/input/mod.rs +++ /dev/null @@ -1,20 +0,0 @@ -pub mod input_mapper; -pub mod input_preprocessor; -pub mod keyboard; -pub mod mouse; - -mod input_mapper_macros; -mod input_mapper_message; -mod input_mapper_message_handler; -mod input_preprocessor_message; -mod input_preprocessor_message_handler; - -#[doc(inline)] -pub use input_mapper_message::{InputMapperMessage, InputMapperMessageDiscriminant}; -#[doc(inline)] -pub use input_mapper_message_handler::InputMapperMessageHandler; - -#[doc(inline)] -pub use input_preprocessor_message::{InputPreprocessorMessage, InputPreprocessorMessageDiscriminant}; -#[doc(inline)] -pub use input_preprocessor_message_handler::InputPreprocessorMessageHandler; diff --git a/editor/src/layout/mod.rs b/editor/src/layout/mod.rs deleted file mode 100644 index 86e8a31e92..0000000000 --- a/editor/src/layout/mod.rs +++ /dev/null @@ -1,5 +0,0 @@ -pub mod layout_message; -pub mod layout_message_handler; -pub mod widgets; - -pub use layout_message::{LayoutMessage, LayoutMessageDiscriminant}; diff --git a/editor/src/layout/widgets.rs b/editor/src/layout/widgets.rs deleted file mode 100644 index c6700c2b81..0000000000 --- a/editor/src/layout/widgets.rs +++ /dev/null @@ -1,776 +0,0 @@ -use super::layout_message::LayoutTarget; -use crate::document::utility_types::KeyboardPlatformLayout; -use crate::input::input_mapper::keys_text_shortcut; -use crate::input::input_mapper::ActionKeys; -use crate::input::keyboard::Key; -use crate::message_prelude::*; -use crate::Color; - -use derivative::*; -use serde::{Deserialize, Serialize}; -use std::rc::Rc; - -pub trait PropertyHolder { - fn properties(&self) -> Layout { - Layout::WidgetLayout(WidgetLayout::default()) - } - - fn register_properties(&self, responses: &mut VecDeque, layout_target: LayoutTarget) { - responses.push_back( - LayoutMessage::SendLayout { - layout: self.properties(), - layout_target, - } - .into(), - ) - } -} - -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] -pub enum Layout { - WidgetLayout(WidgetLayout), - MenuLayout(MenuLayout), -} - -impl Layout { - pub fn unwrap_widget_layout(self, action_input_mapping: &impl Fn(&MessageDiscriminant) -> Vec>, keyboard_platform: KeyboardPlatformLayout) -> WidgetLayout { - if let Layout::WidgetLayout(mut widget_layout) = self { - // Function used multiple times later in this code block to convert `ActionKeys::Action` to `ActionKeys::Keys` and append its shortcut to the tooltip - let apply_shortcut_to_tooltip = |tooltip_shortcut: &mut ActionKeys, tooltip: &mut String| { - tooltip_shortcut.to_keys(action_input_mapping); - - if let ActionKeys::Keys(keys) = tooltip_shortcut { - let shortcut_text = keys_text_shortcut(keys, keyboard_platform); - - if !shortcut_text.is_empty() { - if !tooltip.is_empty() { - tooltip.push(' '); - } - tooltip.push('('); - tooltip.push_str(&shortcut_text); - tooltip.push(')'); - } - } - }; - - // Go through each widget to convert `ActionKeys::Action` to `ActionKeys::Keys` and append the key combination to the widget tooltip - for widget_holder in &mut widget_layout.iter_mut() { - // Handle all the widgets that have tooltips - let mut tooltip_shortcut = match &mut widget_holder.widget { - Widget::CheckboxInput(widget) => Some((&mut widget.tooltip, &mut widget.tooltip_shortcut)), - Widget::ColorInput(widget) => Some((&mut widget.tooltip, &mut widget.tooltip_shortcut)), - Widget::IconButton(widget) => Some((&mut widget.tooltip, &mut widget.tooltip_shortcut)), - Widget::OptionalInput(widget) => Some((&mut widget.tooltip, &mut widget.tooltip_shortcut)), - _ => None, - }; - if let Some((tooltip, Some(tooltip_shortcut))) = &mut tooltip_shortcut { - apply_shortcut_to_tooltip(tooltip_shortcut, tooltip); - } - - // Handle RadioInput separately because its tooltips are children of the widget - if let Widget::RadioInput(radio_input) = &mut widget_holder.widget { - for radio_entry_data in &mut radio_input.entries { - if let RadioEntryData { - tooltip, - tooltip_shortcut: Some(tooltip_shortcut), - .. - } = radio_entry_data - { - apply_shortcut_to_tooltip(tooltip_shortcut, tooltip); - } - } - } - } - - widget_layout - } else { - panic!("Tried to unwrap layout as WidgetLayout. Got {:?}", self) - } - } - - pub fn unwrap_menu_layout(self, action_input_mapping: &impl Fn(&MessageDiscriminant) -> Vec>, _keyboard_platform: KeyboardPlatformLayout) -> MenuLayout { - if let Layout::MenuLayout(mut menu_layout) = self { - for menu_column in &mut menu_layout.layout { - menu_column.children.fill_in_shortcut_actions_with_keys(action_input_mapping); - } - - menu_layout - } else { - panic!("Tried to unwrap layout as MenuLayout. Got {:?}", self) - } - } - - pub fn iter(&self) -> Box + '_> { - match self { - Layout::MenuLayout(menu_layout) => Box::new(menu_layout.iter()), - Layout::WidgetLayout(widget_layout) => Box::new(widget_layout.iter()), - } - } - - pub fn iter_mut(&mut self) -> Box + '_> { - match self { - Layout::MenuLayout(menu_layout) => Box::new(menu_layout.iter_mut()), - Layout::WidgetLayout(widget_layout) => Box::new(widget_layout.iter_mut()), - } - } -} - -impl Default for Layout { - fn default() -> Self { - Layout::WidgetLayout(WidgetLayout::default()) - } -} - -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default)] -pub struct MenuEntryGroups(pub Vec>); - -impl MenuEntryGroups { - pub fn empty() -> Self { - Self(Vec::new()) - } - - pub fn fill_in_shortcut_actions_with_keys(&mut self, action_input_mapping: &impl Fn(&MessageDiscriminant) -> Vec>) { - let entries = self.0.iter_mut().flatten(); - - for entry in entries { - if let Some(action_keys) = &mut entry.shortcut { - action_keys.to_keys(action_input_mapping); - } - - // Recursively do this for the children also - entry.children.fill_in_shortcut_actions_with_keys(action_input_mapping); - } - } -} - -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] -pub struct MenuEntry { - pub label: String, - pub icon: Option, - pub children: MenuEntryGroups, - pub action: WidgetHolder, - pub shortcut: Option, -} - -impl MenuEntry { - pub fn create_action(callback: impl Fn(&()) -> Message + 'static) -> WidgetHolder { - WidgetHolder::new(Widget::Invisible(Invisible { - on_update: WidgetCallback::new(callback), - })) - } - - pub fn no_action() -> WidgetHolder { - MenuEntry::create_action(|_| Message::NoOp) - } -} - -impl Default for MenuEntry { - fn default() -> Self { - Self { - action: MenuEntry::create_action(|_| DialogMessage::RequestComingSoonDialog { issue: None }.into()), - label: "".into(), - icon: None, - children: MenuEntryGroups::empty(), - shortcut: None, - } - } -} - -#[derive(Debug, Default, Clone, Serialize, Deserialize, PartialEq)] -pub struct MenuColumn { - pub label: String, - pub children: MenuEntryGroups, -} - -#[derive(Debug, Default, Clone, PartialEq, Serialize, Deserialize)] -pub struct MenuLayout { - pub layout: Vec, -} - -impl MenuLayout { - pub fn new(layout: Vec) -> Self { - Self { layout } - } - - pub fn iter(&self) -> impl Iterator + '_ { - MenuLayoutIter { - stack: self.layout.iter().flat_map(|column| column.children.0.iter()).flat_map(|group| group.iter()).collect(), - } - } - - pub fn iter_mut(&mut self) -> impl Iterator + '_ { - MenuLayoutIterMut { - stack: self.layout.iter_mut().flat_map(|column| column.children.0.iter_mut()).flat_map(|group| group.iter_mut()).collect(), - } - } -} - -#[derive(Debug, Default)] -pub struct MenuLayoutIter<'a> { - pub stack: Vec<&'a MenuEntry>, -} - -impl<'a> Iterator for MenuLayoutIter<'a> { - type Item = &'a WidgetHolder; - - fn next(&mut self) -> Option { - match self.stack.pop() { - Some(menu_entry) => { - let more_entries = menu_entry.children.0.iter().flat_map(|entry| entry.iter()); - self.stack.extend(more_entries); - - Some(&menu_entry.action) - } - None => None, - } - } -} - -pub struct MenuLayoutIterMut<'a> { - pub stack: Vec<&'a mut MenuEntry>, -} - -impl<'a> Iterator for MenuLayoutIterMut<'a> { - type Item = &'a mut WidgetHolder; - - fn next(&mut self) -> Option { - match self.stack.pop() { - Some(menu_entry) => { - let more_entries = menu_entry.children.0.iter_mut().flat_map(|entry| entry.iter_mut()); - self.stack.extend(more_entries); - - Some(&mut menu_entry.action) - } - None => None, - } - } -} - -#[derive(Debug, Default, Clone, Serialize, Deserialize, PartialEq)] -pub struct WidgetLayout { - pub layout: SubLayout, -} - -impl WidgetLayout { - pub fn new(layout: SubLayout) -> Self { - Self { layout } - } - - pub fn iter(&self) -> WidgetIter<'_> { - WidgetIter { - stack: self.layout.iter().collect(), - current_slice: None, - } - } - - pub fn iter_mut(&mut self) -> WidgetIterMut<'_> { - WidgetIterMut { - stack: self.layout.iter_mut().collect(), - current_slice: None, - } - } -} - -pub type SubLayout = Vec; - -#[remain::sorted] -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] -pub enum LayoutGroup { - #[serde(rename = "column")] - Column { - #[serde(rename = "columnWidgets")] - widgets: Vec, - }, - #[serde(rename = "row")] - Row { - #[serde(rename = "rowWidgets")] - widgets: Vec, - }, - #[serde(rename = "section")] - Section { name: String, layout: SubLayout }, -} - -#[derive(Debug, Default)] -pub struct WidgetIter<'a> { - pub stack: Vec<&'a LayoutGroup>, - pub current_slice: Option<&'a [WidgetHolder]>, -} - -impl<'a> Iterator for WidgetIter<'a> { - type Item = &'a WidgetHolder; - - fn next(&mut self) -> Option { - if let Some(item) = self.current_slice.and_then(|slice| slice.first()) { - self.current_slice = Some(&self.current_slice.unwrap()[1..]); - return Some(item); - } - - match self.stack.pop() { - Some(LayoutGroup::Column { widgets }) => { - self.current_slice = Some(widgets); - self.next() - } - Some(LayoutGroup::Row { widgets }) => { - self.current_slice = Some(widgets); - self.next() - } - Some(LayoutGroup::Section { name: _, layout }) => { - for layout_row in layout { - self.stack.push(layout_row); - } - self.next() - } - None => None, - } - } -} - -#[derive(Debug, Default)] -pub struct WidgetIterMut<'a> { - pub stack: Vec<&'a mut LayoutGroup>, - pub current_slice: Option<&'a mut [WidgetHolder]>, -} - -impl<'a> Iterator for WidgetIterMut<'a> { - type Item = &'a mut WidgetHolder; - - fn next(&mut self) -> Option { - if let Some((first, rest)) = self.current_slice.take().and_then(|slice| slice.split_first_mut()) { - self.current_slice = Some(rest); - return Some(first); - }; - - match self.stack.pop() { - Some(LayoutGroup::Column { widgets }) => { - self.current_slice = Some(widgets); - self.next() - } - Some(LayoutGroup::Row { widgets }) => { - self.current_slice = Some(widgets); - self.next() - } - Some(LayoutGroup::Section { name: _, layout }) => { - for layout_row in layout { - self.stack.push(layout_row); - } - self.next() - } - None => None, - } - } -} - -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] -pub struct WidgetHolder { - #[serde(rename = "widgetId")] - pub widget_id: u64, - pub widget: Widget, -} - -impl WidgetHolder { - pub fn new(widget: Widget) -> Self { - Self { widget_id: generate_uuid(), widget } - } -} - -#[derive(Clone)] -pub struct WidgetCallback { - pub callback: Rc Message + 'static>, -} - -impl WidgetCallback { - pub fn new(callback: impl Fn(&T) -> Message + 'static) -> Self { - Self { callback: Rc::new(callback) } - } -} - -impl Default for WidgetCallback { - fn default() -> Self { - Self::new(|_| Message::NoOp) - } -} - -#[remain::sorted] -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] -pub enum Widget { - CheckboxInput(CheckboxInput), - ColorInput(ColorInput), - DropdownInput(DropdownInput), - FontInput(FontInput), - IconButton(IconButton), - IconLabel(IconLabel), - Invisible(Invisible), - NumberInput(NumberInput), - OptionalInput(OptionalInput), - PopoverButton(PopoverButton), - RadioInput(RadioInput), - Separator(Separator), - SwatchPairInput(SwatchPairInput), - TextAreaInput(TextAreaInput), - TextButton(TextButton), - TextInput(TextInput), - TextLabel(TextLabel), -} - -#[derive(Clone, Default, Derivative, Serialize, Deserialize)] -#[derivative(Debug, PartialEq)] -pub struct CheckboxInput { - pub checked: bool, - - pub icon: String, - - pub tooltip: String, - - #[serde(skip)] - pub tooltip_shortcut: Option, - - // Callbacks - #[serde(skip)] - #[derivative(Debug = "ignore", PartialEq = "ignore")] - pub on_update: WidgetCallback, -} - -#[derive(Clone, Derivative, Serialize, Deserialize)] -#[derivative(Debug, PartialEq, Default)] -pub struct ColorInput { - pub value: Option, - - pub label: Option, - - #[serde(rename = "noTransparency")] - #[derivative(Default(value = "true"))] - pub no_transparency: bool, - - pub disabled: bool, - - pub tooltip: String, - - #[serde(skip)] - pub tooltip_shortcut: Option, - - // Callbacks - #[serde(skip)] - #[derivative(Debug = "ignore", PartialEq = "ignore")] - pub on_update: WidgetCallback, -} - -#[derive(Clone, Serialize, Deserialize, Derivative)] -#[derivative(Debug, PartialEq, Default)] -pub struct DropdownInput { - pub entries: DropdownInputEntries, - - // This uses `u32` instead of `usize` since it will be serialized as a normal JS number (replace this with `usize` after switching to a Rust-based GUI) - #[serde(rename = "selectedIndex")] - pub selected_index: Option, - - #[serde(rename = "drawIcon")] - pub draw_icon: bool, - - #[derivative(Default(value = "true"))] - pub interactive: bool, - - pub disabled: bool, - // - // Callbacks - // `on_update` exists on the `DropdownEntryData`, not this parent `DropdownInput` -} - -pub type DropdownInputEntries = Vec>; - -#[derive(Clone, Serialize, Deserialize, Derivative, Default)] -#[derivative(Debug, PartialEq)] -pub struct DropdownEntryData { - pub value: String, - - pub label: String, - - pub icon: String, - - pub shortcut: Vec, - - #[serde(rename = "shortcutRequiresLock")] - pub shortcut_requires_lock: bool, - - pub disabled: bool, - - pub children: DropdownInputEntries, - - // Callbacks - #[serde(skip)] - #[derivative(Debug = "ignore", PartialEq = "ignore")] - pub on_update: WidgetCallback<()>, -} - -#[derive(Clone, Serialize, Deserialize, Derivative)] -#[derivative(Debug, PartialEq, Default)] -pub struct FontInput { - #[serde(rename = "fontFamily")] - pub font_family: String, - - #[serde(rename = "fontStyle")] - pub font_style: String, - - #[serde(rename = "isStyle")] - pub is_style_picker: bool, - - pub disabled: bool, - - // Callbacks - #[serde(skip)] - #[derivative(Debug = "ignore", PartialEq = "ignore")] - pub on_update: WidgetCallback, -} - -#[derive(Clone, Default, Derivative, Serialize, Deserialize)] -#[derivative(Debug, PartialEq)] -pub struct IconButton { - pub icon: String, - - pub size: u32, // TODO: Convert to an `IconSize` enum - - pub active: bool, - - pub tooltip: String, - - #[serde(skip)] - pub tooltip_shortcut: Option, - - // Callbacks - #[serde(skip)] - #[derivative(Debug = "ignore", PartialEq = "ignore")] - pub on_update: WidgetCallback, -} - -#[derive(Clone, Serialize, Deserialize, Derivative, Debug, Default, PartialEq, Eq)] -pub struct IconLabel { - pub icon: String, - - #[serde(rename = "iconStyle")] - pub icon_style: IconStyle, -} - -#[derive(Clone, Serialize, Deserialize, Derivative, Debug, Default, PartialEq, Eq)] -pub enum IconStyle { - #[default] - Normal, - Node, -} - -/// This widget allows for the flexible use of the layout system. -/// In a custom layout, one can define a widget that is just used to trigger code on the backend. -/// This is used in MenuLayout to pipe the triggering of messages from the frontend to backend. -#[derive(Clone, Serialize, Deserialize, Derivative, Default)] -#[derivative(Debug, PartialEq)] -pub struct Invisible { - #[serde(skip)] - #[derivative(Debug = "ignore", PartialEq = "ignore")] - pub on_update: WidgetCallback<()>, -} - -#[derive(Clone, Serialize, Deserialize, Derivative)] -#[derivative(Debug, PartialEq, Default)] -pub struct NumberInput { - pub label: String, - - pub value: Option, - - pub min: Option, - - pub max: Option, - - #[serde(rename = "isInteger")] - pub is_integer: bool, - - #[serde(rename = "displayDecimalPlaces")] - #[derivative(Default(value = "3"))] - pub display_decimal_places: u32, - - pub unit: String, - - #[serde(rename = "unitIsHiddenWhenEditing")] - #[derivative(Default(value = "true"))] - pub unit_is_hidden_when_editing: bool, - - #[serde(rename = "incrementBehavior")] - pub increment_behavior: NumberInputIncrementBehavior, - - #[serde(rename = "incrementFactor")] - #[derivative(Default(value = "1."))] - pub increment_factor: f64, - - pub disabled: bool, - - // Callbacks - #[serde(skip)] - #[derivative(Debug = "ignore", PartialEq = "ignore")] - pub on_update: WidgetCallback, - - #[serde(skip)] - #[derivative(Debug = "ignore", PartialEq = "ignore")] - pub increment_callback_increase: WidgetCallback, - - #[serde(skip)] - #[derivative(Debug = "ignore", PartialEq = "ignore")] - pub increment_callback_decrease: WidgetCallback, -} - -#[derive(Clone, Serialize, Deserialize, Debug, Default, PartialEq, Eq)] -pub enum NumberInputIncrementBehavior { - #[default] - Add, - Multiply, - Callback, -} - -#[derive(Clone, Default, Derivative, Serialize, Deserialize)] -#[derivative(Debug, PartialEq)] -pub struct OptionalInput { - pub checked: bool, - - pub icon: String, - - pub tooltip: String, - - #[serde(skip)] - pub tooltip_shortcut: Option, - - // Callbacks - #[serde(skip)] - #[derivative(Debug = "ignore", PartialEq = "ignore")] - pub on_update: WidgetCallback, -} - -#[derive(Clone, Serialize, Deserialize, Derivative)] -#[derivative(Debug, PartialEq, Default)] -pub struct PopoverButton { - pub icon: Option, - - // Body - pub header: String, - - pub text: String, -} - -#[derive(Clone, Default, Derivative, Serialize, Deserialize)] -#[derivative(Debug, PartialEq)] -pub struct RadioInput { - pub entries: Vec, - - // This uses `u32` instead of `usize` since it will be serialized as a normal JS number (replace this with `usize` after switching to a Rust-based GUI) - #[serde(rename = "selectedIndex")] - pub selected_index: u32, -} - -#[derive(Clone, Default, Derivative, Serialize, Deserialize)] -#[derivative(Debug, PartialEq)] -pub struct RadioEntryData { - pub value: String, - - pub label: String, - - pub icon: String, - - pub tooltip: String, - - #[serde(skip)] - pub tooltip_shortcut: Option, - - // Callbacks - #[serde(skip)] - #[derivative(Debug = "ignore", PartialEq = "ignore")] - pub on_update: WidgetCallback<()>, -} - -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -pub struct Separator { - pub direction: SeparatorDirection, - - #[serde(rename = "type")] - pub separator_type: SeparatorType, -} - -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -pub enum SeparatorDirection { - Horizontal, - Vertical, -} - -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -pub enum SeparatorType { - Related, - Unrelated, - Section, - List, -} - -#[derive(Clone, Serialize, Deserialize, Derivative)] -#[derivative(Debug, PartialEq, Default)] -pub struct SwatchPairInput { - pub primary: Color, - - pub secondary: Color, -} - -#[derive(Clone, Serialize, Deserialize, Derivative)] -#[derivative(Debug, PartialEq, Default)] -pub struct TextAreaInput { - pub value: String, - - pub label: Option, - - pub disabled: bool, - - // Callbacks - #[serde(skip)] - #[derivative(Debug = "ignore", PartialEq = "ignore")] - pub on_update: WidgetCallback, -} - -#[derive(Clone, Serialize, Deserialize, Derivative, Default)] -#[derivative(Debug, PartialEq)] -#[serde(rename_all(serialize = "camelCase", deserialize = "camelCase"))] -pub struct TextButton { - pub label: String, - - pub emphasized: bool, - - #[serde(rename = "minWidth")] - pub min_width: u32, - - pub disabled: bool, - - // Callbacks - #[serde(skip)] - #[derivative(Debug = "ignore", PartialEq = "ignore")] - pub on_update: WidgetCallback, -} - -#[derive(Clone, Serialize, Deserialize, Derivative)] -#[derivative(Debug, PartialEq, Default)] -pub struct TextInput { - pub value: String, - - pub label: Option, - - pub disabled: bool, - - // Callbacks - #[serde(skip)] - #[derivative(Debug = "ignore", PartialEq = "ignore")] - pub on_update: WidgetCallback, -} - -#[derive(Clone, Serialize, Deserialize, Derivative, Debug, PartialEq, Eq, Default)] -pub struct TextLabel { - pub bold: bool, - - pub italic: bool, - - #[serde(rename = "tableAlign")] - pub table_align: bool, - - pub multiline: bool, - - // Body - pub value: String, -} diff --git a/editor/src/lib.rs b/editor/src/lib.rs index 8759ac3675..6b248ce63e 100644 --- a/editor/src/lib.rs +++ b/editor/src/lib.rs @@ -1,99 +1,12 @@ extern crate graphite_proc_macros; -pub mod communication; +// `macro_use` puts these macros into scope for all descendant code files #[macro_use] -pub mod misc; +mod macros; + +pub mod application; pub mod consts; -pub mod debug; -pub mod dialog; -pub mod document; -pub mod frontend; -pub mod input; -pub mod layout; -pub mod viewport_tools; -pub mod workspace; - -#[doc(inline)] -pub use graphene::color::Color; -#[doc(inline)] -pub use graphene::document::Document as SvgDocument; -#[doc(inline)] -pub use graphene::LayerId; -#[doc(inline)] -pub use misc::EditorError; - -use communication::dispatcher::Dispatcher; -use message_prelude::*; - -// TODO: serialize with serde to save the current editor state -pub struct Editor { - dispatcher: Dispatcher, -} - -impl Editor { - /// Construct a new editor instance. - /// Remember to provide a random seed with `editor::communication::set_uuid_seed(seed)` before any editors can be used. - pub fn new() -> Self { - Self { dispatcher: Dispatcher::new() } - } - - pub fn handle_message>(&mut self, message: T) -> Vec { - self.dispatcher.handle_message(message); - - let mut responses = Vec::new(); - std::mem::swap(&mut responses, &mut self.dispatcher.responses); - - responses - } -} - -impl Default for Editor { - fn default() -> Self { - Self::new() - } -} - -pub mod message_prelude { - pub use crate::communication::broadcast_message::{BroadcastMessage, BroadcastMessageDiscriminant, BroadcastSignal, BroadcastSignalDiscriminant}; - pub use crate::communication::generate_uuid; - pub use crate::communication::message::{AsMessage, Message, MessageDiscriminant}; - pub use crate::communication::message_handler::{ActionList, MessageHandler}; - - pub use crate::document::clipboards::Clipboard; - pub use crate::LayerId; - - pub use crate::debug::{DebugMessage, DebugMessageDiscriminant}; - pub use crate::dialog::messages::*; - pub use crate::document::{ArtboardMessage, ArtboardMessageDiscriminant}; - pub use crate::document::{DocumentMessage, DocumentMessageDiscriminant}; - pub use crate::document::{MenuBarMessage, MenuBarMessageDiscriminant}; - pub use crate::document::{MovementMessage, MovementMessageDiscriminant}; - pub use crate::document::{OverlaysMessage, OverlaysMessageDiscriminant}; - pub use crate::document::{PortfolioMessage, PortfolioMessageDiscriminant}; - pub use crate::document::{PropertiesPanelMessage, PropertiesPanelMessageDiscriminant}; - pub use crate::document::{TransformLayerMessage, TransformLayerMessageDiscriminant}; - pub use crate::frontend::{FrontendMessage, FrontendMessageDiscriminant}; - pub use crate::input::{InputMapperMessage, InputMapperMessageDiscriminant, InputPreprocessorMessage, InputPreprocessorMessageDiscriminant}; - pub use crate::layout::{LayoutMessage, LayoutMessageDiscriminant}; - pub use crate::misc::derivable_custom_traits::{ToDiscriminant, TransitiveChild}; - pub use crate::viewport_tools::tool_message::{ToolMessage, ToolMessageDiscriminant}; - pub use crate::viewport_tools::tools::artboard_tool::{ArtboardToolMessage, ArtboardToolMessageDiscriminant}; - pub use crate::viewport_tools::tools::ellipse_tool::{EllipseToolMessage, EllipseToolMessageDiscriminant}; - pub use crate::viewport_tools::tools::eyedropper_tool::{EyedropperToolMessage, EyedropperToolMessageDiscriminant}; - pub use crate::viewport_tools::tools::fill_tool::{FillToolMessage, FillToolMessageDiscriminant}; - pub use crate::viewport_tools::tools::freehand_tool::{FreehandToolMessage, FreehandToolMessageDiscriminant}; - pub use crate::viewport_tools::tools::gradient_tool::{GradientToolMessage, GradientToolMessageDiscriminant}; - pub use crate::viewport_tools::tools::line_tool::{LineToolMessage, LineToolMessageDiscriminant}; - pub use crate::viewport_tools::tools::navigate_tool::{NavigateToolMessage, NavigateToolMessageDiscriminant}; - pub use crate::viewport_tools::tools::path_tool::{PathToolMessage, PathToolMessageDiscriminant}; - pub use crate::viewport_tools::tools::pen_tool::{PenToolMessage, PenToolMessageDiscriminant}; - pub use crate::viewport_tools::tools::rectangle_tool::{RectangleToolMessage, RectangleToolMessageDiscriminant}; - pub use crate::viewport_tools::tools::select_tool::{SelectToolMessage, SelectToolMessageDiscriminant}; - pub use crate::viewport_tools::tools::shape_tool::{ShapeToolMessage, ShapeToolMessageDiscriminant}; - pub use crate::viewport_tools::tools::spline_tool::{SplineToolMessage, SplineToolMessageDiscriminant}; - pub use crate::viewport_tools::tools::text_tool::{TextToolMessage, TextToolMessageDiscriminant}; - pub use crate::workspace::{WorkspaceMessage, WorkspaceMessageDiscriminant}; - pub use graphite_proc_macros::*; - - pub use std::collections::VecDeque; -} +pub mod dispatcher; +pub mod messages; +pub mod test_utils; +pub mod utility_traits; diff --git a/editor/src/misc/macros.rs b/editor/src/macros.rs similarity index 86% rename from editor/src/misc/macros.rs rename to editor/src/macros.rs index 221c149ced..fa3f108664 100644 --- a/editor/src/misc/macros.rs +++ b/editor/src/macros.rs @@ -43,13 +43,13 @@ macro_rules! actions { /// ``` macro_rules! advertise_actions { ($($v:expr),* $(,)?) => { - fn actions(&self) -> $crate::communication::message_handler::ActionList { + fn actions(&self) -> $crate::utility_traits::ActionList { actions!($($v),*) } }; ($name:ident; $($v:ident),* $(,)?) => { - fn actions(&self) -> $crate::communication::message_handler::ActionList { + fn actions(&self) -> $crate::utility_traits::ActionList { actions!($name; $($v),*) } } diff --git a/editor/src/messages/broadcast/broadcast_event.rs b/editor/src/messages/broadcast/broadcast_event.rs new file mode 100644 index 0000000000..23cc9ebff3 --- /dev/null +++ b/editor/src/messages/broadcast/broadcast_event.rs @@ -0,0 +1,11 @@ +use crate::messages::prelude::*; + +use serde::{Deserialize, Serialize}; + +#[derive(PartialEq, Eq, Clone, Debug, Serialize, Deserialize, Hash)] +#[impl_message(Message, BroadcastMessage, TriggerEvent)] +pub enum BroadcastEvent { + DocumentIsDirty, + ToolAbort, + SelectionChanged, +} diff --git a/editor/src/messages/broadcast/broadcast_message.rs b/editor/src/messages/broadcast/broadcast_message.rs new file mode 100644 index 0000000000..618b53ad87 --- /dev/null +++ b/editor/src/messages/broadcast/broadcast_message.rs @@ -0,0 +1,23 @@ +use crate::messages::prelude::*; + +use serde::{Deserialize, Serialize}; + +#[remain::sorted] +#[impl_message(Message, Broadcast)] +#[derive(PartialEq, Clone, Debug, Serialize, Deserialize)] +pub enum BroadcastMessage { + // Sub-messages + #[remain::unsorted] + #[child] + TriggerEvent(BroadcastEvent), + + // Messages + SubscribeEvent { + on: BroadcastEvent, + send: Box, + }, + UnsubscribeEvent { + on: BroadcastEvent, + message: Box, + }, +} diff --git a/editor/src/messages/broadcast/broadcast_message_handler.rs b/editor/src/messages/broadcast/broadcast_message_handler.rs new file mode 100644 index 0000000000..4b019621bf --- /dev/null +++ b/editor/src/messages/broadcast/broadcast_message_handler.rs @@ -0,0 +1,32 @@ +use crate::messages::prelude::*; + +use std::collections::HashMap; + +#[derive(Debug, Clone, Default)] +pub struct BroadcastMessageHandler { + listeners: HashMap>, +} + +impl MessageHandler for BroadcastMessageHandler { + #[remain::check] + fn process_message(&mut self, message: BroadcastMessage, _data: (), responses: &mut VecDeque) { + #[remain::sorted] + match message { + // Sub-messages + #[remain::unsorted] + BroadcastMessage::TriggerEvent(event) => { + for message in self.listeners.entry(event).or_default() { + responses.push_front(message.clone()) + } + } + + // Messages + BroadcastMessage::SubscribeEvent { on, send } => self.listeners.entry(on).or_default().push(*send), + BroadcastMessage::UnsubscribeEvent { on, message } => self.listeners.entry(on).or_default().retain(|msg| *msg != *message), + } + } + + fn actions(&self) -> ActionList { + vec![] + } +} diff --git a/editor/src/messages/broadcast/mod.rs b/editor/src/messages/broadcast/mod.rs new file mode 100644 index 0000000000..a6ecb88979 --- /dev/null +++ b/editor/src/messages/broadcast/mod.rs @@ -0,0 +1,9 @@ +mod broadcast_message; +mod broadcast_message_handler; + +pub mod broadcast_event; + +#[doc(inline)] +pub use broadcast_message::{BroadcastMessage, BroadcastMessageDiscriminant}; +#[doc(inline)] +pub use broadcast_message_handler::BroadcastMessageHandler; diff --git a/editor/src/debug/debug_message.rs b/editor/src/messages/debug/debug_message.rs similarity index 62% rename from editor/src/debug/debug_message.rs rename to editor/src/messages/debug/debug_message.rs index e684f33e03..ad4ee7489d 100644 --- a/editor/src/debug/debug_message.rs +++ b/editor/src/messages/debug/debug_message.rs @@ -1,4 +1,4 @@ -use crate::message_prelude::*; +use crate::messages::prelude::*; use serde::{Deserialize, Serialize}; @@ -10,11 +10,3 @@ pub enum DebugMessage { MessageNames, MessageContents, } - -#[derive(Debug, Default, Clone, Copy)] -pub enum LoggingMessages { - #[default] - Off, - Names, - Contents, -} diff --git a/editor/src/debug/debug_message_handler.rs b/editor/src/messages/debug/debug_message_handler.rs similarity index 71% rename from editor/src/debug/debug_message_handler.rs rename to editor/src/messages/debug/debug_message_handler.rs index 4bd03b4488..651a9652ca 100644 --- a/editor/src/debug/debug_message_handler.rs +++ b/editor/src/messages/debug/debug_message_handler.rs @@ -1,16 +1,16 @@ -use crate::debug::debug_message::LoggingMessages; -use crate::message_prelude::*; +use super::utility_types::MessageLoggingVerbosity; +use crate::messages::prelude::*; use std::collections::VecDeque; #[derive(Debug, Default)] pub struct DebugMessageHandler { - pub logging_messages_mode: LoggingMessages, + pub message_logging_verbosity: MessageLoggingVerbosity, } impl MessageHandler for DebugMessageHandler { #[remain::check] - fn process_action(&mut self, message: DebugMessage, _data: (), responses: &mut VecDeque) { + fn process_message(&mut self, message: DebugMessage, _data: (), responses: &mut VecDeque) { match message { DebugMessage::ToggleTraceLogs => { if let log::LevelFilter::Debug = log::max_level() { @@ -23,19 +23,19 @@ impl MessageHandler for DebugMessageHandler { responses.push_back(MenuBarMessage::SendLayout.into()); } DebugMessage::MessageOff => { - self.logging_messages_mode = LoggingMessages::Off; + self.message_logging_verbosity = MessageLoggingVerbosity::Off; // Refresh the checkmark beside the menu entry for this responses.push_back(MenuBarMessage::SendLayout.into()); } DebugMessage::MessageNames => { - self.logging_messages_mode = LoggingMessages::Names; + self.message_logging_verbosity = MessageLoggingVerbosity::Names; // Refresh the checkmark beside the menu entry for this responses.push_back(MenuBarMessage::SendLayout.into()); } DebugMessage::MessageContents => { - self.logging_messages_mode = LoggingMessages::Contents; + self.message_logging_verbosity = MessageLoggingVerbosity::Contents; // Refresh the checkmark beside the menu entry for this responses.push_back(MenuBarMessage::SendLayout.into()); diff --git a/editor/src/debug/mod.rs b/editor/src/messages/debug/mod.rs similarity index 80% rename from editor/src/debug/mod.rs rename to editor/src/messages/debug/mod.rs index 436d972aa8..79ad968d15 100644 --- a/editor/src/debug/mod.rs +++ b/editor/src/messages/debug/mod.rs @@ -1,7 +1,8 @@ -pub mod debug_message; - +mod debug_message; mod debug_message_handler; +pub mod utility_types; + #[doc(inline)] pub use debug_message::{DebugMessage, DebugMessageDiscriminant}; #[doc(inline)] diff --git a/editor/src/messages/debug/utility_types.rs b/editor/src/messages/debug/utility_types.rs new file mode 100644 index 0000000000..ee90571e70 --- /dev/null +++ b/editor/src/messages/debug/utility_types.rs @@ -0,0 +1,7 @@ +#[derive(Debug, Default, Clone, Copy)] +pub enum MessageLoggingVerbosity { + #[default] + Off, + Names, + Contents, +} diff --git a/editor/src/dialog/dialog_message.rs b/editor/src/messages/dialog/dialog_message.rs similarity index 74% rename from editor/src/dialog/dialog_message.rs rename to editor/src/messages/dialog/dialog_message.rs index 4d8ff289b3..1c9cefb16c 100644 --- a/editor/src/dialog/dialog_message.rs +++ b/editor/src/messages/dialog/dialog_message.rs @@ -1,5 +1,6 @@ -use super::{ExportDialogUpdate, NewDocumentDialogUpdate}; -use crate::message_prelude::*; +use super::export_dialog::ExportDialogMessage; +use super::new_document_dialog::NewDocumentDialogMessage; +use crate::messages::prelude::*; use serde::{Deserialize, Serialize}; @@ -10,10 +11,10 @@ pub enum DialogMessage { // Sub-messages #[remain::unsorted] #[child] - ExportDialog(ExportDialogUpdate), + ExportDialog(ExportDialogMessage), #[remain::unsorted] #[child] - NewDocumentDialog(NewDocumentDialogUpdate), + NewDocumentDialog(NewDocumentDialogMessage), // Messages CloseAllDocumentsWithConfirmation, diff --git a/editor/src/dialog/dialog_message_handler.rs b/editor/src/messages/dialog/dialog_message_handler.rs similarity index 78% rename from editor/src/dialog/dialog_message_handler.rs rename to editor/src/messages/dialog/dialog_message_handler.rs index c2512302a4..818816fb98 100644 --- a/editor/src/dialog/dialog_message_handler.rs +++ b/editor/src/messages/dialog/dialog_message_handler.rs @@ -1,27 +1,26 @@ -use super::*; -use crate::document::PortfolioMessageHandler; -use crate::layout::layout_message::LayoutTarget; -use crate::layout::widgets::PropertyHolder; -use crate::message_prelude::*; +use super::simple_dialogs::{self, AboutGraphiteDialog, ComingSoonDialog}; +use crate::messages::layout::utility_types::layout_widget::PropertyHolder; +use crate::messages::layout::utility_types::misc::LayoutTarget; +use crate::messages::prelude::*; #[derive(Debug, Default, Clone)] pub struct DialogMessageHandler { - export_dialog: Export, - new_document_dialog: NewDocument, + export_dialog: ExportDialogMessageHandler, + new_document_dialog: NewDocumentDialogMessageHandler, } impl MessageHandler for DialogMessageHandler { #[remain::check] - fn process_action(&mut self, message: DialogMessage, portfolio: &PortfolioMessageHandler, responses: &mut VecDeque) { + fn process_message(&mut self, message: DialogMessage, portfolio: &PortfolioMessageHandler, responses: &mut VecDeque) { #[remain::sorted] match message { #[remain::unsorted] - DialogMessage::ExportDialog(message) => self.export_dialog.process_action(message, (), responses), + DialogMessage::ExportDialog(message) => self.export_dialog.process_message(message, (), responses), #[remain::unsorted] - DialogMessage::NewDocumentDialog(message) => self.new_document_dialog.process_action(message, (), responses), + DialogMessage::NewDocumentDialog(message) => self.new_document_dialog.process_message(message, (), responses), DialogMessage::CloseAllDocumentsWithConfirmation => { - let dialog = dialogs::CloseAllDocuments; + let dialog = simple_dialogs::CloseAllDocumentsDialog; dialog.register_properties(responses, LayoutTarget::DialogDetails); responses.push_back(FrontendMessage::DisplayDialog { icon: "Copy".to_string() }.into()); } @@ -32,7 +31,7 @@ impl MessageHandler for DialogMessageHa } } DialogMessage::DisplayDialogError { title, description } => { - let dialog = dialogs::Error { title, description }; + let dialog = simple_dialogs::ErrorDialog { title, description }; dialog.register_properties(responses, LayoutTarget::DialogDetails); responses.push_back(FrontendMessage::DisplayDialog { icon: "Warning".to_string() }.into()); } @@ -45,13 +44,13 @@ impl MessageHandler for DialogMessageHa ); } DialogMessage::RequestAboutGraphiteDialogWithLocalizedCommitDate { localized_commit_date } => { - let about_graphite = AboutGraphite { localized_commit_date }; + let about_graphite = AboutGraphiteDialog { localized_commit_date }; about_graphite.register_properties(responses, LayoutTarget::DialogDetails); responses.push_back(FrontendMessage::DisplayDialog { icon: "GraphiteLogo".to_string() }.into()); } DialogMessage::RequestComingSoonDialog { issue } => { - let coming_soon = ComingSoon { issue }; + let coming_soon = ComingSoonDialog { issue }; coming_soon.register_properties(responses, LayoutTarget::DialogDetails); responses.push_back(FrontendMessage::DisplayDialog { icon: "Warning".to_string() }.into()); } @@ -78,7 +77,7 @@ impl MessageHandler for DialogMessageHa }) .collect(); - self.export_dialog = Export { + self.export_dialog = ExportDialogMessageHandler { file_name: document.name.clone(), scale_factor: 1., artboards, @@ -90,7 +89,7 @@ impl MessageHandler for DialogMessageHa } } DialogMessage::RequestNewDocumentDialog => { - self.new_document_dialog = NewDocument { + self.new_document_dialog = NewDocumentDialogMessageHandler { name: portfolio.generate_new_document_name(), infinite: true, dimensions: glam::UVec2::new(1920, 1080), diff --git a/editor/src/messages/dialog/export_dialog/export_dialog_message.rs b/editor/src/messages/dialog/export_dialog/export_dialog_message.rs new file mode 100644 index 0000000000..1c7b491bb7 --- /dev/null +++ b/editor/src/messages/dialog/export_dialog/export_dialog_message.rs @@ -0,0 +1,15 @@ +use crate::messages::frontend::utility_types::{ExportBounds, FileType}; +use crate::messages::prelude::*; + +use serde::{Deserialize, Serialize}; + +#[impl_message(Message, DialogMessage, ExportDialog)] +#[derive(PartialEq, Clone, Debug, Serialize, Deserialize)] +pub enum ExportDialogMessage { + FileName(String), + FileType(FileType), + ScaleFactor(f64), + ExportBounds(ExportBounds), + + Submit, +} diff --git a/editor/src/dialog/dialogs/export_dialog.rs b/editor/src/messages/dialog/export_dialog/export_dialog_message_handler.rs similarity index 72% rename from editor/src/dialog/dialogs/export_dialog.rs rename to editor/src/messages/dialog/export_dialog/export_dialog_message_handler.rs index 657eabc5c1..a842492c5f 100644 --- a/editor/src/dialog/dialogs/export_dialog.rs +++ b/editor/src/messages/dialog/export_dialog/export_dialog_message_handler.rs @@ -1,15 +1,19 @@ +use super::ExportDialogMessage; +use crate::messages::frontend::utility_types::{ExportBounds, FileType}; +use crate::messages::layout::utility_types::layout_widget::{Layout, LayoutGroup, PropertyHolder, Widget, WidgetCallback, WidgetHolder, WidgetLayout}; +use crate::messages::layout::utility_types::misc::LayoutTarget; +use crate::messages::layout::utility_types::widgets::button_widgets::TextButton; +use crate::messages::layout::utility_types::widgets::input_widgets::{DropdownEntryData, DropdownInput, NumberInput, RadioEntryData, RadioInput, TextInput}; +use crate::messages::layout::utility_types::widgets::label_widgets::{Separator, SeparatorDirection, SeparatorType, TextLabel}; +use crate::messages::prelude::*; + +use graphene::LayerId; + use std::collections::HashMap; -use crate::frontend::utility_types::{ExportBounds, FileType}; -use crate::layout::layout_message::LayoutTarget; -use crate::layout::widgets::*; -use crate::message_prelude::*; - -use serde::{Deserialize, Serialize}; - /// A dialog to allow users to customize their file export. #[derive(Debug, Clone, Default)] -pub struct Export { +pub struct ExportDialogMessageHandler { pub file_name: String, pub file_type: FileType, pub scale_factor: f64, @@ -18,7 +22,32 @@ pub struct Export { pub has_selection: bool, } -impl PropertyHolder for Export { +impl MessageHandler for ExportDialogMessageHandler { + fn process_message(&mut self, message: ExportDialogMessage, _data: (), responses: &mut VecDeque) { + match message { + ExportDialogMessage::FileName(name) => self.file_name = name, + ExportDialogMessage::FileType(export_type) => self.file_type = export_type, + ExportDialogMessage::ScaleFactor(x) => self.scale_factor = x, + ExportDialogMessage::ExportBounds(export_area) => self.bounds = export_area, + + ExportDialogMessage::Submit => responses.push_front( + DocumentMessage::ExportDocument { + file_name: self.file_name.clone(), + file_type: self.file_type, + scale_factor: self.scale_factor, + bounds: self.bounds, + } + .into(), + ), + } + + self.register_properties(responses, LayoutTarget::DialogDetails); + } + + advertise_actions! {ExportDialogUpdate;} +} + +impl PropertyHolder for ExportDialogMessageHandler { fn properties(&self) -> Layout { let file_name = vec![ WidgetHolder::new(Widget::TextLabel(TextLabel { @@ -32,7 +61,7 @@ impl PropertyHolder for Export { })), WidgetHolder::new(Widget::TextInput(TextInput { value: self.file_name.clone(), - on_update: WidgetCallback::new(|text_input: &TextInput| ExportDialogUpdate::FileName(text_input.value.clone()).into()), + on_update: WidgetCallback::new(|text_input: &TextInput| ExportDialogMessage::FileName(text_input.value.clone()).into()), ..Default::default() })), ]; @@ -41,7 +70,7 @@ impl PropertyHolder for Export { .into_iter() .map(|(val, name)| RadioEntryData { label: name.into(), - on_update: WidgetCallback::new(move |_| ExportDialogUpdate::FileType(val).into()), + on_update: WidgetCallback::new(move |_| ExportDialogMessage::FileType(val).into()), ..RadioEntryData::default() }) .collect(); @@ -73,7 +102,7 @@ impl PropertyHolder for Export { .into_iter() .map(|(val, name, disabled)| DropdownEntryData { label: name, - on_update: WidgetCallback::new(move |_| ExportDialogUpdate::ExportBounds(val).into()), + on_update: WidgetCallback::new(move |_| ExportDialogMessage::ExportBounds(val).into()), disabled, ..Default::default() }) @@ -112,7 +141,7 @@ impl PropertyHolder for Export { unit: " ".into(), min: Some(0.), disabled: self.file_type == FileType::Svg, - on_update: WidgetCallback::new(|number_input: &NumberInput| ExportDialogUpdate::ScaleFactor(number_input.value.unwrap()).into()), + on_update: WidgetCallback::new(|number_input: &NumberInput| ExportDialogMessage::ScaleFactor(number_input.value.unwrap()).into()), ..NumberInput::default() })), ]; @@ -124,7 +153,7 @@ impl PropertyHolder for Export { emphasized: true, on_update: WidgetCallback::new(|_| { DialogMessage::CloseDialogAndThen { - followups: vec![ExportDialogUpdate::Submit.into()], + followups: vec![ExportDialogMessage::Submit.into()], } .into() }), @@ -154,39 +183,3 @@ impl PropertyHolder for Export { ])) } } - -#[impl_message(Message, DialogMessage, ExportDialog)] -#[derive(PartialEq, Clone, Debug, Serialize, Deserialize)] -pub enum ExportDialogUpdate { - FileName(String), - FileType(FileType), - ScaleFactor(f64), - ExportBounds(ExportBounds), - - Submit, -} - -impl MessageHandler for Export { - fn process_action(&mut self, action: ExportDialogUpdate, _data: (), responses: &mut VecDeque) { - match action { - ExportDialogUpdate::FileName(name) => self.file_name = name, - ExportDialogUpdate::FileType(export_type) => self.file_type = export_type, - ExportDialogUpdate::ScaleFactor(x) => self.scale_factor = x, - ExportDialogUpdate::ExportBounds(export_area) => self.bounds = export_area, - - ExportDialogUpdate::Submit => responses.push_front( - DocumentMessage::ExportDocument { - file_name: self.file_name.clone(), - file_type: self.file_type, - scale_factor: self.scale_factor, - bounds: self.bounds, - } - .into(), - ), - } - - self.register_properties(responses, LayoutTarget::DialogDetails); - } - - advertise_actions! {ExportDialogUpdate;} -} diff --git a/editor/src/messages/dialog/export_dialog/mod.rs b/editor/src/messages/dialog/export_dialog/mod.rs new file mode 100644 index 0000000000..0fff9226fd --- /dev/null +++ b/editor/src/messages/dialog/export_dialog/mod.rs @@ -0,0 +1,7 @@ +mod export_dialog_message; +mod export_dialog_message_handler; + +#[doc(inline)] +pub use export_dialog_message::{ExportDialogMessage, ExportDialogMessageDiscriminant}; +#[doc(inline)] +pub use export_dialog_message_handler::ExportDialogMessageHandler; diff --git a/editor/src/messages/dialog/mod.rs b/editor/src/messages/dialog/mod.rs new file mode 100644 index 0000000000..c1cbfca98e --- /dev/null +++ b/editor/src/messages/dialog/mod.rs @@ -0,0 +1,18 @@ +//! Handles modal dialogs that appear as floating menus in the center of the editor window. +//! +//! Dialogs are represented as structs that implement the `PropertyHolder` trait. +//! +//! To open a dialog, call the function `register_properties` on the dialog struct with `responses` and the `LayoutTarget::DialogDetails` enum variant. +//! Then dialog can be opened by sending the `FrontendMessage::DisplayDialog` message; + +mod dialog_message; +mod dialog_message_handler; + +pub mod export_dialog; +pub mod new_document_dialog; +pub mod simple_dialogs; + +#[doc(inline)] +pub use dialog_message::{DialogMessage, DialogMessageDiscriminant}; +#[doc(inline)] +pub use dialog_message_handler::DialogMessageHandler; diff --git a/editor/src/messages/dialog/new_document_dialog/mod.rs b/editor/src/messages/dialog/new_document_dialog/mod.rs new file mode 100644 index 0000000000..c179de74d1 --- /dev/null +++ b/editor/src/messages/dialog/new_document_dialog/mod.rs @@ -0,0 +1,7 @@ +mod new_document_dialog_message; +mod new_document_dialog_message_handler; + +#[doc(inline)] +pub use new_document_dialog_message::{NewDocumentDialogMessage, NewDocumentDialogMessageDiscriminant}; +#[doc(inline)] +pub use new_document_dialog_message_handler::NewDocumentDialogMessageHandler; diff --git a/editor/src/messages/dialog/new_document_dialog/new_document_dialog_message.rs b/editor/src/messages/dialog/new_document_dialog/new_document_dialog_message.rs new file mode 100644 index 0000000000..28614812b8 --- /dev/null +++ b/editor/src/messages/dialog/new_document_dialog/new_document_dialog_message.rs @@ -0,0 +1,14 @@ +use crate::messages::prelude::*; + +use serde::{Deserialize, Serialize}; + +#[impl_message(Message, DialogMessage, NewDocumentDialog)] +#[derive(PartialEq, Clone, Debug, Serialize, Deserialize)] +pub enum NewDocumentDialogMessage { + Name(String), + Infinite(bool), + DimensionsX(f64), + DimensionsY(f64), + + Submit, +} diff --git a/editor/src/dialog/dialogs/new_document_dialog.rs b/editor/src/messages/dialog/new_document_dialog/new_document_dialog_message_handler.rs similarity index 70% rename from editor/src/dialog/dialogs/new_document_dialog.rs rename to editor/src/messages/dialog/new_document_dialog/new_document_dialog_message_handler.rs index ecc7f6072f..802c363f6b 100644 --- a/editor/src/dialog/dialogs/new_document_dialog.rs +++ b/editor/src/messages/dialog/new_document_dialog/new_document_dialog_message_handler.rs @@ -1,19 +1,53 @@ -use crate::layout::layout_message::LayoutTarget; -use crate::layout::widgets::*; -use crate::message_prelude::*; +use super::NewDocumentDialogMessage; +use crate::messages::layout::utility_types::layout_widget::{Layout, LayoutGroup, PropertyHolder, Widget, WidgetCallback, WidgetHolder, WidgetLayout}; +use crate::messages::layout::utility_types::misc::LayoutTarget; +use crate::messages::layout::utility_types::widgets::button_widgets::TextButton; +use crate::messages::layout::utility_types::widgets::input_widgets::{CheckboxInput, NumberInput, TextInput}; +use crate::messages::layout::utility_types::widgets::label_widgets::{Separator, SeparatorDirection, SeparatorType, TextLabel}; +use crate::messages::prelude::*; use glam::UVec2; -use serde::{Deserialize, Serialize}; /// A dialog to allow users to set some initial options about a new document. #[derive(Debug, Clone, Default)] -pub struct NewDocument { +pub struct NewDocumentDialogMessageHandler { pub name: String, pub infinite: bool, pub dimensions: UVec2, } -impl PropertyHolder for NewDocument { +impl MessageHandler for NewDocumentDialogMessageHandler { + fn process_message(&mut self, message: NewDocumentDialogMessage, _data: (), responses: &mut VecDeque) { + match message { + NewDocumentDialogMessage::Name(name) => self.name = name, + NewDocumentDialogMessage::Infinite(infinite) => self.infinite = infinite, + NewDocumentDialogMessage::DimensionsX(x) => self.dimensions.x = x as u32, + NewDocumentDialogMessage::DimensionsY(y) => self.dimensions.y = y as u32, + + NewDocumentDialogMessage::Submit => { + responses.push_back(PortfolioMessage::NewDocumentWithName { name: self.name.clone() }.into()); + + if !self.infinite && self.dimensions.x > 0 && self.dimensions.y > 0 { + responses.push_back( + ArtboardMessage::AddArtboard { + id: None, + position: (0., 0.), + size: (self.dimensions.x as f64, self.dimensions.y as f64), + } + .into(), + ); + responses.push_back(DocumentMessage::ZoomCanvasToFitAll.into()); + } + } + } + + self.register_properties(responses, LayoutTarget::DialogDetails); + } + + advertise_actions! {NewDocumentDialogUpdate;} +} + +impl PropertyHolder for NewDocumentDialogMessageHandler { fn properties(&self) -> Layout { let title = vec![WidgetHolder::new(Widget::TextLabel(TextLabel { value: "New document".into(), @@ -33,7 +67,7 @@ impl PropertyHolder for NewDocument { })), WidgetHolder::new(Widget::TextInput(TextInput { value: self.name.clone(), - on_update: WidgetCallback::new(|text_input: &TextInput| NewDocumentDialogUpdate::Name(text_input.value.clone()).into()), + on_update: WidgetCallback::new(|text_input: &TextInput| NewDocumentDialogMessage::Name(text_input.value.clone()).into()), ..Default::default() })), ]; @@ -51,7 +85,7 @@ impl PropertyHolder for NewDocument { WidgetHolder::new(Widget::CheckboxInput(CheckboxInput { checked: self.infinite, icon: "Checkmark".to_string(), - on_update: WidgetCallback::new(|checkbox_input: &CheckboxInput| NewDocumentDialogUpdate::Infinite(checkbox_input.checked).into()), + on_update: WidgetCallback::new(|checkbox_input: &CheckboxInput| NewDocumentDialogMessage::Infinite(checkbox_input.checked).into()), ..Default::default() })), ]; @@ -73,7 +107,7 @@ impl PropertyHolder for NewDocument { disabled: self.infinite, is_integer: true, min: Some(0.), - on_update: WidgetCallback::new(|number_input: &NumberInput| NewDocumentDialogUpdate::DimensionsX(number_input.value.unwrap()).into()), + on_update: WidgetCallback::new(|number_input: &NumberInput| NewDocumentDialogMessage::DimensionsX(number_input.value.unwrap()).into()), ..NumberInput::default() })), WidgetHolder::new(Widget::Separator(Separator { @@ -87,7 +121,7 @@ impl PropertyHolder for NewDocument { disabled: self.infinite, is_integer: true, min: Some(0.), - on_update: WidgetCallback::new(|number_input: &NumberInput| NewDocumentDialogUpdate::DimensionsY(number_input.value.unwrap()).into()), + on_update: WidgetCallback::new(|number_input: &NumberInput| NewDocumentDialogMessage::DimensionsY(number_input.value.unwrap()).into()), ..NumberInput::default() })), ]; @@ -99,7 +133,7 @@ impl PropertyHolder for NewDocument { emphasized: true, on_update: WidgetCallback::new(|_| { DialogMessage::CloseDialogAndThen { - followups: vec![NewDocumentDialogUpdate::Submit.into()], + followups: vec![NewDocumentDialogMessage::Submit.into()], } .into() }), @@ -122,45 +156,3 @@ impl PropertyHolder for NewDocument { ])) } } - -#[impl_message(Message, DialogMessage, NewDocumentDialog)] -#[derive(PartialEq, Clone, Debug, Serialize, Deserialize)] -pub enum NewDocumentDialogUpdate { - Name(String), - Infinite(bool), - DimensionsX(f64), - DimensionsY(f64), - - Submit, -} - -impl MessageHandler for NewDocument { - fn process_action(&mut self, action: NewDocumentDialogUpdate, _data: (), responses: &mut VecDeque) { - match action { - NewDocumentDialogUpdate::Name(name) => self.name = name, - NewDocumentDialogUpdate::Infinite(infinite) => self.infinite = infinite, - NewDocumentDialogUpdate::DimensionsX(x) => self.dimensions.x = x as u32, - NewDocumentDialogUpdate::DimensionsY(y) => self.dimensions.y = y as u32, - - NewDocumentDialogUpdate::Submit => { - responses.push_back(PortfolioMessage::NewDocumentWithName { name: self.name.clone() }.into()); - - if !self.infinite && self.dimensions.x > 0 && self.dimensions.y > 0 { - responses.push_back( - ArtboardMessage::AddArtboard { - id: None, - position: (0., 0.), - size: (self.dimensions.x as f64, self.dimensions.y as f64), - } - .into(), - ); - responses.push_back(DocumentMessage::ZoomCanvasToFitAll.into()); - } - } - } - - self.register_properties(responses, LayoutTarget::DialogDetails); - } - - advertise_actions! {NewDocumentDialogUpdate;} -} diff --git a/editor/src/dialog/dialogs/about_dialog.rs b/editor/src/messages/dialog/simple_dialogs/about_graphite_dialog.rs similarity index 75% rename from editor/src/dialog/dialogs/about_dialog.rs rename to editor/src/messages/dialog/simple_dialogs/about_graphite_dialog.rs index e19a9ba051..d79be9e957 100644 --- a/editor/src/dialog/dialogs/about_dialog.rs +++ b/editor/src/messages/dialog/simple_dialogs/about_graphite_dialog.rs @@ -1,13 +1,15 @@ -use crate::layout::widgets::*; -use crate::message_prelude::FrontendMessage; -use crate::misc::build_metadata::{commit_info_localized, release_series}; +use crate::application::{commit_info_localized, release_series}; +use crate::messages::layout::utility_types::layout_widget::{Layout, LayoutGroup, PropertyHolder, Widget, WidgetCallback, WidgetHolder, WidgetLayout}; +use crate::messages::layout::utility_types::widgets::button_widgets::TextButton; +use crate::messages::layout::utility_types::widgets::label_widgets::TextLabel; +use crate::messages::prelude::*; /// A dialog for displaying information on [BuildMetadata] viewable via *Help* > *About Graphite* in the menu bar. -pub struct AboutGraphite { +pub struct AboutGraphiteDialog { pub localized_commit_date: String, } -impl PropertyHolder for AboutGraphite { +impl PropertyHolder for AboutGraphiteDialog { fn properties(&self) -> Layout { let links = [ ("Website", "https://graphite.rs"), diff --git a/editor/src/dialog/dialogs/close_all_documents_dialog.rs b/editor/src/messages/dialog/simple_dialogs/close_all_documents_dialog.rs similarity index 74% rename from editor/src/dialog/dialogs/close_all_documents_dialog.rs rename to editor/src/messages/dialog/simple_dialogs/close_all_documents_dialog.rs index 917b3cc74a..479c9b8ca1 100644 --- a/editor/src/dialog/dialogs/close_all_documents_dialog.rs +++ b/editor/src/messages/dialog/simple_dialogs/close_all_documents_dialog.rs @@ -1,10 +1,12 @@ -use crate::layout::widgets::*; -use crate::message_prelude::{DialogMessage, FrontendMessage, PortfolioMessage}; +use crate::messages::layout::utility_types::layout_widget::{Layout, LayoutGroup, PropertyHolder, Widget, WidgetCallback, WidgetHolder, WidgetLayout}; +use crate::messages::layout::utility_types::widgets::button_widgets::TextButton; +use crate::messages::layout::utility_types::widgets::label_widgets::TextLabel; +use crate::messages::prelude::*; /// A dialog for confirming the closing of all documents viewable via `file -> close all` in the menu bar. -pub struct CloseAllDocuments; +pub struct CloseAllDocumentsDialog; -impl PropertyHolder for CloseAllDocuments { +impl PropertyHolder for CloseAllDocumentsDialog { fn properties(&self) -> Layout { let button_widgets = vec![ WidgetHolder::new(Widget::TextButton(TextButton { diff --git a/editor/src/dialog/dialogs/close_document_dialog.rs b/editor/src/messages/dialog/simple_dialogs/close_document_dialog.rs similarity index 70% rename from editor/src/dialog/dialogs/close_document_dialog.rs rename to editor/src/messages/dialog/simple_dialogs/close_document_dialog.rs index 2d54520c9a..be3b3cc217 100644 --- a/editor/src/dialog/dialogs/close_document_dialog.rs +++ b/editor/src/messages/dialog/simple_dialogs/close_document_dialog.rs @@ -1,13 +1,17 @@ -use crate::layout::widgets::*; -use crate::message_prelude::*; +use crate::messages::broadcast::broadcast_event::BroadcastEvent; +use crate::messages::layout::utility_types::layout_widget::{Layout, LayoutGroup, PropertyHolder, Widget, WidgetCallback, WidgetHolder, WidgetLayout}; +use crate::messages::layout::utility_types::widgets::button_widgets::TextButton; +use crate::messages::layout::utility_types::widgets::label_widgets::TextLabel; +use crate::messages::portfolio::document::DocumentMessage; +use crate::messages::prelude::*; /// A dialog for confirming the closing a document with unsaved changes. -pub struct CloseDocument { +pub struct CloseDocumentDialog { pub document_name: String, pub document_id: u64, } -impl PropertyHolder for CloseDocument { +impl PropertyHolder for CloseDocumentDialog { fn properties(&self) -> Layout { let document_id = self.document_id; @@ -29,7 +33,7 @@ impl PropertyHolder for CloseDocument { min_width: 96, on_update: WidgetCallback::new(move |_| { DialogMessage::CloseDialogAndThen { - followups: vec![BroadcastSignal::ToolAbort.into(), PortfolioMessage::CloseDocument { document_id }.into()], + followups: vec![BroadcastEvent::ToolAbort.into(), PortfolioMessage::CloseDocument { document_id }.into()], } .into() }), diff --git a/editor/src/dialog/dialogs/coming_soon_dialog.rs b/editor/src/messages/dialog/simple_dialogs/coming_soon_dialog.rs similarity index 77% rename from editor/src/dialog/dialogs/coming_soon_dialog.rs rename to editor/src/messages/dialog/simple_dialogs/coming_soon_dialog.rs index 6a724dcd19..19938242b8 100644 --- a/editor/src/dialog/dialogs/coming_soon_dialog.rs +++ b/editor/src/messages/dialog/simple_dialogs/coming_soon_dialog.rs @@ -1,14 +1,16 @@ -use crate::layout::widgets::*; -use crate::message_prelude::FrontendMessage; +use crate::messages::layout::utility_types::layout_widget::{Layout, LayoutGroup, PropertyHolder, Widget, WidgetCallback, WidgetHolder, WidgetLayout}; +use crate::messages::layout::utility_types::widgets::button_widgets::TextButton; +use crate::messages::layout::utility_types::widgets::label_widgets::TextLabel; +use crate::messages::prelude::*; use std::fmt::Write; /// A dialog to notify users of an unfinished issue, optionally with an issue number. -pub struct ComingSoon { +pub struct ComingSoonDialog { pub issue: Option, } -impl PropertyHolder for ComingSoon { +impl PropertyHolder for ComingSoonDialog { fn properties(&self) -> Layout { let mut details = "This feature is not implemented yet".to_string(); let mut buttons = vec![WidgetHolder::new(Widget::TextButton(TextButton { diff --git a/editor/src/dialog/dialogs/error_dialog.rs b/editor/src/messages/dialog/simple_dialogs/error_dialog.rs similarity index 67% rename from editor/src/dialog/dialogs/error_dialog.rs rename to editor/src/messages/dialog/simple_dialogs/error_dialog.rs index 3041e02ef5..1b30980284 100644 --- a/editor/src/dialog/dialogs/error_dialog.rs +++ b/editor/src/messages/dialog/simple_dialogs/error_dialog.rs @@ -1,13 +1,15 @@ -use crate::layout::widgets::*; -use crate::message_prelude::FrontendMessage; +use crate::messages::layout::utility_types::layout_widget::{Layout, LayoutGroup, PropertyHolder, Widget, WidgetCallback, WidgetHolder, WidgetLayout}; +use crate::messages::layout::utility_types::widgets::button_widgets::TextButton; +use crate::messages::layout::utility_types::widgets::label_widgets::TextLabel; +use crate::messages::prelude::*; /// A dialog to notify users of a non-fatal error. -pub struct Error { +pub struct ErrorDialog { pub title: String, pub description: String, } -impl PropertyHolder for Error { +impl PropertyHolder for ErrorDialog { fn properties(&self) -> Layout { Layout::WidgetLayout(WidgetLayout::new(vec![ LayoutGroup::Row { diff --git a/editor/src/messages/dialog/simple_dialogs/mod.rs b/editor/src/messages/dialog/simple_dialogs/mod.rs new file mode 100644 index 0000000000..5a018b61f8 --- /dev/null +++ b/editor/src/messages/dialog/simple_dialogs/mod.rs @@ -0,0 +1,11 @@ +mod about_graphite_dialog; +mod close_all_documents_dialog; +mod close_document_dialog; +mod coming_soon_dialog; +mod error_dialog; + +pub use about_graphite_dialog::AboutGraphiteDialog; +pub use close_all_documents_dialog::CloseAllDocumentsDialog; +pub use close_document_dialog::CloseDocumentDialog; +pub use coming_soon_dialog::ComingSoonDialog; +pub use error_dialog::ErrorDialog; diff --git a/editor/src/frontend/frontend_message.rs b/editor/src/messages/frontend/frontend_message.rs similarity index 87% rename from editor/src/frontend/frontend_message.rs rename to editor/src/messages/frontend/frontend_message.rs index 66e4abbdbc..8071cf34eb 100644 --- a/editor/src/frontend/frontend_message.rs +++ b/editor/src/messages/frontend/frontend_message.rs @@ -1,11 +1,12 @@ use super::utility_types::{FrontendDocumentDetails, FrontendImageData, MouseCursorIcon}; -use crate::document::layer_panel::{LayerPanelEntry, RawBuffer}; -use crate::layout::layout_message::LayoutTarget; -use crate::layout::widgets::{MenuColumn, SubLayout}; -use crate::message_prelude::*; -use crate::misc::HintData; -use crate::Color; +use crate::messages::layout::utility_types::layout_widget::SubLayout; +use crate::messages::layout::utility_types::misc::LayoutTarget; +use crate::messages::layout::utility_types::widgets::menu_widgets::MenuColumn; +use crate::messages::portfolio::document::utility_types::layer_panel::{LayerPanelEntry, RawBuffer}; +use crate::messages::prelude::*; +use crate::messages::tool::utility_types::HintData; +use graphene::color::Color; use graphene::layers::text_layer::Font; use serde::{Deserialize, Serialize}; diff --git a/editor/src/frontend/mod.rs b/editor/src/messages/frontend/mod.rs similarity index 100% rename from editor/src/frontend/mod.rs rename to editor/src/messages/frontend/mod.rs index 2b1c077c10..81d963f56a 100644 --- a/editor/src/frontend/mod.rs +++ b/editor/src/messages/frontend/mod.rs @@ -1,6 +1,6 @@ -pub mod utility_types; - mod frontend_message; +pub mod utility_types; + #[doc(inline)] pub use frontend_message::{FrontendMessage, FrontendMessageDiscriminant}; diff --git a/editor/src/frontend/utility_types.rs b/editor/src/messages/frontend/utility_types.rs similarity index 63% rename from editor/src/frontend/utility_types.rs rename to editor/src/messages/frontend/utility_types.rs index 9038fe55a8..1a576a1445 100644 --- a/editor/src/frontend/utility_types.rs +++ b/editor/src/messages/frontend/utility_types.rs @@ -15,8 +15,9 @@ pub struct FrontendImageData { pub image_data: Vec, } -#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize, Deserialize)] +#[derive(Clone, Copy, Debug, Default, Eq, PartialEq, Serialize, Deserialize)] pub enum MouseCursorIcon { + #[default] Default, ZoomIn, ZoomOut, @@ -29,25 +30,14 @@ pub enum MouseCursorIcon { NWSEResize, } -impl Default for MouseCursorIcon { - fn default() -> Self { - MouseCursorIcon::Default - } -} - -#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize, Deserialize)] +#[derive(Clone, Copy, Debug, Default, Eq, PartialEq, Serialize, Deserialize)] pub enum FileType { + #[default] Svg, Png, Jpg, } -impl Default for FileType { - fn default() -> Self { - FileType::Svg - } -} - impl FileType { pub fn to_mime(self) -> &'static str { match self { @@ -58,15 +48,10 @@ impl FileType { } } -#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize, Deserialize)] +#[derive(Clone, Copy, Debug, Default, Eq, PartialEq, Serialize, Deserialize)] pub enum ExportBounds { + #[default] AllArtwork, Selection, Artboard(LayerId), } - -impl Default for ExportBounds { - fn default() -> Self { - ExportBounds::AllArtwork - } -} diff --git a/editor/src/messages/input_mapper/default_mapping.rs b/editor/src/messages/input_mapper/default_mapping.rs new file mode 100644 index 0000000000..b9e943f765 --- /dev/null +++ b/editor/src/messages/input_mapper/default_mapping.rs @@ -0,0 +1,387 @@ +use crate::consts::{BIG_NUDGE_AMOUNT, NUDGE_AMOUNT}; +use crate::messages::input_mapper::input_mapper_message::InputMapperMessage; +use crate::messages::input_mapper::utility_types::input_keyboard::{Key, KeyStates}; +use crate::messages::input_mapper::utility_types::macros::*; +use crate::messages::input_mapper::utility_types::misc::MappingEntry; +use crate::messages::input_mapper::utility_types::misc::{KeyMappingEntries, Mapping}; +use crate::messages::portfolio::document::utility_types::clipboards::Clipboard; +use crate::messages::portfolio::document::utility_types::misc::KeyboardPlatformLayout; +use crate::messages::prelude::*; + +use glam::DVec2; + +pub fn default_mapping() -> Mapping { + use InputMapperMessage::*; + use Key::*; + + // NOTICE: + // If a new mapping you added here isn't working (and perhaps another lower-precedence one is instead), make sure to advertise + // it as an available action in the respective message handler file (such as the bottom of `document_message_handler.rs`). + + let mappings = mapping![ + // HIGHER PRIORITY: + // + // MovementMessage + entry!( + PointerMove; + refresh_keys=[KeyControl], + action_dispatch=MovementMessage::PointerMove { snap_angle: KeyControl, wait_for_snap_angle_release: true, snap_zoom: KeyControl, zoom_from_viewport: None }, + ), + // NORMAL PRIORITY: + // + // TransformLayerMessage + entry!(KeyDown(KeyEnter); action_dispatch=TransformLayerMessage::ApplyTransformOperation), + entry!(KeyDown(Lmb); action_dispatch=TransformLayerMessage::ApplyTransformOperation), + entry!(KeyDown(KeyEscape); action_dispatch=TransformLayerMessage::CancelTransformOperation), + entry!(KeyDown(Rmb); action_dispatch=TransformLayerMessage::CancelTransformOperation), + entry!(KeyDown(KeyX); action_dispatch=TransformLayerMessage::ConstrainX), + entry!(KeyDown(KeyY); action_dispatch=TransformLayerMessage::ConstrainY), + entry!(KeyDown(KeyBackspace); action_dispatch=TransformLayerMessage::TypeBackspace), + entry!(KeyDown(KeyMinus); action_dispatch=TransformLayerMessage::TypeNegate), + entry!(KeyDown(KeyComma); action_dispatch=TransformLayerMessage::TypeDecimalPoint), + entry!(KeyDown(KeyPeriod); action_dispatch=TransformLayerMessage::TypeDecimalPoint), + entry!(PointerMove; refresh_keys=[KeyShift, KeyControl], action_dispatch=TransformLayerMessage::PointerMove { slow_key: KeyShift, snap_key: KeyControl }), + // + // SelectToolMessage + entry!(PointerMove; refresh_keys=[KeyControl, KeyShift, KeyAlt], action_dispatch=SelectToolMessage::PointerMove { axis_align: KeyShift, snap_angle: KeyControl, center: KeyAlt }), + entry!(KeyDown(Lmb); action_dispatch=SelectToolMessage::DragStart { add_to_selection: KeyShift }), + entry!(KeyUp(Lmb); action_dispatch=SelectToolMessage::DragStop), + entry!(KeyDown(KeyEnter); action_dispatch=SelectToolMessage::DragStop), + entry!(DoubleClick; action_dispatch=SelectToolMessage::EditLayer), + entry!(KeyDown(Rmb); action_dispatch=SelectToolMessage::Abort), + entry!(KeyDown(KeyEscape); action_dispatch=SelectToolMessage::Abort), + // + // ArtboardToolMessage + entry!(KeyDown(Lmb); action_dispatch=ArtboardToolMessage::PointerDown), + entry!(PointerMove; refresh_keys=[KeyShift, KeyAlt], action_dispatch=ArtboardToolMessage::PointerMove { constrain_axis_or_aspect: KeyShift, center: KeyAlt }), + entry!(KeyUp(Lmb); action_dispatch=ArtboardToolMessage::PointerUp), + entry!(KeyDown(KeyDelete); action_dispatch=ArtboardToolMessage::DeleteSelected), + entry!(KeyDown(KeyBackspace); action_dispatch=ArtboardToolMessage::DeleteSelected), + // + // NavigateToolMessage + entry!(KeyUp(Lmb); modifiers=[KeyShift], action_dispatch=NavigateToolMessage::ClickZoom { zoom_in: false }), + entry!(KeyUp(Lmb); action_dispatch=NavigateToolMessage::ClickZoom { zoom_in: true }), + entry!(PointerMove; refresh_keys=[KeyControl], action_dispatch=NavigateToolMessage::PointerMove { snap_angle: KeyControl, snap_zoom: KeyControl }), + entry!(KeyDown(Mmb); action_dispatch=NavigateToolMessage::TranslateCanvasBegin), + entry!(KeyDown(Rmb); action_dispatch=NavigateToolMessage::RotateCanvasBegin), + entry!(KeyDown(Lmb); action_dispatch=NavigateToolMessage::ZoomCanvasBegin), + entry!(KeyUp(Rmb); action_dispatch=NavigateToolMessage::TransformCanvasEnd), + entry!(KeyUp(Lmb); action_dispatch=NavigateToolMessage::TransformCanvasEnd), + entry!(KeyUp(Mmb); action_dispatch=NavigateToolMessage::TransformCanvasEnd), + // + // EyedropperToolMessage + entry!(KeyDown(Lmb); action_dispatch=EyedropperToolMessage::LeftMouseDown), + entry!(KeyDown(Rmb); action_dispatch=EyedropperToolMessage::RightMouseDown), + // + // TextToolMessage + entry!(KeyUp(Lmb); action_dispatch=TextToolMessage::Interact), + entry!(KeyDown(KeyEscape); action_dispatch=TextToolMessage::Abort), + entry_multiplatform!( + standard!(KeyDown(KeyEnter); modifiers=[KeyControl], action_dispatch=TextToolMessage::CommitText), + mac_only!(KeyDown(KeyEnter); modifiers=[KeyCommand], action_dispatch=TextToolMessage::CommitText), + ), + // + // GradientToolMessage + entry!(KeyDown(Lmb); action_dispatch=GradientToolMessage::PointerDown), + entry!(PointerMove; refresh_keys=[KeyShift], action_dispatch=GradientToolMessage::PointerMove { constrain_axis: KeyShift }), + entry!(KeyUp(Lmb); action_dispatch=GradientToolMessage::PointerUp), + // + // RectangleToolMessage + entry!(KeyDown(Lmb); action_dispatch=RectangleToolMessage::DragStart), + entry!(KeyUp(Lmb); action_dispatch=RectangleToolMessage::DragStop), + entry!(KeyDown(Rmb); action_dispatch=RectangleToolMessage::Abort), + entry!(KeyDown(KeyEscape); action_dispatch=RectangleToolMessage::Abort), + entry!(PointerMove; refresh_keys=[KeyAlt, KeyShift], action_dispatch=RectangleToolMessage::Resize { center: KeyAlt, lock_ratio: KeyShift }), + // + // EllipseToolMessage + entry!(KeyDown(Lmb); action_dispatch=EllipseToolMessage::DragStart), + entry!(KeyUp(Lmb); action_dispatch=EllipseToolMessage::DragStop), + entry!(KeyDown(Rmb); action_dispatch=EllipseToolMessage::Abort), + entry!(KeyDown(KeyEscape); action_dispatch=EllipseToolMessage::Abort), + entry!(PointerMove; refresh_keys=[KeyAlt, KeyShift], action_dispatch=EllipseToolMessage::Resize { center: KeyAlt, lock_ratio: KeyShift }), + // + // ShapeToolMessage + entry!(KeyDown(Lmb); action_dispatch=ShapeToolMessage::DragStart), + entry!(KeyUp(Lmb); action_dispatch=ShapeToolMessage::DragStop), + entry!(KeyDown(Rmb); action_dispatch=ShapeToolMessage::Abort), + entry!(KeyDown(KeyEscape); action_dispatch=ShapeToolMessage::Abort), + entry!(PointerMove; refresh_keys=[KeyAlt, KeyShift], action_dispatch=ShapeToolMessage::Resize { center: KeyAlt, lock_ratio: KeyShift }), + // + // LineToolMessage + entry!(KeyDown(Lmb); action_dispatch=LineToolMessage::DragStart), + entry!(KeyUp(Lmb); action_dispatch=LineToolMessage::DragStop), + entry!(KeyDown(Rmb); action_dispatch=LineToolMessage::Abort), + entry!(KeyDown(KeyEscape); action_dispatch=LineToolMessage::Abort), + entry!(PointerMove; refresh_keys=[KeyAlt, KeyShift, KeyControl], action_dispatch=LineToolMessage::Redraw { center: KeyAlt, lock_angle: KeyControl, snap_angle: KeyShift }), + // + // PathToolMessage + entry!(KeyDown(Lmb); action_dispatch=PathToolMessage::DragStart { add_to_selection: KeyShift }), + entry!(PointerMove; refresh_keys=[KeyAlt, KeyShift], action_dispatch=PathToolMessage::PointerMove { alt_mirror_angle: KeyAlt, shift_mirror_distance: KeyShift }), + entry!(KeyDown(KeyDelete); action_dispatch=PathToolMessage::Delete), + entry!(KeyDown(KeyBackspace); action_dispatch=PathToolMessage::Delete), + entry!(KeyUp(Lmb); action_dispatch=PathToolMessage::DragStop), + // + // PenToolMessage + entry!(PointerMove; refresh_keys=[KeyShift, KeyControl], action_dispatch=PenToolMessage::PointerMove { snap_angle: KeyControl, break_handle: KeyShift }), + entry!(KeyDown(Lmb); action_dispatch=PenToolMessage::DragStart), + entry!(KeyUp(Lmb); action_dispatch=PenToolMessage::DragStop), + entry!(KeyDown(Rmb); action_dispatch=PenToolMessage::Confirm), + entry!(KeyDown(KeyEscape); action_dispatch=PenToolMessage::Confirm), + entry!(KeyDown(KeyEnter); action_dispatch=PenToolMessage::Confirm), + // + // FreehandToolMessage + entry!(PointerMove; action_dispatch=FreehandToolMessage::PointerMove), + entry!(KeyDown(Lmb); action_dispatch=FreehandToolMessage::DragStart), + entry!(KeyUp(Lmb); action_dispatch=FreehandToolMessage::DragStop), + // + // SplineToolMessage + entry!(PointerMove; action_dispatch=SplineToolMessage::PointerMove), + entry!(KeyDown(Lmb); action_dispatch=SplineToolMessage::DragStart), + entry!(KeyUp(Lmb); action_dispatch=SplineToolMessage::DragStop), + entry!(KeyDown(Rmb); action_dispatch=SplineToolMessage::Confirm), + entry!(KeyDown(KeyEscape); action_dispatch=SplineToolMessage::Confirm), + entry!(KeyDown(KeyEnter); action_dispatch=SplineToolMessage::Confirm), + // + // FillToolMessage + entry!(KeyDown(Lmb); action_dispatch=FillToolMessage::LeftMouseDown), + entry!(KeyDown(Rmb); action_dispatch=FillToolMessage::RightMouseDown), + // + // ToolMessage + entry!(KeyDown(KeyV); action_dispatch=ToolMessage::ActivateToolSelect), + entry!(KeyDown(KeyZ); action_dispatch=ToolMessage::ActivateToolNavigate), + entry!(KeyDown(KeyI); action_dispatch=ToolMessage::ActivateToolEyedropper), + entry!(KeyDown(KeyT); action_dispatch=ToolMessage::ActivateToolText), + entry!(KeyDown(KeyF); action_dispatch=ToolMessage::ActivateToolFill), + entry!(KeyDown(KeyH); action_dispatch=ToolMessage::ActivateToolGradient), + entry!(KeyDown(KeyA); action_dispatch=ToolMessage::ActivateToolPath), + entry!(KeyDown(KeyP); action_dispatch=ToolMessage::ActivateToolPen), + entry!(KeyDown(KeyN); action_dispatch=ToolMessage::ActivateToolFreehand), + entry!(KeyDown(KeyL); action_dispatch=ToolMessage::ActivateToolLine), + entry!(KeyDown(KeyM); action_dispatch=ToolMessage::ActivateToolRectangle), + entry!(KeyDown(KeyE); action_dispatch=ToolMessage::ActivateToolEllipse), + entry!(KeyDown(KeyY); action_dispatch=ToolMessage::ActivateToolShape), + entry_multiplatform!( + standard!(KeyDown(KeyX); modifiers=[KeyShift, KeyControl], action_dispatch=ToolMessage::ResetColors), + mac_only!(KeyDown(KeyX); modifiers=[KeyShift, KeyCommand], action_dispatch=ToolMessage::ResetColors), + ), + entry!(KeyDown(KeyX); modifiers=[KeyShift], action_dispatch=ToolMessage::SwapColors), + entry!(KeyDown(KeyC); modifiers=[KeyAlt], action_dispatch=ToolMessage::SelectRandomPrimaryColor), + // + // DocumentMessage + entry!(KeyDown(KeyDelete); action_dispatch=DocumentMessage::DeleteSelectedLayers), + entry!(KeyDown(KeyBackspace); action_dispatch=DocumentMessage::DeleteSelectedLayers), + entry!(KeyDown(KeyP); modifiers=[KeyAlt], action_dispatch=DocumentMessage::DebugPrintDocument), + entry_multiplatform!( + standard!(KeyDown(KeyZ); modifiers=[KeyControl, KeyShift], action_dispatch=DocumentMessage::Redo), + mac_only!(KeyDown(KeyZ); modifiers=[KeyCommand, KeyShift], action_dispatch=DocumentMessage::Redo), + ), + entry_multiplatform!( + standard!(KeyDown(KeyZ); modifiers=[KeyControl], action_dispatch=DocumentMessage::Undo), + mac_only!(KeyDown(KeyZ); modifiers=[KeyCommand], action_dispatch=DocumentMessage::Undo), + ), + entry_multiplatform!( + standard!(KeyDown(KeyA); modifiers=[KeyControl, KeyAlt], action_dispatch=DocumentMessage::DeselectAllLayers), + mac_only!(KeyDown(KeyA); modifiers=[KeyCommand, KeyAlt], action_dispatch=DocumentMessage::DeselectAllLayers), + ), + entry_multiplatform!( + standard!(KeyDown(KeyA); modifiers=[KeyControl], action_dispatch=DocumentMessage::SelectAllLayers), + mac_only!(KeyDown(KeyA); modifiers=[KeyCommand], action_dispatch=DocumentMessage::SelectAllLayers), + ), + entry_multiplatform!( + standard!(KeyDown(KeyS); modifiers=[KeyControl], action_dispatch=DocumentMessage::SaveDocument), + mac_only!(KeyDown(KeyS); modifiers=[KeyCommand], action_dispatch=DocumentMessage::SaveDocument), + ), + entry_multiplatform!( + standard!(KeyDown(Key0); modifiers=[KeyControl], action_dispatch=DocumentMessage::ZoomCanvasToFitAll), + mac_only!(KeyDown(Key0); modifiers=[KeyCommand], action_dispatch=DocumentMessage::ZoomCanvasToFitAll), + ), + entry_multiplatform!( + standard!(KeyDown(KeyD); modifiers=[KeyControl], action_dispatch=DocumentMessage::DuplicateSelectedLayers), + mac_only!(KeyDown(KeyD); modifiers=[KeyCommand], action_dispatch=DocumentMessage::DuplicateSelectedLayers), + ), + entry_multiplatform!( + standard!(KeyDown(KeyG); modifiers=[KeyControl], action_dispatch=DocumentMessage::GroupSelectedLayers), + mac_only!(KeyDown(KeyG); modifiers=[KeyCommand], action_dispatch=DocumentMessage::GroupSelectedLayers), + ), + entry_multiplatform!( + standard!(KeyDown(KeyG); modifiers=[KeyControl, KeyShift], action_dispatch=DocumentMessage::UngroupSelectedLayers), + mac_only!(KeyDown(KeyG); modifiers=[KeyCommand, KeyShift], action_dispatch=DocumentMessage::UngroupSelectedLayers), + ), + entry_multiplatform!( + standard!(KeyDown(KeyN); modifiers=[KeyControl, KeyShift], action_dispatch=DocumentMessage::CreateEmptyFolder { container_path: vec![] }), + mac_only!(KeyDown(KeyN); modifiers=[KeyCommand, KeyShift], action_dispatch=DocumentMessage::CreateEmptyFolder { container_path: vec![] }), + ), + entry_multiplatform!( + standard!(KeyDown(KeyLeftBracket); modifiers=[KeyControl, KeyShift], action_dispatch=DocumentMessage::SelectedLayersLowerToBack), + mac_only!(KeyDown(KeyLeftBracket); modifiers=[KeyCommand, KeyShift], action_dispatch=DocumentMessage::SelectedLayersLowerToBack), + ), + entry_multiplatform!( + // TODO: Delete this in favor of the KeyLeftBracket (non-shifted version of this key) mapping above once the input system can distinguish between the non-shifted and shifted keys (important for other language keyboards) + standard!(KeyDown(KeyLeftCurlyBracket); modifiers=[KeyControl, KeyShift], action_dispatch=DocumentMessage::SelectedLayersLowerToBack), + mac_only!(KeyDown(KeyLeftCurlyBracket); modifiers=[KeyCommand, KeyShift], action_dispatch=DocumentMessage::SelectedLayersLowerToBack), + ), + entry_multiplatform!( + standard!(KeyDown(KeyRightBracket); modifiers=[KeyControl, KeyShift], action_dispatch=DocumentMessage::SelectedLayersRaiseToFront), + mac_only!(KeyDown(KeyRightBracket); modifiers=[KeyCommand, KeyShift], action_dispatch=DocumentMessage::SelectedLayersRaiseToFront), + ), + entry_multiplatform!( + // TODO: Delete this in favor of the KeyRightBracket (non-shifted version of this key) mapping above once the input system can distinguish between the non-shifted and shifted keys (important for other language keyboards) + standard!(KeyDown(KeyRightCurlyBracket); modifiers=[KeyControl, KeyShift], action_dispatch=DocumentMessage::SelectedLayersRaiseToFront), + mac_only!(KeyDown(KeyRightCurlyBracket); modifiers=[KeyCommand, KeyShift], action_dispatch=DocumentMessage::SelectedLayersRaiseToFront), + ), + entry_multiplatform!( + standard!(KeyDown(KeyLeftBracket); modifiers=[KeyControl], action_dispatch=DocumentMessage::SelectedLayersLower), + mac_only!(KeyDown(KeyLeftBracket); modifiers=[KeyCommand], action_dispatch=DocumentMessage::SelectedLayersLower), + ), + entry_multiplatform!( + standard!(KeyDown(KeyRightBracket); modifiers=[KeyControl], action_dispatch=DocumentMessage::SelectedLayersRaise), + mac_only!(KeyDown(KeyRightBracket); modifiers=[KeyCommand], action_dispatch=DocumentMessage::SelectedLayersRaise), + ), + entry!(KeyDown(KeyArrowUp); modifiers=[KeyShift, KeyArrowLeft], action_dispatch=DocumentMessage::NudgeSelectedLayers { delta_x: -BIG_NUDGE_AMOUNT, delta_y: -BIG_NUDGE_AMOUNT }), + entry!(KeyDown(KeyArrowUp); modifiers=[KeyShift, KeyArrowRight], action_dispatch=DocumentMessage::NudgeSelectedLayers { delta_x: BIG_NUDGE_AMOUNT, delta_y: -BIG_NUDGE_AMOUNT }), + entry!(KeyDown(KeyArrowUp); modifiers=[KeyShift], action_dispatch=DocumentMessage::NudgeSelectedLayers { delta_x: 0., delta_y: -BIG_NUDGE_AMOUNT }), + entry!(KeyDown(KeyArrowDown); modifiers=[KeyShift, KeyArrowLeft], action_dispatch=DocumentMessage::NudgeSelectedLayers { delta_x: -BIG_NUDGE_AMOUNT, delta_y: BIG_NUDGE_AMOUNT }), + entry!(KeyDown(KeyArrowDown); modifiers=[KeyShift, KeyArrowRight], action_dispatch=DocumentMessage::NudgeSelectedLayers { delta_x: BIG_NUDGE_AMOUNT, delta_y: BIG_NUDGE_AMOUNT }), + entry!(KeyDown(KeyArrowDown); modifiers=[KeyShift], action_dispatch=DocumentMessage::NudgeSelectedLayers { delta_x: 0., delta_y: BIG_NUDGE_AMOUNT }), + entry!(KeyDown(KeyArrowLeft); modifiers=[KeyShift, KeyArrowUp], action_dispatch=DocumentMessage::NudgeSelectedLayers { delta_x: -BIG_NUDGE_AMOUNT, delta_y: -BIG_NUDGE_AMOUNT }), + entry!(KeyDown(KeyArrowLeft); modifiers=[KeyShift, KeyArrowDown], action_dispatch=DocumentMessage::NudgeSelectedLayers { delta_x: -BIG_NUDGE_AMOUNT, delta_y: BIG_NUDGE_AMOUNT }), + entry!(KeyDown(KeyArrowLeft); modifiers=[KeyShift], action_dispatch=DocumentMessage::NudgeSelectedLayers { delta_x: -BIG_NUDGE_AMOUNT, delta_y: 0. }), + entry!(KeyDown(KeyArrowRight); modifiers=[KeyShift, KeyArrowUp], action_dispatch=DocumentMessage::NudgeSelectedLayers { delta_x: BIG_NUDGE_AMOUNT, delta_y: -BIG_NUDGE_AMOUNT }), + entry!(KeyDown(KeyArrowRight); modifiers=[KeyShift, KeyArrowDown], action_dispatch=DocumentMessage::NudgeSelectedLayers { delta_x: BIG_NUDGE_AMOUNT, delta_y: BIG_NUDGE_AMOUNT }), + entry!(KeyDown(KeyArrowRight); modifiers=[KeyShift], action_dispatch=DocumentMessage::NudgeSelectedLayers { delta_x: BIG_NUDGE_AMOUNT, delta_y: 0. }), + entry!(KeyDown(KeyArrowUp); modifiers=[KeyArrowLeft], action_dispatch=DocumentMessage::NudgeSelectedLayers { delta_x: -NUDGE_AMOUNT, delta_y: -NUDGE_AMOUNT }), + entry!(KeyDown(KeyArrowUp); modifiers=[KeyArrowRight], action_dispatch=DocumentMessage::NudgeSelectedLayers { delta_x: NUDGE_AMOUNT, delta_y: -NUDGE_AMOUNT }), + entry!(KeyDown(KeyArrowUp); action_dispatch=DocumentMessage::NudgeSelectedLayers { delta_x: 0., delta_y: -NUDGE_AMOUNT }), + entry!(KeyDown(KeyArrowDown); modifiers=[KeyArrowLeft], action_dispatch=DocumentMessage::NudgeSelectedLayers { delta_x: -NUDGE_AMOUNT, delta_y: NUDGE_AMOUNT }), + entry!(KeyDown(KeyArrowDown); modifiers=[KeyArrowRight], action_dispatch=DocumentMessage::NudgeSelectedLayers { delta_x: NUDGE_AMOUNT, delta_y: NUDGE_AMOUNT }), + entry!(KeyDown(KeyArrowDown); action_dispatch=DocumentMessage::NudgeSelectedLayers { delta_x: 0., delta_y: NUDGE_AMOUNT }), + entry!(KeyDown(KeyArrowLeft); modifiers=[KeyArrowUp], action_dispatch=DocumentMessage::NudgeSelectedLayers { delta_x: -NUDGE_AMOUNT, delta_y: -NUDGE_AMOUNT }), + entry!(KeyDown(KeyArrowLeft); modifiers=[KeyArrowDown], action_dispatch=DocumentMessage::NudgeSelectedLayers { delta_x: -NUDGE_AMOUNT, delta_y: NUDGE_AMOUNT }), + entry!(KeyDown(KeyArrowLeft); action_dispatch=DocumentMessage::NudgeSelectedLayers { delta_x: -NUDGE_AMOUNT, delta_y: 0. }), + entry!(KeyDown(KeyArrowRight); modifiers=[KeyArrowUp], action_dispatch=DocumentMessage::NudgeSelectedLayers { delta_x: NUDGE_AMOUNT, delta_y: -NUDGE_AMOUNT }), + entry!(KeyDown(KeyArrowRight); modifiers=[KeyArrowDown], action_dispatch=DocumentMessage::NudgeSelectedLayers { delta_x: NUDGE_AMOUNT, delta_y: NUDGE_AMOUNT }), + entry!(KeyDown(KeyArrowRight); action_dispatch=DocumentMessage::NudgeSelectedLayers { delta_x: NUDGE_AMOUNT, delta_y: 0. }), + // + // TransformLayerMessage + entry!(KeyDown(KeyG); action_dispatch=TransformLayerMessage::BeginGrab), + entry!(KeyDown(KeyR); action_dispatch=TransformLayerMessage::BeginRotate), + entry!(KeyDown(KeyS); action_dispatch=TransformLayerMessage::BeginScale), + // + // MovementMessage + entry!(KeyDown(Mmb); modifiers=[KeyControl], action_dispatch=MovementMessage::RotateCanvasBegin), + entry!(KeyDown(Mmb); modifiers=[KeyShift], action_dispatch=MovementMessage::ZoomCanvasBegin), + entry!(KeyDown(Mmb); action_dispatch=MovementMessage::TranslateCanvasBegin), + entry!(KeyUp(Mmb); action_dispatch=MovementMessage::TransformCanvasEnd), + entry!(KeyDown(Lmb); modifiers=[KeySpace], action_dispatch=MovementMessage::TranslateCanvasBegin), + entry!(KeyUp(Lmb); modifiers=[KeySpace], action_dispatch=MovementMessage::TransformCanvasEnd), + entry_multiplatform!( + standard!(KeyDown(KeyPlus); modifiers=[KeyControl], action_dispatch=MovementMessage::IncreaseCanvasZoom { center_on_mouse: false }), + mac_only!(KeyDown(KeyPlus); modifiers=[KeyCommand], action_dispatch=MovementMessage::IncreaseCanvasZoom { center_on_mouse: false }), + ), + entry_multiplatform!( + standard!(KeyDown(KeyEquals); modifiers=[KeyControl], action_dispatch=MovementMessage::IncreaseCanvasZoom { center_on_mouse: false }), + mac_only!(KeyDown(KeyEquals); modifiers=[KeyCommand], action_dispatch=MovementMessage::IncreaseCanvasZoom { center_on_mouse: false }), + ), + entry_multiplatform!( + standard!(KeyDown(KeyMinus); modifiers=[KeyControl], action_dispatch=MovementMessage::DecreaseCanvasZoom { center_on_mouse: false }), + mac_only!(KeyDown(KeyMinus); modifiers=[KeyCommand], action_dispatch=MovementMessage::DecreaseCanvasZoom { center_on_mouse: false }), + ), + entry_multiplatform!( + standard!(KeyDown(Key1); modifiers=[KeyControl], action_dispatch=MovementMessage::SetCanvasZoom { zoom_factor: 1. }), + mac_only!(KeyDown(Key1); modifiers=[KeyCommand], action_dispatch=MovementMessage::SetCanvasZoom { zoom_factor: 1. }), + ), + entry_multiplatform!( + standard!(KeyDown(Key2); modifiers=[KeyControl], action_dispatch=MovementMessage::SetCanvasZoom { zoom_factor: 2. }), + mac_only!(KeyDown(Key2); modifiers=[KeyCommand], action_dispatch=MovementMessage::SetCanvasZoom { zoom_factor: 2. }), + ), + entry!(WheelScroll; modifiers=[KeyControl], action_dispatch=MovementMessage::WheelCanvasZoom), + entry!(WheelScroll; modifiers=[KeyShift], action_dispatch=MovementMessage::WheelCanvasTranslate { use_y_as_x: true }), + entry!(WheelScroll; action_dispatch=MovementMessage::WheelCanvasTranslate { use_y_as_x: false }), + entry!(KeyDown(KeyPageUp); modifiers=[KeyShift], action_dispatch=MovementMessage::TranslateCanvasByViewportFraction { delta: DVec2::new(1., 0.) }), + entry!(KeyDown(KeyPageDown); modifiers=[KeyShift], action_dispatch=MovementMessage::TranslateCanvasByViewportFraction { delta: DVec2::new(-1., 0.) }), + entry!(KeyDown(KeyPageUp); action_dispatch=MovementMessage::TranslateCanvasByViewportFraction { delta: DVec2::new(0., 1.) }), + entry!(KeyDown(KeyPageDown); action_dispatch=MovementMessage::TranslateCanvasByViewportFraction { delta: DVec2::new(0., -1.) }), + // + // PortfolioMessage + entry_multiplatform!( + standard!(KeyDown(KeyO); modifiers=[KeyControl], action_dispatch=PortfolioMessage::OpenDocument), + mac_only!(KeyDown(KeyO); modifiers=[KeyCommand], action_dispatch=PortfolioMessage::OpenDocument), + ), + entry_multiplatform!( + standard!(KeyDown(KeyI); modifiers=[KeyControl], action_dispatch=PortfolioMessage::Import), + mac_only!(KeyDown(KeyI); modifiers=[KeyCommand], action_dispatch=PortfolioMessage::Import), + ), + entry!(KeyDown(KeyTab); modifiers=[KeyControl], action_dispatch=PortfolioMessage::NextDocument), + entry!(KeyDown(KeyTab); modifiers=[KeyControl, KeyShift], action_dispatch=PortfolioMessage::PrevDocument), + entry_multiplatform!( + standard!(KeyDown(KeyW); modifiers=[KeyControl], action_dispatch=PortfolioMessage::CloseActiveDocumentWithConfirmation), + mac_only!(KeyDown(KeyW); modifiers=[KeyCommand], action_dispatch=PortfolioMessage::CloseActiveDocumentWithConfirmation), + ), + entry_multiplatform!( + standard!(KeyDown(KeyX); modifiers=[KeyControl], action_dispatch=PortfolioMessage::Cut { clipboard: Clipboard::Device }), + mac_only!(KeyDown(KeyX); modifiers=[KeyCommand], action_dispatch=PortfolioMessage::Cut { clipboard: Clipboard::Device }), + ), + entry_multiplatform!( + standard!(KeyDown(KeyC); modifiers=[KeyControl], action_dispatch=PortfolioMessage::Copy { clipboard: Clipboard::Device }), + mac_only!(KeyDown(KeyC); modifiers=[KeyCommand], action_dispatch=PortfolioMessage::Copy { clipboard: Clipboard::Device }), + ), + entry_multiplatform!( + // This shortcut is intercepted in the frontend; it exists here only as a shortcut mapping source + standard!(KeyDown(KeyV); modifiers=[KeyControl], action_dispatch=FrontendMessage::TriggerPaste), + mac_only!(KeyDown(KeyV); modifiers=[KeyCommand], action_dispatch=FrontendMessage::TriggerPaste), + ), + // + // DialogMessage + entry_multiplatform!( + standard!(KeyDown(KeyN); modifiers=[KeyControl], action_dispatch=DialogMessage::RequestNewDocumentDialog), + mac_only!(KeyDown(KeyN); modifiers=[KeyCommand], action_dispatch=DialogMessage::RequestNewDocumentDialog), + ), + entry_multiplatform!( + standard!(KeyDown(KeyW); modifiers=[KeyControl, KeyAlt], action_dispatch=DialogMessage::CloseAllDocumentsWithConfirmation), + mac_only!(KeyDown(KeyW); modifiers=[KeyCommand, KeyAlt], action_dispatch=DialogMessage::CloseAllDocumentsWithConfirmation), + ), + entry_multiplatform!( + standard!(KeyDown(KeyE); modifiers=[KeyControl], action_dispatch=DialogMessage::RequestExportDialog), + mac_only!(KeyDown(KeyE); modifiers=[KeyCommand], action_dispatch=DialogMessage::RequestExportDialog), + ), + // + // DebugMessage + entry!(KeyDown(KeyT); modifiers=[KeyAlt], action_dispatch=DebugMessage::ToggleTraceLogs), + entry!(KeyDown(Key0); modifiers=[KeyAlt], action_dispatch=DebugMessage::MessageOff), + entry!(KeyDown(Key1); modifiers=[KeyAlt], action_dispatch=DebugMessage::MessageNames), + entry!(KeyDown(Key2); modifiers=[KeyAlt], action_dispatch=DebugMessage::MessageContents), + ]; + let (mut key_up, mut key_down, mut double_click, mut wheel_scroll, mut pointer_move) = mappings; + + // TODO: Hardcode these 10 lines into 10 lines of declarations, or make this use a macro to do all 10 in one line + const NUMBER_KEYS: [Key; 10] = [Key0, Key1, Key2, Key3, Key4, Key5, Key6, Key7, Key8, Key9]; + for (i, key) in NUMBER_KEYS.iter().enumerate() { + key_down[*key as usize].0.insert( + 0, + MappingEntry { + action: TransformLayerMessage::TypeDigit { digit: i as u8 }.into(), + input: InputMapperMessage::KeyDown(*key), + platform_layout: None, + modifiers: modifiers!(), + }, + ); + } + + let sort = |list: &mut KeyMappingEntries| list.0.sort_by(|u, v| v.modifiers.ones().cmp(&u.modifiers.ones())); + for list in [&mut key_up, &mut key_down] { + for sublist in list { + sort(sublist); + } + } + sort(&mut double_click); + sort(&mut wheel_scroll); + sort(&mut pointer_move); + + Mapping { + key_up, + key_down, + double_click, + wheel_scroll, + pointer_move, + } +} diff --git a/editor/src/input/input_mapper_message.rs b/editor/src/messages/input_mapper/input_mapper_message.rs similarity index 77% rename from editor/src/input/input_mapper_message.rs rename to editor/src/messages/input_mapper/input_mapper_message.rs index ca9ed905f2..0673e80a6b 100644 --- a/editor/src/input/input_mapper_message.rs +++ b/editor/src/messages/input_mapper/input_mapper_message.rs @@ -1,5 +1,5 @@ -use super::keyboard::Key; -use crate::message_prelude::*; +use crate::messages::input_mapper::utility_types::input_keyboard::Key; +use crate::messages::prelude::*; use serde::{Deserialize, Serialize}; diff --git a/editor/src/input/input_mapper_message_handler.rs b/editor/src/messages/input_mapper/input_mapper_message_handler.rs similarity index 88% rename from editor/src/input/input_mapper_message_handler.rs rename to editor/src/messages/input_mapper/input_mapper_message_handler.rs index 5f91049bd2..c6e3eb3a8f 100644 --- a/editor/src/input/input_mapper_message_handler.rs +++ b/editor/src/messages/input_mapper/input_mapper_message_handler.rs @@ -1,8 +1,7 @@ -use super::input_mapper::Mapping; -use super::keyboard::Key; -use super::InputPreprocessorMessageHandler; -use crate::document::utility_types::KeyboardPlatformLayout; -use crate::message_prelude::*; +use super::utility_types::misc::Mapping; +use crate::messages::input_mapper::utility_types::input_keyboard::{self, Key}; +use crate::messages::portfolio::document::utility_types::misc::KeyboardPlatformLayout; +use crate::messages::prelude::*; use std::fmt::Write; @@ -11,6 +10,17 @@ pub struct InputMapperMessageHandler { mapping: Mapping, } +impl MessageHandler for InputMapperMessageHandler { + fn process_message(&mut self, message: InputMapperMessage, data: (&InputPreprocessorMessageHandler, KeyboardPlatformLayout, ActionList), responses: &mut VecDeque) { + let (input, keyboard_platform, actions) = data; + + if let Some(message) = self.mapping.match_input_message(message, &input.keyboard, actions, keyboard_platform) { + responses.push_back(message); + } + } + advertise_actions!(); +} + impl InputMapperMessageHandler { pub fn hints(&self, actions: ActionList) -> String { let mut output = String::new(); @@ -58,7 +68,7 @@ impl InputMapperMessageHandler { .map(|i| { // TODO: Use a safe solution eventually assert!( - i < super::keyboard::NUMBER_OF_KEYS, + i < input_keyboard::NUMBER_OF_KEYS, "Attempting to convert a Key with enum index {}, which is larger than the number of Key enums", i ); @@ -87,14 +97,3 @@ impl InputMapperMessageHandler { .collect::>() } } - -impl MessageHandler for InputMapperMessageHandler { - fn process_action(&mut self, message: InputMapperMessage, data: (&InputPreprocessorMessageHandler, KeyboardPlatformLayout, ActionList), responses: &mut VecDeque) { - let (input, keyboard_platform, actions) = data; - - if let Some(message) = self.mapping.match_input_message(message, &input.keyboard, actions, keyboard_platform) { - responses.push_back(message); - } - } - advertise_actions!(); -} diff --git a/editor/src/messages/input_mapper/mod.rs b/editor/src/messages/input_mapper/mod.rs new file mode 100644 index 0000000000..55b4bb52cc --- /dev/null +++ b/editor/src/messages/input_mapper/mod.rs @@ -0,0 +1,10 @@ +mod input_mapper_message; +mod input_mapper_message_handler; + +pub mod default_mapping; +pub mod utility_types; + +#[doc(inline)] +pub use input_mapper_message::{InputMapperMessage, InputMapperMessageDiscriminant}; +#[doc(inline)] +pub use input_mapper_message_handler::InputMapperMessageHandler; diff --git a/editor/src/input/keyboard.rs b/editor/src/messages/input_mapper/utility_types/input_keyboard.rs similarity index 90% rename from editor/src/input/keyboard.rs rename to editor/src/messages/input_mapper/utility_types/input_keyboard.rs index dd4b067ace..9d78435d25 100644 --- a/editor/src/input/keyboard.rs +++ b/editor/src/messages/input_mapper/utility_types/input_keyboard.rs @@ -1,5 +1,8 @@ -use crate::message_prelude::*; +use crate::messages::prelude::*; +pub use graphene::DocumentResponse; + +use bitflags::bitflags; use serde::{Deserialize, Serialize}; use std::fmt::{self, Display, Formatter}; use std::ops::{BitAnd, BitAndAssign, BitOr, BitOrAssign, BitXor, BitXorAssign}; @@ -15,6 +18,22 @@ const KEY_MASK_STORAGE_LENGTH: usize = (NUMBER_OF_KEYS + STORAGE_SIZE_BITS - 1) pub type KeyStates = BitVector; +pub enum KeyPosition { + Pressed, + Released, +} + +bitflags! { + #[derive(Default, Serialize, Deserialize)] + #[repr(transparent)] + pub struct ModifierKeys: u8 { + const SHIFT = 0b0000_0001; + const ALT = 0b0000_0010; + const CONTROL = 0b0000_0100; + const META_OR_COMMAND = 0b0000_1000; + } +} + // TODO: Consider renaming to `KeyMessage` for consistency with other messages that implement `#[impl_message(..)]` #[impl_message(Message, InputMapperMessage, KeyDown)] #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)] @@ -106,6 +125,10 @@ impl fmt::Display for Key { pub const NUMBER_OF_KEYS: usize = Key::NumKeys as usize; +/// Only `Key`s that exist on a physical keyboard should be used. +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub struct KeysGroup(pub Vec); + #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)] pub enum MouseMotion { None, diff --git a/editor/src/input/mouse.rs b/editor/src/messages/input_mapper/utility_types/input_mouse.rs similarity index 97% rename from editor/src/input/mouse.rs rename to editor/src/messages/input_mapper/utility_types/input_mouse.rs index cf2de2cf57..7cbf86c471 100644 --- a/editor/src/input/mouse.rs +++ b/editor/src/messages/input_mapper/utility_types/input_mouse.rs @@ -38,8 +38,8 @@ pub struct ScrollDelta { } impl ScrollDelta { - pub fn new(x: i32, y: i32, z: i32) -> ScrollDelta { - ScrollDelta { x, y, z } + pub fn new(x: i32, y: i32, z: i32) -> Self { + Self { x, y, z } } pub fn as_dvec2(&self) -> DVec2 { diff --git a/editor/src/input/input_mapper_macros.rs b/editor/src/messages/input_mapper/utility_types/macros.rs similarity index 95% rename from editor/src/input/input_mapper_macros.rs rename to editor/src/messages/input_mapper/utility_types/macros.rs index 4c5458389a..9bd5d4e0cc 100644 --- a/editor/src/input/input_mapper_macros.rs +++ b/editor/src/messages/input_mapper/utility_types/macros.rs @@ -150,6 +150,14 @@ macro_rules! mapping { }}; } +/// Constructs an `ActionKeys` macro with a certain `Action` variant, conveniently wrapped in `Some()`. +macro_rules! action_keys { + ($action:expr) => { + Some(crate::messages::input_mapper::utility_types::misc::ActionKeys::Action($action.into())) + }; +} + +pub(crate) use action_keys; pub(crate) use entry; pub(crate) use entry_for_layout; pub(crate) use entry_multiplatform; diff --git a/editor/src/messages/input_mapper/utility_types/misc.rs b/editor/src/messages/input_mapper/utility_types/misc.rs new file mode 100644 index 0000000000..d729c90950 --- /dev/null +++ b/editor/src/messages/input_mapper/utility_types/misc.rs @@ -0,0 +1,148 @@ +use crate::messages::input_mapper::default_mapping::default_mapping; +use crate::messages::input_mapper::utility_types::input_keyboard::{Key, KeyStates, NUMBER_OF_KEYS}; +use crate::messages::portfolio::document::utility_types::misc::KeyboardPlatformLayout; +use crate::messages::prelude::*; + +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone)] +pub struct Mapping { + pub key_up: [KeyMappingEntries; NUMBER_OF_KEYS], + pub key_down: [KeyMappingEntries; NUMBER_OF_KEYS], + pub double_click: KeyMappingEntries, + pub wheel_scroll: KeyMappingEntries, + pub pointer_move: KeyMappingEntries, +} + +impl Mapping { + pub fn match_input_message(&self, message: InputMapperMessage, keyboard_state: &KeyStates, actions: ActionList, keyboard_platform: KeyboardPlatformLayout) -> Option { + let list = match message { + InputMapperMessage::KeyDown(key) => &self.key_down[key as usize], + InputMapperMessage::KeyUp(key) => &self.key_up[key as usize], + InputMapperMessage::DoubleClick => &self.double_click, + InputMapperMessage::WheelScroll => &self.wheel_scroll, + InputMapperMessage::PointerMove => &self.pointer_move, + }; + list.match_mapping(keyboard_state, actions, keyboard_platform) + } +} + +impl Default for Mapping { + fn default() -> Self { + default_mapping() + } +} + +#[derive(Debug, Clone)] +pub struct KeyMappingEntries(pub Vec); + +impl KeyMappingEntries { + pub fn match_mapping(&self, keyboard_state: &KeyStates, actions: ActionList, keyboard_platform: KeyboardPlatformLayout) -> Option { + for entry in self.0.iter() { + // Skip this entry if it is platform-specific, and for a layout that does not match the user's keyboard platform layout + if let Some(entry_platform_layout) = entry.platform_layout { + if entry_platform_layout != keyboard_platform { + continue; + } + } + + // Find which currently pressed keys are also the modifiers in this hotkey entry, then compare those against the required modifiers to see if there are zero missing + let pressed_modifiers = *keyboard_state & entry.modifiers; + let all_modifiers_without_pressed_modifiers = entry.modifiers ^ pressed_modifiers; + let all_required_modifiers_pressed = all_modifiers_without_pressed_modifiers.is_empty(); + // Skip this entry if any of the required modifiers are missing + if !all_required_modifiers_pressed { + continue; + } + + if actions.iter().flatten().any(|action| entry.action.to_discriminant() == *action) { + return Some(entry.action.clone()); + } + } + None + } + + pub fn push(&mut self, entry: MappingEntry) { + self.0.push(entry) + } + + pub const fn new() -> Self { + Self(Vec::new()) + } + + pub fn key_array() -> [Self; NUMBER_OF_KEYS] { + const DEFAULT: KeyMappingEntries = KeyMappingEntries::new(); + [DEFAULT; NUMBER_OF_KEYS] + } +} + +#[derive(PartialEq, Clone, Debug)] +pub struct MappingEntry { + /// Serves two purposes: + /// - This is the message that gets dispatched when the hotkey is matched + /// - This message's discriminant is the action; it must be a currently active action to be considered as a shortcut + pub action: Message, + /// The user input event from an input device which this input mapping matches on + pub input: InputMapperMessage, + /// Any additional keys that must be also pressed for this input mapping to match + pub modifiers: KeyStates, + /// The keyboard platform layout which this mapping is exclusive to, or `None` if it's platform-agnostic + pub platform_layout: Option, +} + +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub enum ActionKeys { + Action(MessageDiscriminant), + #[serde(rename = "keys")] + Keys(Vec), +} + +impl ActionKeys { + pub fn to_keys(&mut self, action_input_mapping: &impl Fn(&MessageDiscriminant) -> Vec>) { + match self { + ActionKeys::Action(action) => { + if let Some(keys) = action_input_mapping(action).get_mut(0) { + let mut taken_keys = Vec::new(); + std::mem::swap(keys, &mut taken_keys); + + *self = ActionKeys::Keys(taken_keys); + } else { + *self = ActionKeys::Keys(Vec::new()); + } + } + ActionKeys::Keys(keys) => { + log::warn!("Calling `.to_keys()` on a `ActionKeys::Keys` is a mistake/bug. Keys are: {:?}.", keys); + } + } + } +} + +pub fn keys_text_shortcut(keys: &[Key], keyboard_platform: KeyboardPlatformLayout) -> String { + const JOINER_MARK: &str = "+"; + + let mut joined = keys + .iter() + .map(|key| { + let key_string = key.to_string(); + + if keyboard_platform == KeyboardPlatformLayout::Mac { + match key_string.as_str() { + "Command" => "⌘".to_string(), + "Control" => "⌃".to_string(), + "Alt" => "⌥".to_string(), + "Shift" => "⇧".to_string(), + _ => key_string + JOINER_MARK, + } + } else { + key_string + JOINER_MARK + } + }) + .collect::(); + + // Truncate to cut the joining character off the end if it's present + if joined.ends_with(JOINER_MARK) { + joined.truncate(joined.len() - JOINER_MARK.len()); + } + + joined +} diff --git a/editor/src/messages/input_mapper/utility_types/mod.rs b/editor/src/messages/input_mapper/utility_types/mod.rs new file mode 100644 index 0000000000..c0b5c8a21b --- /dev/null +++ b/editor/src/messages/input_mapper/utility_types/mod.rs @@ -0,0 +1,4 @@ +pub mod input_keyboard; +pub mod input_mouse; +pub mod macros; +pub mod misc; diff --git a/editor/src/input/input_preprocessor_message.rs b/editor/src/messages/input_preprocessor/input_preprocessor_message.rs similarity index 79% rename from editor/src/input/input_preprocessor_message.rs rename to editor/src/messages/input_preprocessor/input_preprocessor_message.rs index 942ac785f3..25d48b6a8f 100644 --- a/editor/src/input/input_preprocessor_message.rs +++ b/editor/src/messages/input_preprocessor/input_preprocessor_message.rs @@ -1,7 +1,6 @@ -use super::input_preprocessor::ModifierKeys; -use super::keyboard::Key; -use super::mouse::{EditorMouseState, ViewportBounds}; -use crate::message_prelude::*; +use crate::messages::input_mapper::utility_types::input_keyboard::{Key, ModifierKeys}; +use crate::messages::input_mapper::utility_types::input_mouse::{EditorMouseState, ViewportBounds}; +use crate::messages::prelude::*; #[doc(inline)] pub use graphene::DocumentResponse; diff --git a/editor/src/input/input_preprocessor_message_handler.rs b/editor/src/messages/input_preprocessor/input_preprocessor_message_handler.rs similarity index 60% rename from editor/src/input/input_preprocessor_message_handler.rs rename to editor/src/messages/input_preprocessor/input_preprocessor_message_handler.rs index caea17e99c..d51b4e9a42 100644 --- a/editor/src/input/input_preprocessor_message_handler.rs +++ b/editor/src/messages/input_preprocessor/input_preprocessor_message_handler.rs @@ -1,8 +1,7 @@ -use super::input_preprocessor::ModifierKeys; -use super::keyboard::{Key, KeyStates}; -use super::mouse::{MouseKeys, MouseState, ViewportBounds}; -use crate::document::utility_types::KeyboardPlatformLayout; -use crate::message_prelude::*; +use crate::messages::input_mapper::utility_types::input_keyboard::{Key, KeyStates, ModifierKeys}; +use crate::messages::input_mapper::utility_types::input_mouse::{MouseKeys, MouseState, ViewportBounds}; +use crate::messages::portfolio::document::utility_types::misc::KeyboardPlatformLayout; +use crate::messages::prelude::*; #[doc(inline)] pub use graphene::DocumentResponse; @@ -18,7 +17,7 @@ pub struct InputPreprocessorMessageHandler { impl MessageHandler for InputPreprocessorMessageHandler { #[remain::check] - fn process_action(&mut self, message: InputPreprocessorMessage, data: KeyboardPlatformLayout, responses: &mut VecDeque) { + fn process_message(&mut self, message: InputPreprocessorMessage, data: KeyboardPlatformLayout, responses: &mut VecDeque) { let keyboard_platform = data; #[remain::sorted] @@ -168,3 +167,97 @@ impl InputPreprocessorMessageHandler { [(0., 0.).into(), self.viewport_bounds.bottom_right - self.viewport_bounds.top_left] } } + +#[cfg(test)] +mod test { + use crate::messages::input_mapper::utility_types::input_keyboard::{Key, ModifierKeys}; + use crate::messages::input_mapper::utility_types::input_mouse::EditorMouseState; + use crate::messages::input_mapper::InputMapperMessage; + use crate::messages::portfolio::document::utility_types::misc::KeyboardPlatformLayout; + use crate::messages::prelude::*; + + use std::collections::VecDeque; + + #[test] + fn process_action_mouse_move_handle_modifier_keys() { + let mut input_preprocessor = InputPreprocessorMessageHandler::default(); + + let editor_mouse_state = EditorMouseState::from_editor_position(4., 809.); + let modifier_keys = ModifierKeys::ALT; + let message = InputPreprocessorMessage::PointerMove { editor_mouse_state, modifier_keys }; + + let mut responses = VecDeque::new(); + + input_preprocessor.process_message(message, KeyboardPlatformLayout::Standard, &mut responses); + + assert!(input_preprocessor.keyboard.get(Key::KeyAlt as usize)); + assert_eq!(responses.pop_front(), Some(InputMapperMessage::KeyDown(Key::KeyAlt).into())); + } + + #[test] + fn process_action_mouse_down_handle_modifier_keys() { + let mut input_preprocessor = InputPreprocessorMessageHandler::default(); + + let editor_mouse_state = EditorMouseState::new(); + let modifier_keys = ModifierKeys::CONTROL; + let message = InputPreprocessorMessage::PointerDown { editor_mouse_state, modifier_keys }; + + let mut responses = VecDeque::new(); + + input_preprocessor.process_message(message, KeyboardPlatformLayout::Standard, &mut responses); + + assert!(input_preprocessor.keyboard.get(Key::KeyControl as usize)); + assert_eq!(responses.pop_front(), Some(InputMapperMessage::KeyDown(Key::KeyControl).into())); + } + + #[test] + fn process_action_mouse_up_handle_modifier_keys() { + let mut input_preprocessor = InputPreprocessorMessageHandler::default(); + + let editor_mouse_state = EditorMouseState::new(); + let modifier_keys = ModifierKeys::SHIFT; + let message = InputPreprocessorMessage::PointerUp { editor_mouse_state, modifier_keys }; + + let mut responses = VecDeque::new(); + + input_preprocessor.process_message(message, KeyboardPlatformLayout::Standard, &mut responses); + + assert!(input_preprocessor.keyboard.get(Key::KeyShift as usize)); + assert_eq!(responses.pop_front(), Some(InputMapperMessage::KeyDown(Key::KeyShift).into())); + } + + #[test] + fn process_action_key_down_handle_modifier_keys() { + let mut input_preprocessor = InputPreprocessorMessageHandler::default(); + input_preprocessor.keyboard.set(Key::KeyControl as usize); + + let key = Key::KeyA; + let modifier_keys = ModifierKeys::empty(); + let message = InputPreprocessorMessage::KeyDown { key, modifier_keys }; + + let mut responses = VecDeque::new(); + + input_preprocessor.process_message(message, KeyboardPlatformLayout::Standard, &mut responses); + + assert!(!input_preprocessor.keyboard.get(Key::KeyControl as usize)); + assert_eq!(responses.pop_front(), Some(InputMapperMessage::KeyUp(Key::KeyControl).into())); + } + + #[test] + fn process_action_key_up_handle_modifier_keys() { + let mut input_preprocessor = InputPreprocessorMessageHandler::default(); + + let key = Key::KeyS; + let modifier_keys = ModifierKeys::CONTROL | ModifierKeys::SHIFT; + let message = InputPreprocessorMessage::KeyUp { key, modifier_keys }; + + let mut responses = VecDeque::new(); + + input_preprocessor.process_message(message, KeyboardPlatformLayout::Standard, &mut responses); + + assert!(input_preprocessor.keyboard.get(Key::KeyControl as usize)); + assert!(input_preprocessor.keyboard.get(Key::KeyShift as usize)); + assert!(responses.contains(&InputMapperMessage::KeyDown(Key::KeyControl).into())); + assert!(responses.contains(&InputMapperMessage::KeyDown(Key::KeyControl).into())); + } +} diff --git a/editor/src/messages/input_preprocessor/mod.rs b/editor/src/messages/input_preprocessor/mod.rs new file mode 100644 index 0000000000..1b934496fb --- /dev/null +++ b/editor/src/messages/input_preprocessor/mod.rs @@ -0,0 +1,7 @@ +mod input_preprocessor_message; +mod input_preprocessor_message_handler; + +#[doc(inline)] +pub use input_preprocessor_message::{InputPreprocessorMessage, InputPreprocessorMessageDiscriminant}; +#[doc(inline)] +pub use input_preprocessor_message_handler::InputPreprocessorMessageHandler; diff --git a/editor/src/messages/layout/layout_message.rs b/editor/src/messages/layout/layout_message.rs new file mode 100644 index 0000000000..ca820cacf0 --- /dev/null +++ b/editor/src/messages/layout/layout_message.rs @@ -0,0 +1,14 @@ +use super::utility_types::misc::LayoutTarget; +use crate::messages::layout::utility_types::layout_widget::Layout; +use crate::messages::prelude::*; + +use serde::{Deserialize, Serialize}; + +#[remain::sorted] +#[impl_message(Message, Layout)] +#[derive(PartialEq, Clone, Debug, Serialize, Deserialize)] +pub enum LayoutMessage { + RefreshLayout { layout_target: LayoutTarget }, + SendLayout { layout: Layout, layout_target: LayoutTarget }, + UpdateLayout { layout_target: LayoutTarget, widget_id: u64, value: serde_json::Value }, +} diff --git a/editor/src/layout/layout_message_handler.rs b/editor/src/messages/layout/layout_message_handler.rs similarity index 93% rename from editor/src/layout/layout_message_handler.rs rename to editor/src/messages/layout/layout_message_handler.rs index 673067e01e..be491e252e 100644 --- a/editor/src/layout/layout_message_handler.rs +++ b/editor/src/messages/layout/layout_message_handler.rs @@ -1,9 +1,9 @@ -use super::layout_message::LayoutTarget; -use super::widgets::Layout; -use crate::document::utility_types::KeyboardPlatformLayout; -use crate::input::keyboard::Key; -use crate::layout::widgets::Widget; -use crate::message_prelude::*; +use super::utility_types::misc::LayoutTarget; +use crate::messages::input_mapper::utility_types::input_keyboard::Key; +use crate::messages::layout::utility_types::layout_widget::Layout; +use crate::messages::layout::utility_types::layout_widget::Widget; +use crate::messages::portfolio::document::utility_types::misc::KeyboardPlatformLayout; +use crate::messages::prelude::*; use graphene::layers::text_layer::Font; @@ -15,74 +15,14 @@ pub struct LayoutMessageHandler { layouts: [Layout; LayoutTarget::LayoutTargetLength as usize], } -impl LayoutMessageHandler { - #[remain::check] - fn send_layout( - &self, - layout_target: LayoutTarget, - responses: &mut VecDeque, - action_input_mapping: &impl Fn(&MessageDiscriminant) -> Vec>, - keyboard_platform: KeyboardPlatformLayout, - ) { - let layout = &self.layouts[layout_target as usize]; - #[remain::sorted] - let message = match layout_target { - LayoutTarget::DialogDetails => FrontendMessage::UpdateDialogDetails { - layout_target, - layout: layout.clone().unwrap_widget_layout(action_input_mapping, keyboard_platform).layout, - }, - LayoutTarget::DocumentBar => FrontendMessage::UpdateDocumentBarLayout { - layout_target, - layout: layout.clone().unwrap_widget_layout(action_input_mapping, keyboard_platform).layout, - }, - LayoutTarget::DocumentMode => FrontendMessage::UpdateDocumentModeLayout { - layout_target, - layout: layout.clone().unwrap_widget_layout(action_input_mapping, keyboard_platform).layout, - }, - LayoutTarget::LayerTreeOptions => FrontendMessage::UpdateLayerTreeOptionsLayout { - layout_target, - layout: layout.clone().unwrap_widget_layout(action_input_mapping, keyboard_platform).layout, - }, - LayoutTarget::MenuBar => FrontendMessage::UpdateMenuBarLayout { - layout_target, - layout: layout.clone().unwrap_menu_layout(action_input_mapping, keyboard_platform).layout, - }, - LayoutTarget::PropertiesOptions => FrontendMessage::UpdatePropertyPanelOptionsLayout { - layout_target, - layout: layout.clone().unwrap_widget_layout(action_input_mapping, keyboard_platform).layout, - }, - LayoutTarget::PropertiesSections => FrontendMessage::UpdatePropertyPanelSectionsLayout { - layout_target, - layout: layout.clone().unwrap_widget_layout(action_input_mapping, keyboard_platform).layout, - }, - LayoutTarget::ToolOptions => FrontendMessage::UpdateToolOptionsLayout { - layout_target, - layout: layout.clone().unwrap_widget_layout(action_input_mapping, keyboard_platform).layout, - }, - LayoutTarget::ToolShelf => FrontendMessage::UpdateToolShelfLayout { - layout_target, - layout: layout.clone().unwrap_widget_layout(action_input_mapping, keyboard_platform).layout, - }, - LayoutTarget::WorkingColors => FrontendMessage::UpdateWorkingColorsLayout { - layout_target, - layout: layout.clone().unwrap_widget_layout(action_input_mapping, keyboard_platform).layout, - }, - - #[remain::unsorted] - LayoutTarget::LayoutTargetLength => panic!("`LayoutTargetLength` is not a valid Layout Target and is used for array indexing"), - }; - responses.push_back(message.into()); - } -} - impl Vec>> MessageHandler for LayoutMessageHandler { #[remain::check] - fn process_action(&mut self, action: LayoutMessage, data: (F, KeyboardPlatformLayout), responses: &mut std::collections::VecDeque) { + fn process_message(&mut self, message: LayoutMessage, data: (F, KeyboardPlatformLayout), responses: &mut std::collections::VecDeque) { let (action_input_mapping, keyboard_platform) = data; use LayoutMessage::*; #[remain::sorted] - match action { + match message { RefreshLayout { layout_target } => { self.send_layout(layout_target, responses, &action_input_mapping, keyboard_platform); } @@ -148,7 +88,7 @@ impl Vec>> MessageHandler {} - Widget::Invisible(invisible) => { + Widget::InvisibleStandinInput(invisible) => { let callback_message = (invisible.on_update.callback)(&()); responses.push_back(callback_message); } @@ -206,7 +146,67 @@ impl Vec>> MessageHandler crate::message_prelude::ActionList { + fn actions(&self) -> ActionList { actions!() } } + +impl LayoutMessageHandler { + #[remain::check] + fn send_layout( + &self, + layout_target: LayoutTarget, + responses: &mut VecDeque, + action_input_mapping: &impl Fn(&MessageDiscriminant) -> Vec>, + keyboard_platform: KeyboardPlatformLayout, + ) { + let layout = &self.layouts[layout_target as usize]; + #[remain::sorted] + let message = match layout_target { + LayoutTarget::DialogDetails => FrontendMessage::UpdateDialogDetails { + layout_target, + layout: layout.clone().unwrap_widget_layout(action_input_mapping, keyboard_platform).layout, + }, + LayoutTarget::DocumentBar => FrontendMessage::UpdateDocumentBarLayout { + layout_target, + layout: layout.clone().unwrap_widget_layout(action_input_mapping, keyboard_platform).layout, + }, + LayoutTarget::DocumentMode => FrontendMessage::UpdateDocumentModeLayout { + layout_target, + layout: layout.clone().unwrap_widget_layout(action_input_mapping, keyboard_platform).layout, + }, + LayoutTarget::LayerTreeOptions => FrontendMessage::UpdateLayerTreeOptionsLayout { + layout_target, + layout: layout.clone().unwrap_widget_layout(action_input_mapping, keyboard_platform).layout, + }, + LayoutTarget::MenuBar => FrontendMessage::UpdateMenuBarLayout { + layout_target, + layout: layout.clone().unwrap_menu_layout(action_input_mapping, keyboard_platform).layout, + }, + LayoutTarget::PropertiesOptions => FrontendMessage::UpdatePropertyPanelOptionsLayout { + layout_target, + layout: layout.clone().unwrap_widget_layout(action_input_mapping, keyboard_platform).layout, + }, + LayoutTarget::PropertiesSections => FrontendMessage::UpdatePropertyPanelSectionsLayout { + layout_target, + layout: layout.clone().unwrap_widget_layout(action_input_mapping, keyboard_platform).layout, + }, + LayoutTarget::ToolOptions => FrontendMessage::UpdateToolOptionsLayout { + layout_target, + layout: layout.clone().unwrap_widget_layout(action_input_mapping, keyboard_platform).layout, + }, + LayoutTarget::ToolShelf => FrontendMessage::UpdateToolShelfLayout { + layout_target, + layout: layout.clone().unwrap_widget_layout(action_input_mapping, keyboard_platform).layout, + }, + LayoutTarget::WorkingColors => FrontendMessage::UpdateWorkingColorsLayout { + layout_target, + layout: layout.clone().unwrap_widget_layout(action_input_mapping, keyboard_platform).layout, + }, + + #[remain::unsorted] + LayoutTarget::LayoutTargetLength => panic!("`LayoutTargetLength` is not a valid Layout Target and is used for array indexing"), + }; + responses.push_back(message.into()); + } +} diff --git a/editor/src/messages/layout/mod.rs b/editor/src/messages/layout/mod.rs new file mode 100644 index 0000000000..bd148eb765 --- /dev/null +++ b/editor/src/messages/layout/mod.rs @@ -0,0 +1,9 @@ +mod layout_message; +mod layout_message_handler; + +pub mod utility_types; + +#[doc(inline)] +pub use layout_message::{LayoutMessage, LayoutMessageDiscriminant}; +#[doc(inline)] +pub use layout_message_handler::LayoutMessageHandler; diff --git a/editor/src/messages/layout/utility_types/layout_widget.rs b/editor/src/messages/layout/utility_types/layout_widget.rs new file mode 100644 index 0000000000..e29b457211 --- /dev/null +++ b/editor/src/messages/layout/utility_types/layout_widget.rs @@ -0,0 +1,292 @@ +use super::widgets::button_widgets::*; +use super::widgets::input_widgets::*; +use super::widgets::label_widgets::*; +use super::widgets::menu_widgets::MenuLayout; +use crate::application::generate_uuid; +use crate::messages::input_mapper::utility_types::input_keyboard::Key; +use crate::messages::input_mapper::utility_types::misc::{keys_text_shortcut, ActionKeys}; +use crate::messages::layout::utility_types::misc::LayoutTarget; +use crate::messages::layout::LayoutMessage; +use crate::messages::portfolio::document::utility_types::misc::KeyboardPlatformLayout; +use crate::messages::prelude::*; + +use serde::{Deserialize, Serialize}; +use std::collections::VecDeque; +use std::rc::Rc; + +pub trait PropertyHolder { + fn properties(&self) -> Layout { + Layout::WidgetLayout(WidgetLayout::default()) + } + + fn register_properties(&self, responses: &mut VecDeque, layout_target: LayoutTarget) { + responses.push_back( + LayoutMessage::SendLayout { + layout: self.properties(), + layout_target, + } + .into(), + ) + } +} + +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub enum Layout { + WidgetLayout(WidgetLayout), + MenuLayout(MenuLayout), +} + +impl Layout { + pub fn unwrap_widget_layout(self, action_input_mapping: &impl Fn(&MessageDiscriminant) -> Vec>, keyboard_platform: KeyboardPlatformLayout) -> WidgetLayout { + if let Layout::WidgetLayout(mut widget_layout) = self { + // Function used multiple times later in this code block to convert `ActionKeys::Action` to `ActionKeys::Keys` and append its shortcut to the tooltip + let apply_shortcut_to_tooltip = |tooltip_shortcut: &mut ActionKeys, tooltip: &mut String| { + tooltip_shortcut.to_keys(action_input_mapping); + + if let ActionKeys::Keys(keys) = tooltip_shortcut { + let shortcut_text = keys_text_shortcut(keys, keyboard_platform); + + if !shortcut_text.is_empty() { + if !tooltip.is_empty() { + tooltip.push(' '); + } + tooltip.push('('); + tooltip.push_str(&shortcut_text); + tooltip.push(')'); + } + } + }; + + // Go through each widget to convert `ActionKeys::Action` to `ActionKeys::Keys` and append the key combination to the widget tooltip + for widget_holder in &mut widget_layout.iter_mut() { + // Handle all the widgets that have tooltips + let mut tooltip_shortcut = match &mut widget_holder.widget { + Widget::CheckboxInput(widget) => Some((&mut widget.tooltip, &mut widget.tooltip_shortcut)), + Widget::ColorInput(widget) => Some((&mut widget.tooltip, &mut widget.tooltip_shortcut)), + Widget::IconButton(widget) => Some((&mut widget.tooltip, &mut widget.tooltip_shortcut)), + Widget::OptionalInput(widget) => Some((&mut widget.tooltip, &mut widget.tooltip_shortcut)), + _ => None, + }; + if let Some((tooltip, Some(tooltip_shortcut))) = &mut tooltip_shortcut { + apply_shortcut_to_tooltip(tooltip_shortcut, tooltip); + } + + // Handle RadioInput separately because its tooltips are children of the widget + if let Widget::RadioInput(radio_input) = &mut widget_holder.widget { + for radio_entry_data in &mut radio_input.entries { + if let RadioEntryData { + tooltip, + tooltip_shortcut: Some(tooltip_shortcut), + .. + } = radio_entry_data + { + apply_shortcut_to_tooltip(tooltip_shortcut, tooltip); + } + } + } + } + + widget_layout + } else { + panic!("Tried to unwrap layout as WidgetLayout. Got {:?}", self) + } + } + + pub fn unwrap_menu_layout(self, action_input_mapping: &impl Fn(&MessageDiscriminant) -> Vec>, _keyboard_platform: KeyboardPlatformLayout) -> MenuLayout { + if let Layout::MenuLayout(mut menu_layout) = self { + for menu_column in &mut menu_layout.layout { + menu_column.children.fill_in_shortcut_actions_with_keys(action_input_mapping); + } + + menu_layout + } else { + panic!("Tried to unwrap layout as MenuLayout. Got {:?}", self) + } + } + + pub fn iter(&self) -> Box + '_> { + match self { + Layout::MenuLayout(menu_layout) => Box::new(menu_layout.iter()), + Layout::WidgetLayout(widget_layout) => Box::new(widget_layout.iter()), + } + } + + pub fn iter_mut(&mut self) -> Box + '_> { + match self { + Layout::MenuLayout(menu_layout) => Box::new(menu_layout.iter_mut()), + Layout::WidgetLayout(widget_layout) => Box::new(widget_layout.iter_mut()), + } + } +} + +impl Default for Layout { + fn default() -> Self { + Layout::WidgetLayout(WidgetLayout::default()) + } +} + +#[derive(Debug, Default, Clone, Serialize, Deserialize, PartialEq)] +pub struct WidgetLayout { + pub layout: SubLayout, +} + +impl WidgetLayout { + pub fn new(layout: SubLayout) -> Self { + Self { layout } + } + + pub fn iter(&self) -> WidgetIter<'_> { + WidgetIter { + stack: self.layout.iter().collect(), + current_slice: None, + } + } + + pub fn iter_mut(&mut self) -> WidgetIterMut<'_> { + WidgetIterMut { + stack: self.layout.iter_mut().collect(), + current_slice: None, + } + } +} + +#[derive(Debug, Default)] +pub struct WidgetIter<'a> { + pub stack: Vec<&'a LayoutGroup>, + pub current_slice: Option<&'a [WidgetHolder]>, +} + +impl<'a> Iterator for WidgetIter<'a> { + type Item = &'a WidgetHolder; + + fn next(&mut self) -> Option { + if let Some(item) = self.current_slice.and_then(|slice| slice.first()) { + self.current_slice = Some(&self.current_slice.unwrap()[1..]); + return Some(item); + } + + match self.stack.pop() { + Some(LayoutGroup::Column { widgets }) => { + self.current_slice = Some(widgets); + self.next() + } + Some(LayoutGroup::Row { widgets }) => { + self.current_slice = Some(widgets); + self.next() + } + Some(LayoutGroup::Section { name: _, layout }) => { + for layout_row in layout { + self.stack.push(layout_row); + } + self.next() + } + None => None, + } + } +} + +#[derive(Debug, Default)] +pub struct WidgetIterMut<'a> { + pub stack: Vec<&'a mut LayoutGroup>, + pub current_slice: Option<&'a mut [WidgetHolder]>, +} + +impl<'a> Iterator for WidgetIterMut<'a> { + type Item = &'a mut WidgetHolder; + + fn next(&mut self) -> Option { + if let Some((first, rest)) = self.current_slice.take().and_then(|slice| slice.split_first_mut()) { + self.current_slice = Some(rest); + return Some(first); + }; + + match self.stack.pop() { + Some(LayoutGroup::Column { widgets }) => { + self.current_slice = Some(widgets); + self.next() + } + Some(LayoutGroup::Row { widgets }) => { + self.current_slice = Some(widgets); + self.next() + } + Some(LayoutGroup::Section { name: _, layout }) => { + for layout_row in layout { + self.stack.push(layout_row); + } + self.next() + } + None => None, + } + } +} + +pub type SubLayout = Vec; + +#[remain::sorted] +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub enum LayoutGroup { + #[serde(rename = "column")] + Column { + #[serde(rename = "columnWidgets")] + widgets: Vec, + }, + #[serde(rename = "row")] + Row { + #[serde(rename = "rowWidgets")] + widgets: Vec, + }, + #[serde(rename = "section")] + Section { name: String, layout: SubLayout }, +} + +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub struct WidgetHolder { + #[serde(rename = "widgetId")] + pub widget_id: u64, + pub widget: Widget, +} + +impl WidgetHolder { + pub fn new(widget: Widget) -> Self { + Self { widget_id: generate_uuid(), widget } + } +} + +#[derive(Clone)] +pub struct WidgetCallback { + pub callback: Rc Message + 'static>, +} + +impl WidgetCallback { + pub fn new(callback: impl Fn(&T) -> Message + 'static) -> Self { + Self { callback: Rc::new(callback) } + } +} + +impl Default for WidgetCallback { + fn default() -> Self { + Self::new(|_| Message::NoOp) + } +} + +#[remain::sorted] +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub enum Widget { + CheckboxInput(CheckboxInput), + ColorInput(ColorInput), + DropdownInput(DropdownInput), + FontInput(FontInput), + IconButton(IconButton), + IconLabel(IconLabel), + InvisibleStandinInput(InvisibleStandinInput), + NumberInput(NumberInput), + OptionalInput(OptionalInput), + PopoverButton(PopoverButton), + RadioInput(RadioInput), + Separator(Separator), + SwatchPairInput(SwatchPairInput), + TextAreaInput(TextAreaInput), + TextButton(TextButton), + TextInput(TextInput), + TextLabel(TextLabel), +} diff --git a/editor/src/layout/layout_message.rs b/editor/src/messages/layout/utility_types/misc.rs similarity index 54% rename from editor/src/layout/layout_message.rs rename to editor/src/messages/layout/utility_types/misc.rs index e212cbed0b..19848a87c0 100644 --- a/editor/src/layout/layout_message.rs +++ b/editor/src/messages/layout/utility_types/misc.rs @@ -1,17 +1,5 @@ -use super::widgets::Layout; -use crate::message_prelude::*; - use serde::{Deserialize, Serialize}; -#[remain::sorted] -#[impl_message(Message, Layout)] -#[derive(PartialEq, Clone, Debug, Serialize, Deserialize)] -pub enum LayoutMessage { - RefreshLayout { layout_target: LayoutTarget }, - SendLayout { layout: Layout, layout_target: LayoutTarget }, - UpdateLayout { layout_target: LayoutTarget, widget_id: u64, value: serde_json::Value }, -} - #[remain::sorted] #[derive(PartialEq, Clone, Debug, Hash, Eq, Copy, Serialize, Deserialize)] #[repr(u8)] diff --git a/editor/src/messages/layout/utility_types/mod.rs b/editor/src/messages/layout/utility_types/mod.rs new file mode 100644 index 0000000000..627175b9f5 --- /dev/null +++ b/editor/src/messages/layout/utility_types/mod.rs @@ -0,0 +1,3 @@ +pub mod layout_widget; +pub mod misc; +pub mod widgets; diff --git a/editor/src/messages/layout/utility_types/widgets/button_widgets.rs b/editor/src/messages/layout/utility_types/widgets/button_widgets.rs new file mode 100644 index 0000000000..97b949e6b0 --- /dev/null +++ b/editor/src/messages/layout/utility_types/widgets/button_widgets.rs @@ -0,0 +1,55 @@ +use crate::messages::input_mapper::utility_types::misc::ActionKeys; +use crate::messages::layout::utility_types::layout_widget::WidgetCallback; + +use derivative::*; +use serde::{Deserialize, Serialize}; + +#[derive(Clone, Default, Derivative, Serialize, Deserialize)] +#[derivative(Debug, PartialEq)] +pub struct IconButton { + pub icon: String, + + pub size: u32, // TODO: Convert to an `IconSize` enum + + pub active: bool, + + pub tooltip: String, + + #[serde(skip)] + pub tooltip_shortcut: Option, + + // Callbacks + #[serde(skip)] + #[derivative(Debug = "ignore", PartialEq = "ignore")] + pub on_update: WidgetCallback, +} + +#[derive(Clone, Serialize, Deserialize, Derivative)] +#[derivative(Debug, PartialEq, Default)] +pub struct PopoverButton { + pub icon: Option, + + // Body + pub header: String, + + pub text: String, +} + +#[derive(Clone, Serialize, Deserialize, Derivative, Default)] +#[derivative(Debug, PartialEq)] +#[serde(rename_all(serialize = "camelCase", deserialize = "camelCase"))] +pub struct TextButton { + pub label: String, + + pub emphasized: bool, + + #[serde(rename = "minWidth")] + pub min_width: u32, + + pub disabled: bool, + + // Callbacks + #[serde(skip)] + #[derivative(Debug = "ignore", PartialEq = "ignore")] + pub on_update: WidgetCallback, +} diff --git a/editor/src/messages/layout/utility_types/widgets/input_widgets.rs b/editor/src/messages/layout/utility_types/widgets/input_widgets.rs new file mode 100644 index 0000000000..e0ed34f61a --- /dev/null +++ b/editor/src/messages/layout/utility_types/widgets/input_widgets.rs @@ -0,0 +1,268 @@ +use crate::messages::input_mapper::utility_types::misc::ActionKeys; +use crate::messages::layout::utility_types::layout_widget::WidgetCallback; + +use graphene::color::Color; + +use derivative::*; +use serde::{Deserialize, Serialize}; + +#[derive(Clone, Default, Derivative, Serialize, Deserialize)] +#[derivative(Debug, PartialEq)] +pub struct CheckboxInput { + pub checked: bool, + + pub icon: String, + + pub tooltip: String, + + #[serde(skip)] + pub tooltip_shortcut: Option, + + // Callbacks + #[serde(skip)] + #[derivative(Debug = "ignore", PartialEq = "ignore")] + pub on_update: WidgetCallback, +} + +#[derive(Clone, Derivative, Serialize, Deserialize)] +#[derivative(Debug, PartialEq, Default)] +pub struct ColorInput { + pub value: Option, + + pub label: Option, + + #[serde(rename = "noTransparency")] + #[derivative(Default(value = "true"))] + pub no_transparency: bool, + + pub disabled: bool, + + pub tooltip: String, + + #[serde(skip)] + pub tooltip_shortcut: Option, + + // Callbacks + #[serde(skip)] + #[derivative(Debug = "ignore", PartialEq = "ignore")] + pub on_update: WidgetCallback, +} + +#[derive(Clone, Serialize, Deserialize, Derivative)] +#[derivative(Debug, PartialEq, Default)] +pub struct DropdownInput { + pub entries: DropdownInputEntries, + + // This uses `u32` instead of `usize` since it will be serialized as a normal JS number (replace this with `usize` after switching to a Rust-based GUI) + #[serde(rename = "selectedIndex")] + pub selected_index: Option, + + #[serde(rename = "drawIcon")] + pub draw_icon: bool, + + #[derivative(Default(value = "true"))] + pub interactive: bool, + + pub disabled: bool, + // + // Callbacks + // `on_update` exists on the `DropdownEntryData`, not this parent `DropdownInput` +} + +pub type DropdownInputEntries = Vec>; + +#[derive(Clone, Serialize, Deserialize, Derivative, Default)] +#[derivative(Debug, PartialEq)] +pub struct DropdownEntryData { + pub value: String, + + pub label: String, + + pub icon: String, + + pub shortcut: Vec, + + #[serde(rename = "shortcutRequiresLock")] + pub shortcut_requires_lock: bool, + + pub disabled: bool, + + pub children: DropdownInputEntries, + + // Callbacks + #[serde(skip)] + #[derivative(Debug = "ignore", PartialEq = "ignore")] + pub on_update: WidgetCallback<()>, +} + +#[derive(Clone, Serialize, Deserialize, Derivative)] +#[derivative(Debug, PartialEq, Default)] +pub struct FontInput { + #[serde(rename = "fontFamily")] + pub font_family: String, + + #[serde(rename = "fontStyle")] + pub font_style: String, + + #[serde(rename = "isStyle")] + pub is_style_picker: bool, + + pub disabled: bool, + + // Callbacks + #[serde(skip)] + #[derivative(Debug = "ignore", PartialEq = "ignore")] + pub on_update: WidgetCallback, +} + +/// This widget allows for the flexible use of the layout system. +/// In a custom layout, one can define a widget that is just used to trigger code on the backend. +/// This is used in MenuLayout to pipe the triggering of messages from the frontend to backend. +#[derive(Clone, Serialize, Deserialize, Derivative, Default)] +#[derivative(Debug, PartialEq)] +pub struct InvisibleStandinInput { + #[serde(skip)] + #[derivative(Debug = "ignore", PartialEq = "ignore")] + pub on_update: WidgetCallback<()>, +} + +#[derive(Clone, Serialize, Deserialize, Derivative)] +#[derivative(Debug, PartialEq, Default)] +pub struct NumberInput { + pub label: String, + + pub value: Option, + + pub min: Option, + + pub max: Option, + + #[serde(rename = "isInteger")] + pub is_integer: bool, + + #[serde(rename = "displayDecimalPlaces")] + #[derivative(Default(value = "3"))] + pub display_decimal_places: u32, + + pub unit: String, + + #[serde(rename = "unitIsHiddenWhenEditing")] + #[derivative(Default(value = "true"))] + pub unit_is_hidden_when_editing: bool, + + #[serde(rename = "incrementBehavior")] + pub increment_behavior: NumberInputIncrementBehavior, + + #[serde(rename = "incrementFactor")] + #[derivative(Default(value = "1."))] + pub increment_factor: f64, + + pub disabled: bool, + + // Callbacks + #[serde(skip)] + #[derivative(Debug = "ignore", PartialEq = "ignore")] + pub on_update: WidgetCallback, + + #[serde(skip)] + #[derivative(Debug = "ignore", PartialEq = "ignore")] + pub increment_callback_increase: WidgetCallback, + + #[serde(skip)] + #[derivative(Debug = "ignore", PartialEq = "ignore")] + pub increment_callback_decrease: WidgetCallback, +} + +#[derive(Clone, Serialize, Deserialize, Debug, Default, PartialEq, Eq)] +pub enum NumberInputIncrementBehavior { + #[default] + Add, + Multiply, + Callback, +} + +#[derive(Clone, Default, Derivative, Serialize, Deserialize)] +#[derivative(Debug, PartialEq)] +pub struct OptionalInput { + pub checked: bool, + + pub icon: String, + + pub tooltip: String, + + #[serde(skip)] + pub tooltip_shortcut: Option, + + // Callbacks + #[serde(skip)] + #[derivative(Debug = "ignore", PartialEq = "ignore")] + pub on_update: WidgetCallback, +} + +#[derive(Clone, Default, Derivative, Serialize, Deserialize)] +#[derivative(Debug, PartialEq)] +pub struct RadioInput { + pub entries: Vec, + + // This uses `u32` instead of `usize` since it will be serialized as a normal JS number (replace this with `usize` after switching to a Rust-based GUI) + #[serde(rename = "selectedIndex")] + pub selected_index: u32, +} + +#[derive(Clone, Default, Derivative, Serialize, Deserialize)] +#[derivative(Debug, PartialEq)] +pub struct RadioEntryData { + pub value: String, + + pub label: String, + + pub icon: String, + + pub tooltip: String, + + #[serde(skip)] + pub tooltip_shortcut: Option, + + // Callbacks + #[serde(skip)] + #[derivative(Debug = "ignore", PartialEq = "ignore")] + pub on_update: WidgetCallback<()>, +} + +#[derive(Clone, Serialize, Deserialize, Derivative)] +#[derivative(Debug, PartialEq, Default)] +pub struct SwatchPairInput { + pub primary: Color, + + pub secondary: Color, +} + +#[derive(Clone, Serialize, Deserialize, Derivative)] +#[derivative(Debug, PartialEq, Default)] +pub struct TextAreaInput { + pub value: String, + + pub label: Option, + + pub disabled: bool, + + // Callbacks + #[serde(skip)] + #[derivative(Debug = "ignore", PartialEq = "ignore")] + pub on_update: WidgetCallback, +} + +#[derive(Clone, Serialize, Deserialize, Derivative)] +#[derivative(Debug, PartialEq, Default)] +pub struct TextInput { + pub value: String, + + pub label: Option, + + pub disabled: bool, + + // Callbacks + #[serde(skip)] + #[derivative(Debug = "ignore", PartialEq = "ignore")] + pub on_update: WidgetCallback, +} diff --git a/editor/src/messages/layout/utility_types/widgets/label_widgets.rs b/editor/src/messages/layout/utility_types/widgets/label_widgets.rs new file mode 100644 index 0000000000..1086feffe2 --- /dev/null +++ b/editor/src/messages/layout/utility_types/widgets/label_widgets.rs @@ -0,0 +1,56 @@ +use derivative::*; +use serde::{Deserialize, Serialize}; + +#[derive(Clone, Serialize, Deserialize, Derivative, Debug, Default, PartialEq, Eq)] +pub struct IconLabel { + pub icon: String, + + #[serde(rename = "iconStyle")] + pub icon_style: IconStyle, +} + +#[derive(Clone, Serialize, Deserialize, Derivative, Debug, Default, PartialEq, Eq)] +pub enum IconStyle { + #[default] + Normal, + Node, +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub struct Separator { + pub direction: SeparatorDirection, + + #[serde(rename = "type")] + pub separator_type: SeparatorType, +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub enum SeparatorDirection { + Horizontal, + Vertical, +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub enum SeparatorType { + Related, + Unrelated, + Section, + List, +} + +#[derive(Clone, Serialize, Deserialize, Derivative, Debug, PartialEq, Eq, Default)] +pub struct TextLabel { + pub bold: bool, + + pub italic: bool, + + #[serde(rename = "tableAlign")] + pub table_align: bool, + + pub multiline: bool, + + // Body + pub value: String, +} + +// TODO: Add UserInputLabel diff --git a/editor/src/messages/layout/utility_types/widgets/menu_widgets.rs b/editor/src/messages/layout/utility_types/widgets/menu_widgets.rs new file mode 100644 index 0000000000..96b9806b61 --- /dev/null +++ b/editor/src/messages/layout/utility_types/widgets/menu_widgets.rs @@ -0,0 +1,134 @@ +use crate::messages::input_mapper::utility_types::input_keyboard::Key; +use crate::messages::input_mapper::utility_types::misc::ActionKeys; +use crate::messages::layout::utility_types::layout_widget::WidgetHolder; +use crate::messages::layout::utility_types::layout_widget::{Widget, WidgetCallback}; +use crate::messages::prelude::*; + +use serde::{Deserialize, Serialize}; + +use super::input_widgets::InvisibleStandinInput; + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default)] +pub struct MenuEntryGroups(pub Vec>); + +impl MenuEntryGroups { + pub fn empty() -> Self { + Self(Vec::new()) + } + + pub fn fill_in_shortcut_actions_with_keys(&mut self, action_input_mapping: &impl Fn(&MessageDiscriminant) -> Vec>) { + let entries = self.0.iter_mut().flatten(); + + for entry in entries { + if let Some(action_keys) = &mut entry.shortcut { + action_keys.to_keys(action_input_mapping); + } + + // Recursively do this for the children also + entry.children.fill_in_shortcut_actions_with_keys(action_input_mapping); + } + } +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +pub struct MenuEntry { + pub label: String, + pub icon: Option, + pub children: MenuEntryGroups, + pub action: WidgetHolder, + pub shortcut: Option, +} + +impl MenuEntry { + pub fn create_action(callback: impl Fn(&()) -> Message + 'static) -> WidgetHolder { + WidgetHolder::new(Widget::InvisibleStandinInput(InvisibleStandinInput { + on_update: WidgetCallback::new(callback), + })) + } + + pub fn no_action() -> WidgetHolder { + MenuEntry::create_action(|_| Message::NoOp) + } +} + +impl Default for MenuEntry { + fn default() -> Self { + Self { + action: MenuEntry::create_action(|_| DialogMessage::RequestComingSoonDialog { issue: None }.into()), + label: "".into(), + icon: None, + children: MenuEntryGroups::empty(), + shortcut: None, + } + } +} + +#[derive(Debug, Default, Clone, Serialize, Deserialize, PartialEq)] +pub struct MenuColumn { + pub label: String, + pub children: MenuEntryGroups, +} + +#[derive(Debug, Default, Clone, PartialEq, Serialize, Deserialize)] +pub struct MenuLayout { + pub layout: Vec, +} + +impl MenuLayout { + pub fn new(layout: Vec) -> Self { + Self { layout } + } + + pub fn iter(&self) -> impl Iterator + '_ { + MenuLayoutIter { + stack: self.layout.iter().flat_map(|column| column.children.0.iter()).flat_map(|group| group.iter()).collect(), + } + } + + pub fn iter_mut(&mut self) -> impl Iterator + '_ { + MenuLayoutIterMut { + stack: self.layout.iter_mut().flat_map(|column| column.children.0.iter_mut()).flat_map(|group| group.iter_mut()).collect(), + } + } +} + +#[derive(Debug, Default)] +pub struct MenuLayoutIter<'a> { + pub stack: Vec<&'a MenuEntry>, +} + +impl<'a> Iterator for MenuLayoutIter<'a> { + type Item = &'a WidgetHolder; + + fn next(&mut self) -> Option { + match self.stack.pop() { + Some(menu_entry) => { + let more_entries = menu_entry.children.0.iter().flat_map(|entry| entry.iter()); + self.stack.extend(more_entries); + + Some(&menu_entry.action) + } + None => None, + } + } +} + +pub struct MenuLayoutIterMut<'a> { + pub stack: Vec<&'a mut MenuEntry>, +} + +impl<'a> Iterator for MenuLayoutIterMut<'a> { + type Item = &'a mut WidgetHolder; + + fn next(&mut self) -> Option { + match self.stack.pop() { + Some(menu_entry) => { + let more_entries = menu_entry.children.0.iter_mut().flat_map(|entry| entry.iter_mut()); + self.stack.extend(more_entries); + + Some(&mut menu_entry.action) + } + None => None, + } + } +} diff --git a/editor/src/messages/layout/utility_types/widgets/mod.rs b/editor/src/messages/layout/utility_types/widgets/mod.rs new file mode 100644 index 0000000000..922029633c --- /dev/null +++ b/editor/src/messages/layout/utility_types/widgets/mod.rs @@ -0,0 +1,4 @@ +pub mod button_widgets; +pub mod input_widgets; +pub mod label_widgets; +pub mod menu_widgets; diff --git a/editor/src/communication/message.rs b/editor/src/messages/message.rs similarity index 82% rename from editor/src/communication/message.rs rename to editor/src/messages/message.rs index 55adb44214..1965f99f40 100644 --- a/editor/src/communication/message.rs +++ b/editor/src/messages/message.rs @@ -1,4 +1,4 @@ -use crate::message_prelude::*; +use crate::messages::prelude::*; use graphite_proc_macros::*; @@ -6,16 +6,6 @@ use serde::{Deserialize, Serialize}; use std::collections::hash_map::DefaultHasher; use std::hash::{Hash, Hasher}; -pub trait AsMessage: TransitiveChild -where - Self::TopParent: TransitiveChild + AsMessage, -{ - fn local_name(self) -> String; - fn global_name(self) -> String { - >::into(self).local_name() - } -} - #[remain::sorted] #[impl_message] #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] @@ -24,6 +14,7 @@ pub enum Message { NoOp, #[remain::unsorted] Init, + #[child] Broadcast(BroadcastMessage), #[child] diff --git a/editor/src/messages/mod.rs b/editor/src/messages/mod.rs new file mode 100644 index 0000000000..4a44f5189c --- /dev/null +++ b/editor/src/messages/mod.rs @@ -0,0 +1,14 @@ +//! The root-level messages forming the first layer of the message system architecture. + +pub mod broadcast; +pub mod debug; +pub mod dialog; +pub mod frontend; +pub mod input_mapper; +pub mod input_preprocessor; +pub mod layout; +pub mod message; +pub mod portfolio; +pub mod prelude; +pub mod tool; +pub mod workspace; diff --git a/editor/src/document/artboard_message.rs b/editor/src/messages/portfolio/document/artboard/artboard_message.rs similarity index 92% rename from editor/src/document/artboard_message.rs rename to editor/src/messages/portfolio/document/artboard/artboard_message.rs index 3719116773..30a91e85a0 100644 --- a/editor/src/document/artboard_message.rs +++ b/editor/src/messages/portfolio/document/artboard/artboard_message.rs @@ -1,5 +1,6 @@ -use crate::message_prelude::*; +use crate::messages::prelude::*; +use graphene::LayerId; use graphene::Operation as DocumentOperation; use serde::{Deserialize, Serialize}; diff --git a/editor/src/document/artboard_message_handler.rs b/editor/src/messages/portfolio/document/artboard/artboard_message_handler.rs similarity index 92% rename from editor/src/document/artboard_message_handler.rs rename to editor/src/messages/portfolio/document/artboard/artboard_message_handler.rs index fd52f5d618..8efe117fa8 100644 --- a/editor/src/document/artboard_message_handler.rs +++ b/editor/src/messages/portfolio/document/artboard/artboard_message_handler.rs @@ -1,10 +1,12 @@ -use crate::message_prelude::*; +use crate::messages::prelude::*; +use crate::application::generate_uuid; use graphene::color::Color; use graphene::document::Document as GrapheneDocument; use graphene::layers::style::{self, Fill, RenderData, ViewMode}; use graphene::layers::text_layer::FontCache; use graphene::DocumentResponse; +use graphene::LayerId; use graphene::Operation as DocumentOperation; use glam::DAffine2; @@ -17,15 +19,9 @@ pub struct ArtboardMessageHandler { pub artboard_ids: Vec, } -impl ArtboardMessageHandler { - pub fn is_infinite_canvas(&self) -> bool { - self.artboard_ids.is_empty() - } -} - impl MessageHandler for ArtboardMessageHandler { #[remain::check] - fn process_action(&mut self, message: ArtboardMessage, font_cache: &FontCache, responses: &mut VecDeque) { + fn process_message(&mut self, message: ArtboardMessage, font_cache: &FontCache, responses: &mut VecDeque) { use ArtboardMessage::*; #[remain::sorted] @@ -41,7 +37,7 @@ impl MessageHandler for ArtboardMessageHandler { DocumentResponse::DocumentChanged => responses.push_back(ArtboardMessage::RenderArtboards.into()), _ => {} }; - responses.push_back(BroadcastSignal::DocumentIsDirty.into()); + responses.push_back(BroadcastEvent::DocumentIsDirty.into()); } } Ok(None) => {} @@ -112,3 +108,9 @@ impl MessageHandler for ArtboardMessageHandler { actions!(ArtboardMessageDiscriminant;) } } + +impl ArtboardMessageHandler { + pub fn is_infinite_canvas(&self) -> bool { + self.artboard_ids.is_empty() + } +} diff --git a/editor/src/messages/portfolio/document/artboard/mod.rs b/editor/src/messages/portfolio/document/artboard/mod.rs new file mode 100644 index 0000000000..7eb959b2c7 --- /dev/null +++ b/editor/src/messages/portfolio/document/artboard/mod.rs @@ -0,0 +1,7 @@ +mod artboard_message; +mod artboard_message_handler; + +#[doc(inline)] +pub use artboard_message::{ArtboardMessage, ArtboardMessageDiscriminant}; +#[doc(inline)] +pub use artboard_message_handler::ArtboardMessageHandler; diff --git a/editor/src/document/document_message.rs b/editor/src/messages/portfolio/document/document_message.rs similarity index 91% rename from editor/src/document/document_message.rs rename to editor/src/messages/portfolio/document/document_message.rs index 54f9795399..2324c16312 100644 --- a/editor/src/document/document_message.rs +++ b/editor/src/messages/portfolio/document/document_message.rs @@ -1,7 +1,7 @@ -use super::layer_panel::LayerMetadata; -use super::utility_types::{AlignAggregate, AlignAxis, FlipAxis}; -use crate::frontend::utility_types::{ExportBounds, FileType}; -use crate::message_prelude::*; +use crate::messages::frontend::utility_types::{ExportBounds, FileType}; +use crate::messages::portfolio::document::utility_types::layer_panel::LayerMetadata; +use crate::messages::portfolio::document::utility_types::misc::{AlignAggregate, AlignAxis, FlipAxis}; +use crate::messages::prelude::*; use graphene::boolean_ops::BooleanOperation as BooleanOperationType; use graphene::layers::blend_mode::BlendMode; @@ -29,7 +29,7 @@ pub enum DocumentMessage { Overlays(OverlaysMessage), #[remain::unsorted] #[child] - TransformLayers(TransformLayerMessage), + TransformLayer(TransformLayerMessage), #[remain::unsorted] #[child] PropertiesPanel(PropertiesPanelMessage), diff --git a/editor/src/document/document_message_handler.rs b/editor/src/messages/portfolio/document/document_message_handler.rs similarity index 94% rename from editor/src/document/document_message_handler.rs rename to editor/src/messages/portfolio/document/document_message_handler.rs index 42b41dbae4..0bdbb9dc6b 100644 --- a/editor/src/document/document_message_handler.rs +++ b/editor/src/messages/portfolio/document/document_message_handler.rs @@ -1,21 +1,20 @@ -use super::clipboards::Clipboard; -use super::layer_panel::{layer_panel_entry, LayerMetadata, LayerPanelEntry, RawBuffer}; -use super::properties_panel_message_handler::PropertiesPanelMessageHandlerData; -use super::utility_types::DocumentMode; -use super::utility_types::{AlignAggregate, AlignAxis, DocumentSave, FlipAxis}; -use super::{vectorize_layer_metadata, PropertiesPanelMessageHandler}; -use super::{ArtboardMessageHandler, MovementMessageHandler, OverlaysMessageHandler, TransformLayerMessageHandler}; +use super::utility_types::error::EditorError; +use crate::application::generate_uuid; use crate::consts::{ASYMPTOTIC_EFFECT, DEFAULT_DOCUMENT_NAME, FILE_SAVE_SUFFIX, GRAPHITE_DOCUMENT_VERSION, SCALE_EFFECT, SCROLLBAR_SPACING, VIEWPORT_ZOOM_TO_FIT_PADDING_SCALE_FACTOR}; -use crate::frontend::utility_types::{FileType, FrontendImageData}; -use crate::input::input_mapper::action_keys::action_shortcut; -use crate::input::InputPreprocessorMessageHandler; -use crate::layout::layout_message::LayoutTarget; -use crate::layout::widgets::{ - DropdownEntryData, DropdownInput, IconButton, Layout, LayoutGroup, NumberInput, NumberInputIncrementBehavior, OptionalInput, PopoverButton, RadioEntryData, RadioInput, Separator, - SeparatorDirection, SeparatorType, Widget, WidgetCallback, WidgetHolder, WidgetLayout, -}; -use crate::message_prelude::*; -use crate::EditorError; +use crate::messages::frontend::utility_types::{FileType, FrontendImageData}; +use crate::messages::input_mapper::utility_types::macros::action_keys; +use crate::messages::layout::utility_types::layout_widget::{Layout, LayoutGroup, Widget, WidgetCallback, WidgetHolder, WidgetLayout}; +use crate::messages::layout::utility_types::misc::LayoutTarget; +use crate::messages::layout::utility_types::widgets::button_widgets::{IconButton, PopoverButton}; +use crate::messages::layout::utility_types::widgets::input_widgets::{DropdownEntryData, DropdownInput, NumberInput, NumberInputIncrementBehavior, OptionalInput, RadioEntryData, RadioInput}; +use crate::messages::layout::utility_types::widgets::label_widgets::{Separator, SeparatorDirection, SeparatorType}; +use crate::messages::portfolio::document::properties_panel::utility_types::PropertiesPanelMessageHandlerData; +use crate::messages::portfolio::document::utility_types::clipboards::Clipboard; +use crate::messages::portfolio::document::utility_types::layer_panel::{LayerMetadata, LayerPanelEntry, RawBuffer}; +use crate::messages::portfolio::document::utility_types::misc::DocumentMode; +use crate::messages::portfolio::document::utility_types::misc::{AlignAggregate, AlignAxis, DocumentSave, FlipAxis}; +use crate::messages::portfolio::document::utility_types::vectorize_layer_metadata; +use crate::messages::prelude::*; use graphene::color::Color; use graphene::document::Document as GrapheneDocument; @@ -90,6 +89,726 @@ impl Default for DocumentMessageHandler { } } +impl MessageHandler for DocumentMessageHandler { + #[remain::check] + fn process_message(&mut self, message: DocumentMessage, (ipp, font_cache): (&InputPreprocessorMessageHandler, &FontCache), responses: &mut VecDeque) { + use DocumentMessage::*; + + #[remain::sorted] + match message { + // Sub-messages + #[remain::unsorted] + DispatchOperation(op) => match self.graphene_document.handle_operation(*op, font_cache) { + Ok(Some(document_responses)) => { + for response in document_responses { + match &response { + DocumentResponse::FolderChanged { path } => responses.push_back(FolderChanged { affected_folder_path: path.clone() }.into()), + DocumentResponse::DeletedLayer { path } => { + self.layer_metadata.remove(path); + } + DocumentResponse::LayerChanged { path } => responses.push_back(LayerChanged { affected_layer_path: path.clone() }.into()), + DocumentResponse::CreatedLayer { path } => { + if self.layer_metadata.contains_key(path) { + log::warn!("CreatedLayer overrides existing layer metadata."); + } + self.layer_metadata.insert(path.clone(), LayerMetadata::new(false)); + + responses.push_back(LayerChanged { affected_layer_path: path.clone() }.into()); + self.layer_range_selection_reference = path.clone(); + responses.push_back( + AddSelectedLayers { + additional_layers: vec![path.clone()], + } + .into(), + ); + } + DocumentResponse::DocumentChanged => responses.push_back(RenderDocument.into()), + }; + responses.push_back(BroadcastEvent::DocumentIsDirty.into()); + } + } + Err(e) => log::error!("DocumentError: {:?}", e), + Ok(_) => (), + }, + #[remain::unsorted] + Artboard(message) => { + self.artboard_message_handler.process_message(message, font_cache, responses); + } + #[remain::unsorted] + Movement(message) => { + self.movement_handler.process_message(message, (&self.graphene_document, ipp), responses); + } + #[remain::unsorted] + Overlays(message) => { + self.overlays_message_handler.process_message(message, (self.overlays_visible, font_cache, ipp), responses); + } + #[remain::unsorted] + TransformLayer(message) => { + self.transform_layer_handler + .process_message(message, (&mut self.layer_metadata, &mut self.graphene_document, ipp, font_cache), responses); + } + #[remain::unsorted] + PropertiesPanel(message) => { + self.properties_panel_message_handler.process_message( + message, + PropertiesPanelMessageHandlerData { + artwork_document: &self.graphene_document, + artboard_document: &self.artboard_message_handler.artboards_graphene_document, + selected_layers: &mut self.layer_metadata.iter().filter_map(|(path, data)| data.selected.then(|| path.as_slice())), + font_cache, + }, + responses, + ); + } + + // Messages + AbortTransaction => { + self.undo(responses).unwrap_or_else(|e| log::warn!("{}", e)); + responses.extend([RenderDocument.into(), DocumentStructureChanged.into()]); + } + AddSelectedLayers { additional_layers } => { + for layer_path in &additional_layers { + responses.extend(self.select_layer(layer_path, font_cache)); + } + + // TODO: Correctly update layer panel in clear_selection instead of here + responses.push_back(FolderChanged { affected_folder_path: vec![] }.into()); + responses.push_back(BroadcastEvent::SelectionChanged.into()); + + self.update_layer_tree_options_bar_widgets(responses, font_cache); + } + AlignSelectedLayers { axis, aggregate } => { + self.backup(responses); + let (paths, boxes): (Vec<_>, Vec<_>) = self + .selected_layers() + .filter_map(|path| self.graphene_document.viewport_bounding_box(path, font_cache).ok()?.map(|b| (path, b))) + .unzip(); + + let axis = match axis { + AlignAxis::X => DVec2::X, + AlignAxis::Y => DVec2::Y, + }; + let lerp = |bbox: &[DVec2; 2]| bbox[0].lerp(bbox[1], 0.5); + if let Some(combined_box) = self.graphene_document.combined_viewport_bounding_box(self.selected_layers(), font_cache) { + let aggregated = match aggregate { + AlignAggregate::Min => combined_box[0], + AlignAggregate::Max => combined_box[1], + AlignAggregate::Center => lerp(&combined_box), + AlignAggregate::Average => boxes.iter().map(|b| lerp(b)).reduce(|a, b| a + b).map(|b| b / boxes.len() as f64).unwrap(), + }; + for (path, bbox) in paths.into_iter().zip(boxes) { + let center = match aggregate { + AlignAggregate::Min => bbox[0], + AlignAggregate::Max => bbox[1], + _ => lerp(&bbox), + }; + let translation = (aggregated - center) * axis; + responses.push_back( + DocumentOperation::TransformLayerInViewport { + path: path.to_vec(), + transform: DAffine2::from_translation(translation).to_cols_array(), + } + .into(), + ); + } + responses.push_back(BroadcastEvent::DocumentIsDirty.into()); + } + } + BooleanOperation(op) => { + // Convert Vec<&[LayerId]> to Vec> because Vec<&[LayerId]> does not implement several traits (Debug, Serialize, Deserialize, ...) required by DocumentOperation enum + responses.push_back(StartTransaction.into()); + responses.push_back( + DocumentOperation::BooleanOperation { + operation: op, + selected: self.selected_layers_sorted().iter().map(|slice| (*slice).into()).collect(), + } + .into(), + ); + responses.push_back(CommitTransaction.into()); + } + CommitTransaction => (), + CreateEmptyFolder { mut container_path } => { + let id = generate_uuid(); + container_path.push(id); + responses.push_back(DocumentMessage::DeselectAllLayers.into()); + responses.push_back(DocumentOperation::CreateFolder { path: container_path.clone() }.into()); + responses.push_back( + DocumentMessage::SetLayerExpansion { + layer_path: container_path, + set_expanded: true, + } + .into(), + ); + } + DebugPrintDocument => { + let initial_level = log::max_level(); + log::set_max_level(log::LevelFilter::Trace); + + log::trace!("{:#?}\n{:#?}", self.graphene_document, self.layer_metadata); + + log::set_max_level(initial_level); + } + DeleteLayer { layer_path } => { + responses.push_front(DocumentOperation::DeleteLayer { path: layer_path.clone() }.into()); + responses.push_front(BroadcastEvent::ToolAbort.into()); + responses.push_back(PropertiesPanelMessage::CheckSelectedWasDeleted { path: layer_path }.into()); + } + DeleteSelectedLayers => { + self.backup(responses); + + for path in self.selected_layers_without_children() { + responses.push_front(DocumentMessage::DeleteLayer { layer_path: path.to_vec() }.into()); + } + + responses.push_front(BroadcastEvent::SelectionChanged.into()); + responses.push_back(BroadcastEvent::DocumentIsDirty.into()); + } + DeleteSelectedManipulatorPoints => { + responses.push_back(StartTransaction.into()); + + responses.push_front( + DocumentOperation::DeleteSelectedManipulatorPoints { + layer_paths: self.selected_layers_without_children().iter().map(|path| path.to_vec()).collect(), + } + .into(), + ); + } + DeselectAllLayers => { + responses.push_front(SetSelectedLayers { replacement_selected_layers: vec![] }.into()); + self.layer_range_selection_reference.clear(); + } + DeselectAllManipulatorPoints => { + for layer_path in self.selected_layers_without_children() { + responses.push_back(DocumentOperation::DeselectAllManipulatorPoints { layer_path: layer_path.to_vec() }.into()); + } + } + DirtyRenderDocument => { + // Mark all non-overlay caches as dirty + GrapheneDocument::mark_children_as_dirty(&mut self.graphene_document.root); + responses.push_back(DocumentMessage::RenderDocument.into()); + } + DirtyRenderDocumentInOutlineView => { + if self.view_mode == ViewMode::Outline { + responses.push_front(DocumentMessage::DirtyRenderDocument.into()); + } + } + DocumentHistoryBackward => self.undo(responses).unwrap_or_else(|e| log::warn!("{}", e)), + DocumentHistoryForward => self.redo(responses).unwrap_or_else(|e| log::warn!("{}", e)), + DocumentStructureChanged => { + let data_buffer: RawBuffer = self.serialize_root().into(); + responses.push_back(FrontendMessage::UpdateDocumentLayerTreeStructure { data_buffer }.into()) + } + DuplicateSelectedLayers => { + self.backup(responses); + for path in self.selected_layers_sorted() { + responses.push_back(DocumentOperation::DuplicateLayer { path: path.to_vec() }.into()); + } + } + ExportDocument { + file_name, + file_type, + scale_factor, + bounds, + } => { + // Allows the user's transform to be restored + let old_transform = self.graphene_document.root.transform; + // Reset the root's transform (required to avoid any rotation by the user) + self.graphene_document.root.transform = DAffine2::IDENTITY; + GrapheneDocument::mark_children_as_dirty(&mut self.graphene_document.root); + + // Calculates the bounding box of the region to be exported + use crate::messages::frontend::utility_types::ExportBounds; + let bbox = match bounds { + ExportBounds::AllArtwork => self.all_layer_bounds(font_cache), + ExportBounds::Selection => self.selected_visible_layers_bounding_box(font_cache), + ExportBounds::Artboard(id) => self.artboard_message_handler.artboards_graphene_document.layer(&[id]).ok().and_then(|layer| layer.aabb(font_cache)), + } + .unwrap_or_default(); + let size = bbox[1] - bbox[0]; + + let file_suffix = &format!(".{file_type:?}").to_lowercase(); + let name = match file_name.ends_with(FILE_SAVE_SUFFIX) { + true => file_name.replace(FILE_SAVE_SUFFIX, file_suffix), + false => file_name + file_suffix, + }; + + let render_data = RenderData::new(self.view_mode, font_cache, None, true); + let rendered = self.graphene_document.render_root(render_data); + let document = format!( + r#"{}{}"#, + bbox[0].x, bbox[0].y, size.x, size.y, size.x, size.y, "\n", rendered + ); + + self.graphene_document.root.transform = old_transform; + GrapheneDocument::mark_children_as_dirty(&mut self.graphene_document.root); + + if file_type == FileType::Svg { + responses.push_back(FrontendMessage::TriggerFileDownload { document, name }.into()); + } else { + let mime = file_type.to_mime().to_string(); + let size = (size * scale_factor).into(); + responses.push_back(FrontendMessage::TriggerRasterDownload { document, name, mime, size }.into()); + } + } + FlipSelectedLayers { flip_axis } => { + self.backup(responses); + let scale = match flip_axis { + FlipAxis::X => DVec2::new(-1., 1.), + FlipAxis::Y => DVec2::new(1., -1.), + }; + if let Some([min, max]) = self.graphene_document.combined_viewport_bounding_box(self.selected_layers(), font_cache) { + let center = (max + min) / 2.; + let bbox_trans = DAffine2::from_translation(-center); + for path in self.selected_layers() { + responses.push_back( + DocumentOperation::TransformLayerInScope { + path: path.to_vec(), + transform: DAffine2::from_scale(scale).to_cols_array(), + scope: bbox_trans.to_cols_array(), + } + .into(), + ); + } + responses.push_back(BroadcastEvent::DocumentIsDirty.into()); + } + } + FolderChanged { affected_folder_path } => { + let affected_layer_path = affected_folder_path; + responses.extend([LayerChanged { affected_layer_path }.into(), DocumentStructureChanged.into()]); + } + GroupSelectedLayers => { + let mut new_folder_path = self.graphene_document.shallowest_common_folder(self.selected_layers()).unwrap_or(&[]).to_vec(); + + // Required for grouping parent folders with their own children + if !new_folder_path.is_empty() && self.selected_layers_contains(&new_folder_path) { + new_folder_path.remove(new_folder_path.len() - 1); + } + + new_folder_path.push(generate_uuid()); + + responses.push_back(PortfolioMessage::Copy { clipboard: Clipboard::Internal }.into()); + responses.push_back(DocumentMessage::DeleteSelectedLayers.into()); + responses.push_back(DocumentOperation::CreateFolder { path: new_folder_path.clone() }.into()); + responses.push_back(DocumentMessage::ToggleLayerExpansion { layer_path: new_folder_path.clone() }.into()); + responses.push_back( + PortfolioMessage::PasteIntoFolder { + clipboard: Clipboard::Internal, + folder_path: new_folder_path.clone(), + insert_index: -1, + } + .into(), + ); + responses.push_back( + DocumentMessage::SetSelectedLayers { + replacement_selected_layers: vec![new_folder_path], + } + .into(), + ); + } + LayerChanged { affected_layer_path } => { + if let Ok(layer_entry) = self.layer_panel_entry(affected_layer_path.clone(), font_cache) { + responses.push_back(FrontendMessage::UpdateDocumentLayerDetails { data: layer_entry }.into()); + } + responses.push_back(PropertiesPanelMessage::CheckSelectedWasUpdated { path: affected_layer_path }.into()); + self.update_layer_tree_options_bar_widgets(responses, font_cache); + } + MoveSelectedLayersTo { + folder_path, + insert_index, + reverse_index, + } => { + let selected_layers = self.selected_layers().collect::>(); + + // Prevent trying to insert into self + if selected_layers.iter().any(|layer| folder_path.starts_with(layer)) { + return; + } + + let insert_index = self.update_insert_index(&selected_layers, &folder_path, insert_index, reverse_index).unwrap(); + + responses.push_back(PortfolioMessage::Copy { clipboard: Clipboard::Internal }.into()); + responses.push_back(DocumentMessage::DeleteSelectedLayers.into()); + responses.push_back( + PortfolioMessage::PasteIntoFolder { + clipboard: Clipboard::Internal, + folder_path, + insert_index, + } + .into(), + ); + } + MoveSelectedManipulatorPoints { layer_path, delta, absolute_position } => { + self.backup(responses); + if let Ok(_layer) = self.graphene_document.layer(&layer_path) { + responses.push_back(DocumentOperation::MoveSelectedManipulatorPoints { layer_path, delta, absolute_position }.into()); + } + } + NudgeSelectedLayers { delta_x, delta_y } => { + self.backup(responses); + for path in self.selected_layers().map(|path| path.to_vec()) { + let operation = DocumentOperation::TransformLayerInViewport { + path, + transform: DAffine2::from_translation((delta_x, delta_y).into()).to_cols_array(), + }; + responses.push_back(operation.into()); + } + responses.push_back(BroadcastEvent::DocumentIsDirty.into()); + } + PasteImage { mime, image_data, mouse } => { + let path = vec![generate_uuid()]; + responses.push_front( + FrontendMessage::UpdateImageData { + image_data: vec![FrontendImageData { + path: path.clone(), + image_data: image_data.clone(), + mime: mime.clone(), + }], + } + .into(), + ); + responses.push_back( + DocumentOperation::AddImage { + path: path.clone(), + transform: DAffine2::ZERO.to_cols_array(), + insert_index: -1, + mime, + image_data, + } + .into(), + ); + responses.push_back( + DocumentMessage::SetSelectedLayers { + replacement_selected_layers: vec![path.clone()], + } + .into(), + ); + + let mouse = mouse.map_or(ipp.viewport_bounds.center(), |pos| pos.into()); + let transform = DAffine2::from_translation(mouse - ipp.viewport_bounds.top_left).to_cols_array(); + responses.push_back(DocumentOperation::SetLayerTransformInViewport { path, transform }.into()); + } + Redo => { + responses.push_back(SelectToolMessage::Abort.into()); + responses.push_back(DocumentHistoryForward.into()); + responses.push_back(BroadcastEvent::DocumentIsDirty.into()); + responses.push_back(RenderDocument.into()); + responses.push_back(FolderChanged { affected_folder_path: vec![] }.into()); + } + RenameLayer { layer_path, new_name } => responses.push_back(DocumentOperation::RenameLayer { layer_path, new_name }.into()), + RenderDocument => { + let render_data = RenderData::new(self.view_mode, font_cache, Some(ipp.document_bounds()), false); + responses.push_back( + FrontendMessage::UpdateDocumentArtwork { + svg: self.graphene_document.render_root(render_data), + } + .into(), + ); + responses.push_back(ArtboardMessage::RenderArtboards.into()); + + let document_transform_scale = self.movement_handler.snapped_scale(); + let scale = 0.5 + ASYMPTOTIC_EFFECT + document_transform_scale * SCALE_EFFECT; + let viewport_size = ipp.viewport_bounds.size(); + let viewport_mid = ipp.viewport_bounds.center(); + let [bounds1, bounds2] = self.document_bounds(font_cache).unwrap_or([viewport_mid; 2]); + let bounds1 = bounds1.min(viewport_mid) - viewport_size * scale; + let bounds2 = bounds2.max(viewport_mid) + viewport_size * scale; + let bounds_length = (bounds2 - bounds1) * (1. + SCROLLBAR_SPACING); + let scrollbar_position = DVec2::splat(0.5) - (bounds1.lerp(bounds2, 0.5) - viewport_mid) / (bounds_length - viewport_size); + let scrollbar_multiplier = bounds_length - viewport_size; + let scrollbar_size = viewport_size / bounds_length; + + let log = document_transform_scale.log2(); + let ruler_interval = if log < 0. { 100. * 2_f64.powf(-log.ceil()) } else { 100. / 2_f64.powf(log.ceil()) }; + let ruler_spacing = ruler_interval * document_transform_scale; + + let ruler_origin = self.graphene_document.root.transform.transform_point2(DVec2::ZERO); + + responses.push_back( + FrontendMessage::UpdateDocumentScrollbars { + position: scrollbar_position.into(), + size: scrollbar_size.into(), + multiplier: scrollbar_multiplier.into(), + } + .into(), + ); + + responses.push_back( + FrontendMessage::UpdateDocumentRulers { + origin: ruler_origin.into(), + spacing: ruler_spacing, + interval: ruler_interval, + } + .into(), + ); + } + RollbackTransaction => { + self.rollback(responses).unwrap_or_else(|e| log::warn!("{}", e)); + responses.extend([RenderDocument.into(), DocumentStructureChanged.into()]); + } + SaveDocument => { + self.set_save_state(true); + responses.push_back(PortfolioMessage::AutoSaveActiveDocument.into()); + // Update the save status of the just saved document + responses.push_back(PortfolioMessage::UpdateOpenDocumentsList.into()); + + let name = match self.name.ends_with(FILE_SAVE_SUFFIX) { + true => self.name.clone(), + false => self.name.clone() + FILE_SAVE_SUFFIX, + }; + responses.push_back( + FrontendMessage::TriggerFileDownload { + document: self.serialize_document(), + name, + } + .into(), + ) + } + SelectAllLayers => { + let all = self.all_layers().map(|path| path.to_vec()).collect(); + responses.push_front(SetSelectedLayers { replacement_selected_layers: all }.into()); + } + SelectedLayersLower => { + responses.push_front(DocumentMessage::SelectedLayersReorder { relative_index_offset: -1 }.into()); + } + SelectedLayersLowerToBack => { + responses.push_front(DocumentMessage::SelectedLayersReorder { relative_index_offset: isize::MIN }.into()); + } + SelectedLayersRaise => { + responses.push_front(DocumentMessage::SelectedLayersReorder { relative_index_offset: 1 }.into()); + } + SelectedLayersRaiseToFront => { + responses.push_front(DocumentMessage::SelectedLayersReorder { relative_index_offset: isize::MAX }.into()); + } + SelectedLayersReorder { relative_index_offset } => { + self.selected_layers_reorder(relative_index_offset, responses); + } + SelectLayer { layer_path, ctrl, shift } => { + let mut paths = vec![]; + let last_selection_exists = !self.layer_range_selection_reference.is_empty(); + + // If we have shift pressed and a layer already selected then fill the range + if shift && last_selection_exists { + // Fill the selection range + self.layer_metadata + .iter() + .filter(|(target, _)| self.graphene_document.layer_is_between(target, &layer_path, &self.layer_range_selection_reference)) + .for_each(|(layer_path, _)| { + paths.push(layer_path.clone()); + }); + } else { + if ctrl { + // Toggle selection when holding ctrl + let layer = self.layer_metadata_mut(&layer_path); + layer.selected = !layer.selected; + responses.push_back( + LayerChanged { + affected_layer_path: layer_path.clone(), + } + .into(), + ); + responses.push_back(BroadcastEvent::SelectionChanged.into()); + } else { + paths.push(layer_path.clone()); + } + + // Set our last selection reference + self.layer_range_selection_reference = layer_path; + } + + // Don't create messages for empty operations + if !paths.is_empty() { + // Add or set our selected layers + if ctrl { + responses.push_front(AddSelectedLayers { additional_layers: paths }.into()); + } else { + responses.push_front(SetSelectedLayers { replacement_selected_layers: paths }.into()); + } + } + } + SetBlendModeForSelectedLayers { blend_mode } => { + self.backup(responses); + for path in self.layer_metadata.iter().filter_map(|(path, data)| data.selected.then(|| path.clone())) { + responses.push_back(DocumentOperation::SetLayerBlendMode { path, blend_mode }.into()); + } + } + SetLayerExpansion { layer_path, set_expanded } => { + self.layer_metadata_mut(&layer_path).expanded = set_expanded; + responses.push_back(DocumentStructureChanged.into()); + responses.push_back(LayerChanged { affected_layer_path: layer_path }.into()) + } + SetLayerName { layer_path, name } => { + if let Some(layer) = self.layer_panel_entry_from_path(&layer_path, font_cache) { + // Only save the history state if the name actually changed to something different + if layer.name != name { + self.backup(responses); + responses.push_back(DocumentOperation::SetLayerName { path: layer_path, name }.into()); + } + } + } + SetOpacityForSelectedLayers { opacity } => { + self.backup(responses); + let opacity = opacity.clamp(0., 1.); + + for path in self.selected_layers().map(|path| path.to_vec()) { + responses.push_back(DocumentOperation::SetLayerOpacity { path, opacity }.into()); + } + } + SetOverlaysVisibility { visible } => { + self.overlays_visible = visible; + responses.push_back(OverlaysMessage::Rerender.into()); + } + SetSelectedLayers { replacement_selected_layers } => { + let selected = self.layer_metadata.iter_mut().filter(|(_, layer_metadata)| layer_metadata.selected); + selected.for_each(|(path, layer_metadata)| { + layer_metadata.selected = false; + responses.push_back(LayerChanged { affected_layer_path: path.clone() }.into()) + }); + + let additional_layers = replacement_selected_layers; + responses.push_front(AddSelectedLayers { additional_layers }.into()); + } + SetSnapping { snap } => { + self.snapping_enabled = snap; + } + SetTexboxEditability { path, editable } => { + let text = self.graphene_document.layer(&path).unwrap().as_text().unwrap(); + responses.push_back(DocumentOperation::SetTextEditability { path, editable }.into()); + if editable { + let color = if let Fill::Solid(solid_color) = text.path_style.fill() { *solid_color } else { Color::BLACK }; + responses.push_back( + FrontendMessage::DisplayEditableTextbox { + text: text.text.clone(), + line_width: text.line_width, + font_size: text.size, + color, + } + .into(), + ); + } else { + responses.push_back(FrontendMessage::DisplayRemoveEditableTextbox.into()); + } + } + SetViewMode { view_mode } => { + self.view_mode = view_mode; + responses.push_front(DocumentMessage::DirtyRenderDocument.into()); + } + StartTransaction => self.backup(responses), + ToggleLayerExpansion { layer_path } => { + self.layer_metadata_mut(&layer_path).expanded ^= true; + responses.push_back(DocumentStructureChanged.into()); + responses.push_back(LayerChanged { affected_layer_path: layer_path }.into()) + } + ToggleLayerVisibility { layer_path } => { + responses.push_back(DocumentOperation::ToggleLayerVisibility { path: layer_path }.into()); + responses.push_back(BroadcastEvent::DocumentIsDirty.into()); + } + ToggleSelectedHandleMirroring { + layer_path, + toggle_distance, + toggle_angle, + } => { + responses.push_back( + DocumentOperation::SetSelectedHandleMirroring { + layer_path, + toggle_distance, + toggle_angle, + } + .into(), + ); + } + Undo => { + responses.push_back(BroadcastEvent::ToolAbort.into()); + responses.push_back(DocumentHistoryBackward.into()); + responses.push_back(BroadcastEvent::DocumentIsDirty.into()); + responses.push_back(RenderDocument.into()); + responses.push_back(FolderChanged { affected_folder_path: vec![] }.into()); + } + UngroupLayers { folder_path } => { + // Select all the children of the folder + let select = self.graphene_document.folder_children_paths(&folder_path); + + let message_buffer = [ + // Select them + DocumentMessage::SetSelectedLayers { replacement_selected_layers: select }.into(), + // Copy them + PortfolioMessage::Copy { clipboard: Clipboard::Internal }.into(), + // Paste them into the folder above + PortfolioMessage::PasteIntoFolder { + clipboard: Clipboard::Internal, + folder_path: folder_path[..folder_path.len() - 1].to_vec(), + insert_index: -1, + } + .into(), + // Delete the parent folder + DocumentMessage::DeleteLayer { layer_path: folder_path }.into(), + ]; + + // Push these messages in reverse due to push_front + for message in message_buffer.into_iter().rev() { + responses.push_front(message); + } + } + UngroupSelectedLayers => { + responses.push_back(DocumentMessage::StartTransaction.into()); + let folder_paths = self.graphene_document.sorted_folders_by_depth(self.selected_layers()); + for folder_path in folder_paths { + responses.push_back(DocumentMessage::UngroupLayers { folder_path: folder_path.to_vec() }.into()); + } + responses.push_back(DocumentMessage::CommitTransaction.into()); + } + UpdateLayerMetadata { layer_path, layer_metadata } => { + self.layer_metadata.insert(layer_path, layer_metadata); + } + ZoomCanvasToFitAll => { + if let Some(bounds) = self.document_bounds(font_cache) { + responses.push_back( + MovementMessage::FitViewportToBounds { + bounds, + padding_scale_factor: Some(VIEWPORT_ZOOM_TO_FIT_PADDING_SCALE_FACTOR), + prevent_zoom_past_100: true, + } + .into(), + ) + } + } + } + } + + fn actions(&self) -> ActionList { + let mut common = actions!(DocumentMessageDiscriminant; + Undo, + Redo, + SelectAllLayers, + DeselectAllLayers, + RenderDocument, + ExportDocument, + SaveDocument, + SetSnapping, + DebugPrintDocument, + ZoomCanvasToFitAll, + CreateEmptyFolder, + ); + + if self.layer_metadata.values().any(|data| data.selected) { + let select = actions!(DocumentMessageDiscriminant; + DeleteSelectedLayers, + DuplicateSelectedLayers, + NudgeSelectedLayers, + SelectedLayersLower, + SelectedLayersLowerToBack, + SelectedLayersRaise, + SelectedLayersRaiseToFront, + GroupSelectedLayers, + UngroupSelectedLayers, + ); + common.extend(select); + } + common.extend(self.movement_handler.actions()); + common.extend(self.transform_layer_handler.actions()); + common + } +} + impl DocumentMessageHandler { pub fn serialize_document(&self) -> String { let val = serde_json::to_string(self); @@ -375,7 +1094,7 @@ impl DocumentMessageHandler { let prev_selected_paths: Vec> = layer_metadata.iter().filter_map(|(layer_id, metadata)| metadata.selected.then(|| layer_id.clone())).collect(); if prev_selected_paths != selected_paths { - responses.push_back(BroadcastSignal::SelectionChanged.into()); + responses.push_back(BroadcastEvent::SelectionChanged.into()); } let document = std::mem::replace(&mut self.graphene_document, document); @@ -405,7 +1124,7 @@ impl DocumentMessageHandler { let next_selected_paths: Vec> = layer_metadata.iter().filter_map(|(layer_id, metadata)| metadata.selected.then(|| layer_id.clone())).collect(); if next_selected_paths != selected_paths { - responses.push_back(BroadcastSignal::SelectionChanged.into()); + responses.push_back(BroadcastEvent::SelectionChanged.into()); } let document = std::mem::replace(&mut self.graphene_document, document); @@ -450,7 +1169,7 @@ impl DocumentMessageHandler { .get_mut(&path) .ok_or_else(|| EditorError::Document(format!("Could not get layer metadata for {:?}", path)))?; let layer = self.graphene_document.layer(&path)?; - let entry = layer_panel_entry(&data, self.graphene_document.multiply_transforms(&path)?, layer, path, font_cache); + let entry = LayerPanelEntry::new(&data, self.graphene_document.multiply_transforms(&path)?, layer, path, font_cache); Ok(entry) } @@ -471,7 +1190,7 @@ impl DocumentMessageHandler { .ok()?; let layer = self.graphene_document.layer(path).ok()?; - Some(layer_panel_entry(layer_metadata, transform, layer, path.to_vec(), font_cache)) + Some(LayerPanelEntry::new(layer_metadata, transform, layer, path.to_vec(), font_cache)) } /// When working with an insert index, deleting the layers may cause the insert index to point to a different location (if the layer being deleted was located before the insert index). @@ -846,7 +1565,7 @@ impl DocumentMessageHandler { WidgetHolder::new(Widget::IconButton(IconButton { icon: "NodeFolder".into(), tooltip: "New Folder".into(), - tooltip_shortcut: action_shortcut!(DocumentMessageDiscriminant::CreateEmptyFolder), + tooltip_shortcut: action_keys!(DocumentMessageDiscriminant::CreateEmptyFolder), size: 24, on_update: WidgetCallback::new(|_| DocumentMessage::CreateEmptyFolder { container_path: vec![] }.into()), ..Default::default() @@ -854,7 +1573,7 @@ impl DocumentMessageHandler { WidgetHolder::new(Widget::IconButton(IconButton { icon: "Trash".into(), tooltip: "Delete Selected".into(), - tooltip_shortcut: action_shortcut!(DocumentMessageDiscriminant::DeleteSelectedLayers), + tooltip_shortcut: action_keys!(DocumentMessageDiscriminant::DeleteSelectedLayers), size: 24, on_update: WidgetCallback::new(|_| DocumentMessage::DeleteSelectedLayers.into()), ..Default::default() @@ -927,723 +1646,3 @@ impl DocumentMessageHandler { } } } - -impl MessageHandler for DocumentMessageHandler { - #[remain::check] - fn process_action(&mut self, message: DocumentMessage, (ipp, font_cache): (&InputPreprocessorMessageHandler, &FontCache), responses: &mut VecDeque) { - use DocumentMessage::*; - - #[remain::sorted] - match message { - // Sub-messages - #[remain::unsorted] - DispatchOperation(op) => match self.graphene_document.handle_operation(*op, font_cache) { - Ok(Some(document_responses)) => { - for response in document_responses { - match &response { - DocumentResponse::FolderChanged { path } => responses.push_back(FolderChanged { affected_folder_path: path.clone() }.into()), - DocumentResponse::DeletedLayer { path } => { - self.layer_metadata.remove(path); - } - DocumentResponse::LayerChanged { path } => responses.push_back(LayerChanged { affected_layer_path: path.clone() }.into()), - DocumentResponse::CreatedLayer { path } => { - if self.layer_metadata.contains_key(path) { - log::warn!("CreatedLayer overrides existing layer metadata."); - } - self.layer_metadata.insert(path.clone(), LayerMetadata::new(false)); - - responses.push_back(LayerChanged { affected_layer_path: path.clone() }.into()); - self.layer_range_selection_reference = path.clone(); - responses.push_back( - AddSelectedLayers { - additional_layers: vec![path.clone()], - } - .into(), - ); - } - DocumentResponse::DocumentChanged => responses.push_back(RenderDocument.into()), - }; - responses.push_back(BroadcastSignal::DocumentIsDirty.into()); - } - } - Err(e) => log::error!("DocumentError: {:?}", e), - Ok(_) => (), - }, - #[remain::unsorted] - Artboard(message) => { - self.artboard_message_handler.process_action(message, font_cache, responses); - } - #[remain::unsorted] - Movement(message) => { - self.movement_handler.process_action(message, (&self.graphene_document, ipp), responses); - } - #[remain::unsorted] - Overlays(message) => { - self.overlays_message_handler.process_action(message, (self.overlays_visible, font_cache, ipp), responses); - } - #[remain::unsorted] - TransformLayers(message) => { - self.transform_layer_handler - .process_action(message, (&mut self.layer_metadata, &mut self.graphene_document, ipp, font_cache), responses); - } - #[remain::unsorted] - PropertiesPanel(message) => { - self.properties_panel_message_handler.process_action( - message, - PropertiesPanelMessageHandlerData { - artwork_document: &self.graphene_document, - artboard_document: &self.artboard_message_handler.artboards_graphene_document, - selected_layers: &mut self.layer_metadata.iter().filter_map(|(path, data)| data.selected.then(|| path.as_slice())), - font_cache, - }, - responses, - ); - } - - // Messages - AbortTransaction => { - self.undo(responses).unwrap_or_else(|e| log::warn!("{}", e)); - responses.extend([RenderDocument.into(), DocumentStructureChanged.into()]); - } - AddSelectedLayers { additional_layers } => { - for layer_path in &additional_layers { - responses.extend(self.select_layer(layer_path, font_cache)); - } - - // TODO: Correctly update layer panel in clear_selection instead of here - responses.push_back(FolderChanged { affected_folder_path: vec![] }.into()); - responses.push_back(BroadcastSignal::SelectionChanged.into()); - - self.update_layer_tree_options_bar_widgets(responses, font_cache); - } - AlignSelectedLayers { axis, aggregate } => { - self.backup(responses); - let (paths, boxes): (Vec<_>, Vec<_>) = self - .selected_layers() - .filter_map(|path| self.graphene_document.viewport_bounding_box(path, font_cache).ok()?.map(|b| (path, b))) - .unzip(); - - let axis = match axis { - AlignAxis::X => DVec2::X, - AlignAxis::Y => DVec2::Y, - }; - let lerp = |bbox: &[DVec2; 2]| bbox[0].lerp(bbox[1], 0.5); - if let Some(combined_box) = self.graphene_document.combined_viewport_bounding_box(self.selected_layers(), font_cache) { - let aggregated = match aggregate { - AlignAggregate::Min => combined_box[0], - AlignAggregate::Max => combined_box[1], - AlignAggregate::Center => lerp(&combined_box), - AlignAggregate::Average => boxes.iter().map(|b| lerp(b)).reduce(|a, b| a + b).map(|b| b / boxes.len() as f64).unwrap(), - }; - for (path, bbox) in paths.into_iter().zip(boxes) { - let center = match aggregate { - AlignAggregate::Min => bbox[0], - AlignAggregate::Max => bbox[1], - _ => lerp(&bbox), - }; - let translation = (aggregated - center) * axis; - responses.push_back( - DocumentOperation::TransformLayerInViewport { - path: path.to_vec(), - transform: DAffine2::from_translation(translation).to_cols_array(), - } - .into(), - ); - } - responses.push_back(BroadcastSignal::DocumentIsDirty.into()); - } - } - BooleanOperation(op) => { - // Convert Vec<&[LayerId]> to Vec> because Vec<&[LayerId]> does not implement several traits (Debug, Serialize, Deserialize, ...) required by DocumentOperation enum - responses.push_back(StartTransaction.into()); - responses.push_back( - DocumentOperation::BooleanOperation { - operation: op, - selected: self.selected_layers_sorted().iter().map(|slice| (*slice).into()).collect(), - } - .into(), - ); - responses.push_back(CommitTransaction.into()); - } - CommitTransaction => (), - CreateEmptyFolder { mut container_path } => { - let id = generate_uuid(); - container_path.push(id); - responses.push_back(DocumentMessage::DeselectAllLayers.into()); - responses.push_back(DocumentOperation::CreateFolder { path: container_path.clone() }.into()); - responses.push_back( - DocumentMessage::SetLayerExpansion { - layer_path: container_path, - set_expanded: true, - } - .into(), - ); - } - DebugPrintDocument => { - let initial_level = log::max_level(); - log::set_max_level(log::LevelFilter::Trace); - - log::trace!("{:#?}\n{:#?}", self.graphene_document, self.layer_metadata); - - log::set_max_level(initial_level); - } - DeleteLayer { layer_path } => { - responses.push_front(DocumentOperation::DeleteLayer { path: layer_path.clone() }.into()); - responses.push_front(BroadcastSignal::ToolAbort.into()); - responses.push_back(PropertiesPanelMessage::CheckSelectedWasDeleted { path: layer_path }.into()); - } - DeleteSelectedLayers => { - self.backup(responses); - - for path in self.selected_layers_without_children() { - responses.push_front(DocumentMessage::DeleteLayer { layer_path: path.to_vec() }.into()); - } - - responses.push_front(BroadcastSignal::SelectionChanged.into()); - responses.push_back(BroadcastSignal::DocumentIsDirty.into()); - } - DeleteSelectedManipulatorPoints => { - responses.push_back(StartTransaction.into()); - - responses.push_front( - DocumentOperation::DeleteSelectedManipulatorPoints { - layer_paths: self.selected_layers_without_children().iter().map(|path| path.to_vec()).collect(), - } - .into(), - ); - } - DeselectAllLayers => { - responses.push_front(SetSelectedLayers { replacement_selected_layers: vec![] }.into()); - self.layer_range_selection_reference.clear(); - } - DeselectAllManipulatorPoints => { - for layer_path in self.selected_layers_without_children() { - responses.push_back(DocumentOperation::DeselectAllManipulatorPoints { layer_path: layer_path.to_vec() }.into()); - } - } - DirtyRenderDocument => { - // Mark all non-overlay caches as dirty - GrapheneDocument::mark_children_as_dirty(&mut self.graphene_document.root); - responses.push_back(DocumentMessage::RenderDocument.into()); - } - DirtyRenderDocumentInOutlineView => { - if self.view_mode == ViewMode::Outline { - responses.push_front(DocumentMessage::DirtyRenderDocument.into()); - } - } - DocumentHistoryBackward => self.undo(responses).unwrap_or_else(|e| log::warn!("{}", e)), - DocumentHistoryForward => self.redo(responses).unwrap_or_else(|e| log::warn!("{}", e)), - DocumentStructureChanged => { - let data_buffer: RawBuffer = self.serialize_root().into(); - responses.push_back(FrontendMessage::UpdateDocumentLayerTreeStructure { data_buffer }.into()) - } - DuplicateSelectedLayers => { - self.backup(responses); - for path in self.selected_layers_sorted() { - responses.push_back(DocumentOperation::DuplicateLayer { path: path.to_vec() }.into()); - } - } - ExportDocument { - file_name, - file_type, - scale_factor, - bounds, - } => { - // Allows the user's transform to be restored - let old_transform = self.graphene_document.root.transform; - // Reset the root's transform (required to avoid any rotation by the user) - self.graphene_document.root.transform = DAffine2::IDENTITY; - GrapheneDocument::mark_children_as_dirty(&mut self.graphene_document.root); - - // Calculates the bounding box of the region to be exported - use crate::frontend::utility_types::ExportBounds; - let bbox = match bounds { - ExportBounds::AllArtwork => self.all_layer_bounds(font_cache), - ExportBounds::Selection => self.selected_visible_layers_bounding_box(font_cache), - ExportBounds::Artboard(id) => self.artboard_message_handler.artboards_graphene_document.layer(&[id]).ok().and_then(|layer| layer.aabb(font_cache)), - } - .unwrap_or_default(); - let size = bbox[1] - bbox[0]; - - let file_suffix = &format!(".{file_type:?}").to_lowercase(); - let name = match file_name.ends_with(FILE_SAVE_SUFFIX) { - true => file_name.replace(FILE_SAVE_SUFFIX, file_suffix), - false => file_name + file_suffix, - }; - - let render_data = RenderData::new(self.view_mode, font_cache, None, true); - let rendered = self.graphene_document.render_root(render_data); - let document = format!( - r#"{}{}"#, - bbox[0].x, bbox[0].y, size.x, size.y, size.x, size.y, "\n", rendered - ); - - self.graphene_document.root.transform = old_transform; - GrapheneDocument::mark_children_as_dirty(&mut self.graphene_document.root); - - if file_type == FileType::Svg { - responses.push_back(FrontendMessage::TriggerFileDownload { document, name }.into()); - } else { - let mime = file_type.to_mime().to_string(); - let size = (size * scale_factor).into(); - responses.push_back(FrontendMessage::TriggerRasterDownload { document, name, mime, size }.into()); - } - } - FlipSelectedLayers { flip_axis } => { - self.backup(responses); - let scale = match flip_axis { - FlipAxis::X => DVec2::new(-1., 1.), - FlipAxis::Y => DVec2::new(1., -1.), - }; - if let Some([min, max]) = self.graphene_document.combined_viewport_bounding_box(self.selected_layers(), font_cache) { - let center = (max + min) / 2.; - let bbox_trans = DAffine2::from_translation(-center); - for path in self.selected_layers() { - responses.push_back( - DocumentOperation::TransformLayerInScope { - path: path.to_vec(), - transform: DAffine2::from_scale(scale).to_cols_array(), - scope: bbox_trans.to_cols_array(), - } - .into(), - ); - } - responses.push_back(BroadcastSignal::DocumentIsDirty.into()); - } - } - FolderChanged { affected_folder_path } => { - let affected_layer_path = affected_folder_path; - responses.extend([LayerChanged { affected_layer_path }.into(), DocumentStructureChanged.into()]); - } - GroupSelectedLayers => { - let mut new_folder_path = self.graphene_document.shallowest_common_folder(self.selected_layers()).unwrap_or(&[]).to_vec(); - - // Required for grouping parent folders with their own children - if !new_folder_path.is_empty() && self.selected_layers_contains(&new_folder_path) { - new_folder_path.remove(new_folder_path.len() - 1); - } - - new_folder_path.push(generate_uuid()); - - responses.push_back(PortfolioMessage::Copy { clipboard: Clipboard::Internal }.into()); - responses.push_back(DocumentMessage::DeleteSelectedLayers.into()); - responses.push_back(DocumentOperation::CreateFolder { path: new_folder_path.clone() }.into()); - responses.push_back(DocumentMessage::ToggleLayerExpansion { layer_path: new_folder_path.clone() }.into()); - responses.push_back( - PortfolioMessage::PasteIntoFolder { - clipboard: Clipboard::Internal, - folder_path: new_folder_path.clone(), - insert_index: -1, - } - .into(), - ); - responses.push_back( - DocumentMessage::SetSelectedLayers { - replacement_selected_layers: vec![new_folder_path], - } - .into(), - ); - } - LayerChanged { affected_layer_path } => { - if let Ok(layer_entry) = self.layer_panel_entry(affected_layer_path.clone(), font_cache) { - responses.push_back(FrontendMessage::UpdateDocumentLayerDetails { data: layer_entry }.into()); - } - responses.push_back(PropertiesPanelMessage::CheckSelectedWasUpdated { path: affected_layer_path }.into()); - self.update_layer_tree_options_bar_widgets(responses, font_cache); - } - MoveSelectedLayersTo { - folder_path, - insert_index, - reverse_index, - } => { - let selected_layers = self.selected_layers().collect::>(); - - // Prevent trying to insert into self - if selected_layers.iter().any(|layer| folder_path.starts_with(layer)) { - return; - } - - let insert_index = self.update_insert_index(&selected_layers, &folder_path, insert_index, reverse_index).unwrap(); - - responses.push_back(PortfolioMessage::Copy { clipboard: Clipboard::Internal }.into()); - responses.push_back(DocumentMessage::DeleteSelectedLayers.into()); - responses.push_back( - PortfolioMessage::PasteIntoFolder { - clipboard: Clipboard::Internal, - folder_path, - insert_index, - } - .into(), - ); - } - MoveSelectedManipulatorPoints { layer_path, delta, absolute_position } => { - self.backup(responses); - if let Ok(_layer) = self.graphene_document.layer(&layer_path) { - responses.push_back(DocumentOperation::MoveSelectedManipulatorPoints { layer_path, delta, absolute_position }.into()); - } - } - NudgeSelectedLayers { delta_x, delta_y } => { - self.backup(responses); - for path in self.selected_layers().map(|path| path.to_vec()) { - let operation = DocumentOperation::TransformLayerInViewport { - path, - transform: DAffine2::from_translation((delta_x, delta_y).into()).to_cols_array(), - }; - responses.push_back(operation.into()); - } - responses.push_back(BroadcastSignal::DocumentIsDirty.into()); - } - PasteImage { mime, image_data, mouse } => { - let path = vec![generate_uuid()]; - responses.push_front( - FrontendMessage::UpdateImageData { - image_data: vec![FrontendImageData { - path: path.clone(), - image_data: image_data.clone(), - mime: mime.clone(), - }], - } - .into(), - ); - responses.push_back( - DocumentOperation::AddImage { - path: path.clone(), - transform: DAffine2::ZERO.to_cols_array(), - insert_index: -1, - mime, - image_data, - } - .into(), - ); - responses.push_back( - DocumentMessage::SetSelectedLayers { - replacement_selected_layers: vec![path.clone()], - } - .into(), - ); - - let mouse = mouse.map_or(ipp.viewport_bounds.center(), |pos| pos.into()); - let transform = DAffine2::from_translation(mouse - ipp.viewport_bounds.top_left).to_cols_array(); - responses.push_back(DocumentOperation::SetLayerTransformInViewport { path, transform }.into()); - } - Redo => { - responses.push_back(SelectToolMessage::Abort.into()); - responses.push_back(DocumentHistoryForward.into()); - responses.push_back(BroadcastSignal::DocumentIsDirty.into()); - responses.push_back(RenderDocument.into()); - responses.push_back(FolderChanged { affected_folder_path: vec![] }.into()); - } - RenameLayer { layer_path, new_name } => responses.push_back(DocumentOperation::RenameLayer { layer_path, new_name }.into()), - RenderDocument => { - let render_data = RenderData::new(self.view_mode, font_cache, Some(ipp.document_bounds()), false); - responses.push_back( - FrontendMessage::UpdateDocumentArtwork { - svg: self.graphene_document.render_root(render_data), - } - .into(), - ); - responses.push_back(ArtboardMessage::RenderArtboards.into()); - - let document_transform_scale = self.movement_handler.snapped_scale(); - let scale = 0.5 + ASYMPTOTIC_EFFECT + document_transform_scale * SCALE_EFFECT; - let viewport_size = ipp.viewport_bounds.size(); - let viewport_mid = ipp.viewport_bounds.center(); - let [bounds1, bounds2] = self.document_bounds(font_cache).unwrap_or([viewport_mid; 2]); - let bounds1 = bounds1.min(viewport_mid) - viewport_size * scale; - let bounds2 = bounds2.max(viewport_mid) + viewport_size * scale; - let bounds_length = (bounds2 - bounds1) * (1. + SCROLLBAR_SPACING); - let scrollbar_position = DVec2::splat(0.5) - (bounds1.lerp(bounds2, 0.5) - viewport_mid) / (bounds_length - viewport_size); - let scrollbar_multiplier = bounds_length - viewport_size; - let scrollbar_size = viewport_size / bounds_length; - - let log = document_transform_scale.log2(); - let ruler_interval = if log < 0. { 100. * 2_f64.powf(-log.ceil()) } else { 100. / 2_f64.powf(log.ceil()) }; - let ruler_spacing = ruler_interval * document_transform_scale; - - let ruler_origin = self.graphene_document.root.transform.transform_point2(DVec2::ZERO); - - responses.push_back( - FrontendMessage::UpdateDocumentScrollbars { - position: scrollbar_position.into(), - size: scrollbar_size.into(), - multiplier: scrollbar_multiplier.into(), - } - .into(), - ); - - responses.push_back( - FrontendMessage::UpdateDocumentRulers { - origin: ruler_origin.into(), - spacing: ruler_spacing, - interval: ruler_interval, - } - .into(), - ); - } - RollbackTransaction => { - self.rollback(responses).unwrap_or_else(|e| log::warn!("{}", e)); - responses.extend([RenderDocument.into(), DocumentStructureChanged.into()]); - } - SaveDocument => { - self.set_save_state(true); - responses.push_back(PortfolioMessage::AutoSaveActiveDocument.into()); - // Update the save status of the just saved document - responses.push_back(PortfolioMessage::UpdateOpenDocumentsList.into()); - - let name = match self.name.ends_with(FILE_SAVE_SUFFIX) { - true => self.name.clone(), - false => self.name.clone() + FILE_SAVE_SUFFIX, - }; - responses.push_back( - FrontendMessage::TriggerFileDownload { - document: self.serialize_document(), - name, - } - .into(), - ) - } - SelectAllLayers => { - let all = self.all_layers().map(|path| path.to_vec()).collect(); - responses.push_front(SetSelectedLayers { replacement_selected_layers: all }.into()); - } - SelectedLayersLower => { - responses.push_front(DocumentMessage::SelectedLayersReorder { relative_index_offset: -1 }.into()); - } - SelectedLayersLowerToBack => { - responses.push_front(DocumentMessage::SelectedLayersReorder { relative_index_offset: isize::MIN }.into()); - } - SelectedLayersRaise => { - responses.push_front(DocumentMessage::SelectedLayersReorder { relative_index_offset: 1 }.into()); - } - SelectedLayersRaiseToFront => { - responses.push_front(DocumentMessage::SelectedLayersReorder { relative_index_offset: isize::MAX }.into()); - } - SelectedLayersReorder { relative_index_offset } => { - self.selected_layers_reorder(relative_index_offset, responses); - } - SelectLayer { layer_path, ctrl, shift } => { - let mut paths = vec![]; - let last_selection_exists = !self.layer_range_selection_reference.is_empty(); - - // If we have shift pressed and a layer already selected then fill the range - if shift && last_selection_exists { - // Fill the selection range - self.layer_metadata - .iter() - .filter(|(target, _)| self.graphene_document.layer_is_between(target, &layer_path, &self.layer_range_selection_reference)) - .for_each(|(layer_path, _)| { - paths.push(layer_path.clone()); - }); - } else { - if ctrl { - // Toggle selection when holding ctrl - let layer = self.layer_metadata_mut(&layer_path); - layer.selected = !layer.selected; - responses.push_back( - LayerChanged { - affected_layer_path: layer_path.clone(), - } - .into(), - ); - responses.push_back(BroadcastSignal::SelectionChanged.into()); - } else { - paths.push(layer_path.clone()); - } - - // Set our last selection reference - self.layer_range_selection_reference = layer_path; - } - - // Don't create messages for empty operations - if !paths.is_empty() { - // Add or set our selected layers - if ctrl { - responses.push_front(AddSelectedLayers { additional_layers: paths }.into()); - } else { - responses.push_front(SetSelectedLayers { replacement_selected_layers: paths }.into()); - } - } - } - SetBlendModeForSelectedLayers { blend_mode } => { - self.backup(responses); - for path in self.layer_metadata.iter().filter_map(|(path, data)| data.selected.then(|| path.clone())) { - responses.push_back(DocumentOperation::SetLayerBlendMode { path, blend_mode }.into()); - } - } - SetLayerExpansion { layer_path, set_expanded } => { - self.layer_metadata_mut(&layer_path).expanded = set_expanded; - responses.push_back(DocumentStructureChanged.into()); - responses.push_back(LayerChanged { affected_layer_path: layer_path }.into()) - } - SetLayerName { layer_path, name } => { - if let Some(layer) = self.layer_panel_entry_from_path(&layer_path, font_cache) { - // Only save the history state if the name actually changed to something different - if layer.name != name { - self.backup(responses); - responses.push_back(DocumentOperation::SetLayerName { path: layer_path, name }.into()); - } - } - } - SetOpacityForSelectedLayers { opacity } => { - self.backup(responses); - let opacity = opacity.clamp(0., 1.); - - for path in self.selected_layers().map(|path| path.to_vec()) { - responses.push_back(DocumentOperation::SetLayerOpacity { path, opacity }.into()); - } - } - SetOverlaysVisibility { visible } => { - self.overlays_visible = visible; - responses.push_back(OverlaysMessage::Rerender.into()); - } - SetSelectedLayers { replacement_selected_layers } => { - let selected = self.layer_metadata.iter_mut().filter(|(_, layer_metadata)| layer_metadata.selected); - selected.for_each(|(path, layer_metadata)| { - layer_metadata.selected = false; - responses.push_back(LayerChanged { affected_layer_path: path.clone() }.into()) - }); - - let additional_layers = replacement_selected_layers; - responses.push_front(AddSelectedLayers { additional_layers }.into()); - } - SetSnapping { snap } => { - self.snapping_enabled = snap; - } - SetTexboxEditability { path, editable } => { - let text = self.graphene_document.layer(&path).unwrap().as_text().unwrap(); - responses.push_back(DocumentOperation::SetTextEditability { path, editable }.into()); - if editable { - let color = if let Fill::Solid(solid_color) = text.path_style.fill() { *solid_color } else { Color::BLACK }; - responses.push_back( - FrontendMessage::DisplayEditableTextbox { - text: text.text.clone(), - line_width: text.line_width, - font_size: text.size, - color, - } - .into(), - ); - } else { - responses.push_back(FrontendMessage::DisplayRemoveEditableTextbox.into()); - } - } - SetViewMode { view_mode } => { - self.view_mode = view_mode; - responses.push_front(DocumentMessage::DirtyRenderDocument.into()); - } - StartTransaction => self.backup(responses), - ToggleLayerExpansion { layer_path } => { - self.layer_metadata_mut(&layer_path).expanded ^= true; - responses.push_back(DocumentStructureChanged.into()); - responses.push_back(LayerChanged { affected_layer_path: layer_path }.into()) - } - ToggleLayerVisibility { layer_path } => { - responses.push_back(DocumentOperation::ToggleLayerVisibility { path: layer_path }.into()); - responses.push_back(BroadcastSignal::DocumentIsDirty.into()); - } - ToggleSelectedHandleMirroring { - layer_path, - toggle_distance, - toggle_angle, - } => { - responses.push_back( - DocumentOperation::SetSelectedHandleMirroring { - layer_path, - toggle_distance, - toggle_angle, - } - .into(), - ); - } - Undo => { - responses.push_back(BroadcastSignal::ToolAbort.into()); - responses.push_back(DocumentHistoryBackward.into()); - responses.push_back(BroadcastSignal::DocumentIsDirty.into()); - responses.push_back(RenderDocument.into()); - responses.push_back(FolderChanged { affected_folder_path: vec![] }.into()); - } - UngroupLayers { folder_path } => { - // Select all the children of the folder - let select = self.graphene_document.folder_children_paths(&folder_path); - - let message_buffer = [ - // Select them - DocumentMessage::SetSelectedLayers { replacement_selected_layers: select }.into(), - // Copy them - PortfolioMessage::Copy { clipboard: Clipboard::Internal }.into(), - // Paste them into the folder above - PortfolioMessage::PasteIntoFolder { - clipboard: Clipboard::Internal, - folder_path: folder_path[..folder_path.len() - 1].to_vec(), - insert_index: -1, - } - .into(), - // Delete the parent folder - DocumentMessage::DeleteLayer { layer_path: folder_path }.into(), - ]; - - // Push these messages in reverse due to push_front - for message in message_buffer.into_iter().rev() { - responses.push_front(message); - } - } - UngroupSelectedLayers => { - responses.push_back(DocumentMessage::StartTransaction.into()); - let folder_paths = self.graphene_document.sorted_folders_by_depth(self.selected_layers()); - for folder_path in folder_paths { - responses.push_back(DocumentMessage::UngroupLayers { folder_path: folder_path.to_vec() }.into()); - } - responses.push_back(DocumentMessage::CommitTransaction.into()); - } - UpdateLayerMetadata { layer_path, layer_metadata } => { - self.layer_metadata.insert(layer_path, layer_metadata); - } - ZoomCanvasToFitAll => { - if let Some(bounds) = self.document_bounds(font_cache) { - responses.push_back( - MovementMessage::FitViewportToBounds { - bounds, - padding_scale_factor: Some(VIEWPORT_ZOOM_TO_FIT_PADDING_SCALE_FACTOR), - prevent_zoom_past_100: true, - } - .into(), - ) - } - } - } - } - - fn actions(&self) -> ActionList { - let mut common = actions!(DocumentMessageDiscriminant; - Undo, - Redo, - SelectAllLayers, - DeselectAllLayers, - RenderDocument, - ExportDocument, - SaveDocument, - SetSnapping, - DebugPrintDocument, - ZoomCanvasToFitAll, - CreateEmptyFolder, - ); - - if self.layer_metadata.values().any(|data| data.selected) { - let select = actions!(DocumentMessageDiscriminant; - DeleteSelectedLayers, - DuplicateSelectedLayers, - NudgeSelectedLayers, - SelectedLayersLower, - SelectedLayersLowerToBack, - SelectedLayersRaise, - SelectedLayersRaiseToFront, - GroupSelectedLayers, - UngroupSelectedLayers, - ); - common.extend(select); - } - common.extend(self.movement_handler.actions()); - common.extend(self.transform_layer_handler.actions()); - common - } -} diff --git a/editor/src/messages/portfolio/document/mod.rs b/editor/src/messages/portfolio/document/mod.rs new file mode 100644 index 0000000000..867c4f6951 --- /dev/null +++ b/editor/src/messages/portfolio/document/mod.rs @@ -0,0 +1,14 @@ +mod document_message; +mod document_message_handler; + +pub mod artboard; +pub mod movement; +pub mod overlays; +pub mod properties_panel; +pub mod transform_layer; +pub mod utility_types; + +#[doc(inline)] +pub use document_message::{DocumentMessage, DocumentMessageDiscriminant}; +#[doc(inline)] +pub use document_message_handler::DocumentMessageHandler; diff --git a/editor/src/messages/portfolio/document/movement/mod.rs b/editor/src/messages/portfolio/document/movement/mod.rs new file mode 100644 index 0000000000..9dd00ababf --- /dev/null +++ b/editor/src/messages/portfolio/document/movement/mod.rs @@ -0,0 +1,7 @@ +mod movement_message; +mod movement_message_handler; + +#[doc(inline)] +pub use movement_message::{MovementMessage, MovementMessageDiscriminant}; +#[doc(inline)] +pub use movement_message_handler::MovementMessageHandler; diff --git a/editor/src/document/movement_message.rs b/editor/src/messages/portfolio/document/movement/movement_message.rs similarity index 88% rename from editor/src/document/movement_message.rs rename to editor/src/messages/portfolio/document/movement/movement_message.rs index 8c49c335c0..332dcd46d5 100644 --- a/editor/src/document/movement_message.rs +++ b/editor/src/messages/portfolio/document/movement/movement_message.rs @@ -1,5 +1,5 @@ -use crate::input::keyboard::Key; -use crate::message_prelude::*; +use crate::messages::input_mapper::utility_types::input_keyboard::Key; +use crate::messages::prelude::*; use glam::DVec2; use serde::{Deserialize, Serialize}; @@ -8,6 +8,7 @@ use serde::{Deserialize, Serialize}; #[impl_message(Message, DocumentMessage, Movement)] #[derive(PartialEq, Clone, Debug, Serialize, Deserialize)] pub enum MovementMessage { + // Messages DecreaseCanvasZoom { center_on_mouse: bool, }, diff --git a/editor/src/document/movement_message_handler.rs b/editor/src/messages/portfolio/document/movement/movement_message_handler.rs similarity index 92% rename from editor/src/document/movement_message_handler.rs rename to editor/src/messages/portfolio/document/movement/movement_message_handler.rs index 8d8f7ef83a..a5e615047e 100644 --- a/editor/src/document/movement_message_handler.rs +++ b/editor/src/messages/portfolio/document/movement/movement_message_handler.rs @@ -1,10 +1,9 @@ use crate::consts::{VIEWPORT_ROTATE_SNAP_INTERVAL, VIEWPORT_SCROLL_RATE, VIEWPORT_ZOOM_LEVELS, VIEWPORT_ZOOM_MOUSE_RATE, VIEWPORT_ZOOM_SCALE_MAX, VIEWPORT_ZOOM_SCALE_MIN, VIEWPORT_ZOOM_WHEEL_RATE}; -use crate::frontend::utility_types::MouseCursorIcon; -use crate::input::keyboard::Key; -use crate::input::mouse::{ViewportBounds, ViewportPosition}; -use crate::input::InputPreprocessorMessageHandler; -use crate::message_prelude::*; -use crate::misc::{HintData, HintGroup, HintInfo, KeysGroup}; +use crate::messages::frontend::utility_types::MouseCursorIcon; +use crate::messages::input_mapper::utility_types::input_keyboard::{Key, KeysGroup}; +use crate::messages::input_mapper::utility_types::input_mouse::{ViewportBounds, ViewportPosition}; +use crate::messages::prelude::*; +use crate::messages::tool::utility_types::{HintData, HintGroup, HintInfo}; use graphene::document::Document; use graphene::Operation as DocumentOperation; @@ -50,72 +49,9 @@ impl Default for MovementMessageHandler { } } -impl MovementMessageHandler { - pub fn snapped_angle(&self) -> f64 { - let increment_radians: f64 = VIEWPORT_ROTATE_SNAP_INTERVAL.to_radians(); - if self.snap_tilt { - (self.tilt / increment_radians).round() * increment_radians - } else { - self.tilt - } - } - - pub fn snapped_scale(&self) -> f64 { - if self.snap_zoom { - *VIEWPORT_ZOOM_LEVELS - .iter() - .min_by(|a, b| (**a - self.zoom).abs().partial_cmp(&(**b - self.zoom).abs()).unwrap()) - .unwrap_or(&self.zoom) - } else { - self.zoom - } - } - - pub fn calculate_offset_transform(&self, offset: DVec2) -> DAffine2 { - // TODO: replace with DAffine2::from_scale_angle_translation and fix the errors - let offset_transform = DAffine2::from_translation(offset); - let scale_transform = DAffine2::from_scale(DVec2::splat(self.snapped_scale())); - let angle_transform = DAffine2::from_angle(self.snapped_angle()); - let translation_transform = DAffine2::from_translation(self.pan); - scale_transform * offset_transform * angle_transform * translation_transform - } - - fn create_document_transform(&self, viewport_bounds: &ViewportBounds, responses: &mut VecDeque) { - let half_viewport = viewport_bounds.size() / 2.; - let scaled_half_viewport = half_viewport / self.snapped_scale(); - responses.push_back( - DocumentOperation::SetLayerTransform { - path: vec![], - transform: self.calculate_offset_transform(scaled_half_viewport).to_cols_array(), - } - .into(), - ); - - responses.push_back( - ArtboardMessage::DispatchOperation( - DocumentOperation::SetLayerTransform { - path: vec![], - transform: self.calculate_offset_transform(scaled_half_viewport).to_cols_array(), - } - .into(), - ) - .into(), - ); - } - - pub fn center_zoom(&self, viewport_bounds: DVec2, zoom_factor: f64, mouse: DVec2) -> Message { - let new_viewport_bounds = viewport_bounds / zoom_factor; - let delta_size = viewport_bounds - new_viewport_bounds; - let mouse_fraction = mouse / viewport_bounds; - let delta = delta_size * (DVec2::splat(0.5) - mouse_fraction); - - MovementMessage::TranslateCanvas { delta }.into() - } -} - impl MessageHandler for MovementMessageHandler { #[remain::check] - fn process_action(&mut self, message: MovementMessage, data: (&Document, &InputPreprocessorMessageHandler), responses: &mut VecDeque) { + fn process_message(&mut self, message: MovementMessage, data: (&Document, &InputPreprocessorMessageHandler), responses: &mut VecDeque) { use MovementMessage::*; let (document, ipp) = data; @@ -153,7 +89,7 @@ impl MessageHandler { self.tilt = angle_radians; self.create_document_transform(&ipp.viewport_bounds, responses); - responses.push_back(BroadcastSignal::DocumentIsDirty.into()); + responses.push_back(BroadcastEvent::DocumentIsDirty.into()); responses.push_back(PortfolioMessage::UpdateDocumentWidgets.into()); } SetCanvasZoom { zoom_factor } => { self.zoom = zoom_factor.clamp(VIEWPORT_ZOOM_SCALE_MIN, VIEWPORT_ZOOM_SCALE_MAX); - responses.push_back(BroadcastSignal::DocumentIsDirty.into()); + responses.push_back(BroadcastEvent::DocumentIsDirty.into()); responses.push_back(DocumentMessage::DirtyRenderDocumentInOutlineView.into()); responses.push_back(PortfolioMessage::UpdateDocumentWidgets.into()); self.create_document_transform(&ipp.viewport_bounds, responses); @@ -258,7 +194,7 @@ impl MessageHandler { self.tilt = self.snapped_angle(); self.zoom = self.snapped_scale(); - responses.push_back(BroadcastSignal::DocumentIsDirty.into()); + responses.push_back(BroadcastEvent::DocumentIsDirty.into()); responses.push_back(ToolMessage::UpdateCursor.into()); responses.push_back(ToolMessage::UpdateHints.into()); self.snap_tilt = false; @@ -272,7 +208,7 @@ impl MessageHandler { @@ -286,7 +222,7 @@ impl MessageHandler { @@ -352,3 +288,66 @@ impl MessageHandler f64 { + let increment_radians: f64 = VIEWPORT_ROTATE_SNAP_INTERVAL.to_radians(); + if self.snap_tilt { + (self.tilt / increment_radians).round() * increment_radians + } else { + self.tilt + } + } + + pub fn snapped_scale(&self) -> f64 { + if self.snap_zoom { + *VIEWPORT_ZOOM_LEVELS + .iter() + .min_by(|a, b| (**a - self.zoom).abs().partial_cmp(&(**b - self.zoom).abs()).unwrap()) + .unwrap_or(&self.zoom) + } else { + self.zoom + } + } + + pub fn calculate_offset_transform(&self, offset: DVec2) -> DAffine2 { + // TODO: replace with DAffine2::from_scale_angle_translation and fix the errors + let offset_transform = DAffine2::from_translation(offset); + let scale_transform = DAffine2::from_scale(DVec2::splat(self.snapped_scale())); + let angle_transform = DAffine2::from_angle(self.snapped_angle()); + let translation_transform = DAffine2::from_translation(self.pan); + scale_transform * offset_transform * angle_transform * translation_transform + } + + fn create_document_transform(&self, viewport_bounds: &ViewportBounds, responses: &mut VecDeque) { + let half_viewport = viewport_bounds.size() / 2.; + let scaled_half_viewport = half_viewport / self.snapped_scale(); + responses.push_back( + DocumentOperation::SetLayerTransform { + path: vec![], + transform: self.calculate_offset_transform(scaled_half_viewport).to_cols_array(), + } + .into(), + ); + + responses.push_back( + ArtboardMessage::DispatchOperation( + DocumentOperation::SetLayerTransform { + path: vec![], + transform: self.calculate_offset_transform(scaled_half_viewport).to_cols_array(), + } + .into(), + ) + .into(), + ); + } + + pub fn center_zoom(&self, viewport_bounds: DVec2, zoom_factor: f64, mouse: DVec2) -> Message { + let new_viewport_bounds = viewport_bounds / zoom_factor; + let delta_size = viewport_bounds - new_viewport_bounds; + let mouse_fraction = mouse / viewport_bounds; + let delta = delta_size * (DVec2::splat(0.5) - mouse_fraction); + + MovementMessage::TranslateCanvas { delta }.into() + } +} diff --git a/editor/src/messages/portfolio/document/overlays/mod.rs b/editor/src/messages/portfolio/document/overlays/mod.rs new file mode 100644 index 0000000000..73c7a59bcb --- /dev/null +++ b/editor/src/messages/portfolio/document/overlays/mod.rs @@ -0,0 +1,7 @@ +mod overlays_message; +mod overlays_message_handler; + +#[doc(inline)] +pub use overlays_message::{OverlaysMessage, OverlaysMessageDiscriminant}; +#[doc(inline)] +pub use overlays_message_handler::OverlaysMessageHandler; diff --git a/editor/src/document/overlays_message.rs b/editor/src/messages/portfolio/document/overlays/overlays_message.rs similarity index 94% rename from editor/src/document/overlays_message.rs rename to editor/src/messages/portfolio/document/overlays/overlays_message.rs index 79354cc8eb..5ce55a567b 100644 --- a/editor/src/document/overlays_message.rs +++ b/editor/src/messages/portfolio/document/overlays/overlays_message.rs @@ -1,4 +1,4 @@ -use crate::message_prelude::*; +use crate::messages::prelude::*; use graphene::Operation as DocumentOperation; diff --git a/editor/src/document/overlays_message_handler.rs b/editor/src/messages/portfolio/document/overlays/overlays_message_handler.rs similarity index 83% rename from editor/src/document/overlays_message_handler.rs rename to editor/src/messages/portfolio/document/overlays/overlays_message_handler.rs index a6f12fd406..f38c132940 100644 --- a/editor/src/document/overlays_message_handler.rs +++ b/editor/src/messages/portfolio/document/overlays/overlays_message_handler.rs @@ -1,5 +1,4 @@ -use crate::input::InputPreprocessorMessageHandler; -use crate::message_prelude::*; +use crate::messages::prelude::*; use graphene::document::Document as GrapheneDocument; use graphene::layers::style::{RenderData, ViewMode}; @@ -12,7 +11,7 @@ pub struct OverlaysMessageHandler { impl MessageHandler for OverlaysMessageHandler { #[remain::check] - fn process_action(&mut self, message: OverlaysMessage, (overlays_visible, font_cache, ipp): (bool, &FontCache, &InputPreprocessorMessageHandler), responses: &mut VecDeque) { + fn process_message(&mut self, message: OverlaysMessage, (overlays_visible, font_cache, ipp): (bool, &FontCache, &InputPreprocessorMessageHandler), responses: &mut VecDeque) { use OverlaysMessage::*; #[remain::sorted] diff --git a/editor/src/messages/portfolio/document/properties_panel/mod.rs b/editor/src/messages/portfolio/document/properties_panel/mod.rs new file mode 100644 index 0000000000..e984589242 --- /dev/null +++ b/editor/src/messages/portfolio/document/properties_panel/mod.rs @@ -0,0 +1,10 @@ +mod properties_panel_message; +mod properties_panel_message_handler; + +pub mod utility_functions; +pub mod utility_types; + +#[doc(inline)] +pub use properties_panel_message::{PropertiesPanelMessage, PropertiesPanelMessageDiscriminant}; +#[doc(inline)] +pub use properties_panel_message_handler::PropertiesPanelMessageHandler; diff --git a/editor/src/document/properties_panel_message.rs b/editor/src/messages/portfolio/document/properties_panel/properties_panel_message.rs similarity index 77% rename from editor/src/document/properties_panel_message.rs rename to editor/src/messages/portfolio/document/properties_panel/properties_panel_message.rs index 9748478acf..210f1503aa 100644 --- a/editor/src/document/properties_panel_message.rs +++ b/editor/src/messages/portfolio/document/properties_panel/properties_panel_message.rs @@ -1,14 +1,17 @@ -use crate::message_prelude::*; - -use super::utility_types::TargetDocument; +use super::utility_types::TransformOp; +use crate::messages::portfolio::document::utility_types::misc::TargetDocument; +use crate::messages::prelude::*; use graphene::layers::style::{Fill, Stroke}; +use graphene::LayerId; + use serde::{Deserialize, Serialize}; #[remain::sorted] #[impl_message(Message, DocumentMessage, PropertiesPanel)] #[derive(PartialEq, Clone, Debug, Serialize, Deserialize)] pub enum PropertiesPanelMessage { + // Messages CheckSelectedWasDeleted { path: Vec }, CheckSelectedWasUpdated { path: Vec }, ClearSelection, @@ -24,14 +27,3 @@ pub enum PropertiesPanelMessage { SetActiveLayers { paths: Vec>, document: TargetDocument }, UpdateSelectedDocumentProperties, } - -#[derive(PartialEq, Eq, Clone, Copy, Debug, Serialize, Deserialize)] -pub enum TransformOp { - X, - Y, - ScaleX, - ScaleY, - Width, - Height, - Rotation, -} 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 new file mode 100644 index 0000000000..d40bd269ac --- /dev/null +++ b/editor/src/messages/portfolio/document/properties_panel/properties_panel_message_handler.rs @@ -0,0 +1,166 @@ +use super::utility_functions::{register_artboard_layer_properties, register_artwork_layer_properties}; +use super::utility_types::PropertiesPanelMessageHandlerData; +use crate::messages::layout::utility_types::layout_widget::{Layout, WidgetLayout}; +use crate::messages::layout::utility_types::misc::LayoutTarget; +use crate::messages::portfolio::document::properties_panel::utility_functions::apply_transform_operation; +use crate::messages::portfolio::document::utility_types::misc::TargetDocument; +use crate::messages::prelude::*; + +use graphene::{LayerId, Operation}; + +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize, Default)] +pub struct PropertiesPanelMessageHandler { + active_selection: Option<(Vec, TargetDocument)>, +} + +impl<'a> MessageHandler> for PropertiesPanelMessageHandler { + #[remain::check] + fn process_message(&mut self, message: PropertiesPanelMessage, data: PropertiesPanelMessageHandlerData, responses: &mut VecDeque) { + let PropertiesPanelMessageHandlerData { + artwork_document, + artboard_document, + selected_layers, + font_cache, + } = data; + let get_document = |document_selector: TargetDocument| match document_selector { + TargetDocument::Artboard => artboard_document, + TargetDocument::Artwork => artwork_document, + }; + use PropertiesPanelMessage::*; + match message { + SetActiveLayers { paths, document } => { + if paths.len() != 1 { + // TODO: Allow for multiple selected layers + responses.push_back(PropertiesPanelMessage::ClearSelection.into()) + } else { + let path = paths.into_iter().next().unwrap(); + self.active_selection = Some((path, document)); + responses.push_back(PropertiesPanelMessage::ResendActiveProperties.into()) + } + } + ClearSelection => { + responses.push_back( + LayoutMessage::SendLayout { + layout: Layout::WidgetLayout(WidgetLayout::new(vec![])), + layout_target: LayoutTarget::PropertiesOptions, + } + .into(), + ); + responses.push_back( + LayoutMessage::SendLayout { + layout: Layout::WidgetLayout(WidgetLayout::new(vec![])), + layout_target: LayoutTarget::PropertiesSections, + } + .into(), + ); + self.active_selection = None; + } + Deactivate => responses.push_back( + BroadcastMessage::UnsubscribeEvent { + on: BroadcastEvent::SelectionChanged, + message: Box::new(PropertiesPanelMessage::UpdateSelectedDocumentProperties.into()), + } + .into(), + ), + Init => responses.push_back( + BroadcastMessage::SubscribeEvent { + on: BroadcastEvent::SelectionChanged, + send: Box::new(PropertiesPanelMessage::UpdateSelectedDocumentProperties.into()), + } + .into(), + ), + ModifyFont { font_family, font_style, size } => { + let (path, _) = self.active_selection.clone().expect("Received update for properties panel with no active layer"); + + responses.push_back(self.create_document_operation(Operation::ModifyFont { path, font_family, font_style, size })); + responses.push_back(ResendActiveProperties.into()); + } + ModifyTransform { value, transform_op } => { + let (path, target_document) = self.active_selection.as_ref().expect("Received update for properties panel with no active layer"); + let layer = get_document(*target_document).layer(path).unwrap(); + + let transform = apply_transform_operation(layer, transform_op, value, font_cache); + + responses.push_back(self.create_document_operation(Operation::SetLayerTransform { path: path.clone(), transform })); + } + ModifyName { name } => { + let (path, _) = self.active_selection.clone().expect("Received update for properties panel with no active layer"); + responses.push_back(self.create_document_operation(Operation::SetLayerName { path, name })) + } + ModifyFill { fill } => { + let (path, _) = self.active_selection.clone().expect("Received update for properties panel with no active layer"); + responses.push_back(self.create_document_operation(Operation::SetLayerFill { path, fill })); + } + ModifyStroke { stroke } => { + let (path, _) = self.active_selection.clone().expect("Received update for properties panel with no active layer"); + responses.push_back(self.create_document_operation(Operation::SetLayerStroke { path, stroke })) + } + ModifyText { new_text } => { + let (path, _) = self.active_selection.clone().expect("Received update for properties panel with no active layer"); + responses.push_back(Operation::SetTextContent { path, new_text }.into()) + } + CheckSelectedWasUpdated { path } => { + if self.matches_selected(&path) { + responses.push_back(PropertiesPanelMessage::ResendActiveProperties.into()) + } + } + CheckSelectedWasDeleted { path } => { + if self.matches_selected(&path) { + self.active_selection = None; + responses.push_back( + LayoutMessage::SendLayout { + layout_target: LayoutTarget::PropertiesOptions, + layout: Layout::WidgetLayout(WidgetLayout::default()), + } + .into(), + ); + responses.push_back( + LayoutMessage::SendLayout { + layout_target: LayoutTarget::PropertiesSections, + layout: Layout::WidgetLayout(WidgetLayout::default()), + } + .into(), + ); + } + } + ResendActiveProperties => { + if let Some((path, target_document)) = self.active_selection.clone() { + let layer = get_document(target_document).layer(&path).unwrap(); + match target_document { + TargetDocument::Artboard => register_artboard_layer_properties(layer, responses, font_cache), + TargetDocument::Artwork => register_artwork_layer_properties(layer, responses, font_cache), + } + } + } + UpdateSelectedDocumentProperties => responses.push_back( + PropertiesPanelMessage::SetActiveLayers { + paths: selected_layers.map(|path| path.to_vec()).collect(), + document: TargetDocument::Artwork, + } + .into(), + ), + } + } + + fn actions(&self) -> ActionList { + actions!(PropertiesMessageDiscriminant;) + } +} + +impl PropertiesPanelMessageHandler { + fn matches_selected(&self, path: &[LayerId]) -> bool { + let last_active_path_id = self.active_selection.as_ref().and_then(|(v, _)| v.last().copied()); + let last_modified = path.last().copied(); + matches!((last_active_path_id, last_modified), (Some(active_last), Some(modified_last)) if active_last == modified_last) + } + + fn create_document_operation(&self, operation: Operation) -> Message { + let (_, target_document) = self.active_selection.as_ref().unwrap(); + match *target_document { + TargetDocument::Artboard => ArtboardMessage::DispatchOperation(Box::new(operation)).into(), + TargetDocument::Artwork => DocumentMessage::DispatchOperation(Box::new(operation)).into(), + } + } +} diff --git a/editor/src/document/properties_panel_message_handler.rs b/editor/src/messages/portfolio/document/properties_panel/utility_functions.rs similarity index 80% rename from editor/src/document/properties_panel_message_handler.rs rename to editor/src/messages/portfolio/document/properties_panel/utility_functions.rs index aac4016522..7e508fca7b 100644 --- a/editor/src/document/properties_panel_message_handler.rs +++ b/editor/src/messages/portfolio/document/properties_panel/utility_functions.rs @@ -1,270 +1,39 @@ -use super::utility_types::TargetDocument; -use crate::document::properties_panel_message::TransformOp; -use crate::layout::layout_message::LayoutTarget; -use crate::layout::widgets::{ - ColorInput, FontInput, IconLabel, IconStyle, Layout, LayoutGroup, NumberInput, PopoverButton, RadioEntryData, RadioInput, Separator, SeparatorDirection, SeparatorType, TextAreaInput, TextInput, - TextLabel, Widget, WidgetCallback, WidgetHolder, WidgetLayout, -}; -use crate::message_prelude::*; +use super::utility_types::TransformOp; +use crate::messages::layout::utility_types::layout_widget::{Layout, LayoutGroup, Widget, WidgetCallback, WidgetHolder, WidgetLayout}; +use crate::messages::layout::utility_types::misc::LayoutTarget; +use crate::messages::layout::utility_types::widgets::button_widgets::PopoverButton; +use crate::messages::layout::utility_types::widgets::input_widgets::{ColorInput, FontInput, NumberInput, RadioEntryData, RadioInput, TextAreaInput, TextInput}; +use crate::messages::layout::utility_types::widgets::label_widgets::{IconLabel, IconStyle, Separator, SeparatorDirection, SeparatorType, TextLabel}; +use crate::messages::prelude::*; +use glam::{DAffine2, DVec2}; use graphene::color::Color; -use graphene::document::Document as GrapheneDocument; use graphene::layers::layer_info::{Layer, LayerDataType, LayerDataTypeDiscriminant}; use graphene::layers::style::{Fill, Gradient, GradientType, LineCap, LineJoin, Stroke}; use graphene::layers::text_layer::{FontCache, TextLayer}; -use graphene::{LayerId, Operation}; -use glam::{DAffine2, DVec2}; -use serde::{Deserialize, Serialize}; use std::f64::consts::PI; use std::rc::Rc; -trait DAffine2Utils { - fn scale_x(&self) -> f64; - fn update_scale_x(self, new_width: f64) -> Self; - fn scale_y(&self) -> f64; - fn update_scale_y(self, new_height: f64) -> Self; - fn x(&self) -> f64; - fn update_x(self, new_x: f64) -> Self; - fn y(&self) -> f64; - fn update_y(self, new_y: f64) -> Self; - fn rotation(&self) -> f64; - fn update_rotation(self, new_rotation: f64) -> Self; +pub fn apply_transform_operation(layer: &Layer, transform_op: TransformOp, value: f64, font_cache: &FontCache) -> [f64; 6] { + let transformation = match transform_op { + TransformOp::X => DAffine2::update_x, + TransformOp::Y => DAffine2::update_y, + TransformOp::ScaleX | TransformOp::Width => DAffine2::update_scale_x, + TransformOp::ScaleY | TransformOp::Height => DAffine2::update_scale_y, + TransformOp::Rotation => DAffine2::update_rotation, + }; + + let scale = match transform_op { + TransformOp::Width => layer.bounding_transform(font_cache).scale_x() / layer.transform.scale_x(), + TransformOp::Height => layer.bounding_transform(font_cache).scale_y() / layer.transform.scale_y(), + _ => 1., + }; + + transformation(layer.transform, value / scale).to_cols_array() } -impl DAffine2Utils for DAffine2 { - fn scale_x(&self) -> f64 { - self.transform_vector2((1., 0.).into()).length() - } - - fn update_scale_x(self, new_width: f64) -> Self { - self * DAffine2::from_scale((new_width / self.scale_x(), 1.).into()) - } - - fn scale_y(&self) -> f64 { - self.transform_vector2((0., 1.).into()).length() - } - - fn update_scale_y(self, new_height: f64) -> Self { - self * DAffine2::from_scale((1., new_height / self.scale_y()).into()) - } - - fn x(&self) -> f64 { - self.translation.x - } - - fn update_x(mut self, new_x: f64) -> Self { - self.translation.x = new_x; - self - } - - fn y(&self) -> f64 { - self.translation.y - } - - fn update_y(mut self, new_y: f64) -> Self { - self.translation.y = new_y; - self - } - - fn rotation(&self) -> f64 { - let cos = self.matrix2.col(0).x / self.scale_x(); - let sin = self.matrix2.col(0).y / self.scale_x(); - sin.atan2(cos) - } - - fn update_rotation(self, new_rotation: f64) -> Self { - let width = self.scale_x(); - let height = self.scale_y(); - let half_width = width / 2.; - let half_height = height / 2.; - - let angle_translation_offset = |angle: f64| DVec2::new(-half_width * angle.cos() + half_height * angle.sin(), -half_width * angle.sin() - half_height * angle.cos()); - let angle_translation_adjustment = angle_translation_offset(new_rotation) - angle_translation_offset(self.rotation()); - - DAffine2::from_scale_angle_translation((width, height).into(), new_rotation, self.translation + angle_translation_adjustment) - } -} - -#[derive(Debug, Clone, Serialize, Deserialize, Default)] -pub struct PropertiesPanelMessageHandler { - active_selection: Option<(Vec, TargetDocument)>, -} - -impl PropertiesPanelMessageHandler { - fn matches_selected(&self, path: &[LayerId]) -> bool { - let last_active_path_id = self.active_selection.as_ref().and_then(|(v, _)| v.last().copied()); - let last_modified = path.last().copied(); - matches!((last_active_path_id, last_modified), (Some(active_last), Some(modified_last)) if active_last == modified_last) - } - - fn create_document_operation(&self, operation: Operation) -> Message { - let (_, target_document) = self.active_selection.as_ref().unwrap(); - match *target_document { - TargetDocument::Artboard => ArtboardMessage::DispatchOperation(Box::new(operation)).into(), - TargetDocument::Artwork => DocumentMessage::DispatchOperation(Box::new(operation)).into(), - } - } -} - -pub struct PropertiesPanelMessageHandlerData<'a> { - pub artwork_document: &'a GrapheneDocument, - pub artboard_document: &'a GrapheneDocument, - pub selected_layers: &'a mut dyn Iterator, - pub font_cache: &'a FontCache, -} - -impl<'a> MessageHandler> for PropertiesPanelMessageHandler { - #[remain::check] - fn process_action(&mut self, message: PropertiesPanelMessage, data: PropertiesPanelMessageHandlerData, responses: &mut VecDeque) { - let PropertiesPanelMessageHandlerData { - artwork_document, - artboard_document, - selected_layers, - font_cache, - } = data; - let get_document = |document_selector: TargetDocument| match document_selector { - TargetDocument::Artboard => artboard_document, - TargetDocument::Artwork => artwork_document, - }; - use PropertiesPanelMessage::*; - match message { - SetActiveLayers { paths, document } => { - if paths.len() != 1 { - // TODO: Allow for multiple selected layers - responses.push_back(PropertiesPanelMessage::ClearSelection.into()) - } else { - let path = paths.into_iter().next().unwrap(); - self.active_selection = Some((path, document)); - responses.push_back(PropertiesPanelMessage::ResendActiveProperties.into()) - } - } - ClearSelection => { - responses.push_back( - LayoutMessage::SendLayout { - layout: Layout::WidgetLayout(WidgetLayout::new(vec![])), - layout_target: LayoutTarget::PropertiesOptions, - } - .into(), - ); - responses.push_back( - LayoutMessage::SendLayout { - layout: Layout::WidgetLayout(WidgetLayout::new(vec![])), - layout_target: LayoutTarget::PropertiesSections, - } - .into(), - ); - self.active_selection = None; - } - Deactivate => responses.push_back( - BroadcastMessage::UnsubscribeSignal { - on: BroadcastSignal::SelectionChanged, - message: Box::new(PropertiesPanelMessage::UpdateSelectedDocumentProperties.into()), - } - .into(), - ), - Init => responses.push_back( - BroadcastMessage::SubscribeSignal { - on: BroadcastSignal::SelectionChanged, - send: Box::new(PropertiesPanelMessage::UpdateSelectedDocumentProperties.into()), - } - .into(), - ), - ModifyFont { font_family, font_style, size } => { - let (path, _) = self.active_selection.clone().expect("Received update for properties panel with no active layer"); - - responses.push_back(self.create_document_operation(Operation::ModifyFont { path, font_family, font_style, size })); - responses.push_back(ResendActiveProperties.into()); - } - ModifyTransform { value, transform_op } => { - let (path, target_document) = self.active_selection.as_ref().expect("Received update for properties panel with no active layer"); - let layer = get_document(*target_document).layer(path).unwrap(); - - use TransformOp::*; - let action = match transform_op { - X => DAffine2::update_x, - Y => DAffine2::update_y, - ScaleX | Width => DAffine2::update_scale_x, - ScaleY | Height => DAffine2::update_scale_y, - Rotation => DAffine2::update_rotation, - }; - - let scale = match transform_op { - Width => layer.bounding_transform(font_cache).scale_x() / layer.transform.scale_x(), - Height => layer.bounding_transform(font_cache).scale_y() / layer.transform.scale_y(), - _ => 1., - }; - - responses.push_back(self.create_document_operation(Operation::SetLayerTransform { - path: path.clone(), - transform: action(layer.transform, value / scale).to_cols_array(), - })); - } - ModifyName { name } => { - let (path, _) = self.active_selection.clone().expect("Received update for properties panel with no active layer"); - responses.push_back(self.create_document_operation(Operation::SetLayerName { path, name })) - } - ModifyFill { fill } => { - let (path, _) = self.active_selection.clone().expect("Received update for properties panel with no active layer"); - responses.push_back(self.create_document_operation(Operation::SetLayerFill { path, fill })); - } - ModifyStroke { stroke } => { - let (path, _) = self.active_selection.clone().expect("Received update for properties panel with no active layer"); - responses.push_back(self.create_document_operation(Operation::SetLayerStroke { path, stroke })) - } - ModifyText { new_text } => { - let (path, _) = self.active_selection.clone().expect("Received update for properties panel with no active layer"); - responses.push_back(Operation::SetTextContent { path, new_text }.into()) - } - CheckSelectedWasUpdated { path } => { - if self.matches_selected(&path) { - responses.push_back(PropertiesPanelMessage::ResendActiveProperties.into()) - } - } - CheckSelectedWasDeleted { path } => { - if self.matches_selected(&path) { - self.active_selection = None; - responses.push_back( - LayoutMessage::SendLayout { - layout_target: LayoutTarget::PropertiesOptions, - layout: Layout::WidgetLayout(WidgetLayout::default()), - } - .into(), - ); - responses.push_back( - LayoutMessage::SendLayout { - layout_target: LayoutTarget::PropertiesSections, - layout: Layout::WidgetLayout(WidgetLayout::default()), - } - .into(), - ); - } - } - ResendActiveProperties => { - if let Some((path, target_document)) = self.active_selection.clone() { - let layer = get_document(target_document).layer(&path).unwrap(); - match target_document { - TargetDocument::Artboard => register_artboard_layer_properties(layer, responses, font_cache), - TargetDocument::Artwork => register_artwork_layer_properties(layer, responses, font_cache), - } - } - } - UpdateSelectedDocumentProperties => responses.push_back( - PropertiesPanelMessage::SetActiveLayers { - paths: selected_layers.map(|path| path.to_vec()).collect(), - document: TargetDocument::Artwork, - } - .into(), - ), - } - } - - fn actions(&self) -> ActionList { - actions!(PropertiesMessageDiscriminant;) - } -} - -fn register_artboard_layer_properties(layer: &Layer, responses: &mut VecDeque, font_cache: &FontCache) { +pub fn register_artboard_layer_properties(layer: &Layer, responses: &mut VecDeque, font_cache: &FontCache) { let options_bar = vec![LayoutGroup::Row { widgets: vec![ WidgetHolder::new(Widget::IconLabel(IconLabel { @@ -448,7 +217,7 @@ fn register_artboard_layer_properties(layer: &Layer, responses: &mut VecDeque, font_cache: &FontCache) { +pub fn register_artwork_layer_properties(layer: &Layer, responses: &mut VecDeque, font_cache: &FontCache) { let options_bar = vec![LayoutGroup::Row { widgets: vec![ match &layer.data { @@ -1164,3 +933,70 @@ fn node_section_stroke(stroke: &Stroke) -> LayoutGroup { ], } } + +trait DAffine2Utils { + fn scale_x(&self) -> f64; + fn update_scale_x(self, new_width: f64) -> Self; + fn scale_y(&self) -> f64; + fn update_scale_y(self, new_height: f64) -> Self; + fn x(&self) -> f64; + fn update_x(self, new_x: f64) -> Self; + fn y(&self) -> f64; + fn update_y(self, new_y: f64) -> Self; + fn rotation(&self) -> f64; + fn update_rotation(self, new_rotation: f64) -> Self; +} + +impl DAffine2Utils for DAffine2 { + fn scale_x(&self) -> f64 { + self.transform_vector2((1., 0.).into()).length() + } + + fn update_scale_x(self, new_width: f64) -> Self { + self * DAffine2::from_scale((new_width / self.scale_x(), 1.).into()) + } + + fn scale_y(&self) -> f64 { + self.transform_vector2((0., 1.).into()).length() + } + + fn update_scale_y(self, new_height: f64) -> Self { + self * DAffine2::from_scale((1., new_height / self.scale_y()).into()) + } + + fn x(&self) -> f64 { + self.translation.x + } + + fn update_x(mut self, new_x: f64) -> Self { + self.translation.x = new_x; + self + } + + fn y(&self) -> f64 { + self.translation.y + } + + fn update_y(mut self, new_y: f64) -> Self { + self.translation.y = new_y; + self + } + + fn rotation(&self) -> f64 { + let cos = self.matrix2.col(0).x / self.scale_x(); + let sin = self.matrix2.col(0).y / self.scale_x(); + sin.atan2(cos) + } + + fn update_rotation(self, new_rotation: f64) -> Self { + let width = self.scale_x(); + let height = self.scale_y(); + let half_width = width / 2.; + let half_height = height / 2.; + + let angle_translation_offset = |angle: f64| DVec2::new(-half_width * angle.cos() + half_height * angle.sin(), -half_width * angle.sin() - half_height * angle.cos()); + let angle_translation_adjustment = angle_translation_offset(new_rotation) - angle_translation_offset(self.rotation()); + + DAffine2::from_scale_angle_translation((width, height).into(), new_rotation, self.translation + angle_translation_adjustment) + } +} diff --git a/editor/src/messages/portfolio/document/properties_panel/utility_types.rs b/editor/src/messages/portfolio/document/properties_panel/utility_types.rs new file mode 100644 index 0000000000..fdd32b7b15 --- /dev/null +++ b/editor/src/messages/portfolio/document/properties_panel/utility_types.rs @@ -0,0 +1,23 @@ +use graphene::document::Document as GrapheneDocument; +use graphene::layers::text_layer::FontCache; +use graphene::LayerId; + +use serde::{Deserialize, Serialize}; + +pub struct PropertiesPanelMessageHandlerData<'a> { + pub artwork_document: &'a GrapheneDocument, + pub artboard_document: &'a GrapheneDocument, + pub selected_layers: &'a mut dyn Iterator, + pub font_cache: &'a FontCache, +} + +#[derive(PartialEq, Eq, Clone, Copy, Debug, Serialize, Deserialize)] +pub enum TransformOp { + X, + Y, + ScaleX, + ScaleY, + Width, + Height, + Rotation, +} diff --git a/editor/src/messages/portfolio/document/transform_layer/mod.rs b/editor/src/messages/portfolio/document/transform_layer/mod.rs new file mode 100644 index 0000000000..5e4c2e2ee4 --- /dev/null +++ b/editor/src/messages/portfolio/document/transform_layer/mod.rs @@ -0,0 +1,7 @@ +mod transform_layer_message; +mod transform_layer_message_handler; + +#[doc(inline)] +pub use transform_layer_message::{TransformLayerMessage, TransformLayerMessageDiscriminant}; +#[doc(inline)] +pub use transform_layer_message_handler::TransformLayerMessageHandler; diff --git a/editor/src/document/transform_layer_message.rs b/editor/src/messages/portfolio/document/transform_layer/transform_layer_message.rs similarity index 69% rename from editor/src/document/transform_layer_message.rs rename to editor/src/messages/portfolio/document/transform_layer/transform_layer_message.rs index cd22e8427c..d148dc84f0 100644 --- a/editor/src/document/transform_layer_message.rs +++ b/editor/src/messages/portfolio/document/transform_layer/transform_layer_message.rs @@ -1,12 +1,13 @@ -use crate::input::keyboard::Key; -use crate::message_prelude::*; +use crate::messages::input_mapper::utility_types::input_keyboard::Key; +use crate::messages::prelude::*; use serde::{Deserialize, Serialize}; #[remain::sorted] -#[impl_message(Message, DocumentMessage, TransformLayers)] +#[impl_message(Message, DocumentMessage, TransformLayer)] #[derive(PartialEq, Eq, Clone, Debug, Serialize, Deserialize)] pub enum TransformLayerMessage { + // Messages ApplyTransformOperation, BeginGrab, BeginRotate, diff --git a/editor/src/document/transform_layer_message_handler.rs b/editor/src/messages/portfolio/document/transform_layer/transform_layer_message_handler.rs similarity index 87% rename from editor/src/document/transform_layer_message_handler.rs rename to editor/src/messages/portfolio/document/transform_layer/transform_layer_message_handler.rs index 2fdd12d880..e9d4f2c8a7 100644 --- a/editor/src/document/transform_layer_message_handler.rs +++ b/editor/src/messages/portfolio/document/transform_layer/transform_layer_message_handler.rs @@ -1,11 +1,11 @@ -use super::layer_panel::LayerMetadata; -use super::transformation::{Axis, OriginalTransforms, Selected, TransformOperation, Typing}; use crate::consts::SLOWING_DIVISOR; -use crate::input::mouse::ViewportPosition; -use crate::input::InputPreprocessorMessageHandler; -use crate::message_prelude::*; +use crate::messages::input_mapper::utility_types::input_mouse::ViewportPosition; +use crate::messages::portfolio::document::utility_types::layer_panel::LayerMetadata; +use crate::messages::portfolio::document::utility_types::transformation::{Axis, OriginalTransforms, Selected, TransformOperation, Typing}; +use crate::messages::prelude::*; use graphene::document::Document; +use graphene::LayerId; use glam::DVec2; use graphene::layers::text_layer::FontCache; @@ -29,7 +29,7 @@ pub struct TransformLayerMessageHandler { type TransformData<'a> = (&'a mut HashMap, LayerMetadata>, &'a mut Document, &'a InputPreprocessorMessageHandler, &'a FontCache); impl<'a> MessageHandler> for TransformLayerMessageHandler { #[remain::check] - fn process_action(&mut self, message: TransformLayerMessage, (layer_metadata, document, ipp, font_cache): TransformData, responses: &mut VecDeque) { + fn process_message(&mut self, message: TransformLayerMessage, (layer_metadata, document, ipp, font_cache): TransformData, responses: &mut VecDeque) { use TransformLayerMessage::*; let selected_layers = layer_metadata.iter().filter_map(|(layer_path, data)| data.selected.then(|| layer_path)).collect::>(); @@ -55,7 +55,7 @@ impl<'a> MessageHandler> for TransformL self.transform_operation = TransformOperation::None; - responses.push_back(BroadcastSignal::DocumentIsDirty.into()); + responses.push_back(BroadcastEvent::DocumentIsDirty.into()); } BeginGrab => { if let TransformOperation::Grabbing(_) = self.transform_operation { @@ -66,7 +66,7 @@ impl<'a> MessageHandler> for TransformL self.transform_operation = TransformOperation::Grabbing(Default::default()); - responses.push_back(BroadcastSignal::DocumentIsDirty.into()); + responses.push_back(BroadcastEvent::DocumentIsDirty.into()); } BeginRotate => { if let TransformOperation::Rotating(_) = self.transform_operation { @@ -77,7 +77,7 @@ impl<'a> MessageHandler> for TransformL self.transform_operation = TransformOperation::Rotating(Default::default()); - responses.push_back(BroadcastSignal::DocumentIsDirty.into()); + responses.push_back(BroadcastEvent::DocumentIsDirty.into()); } BeginScale => { if let TransformOperation::Scaling(_) = self.transform_operation { @@ -89,7 +89,7 @@ impl<'a> MessageHandler> for TransformL self.transform_operation = TransformOperation::Scaling(Default::default()); self.transform_operation.apply_transform_operation(&mut selected, self.snap); - responses.push_back(BroadcastSignal::DocumentIsDirty.into()); + responses.push_back(BroadcastEvent::DocumentIsDirty.into()); } CancelTransformOperation => { selected.revert_operation(); @@ -99,7 +99,7 @@ impl<'a> MessageHandler> for TransformL self.transform_operation = TransformOperation::None; - responses.push_back(BroadcastSignal::DocumentIsDirty.into()); + responses.push_back(BroadcastEvent::DocumentIsDirty.into()); } ConstrainX => self.transform_operation.constrain_axis(Axis::X, &mut selected, self.snap), ConstrainY => self.transform_operation.constrain_axis(Axis::Y, &mut selected, self.snap), diff --git a/editor/src/document/clipboards.rs b/editor/src/messages/portfolio/document/utility_types/clipboards.rs similarity index 100% rename from editor/src/document/clipboards.rs rename to editor/src/messages/portfolio/document/utility_types/clipboards.rs diff --git a/editor/src/misc/error.rs b/editor/src/messages/portfolio/document/utility_types/error.rs similarity index 100% rename from editor/src/misc/error.rs rename to editor/src/messages/portfolio/document/utility_types/error.rs diff --git a/editor/src/document/layer_panel.rs b/editor/src/messages/portfolio/document/utility_types/layer_panel.rs similarity index 52% rename from editor/src/document/layer_panel.rs rename to editor/src/messages/portfolio/document/utility_types/layer_panel.rs index 15943ab3cc..47ec02ff70 100644 --- a/editor/src/document/layer_panel.rs +++ b/editor/src/messages/portfolio/document/utility_types/layer_panel.rs @@ -7,53 +7,6 @@ use glam::{DAffine2, DVec2}; use serde::ser::SerializeStruct; use serde::{Deserialize, Serialize}; -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Copy)] -pub struct LayerMetadata { - pub selected: bool, - pub expanded: bool, -} - -impl LayerMetadata { - pub fn new(expanded: bool) -> LayerMetadata { - LayerMetadata { selected: false, expanded } - } -} - -pub fn layer_panel_entry(layer_metadata: &LayerMetadata, transform: DAffine2, layer: &Layer, path: Vec, font_cache: &FontCache) -> LayerPanelEntry { - let name = layer.name.clone().unwrap_or_else(|| String::from("")); - let arr = layer.data.bounding_box(transform, font_cache).unwrap_or([DVec2::ZERO, DVec2::ZERO]); - let arr = arr.iter().map(|x| (*x).into()).collect::>(); - - let mut thumbnail = String::new(); - let mut svg_defs = String::new(); - let render_data = RenderData::new(ViewMode::Normal, font_cache, None, false); - layer.data.clone().render(&mut thumbnail, &mut svg_defs, &mut vec![transform], render_data); - let transform = transform.to_cols_array().iter().map(ToString::to_string).collect::>().join(","); - let thumbnail = if let [(x_min, y_min), (x_max, y_max)] = arr.as_slice() { - format!( - r#"{}{}"#, - x_min, - y_min, - x_max - x_min, - y_max - y_min, - svg_defs, - transform, - thumbnail, - ) - } else { - String::new() - }; - - LayerPanelEntry { - name, - visible: layer.visible, - layer_type: (&layer.data).into(), - layer_metadata: *layer_metadata, - path, - thumbnail, - } -} - #[derive(Debug, Clone, Deserialize, PartialEq, Eq)] pub struct RawBuffer(Vec); @@ -69,11 +22,9 @@ impl From> for RawBuffer { Self(v_from_raw) } } + impl Serialize for RawBuffer { - fn serialize(&self, serializer: S) -> Result - where - S: serde::Serializer, - { + fn serialize(&self, serializer: S) -> Result { let mut buffer = serializer.serialize_struct("Buffer", 2)?; buffer.serialize_field("pointer", &(self.0.as_ptr() as usize))?; buffer.serialize_field("length", &(self.0.len()))?; @@ -81,6 +32,18 @@ impl Serialize for RawBuffer { } } +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Copy)] +pub struct LayerMetadata { + pub selected: bool, + pub expanded: bool, +} + +impl LayerMetadata { + pub fn new(expanded: bool) -> Self { + Self { selected: false, expanded } + } +} + #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] pub struct LayerPanelEntry { pub name: String, @@ -90,3 +53,40 @@ pub struct LayerPanelEntry { pub path: Vec, pub thumbnail: String, } + +impl LayerPanelEntry { + pub fn new(layer_metadata: &LayerMetadata, transform: DAffine2, layer: &Layer, path: Vec, font_cache: &FontCache) -> Self { + let name = layer.name.clone().unwrap_or_else(|| String::from("")); + let arr = layer.data.bounding_box(transform, font_cache).unwrap_or([DVec2::ZERO, DVec2::ZERO]); + let arr = arr.iter().map(|x| (*x).into()).collect::>(); + + let mut thumbnail = String::new(); + let mut svg_defs = String::new(); + let render_data = RenderData::new(ViewMode::Normal, font_cache, None, false); + layer.data.clone().render(&mut thumbnail, &mut svg_defs, &mut vec![transform], render_data); + let transform = transform.to_cols_array().iter().map(ToString::to_string).collect::>().join(","); + let thumbnail = if let [(x_min, y_min), (x_max, y_max)] = arr.as_slice() { + format!( + r#"{}{}"#, + x_min, + y_min, + x_max - x_min, + y_max - y_min, + svg_defs, + transform, + thumbnail, + ) + } else { + String::new() + }; + + LayerPanelEntry { + name, + visible: layer.visible, + layer_type: (&layer.data).into(), + layer_metadata: *layer_metadata, + path, + thumbnail, + } + } +} diff --git a/editor/src/document/utility_types.rs b/editor/src/messages/portfolio/document/utility_types/misc.rs similarity index 96% rename from editor/src/document/utility_types.rs rename to editor/src/messages/portfolio/document/utility_types/misc.rs index 8c6be16369..51b85f2f1f 100644 --- a/editor/src/document/utility_types.rs +++ b/editor/src/messages/portfolio/document/utility_types/misc.rs @@ -1,4 +1,5 @@ -pub use super::layer_panel::{layer_panel_entry, LayerMetadata, LayerPanelEntry, RawBuffer}; +pub use super::layer_panel::{LayerMetadata, LayerPanelEntry}; + use graphene::document::Document as GrapheneDocument; use graphene::LayerId; diff --git a/editor/src/messages/portfolio/document/utility_types/mod.rs b/editor/src/messages/portfolio/document/utility_types/mod.rs new file mode 100644 index 0000000000..f3e6d21f90 --- /dev/null +++ b/editor/src/messages/portfolio/document/utility_types/mod.rs @@ -0,0 +1,6 @@ +pub mod clipboards; +pub mod error; +pub mod layer_panel; +pub mod misc; +pub mod transformation; +pub mod vectorize_layer_metadata; diff --git a/editor/src/document/transformation.rs b/editor/src/messages/portfolio/document/utility_types/transformation.rs similarity index 98% rename from editor/src/document/transformation.rs rename to editor/src/messages/portfolio/document/utility_types/transformation.rs index 029a1497de..29d7175734 100644 --- a/editor/src/document/transformation.rs +++ b/editor/src/messages/portfolio/document/utility_types/transformation.rs @@ -1,8 +1,9 @@ use crate::consts::{ROTATE_SNAP_ANGLE, SCALE_SNAP_INTERVAL}; -use crate::message_prelude::*; +use crate::messages::prelude::*; use graphene::document::Document; use graphene::layers::text_layer::FontCache; +use graphene::LayerId; use graphene::Operation as DocumentOperation; use glam::{DAffine2, DVec2}; @@ -258,7 +259,7 @@ impl<'a> Selected<'a> { ); } - self.responses.push_back(BroadcastSignal::DocumentIsDirty.into()); + self.responses.push_back(BroadcastEvent::DocumentIsDirty.into()); } } diff --git a/editor/src/document/vectorize_layer_metadata.rs b/editor/src/messages/portfolio/document/utility_types/vectorize_layer_metadata.rs similarity index 100% rename from editor/src/document/vectorize_layer_metadata.rs rename to editor/src/messages/portfolio/document/utility_types/vectorize_layer_metadata.rs diff --git a/editor/src/document/menu_bar_message.rs b/editor/src/messages/portfolio/menu_bar/menu_bar_message.rs similarity index 82% rename from editor/src/document/menu_bar_message.rs rename to editor/src/messages/portfolio/menu_bar/menu_bar_message.rs index db09a12d5d..cb87e4b3ed 100644 --- a/editor/src/document/menu_bar_message.rs +++ b/editor/src/messages/portfolio/menu_bar/menu_bar_message.rs @@ -1,4 +1,4 @@ -use crate::message_prelude::*; +use crate::messages::prelude::*; use serde::{Deserialize, Serialize}; @@ -6,5 +6,6 @@ use serde::{Deserialize, Serialize}; #[impl_message(Message, PortfolioMessage, MenuBar)] #[derive(PartialEq, Eq, Clone, Debug, Hash, Serialize, Deserialize)] pub enum MenuBarMessage { + // Messages SendLayout, } diff --git a/editor/src/document/menu_bar_message_handler.rs b/editor/src/messages/portfolio/menu_bar/menu_bar_message_handler.rs similarity index 79% rename from editor/src/document/menu_bar_message_handler.rs rename to editor/src/messages/portfolio/menu_bar/menu_bar_message_handler.rs index 54360f0650..33d2e911af 100644 --- a/editor/src/document/menu_bar_message_handler.rs +++ b/editor/src/messages/portfolio/menu_bar/menu_bar_message_handler.rs @@ -1,8 +1,10 @@ use super::MenuBarMessage; -use crate::input::input_mapper::action_keys::action_shortcut; -use crate::layout::layout_message::LayoutTarget; -use crate::layout::widgets::*; -use crate::message_prelude::*; +use crate::messages::input_mapper::utility_types::macros::action_keys; +use crate::messages::layout::utility_types::layout_widget::{Layout, PropertyHolder}; +use crate::messages::layout::utility_types::misc::LayoutTarget; +use crate::messages::layout::utility_types::widgets::menu_widgets::{MenuColumn, MenuEntry, MenuEntryGroups, MenuLayout}; +use crate::messages::portfolio::document::utility_types::clipboards::Clipboard; +use crate::messages::prelude::*; use std::collections::VecDeque; @@ -11,7 +13,7 @@ pub struct MenuBarMessageHandler {} impl MessageHandler for MenuBarMessageHandler { #[remain::check] - fn process_action(&mut self, message: MenuBarMessage, _data: (), responses: &mut VecDeque) { + fn process_message(&mut self, message: MenuBarMessage, _data: (), responses: &mut VecDeque) { use MenuBarMessage::*; #[remain::sorted] @@ -36,12 +38,12 @@ impl PropertyHolder for MenuBarMessageHandler { label: "New…".into(), icon: Some("File".into()), action: MenuEntry::create_action(|_| DialogMessage::RequestNewDocumentDialog.into()), - shortcut: action_shortcut!(DialogMessageDiscriminant::RequestNewDocumentDialog), + shortcut: action_keys!(DialogMessageDiscriminant::RequestNewDocumentDialog), children: MenuEntryGroups::empty(), }, MenuEntry { label: "Open…".into(), - shortcut: action_shortcut!(PortfolioMessageDiscriminant::OpenDocument), + shortcut: action_keys!(PortfolioMessageDiscriminant::OpenDocument), action: MenuEntry::create_action(|_| PortfolioMessage::OpenDocument.into()), ..MenuEntry::default() }, @@ -90,13 +92,13 @@ impl PropertyHolder for MenuBarMessageHandler { vec![ MenuEntry { label: "Close".into(), - shortcut: action_shortcut!(PortfolioMessageDiscriminant::CloseActiveDocumentWithConfirmation), + shortcut: action_keys!(PortfolioMessageDiscriminant::CloseActiveDocumentWithConfirmation), action: MenuEntry::create_action(|_| PortfolioMessage::CloseActiveDocumentWithConfirmation.into()), ..MenuEntry::default() }, MenuEntry { label: "Close All".into(), - shortcut: action_shortcut!(DialogMessageDiscriminant::CloseAllDocumentsWithConfirmation), + shortcut: action_keys!(DialogMessageDiscriminant::CloseAllDocumentsWithConfirmation), action: MenuEntry::create_action(|_| DialogMessage::CloseAllDocumentsWithConfirmation.into()), ..MenuEntry::default() }, @@ -104,7 +106,7 @@ impl PropertyHolder for MenuBarMessageHandler { vec![ MenuEntry { label: "Save".into(), - shortcut: action_shortcut!(DocumentMessageDiscriminant::SaveDocument), + shortcut: action_keys!(DocumentMessageDiscriminant::SaveDocument), action: MenuEntry::create_action(|_| DocumentMessage::SaveDocument.into()), ..MenuEntry::default() }, @@ -127,13 +129,13 @@ impl PropertyHolder for MenuBarMessageHandler { vec![ MenuEntry { label: "Import…".into(), - shortcut: action_shortcut!(PortfolioMessageDiscriminant::Import), + shortcut: action_keys!(PortfolioMessageDiscriminant::Import), action: MenuEntry::create_action(|_| PortfolioMessage::Import.into()), ..MenuEntry::default() }, MenuEntry { label: "Export…".into(), - shortcut: action_shortcut!(DialogMessageDiscriminant::RequestExportDialog), + shortcut: action_keys!(DialogMessageDiscriminant::RequestExportDialog), action: MenuEntry::create_action(|_| DialogMessage::RequestExportDialog.into()), ..MenuEntry::default() }, @@ -151,13 +153,13 @@ impl PropertyHolder for MenuBarMessageHandler { vec![ MenuEntry { label: "Undo".into(), - shortcut: action_shortcut!(DocumentMessageDiscriminant::Undo), + shortcut: action_keys!(DocumentMessageDiscriminant::Undo), action: MenuEntry::create_action(|_| DocumentMessage::Undo.into()), ..MenuEntry::default() }, MenuEntry { label: "Redo".into(), - shortcut: action_shortcut!(DocumentMessageDiscriminant::Redo), + shortcut: action_keys!(DocumentMessageDiscriminant::Redo), action: MenuEntry::create_action(|_| DocumentMessage::Redo.into()), ..MenuEntry::default() }, @@ -165,21 +167,21 @@ impl PropertyHolder for MenuBarMessageHandler { vec![ MenuEntry { label: "Cut".into(), - shortcut: action_shortcut!(PortfolioMessageDiscriminant::Cut), + shortcut: action_keys!(PortfolioMessageDiscriminant::Cut), action: MenuEntry::create_action(|_| PortfolioMessage::Cut { clipboard: Clipboard::Device }.into()), ..MenuEntry::default() }, MenuEntry { label: "Copy".into(), icon: Some("Copy".into()), - shortcut: action_shortcut!(PortfolioMessageDiscriminant::Copy), + shortcut: action_keys!(PortfolioMessageDiscriminant::Copy), action: MenuEntry::create_action(|_| PortfolioMessage::Copy { clipboard: Clipboard::Device }.into()), ..MenuEntry::default() }, MenuEntry { label: "Paste".into(), icon: Some("Paste".into()), - shortcut: action_shortcut!(FrontendMessageDiscriminant::TriggerPaste), + shortcut: action_keys!(FrontendMessageDiscriminant::TriggerPaste), action: MenuEntry::create_action(|_| FrontendMessage::TriggerPaste.into()), ..MenuEntry::default() }, @@ -191,13 +193,13 @@ impl PropertyHolder for MenuBarMessageHandler { children: MenuEntryGroups(vec![vec![ MenuEntry { label: "Select All".into(), - shortcut: action_shortcut!(DocumentMessageDiscriminant::SelectAllLayers), + shortcut: action_keys!(DocumentMessageDiscriminant::SelectAllLayers), action: MenuEntry::create_action(|_| DocumentMessage::SelectAllLayers.into()), ..MenuEntry::default() }, MenuEntry { label: "Deselect All".into(), - shortcut: action_shortcut!(DocumentMessageDiscriminant::DeselectAllLayers), + shortcut: action_keys!(DocumentMessageDiscriminant::DeselectAllLayers), action: MenuEntry::create_action(|_| DocumentMessage::DeselectAllLayers.into()), ..MenuEntry::default() }, @@ -207,25 +209,25 @@ impl PropertyHolder for MenuBarMessageHandler { children: MenuEntryGroups(vec![vec![ MenuEntry { label: "Raise To Front".into(), - shortcut: action_shortcut!(DocumentMessageDiscriminant::SelectedLayersRaiseToFront), + shortcut: action_keys!(DocumentMessageDiscriminant::SelectedLayersRaiseToFront), action: MenuEntry::create_action(|_| DocumentMessage::SelectedLayersRaiseToFront.into()), ..MenuEntry::default() }, MenuEntry { label: "Raise".into(), - shortcut: action_shortcut!(DocumentMessageDiscriminant::SelectedLayersRaise), + shortcut: action_keys!(DocumentMessageDiscriminant::SelectedLayersRaise), action: MenuEntry::create_action(|_| DocumentMessage::SelectedLayersRaise.into()), ..MenuEntry::default() }, MenuEntry { label: "Lower".into(), - shortcut: action_shortcut!(DocumentMessageDiscriminant::SelectedLayersLower), + shortcut: action_keys!(DocumentMessageDiscriminant::SelectedLayersLower), action: MenuEntry::create_action(|_| DocumentMessage::SelectedLayersLower.into()), ..MenuEntry::default() }, MenuEntry { label: "Lower to Back".into(), - shortcut: action_shortcut!(DocumentMessageDiscriminant::SelectedLayersLowerToBack), + shortcut: action_keys!(DocumentMessageDiscriminant::SelectedLayersLowerToBack), action: MenuEntry::create_action(|_| DocumentMessage::SelectedLayersLowerToBack.into()), ..MenuEntry::default() }, @@ -287,19 +289,19 @@ impl PropertyHolder for MenuBarMessageHandler { MenuEntry { label: "Off".into(), // icon: Some("Checkmark".into()), // TODO: Find a way to set this icon on the active mode - shortcut: action_shortcut!(DebugMessageDiscriminant::MessageOff), + shortcut: action_keys!(DebugMessageDiscriminant::MessageOff), action: MenuEntry::create_action(|_| DebugMessage::MessageOff.into()), ..MenuEntry::default() }, MenuEntry { label: "Only Names".into(), - shortcut: action_shortcut!(DebugMessageDiscriminant::MessageNames), + shortcut: action_keys!(DebugMessageDiscriminant::MessageNames), action: MenuEntry::create_action(|_| DebugMessage::MessageNames.into()), ..MenuEntry::default() }, MenuEntry { label: "Full Contents".into(), - shortcut: action_shortcut!(DebugMessageDiscriminant::MessageContents), + shortcut: action_keys!(DebugMessageDiscriminant::MessageContents), action: MenuEntry::create_action(|_| DebugMessage::MessageContents.into()), ..MenuEntry::default() }, @@ -309,13 +311,13 @@ impl PropertyHolder for MenuBarMessageHandler { MenuEntry { label: "Debug: Print Trace Logs".into(), icon: Some(if let log::LevelFilter::Trace = log::max_level() { "CheckboxChecked" } else { "CheckboxUnchecked" }.into()), - shortcut: action_shortcut!(DebugMessageDiscriminant::ToggleTraceLogs), + shortcut: action_keys!(DebugMessageDiscriminant::ToggleTraceLogs), action: MenuEntry::create_action(|_| DebugMessage::ToggleTraceLogs.into()), ..MenuEntry::default() }, MenuEntry { label: "Debug: Print Document".into(), - shortcut: action_shortcut!(DocumentMessageDiscriminant::DebugPrintDocument), + shortcut: action_keys!(DocumentMessageDiscriminant::DebugPrintDocument), action: MenuEntry::create_action(|_| DocumentMessage::DebugPrintDocument.into()), ..MenuEntry::default() }, diff --git a/editor/src/messages/portfolio/menu_bar/mod.rs b/editor/src/messages/portfolio/menu_bar/mod.rs new file mode 100644 index 0000000000..27e741b208 --- /dev/null +++ b/editor/src/messages/portfolio/menu_bar/mod.rs @@ -0,0 +1,7 @@ +mod menu_bar_message; +mod menu_bar_message_handler; + +#[doc(inline)] +pub use menu_bar_message::{MenuBarMessage, MenuBarMessageDiscriminant}; +#[doc(inline)] +pub use menu_bar_message_handler::MenuBarMessageHandler; diff --git a/editor/src/messages/portfolio/mod.rs b/editor/src/messages/portfolio/mod.rs new file mode 100644 index 0000000000..46f34cd780 --- /dev/null +++ b/editor/src/messages/portfolio/mod.rs @@ -0,0 +1,10 @@ +mod portfolio_message; +mod portfolio_message_handler; + +pub mod document; +pub mod menu_bar; + +#[doc(inline)] +pub use portfolio_message::{PortfolioMessage, PortfolioMessageDiscriminant}; +#[doc(inline)] +pub use portfolio_message_handler::PortfolioMessageHandler; diff --git a/editor/src/document/portfolio_message.rs b/editor/src/messages/portfolio/portfolio_message.rs similarity index 89% rename from editor/src/document/portfolio_message.rs rename to editor/src/messages/portfolio/portfolio_message.rs index 4d122dc8df..84977b92b4 100644 --- a/editor/src/document/portfolio_message.rs +++ b/editor/src/messages/portfolio/portfolio_message.rs @@ -1,6 +1,6 @@ -use super::clipboards::Clipboard; -use super::utility_types::Platform; -use crate::message_prelude::*; +use crate::messages::portfolio::document::utility_types::clipboards::Clipboard; +use crate::messages::portfolio::document::utility_types::misc::Platform; +use crate::messages::prelude::*; use graphene::layers::text_layer::Font; use graphene::LayerId; diff --git a/editor/src/document/portfolio_message_handler.rs b/editor/src/messages/portfolio/portfolio_message_handler.rs similarity index 93% rename from editor/src/document/portfolio_message_handler.rs rename to editor/src/messages/portfolio/portfolio_message_handler.rs index 12d310a39b..3ce46be570 100644 --- a/editor/src/document/portfolio_message_handler.rs +++ b/editor/src/messages/portfolio/portfolio_message_handler.rs @@ -1,13 +1,12 @@ -use super::clipboards::{CopyBufferEntry, INTERNAL_CLIPBOARD_COUNT}; -use super::utility_types::Platform; -use super::{DocumentMessageHandler, MenuBarMessageHandler}; +use crate::application::generate_uuid; use crate::consts::{DEFAULT_DOCUMENT_NAME, GRAPHITE_DOCUMENT_VERSION}; -use crate::dialog; -use crate::frontend::utility_types::FrontendDocumentDetails; -use crate::input::InputPreprocessorMessageHandler; -use crate::layout::layout_message::LayoutTarget; -use crate::layout::widgets::PropertyHolder; -use crate::message_prelude::*; +use crate::messages::dialog::simple_dialogs; +use crate::messages::frontend::utility_types::FrontendDocumentDetails; +use crate::messages::layout::utility_types::layout_widget::PropertyHolder; +use crate::messages::layout::utility_types::misc::LayoutTarget; +use crate::messages::portfolio::document::utility_types::clipboards::{Clipboard, CopyBufferEntry, INTERNAL_CLIPBOARD_COUNT}; +use crate::messages::portfolio::document::utility_types::misc::Platform; +use crate::messages::prelude::*; use graphene::layers::layer_info::LayerDataTypeDiscriminant; use graphene::layers::text_layer::{Font, FontCache}; @@ -27,86 +26,9 @@ pub struct PortfolioMessageHandler { pub platform: Platform, } -impl PortfolioMessageHandler { - pub fn active_document(&self) -> Option<&DocumentMessageHandler> { - self.active_document_id.and_then(|id| self.documents.get(&id)) - } - - pub fn active_document_mut(&mut self) -> Option<&mut DocumentMessageHandler> { - self.active_document_id.and_then(|id| self.documents.get_mut(&id)) - } - - pub fn generate_new_document_name(&self) -> String { - let mut doc_title_numbers = self - .ordered_document_iterator() - .filter_map(|doc| { - doc.name - .rsplit_once(DEFAULT_DOCUMENT_NAME) - .map(|(prefix, number)| (prefix.is_empty()).then(|| number.trim().parse::().ok()).flatten().unwrap_or(1)) - }) - .collect::>(); - - doc_title_numbers.sort_unstable(); - doc_title_numbers.iter_mut().enumerate().for_each(|(i, number)| *number = *number - i as isize - 2); - // Uses binary search to find the index of the element where number is bigger than i - let new_doc_title_num = doc_title_numbers.binary_search(&0).map_or_else(|e| e, |v| v) + 1; - - match new_doc_title_num { - 1 => DEFAULT_DOCUMENT_NAME.to_string(), - _ => format!("{} {}", DEFAULT_DOCUMENT_NAME, new_doc_title_num), - } - } - - // TODO Fix how this doesn't preserve tab order upon loading new document from *File > Load* - fn load_document(&mut self, new_document: DocumentMessageHandler, document_id: u64, responses: &mut VecDeque) { - self.document_ids.push(document_id); - - responses.extend( - new_document - .layer_metadata - .keys() - .filter_map(|path| new_document.layer_panel_entry_from_path(path, &self.font_cache)) - .map(|entry| FrontendMessage::UpdateDocumentLayerDetails { data: entry }.into()) - .collect::>(), - ); - new_document.update_layer_tree_options_bar_widgets(responses, &self.font_cache); - - new_document.load_layer_resources(responses, &new_document.graphene_document.root.data, Vec::new()); - - self.documents.insert(document_id, new_document); - - if self.active_document().is_some() { - responses.push_back(PropertiesPanelMessage::Deactivate.into()); - responses.push_back(BroadcastSignal::ToolAbort.into()); - responses.push_back(ToolMessage::DeactivateTools.into()); - } - - responses.push_back(PortfolioMessage::UpdateOpenDocumentsList.into()); - responses.push_back(PortfolioMessage::SelectDocument { document_id }.into()); - responses.push_back(PortfolioMessage::UpdateDocumentWidgets.into()); - responses.push_back(ToolMessage::InitTools.into()); - responses.push_back(PropertiesPanelMessage::Init.into()); - responses.push_back(MovementMessage::TranslateCanvas { delta: (0., 0.).into() }.into()); - responses.push_back(DocumentMessage::DocumentStructureChanged.into()) - } - - /// Returns an iterator over the open documents in order. - pub fn ordered_document_iterator(&self) -> impl Iterator { - self.document_ids.iter().map(|id| self.documents.get(id).expect("document id was not found in the document hashmap")) - } - - fn document_index(&self, document_id: u64) -> usize { - self.document_ids.iter().position(|id| id == &document_id).expect("Active document is missing from document ids") - } - - pub fn font_cache(&self) -> &FontCache { - &self.font_cache - } -} - impl MessageHandler for PortfolioMessageHandler { #[remain::check] - fn process_action(&mut self, message: PortfolioMessage, ipp: &InputPreprocessorMessageHandler, responses: &mut VecDeque) { + fn process_message(&mut self, message: PortfolioMessage, ipp: &InputPreprocessorMessageHandler, responses: &mut VecDeque) { use DocumentMessage::*; use PortfolioMessage::*; @@ -116,11 +38,11 @@ impl MessageHandler for Port #[remain::unsorted] Document(message) => { if let Some(document) = self.active_document_id.and_then(|id| self.documents.get_mut(&id)) { - document.process_action(message, (ipp, &self.font_cache), responses) + document.process_message(message, (ipp, &self.font_cache), responses) } } #[remain::unsorted] - MenuBar(message) => self.menu_bar_message_handler.process_action(message, (), responses), + MenuBar(message) => self.menu_bar_message_handler.process_message(message, (), responses), // Messages AutoSaveActiveDocument => { @@ -151,7 +73,7 @@ impl MessageHandler for Port CloseAllDocuments => { if self.active_document_id.is_some() { responses.push_back(PropertiesPanelMessage::Deactivate.into()); - responses.push_back(BroadcastSignal::ToolAbort.into()); + responses.push_back(BroadcastEvent::ToolAbort.into()); responses.push_back(ToolMessage::DeactivateTools.into()); } @@ -203,10 +125,10 @@ impl MessageHandler for Port CloseDocumentWithConfirmation { document_id } => { let target_document = self.documents.get(&document_id).unwrap(); if target_document.is_saved() { - responses.push_back(BroadcastSignal::ToolAbort.into()); + responses.push_back(BroadcastEvent::ToolAbort.into()); responses.push_back(PortfolioMessage::CloseDocument { document_id }.into()); } else { - let dialog = dialog::CloseDocument { + let dialog = simple_dialogs::CloseDocumentDialog { document_name: target_document.name.clone(), document_id, }; @@ -284,7 +206,7 @@ impl MessageHandler for Port let new_document = DocumentMessageHandler::with_name(name, ipp); let document_id = generate_uuid(); if self.active_document().is_some() { - responses.push_back(BroadcastSignal::ToolAbort.into()); + responses.push_back(BroadcastEvent::ToolAbort.into()); responses.push_back(MovementMessage::TranslateCanvas { delta: (0., 0.).into() }.into()); } @@ -457,7 +379,7 @@ impl MessageHandler for Port } if self.active_document().is_some() { - responses.push_back(BroadcastSignal::ToolAbort.into()); + responses.push_back(BroadcastEvent::ToolAbort.into()); } // TODO: Remove this message in favor of having tools have specific data per document instance @@ -469,8 +391,8 @@ impl MessageHandler for Port for layer in self.documents.get(&document_id).unwrap().layer_metadata.keys() { responses.push_back(DocumentMessage::LayerChanged { affected_layer_path: layer.clone() }.into()); } - responses.push_back(BroadcastSignal::SelectionChanged.into()); - responses.push_back(BroadcastSignal::DocumentIsDirty.into()); + responses.push_back(BroadcastEvent::SelectionChanged.into()); + responses.push_back(BroadcastEvent::DocumentIsDirty.into()); responses.push_back(PortfolioMessage::UpdateDocumentWidgets.into()); responses.push_back(MovementMessage::TranslateCanvas { delta: (0., 0.).into() }.into()); } @@ -525,3 +447,80 @@ impl MessageHandler for Port common } } + +impl PortfolioMessageHandler { + pub fn active_document(&self) -> Option<&DocumentMessageHandler> { + self.active_document_id.and_then(|id| self.documents.get(&id)) + } + + pub fn active_document_mut(&mut self) -> Option<&mut DocumentMessageHandler> { + self.active_document_id.and_then(|id| self.documents.get_mut(&id)) + } + + pub fn generate_new_document_name(&self) -> String { + let mut doc_title_numbers = self + .ordered_document_iterator() + .filter_map(|doc| { + doc.name + .rsplit_once(DEFAULT_DOCUMENT_NAME) + .map(|(prefix, number)| (prefix.is_empty()).then(|| number.trim().parse::().ok()).flatten().unwrap_or(1)) + }) + .collect::>(); + + doc_title_numbers.sort_unstable(); + doc_title_numbers.iter_mut().enumerate().for_each(|(i, number)| *number = *number - i as isize - 2); + // Uses binary search to find the index of the element where number is bigger than i + let new_doc_title_num = doc_title_numbers.binary_search(&0).map_or_else(|e| e, |v| v) + 1; + + match new_doc_title_num { + 1 => DEFAULT_DOCUMENT_NAME.to_string(), + _ => format!("{} {}", DEFAULT_DOCUMENT_NAME, new_doc_title_num), + } + } + + // TODO Fix how this doesn't preserve tab order upon loading new document from *File > Load* + fn load_document(&mut self, new_document: DocumentMessageHandler, document_id: u64, responses: &mut VecDeque) { + self.document_ids.push(document_id); + + responses.extend( + new_document + .layer_metadata + .keys() + .filter_map(|path| new_document.layer_panel_entry_from_path(path, &self.font_cache)) + .map(|entry| FrontendMessage::UpdateDocumentLayerDetails { data: entry }.into()) + .collect::>(), + ); + new_document.update_layer_tree_options_bar_widgets(responses, &self.font_cache); + + new_document.load_layer_resources(responses, &new_document.graphene_document.root.data, Vec::new()); + + self.documents.insert(document_id, new_document); + + if self.active_document().is_some() { + responses.push_back(PropertiesPanelMessage::Deactivate.into()); + responses.push_back(BroadcastEvent::ToolAbort.into()); + responses.push_back(ToolMessage::DeactivateTools.into()); + } + + responses.push_back(PortfolioMessage::UpdateOpenDocumentsList.into()); + responses.push_back(PortfolioMessage::SelectDocument { document_id }.into()); + responses.push_back(PortfolioMessage::UpdateDocumentWidgets.into()); + responses.push_back(ToolMessage::InitTools.into()); + responses.push_back(PropertiesPanelMessage::Init.into()); + responses.push_back(MovementMessage::TranslateCanvas { delta: (0., 0.).into() }.into()); + responses.push_back(DocumentMessage::DocumentStructureChanged.into()) + } + + /// Returns an iterator over the open documents in order. + pub fn ordered_document_iterator(&self) -> impl Iterator { + self.document_ids.iter().map(|id| self.documents.get(id).expect("document id was not found in the document hashmap")) + } + + fn document_index(&self, document_id: u64) -> usize { + self.document_ids.iter().position(|id| id == &document_id).expect("Active document is missing from document ids") + } + + pub fn font_cache(&self) -> &FontCache { + &self.font_cache + } +} diff --git a/editor/src/messages/prelude.rs b/editor/src/messages/prelude.rs new file mode 100644 index 0000000000..2c086be071 --- /dev/null +++ b/editor/src/messages/prelude.rs @@ -0,0 +1,46 @@ +// Message, MessageDiscriminant, MessageHandler +pub use crate::messages::broadcast::{BroadcastMessage, BroadcastMessageDiscriminant, BroadcastMessageHandler}; +pub use crate::messages::debug::{DebugMessage, DebugMessageDiscriminant, DebugMessageHandler}; +pub use crate::messages::dialog::export_dialog::ExportDialogMessageHandler; +pub use crate::messages::dialog::new_document_dialog::NewDocumentDialogMessageHandler; +pub use crate::messages::dialog::{DialogMessage, DialogMessageDiscriminant, DialogMessageHandler}; +pub use crate::messages::frontend::{FrontendMessage, FrontendMessageDiscriminant}; +pub use crate::messages::input_mapper::{InputMapperMessage, InputMapperMessageDiscriminant, InputMapperMessageHandler}; +pub use crate::messages::input_preprocessor::{InputPreprocessorMessage, InputPreprocessorMessageDiscriminant, InputPreprocessorMessageHandler}; +pub use crate::messages::layout::{LayoutMessage, LayoutMessageDiscriminant, LayoutMessageHandler}; +pub use crate::messages::portfolio::document::artboard::{ArtboardMessage, ArtboardMessageDiscriminant, ArtboardMessageHandler}; +pub use crate::messages::portfolio::document::movement::{MovementMessage, MovementMessageDiscriminant, MovementMessageHandler}; +pub use crate::messages::portfolio::document::overlays::{OverlaysMessage, OverlaysMessageDiscriminant, OverlaysMessageHandler}; +pub use crate::messages::portfolio::document::properties_panel::{PropertiesPanelMessage, PropertiesPanelMessageDiscriminant, PropertiesPanelMessageHandler}; +pub use crate::messages::portfolio::document::transform_layer::{TransformLayerMessage, TransformLayerMessageDiscriminant, TransformLayerMessageHandler}; +pub use crate::messages::portfolio::document::{DocumentMessage, DocumentMessageDiscriminant, DocumentMessageHandler}; +pub use crate::messages::portfolio::menu_bar::{MenuBarMessage, MenuBarMessageDiscriminant, MenuBarMessageHandler}; +pub use crate::messages::portfolio::{PortfolioMessage, PortfolioMessageDiscriminant, PortfolioMessageHandler}; +pub use crate::messages::tool::{ToolMessage, ToolMessageDiscriminant, ToolMessageHandler}; +pub use crate::messages::workspace::{WorkspaceMessage, WorkspaceMessageDiscriminant, WorkspaceMessageHandler}; + +// Message, MessageDiscriminant +pub use crate::messages::message::{Message, MessageDiscriminant}; +pub use crate::messages::tool::tool_messages::artboard_tool::{ArtboardToolMessage, ArtboardToolMessageDiscriminant}; +pub use crate::messages::tool::tool_messages::ellipse_tool::{EllipseToolMessage, EllipseToolMessageDiscriminant}; +pub use crate::messages::tool::tool_messages::eyedropper_tool::{EyedropperToolMessage, EyedropperToolMessageDiscriminant}; +pub use crate::messages::tool::tool_messages::fill_tool::{FillToolMessage, FillToolMessageDiscriminant}; +pub use crate::messages::tool::tool_messages::freehand_tool::{FreehandToolMessage, FreehandToolMessageDiscriminant}; +pub use crate::messages::tool::tool_messages::gradient_tool::{GradientToolMessage, GradientToolMessageDiscriminant}; +pub use crate::messages::tool::tool_messages::line_tool::{LineToolMessage, LineToolMessageDiscriminant}; +pub use crate::messages::tool::tool_messages::navigate_tool::{NavigateToolMessage, NavigateToolMessageDiscriminant}; +pub use crate::messages::tool::tool_messages::path_tool::{PathToolMessage, PathToolMessageDiscriminant}; +pub use crate::messages::tool::tool_messages::pen_tool::{PenToolMessage, PenToolMessageDiscriminant}; +pub use crate::messages::tool::tool_messages::rectangle_tool::{RectangleToolMessage, RectangleToolMessageDiscriminant}; +pub use crate::messages::tool::tool_messages::select_tool::{SelectToolMessage, SelectToolMessageDiscriminant}; +pub use crate::messages::tool::tool_messages::shape_tool::{ShapeToolMessage, ShapeToolMessageDiscriminant}; +pub use crate::messages::tool::tool_messages::spline_tool::{SplineToolMessage, SplineToolMessageDiscriminant}; +pub use crate::messages::tool::tool_messages::text_tool::{TextToolMessage, TextToolMessageDiscriminant}; + +// Other +pub use crate::messages::broadcast::broadcast_event::{BroadcastEvent, BroadcastEventDiscriminant}; +pub use crate::utility_traits::{ActionList, AsMessage, MessageHandler, ToDiscriminant, TransitiveChild}; + +pub use graphite_proc_macros::*; + +pub use std::collections::VecDeque; diff --git a/editor/src/viewport_tools/tools/shared/mod.rs b/editor/src/messages/tool/common_functionality/mod.rs similarity index 50% rename from editor/src/viewport_tools/tools/shared/mod.rs rename to editor/src/messages/tool/common_functionality/mod.rs index 59a94bc880..ac51b31029 100644 --- a/editor/src/viewport_tools/tools/shared/mod.rs +++ b/editor/src/messages/tool/common_functionality/mod.rs @@ -1,3 +1,6 @@ +pub mod overlay_renderer; pub mod path_outline; pub mod resize; +pub mod shape_editor; +pub mod snapping; pub mod transformation_cage; diff --git a/editor/src/viewport_tools/vector_editor/overlay_renderer.rs b/editor/src/messages/tool/common_functionality/overlay_renderer.rs similarity index 96% rename from editor/src/viewport_tools/vector_editor/overlay_renderer.rs rename to editor/src/messages/tool/common_functionality/overlay_renderer.rs index 2a8e539008..bb5a2dbdef 100644 --- a/editor/src/viewport_tools/vector_editor/overlay_renderer.rs +++ b/editor/src/messages/tool/common_functionality/overlay_renderer.rs @@ -1,13 +1,14 @@ -use super::constants::ROUNDING_BIAS; -use super::manipulator_group::ManipulatorGroup; -use super::manipulator_point::ManipulatorPoint; +use crate::application::generate_uuid; +use crate::consts::VIEWPORT_GRID_ROUNDING_BIAS; use crate::consts::{COLOR_ACCENT, MANIPULATOR_GROUP_MARKER_SIZE, PATH_OUTLINE_WEIGHT}; -use crate::message_prelude::{generate_uuid, DocumentMessage, Message}; +use crate::messages::prelude::*; use graphene::color::Color; use graphene::document::Document; use graphene::layers::style::{self, Fill, Stroke}; -use graphene::layers::vector::constants::ManipulatorType; +use graphene::layers::vector::consts::ManipulatorType; +use graphene::layers::vector::manipulator_group::ManipulatorGroup; +use graphene::layers::vector::manipulator_point::ManipulatorPoint; use graphene::layers::vector::subpath::Subpath; use graphene::{LayerId, Operation}; @@ -210,7 +211,7 @@ impl OverlayRenderer { let line_vector = parent_transform.transform_point2(manipulator_point.position) - parent_transform.transform_point2(handle.position); let scale = DVec2::splat(line_vector.length()); let angle = -line_vector.angle_between(DVec2::X); - let translation = (parent_transform.transform_point2(handle.position) + ROUNDING_BIAS).round() + DVec2::splat(0.5); + let translation = (parent_transform.transform_point2(handle.position) + VIEWPORT_GRID_ROUNDING_BIAS).round() + DVec2::splat(0.5); let transform = DAffine2::from_scale_angle_translation(scale, angle, translation).to_cols_array(); responses.push_back(Self::overlay_transform_message(line_overlay.clone(), transform)); *line_source = Some(line_overlay); @@ -218,7 +219,7 @@ impl OverlayRenderer { let marker_overlay = marker_source.take().unwrap_or_else(|| Self::create_handle_overlay(responses)); let scale = DVec2::splat(MANIPULATOR_GROUP_MARKER_SIZE); let angle = 0.; - let translation = (parent_transform.transform_point2(handle.position) - (scale / 2.) + ROUNDING_BIAS).round(); + let translation = (parent_transform.transform_point2(handle.position) - (scale / 2.) + VIEWPORT_GRID_ROUNDING_BIAS).round(); let transform = DAffine2::from_scale_angle_translation(scale, angle, translation).to_cols_array(); responses.push_back(Self::overlay_transform_message(marker_overlay.clone(), transform)); *marker_source = Some(marker_overlay); @@ -239,7 +240,7 @@ impl OverlayRenderer { if let Some(anchor_overlay) = &overlays[ManipulatorType::Anchor as usize] { let scale = DVec2::splat(MANIPULATOR_GROUP_MARKER_SIZE); let angle = 0.; - let translation = (parent_transform.transform_point2(manipulator_point.position) - (scale / 2.) + ROUNDING_BIAS).round(); + let translation = (parent_transform.transform_point2(manipulator_point.position) - (scale / 2.) + VIEWPORT_GRID_ROUNDING_BIAS).round(); let transform = DAffine2::from_scale_angle_translation(scale, angle, translation).to_cols_array(); let message = Self::overlay_transform_message(anchor_overlay.clone(), transform); diff --git a/editor/src/viewport_tools/tools/shared/path_outline.rs b/editor/src/messages/tool/common_functionality/path_outline.rs similarity index 97% rename from editor/src/viewport_tools/tools/shared/path_outline.rs rename to editor/src/messages/tool/common_functionality/path_outline.rs index 95e7eba60f..89753167c2 100644 --- a/editor/src/viewport_tools/tools/shared/path_outline.rs +++ b/editor/src/messages/tool/common_functionality/path_outline.rs @@ -1,7 +1,6 @@ +use crate::application::generate_uuid; use crate::consts::{COLOR_ACCENT, PATH_OUTLINE_WEIGHT, SELECTION_TOLERANCE}; -use crate::document::DocumentMessageHandler; -use crate::input::InputPreprocessorMessageHandler; -use crate::message_prelude::*; +use crate::messages::prelude::*; use graphene::intersection::Quad; use graphene::layers::layer_info::LayerDataType; diff --git a/editor/src/viewport_tools/tools/shared/resize.rs b/editor/src/messages/tool/common_functionality/resize.rs similarity index 69% rename from editor/src/viewport_tools/tools/shared/resize.rs rename to editor/src/messages/tool/common_functionality/resize.rs index 49e08b39c4..afb0cd233a 100644 --- a/editor/src/viewport_tools/tools/shared/resize.rs +++ b/editor/src/messages/tool/common_functionality/resize.rs @@ -1,11 +1,10 @@ -use crate::document::DocumentMessageHandler; -use crate::input::keyboard::Key; -use crate::input::mouse::ViewportPosition; -use crate::input::InputPreprocessorMessageHandler; -use crate::message_prelude::*; -use crate::viewport_tools::snapping::SnapHandler; +use crate::messages::input_mapper::utility_types::input_keyboard::Key; +use crate::messages::input_mapper::utility_types::input_mouse::ViewportPosition; +use crate::messages::prelude::*; +use crate::messages::tool::common_functionality::snapping::SnapManager; use graphene::layers::text_layer::FontCache; +use graphene::LayerId; use graphene::Operation; use glam::{DAffine2, DVec2, Vec2Swizzles}; @@ -14,15 +13,15 @@ use glam::{DAffine2, DVec2, Vec2Swizzles}; pub struct Resize { pub drag_start: ViewportPosition, pub path: Option>, - snap_handler: SnapHandler, + snap_manager: SnapManager, } impl Resize { /// Starts a resize, assigning the snap targets and snapping the starting position. pub fn start(&mut self, responses: &mut VecDeque, document: &DocumentMessageHandler, mouse_position: DVec2, font_cache: &FontCache) { - self.snap_handler.start_snap(document, document.bounding_boxes(None, None, font_cache), true, true); - self.snap_handler.add_all_document_handles(document, &[], &[], &[]); - self.drag_start = self.snap_handler.snap_position(responses, document, mouse_position); + self.snap_manager.start_snap(document, document.bounding_boxes(None, None, font_cache), true, true); + self.snap_manager.add_all_document_handles(document, &[], &[], &[]); + self.drag_start = self.snap_manager.snap_position(responses, document, mouse_position); } pub fn calculate_transform( @@ -36,7 +35,7 @@ impl Resize { if let Some(path) = &self.path { let mut start = self.drag_start; - let stop = self.snap_handler.snap_position(responses, document, ipp.mouse.position); + let stop = self.snap_manager.snap_position(responses, document, ipp.mouse.position); let mut size = stop - start; if ipp.keyboard.get(lock_ratio as usize) { @@ -60,7 +59,7 @@ impl Resize { } pub fn cleanup(&mut self, responses: &mut VecDeque) { - self.snap_handler.cleanup(responses); + self.snap_manager.cleanup(responses); self.path = None; } } diff --git a/editor/src/viewport_tools/vector_editor/shape_editor.rs b/editor/src/messages/tool/common_functionality/shape_editor.rs similarity index 97% rename from editor/src/viewport_tools/vector_editor/shape_editor.rs rename to editor/src/messages/tool/common_functionality/shape_editor.rs index 8221545006..8980884e9f 100644 --- a/editor/src/viewport_tools/vector_editor/shape_editor.rs +++ b/editor/src/messages/tool/common_functionality/shape_editor.rs @@ -1,9 +1,9 @@ -use super::manipulator_group::ManipulatorGroup; -use super::manipulator_point::ManipulatorPoint; -use super::subpath::Subpath; -use crate::message_prelude::{DocumentMessage, Message}; +use crate::messages::prelude::*; -use graphene::layers::vector::constants::ManipulatorType; +use graphene::layers::vector::consts::ManipulatorType; +use graphene::layers::vector::manipulator_group::ManipulatorGroup; +use graphene::layers::vector::manipulator_point::ManipulatorPoint; +use graphene::layers::vector::subpath::Subpath; use graphene::{LayerId, Operation}; use glam::DVec2; diff --git a/editor/src/viewport_tools/snapping.rs b/editor/src/messages/tool/common_functionality/snapping.rs similarity index 98% rename from editor/src/viewport_tools/snapping.rs rename to editor/src/messages/tool/common_functionality/snapping.rs index ff80492a09..2e990e9dc4 100644 --- a/editor/src/viewport_tools/snapping.rs +++ b/editor/src/messages/tool/common_functionality/snapping.rs @@ -1,13 +1,13 @@ +use crate::application::generate_uuid; use crate::consts::{ COLOR_ACCENT, SNAP_AXIS_OVERLAY_FADE_DISTANCE, SNAP_AXIS_TOLERANCE, SNAP_AXIS_UNSNAPPED_OPACITY, SNAP_POINT_OVERLAY_FADE_FAR, SNAP_POINT_OVERLAY_FADE_NEAR, SNAP_POINT_SIZE, SNAP_POINT_TOLERANCE, SNAP_POINT_UNSNAPPED_OPACITY, }; -use crate::document::DocumentMessageHandler; -use crate::message_prelude::*; +use crate::messages::prelude::*; use graphene::layers::layer_info::{Layer, LayerDataType}; use graphene::layers::style::{self, Stroke}; -use graphene::layers::vector::constants::ManipulatorType; +use graphene::layers::vector::consts::ManipulatorType; use graphene::{LayerId, Operation}; use glam::{DAffine2, DVec2}; @@ -22,28 +22,6 @@ struct SnapOverlays { point_index: usize, } -/// Handles snapping and snap overlays -#[derive(Debug, Clone, Default)] -pub struct SnapHandler { - point_targets: Option>, - bound_targets: Option>, - snap_overlays: SnapOverlays, - snap_x: bool, - snap_y: bool, -} - -/// Converts a bounding box into a set of points for snapping -/// -/// Puts a point in the middle of each edge (top, bottom, left, right) -pub fn expand_bounds([bound1, bound2]: [DVec2; 2]) -> [DVec2; 4] { - [ - DVec2::new((bound1.x + bound2.x) / 2., bound1.y), - DVec2::new((bound1.x + bound2.x) / 2., bound2.y), - DVec2::new(bound1.x, (bound1.y + bound2.y) / 2.), - DVec2::new(bound2.x, (bound1.y + bound2.y) / 2.), - ] -} - impl SnapOverlays { /// Draws an overlay (axis or point) with the correct transform and fade opacity, reusing lines from the pool if available. fn add_overlay(is_axis: bool, responses: &mut VecDeque, transform: [f64; 6], opacity: Option, index: usize, overlay_paths: &mut Vec>) { @@ -179,7 +157,17 @@ impl SnapOverlays { } } -impl SnapHandler { +/// Handles snapping and snap overlays +#[derive(Debug, Clone, Default)] +pub struct SnapManager { + point_targets: Option>, + bound_targets: Option>, + snap_overlays: SnapOverlays, + snap_x: bool, + snap_y: bool, +} + +impl SnapManager { /// Computes the necessary translation to the layer to snap it (as well as updating necessary overlays) fn calculate_snap(&mut self, targets: R, responses: &mut VecDeque) -> DVec2 where @@ -317,3 +305,15 @@ impl SnapHandler { self.point_targets = None; } } + +/// Converts a bounding box into a set of points for snapping +/// +/// Puts a point in the middle of each edge (top, bottom, left, right) +pub fn expand_bounds([bound1, bound2]: [DVec2; 2]) -> [DVec2; 4] { + [ + DVec2::new((bound1.x + bound2.x) / 2., bound1.y), + DVec2::new((bound1.x + bound2.x) / 2., bound2.y), + DVec2::new(bound1.x, (bound1.y + bound2.y) / 2.), + DVec2::new(bound2.x, (bound1.y + bound2.y) / 2.), + ] +} diff --git a/editor/src/viewport_tools/tools/shared/transformation_cage.rs b/editor/src/messages/tool/common_functionality/transformation_cage.rs similarity index 97% rename from editor/src/viewport_tools/tools/shared/transformation_cage.rs rename to editor/src/messages/tool/common_functionality/transformation_cage.rs index 5ec5fd74b3..10d983ce5c 100644 --- a/editor/src/viewport_tools/tools/shared/transformation_cage.rs +++ b/editor/src/messages/tool/common_functionality/transformation_cage.rs @@ -1,11 +1,12 @@ +use crate::application::generate_uuid; use crate::consts::{BOUNDS_ROTATE_THRESHOLD, BOUNDS_SELECT_THRESHOLD, COLOR_ACCENT, MANIPULATOR_GROUP_MARKER_SIZE, SELECTION_DRAG_ANGLE}; -use crate::document::transformation::OriginalTransforms; -use crate::frontend::utility_types::MouseCursorIcon; -use crate::input::InputPreprocessorMessageHandler; -use crate::message_prelude::*; +use crate::messages::frontend::utility_types::MouseCursorIcon; +use crate::messages::portfolio::document::utility_types::transformation::OriginalTransforms; +use crate::messages::prelude::*; use graphene::color::Color; use graphene::layers::style::{self, Fill, Stroke}; +use graphene::LayerId; use graphene::Operation; use glam::{DAffine2, DVec2}; diff --git a/editor/src/messages/tool/mod.rs b/editor/src/messages/tool/mod.rs new file mode 100644 index 0000000000..1c06aeb771 --- /dev/null +++ b/editor/src/messages/tool/mod.rs @@ -0,0 +1,11 @@ +mod tool_message; +mod tool_message_handler; + +pub mod common_functionality; +pub mod tool_messages; +pub mod utility_types; + +#[doc(inline)] +pub use tool_message::{ToolMessage, ToolMessageDiscriminant}; +#[doc(inline)] +pub use tool_message_handler::ToolMessageHandler; diff --git a/editor/src/viewport_tools/tool_message.rs b/editor/src/messages/tool/tool_message.rs similarity index 97% rename from editor/src/viewport_tools/tool_message.rs rename to editor/src/messages/tool/tool_message.rs index 1417ec26f4..9785a11ebe 100644 --- a/editor/src/viewport_tools/tool_message.rs +++ b/editor/src/messages/tool/tool_message.rs @@ -1,5 +1,5 @@ -use super::tool::ToolType; -use crate::message_prelude::*; +use super::utility_types::ToolType; +use crate::messages::prelude::*; use graphene::color::Color; diff --git a/editor/src/viewport_tools/tool_message_handler.rs b/editor/src/messages/tool/tool_message_handler.rs similarity index 51% rename from editor/src/viewport_tools/tool_message_handler.rs rename to editor/src/messages/tool/tool_message_handler.rs index 169d26e29e..93855130e8 100644 --- a/editor/src/viewport_tools/tool_message_handler.rs +++ b/editor/src/messages/tool/tool_message_handler.rs @@ -1,12 +1,9 @@ -use super::tool::{tool_message_to_tool_type, ToolFsmState}; -use crate::document::DocumentMessageHandler; -use crate::input::input_mapper::action_keys::action_shortcut; -use crate::input::InputPreprocessorMessageHandler; -use crate::layout::layout_message::LayoutTarget; -use crate::layout::widgets::PropertyHolder; -use crate::layout::widgets::{IconButton, Layout, LayoutGroup, SwatchPairInput, Widget, WidgetCallback, WidgetHolder, WidgetLayout}; -use crate::message_prelude::*; -use crate::viewport_tools::tool::{DocumentToolData, ToolType}; +use super::utility_types::{tool_message_to_tool_type, ToolFsmState}; +use crate::application::generate_uuid; +use crate::messages::layout::utility_types::layout_widget::PropertyHolder; +use crate::messages::layout::utility_types::misc::LayoutTarget; +use crate::messages::prelude::*; +use crate::messages::tool::utility_types::ToolType; use graphene::color::Color; use graphene::layers::text_layer::FontCache; @@ -20,46 +17,44 @@ pub struct ToolMessageHandler { impl MessageHandler for ToolMessageHandler { #[remain::check] - fn process_action(&mut self, message: ToolMessage, data: (&DocumentMessageHandler, &InputPreprocessorMessageHandler, &FontCache), responses: &mut VecDeque) { - use ToolMessage::*; - + fn process_message(&mut self, message: ToolMessage, data: (&DocumentMessageHandler, &InputPreprocessorMessageHandler, &FontCache), responses: &mut VecDeque) { let (document, input, font_cache) = data; #[remain::sorted] match message { // Messages #[remain::unsorted] - ActivateToolSelect => responses.push_front(ToolMessage::ActivateTool { tool_type: ToolType::Select }.into()), + ToolMessage::ActivateToolSelect => responses.push_front(ToolMessage::ActivateTool { tool_type: ToolType::Select }.into()), #[remain::unsorted] - ActivateToolArtboard => responses.push_front(ToolMessage::ActivateTool { tool_type: ToolType::Artboard }.into()), + ToolMessage::ActivateToolArtboard => responses.push_front(ToolMessage::ActivateTool { tool_type: ToolType::Artboard }.into()), #[remain::unsorted] - ActivateToolNavigate => responses.push_front(ToolMessage::ActivateTool { tool_type: ToolType::Navigate }.into()), + ToolMessage::ActivateToolNavigate => responses.push_front(ToolMessage::ActivateTool { tool_type: ToolType::Navigate }.into()), #[remain::unsorted] - ActivateToolEyedropper => responses.push_front(ToolMessage::ActivateTool { tool_type: ToolType::Eyedropper }.into()), + ToolMessage::ActivateToolEyedropper => responses.push_front(ToolMessage::ActivateTool { tool_type: ToolType::Eyedropper }.into()), #[remain::unsorted] - ActivateToolText => responses.push_front(ToolMessage::ActivateTool { tool_type: ToolType::Text }.into()), + ToolMessage::ActivateToolText => responses.push_front(ToolMessage::ActivateTool { tool_type: ToolType::Text }.into()), #[remain::unsorted] - ActivateToolFill => responses.push_front(ToolMessage::ActivateTool { tool_type: ToolType::Fill }.into()), + ToolMessage::ActivateToolFill => responses.push_front(ToolMessage::ActivateTool { tool_type: ToolType::Fill }.into()), #[remain::unsorted] - ActivateToolGradient => responses.push_front(ToolMessage::ActivateTool { tool_type: ToolType::Gradient }.into()), + ToolMessage::ActivateToolGradient => responses.push_front(ToolMessage::ActivateTool { tool_type: ToolType::Gradient }.into()), #[remain::unsorted] - ActivateToolPath => responses.push_front(ToolMessage::ActivateTool { tool_type: ToolType::Path }.into()), + ToolMessage::ActivateToolPath => responses.push_front(ToolMessage::ActivateTool { tool_type: ToolType::Path }.into()), #[remain::unsorted] - ActivateToolPen => responses.push_front(ToolMessage::ActivateTool { tool_type: ToolType::Pen }.into()), + ToolMessage::ActivateToolPen => responses.push_front(ToolMessage::ActivateTool { tool_type: ToolType::Pen }.into()), #[remain::unsorted] - ActivateToolFreehand => responses.push_front(ToolMessage::ActivateTool { tool_type: ToolType::Freehand }.into()), + ToolMessage::ActivateToolFreehand => responses.push_front(ToolMessage::ActivateTool { tool_type: ToolType::Freehand }.into()), #[remain::unsorted] - ActivateToolSpline => responses.push_front(ToolMessage::ActivateTool { tool_type: ToolType::Spline }.into()), + ToolMessage::ActivateToolSpline => responses.push_front(ToolMessage::ActivateTool { tool_type: ToolType::Spline }.into()), #[remain::unsorted] - ActivateToolLine => responses.push_front(ToolMessage::ActivateTool { tool_type: ToolType::Line }.into()), + ToolMessage::ActivateToolLine => responses.push_front(ToolMessage::ActivateTool { tool_type: ToolType::Line }.into()), #[remain::unsorted] - ActivateToolRectangle => responses.push_front(ToolMessage::ActivateTool { tool_type: ToolType::Rectangle }.into()), + ToolMessage::ActivateToolRectangle => responses.push_front(ToolMessage::ActivateTool { tool_type: ToolType::Rectangle }.into()), #[remain::unsorted] - ActivateToolEllipse => responses.push_front(ToolMessage::ActivateTool { tool_type: ToolType::Ellipse }.into()), + ToolMessage::ActivateToolEllipse => responses.push_front(ToolMessage::ActivateTool { tool_type: ToolType::Ellipse }.into()), #[remain::unsorted] - ActivateToolShape => responses.push_front(ToolMessage::ActivateTool { tool_type: ToolType::Shape }.into()), + ToolMessage::ActivateToolShape => responses.push_front(ToolMessage::ActivateTool { tool_type: ToolType::Shape }.into()), - ActivateTool { tool_type } => { + ToolMessage::ActivateTool { tool_type } => { let tool_data = &mut self.tool_state.tool_data; let document_data = &self.tool_state.document_tool_data; let old_tool = tool_data.active_tool_type; @@ -72,13 +67,13 @@ impl MessageHandler { + ToolMessage::DeactivateTools => { let tool_data = &mut self.tool_state.tool_data; tool_data.tools.get(&tool_data.active_tool_type).unwrap().deactivate(responses); } - InitTools => { + ToolMessage::InitTools => { let tool_data = &mut self.tool_state.tool_data; let document_data = &self.tool_state.document_tool_data; let active_tool = &tool_data.active_tool_type; @@ -127,32 +122,32 @@ impl MessageHandler { + ToolMessage::ResetColors => { let document_data = &mut self.tool_state.document_tool_data; document_data.primary_color = Color::BLACK; document_data.secondary_color = Color::WHITE; - update_working_colors(document_data, responses); + document_data.update_working_colors(responses); } - SelectPrimaryColor { color } => { + ToolMessage::SelectPrimaryColor { color } => { let document_data = &mut self.tool_state.document_tool_data; document_data.primary_color = color; - update_working_colors(&self.tool_state.document_tool_data, responses); + self.tool_state.document_tool_data.update_working_colors(responses); } - SelectRandomPrimaryColor => { + ToolMessage::SelectRandomPrimaryColor => { // Select a random primary color (rgba) based on an UUID let document_data = &mut self.tool_state.document_tool_data; @@ -163,27 +158,27 @@ impl MessageHandler { + ToolMessage::SelectSecondaryColor { color } => { let document_data = &mut self.tool_state.document_tool_data; document_data.secondary_color = color; - update_working_colors(document_data, responses); + document_data.update_working_colors(responses); } - SwapColors => { + ToolMessage::SwapColors => { let document_data = &mut self.tool_state.document_tool_data; std::mem::swap(&mut document_data.primary_color, &mut document_data.secondary_color); - update_working_colors(document_data, responses); + document_data.update_working_colors(responses); } // Sub-messages #[remain::unsorted] tool_message => { let tool_type = match &tool_message { - UpdateCursor | UpdateHints => self.tool_state.tool_data.active_tool_type, + ToolMessage::UpdateCursor | ToolMessage::UpdateHints => self.tool_state.tool_data.active_tool_type, tool_message => tool_message_to_tool_type(tool_message), }; let document_data = &self.tool_state.document_tool_data; @@ -191,7 +186,7 @@ impl MessageHandler) { - let layout = WidgetLayout::new(vec![ - LayoutGroup::Row { - widgets: vec![WidgetHolder::new(Widget::SwatchPairInput(SwatchPairInput { - primary: document_data.primary_color, - secondary: document_data.secondary_color, - }))], - }, - LayoutGroup::Row { - widgets: vec![ - WidgetHolder::new(Widget::IconButton(IconButton { - size: 16, - icon: "Swap".into(), - tooltip: "Swap".into(), - tooltip_shortcut: action_shortcut!(ToolMessageDiscriminant::SwapColors), - on_update: WidgetCallback::new(|_| ToolMessage::SwapColors.into()), - ..Default::default() - })), - WidgetHolder::new(Widget::IconButton(IconButton { - size: 16, - icon: "ResetColors".into(), - tooltip: "Reset".into(), - tooltip_shortcut: action_shortcut!(ToolMessageDiscriminant::ResetColors), - on_update: WidgetCallback::new(|_| ToolMessage::ResetColors.into()), - ..Default::default() - })), - ], - }, - ]); - - responses.push_back( - LayoutMessage::SendLayout { - layout: Layout::WidgetLayout(layout), - layout_target: LayoutTarget::WorkingColors, - } - .into(), - ); -} diff --git a/editor/src/viewport_tools/tools/artboard_tool.rs b/editor/src/messages/tool/tool_messages/artboard_tool.rs similarity index 84% rename from editor/src/viewport_tools/tools/artboard_tool.rs rename to editor/src/messages/tool/tool_messages/artboard_tool.rs index 74c4414045..7e66adb0fd 100644 --- a/editor/src/viewport_tools/tools/artboard_tool.rs +++ b/editor/src/messages/tool/tool_messages/artboard_tool.rs @@ -1,16 +1,17 @@ +use crate::application::generate_uuid; use crate::consts::SELECTION_TOLERANCE; -use crate::document::utility_types::TargetDocument; -use crate::frontend::utility_types::MouseCursorIcon; -use crate::input::keyboard::{Key, MouseMotion}; -use crate::layout::widgets::PropertyHolder; -use crate::message_prelude::*; -use crate::misc::{HintData, HintGroup, HintInfo, KeysGroup}; -use crate::viewport_tools::snapping::SnapHandler; -use crate::viewport_tools::tool::{Fsm, SignalToMessageMap, ToolActionHandlerData, ToolMetadata, ToolTransition, ToolType}; +use crate::messages::frontend::utility_types::MouseCursorIcon; +use crate::messages::input_mapper::utility_types::input_keyboard::{Key, KeysGroup, MouseMotion}; +use crate::messages::layout::utility_types::layout_widget::PropertyHolder; +use crate::messages::portfolio::document::utility_types::misc::TargetDocument; +use crate::messages::prelude::*; +use crate::messages::tool::common_functionality::snapping::SnapManager; +use crate::messages::tool::common_functionality::transformation_cage::*; +use crate::messages::tool::utility_types::{EventToMessageMap, Fsm, ToolActionHandlerData, ToolMetadata, ToolTransition, ToolType}; +use crate::messages::tool::utility_types::{HintData, HintGroup, HintInfo}; use graphene::intersection::Quad; - -use super::shared::transformation_cage::*; +use graphene::LayerId; use glam::{DVec2, Vec2Swizzles}; use serde::{Deserialize, Serialize}; @@ -48,24 +49,24 @@ impl ToolMetadata for ArtboardTool { fn tooltip(&self) -> String { "Artboard Tool".into() } - fn tool_type(&self) -> crate::viewport_tools::tool::ToolType { + fn tool_type(&self) -> crate::messages::tool::utility_types::ToolType { ToolType::Artboard } } impl<'a> MessageHandler> for ArtboardTool { - fn process_action(&mut self, action: ToolMessage, data: ToolActionHandlerData<'a>, responses: &mut VecDeque) { - if action == ToolMessage::UpdateHints { + fn process_message(&mut self, message: ToolMessage, data: ToolActionHandlerData<'a>, responses: &mut VecDeque) { + if message == ToolMessage::UpdateHints { self.fsm_state.update_hints(responses); return; } - if action == ToolMessage::UpdateCursor { + if message == ToolMessage::UpdateCursor { responses.push_back(FrontendMessage::UpdateMouseCursor { cursor: MouseCursorIcon::Default }.into()); return; } - let new_state = self.fsm_state.transition(action, &mut self.data, data, &(), responses); + let new_state = self.fsm_state.transition(message, &mut self.data, data, &(), responses); if self.fsm_state != new_state { self.fsm_state = new_state; @@ -85,8 +86,8 @@ impl<'a> MessageHandler> for ArtboardTool impl PropertyHolder for ArtboardTool {} impl ToolTransition for ArtboardTool { - fn signal_to_message_map(&self) -> SignalToMessageMap { - SignalToMessageMap { + fn event_to_message_map(&self) -> EventToMessageMap { + EventToMessageMap { document_dirty: Some(ArtboardToolMessage::DocumentIsDirty.into()), tool_abort: Some(ArtboardToolMessage::Abort.into()), selection_changed: None, @@ -112,7 +113,7 @@ impl Default for ArtboardToolFsmState { struct ArtboardToolData { bounding_box_overlays: Option, selected_board: Option, - snap_handler: SnapHandler, + snap_manager: SnapManager, cursor: MouseCursorIcon, drag_start: DVec2, drag_current: DVec2, @@ -184,9 +185,9 @@ impl Fsm for ArtboardToolFsmState { let snap_y = selected_edges.0 || selected_edges.1; tool_data - .snap_handler + .snap_manager .start_snap(document, document.bounding_boxes(None, Some(tool_data.selected_board.unwrap()), font_cache), snap_x, snap_y); - tool_data.snap_handler.add_all_document_handles(document, &[], &[], &[]); + tool_data.snap_manager.add_all_document_handles(document, &[], &[], &[]); ArtboardToolFsmState::ResizingBounds } else { @@ -194,14 +195,14 @@ impl Fsm for ArtboardToolFsmState { let quad = Quad::from_box([input.mouse.position - tolerance, input.mouse.position + tolerance]); let intersection = document.artboard_message_handler.artboards_graphene_document.intersects_quad_root(quad, font_cache); - responses.push_back(BroadcastSignal::DocumentIsDirty.into()); + responses.push_back(BroadcastEvent::DocumentIsDirty.into()); if let Some(intersection) = intersection.last() { tool_data.selected_board = Some(intersection[0]); tool_data - .snap_handler + .snap_manager .start_snap(document, document.bounding_boxes(None, Some(intersection[0]), font_cache), true, true); - tool_data.snap_handler.add_all_document_handles(document, &[], &[], &[]); + tool_data.snap_manager.add_all_document_handles(document, &[], &[], &[]); responses.push_back( PropertiesPanelMessage::SetActiveLayers { @@ -216,8 +217,8 @@ impl Fsm for ArtboardToolFsmState { let id = generate_uuid(); tool_data.selected_board = Some(id); - tool_data.snap_handler.start_snap(document, document.bounding_boxes(None, Some(id), font_cache), true, true); - tool_data.snap_handler.add_all_document_handles(document, &[], &[], &[]); + tool_data.snap_manager.start_snap(document, document.bounding_boxes(None, Some(id), font_cache), true, true); + tool_data.snap_manager.add_all_document_handles(document, &[], &[], &[]); responses.push_back( ArtboardMessage::AddArtboard { @@ -241,7 +242,7 @@ impl Fsm for ArtboardToolFsmState { let constrain_square = input.keyboard.get(constrain_axis_or_aspect as usize); let mouse_position = input.mouse.position; - let snapped_mouse_position = tool_data.snap_handler.snap_position(responses, document, mouse_position); + let snapped_mouse_position = tool_data.snap_manager.snap_position(responses, document, mouse_position); let (mut position, size) = movement.new_size(snapped_mouse_position, bounds.transform, from_center, constrain_square); if from_center { @@ -257,7 +258,7 @@ impl Fsm for ArtboardToolFsmState { .into(), ); - responses.push_back(BroadcastSignal::DocumentIsDirty.into()); + responses.push_back(BroadcastEvent::DocumentIsDirty.into()); } } ArtboardToolFsmState::ResizingBounds @@ -270,7 +271,7 @@ impl Fsm for ArtboardToolFsmState { let mouse_delta = mouse_position - tool_data.drag_current; let snap = bounds.evaluate_transform_handle_positions().into_iter().collect(); - let closest_move = tool_data.snap_handler.snap_layers(responses, document, snap, mouse_delta); + let closest_move = tool_data.snap_manager.snap_layers(responses, document, snap, mouse_delta); let size = bounds.bounds[1] - bounds.bounds[0]; @@ -285,7 +286,7 @@ impl Fsm for ArtboardToolFsmState { .into(), ); - responses.push_back(BroadcastSignal::DocumentIsDirty.into()); + responses.push_back(BroadcastEvent::DocumentIsDirty.into()); tool_data.drag_current = mouse_position + closest_move; } @@ -293,7 +294,7 @@ impl Fsm for ArtboardToolFsmState { } (ArtboardToolFsmState::Drawing, ArtboardToolMessage::PointerMove { constrain_axis_or_aspect, center }) => { let mouse_position = input.mouse.position; - let snapped_mouse_position = tool_data.snap_handler.snap_position(responses, document, mouse_position); + let snapped_mouse_position = tool_data.snap_manager.snap_position(responses, document, mouse_position); let root_transform = document.graphene_document.root.transform.inverse(); @@ -331,7 +332,7 @@ impl Fsm for ArtboardToolFsmState { .into(), ); - responses.push_back(BroadcastSignal::DocumentIsDirty.into()); + responses.push_back(BroadcastEvent::DocumentIsDirty.into()); ArtboardToolFsmState::Drawing } @@ -346,7 +347,7 @@ impl Fsm for ArtboardToolFsmState { ArtboardToolFsmState::Ready } (ArtboardToolFsmState::ResizingBounds, ArtboardToolMessage::PointerUp) => { - tool_data.snap_handler.cleanup(responses); + tool_data.snap_manager.cleanup(responses); if let Some(bounds) = &mut tool_data.bounding_box_overlays { bounds.original_transforms.clear(); @@ -355,18 +356,18 @@ impl Fsm for ArtboardToolFsmState { ArtboardToolFsmState::Ready } (ArtboardToolFsmState::Drawing, ArtboardToolMessage::PointerUp) => { - tool_data.snap_handler.cleanup(responses); + tool_data.snap_manager.cleanup(responses); if let Some(bounds) = &mut tool_data.bounding_box_overlays { bounds.original_transforms.clear(); } - responses.push_back(BroadcastSignal::DocumentIsDirty.into()); + responses.push_back(BroadcastEvent::DocumentIsDirty.into()); ArtboardToolFsmState::Ready } (ArtboardToolFsmState::Dragging, ArtboardToolMessage::PointerUp) => { - tool_data.snap_handler.cleanup(responses); + tool_data.snap_manager.cleanup(responses); if let Some(bounds) = &mut tool_data.bounding_box_overlays { bounds.original_transforms.clear(); @@ -377,7 +378,7 @@ impl Fsm for ArtboardToolFsmState { (_, ArtboardToolMessage::DeleteSelected) => { if let Some(artboard) = tool_data.selected_board.take() { responses.push_back(ArtboardMessage::DeleteArtboard { artboard }.into()); - responses.push_back(BroadcastSignal::DocumentIsDirty.into()); + responses.push_back(BroadcastEvent::DocumentIsDirty.into()); } ArtboardToolFsmState::Ready } @@ -395,7 +396,7 @@ impl Fsm for ArtboardToolFsmState { .into(), ); - tool_data.snap_handler.cleanup(responses); + tool_data.snap_manager.cleanup(responses); ArtboardToolFsmState::Ready } _ => self, diff --git a/editor/src/viewport_tools/tools/ellipse_tool.rs b/editor/src/messages/tool/tool_messages/ellipse_tool.rs similarity index 83% rename from editor/src/viewport_tools/tools/ellipse_tool.rs rename to editor/src/messages/tool/tool_messages/ellipse_tool.rs index 9f86d7c157..f06a385c86 100644 --- a/editor/src/viewport_tools/tools/ellipse_tool.rs +++ b/editor/src/messages/tool/tool_messages/ellipse_tool.rs @@ -1,11 +1,11 @@ -use super::shared::resize::Resize; use crate::consts::DRAG_THRESHOLD; -use crate::frontend::utility_types::MouseCursorIcon; -use crate::input::keyboard::{Key, MouseMotion}; -use crate::layout::widgets::PropertyHolder; -use crate::message_prelude::*; -use crate::misc::{HintData, HintGroup, HintInfo, KeysGroup}; -use crate::viewport_tools::tool::{Fsm, SignalToMessageMap, ToolActionHandlerData, ToolMetadata, ToolTransition, ToolType}; +use crate::messages::frontend::utility_types::MouseCursorIcon; +use crate::messages::input_mapper::utility_types::input_keyboard::{Key, KeysGroup, MouseMotion}; +use crate::messages::layout::utility_types::layout_widget::PropertyHolder; +use crate::messages::prelude::*; +use crate::messages::tool::common_functionality::resize::Resize; +use crate::messages::tool::utility_types::{EventToMessageMap, Fsm, ToolActionHandlerData, ToolMetadata, ToolTransition, ToolType}; +use crate::messages::tool::utility_types::{HintData, HintGroup, HintInfo}; use graphene::layers::style; use graphene::Operation; @@ -43,7 +43,7 @@ impl ToolMetadata for EllipseTool { fn tooltip(&self) -> String { "Ellipse Tool".into() } - fn tool_type(&self) -> crate::viewport_tools::tool::ToolType { + fn tool_type(&self) -> crate::messages::tool::utility_types::ToolType { ToolType::Ellipse } } @@ -51,18 +51,18 @@ impl ToolMetadata for EllipseTool { impl PropertyHolder for EllipseTool {} impl<'a> MessageHandler> for EllipseTool { - fn process_action(&mut self, action: ToolMessage, data: ToolActionHandlerData<'a>, responses: &mut VecDeque) { - if action == ToolMessage::UpdateHints { + fn process_message(&mut self, message: ToolMessage, data: ToolActionHandlerData<'a>, responses: &mut VecDeque) { + if message == ToolMessage::UpdateHints { self.fsm_state.update_hints(responses); return; } - if action == ToolMessage::UpdateCursor { + if message == ToolMessage::UpdateCursor { self.fsm_state.update_cursor(responses); return; } - let new_state = self.fsm_state.transition(action, &mut self.data, data, &(), responses); + let new_state = self.fsm_state.transition(message, &mut self.data, data, &(), responses); if self.fsm_state != new_state { self.fsm_state = new_state; @@ -88,8 +88,8 @@ impl<'a> MessageHandler> for EllipseTool } impl ToolTransition for EllipseTool { - fn signal_to_message_map(&self) -> SignalToMessageMap { - SignalToMessageMap { + fn event_to_message_map(&self) -> EventToMessageMap { + EventToMessageMap { document_dirty: None, tool_abort: Some(EllipseToolMessage::Abort.into()), selection_changed: None, diff --git a/editor/src/viewport_tools/tools/eyedropper_tool.rs b/editor/src/messages/tool/tool_messages/eyedropper_tool.rs similarity index 81% rename from editor/src/viewport_tools/tools/eyedropper_tool.rs rename to editor/src/messages/tool/tool_messages/eyedropper_tool.rs index 10aa443a1a..cc223e5096 100644 --- a/editor/src/viewport_tools/tools/eyedropper_tool.rs +++ b/editor/src/messages/tool/tool_messages/eyedropper_tool.rs @@ -1,10 +1,10 @@ use crate::consts::SELECTION_TOLERANCE; -use crate::frontend::utility_types::MouseCursorIcon; -use crate::input::keyboard::MouseMotion; -use crate::layout::widgets::PropertyHolder; -use crate::message_prelude::*; -use crate::misc::{HintData, HintGroup, HintInfo}; -use crate::viewport_tools::tool::{Fsm, SignalToMessageMap, ToolActionHandlerData, ToolMetadata, ToolTransition, ToolType}; +use crate::messages::frontend::utility_types::MouseCursorIcon; +use crate::messages::input_mapper::utility_types::input_keyboard::MouseMotion; +use crate::messages::layout::utility_types::layout_widget::PropertyHolder; +use crate::messages::prelude::*; +use crate::messages::tool::utility_types::{EventToMessageMap, Fsm, ToolActionHandlerData, ToolMetadata, ToolTransition, ToolType}; +use crate::messages::tool::utility_types::{HintData, HintGroup, HintInfo}; use graphene::intersection::Quad; use graphene::layers::layer_info::LayerDataType; @@ -38,7 +38,7 @@ impl ToolMetadata for EyedropperTool { fn tooltip(&self) -> String { "Eyedropper Tool".into() } - fn tool_type(&self) -> crate::viewport_tools::tool::ToolType { + fn tool_type(&self) -> crate::messages::tool::utility_types::ToolType { ToolType::Eyedropper } } @@ -46,18 +46,18 @@ impl ToolMetadata for EyedropperTool { impl PropertyHolder for EyedropperTool {} impl<'a> MessageHandler> for EyedropperTool { - fn process_action(&mut self, action: ToolMessage, data: ToolActionHandlerData<'a>, responses: &mut VecDeque) { - if action == ToolMessage::UpdateHints { + fn process_message(&mut self, message: ToolMessage, data: ToolActionHandlerData<'a>, responses: &mut VecDeque) { + if message == ToolMessage::UpdateHints { self.fsm_state.update_hints(responses); return; } - if action == ToolMessage::UpdateCursor { + if message == ToolMessage::UpdateCursor { self.fsm_state.update_cursor(responses); return; } - let new_state = self.fsm_state.transition(action, &mut self.data, data, &(), responses); + let new_state = self.fsm_state.transition(message, &mut self.data, data, &(), responses); if self.fsm_state != new_state { self.fsm_state = new_state; @@ -73,8 +73,8 @@ impl<'a> MessageHandler> for EyedropperTo } impl ToolTransition for EyedropperTool { - fn signal_to_message_map(&self) -> SignalToMessageMap { - SignalToMessageMap { + fn event_to_message_map(&self) -> EventToMessageMap { + EventToMessageMap { document_dirty: None, tool_abort: Some(EyedropperToolMessage::Abort.into()), selection_changed: None, diff --git a/editor/src/viewport_tools/tools/fill_tool.rs b/editor/src/messages/tool/tool_messages/fill_tool.rs similarity index 80% rename from editor/src/viewport_tools/tools/fill_tool.rs rename to editor/src/messages/tool/tool_messages/fill_tool.rs index 11f2be4085..767bbc1d6e 100644 --- a/editor/src/viewport_tools/tools/fill_tool.rs +++ b/editor/src/messages/tool/tool_messages/fill_tool.rs @@ -1,10 +1,10 @@ use crate::consts::SELECTION_TOLERANCE; -use crate::frontend::utility_types::MouseCursorIcon; -use crate::input::keyboard::MouseMotion; -use crate::layout::widgets::PropertyHolder; -use crate::message_prelude::*; -use crate::misc::{HintData, HintGroup, HintInfo}; -use crate::viewport_tools::tool::{Fsm, SignalToMessageMap, ToolActionHandlerData, ToolMetadata, ToolTransition, ToolType}; +use crate::messages::frontend::utility_types::MouseCursorIcon; +use crate::messages::input_mapper::utility_types::input_keyboard::MouseMotion; +use crate::messages::layout::utility_types::layout_widget::PropertyHolder; +use crate::messages::prelude::*; +use crate::messages::tool::utility_types::{EventToMessageMap, Fsm, ToolActionHandlerData, ToolMetadata, ToolTransition, ToolType}; +use crate::messages::tool::utility_types::{HintData, HintGroup, HintInfo}; use graphene::intersection::Quad; use graphene::Operation; @@ -39,7 +39,7 @@ impl ToolMetadata for FillTool { fn tooltip(&self) -> String { "Fill Tool".into() } - fn tool_type(&self) -> crate::viewport_tools::tool::ToolType { + fn tool_type(&self) -> crate::messages::tool::utility_types::ToolType { ToolType::Fill } } @@ -47,18 +47,18 @@ impl ToolMetadata for FillTool { impl PropertyHolder for FillTool {} impl<'a> MessageHandler> for FillTool { - fn process_action(&mut self, action: ToolMessage, data: ToolActionHandlerData<'a>, responses: &mut VecDeque) { - if action == ToolMessage::UpdateHints { + fn process_message(&mut self, message: ToolMessage, data: ToolActionHandlerData<'a>, responses: &mut VecDeque) { + if message == ToolMessage::UpdateHints { self.fsm_state.update_hints(responses); return; } - if action == ToolMessage::UpdateCursor { + if message == ToolMessage::UpdateCursor { self.fsm_state.update_cursor(responses); return; } - let new_state = self.fsm_state.transition(action, &mut self.data, data, &(), responses); + let new_state = self.fsm_state.transition(message, &mut self.data, data, &(), responses); if self.fsm_state != new_state { self.fsm_state = new_state; @@ -74,8 +74,8 @@ impl<'a> MessageHandler> for FillTool { } impl ToolTransition for FillTool { - fn signal_to_message_map(&self) -> SignalToMessageMap { - SignalToMessageMap { + fn event_to_message_map(&self) -> EventToMessageMap { + EventToMessageMap { document_dirty: None, tool_abort: Some(FillToolMessage::Abort.into()), selection_changed: None, diff --git a/editor/src/viewport_tools/tools/freehand_tool.rs b/editor/src/messages/tool/tool_messages/freehand_tool.rs similarity index 84% rename from editor/src/viewport_tools/tools/freehand_tool.rs rename to editor/src/messages/tool/tool_messages/freehand_tool.rs index 3af5fa1f32..ecb5f29705 100644 --- a/editor/src/viewport_tools/tools/freehand_tool.rs +++ b/editor/src/messages/tool/tool_messages/freehand_tool.rs @@ -1,11 +1,13 @@ -use crate::frontend::utility_types::MouseCursorIcon; -use crate::input::keyboard::MouseMotion; -use crate::layout::widgets::{Layout, LayoutGroup, NumberInput, PropertyHolder, Widget, WidgetCallback, WidgetHolder, WidgetLayout}; -use crate::message_prelude::*; -use crate::misc::{HintData, HintGroup, HintInfo}; -use crate::viewport_tools::tool::{DocumentToolData, Fsm, SignalToMessageMap, ToolActionHandlerData, ToolMetadata, ToolTransition, ToolType}; +use crate::messages::frontend::utility_types::MouseCursorIcon; +use crate::messages::input_mapper::utility_types::input_keyboard::MouseMotion; +use crate::messages::layout::utility_types::layout_widget::{Layout, LayoutGroup, PropertyHolder, Widget, WidgetCallback, WidgetHolder, WidgetLayout}; +use crate::messages::layout::utility_types::widgets::input_widgets::NumberInput; +use crate::messages::prelude::*; +use crate::messages::tool::utility_types::{DocumentToolData, EventToMessageMap, Fsm, ToolActionHandlerData, ToolMetadata, ToolTransition, ToolType}; +use crate::messages::tool::utility_types::{HintData, HintGroup, HintInfo}; use graphene::layers::style; +use graphene::LayerId; use graphene::Operation; use glam::{DAffine2, DVec2}; @@ -62,7 +64,7 @@ impl ToolMetadata for FreehandTool { fn tooltip(&self) -> String { "Freehand Tool".into() } - fn tool_type(&self) -> crate::viewport_tools::tool::ToolType { + fn tool_type(&self) -> crate::messages::tool::utility_types::ToolType { ToolType::Freehand } } @@ -84,25 +86,25 @@ impl PropertyHolder for FreehandTool { } impl<'a> MessageHandler> for FreehandTool { - fn process_action(&mut self, action: ToolMessage, data: ToolActionHandlerData<'a>, responses: &mut VecDeque) { - if action == ToolMessage::UpdateHints { + fn process_message(&mut self, message: ToolMessage, data: ToolActionHandlerData<'a>, responses: &mut VecDeque) { + if message == ToolMessage::UpdateHints { self.fsm_state.update_hints(responses); return; } - if action == ToolMessage::UpdateCursor { + if message == ToolMessage::UpdateCursor { self.fsm_state.update_cursor(responses); return; } - if let ToolMessage::Freehand(FreehandToolMessage::UpdateOptions(action)) = action { + if let ToolMessage::Freehand(FreehandToolMessage::UpdateOptions(action)) = message { match action { FreehandToolMessageOptionsUpdate::LineWeight(line_weight) => self.options.line_weight = line_weight, } return; } - let new_state = self.fsm_state.transition(action, &mut self.data, data, &self.options, responses); + let new_state = self.fsm_state.transition(message, &mut self.data, data, &self.options, responses); if self.fsm_state != new_state { self.fsm_state = new_state; @@ -130,8 +132,8 @@ impl<'a> MessageHandler> for FreehandTool } impl ToolTransition for FreehandTool { - fn signal_to_message_map(&self) -> SignalToMessageMap { - SignalToMessageMap { + fn event_to_message_map(&self) -> EventToMessageMap { + EventToMessageMap { document_dirty: None, tool_abort: Some(FreehandToolMessage::Abort.into()), selection_changed: None, diff --git a/editor/src/viewport_tools/tools/gradient_tool.rs b/editor/src/messages/tool/tool_messages/gradient_tool.rs similarity index 88% rename from editor/src/viewport_tools/tools/gradient_tool.rs rename to editor/src/messages/tool/tool_messages/gradient_tool.rs index 3e712fd7c2..7e6cffa953 100644 --- a/editor/src/viewport_tools/tools/gradient_tool.rs +++ b/editor/src/messages/tool/tool_messages/gradient_tool.rs @@ -1,17 +1,19 @@ +use crate::application::generate_uuid; use crate::consts::{COLOR_ACCENT, LINE_ROTATE_SNAP_ANGLE, MANIPULATOR_GROUP_MARKER_SIZE, SELECTION_TOLERANCE}; -use crate::document::DocumentMessageHandler; -use crate::frontend::utility_types::MouseCursorIcon; -use crate::input::keyboard::{Key, MouseMotion}; -use crate::layout::widgets::{Layout, LayoutGroup, PropertyHolder, RadioEntryData, RadioInput, Widget, WidgetCallback, WidgetHolder, WidgetLayout}; -use crate::message_prelude::*; -use crate::misc::{HintData, HintGroup, HintInfo, KeysGroup}; -use crate::viewport_tools::snapping::SnapHandler; -use crate::viewport_tools::tool::{Fsm, SignalToMessageMap, ToolActionHandlerData, ToolMetadata, ToolTransition, ToolType}; +use crate::messages::frontend::utility_types::MouseCursorIcon; +use crate::messages::input_mapper::utility_types::input_keyboard::{Key, KeysGroup, MouseMotion}; +use crate::messages::layout::utility_types::layout_widget::{Layout, LayoutGroup, PropertyHolder, Widget, WidgetCallback, WidgetHolder, WidgetLayout}; +use crate::messages::layout::utility_types::widgets::input_widgets::{RadioEntryData, RadioInput}; +use crate::messages::prelude::*; +use crate::messages::tool::common_functionality::snapping::SnapManager; +use crate::messages::tool::utility_types::{EventToMessageMap, Fsm, ToolActionHandlerData, ToolMetadata, ToolTransition, ToolType}; +use crate::messages::tool::utility_types::{HintData, HintGroup, HintInfo}; use graphene::color::Color; use graphene::intersection::Quad; use graphene::layers::layer_info::Layer; use graphene::layers::style::{Fill, Gradient, GradientType, PathStyle, Stroke}; +use graphene::LayerId; use graphene::Operation; use glam::{DAffine2, DVec2}; @@ -67,30 +69,30 @@ impl ToolMetadata for GradientTool { fn tooltip(&self) -> String { "Gradient Tool".into() } - fn tool_type(&self) -> crate::viewport_tools::tool::ToolType { + fn tool_type(&self) -> crate::messages::tool::utility_types::ToolType { ToolType::Gradient } } impl<'a> MessageHandler> for GradientTool { - fn process_action(&mut self, action: ToolMessage, data: ToolActionHandlerData<'a>, responses: &mut VecDeque) { - if action == ToolMessage::UpdateHints { + fn process_message(&mut self, message: ToolMessage, data: ToolActionHandlerData<'a>, responses: &mut VecDeque) { + if message == ToolMessage::UpdateHints { self.fsm_state.update_hints(responses); return; } - if action == ToolMessage::UpdateCursor { + if message == ToolMessage::UpdateCursor { self.fsm_state.update_cursor(responses); return; } - if let ToolMessage::Gradient(GradientToolMessage::UpdateOptions(action)) = action { + if let ToolMessage::Gradient(GradientToolMessage::UpdateOptions(action)) = message { match action { GradientOptionsUpdate::Type(gradient_type) => self.options.gradient_type = gradient_type, } return; } - let new_state = self.fsm_state.transition(action, &mut self.data, data, &self.options, responses); + let new_state = self.fsm_state.transition(message, &mut self.data, data, &self.options, responses); if self.fsm_state != new_state { self.fsm_state = new_state; @@ -304,8 +306,8 @@ impl SelectedGradient { } impl ToolTransition for GradientTool { - fn signal_to_message_map(&self) -> SignalToMessageMap { - SignalToMessageMap { + fn event_to_message_map(&self) -> EventToMessageMap { + EventToMessageMap { document_dirty: Some(GradientToolMessage::DocumentIsDirty.into()), tool_abort: Some(GradientToolMessage::Abort.into()), selection_changed: None, @@ -317,12 +319,12 @@ impl ToolTransition for GradientTool { struct GradientToolData { gradient_overlays: Vec, selected_gradient: Option, - snap_handler: SnapHandler, + snap_manager: SnapManager, } -pub fn start_snap(snap_handler: &mut SnapHandler, document: &DocumentMessageHandler, font_cache: &FontCache) { - snap_handler.start_snap(document, document.bounding_boxes(None, None, font_cache), true, true); - snap_handler.add_all_document_handles(document, &[], &[], &[]); +pub fn start_snap(snap_manager: &mut SnapManager, document: &DocumentMessageHandler, font_cache: &FontCache) { + snap_manager.start_snap(document, document.bounding_boxes(None, None, font_cache), true, true); + snap_manager.add_all_document_handles(document, &[], &[], &[]); } impl Fsm for GradientToolFsmState { @@ -361,7 +363,7 @@ impl Fsm for GradientToolFsmState { self } (GradientToolFsmState::Ready, GradientToolMessage::PointerDown) => { - responses.push_back(BroadcastSignal::DocumentIsDirty.into()); + responses.push_back(BroadcastEvent::DocumentIsDirty.into()); let mouse = input.mouse.position; let tolerance = MANIPULATOR_GROUP_MARKER_SIZE.powi(2); @@ -370,7 +372,7 @@ impl Fsm for GradientToolFsmState { for overlay in &tool_data.gradient_overlays { if overlay.evaluate_gradient_start().distance_squared(mouse) < tolerance { dragging = true; - start_snap(&mut tool_data.snap_handler, document, font_cache); + start_snap(&mut tool_data.snap_manager, document, font_cache); tool_data.selected_gradient = Some(SelectedGradient { path: overlay.path.clone(), transform: overlay.transform, @@ -380,7 +382,7 @@ impl Fsm for GradientToolFsmState { } if overlay.evaluate_gradient_end().distance_squared(mouse) < tolerance { dragging = true; - start_snap(&mut tool_data.snap_handler, document, font_cache); + start_snap(&mut tool_data.snap_manager, document, font_cache); tool_data.selected_gradient = Some(SelectedGradient { path: overlay.path.clone(), transform: overlay.transform, @@ -419,7 +421,7 @@ impl Fsm for GradientToolFsmState { tool_data.selected_gradient = Some(selected_gradient); - start_snap(&mut tool_data.snap_handler, document, font_cache); + start_snap(&mut tool_data.snap_manager, document, font_cache); GradientToolFsmState::Drawing } else { @@ -429,20 +431,20 @@ impl Fsm for GradientToolFsmState { } (GradientToolFsmState::Drawing, GradientToolMessage::PointerMove { constrain_axis }) => { if let Some(selected_gradient) = &mut tool_data.selected_gradient { - let mouse = tool_data.snap_handler.snap_position(responses, document, input.mouse.position); + let mouse = tool_data.snap_manager.snap_position(responses, document, input.mouse.position); selected_gradient.update_gradient(mouse, responses, input.keyboard.get(constrain_axis as usize), selected_gradient.gradient.gradient_type); } GradientToolFsmState::Drawing } (GradientToolFsmState::Drawing, GradientToolMessage::PointerUp) => { - tool_data.snap_handler.cleanup(responses); + tool_data.snap_manager.cleanup(responses); GradientToolFsmState::Ready } (_, GradientToolMessage::Abort) => { - tool_data.snap_handler.cleanup(responses); + tool_data.snap_manager.cleanup(responses); while let Some(overlay) = tool_data.gradient_overlays.pop() { overlay.delete_overlays(responses); diff --git a/editor/src/viewport_tools/tools/line_tool.rs b/editor/src/messages/tool/tool_messages/line_tool.rs similarity index 81% rename from editor/src/viewport_tools/tools/line_tool.rs rename to editor/src/messages/tool/tool_messages/line_tool.rs index ecf3331ab0..f8805b42fb 100644 --- a/editor/src/viewport_tools/tools/line_tool.rs +++ b/editor/src/messages/tool/tool_messages/line_tool.rs @@ -1,14 +1,16 @@ use crate::consts::{DRAG_THRESHOLD, LINE_ROTATE_SNAP_ANGLE}; -use crate::frontend::utility_types::MouseCursorIcon; -use crate::input::keyboard::{Key, MouseMotion}; -use crate::input::mouse::ViewportPosition; -use crate::layout::widgets::{Layout, LayoutGroup, NumberInput, PropertyHolder, Widget, WidgetCallback, WidgetHolder, WidgetLayout}; -use crate::message_prelude::*; -use crate::misc::{HintData, HintGroup, HintInfo, KeysGroup}; -use crate::viewport_tools::snapping::SnapHandler; -use crate::viewport_tools::tool::{Fsm, SignalToMessageMap, ToolActionHandlerData, ToolMetadata, ToolTransition, ToolType}; +use crate::messages::frontend::utility_types::MouseCursorIcon; +use crate::messages::input_mapper::utility_types::input_keyboard::{Key, KeysGroup, MouseMotion}; +use crate::messages::input_mapper::utility_types::input_mouse::ViewportPosition; +use crate::messages::layout::utility_types::layout_widget::{Layout, LayoutGroup, PropertyHolder, Widget, WidgetCallback, WidgetHolder, WidgetLayout}; +use crate::messages::layout::utility_types::widgets::input_widgets::NumberInput; +use crate::messages::prelude::*; +use crate::messages::tool::common_functionality::snapping::SnapManager; +use crate::messages::tool::utility_types::{EventToMessageMap, Fsm, ToolActionHandlerData, ToolMetadata, ToolTransition, ToolType}; +use crate::messages::tool::utility_types::{HintData, HintGroup, HintInfo}; use graphene::layers::style; +use graphene::LayerId; use graphene::Operation; use glam::{DAffine2, DVec2}; @@ -63,7 +65,7 @@ impl ToolMetadata for LineTool { fn tooltip(&self) -> String { "Line Tool".into() } - fn tool_type(&self) -> crate::viewport_tools::tool::ToolType { + fn tool_type(&self) -> crate::messages::tool::utility_types::ToolType { ToolType::Line } } @@ -85,25 +87,25 @@ impl PropertyHolder for LineTool { } impl<'a> MessageHandler> for LineTool { - fn process_action(&mut self, action: ToolMessage, tool_data: ToolActionHandlerData<'a>, responses: &mut VecDeque) { - if action == ToolMessage::UpdateHints { + fn process_message(&mut self, message: ToolMessage, tool_data: ToolActionHandlerData<'a>, responses: &mut VecDeque) { + if message == ToolMessage::UpdateHints { self.fsm_state.update_hints(responses); return; } - if action == ToolMessage::UpdateCursor { + if message == ToolMessage::UpdateCursor { self.fsm_state.update_cursor(responses); return; } - if let ToolMessage::Line(LineToolMessage::UpdateOptions(action)) = action { + if let ToolMessage::Line(LineToolMessage::UpdateOptions(action)) = message { match action { LineOptionsUpdate::LineWeight(line_weight) => self.options.line_weight = line_weight, } return; } - let new_state = self.fsm_state.transition(action, &mut self.tool_data, tool_data, &self.options, responses); + let new_state = self.fsm_state.transition(message, &mut self.tool_data, tool_data, &self.options, responses); if self.fsm_state != new_state { self.fsm_state = new_state; @@ -129,8 +131,8 @@ impl<'a> MessageHandler> for LineTool { } impl ToolTransition for LineTool { - fn signal_to_message_map(&self) -> SignalToMessageMap { - SignalToMessageMap { + fn event_to_message_map(&self) -> EventToMessageMap { + EventToMessageMap { document_dirty: None, tool_abort: Some(LineToolMessage::Abort.into()), selection_changed: None, @@ -157,7 +159,7 @@ struct LineToolData { angle: f64, weight: f64, path: Option>, - snap_handler: SnapHandler, + snap_manager: SnapManager, } impl Fsm for LineToolFsmState { @@ -178,9 +180,9 @@ impl Fsm for LineToolFsmState { if let ToolMessage::Line(event) = event { match (self, event) { (Ready, DragStart) => { - tool_data.snap_handler.start_snap(document, document.bounding_boxes(None, None, font_cache), true, true); - tool_data.snap_handler.add_all_document_handles(document, &[], &[], &[]); - tool_data.drag_start = tool_data.snap_handler.snap_position(responses, document, input.mouse.position); + tool_data.snap_manager.start_snap(document, document.bounding_boxes(None, None, font_cache), true, true); + tool_data.snap_manager.add_all_document_handles(document, &[], &[], &[]); + tool_data.drag_start = tool_data.snap_manager.snap_position(responses, document, input.mouse.position); responses.push_back(DocumentMessage::StartTransaction.into()); tool_data.path = Some(document.get_path_for_new_layer()); @@ -201,7 +203,7 @@ impl Fsm for LineToolFsmState { Drawing } (Drawing, Redraw { center, snap_angle, lock_angle }) => { - tool_data.drag_current = tool_data.snap_handler.snap_position(responses, document, input.mouse.position); + tool_data.drag_current = tool_data.snap_manager.snap_position(responses, document, input.mouse.position); let values: Vec<_> = [lock_angle, snap_angle, center].iter().map(|k| input.keyboard.get(*k as usize)).collect(); responses.push_back(generate_transform(tool_data, values[0], values[1], values[2])); @@ -209,8 +211,8 @@ impl Fsm for LineToolFsmState { Drawing } (Drawing, DragStop) => { - tool_data.drag_current = tool_data.snap_handler.snap_position(responses, document, input.mouse.position); - tool_data.snap_handler.cleanup(responses); + tool_data.drag_current = tool_data.snap_manager.snap_position(responses, document, input.mouse.position); + tool_data.snap_manager.cleanup(responses); match tool_data.drag_start.distance(input.mouse.position) <= DRAG_THRESHOLD { true => responses.push_back(DocumentMessage::AbortTransaction.into()), @@ -222,7 +224,7 @@ impl Fsm for LineToolFsmState { Ready } (Drawing, Abort) => { - tool_data.snap_handler.cleanup(responses); + tool_data.snap_manager.cleanup(responses); responses.push_back(DocumentMessage::AbortTransaction.into()); tool_data.path = None; Ready diff --git a/editor/src/viewport_tools/tools/mod.rs b/editor/src/messages/tool/tool_messages/mod.rs similarity index 95% rename from editor/src/viewport_tools/tools/mod.rs rename to editor/src/messages/tool/tool_messages/mod.rs index 324b49652c..b036cca5b8 100644 --- a/editor/src/viewport_tools/tools/mod.rs +++ b/editor/src/messages/tool/tool_messages/mod.rs @@ -11,6 +11,5 @@ pub mod pen_tool; pub mod rectangle_tool; pub mod select_tool; pub mod shape_tool; -pub mod shared; pub mod spline_tool; pub mod text_tool; diff --git a/editor/src/viewport_tools/tools/navigate_tool.rs b/editor/src/messages/tool/tool_messages/navigate_tool.rs similarity index 87% rename from editor/src/viewport_tools/tools/navigate_tool.rs rename to editor/src/messages/tool/tool_messages/navigate_tool.rs index 4924d18569..b693760252 100644 --- a/editor/src/viewport_tools/tools/navigate_tool.rs +++ b/editor/src/messages/tool/tool_messages/navigate_tool.rs @@ -1,9 +1,9 @@ -use crate::frontend::utility_types::MouseCursorIcon; -use crate::input::keyboard::{Key, MouseMotion}; -use crate::layout::widgets::PropertyHolder; -use crate::message_prelude::*; -use crate::misc::{HintData, HintGroup, HintInfo, KeysGroup}; -use crate::viewport_tools::tool::{Fsm, SignalToMessageMap, ToolActionHandlerData, ToolMetadata, ToolTransition, ToolType}; +use crate::messages::frontend::utility_types::MouseCursorIcon; +use crate::messages::input_mapper::utility_types::input_keyboard::{Key, KeysGroup, MouseMotion}; +use crate::messages::layout::utility_types::layout_widget::PropertyHolder; +use crate::messages::prelude::*; +use crate::messages::tool::utility_types::{EventToMessageMap, Fsm, ToolActionHandlerData, ToolMetadata, ToolTransition, ToolType}; +use crate::messages::tool::utility_types::{HintData, HintGroup, HintInfo}; use glam::DVec2; use serde::{Deserialize, Serialize}; @@ -43,7 +43,7 @@ impl ToolMetadata for NavigateTool { fn tooltip(&self) -> String { "Navigate Tool".into() } - fn tool_type(&self) -> crate::viewport_tools::tool::ToolType { + fn tool_type(&self) -> crate::messages::tool::utility_types::ToolType { ToolType::Navigate } } @@ -51,18 +51,18 @@ impl ToolMetadata for NavigateTool { impl PropertyHolder for NavigateTool {} impl<'a> MessageHandler> for NavigateTool { - fn process_action(&mut self, action: ToolMessage, tool_data: ToolActionHandlerData<'a>, responses: &mut VecDeque) { - if action == ToolMessage::UpdateHints { + fn process_message(&mut self, message: ToolMessage, tool_data: ToolActionHandlerData<'a>, responses: &mut VecDeque) { + if message == ToolMessage::UpdateHints { self.fsm_state.update_hints(responses); return; } - if action == ToolMessage::UpdateCursor { + if message == ToolMessage::UpdateCursor { self.fsm_state.update_cursor(responses); return; } - let new_state = self.fsm_state.transition(action, &mut self.tool_data, tool_data, &(), responses); + let new_state = self.fsm_state.transition(message, &mut self.tool_data, tool_data, &(), responses); if self.fsm_state != new_state { self.fsm_state = new_state; @@ -90,8 +90,8 @@ impl<'a> MessageHandler> for NavigateTool } impl ToolTransition for NavigateTool { - fn signal_to_message_map(&self) -> SignalToMessageMap { - SignalToMessageMap { + fn event_to_message_map(&self) -> EventToMessageMap { + EventToMessageMap { document_dirty: None, tool_abort: Some(NavigateToolMessage::Abort.into()), selection_changed: None, diff --git a/editor/src/viewport_tools/tools/path_tool.rs b/editor/src/messages/tool/tool_messages/path_tool.rs similarity index 88% rename from editor/src/viewport_tools/tools/path_tool.rs rename to editor/src/messages/tool/tool_messages/path_tool.rs index 35d7562348..b91465f30e 100644 --- a/editor/src/viewport_tools/tools/path_tool.rs +++ b/editor/src/messages/tool/tool_messages/path_tool.rs @@ -1,16 +1,16 @@ use crate::consts::SELECTION_THRESHOLD; -use crate::frontend::utility_types::MouseCursorIcon; -use crate::input::keyboard::{Key, MouseMotion}; -use crate::layout::widgets::PropertyHolder; -use crate::message_prelude::*; -use crate::misc::{HintData, HintGroup, HintInfo, KeysGroup}; -use crate::viewport_tools::snapping::SnapHandler; -use crate::viewport_tools::tool::{Fsm, SignalToMessageMap, ToolActionHandlerData, ToolMetadata, ToolTransition, ToolType}; -use crate::viewport_tools::vector_editor::overlay_renderer::OverlayRenderer; -use crate::viewport_tools::vector_editor::shape_editor::ShapeEditor; +use crate::messages::frontend::utility_types::MouseCursorIcon; +use crate::messages::input_mapper::utility_types::input_keyboard::{Key, KeysGroup, MouseMotion}; +use crate::messages::layout::utility_types::layout_widget::PropertyHolder; +use crate::messages::prelude::*; +use crate::messages::tool::common_functionality::overlay_renderer::OverlayRenderer; +use crate::messages::tool::common_functionality::shape_editor::ShapeEditor; +use crate::messages::tool::common_functionality::snapping::SnapManager; +use crate::messages::tool::utility_types::{EventToMessageMap, Fsm, ToolActionHandlerData, ToolMetadata, ToolTransition, ToolType}; +use crate::messages::tool::utility_types::{HintData, HintGroup, HintInfo}; use graphene::intersection::Quad; -use graphene::layers::vector::constants::ManipulatorType; +use graphene::layers::vector::consts::ManipulatorType; use glam::DVec2; use serde::{Deserialize, Serialize}; @@ -52,7 +52,7 @@ impl ToolMetadata for PathTool { fn tooltip(&self) -> String { "Path Tool".into() } - fn tool_type(&self) -> crate::viewport_tools::tool::ToolType { + fn tool_type(&self) -> crate::messages::tool::utility_types::ToolType { ToolType::Path } } @@ -60,18 +60,18 @@ impl ToolMetadata for PathTool { impl PropertyHolder for PathTool {} impl<'a> MessageHandler> for PathTool { - fn process_action(&mut self, action: ToolMessage, tool_data: ToolActionHandlerData<'a>, responses: &mut VecDeque) { - if action == ToolMessage::UpdateHints { + fn process_message(&mut self, message: ToolMessage, tool_data: ToolActionHandlerData<'a>, responses: &mut VecDeque) { + if message == ToolMessage::UpdateHints { self.fsm_state.update_hints(responses); return; } - if action == ToolMessage::UpdateCursor { + if message == ToolMessage::UpdateCursor { self.fsm_state.update_cursor(responses); return; } - let new_state = self.fsm_state.transition(action, &mut self.tool_data, tool_data, &(), responses); + let new_state = self.fsm_state.transition(message, &mut self.tool_data, tool_data, &(), responses); if self.fsm_state != new_state { self.fsm_state = new_state; @@ -99,8 +99,8 @@ impl<'a> MessageHandler> for PathTool { } impl ToolTransition for PathTool { - fn signal_to_message_map(&self) -> SignalToMessageMap { - SignalToMessageMap { + fn event_to_message_map(&self) -> EventToMessageMap { + EventToMessageMap { document_dirty: Some(PathToolMessage::DocumentIsDirty.into()), tool_abort: Some(PathToolMessage::Abort.into()), selection_changed: Some(PathToolMessage::SelectionChanged.into()), @@ -124,7 +124,7 @@ impl Default for PathToolFsmState { struct PathToolData { shape_editor: ShapeEditor, overlay_renderer: OverlayRenderer, - snap_handler: SnapHandler, + snap_manager: SnapManager, drag_start_pos: DVec2, alt_debounce: bool, @@ -187,7 +187,7 @@ impl Fsm for PathToolFsmState { let ignore_document = tool_data.shape_editor.selected_layers().clone(); tool_data - .snap_handler + .snap_manager .start_snap(document, document.bounding_boxes(Some(&ignore_document), None, font_cache), true, true); // Do not snap against handles when anchor is selected @@ -201,7 +201,7 @@ impl Fsm for PathToolFsmState { new_selected.extend(extension); let include_handles = tool_data.shape_editor.selected_layers_ref(); - tool_data.snap_handler.add_all_document_handles(document, &include_handles, &[], &new_selected); + tool_data.snap_manager.add_all_document_handles(document, &include_handles, &[], &new_selected); tool_data.drag_start_pos = input.mouse.position; Dragging @@ -259,14 +259,14 @@ impl Fsm for PathToolFsmState { } // Move the selected points by the mouse position - let snapped_position = tool_data.snap_handler.snap_position(responses, document, input.mouse.position); + let snapped_position = tool_data.snap_manager.snap_position(responses, document, input.mouse.position); tool_data.shape_editor.move_selected_points(snapped_position - tool_data.drag_start_pos, snapped_position, responses); tool_data.drag_start_pos = snapped_position; Dragging } // Mouse up (_, DragStop) => { - tool_data.snap_handler.cleanup(responses); + tool_data.snap_manager.cleanup(responses); Ready } // Delete key diff --git a/editor/src/viewport_tools/tools/pen_tool.rs b/editor/src/messages/tool/tool_messages/pen_tool.rs similarity index 89% rename from editor/src/viewport_tools/tools/pen_tool.rs rename to editor/src/messages/tool/tool_messages/pen_tool.rs index 48b0c14c2e..a9b7345fa9 100644 --- a/editor/src/viewport_tools/tools/pen_tool.rs +++ b/editor/src/messages/tool/tool_messages/pen_tool.rs @@ -1,19 +1,19 @@ use crate::consts::LINE_ROTATE_SNAP_ANGLE; -use crate::document::DocumentMessageHandler; -use crate::frontend::utility_types::MouseCursorIcon; -use crate::input::keyboard::{Key, MouseMotion}; -use crate::input::InputPreprocessorMessageHandler; -use crate::layout::widgets::{Layout, LayoutGroup, NumberInput, PropertyHolder, Widget, WidgetCallback, WidgetHolder, WidgetLayout}; -use crate::message_prelude::*; -use crate::misc::{HintData, HintGroup, HintInfo, KeysGroup}; -use crate::viewport_tools::snapping::SnapHandler; -use crate::viewport_tools::tool::{Fsm, SignalToMessageMap, ToolActionHandlerData, ToolMetadata, ToolTransition, ToolType}; -use crate::viewport_tools::vector_editor::overlay_renderer::OverlayRenderer; +use crate::messages::frontend::utility_types::MouseCursorIcon; +use crate::messages::input_mapper::utility_types::input_keyboard::{Key, KeysGroup, MouseMotion}; +use crate::messages::layout::utility_types::layout_widget::{Layout, LayoutGroup, PropertyHolder, Widget, WidgetCallback, WidgetHolder, WidgetLayout}; +use crate::messages::layout::utility_types::widgets::input_widgets::NumberInput; +use crate::messages::prelude::*; +use crate::messages::tool::common_functionality::overlay_renderer::OverlayRenderer; +use crate::messages::tool::common_functionality::snapping::SnapManager; +use crate::messages::tool::utility_types::{EventToMessageMap, Fsm, ToolActionHandlerData, ToolMetadata, ToolTransition, ToolType}; +use crate::messages::tool::utility_types::{HintData, HintGroup, HintInfo}; use graphene::layers::style; -use graphene::layers::vector::constants::ManipulatorType; +use graphene::layers::vector::consts::ManipulatorType; use graphene::layers::vector::manipulator_group::ManipulatorGroup; use graphene::layers::vector::subpath::Subpath; +use graphene::LayerId; use graphene::Operation; use glam::{DAffine2, DVec2}; @@ -80,7 +80,7 @@ impl ToolMetadata for PenTool { fn tooltip(&self) -> String { "Pen Tool".into() } - fn tool_type(&self) -> crate::viewport_tools::tool::ToolType { + fn tool_type(&self) -> crate::messages::tool::utility_types::ToolType { ToolType::Pen } } @@ -102,25 +102,25 @@ impl PropertyHolder for PenTool { } impl<'a> MessageHandler> for PenTool { - fn process_action(&mut self, action: ToolMessage, tool_data: ToolActionHandlerData<'a>, responses: &mut VecDeque) { - if action == ToolMessage::UpdateHints { + fn process_message(&mut self, message: ToolMessage, tool_data: ToolActionHandlerData<'a>, responses: &mut VecDeque) { + if message == ToolMessage::UpdateHints { self.fsm_state.update_hints(responses); return; } - if action == ToolMessage::UpdateCursor { + if message == ToolMessage::UpdateCursor { self.fsm_state.update_cursor(responses); return; } - if let ToolMessage::Pen(PenToolMessage::UpdateOptions(action)) = action { + if let ToolMessage::Pen(PenToolMessage::UpdateOptions(action)) = message { match action { PenOptionsUpdate::LineWeight(line_weight) => self.options.line_weight = line_weight, } return; } - let new_state = self.fsm_state.transition(action, &mut self.tool_data, tool_data, &self.options, responses); + let new_state = self.fsm_state.transition(message, &mut self.tool_data, tool_data, &self.options, responses); if self.fsm_state != new_state { self.fsm_state = new_state; @@ -150,8 +150,8 @@ impl<'a> MessageHandler> for PenTool { } impl ToolTransition for PenTool { - fn signal_to_message_map(&self) -> SignalToMessageMap { - SignalToMessageMap { + fn event_to_message_map(&self) -> EventToMessageMap { + EventToMessageMap { document_dirty: Some(PenToolMessage::DocumentIsDirty.into()), tool_abort: Some(PenToolMessage::Abort.into()), selection_changed: Some(PenToolMessage::SelectionChanged.into()), @@ -169,7 +169,7 @@ struct PenToolData { weight: f64, path: Option>, overlay_renderer: OverlayRenderer, - snap_handler: SnapHandler, + snap_manager: SnapManager, } impl Fsm for PenToolFsmState { @@ -209,9 +209,9 @@ impl Fsm for PenToolFsmState { // Create a new layer and prep snap system tool_data.path = Some(document.get_path_for_new_layer()); - tool_data.snap_handler.start_snap(document, document.bounding_boxes(None, None, font_cache), true, true); - tool_data.snap_handler.add_all_document_handles(document, &[], &[], &[]); - let snapped_position = tool_data.snap_handler.snap_position(responses, document, input.mouse.position); + tool_data.snap_manager.start_snap(document, document.bounding_boxes(None, None, font_cache), true, true); + tool_data.snap_manager.add_all_document_handles(document, &[], &[], &[]); + let snapped_position = tool_data.snap_manager.snap_position(responses, document, input.mouse.position); // Get the position and set properties let transform = tool_data @@ -257,7 +257,7 @@ impl Fsm for PenToolFsmState { } (PenToolFsmState::DraggingHandle, PenToolMessage::PointerMove { snap_angle, break_handle }) => { if let Some(layer_path) = &tool_data.path { - let mouse = tool_data.snap_handler.snap_position(responses, document, input.mouse.position); + let mouse = tool_data.snap_manager.snap_position(responses, document, input.mouse.position); let mut pos = transform.inverse().transform_point2(mouse); if let Some(((&id, manipulator_group), _previous)) = get_subpath(layer_path, document).and_then(last_2_manipulator_groups) { if let Some(anchor) = manipulator_group.points[ManipulatorType::Anchor].as_ref() { @@ -293,7 +293,7 @@ impl Fsm for PenToolFsmState { } (PenToolFsmState::PlacingAnchor, PenToolMessage::PointerMove { snap_angle, .. }) => { if let Some(layer_path) = &tool_data.path { - let mouse = tool_data.snap_handler.snap_position(responses, document, input.mouse.position); + let mouse = tool_data.snap_manager.snap_position(responses, document, input.mouse.position); let mut pos = transform.inverse().transform_point2(mouse); if let Some(((&id, _), previous)) = get_subpath(layer_path, document).and_then(last_2_manipulator_groups) { @@ -352,7 +352,7 @@ impl Fsm for PenToolFsmState { tool_data.overlay_renderer.clear_subpath_overlays(&document.graphene_document, layer_path.to_vec(), responses); } tool_data.path = None; - tool_data.snap_handler.cleanup(responses); + tool_data.snap_manager.cleanup(responses); PenToolFsmState::Ready } diff --git a/editor/src/viewport_tools/tools/rectangle_tool.rs b/editor/src/messages/tool/tool_messages/rectangle_tool.rs similarity index 83% rename from editor/src/viewport_tools/tools/rectangle_tool.rs rename to editor/src/messages/tool/tool_messages/rectangle_tool.rs index c7b5e94141..c370433c80 100644 --- a/editor/src/viewport_tools/tools/rectangle_tool.rs +++ b/editor/src/messages/tool/tool_messages/rectangle_tool.rs @@ -1,11 +1,11 @@ -use super::shared::resize::Resize; use crate::consts::DRAG_THRESHOLD; -use crate::frontend::utility_types::MouseCursorIcon; -use crate::input::keyboard::{Key, MouseMotion}; -use crate::layout::widgets::PropertyHolder; -use crate::message_prelude::*; -use crate::misc::{HintData, HintGroup, HintInfo, KeysGroup}; -use crate::viewport_tools::tool::{Fsm, SignalToMessageMap, ToolActionHandlerData, ToolMetadata, ToolTransition, ToolType}; +use crate::messages::frontend::utility_types::MouseCursorIcon; +use crate::messages::input_mapper::utility_types::input_keyboard::{Key, KeysGroup, MouseMotion}; +use crate::messages::layout::utility_types::layout_widget::PropertyHolder; +use crate::messages::prelude::*; +use crate::messages::tool::common_functionality::resize::Resize; +use crate::messages::tool::utility_types::{EventToMessageMap, Fsm, ToolActionHandlerData, ToolMetadata, ToolTransition, ToolType}; +use crate::messages::tool::utility_types::{HintData, HintGroup, HintInfo}; use graphene::layers::style; use graphene::Operation; @@ -39,18 +39,18 @@ pub enum RectangleToolMessage { impl PropertyHolder for RectangleTool {} impl<'a> MessageHandler> for RectangleTool { - fn process_action(&mut self, action: ToolMessage, tool_data: ToolActionHandlerData<'a>, responses: &mut VecDeque) { - if action == ToolMessage::UpdateHints { + fn process_message(&mut self, message: ToolMessage, tool_data: ToolActionHandlerData<'a>, responses: &mut VecDeque) { + if message == ToolMessage::UpdateHints { self.fsm_state.update_hints(responses); return; } - if action == ToolMessage::UpdateCursor { + if message == ToolMessage::UpdateCursor { self.fsm_state.update_cursor(responses); return; } - let new_state = self.fsm_state.transition(action, &mut self.tool_data, tool_data, &(), responses); + let new_state = self.fsm_state.transition(message, &mut self.tool_data, tool_data, &(), responses); if self.fsm_state != new_state { self.fsm_state = new_state; @@ -82,14 +82,14 @@ impl ToolMetadata for RectangleTool { fn tooltip(&self) -> String { "Rectangle Tool".into() } - fn tool_type(&self) -> crate::viewport_tools::tool::ToolType { + fn tool_type(&self) -> crate::messages::tool::utility_types::ToolType { ToolType::Rectangle } } impl ToolTransition for RectangleTool { - fn signal_to_message_map(&self) -> SignalToMessageMap { - SignalToMessageMap { + fn event_to_message_map(&self) -> EventToMessageMap { + EventToMessageMap { document_dirty: None, tool_abort: Some(RectangleToolMessage::Abort.into()), selection_changed: None, diff --git a/editor/src/viewport_tools/tools/select_tool.rs b/editor/src/messages/tool/tool_messages/select_tool.rs similarity index 92% rename from editor/src/viewport_tools/tools/select_tool.rs rename to editor/src/messages/tool/tool_messages/select_tool.rs index c11ebf53b8..255a6a62fa 100644 --- a/editor/src/viewport_tools/tools/select_tool.rs +++ b/editor/src/messages/tool/tool_messages/select_tool.rs @@ -1,23 +1,26 @@ use crate::consts::{ROTATE_SNAP_ANGLE, SELECTION_TOLERANCE}; -use crate::document::transformation::Selected; -use crate::document::utility_types::{AlignAggregate, AlignAxis, FlipAxis}; -use crate::frontend::utility_types::MouseCursorIcon; -use crate::input::keyboard::{Key, MouseMotion}; -use crate::input::mouse::ViewportPosition; -use crate::layout::widgets::{IconButton, Layout, LayoutGroup, PopoverButton, PropertyHolder, Separator, SeparatorDirection, SeparatorType, Widget, WidgetCallback, WidgetHolder, WidgetLayout}; -use crate::message_prelude::*; -use crate::misc::{HintData, HintGroup, HintInfo, KeysGroup}; -use crate::viewport_tools::snapping::{self, SnapHandler}; -use crate::viewport_tools::tool::{Fsm, SignalToMessageMap, ToolActionHandlerData, ToolMetadata, ToolTransition, ToolType}; +use crate::messages::frontend::utility_types::MouseCursorIcon; +use crate::messages::input_mapper::utility_types::input_keyboard::{Key, KeysGroup, MouseMotion}; +use crate::messages::input_mapper::utility_types::input_mouse::ViewportPosition; +use crate::messages::layout::utility_types::layout_widget::{Layout, LayoutGroup, PropertyHolder, Widget, WidgetCallback, WidgetHolder, WidgetLayout}; +use crate::messages::layout::utility_types::widgets::button_widgets::{IconButton, PopoverButton}; +use crate::messages::layout::utility_types::widgets::label_widgets::{Separator, SeparatorDirection, SeparatorType}; +use crate::messages::portfolio::document::utility_types::misc::{AlignAggregate, AlignAxis, FlipAxis}; +use crate::messages::portfolio::document::utility_types::transformation::Selected; +use crate::messages::prelude::*; +use crate::messages::tool::common_functionality::path_outline::*; +use crate::messages::tool::common_functionality::snapping::{self, SnapManager}; +use crate::messages::tool::common_functionality::transformation_cage::*; +use crate::messages::tool::utility_types::{EventToMessageMap, Fsm, ToolActionHandlerData, ToolMetadata, ToolTransition, ToolType}; +use crate::messages::tool::utility_types::{HintData, HintGroup, HintInfo}; + use graphene::boolean_ops::BooleanOperation; use graphene::document::Document; use graphene::intersection::Quad; use graphene::layers::layer_info::LayerDataType; +use graphene::LayerId; use graphene::Operation; -use super::shared::path_outline::*; -use super::shared::transformation_cage::*; - use glam::{DAffine2, DVec2}; use serde::{Deserialize, Serialize}; @@ -65,7 +68,7 @@ impl ToolMetadata for SelectTool { fn tooltip(&self) -> String { "Select Tool".into() } - fn tool_type(&self) -> crate::viewport_tools::tool::ToolType { + fn tool_type(&self) -> crate::messages::tool::utility_types::ToolType { ToolType::Select } } @@ -246,18 +249,18 @@ impl PropertyHolder for SelectTool { } impl<'a> MessageHandler> for SelectTool { - fn process_action(&mut self, action: ToolMessage, tool_data: ToolActionHandlerData<'a>, responses: &mut VecDeque) { - if action == ToolMessage::UpdateHints { + fn process_message(&mut self, message: ToolMessage, tool_data: ToolActionHandlerData<'a>, responses: &mut VecDeque) { + if message == ToolMessage::UpdateHints { self.fsm_state.update_hints(responses); return; } - if action == ToolMessage::UpdateCursor { + if message == ToolMessage::UpdateCursor { responses.push_back(FrontendMessage::UpdateMouseCursor { cursor: MouseCursorIcon::Default }.into()); return; } - let new_state = self.fsm_state.transition(action, &mut self.tool_data, tool_data, &(), responses); + let new_state = self.fsm_state.transition(message, &mut self.tool_data, tool_data, &(), responses); if self.fsm_state != new_state { self.fsm_state = new_state; @@ -286,8 +289,8 @@ impl<'a> MessageHandler> for SelectTool { } impl ToolTransition for SelectTool { - fn signal_to_message_map(&self) -> SignalToMessageMap { - SignalToMessageMap { + fn event_to_message_map(&self) -> EventToMessageMap { + EventToMessageMap { document_dirty: Some(SelectToolMessage::DocumentIsDirty.into()), tool_abort: Some(SelectToolMessage::Abort.into()), selection_changed: Some(SelectToolMessage::SelectionChanged.into()), @@ -318,7 +321,7 @@ struct SelectToolData { drag_box_overlay_layer: Option>, path_outlines: PathOutline, bounding_box_overlays: Option, - snap_handler: SnapHandler, + snap_manager: SnapManager, cursor: MouseCursorIcon, } @@ -439,9 +442,9 @@ impl Fsm for SelectToolFsmState { let snap_x = selected_edges.2 || selected_edges.3; let snap_y = selected_edges.0 || selected_edges.1; - tool_data.snap_handler.start_snap(document, document.bounding_boxes(Some(&selected), None, font_cache), snap_x, snap_y); + tool_data.snap_manager.start_snap(document, document.bounding_boxes(Some(&selected), None, font_cache), snap_x, snap_y); tool_data - .snap_handler + .snap_manager .add_all_document_handles(document, &[], &selected.iter().map(|x| x.as_slice()).collect::>(), &[]); tool_data.layers_dragging = selected; @@ -463,7 +466,7 @@ impl Fsm for SelectToolFsmState { tool_data.layers_dragging = selected; tool_data - .snap_handler + .snap_manager .start_snap(document, document.bounding_boxes(Some(&tool_data.layers_dragging), None, font_cache), true, true); Dragging @@ -479,7 +482,7 @@ impl Fsm for SelectToolFsmState { responses.push_back(DocumentMessage::StartTransaction.into()); tool_data.layers_dragging.append(&mut selected); tool_data - .snap_handler + .snap_manager .start_snap(document, document.bounding_boxes(Some(&tool_data.layers_dragging), None, font_cache), true, true); Dragging @@ -506,7 +509,7 @@ impl Fsm for SelectToolFsmState { .flat_map(snapping::expand_bounds) .collect(); - let closest_move = tool_data.snap_handler.snap_layers(responses, document, snap, mouse_delta); + let closest_move = tool_data.snap_manager.snap_layers(responses, document, snap, mouse_delta); // TODO: Cache the result of `shallowest_unique_layers` to avoid this heavy computation every frame of movement, see https://github.com/GraphiteEditor/Graphite/pull/481 for path in Document::shallowest_unique_layers(tool_data.layers_dragging.iter()) { responses.push_front( @@ -527,7 +530,7 @@ impl Fsm for SelectToolFsmState { let mouse_position = input.mouse.position; - let snapped_mouse_position = tool_data.snap_handler.snap_position(responses, document, mouse_position); + let snapped_mouse_position = tool_data.snap_manager.snap_position(responses, document, mouse_position); let (_, size) = movement.new_size(snapped_mouse_position, bounds.transform, center, axis_align); let delta = movement.bounds_to_scale_transform(center, size); @@ -603,12 +606,12 @@ impl Fsm for SelectToolFsmState { true => DocumentMessage::Undo, false => DocumentMessage::CommitTransaction, }; - tool_data.snap_handler.cleanup(responses); + tool_data.snap_manager.cleanup(responses); responses.push_front(response.into()); Ready } (ResizingBounds, DragStop) => { - tool_data.snap_handler.cleanup(responses); + tool_data.snap_manager.cleanup(responses); if let Some(bounds) = &mut tool_data.bounding_box_overlays { bounds.original_transforms.clear(); @@ -643,7 +646,7 @@ impl Fsm for SelectToolFsmState { Ready } (Dragging, Abort) => { - tool_data.snap_handler.cleanup(responses); + tool_data.snap_manager.cleanup(responses); responses.push_back(DocumentMessage::Undo.into()); tool_data.path_outlines.clear_selected(responses); @@ -672,7 +675,7 @@ impl Fsm for SelectToolFsmState { tool_data.path_outlines.clear_hovered(responses); tool_data.path_outlines.clear_selected(responses); - tool_data.snap_handler.cleanup(responses); + tool_data.snap_manager.cleanup(responses); Ready } (_, Align { axis, aggregate }) => { diff --git a/editor/src/viewport_tools/tools/shape_tool.rs b/editor/src/messages/tool/tool_messages/shape_tool.rs similarity index 83% rename from editor/src/viewport_tools/tools/shape_tool.rs rename to editor/src/messages/tool/tool_messages/shape_tool.rs index f695297cac..8512536dba 100644 --- a/editor/src/viewport_tools/tools/shape_tool.rs +++ b/editor/src/messages/tool/tool_messages/shape_tool.rs @@ -1,11 +1,12 @@ -use super::shared::resize::Resize; use crate::consts::DRAG_THRESHOLD; -use crate::frontend::utility_types::MouseCursorIcon; -use crate::input::keyboard::{Key, MouseMotion}; -use crate::layout::widgets::{Layout, LayoutGroup, NumberInput, PropertyHolder, Widget, WidgetCallback, WidgetHolder, WidgetLayout}; -use crate::message_prelude::*; -use crate::misc::{HintData, HintGroup, HintInfo, KeysGroup}; -use crate::viewport_tools::tool::{Fsm, SignalToMessageMap, ToolActionHandlerData, ToolMetadata, ToolTransition, ToolType}; +use crate::messages::frontend::utility_types::MouseCursorIcon; +use crate::messages::input_mapper::utility_types::input_keyboard::{Key, KeysGroup, MouseMotion}; +use crate::messages::layout::utility_types::layout_widget::{Layout, LayoutGroup, PropertyHolder, Widget, WidgetCallback, WidgetHolder, WidgetLayout}; +use crate::messages::layout::utility_types::widgets::input_widgets::NumberInput; +use crate::messages::prelude::*; +use crate::messages::tool::common_functionality::resize::Resize; +use crate::messages::tool::utility_types::{EventToMessageMap, Fsm, ToolActionHandlerData, ToolMetadata, ToolTransition, ToolType}; +use crate::messages::tool::utility_types::{HintData, HintGroup, HintInfo}; use graphene::layers::style; use graphene::Operation; @@ -61,7 +62,7 @@ impl ToolMetadata for ShapeTool { fn tooltip(&self) -> String { "Shape Tool".into() } - fn tool_type(&self) -> crate::viewport_tools::tool::ToolType { + fn tool_type(&self) -> crate::messages::tool::utility_types::ToolType { ToolType::Shape } } @@ -83,25 +84,25 @@ impl PropertyHolder for ShapeTool { } impl<'a> MessageHandler> for ShapeTool { - fn process_action(&mut self, action: ToolMessage, tool_data: ToolActionHandlerData<'a>, responses: &mut VecDeque) { - if action == ToolMessage::UpdateHints { + fn process_message(&mut self, message: ToolMessage, tool_data: ToolActionHandlerData<'a>, responses: &mut VecDeque) { + if message == ToolMessage::UpdateHints { self.fsm_state.update_hints(responses); return; } - if action == ToolMessage::UpdateCursor { + if message == ToolMessage::UpdateCursor { self.fsm_state.update_cursor(responses); return; } - if let ToolMessage::Shape(ShapeToolMessage::UpdateOptions(action)) = action { + if let ToolMessage::Shape(ShapeToolMessage::UpdateOptions(action)) = message { match action { ShapeOptionsUpdate::Vertices(vertices) => self.options.vertices = vertices, } return; } - let new_state = self.fsm_state.transition(action, &mut self.tool_data, tool_data, &self.options, responses); + let new_state = self.fsm_state.transition(message, &mut self.tool_data, tool_data, &self.options, responses); if self.fsm_state != new_state { self.fsm_state = new_state; @@ -127,8 +128,8 @@ impl<'a> MessageHandler> for ShapeTool { } impl ToolTransition for ShapeTool { - fn signal_to_message_map(&self) -> SignalToMessageMap { - SignalToMessageMap { + fn event_to_message_map(&self) -> EventToMessageMap { + EventToMessageMap { document_dirty: None, tool_abort: Some(ShapeToolMessage::Abort.into()), selection_changed: None, diff --git a/editor/src/viewport_tools/tools/spline_tool.rs b/editor/src/messages/tool/tool_messages/spline_tool.rs similarity index 81% rename from editor/src/viewport_tools/tools/spline_tool.rs rename to editor/src/messages/tool/tool_messages/spline_tool.rs index 4c7a5f5fc8..96d80a2740 100644 --- a/editor/src/viewport_tools/tools/spline_tool.rs +++ b/editor/src/messages/tool/tool_messages/spline_tool.rs @@ -1,13 +1,15 @@ use crate::consts::DRAG_THRESHOLD; -use crate::frontend::utility_types::MouseCursorIcon; -use crate::input::keyboard::{Key, MouseMotion}; -use crate::layout::widgets::{Layout, LayoutGroup, NumberInput, PropertyHolder, Widget, WidgetCallback, WidgetHolder, WidgetLayout}; -use crate::message_prelude::*; -use crate::misc::{HintData, HintGroup, HintInfo, KeysGroup}; -use crate::viewport_tools::snapping::SnapHandler; -use crate::viewport_tools::tool::{DocumentToolData, Fsm, SignalToMessageMap, ToolActionHandlerData, ToolMetadata, ToolTransition, ToolType}; +use crate::messages::frontend::utility_types::MouseCursorIcon; +use crate::messages::input_mapper::utility_types::input_keyboard::{Key, KeysGroup, MouseMotion}; +use crate::messages::layout::utility_types::layout_widget::{Layout, LayoutGroup, PropertyHolder, Widget, WidgetCallback, WidgetHolder, WidgetLayout}; +use crate::messages::layout::utility_types::widgets::input_widgets::NumberInput; +use crate::messages::prelude::*; +use crate::messages::tool::common_functionality::snapping::SnapManager; +use crate::messages::tool::utility_types::{DocumentToolData, EventToMessageMap, Fsm, ToolActionHandlerData, ToolMetadata, ToolTransition, ToolType}; +use crate::messages::tool::utility_types::{HintData, HintGroup, HintInfo}; use graphene::layers::style; +use graphene::LayerId; use graphene::Operation; use glam::{DAffine2, DVec2}; @@ -66,7 +68,7 @@ impl ToolMetadata for SplineTool { fn tooltip(&self) -> String { "Spline Tool".into() } - fn tool_type(&self) -> crate::viewport_tools::tool::ToolType { + fn tool_type(&self) -> crate::messages::tool::utility_types::ToolType { ToolType::Spline } } @@ -88,25 +90,25 @@ impl PropertyHolder for SplineTool { } impl<'a> MessageHandler> for SplineTool { - fn process_action(&mut self, action: ToolMessage, tool_data: ToolActionHandlerData<'a>, responses: &mut VecDeque) { - if action == ToolMessage::UpdateHints { + fn process_message(&mut self, message: ToolMessage, tool_data: ToolActionHandlerData<'a>, responses: &mut VecDeque) { + if message == ToolMessage::UpdateHints { self.fsm_state.update_hints(responses); return; } - if action == ToolMessage::UpdateCursor { + if message == ToolMessage::UpdateCursor { self.fsm_state.update_cursor(responses); return; } - if let ToolMessage::Spline(SplineToolMessage::UpdateOptions(action)) = action { + if let ToolMessage::Spline(SplineToolMessage::UpdateOptions(action)) = message { match action { SplineOptionsUpdate::LineWeight(line_weight) => self.options.line_weight = line_weight, } return; } - let new_state = self.fsm_state.transition(action, &mut self.tool_data, tool_data, &self.options, responses); + let new_state = self.fsm_state.transition(message, &mut self.tool_data, tool_data, &self.options, responses); if self.fsm_state != new_state { self.fsm_state = new_state; @@ -137,8 +139,8 @@ impl<'a> MessageHandler> for SplineTool { } impl ToolTransition for SplineTool { - fn signal_to_message_map(&self) -> SignalToMessageMap { - SignalToMessageMap { + fn event_to_message_map(&self) -> EventToMessageMap { + EventToMessageMap { document_dirty: None, tool_abort: Some(SplineToolMessage::Abort.into()), selection_changed: None, @@ -157,7 +159,7 @@ struct SplineToolData { next_point: DVec2, weight: f64, path: Option>, - snap_handler: SnapHandler, + snap_manager: SnapManager, } impl Fsm for SplineToolFsmState { @@ -184,9 +186,9 @@ impl Fsm for SplineToolFsmState { responses.push_back(DocumentMessage::DeselectAllLayers.into()); tool_data.path = Some(document.get_path_for_new_layer()); - tool_data.snap_handler.start_snap(document, document.bounding_boxes(None, None, font_cache), true, true); - tool_data.snap_handler.add_all_document_handles(document, &[], &[], &[]); - let snapped_position = tool_data.snap_handler.snap_position(responses, document, input.mouse.position); + tool_data.snap_manager.start_snap(document, document.bounding_boxes(None, None, font_cache), true, true); + tool_data.snap_manager.add_all_document_handles(document, &[], &[], &[]); + let snapped_position = tool_data.snap_manager.snap_position(responses, document, input.mouse.position); let pos = transform.inverse().transform_point2(snapped_position); @@ -200,7 +202,7 @@ impl Fsm for SplineToolFsmState { Drawing } (Drawing, DragStop) => { - let snapped_position = tool_data.snap_handler.snap_position(responses, document, input.mouse.position); + let snapped_position = tool_data.snap_manager.snap_position(responses, document, input.mouse.position); let pos = transform.inverse().transform_point2(snapped_position); if let Some(last_pos) = tool_data.points.last() { @@ -216,7 +218,7 @@ impl Fsm for SplineToolFsmState { Drawing } (Drawing, PointerMove) => { - let snapped_position = tool_data.snap_handler.snap_position(responses, document, input.mouse.position); + let snapped_position = tool_data.snap_manager.snap_position(responses, document, input.mouse.position); let pos = transform.inverse().transform_point2(snapped_position); tool_data.next_point = pos; @@ -236,7 +238,7 @@ impl Fsm for SplineToolFsmState { tool_data.path = None; tool_data.points.clear(); - tool_data.snap_handler.cleanup(responses); + tool_data.snap_manager.cleanup(responses); Ready } diff --git a/editor/src/viewport_tools/tools/text_tool.rs b/editor/src/messages/tool/tool_messages/text_tool.rs similarity index 90% rename from editor/src/viewport_tools/tools/text_tool.rs rename to editor/src/messages/tool/tool_messages/text_tool.rs index d9ec8482a7..4a1b0f0689 100644 --- a/editor/src/viewport_tools/tools/text_tool.rs +++ b/editor/src/messages/tool/tool_messages/text_tool.rs @@ -1,16 +1,19 @@ +use crate::application::generate_uuid; use crate::consts::{COLOR_ACCENT, SELECTION_TOLERANCE}; -use crate::document::DocumentMessageHandler; -use crate::frontend::utility_types::MouseCursorIcon; -use crate::input::keyboard::{Key, MouseMotion}; -use crate::layout::layout_message::LayoutTarget; -use crate::layout::widgets::{FontInput, Layout, LayoutGroup, NumberInput, PropertyHolder, Separator, SeparatorDirection, SeparatorType, Widget, WidgetCallback, WidgetHolder, WidgetLayout}; -use crate::message_prelude::*; -use crate::misc::{HintData, HintGroup, HintInfo, KeysGroup}; -use crate::viewport_tools::tool::{Fsm, SignalToMessageMap, ToolActionHandlerData, ToolMetadata, ToolTransition, ToolType}; +use crate::messages::frontend::utility_types::MouseCursorIcon; +use crate::messages::input_mapper::utility_types::input_keyboard::{Key, KeysGroup, MouseMotion}; +use crate::messages::layout::utility_types::layout_widget::{Layout, LayoutGroup, PropertyHolder, Widget, WidgetCallback, WidgetHolder, WidgetLayout}; +use crate::messages::layout::utility_types::misc::LayoutTarget; +use crate::messages::layout::utility_types::widgets::input_widgets::{FontInput, NumberInput}; +use crate::messages::layout::utility_types::widgets::label_widgets::{Separator, SeparatorDirection, SeparatorType}; +use crate::messages::prelude::*; +use crate::messages::tool::utility_types::{EventToMessageMap, Fsm, ToolActionHandlerData, ToolMetadata, ToolTransition, ToolType}; +use crate::messages::tool::utility_types::{HintData, HintGroup, HintInfo}; use graphene::intersection::Quad; use graphene::layers::style::{self, Fill, Stroke}; use graphene::layers::text_layer::FontCache; +use graphene::LayerId; use graphene::Operation; use glam::{DAffine2, DVec2}; @@ -76,7 +79,7 @@ impl ToolMetadata for TextTool { fn tooltip(&self) -> String { "Text Tool".into() } - fn tool_type(&self) -> crate::viewport_tools::tool::ToolType { + fn tool_type(&self) -> crate::messages::tool::utility_types::ToolType { ToolType::Text } } @@ -134,18 +137,18 @@ impl PropertyHolder for TextTool { } impl<'a> MessageHandler> for TextTool { - fn process_action(&mut self, action: ToolMessage, tool_data: ToolActionHandlerData<'a>, responses: &mut VecDeque) { - if action == ToolMessage::UpdateHints { + fn process_message(&mut self, message: ToolMessage, tool_data: ToolActionHandlerData<'a>, responses: &mut VecDeque) { + if message == ToolMessage::UpdateHints { self.fsm_state.update_hints(responses); return; } - if action == ToolMessage::UpdateCursor { + if message == ToolMessage::UpdateCursor { self.fsm_state.update_cursor(responses); return; } - if let ToolMessage::Text(TextToolMessage::UpdateOptions(action)) = action { + if let ToolMessage::Text(TextToolMessage::UpdateOptions(action)) = message { match action { TextOptionsUpdate::Font { family, style } => { self.options.font_name = family; @@ -158,7 +161,7 @@ impl<'a> MessageHandler> for TextTool { return; } - let new_state = self.fsm_state.transition(action, &mut self.tool_data, tool_data, &self.options, responses); + let new_state = self.fsm_state.transition(message, &mut self.tool_data, tool_data, &self.options, responses); if self.fsm_state != new_state { self.fsm_state = new_state; @@ -184,8 +187,8 @@ impl<'a> MessageHandler> for TextTool { } impl ToolTransition for TextTool { - fn signal_to_message_map(&self) -> SignalToMessageMap { - SignalToMessageMap { + fn event_to_message_map(&self) -> EventToMessageMap { + EventToMessageMap { document_dirty: Some(TextToolMessage::DocumentIsDirty.into()), tool_abort: Some(TextToolMessage::Abort.into()), selection_changed: None, diff --git a/editor/src/viewport_tools/tool.rs b/editor/src/messages/tool/utility_types.rs similarity index 63% rename from editor/src/viewport_tools/tool.rs rename to editor/src/messages/tool/utility_types.rs index 7e95ba6ac6..2811b3c2e4 100644 --- a/editor/src/viewport_tools/tool.rs +++ b/editor/src/messages/tool/utility_types.rs @@ -1,11 +1,14 @@ -use super::tools::*; -use crate::communication::message_handler::MessageHandler; -use crate::document::DocumentMessageHandler; -use crate::input::input_mapper::action_keys::action_shortcut; -use crate::input::input_mapper::ActionKeys; -use crate::input::InputPreprocessorMessageHandler; -use crate::layout::widgets::{IconButton, Layout, LayoutGroup, PropertyHolder, Separator, SeparatorDirection, SeparatorType, Widget, WidgetCallback, WidgetHolder, WidgetLayout}; -use crate::message_prelude::*; +use super::tool_messages::*; +use crate::messages::input_mapper::utility_types::input_keyboard::KeysGroup; +use crate::messages::input_mapper::utility_types::input_keyboard::MouseMotion; +use crate::messages::input_mapper::utility_types::macros::action_keys; +use crate::messages::input_mapper::utility_types::misc::ActionKeys; +use crate::messages::layout::utility_types::layout_widget::{Layout, LayoutGroup, PropertyHolder, Widget, WidgetCallback, WidgetHolder, WidgetLayout}; +use crate::messages::layout::utility_types::misc::LayoutTarget; +use crate::messages::layout::utility_types::widgets::button_widgets::IconButton; +use crate::messages::layout::utility_types::widgets::input_widgets::SwatchPairInput; +use crate::messages::layout::utility_types::widgets::label_widgets::{Separator, SeparatorDirection, SeparatorType}; +use crate::messages::prelude::*; use graphene::color::Color; use graphene::layers::text_layer::FontCache; @@ -16,6 +19,11 @@ use std::fmt::{self, Debug}; pub type ToolActionHandlerData<'a> = (&'a DocumentMessageHandler, &'a DocumentToolData, &'a InputPreprocessorMessageHandler, &'a FontCache); +pub trait ToolCommon: for<'a> MessageHandler> + PropertyHolder + ToolTransition + ToolMetadata {} +impl ToolCommon for T where T: for<'a> MessageHandler> + PropertyHolder + ToolTransition + ToolMetadata {} + +type Tool = dyn ToolCommon; + pub trait Fsm { type ToolData; type ToolOptions; @@ -33,22 +41,63 @@ pub struct DocumentToolData { pub secondary_color: Color, } +impl DocumentToolData { + pub fn update_working_colors(&self, responses: &mut VecDeque) { + let layout = WidgetLayout::new(vec![ + LayoutGroup::Row { + widgets: vec![WidgetHolder::new(Widget::SwatchPairInput(SwatchPairInput { + primary: self.primary_color, + secondary: self.secondary_color, + }))], + }, + LayoutGroup::Row { + widgets: vec![ + WidgetHolder::new(Widget::IconButton(IconButton { + size: 16, + icon: "Swap".into(), + tooltip: "Swap".into(), + tooltip_shortcut: action_keys!(ToolMessageDiscriminant::SwapColors), + on_update: WidgetCallback::new(|_| ToolMessage::SwapColors.into()), + ..Default::default() + })), + WidgetHolder::new(Widget::IconButton(IconButton { + size: 16, + icon: "ResetColors".into(), + tooltip: "Reset".into(), + tooltip_shortcut: action_keys!(ToolMessageDiscriminant::ResetColors), + on_update: WidgetCallback::new(|_| ToolMessage::ResetColors.into()), + ..Default::default() + })), + ], + }, + ]); + + responses.push_back( + LayoutMessage::SendLayout { + layout: Layout::WidgetLayout(layout), + layout_target: LayoutTarget::WorkingColors, + } + .into(), + ); + } +} + #[derive(Clone, Debug)] -pub struct SignalToMessageMap { +pub struct EventToMessageMap { pub document_dirty: Option, pub selection_changed: Option, pub tool_abort: Option, } pub trait ToolTransition { - fn signal_to_message_map(&self) -> SignalToMessageMap; + fn event_to_message_map(&self) -> EventToMessageMap; fn activate(&self, responses: &mut VecDeque) { - let mut subscribe_message = |broadcast_to_tool_mapping: Option, signal: BroadcastSignal| { + let mut subscribe_message = |broadcast_to_tool_mapping: Option, event: BroadcastEvent| { if let Some(mapping) = broadcast_to_tool_mapping { responses.push_back( - BroadcastMessage::SubscribeSignal { - on: signal, + BroadcastMessage::SubscribeEvent { + on: event, send: Box::new(mapping.into()), } .into(), @@ -56,18 +105,18 @@ pub trait ToolTransition { }; }; - let signal_to_tool_map = self.signal_to_message_map(); - subscribe_message(signal_to_tool_map.document_dirty, BroadcastSignal::DocumentIsDirty); - subscribe_message(signal_to_tool_map.tool_abort, BroadcastSignal::ToolAbort); - subscribe_message(signal_to_tool_map.selection_changed, BroadcastSignal::SelectionChanged); + let event_to_tool_map = self.event_to_message_map(); + subscribe_message(event_to_tool_map.document_dirty, BroadcastEvent::DocumentIsDirty); + subscribe_message(event_to_tool_map.tool_abort, BroadcastEvent::ToolAbort); + subscribe_message(event_to_tool_map.selection_changed, BroadcastEvent::SelectionChanged); } fn deactivate(&self, responses: &mut VecDeque) { - let mut unsubscribe_message = |broadcast_to_tool_mapping: Option, signal: BroadcastSignal| { + let mut unsubscribe_message = |broadcast_to_tool_mapping: Option, event: BroadcastEvent| { if let Some(mapping) = broadcast_to_tool_mapping { responses.push_back( - BroadcastMessage::UnsubscribeSignal { - on: signal, + BroadcastMessage::UnsubscribeEvent { + on: event, message: Box::new(mapping.into()), } .into(), @@ -75,10 +124,10 @@ pub trait ToolTransition { }; }; - let signal_to_tool_map = self.signal_to_message_map(); - unsubscribe_message(signal_to_tool_map.document_dirty, BroadcastSignal::DocumentIsDirty); - unsubscribe_message(signal_to_tool_map.tool_abort, BroadcastSignal::ToolAbort); - unsubscribe_message(signal_to_tool_map.selection_changed, BroadcastSignal::SelectionChanged); + let event_to_tool_map = self.event_to_message_map(); + unsubscribe_message(event_to_tool_map.document_dirty, BroadcastEvent::DocumentIsDirty); + unsubscribe_message(event_to_tool_map.tool_abort, BroadcastEvent::ToolAbort); + unsubscribe_message(event_to_tool_map.selection_changed, BroadcastEvent::SelectionChanged); } } @@ -88,11 +137,6 @@ pub trait ToolMetadata { fn tool_type(&self) -> ToolType; } -pub trait ToolCommon: for<'a> MessageHandler> + PropertyHolder + ToolTransition + ToolMetadata {} -impl ToolCommon for T where T: for<'a> MessageHandler> + PropertyHolder + ToolTransition + ToolMetadata {} - -type Tool = dyn ToolCommon; - pub struct ToolData { pub active_tool_type: ToolType, pub tools: HashMap>, @@ -114,21 +158,13 @@ impl ToolData { } } -#[derive(Debug)] -pub struct ToolBarMetadataGroup { - pub tooltip: String, - pub tooltip_shortcut: Option, - pub icon_name: String, - pub tool_type: ToolType, -} - impl PropertyHolder for ToolData { fn properties(&self) -> Layout { let tool_groups_layout = list_tools_in_groups() .iter() - .map(|tool_group| tool_group.iter().map(|tool| ToolBarMetadataGroup { + .map(|tool_group| tool_group.iter().map(|tool| ToolEntry { tooltip: tool.tooltip(), - tooltip_shortcut: action_shortcut!(tool_type_to_activate_tool_message(tool.tool_type())), + tooltip_shortcut: action_keys!(tool_type_to_activate_tool_message(tool.tool_type())), icon_name: tool.icon_name(), tool_type: tool.tool_type(), }).collect::>()) @@ -138,7 +174,7 @@ impl PropertyHolder for ToolData { direction: SeparatorDirection::Vertical, separator_type: SeparatorType::Section, }))); - let buttons = group.into_iter().map(|ToolBarMetadataGroup { tooltip, tooltip_shortcut, tool_type, icon_name }| { + let buttons = group.into_iter().map(|ToolEntry { tooltip, tooltip_shortcut, tool_type, icon_name }| { WidgetHolder::new(Widget::IconButton(IconButton { icon: icon_name, size: 32, @@ -166,6 +202,14 @@ impl PropertyHolder for ToolData { } } +#[derive(Debug)] +pub struct ToolEntry { + pub tooltip: String, + pub tooltip_shortcut: Option, + pub icon_name: String, + pub tool_type: ToolType, +} + #[derive(Debug)] pub struct ToolFsmState { pub document_tool_data: DocumentToolData, @@ -191,10 +235,6 @@ impl ToolFsmState { pub fn new() -> Self { Self::default() } - - pub fn swap_colors(&mut self) { - std::mem::swap(&mut self.document_tool_data.primary_color, &mut self.document_tool_data.secondary_color); - } } #[repr(usize)] @@ -255,39 +295,39 @@ pub fn list_tools_in_groups() -> Vec>> { ] } -pub fn coming_soon_tools() -> Vec> { +pub fn coming_soon_tools() -> Vec> { vec![vec![ - ToolBarMetadataGroup { + ToolEntry { tool_type: ToolType::Brush, icon_name: "RasterBrushTool".into(), tooltip: "Coming Soon: Brush Tool (B)".into(), tooltip_shortcut: None, }, - ToolBarMetadataGroup { + ToolEntry { tool_type: ToolType::Heal, icon_name: "RasterHealTool".into(), tooltip: "Coming Soon: Heal Tool (J)".into(), tooltip_shortcut: None, }, - ToolBarMetadataGroup { + ToolEntry { tool_type: ToolType::Clone, icon_name: "RasterCloneTool".into(), tooltip: "Coming Soon: Clone Tool (C)".into(), tooltip_shortcut: None, }, - ToolBarMetadataGroup { + ToolEntry { tool_type: ToolType::Patch, icon_name: "RasterPatchTool".into(), tooltip: "Coming Soon: Patch Tool".into(), tooltip_shortcut: None, }, - ToolBarMetadataGroup { + ToolEntry { tool_type: ToolType::Detail, icon_name: "RasterDetailTool".into(), tooltip: "Coming Soon: Detail Tool (D)".into(), tooltip_shortcut: None, }, - ToolBarMetadataGroup { + ToolEntry { tool_type: ToolType::Relight, icon_name: "RasterRelightTool".into(), tooltip: "Coming Soon: Relight Tool (O)".into(), @@ -365,3 +405,56 @@ pub fn tool_type_to_activate_tool_message(tool_type: ToolType) -> ToolMessageDis ), } } + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub struct HintData(pub Vec); + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub struct HintGroup(pub Vec); + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub struct HintInfo { + /// A `KeysGroup` specifies all the keys pressed simultaneously to perform an action (like "Ctrl C" to copy). + /// Usually at most one is given, but less commonly, multiple can be used to describe additional hotkeys not used simultaneously (like the four different arrow keys to nudge a layer). + #[serde(rename = "keyGroups")] + pub key_groups: Vec, + /// `None` means that the regular `key_groups` should be used for all platforms, `Some` is an override for a Mac-only input hint. + #[serde(rename = "keyGroupsMac")] + pub key_groups_mac: Option>, + /// An optional `MouseMotion` that can indicate the mouse action, like which mouse button is used and whether a drag occurs. + /// No such icon is shown if `None` is given, and it can be combined with `key_groups` if desired. + pub mouse: Option, + /// The text describing what occurs with this input combination. + pub label: String, + /// Draws a prepended "+" symbol which indicates that this is a refinement upon a previous hint in the group. + pub plus: bool, +} + +#[cfg(test)] +mod tool_crash_on_layer_delete_tests { + use crate::application::{set_uuid_seed, Editor}; + use crate::messages::portfolio::document::DocumentMessage; + use crate::messages::tool::utility_types::ToolType; + use crate::test_utils::EditorTestUtils; + + use test_case::test_case; + + #[test_case(ToolType::Pen; "while using pen tool")] + #[test_case(ToolType::Freehand; "while using freehand tool")] + #[test_case(ToolType::Spline; "while using spline tool")] + #[test_case(ToolType::Line; "while using line tool")] + #[test_case(ToolType::Rectangle; "while using rectangle tool")] + #[test_case(ToolType::Ellipse; "while using ellipse tool")] + #[test_case(ToolType::Shape; "while using shape tool")] + #[test_case(ToolType::Path; "while using path tool")] + fn should_not_crash_when_layer_is_deleted(tool: ToolType) { + set_uuid_seed(0); + let mut test_editor = Editor::new(); + + test_editor.select_tool(tool); + test_editor.lmb_mousedown(0.0, 0.0); + test_editor.move_mouse(100.0, 100.0); + + test_editor.handle_message(DocumentMessage::DeleteSelectedLayers); + } +} diff --git a/editor/src/workspace/mod.rs b/editor/src/messages/workspace/mod.rs similarity index 100% rename from editor/src/workspace/mod.rs rename to editor/src/messages/workspace/mod.rs diff --git a/editor/src/workspace/workspace_message.rs b/editor/src/messages/workspace/workspace_message.rs similarity index 87% rename from editor/src/workspace/workspace_message.rs rename to editor/src/messages/workspace/workspace_message.rs index 49eb33abe3..7251b9f32c 100644 --- a/editor/src/workspace/workspace_message.rs +++ b/editor/src/messages/workspace/workspace_message.rs @@ -1,4 +1,4 @@ -use crate::message_prelude::*; +use crate::messages::prelude::*; use serde::{Deserialize, Serialize}; diff --git a/editor/src/workspace/workspace_message_handler.rs b/editor/src/messages/workspace/workspace_message_handler.rs similarity index 74% rename from editor/src/workspace/workspace_message_handler.rs rename to editor/src/messages/workspace/workspace_message_handler.rs index 46a6025f46..aada39b19a 100644 --- a/editor/src/workspace/workspace_message_handler.rs +++ b/editor/src/messages/workspace/workspace_message_handler.rs @@ -1,5 +1,4 @@ -use crate::input::InputPreprocessorMessageHandler; -use crate::message_prelude::*; +use crate::messages::prelude::*; #[derive(Debug, Clone, Default)] pub struct WorkspaceMessageHandler { @@ -8,7 +7,7 @@ pub struct WorkspaceMessageHandler { impl MessageHandler for WorkspaceMessageHandler { #[remain::check] - fn process_action(&mut self, message: WorkspaceMessage, _ipp: &InputPreprocessorMessageHandler, responses: &mut VecDeque) { + fn process_message(&mut self, message: WorkspaceMessage, _ipp: &InputPreprocessorMessageHandler, responses: &mut VecDeque) { use WorkspaceMessage::*; #[remain::sorted] diff --git a/editor/src/misc/build_metadata.rs b/editor/src/misc/build_metadata.rs deleted file mode 100644 index 424ee886e6..0000000000 --- a/editor/src/misc/build_metadata.rs +++ /dev/null @@ -1,31 +0,0 @@ -//! Provides metadata about the build environment. -//! -//! This data is viewable in the editor via the [AboutGraphite](crate::dialog::AboutGraphite) dialog. - -pub fn release_series() -> String { - format!("Release Series: {}", env!("GRAPHITE_RELEASE_SERIES")) -} - -pub fn commit_info() -> String { - format!("{}\n{}\n{}", commit_timestamp(), commit_hash(), commit_branch()) -} - -pub fn commit_info_localized(localized_commit_date: &str) -> String { - format!("{}\n{}\n{}", commit_timestamp_localized(localized_commit_date), commit_hash(), commit_branch()) -} - -pub fn commit_timestamp() -> String { - format!("Date: {}", env!("GRAPHITE_GIT_COMMIT_DATE")) -} - -pub fn commit_timestamp_localized(localized_commit_date: &str) -> String { - format!("Date: {}", localized_commit_date) -} - -pub fn commit_hash() -> String { - format!("Hash: {}", &env!("GRAPHITE_GIT_COMMIT_HASH")[..8]) -} - -pub fn commit_branch() -> String { - format!("Branch: {}", env!("GRAPHITE_GIT_COMMIT_BRANCH")) -} diff --git a/editor/src/misc/derivable_custom_traits.rs b/editor/src/misc/derivable_custom_traits.rs deleted file mode 100644 index 7396f317ff..0000000000 --- a/editor/src/misc/derivable_custom_traits.rs +++ /dev/null @@ -1,18 +0,0 @@ -//! Traits that can be derived using macros from `graphite-proc-macros` - -use std::collections::HashMap; - -pub trait Hint { - fn hints(&self) -> HashMap; -} - -pub trait ToDiscriminant { - type Discriminant; - - fn to_discriminant(&self) -> Self::Discriminant; -} - -pub trait TransitiveChild: Into + Into { - type TopParent; - type Parent; -} diff --git a/editor/src/misc/hints.rs b/editor/src/misc/hints.rs deleted file mode 100644 index 57d55e4d06..0000000000 --- a/editor/src/misc/hints.rs +++ /dev/null @@ -1,31 +0,0 @@ -use crate::input::keyboard::{Key, MouseMotion}; - -use serde::{Deserialize, Serialize}; - -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -pub struct HintData(pub Vec); - -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -pub struct HintGroup(pub Vec); - -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -pub struct HintInfo { - /// A `KeysGroup` specifies all the keys pressed simultaneously to perform an action (like "Ctrl C" to copy). - /// Usually at most one is given, but less commonly, multiple can be used to describe additional hotkeys not used simultaneously (like the four different arrow keys to nudge a layer). - #[serde(rename = "keyGroups")] - pub key_groups: Vec, - /// `None` means that the regular `key_groups` should be used for all platforms, `Some` is an override for a Mac-only input hint. - #[serde(rename = "keyGroupsMac")] - pub key_groups_mac: Option>, - /// An optional `MouseMotion` that can indicate the mouse action, like which mouse button is used and whether a drag occurs. - /// No such icon is shown if `None` is given, and it can be combined with `key_groups` if desired. - pub mouse: Option, - /// The text describing what occurs with this input combination. - pub label: String, - /// Draws a prepended "+" symbol which indicates that this is a refinement upon a previous hint in the group. - pub plus: bool, -} - -/// Only `Key`s that exist on a physical keyboard should be used. -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -pub struct KeysGroup(pub Vec); diff --git a/editor/src/misc/mod.rs b/editor/src/misc/mod.rs deleted file mode 100644 index c2a13994a2..0000000000 --- a/editor/src/misc/mod.rs +++ /dev/null @@ -1,12 +0,0 @@ -#[macro_use] -pub mod macros; -pub mod build_metadata; -pub mod derivable_custom_traits; -pub mod hints; -pub mod test_utils; - -mod error; - -pub use error::EditorError; -pub use hints::*; -pub use macros::*; diff --git a/editor/src/misc/test_utils.rs b/editor/src/test_utils.rs similarity index 90% rename from editor/src/misc/test_utils.rs rename to editor/src/test_utils.rs index f543e8d281..09d0e615f3 100644 --- a/editor/src/misc/test_utils.rs +++ b/editor/src/test_utils.rs @@ -1,8 +1,8 @@ -use crate::input::input_preprocessor::ModifierKeys; -use crate::input::mouse::{EditorMouseState, MouseKeys, ScrollDelta, ViewportPosition}; -use crate::message_prelude::*; -use crate::viewport_tools::tool::ToolType; -use crate::Editor; +use crate::application::Editor; +use crate::messages::input_mapper::utility_types::input_keyboard::ModifierKeys; +use crate::messages::input_mapper::utility_types::input_mouse::{EditorMouseState, MouseKeys, ScrollDelta, ViewportPosition}; +use crate::messages::prelude::*; +use crate::messages::tool::utility_types::ToolType; use graphene::color::Color; diff --git a/editor/src/utility_traits.rs b/editor/src/utility_traits.rs new file mode 100644 index 0000000000..b77737704b --- /dev/null +++ b/editor/src/utility_traits.rs @@ -0,0 +1,51 @@ +pub use crate::dispatcher::*; +use crate::messages::prelude::*; + +use std::collections::VecDeque; + +/// 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 +where + M::Discriminant: AsMessage, + ::TopParent: TransitiveChild::TopParent, TopParent = ::TopParent> + AsMessage, +{ + /// Return true if the Action is consumed. + fn process_message(&mut self, message: M, data: D, responses: &mut VecDeque); + + fn actions(&self) -> ActionList; +} + +pub type ActionList = Vec>; + +pub trait AsMessage: TransitiveChild +where + Self::TopParent: TransitiveChild + AsMessage, +{ + fn local_name(self) -> String; + fn global_name(self) -> String { + >::into(self).local_name() + } +} + +// TODO: Add Send + Sync requirement +// Use something like rw locks for synchronization +pub trait MessageHandlerData {} + +pub trait ToDiscriminant { + type Discriminant; + + fn to_discriminant(&self) -> Self::Discriminant; +} + +pub trait TransitiveChild: Into + Into { + type TopParent; + type Parent; +} + +use std::collections::HashMap; + +pub trait Hint { + fn hints(&self) -> HashMap; +} diff --git a/editor/src/viewport_tools/mod.rs b/editor/src/viewport_tools/mod.rs deleted file mode 100644 index a10c7123c7..0000000000 --- a/editor/src/viewport_tools/mod.rs +++ /dev/null @@ -1,35 +0,0 @@ -pub mod snapping; -pub mod tool; -pub mod tool_message; -pub mod tool_message_handler; -pub mod tools; -pub mod vector_editor; - -#[cfg(test)] -mod tool_crash_on_layer_delete_tests { - use crate::communication::set_uuid_seed; - use crate::misc::test_utils::EditorTestUtils; - use crate::viewport_tools::tool::ToolType; - use crate::{DocumentMessage, Editor}; - - use test_case::test_case; - - #[test_case(ToolType::Pen ; "while using pen tool")] - #[test_case(ToolType::Freehand ; "while using freehand tool")] - #[test_case(ToolType::Spline ; "while using spline tool")] - #[test_case(ToolType::Line ; "while using line tool")] - #[test_case(ToolType::Rectangle ; "while using rectangle tool")] - #[test_case(ToolType::Ellipse ; "while using ellipse tool")] - #[test_case(ToolType::Shape ; "while using shape tool")] - #[test_case(ToolType::Path ; "while using path tool")] - fn should_not_crash_when_layer_is_deleted(tool: ToolType) { - set_uuid_seed(0); - let mut test_editor = Editor::new(); - - test_editor.select_tool(tool); - test_editor.lmb_mousedown(0.0, 0.0); - test_editor.move_mouse(100.0, 100.0); - - test_editor.handle_message(DocumentMessage::DeleteSelectedLayers); - } -} diff --git a/editor/src/viewport_tools/vector_editor/constants.rs b/editor/src/viewport_tools/vector_editor/constants.rs deleted file mode 100644 index 095cfb0450..0000000000 --- a/editor/src/viewport_tools/vector_editor/constants.rs +++ /dev/null @@ -1,4 +0,0 @@ -// Helps push values that end in approximately half, plus or minus some floating point imprecision, towards the same side of the round() function -pub const ROUNDING_BIAS: f64 = 0.002; -// The angle threshold in radians that we should mirror handles if we are below -pub const MINIMUM_MIRROR_THRESHOLD: f64 = 0.1; diff --git a/editor/src/viewport_tools/vector_editor/mod.rs b/editor/src/viewport_tools/vector_editor/mod.rs deleted file mode 100644 index a305ebca45..0000000000 --- a/editor/src/viewport_tools/vector_editor/mod.rs +++ /dev/null @@ -1,6 +0,0 @@ -pub mod constants; -pub mod overlay_renderer; -pub mod shape_editor; -use graphene::layers::vector::manipulator_group; -use graphene::layers::vector::manipulator_point; -use graphene::layers::vector::subpath; diff --git a/frontend/wasm/src/editor_api.rs b/frontend/wasm/src/editor_api.rs index 93385075f5..8f475f476c 100644 --- a/frontend/wasm/src/editor_api.rs +++ b/frontend/wasm/src/editor_api.rs @@ -5,14 +5,15 @@ use crate::helpers::{translate_key, Error}; use crate::{EDITOR_HAS_CRASHED, EDITOR_INSTANCES, JS_EDITOR_HANDLES}; +use editor::application::generate_uuid; +use editor::application::Editor; use editor::consts::{FILE_SAVE_SUFFIX, GRAPHITE_DOCUMENT_VERSION}; -use editor::document::utility_types::Platform; -use editor::input::input_preprocessor::ModifierKeys; -use editor::input::mouse::{EditorMouseState, ScrollDelta, ViewportBounds}; -use editor::message_prelude::*; -use editor::Color; -use editor::Editor; -use editor::LayerId; +use editor::messages::input_mapper::utility_types::input_keyboard::ModifierKeys; +use editor::messages::input_mapper::utility_types::input_mouse::{EditorMouseState, ScrollDelta, ViewportBounds}; +use editor::messages::portfolio::document::utility_types::misc::Platform; +use editor::messages::prelude::*; +use graphene::color::Color; +use graphene::LayerId; use graphene::Operation; use serde::Serialize; @@ -24,7 +25,7 @@ use wasm_bindgen::prelude::*; /// This is necessary because WASM doesn't have a random number generator. #[wasm_bindgen] pub fn set_random_seed(seed: u64) { - editor::communication::set_uuid_seed(seed); + editor::application::set_uuid_seed(seed); } /// Provides a handle to access the raw WASM memory diff --git a/frontend/wasm/src/helpers.rs b/frontend/wasm/src/helpers.rs index 39d23e2323..da9ba12f41 100644 --- a/frontend/wasm/src/helpers.rs +++ b/frontend/wasm/src/helpers.rs @@ -1,7 +1,7 @@ use crate::JS_EDITOR_HANDLES; -use editor::input::keyboard::Key; -use editor::message_prelude::FrontendMessage; +use editor::messages::input_mapper::utility_types::input_keyboard::Key; +use editor::messages::prelude::*; use std::panic; use wasm_bindgen::prelude::*; diff --git a/frontend/wasm/src/lib.rs b/frontend/wasm/src/lib.rs index b8791df9c2..61741b8c2f 100644 --- a/frontend/wasm/src/lib.rs +++ b/frontend/wasm/src/lib.rs @@ -13,7 +13,7 @@ use wasm_bindgen::prelude::*; pub static EDITOR_HAS_CRASHED: AtomicBool = AtomicBool::new(false); pub static LOGGER: WasmLog = WasmLog; thread_local! { - pub static EDITOR_INSTANCES: RefCell> = RefCell::new(HashMap::new()); + pub static EDITOR_INSTANCES: RefCell> = RefCell::new(HashMap::new()); pub static JS_EDITOR_HANDLES: RefCell> = RefCell::new(HashMap::new()); } diff --git a/graphene/src/layers/text_layer/to_path.rs b/graphene/src/layers/text_layer/to_path.rs index 91b0884e5c..5fe98ae498 100644 --- a/graphene/src/layers/text_layer/to_path.rs +++ b/graphene/src/layers/text_layer/to_path.rs @@ -1,4 +1,4 @@ -use crate::layers::vector::constants::ManipulatorType; +use crate::layers::vector::consts::ManipulatorType; use crate::layers::vector::manipulator_group::ManipulatorGroup; use crate::layers::vector::manipulator_point::ManipulatorPoint; use crate::layers::vector::subpath::Subpath; diff --git a/graphene/src/layers/vector/constants.rs b/graphene/src/layers/vector/consts.rs similarity index 99% rename from graphene/src/layers/vector/constants.rs rename to graphene/src/layers/vector/consts.rs index 4882a09a50..c629c9c3b6 100644 --- a/graphene/src/layers/vector/constants.rs +++ b/graphene/src/layers/vector/consts.rs @@ -36,6 +36,7 @@ impl Index for [T; 3] { &self[mt as usize] } } + // Allows us to use ManipulatorType for indexing, mutably impl IndexMut for [T; 3] { fn index_mut(&mut self, mt: ManipulatorType) -> &mut T { diff --git a/graphene/src/layers/vector/manipulator_group.rs b/graphene/src/layers/vector/manipulator_group.rs index c7705c27f5..52ae831445 100644 --- a/graphene/src/layers/vector/manipulator_group.rs +++ b/graphene/src/layers/vector/manipulator_group.rs @@ -1,5 +1,5 @@ use super::{ - constants::{ManipulatorType, SELECTION_THRESHOLD}, + consts::{ManipulatorType, SELECTION_THRESHOLD}, manipulator_point::ManipulatorPoint, }; use glam::{DAffine2, DVec2}; @@ -64,7 +64,7 @@ impl ManipulatorGroup { } // TODO Convert into bool in subpath - /// Create a [ManipulatorGroup] that represents a close path signal. + /// Create a [ManipulatorGroup] that represents a close path command. pub fn closed() -> Self { Self { // An anchor (the first element) being `None` indicates a ClosePath (i.e. a path end command) diff --git a/graphene/src/layers/vector/manipulator_point.rs b/graphene/src/layers/vector/manipulator_point.rs index 3f1e7fcd59..99c1156778 100644 --- a/graphene/src/layers/vector/manipulator_point.rs +++ b/graphene/src/layers/vector/manipulator_point.rs @@ -1,4 +1,4 @@ -use super::constants::ManipulatorType; +use super::consts::ManipulatorType; use glam::{DAffine2, DVec2}; use serde::{Deserialize, Serialize}; diff --git a/graphene/src/layers/vector/mod.rs b/graphene/src/layers/vector/mod.rs index 65a855f497..c581eb34f5 100644 --- a/graphene/src/layers/vector/mod.rs +++ b/graphene/src/layers/vector/mod.rs @@ -1,4 +1,4 @@ -pub mod constants; +pub mod consts; pub mod manipulator_group; pub mod manipulator_point; pub mod subpath; diff --git a/graphene/src/layers/vector/subpath.rs b/graphene/src/layers/vector/subpath.rs index 52a38242dc..415790b557 100644 --- a/graphene/src/layers/vector/subpath.rs +++ b/graphene/src/layers/vector/subpath.rs @@ -1,4 +1,4 @@ -use super::constants::ManipulatorType; +use super::consts::ManipulatorType; use super::manipulator_group::ManipulatorGroup; use super::manipulator_point::ManipulatorPoint; use crate::layers::id_vec::IdBackedVec; diff --git a/graphene/src/operation.rs b/graphene/src/operation.rs index e29deefb33..27fbe7253e 100644 --- a/graphene/src/operation.rs +++ b/graphene/src/operation.rs @@ -2,7 +2,7 @@ use crate::boolean_ops::BooleanOperation as BooleanOperationType; use crate::layers::blend_mode::BlendMode; use crate::layers::layer_info::Layer; use crate::layers::style::{self, Stroke}; -use crate::layers::vector::constants::ManipulatorType; +use crate::layers::vector::consts::ManipulatorType; use crate::layers::vector::manipulator_group::ManipulatorGroup; use crate::layers::vector::subpath::Subpath; use crate::LayerId; diff --git a/proc-macros/src/lib.rs b/proc-macros/src/lib.rs index d71a37e661..ab126c601e 100644 --- a/proc-macros/src/lib.rs +++ b/proc-macros/src/lib.rs @@ -35,7 +35,7 @@ use syn::parse_macro_input; /// # Example /// ``` /// # use graphite_proc_macros::ToDiscriminant; -/// # use editor::misc::derivable_custom_traits::ToDiscriminant; +/// # use editor::utility_traits::ToDiscriminant; /// # use std::ffi::OsString; /// /// #[derive(ToDiscriminant)] @@ -80,7 +80,7 @@ pub fn derive_discriminant(input_item: TokenStream) -> TokenStream { /// # Example /// ``` /// # use graphite_proc_macros::TransitiveChild; -/// # use editor::misc::derivable_custom_traits::TransitiveChild; +/// # use editor::utility_traits::TransitiveChild; /// /// #[derive(Debug, Eq, PartialEq)] /// struct A { u: u8, b: B }; @@ -130,8 +130,8 @@ pub fn derive_transitive_child(input_item: TokenStream) -> TokenStream { /// See also [`TransitiveChild`] /// ``` /// # use graphite_proc_macros::{TransitiveChild, AsMessage}; -/// # use editor::misc::derivable_custom_traits::TransitiveChild; -/// # use editor::message_prelude::*; +/// # use editor::utility_traits::TransitiveChild; +/// # use editor::messages::prelude::*; /// /// #[derive(AsMessage)] /// pub enum TopMessage { @@ -218,7 +218,7 @@ pub fn impl_message(attr: TokenStream, input_item: TokenStream) -> TokenStream { /// # Example /// ``` /// # use graphite_proc_macros::Hint; -/// # use editor::misc::derivable_custom_traits::Hint; +/// # use editor::utility_traits::Hint; /// /// #[derive(Hint)] /// pub enum StateMachine {