Simplify platform-specific keyboard layouts with Accel key and global platform variable (#748)

* Simplify platform-specific keyboard layouts with Accel key and global platform variable


Co-authored-by: Dennis <dennis@kobert.dev>
This commit is contained in:
Keavon Chambers 2022-08-13 04:28:02 -07:00
parent 41bead9028
commit 863c17b86f
24 changed files with 240 additions and 315 deletions

1
Cargo.lock generated
View file

@ -215,6 +215,7 @@ dependencies = [
"graphite-proc-macros",
"kurbo",
"log",
"once_cell",
"rand_chacha",
"remain",
"serde",

View file

@ -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 (<https://doc.rust-lang.org/core/cell/struct.OnceCell.html>)
[dependencies.graphene]
path = "../graphene"

View file

@ -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<Vec<LayerId>> {
@ -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(),

View file

@ -0,0 +1,5 @@
use crate::messages::portfolio::document::utility_types::misc::Platform;
use once_cell::sync::OnceCell;
pub static GLOBAL_PLATFORM: OnceCell<Platform> = OnceCell::new();

View file

@ -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 },
}

View file

@ -0,0 +1,18 @@
use crate::messages::prelude::*;
#[derive(Debug, Default)]
pub struct GlobalsMessageHandler {}
impl MessageHandler<GlobalsMessage, ()> for GlobalsMessageHandler {
#[remain::check]
fn process_message(&mut self, message: GlobalsMessage, _data: (), _responses: &mut VecDeque<Message>) {
match message {
GlobalsMessage::SetPlatform { platform } => {
GLOBAL_PLATFORM.set(platform).expect("Failed to set GLOBAL_PLATFORM");
}
}
}
advertise_actions!(GlobalsMessageDiscriminant;
);
}

View file

@ -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;

View file

@ -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!(),
},
);

View file

@ -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<InputMapperMessage, (&InputPreprocessorMessageHandler, KeyboardPlatformLayout, ActionList)> for InputMapperMessageHandler {
fn process_message(&mut self, message: InputMapperMessage, data: (&InputPreprocessorMessageHandler, KeyboardPlatformLayout, ActionList), responses: &mut VecDeque<Message>) {
let (input, keyboard_platform, actions) = data;
impl MessageHandler<InputMapperMessage, (&InputPreprocessorMessageHandler, ActionList)> for InputMapperMessageHandler {
fn process_message(&mut self, message: InputMapperMessage, data: (&InputPreprocessorMessageHandler, ActionList), responses: &mut VecDeque<Message>) {
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<KeysGroup> {
pub fn action_input_mapping(&self, action_to_find: &MessageDiscriminant) -> Vec<KeysGroup> {
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::<usize>() >= std::mem::size_of::<Key>());

View file

@ -20,6 +20,14 @@ const KEY_MASK_STORAGE_LENGTH: usize = (NUMBER_OF_KEYS + STORAGE_SIZE_BITS - 1)
pub type KeyStates = BitVector<KEY_MASK_STORAGE_LENGTH>;
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::<String>());
}
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<Key>);
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::<String>();
// 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)
}
}

View file

@ -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<KeyboardPlatformLayout>)
/// 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;

View file

@ -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<Message> {
pub fn match_input_message(&self, message: InputMapperMessage, keyboard_state: &KeyStates, actions: ActionList) -> Option<Message> {
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<MappingEntry>);
impl KeyMappingEntries {
pub fn match_mapping(&self, keyboard_state: &KeyStates, actions: ActionList, keyboard_platform: KeyboardPlatformLayout) -> Option<Message> {
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<Message> {
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<KeyboardPlatformLayout>,
}
#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)]

View file

@ -54,7 +54,7 @@ impl MessageHandler<InputPreprocessorMessage, KeyboardPlatformLayout> 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<InputPreprocessorMessage, KeyboardPlatformLayout> 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<InputPreprocessorMessage, KeyboardPlatformLayout> 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<InputPreprocessorMessage, KeyboardPlatformLayout> 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<InputPreprocessorMessage, KeyboardPlatformLayout> 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<Message>) {
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<Message>) {
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<Message>) {
fn update_modifier_key(&mut self, key: Key, key_is_down: bool, responses: &mut VecDeque<Message>) {
let key_was_down = self.keyboard.get(key as usize);
if key_was_down && !key_is_down {

View file

@ -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<F: Fn(&MessageDiscriminant) -> Vec<KeysGroup>> MessageHandler<LayoutMessage, (F, KeyboardPlatformLayout)> for LayoutMessageHandler {
impl<F: Fn(&MessageDiscriminant) -> Vec<KeysGroup>> MessageHandler<LayoutMessage, F> for LayoutMessageHandler {
#[remain::check]
fn process_message(&mut self, message: LayoutMessage, data: (F, KeyboardPlatformLayout), responses: &mut std::collections::VecDeque<Message>) {
let (action_input_mapping, keyboard_platform) = data;
fn process_message(&mut self, message: LayoutMessage, data: F, responses: &mut std::collections::VecDeque<Message>) {
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<F: Fn(&MessageDiscriminant) -> Vec<KeysGroup>> MessageHandler<LayoutMessage
impl LayoutMessageHandler {
#[remain::check]
fn send_layout(
&self,
layout_target: LayoutTarget,
responses: &mut VecDeque<Message>,
action_input_mapping: &impl Fn(&MessageDiscriminant) -> Vec<KeysGroup>,
keyboard_platform: KeyboardPlatformLayout,
) {
fn send_layout(&self, layout_target: LayoutTarget, responses: &mut VecDeque<Message>, action_input_mapping: &impl Fn(&MessageDiscriminant) -> Vec<KeysGroup>) {
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]

View file

@ -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<KeysGroup>, keyboard_platform: KeyboardPlatformLayout) -> WidgetLayout {
pub fn unwrap_widget_layout(self, action_input_mapping: &impl Fn(&MessageDiscriminant) -> Vec<KeysGroup>) -> 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<KeysGroup>, _keyboard_platform: KeyboardPlatformLayout) -> MenuLayout {
pub fn unwrap_menu_layout(self, action_input_mapping: &impl Fn(&MessageDiscriminant) -> Vec<KeysGroup>) -> 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);

View file

@ -24,6 +24,8 @@ pub enum Message {
#[child]
Frontend(FrontendMessage),
#[child]
Globals(GlobalsMessage),
#[child]
InputMapper(InputMapperMessage),
#[child]
InputPreprocessor(InputPreprocessorMessage),

View file

@ -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;

View file

@ -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,

View file

@ -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,
}

View file

@ -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<u64>,
copy_buffer: [Vec<CopyBufferEntry>; INTERNAL_CLIPBOARD_COUNT as usize],
font_cache: FontCache,
pub platform: Platform,
}
impl MessageHandler<PortfolioMessage, &InputPreprocessorMessageHandler> for PortfolioMessageHandler {
@ -396,7 +394,6 @@ impl MessageHandler<PortfolioMessage, &InputPreprocessorMessageHandler> 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);

View file

@ -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};

View file

@ -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") }));
}

View file

@ -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

View file

@ -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);
}