diff --git a/Cargo.lock b/Cargo.lock index 0c9d4908..f9b8ea94 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -215,6 +215,7 @@ dependencies = [ "graphite-proc-macros", "kurbo", "log", + "once_cell", "rand_chacha", "remain", "serde", diff --git a/editor/Cargo.toml b/editor/Cargo.toml index 25164dc5..207f1359 100644 --- a/editor/Cargo.toml +++ b/editor/Cargo.toml @@ -25,6 +25,7 @@ kurbo = { git = "https://github.com/linebender/kurbo.git", features = [ ] } remain = "0.2.2" derivative = "2.2.0" +once_cell = "1.13.0" # Remove when `core::cell::OnceCell` is stabilized () [dependencies.graphene] path = "../graphene" diff --git a/editor/src/dispatcher.rs b/editor/src/dispatcher.rs index 4ef99728..9c9ecd91 100644 --- a/editor/src/dispatcher.rs +++ b/editor/src/dispatcher.rs @@ -17,6 +17,7 @@ struct DispatcherMessageHandlers { broadcast_message_handler: BroadcastMessageHandler, debug_message_handler: DebugMessageHandler, dialog_message_handler: DialogMessageHandler, + globals_message_handler: GlobalsMessageHandler, input_mapper_message_handler: InputMapperMessageHandler, input_preprocessor_message_handler: InputPreprocessorMessageHandler, layout_message_handler: LayoutMessageHandler, @@ -127,26 +128,25 @@ impl Dispatcher { self.responses.push(message); } } + Globals(message) => { + self.message_handlers.globals_message_handler.process_message(message, (), &mut queue); + } InputMapper(message) => { let actions = self.collect_actions(); - let keyboard_platform = self.message_handlers.portfolio_message_handler.platform.as_keyboard_platform_layout(); self.message_handlers .input_mapper_message_handler - .process_message(message, (&self.message_handlers.input_preprocessor_message_handler, keyboard_platform, actions), &mut queue); + .process_message(message, (&self.message_handlers.input_preprocessor_message_handler, actions), &mut queue); } InputPreprocessor(message) => { - let keyboard_platform = self.message_handlers.portfolio_message_handler.platform.as_keyboard_platform_layout(); + let keyboard_platform = GLOBAL_PLATFORM.get().expect("Failed to get GLOBAL_PLATFORM").as_keyboard_platform_layout(); 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(); - let action_input_mapping = &|action_to_find: &MessageDiscriminant| self.message_handlers.input_mapper_message_handler.action_input_mapping(action_to_find, keyboard_platform); + let action_input_mapping = &|action_to_find: &MessageDiscriminant| self.message_handlers.input_mapper_message_handler.action_input_mapping(action_to_find); - self.message_handlers - .layout_message_handler - .process_message(message, (action_input_mapping, keyboard_platform), &mut queue); + self.message_handlers.layout_message_handler.process_message(message, action_input_mapping, &mut queue); } Portfolio(message) => { self.message_handlers @@ -243,7 +243,6 @@ impl Dispatcher { #[cfg(test)] mod test { - use crate::application::set_uuid_seed; use crate::application::Editor; use crate::messages::portfolio::document::utility_types::clipboards::Clipboard; use crate::messages::prelude::*; @@ -262,14 +261,17 @@ mod test { /// 2. A blue shape /// 3. A green ellipse fn create_editor_with_three_layers() -> Editor { - set_uuid_seed(0); - let mut editor = Editor::new(); + init_logger(); + let mut editor = Editor::create(); editor.new_document(); + editor.select_primary_color(Color::RED); editor.draw_rect(100., 200., 300., 400.); + editor.select_primary_color(Color::BLUE); editor.draw_shape(10., 1200., 1300., 400.); + editor.select_primary_color(Color::GREEN); editor.draw_ellipse(104., 1200., 1300., 400.); @@ -282,7 +284,6 @@ mod test { /// - paste /// - assert that ellipse was copied fn copy_paste_single_layer() { - init_logger(); let mut editor = create_editor_with_three_layers(); let document_before_copy = editor.dispatcher.message_handlers.portfolio_message_handler.active_document().unwrap().graphene_document.clone(); @@ -316,7 +317,6 @@ mod test { /// - paste /// - assert that shape was copied fn copy_paste_single_layer_from_middle() { - init_logger(); let mut editor = create_editor_with_three_layers(); let document_before_copy = editor.dispatcher.message_handlers.portfolio_message_handler.active_document().unwrap().graphene_document.clone(); @@ -351,7 +351,6 @@ mod test { #[test] fn copy_paste_folder() { - init_logger(); let mut editor = create_editor_with_three_layers(); const FOLDER_INDEX: usize = 3; @@ -447,7 +446,6 @@ mod test { /// - paste /// - paste fn copy_paste_deleted_layers() { - init_logger(); let mut editor = create_editor_with_three_layers(); const ELLIPSE_INDEX: usize = 2; @@ -499,7 +497,6 @@ mod test { /// - select ellipse and rect /// - move them down and back up again fn move_selection() { - init_logger(); let mut editor = create_editor_with_three_layers(); fn map_to_vec(paths: Vec<&[LayerId]>) -> Vec> { @@ -555,8 +552,8 @@ mod test { }; init_logger(); - set_uuid_seed(0); - let mut editor = Editor::new(); + let mut editor = Editor::create(); + let test_file = include_str!("../graphite-test-document.graphite"); let responses = editor.handle_message(PortfolioMessage::OpenDocumentFile { document_name: "Graphite Version Test".into(), diff --git a/editor/src/messages/globals/global_variables.rs b/editor/src/messages/globals/global_variables.rs new file mode 100644 index 00000000..be16fa3c --- /dev/null +++ b/editor/src/messages/globals/global_variables.rs @@ -0,0 +1,5 @@ +use crate::messages::portfolio::document::utility_types::misc::Platform; + +use once_cell::sync::OnceCell; + +pub static GLOBAL_PLATFORM: OnceCell = OnceCell::new(); diff --git a/editor/src/messages/globals/globals_message.rs b/editor/src/messages/globals/globals_message.rs new file mode 100644 index 00000000..f71e88e1 --- /dev/null +++ b/editor/src/messages/globals/globals_message.rs @@ -0,0 +1,10 @@ +use crate::messages::portfolio::document::utility_types::misc::Platform; +use crate::messages::prelude::*; + +use serde::{Deserialize, Serialize}; + +#[impl_message(Message, Globals)] +#[derive(PartialEq, Eq, Clone, Debug, Serialize, Deserialize)] +pub enum GlobalsMessage { + SetPlatform { platform: Platform }, +} diff --git a/editor/src/messages/globals/globals_message_handler.rs b/editor/src/messages/globals/globals_message_handler.rs new file mode 100644 index 00000000..0073047a --- /dev/null +++ b/editor/src/messages/globals/globals_message_handler.rs @@ -0,0 +1,18 @@ +use crate::messages::prelude::*; + +#[derive(Debug, Default)] +pub struct GlobalsMessageHandler {} + +impl MessageHandler for GlobalsMessageHandler { + #[remain::check] + fn process_message(&mut self, message: GlobalsMessage, _data: (), _responses: &mut VecDeque) { + match message { + GlobalsMessage::SetPlatform { platform } => { + GLOBAL_PLATFORM.set(platform).expect("Failed to set GLOBAL_PLATFORM"); + } + } + } + + advertise_actions!(GlobalsMessageDiscriminant; + ); +} diff --git a/editor/src/messages/globals/mod.rs b/editor/src/messages/globals/mod.rs new file mode 100644 index 00000000..65cc989f --- /dev/null +++ b/editor/src/messages/globals/mod.rs @@ -0,0 +1,9 @@ +mod globals_message; +mod globals_message_handler; + +pub mod global_variables; + +#[doc(inline)] +pub use globals_message::{GlobalsMessage, GlobalsMessageDiscriminant}; +#[doc(inline)] +pub use globals_message_handler::GlobalsMessageHandler; diff --git a/editor/src/messages/input_mapper/default_mapping.rs b/editor/src/messages/input_mapper/default_mapping.rs index 341b9433..751b72ac 100644 --- a/editor/src/messages/input_mapper/default_mapping.rs +++ b/editor/src/messages/input_mapper/default_mapping.rs @@ -4,7 +4,6 @@ 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; @@ -75,10 +74,7 @@ pub fn default_mapping() -> Mapping { // TextToolMessage entry!(KeyUp(Lmb); action_dispatch=TextToolMessage::Interact), entry!(KeyDown(Escape); action_dispatch=TextToolMessage::Abort), - entry_multiplatform!( - standard!(KeyDown(Enter); modifiers=[Control], action_dispatch=TextToolMessage::CommitText), - mac_only!(KeyDown(Enter); modifiers=[Command], action_dispatch=TextToolMessage::CommitText), - ), + entry!(KeyDown(Enter); modifiers=[Accel], action_dispatch=TextToolMessage::CommitText), // // GradientToolMessage entry!(KeyDown(Lmb); action_dispatch=GradientToolMessage::PointerDown), @@ -159,10 +155,7 @@ pub fn default_mapping() -> Mapping { 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=[Shift, Control], action_dispatch=ToolMessage::ResetColors), - mac_only!(KeyDown(KeyX); modifiers=[Shift, Command], action_dispatch=ToolMessage::ResetColors), - ), + entry!(KeyDown(KeyX); modifiers=[Shift, Accel], action_dispatch=ToolMessage::ResetColors), entry!(KeyDown(KeyX); modifiers=[Shift], action_dispatch=ToolMessage::SwapColors), entry!(KeyDown(KeyC); modifiers=[Alt], action_dispatch=ToolMessage::SelectRandomPrimaryColor), // @@ -170,70 +163,22 @@ pub fn default_mapping() -> Mapping { entry!(KeyDown(Delete); action_dispatch=DocumentMessage::DeleteSelectedLayers), entry!(KeyDown(Backspace); action_dispatch=DocumentMessage::DeleteSelectedLayers), entry!(KeyDown(KeyP); modifiers=[Alt], action_dispatch=DocumentMessage::DebugPrintDocument), - entry_multiplatform!( - standard!(KeyDown(KeyZ); modifiers=[Control, Shift], action_dispatch=DocumentMessage::Redo), - mac_only!(KeyDown(KeyZ); modifiers=[Command, Shift], action_dispatch=DocumentMessage::Redo), - ), - entry_multiplatform!( - standard!(KeyDown(KeyZ); modifiers=[Control], action_dispatch=DocumentMessage::Undo), - mac_only!(KeyDown(KeyZ); modifiers=[Command], action_dispatch=DocumentMessage::Undo), - ), - entry_multiplatform!( - standard!(KeyDown(KeyA); modifiers=[Control, Alt], action_dispatch=DocumentMessage::DeselectAllLayers), - mac_only!(KeyDown(KeyA); modifiers=[Command, Alt], action_dispatch=DocumentMessage::DeselectAllLayers), - ), - entry_multiplatform!( - standard!(KeyDown(KeyA); modifiers=[Control], action_dispatch=DocumentMessage::SelectAllLayers), - mac_only!(KeyDown(KeyA); modifiers=[Command], action_dispatch=DocumentMessage::SelectAllLayers), - ), - entry_multiplatform!( - standard!(KeyDown(KeyS); modifiers=[Control], action_dispatch=DocumentMessage::SaveDocument), - mac_only!(KeyDown(KeyS); modifiers=[Command], action_dispatch=DocumentMessage::SaveDocument), - ), - entry_multiplatform!( - standard!(KeyDown(KeyD); modifiers=[Control], action_dispatch=DocumentMessage::DuplicateSelectedLayers), - mac_only!(KeyDown(KeyD); modifiers=[Command], action_dispatch=DocumentMessage::DuplicateSelectedLayers), - ), - entry_multiplatform!( - standard!(KeyDown(KeyG); modifiers=[Control], action_dispatch=DocumentMessage::GroupSelectedLayers), - mac_only!(KeyDown(KeyG); modifiers=[Command], action_dispatch=DocumentMessage::GroupSelectedLayers), - ), - entry_multiplatform!( - standard!(KeyDown(KeyG); modifiers=[Control, Shift], action_dispatch=DocumentMessage::UngroupSelectedLayers), - mac_only!(KeyDown(KeyG); modifiers=[Command, Shift], action_dispatch=DocumentMessage::UngroupSelectedLayers), - ), - entry_multiplatform!( - standard!(KeyDown(KeyN); modifiers=[Control, Shift], action_dispatch=DocumentMessage::CreateEmptyFolder { container_path: vec![] }), - mac_only!(KeyDown(KeyN); modifiers=[Command, Shift], action_dispatch=DocumentMessage::CreateEmptyFolder { container_path: vec![] }), - ), - entry_multiplatform!( - standard!(KeyDown(Digit0); modifiers=[Control], action_dispatch=DocumentMessage::ZoomCanvasToFitAll), - mac_only!(KeyDown(Digit0); modifiers=[Command], action_dispatch=DocumentMessage::ZoomCanvasToFitAll), - ), - entry_multiplatform!( - standard!(KeyDown(Digit1); modifiers=[Control], action_dispatch=DocumentMessage::ZoomCanvasTo100Percent), - mac_only!(KeyDown(Digit1); modifiers=[Command], action_dispatch=DocumentMessage::ZoomCanvasTo100Percent), - ), - entry_multiplatform!( - standard!(KeyDown(Digit2); modifiers=[Control], action_dispatch=DocumentMessage::ZoomCanvasTo200Percent), - mac_only!(KeyDown(Digit2); modifiers=[Command], action_dispatch=DocumentMessage::ZoomCanvasTo200Percent), - ), - entry_multiplatform!( - standard!(KeyDown(BracketLeft); modifiers=[Control, Shift], action_dispatch=DocumentMessage::SelectedLayersLowerToBack), - mac_only!(KeyDown(BracketLeft); modifiers=[Command, Shift], action_dispatch=DocumentMessage::SelectedLayersLowerToBack), - ), - entry_multiplatform!( - standard!(KeyDown(BracketRight); modifiers=[Control, Shift], action_dispatch=DocumentMessage::SelectedLayersRaiseToFront), - mac_only!(KeyDown(BracketRight); modifiers=[Command, Shift], action_dispatch=DocumentMessage::SelectedLayersRaiseToFront), - ), - entry_multiplatform!( - standard!(KeyDown(BracketLeft); modifiers=[Control], action_dispatch=DocumentMessage::SelectedLayersLower), - mac_only!(KeyDown(BracketLeft); modifiers=[Command], action_dispatch=DocumentMessage::SelectedLayersLower), - ), - entry_multiplatform!( - standard!(KeyDown(BracketRight); modifiers=[Control], action_dispatch=DocumentMessage::SelectedLayersRaise), - mac_only!(KeyDown(BracketRight); modifiers=[Command], action_dispatch=DocumentMessage::SelectedLayersRaise), - ), + entry!(KeyDown(KeyZ); modifiers=[Accel, Shift], action_dispatch=DocumentMessage::Redo), + entry!(KeyDown(KeyZ); modifiers=[Accel], action_dispatch=DocumentMessage::Undo), + entry!(KeyDown(KeyA); modifiers=[Accel, Alt], action_dispatch=DocumentMessage::DeselectAllLayers), + entry!(KeyDown(KeyA); modifiers=[Accel], action_dispatch=DocumentMessage::SelectAllLayers), + entry!(KeyDown(KeyS); modifiers=[Accel], action_dispatch=DocumentMessage::SaveDocument), + entry!(KeyDown(KeyD); modifiers=[Accel], action_dispatch=DocumentMessage::DuplicateSelectedLayers), + entry!(KeyDown(KeyG); modifiers=[Accel], action_dispatch=DocumentMessage::GroupSelectedLayers), + entry!(KeyDown(KeyG); modifiers=[Accel, Shift], action_dispatch=DocumentMessage::UngroupSelectedLayers), + entry!(KeyDown(KeyN); modifiers=[Accel, Shift], action_dispatch=DocumentMessage::CreateEmptyFolder { container_path: vec![] }), + entry!(KeyDown(Digit0); modifiers=[Accel], action_dispatch=DocumentMessage::ZoomCanvasToFitAll), + entry!(KeyDown(Digit1); modifiers=[Accel], action_dispatch=DocumentMessage::ZoomCanvasTo100Percent), + entry!(KeyDown(Digit2); modifiers=[Accel], action_dispatch=DocumentMessage::ZoomCanvasTo200Percent), + entry!(KeyDown(BracketLeft); modifiers=[Accel, Shift], action_dispatch=DocumentMessage::SelectedLayersLowerToBack), + entry!(KeyDown(BracketRight); modifiers=[Accel, Shift], action_dispatch=DocumentMessage::SelectedLayersRaiseToFront), + entry!(KeyDown(BracketLeft); modifiers=[Accel], action_dispatch=DocumentMessage::SelectedLayersLower), + entry!(KeyDown(BracketRight); modifiers=[Accel], action_dispatch=DocumentMessage::SelectedLayersRaise), entry!(KeyDown(ArrowUp); modifiers=[Shift, ArrowLeft], action_dispatch=DocumentMessage::NudgeSelectedLayers { delta_x: -BIG_NUDGE_AMOUNT, delta_y: -BIG_NUDGE_AMOUNT }), entry!(KeyDown(ArrowUp); modifiers=[Shift, ArrowRight], action_dispatch=DocumentMessage::NudgeSelectedLayers { delta_x: BIG_NUDGE_AMOUNT, delta_y: -BIG_NUDGE_AMOUNT }), entry!(KeyDown(ArrowUp); modifiers=[Shift], action_dispatch=DocumentMessage::NudgeSelectedLayers { delta_x: 0., delta_y: -BIG_NUDGE_AMOUNT }), @@ -271,18 +216,9 @@ pub fn default_mapping() -> Mapping { entry!(KeyUp(Mmb); action_dispatch=NavigationMessage::TransformCanvasEnd), entry!(KeyDown(Lmb); modifiers=[Space], action_dispatch=NavigationMessage::TranslateCanvasBegin), entry!(KeyUp(Lmb); modifiers=[Space], action_dispatch=NavigationMessage::TransformCanvasEnd), - entry_multiplatform!( - standard!(KeyDown(NumpadAdd); modifiers=[Control], action_dispatch=NavigationMessage::IncreaseCanvasZoom { center_on_mouse: false }), - mac_only!(KeyDown(NumpadAdd); modifiers=[Command], action_dispatch=NavigationMessage::IncreaseCanvasZoom { center_on_mouse: false }), - ), - entry_multiplatform!( - standard!(KeyDown(Equal); modifiers=[Control], action_dispatch=NavigationMessage::IncreaseCanvasZoom { center_on_mouse: false }), - mac_only!(KeyDown(Equal); modifiers=[Command], action_dispatch=NavigationMessage::IncreaseCanvasZoom { center_on_mouse: false }), - ), - entry_multiplatform!( - standard!(KeyDown(Minus); modifiers=[Control], action_dispatch=NavigationMessage::DecreaseCanvasZoom { center_on_mouse: false }), - mac_only!(KeyDown(Minus); modifiers=[Command], action_dispatch=NavigationMessage::DecreaseCanvasZoom { center_on_mouse: false }), - ), + entry!(KeyDown(NumpadAdd); modifiers=[Accel], action_dispatch=NavigationMessage::IncreaseCanvasZoom { center_on_mouse: false }), + entry!(KeyDown(Equal); modifiers=[Accel], action_dispatch=NavigationMessage::IncreaseCanvasZoom { center_on_mouse: false }), + entry!(KeyDown(Minus); modifiers=[Accel], action_dispatch=NavigationMessage::DecreaseCanvasZoom { center_on_mouse: false }), entry!(WheelScroll; modifiers=[Control], action_dispatch=NavigationMessage::WheelCanvasZoom), entry!(WheelScroll; modifiers=[Shift], action_dispatch=NavigationMessage::WheelCanvasTranslate { use_y_as_x: true }), entry!(WheelScroll; action_dispatch=NavigationMessage::WheelCanvasTranslate { use_y_as_x: false }), @@ -292,47 +228,19 @@ pub fn default_mapping() -> Mapping { entry!(KeyDown(PageDown); action_dispatch=NavigationMessage::TranslateCanvasByViewportFraction { delta: DVec2::new(0., -1.) }), // // PortfolioMessage - entry_multiplatform!( - standard!(KeyDown(KeyO); modifiers=[Control], action_dispatch=PortfolioMessage::OpenDocument), - mac_only!(KeyDown(KeyO); modifiers=[Command], action_dispatch=PortfolioMessage::OpenDocument), - ), - entry_multiplatform!( - standard!(KeyDown(KeyI); modifiers=[Control], action_dispatch=PortfolioMessage::Import), - mac_only!(KeyDown(KeyI); modifiers=[Command], action_dispatch=PortfolioMessage::Import), - ), + entry!(KeyDown(KeyO); modifiers=[Accel], action_dispatch=PortfolioMessage::OpenDocument), + entry!(KeyDown(KeyI); modifiers=[Accel], action_dispatch=PortfolioMessage::Import), entry!(KeyDown(Tab); modifiers=[Control], action_dispatch=PortfolioMessage::NextDocument), entry!(KeyDown(Tab); modifiers=[Control, Shift], action_dispatch=PortfolioMessage::PrevDocument), - entry_multiplatform!( - standard!(KeyDown(KeyW); modifiers=[Control], action_dispatch=PortfolioMessage::CloseActiveDocumentWithConfirmation), - mac_only!(KeyDown(KeyW); modifiers=[Command], action_dispatch=PortfolioMessage::CloseActiveDocumentWithConfirmation), - ), - entry_multiplatform!( - standard!(KeyDown(KeyX); modifiers=[Control], action_dispatch=PortfolioMessage::Cut { clipboard: Clipboard::Device }), - mac_only!(KeyDown(KeyX); modifiers=[Command], action_dispatch=PortfolioMessage::Cut { clipboard: Clipboard::Device }), - ), - entry_multiplatform!( - standard!(KeyDown(KeyC); modifiers=[Control], action_dispatch=PortfolioMessage::Copy { clipboard: Clipboard::Device }), - mac_only!(KeyDown(KeyC); modifiers=[Command], 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=[Control], action_dispatch=FrontendMessage::TriggerPaste), - mac_only!(KeyDown(KeyV); modifiers=[Command], action_dispatch=FrontendMessage::TriggerPaste), - ), + entry!(KeyDown(KeyW); modifiers=[Accel], action_dispatch=PortfolioMessage::CloseActiveDocumentWithConfirmation), + entry!(KeyDown(KeyX); modifiers=[Accel], action_dispatch=PortfolioMessage::Cut { clipboard: Clipboard::Device }), + entry!(KeyDown(KeyC); modifiers=[Accel], action_dispatch=PortfolioMessage::Copy { clipboard: Clipboard::Device }), + entry!(KeyDown(KeyV); modifiers=[Accel], action_dispatch=FrontendMessage::TriggerPaste), // // DialogMessage - entry_multiplatform!( - standard!(KeyDown(KeyN); modifiers=[Control], action_dispatch=DialogMessage::RequestNewDocumentDialog), - mac_only!(KeyDown(KeyN); modifiers=[Command], action_dispatch=DialogMessage::RequestNewDocumentDialog), - ), - entry_multiplatform!( - standard!(KeyDown(KeyW); modifiers=[Control, Alt], action_dispatch=DialogMessage::CloseAllDocumentsWithConfirmation), - mac_only!(KeyDown(KeyW); modifiers=[Command, Alt], action_dispatch=DialogMessage::CloseAllDocumentsWithConfirmation), - ), - entry_multiplatform!( - standard!(KeyDown(KeyE); modifiers=[Control], action_dispatch=DialogMessage::RequestExportDialog), - mac_only!(KeyDown(KeyE); modifiers=[Command], action_dispatch=DialogMessage::RequestExportDialog), - ), + entry!(KeyDown(KeyN); modifiers=[Accel], action_dispatch=DialogMessage::RequestNewDocumentDialog), + entry!(KeyDown(KeyW); modifiers=[Accel, Alt], action_dispatch=DialogMessage::CloseAllDocumentsWithConfirmation), + entry!(KeyDown(KeyE); modifiers=[Accel], action_dispatch=DialogMessage::RequestExportDialog), // // DebugMessage entry!(KeyDown(KeyT); modifiers=[Alt], action_dispatch=DebugMessage::ToggleTraceLogs), @@ -350,7 +258,6 @@ pub fn default_mapping() -> Mapping { MappingEntry { action: TransformLayerMessage::TypeDigit { digit: i as u8 }.into(), input: InputMapperMessage::KeyDown(*key), - platform_layout: None, modifiers: modifiers!(), }, ); diff --git a/editor/src/messages/input_mapper/input_mapper_message_handler.rs b/editor/src/messages/input_mapper/input_mapper_message_handler.rs index 35d15a46..953501e2 100644 --- a/editor/src/messages/input_mapper/input_mapper_message_handler.rs +++ b/editor/src/messages/input_mapper/input_mapper_message_handler.rs @@ -1,7 +1,6 @@ use super::utility_types::input_keyboard::KeysGroup; use super::utility_types::misc::Mapping; use crate::messages::input_mapper::utility_types::input_keyboard::{self, Key}; -use crate::messages::portfolio::document::utility_types::misc::KeyboardPlatformLayout; use crate::messages::prelude::*; use std::fmt::Write; @@ -11,11 +10,11 @@ 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; +impl MessageHandler for InputMapperMessageHandler { + fn process_message(&mut self, message: InputMapperMessage, data: (&InputPreprocessorMessageHandler, ActionList), responses: &mut VecDeque) { + let (input, actions) = data; - if let Some(message) = self.mapping.match_input_message(message, &input.keyboard, actions, keyboard_platform) { + if let Some(message) = self.mapping.match_input_message(message, &input.keyboard, actions) { responses.push_back(message); } } @@ -44,7 +43,7 @@ impl InputMapperMessageHandler { output.replace("Key", "") } - pub fn action_input_mapping(&self, action_to_find: &MessageDiscriminant, keyboard_platform: KeyboardPlatformLayout) -> Vec { + pub fn action_input_mapping(&self, action_to_find: &MessageDiscriminant) -> Vec { let key_up = self.mapping.key_up.iter(); let key_down = self.mapping.key_down.iter(); let double_click = std::iter::once(&self.mapping.double_click); @@ -56,8 +55,6 @@ impl InputMapperMessageHandler { // Filter for the desired message let found_actions = all_mapping_entries.filter(|entry| entry.action.to_discriminant() == *action_to_find); - // Filter for a compatible keyboard platform layout - let found_actions = found_actions.filter(|entry| if let Some(layout) = entry.platform_layout { layout == keyboard_platform } else { true }); // Find the key combinations for all keymaps matching the desired action assert!(std::mem::size_of::() >= std::mem::size_of::()); diff --git a/editor/src/messages/input_mapper/utility_types/input_keyboard.rs b/editor/src/messages/input_mapper/utility_types/input_keyboard.rs index 9157ed53..31586202 100644 --- a/editor/src/messages/input_mapper/utility_types/input_keyboard.rs +++ b/editor/src/messages/input_mapper/utility_types/input_keyboard.rs @@ -20,6 +20,14 @@ const KEY_MASK_STORAGE_LENGTH: usize = (NUMBER_OF_KEYS + STORAGE_SIZE_BITS - 1) pub type KeyStates = BitVector; +pub fn all_required_modifiers_pressed(keyboard_state: &KeyStates, modifiers: &KeyStates) -> bool { + // 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 & *modifiers; + let all_modifiers_without_pressed_modifiers = *modifiers ^ pressed_modifiers; + + all_modifiers_without_pressed_modifiers.is_empty() +} + pub enum KeyPosition { Pressed, Released, @@ -29,10 +37,10 @@ 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; + const SHIFT = 0b_0000_0001; + const ALT = 0b_0000_0010; + const CONTROL = 0b_0000_0100; + const META_OR_COMMAND = 0b_0000_1000; } } @@ -193,6 +201,7 @@ pub enum Key { // Other keys that aren't part of the W3C spec Command, + Accel, Lmb, Rmb, Mmb, @@ -228,6 +237,8 @@ impl fmt::Display for Key { return write!(f, "{}", key_name.chars().skip(KEY_PREFIX.len()).collect::()); } + let keyboard_layout = || GLOBAL_PLATFORM.get().expect("Failed to get GLOBAL_PLATFORM").as_keyboard_platform_layout(); + let name = match self { // Writing system keys Self::Backquote => "`", @@ -243,7 +254,23 @@ impl fmt::Display for Key { Self::Slash => "/", // Functional keys - Self::Control => "Ctrl", + Self::Alt => match keyboard_layout() { + KeyboardPlatformLayout::Standard => "Alt", + KeyboardPlatformLayout::Mac => "⌥", + }, + Self::Meta => match keyboard_layout() { + KeyboardPlatformLayout::Standard => "⊞", + KeyboardPlatformLayout::Mac => "⌘", + }, + Self::Shift => match keyboard_layout() { + KeyboardPlatformLayout::Standard => "Shift", + KeyboardPlatformLayout::Mac => "⇧", + }, + Self::Control => match keyboard_layout() { + KeyboardPlatformLayout::Standard => "Ctrl", + KeyboardPlatformLayout::Mac => "⌃", + }, + Self::Backspace => "⌫", // Control pad keys Self::Delete => "Del", @@ -267,6 +294,13 @@ impl fmt::Display for Key { Self::Escape => "Esc", Self::PrintScreen => "PrtScr", + // Other keys that aren't part of the W3C spec + Self::Command => "⌘", + Self::Accel => match keyboard_layout() { + KeyboardPlatformLayout::Standard => "Ctrl", + KeyboardPlatformLayout::Mac => "⌘", + }, + _ => key_name.as_str(), }; @@ -280,36 +314,31 @@ pub const NUMBER_OF_KEYS: usize = Key::NumKeys as usize; #[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] pub struct KeysGroup(pub Vec); -impl KeysGroup { - pub fn keys_text_shortcut(&self, keyboard_platform: KeyboardPlatformLayout) -> String { - const JOINER_MARK: &str = "+"; +impl fmt::Display for KeysGroup { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + const JOINER_MARK: &str = " "; let mut joined = self .0 .iter() .map(|key| { - let key_string = key.to_string(); + let keyboard_layout = GLOBAL_PLATFORM.get().expect("Failed to get GLOBAL_PLATFORM").as_keyboard_platform_layout(); + let key_is_modifier = matches!(*key, Key::Control | Key::Command | Key::Alt | Key::Shift | Key::Meta | Key::Accel); - 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, - } + if keyboard_layout == KeyboardPlatformLayout::Mac && key_is_modifier { + key.to_string() } else { - key_string + JOINER_MARK + key.to_string() + JOINER_MARK } }) .collect::(); - // Truncate to cut the joining character off the end if it's present + // Cut the joining character off the end, if present if joined.ends_with(JOINER_MARK) { joined.truncate(joined.len() - JOINER_MARK.len()); } - joined + write!(f, "{}", joined) } } diff --git a/editor/src/messages/input_mapper/utility_types/macros.rs b/editor/src/messages/input_mapper/utility_types/macros.rs index 9bd5d4e0..fb61db89 100644 --- a/editor/src/messages/input_mapper/utility_types/macros.rs +++ b/editor/src/messages/input_mapper/utility_types/macros.rs @@ -13,28 +13,24 @@ macro_rules! modifiers { /// Builds a slice of `MappingEntry` struct(s) that are used to: /// - ...dispatch the given `action_dispatch` as an output `Message` if its discriminant is a currently available action /// - ...when the `InputMapperMessage` enum variant, as specified at the start and followed by a semicolon, is received -/// - ...while any further conditions are met, like the optional `modifiers` being pressed or `layout` matching the OS. +/// - ...while the optional `modifiers` being pressed. /// /// Syntax: /// ```rs -/// entry_for_layout!(Key; modifiers?: Key[], refresh_keys?: Key[], action_dispatch: Message, layout: Option) +/// entry_for_layout!(Key; modifiers?: Key[], refresh_keys?: Key[], action_dispatch: Message) /// ``` /// -/// To avoid having to specify the final `layout` argument, instead use the wrapper macros: [entry]!, [standard]!, and [mac]!. -/// The former sets the layout to `None` which means the key mapping is layout-agnostic and compatible with all platforms. -/// /// The actions system controls which actions are currently available. Those are provided by the different message handlers based on the current application state and context. /// Each handler adds or removes actions in the form of message discriminants. Here, we tie an input condition (such as a hotkey) to an action's full message. /// When an action is currently available, and the user enters that input, the action's message is dispatched on the message bus. -macro_rules! entry_for_layout { - ($input:expr; $(modifiers=[$($modifier:ident),*],)? $(refresh_keys=[$($refresh:ident),* $(,)?],)? action_dispatch=$action_dispatch:expr,$(,)? layout=$layout:expr) => { - &[ +macro_rules! entry { + ($input:expr; $(modifiers=[$($modifier:ident),*],)? $(refresh_keys=[$($refresh:ident),* $(,)?],)? action_dispatch=$action_dispatch:expr$(,)?) => { + &[&[ // Cause the `action_dispatch` message to be sent when the specified input occurs. MappingEntry { action: $action_dispatch.into(), input: $input, modifiers: modifiers!($($($modifier),*)?), - platform_layout: $layout, }, // Also cause the `action_dispatch` message to be sent when any of the specified refresh keys change. @@ -48,70 +44,15 @@ macro_rules! entry_for_layout { action: $action_dispatch.into(), input: InputMapperMessage::KeyDown(Key::$refresh), modifiers: modifiers!(), - platform_layout: $layout, }, MappingEntry { action: $action_dispatch.into(), input: InputMapperMessage::KeyUp(Key::$refresh), modifiers: modifiers!(), - platform_layout: $layout, }, )* )* - ] - }; -} - -/// Wraps [entry_for_layout]! and calls it with an agnostic (`None`) keyboard platform `layout` to avoid having to specify that argument. -/// -/// Syntax: -/// ```rs -/// entry!(Key; modifiers?: Key[], refresh_keys?: Key[], action_dispatch: Message) -/// ``` -macro_rules! entry { - ($($arg:tt)*) => { - &[entry_for_layout!($($arg)*, layout=None)] - }; -} - -/// Wraps [entry_for_layout]! and calls it with a `Standard` keyboard platform `layout` to avoid having to specify that argument. -/// -/// Syntax: -/// ```rs -/// standard!(Key; modifiers?: Key[], refresh_keys?: Key[], action_dispatch: Message) -/// ``` -macro_rules! standard { - ($($arg:tt)*) => { - entry_for_layout!($($arg)*, layout=Some(KeyboardPlatformLayout::Standard)) - }; -} - -/// Wraps [entry_for_layout]! and calls it with a `Mac` keyboard platform `layout` to avoid having to specify that argument. -/// -/// Syntax: -/// ```rs -/// mac_only!(Key; modifiers?: Key[], refresh_keys?: Key[], action_dispatch: Message) -/// ``` -macro_rules! mac_only { - ($($arg:tt)*) => { - entry_for_layout!($($arg)*, layout=Some(KeyboardPlatformLayout::Mac)) - }; -} - -/// Groups multiple related entries for different platforms. -/// When a keyboard shortcut is not platform-agnostic, this should be used to contain a [mac]! and/or [standard]! entry. -/// -/// Syntax: -/// -/// ```rs -/// entry_multiplatform!( -/// standard!(Key; modifiers?: Key[], refresh_keys?: Key[], action_dispatch: Message), -/// mac_only!(Key; modifiers?: Key[], refresh_keys?: Key[], action_dispatch: Message), -/// ) -/// ``` -macro_rules! entry_multiplatform { - {$($arg:expr),*,} => { - &[$($arg ),*] + ]] }; } @@ -159,9 +100,5 @@ macro_rules! action_keys { pub(crate) use action_keys; pub(crate) use entry; -pub(crate) use entry_for_layout; -pub(crate) use entry_multiplatform; -pub(crate) use mac_only; pub(crate) use mapping; pub(crate) use modifiers; -pub(crate) use standard; diff --git a/editor/src/messages/input_mapper/utility_types/misc.rs b/editor/src/messages/input_mapper/utility_types/misc.rs index ec203cb2..0eeac0d6 100644 --- a/editor/src/messages/input_mapper/utility_types/misc.rs +++ b/editor/src/messages/input_mapper/utility_types/misc.rs @@ -1,7 +1,6 @@ -use super::input_keyboard::KeysGroup; +use super::input_keyboard::{all_required_modifiers_pressed, KeysGroup}; use crate::messages::input_mapper::default_mapping::default_mapping; use crate::messages::input_mapper::utility_types::input_keyboard::{KeyStates, NUMBER_OF_KEYS}; -use crate::messages::portfolio::document::utility_types::misc::KeyboardPlatformLayout; use crate::messages::prelude::*; use serde::{Deserialize, Serialize}; @@ -16,7 +15,7 @@ pub struct Mapping { } impl Mapping { - pub fn match_input_message(&self, message: InputMapperMessage, keyboard_state: &KeyStates, actions: ActionList, keyboard_platform: KeyboardPlatformLayout) -> Option { + pub fn match_input_message(&self, message: InputMapperMessage, keyboard_state: &KeyStates, actions: ActionList) -> Option { let list = match message { InputMapperMessage::KeyDown(key) => &self.key_down[key as usize], InputMapperMessage::KeyUp(key) => &self.key_up[key as usize], @@ -24,7 +23,7 @@ impl Mapping { InputMapperMessage::WheelScroll => &self.wheel_scroll, InputMapperMessage::PointerMove => &self.pointer_move, }; - list.match_mapping(keyboard_state, actions, keyboard_platform) + list.match_mapping(keyboard_state, actions) } } @@ -38,26 +37,15 @@ impl Default for Mapping { 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(); + pub fn match_mapping(&self, keyboard_state: &KeyStates, actions: ActionList) -> Option { + for mapping in self.0.iter() { // 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()); + if all_required_modifiers_pressed(keyboard_state, &mapping.modifiers) { + // Search for the action in the list of available actions to see if it's currently available to activate + let matching_action_found = actions.iter().flatten().any(|action| mapping.action.to_discriminant() == *action); + if matching_action_found { + return Some(mapping.action.clone()); + } } } None @@ -87,8 +75,6 @@ pub struct MappingEntry { 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, Eq, PartialEq, Serialize, Deserialize)] diff --git a/editor/src/messages/input_preprocessor/input_preprocessor_message_handler.rs b/editor/src/messages/input_preprocessor/input_preprocessor_message_handler.rs index c5d94d6e..44d5ec6a 100644 --- a/editor/src/messages/input_preprocessor/input_preprocessor_message_handler.rs +++ b/editor/src/messages/input_preprocessor/input_preprocessor_message_handler.rs @@ -54,7 +54,7 @@ impl MessageHandler for InputP } } InputPreprocessorMessage::DoubleClick { editor_mouse_state, modifier_keys } => { - self.handle_modifier_keys(modifier_keys, keyboard_platform, responses); + self.update_states_of_modifier_keys(modifier_keys, keyboard_platform, responses); let mouse_state = editor_mouse_state.to_mouse_state(&self.viewport_bounds); self.mouse.position = mouse_state.position; @@ -62,17 +62,17 @@ impl MessageHandler for InputP responses.push_back(InputMapperMessage::DoubleClick.into()); } InputPreprocessorMessage::KeyDown { key, modifier_keys } => { - self.handle_modifier_keys(modifier_keys, keyboard_platform, responses); + self.update_states_of_modifier_keys(modifier_keys, keyboard_platform, responses); self.keyboard.set(key as usize); responses.push_back(InputMapperMessage::KeyDown(key).into()); } InputPreprocessorMessage::KeyUp { key, modifier_keys } => { - self.handle_modifier_keys(modifier_keys, keyboard_platform, responses); + self.update_states_of_modifier_keys(modifier_keys, keyboard_platform, responses); self.keyboard.unset(key as usize); responses.push_back(InputMapperMessage::KeyUp(key).into()); } InputPreprocessorMessage::PointerDown { editor_mouse_state, modifier_keys } => { - self.handle_modifier_keys(modifier_keys, keyboard_platform, responses); + self.update_states_of_modifier_keys(modifier_keys, keyboard_platform, responses); let mouse_state = editor_mouse_state.to_mouse_state(&self.viewport_bounds); self.mouse.position = mouse_state.position; @@ -80,7 +80,7 @@ impl MessageHandler for InputP self.translate_mouse_event(mouse_state, true, responses); } InputPreprocessorMessage::PointerMove { editor_mouse_state, modifier_keys } => { - self.handle_modifier_keys(modifier_keys, keyboard_platform, responses); + self.update_states_of_modifier_keys(modifier_keys, keyboard_platform, responses); let mouse_state = editor_mouse_state.to_mouse_state(&self.viewport_bounds); self.mouse.position = mouse_state.position; @@ -91,7 +91,7 @@ impl MessageHandler for InputP self.translate_mouse_event(mouse_state, false, responses); } InputPreprocessorMessage::PointerUp { editor_mouse_state, modifier_keys } => { - self.handle_modifier_keys(modifier_keys, keyboard_platform, responses); + self.update_states_of_modifier_keys(modifier_keys, keyboard_platform, responses); let mouse_state = editor_mouse_state.to_mouse_state(&self.viewport_bounds); self.mouse.position = mouse_state.position; @@ -99,7 +99,7 @@ impl MessageHandler for InputP self.translate_mouse_event(mouse_state, false, responses); } InputPreprocessorMessage::WheelScroll { editor_mouse_state, modifier_keys } => { - self.handle_modifier_keys(modifier_keys, keyboard_platform, responses); + self.update_states_of_modifier_keys(modifier_keys, keyboard_platform, responses); let mouse_state = editor_mouse_state.to_mouse_state(&self.viewport_bounds); self.mouse.position = mouse_state.position; @@ -138,18 +138,30 @@ impl InputPreprocessorMessageHandler { self.mouse = new_state; } - fn handle_modifier_keys(&mut self, modifier_keys: ModifierKeys, keyboard_platform: KeyboardPlatformLayout, responses: &mut VecDeque) { - self.handle_modifier_key(Key::Shift, modifier_keys.contains(ModifierKeys::SHIFT), responses); - self.handle_modifier_key(Key::Alt, modifier_keys.contains(ModifierKeys::ALT), responses); - self.handle_modifier_key(Key::Control, modifier_keys.contains(ModifierKeys::CONTROL), responses); + fn update_states_of_modifier_keys(&mut self, pressed_modifier_keys: ModifierKeys, keyboard_platform: KeyboardPlatformLayout, responses: &mut VecDeque) { + let is_key_pressed = |key_to_check: ModifierKeys| pressed_modifier_keys.contains(key_to_check); + + // Update the state of the concrete modifier keys based on the source state + self.update_modifier_key(Key::Shift, is_key_pressed(ModifierKeys::SHIFT), responses); + self.update_modifier_key(Key::Alt, is_key_pressed(ModifierKeys::ALT), responses); + self.update_modifier_key(Key::Control, is_key_pressed(ModifierKeys::CONTROL), responses); + + // Update the state of either the concrete Meta or the Command keys based on which one is applicable for this platform let meta_or_command = match keyboard_platform { KeyboardPlatformLayout::Mac => Key::Command, KeyboardPlatformLayout::Standard => Key::Meta, }; - self.handle_modifier_key(meta_or_command, modifier_keys.contains(ModifierKeys::META_OR_COMMAND), responses); + self.update_modifier_key(meta_or_command, is_key_pressed(ModifierKeys::META_OR_COMMAND), responses); + + // Update the state of the virtual Accel key (the primary accelerator key) based on the source state of the Control or Command key, whichever is relevant on this platform + let accel_virtual_key_state = match keyboard_platform { + KeyboardPlatformLayout::Mac => is_key_pressed(ModifierKeys::META_OR_COMMAND), + KeyboardPlatformLayout::Standard => is_key_pressed(ModifierKeys::CONTROL), + }; + self.update_modifier_key(Key::Accel, accel_virtual_key_state, responses); } - fn handle_modifier_key(&mut self, key: Key, key_is_down: bool, responses: &mut VecDeque) { + fn update_modifier_key(&mut self, key: Key, key_is_down: bool, responses: &mut VecDeque) { let key_was_down = self.keyboard.get(key as usize); if key_was_down && !key_is_down { diff --git a/editor/src/messages/layout/layout_message_handler.rs b/editor/src/messages/layout/layout_message_handler.rs index 62fd0137..83fff360 100644 --- a/editor/src/messages/layout/layout_message_handler.rs +++ b/editor/src/messages/layout/layout_message_handler.rs @@ -2,7 +2,6 @@ use super::utility_types::misc::LayoutTarget; use crate::messages::input_mapper::utility_types::input_keyboard::KeysGroup; 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; @@ -14,21 +13,21 @@ pub struct LayoutMessageHandler { layouts: [Layout; LayoutTarget::LayoutTargetLength as usize], } -impl Vec> MessageHandler for LayoutMessageHandler { +impl Vec> MessageHandler for LayoutMessageHandler { #[remain::check] - fn process_message(&mut self, message: LayoutMessage, data: (F, KeyboardPlatformLayout), responses: &mut std::collections::VecDeque) { - let (action_input_mapping, keyboard_platform) = data; + fn process_message(&mut self, message: LayoutMessage, data: F, responses: &mut std::collections::VecDeque) { + let action_input_mapping = data; use LayoutMessage::*; #[remain::sorted] match message { RefreshLayout { layout_target } => { - self.send_layout(layout_target, responses, &action_input_mapping, keyboard_platform); + self.send_layout(layout_target, responses, &action_input_mapping); } SendLayout { layout, layout_target } => { self.layouts[layout_target as usize] = layout; - self.send_layout(layout_target, responses, &action_input_mapping, keyboard_platform); + self.send_layout(layout_target, responses, &action_input_mapping); } UpdateLayout { layout_target, widget_id, value } => { let layout = &mut self.layouts[layout_target as usize]; @@ -152,55 +151,49 @@ impl Vec> MessageHandler, - action_input_mapping: &impl Fn(&MessageDiscriminant) -> Vec, - keyboard_platform: KeyboardPlatformLayout, - ) { + fn send_layout(&self, layout_target: LayoutTarget, responses: &mut VecDeque, action_input_mapping: &impl Fn(&MessageDiscriminant) -> Vec) { 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, + layout: layout.clone().unwrap_widget_layout(action_input_mapping).layout, }, LayoutTarget::DocumentBar => FrontendMessage::UpdateDocumentBarLayout { layout_target, - layout: layout.clone().unwrap_widget_layout(action_input_mapping, keyboard_platform).layout, + layout: layout.clone().unwrap_widget_layout(action_input_mapping).layout, }, LayoutTarget::DocumentMode => FrontendMessage::UpdateDocumentModeLayout { layout_target, - layout: layout.clone().unwrap_widget_layout(action_input_mapping, keyboard_platform).layout, + layout: layout.clone().unwrap_widget_layout(action_input_mapping).layout, }, LayoutTarget::LayerTreeOptions => FrontendMessage::UpdateLayerTreeOptionsLayout { layout_target, - layout: layout.clone().unwrap_widget_layout(action_input_mapping, keyboard_platform).layout, + layout: layout.clone().unwrap_widget_layout(action_input_mapping).layout, }, LayoutTarget::MenuBar => FrontendMessage::UpdateMenuBarLayout { layout_target, - layout: layout.clone().unwrap_menu_layout(action_input_mapping, keyboard_platform).layout, + layout: layout.clone().unwrap_menu_layout(action_input_mapping).layout, }, LayoutTarget::PropertiesOptions => FrontendMessage::UpdatePropertyPanelOptionsLayout { layout_target, - layout: layout.clone().unwrap_widget_layout(action_input_mapping, keyboard_platform).layout, + layout: layout.clone().unwrap_widget_layout(action_input_mapping).layout, }, LayoutTarget::PropertiesSections => FrontendMessage::UpdatePropertyPanelSectionsLayout { layout_target, - layout: layout.clone().unwrap_widget_layout(action_input_mapping, keyboard_platform).layout, + layout: layout.clone().unwrap_widget_layout(action_input_mapping).layout, }, LayoutTarget::ToolOptions => FrontendMessage::UpdateToolOptionsLayout { layout_target, - layout: layout.clone().unwrap_widget_layout(action_input_mapping, keyboard_platform).layout, + layout: layout.clone().unwrap_widget_layout(action_input_mapping).layout, }, LayoutTarget::ToolShelf => FrontendMessage::UpdateToolShelfLayout { layout_target, - layout: layout.clone().unwrap_widget_layout(action_input_mapping, keyboard_platform).layout, + layout: layout.clone().unwrap_widget_layout(action_input_mapping).layout, }, LayoutTarget::WorkingColors => FrontendMessage::UpdateWorkingColorsLayout { layout_target, - layout: layout.clone().unwrap_widget_layout(action_input_mapping, keyboard_platform).layout, + layout: layout.clone().unwrap_widget_layout(action_input_mapping).layout, }, #[remain::unsorted] diff --git a/editor/src/messages/layout/utility_types/layout_widget.rs b/editor/src/messages/layout/utility_types/layout_widget.rs index 47726283..8fef627e 100644 --- a/editor/src/messages/layout/utility_types/layout_widget.rs +++ b/editor/src/messages/layout/utility_types/layout_widget.rs @@ -6,7 +6,6 @@ use crate::application::generate_uuid; use crate::messages::input_mapper::utility_types::input_keyboard::KeysGroup; use crate::messages::input_mapper::utility_types::misc::ActionKeys; use crate::messages::layout::utility_types::misc::LayoutTarget; -use crate::messages::portfolio::document::utility_types::misc::KeyboardPlatformLayout; use crate::messages::prelude::*; use serde::{Deserialize, Serialize}; @@ -35,14 +34,14 @@ pub enum Layout { } impl Layout { - pub fn unwrap_widget_layout(self, action_input_mapping: &impl Fn(&MessageDiscriminant) -> Vec, keyboard_platform: KeyboardPlatformLayout) -> WidgetLayout { + pub fn unwrap_widget_layout(self, action_input_mapping: &impl Fn(&MessageDiscriminant) -> Vec) -> 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.keys_text_shortcut(keyboard_platform); + let shortcut_text = keys.to_string(); if !shortcut_text.is_empty() { if !tooltip.is_empty() { @@ -90,7 +89,7 @@ impl Layout { } } - pub fn unwrap_menu_layout(self, action_input_mapping: &impl Fn(&MessageDiscriminant) -> Vec, _keyboard_platform: KeyboardPlatformLayout) -> MenuLayout { + pub fn unwrap_menu_layout(self, action_input_mapping: &impl Fn(&MessageDiscriminant) -> Vec) -> 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); diff --git a/editor/src/messages/message.rs b/editor/src/messages/message.rs index 1965f99f..76e0358f 100644 --- a/editor/src/messages/message.rs +++ b/editor/src/messages/message.rs @@ -24,6 +24,8 @@ pub enum Message { #[child] Frontend(FrontendMessage), #[child] + Globals(GlobalsMessage), + #[child] InputMapper(InputMapperMessage), #[child] InputPreprocessor(InputPreprocessorMessage), diff --git a/editor/src/messages/mod.rs b/editor/src/messages/mod.rs index 4a44f518..01157db3 100644 --- a/editor/src/messages/mod.rs +++ b/editor/src/messages/mod.rs @@ -4,6 +4,7 @@ pub mod broadcast; pub mod debug; pub mod dialog; pub mod frontend; +pub mod globals; pub mod input_mapper; pub mod input_preprocessor; pub mod layout; diff --git a/editor/src/messages/portfolio/document/utility_types/misc.rs b/editor/src/messages/portfolio/document/utility_types/misc.rs index 51b85f2f..b1259253 100644 --- a/editor/src/messages/portfolio/document/utility_types/misc.rs +++ b/editor/src/messages/portfolio/document/utility_types/misc.rs @@ -77,7 +77,7 @@ impl Platform { match self { Platform::Mac => KeyboardPlatformLayout::Mac, Platform::Unknown => { - log::warn!("The platform has not been set, remember to send `PortfolioMessage::SetPlatform` during editor initialization."); + log::warn!("The platform has not been set, remember to send `GlobalsMessage::SetPlatform` during editor initialization."); KeyboardPlatformLayout::Standard } _ => KeyboardPlatformLayout::Standard, diff --git a/editor/src/messages/portfolio/portfolio_message.rs b/editor/src/messages/portfolio/portfolio_message.rs index 84977b92..cc9d3cca 100644 --- a/editor/src/messages/portfolio/portfolio_message.rs +++ b/editor/src/messages/portfolio/portfolio_message.rs @@ -1,5 +1,4 @@ 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; @@ -84,9 +83,6 @@ pub enum PortfolioMessage { SetActiveDocument { document_id: u64, }, - SetPlatform { - platform: Platform, - }, UpdateDocumentWidgets, UpdateOpenDocumentsList, } diff --git a/editor/src/messages/portfolio/portfolio_message_handler.rs b/editor/src/messages/portfolio/portfolio_message_handler.rs index a3473655..bb39d72f 100644 --- a/editor/src/messages/portfolio/portfolio_message_handler.rs +++ b/editor/src/messages/portfolio/portfolio_message_handler.rs @@ -5,7 +5,6 @@ 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; @@ -22,7 +21,6 @@ pub struct PortfolioMessageHandler { active_document_id: Option, copy_buffer: [Vec; INTERNAL_CLIPBOARD_COUNT as usize], font_cache: FontCache, - pub platform: Platform, } impl MessageHandler for PortfolioMessageHandler { @@ -396,7 +394,6 @@ impl MessageHandler for Port responses.push_back(NavigationMessage::TranslateCanvas { delta: (0., 0.).into() }.into()); } SetActiveDocument { document_id } => self.active_document_id = Some(document_id), - SetPlatform { platform } => self.platform = platform, UpdateDocumentWidgets => { if let Some(document) = self.active_document() { document.update_document_widgets(responses); diff --git a/editor/src/messages/prelude.rs b/editor/src/messages/prelude.rs index 0ffa165d..95bd4eff 100644 --- a/editor/src/messages/prelude.rs +++ b/editor/src/messages/prelude.rs @@ -8,6 +8,7 @@ pub use crate::messages::dialog::export_dialog::{ExportDialogMessage, ExportDial pub use crate::messages::dialog::new_document_dialog::{NewDocumentDialogMessage, NewDocumentDialogMessageDiscriminant, NewDocumentDialogMessageHandler}; pub use crate::messages::dialog::{DialogMessage, DialogMessageDiscriminant, DialogMessageHandler}; pub use crate::messages::frontend::{FrontendMessage, FrontendMessageDiscriminant}; +pub use crate::messages::globals::{GlobalsMessage, GlobalsMessageDiscriminant, GlobalsMessageHandler}; 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}; @@ -42,6 +43,8 @@ pub use crate::messages::tool::tool_messages::spline_tool::{SplineToolMessage, S pub use crate::messages::tool::tool_messages::text_tool::{TextToolMessage, TextToolMessageDiscriminant}; // Helper +pub use crate::messages::globals::global_variables::*; + pub use graphite_proc_macros::*; pub use std::collections::{HashMap, HashSet, VecDeque}; diff --git a/editor/src/test_utils.rs b/editor/src/test_utils.rs index 09d0e615..7a19cc08 100644 --- a/editor/src/test_utils.rs +++ b/editor/src/test_utils.rs @@ -1,6 +1,8 @@ +use crate::application::set_uuid_seed; 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::portfolio::document::utility_types::misc::Platform; use crate::messages::prelude::*; use crate::messages::tool::utility_types::ToolType; @@ -8,6 +10,8 @@ use graphene::color::Color; /// A set of utility functions to make the writing of editor test more declarative pub trait EditorTestUtils { + fn create() -> Editor; + fn new_document(&mut self); fn draw_rect(&mut self, x1: f64, y1: f64, x2: f64, y2: f64); @@ -26,6 +30,20 @@ pub trait EditorTestUtils { } impl EditorTestUtils for Editor { + fn create() -> Editor { + set_uuid_seed(0); + + let mut editor = Editor::new(); + + // We have to set this directly instead of using `GlobalsMessage::SetPlatform` because race conditions with multiple tests can cause that message handler to set it more than once, which is a failure. + // It isn't sufficient to guard the message dispatch here with a check if the once_cell is empty, because that isn't atomic and the time between checking and handling the dispatch can let multiple through. + let _ = GLOBAL_PLATFORM.set(Platform::Windows).is_ok(); + + editor.handle_message(Message::Init); + + editor + } + fn new_document(&mut self) { self.handle_message(Message::Portfolio(PortfolioMessage::NewDocumentWithName { name: String::from("Test document") })); } diff --git a/frontend/src/components/widgets/labels/UserInputLabel.vue b/frontend/src/components/widgets/labels/UserInputLabel.vue index a77332a9..465af2e8 100644 --- a/frontend/src/components/widgets/labels/UserInputLabel.vue +++ b/frontend/src/components/widgets/labels/UserInputLabel.vue @@ -195,8 +195,11 @@ export default defineComponent({ let key = keyWithLabel.key; const label = keyWithLabel.label; - // Replace Alt with Option on Mac - if (key === "Alt" && platformIsMac()) key = "Option"; + // Replace Alt and Accel keys with their Mac-specific equivalents + if (platformIsMac()) { + if (key === "Alt") key = "Option"; + if (key === "Accel") key = "Command"; + } // Either display an icon... // @ts-expect-error We want undefined if it isn't in the object diff --git a/frontend/wasm/src/editor_api.rs b/frontend/wasm/src/editor_api.rs index 2b7aaeb4..9ecbc66d 100644 --- a/frontend/wasm/src/editor_api.rs +++ b/frontend/wasm/src/editor_api.rs @@ -66,8 +66,11 @@ impl JsEditorHandle { return; } + // Get the editor instances, dispatch the message, and store the `FrontendMessage` queue response let frontend_messages = EDITOR_INSTANCES.with(|instances| { + // Mutably borrow the editors, and if successful, we can access them in the closure instances.try_borrow_mut().map(|mut editors| { + // Get the editor instance for this editor ID, then dispatch the message to the backend, and return its response `FrontendMessage` queue editors .get_mut(&self.editor_id) .expect("EDITOR_INSTANCES does not contain the current editor_id") @@ -75,9 +78,10 @@ impl JsEditorHandle { }) }); + // Process any `FrontendMessage` responses resulting from the backend processing the dispatched message if let Ok(frontend_messages) = frontend_messages { + // Send each `FrontendMessage` to the JavaScript frontend for message in frontend_messages.into_iter() { - // Send each FrontendMessage to the JavaScript frontend self.send_frontend_message_to_js(message); } } @@ -115,7 +119,7 @@ impl JsEditorHandle { _ => Platform::Unknown, }; - self.dispatch(PortfolioMessage::SetPlatform { platform }); + self.dispatch(GlobalsMessage::SetPlatform { platform }); self.dispatch(Message::Init); }