mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2025-07-07 15:55:00 +00:00
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 <dennis@kobert.dev>
This commit is contained in:
parent
fa461f3157
commit
f39d6bf00c
73 changed files with 1686 additions and 727 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
@ -176,6 +176,7 @@ dependencies = [
|
|||
"graphite-editor",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"serde",
|
||||
"syn",
|
||||
]
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
name = "bezier-rs-wasm"
|
||||
publish = false
|
||||
version = "0.0.0"
|
||||
rust-version = "1.56.0"
|
||||
rust-version = "1.62.0"
|
||||
authors = ["Graphite Authors <contact@graphite.rs>"]
|
||||
edition = "2021"
|
||||
readme = "../../README.md"
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
name = "bezier-rs"
|
||||
publish = false
|
||||
version = "0.0.0"
|
||||
rust-version = "1.56.0"
|
||||
rust-version = "1.62.0"
|
||||
authors = ["Graphite Authors <contact@graphite.rs>"]
|
||||
edition = "2021"
|
||||
readme = "./README.md"
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
name = "graphite-editor"
|
||||
publish = false
|
||||
version = "0.0.0"
|
||||
rust-version = "1.56.0"
|
||||
rust-version = "1.62.0"
|
||||
authors = ["Graphite Authors <contact@graphite.rs>"]
|
||||
edition = "2021"
|
||||
readme = "../README.md"
|
||||
|
|
|
@ -138,14 +138,25 @@ impl Dispatcher {
|
|||
}
|
||||
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_action(message, (&self.message_handlers.input_preprocessor_message_handler, actions), &mut queue);
|
||||
.process_action(message, (&self.message_handlers.input_preprocessor_message_handler, keyboard_platform, actions), &mut queue);
|
||||
}
|
||||
InputPreprocessor(message) => {
|
||||
self.message_handlers.input_preprocessor_message_handler.process_action(message, (), &mut queue);
|
||||
let keyboard_platform = self.message_handlers.portfolio_message_handler.platform.as_keyboard_platform_layout();
|
||||
|
||||
self.message_handlers.input_preprocessor_message_handler.process_action(message, keyboard_platform, &mut queue);
|
||||
}
|
||||
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);
|
||||
|
||||
self.message_handlers
|
||||
.layout_message_handler
|
||||
.process_action(message, (action_input_mapping, keyboard_platform), &mut queue);
|
||||
}
|
||||
Layout(message) => self.message_handlers.layout_message_handler.process_action(message, (), &mut queue),
|
||||
Portfolio(message) => {
|
||||
self.message_handlers
|
||||
.portfolio_message_handler
|
||||
|
@ -518,15 +529,15 @@ mod test {
|
|||
replacement_selected_layers: sorted_layers[..2].to_vec(),
|
||||
});
|
||||
|
||||
editor.handle_message(DocumentMessage::ReorderSelectedLayers { relative_index_offset: 1 });
|
||||
editor.handle_message(DocumentMessage::SelectedLayersRaise);
|
||||
let (all, non_selected, selected) = verify_order(editor.dispatcher.message_handlers.portfolio_message_handler.active_document_mut().unwrap());
|
||||
assert_eq!(all, non_selected.into_iter().chain(selected.into_iter()).collect::<Vec<_>>());
|
||||
|
||||
editor.handle_message(DocumentMessage::ReorderSelectedLayers { relative_index_offset: -1 });
|
||||
editor.handle_message(DocumentMessage::SelectedLayersLower);
|
||||
let (all, non_selected, selected) = verify_order(editor.dispatcher.message_handlers.portfolio_message_handler.active_document_mut().unwrap());
|
||||
assert_eq!(all, selected.into_iter().chain(non_selected.into_iter()).collect::<Vec<_>>());
|
||||
|
||||
editor.handle_message(DocumentMessage::ReorderSelectedLayers { relative_index_offset: isize::MAX });
|
||||
editor.handle_message(DocumentMessage::SelectedLayersRaiseToFront);
|
||||
let (all, non_selected, selected) = verify_order(editor.dispatcher.message_handlers.portfolio_message_handler.active_document_mut().unwrap());
|
||||
assert_eq!(all, non_selected.into_iter().chain(selected.into_iter()).collect::<Vec<_>>());
|
||||
}
|
||||
|
|
|
@ -35,6 +35,8 @@ pub const PATH_OUTLINE_WEIGHT: f64 = 2.;
|
|||
pub const ROTATE_SNAP_ANGLE: f64 = 15.;
|
||||
pub const SCALE_SNAP_INTERVAL: f64 = 0.1;
|
||||
pub const SLOWING_DIVISOR: f64 = 10.;
|
||||
pub const NUDGE_AMOUNT: f64 = 1.;
|
||||
pub const BIG_NUDGE_AMOUNT: f64 = 10.;
|
||||
|
||||
// Select tool
|
||||
pub const SELECTION_TOLERANCE: f64 = 5.;
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
use crate::message_prelude::*;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use super::{ExportDialogUpdate, NewDocumentDialogUpdate};
|
||||
use crate::message_prelude::*;
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[remain::sorted]
|
||||
#[impl_message(Message, Dialog)]
|
||||
|
|
|
@ -103,12 +103,16 @@ pub enum DocumentMessage {
|
|||
new_name: String,
|
||||
},
|
||||
RenderDocument,
|
||||
ReorderSelectedLayers {
|
||||
relative_index_offset: isize,
|
||||
},
|
||||
RollbackTransaction,
|
||||
SaveDocument,
|
||||
SelectAllLayers,
|
||||
SelectedLayersLower,
|
||||
SelectedLayersLowerToBack,
|
||||
SelectedLayersRaise,
|
||||
SelectedLayersRaiseToFront,
|
||||
SelectedLayersReorder {
|
||||
relative_index_offset: isize,
|
||||
},
|
||||
SelectLayer {
|
||||
layer_path: Vec<LayerId>,
|
||||
ctrl: bool,
|
||||
|
|
|
@ -7,6 +7,7 @@ use super::{vectorize_layer_metadata, PropertiesPanelMessageHandler};
|
|||
use super::{ArtboardMessageHandler, MovementMessageHandler, OverlaysMessageHandler, TransformLayerMessageHandler};
|
||||
use crate::consts::{ASYMPTOTIC_EFFECT, DEFAULT_DOCUMENT_NAME, FILE_SAVE_SUFFIX, GRAPHITE_DOCUMENT_VERSION, SCALE_EFFECT, SCROLLBAR_SPACING, VIEWPORT_ZOOM_TO_FIT_PADDING_SCALE_FACTOR};
|
||||
use crate::frontend::utility_types::{FileType, FrontendImageData};
|
||||
use crate::input::input_mapper::action_keys::action_shortcut;
|
||||
use crate::input::InputPreprocessorMessageHandler;
|
||||
use crate::layout::layout_message::LayoutTarget;
|
||||
use crate::layout::widgets::{
|
||||
|
@ -549,6 +550,7 @@ impl DocumentMessageHandler {
|
|||
icon: "Snapping".into(),
|
||||
tooltip: "Snapping".into(),
|
||||
on_update: WidgetCallback::new(|optional_input: &OptionalInput| DocumentMessage::SetSnapping { snap: optional_input.checked }.into()),
|
||||
..Default::default()
|
||||
})),
|
||||
WidgetHolder::new(Widget::PopoverButton(PopoverButton {
|
||||
header: "Snapping".into(),
|
||||
|
@ -564,6 +566,7 @@ impl DocumentMessageHandler {
|
|||
icon: "Grid".into(),
|
||||
tooltip: "Grid".into(),
|
||||
on_update: WidgetCallback::new(|_| DialogMessage::RequestComingSoonDialog { issue: Some(318) }.into()),
|
||||
..Default::default()
|
||||
})),
|
||||
WidgetHolder::new(Widget::PopoverButton(PopoverButton {
|
||||
header: "Grid".into(),
|
||||
|
@ -579,6 +582,7 @@ impl DocumentMessageHandler {
|
|||
icon: "Overlays".into(),
|
||||
tooltip: "Overlays".into(),
|
||||
on_update: WidgetCallback::new(|optional_input: &OptionalInput| DocumentMessage::SetOverlaysVisibility { visible: optional_input.checked }.into()),
|
||||
..Default::default()
|
||||
})),
|
||||
WidgetHolder::new(Widget::PopoverButton(PopoverButton {
|
||||
header: "Overlays".into(),
|
||||
|
@ -841,14 +845,16 @@ impl DocumentMessageHandler {
|
|||
})),
|
||||
WidgetHolder::new(Widget::IconButton(IconButton {
|
||||
icon: "NodeFolder".into(),
|
||||
tooltip: "New Folder (Ctrl+Shift+N)".into(), // TODO: Customize this tooltip for the Mac version of the keyboard shortcut
|
||||
tooltip: "New Folder".into(),
|
||||
tooltip_shortcut: action_shortcut!(DocumentMessageDiscriminant::CreateEmptyFolder),
|
||||
size: 24,
|
||||
on_update: WidgetCallback::new(|_| DocumentMessage::CreateEmptyFolder { container_path: vec![] }.into()),
|
||||
..Default::default()
|
||||
})),
|
||||
WidgetHolder::new(Widget::IconButton(IconButton {
|
||||
icon: "Trash".into(),
|
||||
tooltip: "Delete Selected (Del)".into(), // TODO: Customize this tooltip for the Mac version of the keyboard shortcut
|
||||
tooltip: "Delete Selected".into(),
|
||||
tooltip_shortcut: action_shortcut!(DocumentMessageDiscriminant::DeleteSelectedLayers),
|
||||
size: 24,
|
||||
on_update: WidgetCallback::new(|_| DocumentMessage::DeleteSelectedLayers.into()),
|
||||
..Default::default()
|
||||
|
@ -864,6 +870,62 @@ impl DocumentMessageHandler {
|
|||
.into(),
|
||||
);
|
||||
}
|
||||
|
||||
pub fn selected_layers_reorder(&mut self, relative_index_offset: isize, responses: &mut VecDeque<Message>) {
|
||||
self.backup(responses);
|
||||
|
||||
let all_layer_paths = self.all_layers_sorted();
|
||||
let selected_layers = self.selected_layers_sorted();
|
||||
|
||||
let first_or_last_selected_layer = match relative_index_offset.signum() {
|
||||
-1 => selected_layers.first(),
|
||||
1 => selected_layers.last(),
|
||||
_ => panic!("selected_layers_reorder() must be given a non-zero value"),
|
||||
};
|
||||
|
||||
if let Some(pivot_layer) = first_or_last_selected_layer {
|
||||
let sibling_layer_paths: Vec<_> = all_layer_paths
|
||||
.iter()
|
||||
.filter(|layer| {
|
||||
// Check if this is a sibling of the pivot layer
|
||||
// TODO: Break this out into a reusable function `fn are_layers_siblings(layer_a, layer_b) -> bool`
|
||||
let containing_folder_path = &pivot_layer[0..pivot_layer.len() - 1];
|
||||
layer.starts_with(containing_folder_path) && pivot_layer.len() == layer.len()
|
||||
})
|
||||
.collect();
|
||||
|
||||
// TODO: Break this out into a reusable function: `fn layer_index_in_containing_folder(layer_path) -> usize`
|
||||
let pivot_index_among_siblings = sibling_layer_paths.iter().position(|path| *path == pivot_layer);
|
||||
|
||||
if let Some(pivot_index) = pivot_index_among_siblings {
|
||||
let max = sibling_layer_paths.len() as i64 - 1;
|
||||
let insert_index = (pivot_index as i64 + relative_index_offset as i64).clamp(0, max) as usize;
|
||||
|
||||
let existing_layer_to_insert_beside = sibling_layer_paths.get(insert_index);
|
||||
|
||||
// TODO: Break this block out into a call to a message called `MoveSelectedLayersNextToLayer { neighbor_path, above_or_below }`
|
||||
if let Some(neighbor_path) = existing_layer_to_insert_beside {
|
||||
let (neighbor_id, folder_path) = neighbor_path.split_last().expect("Can't move the root folder");
|
||||
|
||||
if let Some(folder) = self.graphene_document.layer(folder_path).ok().and_then(|layer| layer.as_folder().ok()) {
|
||||
let neighbor_layer_index = folder.layer_ids.iter().position(|id| id == neighbor_id).unwrap() as isize;
|
||||
|
||||
// If moving down, insert below this layer. If moving up, insert above this layer.
|
||||
let insert_index = if relative_index_offset < 0 { neighbor_layer_index } else { neighbor_layer_index + 1 };
|
||||
|
||||
responses.push_back(
|
||||
DocumentMessage::MoveSelectedLayersTo {
|
||||
folder_path: folder_path.to_vec(),
|
||||
insert_index,
|
||||
reverse_index: false,
|
||||
}
|
||||
.into(),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl MessageHandler<DocumentMessage, (&InputPreprocessorMessageHandler, &FontCache)> for DocumentMessageHandler {
|
||||
|
@ -1318,61 +1380,6 @@ impl MessageHandler<DocumentMessage, (&InputPreprocessorMessageHandler, &FontCac
|
|||
.into(),
|
||||
);
|
||||
}
|
||||
ReorderSelectedLayers { relative_index_offset } => {
|
||||
self.backup(responses);
|
||||
|
||||
let all_layer_paths = self.all_layers_sorted();
|
||||
let selected_layers = self.selected_layers_sorted();
|
||||
|
||||
let first_or_last_selected_layer = match relative_index_offset.signum() {
|
||||
-1 => selected_layers.first(),
|
||||
1 => selected_layers.last(),
|
||||
_ => panic!("ReorderSelectedLayers must be given a non-zero value"),
|
||||
};
|
||||
|
||||
if let Some(pivot_layer) = first_or_last_selected_layer {
|
||||
let sibling_layer_paths: Vec<_> = all_layer_paths
|
||||
.iter()
|
||||
.filter(|layer| {
|
||||
// Check if this is a sibling of the pivot layer
|
||||
// TODO: Break this out into a reusable function `fn are_layers_siblings(layer_a, layer_b) -> bool`
|
||||
let containing_folder_path = &pivot_layer[0..pivot_layer.len() - 1];
|
||||
layer.starts_with(containing_folder_path) && pivot_layer.len() == layer.len()
|
||||
})
|
||||
.collect();
|
||||
|
||||
// TODO: Break this out into a reusable function: `fn layer_index_in_containing_folder(layer_path) -> usize`
|
||||
let pivot_index_among_siblings = sibling_layer_paths.iter().position(|path| *path == pivot_layer);
|
||||
|
||||
if let Some(pivot_index) = pivot_index_among_siblings {
|
||||
let max = sibling_layer_paths.len() as i64 - 1;
|
||||
let insert_index = (pivot_index as i64 + relative_index_offset as i64).clamp(0, max) as usize;
|
||||
|
||||
let existing_layer_to_insert_beside = sibling_layer_paths.get(insert_index);
|
||||
|
||||
// TODO: Break this block out into a call to a message called `MoveSelectedLayersNextToLayer { neighbor_path, above_or_below }`
|
||||
if let Some(neighbor_path) = existing_layer_to_insert_beside {
|
||||
let (neighbor_id, folder_path) = neighbor_path.split_last().expect("Can't move the root folder");
|
||||
|
||||
if let Some(folder) = self.graphene_document.layer(folder_path).ok().and_then(|layer| layer.as_folder().ok()) {
|
||||
let neighbor_layer_index = folder.layer_ids.iter().position(|id| id == neighbor_id).unwrap() as isize;
|
||||
|
||||
// If moving down, insert below this layer. If moving up, insert above this layer.
|
||||
let insert_index = if relative_index_offset < 0 { neighbor_layer_index } else { neighbor_layer_index + 1 };
|
||||
|
||||
responses.push_back(
|
||||
DocumentMessage::MoveSelectedLayersTo {
|
||||
folder_path: folder_path.to_vec(),
|
||||
insert_index,
|
||||
reverse_index: false,
|
||||
}
|
||||
.into(),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
RollbackTransaction => {
|
||||
self.rollback(responses).unwrap_or_else(|e| log::warn!("{}", e));
|
||||
responses.extend([RenderDocument.into(), DocumentStructureChanged.into()]);
|
||||
|
@ -1399,6 +1406,21 @@ impl MessageHandler<DocumentMessage, (&InputPreprocessorMessageHandler, &FontCac
|
|||
let all = self.all_layers().map(|path| path.to_vec()).collect();
|
||||
responses.push_front(SetSelectedLayers { replacement_selected_layers: all }.into());
|
||||
}
|
||||
SelectedLayersLower => {
|
||||
responses.push_front(DocumentMessage::SelectedLayersReorder { relative_index_offset: -1 }.into());
|
||||
}
|
||||
SelectedLayersLowerToBack => {
|
||||
responses.push_front(DocumentMessage::SelectedLayersReorder { relative_index_offset: isize::MIN }.into());
|
||||
}
|
||||
SelectedLayersRaise => {
|
||||
responses.push_front(DocumentMessage::SelectedLayersReorder { relative_index_offset: 1 }.into());
|
||||
}
|
||||
SelectedLayersRaiseToFront => {
|
||||
responses.push_front(DocumentMessage::SelectedLayersReorder { relative_index_offset: isize::MAX }.into());
|
||||
}
|
||||
SelectedLayersReorder { relative_index_offset } => {
|
||||
self.selected_layers_reorder(relative_index_offset, responses);
|
||||
}
|
||||
SelectLayer { layer_path, ctrl, shift } => {
|
||||
let mut paths = vec![];
|
||||
let last_selection_exists = !self.layer_range_selection_reference.is_empty();
|
||||
|
@ -1611,7 +1633,10 @@ impl MessageHandler<DocumentMessage, (&InputPreprocessorMessageHandler, &FontCac
|
|||
DeleteSelectedLayers,
|
||||
DuplicateSelectedLayers,
|
||||
NudgeSelectedLayers,
|
||||
ReorderSelectedLayers,
|
||||
SelectedLayersLower,
|
||||
SelectedLayersLowerToBack,
|
||||
SelectedLayersRaise,
|
||||
SelectedLayersRaiseToFront,
|
||||
GroupSelectedLayers,
|
||||
UngroupSelectedLayers,
|
||||
);
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
use super::MenuBarMessage;
|
||||
use crate::input::keyboard::Key;
|
||||
use crate::input::input_mapper::action_keys::action_shortcut;
|
||||
use crate::layout::layout_message::LayoutTarget;
|
||||
use crate::layout::widgets::*;
|
||||
use crate::message_prelude::*;
|
||||
|
@ -30,31 +30,31 @@ impl PropertyHolder for MenuBarMessageHandler {
|
|||
Layout::MenuLayout(MenuLayout::new(vec![
|
||||
MenuColumn {
|
||||
label: "File".into(),
|
||||
children: vec![
|
||||
children: MenuEntryGroups(vec![
|
||||
vec![
|
||||
MenuEntry {
|
||||
label: "New…".into(),
|
||||
icon: Some("File".into()),
|
||||
action: MenuEntry::create_action(|_| DialogMessage::RequestNewDocumentDialog.into()),
|
||||
shortcut: Some(vec![Key::KeyControl, Key::KeyN]),
|
||||
children: None,
|
||||
shortcut: action_shortcut!(DialogMessageDiscriminant::RequestNewDocumentDialog),
|
||||
children: MenuEntryGroups::empty(),
|
||||
},
|
||||
MenuEntry {
|
||||
label: "Open…".into(),
|
||||
shortcut: Some(vec![Key::KeyControl, Key::KeyO]),
|
||||
shortcut: action_shortcut!(PortfolioMessageDiscriminant::OpenDocument),
|
||||
action: MenuEntry::create_action(|_| PortfolioMessage::OpenDocument.into()),
|
||||
..MenuEntry::default()
|
||||
},
|
||||
MenuEntry {
|
||||
label: "Open Recent".into(),
|
||||
shortcut: Some(vec![Key::KeyControl, Key::KeyShift, Key::KeyO]),
|
||||
shortcut: None,
|
||||
action: MenuEntry::no_action(),
|
||||
icon: None,
|
||||
children: Some(vec![
|
||||
children: MenuEntryGroups(vec![
|
||||
vec![
|
||||
MenuEntry {
|
||||
label: "Reopen Last Closed".into(),
|
||||
shortcut: Some(vec![Key::KeyControl, Key::KeyShift, Key::KeyT]),
|
||||
// shortcut: [Key::KeyControl, Key::KeyShift, Key::KeyT],
|
||||
..MenuEntry::default()
|
||||
},
|
||||
MenuEntry {
|
||||
|
@ -90,13 +90,13 @@ impl PropertyHolder for MenuBarMessageHandler {
|
|||
vec![
|
||||
MenuEntry {
|
||||
label: "Close".into(),
|
||||
shortcut: Some(vec![Key::KeyControl, Key::KeyW]),
|
||||
shortcut: action_shortcut!(PortfolioMessageDiscriminant::CloseActiveDocumentWithConfirmation),
|
||||
action: MenuEntry::create_action(|_| PortfolioMessage::CloseActiveDocumentWithConfirmation.into()),
|
||||
..MenuEntry::default()
|
||||
},
|
||||
MenuEntry {
|
||||
label: "Close All".into(),
|
||||
shortcut: Some(vec![Key::KeyControl, Key::KeyAlt, Key::KeyW]),
|
||||
shortcut: action_shortcut!(DialogMessageDiscriminant::CloseAllDocumentsWithConfirmation),
|
||||
action: MenuEntry::create_action(|_| DialogMessage::CloseAllDocumentsWithConfirmation.into()),
|
||||
..MenuEntry::default()
|
||||
},
|
||||
|
@ -104,19 +104,18 @@ impl PropertyHolder for MenuBarMessageHandler {
|
|||
vec![
|
||||
MenuEntry {
|
||||
label: "Save".into(),
|
||||
shortcut: Some(vec![Key::KeyControl, Key::KeyS]),
|
||||
shortcut: action_shortcut!(DocumentMessageDiscriminant::SaveDocument),
|
||||
action: MenuEntry::create_action(|_| DocumentMessage::SaveDocument.into()),
|
||||
..MenuEntry::default()
|
||||
},
|
||||
MenuEntry {
|
||||
label: "Save As…".into(),
|
||||
shortcut: Some(vec![Key::KeyControl, Key::KeyShift, Key::KeyS]),
|
||||
action: MenuEntry::create_action(|_| DocumentMessage::SaveDocument.into()),
|
||||
// shortcut: [Key::KeyControl, Key::KeyShift, Key::KeyS],
|
||||
..MenuEntry::default()
|
||||
},
|
||||
MenuEntry {
|
||||
label: "Save All".into(),
|
||||
shortcut: Some(vec![Key::KeyControl, Key::KeyAlt, Key::KeyS]),
|
||||
// shortcut: [Key::KeyControl, Key::KeyAlt, Key::KeyS],
|
||||
..MenuEntry::default()
|
||||
},
|
||||
MenuEntry {
|
||||
|
@ -128,37 +127,37 @@ impl PropertyHolder for MenuBarMessageHandler {
|
|||
vec![
|
||||
MenuEntry {
|
||||
label: "Import…".into(),
|
||||
shortcut: Some(vec![Key::KeyControl, Key::KeyI]),
|
||||
shortcut: action_shortcut!(PortfolioMessageDiscriminant::Import),
|
||||
action: MenuEntry::create_action(|_| PortfolioMessage::Import.into()),
|
||||
..MenuEntry::default()
|
||||
},
|
||||
MenuEntry {
|
||||
label: "Export…".into(),
|
||||
shortcut: Some(vec![Key::KeyControl, Key::KeyE]),
|
||||
shortcut: action_shortcut!(DialogMessageDiscriminant::RequestExportDialog),
|
||||
action: MenuEntry::create_action(|_| DialogMessage::RequestExportDialog.into()),
|
||||
..MenuEntry::default()
|
||||
},
|
||||
],
|
||||
vec![MenuEntry {
|
||||
label: "Quit".into(),
|
||||
shortcut: Some(vec![Key::KeyControl, Key::KeyQ]),
|
||||
// shortcut: [Key::KeyControl, Key::KeyQ],
|
||||
..MenuEntry::default()
|
||||
}],
|
||||
],
|
||||
]),
|
||||
},
|
||||
MenuColumn {
|
||||
label: "Edit".into(),
|
||||
children: vec![
|
||||
children: MenuEntryGroups(vec![
|
||||
vec![
|
||||
MenuEntry {
|
||||
label: "Undo".into(),
|
||||
shortcut: Some(vec![Key::KeyControl, Key::KeyZ]),
|
||||
shortcut: action_shortcut!(DocumentMessageDiscriminant::Undo),
|
||||
action: MenuEntry::create_action(|_| DocumentMessage::Undo.into()),
|
||||
..MenuEntry::default()
|
||||
},
|
||||
MenuEntry {
|
||||
label: "Redo".into(),
|
||||
shortcut: Some(vec![Key::KeyControl, Key::KeyShift, Key::KeyZ]),
|
||||
shortcut: action_shortcut!(DocumentMessageDiscriminant::Redo),
|
||||
action: MenuEntry::create_action(|_| DocumentMessage::Redo.into()),
|
||||
..MenuEntry::default()
|
||||
},
|
||||
|
@ -166,93 +165,93 @@ impl PropertyHolder for MenuBarMessageHandler {
|
|||
vec![
|
||||
MenuEntry {
|
||||
label: "Cut".into(),
|
||||
shortcut: Some(vec![Key::KeyControl, Key::KeyX]),
|
||||
shortcut: action_shortcut!(PortfolioMessageDiscriminant::Cut),
|
||||
action: MenuEntry::create_action(|_| PortfolioMessage::Cut { clipboard: Clipboard::Device }.into()),
|
||||
..MenuEntry::default()
|
||||
},
|
||||
MenuEntry {
|
||||
label: "Copy".into(),
|
||||
icon: Some("Copy".into()),
|
||||
shortcut: Some(vec![Key::KeyControl, Key::KeyC]),
|
||||
shortcut: action_shortcut!(PortfolioMessageDiscriminant::Copy),
|
||||
action: MenuEntry::create_action(|_| PortfolioMessage::Copy { clipboard: Clipboard::Device }.into()),
|
||||
..MenuEntry::default()
|
||||
},
|
||||
MenuEntry {
|
||||
label: "Paste".into(),
|
||||
icon: Some("Paste".into()),
|
||||
shortcut: Some(vec![Key::KeyControl, Key::KeyV]),
|
||||
shortcut: action_shortcut!(FrontendMessageDiscriminant::TriggerPaste),
|
||||
action: MenuEntry::create_action(|_| FrontendMessage::TriggerPaste.into()),
|
||||
..MenuEntry::default()
|
||||
},
|
||||
],
|
||||
],
|
||||
]),
|
||||
},
|
||||
MenuColumn {
|
||||
label: "Layer".into(),
|
||||
children: vec![vec![
|
||||
children: MenuEntryGroups(vec![vec![
|
||||
MenuEntry {
|
||||
label: "Select All".into(),
|
||||
shortcut: Some(vec![Key::KeyControl, Key::KeyA]),
|
||||
shortcut: action_shortcut!(DocumentMessageDiscriminant::SelectAllLayers),
|
||||
action: MenuEntry::create_action(|_| DocumentMessage::SelectAllLayers.into()),
|
||||
..MenuEntry::default()
|
||||
},
|
||||
MenuEntry {
|
||||
label: "Deselect All".into(),
|
||||
shortcut: Some(vec![Key::KeyControl, Key::KeyAlt, Key::KeyA]),
|
||||
shortcut: action_shortcut!(DocumentMessageDiscriminant::DeselectAllLayers),
|
||||
action: MenuEntry::create_action(|_| DocumentMessage::DeselectAllLayers.into()),
|
||||
..MenuEntry::default()
|
||||
},
|
||||
MenuEntry {
|
||||
label: "Order".into(),
|
||||
action: MenuEntry::no_action(),
|
||||
children: Some(vec![vec![
|
||||
children: MenuEntryGroups(vec![vec![
|
||||
MenuEntry {
|
||||
label: "Raise To Front".into(),
|
||||
shortcut: Some(vec![Key::KeyControl, Key::KeyShift, Key::KeyLeftBracket]),
|
||||
action: MenuEntry::create_action(|_| DocumentMessage::ReorderSelectedLayers { relative_index_offset: isize::MAX }.into()),
|
||||
shortcut: action_shortcut!(DocumentMessageDiscriminant::SelectedLayersRaiseToFront),
|
||||
action: MenuEntry::create_action(|_| DocumentMessage::SelectedLayersRaiseToFront.into()),
|
||||
..MenuEntry::default()
|
||||
},
|
||||
MenuEntry {
|
||||
label: "Raise".into(),
|
||||
shortcut: Some(vec![Key::KeyControl, Key::KeyRightBracket]),
|
||||
action: MenuEntry::create_action(|_| DocumentMessage::ReorderSelectedLayers { relative_index_offset: 1 }.into()),
|
||||
shortcut: action_shortcut!(DocumentMessageDiscriminant::SelectedLayersRaise),
|
||||
action: MenuEntry::create_action(|_| DocumentMessage::SelectedLayersRaise.into()),
|
||||
..MenuEntry::default()
|
||||
},
|
||||
MenuEntry {
|
||||
label: "Lower".into(),
|
||||
shortcut: Some(vec![Key::KeyControl, Key::KeyLeftBracket]),
|
||||
action: MenuEntry::create_action(|_| DocumentMessage::ReorderSelectedLayers { relative_index_offset: -1 }.into()),
|
||||
shortcut: action_shortcut!(DocumentMessageDiscriminant::SelectedLayersLower),
|
||||
action: MenuEntry::create_action(|_| DocumentMessage::SelectedLayersLower.into()),
|
||||
..MenuEntry::default()
|
||||
},
|
||||
MenuEntry {
|
||||
label: "Lower to Back".into(),
|
||||
shortcut: Some(vec![Key::KeyControl, Key::KeyShift, Key::KeyRightBracket]),
|
||||
action: MenuEntry::create_action(|_| DocumentMessage::ReorderSelectedLayers { relative_index_offset: isize::MIN }.into()),
|
||||
shortcut: action_shortcut!(DocumentMessageDiscriminant::SelectedLayersLowerToBack),
|
||||
action: MenuEntry::create_action(|_| DocumentMessage::SelectedLayersLowerToBack.into()),
|
||||
..MenuEntry::default()
|
||||
},
|
||||
]]),
|
||||
..MenuEntry::default()
|
||||
},
|
||||
]],
|
||||
]]),
|
||||
},
|
||||
MenuColumn {
|
||||
label: "Document".into(),
|
||||
children: vec![vec![MenuEntry {
|
||||
children: MenuEntryGroups(vec![vec![MenuEntry {
|
||||
label: "Menu entries coming soon".into(),
|
||||
..MenuEntry::default()
|
||||
}]],
|
||||
}]]),
|
||||
},
|
||||
MenuColumn {
|
||||
label: "View".into(),
|
||||
children: vec![vec![MenuEntry {
|
||||
children: MenuEntryGroups(vec![vec![MenuEntry {
|
||||
label: "Show/Hide Node Graph (In Development)".into(),
|
||||
action: MenuEntry::create_action(|_| WorkspaceMessage::NodeGraphToggleVisibility.into()),
|
||||
..MenuEntry::default()
|
||||
}]],
|
||||
}]]),
|
||||
},
|
||||
MenuColumn {
|
||||
label: "Help".into(),
|
||||
children: vec![
|
||||
children: MenuEntryGroups(vec![
|
||||
vec![MenuEntry {
|
||||
label: "About Graphite".into(),
|
||||
action: MenuEntry::create_action(|_| DialogMessage::RequestAboutGraphiteDialog.into()),
|
||||
|
@ -284,23 +283,23 @@ impl PropertyHolder for MenuBarMessageHandler {
|
|||
MenuEntry {
|
||||
label: "Debug: Print Messages".into(),
|
||||
action: MenuEntry::no_action(),
|
||||
children: Some(vec![vec![
|
||||
children: MenuEntryGroups(vec![vec![
|
||||
MenuEntry {
|
||||
label: "Off".into(),
|
||||
// icon: Some("Checkmark".into()), // TODO: Find a way to set this icon on the active mode
|
||||
shortcut: Some(vec![Key::KeyAlt, Key::Key0]),
|
||||
shortcut: action_shortcut!(DebugMessageDiscriminant::MessageOff),
|
||||
action: MenuEntry::create_action(|_| DebugMessage::MessageOff.into()),
|
||||
..MenuEntry::default()
|
||||
},
|
||||
MenuEntry {
|
||||
label: "Only Names".into(),
|
||||
shortcut: Some(vec![Key::KeyAlt, Key::Key1]),
|
||||
shortcut: action_shortcut!(DebugMessageDiscriminant::MessageNames),
|
||||
action: MenuEntry::create_action(|_| DebugMessage::MessageNames.into()),
|
||||
..MenuEntry::default()
|
||||
},
|
||||
MenuEntry {
|
||||
label: "Full Contents".into(),
|
||||
shortcut: Some(vec![Key::KeyAlt, Key::Key2]),
|
||||
shortcut: action_shortcut!(DebugMessageDiscriminant::MessageContents),
|
||||
action: MenuEntry::create_action(|_| DebugMessage::MessageContents.into()),
|
||||
..MenuEntry::default()
|
||||
},
|
||||
|
@ -310,14 +309,14 @@ impl PropertyHolder for MenuBarMessageHandler {
|
|||
MenuEntry {
|
||||
label: "Debug: Print Trace Logs".into(),
|
||||
icon: Some(if let log::LevelFilter::Trace = log::max_level() { "CheckboxChecked" } else { "CheckboxUnchecked" }.into()),
|
||||
shortcut: Some(vec![Key::KeyAlt, Key::KeyT]),
|
||||
shortcut: action_shortcut!(DebugMessageDiscriminant::ToggleTraceLogs),
|
||||
action: MenuEntry::create_action(|_| DebugMessage::ToggleTraceLogs.into()),
|
||||
..MenuEntry::default()
|
||||
},
|
||||
MenuEntry {
|
||||
label: "Debug: Print Document".into(),
|
||||
shortcut: Some(vec![Key::KeyAlt, Key::KeyP]),
|
||||
action: MenuEntry::create_action(|_| DebugMessage::ToggleTraceLogs.into()),
|
||||
shortcut: action_shortcut!(DocumentMessageDiscriminant::DebugPrintDocument),
|
||||
action: MenuEntry::create_action(|_| DocumentMessage::DebugPrintDocument.into()),
|
||||
..MenuEntry::default()
|
||||
},
|
||||
MenuEntry {
|
||||
|
@ -326,7 +325,7 @@ impl PropertyHolder for MenuBarMessageHandler {
|
|||
..MenuEntry::default()
|
||||
},
|
||||
],
|
||||
],
|
||||
]),
|
||||
},
|
||||
]))
|
||||
}
|
||||
|
|
|
@ -230,6 +230,7 @@ impl MessageHandler<MovementMessage, (&Document, &InputPreprocessorMessageHandle
|
|||
FrontendMessage::UpdateInputHints {
|
||||
hint_data: HintData(vec![HintGroup(vec![HintInfo {
|
||||
key_groups: vec![KeysGroup(vec![Key::KeyControl])],
|
||||
key_groups_mac: None,
|
||||
mouse: None,
|
||||
label: String::from("Snap 15°"),
|
||||
plus: false,
|
||||
|
@ -311,6 +312,7 @@ impl MessageHandler<MovementMessage, (&Document, &InputPreprocessorMessageHandle
|
|||
FrontendMessage::UpdateInputHints {
|
||||
hint_data: HintData(vec![HintGroup(vec![HintInfo {
|
||||
key_groups: vec![KeysGroup(vec![Key::KeyControl])],
|
||||
key_groups_mac: None,
|
||||
mouse: None,
|
||||
label: String::from("Snap Increments"),
|
||||
plus: false,
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
use super::clipboards::Clipboard;
|
||||
use super::utility_types::Platform;
|
||||
use crate::message_prelude::*;
|
||||
|
||||
use graphene::layers::text_layer::Font;
|
||||
|
@ -83,6 +84,9 @@ pub enum PortfolioMessage {
|
|||
SetActiveDocument {
|
||||
document_id: u64,
|
||||
},
|
||||
SetPlatform {
|
||||
platform: Platform,
|
||||
},
|
||||
UpdateDocumentWidgets,
|
||||
UpdateOpenDocumentsList,
|
||||
}
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
use super::clipboards::{CopyBufferEntry, INTERNAL_CLIPBOARD_COUNT};
|
||||
use super::utility_types::Platform;
|
||||
use super::{DocumentMessageHandler, MenuBarMessageHandler};
|
||||
use crate::consts::{DEFAULT_DOCUMENT_NAME, GRAPHITE_DOCUMENT_VERSION};
|
||||
use crate::dialog;
|
||||
|
@ -23,6 +24,7 @@ pub struct PortfolioMessageHandler {
|
|||
active_document_id: Option<u64>,
|
||||
copy_buffer: [Vec<CopyBufferEntry>; INTERNAL_CLIPBOARD_COUNT as usize],
|
||||
font_cache: FontCache,
|
||||
pub platform: Platform,
|
||||
}
|
||||
|
||||
impl PortfolioMessageHandler {
|
||||
|
@ -473,6 +475,7 @@ impl MessageHandler<PortfolioMessage, &InputPreprocessorMessageHandler> for Port
|
|||
responses.push_back(MovementMessage::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);
|
||||
|
|
|
@ -61,3 +61,34 @@ impl DocumentMode {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Eq, Clone, Copy, Default, Debug, Serialize, Deserialize)]
|
||||
pub enum Platform {
|
||||
#[default]
|
||||
Unknown,
|
||||
Windows,
|
||||
Mac,
|
||||
Linux,
|
||||
}
|
||||
|
||||
impl Platform {
|
||||
pub fn as_keyboard_platform_layout(&self) -> KeyboardPlatformLayout {
|
||||
match self {
|
||||
Platform::Mac => KeyboardPlatformLayout::Mac,
|
||||
Platform::Unknown => {
|
||||
log::warn!("The platform has not been set, remember to send `PortfolioMessage::SetPlatform` during editor initialization.");
|
||||
KeyboardPlatformLayout::Standard
|
||||
}
|
||||
_ => KeyboardPlatformLayout::Standard,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Eq, Clone, Copy, Default, Debug, Serialize, Deserialize)]
|
||||
pub enum KeyboardPlatformLayout {
|
||||
/// Standard keyboard mapping used by Windows and Linux
|
||||
#[default]
|
||||
Standard,
|
||||
/// Keyboard mapping used by Macs where Command is sometimes used in favor of Control
|
||||
Mac,
|
||||
}
|
||||
|
|
|
@ -12,7 +12,7 @@ use serde::{Deserialize, Serialize};
|
|||
|
||||
#[remain::sorted]
|
||||
#[impl_message(Message, Frontend)]
|
||||
#[derive(PartialEq, Clone, Deserialize, Serialize, Debug)]
|
||||
#[derive(PartialEq, Clone, Debug, Serialize, Deserialize)]
|
||||
pub enum FrontendMessage {
|
||||
// Display prefix: make the frontend show something, like a dialog
|
||||
DisplayDialog { icon: String },
|
||||
|
|
|
@ -1,21 +1,21 @@
|
|||
use graphene::LayerId;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(PartialEq, Eq, Clone, Deserialize, Serialize, Debug)]
|
||||
#[derive(PartialEq, Eq, Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct FrontendDocumentDetails {
|
||||
pub is_saved: bool,
|
||||
pub name: String,
|
||||
pub id: u64,
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Eq, Clone, Deserialize, Serialize, Debug)]
|
||||
#[derive(PartialEq, Eq, Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct FrontendImageData {
|
||||
pub path: Vec<LayerId>,
|
||||
pub mime: String,
|
||||
pub image_data: Vec<u8>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Eq, Deserialize, PartialEq, Serialize)]
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize, Deserialize)]
|
||||
pub enum MouseCursorIcon {
|
||||
Default,
|
||||
ZoomIn,
|
||||
|
@ -35,7 +35,7 @@ impl Default for MouseCursorIcon {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Eq, Deserialize, PartialEq, Serialize)]
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize, Deserialize)]
|
||||
pub enum FileType {
|
||||
Svg,
|
||||
Png,
|
||||
|
@ -58,7 +58,7 @@ impl FileType {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Eq, Deserialize, PartialEq, Serialize)]
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize, Deserialize)]
|
||||
pub enum ExportBounds {
|
||||
AllArtwork,
|
||||
Selection,
|
||||
|
|
|
@ -1,25 +1,25 @@
|
|||
use super::input_mapper_macros::*;
|
||||
use super::keyboard::{Key, KeyStates, NUMBER_OF_KEYS};
|
||||
use crate::consts::{BIG_NUDGE_AMOUNT, NUDGE_AMOUNT};
|
||||
use crate::document::clipboards::Clipboard;
|
||||
use crate::document::utility_types::KeyboardPlatformLayout;
|
||||
use crate::message_prelude::*;
|
||||
use crate::viewport_tools::tool::ToolType;
|
||||
|
||||
use glam::DVec2;
|
||||
|
||||
const NUDGE_AMOUNT: f64 = 1.;
|
||||
const SHIFT_NUDGE_AMOUNT: f64 = 10.;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Mapping {
|
||||
pub key_up: [KeyMappingEntries; NUMBER_OF_KEYS],
|
||||
pub key_down: [KeyMappingEntries; NUMBER_OF_KEYS],
|
||||
pub pointer_move: KeyMappingEntries,
|
||||
pub mouse_scroll: KeyMappingEntries,
|
||||
pub double_click: KeyMappingEntries,
|
||||
pub wheel_scroll: KeyMappingEntries,
|
||||
pub pointer_move: KeyMappingEntries,
|
||||
}
|
||||
|
||||
impl Default for Mapping {
|
||||
fn default() -> Self {
|
||||
use input_mapper_macros::{entry, mapping, modifiers};
|
||||
use InputMapperMessage::*;
|
||||
use Key::*;
|
||||
|
||||
// WARNING!
|
||||
|
@ -27,210 +27,317 @@ impl Default for Mapping {
|
|||
// it as an available action in the respective message handler file (such as the bottom of `document_message_handler.rs`).
|
||||
|
||||
let mappings = mapping![
|
||||
// Higher priority than entries in sections below
|
||||
entry! {action=MovementMessage::PointerMove { snap_angle: KeyControl, wait_for_snap_angle_release: true, snap_zoom: KeyControl, zoom_from_viewport: None }, message=InputMapperMessage::PointerMove},
|
||||
// Transform layers
|
||||
entry! {action=TransformLayerMessage::ApplyTransformOperation, key_down=KeyEnter},
|
||||
entry! {action=TransformLayerMessage::ApplyTransformOperation, key_down=Lmb},
|
||||
entry! {action=TransformLayerMessage::CancelTransformOperation, key_down=KeyEscape},
|
||||
entry! {action=TransformLayerMessage::CancelTransformOperation, key_down=Rmb},
|
||||
entry! {action=TransformLayerMessage::ConstrainX, key_down=KeyX},
|
||||
entry! {action=TransformLayerMessage::ConstrainY, key_down=KeyY},
|
||||
entry! {action=TransformLayerMessage::TypeBackspace, key_down=KeyBackspace},
|
||||
entry! {action=TransformLayerMessage::TypeNegate, key_down=KeyMinus},
|
||||
entry! {action=TransformLayerMessage::TypeDecimalPoint, key_down=KeyComma},
|
||||
entry! {action=TransformLayerMessage::TypeDecimalPoint, key_down=KeyPeriod},
|
||||
entry! {action=TransformLayerMessage::PointerMove { slow_key: KeyShift, snap_key: KeyControl }, triggers=[KeyShift, KeyControl]},
|
||||
// Select
|
||||
entry! {action=SelectToolMessage::PointerMove { axis_align: KeyShift, snap_angle: KeyControl, center: KeyAlt }, message=InputMapperMessage::PointerMove},
|
||||
entry! {action=SelectToolMessage::DragStart { add_to_selection: KeyShift }, key_down=Lmb},
|
||||
entry! {action=SelectToolMessage::DragStop, key_up=Lmb},
|
||||
entry! {action=SelectToolMessage::DragStop, key_down=KeyEnter},
|
||||
entry! {action=SelectToolMessage::EditLayer, message=InputMapperMessage::DoubleClick},
|
||||
entry! {action=SelectToolMessage::Abort, key_down=Rmb},
|
||||
entry! {action=SelectToolMessage::Abort, key_down=KeyEscape},
|
||||
// Artboard
|
||||
entry! {action=ArtboardToolMessage::PointerDown, key_down=Lmb},
|
||||
entry! {action=ArtboardToolMessage::PointerMove { constrain_axis_or_aspect: KeyShift, center: KeyAlt }, message=InputMapperMessage::PointerMove},
|
||||
entry! {action=ArtboardToolMessage::PointerUp, key_up=Lmb},
|
||||
entry! {action=ArtboardToolMessage::DeleteSelected, key_down=KeyDelete},
|
||||
entry! {action=ArtboardToolMessage::DeleteSelected, key_down=KeyBackspace},
|
||||
// Navigate
|
||||
entry! {action=NavigateToolMessage::ClickZoom { zoom_in: false }, key_up=Lmb, modifiers=[KeyShift]},
|
||||
entry! {action=NavigateToolMessage::ClickZoom { zoom_in: true }, key_up=Lmb},
|
||||
entry! {action=NavigateToolMessage::PointerMove { snap_angle: KeyControl, snap_zoom: KeyControl }, message=InputMapperMessage::PointerMove},
|
||||
entry! {action=NavigateToolMessage::TranslateCanvasBegin, key_down=Mmb},
|
||||
entry! {action=NavigateToolMessage::RotateCanvasBegin, key_down=Rmb},
|
||||
entry! {action=NavigateToolMessage::ZoomCanvasBegin, key_down=Lmb},
|
||||
entry! {action=NavigateToolMessage::TransformCanvasEnd, key_up=Rmb},
|
||||
entry! {action=NavigateToolMessage::TransformCanvasEnd, key_up=Lmb},
|
||||
entry! {action=NavigateToolMessage::TransformCanvasEnd, key_up=Mmb},
|
||||
// Eyedropper
|
||||
entry! {action=EyedropperToolMessage::LeftMouseDown, key_down=Lmb},
|
||||
entry! {action=EyedropperToolMessage::RightMouseDown, key_down=Rmb},
|
||||
// Text
|
||||
entry! {action=TextMessage::Interact, key_up=Lmb},
|
||||
entry! {action=TextMessage::Abort, key_down=KeyEscape},
|
||||
entry! {action=TextMessage::CommitText, key_down=KeyEnter, modifiers=[KeyControl]},
|
||||
// Gradient
|
||||
entry! {action=GradientToolMessage::PointerDown, key_down=Lmb},
|
||||
entry! {action=GradientToolMessage::PointerMove { constrain_axis: KeyShift }, message=InputMapperMessage::PointerMove},
|
||||
entry! {action=GradientToolMessage::PointerUp, key_up=Lmb},
|
||||
// Rectangle
|
||||
entry! {action=RectangleToolMessage::DragStart, key_down=Lmb},
|
||||
entry! {action=RectangleToolMessage::DragStop, key_up=Lmb},
|
||||
entry! {action=RectangleToolMessage::Abort, key_down=Rmb},
|
||||
entry! {action=RectangleToolMessage::Abort, key_down=KeyEscape},
|
||||
entry! {action=RectangleToolMessage::Resize { center: KeyAlt, lock_ratio: KeyShift }, triggers=[KeyAlt, KeyShift]},
|
||||
// Ellipse
|
||||
entry! {action=EllipseToolMessage::DragStart, key_down=Lmb},
|
||||
entry! {action=EllipseToolMessage::DragStop, key_up=Lmb},
|
||||
entry! {action=EllipseToolMessage::Abort, key_down=Rmb},
|
||||
entry! {action=EllipseToolMessage::Abort, key_down=KeyEscape},
|
||||
entry! {action=EllipseToolMessage::Resize { center: KeyAlt, lock_ratio: KeyShift }, triggers=[KeyAlt, KeyShift]},
|
||||
// Shape
|
||||
entry! {action=ShapeToolMessage::DragStart, key_down=Lmb},
|
||||
entry! {action=ShapeToolMessage::DragStop, key_up=Lmb},
|
||||
entry! {action=ShapeToolMessage::Abort, key_down=Rmb},
|
||||
entry! {action=ShapeToolMessage::Abort, key_down=KeyEscape},
|
||||
entry! {action=ShapeToolMessage::Resize { center: KeyAlt, lock_ratio: KeyShift }, triggers=[KeyAlt, KeyShift]},
|
||||
// Line
|
||||
entry! {action=LineToolMessage::DragStart, key_down=Lmb},
|
||||
entry! {action=LineToolMessage::DragStop, key_up=Lmb},
|
||||
entry! {action=LineToolMessage::Abort, key_down=Rmb},
|
||||
entry! {action=LineToolMessage::Abort, key_down=KeyEscape},
|
||||
entry! {action=LineToolMessage::Redraw { center: KeyAlt, lock_angle: KeyControl, snap_angle: KeyShift }, triggers=[KeyAlt, KeyShift, KeyControl]},
|
||||
// Path
|
||||
entry! {action=PathToolMessage::DragStart { add_to_selection: KeyShift }, key_down=Lmb},
|
||||
entry! {action=PathToolMessage::PointerMove { alt_mirror_angle: KeyAlt, shift_mirror_distance: KeyShift }, message=InputMapperMessage::PointerMove},
|
||||
entry! {action=PathToolMessage::Delete, key_down=KeyDelete},
|
||||
entry! {action=PathToolMessage::Delete, key_down=KeyBackspace},
|
||||
entry! {action=PathToolMessage::DragStop, key_up=Lmb},
|
||||
// Pen
|
||||
entry! {action=PenToolMessage::PointerMove { snap_angle: KeyControl, break_handle: KeyShift }, message=InputMapperMessage::PointerMove},
|
||||
entry! {action=PenToolMessage::DragStart, key_down=Lmb},
|
||||
entry! {action=PenToolMessage::DragStop, key_up=Lmb},
|
||||
entry! {action=PenToolMessage::Confirm, key_down=Rmb},
|
||||
entry! {action=PenToolMessage::Confirm, key_down=KeyEscape},
|
||||
entry! {action=PenToolMessage::Confirm, key_down=KeyEnter},
|
||||
// Freehand
|
||||
entry! {action=FreehandToolMessage::PointerMove, message=InputMapperMessage::PointerMove},
|
||||
entry! {action=FreehandToolMessage::DragStart, key_down=Lmb},
|
||||
entry! {action=FreehandToolMessage::DragStop, key_up=Lmb},
|
||||
// Spline
|
||||
entry! {action=SplineToolMessage::PointerMove, message=InputMapperMessage::PointerMove},
|
||||
entry! {action=SplineToolMessage::DragStart, key_down=Lmb},
|
||||
entry! {action=SplineToolMessage::DragStop, key_up=Lmb},
|
||||
entry! {action=SplineToolMessage::Confirm, key_down=Rmb},
|
||||
entry! {action=SplineToolMessage::Confirm, key_down=KeyEscape},
|
||||
entry! {action=SplineToolMessage::Confirm, key_down=KeyEnter},
|
||||
// Fill
|
||||
entry! {action=FillToolMessage::LeftMouseDown, key_down=Lmb},
|
||||
entry! {action=FillToolMessage::RightMouseDown, key_down=Rmb},
|
||||
// Tool Actions
|
||||
entry! {action=ToolMessage::ActivateTool { tool_type: ToolType::Select }, key_down=KeyV},
|
||||
entry! {action=ToolMessage::ActivateTool { tool_type: ToolType::Navigate }, key_down=KeyZ},
|
||||
entry! {action=ToolMessage::ActivateTool { tool_type: ToolType::Eyedropper }, key_down=KeyI},
|
||||
entry! {action=ToolMessage::ActivateTool { tool_type: ToolType::Text }, key_down=KeyT},
|
||||
entry! {action=ToolMessage::ActivateTool { tool_type: ToolType::Fill }, key_down=KeyF},
|
||||
entry! {action=ToolMessage::ActivateTool { tool_type: ToolType::Gradient }, key_down=KeyH},
|
||||
entry! {action=ToolMessage::ActivateTool { tool_type: ToolType::Path }, key_down=KeyA},
|
||||
entry! {action=ToolMessage::ActivateTool { tool_type: ToolType::Pen }, key_down=KeyP},
|
||||
entry! {action=ToolMessage::ActivateTool { tool_type: ToolType::Freehand }, key_down=KeyN},
|
||||
entry! {action=ToolMessage::ActivateTool { tool_type: ToolType::Line }, key_down=KeyL},
|
||||
entry! {action=ToolMessage::ActivateTool { tool_type: ToolType::Rectangle }, key_down=KeyM},
|
||||
entry! {action=ToolMessage::ActivateTool { tool_type: ToolType::Ellipse }, key_down=KeyE},
|
||||
entry! {action=ToolMessage::ActivateTool { tool_type: ToolType::Shape }, key_down=KeyY},
|
||||
// Colors
|
||||
entry! {action=ToolMessage::ResetColors, key_down=KeyX, modifiers=[KeyShift, KeyControl]},
|
||||
entry! {action=ToolMessage::SwapColors, key_down=KeyX, modifiers=[KeyShift]},
|
||||
entry! {action=ToolMessage::SelectRandomPrimaryColor, key_down=KeyC, modifiers=[KeyAlt]},
|
||||
// Document actions
|
||||
entry! {action=DocumentMessage::Redo, key_down=KeyZ, modifiers=[KeyControl, KeyShift]},
|
||||
entry! {action=DocumentMessage::Undo, key_down=KeyZ, modifiers=[KeyControl]},
|
||||
entry! {action=DocumentMessage::DeselectAllLayers, key_down=KeyA, modifiers=[KeyControl, KeyAlt]},
|
||||
entry! {action=DocumentMessage::SelectAllLayers, key_down=KeyA, modifiers=[KeyControl]},
|
||||
entry! {action=DocumentMessage::DeleteSelectedLayers, key_down=KeyDelete},
|
||||
entry! {action=DocumentMessage::DeleteSelectedLayers, key_down=KeyBackspace},
|
||||
entry! {action=DialogMessage::RequestExportDialog, key_down=KeyE, modifiers=[KeyControl]},
|
||||
entry! {action=DocumentMessage::SaveDocument, key_down=KeyS, modifiers=[KeyControl]},
|
||||
entry! {action=DocumentMessage::SaveDocument, key_down=KeyS, modifiers=[KeyControl, KeyShift]},
|
||||
entry! {action=DocumentMessage::DebugPrintDocument, key_down=KeyP, modifiers=[KeyAlt]},
|
||||
entry! {action=DocumentMessage::ZoomCanvasToFitAll, key_down=Key0, modifiers=[KeyControl]},
|
||||
entry! {action=DocumentMessage::DuplicateSelectedLayers, key_down=KeyD, modifiers=[KeyControl]},
|
||||
entry! {action=DocumentMessage::GroupSelectedLayers, key_down=KeyG, modifiers=[KeyControl]},
|
||||
entry! {action=DocumentMessage::UngroupSelectedLayers, key_down=KeyG, modifiers=[KeyControl, KeyShift]},
|
||||
entry! {action=DocumentMessage::CreateEmptyFolder { container_path: vec![] }, key_down=KeyN, modifiers=[KeyControl, KeyShift]},
|
||||
// Layer transformation
|
||||
entry! {action=TransformLayerMessage::BeginGrab, key_down=KeyG},
|
||||
entry! {action=TransformLayerMessage::BeginRotate, key_down=KeyR},
|
||||
entry! {action=TransformLayerMessage::BeginScale, key_down=KeyS},
|
||||
// Movement actions
|
||||
entry! {action=MovementMessage::RotateCanvasBegin, key_down=Mmb, modifiers=[KeyControl]},
|
||||
entry! {action=MovementMessage::ZoomCanvasBegin, key_down=Mmb, modifiers=[KeyShift]},
|
||||
entry! {action=MovementMessage::TranslateCanvasBegin, key_down=Mmb},
|
||||
entry! {action=MovementMessage::TransformCanvasEnd, key_up=Mmb},
|
||||
entry! {action=MovementMessage::TranslateCanvasBegin, key_down=Lmb, modifiers=[KeySpace]},
|
||||
entry! {action=MovementMessage::TransformCanvasEnd, key_up=Lmb, modifiers=[KeySpace]},
|
||||
entry! {action=MovementMessage::IncreaseCanvasZoom { center_on_mouse: false }, key_down=KeyPlus, modifiers=[KeyControl]},
|
||||
entry! {action=MovementMessage::IncreaseCanvasZoom { center_on_mouse: false }, key_down=KeyEquals, modifiers=[KeyControl]},
|
||||
entry! {action=MovementMessage::DecreaseCanvasZoom { center_on_mouse: false }, key_down=KeyMinus, modifiers=[KeyControl]},
|
||||
entry! {action=MovementMessage::SetCanvasZoom { zoom_factor: 1. }, key_down=Key1, modifiers=[KeyControl]},
|
||||
entry! {action=MovementMessage::SetCanvasZoom { zoom_factor: 2. }, key_down=Key2, modifiers=[KeyControl]},
|
||||
entry! {action=MovementMessage::WheelCanvasZoom, message=InputMapperMessage::MouseScroll, modifiers=[KeyControl]},
|
||||
entry! {action=MovementMessage::WheelCanvasTranslate { use_y_as_x: true }, message=InputMapperMessage::MouseScroll, modifiers=[KeyShift]},
|
||||
entry! {action=MovementMessage::WheelCanvasTranslate { use_y_as_x: false }, message=InputMapperMessage::MouseScroll},
|
||||
entry! {action=MovementMessage::TranslateCanvasByViewportFraction { delta: DVec2::new(1., 0.) }, key_down=KeyPageUp, modifiers=[KeyShift]},
|
||||
entry! {action=MovementMessage::TranslateCanvasByViewportFraction { delta: DVec2::new(-1., 0.) }, key_down=KeyPageDown, modifiers=[KeyShift]},
|
||||
entry! {action=MovementMessage::TranslateCanvasByViewportFraction { delta: DVec2::new(0., 1.) }, key_down=KeyPageUp},
|
||||
entry! {action=MovementMessage::TranslateCanvasByViewportFraction { delta: DVec2::new(0., -1.) }, key_down=KeyPageDown},
|
||||
// Portfolio actions
|
||||
entry! {action=PortfolioMessage::OpenDocument, key_down=KeyO, modifiers=[KeyControl]},
|
||||
entry! {action=PortfolioMessage::Import, key_down=KeyI, modifiers=[KeyControl]},
|
||||
entry! {action=DialogMessage::RequestNewDocumentDialog, key_down=KeyN, modifiers=[KeyControl]},
|
||||
entry! {action=PortfolioMessage::NextDocument, key_down=KeyTab, modifiers=[KeyControl]},
|
||||
entry! {action=PortfolioMessage::PrevDocument, key_down=KeyTab, modifiers=[KeyControl, KeyShift]},
|
||||
entry! {action=DialogMessage::CloseAllDocumentsWithConfirmation, key_down=KeyW, modifiers=[KeyControl, KeyAlt]},
|
||||
entry! {action=PortfolioMessage::CloseActiveDocumentWithConfirmation, key_down=KeyW, modifiers=[KeyControl]},
|
||||
entry! {action=PortfolioMessage::Copy { clipboard: Clipboard::Device }, key_down=KeyC, modifiers=[KeyControl]},
|
||||
entry! {action=PortfolioMessage::Cut { clipboard: Clipboard::Device }, key_down=KeyX, modifiers=[KeyControl]},
|
||||
// Nudging
|
||||
entry! {action=DocumentMessage::NudgeSelectedLayers { delta_x: -SHIFT_NUDGE_AMOUNT, delta_y: -SHIFT_NUDGE_AMOUNT }, key_down=KeyArrowUp, modifiers=[KeyShift, KeyArrowLeft]},
|
||||
entry! {action=DocumentMessage::NudgeSelectedLayers { delta_x: SHIFT_NUDGE_AMOUNT, delta_y: -SHIFT_NUDGE_AMOUNT }, key_down=KeyArrowUp, modifiers=[KeyShift, KeyArrowRight]},
|
||||
entry! {action=DocumentMessage::NudgeSelectedLayers { delta_x: 0., delta_y: -SHIFT_NUDGE_AMOUNT }, key_down=KeyArrowUp, modifiers=[KeyShift]},
|
||||
entry! {action=DocumentMessage::NudgeSelectedLayers { delta_x: -SHIFT_NUDGE_AMOUNT, delta_y: SHIFT_NUDGE_AMOUNT }, key_down=KeyArrowDown, modifiers=[KeyShift, KeyArrowLeft]},
|
||||
entry! {action=DocumentMessage::NudgeSelectedLayers { delta_x: SHIFT_NUDGE_AMOUNT, delta_y: SHIFT_NUDGE_AMOUNT }, key_down=KeyArrowDown, modifiers=[KeyShift, KeyArrowRight]},
|
||||
entry! {action=DocumentMessage::NudgeSelectedLayers { delta_x: 0., delta_y: SHIFT_NUDGE_AMOUNT }, key_down=KeyArrowDown, modifiers=[KeyShift]},
|
||||
entry! {action=DocumentMessage::NudgeSelectedLayers { delta_x: -SHIFT_NUDGE_AMOUNT, delta_y: -SHIFT_NUDGE_AMOUNT }, key_down=KeyArrowLeft, modifiers=[KeyShift, KeyArrowUp]},
|
||||
entry! {action=DocumentMessage::NudgeSelectedLayers { delta_x: -SHIFT_NUDGE_AMOUNT, delta_y: SHIFT_NUDGE_AMOUNT }, key_down=KeyArrowLeft, modifiers=[KeyShift, KeyArrowDown]},
|
||||
entry! {action=DocumentMessage::NudgeSelectedLayers { delta_x: -SHIFT_NUDGE_AMOUNT, delta_y: 0. }, key_down=KeyArrowLeft, modifiers=[KeyShift]},
|
||||
entry! {action=DocumentMessage::NudgeSelectedLayers { delta_x: SHIFT_NUDGE_AMOUNT, delta_y: -SHIFT_NUDGE_AMOUNT }, key_down=KeyArrowRight, modifiers=[KeyShift, KeyArrowUp]},
|
||||
entry! {action=DocumentMessage::NudgeSelectedLayers { delta_x: SHIFT_NUDGE_AMOUNT, delta_y: SHIFT_NUDGE_AMOUNT }, key_down=KeyArrowRight, modifiers=[KeyShift, KeyArrowDown]},
|
||||
entry! {action=DocumentMessage::NudgeSelectedLayers { delta_x: SHIFT_NUDGE_AMOUNT, delta_y: 0. }, key_down=KeyArrowRight, modifiers=[KeyShift]},
|
||||
entry! {action=DocumentMessage::NudgeSelectedLayers { delta_x: -NUDGE_AMOUNT, delta_y: -NUDGE_AMOUNT }, key_down=KeyArrowUp, modifiers=[KeyArrowLeft]},
|
||||
entry! {action=DocumentMessage::NudgeSelectedLayers { delta_x: NUDGE_AMOUNT, delta_y: -NUDGE_AMOUNT }, key_down=KeyArrowUp, modifiers=[KeyArrowRight]},
|
||||
entry! {action=DocumentMessage::NudgeSelectedLayers { delta_x: 0., delta_y: -NUDGE_AMOUNT }, key_down=KeyArrowUp},
|
||||
entry! {action=DocumentMessage::NudgeSelectedLayers { delta_x: -NUDGE_AMOUNT, delta_y: NUDGE_AMOUNT }, key_down=KeyArrowDown, modifiers=[KeyArrowLeft]},
|
||||
entry! {action=DocumentMessage::NudgeSelectedLayers { delta_x: NUDGE_AMOUNT, delta_y: NUDGE_AMOUNT }, key_down=KeyArrowDown, modifiers=[KeyArrowRight]},
|
||||
entry! {action=DocumentMessage::NudgeSelectedLayers { delta_x: 0., delta_y: NUDGE_AMOUNT }, key_down=KeyArrowDown},
|
||||
entry! {action=DocumentMessage::NudgeSelectedLayers { delta_x: -NUDGE_AMOUNT, delta_y: -NUDGE_AMOUNT }, key_down=KeyArrowLeft, modifiers=[KeyArrowUp]},
|
||||
entry! {action=DocumentMessage::NudgeSelectedLayers { delta_x: -NUDGE_AMOUNT, delta_y: NUDGE_AMOUNT }, key_down=KeyArrowLeft, modifiers=[KeyArrowDown]},
|
||||
entry! {action=DocumentMessage::NudgeSelectedLayers { delta_x: -NUDGE_AMOUNT, delta_y: 0. }, key_down=KeyArrowLeft},
|
||||
entry! {action=DocumentMessage::NudgeSelectedLayers { delta_x: NUDGE_AMOUNT, delta_y: -NUDGE_AMOUNT }, key_down=KeyArrowRight, modifiers=[KeyArrowUp]},
|
||||
entry! {action=DocumentMessage::NudgeSelectedLayers { delta_x: NUDGE_AMOUNT, delta_y: NUDGE_AMOUNT }, key_down=KeyArrowRight, modifiers=[KeyArrowDown]},
|
||||
entry! {action=DocumentMessage::NudgeSelectedLayers { delta_x: NUDGE_AMOUNT, delta_y: 0. }, key_down=KeyArrowRight},
|
||||
// Reorder Layers
|
||||
entry! {action=DocumentMessage::ReorderSelectedLayers { relative_index_offset: isize::MAX }, key_down=KeyRightCurlyBracket, modifiers=[KeyControl]}, // TODO: Use KeyRightBracket with Ctrl+Shift modifiers once input system is fixed
|
||||
entry! {action=DocumentMessage::ReorderSelectedLayers { relative_index_offset: 1 }, key_down=KeyRightBracket, modifiers=[KeyControl]},
|
||||
entry! {action=DocumentMessage::ReorderSelectedLayers { relative_index_offset: -1 }, key_down=KeyLeftBracket, modifiers=[KeyControl]},
|
||||
entry! {action=DocumentMessage::ReorderSelectedLayers { relative_index_offset: isize::MIN }, key_down=KeyLeftCurlyBracket, modifiers=[KeyControl]}, // TODO: Use KeyLeftBracket with Ctrl+Shift modifiers once input system is fixed
|
||||
// Debug Actions
|
||||
entry! {action=DebugMessage::ToggleTraceLogs, key_down=KeyT, modifiers=[KeyAlt]},
|
||||
entry! {action=DebugMessage::MessageOff, key_down=Key0, modifiers=[KeyAlt]},
|
||||
entry! {action=DebugMessage::MessageNames, key_down=Key1, modifiers=[KeyAlt]},
|
||||
entry! {action=DebugMessage::MessageContents, key_down=Key2, modifiers=[KeyAlt]},
|
||||
// HIGHER PRIORITY:
|
||||
//
|
||||
// MovementMessage
|
||||
entry!(
|
||||
PointerMove;
|
||||
refresh_keys=[KeyControl],
|
||||
action_dispatch=MovementMessage::PointerMove { snap_angle: KeyControl, wait_for_snap_angle_release: true, snap_zoom: KeyControl, zoom_from_viewport: None },
|
||||
),
|
||||
// NORMAL PRIORITY:
|
||||
//
|
||||
// TransformLayerMessage
|
||||
entry!(KeyDown(KeyEnter); action_dispatch=TransformLayerMessage::ApplyTransformOperation),
|
||||
entry!(KeyDown(Lmb); action_dispatch=TransformLayerMessage::ApplyTransformOperation),
|
||||
entry!(KeyDown(KeyEscape); action_dispatch=TransformLayerMessage::CancelTransformOperation),
|
||||
entry!(KeyDown(Rmb); action_dispatch=TransformLayerMessage::CancelTransformOperation),
|
||||
entry!(KeyDown(KeyX); action_dispatch=TransformLayerMessage::ConstrainX),
|
||||
entry!(KeyDown(KeyY); action_dispatch=TransformLayerMessage::ConstrainY),
|
||||
entry!(KeyDown(KeyBackspace); action_dispatch=TransformLayerMessage::TypeBackspace),
|
||||
entry!(KeyDown(KeyMinus); action_dispatch=TransformLayerMessage::TypeNegate),
|
||||
entry!(KeyDown(KeyComma); action_dispatch=TransformLayerMessage::TypeDecimalPoint),
|
||||
entry!(KeyDown(KeyPeriod); action_dispatch=TransformLayerMessage::TypeDecimalPoint),
|
||||
entry!(PointerMove; refresh_keys=[KeyShift, KeyControl], action_dispatch=TransformLayerMessage::PointerMove { slow_key: KeyShift, snap_key: KeyControl }),
|
||||
// SelectToolMessage
|
||||
entry!(PointerMove; refresh_keys=[KeyControl, KeyShift, KeyAlt], action_dispatch=SelectToolMessage::PointerMove { axis_align: KeyShift, snap_angle: KeyControl, center: KeyAlt }),
|
||||
entry!(KeyDown(Lmb); action_dispatch=SelectToolMessage::DragStart { add_to_selection: KeyShift }),
|
||||
entry!(KeyUp(Lmb); action_dispatch=SelectToolMessage::DragStop),
|
||||
entry!(KeyDown(KeyEnter); action_dispatch=SelectToolMessage::DragStop),
|
||||
entry!(DoubleClick; action_dispatch=SelectToolMessage::EditLayer),
|
||||
entry!(KeyDown(Rmb); action_dispatch=SelectToolMessage::Abort),
|
||||
entry!(KeyDown(KeyEscape); action_dispatch=SelectToolMessage::Abort),
|
||||
// ArtboardToolMessage
|
||||
entry!(KeyDown(Lmb); action_dispatch=ArtboardToolMessage::PointerDown),
|
||||
entry!(PointerMove; refresh_keys=[KeyShift, KeyAlt], action_dispatch=ArtboardToolMessage::PointerMove { constrain_axis_or_aspect: KeyShift, center: KeyAlt }),
|
||||
entry!(KeyUp(Lmb); action_dispatch=ArtboardToolMessage::PointerUp),
|
||||
entry!(KeyDown(KeyDelete); action_dispatch=ArtboardToolMessage::DeleteSelected),
|
||||
entry!(KeyDown(KeyBackspace); action_dispatch=ArtboardToolMessage::DeleteSelected),
|
||||
// NavigateToolMessage
|
||||
entry!(KeyUp(Lmb); modifiers=[KeyShift], action_dispatch=NavigateToolMessage::ClickZoom { zoom_in: false }),
|
||||
entry!(KeyUp(Lmb); action_dispatch=NavigateToolMessage::ClickZoom { zoom_in: true }),
|
||||
entry!(PointerMove; refresh_keys=[KeyControl], action_dispatch=NavigateToolMessage::PointerMove { snap_angle: KeyControl, snap_zoom: KeyControl }),
|
||||
entry!(KeyDown(Mmb); action_dispatch=NavigateToolMessage::TranslateCanvasBegin),
|
||||
entry!(KeyDown(Rmb); action_dispatch=NavigateToolMessage::RotateCanvasBegin),
|
||||
entry!(KeyDown(Lmb); action_dispatch=NavigateToolMessage::ZoomCanvasBegin),
|
||||
entry!(KeyUp(Rmb); action_dispatch=NavigateToolMessage::TransformCanvasEnd),
|
||||
entry!(KeyUp(Lmb); action_dispatch=NavigateToolMessage::TransformCanvasEnd),
|
||||
entry!(KeyUp(Mmb); action_dispatch=NavigateToolMessage::TransformCanvasEnd),
|
||||
// EyedropperToolMessage
|
||||
entry!(KeyDown(Lmb); action_dispatch=EyedropperToolMessage::LeftMouseDown),
|
||||
entry!(KeyDown(Rmb); action_dispatch=EyedropperToolMessage::RightMouseDown),
|
||||
// TextToolMessage
|
||||
entry!(KeyUp(Lmb); action_dispatch=TextToolMessage::Interact),
|
||||
entry!(KeyDown(KeyEscape); action_dispatch=TextToolMessage::Abort),
|
||||
entry_multiplatform!(
|
||||
standard!(KeyDown(KeyEnter); modifiers=[KeyControl], action_dispatch=TextToolMessage::CommitText),
|
||||
mac_only!(KeyDown(KeyEnter); modifiers=[KeyCommand], action_dispatch=TextToolMessage::CommitText),
|
||||
),
|
||||
// GradientToolMessage
|
||||
entry!(KeyDown(Lmb); action_dispatch=GradientToolMessage::PointerDown),
|
||||
entry!(PointerMove; refresh_keys=[KeyShift], action_dispatch=GradientToolMessage::PointerMove { constrain_axis: KeyShift }),
|
||||
entry!(KeyUp(Lmb); action_dispatch=GradientToolMessage::PointerUp),
|
||||
// RectangleToolMessage
|
||||
entry!(KeyDown(Lmb); action_dispatch=RectangleToolMessage::DragStart),
|
||||
entry!(KeyUp(Lmb); action_dispatch=RectangleToolMessage::DragStop),
|
||||
entry!(KeyDown(Rmb); action_dispatch=RectangleToolMessage::Abort),
|
||||
entry!(KeyDown(KeyEscape); action_dispatch=RectangleToolMessage::Abort),
|
||||
entry!(PointerMove; refresh_keys=[KeyAlt, KeyShift], action_dispatch=RectangleToolMessage::Resize { center: KeyAlt, lock_ratio: KeyShift }),
|
||||
// EllipseToolMessage
|
||||
entry!(KeyDown(Lmb); action_dispatch=EllipseToolMessage::DragStart),
|
||||
entry!(KeyUp(Lmb); action_dispatch=EllipseToolMessage::DragStop),
|
||||
entry!(KeyDown(Rmb); action_dispatch=EllipseToolMessage::Abort),
|
||||
entry!(KeyDown(KeyEscape); action_dispatch=EllipseToolMessage::Abort),
|
||||
entry!(PointerMove; refresh_keys=[KeyAlt, KeyShift], action_dispatch=EllipseToolMessage::Resize { center: KeyAlt, lock_ratio: KeyShift }),
|
||||
// ShapeToolMessage
|
||||
entry!(KeyDown(Lmb); action_dispatch=ShapeToolMessage::DragStart),
|
||||
entry!(KeyUp(Lmb); action_dispatch=ShapeToolMessage::DragStop),
|
||||
entry!(KeyDown(Rmb); action_dispatch=ShapeToolMessage::Abort),
|
||||
entry!(KeyDown(KeyEscape); action_dispatch=ShapeToolMessage::Abort),
|
||||
entry!(PointerMove; refresh_keys=[KeyAlt, KeyShift], action_dispatch=ShapeToolMessage::Resize { center: KeyAlt, lock_ratio: KeyShift }),
|
||||
// LineToolMessage
|
||||
entry!(KeyDown(Lmb); action_dispatch=LineToolMessage::DragStart),
|
||||
entry!(KeyUp(Lmb); action_dispatch=LineToolMessage::DragStop),
|
||||
entry!(KeyDown(Rmb); action_dispatch=LineToolMessage::Abort),
|
||||
entry!(KeyDown(KeyEscape); action_dispatch=LineToolMessage::Abort),
|
||||
entry!(PointerMove; refresh_keys=[KeyAlt, KeyShift, KeyControl], action_dispatch=LineToolMessage::Redraw { center: KeyAlt, lock_angle: KeyControl, snap_angle: KeyShift }),
|
||||
// PathToolMessage
|
||||
entry!(KeyDown(Lmb); action_dispatch=PathToolMessage::DragStart { add_to_selection: KeyShift }),
|
||||
entry!(PointerMove; refresh_keys=[KeyAlt, KeyShift], action_dispatch=PathToolMessage::PointerMove { alt_mirror_angle: KeyAlt, shift_mirror_distance: KeyShift }),
|
||||
entry!(KeyDown(KeyDelete); action_dispatch=PathToolMessage::Delete),
|
||||
entry!(KeyDown(KeyBackspace); action_dispatch=PathToolMessage::Delete),
|
||||
entry!(KeyUp(Lmb); action_dispatch=PathToolMessage::DragStop),
|
||||
// PenToolMessage
|
||||
entry!(PointerMove; refresh_keys=[KeyShift, KeyControl], action_dispatch=PenToolMessage::PointerMove { snap_angle: KeyControl, break_handle: KeyShift }),
|
||||
entry!(KeyDown(Lmb); action_dispatch=PenToolMessage::DragStart),
|
||||
entry!(KeyUp(Lmb); action_dispatch=PenToolMessage::DragStop),
|
||||
entry!(KeyDown(Rmb); action_dispatch=PenToolMessage::Confirm),
|
||||
entry!(KeyDown(KeyEscape); action_dispatch=PenToolMessage::Confirm),
|
||||
entry!(KeyDown(KeyEnter); action_dispatch=PenToolMessage::Confirm),
|
||||
// FreehandToolMessage
|
||||
entry!(PointerMove; action_dispatch=FreehandToolMessage::PointerMove),
|
||||
entry!(KeyDown(Lmb); action_dispatch=FreehandToolMessage::DragStart),
|
||||
entry!(KeyUp(Lmb); action_dispatch=FreehandToolMessage::DragStop),
|
||||
// SplineToolMessage
|
||||
entry!(PointerMove; action_dispatch=SplineToolMessage::PointerMove),
|
||||
entry!(KeyDown(Lmb); action_dispatch=SplineToolMessage::DragStart),
|
||||
entry!(KeyUp(Lmb); action_dispatch=SplineToolMessage::DragStop),
|
||||
entry!(KeyDown(Rmb); action_dispatch=SplineToolMessage::Confirm),
|
||||
entry!(KeyDown(KeyEscape); action_dispatch=SplineToolMessage::Confirm),
|
||||
entry!(KeyDown(KeyEnter); action_dispatch=SplineToolMessage::Confirm),
|
||||
// FillToolMessage
|
||||
entry!(KeyDown(Lmb); action_dispatch=FillToolMessage::LeftMouseDown),
|
||||
entry!(KeyDown(Rmb); action_dispatch=FillToolMessage::RightMouseDown),
|
||||
// ToolMessage
|
||||
entry!(KeyDown(KeyV); action_dispatch=ToolMessage::ActivateToolSelect),
|
||||
entry!(KeyDown(KeyZ); action_dispatch=ToolMessage::ActivateToolNavigate),
|
||||
entry!(KeyDown(KeyI); action_dispatch=ToolMessage::ActivateToolEyedropper),
|
||||
entry!(KeyDown(KeyT); action_dispatch=ToolMessage::ActivateToolText),
|
||||
entry!(KeyDown(KeyF); action_dispatch=ToolMessage::ActivateToolFill),
|
||||
entry!(KeyDown(KeyH); action_dispatch=ToolMessage::ActivateToolGradient),
|
||||
entry!(KeyDown(KeyA); action_dispatch=ToolMessage::ActivateToolPath),
|
||||
entry!(KeyDown(KeyP); action_dispatch=ToolMessage::ActivateToolPen),
|
||||
entry!(KeyDown(KeyN); action_dispatch=ToolMessage::ActivateToolFreehand),
|
||||
entry!(KeyDown(KeyL); action_dispatch=ToolMessage::ActivateToolLine),
|
||||
entry!(KeyDown(KeyM); action_dispatch=ToolMessage::ActivateToolRectangle),
|
||||
entry!(KeyDown(KeyE); action_dispatch=ToolMessage::ActivateToolEllipse),
|
||||
entry!(KeyDown(KeyY); action_dispatch=ToolMessage::ActivateToolShape),
|
||||
entry_multiplatform!(
|
||||
standard!(KeyDown(KeyX); modifiers=[KeyShift, KeyControl], action_dispatch=ToolMessage::ResetColors),
|
||||
mac_only!(KeyDown(KeyX); modifiers=[KeyShift, KeyCommand], action_dispatch=ToolMessage::ResetColors),
|
||||
),
|
||||
entry!(KeyDown(KeyX); modifiers=[KeyShift], action_dispatch=ToolMessage::SwapColors),
|
||||
entry!(KeyDown(KeyC); modifiers=[KeyAlt], action_dispatch=ToolMessage::SelectRandomPrimaryColor),
|
||||
// DocumentMessage
|
||||
entry!(KeyDown(KeyDelete); action_dispatch=DocumentMessage::DeleteSelectedLayers),
|
||||
entry!(KeyDown(KeyBackspace); action_dispatch=DocumentMessage::DeleteSelectedLayers),
|
||||
entry!(KeyDown(KeyP); modifiers=[KeyAlt], action_dispatch=DocumentMessage::DebugPrintDocument),
|
||||
entry_multiplatform!(
|
||||
standard!(KeyDown(KeyZ); modifiers=[KeyControl, KeyShift], action_dispatch=DocumentMessage::Redo),
|
||||
mac_only!(KeyDown(KeyZ); modifiers=[KeyCommand, KeyShift], action_dispatch=DocumentMessage::Redo),
|
||||
),
|
||||
entry_multiplatform!(
|
||||
standard!(KeyDown(KeyZ); modifiers=[KeyControl], action_dispatch=DocumentMessage::Undo),
|
||||
mac_only!(KeyDown(KeyZ); modifiers=[KeyCommand], action_dispatch=DocumentMessage::Undo),
|
||||
),
|
||||
entry_multiplatform!(
|
||||
standard!(KeyDown(KeyA); modifiers=[KeyControl, KeyAlt], action_dispatch=DocumentMessage::DeselectAllLayers),
|
||||
mac_only!(KeyDown(KeyA); modifiers=[KeyCommand, KeyAlt], action_dispatch=DocumentMessage::DeselectAllLayers),
|
||||
),
|
||||
entry_multiplatform!(
|
||||
standard!(KeyDown(KeyA); modifiers=[KeyControl], action_dispatch=DocumentMessage::SelectAllLayers),
|
||||
mac_only!(KeyDown(KeyA); modifiers=[KeyCommand], action_dispatch=DocumentMessage::SelectAllLayers),
|
||||
),
|
||||
entry_multiplatform!(
|
||||
standard!(KeyDown(KeyS); modifiers=[KeyControl], action_dispatch=DocumentMessage::SaveDocument),
|
||||
mac_only!(KeyDown(KeyS); modifiers=[KeyCommand], action_dispatch=DocumentMessage::SaveDocument),
|
||||
),
|
||||
entry_multiplatform!(
|
||||
standard!(KeyDown(Key0); modifiers=[KeyControl], action_dispatch=DocumentMessage::ZoomCanvasToFitAll),
|
||||
mac_only!(KeyDown(Key0); modifiers=[KeyCommand], action_dispatch=DocumentMessage::ZoomCanvasToFitAll),
|
||||
),
|
||||
entry_multiplatform!(
|
||||
standard!(KeyDown(KeyD); modifiers=[KeyControl], action_dispatch=DocumentMessage::DuplicateSelectedLayers),
|
||||
mac_only!(KeyDown(KeyD); modifiers=[KeyCommand], action_dispatch=DocumentMessage::DuplicateSelectedLayers),
|
||||
),
|
||||
entry_multiplatform!(
|
||||
standard!(KeyDown(KeyG); modifiers=[KeyControl], action_dispatch=DocumentMessage::GroupSelectedLayers),
|
||||
mac_only!(KeyDown(KeyG); modifiers=[KeyCommand], action_dispatch=DocumentMessage::GroupSelectedLayers),
|
||||
),
|
||||
entry_multiplatform!(
|
||||
standard!(KeyDown(KeyG); modifiers=[KeyControl, KeyShift], action_dispatch=DocumentMessage::UngroupSelectedLayers),
|
||||
mac_only!(KeyDown(KeyG); modifiers=[KeyCommand, KeyShift], action_dispatch=DocumentMessage::UngroupSelectedLayers),
|
||||
),
|
||||
entry_multiplatform!(
|
||||
standard!(KeyDown(KeyN); modifiers=[KeyControl, KeyShift], action_dispatch=DocumentMessage::CreateEmptyFolder { container_path: vec![] }),
|
||||
mac_only!(KeyDown(KeyN); modifiers=[KeyCommand, KeyShift], action_dispatch=DocumentMessage::CreateEmptyFolder { container_path: vec![] }),
|
||||
),
|
||||
entry_multiplatform!(
|
||||
standard!(KeyDown(KeyLeftBracket); modifiers=[KeyControl, KeyShift], action_dispatch=DocumentMessage::SelectedLayersLowerToBack),
|
||||
mac_only!(KeyDown(KeyLeftBracket); modifiers=[KeyCommand, KeyShift], action_dispatch=DocumentMessage::SelectedLayersLowerToBack),
|
||||
),
|
||||
entry_multiplatform!(
|
||||
// TODO: Delete this in favor of the KeyLeftBracket (non-shifted version of this key) mapping above once the input system can distinguish between the non-shifted and shifted keys (important for other language keyboards)
|
||||
standard!(KeyDown(KeyLeftCurlyBracket); modifiers=[KeyControl, KeyShift], action_dispatch=DocumentMessage::SelectedLayersLowerToBack),
|
||||
mac_only!(KeyDown(KeyLeftCurlyBracket); modifiers=[KeyCommand, KeyShift], action_dispatch=DocumentMessage::SelectedLayersLowerToBack),
|
||||
),
|
||||
entry_multiplatform!(
|
||||
standard!(KeyDown(KeyRightBracket); modifiers=[KeyControl, KeyShift], action_dispatch=DocumentMessage::SelectedLayersRaiseToFront),
|
||||
mac_only!(KeyDown(KeyRightBracket); modifiers=[KeyCommand, KeyShift], action_dispatch=DocumentMessage::SelectedLayersRaiseToFront),
|
||||
),
|
||||
entry_multiplatform!(
|
||||
// TODO: Delete this in favor of the KeyRightBracket (non-shifted version of this key) mapping above once the input system can distinguish between the non-shifted and shifted keys (important for other language keyboards)
|
||||
standard!(KeyDown(KeyRightCurlyBracket); modifiers=[KeyControl, KeyShift], action_dispatch=DocumentMessage::SelectedLayersRaiseToFront),
|
||||
mac_only!(KeyDown(KeyRightCurlyBracket); modifiers=[KeyCommand, KeyShift], action_dispatch=DocumentMessage::SelectedLayersRaiseToFront),
|
||||
),
|
||||
entry_multiplatform!(
|
||||
standard!(KeyDown(KeyLeftBracket); modifiers=[KeyControl], action_dispatch=DocumentMessage::SelectedLayersLower),
|
||||
mac_only!(KeyDown(KeyLeftBracket); modifiers=[KeyCommand], action_dispatch=DocumentMessage::SelectedLayersLower),
|
||||
),
|
||||
entry_multiplatform!(
|
||||
standard!(KeyDown(KeyRightBracket); modifiers=[KeyControl], action_dispatch=DocumentMessage::SelectedLayersRaise),
|
||||
mac_only!(KeyDown(KeyRightBracket); modifiers=[KeyCommand], action_dispatch=DocumentMessage::SelectedLayersRaise),
|
||||
),
|
||||
entry!(KeyDown(KeyArrowUp); modifiers=[KeyShift, KeyArrowLeft], action_dispatch=DocumentMessage::NudgeSelectedLayers { delta_x: -BIG_NUDGE_AMOUNT, delta_y: -BIG_NUDGE_AMOUNT }),
|
||||
entry!(KeyDown(KeyArrowUp); modifiers=[KeyShift, KeyArrowRight], action_dispatch=DocumentMessage::NudgeSelectedLayers { delta_x: BIG_NUDGE_AMOUNT, delta_y: -BIG_NUDGE_AMOUNT }),
|
||||
entry!(KeyDown(KeyArrowUp); modifiers=[KeyShift], action_dispatch=DocumentMessage::NudgeSelectedLayers { delta_x: 0., delta_y: -BIG_NUDGE_AMOUNT }),
|
||||
entry!(KeyDown(KeyArrowDown); modifiers=[KeyShift, KeyArrowLeft], action_dispatch=DocumentMessage::NudgeSelectedLayers { delta_x: -BIG_NUDGE_AMOUNT, delta_y: BIG_NUDGE_AMOUNT }),
|
||||
entry!(KeyDown(KeyArrowDown); modifiers=[KeyShift, KeyArrowRight], action_dispatch=DocumentMessage::NudgeSelectedLayers { delta_x: BIG_NUDGE_AMOUNT, delta_y: BIG_NUDGE_AMOUNT }),
|
||||
entry!(KeyDown(KeyArrowDown); modifiers=[KeyShift], action_dispatch=DocumentMessage::NudgeSelectedLayers { delta_x: 0., delta_y: BIG_NUDGE_AMOUNT }),
|
||||
entry!(KeyDown(KeyArrowLeft); modifiers=[KeyShift, KeyArrowUp], action_dispatch=DocumentMessage::NudgeSelectedLayers { delta_x: -BIG_NUDGE_AMOUNT, delta_y: -BIG_NUDGE_AMOUNT }),
|
||||
entry!(KeyDown(KeyArrowLeft); modifiers=[KeyShift, KeyArrowDown], action_dispatch=DocumentMessage::NudgeSelectedLayers { delta_x: -BIG_NUDGE_AMOUNT, delta_y: BIG_NUDGE_AMOUNT }),
|
||||
entry!(KeyDown(KeyArrowLeft); modifiers=[KeyShift], action_dispatch=DocumentMessage::NudgeSelectedLayers { delta_x: -BIG_NUDGE_AMOUNT, delta_y: 0. }),
|
||||
entry!(KeyDown(KeyArrowRight); modifiers=[KeyShift, KeyArrowUp], action_dispatch=DocumentMessage::NudgeSelectedLayers { delta_x: BIG_NUDGE_AMOUNT, delta_y: -BIG_NUDGE_AMOUNT }),
|
||||
entry!(KeyDown(KeyArrowRight); modifiers=[KeyShift, KeyArrowDown], action_dispatch=DocumentMessage::NudgeSelectedLayers { delta_x: BIG_NUDGE_AMOUNT, delta_y: BIG_NUDGE_AMOUNT }),
|
||||
entry!(KeyDown(KeyArrowRight); modifiers=[KeyShift], action_dispatch=DocumentMessage::NudgeSelectedLayers { delta_x: BIG_NUDGE_AMOUNT, delta_y: 0. }),
|
||||
entry!(KeyDown(KeyArrowUp); modifiers=[KeyArrowLeft], action_dispatch=DocumentMessage::NudgeSelectedLayers { delta_x: -NUDGE_AMOUNT, delta_y: -NUDGE_AMOUNT }),
|
||||
entry!(KeyDown(KeyArrowUp); modifiers=[KeyArrowRight], action_dispatch=DocumentMessage::NudgeSelectedLayers { delta_x: NUDGE_AMOUNT, delta_y: -NUDGE_AMOUNT }),
|
||||
entry!(KeyDown(KeyArrowUp); action_dispatch=DocumentMessage::NudgeSelectedLayers { delta_x: 0., delta_y: -NUDGE_AMOUNT }),
|
||||
entry!(KeyDown(KeyArrowDown); modifiers=[KeyArrowLeft], action_dispatch=DocumentMessage::NudgeSelectedLayers { delta_x: -NUDGE_AMOUNT, delta_y: NUDGE_AMOUNT }),
|
||||
entry!(KeyDown(KeyArrowDown); modifiers=[KeyArrowRight], action_dispatch=DocumentMessage::NudgeSelectedLayers { delta_x: NUDGE_AMOUNT, delta_y: NUDGE_AMOUNT }),
|
||||
entry!(KeyDown(KeyArrowDown); action_dispatch=DocumentMessage::NudgeSelectedLayers { delta_x: 0., delta_y: NUDGE_AMOUNT }),
|
||||
entry!(KeyDown(KeyArrowLeft); modifiers=[KeyArrowUp], action_dispatch=DocumentMessage::NudgeSelectedLayers { delta_x: -NUDGE_AMOUNT, delta_y: -NUDGE_AMOUNT }),
|
||||
entry!(KeyDown(KeyArrowLeft); modifiers=[KeyArrowDown], action_dispatch=DocumentMessage::NudgeSelectedLayers { delta_x: -NUDGE_AMOUNT, delta_y: NUDGE_AMOUNT }),
|
||||
entry!(KeyDown(KeyArrowLeft); action_dispatch=DocumentMessage::NudgeSelectedLayers { delta_x: -NUDGE_AMOUNT, delta_y: 0. }),
|
||||
entry!(KeyDown(KeyArrowRight); modifiers=[KeyArrowUp], action_dispatch=DocumentMessage::NudgeSelectedLayers { delta_x: NUDGE_AMOUNT, delta_y: -NUDGE_AMOUNT }),
|
||||
entry!(KeyDown(KeyArrowRight); modifiers=[KeyArrowDown], action_dispatch=DocumentMessage::NudgeSelectedLayers { delta_x: NUDGE_AMOUNT, delta_y: NUDGE_AMOUNT }),
|
||||
entry!(KeyDown(KeyArrowRight); action_dispatch=DocumentMessage::NudgeSelectedLayers { delta_x: NUDGE_AMOUNT, delta_y: 0. }),
|
||||
// TransformLayerMessage
|
||||
entry!(KeyDown(KeyG); action_dispatch=TransformLayerMessage::BeginGrab),
|
||||
entry!(KeyDown(KeyR); action_dispatch=TransformLayerMessage::BeginRotate),
|
||||
entry!(KeyDown(KeyS); action_dispatch=TransformLayerMessage::BeginScale),
|
||||
// MovementMessage
|
||||
entry!(KeyDown(Mmb); modifiers=[KeyControl], action_dispatch=MovementMessage::RotateCanvasBegin),
|
||||
entry!(KeyDown(Mmb); modifiers=[KeyShift], action_dispatch=MovementMessage::ZoomCanvasBegin),
|
||||
entry!(KeyDown(Mmb); action_dispatch=MovementMessage::TranslateCanvasBegin),
|
||||
entry!(KeyUp(Mmb); action_dispatch=MovementMessage::TransformCanvasEnd),
|
||||
entry!(KeyDown(Lmb); modifiers=[KeySpace], action_dispatch=MovementMessage::TranslateCanvasBegin),
|
||||
entry!(KeyUp(Lmb); modifiers=[KeySpace], action_dispatch=MovementMessage::TransformCanvasEnd),
|
||||
entry_multiplatform!(
|
||||
standard!(KeyDown(KeyPlus); modifiers=[KeyControl], action_dispatch=MovementMessage::IncreaseCanvasZoom { center_on_mouse: false }),
|
||||
mac_only!(KeyDown(KeyPlus); modifiers=[KeyCommand], action_dispatch=MovementMessage::IncreaseCanvasZoom { center_on_mouse: false }),
|
||||
),
|
||||
entry_multiplatform!(
|
||||
standard!(KeyDown(KeyEquals); modifiers=[KeyControl], action_dispatch=MovementMessage::IncreaseCanvasZoom { center_on_mouse: false }),
|
||||
mac_only!(KeyDown(KeyEquals); modifiers=[KeyCommand], action_dispatch=MovementMessage::IncreaseCanvasZoom { center_on_mouse: false }),
|
||||
),
|
||||
entry_multiplatform!(
|
||||
standard!(KeyDown(KeyMinus); modifiers=[KeyControl], action_dispatch=MovementMessage::DecreaseCanvasZoom { center_on_mouse: false }),
|
||||
mac_only!(KeyDown(KeyMinus); modifiers=[KeyCommand], action_dispatch=MovementMessage::DecreaseCanvasZoom { center_on_mouse: false }),
|
||||
),
|
||||
entry_multiplatform!(
|
||||
standard!(KeyDown(Key1); modifiers=[KeyControl], action_dispatch=MovementMessage::SetCanvasZoom { zoom_factor: 1. }),
|
||||
mac_only!(KeyDown(Key1); modifiers=[KeyCommand], action_dispatch=MovementMessage::SetCanvasZoom { zoom_factor: 1. }),
|
||||
),
|
||||
entry_multiplatform!(
|
||||
standard!(KeyDown(Key2); modifiers=[KeyControl], action_dispatch=MovementMessage::SetCanvasZoom { zoom_factor: 2. }),
|
||||
mac_only!(KeyDown(Key2); modifiers=[KeyCommand], action_dispatch=MovementMessage::SetCanvasZoom { zoom_factor: 2. }),
|
||||
),
|
||||
entry!(WheelScroll; modifiers=[KeyControl], action_dispatch=MovementMessage::WheelCanvasZoom),
|
||||
entry!(WheelScroll; modifiers=[KeyShift], action_dispatch=MovementMessage::WheelCanvasTranslate { use_y_as_x: true }),
|
||||
entry!(WheelScroll; action_dispatch=MovementMessage::WheelCanvasTranslate { use_y_as_x: false }),
|
||||
entry!(KeyDown(KeyPageUp); modifiers=[KeyShift], action_dispatch=MovementMessage::TranslateCanvasByViewportFraction { delta: DVec2::new(1., 0.) }),
|
||||
entry!(KeyDown(KeyPageDown); modifiers=[KeyShift], action_dispatch=MovementMessage::TranslateCanvasByViewportFraction { delta: DVec2::new(-1., 0.) }),
|
||||
entry!(KeyDown(KeyPageUp); action_dispatch=MovementMessage::TranslateCanvasByViewportFraction { delta: DVec2::new(0., 1.) }),
|
||||
entry!(KeyDown(KeyPageDown); action_dispatch=MovementMessage::TranslateCanvasByViewportFraction { delta: DVec2::new(0., -1.) }),
|
||||
// PortfolioMessage
|
||||
entry_multiplatform!(
|
||||
standard!(KeyDown(KeyO); modifiers=[KeyControl], action_dispatch=PortfolioMessage::OpenDocument),
|
||||
mac_only!(KeyDown(KeyO); modifiers=[KeyCommand], action_dispatch=PortfolioMessage::OpenDocument),
|
||||
),
|
||||
entry_multiplatform!(
|
||||
standard!(KeyDown(KeyI); modifiers=[KeyControl], action_dispatch=PortfolioMessage::Import),
|
||||
mac_only!(KeyDown(KeyI); modifiers=[KeyCommand], action_dispatch=PortfolioMessage::Import),
|
||||
),
|
||||
entry!(KeyDown(KeyTab); modifiers=[KeyControl], action_dispatch=PortfolioMessage::NextDocument),
|
||||
entry!(KeyDown(KeyTab); modifiers=[KeyControl, KeyShift], action_dispatch=PortfolioMessage::PrevDocument),
|
||||
entry_multiplatform!(
|
||||
standard!(KeyDown(KeyW); modifiers=[KeyControl], action_dispatch=PortfolioMessage::CloseActiveDocumentWithConfirmation),
|
||||
mac_only!(KeyDown(KeyW); modifiers=[KeyCommand], action_dispatch=PortfolioMessage::CloseActiveDocumentWithConfirmation),
|
||||
),
|
||||
entry_multiplatform!(
|
||||
standard!(KeyDown(KeyX); modifiers=[KeyControl], action_dispatch=PortfolioMessage::Cut { clipboard: Clipboard::Device }),
|
||||
mac_only!(KeyDown(KeyX); modifiers=[KeyCommand], action_dispatch=PortfolioMessage::Cut { clipboard: Clipboard::Device }),
|
||||
),
|
||||
entry_multiplatform!(
|
||||
standard!(KeyDown(KeyC); modifiers=[KeyControl], action_dispatch=PortfolioMessage::Copy { clipboard: Clipboard::Device }),
|
||||
mac_only!(KeyDown(KeyC); modifiers=[KeyCommand], action_dispatch=PortfolioMessage::Copy { clipboard: Clipboard::Device }),
|
||||
),
|
||||
entry_multiplatform!(
|
||||
// This shortcut is intercepted in the frontend; it exists here only as a shortcut mapping source
|
||||
standard!(KeyDown(KeyV); modifiers=[KeyControl], action_dispatch=FrontendMessage::TriggerPaste),
|
||||
mac_only!(KeyDown(KeyV); modifiers=[KeyCommand], action_dispatch=FrontendMessage::TriggerPaste),
|
||||
),
|
||||
// DialogMessage
|
||||
entry_multiplatform!(
|
||||
standard!(KeyDown(KeyN); modifiers=[KeyControl], action_dispatch=DialogMessage::RequestNewDocumentDialog),
|
||||
mac_only!(KeyDown(KeyN); modifiers=[KeyCommand], action_dispatch=DialogMessage::RequestNewDocumentDialog),
|
||||
),
|
||||
entry_multiplatform!(
|
||||
standard!(KeyDown(KeyW); modifiers=[KeyControl, KeyAlt], action_dispatch=DialogMessage::CloseAllDocumentsWithConfirmation),
|
||||
mac_only!(KeyDown(KeyW); modifiers=[KeyCommand, KeyAlt], action_dispatch=DialogMessage::CloseAllDocumentsWithConfirmation),
|
||||
),
|
||||
entry_multiplatform!(
|
||||
standard!(KeyDown(KeyE); modifiers=[KeyControl], action_dispatch=DialogMessage::RequestExportDialog),
|
||||
mac_only!(KeyDown(KeyE); modifiers=[KeyCommand], action_dispatch=DialogMessage::RequestExportDialog),
|
||||
),
|
||||
// DebugMessage
|
||||
entry!(KeyDown(KeyT); modifiers=[KeyAlt], action_dispatch=DebugMessage::ToggleTraceLogs),
|
||||
entry!(KeyDown(Key0); modifiers=[KeyAlt], action_dispatch=DebugMessage::MessageOff),
|
||||
entry!(KeyDown(Key1); modifiers=[KeyAlt], action_dispatch=DebugMessage::MessageNames),
|
||||
entry!(KeyDown(Key2); modifiers=[KeyAlt], action_dispatch=DebugMessage::MessageContents),
|
||||
];
|
||||
let (mut key_up, mut key_down, mut pointer_move, mut mouse_scroll, mut double_click) = mappings;
|
||||
let (mut key_up, mut key_down, mut double_click, mut wheel_scroll, mut pointer_move) = mappings;
|
||||
|
||||
// TODO: Hardcode these 10 lines into 10 lines of declarations, or make this use a macro to do all 10 in one line
|
||||
const NUMBER_KEYS: [Key; 10] = [Key0, Key1, Key2, Key3, Key4, Key5, Key6, Key7, Key8, Key9];
|
||||
|
@ -239,8 +346,9 @@ impl Default for Mapping {
|
|||
0,
|
||||
MappingEntry {
|
||||
action: TransformLayerMessage::TypeDigit { digit: i as u8 }.into(),
|
||||
trigger: InputMapperMessage::KeyDown(*key),
|
||||
modifiers: modifiers! {},
|
||||
input: InputMapperMessage::KeyDown(*key),
|
||||
platform_layout: None,
|
||||
modifiers: modifiers!(),
|
||||
},
|
||||
);
|
||||
}
|
||||
|
@ -251,48 +359,70 @@ impl Default for Mapping {
|
|||
sort(sublist);
|
||||
}
|
||||
}
|
||||
sort(&mut pointer_move);
|
||||
sort(&mut mouse_scroll);
|
||||
sort(&mut double_click);
|
||||
sort(&mut wheel_scroll);
|
||||
sort(&mut pointer_move);
|
||||
|
||||
Self {
|
||||
key_up,
|
||||
key_down,
|
||||
pointer_move,
|
||||
mouse_scroll,
|
||||
double_click,
|
||||
wheel_scroll,
|
||||
pointer_move,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Mapping {
|
||||
pub fn match_message(&self, message: InputMapperMessage, keys: &KeyStates, actions: ActionList) -> Option<Message> {
|
||||
pub fn match_input_message(&self, message: InputMapperMessage, keyboard_state: &KeyStates, actions: ActionList, keyboard_platform: KeyboardPlatformLayout) -> Option<Message> {
|
||||
let list = match message {
|
||||
InputMapperMessage::KeyDown(key) => &self.key_down[key as usize],
|
||||
InputMapperMessage::KeyUp(key) => &self.key_up[key as usize],
|
||||
InputMapperMessage::DoubleClick => &self.double_click,
|
||||
InputMapperMessage::MouseScroll => &self.mouse_scroll,
|
||||
InputMapperMessage::WheelScroll => &self.wheel_scroll,
|
||||
InputMapperMessage::PointerMove => &self.pointer_move,
|
||||
};
|
||||
list.match_mapping(keys, actions)
|
||||
list.match_mapping(keyboard_state, actions, keyboard_platform)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Clone, Debug)]
|
||||
pub struct MappingEntry {
|
||||
pub trigger: InputMapperMessage,
|
||||
pub modifiers: KeyStates,
|
||||
/// Serves two purposes:
|
||||
/// - This is the message that gets dispatched when the hotkey is matched
|
||||
/// - This message's discriminant is the action; it must be a currently active action to be considered as a shortcut
|
||||
pub action: Message,
|
||||
/// The user input event from an input device which this input mapping matches on
|
||||
pub input: InputMapperMessage,
|
||||
/// Any additional keys that must be also pressed for this input mapping to match
|
||||
pub modifiers: KeyStates,
|
||||
/// The keyboard platform layout which this mapping is exclusive to, or `None` if it's platform-agnostic
|
||||
pub platform_layout: Option<KeyboardPlatformLayout>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct KeyMappingEntries(pub Vec<MappingEntry>);
|
||||
|
||||
impl KeyMappingEntries {
|
||||
fn match_mapping(&self, keys: &KeyStates, actions: ActionList) -> Option<Message> {
|
||||
fn match_mapping(&self, keyboard_state: &KeyStates, actions: ActionList, keyboard_platform: KeyboardPlatformLayout) -> Option<Message> {
|
||||
for entry in self.0.iter() {
|
||||
let all_required_modifiers_pressed = ((*keys & entry.modifiers) ^ entry.modifiers).is_empty();
|
||||
if all_required_modifiers_pressed && actions.iter().flatten().any(|action| entry.action.to_discriminant() == *action) {
|
||||
// Skip this entry if it is platform-specific, and for a layout that does not match the user's keyboard platform layout
|
||||
if let Some(entry_platform_layout) = entry.platform_layout {
|
||||
if entry_platform_layout != keyboard_platform {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// Find which currently pressed keys are also the modifiers in this hotkey entry, then compare those against the required modifiers to see if there are zero missing
|
||||
let pressed_modifiers = *keyboard_state & entry.modifiers;
|
||||
let all_modifiers_without_pressed_modifiers = entry.modifiers ^ pressed_modifiers;
|
||||
let all_required_modifiers_pressed = all_modifiers_without_pressed_modifiers.is_empty();
|
||||
// Skip this entry if any of the required modifiers are missing
|
||||
if !all_required_modifiers_pressed {
|
||||
continue;
|
||||
}
|
||||
|
||||
if actions.iter().flatten().any(|action| entry.action.to_discriminant() == *action) {
|
||||
return Some(entry.action.clone());
|
||||
}
|
||||
}
|
||||
|
@ -313,70 +443,69 @@ impl KeyMappingEntries {
|
|||
}
|
||||
}
|
||||
|
||||
impl Default for KeyMappingEntries {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
pub enum ActionKeys {
|
||||
Action(MessageDiscriminant),
|
||||
#[serde(rename = "keys")]
|
||||
Keys(Vec<Key>),
|
||||
}
|
||||
|
||||
mod input_mapper_macros {
|
||||
macro_rules! modifiers {
|
||||
($($m:ident),*) => {{
|
||||
#[allow(unused_mut)]
|
||||
let mut state = KeyStates::new();
|
||||
$(
|
||||
state.set(Key::$m as usize);
|
||||
)*
|
||||
state
|
||||
}};
|
||||
}
|
||||
impl ActionKeys {
|
||||
pub fn to_keys(&mut self, action_input_mapping: &impl Fn(&MessageDiscriminant) -> Vec<Vec<Key>>) {
|
||||
match self {
|
||||
ActionKeys::Action(action) => {
|
||||
if let Some(keys) = action_input_mapping(action).get_mut(0) {
|
||||
let mut taken_keys = Vec::new();
|
||||
std::mem::swap(keys, &mut taken_keys);
|
||||
|
||||
macro_rules! entry {
|
||||
{action=$action:expr, key_down=$key:ident $(, modifiers=[$($m:ident),* $(,)?])?} => {{
|
||||
entry!{action=$action, message=InputMapperMessage::KeyDown(Key::$key) $(, modifiers=[$($m),*])?}
|
||||
}};
|
||||
{action=$action:expr, key_up=$key:ident $(, modifiers=[$($m:ident),* $(,)?])?} => {{
|
||||
entry!{action=$action, message=InputMapperMessage::KeyUp(Key::$key) $(, modifiers=[$($m),* ])?}
|
||||
}};
|
||||
{action=$action:expr, message=$message:expr $(, modifiers=[$($m:ident),* $(,)?])?} => {{
|
||||
&[MappingEntry {trigger: $message, modifiers: modifiers!($($($m),*)?), action: $action.into()}]
|
||||
}};
|
||||
{action=$action:expr, triggers=[$($m:ident),* $(,)?]} => {{
|
||||
&[
|
||||
MappingEntry {trigger:InputMapperMessage::PointerMove, action: $action.into(), modifiers: modifiers!()},
|
||||
$(
|
||||
MappingEntry {trigger:InputMapperMessage::KeyDown(Key::$m), action: $action.into(), modifiers: modifiers!()},
|
||||
MappingEntry {trigger:InputMapperMessage::KeyUp(Key::$m), action: $action.into(), modifiers: modifiers!()},
|
||||
)*
|
||||
]
|
||||
}};
|
||||
}
|
||||
|
||||
macro_rules! mapping {
|
||||
//[$(<action=$action:expr; message=$key:expr; $(modifiers=[$($m:ident),* $(,)?];)?>)*] => {{
|
||||
[$($entry:expr),* $(,)?] => {{
|
||||
let mut key_up = KeyMappingEntries::key_array();
|
||||
let mut key_down = KeyMappingEntries::key_array();
|
||||
let mut pointer_move: KeyMappingEntries = Default::default();
|
||||
let mut mouse_scroll: KeyMappingEntries = Default::default();
|
||||
let mut double_click: KeyMappingEntries = Default::default();
|
||||
$(
|
||||
for entry in $entry {
|
||||
let arr = match entry.trigger {
|
||||
InputMapperMessage::KeyDown(key) => &mut key_down[key as usize],
|
||||
InputMapperMessage::KeyUp(key) => &mut key_up[key as usize],
|
||||
InputMapperMessage::MouseScroll => &mut mouse_scroll,
|
||||
InputMapperMessage::PointerMove => &mut pointer_move,
|
||||
InputMapperMessage::DoubleClick => &mut double_click,
|
||||
};
|
||||
arr.push(entry.clone());
|
||||
*self = ActionKeys::Keys(taken_keys);
|
||||
} else {
|
||||
*self = ActionKeys::Keys(Vec::new());
|
||||
}
|
||||
)*
|
||||
(key_up, key_down, pointer_move, mouse_scroll, double_click)
|
||||
}};
|
||||
}
|
||||
ActionKeys::Keys(keys) => {
|
||||
log::warn!("Calling `.to_keys()` on a `ActionKeys::Keys` is a mistake/bug. Keys are: {:?}.", keys);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn keys_text_shortcut(keys: &[Key], keyboard_platform: KeyboardPlatformLayout) -> String {
|
||||
const JOINER_MARK: &str = "+";
|
||||
|
||||
let mut joined = keys
|
||||
.iter()
|
||||
.map(|key| {
|
||||
let key_string = key.to_string();
|
||||
|
||||
if keyboard_platform == KeyboardPlatformLayout::Mac {
|
||||
match key_string.as_str() {
|
||||
"Command" => "⌘".to_string(),
|
||||
"Control" => "⌃".to_string(),
|
||||
"Alt" => "⌥".to_string(),
|
||||
"Shift" => "⇧".to_string(),
|
||||
_ => key_string + JOINER_MARK,
|
||||
}
|
||||
} else {
|
||||
key_string + JOINER_MARK
|
||||
}
|
||||
})
|
||||
.collect::<String>();
|
||||
|
||||
// Truncate to cut the joining character off the end if it's present
|
||||
if joined.ends_with(JOINER_MARK) {
|
||||
joined.truncate(joined.len() - JOINER_MARK.len());
|
||||
}
|
||||
|
||||
pub(crate) use entry;
|
||||
pub(crate) use mapping;
|
||||
pub(crate) use modifiers;
|
||||
joined
|
||||
}
|
||||
|
||||
pub mod action_keys {
|
||||
macro_rules! action_shortcut {
|
||||
($action:expr) => {
|
||||
Some(crate::input::input_mapper::ActionKeys::Action($action.into()))
|
||||
};
|
||||
}
|
||||
|
||||
pub(crate) use action_shortcut;
|
||||
}
|
||||
|
|
159
editor/src/input/input_mapper_macros.rs
Normal file
159
editor/src/input/input_mapper_macros.rs
Normal file
|
@ -0,0 +1,159 @@
|
|||
/// Constructs a `KeyStates` bit vector and sets the bit flags for all the given modifier `Key`s.
|
||||
macro_rules! modifiers {
|
||||
($($m:ident),*) => {{
|
||||
#[allow(unused_mut)]
|
||||
let mut state = KeyStates::new();
|
||||
$(
|
||||
state.set(Key::$m as usize);
|
||||
)*
|
||||
state
|
||||
}};
|
||||
}
|
||||
|
||||
/// 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.
|
||||
///
|
||||
/// Syntax:
|
||||
/// ```rs
|
||||
/// entry_for_layout!(Key; modifiers?: Key[], refresh_keys?: Key[], action_dispatch: Message, layout: Option<KeyboardPlatformLayout>)
|
||||
/// ```
|
||||
///
|
||||
/// 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) => {
|
||||
&[
|
||||
// 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.
|
||||
//
|
||||
// For example, a snapping state bound to the Shift key may change if the user presses or releases that key.
|
||||
// In that case, we want to dispatch the action's message even though the pointer didn't necessarily move so
|
||||
// the input handler can update the snapping state without making the user move the mouse to see the change.
|
||||
$(
|
||||
$(
|
||||
MappingEntry {
|
||||
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 ),*]
|
||||
};
|
||||
}
|
||||
|
||||
/// Constructs a `KeyMappingEntries` list for each input type and inserts every given entry into the list corresponding to its input type.
|
||||
/// Returns a tuple of `KeyMappingEntries` in the order:
|
||||
/// ```rs
|
||||
/// (key_up, key_down, double_click, wheel_scroll, pointer_move)
|
||||
/// ```
|
||||
macro_rules! mapping {
|
||||
[$($entry:expr),* $(,)?] => {{
|
||||
let mut key_up = KeyMappingEntries::key_array();
|
||||
let mut key_down = KeyMappingEntries::key_array();
|
||||
let mut double_click = KeyMappingEntries::new();
|
||||
let mut wheel_scroll = KeyMappingEntries::new();
|
||||
let mut pointer_move = KeyMappingEntries::new();
|
||||
|
||||
$(
|
||||
// Each of the many entry slices, one specified per action
|
||||
for entry_slice in $entry {
|
||||
// Each entry in the slice (usually just one, except when `refresh_keys` adds additional key entries)
|
||||
for entry in entry_slice.into_iter() {
|
||||
let corresponding_list = match entry.input {
|
||||
InputMapperMessage::KeyDown(key) => &mut key_down[key as usize],
|
||||
InputMapperMessage::KeyUp(key) => &mut key_up[key as usize],
|
||||
InputMapperMessage::DoubleClick => &mut double_click,
|
||||
InputMapperMessage::WheelScroll => &mut wheel_scroll,
|
||||
InputMapperMessage::PointerMove => &mut pointer_move,
|
||||
};
|
||||
// Push each entry to the corresponding `KeyMappingEntries` list for its input type
|
||||
corresponding_list.push(entry.clone());
|
||||
}
|
||||
}
|
||||
)*
|
||||
|
||||
(key_up, key_down, double_click, wheel_scroll, pointer_move)
|
||||
}};
|
||||
}
|
||||
|
||||
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;
|
|
@ -17,6 +17,6 @@ pub enum InputMapperMessage {
|
|||
|
||||
// Messages
|
||||
DoubleClick,
|
||||
MouseScroll,
|
||||
PointerMove,
|
||||
WheelScroll,
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
use super::input_mapper::Mapping;
|
||||
use super::keyboard::Key;
|
||||
use super::InputPreprocessorMessageHandler;
|
||||
use crate::document::utility_types::KeyboardPlatformLayout;
|
||||
use crate::message_prelude::*;
|
||||
|
||||
use std::fmt::Write;
|
||||
|
@ -31,12 +32,67 @@ impl InputMapperMessageHandler {
|
|||
});
|
||||
output.replace("Key", "")
|
||||
}
|
||||
|
||||
pub fn action_input_mapping(&self, action_to_find: &MessageDiscriminant, keyboard_platform: KeyboardPlatformLayout) -> Vec<Vec<Key>> {
|
||||
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);
|
||||
let wheel_scroll = std::iter::once(&self.mapping.wheel_scroll);
|
||||
let pointer_move = std::iter::once(&self.mapping.pointer_move);
|
||||
|
||||
let all_key_mapping_entries = key_up.chain(key_down).chain(double_click).chain(wheel_scroll).chain(pointer_move);
|
||||
let all_mapping_entries = all_key_mapping_entries.flat_map(|entry| entry.0.iter());
|
||||
|
||||
// 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>());
|
||||
found_actions
|
||||
.map(|entry| {
|
||||
let mut keys = entry
|
||||
.modifiers
|
||||
.iter()
|
||||
.map(|i| {
|
||||
// TODO: Use a safe solution eventually
|
||||
assert!(
|
||||
i < super::keyboard::NUMBER_OF_KEYS,
|
||||
"Attempting to convert a Key with enum index {}, which is larger than the number of Key enums",
|
||||
i
|
||||
);
|
||||
unsafe { std::mem::transmute_copy::<usize, Key>(&i) }
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
if let InputMapperMessage::KeyDown(key) = entry.input {
|
||||
keys.push(key);
|
||||
}
|
||||
|
||||
keys.sort_by(|a, b| {
|
||||
// Order according to platform guidelines mentioned at https://ux.stackexchange.com/questions/58185/normative-ordering-for-modifier-key-combinations
|
||||
const ORDER: [Key; 4] = [Key::KeyControl, Key::KeyAlt, Key::KeyShift, Key::KeyCommand];
|
||||
|
||||
match (ORDER.contains(a), ORDER.contains(b)) {
|
||||
(true, true) => ORDER.iter().position(|key| key == a).unwrap().cmp(&ORDER.iter().position(|key| key == b).unwrap()),
|
||||
(true, false) => std::cmp::Ordering::Less,
|
||||
(false, true) => std::cmp::Ordering::Greater,
|
||||
(false, false) => std::cmp::Ordering::Equal,
|
||||
}
|
||||
});
|
||||
|
||||
keys
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
}
|
||||
}
|
||||
|
||||
impl MessageHandler<InputMapperMessage, (&InputPreprocessorMessageHandler, ActionList)> for InputMapperMessageHandler {
|
||||
fn process_action(&mut self, message: InputMapperMessage, data: (&InputPreprocessorMessageHandler, ActionList), responses: &mut VecDeque<Message>) {
|
||||
let (input, actions) = data;
|
||||
if let Some(message) = self.mapping.match_message(message, &input.keyboard, actions) {
|
||||
impl MessageHandler<InputMapperMessage, (&InputPreprocessorMessageHandler, KeyboardPlatformLayout, ActionList)> for InputMapperMessageHandler {
|
||||
fn process_action(&mut self, message: InputMapperMessage, data: (&InputPreprocessorMessageHandler, KeyboardPlatformLayout, ActionList), responses: &mut VecDeque<Message>) {
|
||||
let (input, keyboard_platform, actions) = data;
|
||||
|
||||
if let Some(message) = self.mapping.match_input_message(message, &input.keyboard, actions, keyboard_platform) {
|
||||
responses.push_back(message);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,14 +13,16 @@ bitflags! {
|
|||
#[derive(Default, Serialize, Deserialize)]
|
||||
#[repr(transparent)]
|
||||
pub struct ModifierKeys: u8 {
|
||||
const CONTROL = 0b0000_0001;
|
||||
const SHIFT = 0b0000_0010;
|
||||
const ALT = 0b0000_0100;
|
||||
const SHIFT = 0b0000_0001;
|
||||
const ALT = 0b0000_0010;
|
||||
const CONTROL = 0b0000_0100;
|
||||
const META_OR_COMMAND = 0b0000_1000;
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use crate::document::utility_types::KeyboardPlatformLayout;
|
||||
use crate::input::input_preprocessor::ModifierKeys;
|
||||
use crate::input::keyboard::Key;
|
||||
use crate::input::mouse::EditorMouseState;
|
||||
|
@ -39,7 +41,7 @@ mod test {
|
|||
|
||||
let mut responses = VecDeque::new();
|
||||
|
||||
input_preprocessor.process_action(message, (), &mut responses);
|
||||
input_preprocessor.process_action(message, KeyboardPlatformLayout::Standard, &mut responses);
|
||||
|
||||
assert!(input_preprocessor.keyboard.get(Key::KeyAlt as usize));
|
||||
assert_eq!(responses.pop_front(), Some(InputMapperMessage::KeyDown(Key::KeyAlt).into()));
|
||||
|
@ -55,7 +57,7 @@ mod test {
|
|||
|
||||
let mut responses = VecDeque::new();
|
||||
|
||||
input_preprocessor.process_action(message, (), &mut responses);
|
||||
input_preprocessor.process_action(message, KeyboardPlatformLayout::Standard, &mut responses);
|
||||
|
||||
assert!(input_preprocessor.keyboard.get(Key::KeyControl as usize));
|
||||
assert_eq!(responses.pop_front(), Some(InputMapperMessage::KeyDown(Key::KeyControl).into()));
|
||||
|
@ -71,7 +73,7 @@ mod test {
|
|||
|
||||
let mut responses = VecDeque::new();
|
||||
|
||||
input_preprocessor.process_action(message, (), &mut responses);
|
||||
input_preprocessor.process_action(message, KeyboardPlatformLayout::Standard, &mut responses);
|
||||
|
||||
assert!(input_preprocessor.keyboard.get(Key::KeyShift as usize));
|
||||
assert_eq!(responses.pop_front(), Some(InputMapperMessage::KeyDown(Key::KeyShift).into()));
|
||||
|
@ -88,7 +90,7 @@ mod test {
|
|||
|
||||
let mut responses = VecDeque::new();
|
||||
|
||||
input_preprocessor.process_action(message, (), &mut responses);
|
||||
input_preprocessor.process_action(message, KeyboardPlatformLayout::Standard, &mut responses);
|
||||
|
||||
assert!(!input_preprocessor.keyboard.get(Key::KeyControl as usize));
|
||||
assert_eq!(responses.pop_front(), Some(InputMapperMessage::KeyUp(Key::KeyControl).into()));
|
||||
|
@ -104,7 +106,7 @@ mod test {
|
|||
|
||||
let mut responses = VecDeque::new();
|
||||
|
||||
input_preprocessor.process_action(message, (), &mut responses);
|
||||
input_preprocessor.process_action(message, KeyboardPlatformLayout::Standard, &mut responses);
|
||||
|
||||
assert!(input_preprocessor.keyboard.get(Key::KeyControl as usize));
|
||||
assert!(input_preprocessor.keyboard.get(Key::KeyShift as usize));
|
||||
|
|
|
@ -16,8 +16,8 @@ pub enum InputPreprocessorMessage {
|
|||
DoubleClick { editor_mouse_state: EditorMouseState, modifier_keys: ModifierKeys },
|
||||
KeyDown { key: Key, modifier_keys: ModifierKeys },
|
||||
KeyUp { key: Key, modifier_keys: ModifierKeys },
|
||||
MouseScroll { editor_mouse_state: EditorMouseState, modifier_keys: ModifierKeys },
|
||||
PointerDown { editor_mouse_state: EditorMouseState, modifier_keys: ModifierKeys },
|
||||
PointerMove { editor_mouse_state: EditorMouseState, modifier_keys: ModifierKeys },
|
||||
PointerUp { editor_mouse_state: EditorMouseState, modifier_keys: ModifierKeys },
|
||||
WheelScroll { editor_mouse_state: EditorMouseState, modifier_keys: ModifierKeys },
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
use super::input_preprocessor::ModifierKeys;
|
||||
use super::keyboard::{Key, KeyStates};
|
||||
use super::mouse::{MouseKeys, MouseState, ViewportBounds};
|
||||
use crate::document::utility_types::KeyboardPlatformLayout;
|
||||
use crate::message_prelude::*;
|
||||
|
||||
#[doc(inline)]
|
||||
|
@ -15,9 +16,11 @@ pub struct InputPreprocessorMessageHandler {
|
|||
pub viewport_bounds: ViewportBounds,
|
||||
}
|
||||
|
||||
impl MessageHandler<InputPreprocessorMessage, ()> for InputPreprocessorMessageHandler {
|
||||
impl MessageHandler<InputPreprocessorMessage, KeyboardPlatformLayout> for InputPreprocessorMessageHandler {
|
||||
#[remain::check]
|
||||
fn process_action(&mut self, message: InputPreprocessorMessage, _data: (), responses: &mut VecDeque<Message>) {
|
||||
fn process_action(&mut self, message: InputPreprocessorMessage, data: KeyboardPlatformLayout, responses: &mut VecDeque<Message>) {
|
||||
let keyboard_platform = data;
|
||||
|
||||
#[remain::sorted]
|
||||
match message {
|
||||
InputPreprocessorMessage::BoundsOfViewports { bounds_of_viewports } => {
|
||||
|
@ -53,7 +56,7 @@ impl MessageHandler<InputPreprocessorMessage, ()> for InputPreprocessorMessageHa
|
|||
}
|
||||
}
|
||||
InputPreprocessorMessage::DoubleClick { editor_mouse_state, modifier_keys } => {
|
||||
self.handle_modifier_keys(modifier_keys, responses);
|
||||
self.handle_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;
|
||||
|
@ -61,26 +64,17 @@ impl MessageHandler<InputPreprocessorMessage, ()> for InputPreprocessorMessageHa
|
|||
responses.push_back(InputMapperMessage::DoubleClick.into());
|
||||
}
|
||||
InputPreprocessorMessage::KeyDown { key, modifier_keys } => {
|
||||
self.handle_modifier_keys(modifier_keys, responses);
|
||||
self.handle_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, responses);
|
||||
self.handle_modifier_keys(modifier_keys, keyboard_platform, responses);
|
||||
self.keyboard.unset(key as usize);
|
||||
responses.push_back(InputMapperMessage::KeyUp(key).into());
|
||||
}
|
||||
InputPreprocessorMessage::MouseScroll { editor_mouse_state, modifier_keys } => {
|
||||
self.handle_modifier_keys(modifier_keys, responses);
|
||||
|
||||
let mouse_state = editor_mouse_state.to_mouse_state(&self.viewport_bounds);
|
||||
self.mouse.position = mouse_state.position;
|
||||
self.mouse.scroll_delta = mouse_state.scroll_delta;
|
||||
|
||||
responses.push_back(InputMapperMessage::MouseScroll.into());
|
||||
}
|
||||
InputPreprocessorMessage::PointerDown { editor_mouse_state, modifier_keys } => {
|
||||
self.handle_modifier_keys(modifier_keys, responses);
|
||||
self.handle_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;
|
||||
|
@ -88,7 +82,7 @@ impl MessageHandler<InputPreprocessorMessage, ()> for InputPreprocessorMessageHa
|
|||
self.translate_mouse_event(mouse_state, true, responses);
|
||||
}
|
||||
InputPreprocessorMessage::PointerMove { editor_mouse_state, modifier_keys } => {
|
||||
self.handle_modifier_keys(modifier_keys, responses);
|
||||
self.handle_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,13 +93,22 @@ impl MessageHandler<InputPreprocessorMessage, ()> for InputPreprocessorMessageHa
|
|||
self.translate_mouse_event(mouse_state, false, responses);
|
||||
}
|
||||
InputPreprocessorMessage::PointerUp { editor_mouse_state, modifier_keys } => {
|
||||
self.handle_modifier_keys(modifier_keys, responses);
|
||||
self.handle_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;
|
||||
|
||||
self.translate_mouse_event(mouse_state, false, responses);
|
||||
}
|
||||
InputPreprocessorMessage::WheelScroll { editor_mouse_state, modifier_keys } => {
|
||||
self.handle_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;
|
||||
self.mouse.scroll_delta = mouse_state.scroll_delta;
|
||||
|
||||
responses.push_back(InputMapperMessage::WheelScroll.into());
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -137,10 +140,15 @@ impl InputPreprocessorMessageHandler {
|
|||
self.mouse = new_state;
|
||||
}
|
||||
|
||||
fn handle_modifier_keys(&mut self, modifier_keys: ModifierKeys, responses: &mut VecDeque<Message>) {
|
||||
self.handle_modifier_key(Key::KeyControl, modifier_keys.contains(ModifierKeys::CONTROL), responses);
|
||||
fn handle_modifier_keys(&mut self, modifier_keys: ModifierKeys, keyboard_platform: KeyboardPlatformLayout, responses: &mut VecDeque<Message>) {
|
||||
self.handle_modifier_key(Key::KeyShift, modifier_keys.contains(ModifierKeys::SHIFT), responses);
|
||||
self.handle_modifier_key(Key::KeyAlt, modifier_keys.contains(ModifierKeys::ALT), responses);
|
||||
self.handle_modifier_key(Key::KeyControl, modifier_keys.contains(ModifierKeys::CONTROL), responses);
|
||||
let meta_or_command = match keyboard_platform {
|
||||
KeyboardPlatformLayout::Mac => Key::KeyCommand,
|
||||
KeyboardPlatformLayout::Standard => Key::KeyMeta,
|
||||
};
|
||||
self.handle_modifier_key(meta_or_command, modifier_keys.contains(ModifierKeys::META_OR_COMMAND), responses);
|
||||
}
|
||||
|
||||
fn handle_modifier_key(&mut self, key: Key, key_is_down: bool, responses: &mut VecDeque<Message>) {
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
use crate::message_prelude::*;
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::fmt::{Display, Formatter};
|
||||
use std::fmt::{self, Display, Formatter};
|
||||
use std::ops::{BitAnd, BitAndAssign, BitOr, BitOrAssign, BitXor, BitXorAssign};
|
||||
|
||||
// TODO: Increase size of type
|
||||
|
@ -21,7 +21,7 @@ pub type KeyStates = BitVector<KEY_MASK_STORAGE_LENGTH>;
|
|||
pub enum Key {
|
||||
UnknownKey,
|
||||
|
||||
// MouseKeys
|
||||
// Mouse keys
|
||||
Lmb,
|
||||
Rmb,
|
||||
Mmb,
|
||||
|
@ -70,6 +70,8 @@ pub enum Key {
|
|||
KeyShift,
|
||||
KeySpace,
|
||||
KeyControl,
|
||||
KeyCommand,
|
||||
KeyMeta,
|
||||
KeyDelete,
|
||||
KeyBackspace,
|
||||
KeyAlt,
|
||||
|
@ -92,6 +94,16 @@ pub enum Key {
|
|||
NumKeys,
|
||||
}
|
||||
|
||||
impl fmt::Display for Key {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> std::fmt::Result {
|
||||
let key_name = format!("{:?}", self);
|
||||
|
||||
let name = if &key_name[0..3] == "Key" { key_name.chars().skip(3).collect::<String>() } else { key_name };
|
||||
|
||||
write!(f, "{}", name)
|
||||
}
|
||||
}
|
||||
|
||||
pub const NUMBER_OF_KEYS: usize = Key::NumKeys as usize;
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
||||
|
@ -162,6 +174,10 @@ impl<const LENGTH: usize> BitVector<LENGTH> {
|
|||
|
||||
result
|
||||
}
|
||||
|
||||
pub fn iter(&self) -> impl Iterator<Item = usize> + '_ {
|
||||
BitVectorIter::<LENGTH> { bitvector: self, iter_index: 0 }
|
||||
}
|
||||
}
|
||||
|
||||
impl<const LENGTH: usize> Default for BitVector<LENGTH> {
|
||||
|
@ -170,6 +186,29 @@ impl<const LENGTH: usize> Default for BitVector<LENGTH> {
|
|||
}
|
||||
}
|
||||
|
||||
struct BitVectorIter<'a, const LENGTH: usize> {
|
||||
bitvector: &'a BitVector<LENGTH>,
|
||||
iter_index: usize,
|
||||
}
|
||||
|
||||
impl<'a, const LENGTH: usize> Iterator for BitVectorIter<'a, LENGTH> {
|
||||
type Item = usize;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
while self.iter_index < (STORAGE_SIZE_BITS as usize) * LENGTH {
|
||||
let bit_value = self.bitvector.get(self.iter_index);
|
||||
|
||||
self.iter_index += 1;
|
||||
|
||||
if bit_value {
|
||||
return Some(self.iter_index - 1);
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
impl<const LENGTH: usize> Display for BitVector<LENGTH> {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
for storage in self.0.iter().rev() {
|
||||
|
|
|
@ -3,6 +3,7 @@ pub mod input_preprocessor;
|
|||
pub mod keyboard;
|
||||
pub mod mouse;
|
||||
|
||||
mod input_mapper_macros;
|
||||
mod input_mapper_message;
|
||||
mod input_mapper_message_handler;
|
||||
mod input_preprocessor_message;
|
||||
|
|
|
@ -5,7 +5,7 @@ use serde::{Deserialize, Serialize};
|
|||
|
||||
#[remain::sorted]
|
||||
#[impl_message(Message, Layout)]
|
||||
#[derive(PartialEq, Clone, Deserialize, Serialize, Debug)]
|
||||
#[derive(PartialEq, Clone, Debug, Serialize, Deserialize)]
|
||||
pub enum LayoutMessage {
|
||||
RefreshLayout { layout_target: LayoutTarget },
|
||||
SendLayout { layout: Layout, layout_target: LayoutTarget },
|
||||
|
@ -13,7 +13,7 @@ pub enum LayoutMessage {
|
|||
}
|
||||
|
||||
#[remain::sorted]
|
||||
#[derive(PartialEq, Clone, Deserialize, Serialize, Debug, Hash, Eq, Copy)]
|
||||
#[derive(PartialEq, Clone, Debug, Hash, Eq, Copy, Serialize, Deserialize)]
|
||||
#[repr(u8)]
|
||||
pub enum LayoutTarget {
|
||||
DialogDetails,
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
use super::layout_message::LayoutTarget;
|
||||
use super::widgets::Layout;
|
||||
use crate::document::utility_types::KeyboardPlatformLayout;
|
||||
use crate::input::keyboard::Key;
|
||||
use crate::layout::widgets::Widget;
|
||||
use crate::message_prelude::*;
|
||||
|
||||
|
@ -15,49 +17,55 @@ pub struct LayoutMessageHandler {
|
|||
|
||||
impl LayoutMessageHandler {
|
||||
#[remain::check]
|
||||
fn send_layout(&self, layout_target: LayoutTarget, responses: &mut VecDeque<Message>) {
|
||||
fn send_layout(
|
||||
&self,
|
||||
layout_target: LayoutTarget,
|
||||
responses: &mut VecDeque<Message>,
|
||||
action_input_mapping: &impl Fn(&MessageDiscriminant) -> Vec<Vec<Key>>,
|
||||
keyboard_platform: KeyboardPlatformLayout,
|
||||
) {
|
||||
let layout = &self.layouts[layout_target as usize];
|
||||
#[remain::sorted]
|
||||
let message = match layout_target {
|
||||
LayoutTarget::DialogDetails => FrontendMessage::UpdateDialogDetails {
|
||||
layout_target,
|
||||
layout: layout.clone().unwrap_widget_layout().layout,
|
||||
layout: layout.clone().unwrap_widget_layout(action_input_mapping, keyboard_platform).layout,
|
||||
},
|
||||
LayoutTarget::DocumentBar => FrontendMessage::UpdateDocumentBarLayout {
|
||||
layout_target,
|
||||
layout: layout.clone().unwrap_widget_layout().layout,
|
||||
layout: layout.clone().unwrap_widget_layout(action_input_mapping, keyboard_platform).layout,
|
||||
},
|
||||
LayoutTarget::DocumentMode => FrontendMessage::UpdateDocumentModeLayout {
|
||||
layout_target,
|
||||
layout: layout.clone().unwrap_widget_layout().layout,
|
||||
layout: layout.clone().unwrap_widget_layout(action_input_mapping, keyboard_platform).layout,
|
||||
},
|
||||
LayoutTarget::LayerTreeOptions => FrontendMessage::UpdateLayerTreeOptionsLayout {
|
||||
layout_target,
|
||||
layout: layout.clone().unwrap_widget_layout().layout,
|
||||
layout: layout.clone().unwrap_widget_layout(action_input_mapping, keyboard_platform).layout,
|
||||
},
|
||||
LayoutTarget::MenuBar => FrontendMessage::UpdateMenuBarLayout {
|
||||
layout_target,
|
||||
layout: layout.clone().unwrap_menu_layout().layout,
|
||||
layout: layout.clone().unwrap_menu_layout(action_input_mapping, keyboard_platform).layout,
|
||||
},
|
||||
LayoutTarget::PropertiesOptions => FrontendMessage::UpdatePropertyPanelOptionsLayout {
|
||||
layout_target,
|
||||
layout: layout.clone().unwrap_widget_layout().layout,
|
||||
layout: layout.clone().unwrap_widget_layout(action_input_mapping, keyboard_platform).layout,
|
||||
},
|
||||
LayoutTarget::PropertiesSections => FrontendMessage::UpdatePropertyPanelSectionsLayout {
|
||||
layout_target,
|
||||
layout: layout.clone().unwrap_widget_layout().layout,
|
||||
layout: layout.clone().unwrap_widget_layout(action_input_mapping, keyboard_platform).layout,
|
||||
},
|
||||
LayoutTarget::ToolOptions => FrontendMessage::UpdateToolOptionsLayout {
|
||||
layout_target,
|
||||
layout: layout.clone().unwrap_widget_layout().layout,
|
||||
layout: layout.clone().unwrap_widget_layout(action_input_mapping, keyboard_platform).layout,
|
||||
},
|
||||
LayoutTarget::ToolShelf => FrontendMessage::UpdateToolShelfLayout {
|
||||
layout_target,
|
||||
layout: layout.clone().unwrap_widget_layout().layout,
|
||||
layout: layout.clone().unwrap_widget_layout(action_input_mapping, keyboard_platform).layout,
|
||||
},
|
||||
LayoutTarget::WorkingColors => FrontendMessage::UpdateWorkingColorsLayout {
|
||||
layout_target,
|
||||
layout: layout.clone().unwrap_widget_layout().layout,
|
||||
layout: layout.clone().unwrap_widget_layout(action_input_mapping, keyboard_platform).layout,
|
||||
},
|
||||
|
||||
#[remain::unsorted]
|
||||
|
@ -67,19 +75,21 @@ impl LayoutMessageHandler {
|
|||
}
|
||||
}
|
||||
|
||||
impl MessageHandler<LayoutMessage, ()> for LayoutMessageHandler {
|
||||
impl<F: Fn(&MessageDiscriminant) -> Vec<Vec<Key>>> MessageHandler<LayoutMessage, (F, KeyboardPlatformLayout)> for LayoutMessageHandler {
|
||||
#[remain::check]
|
||||
fn process_action(&mut self, action: LayoutMessage, _data: (), responses: &mut std::collections::VecDeque<crate::message_prelude::Message>) {
|
||||
fn process_action(&mut self, action: LayoutMessage, data: (F, KeyboardPlatformLayout), responses: &mut std::collections::VecDeque<crate::message_prelude::Message>) {
|
||||
let (action_input_mapping, keyboard_platform) = data;
|
||||
|
||||
use LayoutMessage::*;
|
||||
#[remain::sorted]
|
||||
match action {
|
||||
RefreshLayout { layout_target } => {
|
||||
self.send_layout(layout_target, responses);
|
||||
self.send_layout(layout_target, responses, &action_input_mapping, keyboard_platform);
|
||||
}
|
||||
SendLayout { layout, layout_target } => {
|
||||
self.layouts[layout_target as usize] = layout;
|
||||
|
||||
self.send_layout(layout_target, responses);
|
||||
self.send_layout(layout_target, responses, &action_input_mapping, keyboard_platform);
|
||||
}
|
||||
UpdateLayout { layout_target, widget_id, value } => {
|
||||
let layout = &mut self.layouts[layout_target as usize];
|
||||
|
|
|
@ -1,4 +1,7 @@
|
|||
use super::layout_message::LayoutTarget;
|
||||
use crate::document::utility_types::KeyboardPlatformLayout;
|
||||
use crate::input::input_mapper::keys_text_shortcut;
|
||||
use crate::input::input_mapper::ActionKeys;
|
||||
use crate::input::keyboard::Key;
|
||||
use crate::message_prelude::*;
|
||||
use crate::Color;
|
||||
|
@ -30,16 +33,67 @@ pub enum Layout {
|
|||
}
|
||||
|
||||
impl Layout {
|
||||
pub fn unwrap_widget_layout(self) -> WidgetLayout {
|
||||
if let Layout::WidgetLayout(widget_layout) = self {
|
||||
pub fn unwrap_widget_layout(self, action_input_mapping: &impl Fn(&MessageDiscriminant) -> Vec<Vec<Key>>, keyboard_platform: KeyboardPlatformLayout) -> WidgetLayout {
|
||||
if let Layout::WidgetLayout(mut widget_layout) = self {
|
||||
// Function used multiple times later in this code block to convert `ActionKeys::Action` to `ActionKeys::Keys` and append its shortcut to the tooltip
|
||||
let apply_shortcut_to_tooltip = |tooltip_shortcut: &mut ActionKeys, tooltip: &mut String| {
|
||||
tooltip_shortcut.to_keys(action_input_mapping);
|
||||
|
||||
if let ActionKeys::Keys(keys) = tooltip_shortcut {
|
||||
let shortcut_text = keys_text_shortcut(keys, keyboard_platform);
|
||||
|
||||
if !shortcut_text.is_empty() {
|
||||
if !tooltip.is_empty() {
|
||||
tooltip.push(' ');
|
||||
}
|
||||
tooltip.push('(');
|
||||
tooltip.push_str(&shortcut_text);
|
||||
tooltip.push(')');
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Go through each widget to convert `ActionKeys::Action` to `ActionKeys::Keys` and append the key combination to the widget tooltip
|
||||
for widget_holder in &mut widget_layout.iter_mut() {
|
||||
// Handle all the widgets that have tooltips
|
||||
let mut tooltip_shortcut = match &mut widget_holder.widget {
|
||||
Widget::CheckboxInput(widget) => Some((&mut widget.tooltip, &mut widget.tooltip_shortcut)),
|
||||
Widget::ColorInput(widget) => Some((&mut widget.tooltip, &mut widget.tooltip_shortcut)),
|
||||
Widget::IconButton(widget) => Some((&mut widget.tooltip, &mut widget.tooltip_shortcut)),
|
||||
Widget::OptionalInput(widget) => Some((&mut widget.tooltip, &mut widget.tooltip_shortcut)),
|
||||
_ => None,
|
||||
};
|
||||
if let Some((tooltip, Some(tooltip_shortcut))) = &mut tooltip_shortcut {
|
||||
apply_shortcut_to_tooltip(tooltip_shortcut, tooltip);
|
||||
}
|
||||
|
||||
// Handle RadioInput separately because its tooltips are children of the widget
|
||||
if let Widget::RadioInput(radio_input) = &mut widget_holder.widget {
|
||||
for radio_entry_data in &mut radio_input.entries {
|
||||
if let RadioEntryData {
|
||||
tooltip,
|
||||
tooltip_shortcut: Some(tooltip_shortcut),
|
||||
..
|
||||
} = radio_entry_data
|
||||
{
|
||||
apply_shortcut_to_tooltip(tooltip_shortcut, tooltip);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
widget_layout
|
||||
} else {
|
||||
panic!("Tried to unwrap layout as WidgetLayout. Got {:?}", self)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn unwrap_menu_layout(self) -> MenuLayout {
|
||||
if let Layout::MenuLayout(menu_layout) = self {
|
||||
pub fn unwrap_menu_layout(self, action_input_mapping: &impl Fn(&MessageDiscriminant) -> Vec<Vec<Key>>, _keyboard_platform: KeyboardPlatformLayout) -> MenuLayout {
|
||||
if let Layout::MenuLayout(mut menu_layout) = self {
|
||||
for menu_column in &mut menu_layout.layout {
|
||||
menu_column.children.fill_in_shortcut_actions_with_keys(action_input_mapping);
|
||||
}
|
||||
|
||||
menu_layout
|
||||
} else {
|
||||
panic!("Tried to unwrap layout as MenuLayout. Got {:?}", self)
|
||||
|
@ -67,13 +121,35 @@ impl Default for Layout {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default)]
|
||||
pub struct MenuEntryGroups(pub Vec<Vec<MenuEntry>>);
|
||||
|
||||
impl MenuEntryGroups {
|
||||
pub fn empty() -> Self {
|
||||
Self(Vec::new())
|
||||
}
|
||||
|
||||
pub fn fill_in_shortcut_actions_with_keys(&mut self, action_input_mapping: &impl Fn(&MessageDiscriminant) -> Vec<Vec<Key>>) {
|
||||
let entries = self.0.iter_mut().flatten();
|
||||
|
||||
for entry in entries {
|
||||
if let Some(action_keys) = &mut entry.shortcut {
|
||||
action_keys.to_keys(action_input_mapping);
|
||||
}
|
||||
|
||||
// Recursively do this for the children also
|
||||
entry.children.fill_in_shortcut_actions_with_keys(action_input_mapping);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||
pub struct MenuEntry {
|
||||
pub label: String,
|
||||
pub icon: Option<String>,
|
||||
pub children: Option<Vec<Vec<MenuEntry>>>,
|
||||
pub children: MenuEntryGroups,
|
||||
pub action: WidgetHolder,
|
||||
pub shortcut: Option<Vec<Key>>,
|
||||
pub shortcut: Option<ActionKeys>,
|
||||
}
|
||||
|
||||
impl MenuEntry {
|
||||
|
@ -94,7 +170,7 @@ impl Default for MenuEntry {
|
|||
action: MenuEntry::create_action(|_| DialogMessage::RequestComingSoonDialog { issue: None }.into()),
|
||||
label: "".into(),
|
||||
icon: None,
|
||||
children: None,
|
||||
children: MenuEntryGroups::empty(),
|
||||
shortcut: None,
|
||||
}
|
||||
}
|
||||
|
@ -103,10 +179,10 @@ impl Default for MenuEntry {
|
|||
#[derive(Debug, Default, Clone, Serialize, Deserialize, PartialEq)]
|
||||
pub struct MenuColumn {
|
||||
pub label: String,
|
||||
pub children: Vec<Vec<MenuEntry>>,
|
||||
pub children: MenuEntryGroups,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Clone, Serialize, Deserialize, PartialEq)]
|
||||
#[derive(Debug, Default, Clone, PartialEq, Serialize, Deserialize)]
|
||||
pub struct MenuLayout {
|
||||
pub layout: Vec<MenuColumn>,
|
||||
}
|
||||
|
@ -118,13 +194,13 @@ impl MenuLayout {
|
|||
|
||||
pub fn iter(&self) -> impl Iterator<Item = &WidgetHolder> + '_ {
|
||||
MenuLayoutIter {
|
||||
stack: self.layout.iter().flat_map(|column| column.children.iter()).flat_map(|group| group.iter()).collect(),
|
||||
stack: self.layout.iter().flat_map(|column| column.children.0.iter()).flat_map(|group| group.iter()).collect(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn iter_mut(&mut self) -> impl Iterator<Item = &mut WidgetHolder> + '_ {
|
||||
MenuLayoutIterMut {
|
||||
stack: self.layout.iter_mut().flat_map(|column| column.children.iter_mut()).flat_map(|group| group.iter_mut()).collect(),
|
||||
stack: self.layout.iter_mut().flat_map(|column| column.children.0.iter_mut()).flat_map(|group| group.iter_mut()).collect(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -140,7 +216,9 @@ impl<'a> Iterator for MenuLayoutIter<'a> {
|
|||
fn next(&mut self) -> Option<Self::Item> {
|
||||
match self.stack.pop() {
|
||||
Some(menu_entry) => {
|
||||
self.stack.extend(menu_entry.children.iter().flat_map(|group| group.iter()).flat_map(|entry| entry.iter()));
|
||||
let more_entries = menu_entry.children.0.iter().flat_map(|entry| entry.iter());
|
||||
self.stack.extend(more_entries);
|
||||
|
||||
Some(&menu_entry.action)
|
||||
}
|
||||
None => None,
|
||||
|
@ -158,7 +236,9 @@ impl<'a> Iterator for MenuLayoutIterMut<'a> {
|
|||
fn next(&mut self) -> Option<Self::Item> {
|
||||
match self.stack.pop() {
|
||||
Some(menu_entry) => {
|
||||
self.stack.extend(menu_entry.children.iter_mut().flat_map(|group| group.iter_mut()).flat_map(|entry| entry.iter_mut()));
|
||||
let more_entries = menu_entry.children.0.iter_mut().flat_map(|entry| entry.iter_mut());
|
||||
self.stack.extend(more_entries);
|
||||
|
||||
Some(&mut menu_entry.action)
|
||||
}
|
||||
None => None,
|
||||
|
@ -280,7 +360,7 @@ impl<'a> Iterator for WidgetIterMut<'a> {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
pub struct WidgetHolder {
|
||||
#[serde(rename = "widgetId")]
|
||||
pub widget_id: u64,
|
||||
|
@ -332,7 +412,7 @@ pub enum Widget {
|
|||
TextLabel(TextLabel),
|
||||
}
|
||||
|
||||
#[derive(Clone, Serialize, Deserialize, Derivative, Default)]
|
||||
#[derive(Clone, Default, Derivative, Serialize, Deserialize)]
|
||||
#[derivative(Debug, PartialEq)]
|
||||
pub struct CheckboxInput {
|
||||
pub checked: bool,
|
||||
|
@ -341,13 +421,16 @@ pub struct CheckboxInput {
|
|||
|
||||
pub tooltip: String,
|
||||
|
||||
#[serde(skip)]
|
||||
pub tooltip_shortcut: Option<ActionKeys>,
|
||||
|
||||
// Callbacks
|
||||
#[serde(skip)]
|
||||
#[derivative(Debug = "ignore", PartialEq = "ignore")]
|
||||
pub on_update: WidgetCallback<CheckboxInput>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Serialize, Deserialize, Derivative)]
|
||||
#[derive(Clone, Derivative, Serialize, Deserialize)]
|
||||
#[derivative(Debug, PartialEq, Default)]
|
||||
pub struct ColorInput {
|
||||
pub value: Option<String>,
|
||||
|
@ -362,6 +445,9 @@ pub struct ColorInput {
|
|||
|
||||
pub tooltip: String,
|
||||
|
||||
#[serde(skip)]
|
||||
pub tooltip_shortcut: Option<ActionKeys>,
|
||||
|
||||
// Callbacks
|
||||
#[serde(skip)]
|
||||
#[derivative(Debug = "ignore", PartialEq = "ignore")]
|
||||
|
@ -435,7 +521,7 @@ pub struct FontInput {
|
|||
pub on_update: WidgetCallback<FontInput>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Serialize, Deserialize, Derivative, Default)]
|
||||
#[derive(Clone, Default, Derivative, Serialize, Deserialize)]
|
||||
#[derivative(Debug, PartialEq)]
|
||||
pub struct IconButton {
|
||||
pub icon: String,
|
||||
|
@ -446,6 +532,9 @@ pub struct IconButton {
|
|||
|
||||
pub tooltip: String,
|
||||
|
||||
#[serde(skip)]
|
||||
pub tooltip_shortcut: Option<ActionKeys>,
|
||||
|
||||
// Callbacks
|
||||
#[serde(skip)]
|
||||
#[derivative(Debug = "ignore", PartialEq = "ignore")]
|
||||
|
@ -533,7 +622,7 @@ pub enum NumberInputIncrementBehavior {
|
|||
Callback,
|
||||
}
|
||||
|
||||
#[derive(Clone, Serialize, Deserialize, Derivative, Default)]
|
||||
#[derive(Clone, Default, Derivative, Serialize, Deserialize)]
|
||||
#[derivative(Debug, PartialEq)]
|
||||
pub struct OptionalInput {
|
||||
pub checked: bool,
|
||||
|
@ -542,6 +631,9 @@ pub struct OptionalInput {
|
|||
|
||||
pub tooltip: String,
|
||||
|
||||
#[serde(skip)]
|
||||
pub tooltip_shortcut: Option<ActionKeys>,
|
||||
|
||||
// Callbacks
|
||||
#[serde(skip)]
|
||||
#[derivative(Debug = "ignore", PartialEq = "ignore")]
|
||||
|
@ -559,7 +651,7 @@ pub struct PopoverButton {
|
|||
pub text: String,
|
||||
}
|
||||
|
||||
#[derive(Clone, Serialize, Deserialize, Derivative, Default)]
|
||||
#[derive(Clone, Default, Derivative, Serialize, Deserialize)]
|
||||
#[derivative(Debug, PartialEq)]
|
||||
pub struct RadioInput {
|
||||
pub entries: Vec<RadioEntryData>,
|
||||
|
@ -569,7 +661,7 @@ pub struct RadioInput {
|
|||
pub selected_index: u32,
|
||||
}
|
||||
|
||||
#[derive(Clone, Serialize, Deserialize, Derivative, Default)]
|
||||
#[derive(Clone, Default, Derivative, Serialize, Deserialize)]
|
||||
#[derivative(Debug, PartialEq)]
|
||||
pub struct RadioEntryData {
|
||||
pub value: String,
|
||||
|
@ -580,6 +672,9 @@ pub struct RadioEntryData {
|
|||
|
||||
pub tooltip: String,
|
||||
|
||||
#[serde(skip)]
|
||||
pub tooltip_shortcut: Option<ActionKeys>,
|
||||
|
||||
// Callbacks
|
||||
#[serde(skip)]
|
||||
#[derivative(Debug = "ignore", PartialEq = "ignore")]
|
||||
|
|
|
@ -91,7 +91,7 @@ pub mod message_prelude {
|
|||
pub use crate::viewport_tools::tools::select_tool::{SelectToolMessage, SelectToolMessageDiscriminant};
|
||||
pub use crate::viewport_tools::tools::shape_tool::{ShapeToolMessage, ShapeToolMessageDiscriminant};
|
||||
pub use crate::viewport_tools::tools::spline_tool::{SplineToolMessage, SplineToolMessageDiscriminant};
|
||||
pub use crate::viewport_tools::tools::text_tool::{TextMessage, TextMessageDiscriminant};
|
||||
pub use crate::viewport_tools::tools::text_tool::{TextToolMessage, TextToolMessageDiscriminant};
|
||||
pub use crate::workspace::{WorkspaceMessage, WorkspaceMessageDiscriminant};
|
||||
pub use graphite_proc_macros::*;
|
||||
|
||||
|
|
|
@ -10,10 +10,19 @@ pub struct HintGroup(pub Vec<HintInfo>);
|
|||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct HintInfo {
|
||||
/// A `KeysGroup` specifies all the keys pressed simultaneously to perform an action (like "Ctrl C" to copy).
|
||||
/// Usually at most one is given, but less commonly, multiple can be used to describe additional hotkeys not used simultaneously (like the four different arrow keys to nudge a layer).
|
||||
#[serde(rename = "keyGroups")]
|
||||
pub key_groups: Vec<KeysGroup>,
|
||||
/// `None` means that the regular `key_groups` should be used for all platforms, `Some` is an override for a Mac-only input hint.
|
||||
#[serde(rename = "keyGroupsMac")]
|
||||
pub key_groups_mac: Option<Vec<KeysGroup>>,
|
||||
/// An optional `MouseMotion` that can indicate the mouse action, like which mouse button is used and whether a drag occurs.
|
||||
/// No such icon is shown if `None` is given, and it can be combined with `key_groups` if desired.
|
||||
pub mouse: Option<MouseMotion>,
|
||||
/// The text describing what occurs with this input combination.
|
||||
pub label: String,
|
||||
/// Prepend the "+" symbol indicating that this is a refinement upon a previous entry in the group.
|
||||
/// Draws a prepended "+" symbol which indicates that this is a refinement upon a previous hint in the group.
|
||||
pub plus: bool,
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
use super::tools::*;
|
||||
use crate::communication::message_handler::MessageHandler;
|
||||
use crate::document::DocumentMessageHandler;
|
||||
use crate::input::input_mapper::action_keys::action_shortcut;
|
||||
use crate::input::input_mapper::ActionKeys;
|
||||
use crate::input::InputPreprocessorMessageHandler;
|
||||
use crate::layout::widgets::{IconButton, Layout, LayoutGroup, PropertyHolder, Separator, SeparatorDirection, SeparatorType, Widget, WidgetCallback, WidgetHolder, WidgetLayout};
|
||||
use crate::message_prelude::*;
|
||||
|
@ -115,6 +117,7 @@ impl ToolData {
|
|||
#[derive(Debug)]
|
||||
pub struct ToolBarMetadataGroup {
|
||||
pub tooltip: String,
|
||||
pub tooltip_shortcut: Option<ActionKeys>,
|
||||
pub icon_name: String,
|
||||
pub tool_type: ToolType,
|
||||
}
|
||||
|
@ -123,18 +126,24 @@ impl PropertyHolder for ToolData {
|
|||
fn properties(&self) -> Layout {
|
||||
let tool_groups_layout = list_tools_in_groups()
|
||||
.iter()
|
||||
.map(|tool_group| tool_group.iter().map(|tool| ToolBarMetadataGroup {tooltip: tool.tooltip(), icon_name: tool.icon_name(), tool_type: tool.tool_type()}).collect::<Vec<_>>())
|
||||
.map(|tool_group| tool_group.iter().map(|tool| ToolBarMetadataGroup {
|
||||
tooltip: tool.tooltip(),
|
||||
tooltip_shortcut: action_shortcut!(tool_type_to_activate_tool_message(tool.tool_type())),
|
||||
icon_name: tool.icon_name(),
|
||||
tool_type: tool.tool_type(),
|
||||
}).collect::<Vec<_>>())
|
||||
.chain(coming_soon_tools())
|
||||
.flat_map(|group| {
|
||||
let separator = std::iter::once(WidgetHolder::new(Widget::Separator(Separator {
|
||||
direction: SeparatorDirection::Vertical,
|
||||
separator_type: SeparatorType::Section,
|
||||
})));
|
||||
let buttons = group.into_iter().map(|ToolBarMetadataGroup {tooltip, tool_type, icon_name}| {
|
||||
let buttons = group.into_iter().map(|ToolBarMetadataGroup { tooltip, tooltip_shortcut, tool_type, icon_name }| {
|
||||
WidgetHolder::new(Widget::IconButton(IconButton {
|
||||
icon: icon_name,
|
||||
size: 32,
|
||||
tooltip: tooltip.clone(),
|
||||
tooltip_shortcut,
|
||||
active: self.active_tool_type == tool_type,
|
||||
on_update: WidgetCallback::new(move |_| {
|
||||
if !tooltip.contains("Coming Soon") {
|
||||
|
@ -143,7 +152,6 @@ impl PropertyHolder for ToolData {
|
|||
DialogMessage::RequestComingSoonDialog { issue: None }.into()
|
||||
}
|
||||
}),
|
||||
..Default::default()
|
||||
}))
|
||||
});
|
||||
separator.chain(buttons)
|
||||
|
@ -253,68 +261,107 @@ pub fn coming_soon_tools() -> Vec<Vec<ToolBarMetadataGroup>> {
|
|||
tool_type: ToolType::Brush,
|
||||
icon_name: "RasterBrushTool".into(),
|
||||
tooltip: "Coming Soon: Brush Tool (B)".into(),
|
||||
tooltip_shortcut: None,
|
||||
},
|
||||
ToolBarMetadataGroup {
|
||||
tool_type: ToolType::Heal,
|
||||
icon_name: "RasterHealTool".into(),
|
||||
tooltip: "Coming Soon: Heal Tool (J)".into(),
|
||||
tooltip_shortcut: None,
|
||||
},
|
||||
ToolBarMetadataGroup {
|
||||
tool_type: ToolType::Clone,
|
||||
icon_name: "RasterCloneTool".into(),
|
||||
tooltip: "Coming Soon: Clone Tool (C))".into(),
|
||||
tooltip: "Coming Soon: Clone Tool (C)".into(),
|
||||
tooltip_shortcut: None,
|
||||
},
|
||||
ToolBarMetadataGroup {
|
||||
tool_type: ToolType::Patch,
|
||||
icon_name: "RasterPatchTool".into(),
|
||||
tooltip: "Coming Soon: Patch Tool".into(),
|
||||
tooltip_shortcut: None,
|
||||
},
|
||||
ToolBarMetadataGroup {
|
||||
tool_type: ToolType::Detail,
|
||||
icon_name: "RasterDetailTool".into(),
|
||||
tooltip: "Coming Soon: Detail Tool (D)".into(),
|
||||
tooltip_shortcut: None,
|
||||
},
|
||||
ToolBarMetadataGroup {
|
||||
tool_type: ToolType::Relight,
|
||||
icon_name: "RasterRelightTool".into(),
|
||||
tooltip: "Coming Soon: Relight Tool (O".into(),
|
||||
tooltip: "Coming Soon: Relight Tool (O)".into(),
|
||||
tooltip_shortcut: None,
|
||||
},
|
||||
]]
|
||||
}
|
||||
|
||||
pub fn message_to_tool_type(message: &ToolMessage) -> ToolType {
|
||||
use ToolMessage::*;
|
||||
|
||||
match message {
|
||||
pub fn tool_message_to_tool_type(tool_message: &ToolMessage) -> ToolType {
|
||||
match tool_message {
|
||||
// General tool group
|
||||
Select(_) => ToolType::Select,
|
||||
Artboard(_) => ToolType::Artboard,
|
||||
Navigate(_) => ToolType::Navigate,
|
||||
Eyedropper(_) => ToolType::Eyedropper,
|
||||
Fill(_) => ToolType::Fill,
|
||||
Gradient(_) => ToolType::Gradient,
|
||||
ToolMessage::Select(_) => ToolType::Select,
|
||||
ToolMessage::Artboard(_) => ToolType::Artboard,
|
||||
ToolMessage::Navigate(_) => ToolType::Navigate,
|
||||
ToolMessage::Eyedropper(_) => ToolType::Eyedropper,
|
||||
ToolMessage::Fill(_) => ToolType::Fill,
|
||||
ToolMessage::Gradient(_) => ToolType::Gradient,
|
||||
|
||||
// Vector tool group
|
||||
Path(_) => ToolType::Path,
|
||||
Pen(_) => ToolType::Pen,
|
||||
Freehand(_) => ToolType::Freehand,
|
||||
Spline(_) => ToolType::Spline,
|
||||
Line(_) => ToolType::Line,
|
||||
Rectangle(_) => ToolType::Rectangle,
|
||||
Ellipse(_) => ToolType::Ellipse,
|
||||
Shape(_) => ToolType::Shape,
|
||||
Text(_) => ToolType::Text,
|
||||
ToolMessage::Path(_) => ToolType::Path,
|
||||
ToolMessage::Pen(_) => ToolType::Pen,
|
||||
ToolMessage::Freehand(_) => ToolType::Freehand,
|
||||
ToolMessage::Spline(_) => ToolType::Spline,
|
||||
ToolMessage::Line(_) => ToolType::Line,
|
||||
ToolMessage::Rectangle(_) => ToolType::Rectangle,
|
||||
ToolMessage::Ellipse(_) => ToolType::Ellipse,
|
||||
ToolMessage::Shape(_) => ToolType::Shape,
|
||||
ToolMessage::Text(_) => ToolType::Text,
|
||||
|
||||
// Raster tool group
|
||||
// Brush(_) => ToolType::Brush,
|
||||
// Heal(_) => ToolType::Heal,
|
||||
// Clone(_) => ToolType::Clone,
|
||||
// Patch(_) => ToolType::Patch,
|
||||
// Detail(_) => ToolType::Detail,
|
||||
// Relight(_) => ToolType::Relight,
|
||||
// ToolMessage::Brush(_) => ToolType::Brush,
|
||||
// ToolMessage::Heal(_) => ToolType::Heal,
|
||||
// ToolMessage::Clone(_) => ToolType::Clone,
|
||||
// ToolMessage::Patch(_) => ToolType::Patch,
|
||||
// ToolMessage::Detail(_) => ToolType::Detail,
|
||||
// ToolMessage::Relight(_) => ToolType::Relight,
|
||||
_ => panic!(
|
||||
"Conversion from message to tool type impossible because the given ToolMessage does not belong to a tool. Got: {:?}",
|
||||
message
|
||||
"Conversion from ToolMessage to ToolType impossible because the given ToolMessage does not have a matching ToolType. Got: {:?}",
|
||||
tool_message
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn tool_type_to_activate_tool_message(tool_type: ToolType) -> ToolMessageDiscriminant {
|
||||
match tool_type {
|
||||
// General tool group
|
||||
ToolType::Select => ToolMessageDiscriminant::ActivateToolSelect,
|
||||
ToolType::Artboard => ToolMessageDiscriminant::ActivateToolArtboard,
|
||||
ToolType::Navigate => ToolMessageDiscriminant::ActivateToolNavigate,
|
||||
ToolType::Eyedropper => ToolMessageDiscriminant::ActivateToolEyedropper,
|
||||
ToolType::Fill => ToolMessageDiscriminant::ActivateToolFill,
|
||||
ToolType::Gradient => ToolMessageDiscriminant::ActivateToolGradient,
|
||||
|
||||
// Vector tool group
|
||||
ToolType::Path => ToolMessageDiscriminant::ActivateToolPath,
|
||||
ToolType::Pen => ToolMessageDiscriminant::ActivateToolPen,
|
||||
ToolType::Freehand => ToolMessageDiscriminant::ActivateToolFreehand,
|
||||
ToolType::Spline => ToolMessageDiscriminant::ActivateToolSpline,
|
||||
ToolType::Line => ToolMessageDiscriminant::ActivateToolLine,
|
||||
ToolType::Rectangle => ToolMessageDiscriminant::ActivateToolRectangle,
|
||||
ToolType::Ellipse => ToolMessageDiscriminant::ActivateToolEllipse,
|
||||
ToolType::Shape => ToolMessageDiscriminant::ActivateToolShape,
|
||||
ToolType::Text => ToolMessageDiscriminant::ActivateToolText,
|
||||
|
||||
// Raster tool group
|
||||
// ToolType::Brush => ToolMessageDiscriminant::ActivateToolBrush,
|
||||
// ToolType::Heal => ToolMessageDiscriminant::ActivateToolHeal,
|
||||
// ToolType::Clone => ToolMessageDiscriminant::ActivateToolClone,
|
||||
// ToolType::Patch => ToolMessageDiscriminant::ActivateToolPatch,
|
||||
// ToolType::Detail => ToolMessageDiscriminant::ActivateToolDetail,
|
||||
// ToolType::Relight => ToolMessageDiscriminant::ActivateToolRelight,
|
||||
_ => panic!(
|
||||
"Conversion from ToolType to ToolMessage impossible because the given ToolType does not have a matching ToolMessage. Got: {:?}",
|
||||
tool_type
|
||||
),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -28,6 +28,7 @@ pub enum ToolMessage {
|
|||
#[remain::unsorted]
|
||||
#[child]
|
||||
Gradient(GradientToolMessage),
|
||||
|
||||
#[remain::unsorted]
|
||||
#[child]
|
||||
Path(PathToolMessage),
|
||||
|
@ -54,7 +55,8 @@ pub enum ToolMessage {
|
|||
Shape(ShapeToolMessage),
|
||||
#[remain::unsorted]
|
||||
#[child]
|
||||
Text(TextMessage),
|
||||
Text(TextToolMessage),
|
||||
|
||||
// #[remain::unsorted]
|
||||
// #[child]
|
||||
// Brush(BrushToolMessage),
|
||||
|
@ -75,6 +77,38 @@ pub enum ToolMessage {
|
|||
// Detail(DetailToolMessage),
|
||||
|
||||
// Messages
|
||||
#[remain::unsorted]
|
||||
ActivateToolSelect,
|
||||
#[remain::unsorted]
|
||||
ActivateToolArtboard,
|
||||
#[remain::unsorted]
|
||||
ActivateToolNavigate,
|
||||
#[remain::unsorted]
|
||||
ActivateToolEyedropper,
|
||||
#[remain::unsorted]
|
||||
ActivateToolText,
|
||||
#[remain::unsorted]
|
||||
ActivateToolFill,
|
||||
#[remain::unsorted]
|
||||
ActivateToolGradient,
|
||||
|
||||
#[remain::unsorted]
|
||||
ActivateToolPath,
|
||||
#[remain::unsorted]
|
||||
ActivateToolPen,
|
||||
#[remain::unsorted]
|
||||
ActivateToolFreehand,
|
||||
#[remain::unsorted]
|
||||
ActivateToolSpline,
|
||||
#[remain::unsorted]
|
||||
ActivateToolLine,
|
||||
#[remain::unsorted]
|
||||
ActivateToolRectangle,
|
||||
#[remain::unsorted]
|
||||
ActivateToolEllipse,
|
||||
#[remain::unsorted]
|
||||
ActivateToolShape,
|
||||
|
||||
ActivateTool {
|
||||
tool_type: ToolType,
|
||||
},
|
||||
|
|
|
@ -1,11 +1,12 @@
|
|||
use super::tool::{message_to_tool_type, ToolFsmState};
|
||||
use super::tool::{tool_message_to_tool_type, ToolFsmState};
|
||||
use crate::document::DocumentMessageHandler;
|
||||
use crate::input::input_mapper::action_keys::action_shortcut;
|
||||
use crate::input::InputPreprocessorMessageHandler;
|
||||
use crate::layout::layout_message::LayoutTarget;
|
||||
use crate::layout::widgets::PropertyHolder;
|
||||
use crate::layout::widgets::{IconButton, Layout, LayoutGroup, SwatchPairInput, Widget, WidgetCallback, WidgetHolder, WidgetLayout};
|
||||
use crate::message_prelude::*;
|
||||
use crate::viewport_tools::tool::DocumentToolData;
|
||||
use crate::viewport_tools::tool::{DocumentToolData, ToolType};
|
||||
|
||||
use graphene::color::Color;
|
||||
use graphene::layers::text_layer::FontCache;
|
||||
|
@ -26,6 +27,38 @@ impl MessageHandler<ToolMessage, (&DocumentMessageHandler, &InputPreprocessorMes
|
|||
#[remain::sorted]
|
||||
match message {
|
||||
// Messages
|
||||
#[remain::unsorted]
|
||||
ActivateToolSelect => responses.push_front(ToolMessage::ActivateTool { tool_type: ToolType::Select }.into()),
|
||||
#[remain::unsorted]
|
||||
ActivateToolArtboard => responses.push_front(ToolMessage::ActivateTool { tool_type: ToolType::Artboard }.into()),
|
||||
#[remain::unsorted]
|
||||
ActivateToolNavigate => responses.push_front(ToolMessage::ActivateTool { tool_type: ToolType::Navigate }.into()),
|
||||
#[remain::unsorted]
|
||||
ActivateToolEyedropper => responses.push_front(ToolMessage::ActivateTool { tool_type: ToolType::Eyedropper }.into()),
|
||||
#[remain::unsorted]
|
||||
ActivateToolText => responses.push_front(ToolMessage::ActivateTool { tool_type: ToolType::Text }.into()),
|
||||
#[remain::unsorted]
|
||||
ActivateToolFill => responses.push_front(ToolMessage::ActivateTool { tool_type: ToolType::Fill }.into()),
|
||||
#[remain::unsorted]
|
||||
ActivateToolGradient => responses.push_front(ToolMessage::ActivateTool { tool_type: ToolType::Gradient }.into()),
|
||||
|
||||
#[remain::unsorted]
|
||||
ActivateToolPath => responses.push_front(ToolMessage::ActivateTool { tool_type: ToolType::Path }.into()),
|
||||
#[remain::unsorted]
|
||||
ActivateToolPen => responses.push_front(ToolMessage::ActivateTool { tool_type: ToolType::Pen }.into()),
|
||||
#[remain::unsorted]
|
||||
ActivateToolFreehand => responses.push_front(ToolMessage::ActivateTool { tool_type: ToolType::Freehand }.into()),
|
||||
#[remain::unsorted]
|
||||
ActivateToolSpline => responses.push_front(ToolMessage::ActivateTool { tool_type: ToolType::Spline }.into()),
|
||||
#[remain::unsorted]
|
||||
ActivateToolLine => responses.push_front(ToolMessage::ActivateTool { tool_type: ToolType::Line }.into()),
|
||||
#[remain::unsorted]
|
||||
ActivateToolRectangle => responses.push_front(ToolMessage::ActivateTool { tool_type: ToolType::Rectangle }.into()),
|
||||
#[remain::unsorted]
|
||||
ActivateToolEllipse => responses.push_front(ToolMessage::ActivateTool { tool_type: ToolType::Ellipse }.into()),
|
||||
#[remain::unsorted]
|
||||
ActivateToolShape => responses.push_front(ToolMessage::ActivateTool { tool_type: ToolType::Shape }.into()),
|
||||
|
||||
ActivateTool { tool_type } => {
|
||||
let tool_data = &mut self.tool_state.tool_data;
|
||||
let document_data = &self.tool_state.document_tool_data;
|
||||
|
@ -151,7 +184,7 @@ impl MessageHandler<ToolMessage, (&DocumentMessageHandler, &InputPreprocessorMes
|
|||
tool_message => {
|
||||
let tool_type = match &tool_message {
|
||||
UpdateCursor | UpdateHints => self.tool_state.tool_data.active_tool_type,
|
||||
tool_message => message_to_tool_type(tool_message),
|
||||
tool_message => tool_message_to_tool_type(tool_message),
|
||||
};
|
||||
let document_data = &self.tool_state.document_tool_data;
|
||||
let tool_data = &mut self.tool_state.tool_data;
|
||||
|
@ -167,7 +200,21 @@ impl MessageHandler<ToolMessage, (&DocumentMessageHandler, &InputPreprocessorMes
|
|||
|
||||
fn actions(&self) -> ActionList {
|
||||
let mut list = actions!(ToolMessageDiscriminant;
|
||||
ActivateTool,
|
||||
ActivateToolSelect,
|
||||
ActivateToolArtboard,
|
||||
ActivateToolNavigate,
|
||||
ActivateToolEyedropper,
|
||||
ActivateToolText,
|
||||
ActivateToolFill,
|
||||
ActivateToolGradient,
|
||||
ActivateToolPath,
|
||||
ActivateToolPen,
|
||||
ActivateToolFreehand,
|
||||
ActivateToolSpline,
|
||||
ActivateToolLine,
|
||||
ActivateToolRectangle,
|
||||
ActivateToolEllipse,
|
||||
ActivateToolShape,
|
||||
SelectRandomPrimaryColor,
|
||||
ResetColors,
|
||||
SwapColors,
|
||||
|
@ -191,14 +238,16 @@ fn update_working_colors(document_data: &DocumentToolData, responses: &mut VecDe
|
|||
WidgetHolder::new(Widget::IconButton(IconButton {
|
||||
size: 16,
|
||||
icon: "Swap".into(),
|
||||
tooltip: "Swap (Shift+X)".into(), // TODO: Customize this tooltip for the Mac version of the keyboard shortcut
|
||||
tooltip: "Swap".into(),
|
||||
tooltip_shortcut: action_shortcut!(ToolMessageDiscriminant::SwapColors),
|
||||
on_update: WidgetCallback::new(|_| ToolMessage::SwapColors.into()),
|
||||
..Default::default()
|
||||
})),
|
||||
WidgetHolder::new(Widget::IconButton(IconButton {
|
||||
size: 16,
|
||||
icon: "ResetColors".into(), // TODO: Customize this tooltip for the Mac version of the keyboard shortcut
|
||||
tooltip: "Reset (Ctrl+Shift+X)".into(),
|
||||
icon: "ResetColors".into(),
|
||||
tooltip: "Reset".into(),
|
||||
tooltip_shortcut: action_shortcut!(ToolMessageDiscriminant::ResetColors),
|
||||
on_update: WidgetCallback::new(|_| ToolMessage::ResetColors.into()),
|
||||
..Default::default()
|
||||
})),
|
||||
|
|
|
@ -243,8 +243,10 @@ impl Fsm for ArtboardToolFsmState {
|
|||
let mouse_position = input.mouse.position;
|
||||
let snapped_mouse_position = tool_data.snap_handler.snap_position(responses, document, mouse_position);
|
||||
|
||||
let [position, size] = movement.new_size(snapped_mouse_position, bounds.transform, from_center, constrain_square);
|
||||
let position = movement.center_position(position, size, from_center);
|
||||
let (mut position, size) = movement.new_size(snapped_mouse_position, bounds.transform, from_center, constrain_square);
|
||||
if from_center {
|
||||
position = movement.center_position(position, size);
|
||||
}
|
||||
|
||||
responses.push_back(
|
||||
ArtboardMessage::ResizeArtboard {
|
||||
|
@ -408,18 +410,21 @@ impl Fsm for ArtboardToolFsmState {
|
|||
ArtboardToolFsmState::Ready => HintData(vec![
|
||||
HintGroup(vec![HintInfo {
|
||||
key_groups: vec![],
|
||||
key_groups_mac: None,
|
||||
mouse: Some(MouseMotion::LmbDrag),
|
||||
label: String::from("Draw Artboard"),
|
||||
plus: false,
|
||||
}]),
|
||||
HintGroup(vec![HintInfo {
|
||||
key_groups: vec![],
|
||||
key_groups_mac: None,
|
||||
mouse: Some(MouseMotion::LmbDrag),
|
||||
label: String::from("Move Artboard"),
|
||||
plus: false,
|
||||
}]),
|
||||
HintGroup(vec![HintInfo {
|
||||
key_groups: vec![KeysGroup(vec![Key::KeyBackspace])],
|
||||
key_groups_mac: None,
|
||||
mouse: None,
|
||||
label: String::from("Delete Artboard"),
|
||||
plus: false,
|
||||
|
@ -427,6 +432,7 @@ impl Fsm for ArtboardToolFsmState {
|
|||
]),
|
||||
ArtboardToolFsmState::Dragging => HintData(vec![HintGroup(vec![HintInfo {
|
||||
key_groups: vec![KeysGroup(vec![Key::KeyShift])],
|
||||
key_groups_mac: None,
|
||||
mouse: None,
|
||||
label: String::from("Constrain to Axis"),
|
||||
plus: false,
|
||||
|
@ -434,12 +440,14 @@ impl Fsm for ArtboardToolFsmState {
|
|||
ArtboardToolFsmState::Drawing | ArtboardToolFsmState::ResizingBounds => HintData(vec![HintGroup(vec![
|
||||
HintInfo {
|
||||
key_groups: vec![KeysGroup(vec![Key::KeyShift])],
|
||||
key_groups_mac: None,
|
||||
mouse: None,
|
||||
label: String::from("Constrain Square"),
|
||||
plus: false,
|
||||
},
|
||||
HintInfo {
|
||||
key_groups: vec![KeysGroup(vec![Key::KeyAlt])],
|
||||
key_groups_mac: None,
|
||||
mouse: None,
|
||||
label: String::from("From Center"),
|
||||
plus: false,
|
||||
|
|
|
@ -41,7 +41,7 @@ impl ToolMetadata for EllipseTool {
|
|||
"VectorEllipseTool".into()
|
||||
}
|
||||
fn tooltip(&self) -> String {
|
||||
"Ellipse Tool (E)".into()
|
||||
"Ellipse Tool".into()
|
||||
}
|
||||
fn tool_type(&self) -> crate::viewport_tools::tool::ToolType {
|
||||
ToolType::Ellipse
|
||||
|
@ -185,18 +185,21 @@ impl Fsm for EllipseToolFsmState {
|
|||
EllipseToolFsmState::Ready => HintData(vec![HintGroup(vec![
|
||||
HintInfo {
|
||||
key_groups: vec![],
|
||||
key_groups_mac: None,
|
||||
mouse: Some(MouseMotion::LmbDrag),
|
||||
label: String::from("Draw Ellipse"),
|
||||
plus: false,
|
||||
},
|
||||
HintInfo {
|
||||
key_groups: vec![KeysGroup(vec![Key::KeyShift])],
|
||||
key_groups_mac: None,
|
||||
mouse: None,
|
||||
label: String::from("Constrain Circular"),
|
||||
plus: true,
|
||||
},
|
||||
HintInfo {
|
||||
key_groups: vec![KeysGroup(vec![Key::KeyAlt])],
|
||||
key_groups_mac: None,
|
||||
mouse: None,
|
||||
label: String::from("From Center"),
|
||||
plus: true,
|
||||
|
@ -205,12 +208,14 @@ impl Fsm for EllipseToolFsmState {
|
|||
EllipseToolFsmState::Drawing => HintData(vec![HintGroup(vec![
|
||||
HintInfo {
|
||||
key_groups: vec![KeysGroup(vec![Key::KeyShift])],
|
||||
key_groups_mac: None,
|
||||
mouse: None,
|
||||
label: String::from("Constrain Circular"),
|
||||
plus: false,
|
||||
},
|
||||
HintInfo {
|
||||
key_groups: vec![KeysGroup(vec![Key::KeyAlt])],
|
||||
key_groups_mac: None,
|
||||
mouse: None,
|
||||
label: String::from("From Center"),
|
||||
plus: false,
|
||||
|
|
|
@ -36,7 +36,7 @@ impl ToolMetadata for EyedropperTool {
|
|||
"GeneralEyedropperTool".into()
|
||||
}
|
||||
fn tooltip(&self) -> String {
|
||||
"Eyedropper Tool (I)".into()
|
||||
"Eyedropper Tool".into()
|
||||
}
|
||||
fn tool_type(&self) -> crate::viewport_tools::tool::ToolType {
|
||||
ToolType::Eyedropper
|
||||
|
@ -147,12 +147,14 @@ impl Fsm for EyedropperToolFsmState {
|
|||
EyedropperToolFsmState::Ready => HintData(vec![HintGroup(vec![
|
||||
HintInfo {
|
||||
key_groups: vec![],
|
||||
key_groups_mac: None,
|
||||
mouse: Some(MouseMotion::Lmb),
|
||||
label: String::from("Sample to Primary"),
|
||||
plus: false,
|
||||
},
|
||||
HintInfo {
|
||||
key_groups: vec![],
|
||||
key_groups_mac: None,
|
||||
mouse: Some(MouseMotion::Rmb),
|
||||
label: String::from("Sample to Secondary"),
|
||||
plus: false,
|
||||
|
|
|
@ -37,7 +37,7 @@ impl ToolMetadata for FillTool {
|
|||
"GeneralFillTool".into()
|
||||
}
|
||||
fn tooltip(&self) -> String {
|
||||
"Fill Tool (F)".into()
|
||||
"Fill Tool".into()
|
||||
}
|
||||
fn tool_type(&self) -> crate::viewport_tools::tool::ToolType {
|
||||
ToolType::Fill
|
||||
|
@ -146,12 +146,14 @@ impl Fsm for FillToolFsmState {
|
|||
FillToolFsmState::Ready => HintData(vec![HintGroup(vec![
|
||||
HintInfo {
|
||||
key_groups: vec![],
|
||||
key_groups_mac: None,
|
||||
mouse: Some(MouseMotion::Lmb),
|
||||
label: String::from("Fill with Primary"),
|
||||
plus: false,
|
||||
},
|
||||
HintInfo {
|
||||
key_groups: vec![],
|
||||
key_groups_mac: None,
|
||||
mouse: Some(MouseMotion::Rmb),
|
||||
label: String::from("Fill with Secondary"),
|
||||
plus: false,
|
||||
|
|
|
@ -60,7 +60,7 @@ impl ToolMetadata for FreehandTool {
|
|||
"VectorFreehandTool".into()
|
||||
}
|
||||
fn tooltip(&self) -> String {
|
||||
"Freehand Tool (N)".into()
|
||||
"Freehand Tool".into()
|
||||
}
|
||||
fn tool_type(&self) -> crate::viewport_tools::tool::ToolType {
|
||||
ToolType::Freehand
|
||||
|
@ -222,6 +222,7 @@ impl Fsm for FreehandToolFsmState {
|
|||
let hint_data = match self {
|
||||
FreehandToolFsmState::Ready => HintData(vec![HintGroup(vec![HintInfo {
|
||||
key_groups: vec![],
|
||||
key_groups_mac: None,
|
||||
mouse: Some(MouseMotion::LmbDrag),
|
||||
label: String::from("Draw Polyline"),
|
||||
plus: false,
|
||||
|
|
|
@ -65,7 +65,7 @@ impl ToolMetadata for GradientTool {
|
|||
"GeneralGradientTool".into()
|
||||
}
|
||||
fn tooltip(&self) -> String {
|
||||
"Gradient Tool (H))".into()
|
||||
"Gradient Tool".into()
|
||||
}
|
||||
fn tool_type(&self) -> crate::viewport_tools::tool::ToolType {
|
||||
ToolType::Gradient
|
||||
|
@ -461,12 +461,14 @@ impl Fsm for GradientToolFsmState {
|
|||
GradientToolFsmState::Ready => HintData(vec![HintGroup(vec![
|
||||
HintInfo {
|
||||
key_groups: vec![],
|
||||
key_groups_mac: None,
|
||||
mouse: Some(MouseMotion::LmbDrag),
|
||||
label: String::from("Draw Gradient"),
|
||||
plus: false,
|
||||
},
|
||||
HintInfo {
|
||||
key_groups: vec![KeysGroup(vec![Key::KeyShift])],
|
||||
key_groups_mac: None,
|
||||
mouse: None,
|
||||
label: String::from("Snap 15°"),
|
||||
plus: true,
|
||||
|
@ -474,6 +476,7 @@ impl Fsm for GradientToolFsmState {
|
|||
])]),
|
||||
GradientToolFsmState::Drawing => HintData(vec![HintGroup(vec![HintInfo {
|
||||
key_groups: vec![KeysGroup(vec![Key::KeyShift])],
|
||||
key_groups_mac: None,
|
||||
mouse: None,
|
||||
label: String::from("Snap 15°"),
|
||||
plus: false,
|
||||
|
|
|
@ -61,7 +61,7 @@ impl ToolMetadata for LineTool {
|
|||
"VectorLineTool".into()
|
||||
}
|
||||
fn tooltip(&self) -> String {
|
||||
"Line Tool (L)".into()
|
||||
"Line Tool".into()
|
||||
}
|
||||
fn tool_type(&self) -> crate::viewport_tools::tool::ToolType {
|
||||
ToolType::Line
|
||||
|
@ -239,24 +239,28 @@ impl Fsm for LineToolFsmState {
|
|||
LineToolFsmState::Ready => HintData(vec![HintGroup(vec![
|
||||
HintInfo {
|
||||
key_groups: vec![],
|
||||
key_groups_mac: None,
|
||||
mouse: Some(MouseMotion::LmbDrag),
|
||||
label: String::from("Draw Line"),
|
||||
plus: false,
|
||||
},
|
||||
HintInfo {
|
||||
key_groups: vec![KeysGroup(vec![Key::KeyShift])],
|
||||
key_groups_mac: None,
|
||||
mouse: None,
|
||||
label: String::from("Snap 15°"),
|
||||
plus: true,
|
||||
},
|
||||
HintInfo {
|
||||
key_groups: vec![KeysGroup(vec![Key::KeyAlt])],
|
||||
key_groups_mac: None,
|
||||
mouse: None,
|
||||
label: String::from("From Center"),
|
||||
plus: true,
|
||||
},
|
||||
HintInfo {
|
||||
key_groups: vec![KeysGroup(vec![Key::KeyControl])],
|
||||
key_groups_mac: None,
|
||||
mouse: None,
|
||||
label: String::from("Lock Angle"),
|
||||
plus: true,
|
||||
|
@ -265,18 +269,21 @@ impl Fsm for LineToolFsmState {
|
|||
LineToolFsmState::Drawing => HintData(vec![HintGroup(vec![
|
||||
HintInfo {
|
||||
key_groups: vec![KeysGroup(vec![Key::KeyShift])],
|
||||
key_groups_mac: None,
|
||||
mouse: None,
|
||||
label: String::from("Snap 15°"),
|
||||
plus: false,
|
||||
},
|
||||
HintInfo {
|
||||
key_groups: vec![KeysGroup(vec![Key::KeyAlt])],
|
||||
key_groups_mac: None,
|
||||
mouse: None,
|
||||
label: String::from("From Center"),
|
||||
plus: false,
|
||||
},
|
||||
HintInfo {
|
||||
key_groups: vec![KeysGroup(vec![Key::KeyControl])],
|
||||
key_groups_mac: None,
|
||||
mouse: None,
|
||||
label: String::from("Lock Angle"),
|
||||
plus: false,
|
||||
|
|
|
@ -41,7 +41,7 @@ impl ToolMetadata for NavigateTool {
|
|||
"GeneralNavigateTool".into()
|
||||
}
|
||||
fn tooltip(&self) -> String {
|
||||
"Navigate Tool (Z)".into()
|
||||
"Navigate Tool".into()
|
||||
}
|
||||
fn tool_type(&self) -> crate::viewport_tools::tool::ToolType {
|
||||
ToolType::Navigate
|
||||
|
@ -195,12 +195,14 @@ impl Fsm for NavigateToolFsmState {
|
|||
HintGroup(vec![
|
||||
HintInfo {
|
||||
key_groups: vec![],
|
||||
key_groups_mac: None,
|
||||
mouse: Some(MouseMotion::Lmb),
|
||||
label: String::from("Zoom In"),
|
||||
plus: false,
|
||||
},
|
||||
HintInfo {
|
||||
key_groups: vec![KeysGroup(vec![Key::KeyShift])],
|
||||
key_groups_mac: None,
|
||||
mouse: None,
|
||||
label: String::from("Zoom Out"),
|
||||
plus: true,
|
||||
|
@ -209,12 +211,14 @@ impl Fsm for NavigateToolFsmState {
|
|||
HintGroup(vec![
|
||||
HintInfo {
|
||||
key_groups: vec![],
|
||||
key_groups_mac: None,
|
||||
mouse: Some(MouseMotion::LmbDrag),
|
||||
label: String::from("Zoom"),
|
||||
plus: false,
|
||||
},
|
||||
HintInfo {
|
||||
key_groups: vec![KeysGroup(vec![Key::KeyControl])],
|
||||
key_groups_mac: None,
|
||||
mouse: None,
|
||||
label: String::from("Snap Increments"),
|
||||
plus: true,
|
||||
|
@ -222,6 +226,7 @@ impl Fsm for NavigateToolFsmState {
|
|||
]),
|
||||
HintGroup(vec![HintInfo {
|
||||
key_groups: vec![],
|
||||
key_groups_mac: None,
|
||||
mouse: Some(MouseMotion::MmbDrag),
|
||||
label: String::from("Pan"),
|
||||
plus: false,
|
||||
|
@ -229,12 +234,14 @@ impl Fsm for NavigateToolFsmState {
|
|||
HintGroup(vec![
|
||||
HintInfo {
|
||||
key_groups: vec![],
|
||||
key_groups_mac: None,
|
||||
mouse: Some(MouseMotion::RmbDrag),
|
||||
label: String::from("Tilt"),
|
||||
plus: false,
|
||||
},
|
||||
HintInfo {
|
||||
key_groups: vec![KeysGroup(vec![Key::KeyControl])],
|
||||
key_groups_mac: None,
|
||||
mouse: None,
|
||||
label: String::from("Snap 15°"),
|
||||
plus: true,
|
||||
|
@ -243,12 +250,14 @@ impl Fsm for NavigateToolFsmState {
|
|||
]),
|
||||
NavigateToolFsmState::Tilting => HintData(vec![HintGroup(vec![HintInfo {
|
||||
key_groups: vec![KeysGroup(vec![Key::KeyControl])],
|
||||
key_groups_mac: None,
|
||||
mouse: None,
|
||||
label: String::from("Snap 15°"),
|
||||
plus: false,
|
||||
}])]),
|
||||
NavigateToolFsmState::Zooming => HintData(vec![HintGroup(vec![HintInfo {
|
||||
key_groups: vec![KeysGroup(vec![Key::KeyControl])],
|
||||
key_groups_mac: None,
|
||||
mouse: None,
|
||||
label: String::from("Snap Increments"),
|
||||
plus: false,
|
||||
|
|
|
@ -50,7 +50,7 @@ impl ToolMetadata for PathTool {
|
|||
"VectorPathTool".into()
|
||||
}
|
||||
fn tooltip(&self) -> String {
|
||||
"Path Tool (A)".into()
|
||||
"Path Tool".into()
|
||||
}
|
||||
fn tool_type(&self) -> crate::viewport_tools::tool::ToolType {
|
||||
ToolType::Path
|
||||
|
@ -306,12 +306,14 @@ impl Fsm for PathToolFsmState {
|
|||
HintGroup(vec![
|
||||
HintInfo {
|
||||
key_groups: vec![],
|
||||
key_groups_mac: None,
|
||||
mouse: Some(MouseMotion::Lmb),
|
||||
label: String::from("Select Point"),
|
||||
plus: false,
|
||||
},
|
||||
HintInfo {
|
||||
key_groups: vec![KeysGroup(vec![Key::KeyShift])],
|
||||
key_groups_mac: None,
|
||||
mouse: None,
|
||||
label: String::from("Grow/Shrink Selection"),
|
||||
plus: true,
|
||||
|
@ -319,6 +321,7 @@ impl Fsm for PathToolFsmState {
|
|||
]),
|
||||
HintGroup(vec![HintInfo {
|
||||
key_groups: vec![],
|
||||
key_groups_mac: None,
|
||||
mouse: Some(MouseMotion::LmbDrag),
|
||||
label: String::from("Drag Selected"),
|
||||
plus: false,
|
||||
|
@ -331,12 +334,14 @@ impl Fsm for PathToolFsmState {
|
|||
KeysGroup(vec![Key::KeyArrowDown]),
|
||||
KeysGroup(vec![Key::KeyArrowLeft]),
|
||||
],
|
||||
key_groups_mac: None,
|
||||
mouse: None,
|
||||
label: String::from("Nudge Selected (coming soon)"),
|
||||
plus: false,
|
||||
},
|
||||
HintInfo {
|
||||
key_groups: vec![KeysGroup(vec![Key::KeyShift])],
|
||||
key_groups_mac: None,
|
||||
mouse: None,
|
||||
label: String::from("Big Increment Nudge"),
|
||||
plus: true,
|
||||
|
@ -345,18 +350,21 @@ impl Fsm for PathToolFsmState {
|
|||
HintGroup(vec![
|
||||
HintInfo {
|
||||
key_groups: vec![KeysGroup(vec![Key::KeyG])],
|
||||
key_groups_mac: None,
|
||||
mouse: None,
|
||||
label: String::from("Grab Selected (coming soon)"),
|
||||
plus: false,
|
||||
},
|
||||
HintInfo {
|
||||
key_groups: vec![KeysGroup(vec![Key::KeyR])],
|
||||
key_groups_mac: None,
|
||||
mouse: None,
|
||||
label: String::from("Rotate Selected (coming soon)"),
|
||||
plus: false,
|
||||
},
|
||||
HintInfo {
|
||||
key_groups: vec![KeysGroup(vec![Key::KeyS])],
|
||||
key_groups_mac: None,
|
||||
mouse: None,
|
||||
label: String::from("Scale Selected (coming soon)"),
|
||||
plus: false,
|
||||
|
@ -366,12 +374,14 @@ impl Fsm for PathToolFsmState {
|
|||
PathToolFsmState::Dragging => HintData(vec![HintGroup(vec![
|
||||
HintInfo {
|
||||
key_groups: vec![KeysGroup(vec![Key::KeyAlt])],
|
||||
key_groups_mac: None,
|
||||
mouse: None,
|
||||
label: String::from("Split/Align Handles (Toggle)"),
|
||||
plus: false,
|
||||
},
|
||||
HintInfo {
|
||||
key_groups: vec![KeysGroup(vec![Key::KeyShift])],
|
||||
key_groups_mac: None,
|
||||
mouse: None,
|
||||
label: String::from("Share Lengths of Aligned Handles"),
|
||||
plus: false,
|
||||
|
|
|
@ -78,7 +78,7 @@ impl ToolMetadata for PenTool {
|
|||
"VectorPenTool".into()
|
||||
}
|
||||
fn tooltip(&self) -> String {
|
||||
"Pen Tool (P)".into()
|
||||
"Pen Tool".into()
|
||||
}
|
||||
fn tool_type(&self) -> crate::viewport_tools::tool::ToolType {
|
||||
ToolType::Pen
|
||||
|
@ -374,6 +374,7 @@ impl Fsm for PenToolFsmState {
|
|||
let hint_data = match self {
|
||||
PenToolFsmState::Ready => HintData(vec![HintGroup(vec![HintInfo {
|
||||
key_groups: vec![],
|
||||
key_groups_mac: None,
|
||||
mouse: Some(MouseMotion::Lmb),
|
||||
label: String::from("Draw Path"),
|
||||
plus: false,
|
||||
|
@ -381,30 +382,35 @@ impl Fsm for PenToolFsmState {
|
|||
PenToolFsmState::DraggingHandle | PenToolFsmState::PlacingAnchor => HintData(vec![
|
||||
HintGroup(vec![HintInfo {
|
||||
key_groups: vec![],
|
||||
key_groups_mac: None,
|
||||
mouse: Some(MouseMotion::LmbDrag),
|
||||
label: String::from("Add Handle"),
|
||||
plus: false,
|
||||
}]),
|
||||
HintGroup(vec![HintInfo {
|
||||
key_groups: vec![],
|
||||
key_groups_mac: None,
|
||||
mouse: Some(MouseMotion::Lmb),
|
||||
label: String::from("Add Anchor"),
|
||||
plus: false,
|
||||
}]),
|
||||
HintGroup(vec![HintInfo {
|
||||
key_groups: vec![KeysGroup(vec![Key::KeyControl])],
|
||||
key_groups_mac: None,
|
||||
mouse: None,
|
||||
label: String::from("Snap 15°"),
|
||||
plus: false,
|
||||
}]),
|
||||
HintGroup(vec![HintInfo {
|
||||
key_groups: vec![KeysGroup(vec![Key::KeyShift])],
|
||||
key_groups_mac: None,
|
||||
mouse: None,
|
||||
label: String::from("Break Handle"),
|
||||
plus: false,
|
||||
}]),
|
||||
HintGroup(vec![HintInfo {
|
||||
key_groups: vec![KeysGroup(vec![Key::KeyEnter])],
|
||||
key_groups_mac: None,
|
||||
mouse: None,
|
||||
label: String::from("End Path"),
|
||||
plus: false,
|
||||
|
|
|
@ -80,7 +80,7 @@ impl ToolMetadata for RectangleTool {
|
|||
"VectorRectangleTool".into()
|
||||
}
|
||||
fn tooltip(&self) -> String {
|
||||
"Rectangle Tool (M)".into()
|
||||
"Rectangle Tool".into()
|
||||
}
|
||||
fn tool_type(&self) -> crate::viewport_tools::tool::ToolType {
|
||||
ToolType::Rectangle
|
||||
|
@ -186,18 +186,21 @@ impl Fsm for RectangleToolFsmState {
|
|||
RectangleToolFsmState::Ready => HintData(vec![HintGroup(vec![
|
||||
HintInfo {
|
||||
key_groups: vec![],
|
||||
key_groups_mac: None,
|
||||
mouse: Some(MouseMotion::LmbDrag),
|
||||
label: String::from("Draw Rectangle"),
|
||||
plus: false,
|
||||
},
|
||||
HintInfo {
|
||||
key_groups: vec![KeysGroup(vec![Key::KeyShift])],
|
||||
key_groups_mac: None,
|
||||
mouse: None,
|
||||
label: String::from("Constrain Square"),
|
||||
plus: true,
|
||||
},
|
||||
HintInfo {
|
||||
key_groups: vec![KeysGroup(vec![Key::KeyAlt])],
|
||||
key_groups_mac: None,
|
||||
mouse: None,
|
||||
label: String::from("From Center"),
|
||||
plus: true,
|
||||
|
@ -206,12 +209,14 @@ impl Fsm for RectangleToolFsmState {
|
|||
RectangleToolFsmState::Drawing => HintData(vec![HintGroup(vec![
|
||||
HintInfo {
|
||||
key_groups: vec![KeysGroup(vec![Key::KeyShift])],
|
||||
key_groups_mac: None,
|
||||
mouse: None,
|
||||
label: String::from("Constrain Square"),
|
||||
plus: false,
|
||||
},
|
||||
HintInfo {
|
||||
key_groups: vec![KeysGroup(vec![Key::KeyAlt])],
|
||||
key_groups_mac: None,
|
||||
mouse: None,
|
||||
label: String::from("From Center"),
|
||||
plus: false,
|
||||
|
|
|
@ -63,7 +63,7 @@ impl ToolMetadata for SelectTool {
|
|||
"GeneralSelectTool".into()
|
||||
}
|
||||
fn tooltip(&self) -> String {
|
||||
"Select Tool (V)".into()
|
||||
"Select Tool".into()
|
||||
}
|
||||
fn tool_type(&self) -> crate::viewport_tools::tool::ToolType {
|
||||
ToolType::Select
|
||||
|
@ -390,7 +390,7 @@ impl Fsm for SelectToolFsmState {
|
|||
match intersect.data {
|
||||
LayerDataType::Text(_) => {
|
||||
responses.push_front(ToolMessage::ActivateTool { tool_type: ToolType::Text }.into());
|
||||
responses.push_back(TextMessage::Interact.into());
|
||||
responses.push_back(TextToolMessage::Interact.into());
|
||||
}
|
||||
LayerDataType::Shape(_) => {
|
||||
responses.push_front(ToolMessage::ActivateTool { tool_type: ToolType::Path }.into());
|
||||
|
@ -529,7 +529,7 @@ impl Fsm for SelectToolFsmState {
|
|||
|
||||
let snapped_mouse_position = tool_data.snap_handler.snap_position(responses, document, mouse_position);
|
||||
|
||||
let [_position, size] = movement.new_size(snapped_mouse_position, bounds.transform, center, axis_align);
|
||||
let (_, size) = movement.new_size(snapped_mouse_position, bounds.transform, center, axis_align);
|
||||
let delta = movement.bounds_to_scale_transform(center, size);
|
||||
|
||||
let selected = tool_data.layers_dragging.iter().collect::<Vec<_>>();
|
||||
|
@ -702,6 +702,7 @@ impl Fsm for SelectToolFsmState {
|
|||
SelectToolFsmState::Ready => HintData(vec![
|
||||
HintGroup(vec![HintInfo {
|
||||
key_groups: vec![],
|
||||
key_groups_mac: None,
|
||||
mouse: Some(MouseMotion::LmbDrag),
|
||||
label: String::from("Drag Selected"),
|
||||
plus: false,
|
||||
|
@ -709,18 +710,21 @@ impl Fsm for SelectToolFsmState {
|
|||
HintGroup(vec![
|
||||
HintInfo {
|
||||
key_groups: vec![KeysGroup(vec![Key::KeyG])],
|
||||
key_groups_mac: None,
|
||||
mouse: None,
|
||||
label: String::from("Grab Selected"),
|
||||
plus: false,
|
||||
},
|
||||
HintInfo {
|
||||
key_groups: vec![KeysGroup(vec![Key::KeyR])],
|
||||
key_groups_mac: None,
|
||||
mouse: None,
|
||||
label: String::from("Rotate Selected"),
|
||||
plus: false,
|
||||
},
|
||||
HintInfo {
|
||||
key_groups: vec![KeysGroup(vec![Key::KeyS])],
|
||||
key_groups_mac: None,
|
||||
mouse: None,
|
||||
label: String::from("Scale Selected"),
|
||||
plus: false,
|
||||
|
@ -729,18 +733,21 @@ impl Fsm for SelectToolFsmState {
|
|||
HintGroup(vec![
|
||||
HintInfo {
|
||||
key_groups: vec![],
|
||||
key_groups_mac: None,
|
||||
mouse: Some(MouseMotion::Lmb),
|
||||
label: String::from("Select Object"),
|
||||
plus: false,
|
||||
},
|
||||
HintInfo {
|
||||
key_groups: vec![KeysGroup(vec![Key::KeyControl])],
|
||||
key_groups_mac: Some(vec![KeysGroup(vec![Key::KeyCommand])]),
|
||||
mouse: None,
|
||||
label: String::from("Innermost"),
|
||||
plus: true,
|
||||
},
|
||||
HintInfo {
|
||||
key_groups: vec![KeysGroup(vec![Key::KeyShift])],
|
||||
key_groups_mac: None,
|
||||
mouse: None,
|
||||
label: String::from("Grow/Shrink Selection"),
|
||||
plus: true,
|
||||
|
@ -749,12 +756,14 @@ impl Fsm for SelectToolFsmState {
|
|||
HintGroup(vec![
|
||||
HintInfo {
|
||||
key_groups: vec![],
|
||||
key_groups_mac: None,
|
||||
mouse: Some(MouseMotion::LmbDrag),
|
||||
label: String::from("Select Area"),
|
||||
plus: false,
|
||||
},
|
||||
HintInfo {
|
||||
key_groups: vec![KeysGroup(vec![Key::KeyShift])],
|
||||
key_groups_mac: None,
|
||||
mouse: None,
|
||||
label: String::from("Grow/Shrink Selection"),
|
||||
plus: true,
|
||||
|
@ -768,12 +777,14 @@ impl Fsm for SelectToolFsmState {
|
|||
KeysGroup(vec![Key::KeyArrowDown]),
|
||||
KeysGroup(vec![Key::KeyArrowLeft]),
|
||||
],
|
||||
key_groups_mac: None,
|
||||
mouse: None,
|
||||
label: String::from("Nudge Selected"),
|
||||
plus: false,
|
||||
},
|
||||
HintInfo {
|
||||
key_groups: vec![KeysGroup(vec![Key::KeyShift])],
|
||||
key_groups_mac: None,
|
||||
mouse: None,
|
||||
label: String::from("Big Increment Nudge"),
|
||||
plus: true,
|
||||
|
@ -782,12 +793,14 @@ impl Fsm for SelectToolFsmState {
|
|||
HintGroup(vec![
|
||||
HintInfo {
|
||||
key_groups: vec![KeysGroup(vec![Key::KeyAlt])],
|
||||
key_groups_mac: None,
|
||||
mouse: Some(MouseMotion::LmbDrag),
|
||||
label: String::from("Move Duplicate"),
|
||||
plus: false,
|
||||
},
|
||||
HintInfo {
|
||||
key_groups: vec![KeysGroup(vec![Key::KeyControl, Key::KeyD])],
|
||||
key_groups_mac: Some(vec![KeysGroup(vec![Key::KeyCommand, Key::KeyD])]),
|
||||
mouse: None,
|
||||
label: String::from("Duplicate"),
|
||||
plus: false,
|
||||
|
@ -797,12 +810,14 @@ impl Fsm for SelectToolFsmState {
|
|||
SelectToolFsmState::Dragging => HintData(vec![HintGroup(vec![
|
||||
HintInfo {
|
||||
key_groups: vec![KeysGroup(vec![Key::KeyShift])],
|
||||
key_groups_mac: None,
|
||||
mouse: None,
|
||||
label: String::from("Constrain to Axis"),
|
||||
plus: false,
|
||||
},
|
||||
HintInfo {
|
||||
key_groups: vec![KeysGroup(vec![Key::KeyControl])],
|
||||
key_groups_mac: None,
|
||||
mouse: None,
|
||||
label: String::from("Snap to Points (coming soon)"),
|
||||
plus: false,
|
||||
|
@ -812,6 +827,7 @@ impl Fsm for SelectToolFsmState {
|
|||
SelectToolFsmState::ResizingBounds => HintData(vec![]),
|
||||
SelectToolFsmState::RotatingBounds => HintData(vec![HintGroup(vec![HintInfo {
|
||||
key_groups: vec![KeysGroup(vec![Key::KeyControl])],
|
||||
key_groups_mac: None,
|
||||
mouse: None,
|
||||
label: String::from("Snap 15°"),
|
||||
plus: false,
|
||||
|
|
|
@ -59,7 +59,7 @@ impl ToolMetadata for ShapeTool {
|
|||
"VectorShapeTool".into()
|
||||
}
|
||||
fn tooltip(&self) -> String {
|
||||
"Shape Tool (Y)".into()
|
||||
"Shape Tool".into()
|
||||
}
|
||||
fn tool_type(&self) -> crate::viewport_tools::tool::ToolType {
|
||||
ToolType::Shape
|
||||
|
@ -228,18 +228,21 @@ impl Fsm for ShapeToolFsmState {
|
|||
ShapeToolFsmState::Ready => HintData(vec![HintGroup(vec![
|
||||
HintInfo {
|
||||
key_groups: vec![],
|
||||
key_groups_mac: None,
|
||||
mouse: Some(MouseMotion::LmbDrag),
|
||||
label: String::from("Draw Shape"),
|
||||
plus: false,
|
||||
},
|
||||
HintInfo {
|
||||
key_groups: vec![KeysGroup(vec![Key::KeyShift])],
|
||||
key_groups_mac: None,
|
||||
mouse: None,
|
||||
label: String::from("Constrain 1:1 Aspect"),
|
||||
plus: true,
|
||||
},
|
||||
HintInfo {
|
||||
key_groups: vec![KeysGroup(vec![Key::KeyAlt])],
|
||||
key_groups_mac: None,
|
||||
mouse: None,
|
||||
label: String::from("From Center"),
|
||||
plus: true,
|
||||
|
@ -248,12 +251,14 @@ impl Fsm for ShapeToolFsmState {
|
|||
ShapeToolFsmState::Drawing => HintData(vec![HintGroup(vec![
|
||||
HintInfo {
|
||||
key_groups: vec![KeysGroup(vec![Key::KeyShift])],
|
||||
key_groups_mac: None,
|
||||
mouse: None,
|
||||
label: String::from("Constrain 1:1 Aspect"),
|
||||
plus: false,
|
||||
},
|
||||
HintInfo {
|
||||
key_groups: vec![KeysGroup(vec![Key::KeyAlt])],
|
||||
key_groups_mac: None,
|
||||
mouse: None,
|
||||
label: String::from("From Center"),
|
||||
plus: false,
|
||||
|
|
|
@ -61,7 +61,7 @@ impl SelectedEdges {
|
|||
}
|
||||
|
||||
/// Computes the new bounds with the given mouse move and modifier keys
|
||||
pub fn new_size(&self, mouse: DVec2, transform: DAffine2, center: bool, constrain: bool) -> [DVec2; 2] {
|
||||
pub fn new_size(&self, mouse: DVec2, transform: DAffine2, center: bool, constrain: bool) -> (DVec2, DVec2) {
|
||||
let mouse = transform.inverse().transform_point2(mouse);
|
||||
|
||||
let mut min = self.bounds[0];
|
||||
|
@ -72,7 +72,9 @@ impl SelectedEdges {
|
|||
max.y = mouse.y;
|
||||
}
|
||||
if self.left {
|
||||
min.x = mouse.x
|
||||
let delta = min.x - mouse.x;
|
||||
min.x = mouse.x;
|
||||
max.x += delta;
|
||||
} else if self.right {
|
||||
max.x = mouse.x;
|
||||
}
|
||||
|
@ -96,34 +98,38 @@ impl SelectedEdges {
|
|||
}
|
||||
}
|
||||
|
||||
[min, size]
|
||||
(min, size)
|
||||
}
|
||||
|
||||
/// Offsets the transformation pivot in order to scale from the center
|
||||
fn offset_pivot(&self, center: bool, size: DVec2) -> DVec2 {
|
||||
let mut offset = DVec2::ZERO;
|
||||
|
||||
if center && self.right {
|
||||
if !center {
|
||||
return offset;
|
||||
}
|
||||
|
||||
if self.right {
|
||||
offset.x -= size.x / 2.;
|
||||
}
|
||||
if center && self.left {
|
||||
if self.left {
|
||||
offset.x += size.x / 2.;
|
||||
}
|
||||
if center && self.bottom {
|
||||
if self.bottom {
|
||||
offset.y -= size.y / 2.;
|
||||
}
|
||||
if center && self.top {
|
||||
if self.top {
|
||||
offset.y += size.y / 2.;
|
||||
}
|
||||
offset
|
||||
}
|
||||
|
||||
/// Moves the position to account for centring (only necessary with absolute transforms - e.g. with artboards)
|
||||
pub fn center_position(&self, mut position: DVec2, size: DVec2, center: bool) -> DVec2 {
|
||||
if center && self.right {
|
||||
/// Moves the position to account for centering (only necessary with absolute transforms - e.g. with artboards)
|
||||
pub fn center_position(&self, mut position: DVec2, size: DVec2) -> DVec2 {
|
||||
if self.right {
|
||||
position.x -= size.x / 2.;
|
||||
}
|
||||
if center && self.bottom {
|
||||
if self.bottom {
|
||||
position.y -= size.y / 2.;
|
||||
}
|
||||
|
||||
|
|
|
@ -251,6 +251,7 @@ impl Fsm for SplineToolFsmState {
|
|||
let hint_data = match self {
|
||||
SplineToolFsmState::Ready => HintData(vec![HintGroup(vec![HintInfo {
|
||||
key_groups: vec![],
|
||||
key_groups_mac: None,
|
||||
mouse: Some(MouseMotion::Lmb),
|
||||
label: String::from("Draw Spline"),
|
||||
plus: false,
|
||||
|
@ -258,12 +259,14 @@ impl Fsm for SplineToolFsmState {
|
|||
SplineToolFsmState::Drawing => HintData(vec![
|
||||
HintGroup(vec![HintInfo {
|
||||
key_groups: vec![],
|
||||
key_groups_mac: None,
|
||||
mouse: Some(MouseMotion::Lmb),
|
||||
label: String::from("Extend Spline"),
|
||||
plus: false,
|
||||
}]),
|
||||
HintGroup(vec![HintInfo {
|
||||
key_groups: vec![KeysGroup(vec![Key::KeyEnter])],
|
||||
key_groups_mac: None,
|
||||
mouse: None,
|
||||
label: String::from("End Spline"),
|
||||
plus: false,
|
||||
|
|
|
@ -42,7 +42,7 @@ impl Default for TextOptions {
|
|||
#[remain::sorted]
|
||||
#[impl_message(Message, ToolMessage, Text)]
|
||||
#[derive(PartialEq, Eq, Clone, Debug, Hash, Serialize, Deserialize)]
|
||||
pub enum TextMessage {
|
||||
pub enum TextToolMessage {
|
||||
// Standard messages
|
||||
#[remain::unsorted]
|
||||
Abort,
|
||||
|
@ -74,7 +74,7 @@ impl ToolMetadata for TextTool {
|
|||
"VectorTextTool".into()
|
||||
}
|
||||
fn tooltip(&self) -> String {
|
||||
"Text Tool (T)".into()
|
||||
"Text Tool".into()
|
||||
}
|
||||
fn tool_type(&self) -> crate::viewport_tools::tool::ToolType {
|
||||
ToolType::Text
|
||||
|
@ -90,7 +90,7 @@ impl PropertyHolder for TextTool {
|
|||
font_family: self.options.font_name.clone(),
|
||||
font_style: self.options.font_style.clone(),
|
||||
on_update: WidgetCallback::new(|font_input: &FontInput| {
|
||||
TextMessage::UpdateOptions(TextOptionsUpdate::Font {
|
||||
TextToolMessage::UpdateOptions(TextOptionsUpdate::Font {
|
||||
family: font_input.font_family.clone(),
|
||||
style: font_input.font_style.clone(),
|
||||
})
|
||||
|
@ -107,7 +107,7 @@ impl PropertyHolder for TextTool {
|
|||
font_family: self.options.font_name.clone(),
|
||||
font_style: self.options.font_style.clone(),
|
||||
on_update: WidgetCallback::new(|font_input: &FontInput| {
|
||||
TextMessage::UpdateOptions(TextOptionsUpdate::Font {
|
||||
TextToolMessage::UpdateOptions(TextOptionsUpdate::Font {
|
||||
family: font_input.font_family.clone(),
|
||||
style: font_input.font_style.clone(),
|
||||
})
|
||||
|
@ -125,7 +125,7 @@ impl PropertyHolder for TextTool {
|
|||
value: Some(self.options.font_size as f64),
|
||||
is_integer: true,
|
||||
min: Some(1.),
|
||||
on_update: WidgetCallback::new(|number_input: &NumberInput| TextMessage::UpdateOptions(TextOptionsUpdate::FontSize(number_input.value.unwrap() as u32)).into()),
|
||||
on_update: WidgetCallback::new(|number_input: &NumberInput| TextToolMessage::UpdateOptions(TextOptionsUpdate::FontSize(number_input.value.unwrap() as u32)).into()),
|
||||
..NumberInput::default()
|
||||
})),
|
||||
],
|
||||
|
@ -145,7 +145,7 @@ impl<'a> MessageHandler<ToolMessage, ToolActionHandlerData<'a>> for TextTool {
|
|||
return;
|
||||
}
|
||||
|
||||
if let ToolMessage::Text(TextMessage::UpdateOptions(action)) = action {
|
||||
if let ToolMessage::Text(TextToolMessage::UpdateOptions(action)) = action {
|
||||
match action {
|
||||
TextOptionsUpdate::Font { family, style } => {
|
||||
self.options.font_name = family;
|
||||
|
@ -171,10 +171,10 @@ impl<'a> MessageHandler<ToolMessage, ToolActionHandlerData<'a>> for TextTool {
|
|||
use TextToolFsmState::*;
|
||||
|
||||
match self.fsm_state {
|
||||
Ready => actions!(TextMessageDiscriminant;
|
||||
Ready => actions!(TextToolMessageDiscriminant;
|
||||
Interact,
|
||||
),
|
||||
Editing => actions!(TextMessageDiscriminant;
|
||||
Editing => actions!(TextToolMessageDiscriminant;
|
||||
Interact,
|
||||
Abort,
|
||||
CommitText,
|
||||
|
@ -186,8 +186,8 @@ impl<'a> MessageHandler<ToolMessage, ToolActionHandlerData<'a>> for TextTool {
|
|||
impl ToolTransition for TextTool {
|
||||
fn signal_to_message_map(&self) -> SignalToMessageMap {
|
||||
SignalToMessageMap {
|
||||
document_dirty: Some(TextMessage::DocumentIsDirty.into()),
|
||||
tool_abort: Some(TextMessage::Abort.into()),
|
||||
document_dirty: Some(TextToolMessage::DocumentIsDirty.into()),
|
||||
tool_abort: Some(TextToolMessage::Abort.into()),
|
||||
selection_changed: None,
|
||||
}
|
||||
}
|
||||
|
@ -275,8 +275,8 @@ impl Fsm for TextToolFsmState {
|
|||
tool_options: &Self::ToolOptions,
|
||||
responses: &mut VecDeque<Message>,
|
||||
) -> Self {
|
||||
use TextMessage::*;
|
||||
use TextToolFsmState::*;
|
||||
use TextToolMessage::*;
|
||||
|
||||
if let ToolMessage::Text(event) = event {
|
||||
match (self, event) {
|
||||
|
@ -456,12 +456,14 @@ impl Fsm for TextToolFsmState {
|
|||
TextToolFsmState::Ready => HintData(vec![HintGroup(vec![
|
||||
HintInfo {
|
||||
key_groups: vec![],
|
||||
key_groups_mac: None,
|
||||
mouse: Some(MouseMotion::Lmb),
|
||||
label: String::from("Add Text"),
|
||||
plus: false,
|
||||
},
|
||||
HintInfo {
|
||||
key_groups: vec![],
|
||||
key_groups_mac: None,
|
||||
mouse: Some(MouseMotion::Lmb),
|
||||
label: String::from("Edit Text"),
|
||||
plus: false,
|
||||
|
@ -470,12 +472,14 @@ impl Fsm for TextToolFsmState {
|
|||
TextToolFsmState::Editing => HintData(vec![HintGroup(vec![
|
||||
HintInfo {
|
||||
key_groups: vec![KeysGroup(vec![Key::KeyControl, Key::KeyEnter])],
|
||||
key_groups_mac: Some(vec![KeysGroup(vec![Key::KeyCommand, Key::KeyEnter])]),
|
||||
mouse: None,
|
||||
label: String::from("Commit Edit"),
|
||||
plus: false,
|
||||
},
|
||||
HintInfo {
|
||||
key_groups: vec![KeysGroup(vec![Key::KeyEscape])],
|
||||
key_groups_mac: None,
|
||||
mouse: None,
|
||||
label: String::from("Discard Edit"),
|
||||
plus: false,
|
||||
|
|
3
frontend/assets/icon-12px-solid/keyboard-control.svg
Normal file
3
frontend/assets/icon-12px-solid/keyboard-control.svg
Normal file
|
@ -0,0 +1,3 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 12 12">
|
||||
<polygon points="6,1 1,5 1.6,5.8 6,2.3 10.4,5.8 11,5" />
|
||||
</svg>
|
After Width: | Height: | Size: 126 B |
|
@ -230,6 +230,7 @@ import { createFullscreenState, FullscreenState } from "@/state-providers/fullsc
|
|||
import { createPanelsState, PanelsState } from "@/state-providers/panels";
|
||||
import { createPortfolioState, PortfolioState } from "@/state-providers/portfolio";
|
||||
import { createWorkspaceState, WorkspaceState } from "@/state-providers/workspace";
|
||||
import { operatingSystem } from "@/utility-functions/platform";
|
||||
import { createEditor, Editor } from "@/wasm-communication/editor";
|
||||
|
||||
import MainWindow from "@/components/window/MainWindow.vue";
|
||||
|
@ -293,7 +294,8 @@ export default defineComponent({
|
|||
});
|
||||
|
||||
// Initialize certain setup tasks required by the editor backend to be ready for the user now that the frontend is ready
|
||||
this.editor.instance.init_after_frontend_ready();
|
||||
const platform = operatingSystem();
|
||||
this.editor.instance.init_after_frontend_ready(platform);
|
||||
},
|
||||
beforeUnmount() {
|
||||
// Call the destructor for each manager
|
||||
|
|
|
@ -33,8 +33,7 @@
|
|||
|
||||
<span class="entry-label" :style="{ fontFamily: `${!entry.font ? 'inherit' : entry.value}` }">{{ entry.label }}</span>
|
||||
|
||||
<IconLabel v-if="entry.shortcutRequiresLock && !fullscreen.state.keyboardLocked" :icon="'Info'" :title="keyboardLockInfoMessage" />
|
||||
<UserInputLabel v-else-if="entry.shortcut?.length" :inputKeys="[entry.shortcut]" />
|
||||
<UserInputLabel v-if="entry.shortcut?.keys.length" :inputKeys="[entry.shortcut.keys]" :requiresLock="entry.shortcutRequiresLock" />
|
||||
|
||||
<div class="submenu-arrow" v-if="entry.children?.length"></div>
|
||||
<div class="no-submenu-arrow" v-else></div>
|
||||
|
@ -64,6 +63,10 @@
|
|||
.floating-menu-container .floating-menu-content {
|
||||
padding: 4px 0;
|
||||
|
||||
.separator div {
|
||||
background: var(--color-4-dimgray);
|
||||
}
|
||||
|
||||
.scroll-spacer {
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
|
@ -128,29 +131,24 @@
|
|||
&.open,
|
||||
&.active {
|
||||
background: var(--color-6-lowergray);
|
||||
color: var(--color-f-white);
|
||||
|
||||
&.active {
|
||||
background: var(--color-accent);
|
||||
}
|
||||
|
||||
svg {
|
||||
.entry-icon svg {
|
||||
fill: var(--color-f-white);
|
||||
}
|
||||
|
||||
span {
|
||||
color: var(--color-f-white);
|
||||
}
|
||||
}
|
||||
|
||||
&.disabled {
|
||||
color: var(--color-8-uppergray);
|
||||
|
||||
&:hover {
|
||||
background: none;
|
||||
}
|
||||
|
||||
span {
|
||||
color: var(--color-8-uppergray);
|
||||
}
|
||||
|
||||
svg {
|
||||
fill: var(--color-8-uppergray);
|
||||
}
|
||||
|
@ -172,11 +170,7 @@ import IconLabel from "@/components/widgets/labels/IconLabel.vue";
|
|||
import Separator from "@/components/widgets/labels/Separator.vue";
|
||||
import UserInputLabel from "@/components/widgets/labels/UserInputLabel.vue";
|
||||
|
||||
const KEYBOARD_LOCK_USE_FULLSCREEN = "This hotkey is reserved by the browser, but becomes available in fullscreen mode";
|
||||
const KEYBOARD_LOCK_SWITCH_BROWSER = "This hotkey is reserved by the browser, but becomes available in Chrome, Edge, and Opera which support the Keyboard.lock() API";
|
||||
|
||||
const MenuList = defineComponent({
|
||||
inject: ["fullscreen"],
|
||||
emits: ["update:open", "update:activeEntry", "naturalWidth"],
|
||||
props: {
|
||||
entries: { type: Array as PropType<SectionsOfMenuListEntries>, required: true },
|
||||
|
@ -193,7 +187,6 @@ const MenuList = defineComponent({
|
|||
data() {
|
||||
return {
|
||||
isOpen: this.open,
|
||||
keyboardLockInfoMessage: this.fullscreen.keyboardLockApiSupported ? KEYBOARD_LOCK_USE_FULLSCREEN : KEYBOARD_LOCK_SWITCH_BROWSER,
|
||||
highlighted: this.activeEntry as MenuListEntry | undefined,
|
||||
virtualScrollingEntriesStart: 0,
|
||||
};
|
||||
|
|
|
@ -31,14 +31,18 @@
|
|||
<LayoutRow
|
||||
class="layer"
|
||||
:class="{ selected: listing.entry.layer_metadata.selected }"
|
||||
@click.shift.exact.stop="!listing.editingName && selectLayer(listing.entry, false, true)"
|
||||
@click.shift.ctrl.exact.stop="!listing.editingName && selectLayer(listing.entry, true, true)"
|
||||
@click.ctrl.exact.stop="!listing.editingName && selectLayer(listing.entry, true, false)"
|
||||
@click.exact.stop="!listing.editingName && selectLayer(listing.entry, false, false)"
|
||||
:data-index="index"
|
||||
:title="`${listing.entry.name}${devMode ? '\nLayer Path: ' + listing.entry.path.join(' / ') : ''}` || null"
|
||||
:draggable="draggable"
|
||||
@dragstart="(e) => draggable && dragStart(e, listing.entry)"
|
||||
:title="`${listing.entry.name}\n${devMode ? 'Layer Path: ' + listing.entry.path.join(' / ') : ''}`.trim() || null"
|
||||
@dragstart="(e) => draggable && dragStart(e, listing)"
|
||||
@click.exact="(e) => selectLayer(false, false, false, listing, e)"
|
||||
@click.shift.exact="(e) => selectLayer(false, false, true, listing, e)"
|
||||
@click.ctrl.exact="(e) => selectLayer(true, false, false, listing, e)"
|
||||
@click.ctrl.shift.exact="(e) => selectLayer(true, false, true, listing, e)"
|
||||
@click.meta.exact="(e) => selectLayer(false, true, false, listing, e)"
|
||||
@click.meta.shift.exact="(e) => selectLayer(false, true, true, listing, e)"
|
||||
@click.ctrl.meta="(e) => e.stopPropagation()"
|
||||
@click.alt="(e) => e.stopPropagation()"
|
||||
>
|
||||
<LayoutRow class="layer-type-icon">
|
||||
<IconLabel v-if="listing.entry.layer_type === 'Folder'" :icon="'NodeFolder'" :iconStyle="'Node'" title="Folder" />
|
||||
|
@ -53,10 +57,10 @@
|
|||
:value="listing.entry.name"
|
||||
:placeholder="listing.entry.layer_type"
|
||||
:disabled="!listing.editingName"
|
||||
@change="(e) => onEditLayerNameChange(listing, e.target)"
|
||||
@blur="() => onEditLayerNameDeselect(listing)"
|
||||
@keydown.esc="onEditLayerNameDeselect(listing)"
|
||||
@keydown.enter="(e) => onEditLayerNameChange(listing, e.target)"
|
||||
@keydown.escape="onEditLayerNameDeselect(listing)"
|
||||
@change="(e) => onEditLayerNameChange(listing, e.target)"
|
||||
/>
|
||||
</LayoutRow>
|
||||
<div class="thumbnail" v-html="listing.entry.thumbnail"></div>
|
||||
|
@ -263,6 +267,7 @@
|
|||
<script lang="ts">
|
||||
import { defineComponent, nextTick } from "vue";
|
||||
|
||||
import { operatingSystemIsMac } from "@/utility-functions/platform";
|
||||
import { defaultWidgetLayout, UpdateDocumentLayerTreeStructure, UpdateDocumentLayerDetails, UpdateLayerTreeOptionsLayout, LayerPanelEntry } from "@/wasm-communication/messages";
|
||||
|
||||
import LayoutCol from "@/components/layout/LayoutCol.vue";
|
||||
|
@ -342,8 +347,18 @@ export default defineComponent({
|
|||
await nextTick();
|
||||
window.getSelection()?.removeAllRanges();
|
||||
},
|
||||
async selectLayer(clickedLayer: LayerPanelEntry, ctrl: boolean, shift: boolean) {
|
||||
this.editor.instance.select_layer(clickedLayer.path, ctrl, shift);
|
||||
async selectLayer(ctrl: boolean, cmd: boolean, shift: boolean, listing: LayerListingInfo, event: Event) {
|
||||
if (listing.editingName) return;
|
||||
|
||||
const ctrlOrCmd = operatingSystemIsMac() ? cmd : ctrl;
|
||||
// Pressing the Ctrl key on a Mac, or the Cmd key on another platform, is a violation of the `.exact` qualifier so we filter it out here
|
||||
const opposite = operatingSystemIsMac() ? ctrl : cmd;
|
||||
|
||||
if (!opposite) this.editor.instance.select_layer(listing.entry.path, ctrlOrCmd, shift);
|
||||
|
||||
// We always want to stop propagation so the click event doesn't pass through the layer and cause a deselection by clicking the layer panel background
|
||||
// This is also why we cover the remaining cases not considered by the `.exact` qualifier, in the last two bindings on the layer element, with a `stopPropagation()` call
|
||||
event.stopPropagation();
|
||||
},
|
||||
async deselectAllLayers() {
|
||||
this.editor.instance.deselect_all_layers();
|
||||
|
@ -409,8 +424,9 @@ export default defineComponent({
|
|||
|
||||
return { insertFolder, insertIndex, highlightFolder, markerHeight };
|
||||
},
|
||||
async dragStart(event: DragEvent, layer: LayerPanelEntry) {
|
||||
if (!layer.layer_metadata.selected) this.selectLayer(layer, event.ctrlKey, event.shiftKey);
|
||||
async dragStart(event: DragEvent, listing: LayerListingInfo) {
|
||||
const layer = listing.entry;
|
||||
if (!layer.layer_metadata.selected) this.selectLayer(event.ctrlKey, event.metaKey, event.shiftKey, listing, event);
|
||||
|
||||
// Set style of cursor for drag
|
||||
if (event.dataTransfer) {
|
||||
|
|
|
@ -29,7 +29,8 @@
|
|||
@focus="() => $emit('textFocused')"
|
||||
@blur="() => $emit('textChanged')"
|
||||
@change="() => $emit('textChanged')"
|
||||
@keydown.ctrl.enter="() => $emit('textChanged')"
|
||||
@keydown.ctrl.enter="() => !macKeyboardLayout && $emit('textChanged')"
|
||||
@keydown.meta.enter="() => macKeyboardLayout && $emit('textChanged')"
|
||||
@keydown.esc="() => $emit('cancelTextChange')"
|
||||
></textarea>
|
||||
<label v-if="label" :for="`field-input-${id}`">{{ label }}</label>
|
||||
|
@ -116,6 +117,8 @@
|
|||
<script lang="ts">
|
||||
import { defineComponent, PropType } from "vue";
|
||||
|
||||
import { operatingSystemIsMac } from "@/utility-functions/platform";
|
||||
|
||||
import LayoutRow from "@/components/layout/LayoutRow.vue";
|
||||
|
||||
export default defineComponent({
|
||||
|
@ -130,6 +133,7 @@ export default defineComponent({
|
|||
data() {
|
||||
return {
|
||||
id: `${Math.random()}`.substring(2),
|
||||
macKeyboardLayout: operatingSystemIsMac(),
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
|
|
|
@ -101,7 +101,7 @@ export default defineComponent({
|
|||
...entry,
|
||||
children: entry.children ? menuEntryToFrontendMenuEntry(entry.children) : undefined,
|
||||
action: (): void => this.editor.instance.update_layout(updateMenuBarLayout.layout_target, entry.action.widgetId, undefined),
|
||||
shortcutRequiresLock: entry.shortcut ? shortcutRequiresLock(entry.shortcut) : undefined,
|
||||
shortcutRequiresLock: entry.shortcut?.keys ? shortcutRequiresLock(entry.shortcut.keys) : undefined,
|
||||
}))
|
||||
);
|
||||
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
<template>
|
||||
<LayoutRow class="user-input-label">
|
||||
<IconLabel class="user-input-label keyboard-lock-notice" v-if="displayKeyboardLockNotice" :icon="'Info'" :title="keyboardLockInfoMessage" />
|
||||
<LayoutRow class="user-input-label" v-else>
|
||||
<template v-for="(keyGroup, keyGroupIndex) in inputKeys" :key="keyGroupIndex">
|
||||
<span class="group-gap" v-if="keyGroupIndex > 0"></span>
|
||||
<template v-for="(keyInfo, index) in keyTextOrIconList(keyGroup)" :key="index">
|
||||
|
@ -45,15 +46,14 @@
|
|||
font-family: "Inconsolata", monospace;
|
||||
font-weight: 400;
|
||||
text-align: center;
|
||||
color: var(--color-e-nearwhite);
|
||||
border: 1px;
|
||||
box-sizing: border-box;
|
||||
border-style: solid;
|
||||
border-color: var(--color-7-middlegray);
|
||||
border-radius: 4px;
|
||||
height: 16px;
|
||||
// Firefox renders the text 1px lower than Chrome (tested on Windows) with 16px line-height, so moving it up 1 pixel by using 15px makes them agree
|
||||
line-height: 15px;
|
||||
box-sizing: border-box;
|
||||
border: 1px solid;
|
||||
border-radius: 4px;
|
||||
border-color: var(--color-7-middlegray);
|
||||
color: var(--color-e-nearwhite);
|
||||
|
||||
&.width-16 {
|
||||
width: 16px;
|
||||
|
@ -93,6 +93,40 @@
|
|||
.hint-text {
|
||||
margin-left: 4px;
|
||||
}
|
||||
|
||||
.floating-menu-content & {
|
||||
.input-key {
|
||||
border-color: var(--color-4-dimgray);
|
||||
color: var(--color-8-uppergray);
|
||||
}
|
||||
|
||||
.input-key .icon-label svg,
|
||||
&.keyboard-lock-notice.keyboard-lock-notice svg,
|
||||
.input-mouse .bright {
|
||||
fill: var(--color-8-uppergray);
|
||||
}
|
||||
|
||||
.input-mouse .dim {
|
||||
fill: var(--color-4-dimgray);
|
||||
}
|
||||
}
|
||||
|
||||
.floating-menu-content .row:hover > & {
|
||||
.input-key {
|
||||
border-color: var(--color-7-middlegray);
|
||||
color: var(--color-9-palegray);
|
||||
}
|
||||
|
||||
.input-key .icon-label svg,
|
||||
&.keyboard-lock-notice.keyboard-lock-notice svg,
|
||||
.input-mouse .bright {
|
||||
fill: var(--color-9-palegray);
|
||||
}
|
||||
|
||||
.input-mouse .dim {
|
||||
fill: var(--color-7-middlegray);
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
|
@ -100,92 +134,123 @@
|
|||
import { defineComponent, PropType } from "vue";
|
||||
|
||||
import { IconName } from "@/utility-functions/icons";
|
||||
import { operatingSystemIsMac } from "@/utility-functions/platform";
|
||||
import { HintInfo, KeysGroup } from "@/wasm-communication/messages";
|
||||
|
||||
import LayoutRow from "@/components/layout/LayoutRow.vue";
|
||||
import IconLabel from "@/components/widgets/labels/IconLabel.vue";
|
||||
|
||||
// Definitions
|
||||
const textMap = {
|
||||
Shift: "Shift",
|
||||
Control: "Ctrl",
|
||||
Alt: "Alt",
|
||||
Delete: "Del",
|
||||
PageUp: "PgUp",
|
||||
PageDown: "PgDn",
|
||||
Equals: "=",
|
||||
Minus: "-",
|
||||
Plus: "+",
|
||||
Escape: "Esc",
|
||||
Comma: ",",
|
||||
Period: ".",
|
||||
LeftBracket: "[",
|
||||
RightBracket: "]",
|
||||
LeftCurlyBracket: "{",
|
||||
RightCurlyBracket: "}",
|
||||
};
|
||||
const iconsAndWidthsStandard = {
|
||||
ArrowUp: 1,
|
||||
ArrowRight: 1,
|
||||
ArrowDown: 1,
|
||||
ArrowLeft: 1,
|
||||
Backspace: 2,
|
||||
Enter: 2,
|
||||
Tab: 2,
|
||||
Space: 3,
|
||||
};
|
||||
const iconsAndWidthsMac = {
|
||||
Shift: 2,
|
||||
Control: 2,
|
||||
Option: 2,
|
||||
Command: 2,
|
||||
};
|
||||
|
||||
export default defineComponent({
|
||||
components: {
|
||||
IconLabel,
|
||||
LayoutRow,
|
||||
},
|
||||
inject: ["fullscreen"],
|
||||
props: {
|
||||
inputKeys: { type: Array as PropType<HintInfo["key_groups"]>, default: () => [] },
|
||||
inputKeys: { type: Array as PropType<HintInfo["keyGroups"]>, default: () => [] },
|
||||
inputMouse: { type: String as PropType<HintInfo["mouse"]>, default: null },
|
||||
requiresLock: { type: Boolean as PropType<boolean>, default: false },
|
||||
},
|
||||
computed: {
|
||||
hasSlotContent(): boolean {
|
||||
return Boolean(this.$slots.default);
|
||||
},
|
||||
keyboardLockInfoMessage(): string {
|
||||
const USE_FULLSCREEN = "This hotkey is reserved by the browser, but becomes available in fullscreen mode";
|
||||
const SWITCH_BROWSER = "This hotkey is reserved by the browser, but becomes available in Chrome, Edge, and Opera which support the Keyboard.lock() API";
|
||||
|
||||
return this.fullscreen.keyboardLockApiSupported ? USE_FULLSCREEN : SWITCH_BROWSER;
|
||||
},
|
||||
displayKeyboardLockNotice(): boolean {
|
||||
return this.requiresLock && !this.fullscreen.state.keyboardLocked;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
keyTextOrIconList(keyGroup: KeysGroup): { text: string | null; icon: IconName | null; width: string }[] {
|
||||
return keyGroup.map((inputKey) => this.keyTextOrIcon(inputKey));
|
||||
},
|
||||
keyTextOrIcon(keyText: string): { text: string | null; icon: IconName | null; width: string } {
|
||||
// Definitions
|
||||
const textMap: Record<string, string> = {
|
||||
Control: "Ctrl",
|
||||
Alt: "Alt",
|
||||
Delete: "Del",
|
||||
PageUp: "PgUp",
|
||||
PageDown: "PgDn",
|
||||
Equals: "=",
|
||||
Minus: "-",
|
||||
Plus: "+",
|
||||
Escape: "Esc",
|
||||
Comma: ",",
|
||||
Period: ".",
|
||||
LeftBracket: "[",
|
||||
RightBracket: "]",
|
||||
LeftCurlyBracket: "{",
|
||||
RightCurlyBracket: "}",
|
||||
};
|
||||
const iconsAndWidths: Record<string, number> = {
|
||||
ArrowUp: 1,
|
||||
ArrowRight: 1,
|
||||
ArrowDown: 1,
|
||||
ArrowLeft: 1,
|
||||
Backspace: 2,
|
||||
Command: 2,
|
||||
Enter: 2,
|
||||
Option: 2,
|
||||
Shift: 2,
|
||||
Tab: 2,
|
||||
Space: 3,
|
||||
};
|
||||
keyTextOrIcon(input: string): { text: string | null; icon: IconName | null; width: string } {
|
||||
let keyText = input;
|
||||
if (operatingSystemIsMac()) {
|
||||
keyText = keyText.replace("Alt", "Option");
|
||||
}
|
||||
|
||||
const iconsAndWidths = operatingSystemIsMac() ? { ...iconsAndWidthsStandard, ...iconsAndWidthsMac } : iconsAndWidthsStandard;
|
||||
|
||||
// Strip off the "Key" prefix
|
||||
const text = keyText.replace(/^(?:Key)?(.*)$/, "$1");
|
||||
|
||||
// If it's an icon, return the icon identifier
|
||||
if (text in iconsAndWidths) {
|
||||
if (Object.keys(iconsAndWidths).includes(text)) {
|
||||
// @ts-expect-error This is safe because of the if block we are in
|
||||
const width = iconsAndWidths[text] * 8 + 8;
|
||||
return {
|
||||
text: null,
|
||||
icon: this.keyboardHintIcon(text),
|
||||
width: `width-${iconsAndWidths[text] * 8 + 8}`,
|
||||
width: `width-${width}`,
|
||||
};
|
||||
}
|
||||
|
||||
// Otherwise, return the text string
|
||||
let result;
|
||||
|
||||
// Letters and numbers
|
||||
if (/^[A-Z0-9]$/.test(text)) result = text;
|
||||
if (/^[A-Z0-9]$/.test(text)) {
|
||||
result = text;
|
||||
}
|
||||
// Abbreviated names
|
||||
else if (text in textMap) result = textMap[text];
|
||||
else if (Object.keys(textMap).includes(text)) {
|
||||
// @ts-expect-error This is safe because of the if block we are in
|
||||
result = textMap[text];
|
||||
}
|
||||
// Other
|
||||
else result = text;
|
||||
else {
|
||||
result = text;
|
||||
}
|
||||
|
||||
return { text: result, icon: null, width: `width-${(result || " ").length * 8 + 8}` };
|
||||
},
|
||||
mouseHintIcon(input: HintInfo["mouse"]): IconName {
|
||||
return `MouseHint${input}` as IconName;
|
||||
},
|
||||
keyboardHintIcon(input: HintInfo["key_groups"][0][0]): IconName {
|
||||
keyboardHintIcon(input: HintInfo["keyGroups"][0][0]): IconName {
|
||||
return `Keyboard${input}` as IconName;
|
||||
},
|
||||
},
|
||||
components: {
|
||||
IconLabel,
|
||||
LayoutRow,
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
<Separator :type="'Section'" v-if="index !== 0" />
|
||||
<template v-for="hint in hintGroup" :key="hint">
|
||||
<LayoutRow v-if="hint.plus" class="plus">+</LayoutRow>
|
||||
<UserInputLabel :inputMouse="hint.mouse" :inputKeys="hint.key_groups">{{ hint.label }}</UserInputLabel>
|
||||
<UserInputLabel :inputMouse="hint.mouse" :inputKeys="inputKeysForPlatform(hint)">{{ hint.label }}</UserInputLabel>
|
||||
</template>
|
||||
</template>
|
||||
</LayoutRow>
|
||||
|
@ -44,7 +44,8 @@
|
|||
<script lang="ts">
|
||||
import { defineComponent } from "vue";
|
||||
|
||||
import { HintData, UpdateInputHints } from "@/wasm-communication/messages";
|
||||
import { operatingSystemIsMac } from "@/utility-functions/platform";
|
||||
import { HintData, HintInfo, KeysGroup, UpdateInputHints } from "@/wasm-communication/messages";
|
||||
|
||||
import LayoutRow from "@/components/layout/LayoutRow.vue";
|
||||
import Separator from "@/components/widgets/labels/Separator.vue";
|
||||
|
@ -57,6 +58,12 @@ export default defineComponent({
|
|||
hintData: [] as HintData,
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
inputKeysForPlatform(hint: HintInfo): KeysGroup[] {
|
||||
if (operatingSystemIsMac() && hint.keyGroupsMac) return hint.keyGroupsMac;
|
||||
return hint.keyGroups;
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.editor.subscriptions.subscribeJsMessage(UpdateInputHints, (updateInputHints) => {
|
||||
this.hintData = updateInputHints.hint_data;
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<template>
|
||||
<LayoutRow class="window-buttons-web" @click="() => handleClick()" :title="fullscreen.state.windowFullscreen ? 'Exit Fullscreen (F11)' : 'Enter Fullscreen (F11)'">
|
||||
<LayoutRow class="window-buttons-web" @click="() => handleClick()" :title="(fullscreen.state.windowFullscreen ? 'Exit' : 'Enter') + ' Fullscreen (F11)'">
|
||||
<TextLabel v-if="requestFullscreenHotkeys" :italic="true">Go fullscreen to access all hotkeys</TextLabel>
|
||||
<IconLabel :icon="fullscreen.state.windowFullscreen ? 'FullscreenExit' : 'FullscreenEnter'" />
|
||||
</LayoutRow>
|
||||
|
|
|
@ -45,8 +45,8 @@
|
|||
<Separator :type="'Unrelated'" />
|
||||
</LayoutCol>
|
||||
<LayoutCol>
|
||||
<UserInputLabel :inputKeys="[['KeyControl', 'KeyN']]" />
|
||||
<UserInputLabel :inputKeys="[['KeyControl', 'KeyO']]" />
|
||||
<UserInputLabel :inputKeys="[[controlOrCommandKey(), 'KeyN']]" />
|
||||
<UserInputLabel :inputKeys="[[controlOrCommandKey(), 'KeyO']]" />
|
||||
</LayoutCol>
|
||||
</LayoutRow>
|
||||
</LayoutCol>
|
||||
|
@ -216,6 +216,8 @@
|
|||
<script lang="ts">
|
||||
import { defineComponent, PropType } from "vue";
|
||||
|
||||
import { operatingSystemIsMac } from "@/utility-functions/platform";
|
||||
|
||||
import LayoutCol from "@/components/layout/LayoutCol.vue";
|
||||
import LayoutRow from "@/components/layout/LayoutRow.vue";
|
||||
import Document from "@/components/panels/Document.vue";
|
||||
|
@ -257,6 +259,10 @@ export default defineComponent({
|
|||
openDocument() {
|
||||
this.editor.instance.document_open();
|
||||
},
|
||||
controlOrCommandKey() {
|
||||
// TODO: Remove this by properly feeding these keys from a layout provided by the backend
|
||||
return operatingSystemIsMac() ? "KeyCommand" : "KeyControl";
|
||||
},
|
||||
},
|
||||
components: {
|
||||
LayoutCol,
|
||||
|
|
|
@ -2,6 +2,7 @@ import { DialogState } from "@/state-providers/dialog";
|
|||
import { FullscreenState } from "@/state-providers/fullscreen";
|
||||
import { PortfolioState } from "@/state-providers/portfolio";
|
||||
import { makeKeyboardModifiersBitfield, textInputCleanup, getLatinKey } from "@/utility-functions/keyboard-entry";
|
||||
import { operatingSystemIsMac } from "@/utility-functions/platform";
|
||||
import { stripIndents } from "@/utility-functions/strip-indents";
|
||||
import { Editor } from "@/wasm-communication/editor";
|
||||
import { TriggerPaste } from "@/wasm-communication/messages";
|
||||
|
@ -39,7 +40,7 @@ export function createInputManager(editor: Editor, container: HTMLElement, dialo
|
|||
{ target: window, eventName: "pointerup", action: (e: PointerEvent): void => onPointerUp(e) },
|
||||
{ target: window, eventName: "dblclick", action: (e: PointerEvent): void => onDoubleClick(e) },
|
||||
{ target: window, eventName: "mousedown", action: (e: MouseEvent): void => onMouseDown(e) },
|
||||
{ target: window, eventName: "wheel", action: (e: WheelEvent): void => onMouseScroll(e), options: { passive: false } },
|
||||
{ target: window, eventName: "wheel", action: (e: WheelEvent): void => onWheelScroll(e), options: { passive: false } },
|
||||
{ target: window, eventName: "modifyinputfield", action: (e: CustomEvent): void => onModifyInputField(e) },
|
||||
{ target: window.document.body, eventName: "paste", action: (e: ClipboardEvent): void => onPaste(e) },
|
||||
{
|
||||
|
@ -69,13 +70,14 @@ export function createInputManager(editor: Editor, container: HTMLElement, dialo
|
|||
const key = getLatinKey(e);
|
||||
if (!key) return false;
|
||||
|
||||
// TODO: Switch to a system where everything is sent to the backend, then the input preprocessor makes decisions and kicks some inputs back to the frontend
|
||||
const ctrlOrCmd = operatingSystemIsMac() ? e.metaKey : e.ctrlKey;
|
||||
|
||||
// Don't redirect user input from text entry into HTML elements
|
||||
if (key !== "escape" && !(e.ctrlKey && key === "enter") && targetIsTextField(e.target)) {
|
||||
return false;
|
||||
}
|
||||
if (key !== "escape" && !(ctrlOrCmd && key === "enter") && targetIsTextField(e.target)) return false;
|
||||
|
||||
// Don't redirect paste
|
||||
if (key === "v" && e.ctrlKey) return false;
|
||||
if (key === "v" && ctrlOrCmd) return false;
|
||||
|
||||
// Don't redirect a fullscreen request
|
||||
if (key === "f11" && e.type === "keydown" && !e.repeat) {
|
||||
|
@ -85,13 +87,13 @@ export function createInputManager(editor: Editor, container: HTMLElement, dialo
|
|||
}
|
||||
|
||||
// Don't redirect a reload request
|
||||
if (key === "f5") return false;
|
||||
if (key === "f5" || (ctrlOrCmd && key === "r")) return false;
|
||||
|
||||
// Don't redirect debugging tools
|
||||
if (key === "f12" || key === "f8") return false;
|
||||
if ((e.ctrlKey || e.metaKey) && e.shiftKey && key === "c") return false;
|
||||
if ((e.ctrlKey || e.metaKey) && e.shiftKey && key === "i") return false;
|
||||
if ((e.ctrlKey || e.metaKey) && e.shiftKey && key === "j") return false;
|
||||
if (ctrlOrCmd && e.shiftKey && key === "c") return false;
|
||||
if (ctrlOrCmd && e.shiftKey && key === "i") return false;
|
||||
if (ctrlOrCmd && e.shiftKey && key === "j") return false;
|
||||
|
||||
// Don't redirect tab or enter if not in canvas (to allow navigating elements)
|
||||
if (!canvasFocused && !targetIsTextField(e.target) && ["tab", "enter", " ", "arrowdown", "arrowup", "arrowleft", "arrowright"].includes(key.toLowerCase())) return false;
|
||||
|
@ -200,7 +202,7 @@ export function createInputManager(editor: Editor, container: HTMLElement, dialo
|
|||
if (e.button === 1) e.preventDefault();
|
||||
}
|
||||
|
||||
function onMouseScroll(e: WheelEvent): void {
|
||||
function onWheelScroll(e: WheelEvent): void {
|
||||
const { target } = e;
|
||||
const isTargetingCanvas = target instanceof Element && target.closest("[data-canvas]");
|
||||
|
||||
|
@ -215,7 +217,7 @@ export function createInputManager(editor: Editor, container: HTMLElement, dialo
|
|||
if (isTargetingCanvas) {
|
||||
e.preventDefault();
|
||||
const modifiers = makeKeyboardModifiersBitfield(e);
|
||||
editor.instance.on_mouse_scroll(e.clientX, e.clientY, e.buttons, e.deltaX, e.deltaY, e.deltaZ, modifiers);
|
||||
editor.instance.on_wheel_scroll(e.clientX, e.clientY, e.buttons, e.deltaX, e.deltaY, e.deltaZ, modifiers);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import { TextButtonWidget } from "@/components/widgets/buttons/TextButton";
|
||||
import { DialogState } from "@/state-providers/dialog";
|
||||
import { IconName } from "@/utility-functions/icons";
|
||||
import { browserVersion, operatingSystem } from "@/utility-functions/platform";
|
||||
import { stripIndents } from "@/utility-functions/strip-indents";
|
||||
import { Editor } from "@/wasm-communication/editor";
|
||||
import { DisplayDialogPanic, Widget, WidgetLayout } from "@/wasm-communication/messages";
|
||||
|
@ -68,7 +69,7 @@ function githubUrl(panicDetails: string): string {
|
|||
Provide any further information or context that you think would be helpful in fixing the issue. Screenshots or video can be linked or attached to this issue.
|
||||
|
||||
**Browser and OS**
|
||||
${browserVersion()}, ${operatingSystem()}
|
||||
${browserVersion()}, ${operatingSystem(true).replace("Unknown", "YOUR OPERATING SYSTEM")}
|
||||
|
||||
**Stack Trace**
|
||||
Copied from the crash dialog in the Graphite Editor:
|
||||
|
@ -94,47 +95,3 @@ function githubUrl(panicDetails: string): string {
|
|||
|
||||
return url.toString();
|
||||
}
|
||||
|
||||
function browserVersion(): string {
|
||||
const agent = window.navigator.userAgent;
|
||||
let match = agent.match(/(opera|chrome|safari|firefox|msie|trident(?=\/))\/?\s*(\d+)/i) || [];
|
||||
|
||||
if (/trident/i.test(match[1])) {
|
||||
const browser = /\brv[ :]+(\d+)/g.exec(agent) || [];
|
||||
return `IE ${browser[1] || ""}`.trim();
|
||||
}
|
||||
|
||||
if (match[1] === "Chrome") {
|
||||
let browser = agent.match(/\bEdg\/(\d+)/);
|
||||
if (browser !== null) return `Edge (Chromium) ${browser[1]}`;
|
||||
|
||||
browser = agent.match(/\bOPR\/(\d+)/);
|
||||
if (browser !== null) return `Opera ${browser[1]}`;
|
||||
}
|
||||
|
||||
match = match[2] ? [match[1], match[2]] : [navigator.appName, navigator.appVersion, "-?"];
|
||||
|
||||
const browser = agent.match(/version\/(\d+)/i);
|
||||
if (browser !== null) match.splice(1, 1, browser[1]);
|
||||
|
||||
return `${match[0]} ${match[1]}`;
|
||||
}
|
||||
|
||||
function operatingSystem(): string {
|
||||
const osTable: Record<string, string> = {
|
||||
"Windows NT 10": "Windows 10 or 11",
|
||||
"Windows NT 6.3": "Windows 8.1",
|
||||
"Windows NT 6.2": "Windows 8",
|
||||
"Windows NT 6.1": "Windows 7",
|
||||
"Windows NT 6.0": "Windows Vista",
|
||||
"Windows NT 5.1": "Windows XP",
|
||||
"Windows NT 5.0": "Windows 2000",
|
||||
Mac: "Mac",
|
||||
X11: "Unix",
|
||||
Linux: "Linux",
|
||||
Unknown: "YOUR OPERATING SYSTEM",
|
||||
};
|
||||
|
||||
const userAgentOS = Object.keys(osTable).find((key) => window.navigator.userAgent.includes(key));
|
||||
return osTable[userAgentOS || "Unknown"];
|
||||
}
|
||||
|
|
|
@ -14,10 +14,8 @@ import App from "@/App.vue";
|
|||
const body = document.body;
|
||||
const message = stripIndents`
|
||||
<style>
|
||||
h2, p, a {
|
||||
text-align: center;
|
||||
color: white;
|
||||
}
|
||||
h2, p, a { text-align: center; color: white; }
|
||||
#app { display: none; }
|
||||
</style>
|
||||
<h2>This browser is too old</h2>
|
||||
<p>Please upgrade to a modern web browser such as the latest Firefox, Chrome, Edge, or Safari version 15 or newer.</p>
|
||||
|
|
|
@ -21,6 +21,7 @@ import KeyboardArrowRight from "@/../assets/icon-12px-solid/keyboard-arrow-right
|
|||
import KeyboardArrowUp from "@/../assets/icon-12px-solid/keyboard-arrow-up.svg";
|
||||
import KeyboardBackspace from "@/../assets/icon-12px-solid/keyboard-backspace.svg";
|
||||
import KeyboardCommand from "@/../assets/icon-12px-solid/keyboard-command.svg";
|
||||
import KeyboardControl from "@/../assets/icon-12px-solid/keyboard-control.svg";
|
||||
import KeyboardEnter from "@/../assets/icon-12px-solid/keyboard-enter.svg";
|
||||
import KeyboardOption from "@/../assets/icon-12px-solid/keyboard-option.svg";
|
||||
import KeyboardShift from "@/../assets/icon-12px-solid/keyboard-shift.svg";
|
||||
|
@ -52,6 +53,7 @@ const SOLID_12PX = {
|
|||
KeyboardArrowUp: { component: KeyboardArrowUp, size: 12 },
|
||||
KeyboardBackspace: { component: KeyboardBackspace, size: 12 },
|
||||
KeyboardCommand: { component: KeyboardCommand, size: 12 },
|
||||
KeyboardControl: { component: KeyboardControl, size: 12 },
|
||||
KeyboardEnter: { component: KeyboardEnter, size: 12 },
|
||||
KeyboardOption: { component: KeyboardOption, size: 12 },
|
||||
KeyboardShift: { component: KeyboardShift, size: 12 },
|
||||
|
|
|
@ -1,5 +1,14 @@
|
|||
export function makeKeyboardModifiersBitfield(e: WheelEvent | PointerEvent | KeyboardEvent): number {
|
||||
return Number(e.ctrlKey) | (Number(e.shiftKey) << 1) | (Number(e.altKey) << 2);
|
||||
return (
|
||||
// Shift (all platforms)
|
||||
(Number(e.shiftKey) << 0) |
|
||||
// Alt (all platforms, also called Option on Mac)
|
||||
(Number(e.altKey) << 1) |
|
||||
// Control (all platforms)
|
||||
(Number(e.ctrlKey) << 2) |
|
||||
// Meta (Windows/Linux) or Command (Mac)
|
||||
(Number(e.metaKey) << 3)
|
||||
);
|
||||
}
|
||||
|
||||
// Necessary because innerText puts an extra newline character at the end when the text is more than one line.
|
||||
|
@ -13,7 +22,7 @@ export function getLatinKey(e: KeyboardEvent): string | null {
|
|||
const key = e.key.toLowerCase();
|
||||
const isPrintable = !ALL_PRINTABLE_KEYS.has(e.key);
|
||||
|
||||
// Control (non-printable) characters are handled normally
|
||||
// Control characters (those which are non-printable) are handled normally
|
||||
if (!isPrintable) return key;
|
||||
|
||||
// These non-Latin characters should fall back to the Latin equivalent at the key location
|
||||
|
|
54
frontend/src/utility-functions/platform.ts
Normal file
54
frontend/src/utility-functions/platform.ts
Normal file
|
@ -0,0 +1,54 @@
|
|||
export function browserVersion(): string {
|
||||
const agent = window.navigator.userAgent;
|
||||
let match = agent.match(/(opera|chrome|safari|firefox|msie|trident(?=\/))\/?\s*(\d+)/i) || [];
|
||||
|
||||
if (/trident/i.test(match[1])) {
|
||||
const browser = /\brv[ :]+(\d+)/g.exec(agent) || [];
|
||||
return `IE ${browser[1] || ""}`.trim();
|
||||
}
|
||||
|
||||
if (match[1] === "Chrome") {
|
||||
let browser = agent.match(/\bEdg\/(\d+)/);
|
||||
if (browser !== null) return `Edge (Chromium) ${browser[1]}`;
|
||||
|
||||
browser = agent.match(/\bOPR\/(\d+)/);
|
||||
if (browser !== null) return `Opera ${browser[1]}`;
|
||||
}
|
||||
|
||||
match = match[2] ? [match[1], match[2]] : [navigator.appName, navigator.appVersion, "-?"];
|
||||
|
||||
const browser = agent.match(/version\/(\d+)/i);
|
||||
if (browser !== null) match.splice(1, 1, browser[1]);
|
||||
|
||||
return `${match[0]} ${match[1]}`;
|
||||
}
|
||||
|
||||
export function operatingSystem(detailed = false): string {
|
||||
const osTableDetailed: Record<string, string> = {
|
||||
"Windows NT 10": "Windows 10 or 11",
|
||||
"Windows NT 6.3": "Windows 8.1",
|
||||
"Windows NT 6.2": "Windows 8",
|
||||
"Windows NT 6.1": "Windows 7",
|
||||
"Windows NT 6.0": "Windows Vista",
|
||||
"Windows NT 5.1": "Windows XP",
|
||||
"Windows NT 5.0": "Windows 2000",
|
||||
Mac: "Mac",
|
||||
X11: "Unix",
|
||||
Linux: "Linux",
|
||||
Unknown: "Unknown",
|
||||
};
|
||||
const osTableSimple: Record<string, string> = {
|
||||
Windows: "Windows",
|
||||
Mac: "Mac",
|
||||
Linux: "Linux",
|
||||
Unknown: "Unknown",
|
||||
};
|
||||
const osTable = detailed ? osTableDetailed : osTableSimple;
|
||||
|
||||
const userAgentOS = Object.keys(osTable).find((key) => window.navigator.userAgent.includes(key));
|
||||
return osTable[userAgentOS || "Unknown"];
|
||||
}
|
||||
|
||||
export function operatingSystemIsMac(): boolean {
|
||||
return operatingSystem() === "Mac";
|
||||
}
|
|
@ -79,7 +79,9 @@ export type HintData = HintGroup[];
|
|||
export type HintGroup = HintInfo[];
|
||||
|
||||
export class HintInfo {
|
||||
readonly key_groups!: KeysGroup[];
|
||||
readonly keyGroups!: KeysGroup[];
|
||||
|
||||
readonly keyGroupsMac!: KeysGroup[] | null;
|
||||
|
||||
readonly mouse!: MouseMotion | null;
|
||||
|
||||
|
@ -404,12 +406,14 @@ export class ColorInput extends WidgetProps {
|
|||
tooltip!: string;
|
||||
}
|
||||
|
||||
export type Keys = { keys: string[] };
|
||||
|
||||
export interface MenuListEntryData<Value = string> {
|
||||
value?: Value;
|
||||
label?: string;
|
||||
icon?: IconName;
|
||||
font?: URL;
|
||||
shortcut?: string[];
|
||||
shortcut?: Keys;
|
||||
shortcutRequiresLock?: boolean;
|
||||
disabled?: boolean;
|
||||
action?: () => void;
|
||||
|
@ -781,7 +785,7 @@ export type MenuColumn = {
|
|||
};
|
||||
|
||||
export type MenuEntry = {
|
||||
shortcut: string[] | undefined;
|
||||
shortcut: Keys | undefined;
|
||||
action: Widget;
|
||||
label: string;
|
||||
icon: string | undefined;
|
||||
|
|
|
@ -79,7 +79,7 @@ function formatThirdPartyLicenses(jsLicenses) {
|
|||
if (process.env.NODE_ENV === "production" && process.env.SKIP_CARGO_ABOUT === undefined) {
|
||||
try {
|
||||
rustLicenses = generateRustLicenses();
|
||||
} catch (e) {
|
||||
} catch (err) {
|
||||
// Nothing to show. Error messages were printed above.
|
||||
}
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
name = "graphite-wasm"
|
||||
publish = false
|
||||
version = "0.0.0"
|
||||
rust-version = "1.56.0"
|
||||
rust-version = "1.62.0"
|
||||
authors = ["Graphite Authors <contact@graphite.rs>"]
|
||||
edition = "2021"
|
||||
readme = "../../README.md"
|
||||
|
|
|
@ -6,6 +6,7 @@ use crate::helpers::{translate_key, Error};
|
|||
use crate::{EDITOR_HAS_CRASHED, EDITOR_INSTANCES, JS_EDITOR_HANDLES};
|
||||
|
||||
use editor::consts::{FILE_SAVE_SUFFIX, GRAPHITE_DOCUMENT_VERSION};
|
||||
use editor::document::utility_types::Platform;
|
||||
use editor::input::input_preprocessor::ModifierKeys;
|
||||
use editor::input::mouse::{EditorMouseState, ScrollDelta, ViewportBounds};
|
||||
use editor::message_prelude::*;
|
||||
|
@ -105,7 +106,15 @@ impl JsEditorHandle {
|
|||
// the backend from the web frontend.
|
||||
// ========================================================================
|
||||
|
||||
pub fn init_after_frontend_ready(&self) {
|
||||
pub fn init_after_frontend_ready(&self, platform: String) {
|
||||
let platform = match platform.as_str() {
|
||||
"Windows" => Platform::Windows,
|
||||
"Mac" => Platform::Mac,
|
||||
"Linux" => Platform::Linux,
|
||||
_ => Platform::Unknown,
|
||||
};
|
||||
|
||||
self.dispatch(PortfolioMessage::SetPlatform { platform });
|
||||
self.dispatch(Message::Init);
|
||||
}
|
||||
|
||||
|
@ -217,13 +226,13 @@ impl JsEditorHandle {
|
|||
}
|
||||
|
||||
/// Mouse scrolling within the screenspace bounds of the viewport
|
||||
pub fn on_mouse_scroll(&self, x: f64, y: f64, mouse_keys: u8, wheel_delta_x: i32, wheel_delta_y: i32, wheel_delta_z: i32, modifiers: u8) {
|
||||
pub fn on_wheel_scroll(&self, x: f64, y: f64, mouse_keys: u8, wheel_delta_x: i32, wheel_delta_y: i32, wheel_delta_z: i32, modifiers: u8) {
|
||||
let mut editor_mouse_state = EditorMouseState::from_keys_and_editor_position(mouse_keys, (x, y).into());
|
||||
editor_mouse_state.scroll_delta = ScrollDelta::new(wheel_delta_x, wheel_delta_y, wheel_delta_z);
|
||||
|
||||
let modifier_keys = ModifierKeys::from_bits(modifiers).expect("Invalid modifier keys");
|
||||
|
||||
let message = InputPreprocessorMessage::MouseScroll { editor_mouse_state, modifier_keys };
|
||||
let message = InputPreprocessorMessage::WheelScroll { editor_mouse_state, modifier_keys };
|
||||
self.dispatch(message);
|
||||
}
|
||||
|
||||
|
@ -280,7 +289,7 @@ impl JsEditorHandle {
|
|||
|
||||
/// A text box was committed
|
||||
pub fn on_change_text(&self, new_text: String) -> Result<(), JsValue> {
|
||||
let message = TextMessage::TextChange { new_text };
|
||||
let message = TextToolMessage::TextChange { new_text };
|
||||
self.dispatch(message);
|
||||
|
||||
Ok(())
|
||||
|
@ -302,7 +311,7 @@ impl JsEditorHandle {
|
|||
|
||||
/// A text box was changed
|
||||
pub fn update_bounds(&self, new_text: String) -> Result<(), JsValue> {
|
||||
let message = TextMessage::UpdateBounds { new_text };
|
||||
let message = TextToolMessage::UpdateBounds { new_text };
|
||||
self.dispatch(message);
|
||||
|
||||
Ok(())
|
||||
|
|
|
@ -122,6 +122,7 @@ pub fn translate_key(name: &str) -> Key {
|
|||
"capslock" => KeyShift,
|
||||
" " => KeySpace,
|
||||
"control" => KeyControl,
|
||||
"command" => KeyCommand,
|
||||
"delete" => KeyDelete,
|
||||
"backspace" => KeyBackspace,
|
||||
"alt" => KeyAlt,
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
name = "graphite-graphene"
|
||||
publish = false
|
||||
version = "0.0.0"
|
||||
rust-version = "1.56.0"
|
||||
rust-version = "1.62.0"
|
||||
authors = ["Graphite Authors <contact@graphite.rs>"]
|
||||
edition = "2021"
|
||||
readme = "../README.md"
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
name = "graphite-proc-macros"
|
||||
publish = false
|
||||
version = "0.0.0"
|
||||
rust-version = "1.56.0"
|
||||
rust-version = "1.62.0"
|
||||
authors = ["Graphite Authors <contact@graphite.rs>"]
|
||||
edition = "2021"
|
||||
readme = "../README.md"
|
||||
|
@ -14,6 +14,10 @@ license = "Apache-2.0"
|
|||
path = "src/lib.rs"
|
||||
proc-macro = true
|
||||
|
||||
[features]
|
||||
default = ["serde-discriminant"]
|
||||
serde-discriminant = []
|
||||
|
||||
[dependencies]
|
||||
proc-macro2 = "1.0.26"
|
||||
syn = { version = "1.0.68", features = ["full"] }
|
||||
|
@ -22,3 +26,6 @@ quote = "1.0.9"
|
|||
[dev-dependencies.editor]
|
||||
path = "../editor"
|
||||
package = "graphite-editor"
|
||||
|
||||
[dev-dependencies]
|
||||
serde = "1"
|
||||
|
|
|
@ -112,8 +112,16 @@ pub fn derive_discriminant_impl(input_item: TokenStream) -> syn::Result<TokenStr
|
|||
)
|
||||
})
|
||||
.unzip::<_, _, Vec<_>, Vec<_>>();
|
||||
#[cfg(feature = "serde-discriminant")]
|
||||
let serde = quote::quote! {
|
||||
#[derive(serde::Serialize, serde::Deserialize)]
|
||||
};
|
||||
|
||||
#[cfg(not(feature = "serde-discriminant"))]
|
||||
let serde = quote::quote! {};
|
||||
|
||||
let res = quote::quote! {
|
||||
#serde
|
||||
#discriminant
|
||||
|
||||
impl ToDiscriminant for #input_type {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue