mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2025-08-04 05:18:19 +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",
|
"graphite-editor",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
|
"serde",
|
||||||
"syn",
|
"syn",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
name = "bezier-rs-wasm"
|
name = "bezier-rs-wasm"
|
||||||
publish = false
|
publish = false
|
||||||
version = "0.0.0"
|
version = "0.0.0"
|
||||||
rust-version = "1.56.0"
|
rust-version = "1.62.0"
|
||||||
authors = ["Graphite Authors <contact@graphite.rs>"]
|
authors = ["Graphite Authors <contact@graphite.rs>"]
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
readme = "../../README.md"
|
readme = "../../README.md"
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
name = "bezier-rs"
|
name = "bezier-rs"
|
||||||
publish = false
|
publish = false
|
||||||
version = "0.0.0"
|
version = "0.0.0"
|
||||||
rust-version = "1.56.0"
|
rust-version = "1.62.0"
|
||||||
authors = ["Graphite Authors <contact@graphite.rs>"]
|
authors = ["Graphite Authors <contact@graphite.rs>"]
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
readme = "./README.md"
|
readme = "./README.md"
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
name = "graphite-editor"
|
name = "graphite-editor"
|
||||||
publish = false
|
publish = false
|
||||||
version = "0.0.0"
|
version = "0.0.0"
|
||||||
rust-version = "1.56.0"
|
rust-version = "1.62.0"
|
||||||
authors = ["Graphite Authors <contact@graphite.rs>"]
|
authors = ["Graphite Authors <contact@graphite.rs>"]
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
readme = "../README.md"
|
readme = "../README.md"
|
||||||
|
|
|
@ -138,14 +138,25 @@ impl Dispatcher {
|
||||||
}
|
}
|
||||||
InputMapper(message) => {
|
InputMapper(message) => {
|
||||||
let actions = self.collect_actions();
|
let actions = self.collect_actions();
|
||||||
|
let keyboard_platform = self.message_handlers.portfolio_message_handler.platform.as_keyboard_platform_layout();
|
||||||
|
|
||||||
self.message_handlers
|
self.message_handlers
|
||||||
.input_mapper_message_handler
|
.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) => {
|
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) => {
|
Portfolio(message) => {
|
||||||
self.message_handlers
|
self.message_handlers
|
||||||
.portfolio_message_handler
|
.portfolio_message_handler
|
||||||
|
@ -518,15 +529,15 @@ mod test {
|
||||||
replacement_selected_layers: sorted_layers[..2].to_vec(),
|
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());
|
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<_>>());
|
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());
|
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<_>>());
|
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());
|
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<_>>());
|
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 ROTATE_SNAP_ANGLE: f64 = 15.;
|
||||||
pub const SCALE_SNAP_INTERVAL: f64 = 0.1;
|
pub const SCALE_SNAP_INTERVAL: f64 = 0.1;
|
||||||
pub const SLOWING_DIVISOR: f64 = 10.;
|
pub const SLOWING_DIVISOR: f64 = 10.;
|
||||||
|
pub const NUDGE_AMOUNT: f64 = 1.;
|
||||||
|
pub const BIG_NUDGE_AMOUNT: f64 = 10.;
|
||||||
|
|
||||||
// Select tool
|
// Select tool
|
||||||
pub const SELECTION_TOLERANCE: f64 = 5.;
|
pub const SELECTION_TOLERANCE: f64 = 5.;
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
use crate::message_prelude::*;
|
|
||||||
use serde::{Deserialize, Serialize};
|
|
||||||
|
|
||||||
use super::{ExportDialogUpdate, NewDocumentDialogUpdate};
|
use super::{ExportDialogUpdate, NewDocumentDialogUpdate};
|
||||||
|
use crate::message_prelude::*;
|
||||||
|
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
#[remain::sorted]
|
#[remain::sorted]
|
||||||
#[impl_message(Message, Dialog)]
|
#[impl_message(Message, Dialog)]
|
||||||
|
|
|
@ -103,12 +103,16 @@ pub enum DocumentMessage {
|
||||||
new_name: String,
|
new_name: String,
|
||||||
},
|
},
|
||||||
RenderDocument,
|
RenderDocument,
|
||||||
ReorderSelectedLayers {
|
|
||||||
relative_index_offset: isize,
|
|
||||||
},
|
|
||||||
RollbackTransaction,
|
RollbackTransaction,
|
||||||
SaveDocument,
|
SaveDocument,
|
||||||
SelectAllLayers,
|
SelectAllLayers,
|
||||||
|
SelectedLayersLower,
|
||||||
|
SelectedLayersLowerToBack,
|
||||||
|
SelectedLayersRaise,
|
||||||
|
SelectedLayersRaiseToFront,
|
||||||
|
SelectedLayersReorder {
|
||||||
|
relative_index_offset: isize,
|
||||||
|
},
|
||||||
SelectLayer {
|
SelectLayer {
|
||||||
layer_path: Vec<LayerId>,
|
layer_path: Vec<LayerId>,
|
||||||
ctrl: bool,
|
ctrl: bool,
|
||||||
|
|
|
@ -7,6 +7,7 @@ use super::{vectorize_layer_metadata, PropertiesPanelMessageHandler};
|
||||||
use super::{ArtboardMessageHandler, MovementMessageHandler, OverlaysMessageHandler, TransformLayerMessageHandler};
|
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::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::frontend::utility_types::{FileType, FrontendImageData};
|
||||||
|
use crate::input::input_mapper::action_keys::action_shortcut;
|
||||||
use crate::input::InputPreprocessorMessageHandler;
|
use crate::input::InputPreprocessorMessageHandler;
|
||||||
use crate::layout::layout_message::LayoutTarget;
|
use crate::layout::layout_message::LayoutTarget;
|
||||||
use crate::layout::widgets::{
|
use crate::layout::widgets::{
|
||||||
|
@ -549,6 +550,7 @@ impl DocumentMessageHandler {
|
||||||
icon: "Snapping".into(),
|
icon: "Snapping".into(),
|
||||||
tooltip: "Snapping".into(),
|
tooltip: "Snapping".into(),
|
||||||
on_update: WidgetCallback::new(|optional_input: &OptionalInput| DocumentMessage::SetSnapping { snap: optional_input.checked }.into()),
|
on_update: WidgetCallback::new(|optional_input: &OptionalInput| DocumentMessage::SetSnapping { snap: optional_input.checked }.into()),
|
||||||
|
..Default::default()
|
||||||
})),
|
})),
|
||||||
WidgetHolder::new(Widget::PopoverButton(PopoverButton {
|
WidgetHolder::new(Widget::PopoverButton(PopoverButton {
|
||||||
header: "Snapping".into(),
|
header: "Snapping".into(),
|
||||||
|
@ -564,6 +566,7 @@ impl DocumentMessageHandler {
|
||||||
icon: "Grid".into(),
|
icon: "Grid".into(),
|
||||||
tooltip: "Grid".into(),
|
tooltip: "Grid".into(),
|
||||||
on_update: WidgetCallback::new(|_| DialogMessage::RequestComingSoonDialog { issue: Some(318) }.into()),
|
on_update: WidgetCallback::new(|_| DialogMessage::RequestComingSoonDialog { issue: Some(318) }.into()),
|
||||||
|
..Default::default()
|
||||||
})),
|
})),
|
||||||
WidgetHolder::new(Widget::PopoverButton(PopoverButton {
|
WidgetHolder::new(Widget::PopoverButton(PopoverButton {
|
||||||
header: "Grid".into(),
|
header: "Grid".into(),
|
||||||
|
@ -579,6 +582,7 @@ impl DocumentMessageHandler {
|
||||||
icon: "Overlays".into(),
|
icon: "Overlays".into(),
|
||||||
tooltip: "Overlays".into(),
|
tooltip: "Overlays".into(),
|
||||||
on_update: WidgetCallback::new(|optional_input: &OptionalInput| DocumentMessage::SetOverlaysVisibility { visible: optional_input.checked }.into()),
|
on_update: WidgetCallback::new(|optional_input: &OptionalInput| DocumentMessage::SetOverlaysVisibility { visible: optional_input.checked }.into()),
|
||||||
|
..Default::default()
|
||||||
})),
|
})),
|
||||||
WidgetHolder::new(Widget::PopoverButton(PopoverButton {
|
WidgetHolder::new(Widget::PopoverButton(PopoverButton {
|
||||||
header: "Overlays".into(),
|
header: "Overlays".into(),
|
||||||
|
@ -841,14 +845,16 @@ impl DocumentMessageHandler {
|
||||||
})),
|
})),
|
||||||
WidgetHolder::new(Widget::IconButton(IconButton {
|
WidgetHolder::new(Widget::IconButton(IconButton {
|
||||||
icon: "NodeFolder".into(),
|
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,
|
size: 24,
|
||||||
on_update: WidgetCallback::new(|_| DocumentMessage::CreateEmptyFolder { container_path: vec![] }.into()),
|
on_update: WidgetCallback::new(|_| DocumentMessage::CreateEmptyFolder { container_path: vec![] }.into()),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
})),
|
})),
|
||||||
WidgetHolder::new(Widget::IconButton(IconButton {
|
WidgetHolder::new(Widget::IconButton(IconButton {
|
||||||
icon: "Trash".into(),
|
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,
|
size: 24,
|
||||||
on_update: WidgetCallback::new(|_| DocumentMessage::DeleteSelectedLayers.into()),
|
on_update: WidgetCallback::new(|_| DocumentMessage::DeleteSelectedLayers.into()),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
|
@ -864,6 +870,62 @@ impl DocumentMessageHandler {
|
||||||
.into(),
|
.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 {
|
impl MessageHandler<DocumentMessage, (&InputPreprocessorMessageHandler, &FontCache)> for DocumentMessageHandler {
|
||||||
|
@ -1318,61 +1380,6 @@ impl MessageHandler<DocumentMessage, (&InputPreprocessorMessageHandler, &FontCac
|
||||||
.into(),
|
.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 => {
|
RollbackTransaction => {
|
||||||
self.rollback(responses).unwrap_or_else(|e| log::warn!("{}", e));
|
self.rollback(responses).unwrap_or_else(|e| log::warn!("{}", e));
|
||||||
responses.extend([RenderDocument.into(), DocumentStructureChanged.into()]);
|
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();
|
let all = self.all_layers().map(|path| path.to_vec()).collect();
|
||||||
responses.push_front(SetSelectedLayers { replacement_selected_layers: all }.into());
|
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 } => {
|
SelectLayer { layer_path, ctrl, shift } => {
|
||||||
let mut paths = vec![];
|
let mut paths = vec![];
|
||||||
let last_selection_exists = !self.layer_range_selection_reference.is_empty();
|
let last_selection_exists = !self.layer_range_selection_reference.is_empty();
|
||||||
|
@ -1611,7 +1633,10 @@ impl MessageHandler<DocumentMessage, (&InputPreprocessorMessageHandler, &FontCac
|
||||||
DeleteSelectedLayers,
|
DeleteSelectedLayers,
|
||||||
DuplicateSelectedLayers,
|
DuplicateSelectedLayers,
|
||||||
NudgeSelectedLayers,
|
NudgeSelectedLayers,
|
||||||
ReorderSelectedLayers,
|
SelectedLayersLower,
|
||||||
|
SelectedLayersLowerToBack,
|
||||||
|
SelectedLayersRaise,
|
||||||
|
SelectedLayersRaiseToFront,
|
||||||
GroupSelectedLayers,
|
GroupSelectedLayers,
|
||||||
UngroupSelectedLayers,
|
UngroupSelectedLayers,
|
||||||
);
|
);
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
use super::MenuBarMessage;
|
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::layout_message::LayoutTarget;
|
||||||
use crate::layout::widgets::*;
|
use crate::layout::widgets::*;
|
||||||
use crate::message_prelude::*;
|
use crate::message_prelude::*;
|
||||||
|
@ -30,31 +30,31 @@ impl PropertyHolder for MenuBarMessageHandler {
|
||||||
Layout::MenuLayout(MenuLayout::new(vec![
|
Layout::MenuLayout(MenuLayout::new(vec![
|
||||||
MenuColumn {
|
MenuColumn {
|
||||||
label: "File".into(),
|
label: "File".into(),
|
||||||
children: vec![
|
children: MenuEntryGroups(vec![
|
||||||
vec![
|
vec![
|
||||||
MenuEntry {
|
MenuEntry {
|
||||||
label: "New…".into(),
|
label: "New…".into(),
|
||||||
icon: Some("File".into()),
|
icon: Some("File".into()),
|
||||||
action: MenuEntry::create_action(|_| DialogMessage::RequestNewDocumentDialog.into()),
|
action: MenuEntry::create_action(|_| DialogMessage::RequestNewDocumentDialog.into()),
|
||||||
shortcut: Some(vec![Key::KeyControl, Key::KeyN]),
|
shortcut: action_shortcut!(DialogMessageDiscriminant::RequestNewDocumentDialog),
|
||||||
children: None,
|
children: MenuEntryGroups::empty(),
|
||||||
},
|
},
|
||||||
MenuEntry {
|
MenuEntry {
|
||||||
label: "Open…".into(),
|
label: "Open…".into(),
|
||||||
shortcut: Some(vec![Key::KeyControl, Key::KeyO]),
|
shortcut: action_shortcut!(PortfolioMessageDiscriminant::OpenDocument),
|
||||||
action: MenuEntry::create_action(|_| PortfolioMessage::OpenDocument.into()),
|
action: MenuEntry::create_action(|_| PortfolioMessage::OpenDocument.into()),
|
||||||
..MenuEntry::default()
|
..MenuEntry::default()
|
||||||
},
|
},
|
||||||
MenuEntry {
|
MenuEntry {
|
||||||
label: "Open Recent".into(),
|
label: "Open Recent".into(),
|
||||||
shortcut: Some(vec![Key::KeyControl, Key::KeyShift, Key::KeyO]),
|
shortcut: None,
|
||||||
action: MenuEntry::no_action(),
|
action: MenuEntry::no_action(),
|
||||||
icon: None,
|
icon: None,
|
||||||
children: Some(vec![
|
children: MenuEntryGroups(vec![
|
||||||
vec![
|
vec![
|
||||||
MenuEntry {
|
MenuEntry {
|
||||||
label: "Reopen Last Closed".into(),
|
label: "Reopen Last Closed".into(),
|
||||||
shortcut: Some(vec![Key::KeyControl, Key::KeyShift, Key::KeyT]),
|
// shortcut: [Key::KeyControl, Key::KeyShift, Key::KeyT],
|
||||||
..MenuEntry::default()
|
..MenuEntry::default()
|
||||||
},
|
},
|
||||||
MenuEntry {
|
MenuEntry {
|
||||||
|
@ -90,13 +90,13 @@ impl PropertyHolder for MenuBarMessageHandler {
|
||||||
vec![
|
vec![
|
||||||
MenuEntry {
|
MenuEntry {
|
||||||
label: "Close".into(),
|
label: "Close".into(),
|
||||||
shortcut: Some(vec![Key::KeyControl, Key::KeyW]),
|
shortcut: action_shortcut!(PortfolioMessageDiscriminant::CloseActiveDocumentWithConfirmation),
|
||||||
action: MenuEntry::create_action(|_| PortfolioMessage::CloseActiveDocumentWithConfirmation.into()),
|
action: MenuEntry::create_action(|_| PortfolioMessage::CloseActiveDocumentWithConfirmation.into()),
|
||||||
..MenuEntry::default()
|
..MenuEntry::default()
|
||||||
},
|
},
|
||||||
MenuEntry {
|
MenuEntry {
|
||||||
label: "Close All".into(),
|
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()),
|
action: MenuEntry::create_action(|_| DialogMessage::CloseAllDocumentsWithConfirmation.into()),
|
||||||
..MenuEntry::default()
|
..MenuEntry::default()
|
||||||
},
|
},
|
||||||
|
@ -104,19 +104,18 @@ impl PropertyHolder for MenuBarMessageHandler {
|
||||||
vec![
|
vec![
|
||||||
MenuEntry {
|
MenuEntry {
|
||||||
label: "Save".into(),
|
label: "Save".into(),
|
||||||
shortcut: Some(vec![Key::KeyControl, Key::KeyS]),
|
shortcut: action_shortcut!(DocumentMessageDiscriminant::SaveDocument),
|
||||||
action: MenuEntry::create_action(|_| DocumentMessage::SaveDocument.into()),
|
action: MenuEntry::create_action(|_| DocumentMessage::SaveDocument.into()),
|
||||||
..MenuEntry::default()
|
..MenuEntry::default()
|
||||||
},
|
},
|
||||||
MenuEntry {
|
MenuEntry {
|
||||||
label: "Save As…".into(),
|
label: "Save As…".into(),
|
||||||
shortcut: Some(vec![Key::KeyControl, Key::KeyShift, Key::KeyS]),
|
// shortcut: [Key::KeyControl, Key::KeyShift, Key::KeyS],
|
||||||
action: MenuEntry::create_action(|_| DocumentMessage::SaveDocument.into()),
|
|
||||||
..MenuEntry::default()
|
..MenuEntry::default()
|
||||||
},
|
},
|
||||||
MenuEntry {
|
MenuEntry {
|
||||||
label: "Save All".into(),
|
label: "Save All".into(),
|
||||||
shortcut: Some(vec![Key::KeyControl, Key::KeyAlt, Key::KeyS]),
|
// shortcut: [Key::KeyControl, Key::KeyAlt, Key::KeyS],
|
||||||
..MenuEntry::default()
|
..MenuEntry::default()
|
||||||
},
|
},
|
||||||
MenuEntry {
|
MenuEntry {
|
||||||
|
@ -128,37 +127,37 @@ impl PropertyHolder for MenuBarMessageHandler {
|
||||||
vec![
|
vec![
|
||||||
MenuEntry {
|
MenuEntry {
|
||||||
label: "Import…".into(),
|
label: "Import…".into(),
|
||||||
shortcut: Some(vec![Key::KeyControl, Key::KeyI]),
|
shortcut: action_shortcut!(PortfolioMessageDiscriminant::Import),
|
||||||
action: MenuEntry::create_action(|_| PortfolioMessage::Import.into()),
|
action: MenuEntry::create_action(|_| PortfolioMessage::Import.into()),
|
||||||
..MenuEntry::default()
|
..MenuEntry::default()
|
||||||
},
|
},
|
||||||
MenuEntry {
|
MenuEntry {
|
||||||
label: "Export…".into(),
|
label: "Export…".into(),
|
||||||
shortcut: Some(vec![Key::KeyControl, Key::KeyE]),
|
shortcut: action_shortcut!(DialogMessageDiscriminant::RequestExportDialog),
|
||||||
action: MenuEntry::create_action(|_| DialogMessage::RequestExportDialog.into()),
|
action: MenuEntry::create_action(|_| DialogMessage::RequestExportDialog.into()),
|
||||||
..MenuEntry::default()
|
..MenuEntry::default()
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
vec![MenuEntry {
|
vec![MenuEntry {
|
||||||
label: "Quit".into(),
|
label: "Quit".into(),
|
||||||
shortcut: Some(vec![Key::KeyControl, Key::KeyQ]),
|
// shortcut: [Key::KeyControl, Key::KeyQ],
|
||||||
..MenuEntry::default()
|
..MenuEntry::default()
|
||||||
}],
|
}],
|
||||||
],
|
]),
|
||||||
},
|
},
|
||||||
MenuColumn {
|
MenuColumn {
|
||||||
label: "Edit".into(),
|
label: "Edit".into(),
|
||||||
children: vec![
|
children: MenuEntryGroups(vec![
|
||||||
vec![
|
vec![
|
||||||
MenuEntry {
|
MenuEntry {
|
||||||
label: "Undo".into(),
|
label: "Undo".into(),
|
||||||
shortcut: Some(vec![Key::KeyControl, Key::KeyZ]),
|
shortcut: action_shortcut!(DocumentMessageDiscriminant::Undo),
|
||||||
action: MenuEntry::create_action(|_| DocumentMessage::Undo.into()),
|
action: MenuEntry::create_action(|_| DocumentMessage::Undo.into()),
|
||||||
..MenuEntry::default()
|
..MenuEntry::default()
|
||||||
},
|
},
|
||||||
MenuEntry {
|
MenuEntry {
|
||||||
label: "Redo".into(),
|
label: "Redo".into(),
|
||||||
shortcut: Some(vec![Key::KeyControl, Key::KeyShift, Key::KeyZ]),
|
shortcut: action_shortcut!(DocumentMessageDiscriminant::Redo),
|
||||||
action: MenuEntry::create_action(|_| DocumentMessage::Redo.into()),
|
action: MenuEntry::create_action(|_| DocumentMessage::Redo.into()),
|
||||||
..MenuEntry::default()
|
..MenuEntry::default()
|
||||||
},
|
},
|
||||||
|
@ -166,93 +165,93 @@ impl PropertyHolder for MenuBarMessageHandler {
|
||||||
vec![
|
vec![
|
||||||
MenuEntry {
|
MenuEntry {
|
||||||
label: "Cut".into(),
|
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()),
|
action: MenuEntry::create_action(|_| PortfolioMessage::Cut { clipboard: Clipboard::Device }.into()),
|
||||||
..MenuEntry::default()
|
..MenuEntry::default()
|
||||||
},
|
},
|
||||||
MenuEntry {
|
MenuEntry {
|
||||||
label: "Copy".into(),
|
label: "Copy".into(),
|
||||||
icon: Some("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()),
|
action: MenuEntry::create_action(|_| PortfolioMessage::Copy { clipboard: Clipboard::Device }.into()),
|
||||||
..MenuEntry::default()
|
..MenuEntry::default()
|
||||||
},
|
},
|
||||||
MenuEntry {
|
MenuEntry {
|
||||||
label: "Paste".into(),
|
label: "Paste".into(),
|
||||||
icon: Some("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()),
|
action: MenuEntry::create_action(|_| FrontendMessage::TriggerPaste.into()),
|
||||||
..MenuEntry::default()
|
..MenuEntry::default()
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
],
|
]),
|
||||||
},
|
},
|
||||||
MenuColumn {
|
MenuColumn {
|
||||||
label: "Layer".into(),
|
label: "Layer".into(),
|
||||||
children: vec![vec![
|
children: MenuEntryGroups(vec![vec![
|
||||||
MenuEntry {
|
MenuEntry {
|
||||||
label: "Select All".into(),
|
label: "Select All".into(),
|
||||||
shortcut: Some(vec![Key::KeyControl, Key::KeyA]),
|
shortcut: action_shortcut!(DocumentMessageDiscriminant::SelectAllLayers),
|
||||||
action: MenuEntry::create_action(|_| DocumentMessage::SelectAllLayers.into()),
|
action: MenuEntry::create_action(|_| DocumentMessage::SelectAllLayers.into()),
|
||||||
..MenuEntry::default()
|
..MenuEntry::default()
|
||||||
},
|
},
|
||||||
MenuEntry {
|
MenuEntry {
|
||||||
label: "Deselect All".into(),
|
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()),
|
action: MenuEntry::create_action(|_| DocumentMessage::DeselectAllLayers.into()),
|
||||||
..MenuEntry::default()
|
..MenuEntry::default()
|
||||||
},
|
},
|
||||||
MenuEntry {
|
MenuEntry {
|
||||||
label: "Order".into(),
|
label: "Order".into(),
|
||||||
action: MenuEntry::no_action(),
|
action: MenuEntry::no_action(),
|
||||||
children: Some(vec![vec![
|
children: MenuEntryGroups(vec![vec![
|
||||||
MenuEntry {
|
MenuEntry {
|
||||||
label: "Raise To Front".into(),
|
label: "Raise To Front".into(),
|
||||||
shortcut: Some(vec![Key::KeyControl, Key::KeyShift, Key::KeyLeftBracket]),
|
shortcut: action_shortcut!(DocumentMessageDiscriminant::SelectedLayersRaiseToFront),
|
||||||
action: MenuEntry::create_action(|_| DocumentMessage::ReorderSelectedLayers { relative_index_offset: isize::MAX }.into()),
|
action: MenuEntry::create_action(|_| DocumentMessage::SelectedLayersRaiseToFront.into()),
|
||||||
..MenuEntry::default()
|
..MenuEntry::default()
|
||||||
},
|
},
|
||||||
MenuEntry {
|
MenuEntry {
|
||||||
label: "Raise".into(),
|
label: "Raise".into(),
|
||||||
shortcut: Some(vec![Key::KeyControl, Key::KeyRightBracket]),
|
shortcut: action_shortcut!(DocumentMessageDiscriminant::SelectedLayersRaise),
|
||||||
action: MenuEntry::create_action(|_| DocumentMessage::ReorderSelectedLayers { relative_index_offset: 1 }.into()),
|
action: MenuEntry::create_action(|_| DocumentMessage::SelectedLayersRaise.into()),
|
||||||
..MenuEntry::default()
|
..MenuEntry::default()
|
||||||
},
|
},
|
||||||
MenuEntry {
|
MenuEntry {
|
||||||
label: "Lower".into(),
|
label: "Lower".into(),
|
||||||
shortcut: Some(vec![Key::KeyControl, Key::KeyLeftBracket]),
|
shortcut: action_shortcut!(DocumentMessageDiscriminant::SelectedLayersLower),
|
||||||
action: MenuEntry::create_action(|_| DocumentMessage::ReorderSelectedLayers { relative_index_offset: -1 }.into()),
|
action: MenuEntry::create_action(|_| DocumentMessage::SelectedLayersLower.into()),
|
||||||
..MenuEntry::default()
|
..MenuEntry::default()
|
||||||
},
|
},
|
||||||
MenuEntry {
|
MenuEntry {
|
||||||
label: "Lower to Back".into(),
|
label: "Lower to Back".into(),
|
||||||
shortcut: Some(vec![Key::KeyControl, Key::KeyShift, Key::KeyRightBracket]),
|
shortcut: action_shortcut!(DocumentMessageDiscriminant::SelectedLayersLowerToBack),
|
||||||
action: MenuEntry::create_action(|_| DocumentMessage::ReorderSelectedLayers { relative_index_offset: isize::MIN }.into()),
|
action: MenuEntry::create_action(|_| DocumentMessage::SelectedLayersLowerToBack.into()),
|
||||||
..MenuEntry::default()
|
..MenuEntry::default()
|
||||||
},
|
},
|
||||||
]]),
|
]]),
|
||||||
..MenuEntry::default()
|
..MenuEntry::default()
|
||||||
},
|
},
|
||||||
]],
|
]]),
|
||||||
},
|
},
|
||||||
MenuColumn {
|
MenuColumn {
|
||||||
label: "Document".into(),
|
label: "Document".into(),
|
||||||
children: vec![vec![MenuEntry {
|
children: MenuEntryGroups(vec![vec![MenuEntry {
|
||||||
label: "Menu entries coming soon".into(),
|
label: "Menu entries coming soon".into(),
|
||||||
..MenuEntry::default()
|
..MenuEntry::default()
|
||||||
}]],
|
}]]),
|
||||||
},
|
},
|
||||||
MenuColumn {
|
MenuColumn {
|
||||||
label: "View".into(),
|
label: "View".into(),
|
||||||
children: vec![vec![MenuEntry {
|
children: MenuEntryGroups(vec![vec![MenuEntry {
|
||||||
label: "Show/Hide Node Graph (In Development)".into(),
|
label: "Show/Hide Node Graph (In Development)".into(),
|
||||||
action: MenuEntry::create_action(|_| WorkspaceMessage::NodeGraphToggleVisibility.into()),
|
action: MenuEntry::create_action(|_| WorkspaceMessage::NodeGraphToggleVisibility.into()),
|
||||||
..MenuEntry::default()
|
..MenuEntry::default()
|
||||||
}]],
|
}]]),
|
||||||
},
|
},
|
||||||
MenuColumn {
|
MenuColumn {
|
||||||
label: "Help".into(),
|
label: "Help".into(),
|
||||||
children: vec![
|
children: MenuEntryGroups(vec![
|
||||||
vec![MenuEntry {
|
vec![MenuEntry {
|
||||||
label: "About Graphite".into(),
|
label: "About Graphite".into(),
|
||||||
action: MenuEntry::create_action(|_| DialogMessage::RequestAboutGraphiteDialog.into()),
|
action: MenuEntry::create_action(|_| DialogMessage::RequestAboutGraphiteDialog.into()),
|
||||||
|
@ -284,23 +283,23 @@ impl PropertyHolder for MenuBarMessageHandler {
|
||||||
MenuEntry {
|
MenuEntry {
|
||||||
label: "Debug: Print Messages".into(),
|
label: "Debug: Print Messages".into(),
|
||||||
action: MenuEntry::no_action(),
|
action: MenuEntry::no_action(),
|
||||||
children: Some(vec![vec![
|
children: MenuEntryGroups(vec![vec![
|
||||||
MenuEntry {
|
MenuEntry {
|
||||||
label: "Off".into(),
|
label: "Off".into(),
|
||||||
// icon: Some("Checkmark".into()), // TODO: Find a way to set this icon on the active mode
|
// 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()),
|
action: MenuEntry::create_action(|_| DebugMessage::MessageOff.into()),
|
||||||
..MenuEntry::default()
|
..MenuEntry::default()
|
||||||
},
|
},
|
||||||
MenuEntry {
|
MenuEntry {
|
||||||
label: "Only Names".into(),
|
label: "Only Names".into(),
|
||||||
shortcut: Some(vec![Key::KeyAlt, Key::Key1]),
|
shortcut: action_shortcut!(DebugMessageDiscriminant::MessageNames),
|
||||||
action: MenuEntry::create_action(|_| DebugMessage::MessageNames.into()),
|
action: MenuEntry::create_action(|_| DebugMessage::MessageNames.into()),
|
||||||
..MenuEntry::default()
|
..MenuEntry::default()
|
||||||
},
|
},
|
||||||
MenuEntry {
|
MenuEntry {
|
||||||
label: "Full Contents".into(),
|
label: "Full Contents".into(),
|
||||||
shortcut: Some(vec![Key::KeyAlt, Key::Key2]),
|
shortcut: action_shortcut!(DebugMessageDiscriminant::MessageContents),
|
||||||
action: MenuEntry::create_action(|_| DebugMessage::MessageContents.into()),
|
action: MenuEntry::create_action(|_| DebugMessage::MessageContents.into()),
|
||||||
..MenuEntry::default()
|
..MenuEntry::default()
|
||||||
},
|
},
|
||||||
|
@ -310,14 +309,14 @@ impl PropertyHolder for MenuBarMessageHandler {
|
||||||
MenuEntry {
|
MenuEntry {
|
||||||
label: "Debug: Print Trace Logs".into(),
|
label: "Debug: Print Trace Logs".into(),
|
||||||
icon: Some(if let log::LevelFilter::Trace = log::max_level() { "CheckboxChecked" } else { "CheckboxUnchecked" }.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()),
|
action: MenuEntry::create_action(|_| DebugMessage::ToggleTraceLogs.into()),
|
||||||
..MenuEntry::default()
|
..MenuEntry::default()
|
||||||
},
|
},
|
||||||
MenuEntry {
|
MenuEntry {
|
||||||
label: "Debug: Print Document".into(),
|
label: "Debug: Print Document".into(),
|
||||||
shortcut: Some(vec![Key::KeyAlt, Key::KeyP]),
|
shortcut: action_shortcut!(DocumentMessageDiscriminant::DebugPrintDocument),
|
||||||
action: MenuEntry::create_action(|_| DebugMessage::ToggleTraceLogs.into()),
|
action: MenuEntry::create_action(|_| DocumentMessage::DebugPrintDocument.into()),
|
||||||
..MenuEntry::default()
|
..MenuEntry::default()
|
||||||
},
|
},
|
||||||
MenuEntry {
|
MenuEntry {
|
||||||
|
@ -326,7 +325,7 @@ impl PropertyHolder for MenuBarMessageHandler {
|
||||||
..MenuEntry::default()
|
..MenuEntry::default()
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
],
|
]),
|
||||||
},
|
},
|
||||||
]))
|
]))
|
||||||
}
|
}
|
||||||
|
|
|
@ -230,6 +230,7 @@ impl MessageHandler<MovementMessage, (&Document, &InputPreprocessorMessageHandle
|
||||||
FrontendMessage::UpdateInputHints {
|
FrontendMessage::UpdateInputHints {
|
||||||
hint_data: HintData(vec![HintGroup(vec![HintInfo {
|
hint_data: HintData(vec![HintGroup(vec![HintInfo {
|
||||||
key_groups: vec![KeysGroup(vec![Key::KeyControl])],
|
key_groups: vec![KeysGroup(vec![Key::KeyControl])],
|
||||||
|
key_groups_mac: None,
|
||||||
mouse: None,
|
mouse: None,
|
||||||
label: String::from("Snap 15°"),
|
label: String::from("Snap 15°"),
|
||||||
plus: false,
|
plus: false,
|
||||||
|
@ -311,6 +312,7 @@ impl MessageHandler<MovementMessage, (&Document, &InputPreprocessorMessageHandle
|
||||||
FrontendMessage::UpdateInputHints {
|
FrontendMessage::UpdateInputHints {
|
||||||
hint_data: HintData(vec![HintGroup(vec![HintInfo {
|
hint_data: HintData(vec![HintGroup(vec![HintInfo {
|
||||||
key_groups: vec![KeysGroup(vec![Key::KeyControl])],
|
key_groups: vec![KeysGroup(vec![Key::KeyControl])],
|
||||||
|
key_groups_mac: None,
|
||||||
mouse: None,
|
mouse: None,
|
||||||
label: String::from("Snap Increments"),
|
label: String::from("Snap Increments"),
|
||||||
plus: false,
|
plus: false,
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
use super::clipboards::Clipboard;
|
use super::clipboards::Clipboard;
|
||||||
|
use super::utility_types::Platform;
|
||||||
use crate::message_prelude::*;
|
use crate::message_prelude::*;
|
||||||
|
|
||||||
use graphene::layers::text_layer::Font;
|
use graphene::layers::text_layer::Font;
|
||||||
|
@ -83,6 +84,9 @@ pub enum PortfolioMessage {
|
||||||
SetActiveDocument {
|
SetActiveDocument {
|
||||||
document_id: u64,
|
document_id: u64,
|
||||||
},
|
},
|
||||||
|
SetPlatform {
|
||||||
|
platform: Platform,
|
||||||
|
},
|
||||||
UpdateDocumentWidgets,
|
UpdateDocumentWidgets,
|
||||||
UpdateOpenDocumentsList,
|
UpdateOpenDocumentsList,
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
use super::clipboards::{CopyBufferEntry, INTERNAL_CLIPBOARD_COUNT};
|
use super::clipboards::{CopyBufferEntry, INTERNAL_CLIPBOARD_COUNT};
|
||||||
|
use super::utility_types::Platform;
|
||||||
use super::{DocumentMessageHandler, MenuBarMessageHandler};
|
use super::{DocumentMessageHandler, MenuBarMessageHandler};
|
||||||
use crate::consts::{DEFAULT_DOCUMENT_NAME, GRAPHITE_DOCUMENT_VERSION};
|
use crate::consts::{DEFAULT_DOCUMENT_NAME, GRAPHITE_DOCUMENT_VERSION};
|
||||||
use crate::dialog;
|
use crate::dialog;
|
||||||
|
@ -23,6 +24,7 @@ pub struct PortfolioMessageHandler {
|
||||||
active_document_id: Option<u64>,
|
active_document_id: Option<u64>,
|
||||||
copy_buffer: [Vec<CopyBufferEntry>; INTERNAL_CLIPBOARD_COUNT as usize],
|
copy_buffer: [Vec<CopyBufferEntry>; INTERNAL_CLIPBOARD_COUNT as usize],
|
||||||
font_cache: FontCache,
|
font_cache: FontCache,
|
||||||
|
pub platform: Platform,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PortfolioMessageHandler {
|
impl PortfolioMessageHandler {
|
||||||
|
@ -473,6 +475,7 @@ impl MessageHandler<PortfolioMessage, &InputPreprocessorMessageHandler> for Port
|
||||||
responses.push_back(MovementMessage::TranslateCanvas { delta: (0., 0.).into() }.into());
|
responses.push_back(MovementMessage::TranslateCanvas { delta: (0., 0.).into() }.into());
|
||||||
}
|
}
|
||||||
SetActiveDocument { document_id } => self.active_document_id = Some(document_id),
|
SetActiveDocument { document_id } => self.active_document_id = Some(document_id),
|
||||||
|
SetPlatform { platform } => self.platform = platform,
|
||||||
UpdateDocumentWidgets => {
|
UpdateDocumentWidgets => {
|
||||||
if let Some(document) = self.active_document() {
|
if let Some(document) = self.active_document() {
|
||||||
document.update_document_widgets(responses);
|
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]
|
#[remain::sorted]
|
||||||
#[impl_message(Message, Frontend)]
|
#[impl_message(Message, Frontend)]
|
||||||
#[derive(PartialEq, Clone, Deserialize, Serialize, Debug)]
|
#[derive(PartialEq, Clone, Debug, Serialize, Deserialize)]
|
||||||
pub enum FrontendMessage {
|
pub enum FrontendMessage {
|
||||||
// Display prefix: make the frontend show something, like a dialog
|
// Display prefix: make the frontend show something, like a dialog
|
||||||
DisplayDialog { icon: String },
|
DisplayDialog { icon: String },
|
||||||
|
|
|
@ -1,21 +1,21 @@
|
||||||
use graphene::LayerId;
|
use graphene::LayerId;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
#[derive(PartialEq, Eq, Clone, Deserialize, Serialize, Debug)]
|
#[derive(PartialEq, Eq, Clone, Debug, Serialize, Deserialize)]
|
||||||
pub struct FrontendDocumentDetails {
|
pub struct FrontendDocumentDetails {
|
||||||
pub is_saved: bool,
|
pub is_saved: bool,
|
||||||
pub name: String,
|
pub name: String,
|
||||||
pub id: u64,
|
pub id: u64,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(PartialEq, Eq, Clone, Deserialize, Serialize, Debug)]
|
#[derive(PartialEq, Eq, Clone, Debug, Serialize, Deserialize)]
|
||||||
pub struct FrontendImageData {
|
pub struct FrontendImageData {
|
||||||
pub path: Vec<LayerId>,
|
pub path: Vec<LayerId>,
|
||||||
pub mime: String,
|
pub mime: String,
|
||||||
pub image_data: Vec<u8>,
|
pub image_data: Vec<u8>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, Eq, Deserialize, PartialEq, Serialize)]
|
#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize, Deserialize)]
|
||||||
pub enum MouseCursorIcon {
|
pub enum MouseCursorIcon {
|
||||||
Default,
|
Default,
|
||||||
ZoomIn,
|
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 {
|
pub enum FileType {
|
||||||
Svg,
|
Svg,
|
||||||
Png,
|
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 {
|
pub enum ExportBounds {
|
||||||
AllArtwork,
|
AllArtwork,
|
||||||
Selection,
|
Selection,
|
||||||
|
|
|
@ -1,25 +1,25 @@
|
||||||
|
use super::input_mapper_macros::*;
|
||||||
use super::keyboard::{Key, KeyStates, NUMBER_OF_KEYS};
|
use super::keyboard::{Key, KeyStates, NUMBER_OF_KEYS};
|
||||||
|
use crate::consts::{BIG_NUDGE_AMOUNT, NUDGE_AMOUNT};
|
||||||
use crate::document::clipboards::Clipboard;
|
use crate::document::clipboards::Clipboard;
|
||||||
|
use crate::document::utility_types::KeyboardPlatformLayout;
|
||||||
use crate::message_prelude::*;
|
use crate::message_prelude::*;
|
||||||
use crate::viewport_tools::tool::ToolType;
|
|
||||||
|
|
||||||
use glam::DVec2;
|
use glam::DVec2;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
const NUDGE_AMOUNT: f64 = 1.;
|
|
||||||
const SHIFT_NUDGE_AMOUNT: f64 = 10.;
|
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct Mapping {
|
pub struct Mapping {
|
||||||
pub key_up: [KeyMappingEntries; NUMBER_OF_KEYS],
|
pub key_up: [KeyMappingEntries; NUMBER_OF_KEYS],
|
||||||
pub key_down: [KeyMappingEntries; NUMBER_OF_KEYS],
|
pub key_down: [KeyMappingEntries; NUMBER_OF_KEYS],
|
||||||
pub pointer_move: KeyMappingEntries,
|
|
||||||
pub mouse_scroll: KeyMappingEntries,
|
|
||||||
pub double_click: KeyMappingEntries,
|
pub double_click: KeyMappingEntries,
|
||||||
|
pub wheel_scroll: KeyMappingEntries,
|
||||||
|
pub pointer_move: KeyMappingEntries,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for Mapping {
|
impl Default for Mapping {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
use input_mapper_macros::{entry, mapping, modifiers};
|
use InputMapperMessage::*;
|
||||||
use Key::*;
|
use Key::*;
|
||||||
|
|
||||||
// WARNING!
|
// 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`).
|
// it as an available action in the respective message handler file (such as the bottom of `document_message_handler.rs`).
|
||||||
|
|
||||||
let mappings = mapping![
|
let mappings = mapping![
|
||||||
// Higher priority than entries in sections below
|
// HIGHER PRIORITY:
|
||||||
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
|
// MovementMessage
|
||||||
entry! {action=TransformLayerMessage::ApplyTransformOperation, key_down=KeyEnter},
|
entry!(
|
||||||
entry! {action=TransformLayerMessage::ApplyTransformOperation, key_down=Lmb},
|
PointerMove;
|
||||||
entry! {action=TransformLayerMessage::CancelTransformOperation, key_down=KeyEscape},
|
refresh_keys=[KeyControl],
|
||||||
entry! {action=TransformLayerMessage::CancelTransformOperation, key_down=Rmb},
|
action_dispatch=MovementMessage::PointerMove { snap_angle: KeyControl, wait_for_snap_angle_release: true, snap_zoom: KeyControl, zoom_from_viewport: None },
|
||||||
entry! {action=TransformLayerMessage::ConstrainX, key_down=KeyX},
|
),
|
||||||
entry! {action=TransformLayerMessage::ConstrainY, key_down=KeyY},
|
// NORMAL PRIORITY:
|
||||||
entry! {action=TransformLayerMessage::TypeBackspace, key_down=KeyBackspace},
|
//
|
||||||
entry! {action=TransformLayerMessage::TypeNegate, key_down=KeyMinus},
|
// TransformLayerMessage
|
||||||
entry! {action=TransformLayerMessage::TypeDecimalPoint, key_down=KeyComma},
|
entry!(KeyDown(KeyEnter); action_dispatch=TransformLayerMessage::ApplyTransformOperation),
|
||||||
entry! {action=TransformLayerMessage::TypeDecimalPoint, key_down=KeyPeriod},
|
entry!(KeyDown(Lmb); action_dispatch=TransformLayerMessage::ApplyTransformOperation),
|
||||||
entry! {action=TransformLayerMessage::PointerMove { slow_key: KeyShift, snap_key: KeyControl }, triggers=[KeyShift, KeyControl]},
|
entry!(KeyDown(KeyEscape); action_dispatch=TransformLayerMessage::CancelTransformOperation),
|
||||||
// Select
|
entry!(KeyDown(Rmb); action_dispatch=TransformLayerMessage::CancelTransformOperation),
|
||||||
entry! {action=SelectToolMessage::PointerMove { axis_align: KeyShift, snap_angle: KeyControl, center: KeyAlt }, message=InputMapperMessage::PointerMove},
|
entry!(KeyDown(KeyX); action_dispatch=TransformLayerMessage::ConstrainX),
|
||||||
entry! {action=SelectToolMessage::DragStart { add_to_selection: KeyShift }, key_down=Lmb},
|
entry!(KeyDown(KeyY); action_dispatch=TransformLayerMessage::ConstrainY),
|
||||||
entry! {action=SelectToolMessage::DragStop, key_up=Lmb},
|
entry!(KeyDown(KeyBackspace); action_dispatch=TransformLayerMessage::TypeBackspace),
|
||||||
entry! {action=SelectToolMessage::DragStop, key_down=KeyEnter},
|
entry!(KeyDown(KeyMinus); action_dispatch=TransformLayerMessage::TypeNegate),
|
||||||
entry! {action=SelectToolMessage::EditLayer, message=InputMapperMessage::DoubleClick},
|
entry!(KeyDown(KeyComma); action_dispatch=TransformLayerMessage::TypeDecimalPoint),
|
||||||
entry! {action=SelectToolMessage::Abort, key_down=Rmb},
|
entry!(KeyDown(KeyPeriod); action_dispatch=TransformLayerMessage::TypeDecimalPoint),
|
||||||
entry! {action=SelectToolMessage::Abort, key_down=KeyEscape},
|
entry!(PointerMove; refresh_keys=[KeyShift, KeyControl], action_dispatch=TransformLayerMessage::PointerMove { slow_key: KeyShift, snap_key: KeyControl }),
|
||||||
// Artboard
|
// SelectToolMessage
|
||||||
entry! {action=ArtboardToolMessage::PointerDown, key_down=Lmb},
|
entry!(PointerMove; refresh_keys=[KeyControl, KeyShift, KeyAlt], action_dispatch=SelectToolMessage::PointerMove { axis_align: KeyShift, snap_angle: KeyControl, center: KeyAlt }),
|
||||||
entry! {action=ArtboardToolMessage::PointerMove { constrain_axis_or_aspect: KeyShift, center: KeyAlt }, message=InputMapperMessage::PointerMove},
|
entry!(KeyDown(Lmb); action_dispatch=SelectToolMessage::DragStart { add_to_selection: KeyShift }),
|
||||||
entry! {action=ArtboardToolMessage::PointerUp, key_up=Lmb},
|
entry!(KeyUp(Lmb); action_dispatch=SelectToolMessage::DragStop),
|
||||||
entry! {action=ArtboardToolMessage::DeleteSelected, key_down=KeyDelete},
|
entry!(KeyDown(KeyEnter); action_dispatch=SelectToolMessage::DragStop),
|
||||||
entry! {action=ArtboardToolMessage::DeleteSelected, key_down=KeyBackspace},
|
entry!(DoubleClick; action_dispatch=SelectToolMessage::EditLayer),
|
||||||
// Navigate
|
entry!(KeyDown(Rmb); action_dispatch=SelectToolMessage::Abort),
|
||||||
entry! {action=NavigateToolMessage::ClickZoom { zoom_in: false }, key_up=Lmb, modifiers=[KeyShift]},
|
entry!(KeyDown(KeyEscape); action_dispatch=SelectToolMessage::Abort),
|
||||||
entry! {action=NavigateToolMessage::ClickZoom { zoom_in: true }, key_up=Lmb},
|
// ArtboardToolMessage
|
||||||
entry! {action=NavigateToolMessage::PointerMove { snap_angle: KeyControl, snap_zoom: KeyControl }, message=InputMapperMessage::PointerMove},
|
entry!(KeyDown(Lmb); action_dispatch=ArtboardToolMessage::PointerDown),
|
||||||
entry! {action=NavigateToolMessage::TranslateCanvasBegin, key_down=Mmb},
|
entry!(PointerMove; refresh_keys=[KeyShift, KeyAlt], action_dispatch=ArtboardToolMessage::PointerMove { constrain_axis_or_aspect: KeyShift, center: KeyAlt }),
|
||||||
entry! {action=NavigateToolMessage::RotateCanvasBegin, key_down=Rmb},
|
entry!(KeyUp(Lmb); action_dispatch=ArtboardToolMessage::PointerUp),
|
||||||
entry! {action=NavigateToolMessage::ZoomCanvasBegin, key_down=Lmb},
|
entry!(KeyDown(KeyDelete); action_dispatch=ArtboardToolMessage::DeleteSelected),
|
||||||
entry! {action=NavigateToolMessage::TransformCanvasEnd, key_up=Rmb},
|
entry!(KeyDown(KeyBackspace); action_dispatch=ArtboardToolMessage::DeleteSelected),
|
||||||
entry! {action=NavigateToolMessage::TransformCanvasEnd, key_up=Lmb},
|
// NavigateToolMessage
|
||||||
entry! {action=NavigateToolMessage::TransformCanvasEnd, key_up=Mmb},
|
entry!(KeyUp(Lmb); modifiers=[KeyShift], action_dispatch=NavigateToolMessage::ClickZoom { zoom_in: false }),
|
||||||
// Eyedropper
|
entry!(KeyUp(Lmb); action_dispatch=NavigateToolMessage::ClickZoom { zoom_in: true }),
|
||||||
entry! {action=EyedropperToolMessage::LeftMouseDown, key_down=Lmb},
|
entry!(PointerMove; refresh_keys=[KeyControl], action_dispatch=NavigateToolMessage::PointerMove { snap_angle: KeyControl, snap_zoom: KeyControl }),
|
||||||
entry! {action=EyedropperToolMessage::RightMouseDown, key_down=Rmb},
|
entry!(KeyDown(Mmb); action_dispatch=NavigateToolMessage::TranslateCanvasBegin),
|
||||||
// Text
|
entry!(KeyDown(Rmb); action_dispatch=NavigateToolMessage::RotateCanvasBegin),
|
||||||
entry! {action=TextMessage::Interact, key_up=Lmb},
|
entry!(KeyDown(Lmb); action_dispatch=NavigateToolMessage::ZoomCanvasBegin),
|
||||||
entry! {action=TextMessage::Abort, key_down=KeyEscape},
|
entry!(KeyUp(Rmb); action_dispatch=NavigateToolMessage::TransformCanvasEnd),
|
||||||
entry! {action=TextMessage::CommitText, key_down=KeyEnter, modifiers=[KeyControl]},
|
entry!(KeyUp(Lmb); action_dispatch=NavigateToolMessage::TransformCanvasEnd),
|
||||||
// Gradient
|
entry!(KeyUp(Mmb); action_dispatch=NavigateToolMessage::TransformCanvasEnd),
|
||||||
entry! {action=GradientToolMessage::PointerDown, key_down=Lmb},
|
// EyedropperToolMessage
|
||||||
entry! {action=GradientToolMessage::PointerMove { constrain_axis: KeyShift }, message=InputMapperMessage::PointerMove},
|
entry!(KeyDown(Lmb); action_dispatch=EyedropperToolMessage::LeftMouseDown),
|
||||||
entry! {action=GradientToolMessage::PointerUp, key_up=Lmb},
|
entry!(KeyDown(Rmb); action_dispatch=EyedropperToolMessage::RightMouseDown),
|
||||||
// Rectangle
|
// TextToolMessage
|
||||||
entry! {action=RectangleToolMessage::DragStart, key_down=Lmb},
|
entry!(KeyUp(Lmb); action_dispatch=TextToolMessage::Interact),
|
||||||
entry! {action=RectangleToolMessage::DragStop, key_up=Lmb},
|
entry!(KeyDown(KeyEscape); action_dispatch=TextToolMessage::Abort),
|
||||||
entry! {action=RectangleToolMessage::Abort, key_down=Rmb},
|
entry_multiplatform!(
|
||||||
entry! {action=RectangleToolMessage::Abort, key_down=KeyEscape},
|
standard!(KeyDown(KeyEnter); modifiers=[KeyControl], action_dispatch=TextToolMessage::CommitText),
|
||||||
entry! {action=RectangleToolMessage::Resize { center: KeyAlt, lock_ratio: KeyShift }, triggers=[KeyAlt, KeyShift]},
|
mac_only!(KeyDown(KeyEnter); modifiers=[KeyCommand], action_dispatch=TextToolMessage::CommitText),
|
||||||
// Ellipse
|
),
|
||||||
entry! {action=EllipseToolMessage::DragStart, key_down=Lmb},
|
// GradientToolMessage
|
||||||
entry! {action=EllipseToolMessage::DragStop, key_up=Lmb},
|
entry!(KeyDown(Lmb); action_dispatch=GradientToolMessage::PointerDown),
|
||||||
entry! {action=EllipseToolMessage::Abort, key_down=Rmb},
|
entry!(PointerMove; refresh_keys=[KeyShift], action_dispatch=GradientToolMessage::PointerMove { constrain_axis: KeyShift }),
|
||||||
entry! {action=EllipseToolMessage::Abort, key_down=KeyEscape},
|
entry!(KeyUp(Lmb); action_dispatch=GradientToolMessage::PointerUp),
|
||||||
entry! {action=EllipseToolMessage::Resize { center: KeyAlt, lock_ratio: KeyShift }, triggers=[KeyAlt, KeyShift]},
|
// RectangleToolMessage
|
||||||
// Shape
|
entry!(KeyDown(Lmb); action_dispatch=RectangleToolMessage::DragStart),
|
||||||
entry! {action=ShapeToolMessage::DragStart, key_down=Lmb},
|
entry!(KeyUp(Lmb); action_dispatch=RectangleToolMessage::DragStop),
|
||||||
entry! {action=ShapeToolMessage::DragStop, key_up=Lmb},
|
entry!(KeyDown(Rmb); action_dispatch=RectangleToolMessage::Abort),
|
||||||
entry! {action=ShapeToolMessage::Abort, key_down=Rmb},
|
entry!(KeyDown(KeyEscape); action_dispatch=RectangleToolMessage::Abort),
|
||||||
entry! {action=ShapeToolMessage::Abort, key_down=KeyEscape},
|
entry!(PointerMove; refresh_keys=[KeyAlt, KeyShift], action_dispatch=RectangleToolMessage::Resize { center: KeyAlt, lock_ratio: KeyShift }),
|
||||||
entry! {action=ShapeToolMessage::Resize { center: KeyAlt, lock_ratio: KeyShift }, triggers=[KeyAlt, KeyShift]},
|
// EllipseToolMessage
|
||||||
// Line
|
entry!(KeyDown(Lmb); action_dispatch=EllipseToolMessage::DragStart),
|
||||||
entry! {action=LineToolMessage::DragStart, key_down=Lmb},
|
entry!(KeyUp(Lmb); action_dispatch=EllipseToolMessage::DragStop),
|
||||||
entry! {action=LineToolMessage::DragStop, key_up=Lmb},
|
entry!(KeyDown(Rmb); action_dispatch=EllipseToolMessage::Abort),
|
||||||
entry! {action=LineToolMessage::Abort, key_down=Rmb},
|
entry!(KeyDown(KeyEscape); action_dispatch=EllipseToolMessage::Abort),
|
||||||
entry! {action=LineToolMessage::Abort, key_down=KeyEscape},
|
entry!(PointerMove; refresh_keys=[KeyAlt, KeyShift], action_dispatch=EllipseToolMessage::Resize { center: KeyAlt, lock_ratio: KeyShift }),
|
||||||
entry! {action=LineToolMessage::Redraw { center: KeyAlt, lock_angle: KeyControl, snap_angle: KeyShift }, triggers=[KeyAlt, KeyShift, KeyControl]},
|
// ShapeToolMessage
|
||||||
// Path
|
entry!(KeyDown(Lmb); action_dispatch=ShapeToolMessage::DragStart),
|
||||||
entry! {action=PathToolMessage::DragStart { add_to_selection: KeyShift }, key_down=Lmb},
|
entry!(KeyUp(Lmb); action_dispatch=ShapeToolMessage::DragStop),
|
||||||
entry! {action=PathToolMessage::PointerMove { alt_mirror_angle: KeyAlt, shift_mirror_distance: KeyShift }, message=InputMapperMessage::PointerMove},
|
entry!(KeyDown(Rmb); action_dispatch=ShapeToolMessage::Abort),
|
||||||
entry! {action=PathToolMessage::Delete, key_down=KeyDelete},
|
entry!(KeyDown(KeyEscape); action_dispatch=ShapeToolMessage::Abort),
|
||||||
entry! {action=PathToolMessage::Delete, key_down=KeyBackspace},
|
entry!(PointerMove; refresh_keys=[KeyAlt, KeyShift], action_dispatch=ShapeToolMessage::Resize { center: KeyAlt, lock_ratio: KeyShift }),
|
||||||
entry! {action=PathToolMessage::DragStop, key_up=Lmb},
|
// LineToolMessage
|
||||||
// Pen
|
entry!(KeyDown(Lmb); action_dispatch=LineToolMessage::DragStart),
|
||||||
entry! {action=PenToolMessage::PointerMove { snap_angle: KeyControl, break_handle: KeyShift }, message=InputMapperMessage::PointerMove},
|
entry!(KeyUp(Lmb); action_dispatch=LineToolMessage::DragStop),
|
||||||
entry! {action=PenToolMessage::DragStart, key_down=Lmb},
|
entry!(KeyDown(Rmb); action_dispatch=LineToolMessage::Abort),
|
||||||
entry! {action=PenToolMessage::DragStop, key_up=Lmb},
|
entry!(KeyDown(KeyEscape); action_dispatch=LineToolMessage::Abort),
|
||||||
entry! {action=PenToolMessage::Confirm, key_down=Rmb},
|
entry!(PointerMove; refresh_keys=[KeyAlt, KeyShift, KeyControl], action_dispatch=LineToolMessage::Redraw { center: KeyAlt, lock_angle: KeyControl, snap_angle: KeyShift }),
|
||||||
entry! {action=PenToolMessage::Confirm, key_down=KeyEscape},
|
// PathToolMessage
|
||||||
entry! {action=PenToolMessage::Confirm, key_down=KeyEnter},
|
entry!(KeyDown(Lmb); action_dispatch=PathToolMessage::DragStart { add_to_selection: KeyShift }),
|
||||||
// Freehand
|
entry!(PointerMove; refresh_keys=[KeyAlt, KeyShift], action_dispatch=PathToolMessage::PointerMove { alt_mirror_angle: KeyAlt, shift_mirror_distance: KeyShift }),
|
||||||
entry! {action=FreehandToolMessage::PointerMove, message=InputMapperMessage::PointerMove},
|
entry!(KeyDown(KeyDelete); action_dispatch=PathToolMessage::Delete),
|
||||||
entry! {action=FreehandToolMessage::DragStart, key_down=Lmb},
|
entry!(KeyDown(KeyBackspace); action_dispatch=PathToolMessage::Delete),
|
||||||
entry! {action=FreehandToolMessage::DragStop, key_up=Lmb},
|
entry!(KeyUp(Lmb); action_dispatch=PathToolMessage::DragStop),
|
||||||
// Spline
|
// PenToolMessage
|
||||||
entry! {action=SplineToolMessage::PointerMove, message=InputMapperMessage::PointerMove},
|
entry!(PointerMove; refresh_keys=[KeyShift, KeyControl], action_dispatch=PenToolMessage::PointerMove { snap_angle: KeyControl, break_handle: KeyShift }),
|
||||||
entry! {action=SplineToolMessage::DragStart, key_down=Lmb},
|
entry!(KeyDown(Lmb); action_dispatch=PenToolMessage::DragStart),
|
||||||
entry! {action=SplineToolMessage::DragStop, key_up=Lmb},
|
entry!(KeyUp(Lmb); action_dispatch=PenToolMessage::DragStop),
|
||||||
entry! {action=SplineToolMessage::Confirm, key_down=Rmb},
|
entry!(KeyDown(Rmb); action_dispatch=PenToolMessage::Confirm),
|
||||||
entry! {action=SplineToolMessage::Confirm, key_down=KeyEscape},
|
entry!(KeyDown(KeyEscape); action_dispatch=PenToolMessage::Confirm),
|
||||||
entry! {action=SplineToolMessage::Confirm, key_down=KeyEnter},
|
entry!(KeyDown(KeyEnter); action_dispatch=PenToolMessage::Confirm),
|
||||||
// Fill
|
// FreehandToolMessage
|
||||||
entry! {action=FillToolMessage::LeftMouseDown, key_down=Lmb},
|
entry!(PointerMove; action_dispatch=FreehandToolMessage::PointerMove),
|
||||||
entry! {action=FillToolMessage::RightMouseDown, key_down=Rmb},
|
entry!(KeyDown(Lmb); action_dispatch=FreehandToolMessage::DragStart),
|
||||||
// Tool Actions
|
entry!(KeyUp(Lmb); action_dispatch=FreehandToolMessage::DragStop),
|
||||||
entry! {action=ToolMessage::ActivateTool { tool_type: ToolType::Select }, key_down=KeyV},
|
// SplineToolMessage
|
||||||
entry! {action=ToolMessage::ActivateTool { tool_type: ToolType::Navigate }, key_down=KeyZ},
|
entry!(PointerMove; action_dispatch=SplineToolMessage::PointerMove),
|
||||||
entry! {action=ToolMessage::ActivateTool { tool_type: ToolType::Eyedropper }, key_down=KeyI},
|
entry!(KeyDown(Lmb); action_dispatch=SplineToolMessage::DragStart),
|
||||||
entry! {action=ToolMessage::ActivateTool { tool_type: ToolType::Text }, key_down=KeyT},
|
entry!(KeyUp(Lmb); action_dispatch=SplineToolMessage::DragStop),
|
||||||
entry! {action=ToolMessage::ActivateTool { tool_type: ToolType::Fill }, key_down=KeyF},
|
entry!(KeyDown(Rmb); action_dispatch=SplineToolMessage::Confirm),
|
||||||
entry! {action=ToolMessage::ActivateTool { tool_type: ToolType::Gradient }, key_down=KeyH},
|
entry!(KeyDown(KeyEscape); action_dispatch=SplineToolMessage::Confirm),
|
||||||
entry! {action=ToolMessage::ActivateTool { tool_type: ToolType::Path }, key_down=KeyA},
|
entry!(KeyDown(KeyEnter); action_dispatch=SplineToolMessage::Confirm),
|
||||||
entry! {action=ToolMessage::ActivateTool { tool_type: ToolType::Pen }, key_down=KeyP},
|
// FillToolMessage
|
||||||
entry! {action=ToolMessage::ActivateTool { tool_type: ToolType::Freehand }, key_down=KeyN},
|
entry!(KeyDown(Lmb); action_dispatch=FillToolMessage::LeftMouseDown),
|
||||||
entry! {action=ToolMessage::ActivateTool { tool_type: ToolType::Line }, key_down=KeyL},
|
entry!(KeyDown(Rmb); action_dispatch=FillToolMessage::RightMouseDown),
|
||||||
entry! {action=ToolMessage::ActivateTool { tool_type: ToolType::Rectangle }, key_down=KeyM},
|
// ToolMessage
|
||||||
entry! {action=ToolMessage::ActivateTool { tool_type: ToolType::Ellipse }, key_down=KeyE},
|
entry!(KeyDown(KeyV); action_dispatch=ToolMessage::ActivateToolSelect),
|
||||||
entry! {action=ToolMessage::ActivateTool { tool_type: ToolType::Shape }, key_down=KeyY},
|
entry!(KeyDown(KeyZ); action_dispatch=ToolMessage::ActivateToolNavigate),
|
||||||
// Colors
|
entry!(KeyDown(KeyI); action_dispatch=ToolMessage::ActivateToolEyedropper),
|
||||||
entry! {action=ToolMessage::ResetColors, key_down=KeyX, modifiers=[KeyShift, KeyControl]},
|
entry!(KeyDown(KeyT); action_dispatch=ToolMessage::ActivateToolText),
|
||||||
entry! {action=ToolMessage::SwapColors, key_down=KeyX, modifiers=[KeyShift]},
|
entry!(KeyDown(KeyF); action_dispatch=ToolMessage::ActivateToolFill),
|
||||||
entry! {action=ToolMessage::SelectRandomPrimaryColor, key_down=KeyC, modifiers=[KeyAlt]},
|
entry!(KeyDown(KeyH); action_dispatch=ToolMessage::ActivateToolGradient),
|
||||||
// Document actions
|
entry!(KeyDown(KeyA); action_dispatch=ToolMessage::ActivateToolPath),
|
||||||
entry! {action=DocumentMessage::Redo, key_down=KeyZ, modifiers=[KeyControl, KeyShift]},
|
entry!(KeyDown(KeyP); action_dispatch=ToolMessage::ActivateToolPen),
|
||||||
entry! {action=DocumentMessage::Undo, key_down=KeyZ, modifiers=[KeyControl]},
|
entry!(KeyDown(KeyN); action_dispatch=ToolMessage::ActivateToolFreehand),
|
||||||
entry! {action=DocumentMessage::DeselectAllLayers, key_down=KeyA, modifiers=[KeyControl, KeyAlt]},
|
entry!(KeyDown(KeyL); action_dispatch=ToolMessage::ActivateToolLine),
|
||||||
entry! {action=DocumentMessage::SelectAllLayers, key_down=KeyA, modifiers=[KeyControl]},
|
entry!(KeyDown(KeyM); action_dispatch=ToolMessage::ActivateToolRectangle),
|
||||||
entry! {action=DocumentMessage::DeleteSelectedLayers, key_down=KeyDelete},
|
entry!(KeyDown(KeyE); action_dispatch=ToolMessage::ActivateToolEllipse),
|
||||||
entry! {action=DocumentMessage::DeleteSelectedLayers, key_down=KeyBackspace},
|
entry!(KeyDown(KeyY); action_dispatch=ToolMessage::ActivateToolShape),
|
||||||
entry! {action=DialogMessage::RequestExportDialog, key_down=KeyE, modifiers=[KeyControl]},
|
entry_multiplatform!(
|
||||||
entry! {action=DocumentMessage::SaveDocument, key_down=KeyS, modifiers=[KeyControl]},
|
standard!(KeyDown(KeyX); modifiers=[KeyShift, KeyControl], action_dispatch=ToolMessage::ResetColors),
|
||||||
entry! {action=DocumentMessage::SaveDocument, key_down=KeyS, modifiers=[KeyControl, KeyShift]},
|
mac_only!(KeyDown(KeyX); modifiers=[KeyShift, KeyCommand], action_dispatch=ToolMessage::ResetColors),
|
||||||
entry! {action=DocumentMessage::DebugPrintDocument, key_down=KeyP, modifiers=[KeyAlt]},
|
),
|
||||||
entry! {action=DocumentMessage::ZoomCanvasToFitAll, key_down=Key0, modifiers=[KeyControl]},
|
entry!(KeyDown(KeyX); modifiers=[KeyShift], action_dispatch=ToolMessage::SwapColors),
|
||||||
entry! {action=DocumentMessage::DuplicateSelectedLayers, key_down=KeyD, modifiers=[KeyControl]},
|
entry!(KeyDown(KeyC); modifiers=[KeyAlt], action_dispatch=ToolMessage::SelectRandomPrimaryColor),
|
||||||
entry! {action=DocumentMessage::GroupSelectedLayers, key_down=KeyG, modifiers=[KeyControl]},
|
// DocumentMessage
|
||||||
entry! {action=DocumentMessage::UngroupSelectedLayers, key_down=KeyG, modifiers=[KeyControl, KeyShift]},
|
entry!(KeyDown(KeyDelete); action_dispatch=DocumentMessage::DeleteSelectedLayers),
|
||||||
entry! {action=DocumentMessage::CreateEmptyFolder { container_path: vec![] }, key_down=KeyN, modifiers=[KeyControl, KeyShift]},
|
entry!(KeyDown(KeyBackspace); action_dispatch=DocumentMessage::DeleteSelectedLayers),
|
||||||
// Layer transformation
|
entry!(KeyDown(KeyP); modifiers=[KeyAlt], action_dispatch=DocumentMessage::DebugPrintDocument),
|
||||||
entry! {action=TransformLayerMessage::BeginGrab, key_down=KeyG},
|
entry_multiplatform!(
|
||||||
entry! {action=TransformLayerMessage::BeginRotate, key_down=KeyR},
|
standard!(KeyDown(KeyZ); modifiers=[KeyControl, KeyShift], action_dispatch=DocumentMessage::Redo),
|
||||||
entry! {action=TransformLayerMessage::BeginScale, key_down=KeyS},
|
mac_only!(KeyDown(KeyZ); modifiers=[KeyCommand, KeyShift], action_dispatch=DocumentMessage::Redo),
|
||||||
// Movement actions
|
),
|
||||||
entry! {action=MovementMessage::RotateCanvasBegin, key_down=Mmb, modifiers=[KeyControl]},
|
entry_multiplatform!(
|
||||||
entry! {action=MovementMessage::ZoomCanvasBegin, key_down=Mmb, modifiers=[KeyShift]},
|
standard!(KeyDown(KeyZ); modifiers=[KeyControl], action_dispatch=DocumentMessage::Undo),
|
||||||
entry! {action=MovementMessage::TranslateCanvasBegin, key_down=Mmb},
|
mac_only!(KeyDown(KeyZ); modifiers=[KeyCommand], action_dispatch=DocumentMessage::Undo),
|
||||||
entry! {action=MovementMessage::TransformCanvasEnd, key_up=Mmb},
|
),
|
||||||
entry! {action=MovementMessage::TranslateCanvasBegin, key_down=Lmb, modifiers=[KeySpace]},
|
entry_multiplatform!(
|
||||||
entry! {action=MovementMessage::TransformCanvasEnd, key_up=Lmb, modifiers=[KeySpace]},
|
standard!(KeyDown(KeyA); modifiers=[KeyControl, KeyAlt], action_dispatch=DocumentMessage::DeselectAllLayers),
|
||||||
entry! {action=MovementMessage::IncreaseCanvasZoom { center_on_mouse: false }, key_down=KeyPlus, modifiers=[KeyControl]},
|
mac_only!(KeyDown(KeyA); modifiers=[KeyCommand, KeyAlt], action_dispatch=DocumentMessage::DeselectAllLayers),
|
||||||
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_multiplatform!(
|
||||||
entry! {action=MovementMessage::SetCanvasZoom { zoom_factor: 1. }, key_down=Key1, modifiers=[KeyControl]},
|
standard!(KeyDown(KeyA); modifiers=[KeyControl], action_dispatch=DocumentMessage::SelectAllLayers),
|
||||||
entry! {action=MovementMessage::SetCanvasZoom { zoom_factor: 2. }, key_down=Key2, modifiers=[KeyControl]},
|
mac_only!(KeyDown(KeyA); modifiers=[KeyCommand], action_dispatch=DocumentMessage::SelectAllLayers),
|
||||||
entry! {action=MovementMessage::WheelCanvasZoom, message=InputMapperMessage::MouseScroll, modifiers=[KeyControl]},
|
),
|
||||||
entry! {action=MovementMessage::WheelCanvasTranslate { use_y_as_x: true }, message=InputMapperMessage::MouseScroll, modifiers=[KeyShift]},
|
entry_multiplatform!(
|
||||||
entry! {action=MovementMessage::WheelCanvasTranslate { use_y_as_x: false }, message=InputMapperMessage::MouseScroll},
|
standard!(KeyDown(KeyS); modifiers=[KeyControl], action_dispatch=DocumentMessage::SaveDocument),
|
||||||
entry! {action=MovementMessage::TranslateCanvasByViewportFraction { delta: DVec2::new(1., 0.) }, key_down=KeyPageUp, modifiers=[KeyShift]},
|
mac_only!(KeyDown(KeyS); modifiers=[KeyCommand], action_dispatch=DocumentMessage::SaveDocument),
|
||||||
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_multiplatform!(
|
||||||
entry! {action=MovementMessage::TranslateCanvasByViewportFraction { delta: DVec2::new(0., -1.) }, key_down=KeyPageDown},
|
standard!(KeyDown(Key0); modifiers=[KeyControl], action_dispatch=DocumentMessage::ZoomCanvasToFitAll),
|
||||||
// Portfolio actions
|
mac_only!(KeyDown(Key0); modifiers=[KeyCommand], action_dispatch=DocumentMessage::ZoomCanvasToFitAll),
|
||||||
entry! {action=PortfolioMessage::OpenDocument, key_down=KeyO, modifiers=[KeyControl]},
|
),
|
||||||
entry! {action=PortfolioMessage::Import, key_down=KeyI, modifiers=[KeyControl]},
|
entry_multiplatform!(
|
||||||
entry! {action=DialogMessage::RequestNewDocumentDialog, key_down=KeyN, modifiers=[KeyControl]},
|
standard!(KeyDown(KeyD); modifiers=[KeyControl], action_dispatch=DocumentMessage::DuplicateSelectedLayers),
|
||||||
entry! {action=PortfolioMessage::NextDocument, key_down=KeyTab, modifiers=[KeyControl]},
|
mac_only!(KeyDown(KeyD); modifiers=[KeyCommand], action_dispatch=DocumentMessage::DuplicateSelectedLayers),
|
||||||
entry! {action=PortfolioMessage::PrevDocument, key_down=KeyTab, modifiers=[KeyControl, KeyShift]},
|
),
|
||||||
entry! {action=DialogMessage::CloseAllDocumentsWithConfirmation, key_down=KeyW, modifiers=[KeyControl, KeyAlt]},
|
entry_multiplatform!(
|
||||||
entry! {action=PortfolioMessage::CloseActiveDocumentWithConfirmation, key_down=KeyW, modifiers=[KeyControl]},
|
standard!(KeyDown(KeyG); modifiers=[KeyControl], action_dispatch=DocumentMessage::GroupSelectedLayers),
|
||||||
entry! {action=PortfolioMessage::Copy { clipboard: Clipboard::Device }, key_down=KeyC, modifiers=[KeyControl]},
|
mac_only!(KeyDown(KeyG); modifiers=[KeyCommand], action_dispatch=DocumentMessage::GroupSelectedLayers),
|
||||||
entry! {action=PortfolioMessage::Cut { clipboard: Clipboard::Device }, key_down=KeyX, modifiers=[KeyControl]},
|
),
|
||||||
// Nudging
|
entry_multiplatform!(
|
||||||
entry! {action=DocumentMessage::NudgeSelectedLayers { delta_x: -SHIFT_NUDGE_AMOUNT, delta_y: -SHIFT_NUDGE_AMOUNT }, key_down=KeyArrowUp, modifiers=[KeyShift, KeyArrowLeft]},
|
standard!(KeyDown(KeyG); modifiers=[KeyControl, KeyShift], action_dispatch=DocumentMessage::UngroupSelectedLayers),
|
||||||
entry! {action=DocumentMessage::NudgeSelectedLayers { delta_x: SHIFT_NUDGE_AMOUNT, delta_y: -SHIFT_NUDGE_AMOUNT }, key_down=KeyArrowUp, modifiers=[KeyShift, KeyArrowRight]},
|
mac_only!(KeyDown(KeyG); modifiers=[KeyCommand, KeyShift], action_dispatch=DocumentMessage::UngroupSelectedLayers),
|
||||||
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_multiplatform!(
|
||||||
entry! {action=DocumentMessage::NudgeSelectedLayers { delta_x: SHIFT_NUDGE_AMOUNT, delta_y: SHIFT_NUDGE_AMOUNT }, key_down=KeyArrowDown, modifiers=[KeyShift, KeyArrowRight]},
|
standard!(KeyDown(KeyN); modifiers=[KeyControl, KeyShift], action_dispatch=DocumentMessage::CreateEmptyFolder { container_path: vec![] }),
|
||||||
entry! {action=DocumentMessage::NudgeSelectedLayers { delta_x: 0., delta_y: SHIFT_NUDGE_AMOUNT }, key_down=KeyArrowDown, modifiers=[KeyShift]},
|
mac_only!(KeyDown(KeyN); modifiers=[KeyCommand, KeyShift], action_dispatch=DocumentMessage::CreateEmptyFolder { container_path: vec![] }),
|
||||||
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_multiplatform!(
|
||||||
entry! {action=DocumentMessage::NudgeSelectedLayers { delta_x: -SHIFT_NUDGE_AMOUNT, delta_y: 0. }, key_down=KeyArrowLeft, modifiers=[KeyShift]},
|
standard!(KeyDown(KeyLeftBracket); modifiers=[KeyControl, KeyShift], action_dispatch=DocumentMessage::SelectedLayersLowerToBack),
|
||||||
entry! {action=DocumentMessage::NudgeSelectedLayers { delta_x: SHIFT_NUDGE_AMOUNT, delta_y: -SHIFT_NUDGE_AMOUNT }, key_down=KeyArrowRight, modifiers=[KeyShift, KeyArrowUp]},
|
mac_only!(KeyDown(KeyLeftBracket); modifiers=[KeyCommand, KeyShift], action_dispatch=DocumentMessage::SelectedLayersLowerToBack),
|
||||||
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_multiplatform!(
|
||||||
entry! {action=DocumentMessage::NudgeSelectedLayers { delta_x: -NUDGE_AMOUNT, delta_y: -NUDGE_AMOUNT }, key_down=KeyArrowUp, modifiers=[KeyArrowLeft]},
|
// 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)
|
||||||
entry! {action=DocumentMessage::NudgeSelectedLayers { delta_x: NUDGE_AMOUNT, delta_y: -NUDGE_AMOUNT }, key_down=KeyArrowUp, modifiers=[KeyArrowRight]},
|
standard!(KeyDown(KeyLeftCurlyBracket); modifiers=[KeyControl, KeyShift], action_dispatch=DocumentMessage::SelectedLayersLowerToBack),
|
||||||
entry! {action=DocumentMessage::NudgeSelectedLayers { delta_x: 0., delta_y: -NUDGE_AMOUNT }, key_down=KeyArrowUp},
|
mac_only!(KeyDown(KeyLeftCurlyBracket); modifiers=[KeyCommand, KeyShift], action_dispatch=DocumentMessage::SelectedLayersLowerToBack),
|
||||||
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_multiplatform!(
|
||||||
entry! {action=DocumentMessage::NudgeSelectedLayers { delta_x: 0., delta_y: NUDGE_AMOUNT }, key_down=KeyArrowDown},
|
standard!(KeyDown(KeyRightBracket); modifiers=[KeyControl, KeyShift], action_dispatch=DocumentMessage::SelectedLayersRaiseToFront),
|
||||||
entry! {action=DocumentMessage::NudgeSelectedLayers { delta_x: -NUDGE_AMOUNT, delta_y: -NUDGE_AMOUNT }, key_down=KeyArrowLeft, modifiers=[KeyArrowUp]},
|
mac_only!(KeyDown(KeyRightBracket); modifiers=[KeyCommand, KeyShift], action_dispatch=DocumentMessage::SelectedLayersRaiseToFront),
|
||||||
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_multiplatform!(
|
||||||
entry! {action=DocumentMessage::NudgeSelectedLayers { delta_x: NUDGE_AMOUNT, delta_y: -NUDGE_AMOUNT }, key_down=KeyArrowRight, modifiers=[KeyArrowUp]},
|
// 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)
|
||||||
entry! {action=DocumentMessage::NudgeSelectedLayers { delta_x: NUDGE_AMOUNT, delta_y: NUDGE_AMOUNT }, key_down=KeyArrowRight, modifiers=[KeyArrowDown]},
|
standard!(KeyDown(KeyRightCurlyBracket); modifiers=[KeyControl, KeyShift], action_dispatch=DocumentMessage::SelectedLayersRaiseToFront),
|
||||||
entry! {action=DocumentMessage::NudgeSelectedLayers { delta_x: NUDGE_AMOUNT, delta_y: 0. }, key_down=KeyArrowRight},
|
mac_only!(KeyDown(KeyRightCurlyBracket); modifiers=[KeyCommand, KeyShift], action_dispatch=DocumentMessage::SelectedLayersRaiseToFront),
|
||||||
// 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_multiplatform!(
|
||||||
entry! {action=DocumentMessage::ReorderSelectedLayers { relative_index_offset: 1 }, key_down=KeyRightBracket, modifiers=[KeyControl]},
|
standard!(KeyDown(KeyLeftBracket); modifiers=[KeyControl], action_dispatch=DocumentMessage::SelectedLayersLower),
|
||||||
entry! {action=DocumentMessage::ReorderSelectedLayers { relative_index_offset: -1 }, key_down=KeyLeftBracket, modifiers=[KeyControl]},
|
mac_only!(KeyDown(KeyLeftBracket); modifiers=[KeyCommand], action_dispatch=DocumentMessage::SelectedLayersLower),
|
||||||
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_multiplatform!(
|
||||||
entry! {action=DebugMessage::ToggleTraceLogs, key_down=KeyT, modifiers=[KeyAlt]},
|
standard!(KeyDown(KeyRightBracket); modifiers=[KeyControl], action_dispatch=DocumentMessage::SelectedLayersRaise),
|
||||||
entry! {action=DebugMessage::MessageOff, key_down=Key0, modifiers=[KeyAlt]},
|
mac_only!(KeyDown(KeyRightBracket); modifiers=[KeyCommand], action_dispatch=DocumentMessage::SelectedLayersRaise),
|
||||||
entry! {action=DebugMessage::MessageNames, key_down=Key1, modifiers=[KeyAlt]},
|
),
|
||||||
entry! {action=DebugMessage::MessageContents, key_down=Key2, modifiers=[KeyAlt]},
|
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
|
// 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];
|
const NUMBER_KEYS: [Key; 10] = [Key0, Key1, Key2, Key3, Key4, Key5, Key6, Key7, Key8, Key9];
|
||||||
|
@ -239,8 +346,9 @@ impl Default for Mapping {
|
||||||
0,
|
0,
|
||||||
MappingEntry {
|
MappingEntry {
|
||||||
action: TransformLayerMessage::TypeDigit { digit: i as u8 }.into(),
|
action: TransformLayerMessage::TypeDigit { digit: i as u8 }.into(),
|
||||||
trigger: InputMapperMessage::KeyDown(*key),
|
input: InputMapperMessage::KeyDown(*key),
|
||||||
modifiers: modifiers! {},
|
platform_layout: None,
|
||||||
|
modifiers: modifiers!(),
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -251,48 +359,70 @@ impl Default for Mapping {
|
||||||
sort(sublist);
|
sort(sublist);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
sort(&mut pointer_move);
|
|
||||||
sort(&mut mouse_scroll);
|
|
||||||
sort(&mut double_click);
|
sort(&mut double_click);
|
||||||
|
sort(&mut wheel_scroll);
|
||||||
|
sort(&mut pointer_move);
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
key_up,
|
key_up,
|
||||||
key_down,
|
key_down,
|
||||||
pointer_move,
|
|
||||||
mouse_scroll,
|
|
||||||
double_click,
|
double_click,
|
||||||
|
wheel_scroll,
|
||||||
|
pointer_move,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Mapping {
|
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 {
|
let list = match message {
|
||||||
InputMapperMessage::KeyDown(key) => &self.key_down[key as usize],
|
InputMapperMessage::KeyDown(key) => &self.key_down[key as usize],
|
||||||
InputMapperMessage::KeyUp(key) => &self.key_up[key as usize],
|
InputMapperMessage::KeyUp(key) => &self.key_up[key as usize],
|
||||||
InputMapperMessage::DoubleClick => &self.double_click,
|
InputMapperMessage::DoubleClick => &self.double_click,
|
||||||
InputMapperMessage::MouseScroll => &self.mouse_scroll,
|
InputMapperMessage::WheelScroll => &self.wheel_scroll,
|
||||||
InputMapperMessage::PointerMove => &self.pointer_move,
|
InputMapperMessage::PointerMove => &self.pointer_move,
|
||||||
};
|
};
|
||||||
list.match_mapping(keys, actions)
|
list.match_mapping(keyboard_state, actions, keyboard_platform)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(PartialEq, Clone, Debug)]
|
#[derive(PartialEq, Clone, Debug)]
|
||||||
pub struct MappingEntry {
|
pub struct MappingEntry {
|
||||||
pub trigger: InputMapperMessage,
|
/// Serves two purposes:
|
||||||
pub modifiers: KeyStates,
|
/// - 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,
|
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)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct KeyMappingEntries(pub Vec<MappingEntry>);
|
pub struct KeyMappingEntries(pub Vec<MappingEntry>);
|
||||||
|
|
||||||
impl KeyMappingEntries {
|
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() {
|
for entry in self.0.iter() {
|
||||||
let all_required_modifiers_pressed = ((*keys & entry.modifiers) ^ entry.modifiers).is_empty();
|
// Skip this entry if it is platform-specific, and for a layout that does not match the user's keyboard platform layout
|
||||||
if all_required_modifiers_pressed && actions.iter().flatten().any(|action| entry.action.to_discriminant() == *action) {
|
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());
|
return Some(entry.action.clone());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -313,70 +443,69 @@ impl KeyMappingEntries {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for KeyMappingEntries {
|
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||||
fn default() -> Self {
|
pub enum ActionKeys {
|
||||||
Self::new()
|
Action(MessageDiscriminant),
|
||||||
}
|
#[serde(rename = "keys")]
|
||||||
|
Keys(Vec<Key>),
|
||||||
}
|
}
|
||||||
|
|
||||||
mod input_mapper_macros {
|
impl ActionKeys {
|
||||||
macro_rules! modifiers {
|
pub fn to_keys(&mut self, action_input_mapping: &impl Fn(&MessageDiscriminant) -> Vec<Vec<Key>>) {
|
||||||
($($m:ident),*) => {{
|
match self {
|
||||||
#[allow(unused_mut)]
|
ActionKeys::Action(action) => {
|
||||||
let mut state = KeyStates::new();
|
if let Some(keys) = action_input_mapping(action).get_mut(0) {
|
||||||
$(
|
let mut taken_keys = Vec::new();
|
||||||
state.set(Key::$m as usize);
|
std::mem::swap(keys, &mut taken_keys);
|
||||||
)*
|
|
||||||
state
|
|
||||||
}};
|
|
||||||
}
|
|
||||||
|
|
||||||
macro_rules! entry {
|
*self = ActionKeys::Keys(taken_keys);
|
||||||
{action=$action:expr, key_down=$key:ident $(, modifiers=[$($m:ident),* $(,)?])?} => {{
|
} else {
|
||||||
entry!{action=$action, message=InputMapperMessage::KeyDown(Key::$key) $(, modifiers=[$($m),*])?}
|
*self = ActionKeys::Keys(Vec::new());
|
||||||
}};
|
|
||||||
{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());
|
|
||||||
}
|
}
|
||||||
)*
|
}
|
||||||
(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;
|
joined
|
||||||
pub(crate) use mapping;
|
}
|
||||||
pub(crate) use modifiers;
|
|
||||||
|
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
|
// Messages
|
||||||
DoubleClick,
|
DoubleClick,
|
||||||
MouseScroll,
|
|
||||||
PointerMove,
|
PointerMove,
|
||||||
|
WheelScroll,
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
use super::input_mapper::Mapping;
|
use super::input_mapper::Mapping;
|
||||||
use super::keyboard::Key;
|
use super::keyboard::Key;
|
||||||
use super::InputPreprocessorMessageHandler;
|
use super::InputPreprocessorMessageHandler;
|
||||||
|
use crate::document::utility_types::KeyboardPlatformLayout;
|
||||||
use crate::message_prelude::*;
|
use crate::message_prelude::*;
|
||||||
|
|
||||||
use std::fmt::Write;
|
use std::fmt::Write;
|
||||||
|
@ -31,12 +32,67 @@ impl InputMapperMessageHandler {
|
||||||
});
|
});
|
||||||
output.replace("Key", "")
|
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 {
|
impl MessageHandler<InputMapperMessage, (&InputPreprocessorMessageHandler, KeyboardPlatformLayout, ActionList)> for InputMapperMessageHandler {
|
||||||
fn process_action(&mut self, message: InputMapperMessage, data: (&InputPreprocessorMessageHandler, ActionList), responses: &mut VecDeque<Message>) {
|
fn process_action(&mut self, message: InputMapperMessage, data: (&InputPreprocessorMessageHandler, KeyboardPlatformLayout, ActionList), responses: &mut VecDeque<Message>) {
|
||||||
let (input, actions) = data;
|
let (input, keyboard_platform, actions) = data;
|
||||||
if let Some(message) = self.mapping.match_message(message, &input.keyboard, actions) {
|
|
||||||
|
if let Some(message) = self.mapping.match_input_message(message, &input.keyboard, actions, keyboard_platform) {
|
||||||
responses.push_back(message);
|
responses.push_back(message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,14 +13,16 @@ bitflags! {
|
||||||
#[derive(Default, Serialize, Deserialize)]
|
#[derive(Default, Serialize, Deserialize)]
|
||||||
#[repr(transparent)]
|
#[repr(transparent)]
|
||||||
pub struct ModifierKeys: u8 {
|
pub struct ModifierKeys: u8 {
|
||||||
const CONTROL = 0b0000_0001;
|
const SHIFT = 0b0000_0001;
|
||||||
const SHIFT = 0b0000_0010;
|
const ALT = 0b0000_0010;
|
||||||
const ALT = 0b0000_0100;
|
const CONTROL = 0b0000_0100;
|
||||||
|
const META_OR_COMMAND = 0b0000_1000;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
|
use crate::document::utility_types::KeyboardPlatformLayout;
|
||||||
use crate::input::input_preprocessor::ModifierKeys;
|
use crate::input::input_preprocessor::ModifierKeys;
|
||||||
use crate::input::keyboard::Key;
|
use crate::input::keyboard::Key;
|
||||||
use crate::input::mouse::EditorMouseState;
|
use crate::input::mouse::EditorMouseState;
|
||||||
|
@ -39,7 +41,7 @@ mod test {
|
||||||
|
|
||||||
let mut responses = VecDeque::new();
|
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!(input_preprocessor.keyboard.get(Key::KeyAlt as usize));
|
||||||
assert_eq!(responses.pop_front(), Some(InputMapperMessage::KeyDown(Key::KeyAlt).into()));
|
assert_eq!(responses.pop_front(), Some(InputMapperMessage::KeyDown(Key::KeyAlt).into()));
|
||||||
|
@ -55,7 +57,7 @@ mod test {
|
||||||
|
|
||||||
let mut responses = VecDeque::new();
|
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::KeyControl as usize));
|
||||||
assert_eq!(responses.pop_front(), Some(InputMapperMessage::KeyDown(Key::KeyControl).into()));
|
assert_eq!(responses.pop_front(), Some(InputMapperMessage::KeyDown(Key::KeyControl).into()));
|
||||||
|
@ -71,7 +73,7 @@ mod test {
|
||||||
|
|
||||||
let mut responses = VecDeque::new();
|
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!(input_preprocessor.keyboard.get(Key::KeyShift as usize));
|
||||||
assert_eq!(responses.pop_front(), Some(InputMapperMessage::KeyDown(Key::KeyShift).into()));
|
assert_eq!(responses.pop_front(), Some(InputMapperMessage::KeyDown(Key::KeyShift).into()));
|
||||||
|
@ -88,7 +90,7 @@ mod test {
|
||||||
|
|
||||||
let mut responses = VecDeque::new();
|
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::KeyControl as usize));
|
||||||
assert_eq!(responses.pop_front(), Some(InputMapperMessage::KeyUp(Key::KeyControl).into()));
|
assert_eq!(responses.pop_front(), Some(InputMapperMessage::KeyUp(Key::KeyControl).into()));
|
||||||
|
@ -104,7 +106,7 @@ mod test {
|
||||||
|
|
||||||
let mut responses = VecDeque::new();
|
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::KeyControl as usize));
|
||||||
assert!(input_preprocessor.keyboard.get(Key::KeyShift 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 },
|
DoubleClick { editor_mouse_state: EditorMouseState, modifier_keys: ModifierKeys },
|
||||||
KeyDown { key: Key, modifier_keys: ModifierKeys },
|
KeyDown { key: Key, modifier_keys: ModifierKeys },
|
||||||
KeyUp { 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 },
|
PointerDown { editor_mouse_state: EditorMouseState, modifier_keys: ModifierKeys },
|
||||||
PointerMove { editor_mouse_state: EditorMouseState, modifier_keys: ModifierKeys },
|
PointerMove { editor_mouse_state: EditorMouseState, modifier_keys: ModifierKeys },
|
||||||
PointerUp { 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::input_preprocessor::ModifierKeys;
|
||||||
use super::keyboard::{Key, KeyStates};
|
use super::keyboard::{Key, KeyStates};
|
||||||
use super::mouse::{MouseKeys, MouseState, ViewportBounds};
|
use super::mouse::{MouseKeys, MouseState, ViewportBounds};
|
||||||
|
use crate::document::utility_types::KeyboardPlatformLayout;
|
||||||
use crate::message_prelude::*;
|
use crate::message_prelude::*;
|
||||||
|
|
||||||
#[doc(inline)]
|
#[doc(inline)]
|
||||||
|
@ -15,9 +16,11 @@ pub struct InputPreprocessorMessageHandler {
|
||||||
pub viewport_bounds: ViewportBounds,
|
pub viewport_bounds: ViewportBounds,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MessageHandler<InputPreprocessorMessage, ()> for InputPreprocessorMessageHandler {
|
impl MessageHandler<InputPreprocessorMessage, KeyboardPlatformLayout> for InputPreprocessorMessageHandler {
|
||||||
#[remain::check]
|
#[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]
|
#[remain::sorted]
|
||||||
match message {
|
match message {
|
||||||
InputPreprocessorMessage::BoundsOfViewports { bounds_of_viewports } => {
|
InputPreprocessorMessage::BoundsOfViewports { bounds_of_viewports } => {
|
||||||
|
@ -53,7 +56,7 @@ impl MessageHandler<InputPreprocessorMessage, ()> for InputPreprocessorMessageHa
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
InputPreprocessorMessage::DoubleClick { editor_mouse_state, modifier_keys } => {
|
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);
|
let mouse_state = editor_mouse_state.to_mouse_state(&self.viewport_bounds);
|
||||||
self.mouse.position = mouse_state.position;
|
self.mouse.position = mouse_state.position;
|
||||||
|
@ -61,26 +64,17 @@ impl MessageHandler<InputPreprocessorMessage, ()> for InputPreprocessorMessageHa
|
||||||
responses.push_back(InputMapperMessage::DoubleClick.into());
|
responses.push_back(InputMapperMessage::DoubleClick.into());
|
||||||
}
|
}
|
||||||
InputPreprocessorMessage::KeyDown { key, modifier_keys } => {
|
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);
|
self.keyboard.set(key as usize);
|
||||||
responses.push_back(InputMapperMessage::KeyDown(key).into());
|
responses.push_back(InputMapperMessage::KeyDown(key).into());
|
||||||
}
|
}
|
||||||
InputPreprocessorMessage::KeyUp { key, modifier_keys } => {
|
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);
|
self.keyboard.unset(key as usize);
|
||||||
responses.push_back(InputMapperMessage::KeyUp(key).into());
|
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 } => {
|
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);
|
let mouse_state = editor_mouse_state.to_mouse_state(&self.viewport_bounds);
|
||||||
self.mouse.position = mouse_state.position;
|
self.mouse.position = mouse_state.position;
|
||||||
|
@ -88,7 +82,7 @@ impl MessageHandler<InputPreprocessorMessage, ()> for InputPreprocessorMessageHa
|
||||||
self.translate_mouse_event(mouse_state, true, responses);
|
self.translate_mouse_event(mouse_state, true, responses);
|
||||||
}
|
}
|
||||||
InputPreprocessorMessage::PointerMove { editor_mouse_state, modifier_keys } => {
|
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);
|
let mouse_state = editor_mouse_state.to_mouse_state(&self.viewport_bounds);
|
||||||
self.mouse.position = mouse_state.position;
|
self.mouse.position = mouse_state.position;
|
||||||
|
@ -99,13 +93,22 @@ impl MessageHandler<InputPreprocessorMessage, ()> for InputPreprocessorMessageHa
|
||||||
self.translate_mouse_event(mouse_state, false, responses);
|
self.translate_mouse_event(mouse_state, false, responses);
|
||||||
}
|
}
|
||||||
InputPreprocessorMessage::PointerUp { editor_mouse_state, modifier_keys } => {
|
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);
|
let mouse_state = editor_mouse_state.to_mouse_state(&self.viewport_bounds);
|
||||||
self.mouse.position = mouse_state.position;
|
self.mouse.position = mouse_state.position;
|
||||||
|
|
||||||
self.translate_mouse_event(mouse_state, false, responses);
|
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;
|
self.mouse = new_state;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handle_modifier_keys(&mut self, modifier_keys: ModifierKeys, responses: &mut VecDeque<Message>) {
|
fn handle_modifier_keys(&mut self, modifier_keys: ModifierKeys, keyboard_platform: KeyboardPlatformLayout, responses: &mut VecDeque<Message>) {
|
||||||
self.handle_modifier_key(Key::KeyControl, modifier_keys.contains(ModifierKeys::CONTROL), responses);
|
|
||||||
self.handle_modifier_key(Key::KeyShift, modifier_keys.contains(ModifierKeys::SHIFT), responses);
|
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::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>) {
|
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 crate::message_prelude::*;
|
||||||
|
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::fmt::{Display, Formatter};
|
use std::fmt::{self, Display, Formatter};
|
||||||
use std::ops::{BitAnd, BitAndAssign, BitOr, BitOrAssign, BitXor, BitXorAssign};
|
use std::ops::{BitAnd, BitAndAssign, BitOr, BitOrAssign, BitXor, BitXorAssign};
|
||||||
|
|
||||||
// TODO: Increase size of type
|
// TODO: Increase size of type
|
||||||
|
@ -21,7 +21,7 @@ pub type KeyStates = BitVector<KEY_MASK_STORAGE_LENGTH>;
|
||||||
pub enum Key {
|
pub enum Key {
|
||||||
UnknownKey,
|
UnknownKey,
|
||||||
|
|
||||||
// MouseKeys
|
// Mouse keys
|
||||||
Lmb,
|
Lmb,
|
||||||
Rmb,
|
Rmb,
|
||||||
Mmb,
|
Mmb,
|
||||||
|
@ -70,6 +70,8 @@ pub enum Key {
|
||||||
KeyShift,
|
KeyShift,
|
||||||
KeySpace,
|
KeySpace,
|
||||||
KeyControl,
|
KeyControl,
|
||||||
|
KeyCommand,
|
||||||
|
KeyMeta,
|
||||||
KeyDelete,
|
KeyDelete,
|
||||||
KeyBackspace,
|
KeyBackspace,
|
||||||
KeyAlt,
|
KeyAlt,
|
||||||
|
@ -92,6 +94,16 @@ pub enum Key {
|
||||||
NumKeys,
|
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;
|
pub const NUMBER_OF_KEYS: usize = Key::NumKeys as usize;
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
||||||
|
@ -162,6 +174,10 @@ impl<const LENGTH: usize> BitVector<LENGTH> {
|
||||||
|
|
||||||
result
|
result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn iter(&self) -> impl Iterator<Item = usize> + '_ {
|
||||||
|
BitVectorIter::<LENGTH> { bitvector: self, iter_index: 0 }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<const LENGTH: usize> Default for BitVector<LENGTH> {
|
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> {
|
impl<const LENGTH: usize> Display for BitVector<LENGTH> {
|
||||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||||
for storage in self.0.iter().rev() {
|
for storage in self.0.iter().rev() {
|
||||||
|
|
|
@ -3,6 +3,7 @@ pub mod input_preprocessor;
|
||||||
pub mod keyboard;
|
pub mod keyboard;
|
||||||
pub mod mouse;
|
pub mod mouse;
|
||||||
|
|
||||||
|
mod input_mapper_macros;
|
||||||
mod input_mapper_message;
|
mod input_mapper_message;
|
||||||
mod input_mapper_message_handler;
|
mod input_mapper_message_handler;
|
||||||
mod input_preprocessor_message;
|
mod input_preprocessor_message;
|
||||||
|
|
|
@ -5,7 +5,7 @@ use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
#[remain::sorted]
|
#[remain::sorted]
|
||||||
#[impl_message(Message, Layout)]
|
#[impl_message(Message, Layout)]
|
||||||
#[derive(PartialEq, Clone, Deserialize, Serialize, Debug)]
|
#[derive(PartialEq, Clone, Debug, Serialize, Deserialize)]
|
||||||
pub enum LayoutMessage {
|
pub enum LayoutMessage {
|
||||||
RefreshLayout { layout_target: LayoutTarget },
|
RefreshLayout { layout_target: LayoutTarget },
|
||||||
SendLayout { layout: Layout, layout_target: LayoutTarget },
|
SendLayout { layout: Layout, layout_target: LayoutTarget },
|
||||||
|
@ -13,7 +13,7 @@ pub enum LayoutMessage {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[remain::sorted]
|
#[remain::sorted]
|
||||||
#[derive(PartialEq, Clone, Deserialize, Serialize, Debug, Hash, Eq, Copy)]
|
#[derive(PartialEq, Clone, Debug, Hash, Eq, Copy, Serialize, Deserialize)]
|
||||||
#[repr(u8)]
|
#[repr(u8)]
|
||||||
pub enum LayoutTarget {
|
pub enum LayoutTarget {
|
||||||
DialogDetails,
|
DialogDetails,
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
use super::layout_message::LayoutTarget;
|
use super::layout_message::LayoutTarget;
|
||||||
use super::widgets::Layout;
|
use super::widgets::Layout;
|
||||||
|
use crate::document::utility_types::KeyboardPlatformLayout;
|
||||||
|
use crate::input::keyboard::Key;
|
||||||
use crate::layout::widgets::Widget;
|
use crate::layout::widgets::Widget;
|
||||||
use crate::message_prelude::*;
|
use crate::message_prelude::*;
|
||||||
|
|
||||||
|
@ -15,49 +17,55 @@ pub struct LayoutMessageHandler {
|
||||||
|
|
||||||
impl LayoutMessageHandler {
|
impl LayoutMessageHandler {
|
||||||
#[remain::check]
|
#[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];
|
let layout = &self.layouts[layout_target as usize];
|
||||||
#[remain::sorted]
|
#[remain::sorted]
|
||||||
let message = match layout_target {
|
let message = match layout_target {
|
||||||
LayoutTarget::DialogDetails => FrontendMessage::UpdateDialogDetails {
|
LayoutTarget::DialogDetails => FrontendMessage::UpdateDialogDetails {
|
||||||
layout_target,
|
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 {
|
LayoutTarget::DocumentBar => FrontendMessage::UpdateDocumentBarLayout {
|
||||||
layout_target,
|
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 {
|
LayoutTarget::DocumentMode => FrontendMessage::UpdateDocumentModeLayout {
|
||||||
layout_target,
|
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 {
|
LayoutTarget::LayerTreeOptions => FrontendMessage::UpdateLayerTreeOptionsLayout {
|
||||||
layout_target,
|
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 {
|
LayoutTarget::MenuBar => FrontendMessage::UpdateMenuBarLayout {
|
||||||
layout_target,
|
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 {
|
LayoutTarget::PropertiesOptions => FrontendMessage::UpdatePropertyPanelOptionsLayout {
|
||||||
layout_target,
|
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 {
|
LayoutTarget::PropertiesSections => FrontendMessage::UpdatePropertyPanelSectionsLayout {
|
||||||
layout_target,
|
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 {
|
LayoutTarget::ToolOptions => FrontendMessage::UpdateToolOptionsLayout {
|
||||||
layout_target,
|
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 {
|
LayoutTarget::ToolShelf => FrontendMessage::UpdateToolShelfLayout {
|
||||||
layout_target,
|
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 {
|
LayoutTarget::WorkingColors => FrontendMessage::UpdateWorkingColorsLayout {
|
||||||
layout_target,
|
layout_target,
|
||||||
layout: layout.clone().unwrap_widget_layout().layout,
|
layout: layout.clone().unwrap_widget_layout(action_input_mapping, keyboard_platform).layout,
|
||||||
},
|
},
|
||||||
|
|
||||||
#[remain::unsorted]
|
#[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]
|
#[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::*;
|
use LayoutMessage::*;
|
||||||
#[remain::sorted]
|
#[remain::sorted]
|
||||||
match action {
|
match action {
|
||||||
RefreshLayout { layout_target } => {
|
RefreshLayout { layout_target } => {
|
||||||
self.send_layout(layout_target, responses);
|
self.send_layout(layout_target, responses, &action_input_mapping, keyboard_platform);
|
||||||
}
|
}
|
||||||
SendLayout { layout, layout_target } => {
|
SendLayout { layout, layout_target } => {
|
||||||
self.layouts[layout_target as usize] = layout;
|
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 } => {
|
UpdateLayout { layout_target, widget_id, value } => {
|
||||||
let layout = &mut self.layouts[layout_target as usize];
|
let layout = &mut self.layouts[layout_target as usize];
|
||||||
|
|
|
@ -1,4 +1,7 @@
|
||||||
use super::layout_message::LayoutTarget;
|
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::input::keyboard::Key;
|
||||||
use crate::message_prelude::*;
|
use crate::message_prelude::*;
|
||||||
use crate::Color;
|
use crate::Color;
|
||||||
|
@ -30,16 +33,67 @@ pub enum Layout {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Layout {
|
impl Layout {
|
||||||
pub fn unwrap_widget_layout(self) -> WidgetLayout {
|
pub fn unwrap_widget_layout(self, action_input_mapping: &impl Fn(&MessageDiscriminant) -> Vec<Vec<Key>>, keyboard_platform: KeyboardPlatformLayout) -> WidgetLayout {
|
||||||
if let Layout::WidgetLayout(widget_layout) = self {
|
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
|
widget_layout
|
||||||
} else {
|
} else {
|
||||||
panic!("Tried to unwrap layout as WidgetLayout. Got {:?}", self)
|
panic!("Tried to unwrap layout as WidgetLayout. Got {:?}", self)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn unwrap_menu_layout(self) -> MenuLayout {
|
pub fn unwrap_menu_layout(self, action_input_mapping: &impl Fn(&MessageDiscriminant) -> Vec<Vec<Key>>, _keyboard_platform: KeyboardPlatformLayout) -> MenuLayout {
|
||||||
if let Layout::MenuLayout(menu_layout) = self {
|
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
|
menu_layout
|
||||||
} else {
|
} else {
|
||||||
panic!("Tried to unwrap layout as MenuLayout. Got {:?}", self)
|
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)]
|
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||||
pub struct MenuEntry {
|
pub struct MenuEntry {
|
||||||
pub label: String,
|
pub label: String,
|
||||||
pub icon: Option<String>,
|
pub icon: Option<String>,
|
||||||
pub children: Option<Vec<Vec<MenuEntry>>>,
|
pub children: MenuEntryGroups,
|
||||||
pub action: WidgetHolder,
|
pub action: WidgetHolder,
|
||||||
pub shortcut: Option<Vec<Key>>,
|
pub shortcut: Option<ActionKeys>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MenuEntry {
|
impl MenuEntry {
|
||||||
|
@ -94,7 +170,7 @@ impl Default for MenuEntry {
|
||||||
action: MenuEntry::create_action(|_| DialogMessage::RequestComingSoonDialog { issue: None }.into()),
|
action: MenuEntry::create_action(|_| DialogMessage::RequestComingSoonDialog { issue: None }.into()),
|
||||||
label: "".into(),
|
label: "".into(),
|
||||||
icon: None,
|
icon: None,
|
||||||
children: None,
|
children: MenuEntryGroups::empty(),
|
||||||
shortcut: None,
|
shortcut: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -103,10 +179,10 @@ impl Default for MenuEntry {
|
||||||
#[derive(Debug, Default, Clone, Serialize, Deserialize, PartialEq)]
|
#[derive(Debug, Default, Clone, Serialize, Deserialize, PartialEq)]
|
||||||
pub struct MenuColumn {
|
pub struct MenuColumn {
|
||||||
pub label: String,
|
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 struct MenuLayout {
|
||||||
pub layout: Vec<MenuColumn>,
|
pub layout: Vec<MenuColumn>,
|
||||||
}
|
}
|
||||||
|
@ -118,13 +194,13 @@ impl MenuLayout {
|
||||||
|
|
||||||
pub fn iter(&self) -> impl Iterator<Item = &WidgetHolder> + '_ {
|
pub fn iter(&self) -> impl Iterator<Item = &WidgetHolder> + '_ {
|
||||||
MenuLayoutIter {
|
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> + '_ {
|
pub fn iter_mut(&mut self) -> impl Iterator<Item = &mut WidgetHolder> + '_ {
|
||||||
MenuLayoutIterMut {
|
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> {
|
fn next(&mut self) -> Option<Self::Item> {
|
||||||
match self.stack.pop() {
|
match self.stack.pop() {
|
||||||
Some(menu_entry) => {
|
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)
|
Some(&menu_entry.action)
|
||||||
}
|
}
|
||||||
None => None,
|
None => None,
|
||||||
|
@ -158,7 +236,9 @@ impl<'a> Iterator for MenuLayoutIterMut<'a> {
|
||||||
fn next(&mut self) -> Option<Self::Item> {
|
fn next(&mut self) -> Option<Self::Item> {
|
||||||
match self.stack.pop() {
|
match self.stack.pop() {
|
||||||
Some(menu_entry) => {
|
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)
|
Some(&mut menu_entry.action)
|
||||||
}
|
}
|
||||||
None => None,
|
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 {
|
pub struct WidgetHolder {
|
||||||
#[serde(rename = "widgetId")]
|
#[serde(rename = "widgetId")]
|
||||||
pub widget_id: u64,
|
pub widget_id: u64,
|
||||||
|
@ -332,7 +412,7 @@ pub enum Widget {
|
||||||
TextLabel(TextLabel),
|
TextLabel(TextLabel),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Serialize, Deserialize, Derivative, Default)]
|
#[derive(Clone, Default, Derivative, Serialize, Deserialize)]
|
||||||
#[derivative(Debug, PartialEq)]
|
#[derivative(Debug, PartialEq)]
|
||||||
pub struct CheckboxInput {
|
pub struct CheckboxInput {
|
||||||
pub checked: bool,
|
pub checked: bool,
|
||||||
|
@ -341,13 +421,16 @@ pub struct CheckboxInput {
|
||||||
|
|
||||||
pub tooltip: String,
|
pub tooltip: String,
|
||||||
|
|
||||||
|
#[serde(skip)]
|
||||||
|
pub tooltip_shortcut: Option<ActionKeys>,
|
||||||
|
|
||||||
// Callbacks
|
// Callbacks
|
||||||
#[serde(skip)]
|
#[serde(skip)]
|
||||||
#[derivative(Debug = "ignore", PartialEq = "ignore")]
|
#[derivative(Debug = "ignore", PartialEq = "ignore")]
|
||||||
pub on_update: WidgetCallback<CheckboxInput>,
|
pub on_update: WidgetCallback<CheckboxInput>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Serialize, Deserialize, Derivative)]
|
#[derive(Clone, Derivative, Serialize, Deserialize)]
|
||||||
#[derivative(Debug, PartialEq, Default)]
|
#[derivative(Debug, PartialEq, Default)]
|
||||||
pub struct ColorInput {
|
pub struct ColorInput {
|
||||||
pub value: Option<String>,
|
pub value: Option<String>,
|
||||||
|
@ -362,6 +445,9 @@ pub struct ColorInput {
|
||||||
|
|
||||||
pub tooltip: String,
|
pub tooltip: String,
|
||||||
|
|
||||||
|
#[serde(skip)]
|
||||||
|
pub tooltip_shortcut: Option<ActionKeys>,
|
||||||
|
|
||||||
// Callbacks
|
// Callbacks
|
||||||
#[serde(skip)]
|
#[serde(skip)]
|
||||||
#[derivative(Debug = "ignore", PartialEq = "ignore")]
|
#[derivative(Debug = "ignore", PartialEq = "ignore")]
|
||||||
|
@ -435,7 +521,7 @@ pub struct FontInput {
|
||||||
pub on_update: WidgetCallback<FontInput>,
|
pub on_update: WidgetCallback<FontInput>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Serialize, Deserialize, Derivative, Default)]
|
#[derive(Clone, Default, Derivative, Serialize, Deserialize)]
|
||||||
#[derivative(Debug, PartialEq)]
|
#[derivative(Debug, PartialEq)]
|
||||||
pub struct IconButton {
|
pub struct IconButton {
|
||||||
pub icon: String,
|
pub icon: String,
|
||||||
|
@ -446,6 +532,9 @@ pub struct IconButton {
|
||||||
|
|
||||||
pub tooltip: String,
|
pub tooltip: String,
|
||||||
|
|
||||||
|
#[serde(skip)]
|
||||||
|
pub tooltip_shortcut: Option<ActionKeys>,
|
||||||
|
|
||||||
// Callbacks
|
// Callbacks
|
||||||
#[serde(skip)]
|
#[serde(skip)]
|
||||||
#[derivative(Debug = "ignore", PartialEq = "ignore")]
|
#[derivative(Debug = "ignore", PartialEq = "ignore")]
|
||||||
|
@ -533,7 +622,7 @@ pub enum NumberInputIncrementBehavior {
|
||||||
Callback,
|
Callback,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Serialize, Deserialize, Derivative, Default)]
|
#[derive(Clone, Default, Derivative, Serialize, Deserialize)]
|
||||||
#[derivative(Debug, PartialEq)]
|
#[derivative(Debug, PartialEq)]
|
||||||
pub struct OptionalInput {
|
pub struct OptionalInput {
|
||||||
pub checked: bool,
|
pub checked: bool,
|
||||||
|
@ -542,6 +631,9 @@ pub struct OptionalInput {
|
||||||
|
|
||||||
pub tooltip: String,
|
pub tooltip: String,
|
||||||
|
|
||||||
|
#[serde(skip)]
|
||||||
|
pub tooltip_shortcut: Option<ActionKeys>,
|
||||||
|
|
||||||
// Callbacks
|
// Callbacks
|
||||||
#[serde(skip)]
|
#[serde(skip)]
|
||||||
#[derivative(Debug = "ignore", PartialEq = "ignore")]
|
#[derivative(Debug = "ignore", PartialEq = "ignore")]
|
||||||
|
@ -559,7 +651,7 @@ pub struct PopoverButton {
|
||||||
pub text: String,
|
pub text: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Serialize, Deserialize, Derivative, Default)]
|
#[derive(Clone, Default, Derivative, Serialize, Deserialize)]
|
||||||
#[derivative(Debug, PartialEq)]
|
#[derivative(Debug, PartialEq)]
|
||||||
pub struct RadioInput {
|
pub struct RadioInput {
|
||||||
pub entries: Vec<RadioEntryData>,
|
pub entries: Vec<RadioEntryData>,
|
||||||
|
@ -569,7 +661,7 @@ pub struct RadioInput {
|
||||||
pub selected_index: u32,
|
pub selected_index: u32,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Serialize, Deserialize, Derivative, Default)]
|
#[derive(Clone, Default, Derivative, Serialize, Deserialize)]
|
||||||
#[derivative(Debug, PartialEq)]
|
#[derivative(Debug, PartialEq)]
|
||||||
pub struct RadioEntryData {
|
pub struct RadioEntryData {
|
||||||
pub value: String,
|
pub value: String,
|
||||||
|
@ -580,6 +672,9 @@ pub struct RadioEntryData {
|
||||||
|
|
||||||
pub tooltip: String,
|
pub tooltip: String,
|
||||||
|
|
||||||
|
#[serde(skip)]
|
||||||
|
pub tooltip_shortcut: Option<ActionKeys>,
|
||||||
|
|
||||||
// Callbacks
|
// Callbacks
|
||||||
#[serde(skip)]
|
#[serde(skip)]
|
||||||
#[derivative(Debug = "ignore", PartialEq = "ignore")]
|
#[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::select_tool::{SelectToolMessage, SelectToolMessageDiscriminant};
|
||||||
pub use crate::viewport_tools::tools::shape_tool::{ShapeToolMessage, ShapeToolMessageDiscriminant};
|
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::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 crate::workspace::{WorkspaceMessage, WorkspaceMessageDiscriminant};
|
||||||
pub use graphite_proc_macros::*;
|
pub use graphite_proc_macros::*;
|
||||||
|
|
||||||
|
|
|
@ -10,10 +10,19 @@ pub struct HintGroup(pub Vec<HintInfo>);
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
pub struct HintInfo {
|
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>,
|
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>,
|
pub mouse: Option<MouseMotion>,
|
||||||
|
/// The text describing what occurs with this input combination.
|
||||||
pub label: String,
|
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,
|
pub plus: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
use super::tools::*;
|
use super::tools::*;
|
||||||
use crate::communication::message_handler::MessageHandler;
|
use crate::communication::message_handler::MessageHandler;
|
||||||
use crate::document::DocumentMessageHandler;
|
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::input::InputPreprocessorMessageHandler;
|
||||||
use crate::layout::widgets::{IconButton, Layout, LayoutGroup, PropertyHolder, Separator, SeparatorDirection, SeparatorType, Widget, WidgetCallback, WidgetHolder, WidgetLayout};
|
use crate::layout::widgets::{IconButton, Layout, LayoutGroup, PropertyHolder, Separator, SeparatorDirection, SeparatorType, Widget, WidgetCallback, WidgetHolder, WidgetLayout};
|
||||||
use crate::message_prelude::*;
|
use crate::message_prelude::*;
|
||||||
|
@ -115,6 +117,7 @@ impl ToolData {
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct ToolBarMetadataGroup {
|
pub struct ToolBarMetadataGroup {
|
||||||
pub tooltip: String,
|
pub tooltip: String,
|
||||||
|
pub tooltip_shortcut: Option<ActionKeys>,
|
||||||
pub icon_name: String,
|
pub icon_name: String,
|
||||||
pub tool_type: ToolType,
|
pub tool_type: ToolType,
|
||||||
}
|
}
|
||||||
|
@ -123,18 +126,24 @@ impl PropertyHolder for ToolData {
|
||||||
fn properties(&self) -> Layout {
|
fn properties(&self) -> Layout {
|
||||||
let tool_groups_layout = list_tools_in_groups()
|
let tool_groups_layout = list_tools_in_groups()
|
||||||
.iter()
|
.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())
|
.chain(coming_soon_tools())
|
||||||
.flat_map(|group| {
|
.flat_map(|group| {
|
||||||
let separator = std::iter::once(WidgetHolder::new(Widget::Separator(Separator {
|
let separator = std::iter::once(WidgetHolder::new(Widget::Separator(Separator {
|
||||||
direction: SeparatorDirection::Vertical,
|
direction: SeparatorDirection::Vertical,
|
||||||
separator_type: SeparatorType::Section,
|
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 {
|
WidgetHolder::new(Widget::IconButton(IconButton {
|
||||||
icon: icon_name,
|
icon: icon_name,
|
||||||
size: 32,
|
size: 32,
|
||||||
tooltip: tooltip.clone(),
|
tooltip: tooltip.clone(),
|
||||||
|
tooltip_shortcut,
|
||||||
active: self.active_tool_type == tool_type,
|
active: self.active_tool_type == tool_type,
|
||||||
on_update: WidgetCallback::new(move |_| {
|
on_update: WidgetCallback::new(move |_| {
|
||||||
if !tooltip.contains("Coming Soon") {
|
if !tooltip.contains("Coming Soon") {
|
||||||
|
@ -143,7 +152,6 @@ impl PropertyHolder for ToolData {
|
||||||
DialogMessage::RequestComingSoonDialog { issue: None }.into()
|
DialogMessage::RequestComingSoonDialog { issue: None }.into()
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
..Default::default()
|
|
||||||
}))
|
}))
|
||||||
});
|
});
|
||||||
separator.chain(buttons)
|
separator.chain(buttons)
|
||||||
|
@ -253,68 +261,107 @@ pub fn coming_soon_tools() -> Vec<Vec<ToolBarMetadataGroup>> {
|
||||||
tool_type: ToolType::Brush,
|
tool_type: ToolType::Brush,
|
||||||
icon_name: "RasterBrushTool".into(),
|
icon_name: "RasterBrushTool".into(),
|
||||||
tooltip: "Coming Soon: Brush Tool (B)".into(),
|
tooltip: "Coming Soon: Brush Tool (B)".into(),
|
||||||
|
tooltip_shortcut: None,
|
||||||
},
|
},
|
||||||
ToolBarMetadataGroup {
|
ToolBarMetadataGroup {
|
||||||
tool_type: ToolType::Heal,
|
tool_type: ToolType::Heal,
|
||||||
icon_name: "RasterHealTool".into(),
|
icon_name: "RasterHealTool".into(),
|
||||||
tooltip: "Coming Soon: Heal Tool (J)".into(),
|
tooltip: "Coming Soon: Heal Tool (J)".into(),
|
||||||
|
tooltip_shortcut: None,
|
||||||
},
|
},
|
||||||
ToolBarMetadataGroup {
|
ToolBarMetadataGroup {
|
||||||
tool_type: ToolType::Clone,
|
tool_type: ToolType::Clone,
|
||||||
icon_name: "RasterCloneTool".into(),
|
icon_name: "RasterCloneTool".into(),
|
||||||
tooltip: "Coming Soon: Clone Tool (C))".into(),
|
tooltip: "Coming Soon: Clone Tool (C)".into(),
|
||||||
|
tooltip_shortcut: None,
|
||||||
},
|
},
|
||||||
ToolBarMetadataGroup {
|
ToolBarMetadataGroup {
|
||||||
tool_type: ToolType::Patch,
|
tool_type: ToolType::Patch,
|
||||||
icon_name: "RasterPatchTool".into(),
|
icon_name: "RasterPatchTool".into(),
|
||||||
tooltip: "Coming Soon: Patch Tool".into(),
|
tooltip: "Coming Soon: Patch Tool".into(),
|
||||||
|
tooltip_shortcut: None,
|
||||||
},
|
},
|
||||||
ToolBarMetadataGroup {
|
ToolBarMetadataGroup {
|
||||||
tool_type: ToolType::Detail,
|
tool_type: ToolType::Detail,
|
||||||
icon_name: "RasterDetailTool".into(),
|
icon_name: "RasterDetailTool".into(),
|
||||||
tooltip: "Coming Soon: Detail Tool (D)".into(),
|
tooltip: "Coming Soon: Detail Tool (D)".into(),
|
||||||
|
tooltip_shortcut: None,
|
||||||
},
|
},
|
||||||
ToolBarMetadataGroup {
|
ToolBarMetadataGroup {
|
||||||
tool_type: ToolType::Relight,
|
tool_type: ToolType::Relight,
|
||||||
icon_name: "RasterRelightTool".into(),
|
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 {
|
pub fn tool_message_to_tool_type(tool_message: &ToolMessage) -> ToolType {
|
||||||
use ToolMessage::*;
|
match tool_message {
|
||||||
|
|
||||||
match message {
|
|
||||||
// General tool group
|
// General tool group
|
||||||
Select(_) => ToolType::Select,
|
ToolMessage::Select(_) => ToolType::Select,
|
||||||
Artboard(_) => ToolType::Artboard,
|
ToolMessage::Artboard(_) => ToolType::Artboard,
|
||||||
Navigate(_) => ToolType::Navigate,
|
ToolMessage::Navigate(_) => ToolType::Navigate,
|
||||||
Eyedropper(_) => ToolType::Eyedropper,
|
ToolMessage::Eyedropper(_) => ToolType::Eyedropper,
|
||||||
Fill(_) => ToolType::Fill,
|
ToolMessage::Fill(_) => ToolType::Fill,
|
||||||
Gradient(_) => ToolType::Gradient,
|
ToolMessage::Gradient(_) => ToolType::Gradient,
|
||||||
|
|
||||||
// Vector tool group
|
// Vector tool group
|
||||||
Path(_) => ToolType::Path,
|
ToolMessage::Path(_) => ToolType::Path,
|
||||||
Pen(_) => ToolType::Pen,
|
ToolMessage::Pen(_) => ToolType::Pen,
|
||||||
Freehand(_) => ToolType::Freehand,
|
ToolMessage::Freehand(_) => ToolType::Freehand,
|
||||||
Spline(_) => ToolType::Spline,
|
ToolMessage::Spline(_) => ToolType::Spline,
|
||||||
Line(_) => ToolType::Line,
|
ToolMessage::Line(_) => ToolType::Line,
|
||||||
Rectangle(_) => ToolType::Rectangle,
|
ToolMessage::Rectangle(_) => ToolType::Rectangle,
|
||||||
Ellipse(_) => ToolType::Ellipse,
|
ToolMessage::Ellipse(_) => ToolType::Ellipse,
|
||||||
Shape(_) => ToolType::Shape,
|
ToolMessage::Shape(_) => ToolType::Shape,
|
||||||
Text(_) => ToolType::Text,
|
ToolMessage::Text(_) => ToolType::Text,
|
||||||
|
|
||||||
// Raster tool group
|
// Raster tool group
|
||||||
// Brush(_) => ToolType::Brush,
|
// ToolMessage::Brush(_) => ToolType::Brush,
|
||||||
// Heal(_) => ToolType::Heal,
|
// ToolMessage::Heal(_) => ToolType::Heal,
|
||||||
// Clone(_) => ToolType::Clone,
|
// ToolMessage::Clone(_) => ToolType::Clone,
|
||||||
// Patch(_) => ToolType::Patch,
|
// ToolMessage::Patch(_) => ToolType::Patch,
|
||||||
// Detail(_) => ToolType::Detail,
|
// ToolMessage::Detail(_) => ToolType::Detail,
|
||||||
// Relight(_) => ToolType::Relight,
|
// ToolMessage::Relight(_) => ToolType::Relight,
|
||||||
_ => panic!(
|
_ => panic!(
|
||||||
"Conversion from message to tool type impossible because the given ToolMessage does not belong to a tool. Got: {:?}",
|
"Conversion from ToolMessage to ToolType impossible because the given ToolMessage does not have a matching ToolType. Got: {:?}",
|
||||||
message
|
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]
|
#[remain::unsorted]
|
||||||
#[child]
|
#[child]
|
||||||
Gradient(GradientToolMessage),
|
Gradient(GradientToolMessage),
|
||||||
|
|
||||||
#[remain::unsorted]
|
#[remain::unsorted]
|
||||||
#[child]
|
#[child]
|
||||||
Path(PathToolMessage),
|
Path(PathToolMessage),
|
||||||
|
@ -54,7 +55,8 @@ pub enum ToolMessage {
|
||||||
Shape(ShapeToolMessage),
|
Shape(ShapeToolMessage),
|
||||||
#[remain::unsorted]
|
#[remain::unsorted]
|
||||||
#[child]
|
#[child]
|
||||||
Text(TextMessage),
|
Text(TextToolMessage),
|
||||||
|
|
||||||
// #[remain::unsorted]
|
// #[remain::unsorted]
|
||||||
// #[child]
|
// #[child]
|
||||||
// Brush(BrushToolMessage),
|
// Brush(BrushToolMessage),
|
||||||
|
@ -75,6 +77,38 @@ pub enum ToolMessage {
|
||||||
// Detail(DetailToolMessage),
|
// Detail(DetailToolMessage),
|
||||||
|
|
||||||
// Messages
|
// 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 {
|
ActivateTool {
|
||||||
tool_type: ToolType,
|
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::document::DocumentMessageHandler;
|
||||||
|
use crate::input::input_mapper::action_keys::action_shortcut;
|
||||||
use crate::input::InputPreprocessorMessageHandler;
|
use crate::input::InputPreprocessorMessageHandler;
|
||||||
use crate::layout::layout_message::LayoutTarget;
|
use crate::layout::layout_message::LayoutTarget;
|
||||||
use crate::layout::widgets::PropertyHolder;
|
use crate::layout::widgets::PropertyHolder;
|
||||||
use crate::layout::widgets::{IconButton, Layout, LayoutGroup, SwatchPairInput, Widget, WidgetCallback, WidgetHolder, WidgetLayout};
|
use crate::layout::widgets::{IconButton, Layout, LayoutGroup, SwatchPairInput, Widget, WidgetCallback, WidgetHolder, WidgetLayout};
|
||||||
use crate::message_prelude::*;
|
use crate::message_prelude::*;
|
||||||
use crate::viewport_tools::tool::DocumentToolData;
|
use crate::viewport_tools::tool::{DocumentToolData, ToolType};
|
||||||
|
|
||||||
use graphene::color::Color;
|
use graphene::color::Color;
|
||||||
use graphene::layers::text_layer::FontCache;
|
use graphene::layers::text_layer::FontCache;
|
||||||
|
@ -26,6 +27,38 @@ impl MessageHandler<ToolMessage, (&DocumentMessageHandler, &InputPreprocessorMes
|
||||||
#[remain::sorted]
|
#[remain::sorted]
|
||||||
match message {
|
match message {
|
||||||
// Messages
|
// 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 } => {
|
ActivateTool { tool_type } => {
|
||||||
let tool_data = &mut self.tool_state.tool_data;
|
let tool_data = &mut self.tool_state.tool_data;
|
||||||
let document_data = &self.tool_state.document_tool_data;
|
let document_data = &self.tool_state.document_tool_data;
|
||||||
|
@ -151,7 +184,7 @@ impl MessageHandler<ToolMessage, (&DocumentMessageHandler, &InputPreprocessorMes
|
||||||
tool_message => {
|
tool_message => {
|
||||||
let tool_type = match &tool_message {
|
let tool_type = match &tool_message {
|
||||||
UpdateCursor | UpdateHints => self.tool_state.tool_data.active_tool_type,
|
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 document_data = &self.tool_state.document_tool_data;
|
||||||
let tool_data = &mut self.tool_state.tool_data;
|
let tool_data = &mut self.tool_state.tool_data;
|
||||||
|
@ -167,7 +200,21 @@ impl MessageHandler<ToolMessage, (&DocumentMessageHandler, &InputPreprocessorMes
|
||||||
|
|
||||||
fn actions(&self) -> ActionList {
|
fn actions(&self) -> ActionList {
|
||||||
let mut list = actions!(ToolMessageDiscriminant;
|
let mut list = actions!(ToolMessageDiscriminant;
|
||||||
ActivateTool,
|
ActivateToolSelect,
|
||||||
|
ActivateToolArtboard,
|
||||||
|
ActivateToolNavigate,
|
||||||
|
ActivateToolEyedropper,
|
||||||
|
ActivateToolText,
|
||||||
|
ActivateToolFill,
|
||||||
|
ActivateToolGradient,
|
||||||
|
ActivateToolPath,
|
||||||
|
ActivateToolPen,
|
||||||
|
ActivateToolFreehand,
|
||||||
|
ActivateToolSpline,
|
||||||
|
ActivateToolLine,
|
||||||
|
ActivateToolRectangle,
|
||||||
|
ActivateToolEllipse,
|
||||||
|
ActivateToolShape,
|
||||||
SelectRandomPrimaryColor,
|
SelectRandomPrimaryColor,
|
||||||
ResetColors,
|
ResetColors,
|
||||||
SwapColors,
|
SwapColors,
|
||||||
|
@ -191,14 +238,16 @@ fn update_working_colors(document_data: &DocumentToolData, responses: &mut VecDe
|
||||||
WidgetHolder::new(Widget::IconButton(IconButton {
|
WidgetHolder::new(Widget::IconButton(IconButton {
|
||||||
size: 16,
|
size: 16,
|
||||||
icon: "Swap".into(),
|
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()),
|
on_update: WidgetCallback::new(|_| ToolMessage::SwapColors.into()),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
})),
|
})),
|
||||||
WidgetHolder::new(Widget::IconButton(IconButton {
|
WidgetHolder::new(Widget::IconButton(IconButton {
|
||||||
size: 16,
|
size: 16,
|
||||||
icon: "ResetColors".into(), // TODO: Customize this tooltip for the Mac version of the keyboard shortcut
|
icon: "ResetColors".into(),
|
||||||
tooltip: "Reset (Ctrl+Shift+X)".into(),
|
tooltip: "Reset".into(),
|
||||||
|
tooltip_shortcut: action_shortcut!(ToolMessageDiscriminant::ResetColors),
|
||||||
on_update: WidgetCallback::new(|_| ToolMessage::ResetColors.into()),
|
on_update: WidgetCallback::new(|_| ToolMessage::ResetColors.into()),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
})),
|
})),
|
||||||
|
|
|
@ -243,8 +243,10 @@ impl Fsm for ArtboardToolFsmState {
|
||||||
let mouse_position = input.mouse.position;
|
let mouse_position = input.mouse.position;
|
||||||
let snapped_mouse_position = tool_data.snap_handler.snap_position(responses, document, mouse_position);
|
let snapped_mouse_position = tool_data.snap_handler.snap_position(responses, document, mouse_position);
|
||||||
|
|
||||||
let [position, size] = movement.new_size(snapped_mouse_position, bounds.transform, from_center, constrain_square);
|
let (mut position, size) = movement.new_size(snapped_mouse_position, bounds.transform, from_center, constrain_square);
|
||||||
let position = movement.center_position(position, size, from_center);
|
if from_center {
|
||||||
|
position = movement.center_position(position, size);
|
||||||
|
}
|
||||||
|
|
||||||
responses.push_back(
|
responses.push_back(
|
||||||
ArtboardMessage::ResizeArtboard {
|
ArtboardMessage::ResizeArtboard {
|
||||||
|
@ -408,18 +410,21 @@ impl Fsm for ArtboardToolFsmState {
|
||||||
ArtboardToolFsmState::Ready => HintData(vec![
|
ArtboardToolFsmState::Ready => HintData(vec![
|
||||||
HintGroup(vec![HintInfo {
|
HintGroup(vec![HintInfo {
|
||||||
key_groups: vec![],
|
key_groups: vec![],
|
||||||
|
key_groups_mac: None,
|
||||||
mouse: Some(MouseMotion::LmbDrag),
|
mouse: Some(MouseMotion::LmbDrag),
|
||||||
label: String::from("Draw Artboard"),
|
label: String::from("Draw Artboard"),
|
||||||
plus: false,
|
plus: false,
|
||||||
}]),
|
}]),
|
||||||
HintGroup(vec![HintInfo {
|
HintGroup(vec![HintInfo {
|
||||||
key_groups: vec![],
|
key_groups: vec![],
|
||||||
|
key_groups_mac: None,
|
||||||
mouse: Some(MouseMotion::LmbDrag),
|
mouse: Some(MouseMotion::LmbDrag),
|
||||||
label: String::from("Move Artboard"),
|
label: String::from("Move Artboard"),
|
||||||
plus: false,
|
plus: false,
|
||||||
}]),
|
}]),
|
||||||
HintGroup(vec![HintInfo {
|
HintGroup(vec![HintInfo {
|
||||||
key_groups: vec![KeysGroup(vec![Key::KeyBackspace])],
|
key_groups: vec![KeysGroup(vec![Key::KeyBackspace])],
|
||||||
|
key_groups_mac: None,
|
||||||
mouse: None,
|
mouse: None,
|
||||||
label: String::from("Delete Artboard"),
|
label: String::from("Delete Artboard"),
|
||||||
plus: false,
|
plus: false,
|
||||||
|
@ -427,6 +432,7 @@ impl Fsm for ArtboardToolFsmState {
|
||||||
]),
|
]),
|
||||||
ArtboardToolFsmState::Dragging => HintData(vec![HintGroup(vec![HintInfo {
|
ArtboardToolFsmState::Dragging => HintData(vec![HintGroup(vec![HintInfo {
|
||||||
key_groups: vec![KeysGroup(vec![Key::KeyShift])],
|
key_groups: vec![KeysGroup(vec![Key::KeyShift])],
|
||||||
|
key_groups_mac: None,
|
||||||
mouse: None,
|
mouse: None,
|
||||||
label: String::from("Constrain to Axis"),
|
label: String::from("Constrain to Axis"),
|
||||||
plus: false,
|
plus: false,
|
||||||
|
@ -434,12 +440,14 @@ impl Fsm for ArtboardToolFsmState {
|
||||||
ArtboardToolFsmState::Drawing | ArtboardToolFsmState::ResizingBounds => HintData(vec![HintGroup(vec![
|
ArtboardToolFsmState::Drawing | ArtboardToolFsmState::ResizingBounds => HintData(vec![HintGroup(vec![
|
||||||
HintInfo {
|
HintInfo {
|
||||||
key_groups: vec![KeysGroup(vec![Key::KeyShift])],
|
key_groups: vec![KeysGroup(vec![Key::KeyShift])],
|
||||||
|
key_groups_mac: None,
|
||||||
mouse: None,
|
mouse: None,
|
||||||
label: String::from("Constrain Square"),
|
label: String::from("Constrain Square"),
|
||||||
plus: false,
|
plus: false,
|
||||||
},
|
},
|
||||||
HintInfo {
|
HintInfo {
|
||||||
key_groups: vec![KeysGroup(vec![Key::KeyAlt])],
|
key_groups: vec![KeysGroup(vec![Key::KeyAlt])],
|
||||||
|
key_groups_mac: None,
|
||||||
mouse: None,
|
mouse: None,
|
||||||
label: String::from("From Center"),
|
label: String::from("From Center"),
|
||||||
plus: false,
|
plus: false,
|
||||||
|
|
|
@ -41,7 +41,7 @@ impl ToolMetadata for EllipseTool {
|
||||||
"VectorEllipseTool".into()
|
"VectorEllipseTool".into()
|
||||||
}
|
}
|
||||||
fn tooltip(&self) -> String {
|
fn tooltip(&self) -> String {
|
||||||
"Ellipse Tool (E)".into()
|
"Ellipse Tool".into()
|
||||||
}
|
}
|
||||||
fn tool_type(&self) -> crate::viewport_tools::tool::ToolType {
|
fn tool_type(&self) -> crate::viewport_tools::tool::ToolType {
|
||||||
ToolType::Ellipse
|
ToolType::Ellipse
|
||||||
|
@ -185,18 +185,21 @@ impl Fsm for EllipseToolFsmState {
|
||||||
EllipseToolFsmState::Ready => HintData(vec![HintGroup(vec![
|
EllipseToolFsmState::Ready => HintData(vec![HintGroup(vec![
|
||||||
HintInfo {
|
HintInfo {
|
||||||
key_groups: vec![],
|
key_groups: vec![],
|
||||||
|
key_groups_mac: None,
|
||||||
mouse: Some(MouseMotion::LmbDrag),
|
mouse: Some(MouseMotion::LmbDrag),
|
||||||
label: String::from("Draw Ellipse"),
|
label: String::from("Draw Ellipse"),
|
||||||
plus: false,
|
plus: false,
|
||||||
},
|
},
|
||||||
HintInfo {
|
HintInfo {
|
||||||
key_groups: vec![KeysGroup(vec![Key::KeyShift])],
|
key_groups: vec![KeysGroup(vec![Key::KeyShift])],
|
||||||
|
key_groups_mac: None,
|
||||||
mouse: None,
|
mouse: None,
|
||||||
label: String::from("Constrain Circular"),
|
label: String::from("Constrain Circular"),
|
||||||
plus: true,
|
plus: true,
|
||||||
},
|
},
|
||||||
HintInfo {
|
HintInfo {
|
||||||
key_groups: vec![KeysGroup(vec![Key::KeyAlt])],
|
key_groups: vec![KeysGroup(vec![Key::KeyAlt])],
|
||||||
|
key_groups_mac: None,
|
||||||
mouse: None,
|
mouse: None,
|
||||||
label: String::from("From Center"),
|
label: String::from("From Center"),
|
||||||
plus: true,
|
plus: true,
|
||||||
|
@ -205,12 +208,14 @@ impl Fsm for EllipseToolFsmState {
|
||||||
EllipseToolFsmState::Drawing => HintData(vec![HintGroup(vec![
|
EllipseToolFsmState::Drawing => HintData(vec![HintGroup(vec![
|
||||||
HintInfo {
|
HintInfo {
|
||||||
key_groups: vec![KeysGroup(vec![Key::KeyShift])],
|
key_groups: vec![KeysGroup(vec![Key::KeyShift])],
|
||||||
|
key_groups_mac: None,
|
||||||
mouse: None,
|
mouse: None,
|
||||||
label: String::from("Constrain Circular"),
|
label: String::from("Constrain Circular"),
|
||||||
plus: false,
|
plus: false,
|
||||||
},
|
},
|
||||||
HintInfo {
|
HintInfo {
|
||||||
key_groups: vec![KeysGroup(vec![Key::KeyAlt])],
|
key_groups: vec![KeysGroup(vec![Key::KeyAlt])],
|
||||||
|
key_groups_mac: None,
|
||||||
mouse: None,
|
mouse: None,
|
||||||
label: String::from("From Center"),
|
label: String::from("From Center"),
|
||||||
plus: false,
|
plus: false,
|
||||||
|
|
|
@ -36,7 +36,7 @@ impl ToolMetadata for EyedropperTool {
|
||||||
"GeneralEyedropperTool".into()
|
"GeneralEyedropperTool".into()
|
||||||
}
|
}
|
||||||
fn tooltip(&self) -> String {
|
fn tooltip(&self) -> String {
|
||||||
"Eyedropper Tool (I)".into()
|
"Eyedropper Tool".into()
|
||||||
}
|
}
|
||||||
fn tool_type(&self) -> crate::viewport_tools::tool::ToolType {
|
fn tool_type(&self) -> crate::viewport_tools::tool::ToolType {
|
||||||
ToolType::Eyedropper
|
ToolType::Eyedropper
|
||||||
|
@ -147,12 +147,14 @@ impl Fsm for EyedropperToolFsmState {
|
||||||
EyedropperToolFsmState::Ready => HintData(vec![HintGroup(vec![
|
EyedropperToolFsmState::Ready => HintData(vec![HintGroup(vec![
|
||||||
HintInfo {
|
HintInfo {
|
||||||
key_groups: vec![],
|
key_groups: vec![],
|
||||||
|
key_groups_mac: None,
|
||||||
mouse: Some(MouseMotion::Lmb),
|
mouse: Some(MouseMotion::Lmb),
|
||||||
label: String::from("Sample to Primary"),
|
label: String::from("Sample to Primary"),
|
||||||
plus: false,
|
plus: false,
|
||||||
},
|
},
|
||||||
HintInfo {
|
HintInfo {
|
||||||
key_groups: vec![],
|
key_groups: vec![],
|
||||||
|
key_groups_mac: None,
|
||||||
mouse: Some(MouseMotion::Rmb),
|
mouse: Some(MouseMotion::Rmb),
|
||||||
label: String::from("Sample to Secondary"),
|
label: String::from("Sample to Secondary"),
|
||||||
plus: false,
|
plus: false,
|
||||||
|
|
|
@ -37,7 +37,7 @@ impl ToolMetadata for FillTool {
|
||||||
"GeneralFillTool".into()
|
"GeneralFillTool".into()
|
||||||
}
|
}
|
||||||
fn tooltip(&self) -> String {
|
fn tooltip(&self) -> String {
|
||||||
"Fill Tool (F)".into()
|
"Fill Tool".into()
|
||||||
}
|
}
|
||||||
fn tool_type(&self) -> crate::viewport_tools::tool::ToolType {
|
fn tool_type(&self) -> crate::viewport_tools::tool::ToolType {
|
||||||
ToolType::Fill
|
ToolType::Fill
|
||||||
|
@ -146,12 +146,14 @@ impl Fsm for FillToolFsmState {
|
||||||
FillToolFsmState::Ready => HintData(vec![HintGroup(vec![
|
FillToolFsmState::Ready => HintData(vec![HintGroup(vec![
|
||||||
HintInfo {
|
HintInfo {
|
||||||
key_groups: vec![],
|
key_groups: vec![],
|
||||||
|
key_groups_mac: None,
|
||||||
mouse: Some(MouseMotion::Lmb),
|
mouse: Some(MouseMotion::Lmb),
|
||||||
label: String::from("Fill with Primary"),
|
label: String::from("Fill with Primary"),
|
||||||
plus: false,
|
plus: false,
|
||||||
},
|
},
|
||||||
HintInfo {
|
HintInfo {
|
||||||
key_groups: vec![],
|
key_groups: vec![],
|
||||||
|
key_groups_mac: None,
|
||||||
mouse: Some(MouseMotion::Rmb),
|
mouse: Some(MouseMotion::Rmb),
|
||||||
label: String::from("Fill with Secondary"),
|
label: String::from("Fill with Secondary"),
|
||||||
plus: false,
|
plus: false,
|
||||||
|
|
|
@ -60,7 +60,7 @@ impl ToolMetadata for FreehandTool {
|
||||||
"VectorFreehandTool".into()
|
"VectorFreehandTool".into()
|
||||||
}
|
}
|
||||||
fn tooltip(&self) -> String {
|
fn tooltip(&self) -> String {
|
||||||
"Freehand Tool (N)".into()
|
"Freehand Tool".into()
|
||||||
}
|
}
|
||||||
fn tool_type(&self) -> crate::viewport_tools::tool::ToolType {
|
fn tool_type(&self) -> crate::viewport_tools::tool::ToolType {
|
||||||
ToolType::Freehand
|
ToolType::Freehand
|
||||||
|
@ -222,6 +222,7 @@ impl Fsm for FreehandToolFsmState {
|
||||||
let hint_data = match self {
|
let hint_data = match self {
|
||||||
FreehandToolFsmState::Ready => HintData(vec![HintGroup(vec![HintInfo {
|
FreehandToolFsmState::Ready => HintData(vec![HintGroup(vec![HintInfo {
|
||||||
key_groups: vec![],
|
key_groups: vec![],
|
||||||
|
key_groups_mac: None,
|
||||||
mouse: Some(MouseMotion::LmbDrag),
|
mouse: Some(MouseMotion::LmbDrag),
|
||||||
label: String::from("Draw Polyline"),
|
label: String::from("Draw Polyline"),
|
||||||
plus: false,
|
plus: false,
|
||||||
|
|
|
@ -65,7 +65,7 @@ impl ToolMetadata for GradientTool {
|
||||||
"GeneralGradientTool".into()
|
"GeneralGradientTool".into()
|
||||||
}
|
}
|
||||||
fn tooltip(&self) -> String {
|
fn tooltip(&self) -> String {
|
||||||
"Gradient Tool (H))".into()
|
"Gradient Tool".into()
|
||||||
}
|
}
|
||||||
fn tool_type(&self) -> crate::viewport_tools::tool::ToolType {
|
fn tool_type(&self) -> crate::viewport_tools::tool::ToolType {
|
||||||
ToolType::Gradient
|
ToolType::Gradient
|
||||||
|
@ -461,12 +461,14 @@ impl Fsm for GradientToolFsmState {
|
||||||
GradientToolFsmState::Ready => HintData(vec![HintGroup(vec![
|
GradientToolFsmState::Ready => HintData(vec![HintGroup(vec![
|
||||||
HintInfo {
|
HintInfo {
|
||||||
key_groups: vec![],
|
key_groups: vec![],
|
||||||
|
key_groups_mac: None,
|
||||||
mouse: Some(MouseMotion::LmbDrag),
|
mouse: Some(MouseMotion::LmbDrag),
|
||||||
label: String::from("Draw Gradient"),
|
label: String::from("Draw Gradient"),
|
||||||
plus: false,
|
plus: false,
|
||||||
},
|
},
|
||||||
HintInfo {
|
HintInfo {
|
||||||
key_groups: vec![KeysGroup(vec![Key::KeyShift])],
|
key_groups: vec![KeysGroup(vec![Key::KeyShift])],
|
||||||
|
key_groups_mac: None,
|
||||||
mouse: None,
|
mouse: None,
|
||||||
label: String::from("Snap 15°"),
|
label: String::from("Snap 15°"),
|
||||||
plus: true,
|
plus: true,
|
||||||
|
@ -474,6 +476,7 @@ impl Fsm for GradientToolFsmState {
|
||||||
])]),
|
])]),
|
||||||
GradientToolFsmState::Drawing => HintData(vec![HintGroup(vec![HintInfo {
|
GradientToolFsmState::Drawing => HintData(vec![HintGroup(vec![HintInfo {
|
||||||
key_groups: vec![KeysGroup(vec![Key::KeyShift])],
|
key_groups: vec![KeysGroup(vec![Key::KeyShift])],
|
||||||
|
key_groups_mac: None,
|
||||||
mouse: None,
|
mouse: None,
|
||||||
label: String::from("Snap 15°"),
|
label: String::from("Snap 15°"),
|
||||||
plus: false,
|
plus: false,
|
||||||
|
|
|
@ -61,7 +61,7 @@ impl ToolMetadata for LineTool {
|
||||||
"VectorLineTool".into()
|
"VectorLineTool".into()
|
||||||
}
|
}
|
||||||
fn tooltip(&self) -> String {
|
fn tooltip(&self) -> String {
|
||||||
"Line Tool (L)".into()
|
"Line Tool".into()
|
||||||
}
|
}
|
||||||
fn tool_type(&self) -> crate::viewport_tools::tool::ToolType {
|
fn tool_type(&self) -> crate::viewport_tools::tool::ToolType {
|
||||||
ToolType::Line
|
ToolType::Line
|
||||||
|
@ -239,24 +239,28 @@ impl Fsm for LineToolFsmState {
|
||||||
LineToolFsmState::Ready => HintData(vec![HintGroup(vec![
|
LineToolFsmState::Ready => HintData(vec![HintGroup(vec![
|
||||||
HintInfo {
|
HintInfo {
|
||||||
key_groups: vec![],
|
key_groups: vec![],
|
||||||
|
key_groups_mac: None,
|
||||||
mouse: Some(MouseMotion::LmbDrag),
|
mouse: Some(MouseMotion::LmbDrag),
|
||||||
label: String::from("Draw Line"),
|
label: String::from("Draw Line"),
|
||||||
plus: false,
|
plus: false,
|
||||||
},
|
},
|
||||||
HintInfo {
|
HintInfo {
|
||||||
key_groups: vec![KeysGroup(vec![Key::KeyShift])],
|
key_groups: vec![KeysGroup(vec![Key::KeyShift])],
|
||||||
|
key_groups_mac: None,
|
||||||
mouse: None,
|
mouse: None,
|
||||||
label: String::from("Snap 15°"),
|
label: String::from("Snap 15°"),
|
||||||
plus: true,
|
plus: true,
|
||||||
},
|
},
|
||||||
HintInfo {
|
HintInfo {
|
||||||
key_groups: vec![KeysGroup(vec![Key::KeyAlt])],
|
key_groups: vec![KeysGroup(vec![Key::KeyAlt])],
|
||||||
|
key_groups_mac: None,
|
||||||
mouse: None,
|
mouse: None,
|
||||||
label: String::from("From Center"),
|
label: String::from("From Center"),
|
||||||
plus: true,
|
plus: true,
|
||||||
},
|
},
|
||||||
HintInfo {
|
HintInfo {
|
||||||
key_groups: vec![KeysGroup(vec![Key::KeyControl])],
|
key_groups: vec![KeysGroup(vec![Key::KeyControl])],
|
||||||
|
key_groups_mac: None,
|
||||||
mouse: None,
|
mouse: None,
|
||||||
label: String::from("Lock Angle"),
|
label: String::from("Lock Angle"),
|
||||||
plus: true,
|
plus: true,
|
||||||
|
@ -265,18 +269,21 @@ impl Fsm for LineToolFsmState {
|
||||||
LineToolFsmState::Drawing => HintData(vec![HintGroup(vec![
|
LineToolFsmState::Drawing => HintData(vec![HintGroup(vec![
|
||||||
HintInfo {
|
HintInfo {
|
||||||
key_groups: vec![KeysGroup(vec![Key::KeyShift])],
|
key_groups: vec![KeysGroup(vec![Key::KeyShift])],
|
||||||
|
key_groups_mac: None,
|
||||||
mouse: None,
|
mouse: None,
|
||||||
label: String::from("Snap 15°"),
|
label: String::from("Snap 15°"),
|
||||||
plus: false,
|
plus: false,
|
||||||
},
|
},
|
||||||
HintInfo {
|
HintInfo {
|
||||||
key_groups: vec![KeysGroup(vec![Key::KeyAlt])],
|
key_groups: vec![KeysGroup(vec![Key::KeyAlt])],
|
||||||
|
key_groups_mac: None,
|
||||||
mouse: None,
|
mouse: None,
|
||||||
label: String::from("From Center"),
|
label: String::from("From Center"),
|
||||||
plus: false,
|
plus: false,
|
||||||
},
|
},
|
||||||
HintInfo {
|
HintInfo {
|
||||||
key_groups: vec![KeysGroup(vec![Key::KeyControl])],
|
key_groups: vec![KeysGroup(vec![Key::KeyControl])],
|
||||||
|
key_groups_mac: None,
|
||||||
mouse: None,
|
mouse: None,
|
||||||
label: String::from("Lock Angle"),
|
label: String::from("Lock Angle"),
|
||||||
plus: false,
|
plus: false,
|
||||||
|
|
|
@ -41,7 +41,7 @@ impl ToolMetadata for NavigateTool {
|
||||||
"GeneralNavigateTool".into()
|
"GeneralNavigateTool".into()
|
||||||
}
|
}
|
||||||
fn tooltip(&self) -> String {
|
fn tooltip(&self) -> String {
|
||||||
"Navigate Tool (Z)".into()
|
"Navigate Tool".into()
|
||||||
}
|
}
|
||||||
fn tool_type(&self) -> crate::viewport_tools::tool::ToolType {
|
fn tool_type(&self) -> crate::viewport_tools::tool::ToolType {
|
||||||
ToolType::Navigate
|
ToolType::Navigate
|
||||||
|
@ -195,12 +195,14 @@ impl Fsm for NavigateToolFsmState {
|
||||||
HintGroup(vec![
|
HintGroup(vec![
|
||||||
HintInfo {
|
HintInfo {
|
||||||
key_groups: vec![],
|
key_groups: vec![],
|
||||||
|
key_groups_mac: None,
|
||||||
mouse: Some(MouseMotion::Lmb),
|
mouse: Some(MouseMotion::Lmb),
|
||||||
label: String::from("Zoom In"),
|
label: String::from("Zoom In"),
|
||||||
plus: false,
|
plus: false,
|
||||||
},
|
},
|
||||||
HintInfo {
|
HintInfo {
|
||||||
key_groups: vec![KeysGroup(vec![Key::KeyShift])],
|
key_groups: vec![KeysGroup(vec![Key::KeyShift])],
|
||||||
|
key_groups_mac: None,
|
||||||
mouse: None,
|
mouse: None,
|
||||||
label: String::from("Zoom Out"),
|
label: String::from("Zoom Out"),
|
||||||
plus: true,
|
plus: true,
|
||||||
|
@ -209,12 +211,14 @@ impl Fsm for NavigateToolFsmState {
|
||||||
HintGroup(vec![
|
HintGroup(vec![
|
||||||
HintInfo {
|
HintInfo {
|
||||||
key_groups: vec![],
|
key_groups: vec![],
|
||||||
|
key_groups_mac: None,
|
||||||
mouse: Some(MouseMotion::LmbDrag),
|
mouse: Some(MouseMotion::LmbDrag),
|
||||||
label: String::from("Zoom"),
|
label: String::from("Zoom"),
|
||||||
plus: false,
|
plus: false,
|
||||||
},
|
},
|
||||||
HintInfo {
|
HintInfo {
|
||||||
key_groups: vec![KeysGroup(vec![Key::KeyControl])],
|
key_groups: vec![KeysGroup(vec![Key::KeyControl])],
|
||||||
|
key_groups_mac: None,
|
||||||
mouse: None,
|
mouse: None,
|
||||||
label: String::from("Snap Increments"),
|
label: String::from("Snap Increments"),
|
||||||
plus: true,
|
plus: true,
|
||||||
|
@ -222,6 +226,7 @@ impl Fsm for NavigateToolFsmState {
|
||||||
]),
|
]),
|
||||||
HintGroup(vec![HintInfo {
|
HintGroup(vec![HintInfo {
|
||||||
key_groups: vec![],
|
key_groups: vec![],
|
||||||
|
key_groups_mac: None,
|
||||||
mouse: Some(MouseMotion::MmbDrag),
|
mouse: Some(MouseMotion::MmbDrag),
|
||||||
label: String::from("Pan"),
|
label: String::from("Pan"),
|
||||||
plus: false,
|
plus: false,
|
||||||
|
@ -229,12 +234,14 @@ impl Fsm for NavigateToolFsmState {
|
||||||
HintGroup(vec![
|
HintGroup(vec![
|
||||||
HintInfo {
|
HintInfo {
|
||||||
key_groups: vec![],
|
key_groups: vec![],
|
||||||
|
key_groups_mac: None,
|
||||||
mouse: Some(MouseMotion::RmbDrag),
|
mouse: Some(MouseMotion::RmbDrag),
|
||||||
label: String::from("Tilt"),
|
label: String::from("Tilt"),
|
||||||
plus: false,
|
plus: false,
|
||||||
},
|
},
|
||||||
HintInfo {
|
HintInfo {
|
||||||
key_groups: vec![KeysGroup(vec![Key::KeyControl])],
|
key_groups: vec![KeysGroup(vec![Key::KeyControl])],
|
||||||
|
key_groups_mac: None,
|
||||||
mouse: None,
|
mouse: None,
|
||||||
label: String::from("Snap 15°"),
|
label: String::from("Snap 15°"),
|
||||||
plus: true,
|
plus: true,
|
||||||
|
@ -243,12 +250,14 @@ impl Fsm for NavigateToolFsmState {
|
||||||
]),
|
]),
|
||||||
NavigateToolFsmState::Tilting => HintData(vec![HintGroup(vec![HintInfo {
|
NavigateToolFsmState::Tilting => HintData(vec![HintGroup(vec![HintInfo {
|
||||||
key_groups: vec![KeysGroup(vec![Key::KeyControl])],
|
key_groups: vec![KeysGroup(vec![Key::KeyControl])],
|
||||||
|
key_groups_mac: None,
|
||||||
mouse: None,
|
mouse: None,
|
||||||
label: String::from("Snap 15°"),
|
label: String::from("Snap 15°"),
|
||||||
plus: false,
|
plus: false,
|
||||||
}])]),
|
}])]),
|
||||||
NavigateToolFsmState::Zooming => HintData(vec![HintGroup(vec![HintInfo {
|
NavigateToolFsmState::Zooming => HintData(vec![HintGroup(vec![HintInfo {
|
||||||
key_groups: vec![KeysGroup(vec![Key::KeyControl])],
|
key_groups: vec![KeysGroup(vec![Key::KeyControl])],
|
||||||
|
key_groups_mac: None,
|
||||||
mouse: None,
|
mouse: None,
|
||||||
label: String::from("Snap Increments"),
|
label: String::from("Snap Increments"),
|
||||||
plus: false,
|
plus: false,
|
||||||
|
|
|
@ -50,7 +50,7 @@ impl ToolMetadata for PathTool {
|
||||||
"VectorPathTool".into()
|
"VectorPathTool".into()
|
||||||
}
|
}
|
||||||
fn tooltip(&self) -> String {
|
fn tooltip(&self) -> String {
|
||||||
"Path Tool (A)".into()
|
"Path Tool".into()
|
||||||
}
|
}
|
||||||
fn tool_type(&self) -> crate::viewport_tools::tool::ToolType {
|
fn tool_type(&self) -> crate::viewport_tools::tool::ToolType {
|
||||||
ToolType::Path
|
ToolType::Path
|
||||||
|
@ -306,12 +306,14 @@ impl Fsm for PathToolFsmState {
|
||||||
HintGroup(vec![
|
HintGroup(vec![
|
||||||
HintInfo {
|
HintInfo {
|
||||||
key_groups: vec![],
|
key_groups: vec![],
|
||||||
|
key_groups_mac: None,
|
||||||
mouse: Some(MouseMotion::Lmb),
|
mouse: Some(MouseMotion::Lmb),
|
||||||
label: String::from("Select Point"),
|
label: String::from("Select Point"),
|
||||||
plus: false,
|
plus: false,
|
||||||
},
|
},
|
||||||
HintInfo {
|
HintInfo {
|
||||||
key_groups: vec![KeysGroup(vec![Key::KeyShift])],
|
key_groups: vec![KeysGroup(vec![Key::KeyShift])],
|
||||||
|
key_groups_mac: None,
|
||||||
mouse: None,
|
mouse: None,
|
||||||
label: String::from("Grow/Shrink Selection"),
|
label: String::from("Grow/Shrink Selection"),
|
||||||
plus: true,
|
plus: true,
|
||||||
|
@ -319,6 +321,7 @@ impl Fsm for PathToolFsmState {
|
||||||
]),
|
]),
|
||||||
HintGroup(vec![HintInfo {
|
HintGroup(vec![HintInfo {
|
||||||
key_groups: vec![],
|
key_groups: vec![],
|
||||||
|
key_groups_mac: None,
|
||||||
mouse: Some(MouseMotion::LmbDrag),
|
mouse: Some(MouseMotion::LmbDrag),
|
||||||
label: String::from("Drag Selected"),
|
label: String::from("Drag Selected"),
|
||||||
plus: false,
|
plus: false,
|
||||||
|
@ -331,12 +334,14 @@ impl Fsm for PathToolFsmState {
|
||||||
KeysGroup(vec![Key::KeyArrowDown]),
|
KeysGroup(vec![Key::KeyArrowDown]),
|
||||||
KeysGroup(vec![Key::KeyArrowLeft]),
|
KeysGroup(vec![Key::KeyArrowLeft]),
|
||||||
],
|
],
|
||||||
|
key_groups_mac: None,
|
||||||
mouse: None,
|
mouse: None,
|
||||||
label: String::from("Nudge Selected (coming soon)"),
|
label: String::from("Nudge Selected (coming soon)"),
|
||||||
plus: false,
|
plus: false,
|
||||||
},
|
},
|
||||||
HintInfo {
|
HintInfo {
|
||||||
key_groups: vec![KeysGroup(vec![Key::KeyShift])],
|
key_groups: vec![KeysGroup(vec![Key::KeyShift])],
|
||||||
|
key_groups_mac: None,
|
||||||
mouse: None,
|
mouse: None,
|
||||||
label: String::from("Big Increment Nudge"),
|
label: String::from("Big Increment Nudge"),
|
||||||
plus: true,
|
plus: true,
|
||||||
|
@ -345,18 +350,21 @@ impl Fsm for PathToolFsmState {
|
||||||
HintGroup(vec![
|
HintGroup(vec![
|
||||||
HintInfo {
|
HintInfo {
|
||||||
key_groups: vec![KeysGroup(vec![Key::KeyG])],
|
key_groups: vec![KeysGroup(vec![Key::KeyG])],
|
||||||
|
key_groups_mac: None,
|
||||||
mouse: None,
|
mouse: None,
|
||||||
label: String::from("Grab Selected (coming soon)"),
|
label: String::from("Grab Selected (coming soon)"),
|
||||||
plus: false,
|
plus: false,
|
||||||
},
|
},
|
||||||
HintInfo {
|
HintInfo {
|
||||||
key_groups: vec![KeysGroup(vec![Key::KeyR])],
|
key_groups: vec![KeysGroup(vec![Key::KeyR])],
|
||||||
|
key_groups_mac: None,
|
||||||
mouse: None,
|
mouse: None,
|
||||||
label: String::from("Rotate Selected (coming soon)"),
|
label: String::from("Rotate Selected (coming soon)"),
|
||||||
plus: false,
|
plus: false,
|
||||||
},
|
},
|
||||||
HintInfo {
|
HintInfo {
|
||||||
key_groups: vec![KeysGroup(vec![Key::KeyS])],
|
key_groups: vec![KeysGroup(vec![Key::KeyS])],
|
||||||
|
key_groups_mac: None,
|
||||||
mouse: None,
|
mouse: None,
|
||||||
label: String::from("Scale Selected (coming soon)"),
|
label: String::from("Scale Selected (coming soon)"),
|
||||||
plus: false,
|
plus: false,
|
||||||
|
@ -366,12 +374,14 @@ impl Fsm for PathToolFsmState {
|
||||||
PathToolFsmState::Dragging => HintData(vec![HintGroup(vec![
|
PathToolFsmState::Dragging => HintData(vec![HintGroup(vec![
|
||||||
HintInfo {
|
HintInfo {
|
||||||
key_groups: vec![KeysGroup(vec![Key::KeyAlt])],
|
key_groups: vec![KeysGroup(vec![Key::KeyAlt])],
|
||||||
|
key_groups_mac: None,
|
||||||
mouse: None,
|
mouse: None,
|
||||||
label: String::from("Split/Align Handles (Toggle)"),
|
label: String::from("Split/Align Handles (Toggle)"),
|
||||||
plus: false,
|
plus: false,
|
||||||
},
|
},
|
||||||
HintInfo {
|
HintInfo {
|
||||||
key_groups: vec![KeysGroup(vec![Key::KeyShift])],
|
key_groups: vec![KeysGroup(vec![Key::KeyShift])],
|
||||||
|
key_groups_mac: None,
|
||||||
mouse: None,
|
mouse: None,
|
||||||
label: String::from("Share Lengths of Aligned Handles"),
|
label: String::from("Share Lengths of Aligned Handles"),
|
||||||
plus: false,
|
plus: false,
|
||||||
|
|
|
@ -78,7 +78,7 @@ impl ToolMetadata for PenTool {
|
||||||
"VectorPenTool".into()
|
"VectorPenTool".into()
|
||||||
}
|
}
|
||||||
fn tooltip(&self) -> String {
|
fn tooltip(&self) -> String {
|
||||||
"Pen Tool (P)".into()
|
"Pen Tool".into()
|
||||||
}
|
}
|
||||||
fn tool_type(&self) -> crate::viewport_tools::tool::ToolType {
|
fn tool_type(&self) -> crate::viewport_tools::tool::ToolType {
|
||||||
ToolType::Pen
|
ToolType::Pen
|
||||||
|
@ -374,6 +374,7 @@ impl Fsm for PenToolFsmState {
|
||||||
let hint_data = match self {
|
let hint_data = match self {
|
||||||
PenToolFsmState::Ready => HintData(vec![HintGroup(vec![HintInfo {
|
PenToolFsmState::Ready => HintData(vec![HintGroup(vec![HintInfo {
|
||||||
key_groups: vec![],
|
key_groups: vec![],
|
||||||
|
key_groups_mac: None,
|
||||||
mouse: Some(MouseMotion::Lmb),
|
mouse: Some(MouseMotion::Lmb),
|
||||||
label: String::from("Draw Path"),
|
label: String::from("Draw Path"),
|
||||||
plus: false,
|
plus: false,
|
||||||
|
@ -381,30 +382,35 @@ impl Fsm for PenToolFsmState {
|
||||||
PenToolFsmState::DraggingHandle | PenToolFsmState::PlacingAnchor => HintData(vec![
|
PenToolFsmState::DraggingHandle | PenToolFsmState::PlacingAnchor => HintData(vec![
|
||||||
HintGroup(vec![HintInfo {
|
HintGroup(vec![HintInfo {
|
||||||
key_groups: vec![],
|
key_groups: vec![],
|
||||||
|
key_groups_mac: None,
|
||||||
mouse: Some(MouseMotion::LmbDrag),
|
mouse: Some(MouseMotion::LmbDrag),
|
||||||
label: String::from("Add Handle"),
|
label: String::from("Add Handle"),
|
||||||
plus: false,
|
plus: false,
|
||||||
}]),
|
}]),
|
||||||
HintGroup(vec![HintInfo {
|
HintGroup(vec![HintInfo {
|
||||||
key_groups: vec![],
|
key_groups: vec![],
|
||||||
|
key_groups_mac: None,
|
||||||
mouse: Some(MouseMotion::Lmb),
|
mouse: Some(MouseMotion::Lmb),
|
||||||
label: String::from("Add Anchor"),
|
label: String::from("Add Anchor"),
|
||||||
plus: false,
|
plus: false,
|
||||||
}]),
|
}]),
|
||||||
HintGroup(vec![HintInfo {
|
HintGroup(vec![HintInfo {
|
||||||
key_groups: vec![KeysGroup(vec![Key::KeyControl])],
|
key_groups: vec![KeysGroup(vec![Key::KeyControl])],
|
||||||
|
key_groups_mac: None,
|
||||||
mouse: None,
|
mouse: None,
|
||||||
label: String::from("Snap 15°"),
|
label: String::from("Snap 15°"),
|
||||||
plus: false,
|
plus: false,
|
||||||
}]),
|
}]),
|
||||||
HintGroup(vec![HintInfo {
|
HintGroup(vec![HintInfo {
|
||||||
key_groups: vec![KeysGroup(vec![Key::KeyShift])],
|
key_groups: vec![KeysGroup(vec![Key::KeyShift])],
|
||||||
|
key_groups_mac: None,
|
||||||
mouse: None,
|
mouse: None,
|
||||||
label: String::from("Break Handle"),
|
label: String::from("Break Handle"),
|
||||||
plus: false,
|
plus: false,
|
||||||
}]),
|
}]),
|
||||||
HintGroup(vec![HintInfo {
|
HintGroup(vec![HintInfo {
|
||||||
key_groups: vec![KeysGroup(vec![Key::KeyEnter])],
|
key_groups: vec![KeysGroup(vec![Key::KeyEnter])],
|
||||||
|
key_groups_mac: None,
|
||||||
mouse: None,
|
mouse: None,
|
||||||
label: String::from("End Path"),
|
label: String::from("End Path"),
|
||||||
plus: false,
|
plus: false,
|
||||||
|
|
|
@ -80,7 +80,7 @@ impl ToolMetadata for RectangleTool {
|
||||||
"VectorRectangleTool".into()
|
"VectorRectangleTool".into()
|
||||||
}
|
}
|
||||||
fn tooltip(&self) -> String {
|
fn tooltip(&self) -> String {
|
||||||
"Rectangle Tool (M)".into()
|
"Rectangle Tool".into()
|
||||||
}
|
}
|
||||||
fn tool_type(&self) -> crate::viewport_tools::tool::ToolType {
|
fn tool_type(&self) -> crate::viewport_tools::tool::ToolType {
|
||||||
ToolType::Rectangle
|
ToolType::Rectangle
|
||||||
|
@ -186,18 +186,21 @@ impl Fsm for RectangleToolFsmState {
|
||||||
RectangleToolFsmState::Ready => HintData(vec![HintGroup(vec![
|
RectangleToolFsmState::Ready => HintData(vec![HintGroup(vec![
|
||||||
HintInfo {
|
HintInfo {
|
||||||
key_groups: vec![],
|
key_groups: vec![],
|
||||||
|
key_groups_mac: None,
|
||||||
mouse: Some(MouseMotion::LmbDrag),
|
mouse: Some(MouseMotion::LmbDrag),
|
||||||
label: String::from("Draw Rectangle"),
|
label: String::from("Draw Rectangle"),
|
||||||
plus: false,
|
plus: false,
|
||||||
},
|
},
|
||||||
HintInfo {
|
HintInfo {
|
||||||
key_groups: vec![KeysGroup(vec![Key::KeyShift])],
|
key_groups: vec![KeysGroup(vec![Key::KeyShift])],
|
||||||
|
key_groups_mac: None,
|
||||||
mouse: None,
|
mouse: None,
|
||||||
label: String::from("Constrain Square"),
|
label: String::from("Constrain Square"),
|
||||||
plus: true,
|
plus: true,
|
||||||
},
|
},
|
||||||
HintInfo {
|
HintInfo {
|
||||||
key_groups: vec![KeysGroup(vec![Key::KeyAlt])],
|
key_groups: vec![KeysGroup(vec![Key::KeyAlt])],
|
||||||
|
key_groups_mac: None,
|
||||||
mouse: None,
|
mouse: None,
|
||||||
label: String::from("From Center"),
|
label: String::from("From Center"),
|
||||||
plus: true,
|
plus: true,
|
||||||
|
@ -206,12 +209,14 @@ impl Fsm for RectangleToolFsmState {
|
||||||
RectangleToolFsmState::Drawing => HintData(vec![HintGroup(vec![
|
RectangleToolFsmState::Drawing => HintData(vec![HintGroup(vec![
|
||||||
HintInfo {
|
HintInfo {
|
||||||
key_groups: vec![KeysGroup(vec![Key::KeyShift])],
|
key_groups: vec![KeysGroup(vec![Key::KeyShift])],
|
||||||
|
key_groups_mac: None,
|
||||||
mouse: None,
|
mouse: None,
|
||||||
label: String::from("Constrain Square"),
|
label: String::from("Constrain Square"),
|
||||||
plus: false,
|
plus: false,
|
||||||
},
|
},
|
||||||
HintInfo {
|
HintInfo {
|
||||||
key_groups: vec![KeysGroup(vec![Key::KeyAlt])],
|
key_groups: vec![KeysGroup(vec![Key::KeyAlt])],
|
||||||
|
key_groups_mac: None,
|
||||||
mouse: None,
|
mouse: None,
|
||||||
label: String::from("From Center"),
|
label: String::from("From Center"),
|
||||||
plus: false,
|
plus: false,
|
||||||
|
|
|
@ -63,7 +63,7 @@ impl ToolMetadata for SelectTool {
|
||||||
"GeneralSelectTool".into()
|
"GeneralSelectTool".into()
|
||||||
}
|
}
|
||||||
fn tooltip(&self) -> String {
|
fn tooltip(&self) -> String {
|
||||||
"Select Tool (V)".into()
|
"Select Tool".into()
|
||||||
}
|
}
|
||||||
fn tool_type(&self) -> crate::viewport_tools::tool::ToolType {
|
fn tool_type(&self) -> crate::viewport_tools::tool::ToolType {
|
||||||
ToolType::Select
|
ToolType::Select
|
||||||
|
@ -390,7 +390,7 @@ impl Fsm for SelectToolFsmState {
|
||||||
match intersect.data {
|
match intersect.data {
|
||||||
LayerDataType::Text(_) => {
|
LayerDataType::Text(_) => {
|
||||||
responses.push_front(ToolMessage::ActivateTool { tool_type: ToolType::Text }.into());
|
responses.push_front(ToolMessage::ActivateTool { tool_type: ToolType::Text }.into());
|
||||||
responses.push_back(TextMessage::Interact.into());
|
responses.push_back(TextToolMessage::Interact.into());
|
||||||
}
|
}
|
||||||
LayerDataType::Shape(_) => {
|
LayerDataType::Shape(_) => {
|
||||||
responses.push_front(ToolMessage::ActivateTool { tool_type: ToolType::Path }.into());
|
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 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 delta = movement.bounds_to_scale_transform(center, size);
|
||||||
|
|
||||||
let selected = tool_data.layers_dragging.iter().collect::<Vec<_>>();
|
let selected = tool_data.layers_dragging.iter().collect::<Vec<_>>();
|
||||||
|
@ -702,6 +702,7 @@ impl Fsm for SelectToolFsmState {
|
||||||
SelectToolFsmState::Ready => HintData(vec![
|
SelectToolFsmState::Ready => HintData(vec![
|
||||||
HintGroup(vec![HintInfo {
|
HintGroup(vec![HintInfo {
|
||||||
key_groups: vec![],
|
key_groups: vec![],
|
||||||
|
key_groups_mac: None,
|
||||||
mouse: Some(MouseMotion::LmbDrag),
|
mouse: Some(MouseMotion::LmbDrag),
|
||||||
label: String::from("Drag Selected"),
|
label: String::from("Drag Selected"),
|
||||||
plus: false,
|
plus: false,
|
||||||
|
@ -709,18 +710,21 @@ impl Fsm for SelectToolFsmState {
|
||||||
HintGroup(vec![
|
HintGroup(vec![
|
||||||
HintInfo {
|
HintInfo {
|
||||||
key_groups: vec![KeysGroup(vec![Key::KeyG])],
|
key_groups: vec![KeysGroup(vec![Key::KeyG])],
|
||||||
|
key_groups_mac: None,
|
||||||
mouse: None,
|
mouse: None,
|
||||||
label: String::from("Grab Selected"),
|
label: String::from("Grab Selected"),
|
||||||
plus: false,
|
plus: false,
|
||||||
},
|
},
|
||||||
HintInfo {
|
HintInfo {
|
||||||
key_groups: vec![KeysGroup(vec![Key::KeyR])],
|
key_groups: vec![KeysGroup(vec![Key::KeyR])],
|
||||||
|
key_groups_mac: None,
|
||||||
mouse: None,
|
mouse: None,
|
||||||
label: String::from("Rotate Selected"),
|
label: String::from("Rotate Selected"),
|
||||||
plus: false,
|
plus: false,
|
||||||
},
|
},
|
||||||
HintInfo {
|
HintInfo {
|
||||||
key_groups: vec![KeysGroup(vec![Key::KeyS])],
|
key_groups: vec![KeysGroup(vec![Key::KeyS])],
|
||||||
|
key_groups_mac: None,
|
||||||
mouse: None,
|
mouse: None,
|
||||||
label: String::from("Scale Selected"),
|
label: String::from("Scale Selected"),
|
||||||
plus: false,
|
plus: false,
|
||||||
|
@ -729,18 +733,21 @@ impl Fsm for SelectToolFsmState {
|
||||||
HintGroup(vec![
|
HintGroup(vec![
|
||||||
HintInfo {
|
HintInfo {
|
||||||
key_groups: vec![],
|
key_groups: vec![],
|
||||||
|
key_groups_mac: None,
|
||||||
mouse: Some(MouseMotion::Lmb),
|
mouse: Some(MouseMotion::Lmb),
|
||||||
label: String::from("Select Object"),
|
label: String::from("Select Object"),
|
||||||
plus: false,
|
plus: false,
|
||||||
},
|
},
|
||||||
HintInfo {
|
HintInfo {
|
||||||
key_groups: vec![KeysGroup(vec![Key::KeyControl])],
|
key_groups: vec![KeysGroup(vec![Key::KeyControl])],
|
||||||
|
key_groups_mac: Some(vec![KeysGroup(vec![Key::KeyCommand])]),
|
||||||
mouse: None,
|
mouse: None,
|
||||||
label: String::from("Innermost"),
|
label: String::from("Innermost"),
|
||||||
plus: true,
|
plus: true,
|
||||||
},
|
},
|
||||||
HintInfo {
|
HintInfo {
|
||||||
key_groups: vec![KeysGroup(vec![Key::KeyShift])],
|
key_groups: vec![KeysGroup(vec![Key::KeyShift])],
|
||||||
|
key_groups_mac: None,
|
||||||
mouse: None,
|
mouse: None,
|
||||||
label: String::from("Grow/Shrink Selection"),
|
label: String::from("Grow/Shrink Selection"),
|
||||||
plus: true,
|
plus: true,
|
||||||
|
@ -749,12 +756,14 @@ impl Fsm for SelectToolFsmState {
|
||||||
HintGroup(vec![
|
HintGroup(vec![
|
||||||
HintInfo {
|
HintInfo {
|
||||||
key_groups: vec![],
|
key_groups: vec![],
|
||||||
|
key_groups_mac: None,
|
||||||
mouse: Some(MouseMotion::LmbDrag),
|
mouse: Some(MouseMotion::LmbDrag),
|
||||||
label: String::from("Select Area"),
|
label: String::from("Select Area"),
|
||||||
plus: false,
|
plus: false,
|
||||||
},
|
},
|
||||||
HintInfo {
|
HintInfo {
|
||||||
key_groups: vec![KeysGroup(vec![Key::KeyShift])],
|
key_groups: vec![KeysGroup(vec![Key::KeyShift])],
|
||||||
|
key_groups_mac: None,
|
||||||
mouse: None,
|
mouse: None,
|
||||||
label: String::from("Grow/Shrink Selection"),
|
label: String::from("Grow/Shrink Selection"),
|
||||||
plus: true,
|
plus: true,
|
||||||
|
@ -768,12 +777,14 @@ impl Fsm for SelectToolFsmState {
|
||||||
KeysGroup(vec![Key::KeyArrowDown]),
|
KeysGroup(vec![Key::KeyArrowDown]),
|
||||||
KeysGroup(vec![Key::KeyArrowLeft]),
|
KeysGroup(vec![Key::KeyArrowLeft]),
|
||||||
],
|
],
|
||||||
|
key_groups_mac: None,
|
||||||
mouse: None,
|
mouse: None,
|
||||||
label: String::from("Nudge Selected"),
|
label: String::from("Nudge Selected"),
|
||||||
plus: false,
|
plus: false,
|
||||||
},
|
},
|
||||||
HintInfo {
|
HintInfo {
|
||||||
key_groups: vec![KeysGroup(vec![Key::KeyShift])],
|
key_groups: vec![KeysGroup(vec![Key::KeyShift])],
|
||||||
|
key_groups_mac: None,
|
||||||
mouse: None,
|
mouse: None,
|
||||||
label: String::from("Big Increment Nudge"),
|
label: String::from("Big Increment Nudge"),
|
||||||
plus: true,
|
plus: true,
|
||||||
|
@ -782,12 +793,14 @@ impl Fsm for SelectToolFsmState {
|
||||||
HintGroup(vec![
|
HintGroup(vec![
|
||||||
HintInfo {
|
HintInfo {
|
||||||
key_groups: vec![KeysGroup(vec![Key::KeyAlt])],
|
key_groups: vec![KeysGroup(vec![Key::KeyAlt])],
|
||||||
|
key_groups_mac: None,
|
||||||
mouse: Some(MouseMotion::LmbDrag),
|
mouse: Some(MouseMotion::LmbDrag),
|
||||||
label: String::from("Move Duplicate"),
|
label: String::from("Move Duplicate"),
|
||||||
plus: false,
|
plus: false,
|
||||||
},
|
},
|
||||||
HintInfo {
|
HintInfo {
|
||||||
key_groups: vec![KeysGroup(vec![Key::KeyControl, Key::KeyD])],
|
key_groups: vec![KeysGroup(vec![Key::KeyControl, Key::KeyD])],
|
||||||
|
key_groups_mac: Some(vec![KeysGroup(vec![Key::KeyCommand, Key::KeyD])]),
|
||||||
mouse: None,
|
mouse: None,
|
||||||
label: String::from("Duplicate"),
|
label: String::from("Duplicate"),
|
||||||
plus: false,
|
plus: false,
|
||||||
|
@ -797,12 +810,14 @@ impl Fsm for SelectToolFsmState {
|
||||||
SelectToolFsmState::Dragging => HintData(vec![HintGroup(vec![
|
SelectToolFsmState::Dragging => HintData(vec![HintGroup(vec![
|
||||||
HintInfo {
|
HintInfo {
|
||||||
key_groups: vec![KeysGroup(vec![Key::KeyShift])],
|
key_groups: vec![KeysGroup(vec![Key::KeyShift])],
|
||||||
|
key_groups_mac: None,
|
||||||
mouse: None,
|
mouse: None,
|
||||||
label: String::from("Constrain to Axis"),
|
label: String::from("Constrain to Axis"),
|
||||||
plus: false,
|
plus: false,
|
||||||
},
|
},
|
||||||
HintInfo {
|
HintInfo {
|
||||||
key_groups: vec![KeysGroup(vec![Key::KeyControl])],
|
key_groups: vec![KeysGroup(vec![Key::KeyControl])],
|
||||||
|
key_groups_mac: None,
|
||||||
mouse: None,
|
mouse: None,
|
||||||
label: String::from("Snap to Points (coming soon)"),
|
label: String::from("Snap to Points (coming soon)"),
|
||||||
plus: false,
|
plus: false,
|
||||||
|
@ -812,6 +827,7 @@ impl Fsm for SelectToolFsmState {
|
||||||
SelectToolFsmState::ResizingBounds => HintData(vec![]),
|
SelectToolFsmState::ResizingBounds => HintData(vec![]),
|
||||||
SelectToolFsmState::RotatingBounds => HintData(vec![HintGroup(vec![HintInfo {
|
SelectToolFsmState::RotatingBounds => HintData(vec![HintGroup(vec![HintInfo {
|
||||||
key_groups: vec![KeysGroup(vec![Key::KeyControl])],
|
key_groups: vec![KeysGroup(vec![Key::KeyControl])],
|
||||||
|
key_groups_mac: None,
|
||||||
mouse: None,
|
mouse: None,
|
||||||
label: String::from("Snap 15°"),
|
label: String::from("Snap 15°"),
|
||||||
plus: false,
|
plus: false,
|
||||||
|
|
|
@ -59,7 +59,7 @@ impl ToolMetadata for ShapeTool {
|
||||||
"VectorShapeTool".into()
|
"VectorShapeTool".into()
|
||||||
}
|
}
|
||||||
fn tooltip(&self) -> String {
|
fn tooltip(&self) -> String {
|
||||||
"Shape Tool (Y)".into()
|
"Shape Tool".into()
|
||||||
}
|
}
|
||||||
fn tool_type(&self) -> crate::viewport_tools::tool::ToolType {
|
fn tool_type(&self) -> crate::viewport_tools::tool::ToolType {
|
||||||
ToolType::Shape
|
ToolType::Shape
|
||||||
|
@ -228,18 +228,21 @@ impl Fsm for ShapeToolFsmState {
|
||||||
ShapeToolFsmState::Ready => HintData(vec![HintGroup(vec![
|
ShapeToolFsmState::Ready => HintData(vec![HintGroup(vec![
|
||||||
HintInfo {
|
HintInfo {
|
||||||
key_groups: vec![],
|
key_groups: vec![],
|
||||||
|
key_groups_mac: None,
|
||||||
mouse: Some(MouseMotion::LmbDrag),
|
mouse: Some(MouseMotion::LmbDrag),
|
||||||
label: String::from("Draw Shape"),
|
label: String::from("Draw Shape"),
|
||||||
plus: false,
|
plus: false,
|
||||||
},
|
},
|
||||||
HintInfo {
|
HintInfo {
|
||||||
key_groups: vec![KeysGroup(vec![Key::KeyShift])],
|
key_groups: vec![KeysGroup(vec![Key::KeyShift])],
|
||||||
|
key_groups_mac: None,
|
||||||
mouse: None,
|
mouse: None,
|
||||||
label: String::from("Constrain 1:1 Aspect"),
|
label: String::from("Constrain 1:1 Aspect"),
|
||||||
plus: true,
|
plus: true,
|
||||||
},
|
},
|
||||||
HintInfo {
|
HintInfo {
|
||||||
key_groups: vec![KeysGroup(vec![Key::KeyAlt])],
|
key_groups: vec![KeysGroup(vec![Key::KeyAlt])],
|
||||||
|
key_groups_mac: None,
|
||||||
mouse: None,
|
mouse: None,
|
||||||
label: String::from("From Center"),
|
label: String::from("From Center"),
|
||||||
plus: true,
|
plus: true,
|
||||||
|
@ -248,12 +251,14 @@ impl Fsm for ShapeToolFsmState {
|
||||||
ShapeToolFsmState::Drawing => HintData(vec![HintGroup(vec![
|
ShapeToolFsmState::Drawing => HintData(vec![HintGroup(vec![
|
||||||
HintInfo {
|
HintInfo {
|
||||||
key_groups: vec![KeysGroup(vec![Key::KeyShift])],
|
key_groups: vec![KeysGroup(vec![Key::KeyShift])],
|
||||||
|
key_groups_mac: None,
|
||||||
mouse: None,
|
mouse: None,
|
||||||
label: String::from("Constrain 1:1 Aspect"),
|
label: String::from("Constrain 1:1 Aspect"),
|
||||||
plus: false,
|
plus: false,
|
||||||
},
|
},
|
||||||
HintInfo {
|
HintInfo {
|
||||||
key_groups: vec![KeysGroup(vec![Key::KeyAlt])],
|
key_groups: vec![KeysGroup(vec![Key::KeyAlt])],
|
||||||
|
key_groups_mac: None,
|
||||||
mouse: None,
|
mouse: None,
|
||||||
label: String::from("From Center"),
|
label: String::from("From Center"),
|
||||||
plus: false,
|
plus: false,
|
||||||
|
|
|
@ -61,7 +61,7 @@ impl SelectedEdges {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Computes the new bounds with the given mouse move and modifier keys
|
/// 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 mouse = transform.inverse().transform_point2(mouse);
|
||||||
|
|
||||||
let mut min = self.bounds[0];
|
let mut min = self.bounds[0];
|
||||||
|
@ -72,7 +72,9 @@ impl SelectedEdges {
|
||||||
max.y = mouse.y;
|
max.y = mouse.y;
|
||||||
}
|
}
|
||||||
if self.left {
|
if self.left {
|
||||||
min.x = mouse.x
|
let delta = min.x - mouse.x;
|
||||||
|
min.x = mouse.x;
|
||||||
|
max.x += delta;
|
||||||
} else if self.right {
|
} else if self.right {
|
||||||
max.x = mouse.x;
|
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
|
/// Offsets the transformation pivot in order to scale from the center
|
||||||
fn offset_pivot(&self, center: bool, size: DVec2) -> DVec2 {
|
fn offset_pivot(&self, center: bool, size: DVec2) -> DVec2 {
|
||||||
let mut offset = DVec2::ZERO;
|
let mut offset = DVec2::ZERO;
|
||||||
|
|
||||||
if center && self.right {
|
if !center {
|
||||||
|
return offset;
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.right {
|
||||||
offset.x -= size.x / 2.;
|
offset.x -= size.x / 2.;
|
||||||
}
|
}
|
||||||
if center && self.left {
|
if self.left {
|
||||||
offset.x += size.x / 2.;
|
offset.x += size.x / 2.;
|
||||||
}
|
}
|
||||||
if center && self.bottom {
|
if self.bottom {
|
||||||
offset.y -= size.y / 2.;
|
offset.y -= size.y / 2.;
|
||||||
}
|
}
|
||||||
if center && self.top {
|
if self.top {
|
||||||
offset.y += size.y / 2.;
|
offset.y += size.y / 2.;
|
||||||
}
|
}
|
||||||
offset
|
offset
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Moves the position to account for centring (only necessary with absolute transforms - e.g. with artboards)
|
/// 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, center: bool) -> DVec2 {
|
pub fn center_position(&self, mut position: DVec2, size: DVec2) -> DVec2 {
|
||||||
if center && self.right {
|
if self.right {
|
||||||
position.x -= size.x / 2.;
|
position.x -= size.x / 2.;
|
||||||
}
|
}
|
||||||
if center && self.bottom {
|
if self.bottom {
|
||||||
position.y -= size.y / 2.;
|
position.y -= size.y / 2.;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -251,6 +251,7 @@ impl Fsm for SplineToolFsmState {
|
||||||
let hint_data = match self {
|
let hint_data = match self {
|
||||||
SplineToolFsmState::Ready => HintData(vec![HintGroup(vec![HintInfo {
|
SplineToolFsmState::Ready => HintData(vec![HintGroup(vec![HintInfo {
|
||||||
key_groups: vec![],
|
key_groups: vec![],
|
||||||
|
key_groups_mac: None,
|
||||||
mouse: Some(MouseMotion::Lmb),
|
mouse: Some(MouseMotion::Lmb),
|
||||||
label: String::from("Draw Spline"),
|
label: String::from("Draw Spline"),
|
||||||
plus: false,
|
plus: false,
|
||||||
|
@ -258,12 +259,14 @@ impl Fsm for SplineToolFsmState {
|
||||||
SplineToolFsmState::Drawing => HintData(vec![
|
SplineToolFsmState::Drawing => HintData(vec![
|
||||||
HintGroup(vec![HintInfo {
|
HintGroup(vec![HintInfo {
|
||||||
key_groups: vec![],
|
key_groups: vec![],
|
||||||
|
key_groups_mac: None,
|
||||||
mouse: Some(MouseMotion::Lmb),
|
mouse: Some(MouseMotion::Lmb),
|
||||||
label: String::from("Extend Spline"),
|
label: String::from("Extend Spline"),
|
||||||
plus: false,
|
plus: false,
|
||||||
}]),
|
}]),
|
||||||
HintGroup(vec![HintInfo {
|
HintGroup(vec![HintInfo {
|
||||||
key_groups: vec![KeysGroup(vec![Key::KeyEnter])],
|
key_groups: vec![KeysGroup(vec![Key::KeyEnter])],
|
||||||
|
key_groups_mac: None,
|
||||||
mouse: None,
|
mouse: None,
|
||||||
label: String::from("End Spline"),
|
label: String::from("End Spline"),
|
||||||
plus: false,
|
plus: false,
|
||||||
|
|
|
@ -42,7 +42,7 @@ impl Default for TextOptions {
|
||||||
#[remain::sorted]
|
#[remain::sorted]
|
||||||
#[impl_message(Message, ToolMessage, Text)]
|
#[impl_message(Message, ToolMessage, Text)]
|
||||||
#[derive(PartialEq, Eq, Clone, Debug, Hash, Serialize, Deserialize)]
|
#[derive(PartialEq, Eq, Clone, Debug, Hash, Serialize, Deserialize)]
|
||||||
pub enum TextMessage {
|
pub enum TextToolMessage {
|
||||||
// Standard messages
|
// Standard messages
|
||||||
#[remain::unsorted]
|
#[remain::unsorted]
|
||||||
Abort,
|
Abort,
|
||||||
|
@ -74,7 +74,7 @@ impl ToolMetadata for TextTool {
|
||||||
"VectorTextTool".into()
|
"VectorTextTool".into()
|
||||||
}
|
}
|
||||||
fn tooltip(&self) -> String {
|
fn tooltip(&self) -> String {
|
||||||
"Text Tool (T)".into()
|
"Text Tool".into()
|
||||||
}
|
}
|
||||||
fn tool_type(&self) -> crate::viewport_tools::tool::ToolType {
|
fn tool_type(&self) -> crate::viewport_tools::tool::ToolType {
|
||||||
ToolType::Text
|
ToolType::Text
|
||||||
|
@ -90,7 +90,7 @@ impl PropertyHolder for TextTool {
|
||||||
font_family: self.options.font_name.clone(),
|
font_family: self.options.font_name.clone(),
|
||||||
font_style: self.options.font_style.clone(),
|
font_style: self.options.font_style.clone(),
|
||||||
on_update: WidgetCallback::new(|font_input: &FontInput| {
|
on_update: WidgetCallback::new(|font_input: &FontInput| {
|
||||||
TextMessage::UpdateOptions(TextOptionsUpdate::Font {
|
TextToolMessage::UpdateOptions(TextOptionsUpdate::Font {
|
||||||
family: font_input.font_family.clone(),
|
family: font_input.font_family.clone(),
|
||||||
style: font_input.font_style.clone(),
|
style: font_input.font_style.clone(),
|
||||||
})
|
})
|
||||||
|
@ -107,7 +107,7 @@ impl PropertyHolder for TextTool {
|
||||||
font_family: self.options.font_name.clone(),
|
font_family: self.options.font_name.clone(),
|
||||||
font_style: self.options.font_style.clone(),
|
font_style: self.options.font_style.clone(),
|
||||||
on_update: WidgetCallback::new(|font_input: &FontInput| {
|
on_update: WidgetCallback::new(|font_input: &FontInput| {
|
||||||
TextMessage::UpdateOptions(TextOptionsUpdate::Font {
|
TextToolMessage::UpdateOptions(TextOptionsUpdate::Font {
|
||||||
family: font_input.font_family.clone(),
|
family: font_input.font_family.clone(),
|
||||||
style: font_input.font_style.clone(),
|
style: font_input.font_style.clone(),
|
||||||
})
|
})
|
||||||
|
@ -125,7 +125,7 @@ impl PropertyHolder for TextTool {
|
||||||
value: Some(self.options.font_size as f64),
|
value: Some(self.options.font_size as f64),
|
||||||
is_integer: true,
|
is_integer: true,
|
||||||
min: Some(1.),
|
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()
|
..NumberInput::default()
|
||||||
})),
|
})),
|
||||||
],
|
],
|
||||||
|
@ -145,7 +145,7 @@ impl<'a> MessageHandler<ToolMessage, ToolActionHandlerData<'a>> for TextTool {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if let ToolMessage::Text(TextMessage::UpdateOptions(action)) = action {
|
if let ToolMessage::Text(TextToolMessage::UpdateOptions(action)) = action {
|
||||||
match action {
|
match action {
|
||||||
TextOptionsUpdate::Font { family, style } => {
|
TextOptionsUpdate::Font { family, style } => {
|
||||||
self.options.font_name = family;
|
self.options.font_name = family;
|
||||||
|
@ -171,10 +171,10 @@ impl<'a> MessageHandler<ToolMessage, ToolActionHandlerData<'a>> for TextTool {
|
||||||
use TextToolFsmState::*;
|
use TextToolFsmState::*;
|
||||||
|
|
||||||
match self.fsm_state {
|
match self.fsm_state {
|
||||||
Ready => actions!(TextMessageDiscriminant;
|
Ready => actions!(TextToolMessageDiscriminant;
|
||||||
Interact,
|
Interact,
|
||||||
),
|
),
|
||||||
Editing => actions!(TextMessageDiscriminant;
|
Editing => actions!(TextToolMessageDiscriminant;
|
||||||
Interact,
|
Interact,
|
||||||
Abort,
|
Abort,
|
||||||
CommitText,
|
CommitText,
|
||||||
|
@ -186,8 +186,8 @@ impl<'a> MessageHandler<ToolMessage, ToolActionHandlerData<'a>> for TextTool {
|
||||||
impl ToolTransition for TextTool {
|
impl ToolTransition for TextTool {
|
||||||
fn signal_to_message_map(&self) -> SignalToMessageMap {
|
fn signal_to_message_map(&self) -> SignalToMessageMap {
|
||||||
SignalToMessageMap {
|
SignalToMessageMap {
|
||||||
document_dirty: Some(TextMessage::DocumentIsDirty.into()),
|
document_dirty: Some(TextToolMessage::DocumentIsDirty.into()),
|
||||||
tool_abort: Some(TextMessage::Abort.into()),
|
tool_abort: Some(TextToolMessage::Abort.into()),
|
||||||
selection_changed: None,
|
selection_changed: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -275,8 +275,8 @@ impl Fsm for TextToolFsmState {
|
||||||
tool_options: &Self::ToolOptions,
|
tool_options: &Self::ToolOptions,
|
||||||
responses: &mut VecDeque<Message>,
|
responses: &mut VecDeque<Message>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
use TextMessage::*;
|
|
||||||
use TextToolFsmState::*;
|
use TextToolFsmState::*;
|
||||||
|
use TextToolMessage::*;
|
||||||
|
|
||||||
if let ToolMessage::Text(event) = event {
|
if let ToolMessage::Text(event) = event {
|
||||||
match (self, event) {
|
match (self, event) {
|
||||||
|
@ -456,12 +456,14 @@ impl Fsm for TextToolFsmState {
|
||||||
TextToolFsmState::Ready => HintData(vec![HintGroup(vec![
|
TextToolFsmState::Ready => HintData(vec![HintGroup(vec![
|
||||||
HintInfo {
|
HintInfo {
|
||||||
key_groups: vec![],
|
key_groups: vec![],
|
||||||
|
key_groups_mac: None,
|
||||||
mouse: Some(MouseMotion::Lmb),
|
mouse: Some(MouseMotion::Lmb),
|
||||||
label: String::from("Add Text"),
|
label: String::from("Add Text"),
|
||||||
plus: false,
|
plus: false,
|
||||||
},
|
},
|
||||||
HintInfo {
|
HintInfo {
|
||||||
key_groups: vec![],
|
key_groups: vec![],
|
||||||
|
key_groups_mac: None,
|
||||||
mouse: Some(MouseMotion::Lmb),
|
mouse: Some(MouseMotion::Lmb),
|
||||||
label: String::from("Edit Text"),
|
label: String::from("Edit Text"),
|
||||||
plus: false,
|
plus: false,
|
||||||
|
@ -470,12 +472,14 @@ impl Fsm for TextToolFsmState {
|
||||||
TextToolFsmState::Editing => HintData(vec![HintGroup(vec![
|
TextToolFsmState::Editing => HintData(vec![HintGroup(vec![
|
||||||
HintInfo {
|
HintInfo {
|
||||||
key_groups: vec![KeysGroup(vec![Key::KeyControl, Key::KeyEnter])],
|
key_groups: vec![KeysGroup(vec![Key::KeyControl, Key::KeyEnter])],
|
||||||
|
key_groups_mac: Some(vec![KeysGroup(vec![Key::KeyCommand, Key::KeyEnter])]),
|
||||||
mouse: None,
|
mouse: None,
|
||||||
label: String::from("Commit Edit"),
|
label: String::from("Commit Edit"),
|
||||||
plus: false,
|
plus: false,
|
||||||
},
|
},
|
||||||
HintInfo {
|
HintInfo {
|
||||||
key_groups: vec![KeysGroup(vec![Key::KeyEscape])],
|
key_groups: vec![KeysGroup(vec![Key::KeyEscape])],
|
||||||
|
key_groups_mac: None,
|
||||||
mouse: None,
|
mouse: None,
|
||||||
label: String::from("Discard Edit"),
|
label: String::from("Discard Edit"),
|
||||||
plus: false,
|
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 { createPanelsState, PanelsState } from "@/state-providers/panels";
|
||||||
import { createPortfolioState, PortfolioState } from "@/state-providers/portfolio";
|
import { createPortfolioState, PortfolioState } from "@/state-providers/portfolio";
|
||||||
import { createWorkspaceState, WorkspaceState } from "@/state-providers/workspace";
|
import { createWorkspaceState, WorkspaceState } from "@/state-providers/workspace";
|
||||||
|
import { operatingSystem } from "@/utility-functions/platform";
|
||||||
import { createEditor, Editor } from "@/wasm-communication/editor";
|
import { createEditor, Editor } from "@/wasm-communication/editor";
|
||||||
|
|
||||||
import MainWindow from "@/components/window/MainWindow.vue";
|
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
|
// 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() {
|
beforeUnmount() {
|
||||||
// Call the destructor for each manager
|
// Call the destructor for each manager
|
||||||
|
|
|
@ -33,8 +33,7 @@
|
||||||
|
|
||||||
<span class="entry-label" :style="{ fontFamily: `${!entry.font ? 'inherit' : entry.value}` }">{{ entry.label }}</span>
|
<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-if="entry.shortcut?.keys.length" :inputKeys="[entry.shortcut.keys]" :requiresLock="entry.shortcutRequiresLock" />
|
||||||
<UserInputLabel v-else-if="entry.shortcut?.length" :inputKeys="[entry.shortcut]" />
|
|
||||||
|
|
||||||
<div class="submenu-arrow" v-if="entry.children?.length"></div>
|
<div class="submenu-arrow" v-if="entry.children?.length"></div>
|
||||||
<div class="no-submenu-arrow" v-else></div>
|
<div class="no-submenu-arrow" v-else></div>
|
||||||
|
@ -64,6 +63,10 @@
|
||||||
.floating-menu-container .floating-menu-content {
|
.floating-menu-container .floating-menu-content {
|
||||||
padding: 4px 0;
|
padding: 4px 0;
|
||||||
|
|
||||||
|
.separator div {
|
||||||
|
background: var(--color-4-dimgray);
|
||||||
|
}
|
||||||
|
|
||||||
.scroll-spacer {
|
.scroll-spacer {
|
||||||
flex: 0 0 auto;
|
flex: 0 0 auto;
|
||||||
}
|
}
|
||||||
|
@ -128,29 +131,24 @@
|
||||||
&.open,
|
&.open,
|
||||||
&.active {
|
&.active {
|
||||||
background: var(--color-6-lowergray);
|
background: var(--color-6-lowergray);
|
||||||
|
color: var(--color-f-white);
|
||||||
|
|
||||||
&.active {
|
&.active {
|
||||||
background: var(--color-accent);
|
background: var(--color-accent);
|
||||||
}
|
}
|
||||||
|
|
||||||
svg {
|
.entry-icon svg {
|
||||||
fill: var(--color-f-white);
|
fill: var(--color-f-white);
|
||||||
}
|
}
|
||||||
|
|
||||||
span {
|
|
||||||
color: var(--color-f-white);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
&.disabled {
|
&.disabled {
|
||||||
|
color: var(--color-8-uppergray);
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
background: none;
|
background: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
span {
|
|
||||||
color: var(--color-8-uppergray);
|
|
||||||
}
|
|
||||||
|
|
||||||
svg {
|
svg {
|
||||||
fill: var(--color-8-uppergray);
|
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 Separator from "@/components/widgets/labels/Separator.vue";
|
||||||
import UserInputLabel from "@/components/widgets/labels/UserInputLabel.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({
|
const MenuList = defineComponent({
|
||||||
inject: ["fullscreen"],
|
|
||||||
emits: ["update:open", "update:activeEntry", "naturalWidth"],
|
emits: ["update:open", "update:activeEntry", "naturalWidth"],
|
||||||
props: {
|
props: {
|
||||||
entries: { type: Array as PropType<SectionsOfMenuListEntries>, required: true },
|
entries: { type: Array as PropType<SectionsOfMenuListEntries>, required: true },
|
||||||
|
@ -193,7 +187,6 @@ const MenuList = defineComponent({
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
isOpen: this.open,
|
isOpen: this.open,
|
||||||
keyboardLockInfoMessage: this.fullscreen.keyboardLockApiSupported ? KEYBOARD_LOCK_USE_FULLSCREEN : KEYBOARD_LOCK_SWITCH_BROWSER,
|
|
||||||
highlighted: this.activeEntry as MenuListEntry | undefined,
|
highlighted: this.activeEntry as MenuListEntry | undefined,
|
||||||
virtualScrollingEntriesStart: 0,
|
virtualScrollingEntriesStart: 0,
|
||||||
};
|
};
|
||||||
|
|
|
@ -31,14 +31,18 @@
|
||||||
<LayoutRow
|
<LayoutRow
|
||||||
class="layer"
|
class="layer"
|
||||||
:class="{ selected: listing.entry.layer_metadata.selected }"
|
: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"
|
:data-index="index"
|
||||||
|
:title="`${listing.entry.name}${devMode ? '\nLayer Path: ' + listing.entry.path.join(' / ') : ''}` || null"
|
||||||
:draggable="draggable"
|
:draggable="draggable"
|
||||||
@dragstart="(e) => draggable && dragStart(e, listing.entry)"
|
@dragstart="(e) => draggable && dragStart(e, listing)"
|
||||||
:title="`${listing.entry.name}\n${devMode ? 'Layer Path: ' + listing.entry.path.join(' / ') : ''}`.trim() || null"
|
@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">
|
<LayoutRow class="layer-type-icon">
|
||||||
<IconLabel v-if="listing.entry.layer_type === 'Folder'" :icon="'NodeFolder'" :iconStyle="'Node'" title="Folder" />
|
<IconLabel v-if="listing.entry.layer_type === 'Folder'" :icon="'NodeFolder'" :iconStyle="'Node'" title="Folder" />
|
||||||
|
@ -53,10 +57,10 @@
|
||||||
:value="listing.entry.name"
|
:value="listing.entry.name"
|
||||||
:placeholder="listing.entry.layer_type"
|
:placeholder="listing.entry.layer_type"
|
||||||
:disabled="!listing.editingName"
|
:disabled="!listing.editingName"
|
||||||
@change="(e) => onEditLayerNameChange(listing, e.target)"
|
|
||||||
@blur="() => onEditLayerNameDeselect(listing)"
|
@blur="() => onEditLayerNameDeselect(listing)"
|
||||||
|
@keydown.esc="onEditLayerNameDeselect(listing)"
|
||||||
@keydown.enter="(e) => onEditLayerNameChange(listing, e.target)"
|
@keydown.enter="(e) => onEditLayerNameChange(listing, e.target)"
|
||||||
@keydown.escape="onEditLayerNameDeselect(listing)"
|
@change="(e) => onEditLayerNameChange(listing, e.target)"
|
||||||
/>
|
/>
|
||||||
</LayoutRow>
|
</LayoutRow>
|
||||||
<div class="thumbnail" v-html="listing.entry.thumbnail"></div>
|
<div class="thumbnail" v-html="listing.entry.thumbnail"></div>
|
||||||
|
@ -263,6 +267,7 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { defineComponent, nextTick } from "vue";
|
import { defineComponent, nextTick } from "vue";
|
||||||
|
|
||||||
|
import { operatingSystemIsMac } from "@/utility-functions/platform";
|
||||||
import { defaultWidgetLayout, UpdateDocumentLayerTreeStructure, UpdateDocumentLayerDetails, UpdateLayerTreeOptionsLayout, LayerPanelEntry } from "@/wasm-communication/messages";
|
import { defaultWidgetLayout, UpdateDocumentLayerTreeStructure, UpdateDocumentLayerDetails, UpdateLayerTreeOptionsLayout, LayerPanelEntry } from "@/wasm-communication/messages";
|
||||||
|
|
||||||
import LayoutCol from "@/components/layout/LayoutCol.vue";
|
import LayoutCol from "@/components/layout/LayoutCol.vue";
|
||||||
|
@ -342,8 +347,18 @@ export default defineComponent({
|
||||||
await nextTick();
|
await nextTick();
|
||||||
window.getSelection()?.removeAllRanges();
|
window.getSelection()?.removeAllRanges();
|
||||||
},
|
},
|
||||||
async selectLayer(clickedLayer: LayerPanelEntry, ctrl: boolean, shift: boolean) {
|
async selectLayer(ctrl: boolean, cmd: boolean, shift: boolean, listing: LayerListingInfo, event: Event) {
|
||||||
this.editor.instance.select_layer(clickedLayer.path, ctrl, shift);
|
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() {
|
async deselectAllLayers() {
|
||||||
this.editor.instance.deselect_all_layers();
|
this.editor.instance.deselect_all_layers();
|
||||||
|
@ -409,8 +424,9 @@ export default defineComponent({
|
||||||
|
|
||||||
return { insertFolder, insertIndex, highlightFolder, markerHeight };
|
return { insertFolder, insertIndex, highlightFolder, markerHeight };
|
||||||
},
|
},
|
||||||
async dragStart(event: DragEvent, layer: LayerPanelEntry) {
|
async dragStart(event: DragEvent, listing: LayerListingInfo) {
|
||||||
if (!layer.layer_metadata.selected) this.selectLayer(layer, event.ctrlKey, event.shiftKey);
|
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
|
// Set style of cursor for drag
|
||||||
if (event.dataTransfer) {
|
if (event.dataTransfer) {
|
||||||
|
|
|
@ -29,7 +29,8 @@
|
||||||
@focus="() => $emit('textFocused')"
|
@focus="() => $emit('textFocused')"
|
||||||
@blur="() => $emit('textChanged')"
|
@blur="() => $emit('textChanged')"
|
||||||
@change="() => $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')"
|
@keydown.esc="() => $emit('cancelTextChange')"
|
||||||
></textarea>
|
></textarea>
|
||||||
<label v-if="label" :for="`field-input-${id}`">{{ label }}</label>
|
<label v-if="label" :for="`field-input-${id}`">{{ label }}</label>
|
||||||
|
@ -116,6 +117,8 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { defineComponent, PropType } from "vue";
|
import { defineComponent, PropType } from "vue";
|
||||||
|
|
||||||
|
import { operatingSystemIsMac } from "@/utility-functions/platform";
|
||||||
|
|
||||||
import LayoutRow from "@/components/layout/LayoutRow.vue";
|
import LayoutRow from "@/components/layout/LayoutRow.vue";
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
|
@ -130,6 +133,7 @@ export default defineComponent({
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
id: `${Math.random()}`.substring(2),
|
id: `${Math.random()}`.substring(2),
|
||||||
|
macKeyboardLayout: operatingSystemIsMac(),
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
|
|
|
@ -101,7 +101,7 @@ export default defineComponent({
|
||||||
...entry,
|
...entry,
|
||||||
children: entry.children ? menuEntryToFrontendMenuEntry(entry.children) : undefined,
|
children: entry.children ? menuEntryToFrontendMenuEntry(entry.children) : undefined,
|
||||||
action: (): void => this.editor.instance.update_layout(updateMenuBarLayout.layout_target, entry.action.widgetId, 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>
|
<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">
|
<template v-for="(keyGroup, keyGroupIndex) in inputKeys" :key="keyGroupIndex">
|
||||||
<span class="group-gap" v-if="keyGroupIndex > 0"></span>
|
<span class="group-gap" v-if="keyGroupIndex > 0"></span>
|
||||||
<template v-for="(keyInfo, index) in keyTextOrIconList(keyGroup)" :key="index">
|
<template v-for="(keyInfo, index) in keyTextOrIconList(keyGroup)" :key="index">
|
||||||
|
@ -45,15 +46,14 @@
|
||||||
font-family: "Inconsolata", monospace;
|
font-family: "Inconsolata", monospace;
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
text-align: center;
|
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;
|
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
|
// 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;
|
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-16 {
|
||||||
width: 16px;
|
width: 16px;
|
||||||
|
@ -93,6 +93,40 @@
|
||||||
.hint-text {
|
.hint-text {
|
||||||
margin-left: 4px;
|
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>
|
</style>
|
||||||
|
|
||||||
|
@ -100,92 +134,123 @@
|
||||||
import { defineComponent, PropType } from "vue";
|
import { defineComponent, PropType } from "vue";
|
||||||
|
|
||||||
import { IconName } from "@/utility-functions/icons";
|
import { IconName } from "@/utility-functions/icons";
|
||||||
|
import { operatingSystemIsMac } from "@/utility-functions/platform";
|
||||||
import { HintInfo, KeysGroup } from "@/wasm-communication/messages";
|
import { HintInfo, KeysGroup } from "@/wasm-communication/messages";
|
||||||
|
|
||||||
import LayoutRow from "@/components/layout/LayoutRow.vue";
|
import LayoutRow from "@/components/layout/LayoutRow.vue";
|
||||||
import IconLabel from "@/components/widgets/labels/IconLabel.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({
|
export default defineComponent({
|
||||||
components: {
|
inject: ["fullscreen"],
|
||||||
IconLabel,
|
|
||||||
LayoutRow,
|
|
||||||
},
|
|
||||||
props: {
|
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 },
|
inputMouse: { type: String as PropType<HintInfo["mouse"]>, default: null },
|
||||||
|
requiresLock: { type: Boolean as PropType<boolean>, default: false },
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
hasSlotContent(): boolean {
|
hasSlotContent(): boolean {
|
||||||
return Boolean(this.$slots.default);
|
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: {
|
methods: {
|
||||||
keyTextOrIconList(keyGroup: KeysGroup): { text: string | null; icon: IconName | null; width: string }[] {
|
keyTextOrIconList(keyGroup: KeysGroup): { text: string | null; icon: IconName | null; width: string }[] {
|
||||||
return keyGroup.map((inputKey) => this.keyTextOrIcon(inputKey));
|
return keyGroup.map((inputKey) => this.keyTextOrIcon(inputKey));
|
||||||
},
|
},
|
||||||
keyTextOrIcon(keyText: string): { text: string | null; icon: IconName | null; width: string } {
|
keyTextOrIcon(input: string): { text: string | null; icon: IconName | null; width: string } {
|
||||||
// Definitions
|
let keyText = input;
|
||||||
const textMap: Record<string, string> = {
|
if (operatingSystemIsMac()) {
|
||||||
Control: "Ctrl",
|
keyText = keyText.replace("Alt", "Option");
|
||||||
Alt: "Alt",
|
}
|
||||||
Delete: "Del",
|
|
||||||
PageUp: "PgUp",
|
const iconsAndWidths = operatingSystemIsMac() ? { ...iconsAndWidthsStandard, ...iconsAndWidthsMac } : iconsAndWidthsStandard;
|
||||||
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,
|
|
||||||
};
|
|
||||||
|
|
||||||
// Strip off the "Key" prefix
|
// Strip off the "Key" prefix
|
||||||
const text = keyText.replace(/^(?:Key)?(.*)$/, "$1");
|
const text = keyText.replace(/^(?:Key)?(.*)$/, "$1");
|
||||||
|
|
||||||
// If it's an icon, return the icon identifier
|
// 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 {
|
return {
|
||||||
text: null,
|
text: null,
|
||||||
icon: this.keyboardHintIcon(text),
|
icon: this.keyboardHintIcon(text),
|
||||||
width: `width-${iconsAndWidths[text] * 8 + 8}`,
|
width: `width-${width}`,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// Otherwise, return the text string
|
// Otherwise, return the text string
|
||||||
let result;
|
let result;
|
||||||
|
|
||||||
// Letters and numbers
|
// Letters and numbers
|
||||||
if (/^[A-Z0-9]$/.test(text)) result = text;
|
if (/^[A-Z0-9]$/.test(text)) {
|
||||||
|
result = text;
|
||||||
|
}
|
||||||
// Abbreviated names
|
// 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
|
// Other
|
||||||
else result = text;
|
else {
|
||||||
|
result = text;
|
||||||
|
}
|
||||||
|
|
||||||
return { text: result, icon: null, width: `width-${(result || " ").length * 8 + 8}` };
|
return { text: result, icon: null, width: `width-${(result || " ").length * 8 + 8}` };
|
||||||
},
|
},
|
||||||
mouseHintIcon(input: HintInfo["mouse"]): IconName {
|
mouseHintIcon(input: HintInfo["mouse"]): IconName {
|
||||||
return `MouseHint${input}` as 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;
|
return `Keyboard${input}` as IconName;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
components: {
|
||||||
|
IconLabel,
|
||||||
|
LayoutRow,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
<Separator :type="'Section'" v-if="index !== 0" />
|
<Separator :type="'Section'" v-if="index !== 0" />
|
||||||
<template v-for="hint in hintGroup" :key="hint">
|
<template v-for="hint in hintGroup" :key="hint">
|
||||||
<LayoutRow v-if="hint.plus" class="plus">+</LayoutRow>
|
<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>
|
||||||
</template>
|
</template>
|
||||||
</LayoutRow>
|
</LayoutRow>
|
||||||
|
@ -44,7 +44,8 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { defineComponent } from "vue";
|
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 LayoutRow from "@/components/layout/LayoutRow.vue";
|
||||||
import Separator from "@/components/widgets/labels/Separator.vue";
|
import Separator from "@/components/widgets/labels/Separator.vue";
|
||||||
|
@ -57,6 +58,12 @@ export default defineComponent({
|
||||||
hintData: [] as HintData,
|
hintData: [] as HintData,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
methods: {
|
||||||
|
inputKeysForPlatform(hint: HintInfo): KeysGroup[] {
|
||||||
|
if (operatingSystemIsMac() && hint.keyGroupsMac) return hint.keyGroupsMac;
|
||||||
|
return hint.keyGroups;
|
||||||
|
},
|
||||||
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
this.editor.subscriptions.subscribeJsMessage(UpdateInputHints, (updateInputHints) => {
|
this.editor.subscriptions.subscribeJsMessage(UpdateInputHints, (updateInputHints) => {
|
||||||
this.hintData = updateInputHints.hint_data;
|
this.hintData = updateInputHints.hint_data;
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<template>
|
<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>
|
<TextLabel v-if="requestFullscreenHotkeys" :italic="true">Go fullscreen to access all hotkeys</TextLabel>
|
||||||
<IconLabel :icon="fullscreen.state.windowFullscreen ? 'FullscreenExit' : 'FullscreenEnter'" />
|
<IconLabel :icon="fullscreen.state.windowFullscreen ? 'FullscreenExit' : 'FullscreenEnter'" />
|
||||||
</LayoutRow>
|
</LayoutRow>
|
||||||
|
|
|
@ -45,8 +45,8 @@
|
||||||
<Separator :type="'Unrelated'" />
|
<Separator :type="'Unrelated'" />
|
||||||
</LayoutCol>
|
</LayoutCol>
|
||||||
<LayoutCol>
|
<LayoutCol>
|
||||||
<UserInputLabel :inputKeys="[['KeyControl', 'KeyN']]" />
|
<UserInputLabel :inputKeys="[[controlOrCommandKey(), 'KeyN']]" />
|
||||||
<UserInputLabel :inputKeys="[['KeyControl', 'KeyO']]" />
|
<UserInputLabel :inputKeys="[[controlOrCommandKey(), 'KeyO']]" />
|
||||||
</LayoutCol>
|
</LayoutCol>
|
||||||
</LayoutRow>
|
</LayoutRow>
|
||||||
</LayoutCol>
|
</LayoutCol>
|
||||||
|
@ -216,6 +216,8 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { defineComponent, PropType } from "vue";
|
import { defineComponent, PropType } from "vue";
|
||||||
|
|
||||||
|
import { operatingSystemIsMac } from "@/utility-functions/platform";
|
||||||
|
|
||||||
import LayoutCol from "@/components/layout/LayoutCol.vue";
|
import LayoutCol from "@/components/layout/LayoutCol.vue";
|
||||||
import LayoutRow from "@/components/layout/LayoutRow.vue";
|
import LayoutRow from "@/components/layout/LayoutRow.vue";
|
||||||
import Document from "@/components/panels/Document.vue";
|
import Document from "@/components/panels/Document.vue";
|
||||||
|
@ -257,6 +259,10 @@ export default defineComponent({
|
||||||
openDocument() {
|
openDocument() {
|
||||||
this.editor.instance.document_open();
|
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: {
|
components: {
|
||||||
LayoutCol,
|
LayoutCol,
|
||||||
|
|
|
@ -2,6 +2,7 @@ import { DialogState } from "@/state-providers/dialog";
|
||||||
import { FullscreenState } from "@/state-providers/fullscreen";
|
import { FullscreenState } from "@/state-providers/fullscreen";
|
||||||
import { PortfolioState } from "@/state-providers/portfolio";
|
import { PortfolioState } from "@/state-providers/portfolio";
|
||||||
import { makeKeyboardModifiersBitfield, textInputCleanup, getLatinKey } from "@/utility-functions/keyboard-entry";
|
import { makeKeyboardModifiersBitfield, textInputCleanup, getLatinKey } from "@/utility-functions/keyboard-entry";
|
||||||
|
import { operatingSystemIsMac } from "@/utility-functions/platform";
|
||||||
import { stripIndents } from "@/utility-functions/strip-indents";
|
import { stripIndents } from "@/utility-functions/strip-indents";
|
||||||
import { Editor } from "@/wasm-communication/editor";
|
import { Editor } from "@/wasm-communication/editor";
|
||||||
import { TriggerPaste } from "@/wasm-communication/messages";
|
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: "pointerup", action: (e: PointerEvent): void => onPointerUp(e) },
|
||||||
{ target: window, eventName: "dblclick", action: (e: PointerEvent): void => onDoubleClick(e) },
|
{ target: window, eventName: "dblclick", action: (e: PointerEvent): void => onDoubleClick(e) },
|
||||||
{ target: window, eventName: "mousedown", action: (e: MouseEvent): void => onMouseDown(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, eventName: "modifyinputfield", action: (e: CustomEvent): void => onModifyInputField(e) },
|
||||||
{ target: window.document.body, eventName: "paste", action: (e: ClipboardEvent): void => onPaste(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);
|
const key = getLatinKey(e);
|
||||||
if (!key) return false;
|
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
|
// Don't redirect user input from text entry into HTML elements
|
||||||
if (key !== "escape" && !(e.ctrlKey && key === "enter") && targetIsTextField(e.target)) {
|
if (key !== "escape" && !(ctrlOrCmd && key === "enter") && targetIsTextField(e.target)) return false;
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Don't redirect paste
|
// Don't redirect paste
|
||||||
if (key === "v" && e.ctrlKey) return false;
|
if (key === "v" && ctrlOrCmd) return false;
|
||||||
|
|
||||||
// Don't redirect a fullscreen request
|
// Don't redirect a fullscreen request
|
||||||
if (key === "f11" && e.type === "keydown" && !e.repeat) {
|
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
|
// Don't redirect a reload request
|
||||||
if (key === "f5") return false;
|
if (key === "f5" || (ctrlOrCmd && key === "r")) return false;
|
||||||
|
|
||||||
// Don't redirect debugging tools
|
// Don't redirect debugging tools
|
||||||
if (key === "f12" || key === "f8") return false;
|
if (key === "f12" || key === "f8") return false;
|
||||||
if ((e.ctrlKey || e.metaKey) && e.shiftKey && key === "c") return false;
|
if (ctrlOrCmd && e.shiftKey && key === "c") return false;
|
||||||
if ((e.ctrlKey || e.metaKey) && e.shiftKey && key === "i") return false;
|
if (ctrlOrCmd && e.shiftKey && key === "i") return false;
|
||||||
if ((e.ctrlKey || e.metaKey) && e.shiftKey && key === "j") return false;
|
if (ctrlOrCmd && e.shiftKey && key === "j") return false;
|
||||||
|
|
||||||
// Don't redirect tab or enter if not in canvas (to allow navigating elements)
|
// 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;
|
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();
|
if (e.button === 1) e.preventDefault();
|
||||||
}
|
}
|
||||||
|
|
||||||
function onMouseScroll(e: WheelEvent): void {
|
function onWheelScroll(e: WheelEvent): void {
|
||||||
const { target } = e;
|
const { target } = e;
|
||||||
const isTargetingCanvas = target instanceof Element && target.closest("[data-canvas]");
|
const isTargetingCanvas = target instanceof Element && target.closest("[data-canvas]");
|
||||||
|
|
||||||
|
@ -215,7 +217,7 @@ export function createInputManager(editor: Editor, container: HTMLElement, dialo
|
||||||
if (isTargetingCanvas) {
|
if (isTargetingCanvas) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
const modifiers = makeKeyboardModifiersBitfield(e);
|
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 { TextButtonWidget } from "@/components/widgets/buttons/TextButton";
|
||||||
import { DialogState } from "@/state-providers/dialog";
|
import { DialogState } from "@/state-providers/dialog";
|
||||||
import { IconName } from "@/utility-functions/icons";
|
import { IconName } from "@/utility-functions/icons";
|
||||||
|
import { browserVersion, operatingSystem } from "@/utility-functions/platform";
|
||||||
import { stripIndents } from "@/utility-functions/strip-indents";
|
import { stripIndents } from "@/utility-functions/strip-indents";
|
||||||
import { Editor } from "@/wasm-communication/editor";
|
import { Editor } from "@/wasm-communication/editor";
|
||||||
import { DisplayDialogPanic, Widget, WidgetLayout } from "@/wasm-communication/messages";
|
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.
|
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**
|
**Browser and OS**
|
||||||
${browserVersion()}, ${operatingSystem()}
|
${browserVersion()}, ${operatingSystem(true).replace("Unknown", "YOUR OPERATING SYSTEM")}
|
||||||
|
|
||||||
**Stack Trace**
|
**Stack Trace**
|
||||||
Copied from the crash dialog in the Graphite Editor:
|
Copied from the crash dialog in the Graphite Editor:
|
||||||
|
@ -94,47 +95,3 @@ function githubUrl(panicDetails: string): string {
|
||||||
|
|
||||||
return url.toString();
|
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 body = document.body;
|
||||||
const message = stripIndents`
|
const message = stripIndents`
|
||||||
<style>
|
<style>
|
||||||
h2, p, a {
|
h2, p, a { text-align: center; color: white; }
|
||||||
text-align: center;
|
#app { display: none; }
|
||||||
color: white;
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
<h2>This browser is too old</h2>
|
<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>
|
<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 KeyboardArrowUp from "@/../assets/icon-12px-solid/keyboard-arrow-up.svg";
|
||||||
import KeyboardBackspace from "@/../assets/icon-12px-solid/keyboard-backspace.svg";
|
import KeyboardBackspace from "@/../assets/icon-12px-solid/keyboard-backspace.svg";
|
||||||
import KeyboardCommand from "@/../assets/icon-12px-solid/keyboard-command.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 KeyboardEnter from "@/../assets/icon-12px-solid/keyboard-enter.svg";
|
||||||
import KeyboardOption from "@/../assets/icon-12px-solid/keyboard-option.svg";
|
import KeyboardOption from "@/../assets/icon-12px-solid/keyboard-option.svg";
|
||||||
import KeyboardShift from "@/../assets/icon-12px-solid/keyboard-shift.svg";
|
import KeyboardShift from "@/../assets/icon-12px-solid/keyboard-shift.svg";
|
||||||
|
@ -52,6 +53,7 @@ const SOLID_12PX = {
|
||||||
KeyboardArrowUp: { component: KeyboardArrowUp, size: 12 },
|
KeyboardArrowUp: { component: KeyboardArrowUp, size: 12 },
|
||||||
KeyboardBackspace: { component: KeyboardBackspace, size: 12 },
|
KeyboardBackspace: { component: KeyboardBackspace, size: 12 },
|
||||||
KeyboardCommand: { component: KeyboardCommand, size: 12 },
|
KeyboardCommand: { component: KeyboardCommand, size: 12 },
|
||||||
|
KeyboardControl: { component: KeyboardControl, size: 12 },
|
||||||
KeyboardEnter: { component: KeyboardEnter, size: 12 },
|
KeyboardEnter: { component: KeyboardEnter, size: 12 },
|
||||||
KeyboardOption: { component: KeyboardOption, size: 12 },
|
KeyboardOption: { component: KeyboardOption, size: 12 },
|
||||||
KeyboardShift: { component: KeyboardShift, size: 12 },
|
KeyboardShift: { component: KeyboardShift, size: 12 },
|
||||||
|
|
|
@ -1,5 +1,14 @@
|
||||||
export function makeKeyboardModifiersBitfield(e: WheelEvent | PointerEvent | KeyboardEvent): number {
|
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.
|
// 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 key = e.key.toLowerCase();
|
||||||
const isPrintable = !ALL_PRINTABLE_KEYS.has(e.key);
|
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;
|
if (!isPrintable) return key;
|
||||||
|
|
||||||
// These non-Latin characters should fall back to the Latin equivalent at the key location
|
// 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 type HintGroup = HintInfo[];
|
||||||
|
|
||||||
export class HintInfo {
|
export class HintInfo {
|
||||||
readonly key_groups!: KeysGroup[];
|
readonly keyGroups!: KeysGroup[];
|
||||||
|
|
||||||
|
readonly keyGroupsMac!: KeysGroup[] | null;
|
||||||
|
|
||||||
readonly mouse!: MouseMotion | null;
|
readonly mouse!: MouseMotion | null;
|
||||||
|
|
||||||
|
@ -404,12 +406,14 @@ export class ColorInput extends WidgetProps {
|
||||||
tooltip!: string;
|
tooltip!: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type Keys = { keys: string[] };
|
||||||
|
|
||||||
export interface MenuListEntryData<Value = string> {
|
export interface MenuListEntryData<Value = string> {
|
||||||
value?: Value;
|
value?: Value;
|
||||||
label?: string;
|
label?: string;
|
||||||
icon?: IconName;
|
icon?: IconName;
|
||||||
font?: URL;
|
font?: URL;
|
||||||
shortcut?: string[];
|
shortcut?: Keys;
|
||||||
shortcutRequiresLock?: boolean;
|
shortcutRequiresLock?: boolean;
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
action?: () => void;
|
action?: () => void;
|
||||||
|
@ -781,7 +785,7 @@ export type MenuColumn = {
|
||||||
};
|
};
|
||||||
|
|
||||||
export type MenuEntry = {
|
export type MenuEntry = {
|
||||||
shortcut: string[] | undefined;
|
shortcut: Keys | undefined;
|
||||||
action: Widget;
|
action: Widget;
|
||||||
label: string;
|
label: string;
|
||||||
icon: string | undefined;
|
icon: string | undefined;
|
||||||
|
|
|
@ -79,7 +79,7 @@ function formatThirdPartyLicenses(jsLicenses) {
|
||||||
if (process.env.NODE_ENV === "production" && process.env.SKIP_CARGO_ABOUT === undefined) {
|
if (process.env.NODE_ENV === "production" && process.env.SKIP_CARGO_ABOUT === undefined) {
|
||||||
try {
|
try {
|
||||||
rustLicenses = generateRustLicenses();
|
rustLicenses = generateRustLicenses();
|
||||||
} catch (e) {
|
} catch (err) {
|
||||||
// Nothing to show. Error messages were printed above.
|
// Nothing to show. Error messages were printed above.
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
name = "graphite-wasm"
|
name = "graphite-wasm"
|
||||||
publish = false
|
publish = false
|
||||||
version = "0.0.0"
|
version = "0.0.0"
|
||||||
rust-version = "1.56.0"
|
rust-version = "1.62.0"
|
||||||
authors = ["Graphite Authors <contact@graphite.rs>"]
|
authors = ["Graphite Authors <contact@graphite.rs>"]
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
readme = "../../README.md"
|
readme = "../../README.md"
|
||||||
|
|
|
@ -6,6 +6,7 @@ use crate::helpers::{translate_key, Error};
|
||||||
use crate::{EDITOR_HAS_CRASHED, EDITOR_INSTANCES, JS_EDITOR_HANDLES};
|
use crate::{EDITOR_HAS_CRASHED, EDITOR_INSTANCES, JS_EDITOR_HANDLES};
|
||||||
|
|
||||||
use editor::consts::{FILE_SAVE_SUFFIX, GRAPHITE_DOCUMENT_VERSION};
|
use editor::consts::{FILE_SAVE_SUFFIX, GRAPHITE_DOCUMENT_VERSION};
|
||||||
|
use editor::document::utility_types::Platform;
|
||||||
use editor::input::input_preprocessor::ModifierKeys;
|
use editor::input::input_preprocessor::ModifierKeys;
|
||||||
use editor::input::mouse::{EditorMouseState, ScrollDelta, ViewportBounds};
|
use editor::input::mouse::{EditorMouseState, ScrollDelta, ViewportBounds};
|
||||||
use editor::message_prelude::*;
|
use editor::message_prelude::*;
|
||||||
|
@ -105,7 +106,15 @@ impl JsEditorHandle {
|
||||||
// the backend from the web frontend.
|
// 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);
|
self.dispatch(Message::Init);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -217,13 +226,13 @@ impl JsEditorHandle {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Mouse scrolling within the screenspace bounds of the viewport
|
/// 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());
|
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);
|
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 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);
|
self.dispatch(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -280,7 +289,7 @@ impl JsEditorHandle {
|
||||||
|
|
||||||
/// A text box was committed
|
/// A text box was committed
|
||||||
pub fn on_change_text(&self, new_text: String) -> Result<(), JsValue> {
|
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);
|
self.dispatch(message);
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -302,7 +311,7 @@ impl JsEditorHandle {
|
||||||
|
|
||||||
/// A text box was changed
|
/// A text box was changed
|
||||||
pub fn update_bounds(&self, new_text: String) -> Result<(), JsValue> {
|
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);
|
self.dispatch(message);
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
|
@ -122,6 +122,7 @@ pub fn translate_key(name: &str) -> Key {
|
||||||
"capslock" => KeyShift,
|
"capslock" => KeyShift,
|
||||||
" " => KeySpace,
|
" " => KeySpace,
|
||||||
"control" => KeyControl,
|
"control" => KeyControl,
|
||||||
|
"command" => KeyCommand,
|
||||||
"delete" => KeyDelete,
|
"delete" => KeyDelete,
|
||||||
"backspace" => KeyBackspace,
|
"backspace" => KeyBackspace,
|
||||||
"alt" => KeyAlt,
|
"alt" => KeyAlt,
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
name = "graphite-graphene"
|
name = "graphite-graphene"
|
||||||
publish = false
|
publish = false
|
||||||
version = "0.0.0"
|
version = "0.0.0"
|
||||||
rust-version = "1.56.0"
|
rust-version = "1.62.0"
|
||||||
authors = ["Graphite Authors <contact@graphite.rs>"]
|
authors = ["Graphite Authors <contact@graphite.rs>"]
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
readme = "../README.md"
|
readme = "../README.md"
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
name = "graphite-proc-macros"
|
name = "graphite-proc-macros"
|
||||||
publish = false
|
publish = false
|
||||||
version = "0.0.0"
|
version = "0.0.0"
|
||||||
rust-version = "1.56.0"
|
rust-version = "1.62.0"
|
||||||
authors = ["Graphite Authors <contact@graphite.rs>"]
|
authors = ["Graphite Authors <contact@graphite.rs>"]
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
readme = "../README.md"
|
readme = "../README.md"
|
||||||
|
@ -14,6 +14,10 @@ license = "Apache-2.0"
|
||||||
path = "src/lib.rs"
|
path = "src/lib.rs"
|
||||||
proc-macro = true
|
proc-macro = true
|
||||||
|
|
||||||
|
[features]
|
||||||
|
default = ["serde-discriminant"]
|
||||||
|
serde-discriminant = []
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
proc-macro2 = "1.0.26"
|
proc-macro2 = "1.0.26"
|
||||||
syn = { version = "1.0.68", features = ["full"] }
|
syn = { version = "1.0.68", features = ["full"] }
|
||||||
|
@ -22,3 +26,6 @@ quote = "1.0.9"
|
||||||
[dev-dependencies.editor]
|
[dev-dependencies.editor]
|
||||||
path = "../editor"
|
path = "../editor"
|
||||||
package = "graphite-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<_>>();
|
.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! {
|
let res = quote::quote! {
|
||||||
|
#serde
|
||||||
#discriminant
|
#discriminant
|
||||||
|
|
||||||
impl ToDiscriminant for #input_type {
|
impl ToDiscriminant for #input_type {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue