Skip to content

Commit 52b3553

Browse files
KeavonTrueDoctor
andcommitted
Add full support for Mac-specific keyboard layouts (#736)
* IPP for Mac, flawed initial experiments * Cleanup and progress, but not compiling yet * Fix error and rename nonmac -> standard * Extentd ipp macros to accomodate mac input * Add Mac versions of shortcuts; refactor and document the input mapper macros * Change frontend styling for user input labels in floating menus * Additional macro documentation * A little more documentation * Improve entry macro syntax * Move input mapper macros to a separate file * Adapt the keyboard shortcuts to the user's OS * Display keyboard shortcuts in the menu bar based on OS * Change Input Mapper macro syntax from {} to () * Fix esc key bug in Vue * Tweaks * Interim solution for Mac-specific hints * Feed tooltip input hotkeys from their actions * Fix hotkeys for tools because of missing actions * Make Vue respect Ctrl/Cmd differences per platform * Remove commented lines * Code review pass by me * Code review suggestions with TrueDoctor * Turn FutureKeyMapping struct into ActionKeys enum which is a bit cleaner * Add serde derive attributes for message discriminants * Re-add serde deserialize * Fix not mutating ActionKeys conversion; remove custom serializer * Add serde to dev dependencies Co-authored-by: Dennis <[email protected]>
1 parent 4fd0042 commit 52b3553

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

73 files changed

+1682
-723
lines changed

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

bezier-rs/docs/interactive-docs/wasm/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
name = "bezier-rs-wasm"
33
publish = false
44
version = "0.0.0"
5-
rust-version = "1.56.0"
5+
rust-version = "1.62.0"
66
authors = ["Graphite Authors <[email protected]>"]
77
edition = "2021"
88
readme = "../../README.md"

bezier-rs/lib/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
name = "bezier-rs"
33
publish = false
44
version = "0.0.0"
5-
rust-version = "1.56.0"
5+
rust-version = "1.62.0"
66
authors = ["Graphite Authors <[email protected]>"]
77
edition = "2021"
88
readme = "./README.md"

editor/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
name = "graphite-editor"
33
publish = false
44
version = "0.0.0"
5-
rust-version = "1.56.0"
5+
rust-version = "1.62.0"
66
authors = ["Graphite Authors <[email protected]>"]
77
edition = "2021"
88
readme = "../README.md"

editor/src/communication/dispatcher.rs

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -138,14 +138,25 @@ impl Dispatcher {
138138
}
139139
InputMapper(message) => {
140140
let actions = self.collect_actions();
141+
let keyboard_platform = self.message_handlers.portfolio_message_handler.platform.as_keyboard_platform_layout();
142+
141143
self.message_handlers
142144
.input_mapper_message_handler
143-
.process_action(message, (&self.message_handlers.input_preprocessor_message_handler, actions), &mut queue);
145+
.process_action(message, (&self.message_handlers.input_preprocessor_message_handler, keyboard_platform, actions), &mut queue);
144146
}
145147
InputPreprocessor(message) => {
146-
self.message_handlers.input_preprocessor_message_handler.process_action(message, (), &mut queue);
148+
let keyboard_platform = self.message_handlers.portfolio_message_handler.platform.as_keyboard_platform_layout();
149+
150+
self.message_handlers.input_preprocessor_message_handler.process_action(message, keyboard_platform, &mut queue);
151+
}
152+
Layout(message) => {
153+
let keyboard_platform = self.message_handlers.portfolio_message_handler.platform.as_keyboard_platform_layout();
154+
let action_input_mapping = &|action_to_find: &MessageDiscriminant| self.message_handlers.input_mapper_message_handler.action_input_mapping(action_to_find, keyboard_platform);
155+
156+
self.message_handlers
157+
.layout_message_handler
158+
.process_action(message, (action_input_mapping, keyboard_platform), &mut queue);
147159
}
148-
Layout(message) => self.message_handlers.layout_message_handler.process_action(message, (), &mut queue),
149160
Portfolio(message) => {
150161
self.message_handlers
151162
.portfolio_message_handler
@@ -518,15 +529,15 @@ mod test {
518529
replacement_selected_layers: sorted_layers[..2].to_vec(),
519530
});
520531

521-
editor.handle_message(DocumentMessage::ReorderSelectedLayers { relative_index_offset: 1 });
532+
editor.handle_message(DocumentMessage::SelectedLayersRaise);
522533
let (all, non_selected, selected) = verify_order(editor.dispatcher.message_handlers.portfolio_message_handler.active_document_mut().unwrap());
523534
assert_eq!(all, non_selected.into_iter().chain(selected.into_iter()).collect::<Vec<_>>());
524535

525-
editor.handle_message(DocumentMessage::ReorderSelectedLayers { relative_index_offset: -1 });
536+
editor.handle_message(DocumentMessage::SelectedLayersLower);
526537
let (all, non_selected, selected) = verify_order(editor.dispatcher.message_handlers.portfolio_message_handler.active_document_mut().unwrap());
527538
assert_eq!(all, selected.into_iter().chain(non_selected.into_iter()).collect::<Vec<_>>());
528539

529-
editor.handle_message(DocumentMessage::ReorderSelectedLayers { relative_index_offset: isize::MAX });
540+
editor.handle_message(DocumentMessage::SelectedLayersRaiseToFront);
530541
let (all, non_selected, selected) = verify_order(editor.dispatcher.message_handlers.portfolio_message_handler.active_document_mut().unwrap());
531542
assert_eq!(all, non_selected.into_iter().chain(selected.into_iter()).collect::<Vec<_>>());
532543
}

editor/src/consts.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,8 @@ pub const PATH_OUTLINE_WEIGHT: f64 = 2.;
3535
pub const ROTATE_SNAP_ANGLE: f64 = 15.;
3636
pub const SCALE_SNAP_INTERVAL: f64 = 0.1;
3737
pub const SLOWING_DIVISOR: f64 = 10.;
38+
pub const NUDGE_AMOUNT: f64 = 1.;
39+
pub const BIG_NUDGE_AMOUNT: f64 = 10.;
3840

3941
// Select tool
4042
pub const SELECTION_TOLERANCE: f64 = 5.;

editor/src/dialog/dialog_message.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1+
use super::{ExportDialogUpdate, NewDocumentDialogUpdate};
12
use crate::message_prelude::*;
2-
use serde::{Deserialize, Serialize};
33

4-
use super::{ExportDialogUpdate, NewDocumentDialogUpdate};
4+
use serde::{Deserialize, Serialize};
55

66
#[remain::sorted]
77
#[impl_message(Message, Dialog)]

editor/src/document/document_message.rs

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -103,12 +103,16 @@ pub enum DocumentMessage {
103103
new_name: String,
104104
},
105105
RenderDocument,
106-
ReorderSelectedLayers {
107-
relative_index_offset: isize,
108-
},
109106
RollbackTransaction,
110107
SaveDocument,
111108
SelectAllLayers,
109+
SelectedLayersLower,
110+
SelectedLayersLowerToBack,
111+
SelectedLayersRaise,
112+
SelectedLayersRaiseToFront,
113+
SelectedLayersReorder {
114+
relative_index_offset: isize,
115+
},
112116
SelectLayer {
113117
layer_path: Vec<LayerId>,
114118
ctrl: bool,

editor/src/document/document_message_handler.rs

Lines changed: 83 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ use super::{vectorize_layer_metadata, PropertiesPanelMessageHandler};
77
use super::{ArtboardMessageHandler, MovementMessageHandler, OverlaysMessageHandler, TransformLayerMessageHandler};
88
use crate::consts::{ASYMPTOTIC_EFFECT, DEFAULT_DOCUMENT_NAME, FILE_SAVE_SUFFIX, GRAPHITE_DOCUMENT_VERSION, SCALE_EFFECT, SCROLLBAR_SPACING, VIEWPORT_ZOOM_TO_FIT_PADDING_SCALE_FACTOR};
99
use crate::frontend::utility_types::{FileType, FrontendImageData};
10+
use crate::input::input_mapper::action_keys::action_shortcut;
1011
use crate::input::InputPreprocessorMessageHandler;
1112
use crate::layout::layout_message::LayoutTarget;
1213
use crate::layout::widgets::{
@@ -549,6 +550,7 @@ impl DocumentMessageHandler {
549550
icon: "Snapping".into(),
550551
tooltip: "Snapping".into(),
551552
on_update: WidgetCallback::new(|optional_input: &OptionalInput| DocumentMessage::SetSnapping { snap: optional_input.checked }.into()),
553+
..Default::default()
552554
})),
553555
WidgetHolder::new(Widget::PopoverButton(PopoverButton {
554556
header: "Snapping".into(),
@@ -564,6 +566,7 @@ impl DocumentMessageHandler {
564566
icon: "Grid".into(),
565567
tooltip: "Grid".into(),
566568
on_update: WidgetCallback::new(|_| DialogMessage::RequestComingSoonDialog { issue: Some(318) }.into()),
569+
..Default::default()
567570
})),
568571
WidgetHolder::new(Widget::PopoverButton(PopoverButton {
569572
header: "Grid".into(),
@@ -579,6 +582,7 @@ impl DocumentMessageHandler {
579582
icon: "Overlays".into(),
580583
tooltip: "Overlays".into(),
581584
on_update: WidgetCallback::new(|optional_input: &OptionalInput| DocumentMessage::SetOverlaysVisibility { visible: optional_input.checked }.into()),
585+
..Default::default()
582586
})),
583587
WidgetHolder::new(Widget::PopoverButton(PopoverButton {
584588
header: "Overlays".into(),
@@ -841,14 +845,16 @@ impl DocumentMessageHandler {
841845
})),
842846
WidgetHolder::new(Widget::IconButton(IconButton {
843847
icon: "NodeFolder".into(),
844-
tooltip: "New Folder (Ctrl+Shift+N)".into(), // TODO: Customize this tooltip for the Mac version of the keyboard shortcut
848+
tooltip: "New Folder".into(),
849+
tooltip_shortcut: action_shortcut!(DocumentMessageDiscriminant::CreateEmptyFolder),
845850
size: 24,
846851
on_update: WidgetCallback::new(|_| DocumentMessage::CreateEmptyFolder { container_path: vec![] }.into()),
847852
..Default::default()
848853
})),
849854
WidgetHolder::new(Widget::IconButton(IconButton {
850855
icon: "Trash".into(),
851-
tooltip: "Delete Selected (Del)".into(), // TODO: Customize this tooltip for the Mac version of the keyboard shortcut
856+
tooltip: "Delete Selected".into(),
857+
tooltip_shortcut: action_shortcut!(DocumentMessageDiscriminant::DeleteSelectedLayers),
852858
size: 24,
853859
on_update: WidgetCallback::new(|_| DocumentMessage::DeleteSelectedLayers.into()),
854860
..Default::default()
@@ -864,6 +870,62 @@ impl DocumentMessageHandler {
864870
.into(),
865871
);
866872
}
873+
874+
pub fn selected_layers_reorder(&mut self, relative_index_offset: isize, responses: &mut VecDeque<Message>) {
875+
self.backup(responses);
876+
877+
let all_layer_paths = self.all_layers_sorted();
878+
let selected_layers = self.selected_layers_sorted();
879+
880+
let first_or_last_selected_layer = match relative_index_offset.signum() {
881+
-1 => selected_layers.first(),
882+
1 => selected_layers.last(),
883+
_ => panic!("selected_layers_reorder() must be given a non-zero value"),
884+
};
885+
886+
if let Some(pivot_layer) = first_or_last_selected_layer {
887+
let sibling_layer_paths: Vec<_> = all_layer_paths
888+
.iter()
889+
.filter(|layer| {
890+
// Check if this is a sibling of the pivot layer
891+
// TODO: Break this out into a reusable function `fn are_layers_siblings(layer_a, layer_b) -> bool`
892+
let containing_folder_path = &pivot_layer[0..pivot_layer.len() - 1];
893+
layer.starts_with(containing_folder_path) && pivot_layer.len() == layer.len()
894+
})
895+
.collect();
896+
897+
// TODO: Break this out into a reusable function: `fn layer_index_in_containing_folder(layer_path) -> usize`
898+
let pivot_index_among_siblings = sibling_layer_paths.iter().position(|path| *path == pivot_layer);
899+
900+
if let Some(pivot_index) = pivot_index_among_siblings {
901+
let max = sibling_layer_paths.len() as i64 - 1;
902+
let insert_index = (pivot_index as i64 + relative_index_offset as i64).clamp(0, max) as usize;
903+
904+
let existing_layer_to_insert_beside = sibling_layer_paths.get(insert_index);
905+
906+
// TODO: Break this block out into a call to a message called `MoveSelectedLayersNextToLayer { neighbor_path, above_or_below }`
907+
if let Some(neighbor_path) = existing_layer_to_insert_beside {
908+
let (neighbor_id, folder_path) = neighbor_path.split_last().expect("Can't move the root folder");
909+
910+
if let Some(folder) = self.graphene_document.layer(folder_path).ok().and_then(|layer| layer.as_folder().ok()) {
911+
let neighbor_layer_index = folder.layer_ids.iter().position(|id| id == neighbor_id).unwrap() as isize;
912+
913+
// If moving down, insert below this layer. If moving up, insert above this layer.
914+
let insert_index = if relative_index_offset < 0 { neighbor_layer_index } else { neighbor_layer_index + 1 };
915+
916+
responses.push_back(
917+
DocumentMessage::MoveSelectedLayersTo {
918+
folder_path: folder_path.to_vec(),
919+
insert_index,
920+
reverse_index: false,
921+
}
922+
.into(),
923+
);
924+
}
925+
}
926+
}
927+
}
928+
}
867929
}
868930

869931
impl MessageHandler<DocumentMessage, (&InputPreprocessorMessageHandler, &FontCache)> for DocumentMessageHandler {
@@ -1318,61 +1380,6 @@ impl MessageHandler<DocumentMessage, (&InputPreprocessorMessageHandler, &FontCac
13181380
.into(),
13191381
);
13201382
}
1321-
ReorderSelectedLayers { relative_index_offset } => {
1322-
self.backup(responses);
1323-
1324-
let all_layer_paths = self.all_layers_sorted();
1325-
let selected_layers = self.selected_layers_sorted();
1326-
1327-
let first_or_last_selected_layer = match relative_index_offset.signum() {
1328-
-1 => selected_layers.first(),
1329-
1 => selected_layers.last(),
1330-
_ => panic!("ReorderSelectedLayers must be given a non-zero value"),
1331-
};
1332-
1333-
if let Some(pivot_layer) = first_or_last_selected_layer {
1334-
let sibling_layer_paths: Vec<_> = all_layer_paths
1335-
.iter()
1336-
.filter(|layer| {
1337-
// Check if this is a sibling of the pivot layer
1338-
// TODO: Break this out into a reusable function `fn are_layers_siblings(layer_a, layer_b) -> bool`
1339-
let containing_folder_path = &pivot_layer[0..pivot_layer.len() - 1];
1340-
layer.starts_with(containing_folder_path) && pivot_layer.len() == layer.len()
1341-
})
1342-
.collect();
1343-
1344-
// TODO: Break this out into a reusable function: `fn layer_index_in_containing_folder(layer_path) -> usize`
1345-
let pivot_index_among_siblings = sibling_layer_paths.iter().position(|path| *path == pivot_layer);
1346-
1347-
if let Some(pivot_index) = pivot_index_among_siblings {
1348-
let max = sibling_layer_paths.len() as i64 - 1;
1349-
let insert_index = (pivot_index as i64 + relative_index_offset as i64).clamp(0, max) as usize;
1350-
1351-
let existing_layer_to_insert_beside = sibling_layer_paths.get(insert_index);
1352-
1353-
// TODO: Break this block out into a call to a message called `MoveSelectedLayersNextToLayer { neighbor_path, above_or_below }`
1354-
if let Some(neighbor_path) = existing_layer_to_insert_beside {
1355-
let (neighbor_id, folder_path) = neighbor_path.split_last().expect("Can't move the root folder");
1356-
1357-
if let Some(folder) = self.graphene_document.layer(folder_path).ok().and_then(|layer| layer.as_folder().ok()) {
1358-
let neighbor_layer_index = folder.layer_ids.iter().position(|id| id == neighbor_id).unwrap() as isize;
1359-
1360-
// If moving down, insert below this layer. If moving up, insert above this layer.
1361-
let insert_index = if relative_index_offset < 0 { neighbor_layer_index } else { neighbor_layer_index + 1 };
1362-
1363-
responses.push_back(
1364-
DocumentMessage::MoveSelectedLayersTo {
1365-
folder_path: folder_path.to_vec(),
1366-
insert_index,
1367-
reverse_index: false,
1368-
}
1369-
.into(),
1370-
);
1371-
}
1372-
}
1373-
}
1374-
}
1375-
}
13761383
RollbackTransaction => {
13771384
self.rollback(responses).unwrap_or_else(|e| log::warn!("{}", e));
13781385
responses.extend([RenderDocument.into(), DocumentStructureChanged.into()]);
@@ -1399,6 +1406,21 @@ impl MessageHandler<DocumentMessage, (&InputPreprocessorMessageHandler, &FontCac
13991406
let all = self.all_layers().map(|path| path.to_vec()).collect();
14001407
responses.push_front(SetSelectedLayers { replacement_selected_layers: all }.into());
14011408
}
1409+
SelectedLayersLower => {
1410+
responses.push_front(DocumentMessage::SelectedLayersReorder { relative_index_offset: -1 }.into());
1411+
}
1412+
SelectedLayersLowerToBack => {
1413+
responses.push_front(DocumentMessage::SelectedLayersReorder { relative_index_offset: isize::MIN }.into());
1414+
}
1415+
SelectedLayersRaise => {
1416+
responses.push_front(DocumentMessage::SelectedLayersReorder { relative_index_offset: 1 }.into());
1417+
}
1418+
SelectedLayersRaiseToFront => {
1419+
responses.push_front(DocumentMessage::SelectedLayersReorder { relative_index_offset: isize::MAX }.into());
1420+
}
1421+
SelectedLayersReorder { relative_index_offset } => {
1422+
self.selected_layers_reorder(relative_index_offset, responses);
1423+
}
14021424
SelectLayer { layer_path, ctrl, shift } => {
14031425
let mut paths = vec![];
14041426
let last_selection_exists = !self.layer_range_selection_reference.is_empty();
@@ -1611,7 +1633,10 @@ impl MessageHandler<DocumentMessage, (&InputPreprocessorMessageHandler, &FontCac
16111633
DeleteSelectedLayers,
16121634
DuplicateSelectedLayers,
16131635
NudgeSelectedLayers,
1614-
ReorderSelectedLayers,
1636+
SelectedLayersLower,
1637+
SelectedLayersLowerToBack,
1638+
SelectedLayersRaise,
1639+
SelectedLayersRaiseToFront,
16151640
GroupSelectedLayers,
16161641
UngroupSelectedLayers,
16171642
);

0 commit comments

Comments
 (0)