Restructure the entire editor codebase to consistently match the message hierarchy

Closes #744
This commit is contained in:
Keavon Chambers
2022-08-05 05:38:11 -07:00
parent 9e1f4eb46d
commit ddfd7db0a0
154 changed files with 4199 additions and 4064 deletions

View File

@@ -5,9 +5,9 @@ use crate::consts::*;
impl Subpath {
/// Create a new `Subpath` using a list of [ManipulatorGroup]s.
/// A `Subpath` with less than 2 [ManipulatorGroup]s may not be closed.
pub fn new(manipulator_groups: Vec<ManipulatorGroup>, closed: bool) -> Subpath {
pub fn new(manipulator_groups: Vec<ManipulatorGroup>, closed: bool) -> Self {
assert!(!closed || manipulator_groups.len() > 1, "A closed Subpath must contain more than 1 ManipulatorGroup.");
Subpath { manipulator_groups, closed }
Self { manipulator_groups, closed }
}
/// Create a `Subpath` consisting of 2 manipulator groups from a `Bezier`.

83
editor/src/application.rs Normal file
View File

@@ -0,0 +1,83 @@
use crate::dispatcher::Dispatcher;
use crate::messages::prelude::*;
use rand_chacha::rand_core::{RngCore, SeedableRng};
use rand_chacha::ChaCha20Rng;
use spin::Mutex;
use std::cell::Cell;
static RNG: Mutex<Option<ChaCha20Rng>> = Mutex::new(None);
thread_local! {
pub static UUID_SEED: Cell<Option<u64>> = Cell::new(None);
}
// TODO: serialize with serde to save the current editor state
pub struct Editor {
pub dispatcher: Dispatcher,
}
impl Editor {
/// Construct a new editor instance.
/// Remember to provide a random seed with `editor::set_uuid_seed(seed)` before any editors can be used.
pub fn new() -> Self {
Self { dispatcher: Dispatcher::new() }
}
pub fn handle_message<T: Into<Message>>(&mut self, message: T) -> Vec<FrontendMessage> {
self.dispatcher.handle_message(message);
let mut responses = Vec::new();
std::mem::swap(&mut responses, &mut self.dispatcher.responses);
responses
}
}
impl Default for Editor {
fn default() -> Self {
Self::new()
}
}
pub fn set_uuid_seed(random_seed: u64) {
UUID_SEED.with(|seed| seed.set(Some(random_seed)))
}
pub fn generate_uuid() -> u64 {
let mut lock = RNG.lock();
if lock.is_none() {
UUID_SEED.with(|seed| {
let random_seed = seed.get().expect("Random seed not set before editor was initialized");
*lock = Some(ChaCha20Rng::seed_from_u64(random_seed));
})
}
lock.as_mut().map(ChaCha20Rng::next_u64).unwrap()
}
pub fn release_series() -> String {
format!("Release Series: {}", env!("GRAPHITE_RELEASE_SERIES"))
}
pub fn commit_info() -> String {
format!("{}\n{}\n{}", commit_timestamp(), commit_hash(), commit_branch())
}
pub fn commit_info_localized(localized_commit_date: &str) -> String {
format!("{}\n{}\n{}", commit_timestamp_localized(localized_commit_date), commit_hash(), commit_branch())
}
pub fn commit_timestamp() -> String {
format!("Date: {}", env!("GRAPHITE_GIT_COMMIT_DATE"))
}
pub fn commit_timestamp_localized(localized_commit_date: &str) -> String {
format!("Date: {}", localized_commit_date)
}
pub fn commit_hash() -> String {
format!("Hash: {}", &env!("GRAPHITE_GIT_COMMIT_HASH")[..8])
}
pub fn commit_branch() -> String {
format!("Branch: {}", env!("GRAPHITE_GIT_COMMIT_BRANCH"))
}

View File

@@ -1,26 +0,0 @@
use crate::message_prelude::*;
use serde::{Deserialize, Serialize};
#[impl_message(Message, Broadcast)]
#[derive(PartialEq, Clone, Debug, Serialize, Deserialize)]
pub enum BroadcastMessage {
SubscribeSignal {
on: BroadcastSignal,
send: Box<Message>,
},
UnsubscribeSignal {
on: BroadcastSignal,
message: Box<Message>,
},
#[child]
TriggerSignal(BroadcastSignal),
}
#[derive(PartialEq, Eq, Clone, Debug, Serialize, Deserialize, Hash)]
#[impl_message(Message, BroadcastMessage, TriggerSignal)]
pub enum BroadcastSignal {
DocumentIsDirty,
ToolAbort,
SelectionChanged,
}

View File

@@ -1,27 +0,0 @@
use crate::message_prelude::*;
use std::collections::HashMap;
#[derive(Debug, Clone, Default)]
pub struct BroadcastMessageHandler {
listeners: HashMap<BroadcastSignal, Vec<Message>>,
}
impl MessageHandler<BroadcastMessage, ()> for BroadcastMessageHandler {
fn process_action(&mut self, action: BroadcastMessage, _data: (), responses: &mut VecDeque<Message>) {
use BroadcastMessage::*;
match action {
SubscribeSignal { on, send } => self.listeners.entry(on).or_default().push(*send),
UnsubscribeSignal { on, message } => self.listeners.entry(on).or_default().retain(|msg| *msg != *message),
TriggerSignal(signal) => {
for message in self.listeners.entry(signal).or_default() {
responses.push_front(message.clone())
}
}
}
}
fn actions(&self) -> ActionList {
vec![]
}
}

View File

@@ -1,22 +0,0 @@
pub use crate::communication::dispatcher::*;
pub use crate::input::InputPreprocessorMessageHandler;
use crate::message_prelude::*;
use std::collections::VecDeque;
pub type ActionList = Vec<Vec<MessageDiscriminant>>;
// TODO: Add Send + Sync requirement
// Use something like rw locks for synchronization
pub trait MessageHandlerData {}
pub trait MessageHandler<A: ToDiscriminant, T>
where
A::Discriminant: AsMessage,
<A::Discriminant as TransitiveChild>::TopParent: TransitiveChild<Parent = <A::Discriminant as TransitiveChild>::TopParent, TopParent = <A::Discriminant as TransitiveChild>::TopParent> + AsMessage,
{
/// Return true if the Action is consumed.
fn process_action(&mut self, action: A, data: T, responses: &mut VecDeque<Message>);
fn actions(&self) -> ActionList;
}

View File

@@ -1,34 +0,0 @@
pub mod broadcast_message;
pub mod broadcast_message_handler;
pub mod dispatcher;
pub mod message;
pub mod message_handler;
pub use crate::communication::dispatcher::*;
pub use crate::input::InputPreprocessorMessageHandler;
use rand_chacha::rand_core::{RngCore, SeedableRng};
use rand_chacha::ChaCha20Rng;
use spin::Mutex;
use std::cell::Cell;
static RNG: Mutex<Option<ChaCha20Rng>> = Mutex::new(None);
thread_local! {
pub static UUID_SEED: Cell<Option<u64>> = Cell::new(None);
}
pub fn set_uuid_seed(random_seed: u64) {
UUID_SEED.with(|seed| seed.set(Some(random_seed)))
}
pub fn generate_uuid() -> u64 {
let mut lock = RNG.lock();
if lock.is_none() {
UUID_SEED.with(|seed| {
let random_seed = seed.get().expect("random seed not set before editor was initialized");
*lock = Some(ChaCha20Rng::seed_from_u64(random_seed));
})
}
lock.as_mut().map(ChaCha20Rng::next_u64).unwrap()
}

View File

@@ -11,6 +11,8 @@ pub const VIEWPORT_ZOOM_LEVELS: [f64; 74] = [
128., 160., 200., 256., 320., 400., 512., 640., 800., 1024., 1280., 1600., 2048., 2560.,
];
pub const VIEWPORT_GRID_ROUNDING_BIAS: f64 = 0.002; // Helps push values that end in approximately half, plus or minus some floating point imprecision, towards the same side of the round() function
pub const VIEWPORT_SCROLL_RATE: f64 = 0.6;
pub const VIEWPORT_ROTATE_SNAP_INTERVAL: f64 = 15.;
@@ -72,5 +74,5 @@ pub const DEFAULT_FONT_FAMILY: &str = "Merriweather";
pub const DEFAULT_FONT_STYLE: &str = "Normal (400)";
// Document
pub const GRAPHITE_DOCUMENT_VERSION: &str = "0.0.10"; // Remember to save a simple document and replace the test file at: editor\src\communication\graphite-test-document.graphite
pub const GRAPHITE_DOCUMENT_VERSION: &str = "0.0.10"; // Remember to save a simple document and replace the test file `graphite-test-document.graphite`
pub const VIEWPORT_ZOOM_TO_FIT_PADDING_SCALE_FACTOR: f32 = 1.05;

View File

@@ -1,15 +0,0 @@
mod about_dialog;
mod close_all_documents_dialog;
mod close_document_dialog;
mod coming_soon_dialog;
mod error_dialog;
mod export_dialog;
mod new_document_dialog;
pub use about_dialog::AboutGraphite;
pub use close_all_documents_dialog::CloseAllDocuments;
pub use close_document_dialog::CloseDocument;
pub use coming_soon_dialog::ComingSoon;
pub use error_dialog::Error;
pub use export_dialog::{Export, ExportDialogUpdate, ExportDialogUpdateDiscriminant};
pub use new_document_dialog::{NewDocument, NewDocumentDialogUpdate, NewDocumentDialogUpdateDiscriminant};

View File

@@ -1,17 +0,0 @@
//! Handles dialogs/modals/popups that appear as boxes in the centre of the editor.
//!
//! Dialogs are represented as structs that implement the [`crate::layout::widgets::PropertyHolder`] trait.
//!
//! To open a dialog, call the function `register_properties` on the dialog struct with `responses` and the `LayoutTarget::DialogDetails`
//! and then you can open the dialog with [`crate::message_prelude::FrontendMessage::DisplayDialog`]
mod dialog_message;
mod dialog_message_handler;
mod dialogs;
pub mod messages {
pub use super::dialog_message::{DialogMessage, DialogMessageDiscriminant};
pub use super::dialog_message_handler::DialogMessageHandler;
}
pub use dialogs::*;

View File

@@ -1,13 +1,6 @@
use super::broadcast_message_handler::BroadcastMessageHandler;
use crate::consts::{DEFAULT_FONT_FAMILY, DEFAULT_FONT_STYLE};
use crate::debug::debug_message::LoggingMessages;
use crate::debug::DebugMessageHandler;
use crate::document::PortfolioMessageHandler;
use crate::input::{InputMapperMessageHandler, InputPreprocessorMessageHandler};
use crate::layout::layout_message_handler::LayoutMessageHandler;
use crate::message_prelude::*;
use crate::viewport_tools::tool_message_handler::ToolMessageHandler;
use crate::workspace::WorkspaceMessageHandler;
use crate::messages::debug::utility_types::MessageLoggingVerbosity;
use crate::messages::prelude::*;
use graphene::layers::text_layer::Font;
@@ -47,7 +40,7 @@ const SIDE_EFFECT_FREE_MESSAGES: &[MessageDiscriminant] = &[
MessageDiscriminant::Portfolio(PortfolioMessageDiscriminant::Document(DocumentMessageDiscriminant::DocumentStructureChanged)),
MessageDiscriminant::Frontend(FrontendMessageDiscriminant::UpdateDocumentLayerTreeStructure),
MessageDiscriminant::Frontend(FrontendMessageDiscriminant::TriggerFontLoad),
MessageDiscriminant::Broadcast(BroadcastMessageDiscriminant::TriggerSignal(BroadcastSignalDiscriminant::DocumentIsDirty)),
MessageDiscriminant::Broadcast(BroadcastMessageDiscriminant::TriggerEvent(BroadcastEventDiscriminant::DocumentIsDirty)),
];
impl Dispatcher {
@@ -80,11 +73,11 @@ impl Dispatcher {
if SIDE_EFFECT_FREE_MESSAGES.contains(&message.to_discriminant()) {
let already_in_queue = self.message_queues.first().filter(|queue| queue.contains(&message)).is_some();
if already_in_queue {
self.log_deferred_message(&message, &self.message_queues, self.message_handlers.debug_message_handler.logging_messages_mode);
self.log_deferred_message(&message, &self.message_queues, self.message_handlers.debug_message_handler.message_logging_verbosity);
self.cleanup_queues(false);
continue;
} else if self.message_queues.len() > 1 {
self.log_deferred_message(&message, &self.message_queues, self.message_handlers.debug_message_handler.logging_messages_mode);
self.log_deferred_message(&message, &self.message_queues, self.message_handlers.debug_message_handler.message_logging_verbosity);
self.cleanup_queues(true);
self.message_queues[0].push_back(message);
continue;
@@ -92,7 +85,7 @@ impl Dispatcher {
}
// Print the message at a verbosity level of `log`
self.log_message(&message, &self.message_queues, self.message_handlers.debug_message_handler.logging_messages_mode);
self.log_message(&message, &self.message_queues, self.message_handlers.debug_message_handler.message_logging_verbosity);
// Create a new queue for the child messages
let mut queue = VecDeque::new();
@@ -114,14 +107,14 @@ impl Dispatcher {
queue.push_back(message);
}
Broadcast(message) => self.message_handlers.broadcast_message_handler.process_action(message, (), &mut queue),
Broadcast(message) => self.message_handlers.broadcast_message_handler.process_message(message, (), &mut queue),
Debug(message) => {
self.message_handlers.debug_message_handler.process_action(message, (), &mut queue);
self.message_handlers.debug_message_handler.process_message(message, (), &mut queue);
}
Dialog(message) => {
self.message_handlers
.dialog_message_handler
.process_action(message, &self.message_handlers.portfolio_message_handler, &mut queue);
.process_message(message, &self.message_handlers.portfolio_message_handler, &mut queue);
}
Frontend(message) => {
// Handle these messages immediately by returning early
@@ -142,12 +135,12 @@ impl Dispatcher {
self.message_handlers
.input_mapper_message_handler
.process_action(message, (&self.message_handlers.input_preprocessor_message_handler, keyboard_platform, actions), &mut queue);
.process_message(message, (&self.message_handlers.input_preprocessor_message_handler, keyboard_platform, actions), &mut queue);
}
InputPreprocessor(message) => {
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);
self.message_handlers.input_preprocessor_message_handler.process_message(message, keyboard_platform, &mut queue);
}
Layout(message) => {
let keyboard_platform = self.message_handlers.portfolio_message_handler.platform.as_keyboard_platform_layout();
@@ -155,16 +148,16 @@ impl Dispatcher {
self.message_handlers
.layout_message_handler
.process_action(message, (action_input_mapping, keyboard_platform), &mut queue);
.process_message(message, (action_input_mapping, keyboard_platform), &mut queue);
}
Portfolio(message) => {
self.message_handlers
.portfolio_message_handler
.process_action(message, &self.message_handlers.input_preprocessor_message_handler, &mut queue);
.process_message(message, &self.message_handlers.input_preprocessor_message_handler, &mut queue);
}
Tool(message) => {
if let Some(document) = self.message_handlers.portfolio_message_handler.active_document() {
self.message_handlers.tool_message_handler.process_action(
self.message_handlers.tool_message_handler.process_message(
message,
(
document,
@@ -180,7 +173,7 @@ impl Dispatcher {
Workspace(message) => {
self.message_handlers
.workspace_message_handler
.process_action(message, &self.message_handlers.input_preprocessor_message_handler, &mut queue);
.process_message(message, &self.message_handlers.input_preprocessor_message_handler, &mut queue);
}
}
@@ -226,14 +219,14 @@ impl Dispatcher {
/// Logs a message that is about to be executed,
/// either as a tree with a discriminant or the entire payload (depending on settings)
fn log_message(&self, message: &Message, queues: &[VecDeque<Message>], log_tree_contents: LoggingMessages) {
fn log_message(&self, message: &Message, queues: &[VecDeque<Message>], message_logging_verbosity: MessageLoggingVerbosity) {
if !MessageDiscriminant::from(message).local_name().ends_with("PointerMove") {
match log_tree_contents {
LoggingMessages::Off => {}
LoggingMessages::Names => {
match message_logging_verbosity {
MessageLoggingVerbosity::Off => {}
MessageLoggingVerbosity::Names => {
log::info!("{}{:?}", Self::create_indents(queues), message.to_discriminant());
}
LoggingMessages::Contents => {
MessageLoggingVerbosity::Contents => {
if !(matches!(message, Message::InputPreprocessor(_))) {
log::info!("Message: {}{:?}", Self::create_indents(queues), message);
}
@@ -243,8 +236,8 @@ impl Dispatcher {
}
/// Logs into the tree that the message is in the side effect free messages and its execution will be deferred
fn log_deferred_message(&self, message: &Message, queues: &[VecDeque<Message>], log_tree_contents: LoggingMessages) {
if let LoggingMessages::Names = log_tree_contents {
fn log_deferred_message(&self, message: &Message, queues: &[VecDeque<Message>], message_logging_verbosity: MessageLoggingVerbosity) {
if let MessageLoggingVerbosity::Names = message_logging_verbosity {
log::info!("{}Deferred \"{:?}\" because it's a SIDE_EFFECT_FREE_MESSAGE", Self::create_indents(queues), message.to_discriminant());
}
}
@@ -252,14 +245,14 @@ impl Dispatcher {
#[cfg(test)]
mod test {
use crate::communication::set_uuid_seed;
use crate::document::clipboards::Clipboard;
use crate::document::DocumentMessageHandler;
use crate::message_prelude::*;
use crate::misc::test_utils::EditorTestUtils;
use crate::Editor;
use crate::application::set_uuid_seed;
use crate::application::Editor;
use crate::messages::portfolio::document::utility_types::clipboards::Clipboard;
use crate::messages::prelude::*;
use crate::test_utils::EditorTestUtils;
use graphene::color::Color;
use graphene::LayerId;
use graphene::Operation;
fn init_logger() {
@@ -546,7 +539,9 @@ mod test {
/// If this test is failing take a look at `GRAPHITE_DOCUMENT_VERSION` in `editor/src/consts.rs`, it may need to be updated.
/// This test will fail when you make changes to the underlying serialization format for a document.
fn check_if_graphite_file_version_upgrade_is_needed() {
use crate::layout::widgets::{LayoutGroup, TextLabel, Widget};
use crate::messages::layout::utility_types::layout_widget::{LayoutGroup, Widget};
use crate::messages::layout::utility_types::widgets::label_widgets::TextLabel;
let print_problem_to_terminal_on_failure = |value: &String| {
println!();
println!("-------------------------------------------------");
@@ -564,7 +559,7 @@ mod test {
init_logger();
set_uuid_seed(0);
let mut editor = Editor::new();
let test_file = include_str!("./graphite-test-document.graphite");
let test_file = include_str!("../graphite-test-document.graphite");
let responses = editor.handle_message(PortfolioMessage::OpenDocumentFile {
document_name: "Graphite Version Test".into(),
document_serialized_content: test_file.into(),

View File

@@ -1,62 +0,0 @@
pub mod clipboards;
pub mod layer_panel;
pub mod transformation;
pub mod utility_types;
pub mod vectorize_layer_metadata;
mod artboard_message;
mod artboard_message_handler;
mod document_message;
mod document_message_handler;
mod menu_bar_message;
mod menu_bar_message_handler;
mod movement_message;
mod movement_message_handler;
mod overlays_message;
mod overlays_message_handler;
mod portfolio_message;
mod portfolio_message_handler;
mod properties_panel_message;
mod properties_panel_message_handler;
mod transform_layer_message;
mod transform_layer_message_handler;
#[doc(inline)]
pub use artboard_message::{ArtboardMessage, ArtboardMessageDiscriminant};
#[doc(inline)]
pub use artboard_message_handler::ArtboardMessageHandler;
#[doc(inline)]
pub use document_message::{DocumentMessage, DocumentMessageDiscriminant};
#[doc(inline)]
pub use document_message_handler::DocumentMessageHandler;
#[doc(inline)]
pub use movement_message::{MovementMessage, MovementMessageDiscriminant};
#[doc(inline)]
pub use movement_message_handler::MovementMessageHandler;
#[doc(inline)]
pub use menu_bar_message::{MenuBarMessage, MenuBarMessageDiscriminant};
#[doc(inline)]
pub use menu_bar_message_handler::MenuBarMessageHandler;
#[doc(inline)]
pub use overlays_message::{OverlaysMessage, OverlaysMessageDiscriminant};
#[doc(inline)]
pub use overlays_message_handler::OverlaysMessageHandler;
#[doc(inline)]
pub use portfolio_message::{PortfolioMessage, PortfolioMessageDiscriminant};
#[doc(inline)]
pub use portfolio_message_handler::PortfolioMessageHandler;
#[doc(inline)]
pub use properties_panel_message::{PropertiesPanelMessage, PropertiesPanelMessageDiscriminant};
#[doc(inline)]
pub use properties_panel_message_handler::PropertiesPanelMessageHandler;
#[doc(inline)]
pub use transform_layer_message::{TransformLayerMessage, TransformLayerMessageDiscriminant};
#[doc(inline)]
pub use transform_layer_message_handler::TransformLayerMessageHandler;

View File

@@ -1,511 +0,0 @@
use super::input_mapper_macros::*;
use super::keyboard::{Key, KeyStates, NUMBER_OF_KEYS};
use crate::consts::{BIG_NUDGE_AMOUNT, NUDGE_AMOUNT};
use crate::document::clipboards::Clipboard;
use crate::document::utility_types::KeyboardPlatformLayout;
use crate::message_prelude::*;
use glam::DVec2;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone)]
pub struct Mapping {
pub key_up: [KeyMappingEntries; NUMBER_OF_KEYS],
pub key_down: [KeyMappingEntries; NUMBER_OF_KEYS],
pub double_click: KeyMappingEntries,
pub wheel_scroll: KeyMappingEntries,
pub pointer_move: KeyMappingEntries,
}
impl Default for Mapping {
fn default() -> Self {
use InputMapperMessage::*;
use Key::*;
// WARNING!
// If a new mapping you added here isn't working (and perhaps another lower-precedence one is instead), make sure to advertise
// it as an available action in the respective message handler file (such as the bottom of `document_message_handler.rs`).
let mappings = mapping![
// HIGHER PRIORITY:
//
// MovementMessage
entry!(
PointerMove;
refresh_keys=[KeyControl],
action_dispatch=MovementMessage::PointerMove { snap_angle: KeyControl, wait_for_snap_angle_release: true, snap_zoom: KeyControl, zoom_from_viewport: None },
),
// NORMAL PRIORITY:
//
// TransformLayerMessage
entry!(KeyDown(KeyEnter); action_dispatch=TransformLayerMessage::ApplyTransformOperation),
entry!(KeyDown(Lmb); action_dispatch=TransformLayerMessage::ApplyTransformOperation),
entry!(KeyDown(KeyEscape); action_dispatch=TransformLayerMessage::CancelTransformOperation),
entry!(KeyDown(Rmb); action_dispatch=TransformLayerMessage::CancelTransformOperation),
entry!(KeyDown(KeyX); action_dispatch=TransformLayerMessage::ConstrainX),
entry!(KeyDown(KeyY); action_dispatch=TransformLayerMessage::ConstrainY),
entry!(KeyDown(KeyBackspace); action_dispatch=TransformLayerMessage::TypeBackspace),
entry!(KeyDown(KeyMinus); action_dispatch=TransformLayerMessage::TypeNegate),
entry!(KeyDown(KeyComma); action_dispatch=TransformLayerMessage::TypeDecimalPoint),
entry!(KeyDown(KeyPeriod); action_dispatch=TransformLayerMessage::TypeDecimalPoint),
entry!(PointerMove; refresh_keys=[KeyShift, KeyControl], action_dispatch=TransformLayerMessage::PointerMove { slow_key: KeyShift, snap_key: KeyControl }),
// SelectToolMessage
entry!(PointerMove; refresh_keys=[KeyControl, KeyShift, KeyAlt], action_dispatch=SelectToolMessage::PointerMove { axis_align: KeyShift, snap_angle: KeyControl, center: KeyAlt }),
entry!(KeyDown(Lmb); action_dispatch=SelectToolMessage::DragStart { add_to_selection: KeyShift }),
entry!(KeyUp(Lmb); action_dispatch=SelectToolMessage::DragStop),
entry!(KeyDown(KeyEnter); action_dispatch=SelectToolMessage::DragStop),
entry!(DoubleClick; action_dispatch=SelectToolMessage::EditLayer),
entry!(KeyDown(Rmb); action_dispatch=SelectToolMessage::Abort),
entry!(KeyDown(KeyEscape); action_dispatch=SelectToolMessage::Abort),
// ArtboardToolMessage
entry!(KeyDown(Lmb); action_dispatch=ArtboardToolMessage::PointerDown),
entry!(PointerMove; refresh_keys=[KeyShift, KeyAlt], action_dispatch=ArtboardToolMessage::PointerMove { constrain_axis_or_aspect: KeyShift, center: KeyAlt }),
entry!(KeyUp(Lmb); action_dispatch=ArtboardToolMessage::PointerUp),
entry!(KeyDown(KeyDelete); action_dispatch=ArtboardToolMessage::DeleteSelected),
entry!(KeyDown(KeyBackspace); action_dispatch=ArtboardToolMessage::DeleteSelected),
// NavigateToolMessage
entry!(KeyUp(Lmb); modifiers=[KeyShift], action_dispatch=NavigateToolMessage::ClickZoom { zoom_in: false }),
entry!(KeyUp(Lmb); action_dispatch=NavigateToolMessage::ClickZoom { zoom_in: true }),
entry!(PointerMove; refresh_keys=[KeyControl], action_dispatch=NavigateToolMessage::PointerMove { snap_angle: KeyControl, snap_zoom: KeyControl }),
entry!(KeyDown(Mmb); action_dispatch=NavigateToolMessage::TranslateCanvasBegin),
entry!(KeyDown(Rmb); action_dispatch=NavigateToolMessage::RotateCanvasBegin),
entry!(KeyDown(Lmb); action_dispatch=NavigateToolMessage::ZoomCanvasBegin),
entry!(KeyUp(Rmb); action_dispatch=NavigateToolMessage::TransformCanvasEnd),
entry!(KeyUp(Lmb); action_dispatch=NavigateToolMessage::TransformCanvasEnd),
entry!(KeyUp(Mmb); action_dispatch=NavigateToolMessage::TransformCanvasEnd),
// EyedropperToolMessage
entry!(KeyDown(Lmb); action_dispatch=EyedropperToolMessage::LeftMouseDown),
entry!(KeyDown(Rmb); action_dispatch=EyedropperToolMessage::RightMouseDown),
// TextToolMessage
entry!(KeyUp(Lmb); action_dispatch=TextToolMessage::Interact),
entry!(KeyDown(KeyEscape); action_dispatch=TextToolMessage::Abort),
entry_multiplatform!(
standard!(KeyDown(KeyEnter); modifiers=[KeyControl], action_dispatch=TextToolMessage::CommitText),
mac_only!(KeyDown(KeyEnter); modifiers=[KeyCommand], action_dispatch=TextToolMessage::CommitText),
),
// GradientToolMessage
entry!(KeyDown(Lmb); action_dispatch=GradientToolMessage::PointerDown),
entry!(PointerMove; refresh_keys=[KeyShift], action_dispatch=GradientToolMessage::PointerMove { constrain_axis: KeyShift }),
entry!(KeyUp(Lmb); action_dispatch=GradientToolMessage::PointerUp),
// RectangleToolMessage
entry!(KeyDown(Lmb); action_dispatch=RectangleToolMessage::DragStart),
entry!(KeyUp(Lmb); action_dispatch=RectangleToolMessage::DragStop),
entry!(KeyDown(Rmb); action_dispatch=RectangleToolMessage::Abort),
entry!(KeyDown(KeyEscape); action_dispatch=RectangleToolMessage::Abort),
entry!(PointerMove; refresh_keys=[KeyAlt, KeyShift], action_dispatch=RectangleToolMessage::Resize { center: KeyAlt, lock_ratio: KeyShift }),
// EllipseToolMessage
entry!(KeyDown(Lmb); action_dispatch=EllipseToolMessage::DragStart),
entry!(KeyUp(Lmb); action_dispatch=EllipseToolMessage::DragStop),
entry!(KeyDown(Rmb); action_dispatch=EllipseToolMessage::Abort),
entry!(KeyDown(KeyEscape); action_dispatch=EllipseToolMessage::Abort),
entry!(PointerMove; refresh_keys=[KeyAlt, KeyShift], action_dispatch=EllipseToolMessage::Resize { center: KeyAlt, lock_ratio: KeyShift }),
// ShapeToolMessage
entry!(KeyDown(Lmb); action_dispatch=ShapeToolMessage::DragStart),
entry!(KeyUp(Lmb); action_dispatch=ShapeToolMessage::DragStop),
entry!(KeyDown(Rmb); action_dispatch=ShapeToolMessage::Abort),
entry!(KeyDown(KeyEscape); action_dispatch=ShapeToolMessage::Abort),
entry!(PointerMove; refresh_keys=[KeyAlt, KeyShift], action_dispatch=ShapeToolMessage::Resize { center: KeyAlt, lock_ratio: KeyShift }),
// LineToolMessage
entry!(KeyDown(Lmb); action_dispatch=LineToolMessage::DragStart),
entry!(KeyUp(Lmb); action_dispatch=LineToolMessage::DragStop),
entry!(KeyDown(Rmb); action_dispatch=LineToolMessage::Abort),
entry!(KeyDown(KeyEscape); action_dispatch=LineToolMessage::Abort),
entry!(PointerMove; refresh_keys=[KeyAlt, KeyShift, KeyControl], action_dispatch=LineToolMessage::Redraw { center: KeyAlt, lock_angle: KeyControl, snap_angle: KeyShift }),
// PathToolMessage
entry!(KeyDown(Lmb); action_dispatch=PathToolMessage::DragStart { add_to_selection: KeyShift }),
entry!(PointerMove; refresh_keys=[KeyAlt, KeyShift], action_dispatch=PathToolMessage::PointerMove { alt_mirror_angle: KeyAlt, shift_mirror_distance: KeyShift }),
entry!(KeyDown(KeyDelete); action_dispatch=PathToolMessage::Delete),
entry!(KeyDown(KeyBackspace); action_dispatch=PathToolMessage::Delete),
entry!(KeyUp(Lmb); action_dispatch=PathToolMessage::DragStop),
// PenToolMessage
entry!(PointerMove; refresh_keys=[KeyShift, KeyControl], action_dispatch=PenToolMessage::PointerMove { snap_angle: KeyControl, break_handle: KeyShift }),
entry!(KeyDown(Lmb); action_dispatch=PenToolMessage::DragStart),
entry!(KeyUp(Lmb); action_dispatch=PenToolMessage::DragStop),
entry!(KeyDown(Rmb); action_dispatch=PenToolMessage::Confirm),
entry!(KeyDown(KeyEscape); action_dispatch=PenToolMessage::Confirm),
entry!(KeyDown(KeyEnter); action_dispatch=PenToolMessage::Confirm),
// FreehandToolMessage
entry!(PointerMove; action_dispatch=FreehandToolMessage::PointerMove),
entry!(KeyDown(Lmb); action_dispatch=FreehandToolMessage::DragStart),
entry!(KeyUp(Lmb); action_dispatch=FreehandToolMessage::DragStop),
// SplineToolMessage
entry!(PointerMove; action_dispatch=SplineToolMessage::PointerMove),
entry!(KeyDown(Lmb); action_dispatch=SplineToolMessage::DragStart),
entry!(KeyUp(Lmb); action_dispatch=SplineToolMessage::DragStop),
entry!(KeyDown(Rmb); action_dispatch=SplineToolMessage::Confirm),
entry!(KeyDown(KeyEscape); action_dispatch=SplineToolMessage::Confirm),
entry!(KeyDown(KeyEnter); action_dispatch=SplineToolMessage::Confirm),
// FillToolMessage
entry!(KeyDown(Lmb); action_dispatch=FillToolMessage::LeftMouseDown),
entry!(KeyDown(Rmb); action_dispatch=FillToolMessage::RightMouseDown),
// ToolMessage
entry!(KeyDown(KeyV); action_dispatch=ToolMessage::ActivateToolSelect),
entry!(KeyDown(KeyZ); action_dispatch=ToolMessage::ActivateToolNavigate),
entry!(KeyDown(KeyI); action_dispatch=ToolMessage::ActivateToolEyedropper),
entry!(KeyDown(KeyT); action_dispatch=ToolMessage::ActivateToolText),
entry!(KeyDown(KeyF); action_dispatch=ToolMessage::ActivateToolFill),
entry!(KeyDown(KeyH); action_dispatch=ToolMessage::ActivateToolGradient),
entry!(KeyDown(KeyA); action_dispatch=ToolMessage::ActivateToolPath),
entry!(KeyDown(KeyP); action_dispatch=ToolMessage::ActivateToolPen),
entry!(KeyDown(KeyN); action_dispatch=ToolMessage::ActivateToolFreehand),
entry!(KeyDown(KeyL); action_dispatch=ToolMessage::ActivateToolLine),
entry!(KeyDown(KeyM); action_dispatch=ToolMessage::ActivateToolRectangle),
entry!(KeyDown(KeyE); action_dispatch=ToolMessage::ActivateToolEllipse),
entry!(KeyDown(KeyY); action_dispatch=ToolMessage::ActivateToolShape),
entry_multiplatform!(
standard!(KeyDown(KeyX); modifiers=[KeyShift, KeyControl], action_dispatch=ToolMessage::ResetColors),
mac_only!(KeyDown(KeyX); modifiers=[KeyShift, KeyCommand], action_dispatch=ToolMessage::ResetColors),
),
entry!(KeyDown(KeyX); modifiers=[KeyShift], action_dispatch=ToolMessage::SwapColors),
entry!(KeyDown(KeyC); modifiers=[KeyAlt], action_dispatch=ToolMessage::SelectRandomPrimaryColor),
// DocumentMessage
entry!(KeyDown(KeyDelete); action_dispatch=DocumentMessage::DeleteSelectedLayers),
entry!(KeyDown(KeyBackspace); action_dispatch=DocumentMessage::DeleteSelectedLayers),
entry!(KeyDown(KeyP); modifiers=[KeyAlt], action_dispatch=DocumentMessage::DebugPrintDocument),
entry_multiplatform!(
standard!(KeyDown(KeyZ); modifiers=[KeyControl, KeyShift], action_dispatch=DocumentMessage::Redo),
mac_only!(KeyDown(KeyZ); modifiers=[KeyCommand, KeyShift], action_dispatch=DocumentMessage::Redo),
),
entry_multiplatform!(
standard!(KeyDown(KeyZ); modifiers=[KeyControl], action_dispatch=DocumentMessage::Undo),
mac_only!(KeyDown(KeyZ); modifiers=[KeyCommand], action_dispatch=DocumentMessage::Undo),
),
entry_multiplatform!(
standard!(KeyDown(KeyA); modifiers=[KeyControl, KeyAlt], action_dispatch=DocumentMessage::DeselectAllLayers),
mac_only!(KeyDown(KeyA); modifiers=[KeyCommand, KeyAlt], action_dispatch=DocumentMessage::DeselectAllLayers),
),
entry_multiplatform!(
standard!(KeyDown(KeyA); modifiers=[KeyControl], action_dispatch=DocumentMessage::SelectAllLayers),
mac_only!(KeyDown(KeyA); modifiers=[KeyCommand], action_dispatch=DocumentMessage::SelectAllLayers),
),
entry_multiplatform!(
standard!(KeyDown(KeyS); modifiers=[KeyControl], action_dispatch=DocumentMessage::SaveDocument),
mac_only!(KeyDown(KeyS); modifiers=[KeyCommand], action_dispatch=DocumentMessage::SaveDocument),
),
entry_multiplatform!(
standard!(KeyDown(Key0); modifiers=[KeyControl], action_dispatch=DocumentMessage::ZoomCanvasToFitAll),
mac_only!(KeyDown(Key0); modifiers=[KeyCommand], action_dispatch=DocumentMessage::ZoomCanvasToFitAll),
),
entry_multiplatform!(
standard!(KeyDown(KeyD); modifiers=[KeyControl], action_dispatch=DocumentMessage::DuplicateSelectedLayers),
mac_only!(KeyDown(KeyD); modifiers=[KeyCommand], action_dispatch=DocumentMessage::DuplicateSelectedLayers),
),
entry_multiplatform!(
standard!(KeyDown(KeyG); modifiers=[KeyControl], action_dispatch=DocumentMessage::GroupSelectedLayers),
mac_only!(KeyDown(KeyG); modifiers=[KeyCommand], action_dispatch=DocumentMessage::GroupSelectedLayers),
),
entry_multiplatform!(
standard!(KeyDown(KeyG); modifiers=[KeyControl, KeyShift], action_dispatch=DocumentMessage::UngroupSelectedLayers),
mac_only!(KeyDown(KeyG); modifiers=[KeyCommand, KeyShift], action_dispatch=DocumentMessage::UngroupSelectedLayers),
),
entry_multiplatform!(
standard!(KeyDown(KeyN); modifiers=[KeyControl, KeyShift], action_dispatch=DocumentMessage::CreateEmptyFolder { container_path: vec![] }),
mac_only!(KeyDown(KeyN); modifiers=[KeyCommand, KeyShift], action_dispatch=DocumentMessage::CreateEmptyFolder { container_path: vec![] }),
),
entry_multiplatform!(
standard!(KeyDown(KeyLeftBracket); modifiers=[KeyControl, KeyShift], action_dispatch=DocumentMessage::SelectedLayersLowerToBack),
mac_only!(KeyDown(KeyLeftBracket); modifiers=[KeyCommand, KeyShift], action_dispatch=DocumentMessage::SelectedLayersLowerToBack),
),
entry_multiplatform!(
// TODO: Delete this in favor of the KeyLeftBracket (non-shifted version of this key) mapping above once the input system can distinguish between the non-shifted and shifted keys (important for other language keyboards)
standard!(KeyDown(KeyLeftCurlyBracket); modifiers=[KeyControl, KeyShift], action_dispatch=DocumentMessage::SelectedLayersLowerToBack),
mac_only!(KeyDown(KeyLeftCurlyBracket); modifiers=[KeyCommand, KeyShift], action_dispatch=DocumentMessage::SelectedLayersLowerToBack),
),
entry_multiplatform!(
standard!(KeyDown(KeyRightBracket); modifiers=[KeyControl, KeyShift], action_dispatch=DocumentMessage::SelectedLayersRaiseToFront),
mac_only!(KeyDown(KeyRightBracket); modifiers=[KeyCommand, KeyShift], action_dispatch=DocumentMessage::SelectedLayersRaiseToFront),
),
entry_multiplatform!(
// TODO: Delete this in favor of the KeyRightBracket (non-shifted version of this key) mapping above once the input system can distinguish between the non-shifted and shifted keys (important for other language keyboards)
standard!(KeyDown(KeyRightCurlyBracket); modifiers=[KeyControl, KeyShift], action_dispatch=DocumentMessage::SelectedLayersRaiseToFront),
mac_only!(KeyDown(KeyRightCurlyBracket); modifiers=[KeyCommand, KeyShift], action_dispatch=DocumentMessage::SelectedLayersRaiseToFront),
),
entry_multiplatform!(
standard!(KeyDown(KeyLeftBracket); modifiers=[KeyControl], action_dispatch=DocumentMessage::SelectedLayersLower),
mac_only!(KeyDown(KeyLeftBracket); modifiers=[KeyCommand], action_dispatch=DocumentMessage::SelectedLayersLower),
),
entry_multiplatform!(
standard!(KeyDown(KeyRightBracket); modifiers=[KeyControl], action_dispatch=DocumentMessage::SelectedLayersRaise),
mac_only!(KeyDown(KeyRightBracket); modifiers=[KeyCommand], action_dispatch=DocumentMessage::SelectedLayersRaise),
),
entry!(KeyDown(KeyArrowUp); modifiers=[KeyShift, KeyArrowLeft], action_dispatch=DocumentMessage::NudgeSelectedLayers { delta_x: -BIG_NUDGE_AMOUNT, delta_y: -BIG_NUDGE_AMOUNT }),
entry!(KeyDown(KeyArrowUp); modifiers=[KeyShift, KeyArrowRight], action_dispatch=DocumentMessage::NudgeSelectedLayers { delta_x: BIG_NUDGE_AMOUNT, delta_y: -BIG_NUDGE_AMOUNT }),
entry!(KeyDown(KeyArrowUp); modifiers=[KeyShift], action_dispatch=DocumentMessage::NudgeSelectedLayers { delta_x: 0., delta_y: -BIG_NUDGE_AMOUNT }),
entry!(KeyDown(KeyArrowDown); modifiers=[KeyShift, KeyArrowLeft], action_dispatch=DocumentMessage::NudgeSelectedLayers { delta_x: -BIG_NUDGE_AMOUNT, delta_y: BIG_NUDGE_AMOUNT }),
entry!(KeyDown(KeyArrowDown); modifiers=[KeyShift, KeyArrowRight], action_dispatch=DocumentMessage::NudgeSelectedLayers { delta_x: BIG_NUDGE_AMOUNT, delta_y: BIG_NUDGE_AMOUNT }),
entry!(KeyDown(KeyArrowDown); modifiers=[KeyShift], action_dispatch=DocumentMessage::NudgeSelectedLayers { delta_x: 0., delta_y: BIG_NUDGE_AMOUNT }),
entry!(KeyDown(KeyArrowLeft); modifiers=[KeyShift, KeyArrowUp], action_dispatch=DocumentMessage::NudgeSelectedLayers { delta_x: -BIG_NUDGE_AMOUNT, delta_y: -BIG_NUDGE_AMOUNT }),
entry!(KeyDown(KeyArrowLeft); modifiers=[KeyShift, KeyArrowDown], action_dispatch=DocumentMessage::NudgeSelectedLayers { delta_x: -BIG_NUDGE_AMOUNT, delta_y: BIG_NUDGE_AMOUNT }),
entry!(KeyDown(KeyArrowLeft); modifiers=[KeyShift], action_dispatch=DocumentMessage::NudgeSelectedLayers { delta_x: -BIG_NUDGE_AMOUNT, delta_y: 0. }),
entry!(KeyDown(KeyArrowRight); modifiers=[KeyShift, KeyArrowUp], action_dispatch=DocumentMessage::NudgeSelectedLayers { delta_x: BIG_NUDGE_AMOUNT, delta_y: -BIG_NUDGE_AMOUNT }),
entry!(KeyDown(KeyArrowRight); modifiers=[KeyShift, KeyArrowDown], action_dispatch=DocumentMessage::NudgeSelectedLayers { delta_x: BIG_NUDGE_AMOUNT, delta_y: BIG_NUDGE_AMOUNT }),
entry!(KeyDown(KeyArrowRight); modifiers=[KeyShift], action_dispatch=DocumentMessage::NudgeSelectedLayers { delta_x: BIG_NUDGE_AMOUNT, delta_y: 0. }),
entry!(KeyDown(KeyArrowUp); modifiers=[KeyArrowLeft], action_dispatch=DocumentMessage::NudgeSelectedLayers { delta_x: -NUDGE_AMOUNT, delta_y: -NUDGE_AMOUNT }),
entry!(KeyDown(KeyArrowUp); modifiers=[KeyArrowRight], action_dispatch=DocumentMessage::NudgeSelectedLayers { delta_x: NUDGE_AMOUNT, delta_y: -NUDGE_AMOUNT }),
entry!(KeyDown(KeyArrowUp); action_dispatch=DocumentMessage::NudgeSelectedLayers { delta_x: 0., delta_y: -NUDGE_AMOUNT }),
entry!(KeyDown(KeyArrowDown); modifiers=[KeyArrowLeft], action_dispatch=DocumentMessage::NudgeSelectedLayers { delta_x: -NUDGE_AMOUNT, delta_y: NUDGE_AMOUNT }),
entry!(KeyDown(KeyArrowDown); modifiers=[KeyArrowRight], action_dispatch=DocumentMessage::NudgeSelectedLayers { delta_x: NUDGE_AMOUNT, delta_y: NUDGE_AMOUNT }),
entry!(KeyDown(KeyArrowDown); action_dispatch=DocumentMessage::NudgeSelectedLayers { delta_x: 0., delta_y: NUDGE_AMOUNT }),
entry!(KeyDown(KeyArrowLeft); modifiers=[KeyArrowUp], action_dispatch=DocumentMessage::NudgeSelectedLayers { delta_x: -NUDGE_AMOUNT, delta_y: -NUDGE_AMOUNT }),
entry!(KeyDown(KeyArrowLeft); modifiers=[KeyArrowDown], action_dispatch=DocumentMessage::NudgeSelectedLayers { delta_x: -NUDGE_AMOUNT, delta_y: NUDGE_AMOUNT }),
entry!(KeyDown(KeyArrowLeft); action_dispatch=DocumentMessage::NudgeSelectedLayers { delta_x: -NUDGE_AMOUNT, delta_y: 0. }),
entry!(KeyDown(KeyArrowRight); modifiers=[KeyArrowUp], action_dispatch=DocumentMessage::NudgeSelectedLayers { delta_x: NUDGE_AMOUNT, delta_y: -NUDGE_AMOUNT }),
entry!(KeyDown(KeyArrowRight); modifiers=[KeyArrowDown], action_dispatch=DocumentMessage::NudgeSelectedLayers { delta_x: NUDGE_AMOUNT, delta_y: NUDGE_AMOUNT }),
entry!(KeyDown(KeyArrowRight); action_dispatch=DocumentMessage::NudgeSelectedLayers { delta_x: NUDGE_AMOUNT, delta_y: 0. }),
// TransformLayerMessage
entry!(KeyDown(KeyG); action_dispatch=TransformLayerMessage::BeginGrab),
entry!(KeyDown(KeyR); action_dispatch=TransformLayerMessage::BeginRotate),
entry!(KeyDown(KeyS); action_dispatch=TransformLayerMessage::BeginScale),
// MovementMessage
entry!(KeyDown(Mmb); modifiers=[KeyControl], action_dispatch=MovementMessage::RotateCanvasBegin),
entry!(KeyDown(Mmb); modifiers=[KeyShift], action_dispatch=MovementMessage::ZoomCanvasBegin),
entry!(KeyDown(Mmb); action_dispatch=MovementMessage::TranslateCanvasBegin),
entry!(KeyUp(Mmb); action_dispatch=MovementMessage::TransformCanvasEnd),
entry!(KeyDown(Lmb); modifiers=[KeySpace], action_dispatch=MovementMessage::TranslateCanvasBegin),
entry!(KeyUp(Lmb); modifiers=[KeySpace], action_dispatch=MovementMessage::TransformCanvasEnd),
entry_multiplatform!(
standard!(KeyDown(KeyPlus); modifiers=[KeyControl], action_dispatch=MovementMessage::IncreaseCanvasZoom { center_on_mouse: false }),
mac_only!(KeyDown(KeyPlus); modifiers=[KeyCommand], action_dispatch=MovementMessage::IncreaseCanvasZoom { center_on_mouse: false }),
),
entry_multiplatform!(
standard!(KeyDown(KeyEquals); modifiers=[KeyControl], action_dispatch=MovementMessage::IncreaseCanvasZoom { center_on_mouse: false }),
mac_only!(KeyDown(KeyEquals); modifiers=[KeyCommand], action_dispatch=MovementMessage::IncreaseCanvasZoom { center_on_mouse: false }),
),
entry_multiplatform!(
standard!(KeyDown(KeyMinus); modifiers=[KeyControl], action_dispatch=MovementMessage::DecreaseCanvasZoom { center_on_mouse: false }),
mac_only!(KeyDown(KeyMinus); modifiers=[KeyCommand], action_dispatch=MovementMessage::DecreaseCanvasZoom { center_on_mouse: false }),
),
entry_multiplatform!(
standard!(KeyDown(Key1); modifiers=[KeyControl], action_dispatch=MovementMessage::SetCanvasZoom { zoom_factor: 1. }),
mac_only!(KeyDown(Key1); modifiers=[KeyCommand], action_dispatch=MovementMessage::SetCanvasZoom { zoom_factor: 1. }),
),
entry_multiplatform!(
standard!(KeyDown(Key2); modifiers=[KeyControl], action_dispatch=MovementMessage::SetCanvasZoom { zoom_factor: 2. }),
mac_only!(KeyDown(Key2); modifiers=[KeyCommand], action_dispatch=MovementMessage::SetCanvasZoom { zoom_factor: 2. }),
),
entry!(WheelScroll; modifiers=[KeyControl], action_dispatch=MovementMessage::WheelCanvasZoom),
entry!(WheelScroll; modifiers=[KeyShift], action_dispatch=MovementMessage::WheelCanvasTranslate { use_y_as_x: true }),
entry!(WheelScroll; action_dispatch=MovementMessage::WheelCanvasTranslate { use_y_as_x: false }),
entry!(KeyDown(KeyPageUp); modifiers=[KeyShift], action_dispatch=MovementMessage::TranslateCanvasByViewportFraction { delta: DVec2::new(1., 0.) }),
entry!(KeyDown(KeyPageDown); modifiers=[KeyShift], action_dispatch=MovementMessage::TranslateCanvasByViewportFraction { delta: DVec2::new(-1., 0.) }),
entry!(KeyDown(KeyPageUp); action_dispatch=MovementMessage::TranslateCanvasByViewportFraction { delta: DVec2::new(0., 1.) }),
entry!(KeyDown(KeyPageDown); action_dispatch=MovementMessage::TranslateCanvasByViewportFraction { delta: DVec2::new(0., -1.) }),
// PortfolioMessage
entry_multiplatform!(
standard!(KeyDown(KeyO); modifiers=[KeyControl], action_dispatch=PortfolioMessage::OpenDocument),
mac_only!(KeyDown(KeyO); modifiers=[KeyCommand], action_dispatch=PortfolioMessage::OpenDocument),
),
entry_multiplatform!(
standard!(KeyDown(KeyI); modifiers=[KeyControl], action_dispatch=PortfolioMessage::Import),
mac_only!(KeyDown(KeyI); modifiers=[KeyCommand], action_dispatch=PortfolioMessage::Import),
),
entry!(KeyDown(KeyTab); modifiers=[KeyControl], action_dispatch=PortfolioMessage::NextDocument),
entry!(KeyDown(KeyTab); modifiers=[KeyControl, KeyShift], action_dispatch=PortfolioMessage::PrevDocument),
entry_multiplatform!(
standard!(KeyDown(KeyW); modifiers=[KeyControl], action_dispatch=PortfolioMessage::CloseActiveDocumentWithConfirmation),
mac_only!(KeyDown(KeyW); modifiers=[KeyCommand], action_dispatch=PortfolioMessage::CloseActiveDocumentWithConfirmation),
),
entry_multiplatform!(
standard!(KeyDown(KeyX); modifiers=[KeyControl], action_dispatch=PortfolioMessage::Cut { clipboard: Clipboard::Device }),
mac_only!(KeyDown(KeyX); modifiers=[KeyCommand], action_dispatch=PortfolioMessage::Cut { clipboard: Clipboard::Device }),
),
entry_multiplatform!(
standard!(KeyDown(KeyC); modifiers=[KeyControl], action_dispatch=PortfolioMessage::Copy { clipboard: Clipboard::Device }),
mac_only!(KeyDown(KeyC); modifiers=[KeyCommand], action_dispatch=PortfolioMessage::Copy { clipboard: Clipboard::Device }),
),
entry_multiplatform!(
// This shortcut is intercepted in the frontend; it exists here only as a shortcut mapping source
standard!(KeyDown(KeyV); modifiers=[KeyControl], action_dispatch=FrontendMessage::TriggerPaste),
mac_only!(KeyDown(KeyV); modifiers=[KeyCommand], action_dispatch=FrontendMessage::TriggerPaste),
),
// DialogMessage
entry_multiplatform!(
standard!(KeyDown(KeyN); modifiers=[KeyControl], action_dispatch=DialogMessage::RequestNewDocumentDialog),
mac_only!(KeyDown(KeyN); modifiers=[KeyCommand], action_dispatch=DialogMessage::RequestNewDocumentDialog),
),
entry_multiplatform!(
standard!(KeyDown(KeyW); modifiers=[KeyControl, KeyAlt], action_dispatch=DialogMessage::CloseAllDocumentsWithConfirmation),
mac_only!(KeyDown(KeyW); modifiers=[KeyCommand, KeyAlt], action_dispatch=DialogMessage::CloseAllDocumentsWithConfirmation),
),
entry_multiplatform!(
standard!(KeyDown(KeyE); modifiers=[KeyControl], action_dispatch=DialogMessage::RequestExportDialog),
mac_only!(KeyDown(KeyE); modifiers=[KeyCommand], action_dispatch=DialogMessage::RequestExportDialog),
),
// DebugMessage
entry!(KeyDown(KeyT); modifiers=[KeyAlt], action_dispatch=DebugMessage::ToggleTraceLogs),
entry!(KeyDown(Key0); modifiers=[KeyAlt], action_dispatch=DebugMessage::MessageOff),
entry!(KeyDown(Key1); modifiers=[KeyAlt], action_dispatch=DebugMessage::MessageNames),
entry!(KeyDown(Key2); modifiers=[KeyAlt], action_dispatch=DebugMessage::MessageContents),
];
let (mut key_up, mut key_down, mut double_click, mut wheel_scroll, mut pointer_move) = mappings;
// TODO: Hardcode these 10 lines into 10 lines of declarations, or make this use a macro to do all 10 in one line
const NUMBER_KEYS: [Key; 10] = [Key0, Key1, Key2, Key3, Key4, Key5, Key6, Key7, Key8, Key9];
for (i, key) in NUMBER_KEYS.iter().enumerate() {
key_down[*key as usize].0.insert(
0,
MappingEntry {
action: TransformLayerMessage::TypeDigit { digit: i as u8 }.into(),
input: InputMapperMessage::KeyDown(*key),
platform_layout: None,
modifiers: modifiers!(),
},
);
}
let sort = |list: &mut KeyMappingEntries| list.0.sort_by(|u, v| v.modifiers.ones().cmp(&u.modifiers.ones()));
for list in [&mut key_up, &mut key_down] {
for sublist in list {
sort(sublist);
}
}
sort(&mut double_click);
sort(&mut wheel_scroll);
sort(&mut pointer_move);
Self {
key_up,
key_down,
double_click,
wheel_scroll,
pointer_move,
}
}
}
impl Mapping {
pub fn match_input_message(&self, message: InputMapperMessage, keyboard_state: &KeyStates, actions: ActionList, keyboard_platform: KeyboardPlatformLayout) -> Option<Message> {
let list = match message {
InputMapperMessage::KeyDown(key) => &self.key_down[key as usize],
InputMapperMessage::KeyUp(key) => &self.key_up[key as usize],
InputMapperMessage::DoubleClick => &self.double_click,
InputMapperMessage::WheelScroll => &self.wheel_scroll,
InputMapperMessage::PointerMove => &self.pointer_move,
};
list.match_mapping(keyboard_state, actions, keyboard_platform)
}
}
#[derive(PartialEq, Clone, Debug)]
pub struct MappingEntry {
/// Serves two purposes:
/// - This is the message that gets dispatched when the hotkey is matched
/// - This message's discriminant is the action; it must be a currently active action to be considered as a shortcut
pub action: Message,
/// The user input event from an input device which this input mapping matches on
pub input: InputMapperMessage,
/// Any additional keys that must be also pressed for this input mapping to match
pub modifiers: KeyStates,
/// The keyboard platform layout which this mapping is exclusive to, or `None` if it's platform-agnostic
pub platform_layout: Option<KeyboardPlatformLayout>,
}
#[derive(Debug, Clone)]
pub struct KeyMappingEntries(pub Vec<MappingEntry>);
impl KeyMappingEntries {
fn match_mapping(&self, keyboard_state: &KeyStates, actions: ActionList, keyboard_platform: KeyboardPlatformLayout) -> Option<Message> {
for entry in self.0.iter() {
// Skip this entry if it is platform-specific, and for a layout that does not match the user's keyboard platform layout
if let Some(entry_platform_layout) = entry.platform_layout {
if entry_platform_layout != keyboard_platform {
continue;
}
}
// Find which currently pressed keys are also the modifiers in this hotkey entry, then compare those against the required modifiers to see if there are zero missing
let pressed_modifiers = *keyboard_state & entry.modifiers;
let all_modifiers_without_pressed_modifiers = entry.modifiers ^ pressed_modifiers;
let all_required_modifiers_pressed = all_modifiers_without_pressed_modifiers.is_empty();
// 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());
}
}
None
}
fn push(&mut self, entry: MappingEntry) {
self.0.push(entry)
}
const fn new() -> Self {
Self(Vec::new())
}
fn key_array() -> [Self; NUMBER_OF_KEYS] {
const DEFAULT: KeyMappingEntries = KeyMappingEntries::new();
[DEFAULT; NUMBER_OF_KEYS]
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub enum ActionKeys {
Action(MessageDiscriminant),
#[serde(rename = "keys")]
Keys(Vec<Key>),
}
impl ActionKeys {
pub fn to_keys(&mut self, action_input_mapping: &impl Fn(&MessageDiscriminant) -> Vec<Vec<Key>>) {
match self {
ActionKeys::Action(action) => {
if let Some(keys) = action_input_mapping(action).get_mut(0) {
let mut taken_keys = Vec::new();
std::mem::swap(keys, &mut taken_keys);
*self = ActionKeys::Keys(taken_keys);
} else {
*self = ActionKeys::Keys(Vec::new());
}
}
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());
}
joined
}
pub mod action_keys {
macro_rules! action_shortcut {
($action:expr) => {
Some(crate::input::input_mapper::ActionKeys::Action($action.into()))
};
}
pub(crate) use action_shortcut;
}

View File

@@ -1,116 +0,0 @@
#[doc(inline)]
pub use graphene::DocumentResponse;
use bitflags::bitflags;
use serde::{Deserialize, Serialize};
pub enum KeyPosition {
Pressed,
Released,
}
bitflags! {
#[derive(Default, Serialize, Deserialize)]
#[repr(transparent)]
pub struct ModifierKeys: u8 {
const SHIFT = 0b0000_0001;
const ALT = 0b0000_0010;
const CONTROL = 0b0000_0100;
const META_OR_COMMAND = 0b0000_1000;
}
}
#[cfg(test)]
mod test {
use crate::document::utility_types::KeyboardPlatformLayout;
use crate::input::input_preprocessor::ModifierKeys;
use crate::input::keyboard::Key;
use crate::input::mouse::EditorMouseState;
use crate::input::{InputMapperMessage, InputPreprocessorMessage, InputPreprocessorMessageHandler};
use crate::message_prelude::MessageHandler;
use std::collections::VecDeque;
#[test]
fn process_action_mouse_move_handle_modifier_keys() {
let mut input_preprocessor = InputPreprocessorMessageHandler::default();
let editor_mouse_state = EditorMouseState::from_editor_position(4., 809.);
let modifier_keys = ModifierKeys::ALT;
let message = InputPreprocessorMessage::PointerMove { editor_mouse_state, modifier_keys };
let mut responses = VecDeque::new();
input_preprocessor.process_action(message, KeyboardPlatformLayout::Standard, &mut responses);
assert!(input_preprocessor.keyboard.get(Key::KeyAlt as usize));
assert_eq!(responses.pop_front(), Some(InputMapperMessage::KeyDown(Key::KeyAlt).into()));
}
#[test]
fn process_action_mouse_down_handle_modifier_keys() {
let mut input_preprocessor = InputPreprocessorMessageHandler::default();
let editor_mouse_state = EditorMouseState::new();
let modifier_keys = ModifierKeys::CONTROL;
let message = InputPreprocessorMessage::PointerDown { editor_mouse_state, modifier_keys };
let mut responses = VecDeque::new();
input_preprocessor.process_action(message, KeyboardPlatformLayout::Standard, &mut responses);
assert!(input_preprocessor.keyboard.get(Key::KeyControl as usize));
assert_eq!(responses.pop_front(), Some(InputMapperMessage::KeyDown(Key::KeyControl).into()));
}
#[test]
fn process_action_mouse_up_handle_modifier_keys() {
let mut input_preprocessor = InputPreprocessorMessageHandler::default();
let editor_mouse_state = EditorMouseState::new();
let modifier_keys = ModifierKeys::SHIFT;
let message = InputPreprocessorMessage::PointerUp { editor_mouse_state, modifier_keys };
let mut responses = VecDeque::new();
input_preprocessor.process_action(message, KeyboardPlatformLayout::Standard, &mut responses);
assert!(input_preprocessor.keyboard.get(Key::KeyShift as usize));
assert_eq!(responses.pop_front(), Some(InputMapperMessage::KeyDown(Key::KeyShift).into()));
}
#[test]
fn process_action_key_down_handle_modifier_keys() {
let mut input_preprocessor = InputPreprocessorMessageHandler::default();
input_preprocessor.keyboard.set(Key::KeyControl as usize);
let key = Key::KeyA;
let modifier_keys = ModifierKeys::empty();
let message = InputPreprocessorMessage::KeyDown { key, modifier_keys };
let mut responses = VecDeque::new();
input_preprocessor.process_action(message, KeyboardPlatformLayout::Standard, &mut responses);
assert!(!input_preprocessor.keyboard.get(Key::KeyControl as usize));
assert_eq!(responses.pop_front(), Some(InputMapperMessage::KeyUp(Key::KeyControl).into()));
}
#[test]
fn process_action_key_up_handle_modifier_keys() {
let mut input_preprocessor = InputPreprocessorMessageHandler::default();
let key = Key::KeyS;
let modifier_keys = ModifierKeys::CONTROL | ModifierKeys::SHIFT;
let message = InputPreprocessorMessage::KeyUp { key, modifier_keys };
let mut responses = VecDeque::new();
input_preprocessor.process_action(message, KeyboardPlatformLayout::Standard, &mut responses);
assert!(input_preprocessor.keyboard.get(Key::KeyControl as usize));
assert!(input_preprocessor.keyboard.get(Key::KeyShift as usize));
assert!(responses.contains(&InputMapperMessage::KeyDown(Key::KeyControl).into()));
assert!(responses.contains(&InputMapperMessage::KeyDown(Key::KeyControl).into()));
}
}

View File

@@ -1,20 +0,0 @@
pub mod input_mapper;
pub mod input_preprocessor;
pub mod keyboard;
pub mod mouse;
mod input_mapper_macros;
mod input_mapper_message;
mod input_mapper_message_handler;
mod input_preprocessor_message;
mod input_preprocessor_message_handler;
#[doc(inline)]
pub use input_mapper_message::{InputMapperMessage, InputMapperMessageDiscriminant};
#[doc(inline)]
pub use input_mapper_message_handler::InputMapperMessageHandler;
#[doc(inline)]
pub use input_preprocessor_message::{InputPreprocessorMessage, InputPreprocessorMessageDiscriminant};
#[doc(inline)]
pub use input_preprocessor_message_handler::InputPreprocessorMessageHandler;

View File

@@ -1,5 +0,0 @@
pub mod layout_message;
pub mod layout_message_handler;
pub mod widgets;
pub use layout_message::{LayoutMessage, LayoutMessageDiscriminant};

View File

@@ -1,776 +0,0 @@
use super::layout_message::LayoutTarget;
use crate::document::utility_types::KeyboardPlatformLayout;
use crate::input::input_mapper::keys_text_shortcut;
use crate::input::input_mapper::ActionKeys;
use crate::input::keyboard::Key;
use crate::message_prelude::*;
use crate::Color;
use derivative::*;
use serde::{Deserialize, Serialize};
use std::rc::Rc;
pub trait PropertyHolder {
fn properties(&self) -> Layout {
Layout::WidgetLayout(WidgetLayout::default())
}
fn register_properties(&self, responses: &mut VecDeque<Message>, layout_target: LayoutTarget) {
responses.push_back(
LayoutMessage::SendLayout {
layout: self.properties(),
layout_target,
}
.into(),
)
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub enum Layout {
WidgetLayout(WidgetLayout),
MenuLayout(MenuLayout),
}
impl Layout {
pub fn unwrap_widget_layout(self, action_input_mapping: &impl Fn(&MessageDiscriminant) -> Vec<Vec<Key>>, keyboard_platform: KeyboardPlatformLayout) -> WidgetLayout {
if let Layout::WidgetLayout(mut widget_layout) = self {
// Function used multiple times later in this code block to convert `ActionKeys::Action` to `ActionKeys::Keys` and append its shortcut to the tooltip
let apply_shortcut_to_tooltip = |tooltip_shortcut: &mut ActionKeys, tooltip: &mut String| {
tooltip_shortcut.to_keys(action_input_mapping);
if let ActionKeys::Keys(keys) = tooltip_shortcut {
let shortcut_text = keys_text_shortcut(keys, keyboard_platform);
if !shortcut_text.is_empty() {
if !tooltip.is_empty() {
tooltip.push(' ');
}
tooltip.push('(');
tooltip.push_str(&shortcut_text);
tooltip.push(')');
}
}
};
// Go through each widget to convert `ActionKeys::Action` to `ActionKeys::Keys` and append the key combination to the widget tooltip
for widget_holder in &mut widget_layout.iter_mut() {
// Handle all the widgets that have tooltips
let mut tooltip_shortcut = match &mut widget_holder.widget {
Widget::CheckboxInput(widget) => Some((&mut widget.tooltip, &mut widget.tooltip_shortcut)),
Widget::ColorInput(widget) => Some((&mut widget.tooltip, &mut widget.tooltip_shortcut)),
Widget::IconButton(widget) => Some((&mut widget.tooltip, &mut widget.tooltip_shortcut)),
Widget::OptionalInput(widget) => Some((&mut widget.tooltip, &mut widget.tooltip_shortcut)),
_ => None,
};
if let Some((tooltip, Some(tooltip_shortcut))) = &mut tooltip_shortcut {
apply_shortcut_to_tooltip(tooltip_shortcut, tooltip);
}
// Handle RadioInput separately because its tooltips are children of the widget
if let Widget::RadioInput(radio_input) = &mut widget_holder.widget {
for radio_entry_data in &mut radio_input.entries {
if let RadioEntryData {
tooltip,
tooltip_shortcut: Some(tooltip_shortcut),
..
} = radio_entry_data
{
apply_shortcut_to_tooltip(tooltip_shortcut, tooltip);
}
}
}
}
widget_layout
} else {
panic!("Tried to unwrap layout as WidgetLayout. Got {:?}", self)
}
}
pub fn unwrap_menu_layout(self, action_input_mapping: &impl Fn(&MessageDiscriminant) -> Vec<Vec<Key>>, _keyboard_platform: KeyboardPlatformLayout) -> MenuLayout {
if let Layout::MenuLayout(mut menu_layout) = self {
for menu_column in &mut menu_layout.layout {
menu_column.children.fill_in_shortcut_actions_with_keys(action_input_mapping);
}
menu_layout
} else {
panic!("Tried to unwrap layout as MenuLayout. Got {:?}", self)
}
}
pub fn iter(&self) -> Box<dyn Iterator<Item = &WidgetHolder> + '_> {
match self {
Layout::MenuLayout(menu_layout) => Box::new(menu_layout.iter()),
Layout::WidgetLayout(widget_layout) => Box::new(widget_layout.iter()),
}
}
pub fn iter_mut(&mut self) -> Box<dyn Iterator<Item = &mut WidgetHolder> + '_> {
match self {
Layout::MenuLayout(menu_layout) => Box::new(menu_layout.iter_mut()),
Layout::WidgetLayout(widget_layout) => Box::new(widget_layout.iter_mut()),
}
}
}
impl Default for Layout {
fn default() -> Self {
Layout::WidgetLayout(WidgetLayout::default())
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default)]
pub struct MenuEntryGroups(pub Vec<Vec<MenuEntry>>);
impl MenuEntryGroups {
pub fn empty() -> Self {
Self(Vec::new())
}
pub fn fill_in_shortcut_actions_with_keys(&mut self, action_input_mapping: &impl Fn(&MessageDiscriminant) -> Vec<Vec<Key>>) {
let entries = self.0.iter_mut().flatten();
for entry in entries {
if let Some(action_keys) = &mut entry.shortcut {
action_keys.to_keys(action_input_mapping);
}
// Recursively do this for the children also
entry.children.fill_in_shortcut_actions_with_keys(action_input_mapping);
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct MenuEntry {
pub label: String,
pub icon: Option<String>,
pub children: MenuEntryGroups,
pub action: WidgetHolder,
pub shortcut: Option<ActionKeys>,
}
impl MenuEntry {
pub fn create_action(callback: impl Fn(&()) -> Message + 'static) -> WidgetHolder {
WidgetHolder::new(Widget::Invisible(Invisible {
on_update: WidgetCallback::new(callback),
}))
}
pub fn no_action() -> WidgetHolder {
MenuEntry::create_action(|_| Message::NoOp)
}
}
impl Default for MenuEntry {
fn default() -> Self {
Self {
action: MenuEntry::create_action(|_| DialogMessage::RequestComingSoonDialog { issue: None }.into()),
label: "".into(),
icon: None,
children: MenuEntryGroups::empty(),
shortcut: None,
}
}
}
#[derive(Debug, Default, Clone, Serialize, Deserialize, PartialEq)]
pub struct MenuColumn {
pub label: String,
pub children: MenuEntryGroups,
}
#[derive(Debug, Default, Clone, PartialEq, Serialize, Deserialize)]
pub struct MenuLayout {
pub layout: Vec<MenuColumn>,
}
impl MenuLayout {
pub fn new(layout: Vec<MenuColumn>) -> Self {
Self { layout }
}
pub fn iter(&self) -> impl Iterator<Item = &WidgetHolder> + '_ {
MenuLayoutIter {
stack: self.layout.iter().flat_map(|column| column.children.0.iter()).flat_map(|group| group.iter()).collect(),
}
}
pub fn iter_mut(&mut self) -> impl Iterator<Item = &mut WidgetHolder> + '_ {
MenuLayoutIterMut {
stack: self.layout.iter_mut().flat_map(|column| column.children.0.iter_mut()).flat_map(|group| group.iter_mut()).collect(),
}
}
}
#[derive(Debug, Default)]
pub struct MenuLayoutIter<'a> {
pub stack: Vec<&'a MenuEntry>,
}
impl<'a> Iterator for MenuLayoutIter<'a> {
type Item = &'a WidgetHolder;
fn next(&mut self) -> Option<Self::Item> {
match self.stack.pop() {
Some(menu_entry) => {
let more_entries = menu_entry.children.0.iter().flat_map(|entry| entry.iter());
self.stack.extend(more_entries);
Some(&menu_entry.action)
}
None => None,
}
}
}
pub struct MenuLayoutIterMut<'a> {
pub stack: Vec<&'a mut MenuEntry>,
}
impl<'a> Iterator for MenuLayoutIterMut<'a> {
type Item = &'a mut WidgetHolder;
fn next(&mut self) -> Option<Self::Item> {
match self.stack.pop() {
Some(menu_entry) => {
let more_entries = menu_entry.children.0.iter_mut().flat_map(|entry| entry.iter_mut());
self.stack.extend(more_entries);
Some(&mut menu_entry.action)
}
None => None,
}
}
}
#[derive(Debug, Default, Clone, Serialize, Deserialize, PartialEq)]
pub struct WidgetLayout {
pub layout: SubLayout,
}
impl WidgetLayout {
pub fn new(layout: SubLayout) -> Self {
Self { layout }
}
pub fn iter(&self) -> WidgetIter<'_> {
WidgetIter {
stack: self.layout.iter().collect(),
current_slice: None,
}
}
pub fn iter_mut(&mut self) -> WidgetIterMut<'_> {
WidgetIterMut {
stack: self.layout.iter_mut().collect(),
current_slice: None,
}
}
}
pub type SubLayout = Vec<LayoutGroup>;
#[remain::sorted]
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub enum LayoutGroup {
#[serde(rename = "column")]
Column {
#[serde(rename = "columnWidgets")]
widgets: Vec<WidgetHolder>,
},
#[serde(rename = "row")]
Row {
#[serde(rename = "rowWidgets")]
widgets: Vec<WidgetHolder>,
},
#[serde(rename = "section")]
Section { name: String, layout: SubLayout },
}
#[derive(Debug, Default)]
pub struct WidgetIter<'a> {
pub stack: Vec<&'a LayoutGroup>,
pub current_slice: Option<&'a [WidgetHolder]>,
}
impl<'a> Iterator for WidgetIter<'a> {
type Item = &'a WidgetHolder;
fn next(&mut self) -> Option<Self::Item> {
if let Some(item) = self.current_slice.and_then(|slice| slice.first()) {
self.current_slice = Some(&self.current_slice.unwrap()[1..]);
return Some(item);
}
match self.stack.pop() {
Some(LayoutGroup::Column { widgets }) => {
self.current_slice = Some(widgets);
self.next()
}
Some(LayoutGroup::Row { widgets }) => {
self.current_slice = Some(widgets);
self.next()
}
Some(LayoutGroup::Section { name: _, layout }) => {
for layout_row in layout {
self.stack.push(layout_row);
}
self.next()
}
None => None,
}
}
}
#[derive(Debug, Default)]
pub struct WidgetIterMut<'a> {
pub stack: Vec<&'a mut LayoutGroup>,
pub current_slice: Option<&'a mut [WidgetHolder]>,
}
impl<'a> Iterator for WidgetIterMut<'a> {
type Item = &'a mut WidgetHolder;
fn next(&mut self) -> Option<Self::Item> {
if let Some((first, rest)) = self.current_slice.take().and_then(|slice| slice.split_first_mut()) {
self.current_slice = Some(rest);
return Some(first);
};
match self.stack.pop() {
Some(LayoutGroup::Column { widgets }) => {
self.current_slice = Some(widgets);
self.next()
}
Some(LayoutGroup::Row { widgets }) => {
self.current_slice = Some(widgets);
self.next()
}
Some(LayoutGroup::Section { name: _, layout }) => {
for layout_row in layout {
self.stack.push(layout_row);
}
self.next()
}
None => None,
}
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct WidgetHolder {
#[serde(rename = "widgetId")]
pub widget_id: u64,
pub widget: Widget,
}
impl WidgetHolder {
pub fn new(widget: Widget) -> Self {
Self { widget_id: generate_uuid(), widget }
}
}
#[derive(Clone)]
pub struct WidgetCallback<T> {
pub callback: Rc<dyn Fn(&T) -> Message + 'static>,
}
impl<T> WidgetCallback<T> {
pub fn new(callback: impl Fn(&T) -> Message + 'static) -> Self {
Self { callback: Rc::new(callback) }
}
}
impl<T> Default for WidgetCallback<T> {
fn default() -> Self {
Self::new(|_| Message::NoOp)
}
}
#[remain::sorted]
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub enum Widget {
CheckboxInput(CheckboxInput),
ColorInput(ColorInput),
DropdownInput(DropdownInput),
FontInput(FontInput),
IconButton(IconButton),
IconLabel(IconLabel),
Invisible(Invisible),
NumberInput(NumberInput),
OptionalInput(OptionalInput),
PopoverButton(PopoverButton),
RadioInput(RadioInput),
Separator(Separator),
SwatchPairInput(SwatchPairInput),
TextAreaInput(TextAreaInput),
TextButton(TextButton),
TextInput(TextInput),
TextLabel(TextLabel),
}
#[derive(Clone, Default, Derivative, Serialize, Deserialize)]
#[derivative(Debug, PartialEq)]
pub struct CheckboxInput {
pub checked: bool,
pub icon: String,
pub tooltip: String,
#[serde(skip)]
pub tooltip_shortcut: Option<ActionKeys>,
// Callbacks
#[serde(skip)]
#[derivative(Debug = "ignore", PartialEq = "ignore")]
pub on_update: WidgetCallback<CheckboxInput>,
}
#[derive(Clone, Derivative, Serialize, Deserialize)]
#[derivative(Debug, PartialEq, Default)]
pub struct ColorInput {
pub value: Option<String>,
pub label: Option<String>,
#[serde(rename = "noTransparency")]
#[derivative(Default(value = "true"))]
pub no_transparency: bool,
pub disabled: bool,
pub tooltip: String,
#[serde(skip)]
pub tooltip_shortcut: Option<ActionKeys>,
// Callbacks
#[serde(skip)]
#[derivative(Debug = "ignore", PartialEq = "ignore")]
pub on_update: WidgetCallback<ColorInput>,
}
#[derive(Clone, Serialize, Deserialize, Derivative)]
#[derivative(Debug, PartialEq, Default)]
pub struct DropdownInput {
pub entries: DropdownInputEntries,
// This uses `u32` instead of `usize` since it will be serialized as a normal JS number (replace this with `usize` after switching to a Rust-based GUI)
#[serde(rename = "selectedIndex")]
pub selected_index: Option<u32>,
#[serde(rename = "drawIcon")]
pub draw_icon: bool,
#[derivative(Default(value = "true"))]
pub interactive: bool,
pub disabled: bool,
//
// Callbacks
// `on_update` exists on the `DropdownEntryData`, not this parent `DropdownInput`
}
pub type DropdownInputEntries = Vec<Vec<DropdownEntryData>>;
#[derive(Clone, Serialize, Deserialize, Derivative, Default)]
#[derivative(Debug, PartialEq)]
pub struct DropdownEntryData {
pub value: String,
pub label: String,
pub icon: String,
pub shortcut: Vec<String>,
#[serde(rename = "shortcutRequiresLock")]
pub shortcut_requires_lock: bool,
pub disabled: bool,
pub children: DropdownInputEntries,
// Callbacks
#[serde(skip)]
#[derivative(Debug = "ignore", PartialEq = "ignore")]
pub on_update: WidgetCallback<()>,
}
#[derive(Clone, Serialize, Deserialize, Derivative)]
#[derivative(Debug, PartialEq, Default)]
pub struct FontInput {
#[serde(rename = "fontFamily")]
pub font_family: String,
#[serde(rename = "fontStyle")]
pub font_style: String,
#[serde(rename = "isStyle")]
pub is_style_picker: bool,
pub disabled: bool,
// Callbacks
#[serde(skip)]
#[derivative(Debug = "ignore", PartialEq = "ignore")]
pub on_update: WidgetCallback<FontInput>,
}
#[derive(Clone, Default, Derivative, Serialize, Deserialize)]
#[derivative(Debug, PartialEq)]
pub struct IconButton {
pub icon: String,
pub size: u32, // TODO: Convert to an `IconSize` enum
pub active: bool,
pub tooltip: String,
#[serde(skip)]
pub tooltip_shortcut: Option<ActionKeys>,
// Callbacks
#[serde(skip)]
#[derivative(Debug = "ignore", PartialEq = "ignore")]
pub on_update: WidgetCallback<IconButton>,
}
#[derive(Clone, Serialize, Deserialize, Derivative, Debug, Default, PartialEq, Eq)]
pub struct IconLabel {
pub icon: String,
#[serde(rename = "iconStyle")]
pub icon_style: IconStyle,
}
#[derive(Clone, Serialize, Deserialize, Derivative, Debug, Default, PartialEq, Eq)]
pub enum IconStyle {
#[default]
Normal,
Node,
}
/// This widget allows for the flexible use of the layout system.
/// In a custom layout, one can define a widget that is just used to trigger code on the backend.
/// This is used in MenuLayout to pipe the triggering of messages from the frontend to backend.
#[derive(Clone, Serialize, Deserialize, Derivative, Default)]
#[derivative(Debug, PartialEq)]
pub struct Invisible {
#[serde(skip)]
#[derivative(Debug = "ignore", PartialEq = "ignore")]
pub on_update: WidgetCallback<()>,
}
#[derive(Clone, Serialize, Deserialize, Derivative)]
#[derivative(Debug, PartialEq, Default)]
pub struct NumberInput {
pub label: String,
pub value: Option<f64>,
pub min: Option<f64>,
pub max: Option<f64>,
#[serde(rename = "isInteger")]
pub is_integer: bool,
#[serde(rename = "displayDecimalPlaces")]
#[derivative(Default(value = "3"))]
pub display_decimal_places: u32,
pub unit: String,
#[serde(rename = "unitIsHiddenWhenEditing")]
#[derivative(Default(value = "true"))]
pub unit_is_hidden_when_editing: bool,
#[serde(rename = "incrementBehavior")]
pub increment_behavior: NumberInputIncrementBehavior,
#[serde(rename = "incrementFactor")]
#[derivative(Default(value = "1."))]
pub increment_factor: f64,
pub disabled: bool,
// Callbacks
#[serde(skip)]
#[derivative(Debug = "ignore", PartialEq = "ignore")]
pub on_update: WidgetCallback<NumberInput>,
#[serde(skip)]
#[derivative(Debug = "ignore", PartialEq = "ignore")]
pub increment_callback_increase: WidgetCallback<NumberInput>,
#[serde(skip)]
#[derivative(Debug = "ignore", PartialEq = "ignore")]
pub increment_callback_decrease: WidgetCallback<NumberInput>,
}
#[derive(Clone, Serialize, Deserialize, Debug, Default, PartialEq, Eq)]
pub enum NumberInputIncrementBehavior {
#[default]
Add,
Multiply,
Callback,
}
#[derive(Clone, Default, Derivative, Serialize, Deserialize)]
#[derivative(Debug, PartialEq)]
pub struct OptionalInput {
pub checked: bool,
pub icon: String,
pub tooltip: String,
#[serde(skip)]
pub tooltip_shortcut: Option<ActionKeys>,
// Callbacks
#[serde(skip)]
#[derivative(Debug = "ignore", PartialEq = "ignore")]
pub on_update: WidgetCallback<OptionalInput>,
}
#[derive(Clone, Serialize, Deserialize, Derivative)]
#[derivative(Debug, PartialEq, Default)]
pub struct PopoverButton {
pub icon: Option<String>,
// Body
pub header: String,
pub text: String,
}
#[derive(Clone, Default, Derivative, Serialize, Deserialize)]
#[derivative(Debug, PartialEq)]
pub struct RadioInput {
pub entries: Vec<RadioEntryData>,
// This uses `u32` instead of `usize` since it will be serialized as a normal JS number (replace this with `usize` after switching to a Rust-based GUI)
#[serde(rename = "selectedIndex")]
pub selected_index: u32,
}
#[derive(Clone, Default, Derivative, Serialize, Deserialize)]
#[derivative(Debug, PartialEq)]
pub struct RadioEntryData {
pub value: String,
pub label: String,
pub icon: String,
pub tooltip: String,
#[serde(skip)]
pub tooltip_shortcut: Option<ActionKeys>,
// Callbacks
#[serde(skip)]
#[derivative(Debug = "ignore", PartialEq = "ignore")]
pub on_update: WidgetCallback<()>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct Separator {
pub direction: SeparatorDirection,
#[serde(rename = "type")]
pub separator_type: SeparatorType,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub enum SeparatorDirection {
Horizontal,
Vertical,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub enum SeparatorType {
Related,
Unrelated,
Section,
List,
}
#[derive(Clone, Serialize, Deserialize, Derivative)]
#[derivative(Debug, PartialEq, Default)]
pub struct SwatchPairInput {
pub primary: Color,
pub secondary: Color,
}
#[derive(Clone, Serialize, Deserialize, Derivative)]
#[derivative(Debug, PartialEq, Default)]
pub struct TextAreaInput {
pub value: String,
pub label: Option<String>,
pub disabled: bool,
// Callbacks
#[serde(skip)]
#[derivative(Debug = "ignore", PartialEq = "ignore")]
pub on_update: WidgetCallback<TextAreaInput>,
}
#[derive(Clone, Serialize, Deserialize, Derivative, Default)]
#[derivative(Debug, PartialEq)]
#[serde(rename_all(serialize = "camelCase", deserialize = "camelCase"))]
pub struct TextButton {
pub label: String,
pub emphasized: bool,
#[serde(rename = "minWidth")]
pub min_width: u32,
pub disabled: bool,
// Callbacks
#[serde(skip)]
#[derivative(Debug = "ignore", PartialEq = "ignore")]
pub on_update: WidgetCallback<TextButton>,
}
#[derive(Clone, Serialize, Deserialize, Derivative)]
#[derivative(Debug, PartialEq, Default)]
pub struct TextInput {
pub value: String,
pub label: Option<String>,
pub disabled: bool,
// Callbacks
#[serde(skip)]
#[derivative(Debug = "ignore", PartialEq = "ignore")]
pub on_update: WidgetCallback<TextInput>,
}
#[derive(Clone, Serialize, Deserialize, Derivative, Debug, PartialEq, Eq, Default)]
pub struct TextLabel {
pub bold: bool,
pub italic: bool,
#[serde(rename = "tableAlign")]
pub table_align: bool,
pub multiline: bool,
// Body
pub value: String,
}

View File

@@ -1,99 +1,12 @@
extern crate graphite_proc_macros;
pub mod communication;
// `macro_use` puts these macros into scope for all descendant code files
#[macro_use]
pub mod misc;
mod macros;
pub mod application;
pub mod consts;
pub mod debug;
pub mod dialog;
pub mod document;
pub mod frontend;
pub mod input;
pub mod layout;
pub mod viewport_tools;
pub mod workspace;
#[doc(inline)]
pub use graphene::color::Color;
#[doc(inline)]
pub use graphene::document::Document as SvgDocument;
#[doc(inline)]
pub use graphene::LayerId;
#[doc(inline)]
pub use misc::EditorError;
use communication::dispatcher::Dispatcher;
use message_prelude::*;
// TODO: serialize with serde to save the current editor state
pub struct Editor {
dispatcher: Dispatcher,
}
impl Editor {
/// Construct a new editor instance.
/// Remember to provide a random seed with `editor::communication::set_uuid_seed(seed)` before any editors can be used.
pub fn new() -> Self {
Self { dispatcher: Dispatcher::new() }
}
pub fn handle_message<T: Into<Message>>(&mut self, message: T) -> Vec<FrontendMessage> {
self.dispatcher.handle_message(message);
let mut responses = Vec::new();
std::mem::swap(&mut responses, &mut self.dispatcher.responses);
responses
}
}
impl Default for Editor {
fn default() -> Self {
Self::new()
}
}
pub mod message_prelude {
pub use crate::communication::broadcast_message::{BroadcastMessage, BroadcastMessageDiscriminant, BroadcastSignal, BroadcastSignalDiscriminant};
pub use crate::communication::generate_uuid;
pub use crate::communication::message::{AsMessage, Message, MessageDiscriminant};
pub use crate::communication::message_handler::{ActionList, MessageHandler};
pub use crate::document::clipboards::Clipboard;
pub use crate::LayerId;
pub use crate::debug::{DebugMessage, DebugMessageDiscriminant};
pub use crate::dialog::messages::*;
pub use crate::document::{ArtboardMessage, ArtboardMessageDiscriminant};
pub use crate::document::{DocumentMessage, DocumentMessageDiscriminant};
pub use crate::document::{MenuBarMessage, MenuBarMessageDiscriminant};
pub use crate::document::{MovementMessage, MovementMessageDiscriminant};
pub use crate::document::{OverlaysMessage, OverlaysMessageDiscriminant};
pub use crate::document::{PortfolioMessage, PortfolioMessageDiscriminant};
pub use crate::document::{PropertiesPanelMessage, PropertiesPanelMessageDiscriminant};
pub use crate::document::{TransformLayerMessage, TransformLayerMessageDiscriminant};
pub use crate::frontend::{FrontendMessage, FrontendMessageDiscriminant};
pub use crate::input::{InputMapperMessage, InputMapperMessageDiscriminant, InputPreprocessorMessage, InputPreprocessorMessageDiscriminant};
pub use crate::layout::{LayoutMessage, LayoutMessageDiscriminant};
pub use crate::misc::derivable_custom_traits::{ToDiscriminant, TransitiveChild};
pub use crate::viewport_tools::tool_message::{ToolMessage, ToolMessageDiscriminant};
pub use crate::viewport_tools::tools::artboard_tool::{ArtboardToolMessage, ArtboardToolMessageDiscriminant};
pub use crate::viewport_tools::tools::ellipse_tool::{EllipseToolMessage, EllipseToolMessageDiscriminant};
pub use crate::viewport_tools::tools::eyedropper_tool::{EyedropperToolMessage, EyedropperToolMessageDiscriminant};
pub use crate::viewport_tools::tools::fill_tool::{FillToolMessage, FillToolMessageDiscriminant};
pub use crate::viewport_tools::tools::freehand_tool::{FreehandToolMessage, FreehandToolMessageDiscriminant};
pub use crate::viewport_tools::tools::gradient_tool::{GradientToolMessage, GradientToolMessageDiscriminant};
pub use crate::viewport_tools::tools::line_tool::{LineToolMessage, LineToolMessageDiscriminant};
pub use crate::viewport_tools::tools::navigate_tool::{NavigateToolMessage, NavigateToolMessageDiscriminant};
pub use crate::viewport_tools::tools::path_tool::{PathToolMessage, PathToolMessageDiscriminant};
pub use crate::viewport_tools::tools::pen_tool::{PenToolMessage, PenToolMessageDiscriminant};
pub use crate::viewport_tools::tools::rectangle_tool::{RectangleToolMessage, RectangleToolMessageDiscriminant};
pub use crate::viewport_tools::tools::select_tool::{SelectToolMessage, SelectToolMessageDiscriminant};
pub use crate::viewport_tools::tools::shape_tool::{ShapeToolMessage, ShapeToolMessageDiscriminant};
pub use crate::viewport_tools::tools::spline_tool::{SplineToolMessage, SplineToolMessageDiscriminant};
pub use crate::viewport_tools::tools::text_tool::{TextToolMessage, TextToolMessageDiscriminant};
pub use crate::workspace::{WorkspaceMessage, WorkspaceMessageDiscriminant};
pub use graphite_proc_macros::*;
pub use std::collections::VecDeque;
}
pub mod dispatcher;
pub mod messages;
pub mod test_utils;
pub mod utility_traits;

View File

@@ -43,13 +43,13 @@ macro_rules! actions {
/// ```
macro_rules! advertise_actions {
($($v:expr),* $(,)?) => {
fn actions(&self) -> $crate::communication::message_handler::ActionList {
fn actions(&self) -> $crate::utility_traits::ActionList {
actions!($($v),*)
}
};
($name:ident; $($v:ident),* $(,)?) => {
fn actions(&self) -> $crate::communication::message_handler::ActionList {
fn actions(&self) -> $crate::utility_traits::ActionList {
actions!($name; $($v),*)
}
}

View File

@@ -0,0 +1,11 @@
use crate::messages::prelude::*;
use serde::{Deserialize, Serialize};
#[derive(PartialEq, Eq, Clone, Debug, Serialize, Deserialize, Hash)]
#[impl_message(Message, BroadcastMessage, TriggerEvent)]
pub enum BroadcastEvent {
DocumentIsDirty,
ToolAbort,
SelectionChanged,
}

View File

@@ -0,0 +1,23 @@
use crate::messages::prelude::*;
use serde::{Deserialize, Serialize};
#[remain::sorted]
#[impl_message(Message, Broadcast)]
#[derive(PartialEq, Clone, Debug, Serialize, Deserialize)]
pub enum BroadcastMessage {
// Sub-messages
#[remain::unsorted]
#[child]
TriggerEvent(BroadcastEvent),
// Messages
SubscribeEvent {
on: BroadcastEvent,
send: Box<Message>,
},
UnsubscribeEvent {
on: BroadcastEvent,
message: Box<Message>,
},
}

View File

@@ -0,0 +1,32 @@
use crate::messages::prelude::*;
use std::collections::HashMap;
#[derive(Debug, Clone, Default)]
pub struct BroadcastMessageHandler {
listeners: HashMap<BroadcastEvent, Vec<Message>>,
}
impl MessageHandler<BroadcastMessage, ()> for BroadcastMessageHandler {
#[remain::check]
fn process_message(&mut self, message: BroadcastMessage, _data: (), responses: &mut VecDeque<Message>) {
#[remain::sorted]
match message {
// Sub-messages
#[remain::unsorted]
BroadcastMessage::TriggerEvent(event) => {
for message in self.listeners.entry(event).or_default() {
responses.push_front(message.clone())
}
}
// Messages
BroadcastMessage::SubscribeEvent { on, send } => self.listeners.entry(on).or_default().push(*send),
BroadcastMessage::UnsubscribeEvent { on, message } => self.listeners.entry(on).or_default().retain(|msg| *msg != *message),
}
}
fn actions(&self) -> ActionList {
vec![]
}
}

View File

@@ -0,0 +1,9 @@
mod broadcast_message;
mod broadcast_message_handler;
pub mod broadcast_event;
#[doc(inline)]
pub use broadcast_message::{BroadcastMessage, BroadcastMessageDiscriminant};
#[doc(inline)]
pub use broadcast_message_handler::BroadcastMessageHandler;

View File

@@ -1,4 +1,4 @@
use crate::message_prelude::*;
use crate::messages::prelude::*;
use serde::{Deserialize, Serialize};
@@ -10,11 +10,3 @@ pub enum DebugMessage {
MessageNames,
MessageContents,
}
#[derive(Debug, Default, Clone, Copy)]
pub enum LoggingMessages {
#[default]
Off,
Names,
Contents,
}

View File

@@ -1,16 +1,16 @@
use crate::debug::debug_message::LoggingMessages;
use crate::message_prelude::*;
use super::utility_types::MessageLoggingVerbosity;
use crate::messages::prelude::*;
use std::collections::VecDeque;
#[derive(Debug, Default)]
pub struct DebugMessageHandler {
pub logging_messages_mode: LoggingMessages,
pub message_logging_verbosity: MessageLoggingVerbosity,
}
impl MessageHandler<DebugMessage, ()> for DebugMessageHandler {
#[remain::check]
fn process_action(&mut self, message: DebugMessage, _data: (), responses: &mut VecDeque<Message>) {
fn process_message(&mut self, message: DebugMessage, _data: (), responses: &mut VecDeque<Message>) {
match message {
DebugMessage::ToggleTraceLogs => {
if let log::LevelFilter::Debug = log::max_level() {
@@ -23,19 +23,19 @@ impl MessageHandler<DebugMessage, ()> for DebugMessageHandler {
responses.push_back(MenuBarMessage::SendLayout.into());
}
DebugMessage::MessageOff => {
self.logging_messages_mode = LoggingMessages::Off;
self.message_logging_verbosity = MessageLoggingVerbosity::Off;
// Refresh the checkmark beside the menu entry for this
responses.push_back(MenuBarMessage::SendLayout.into());
}
DebugMessage::MessageNames => {
self.logging_messages_mode = LoggingMessages::Names;
self.message_logging_verbosity = MessageLoggingVerbosity::Names;
// Refresh the checkmark beside the menu entry for this
responses.push_back(MenuBarMessage::SendLayout.into());
}
DebugMessage::MessageContents => {
self.logging_messages_mode = LoggingMessages::Contents;
self.message_logging_verbosity = MessageLoggingVerbosity::Contents;
// Refresh the checkmark beside the menu entry for this
responses.push_back(MenuBarMessage::SendLayout.into());

View File

@@ -1,7 +1,8 @@
pub mod debug_message;
mod debug_message;
mod debug_message_handler;
pub mod utility_types;
#[doc(inline)]
pub use debug_message::{DebugMessage, DebugMessageDiscriminant};
#[doc(inline)]

View File

@@ -0,0 +1,7 @@
#[derive(Debug, Default, Clone, Copy)]
pub enum MessageLoggingVerbosity {
#[default]
Off,
Names,
Contents,
}

View File

@@ -1,5 +1,6 @@
use super::{ExportDialogUpdate, NewDocumentDialogUpdate};
use crate::message_prelude::*;
use super::export_dialog::ExportDialogMessage;
use super::new_document_dialog::NewDocumentDialogMessage;
use crate::messages::prelude::*;
use serde::{Deserialize, Serialize};
@@ -10,10 +11,10 @@ pub enum DialogMessage {
// Sub-messages
#[remain::unsorted]
#[child]
ExportDialog(ExportDialogUpdate),
ExportDialog(ExportDialogMessage),
#[remain::unsorted]
#[child]
NewDocumentDialog(NewDocumentDialogUpdate),
NewDocumentDialog(NewDocumentDialogMessage),
// Messages
CloseAllDocumentsWithConfirmation,

View File

@@ -1,27 +1,26 @@
use super::*;
use crate::document::PortfolioMessageHandler;
use crate::layout::layout_message::LayoutTarget;
use crate::layout::widgets::PropertyHolder;
use crate::message_prelude::*;
use super::simple_dialogs::{self, AboutGraphiteDialog, ComingSoonDialog};
use crate::messages::layout::utility_types::layout_widget::PropertyHolder;
use crate::messages::layout::utility_types::misc::LayoutTarget;
use crate::messages::prelude::*;
#[derive(Debug, Default, Clone)]
pub struct DialogMessageHandler {
export_dialog: Export,
new_document_dialog: NewDocument,
export_dialog: ExportDialogMessageHandler,
new_document_dialog: NewDocumentDialogMessageHandler,
}
impl MessageHandler<DialogMessage, &PortfolioMessageHandler> for DialogMessageHandler {
#[remain::check]
fn process_action(&mut self, message: DialogMessage, portfolio: &PortfolioMessageHandler, responses: &mut VecDeque<Message>) {
fn process_message(&mut self, message: DialogMessage, portfolio: &PortfolioMessageHandler, responses: &mut VecDeque<Message>) {
#[remain::sorted]
match message {
#[remain::unsorted]
DialogMessage::ExportDialog(message) => self.export_dialog.process_action(message, (), responses),
DialogMessage::ExportDialog(message) => self.export_dialog.process_message(message, (), responses),
#[remain::unsorted]
DialogMessage::NewDocumentDialog(message) => self.new_document_dialog.process_action(message, (), responses),
DialogMessage::NewDocumentDialog(message) => self.new_document_dialog.process_message(message, (), responses),
DialogMessage::CloseAllDocumentsWithConfirmation => {
let dialog = dialogs::CloseAllDocuments;
let dialog = simple_dialogs::CloseAllDocumentsDialog;
dialog.register_properties(responses, LayoutTarget::DialogDetails);
responses.push_back(FrontendMessage::DisplayDialog { icon: "Copy".to_string() }.into());
}
@@ -32,7 +31,7 @@ impl MessageHandler<DialogMessage, &PortfolioMessageHandler> for DialogMessageHa
}
}
DialogMessage::DisplayDialogError { title, description } => {
let dialog = dialogs::Error { title, description };
let dialog = simple_dialogs::ErrorDialog { title, description };
dialog.register_properties(responses, LayoutTarget::DialogDetails);
responses.push_back(FrontendMessage::DisplayDialog { icon: "Warning".to_string() }.into());
}
@@ -45,13 +44,13 @@ impl MessageHandler<DialogMessage, &PortfolioMessageHandler> for DialogMessageHa
);
}
DialogMessage::RequestAboutGraphiteDialogWithLocalizedCommitDate { localized_commit_date } => {
let about_graphite = AboutGraphite { localized_commit_date };
let about_graphite = AboutGraphiteDialog { localized_commit_date };
about_graphite.register_properties(responses, LayoutTarget::DialogDetails);
responses.push_back(FrontendMessage::DisplayDialog { icon: "GraphiteLogo".to_string() }.into());
}
DialogMessage::RequestComingSoonDialog { issue } => {
let coming_soon = ComingSoon { issue };
let coming_soon = ComingSoonDialog { issue };
coming_soon.register_properties(responses, LayoutTarget::DialogDetails);
responses.push_back(FrontendMessage::DisplayDialog { icon: "Warning".to_string() }.into());
}
@@ -78,7 +77,7 @@ impl MessageHandler<DialogMessage, &PortfolioMessageHandler> for DialogMessageHa
})
.collect();
self.export_dialog = Export {
self.export_dialog = ExportDialogMessageHandler {
file_name: document.name.clone(),
scale_factor: 1.,
artboards,
@@ -90,7 +89,7 @@ impl MessageHandler<DialogMessage, &PortfolioMessageHandler> for DialogMessageHa
}
}
DialogMessage::RequestNewDocumentDialog => {
self.new_document_dialog = NewDocument {
self.new_document_dialog = NewDocumentDialogMessageHandler {
name: portfolio.generate_new_document_name(),
infinite: true,
dimensions: glam::UVec2::new(1920, 1080),

View File

@@ -0,0 +1,15 @@
use crate::messages::frontend::utility_types::{ExportBounds, FileType};
use crate::messages::prelude::*;
use serde::{Deserialize, Serialize};
#[impl_message(Message, DialogMessage, ExportDialog)]
#[derive(PartialEq, Clone, Debug, Serialize, Deserialize)]
pub enum ExportDialogMessage {
FileName(String),
FileType(FileType),
ScaleFactor(f64),
ExportBounds(ExportBounds),
Submit,
}

View File

@@ -1,15 +1,19 @@
use super::ExportDialogMessage;
use crate::messages::frontend::utility_types::{ExportBounds, FileType};
use crate::messages::layout::utility_types::layout_widget::{Layout, LayoutGroup, PropertyHolder, Widget, WidgetCallback, WidgetHolder, WidgetLayout};
use crate::messages::layout::utility_types::misc::LayoutTarget;
use crate::messages::layout::utility_types::widgets::button_widgets::TextButton;
use crate::messages::layout::utility_types::widgets::input_widgets::{DropdownEntryData, DropdownInput, NumberInput, RadioEntryData, RadioInput, TextInput};
use crate::messages::layout::utility_types::widgets::label_widgets::{Separator, SeparatorDirection, SeparatorType, TextLabel};
use crate::messages::prelude::*;
use graphene::LayerId;
use std::collections::HashMap;
use crate::frontend::utility_types::{ExportBounds, FileType};
use crate::layout::layout_message::LayoutTarget;
use crate::layout::widgets::*;
use crate::message_prelude::*;
use serde::{Deserialize, Serialize};
/// A dialog to allow users to customize their file export.
#[derive(Debug, Clone, Default)]
pub struct Export {
pub struct ExportDialogMessageHandler {
pub file_name: String,
pub file_type: FileType,
pub scale_factor: f64,
@@ -18,7 +22,32 @@ pub struct Export {
pub has_selection: bool,
}
impl PropertyHolder for Export {
impl MessageHandler<ExportDialogMessage, ()> for ExportDialogMessageHandler {
fn process_message(&mut self, message: ExportDialogMessage, _data: (), responses: &mut VecDeque<Message>) {
match message {
ExportDialogMessage::FileName(name) => self.file_name = name,
ExportDialogMessage::FileType(export_type) => self.file_type = export_type,
ExportDialogMessage::ScaleFactor(x) => self.scale_factor = x,
ExportDialogMessage::ExportBounds(export_area) => self.bounds = export_area,
ExportDialogMessage::Submit => responses.push_front(
DocumentMessage::ExportDocument {
file_name: self.file_name.clone(),
file_type: self.file_type,
scale_factor: self.scale_factor,
bounds: self.bounds,
}
.into(),
),
}
self.register_properties(responses, LayoutTarget::DialogDetails);
}
advertise_actions! {ExportDialogUpdate;}
}
impl PropertyHolder for ExportDialogMessageHandler {
fn properties(&self) -> Layout {
let file_name = vec![
WidgetHolder::new(Widget::TextLabel(TextLabel {
@@ -32,7 +61,7 @@ impl PropertyHolder for Export {
})),
WidgetHolder::new(Widget::TextInput(TextInput {
value: self.file_name.clone(),
on_update: WidgetCallback::new(|text_input: &TextInput| ExportDialogUpdate::FileName(text_input.value.clone()).into()),
on_update: WidgetCallback::new(|text_input: &TextInput| ExportDialogMessage::FileName(text_input.value.clone()).into()),
..Default::default()
})),
];
@@ -41,7 +70,7 @@ impl PropertyHolder for Export {
.into_iter()
.map(|(val, name)| RadioEntryData {
label: name.into(),
on_update: WidgetCallback::new(move |_| ExportDialogUpdate::FileType(val).into()),
on_update: WidgetCallback::new(move |_| ExportDialogMessage::FileType(val).into()),
..RadioEntryData::default()
})
.collect();
@@ -73,7 +102,7 @@ impl PropertyHolder for Export {
.into_iter()
.map(|(val, name, disabled)| DropdownEntryData {
label: name,
on_update: WidgetCallback::new(move |_| ExportDialogUpdate::ExportBounds(val).into()),
on_update: WidgetCallback::new(move |_| ExportDialogMessage::ExportBounds(val).into()),
disabled,
..Default::default()
})
@@ -112,7 +141,7 @@ impl PropertyHolder for Export {
unit: " ".into(),
min: Some(0.),
disabled: self.file_type == FileType::Svg,
on_update: WidgetCallback::new(|number_input: &NumberInput| ExportDialogUpdate::ScaleFactor(number_input.value.unwrap()).into()),
on_update: WidgetCallback::new(|number_input: &NumberInput| ExportDialogMessage::ScaleFactor(number_input.value.unwrap()).into()),
..NumberInput::default()
})),
];
@@ -124,7 +153,7 @@ impl PropertyHolder for Export {
emphasized: true,
on_update: WidgetCallback::new(|_| {
DialogMessage::CloseDialogAndThen {
followups: vec![ExportDialogUpdate::Submit.into()],
followups: vec![ExportDialogMessage::Submit.into()],
}
.into()
}),
@@ -154,39 +183,3 @@ impl PropertyHolder for Export {
]))
}
}
#[impl_message(Message, DialogMessage, ExportDialog)]
#[derive(PartialEq, Clone, Debug, Serialize, Deserialize)]
pub enum ExportDialogUpdate {
FileName(String),
FileType(FileType),
ScaleFactor(f64),
ExportBounds(ExportBounds),
Submit,
}
impl MessageHandler<ExportDialogUpdate, ()> for Export {
fn process_action(&mut self, action: ExportDialogUpdate, _data: (), responses: &mut VecDeque<Message>) {
match action {
ExportDialogUpdate::FileName(name) => self.file_name = name,
ExportDialogUpdate::FileType(export_type) => self.file_type = export_type,
ExportDialogUpdate::ScaleFactor(x) => self.scale_factor = x,
ExportDialogUpdate::ExportBounds(export_area) => self.bounds = export_area,
ExportDialogUpdate::Submit => responses.push_front(
DocumentMessage::ExportDocument {
file_name: self.file_name.clone(),
file_type: self.file_type,
scale_factor: self.scale_factor,
bounds: self.bounds,
}
.into(),
),
}
self.register_properties(responses, LayoutTarget::DialogDetails);
}
advertise_actions! {ExportDialogUpdate;}
}

View File

@@ -0,0 +1,7 @@
mod export_dialog_message;
mod export_dialog_message_handler;
#[doc(inline)]
pub use export_dialog_message::{ExportDialogMessage, ExportDialogMessageDiscriminant};
#[doc(inline)]
pub use export_dialog_message_handler::ExportDialogMessageHandler;

View File

@@ -0,0 +1,18 @@
//! Handles modal dialogs that appear as floating menus in the center of the editor window.
//!
//! Dialogs are represented as structs that implement the `PropertyHolder` trait.
//!
//! To open a dialog, call the function `register_properties` on the dialog struct with `responses` and the `LayoutTarget::DialogDetails` enum variant.
//! Then dialog can be opened by sending the `FrontendMessage::DisplayDialog` message;
mod dialog_message;
mod dialog_message_handler;
pub mod export_dialog;
pub mod new_document_dialog;
pub mod simple_dialogs;
#[doc(inline)]
pub use dialog_message::{DialogMessage, DialogMessageDiscriminant};
#[doc(inline)]
pub use dialog_message_handler::DialogMessageHandler;

View File

@@ -0,0 +1,7 @@
mod new_document_dialog_message;
mod new_document_dialog_message_handler;
#[doc(inline)]
pub use new_document_dialog_message::{NewDocumentDialogMessage, NewDocumentDialogMessageDiscriminant};
#[doc(inline)]
pub use new_document_dialog_message_handler::NewDocumentDialogMessageHandler;

View File

@@ -0,0 +1,14 @@
use crate::messages::prelude::*;
use serde::{Deserialize, Serialize};
#[impl_message(Message, DialogMessage, NewDocumentDialog)]
#[derive(PartialEq, Clone, Debug, Serialize, Deserialize)]
pub enum NewDocumentDialogMessage {
Name(String),
Infinite(bool),
DimensionsX(f64),
DimensionsY(f64),
Submit,
}

View File

@@ -1,19 +1,53 @@
use crate::layout::layout_message::LayoutTarget;
use crate::layout::widgets::*;
use crate::message_prelude::*;
use super::NewDocumentDialogMessage;
use crate::messages::layout::utility_types::layout_widget::{Layout, LayoutGroup, PropertyHolder, Widget, WidgetCallback, WidgetHolder, WidgetLayout};
use crate::messages::layout::utility_types::misc::LayoutTarget;
use crate::messages::layout::utility_types::widgets::button_widgets::TextButton;
use crate::messages::layout::utility_types::widgets::input_widgets::{CheckboxInput, NumberInput, TextInput};
use crate::messages::layout::utility_types::widgets::label_widgets::{Separator, SeparatorDirection, SeparatorType, TextLabel};
use crate::messages::prelude::*;
use glam::UVec2;
use serde::{Deserialize, Serialize};
/// A dialog to allow users to set some initial options about a new document.
#[derive(Debug, Clone, Default)]
pub struct NewDocument {
pub struct NewDocumentDialogMessageHandler {
pub name: String,
pub infinite: bool,
pub dimensions: UVec2,
}
impl PropertyHolder for NewDocument {
impl MessageHandler<NewDocumentDialogMessage, ()> for NewDocumentDialogMessageHandler {
fn process_message(&mut self, message: NewDocumentDialogMessage, _data: (), responses: &mut VecDeque<Message>) {
match message {
NewDocumentDialogMessage::Name(name) => self.name = name,
NewDocumentDialogMessage::Infinite(infinite) => self.infinite = infinite,
NewDocumentDialogMessage::DimensionsX(x) => self.dimensions.x = x as u32,
NewDocumentDialogMessage::DimensionsY(y) => self.dimensions.y = y as u32,
NewDocumentDialogMessage::Submit => {
responses.push_back(PortfolioMessage::NewDocumentWithName { name: self.name.clone() }.into());
if !self.infinite && self.dimensions.x > 0 && self.dimensions.y > 0 {
responses.push_back(
ArtboardMessage::AddArtboard {
id: None,
position: (0., 0.),
size: (self.dimensions.x as f64, self.dimensions.y as f64),
}
.into(),
);
responses.push_back(DocumentMessage::ZoomCanvasToFitAll.into());
}
}
}
self.register_properties(responses, LayoutTarget::DialogDetails);
}
advertise_actions! {NewDocumentDialogUpdate;}
}
impl PropertyHolder for NewDocumentDialogMessageHandler {
fn properties(&self) -> Layout {
let title = vec![WidgetHolder::new(Widget::TextLabel(TextLabel {
value: "New document".into(),
@@ -33,7 +67,7 @@ impl PropertyHolder for NewDocument {
})),
WidgetHolder::new(Widget::TextInput(TextInput {
value: self.name.clone(),
on_update: WidgetCallback::new(|text_input: &TextInput| NewDocumentDialogUpdate::Name(text_input.value.clone()).into()),
on_update: WidgetCallback::new(|text_input: &TextInput| NewDocumentDialogMessage::Name(text_input.value.clone()).into()),
..Default::default()
})),
];
@@ -51,7 +85,7 @@ impl PropertyHolder for NewDocument {
WidgetHolder::new(Widget::CheckboxInput(CheckboxInput {
checked: self.infinite,
icon: "Checkmark".to_string(),
on_update: WidgetCallback::new(|checkbox_input: &CheckboxInput| NewDocumentDialogUpdate::Infinite(checkbox_input.checked).into()),
on_update: WidgetCallback::new(|checkbox_input: &CheckboxInput| NewDocumentDialogMessage::Infinite(checkbox_input.checked).into()),
..Default::default()
})),
];
@@ -73,7 +107,7 @@ impl PropertyHolder for NewDocument {
disabled: self.infinite,
is_integer: true,
min: Some(0.),
on_update: WidgetCallback::new(|number_input: &NumberInput| NewDocumentDialogUpdate::DimensionsX(number_input.value.unwrap()).into()),
on_update: WidgetCallback::new(|number_input: &NumberInput| NewDocumentDialogMessage::DimensionsX(number_input.value.unwrap()).into()),
..NumberInput::default()
})),
WidgetHolder::new(Widget::Separator(Separator {
@@ -87,7 +121,7 @@ impl PropertyHolder for NewDocument {
disabled: self.infinite,
is_integer: true,
min: Some(0.),
on_update: WidgetCallback::new(|number_input: &NumberInput| NewDocumentDialogUpdate::DimensionsY(number_input.value.unwrap()).into()),
on_update: WidgetCallback::new(|number_input: &NumberInput| NewDocumentDialogMessage::DimensionsY(number_input.value.unwrap()).into()),
..NumberInput::default()
})),
];
@@ -99,7 +133,7 @@ impl PropertyHolder for NewDocument {
emphasized: true,
on_update: WidgetCallback::new(|_| {
DialogMessage::CloseDialogAndThen {
followups: vec![NewDocumentDialogUpdate::Submit.into()],
followups: vec![NewDocumentDialogMessage::Submit.into()],
}
.into()
}),
@@ -122,45 +156,3 @@ impl PropertyHolder for NewDocument {
]))
}
}
#[impl_message(Message, DialogMessage, NewDocumentDialog)]
#[derive(PartialEq, Clone, Debug, Serialize, Deserialize)]
pub enum NewDocumentDialogUpdate {
Name(String),
Infinite(bool),
DimensionsX(f64),
DimensionsY(f64),
Submit,
}
impl MessageHandler<NewDocumentDialogUpdate, ()> for NewDocument {
fn process_action(&mut self, action: NewDocumentDialogUpdate, _data: (), responses: &mut VecDeque<Message>) {
match action {
NewDocumentDialogUpdate::Name(name) => self.name = name,
NewDocumentDialogUpdate::Infinite(infinite) => self.infinite = infinite,
NewDocumentDialogUpdate::DimensionsX(x) => self.dimensions.x = x as u32,
NewDocumentDialogUpdate::DimensionsY(y) => self.dimensions.y = y as u32,
NewDocumentDialogUpdate::Submit => {
responses.push_back(PortfolioMessage::NewDocumentWithName { name: self.name.clone() }.into());
if !self.infinite && self.dimensions.x > 0 && self.dimensions.y > 0 {
responses.push_back(
ArtboardMessage::AddArtboard {
id: None,
position: (0., 0.),
size: (self.dimensions.x as f64, self.dimensions.y as f64),
}
.into(),
);
responses.push_back(DocumentMessage::ZoomCanvasToFitAll.into());
}
}
}
self.register_properties(responses, LayoutTarget::DialogDetails);
}
advertise_actions! {NewDocumentDialogUpdate;}
}

View File

@@ -1,13 +1,15 @@
use crate::layout::widgets::*;
use crate::message_prelude::FrontendMessage;
use crate::misc::build_metadata::{commit_info_localized, release_series};
use crate::application::{commit_info_localized, release_series};
use crate::messages::layout::utility_types::layout_widget::{Layout, LayoutGroup, PropertyHolder, Widget, WidgetCallback, WidgetHolder, WidgetLayout};
use crate::messages::layout::utility_types::widgets::button_widgets::TextButton;
use crate::messages::layout::utility_types::widgets::label_widgets::TextLabel;
use crate::messages::prelude::*;
/// A dialog for displaying information on [BuildMetadata] viewable via *Help* > *About Graphite* in the menu bar.
pub struct AboutGraphite {
pub struct AboutGraphiteDialog {
pub localized_commit_date: String,
}
impl PropertyHolder for AboutGraphite {
impl PropertyHolder for AboutGraphiteDialog {
fn properties(&self) -> Layout {
let links = [
("Website", "https://graphite.rs"),

View File

@@ -1,10 +1,12 @@
use crate::layout::widgets::*;
use crate::message_prelude::{DialogMessage, FrontendMessage, PortfolioMessage};
use crate::messages::layout::utility_types::layout_widget::{Layout, LayoutGroup, PropertyHolder, Widget, WidgetCallback, WidgetHolder, WidgetLayout};
use crate::messages::layout::utility_types::widgets::button_widgets::TextButton;
use crate::messages::layout::utility_types::widgets::label_widgets::TextLabel;
use crate::messages::prelude::*;
/// A dialog for confirming the closing of all documents viewable via `file -> close all` in the menu bar.
pub struct CloseAllDocuments;
pub struct CloseAllDocumentsDialog;
impl PropertyHolder for CloseAllDocuments {
impl PropertyHolder for CloseAllDocumentsDialog {
fn properties(&self) -> Layout {
let button_widgets = vec![
WidgetHolder::new(Widget::TextButton(TextButton {

View File

@@ -1,13 +1,17 @@
use crate::layout::widgets::*;
use crate::message_prelude::*;
use crate::messages::broadcast::broadcast_event::BroadcastEvent;
use crate::messages::layout::utility_types::layout_widget::{Layout, LayoutGroup, PropertyHolder, Widget, WidgetCallback, WidgetHolder, WidgetLayout};
use crate::messages::layout::utility_types::widgets::button_widgets::TextButton;
use crate::messages::layout::utility_types::widgets::label_widgets::TextLabel;
use crate::messages::portfolio::document::DocumentMessage;
use crate::messages::prelude::*;
/// A dialog for confirming the closing a document with unsaved changes.
pub struct CloseDocument {
pub struct CloseDocumentDialog {
pub document_name: String,
pub document_id: u64,
}
impl PropertyHolder for CloseDocument {
impl PropertyHolder for CloseDocumentDialog {
fn properties(&self) -> Layout {
let document_id = self.document_id;
@@ -29,7 +33,7 @@ impl PropertyHolder for CloseDocument {
min_width: 96,
on_update: WidgetCallback::new(move |_| {
DialogMessage::CloseDialogAndThen {
followups: vec![BroadcastSignal::ToolAbort.into(), PortfolioMessage::CloseDocument { document_id }.into()],
followups: vec![BroadcastEvent::ToolAbort.into(), PortfolioMessage::CloseDocument { document_id }.into()],
}
.into()
}),

View File

@@ -1,14 +1,16 @@
use crate::layout::widgets::*;
use crate::message_prelude::FrontendMessage;
use crate::messages::layout::utility_types::layout_widget::{Layout, LayoutGroup, PropertyHolder, Widget, WidgetCallback, WidgetHolder, WidgetLayout};
use crate::messages::layout::utility_types::widgets::button_widgets::TextButton;
use crate::messages::layout::utility_types::widgets::label_widgets::TextLabel;
use crate::messages::prelude::*;
use std::fmt::Write;
/// A dialog to notify users of an unfinished issue, optionally with an issue number.
pub struct ComingSoon {
pub struct ComingSoonDialog {
pub issue: Option<i32>,
}
impl PropertyHolder for ComingSoon {
impl PropertyHolder for ComingSoonDialog {
fn properties(&self) -> Layout {
let mut details = "This feature is not implemented yet".to_string();
let mut buttons = vec![WidgetHolder::new(Widget::TextButton(TextButton {

View File

@@ -1,13 +1,15 @@
use crate::layout::widgets::*;
use crate::message_prelude::FrontendMessage;
use crate::messages::layout::utility_types::layout_widget::{Layout, LayoutGroup, PropertyHolder, Widget, WidgetCallback, WidgetHolder, WidgetLayout};
use crate::messages::layout::utility_types::widgets::button_widgets::TextButton;
use crate::messages::layout::utility_types::widgets::label_widgets::TextLabel;
use crate::messages::prelude::*;
/// A dialog to notify users of a non-fatal error.
pub struct Error {
pub struct ErrorDialog {
pub title: String,
pub description: String,
}
impl PropertyHolder for Error {
impl PropertyHolder for ErrorDialog {
fn properties(&self) -> Layout {
Layout::WidgetLayout(WidgetLayout::new(vec![
LayoutGroup::Row {

View File

@@ -0,0 +1,11 @@
mod about_graphite_dialog;
mod close_all_documents_dialog;
mod close_document_dialog;
mod coming_soon_dialog;
mod error_dialog;
pub use about_graphite_dialog::AboutGraphiteDialog;
pub use close_all_documents_dialog::CloseAllDocumentsDialog;
pub use close_document_dialog::CloseDocumentDialog;
pub use coming_soon_dialog::ComingSoonDialog;
pub use error_dialog::ErrorDialog;

View File

@@ -1,11 +1,12 @@
use super::utility_types::{FrontendDocumentDetails, FrontendImageData, MouseCursorIcon};
use crate::document::layer_panel::{LayerPanelEntry, RawBuffer};
use crate::layout::layout_message::LayoutTarget;
use crate::layout::widgets::{MenuColumn, SubLayout};
use crate::message_prelude::*;
use crate::misc::HintData;
use crate::Color;
use crate::messages::layout::utility_types::layout_widget::SubLayout;
use crate::messages::layout::utility_types::misc::LayoutTarget;
use crate::messages::layout::utility_types::widgets::menu_widgets::MenuColumn;
use crate::messages::portfolio::document::utility_types::layer_panel::{LayerPanelEntry, RawBuffer};
use crate::messages::prelude::*;
use crate::messages::tool::utility_types::HintData;
use graphene::color::Color;
use graphene::layers::text_layer::Font;
use serde::{Deserialize, Serialize};

View File

@@ -1,6 +1,6 @@
pub mod utility_types;
mod frontend_message;
pub mod utility_types;
#[doc(inline)]
pub use frontend_message::{FrontendMessage, FrontendMessageDiscriminant};

View File

@@ -15,8 +15,9 @@ pub struct FrontendImageData {
pub image_data: Vec<u8>,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize, Deserialize)]
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq, Serialize, Deserialize)]
pub enum MouseCursorIcon {
#[default]
Default,
ZoomIn,
ZoomOut,
@@ -29,25 +30,14 @@ pub enum MouseCursorIcon {
NWSEResize,
}
impl Default for MouseCursorIcon {
fn default() -> Self {
MouseCursorIcon::Default
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize, Deserialize)]
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq, Serialize, Deserialize)]
pub enum FileType {
#[default]
Svg,
Png,
Jpg,
}
impl Default for FileType {
fn default() -> Self {
FileType::Svg
}
}
impl FileType {
pub fn to_mime(self) -> &'static str {
match self {
@@ -58,15 +48,10 @@ impl FileType {
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize, Deserialize)]
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq, Serialize, Deserialize)]
pub enum ExportBounds {
#[default]
AllArtwork,
Selection,
Artboard(LayerId),
}
impl Default for ExportBounds {
fn default() -> Self {
ExportBounds::AllArtwork
}
}

View File

@@ -0,0 +1,387 @@
use crate::consts::{BIG_NUDGE_AMOUNT, NUDGE_AMOUNT};
use crate::messages::input_mapper::input_mapper_message::InputMapperMessage;
use crate::messages::input_mapper::utility_types::input_keyboard::{Key, KeyStates};
use crate::messages::input_mapper::utility_types::macros::*;
use crate::messages::input_mapper::utility_types::misc::MappingEntry;
use crate::messages::input_mapper::utility_types::misc::{KeyMappingEntries, Mapping};
use crate::messages::portfolio::document::utility_types::clipboards::Clipboard;
use crate::messages::portfolio::document::utility_types::misc::KeyboardPlatformLayout;
use crate::messages::prelude::*;
use glam::DVec2;
pub fn default_mapping() -> Mapping {
use InputMapperMessage::*;
use Key::*;
// NOTICE:
// If a new mapping you added here isn't working (and perhaps another lower-precedence one is instead), make sure to advertise
// it as an available action in the respective message handler file (such as the bottom of `document_message_handler.rs`).
let mappings = mapping![
// HIGHER PRIORITY:
//
// MovementMessage
entry!(
PointerMove;
refresh_keys=[KeyControl],
action_dispatch=MovementMessage::PointerMove { snap_angle: KeyControl, wait_for_snap_angle_release: true, snap_zoom: KeyControl, zoom_from_viewport: None },
),
// NORMAL PRIORITY:
//
// TransformLayerMessage
entry!(KeyDown(KeyEnter); action_dispatch=TransformLayerMessage::ApplyTransformOperation),
entry!(KeyDown(Lmb); action_dispatch=TransformLayerMessage::ApplyTransformOperation),
entry!(KeyDown(KeyEscape); action_dispatch=TransformLayerMessage::CancelTransformOperation),
entry!(KeyDown(Rmb); action_dispatch=TransformLayerMessage::CancelTransformOperation),
entry!(KeyDown(KeyX); action_dispatch=TransformLayerMessage::ConstrainX),
entry!(KeyDown(KeyY); action_dispatch=TransformLayerMessage::ConstrainY),
entry!(KeyDown(KeyBackspace); action_dispatch=TransformLayerMessage::TypeBackspace),
entry!(KeyDown(KeyMinus); action_dispatch=TransformLayerMessage::TypeNegate),
entry!(KeyDown(KeyComma); action_dispatch=TransformLayerMessage::TypeDecimalPoint),
entry!(KeyDown(KeyPeriod); action_dispatch=TransformLayerMessage::TypeDecimalPoint),
entry!(PointerMove; refresh_keys=[KeyShift, KeyControl], action_dispatch=TransformLayerMessage::PointerMove { slow_key: KeyShift, snap_key: KeyControl }),
//
// SelectToolMessage
entry!(PointerMove; refresh_keys=[KeyControl, KeyShift, KeyAlt], action_dispatch=SelectToolMessage::PointerMove { axis_align: KeyShift, snap_angle: KeyControl, center: KeyAlt }),
entry!(KeyDown(Lmb); action_dispatch=SelectToolMessage::DragStart { add_to_selection: KeyShift }),
entry!(KeyUp(Lmb); action_dispatch=SelectToolMessage::DragStop),
entry!(KeyDown(KeyEnter); action_dispatch=SelectToolMessage::DragStop),
entry!(DoubleClick; action_dispatch=SelectToolMessage::EditLayer),
entry!(KeyDown(Rmb); action_dispatch=SelectToolMessage::Abort),
entry!(KeyDown(KeyEscape); action_dispatch=SelectToolMessage::Abort),
//
// ArtboardToolMessage
entry!(KeyDown(Lmb); action_dispatch=ArtboardToolMessage::PointerDown),
entry!(PointerMove; refresh_keys=[KeyShift, KeyAlt], action_dispatch=ArtboardToolMessage::PointerMove { constrain_axis_or_aspect: KeyShift, center: KeyAlt }),
entry!(KeyUp(Lmb); action_dispatch=ArtboardToolMessage::PointerUp),
entry!(KeyDown(KeyDelete); action_dispatch=ArtboardToolMessage::DeleteSelected),
entry!(KeyDown(KeyBackspace); action_dispatch=ArtboardToolMessage::DeleteSelected),
//
// NavigateToolMessage
entry!(KeyUp(Lmb); modifiers=[KeyShift], action_dispatch=NavigateToolMessage::ClickZoom { zoom_in: false }),
entry!(KeyUp(Lmb); action_dispatch=NavigateToolMessage::ClickZoom { zoom_in: true }),
entry!(PointerMove; refresh_keys=[KeyControl], action_dispatch=NavigateToolMessage::PointerMove { snap_angle: KeyControl, snap_zoom: KeyControl }),
entry!(KeyDown(Mmb); action_dispatch=NavigateToolMessage::TranslateCanvasBegin),
entry!(KeyDown(Rmb); action_dispatch=NavigateToolMessage::RotateCanvasBegin),
entry!(KeyDown(Lmb); action_dispatch=NavigateToolMessage::ZoomCanvasBegin),
entry!(KeyUp(Rmb); action_dispatch=NavigateToolMessage::TransformCanvasEnd),
entry!(KeyUp(Lmb); action_dispatch=NavigateToolMessage::TransformCanvasEnd),
entry!(KeyUp(Mmb); action_dispatch=NavigateToolMessage::TransformCanvasEnd),
//
// EyedropperToolMessage
entry!(KeyDown(Lmb); action_dispatch=EyedropperToolMessage::LeftMouseDown),
entry!(KeyDown(Rmb); action_dispatch=EyedropperToolMessage::RightMouseDown),
//
// TextToolMessage
entry!(KeyUp(Lmb); action_dispatch=TextToolMessage::Interact),
entry!(KeyDown(KeyEscape); action_dispatch=TextToolMessage::Abort),
entry_multiplatform!(
standard!(KeyDown(KeyEnter); modifiers=[KeyControl], action_dispatch=TextToolMessage::CommitText),
mac_only!(KeyDown(KeyEnter); modifiers=[KeyCommand], action_dispatch=TextToolMessage::CommitText),
),
//
// GradientToolMessage
entry!(KeyDown(Lmb); action_dispatch=GradientToolMessage::PointerDown),
entry!(PointerMove; refresh_keys=[KeyShift], action_dispatch=GradientToolMessage::PointerMove { constrain_axis: KeyShift }),
entry!(KeyUp(Lmb); action_dispatch=GradientToolMessage::PointerUp),
//
// RectangleToolMessage
entry!(KeyDown(Lmb); action_dispatch=RectangleToolMessage::DragStart),
entry!(KeyUp(Lmb); action_dispatch=RectangleToolMessage::DragStop),
entry!(KeyDown(Rmb); action_dispatch=RectangleToolMessage::Abort),
entry!(KeyDown(KeyEscape); action_dispatch=RectangleToolMessage::Abort),
entry!(PointerMove; refresh_keys=[KeyAlt, KeyShift], action_dispatch=RectangleToolMessage::Resize { center: KeyAlt, lock_ratio: KeyShift }),
//
// EllipseToolMessage
entry!(KeyDown(Lmb); action_dispatch=EllipseToolMessage::DragStart),
entry!(KeyUp(Lmb); action_dispatch=EllipseToolMessage::DragStop),
entry!(KeyDown(Rmb); action_dispatch=EllipseToolMessage::Abort),
entry!(KeyDown(KeyEscape); action_dispatch=EllipseToolMessage::Abort),
entry!(PointerMove; refresh_keys=[KeyAlt, KeyShift], action_dispatch=EllipseToolMessage::Resize { center: KeyAlt, lock_ratio: KeyShift }),
//
// ShapeToolMessage
entry!(KeyDown(Lmb); action_dispatch=ShapeToolMessage::DragStart),
entry!(KeyUp(Lmb); action_dispatch=ShapeToolMessage::DragStop),
entry!(KeyDown(Rmb); action_dispatch=ShapeToolMessage::Abort),
entry!(KeyDown(KeyEscape); action_dispatch=ShapeToolMessage::Abort),
entry!(PointerMove; refresh_keys=[KeyAlt, KeyShift], action_dispatch=ShapeToolMessage::Resize { center: KeyAlt, lock_ratio: KeyShift }),
//
// LineToolMessage
entry!(KeyDown(Lmb); action_dispatch=LineToolMessage::DragStart),
entry!(KeyUp(Lmb); action_dispatch=LineToolMessage::DragStop),
entry!(KeyDown(Rmb); action_dispatch=LineToolMessage::Abort),
entry!(KeyDown(KeyEscape); action_dispatch=LineToolMessage::Abort),
entry!(PointerMove; refresh_keys=[KeyAlt, KeyShift, KeyControl], action_dispatch=LineToolMessage::Redraw { center: KeyAlt, lock_angle: KeyControl, snap_angle: KeyShift }),
//
// PathToolMessage
entry!(KeyDown(Lmb); action_dispatch=PathToolMessage::DragStart { add_to_selection: KeyShift }),
entry!(PointerMove; refresh_keys=[KeyAlt, KeyShift], action_dispatch=PathToolMessage::PointerMove { alt_mirror_angle: KeyAlt, shift_mirror_distance: KeyShift }),
entry!(KeyDown(KeyDelete); action_dispatch=PathToolMessage::Delete),
entry!(KeyDown(KeyBackspace); action_dispatch=PathToolMessage::Delete),
entry!(KeyUp(Lmb); action_dispatch=PathToolMessage::DragStop),
//
// PenToolMessage
entry!(PointerMove; refresh_keys=[KeyShift, KeyControl], action_dispatch=PenToolMessage::PointerMove { snap_angle: KeyControl, break_handle: KeyShift }),
entry!(KeyDown(Lmb); action_dispatch=PenToolMessage::DragStart),
entry!(KeyUp(Lmb); action_dispatch=PenToolMessage::DragStop),
entry!(KeyDown(Rmb); action_dispatch=PenToolMessage::Confirm),
entry!(KeyDown(KeyEscape); action_dispatch=PenToolMessage::Confirm),
entry!(KeyDown(KeyEnter); action_dispatch=PenToolMessage::Confirm),
//
// FreehandToolMessage
entry!(PointerMove; action_dispatch=FreehandToolMessage::PointerMove),
entry!(KeyDown(Lmb); action_dispatch=FreehandToolMessage::DragStart),
entry!(KeyUp(Lmb); action_dispatch=FreehandToolMessage::DragStop),
//
// SplineToolMessage
entry!(PointerMove; action_dispatch=SplineToolMessage::PointerMove),
entry!(KeyDown(Lmb); action_dispatch=SplineToolMessage::DragStart),
entry!(KeyUp(Lmb); action_dispatch=SplineToolMessage::DragStop),
entry!(KeyDown(Rmb); action_dispatch=SplineToolMessage::Confirm),
entry!(KeyDown(KeyEscape); action_dispatch=SplineToolMessage::Confirm),
entry!(KeyDown(KeyEnter); action_dispatch=SplineToolMessage::Confirm),
//
// FillToolMessage
entry!(KeyDown(Lmb); action_dispatch=FillToolMessage::LeftMouseDown),
entry!(KeyDown(Rmb); action_dispatch=FillToolMessage::RightMouseDown),
//
// ToolMessage
entry!(KeyDown(KeyV); action_dispatch=ToolMessage::ActivateToolSelect),
entry!(KeyDown(KeyZ); action_dispatch=ToolMessage::ActivateToolNavigate),
entry!(KeyDown(KeyI); action_dispatch=ToolMessage::ActivateToolEyedropper),
entry!(KeyDown(KeyT); action_dispatch=ToolMessage::ActivateToolText),
entry!(KeyDown(KeyF); action_dispatch=ToolMessage::ActivateToolFill),
entry!(KeyDown(KeyH); action_dispatch=ToolMessage::ActivateToolGradient),
entry!(KeyDown(KeyA); action_dispatch=ToolMessage::ActivateToolPath),
entry!(KeyDown(KeyP); action_dispatch=ToolMessage::ActivateToolPen),
entry!(KeyDown(KeyN); action_dispatch=ToolMessage::ActivateToolFreehand),
entry!(KeyDown(KeyL); action_dispatch=ToolMessage::ActivateToolLine),
entry!(KeyDown(KeyM); action_dispatch=ToolMessage::ActivateToolRectangle),
entry!(KeyDown(KeyE); action_dispatch=ToolMessage::ActivateToolEllipse),
entry!(KeyDown(KeyY); action_dispatch=ToolMessage::ActivateToolShape),
entry_multiplatform!(
standard!(KeyDown(KeyX); modifiers=[KeyShift, KeyControl], action_dispatch=ToolMessage::ResetColors),
mac_only!(KeyDown(KeyX); modifiers=[KeyShift, KeyCommand], action_dispatch=ToolMessage::ResetColors),
),
entry!(KeyDown(KeyX); modifiers=[KeyShift], action_dispatch=ToolMessage::SwapColors),
entry!(KeyDown(KeyC); modifiers=[KeyAlt], action_dispatch=ToolMessage::SelectRandomPrimaryColor),
//
// DocumentMessage
entry!(KeyDown(KeyDelete); action_dispatch=DocumentMessage::DeleteSelectedLayers),
entry!(KeyDown(KeyBackspace); action_dispatch=DocumentMessage::DeleteSelectedLayers),
entry!(KeyDown(KeyP); modifiers=[KeyAlt], action_dispatch=DocumentMessage::DebugPrintDocument),
entry_multiplatform!(
standard!(KeyDown(KeyZ); modifiers=[KeyControl, KeyShift], action_dispatch=DocumentMessage::Redo),
mac_only!(KeyDown(KeyZ); modifiers=[KeyCommand, KeyShift], action_dispatch=DocumentMessage::Redo),
),
entry_multiplatform!(
standard!(KeyDown(KeyZ); modifiers=[KeyControl], action_dispatch=DocumentMessage::Undo),
mac_only!(KeyDown(KeyZ); modifiers=[KeyCommand], action_dispatch=DocumentMessage::Undo),
),
entry_multiplatform!(
standard!(KeyDown(KeyA); modifiers=[KeyControl, KeyAlt], action_dispatch=DocumentMessage::DeselectAllLayers),
mac_only!(KeyDown(KeyA); modifiers=[KeyCommand, KeyAlt], action_dispatch=DocumentMessage::DeselectAllLayers),
),
entry_multiplatform!(
standard!(KeyDown(KeyA); modifiers=[KeyControl], action_dispatch=DocumentMessage::SelectAllLayers),
mac_only!(KeyDown(KeyA); modifiers=[KeyCommand], action_dispatch=DocumentMessage::SelectAllLayers),
),
entry_multiplatform!(
standard!(KeyDown(KeyS); modifiers=[KeyControl], action_dispatch=DocumentMessage::SaveDocument),
mac_only!(KeyDown(KeyS); modifiers=[KeyCommand], action_dispatch=DocumentMessage::SaveDocument),
),
entry_multiplatform!(
standard!(KeyDown(Key0); modifiers=[KeyControl], action_dispatch=DocumentMessage::ZoomCanvasToFitAll),
mac_only!(KeyDown(Key0); modifiers=[KeyCommand], action_dispatch=DocumentMessage::ZoomCanvasToFitAll),
),
entry_multiplatform!(
standard!(KeyDown(KeyD); modifiers=[KeyControl], action_dispatch=DocumentMessage::DuplicateSelectedLayers),
mac_only!(KeyDown(KeyD); modifiers=[KeyCommand], action_dispatch=DocumentMessage::DuplicateSelectedLayers),
),
entry_multiplatform!(
standard!(KeyDown(KeyG); modifiers=[KeyControl], action_dispatch=DocumentMessage::GroupSelectedLayers),
mac_only!(KeyDown(KeyG); modifiers=[KeyCommand], action_dispatch=DocumentMessage::GroupSelectedLayers),
),
entry_multiplatform!(
standard!(KeyDown(KeyG); modifiers=[KeyControl, KeyShift], action_dispatch=DocumentMessage::UngroupSelectedLayers),
mac_only!(KeyDown(KeyG); modifiers=[KeyCommand, KeyShift], action_dispatch=DocumentMessage::UngroupSelectedLayers),
),
entry_multiplatform!(
standard!(KeyDown(KeyN); modifiers=[KeyControl, KeyShift], action_dispatch=DocumentMessage::CreateEmptyFolder { container_path: vec![] }),
mac_only!(KeyDown(KeyN); modifiers=[KeyCommand, KeyShift], action_dispatch=DocumentMessage::CreateEmptyFolder { container_path: vec![] }),
),
entry_multiplatform!(
standard!(KeyDown(KeyLeftBracket); modifiers=[KeyControl, KeyShift], action_dispatch=DocumentMessage::SelectedLayersLowerToBack),
mac_only!(KeyDown(KeyLeftBracket); modifiers=[KeyCommand, KeyShift], action_dispatch=DocumentMessage::SelectedLayersLowerToBack),
),
entry_multiplatform!(
// TODO: Delete this in favor of the KeyLeftBracket (non-shifted version of this key) mapping above once the input system can distinguish between the non-shifted and shifted keys (important for other language keyboards)
standard!(KeyDown(KeyLeftCurlyBracket); modifiers=[KeyControl, KeyShift], action_dispatch=DocumentMessage::SelectedLayersLowerToBack),
mac_only!(KeyDown(KeyLeftCurlyBracket); modifiers=[KeyCommand, KeyShift], action_dispatch=DocumentMessage::SelectedLayersLowerToBack),
),
entry_multiplatform!(
standard!(KeyDown(KeyRightBracket); modifiers=[KeyControl, KeyShift], action_dispatch=DocumentMessage::SelectedLayersRaiseToFront),
mac_only!(KeyDown(KeyRightBracket); modifiers=[KeyCommand, KeyShift], action_dispatch=DocumentMessage::SelectedLayersRaiseToFront),
),
entry_multiplatform!(
// TODO: Delete this in favor of the KeyRightBracket (non-shifted version of this key) mapping above once the input system can distinguish between the non-shifted and shifted keys (important for other language keyboards)
standard!(KeyDown(KeyRightCurlyBracket); modifiers=[KeyControl, KeyShift], action_dispatch=DocumentMessage::SelectedLayersRaiseToFront),
mac_only!(KeyDown(KeyRightCurlyBracket); modifiers=[KeyCommand, KeyShift], action_dispatch=DocumentMessage::SelectedLayersRaiseToFront),
),
entry_multiplatform!(
standard!(KeyDown(KeyLeftBracket); modifiers=[KeyControl], action_dispatch=DocumentMessage::SelectedLayersLower),
mac_only!(KeyDown(KeyLeftBracket); modifiers=[KeyCommand], action_dispatch=DocumentMessage::SelectedLayersLower),
),
entry_multiplatform!(
standard!(KeyDown(KeyRightBracket); modifiers=[KeyControl], action_dispatch=DocumentMessage::SelectedLayersRaise),
mac_only!(KeyDown(KeyRightBracket); modifiers=[KeyCommand], action_dispatch=DocumentMessage::SelectedLayersRaise),
),
entry!(KeyDown(KeyArrowUp); modifiers=[KeyShift, KeyArrowLeft], action_dispatch=DocumentMessage::NudgeSelectedLayers { delta_x: -BIG_NUDGE_AMOUNT, delta_y: -BIG_NUDGE_AMOUNT }),
entry!(KeyDown(KeyArrowUp); modifiers=[KeyShift, KeyArrowRight], action_dispatch=DocumentMessage::NudgeSelectedLayers { delta_x: BIG_NUDGE_AMOUNT, delta_y: -BIG_NUDGE_AMOUNT }),
entry!(KeyDown(KeyArrowUp); modifiers=[KeyShift], action_dispatch=DocumentMessage::NudgeSelectedLayers { delta_x: 0., delta_y: -BIG_NUDGE_AMOUNT }),
entry!(KeyDown(KeyArrowDown); modifiers=[KeyShift, KeyArrowLeft], action_dispatch=DocumentMessage::NudgeSelectedLayers { delta_x: -BIG_NUDGE_AMOUNT, delta_y: BIG_NUDGE_AMOUNT }),
entry!(KeyDown(KeyArrowDown); modifiers=[KeyShift, KeyArrowRight], action_dispatch=DocumentMessage::NudgeSelectedLayers { delta_x: BIG_NUDGE_AMOUNT, delta_y: BIG_NUDGE_AMOUNT }),
entry!(KeyDown(KeyArrowDown); modifiers=[KeyShift], action_dispatch=DocumentMessage::NudgeSelectedLayers { delta_x: 0., delta_y: BIG_NUDGE_AMOUNT }),
entry!(KeyDown(KeyArrowLeft); modifiers=[KeyShift, KeyArrowUp], action_dispatch=DocumentMessage::NudgeSelectedLayers { delta_x: -BIG_NUDGE_AMOUNT, delta_y: -BIG_NUDGE_AMOUNT }),
entry!(KeyDown(KeyArrowLeft); modifiers=[KeyShift, KeyArrowDown], action_dispatch=DocumentMessage::NudgeSelectedLayers { delta_x: -BIG_NUDGE_AMOUNT, delta_y: BIG_NUDGE_AMOUNT }),
entry!(KeyDown(KeyArrowLeft); modifiers=[KeyShift], action_dispatch=DocumentMessage::NudgeSelectedLayers { delta_x: -BIG_NUDGE_AMOUNT, delta_y: 0. }),
entry!(KeyDown(KeyArrowRight); modifiers=[KeyShift, KeyArrowUp], action_dispatch=DocumentMessage::NudgeSelectedLayers { delta_x: BIG_NUDGE_AMOUNT, delta_y: -BIG_NUDGE_AMOUNT }),
entry!(KeyDown(KeyArrowRight); modifiers=[KeyShift, KeyArrowDown], action_dispatch=DocumentMessage::NudgeSelectedLayers { delta_x: BIG_NUDGE_AMOUNT, delta_y: BIG_NUDGE_AMOUNT }),
entry!(KeyDown(KeyArrowRight); modifiers=[KeyShift], action_dispatch=DocumentMessage::NudgeSelectedLayers { delta_x: BIG_NUDGE_AMOUNT, delta_y: 0. }),
entry!(KeyDown(KeyArrowUp); modifiers=[KeyArrowLeft], action_dispatch=DocumentMessage::NudgeSelectedLayers { delta_x: -NUDGE_AMOUNT, delta_y: -NUDGE_AMOUNT }),
entry!(KeyDown(KeyArrowUp); modifiers=[KeyArrowRight], action_dispatch=DocumentMessage::NudgeSelectedLayers { delta_x: NUDGE_AMOUNT, delta_y: -NUDGE_AMOUNT }),
entry!(KeyDown(KeyArrowUp); action_dispatch=DocumentMessage::NudgeSelectedLayers { delta_x: 0., delta_y: -NUDGE_AMOUNT }),
entry!(KeyDown(KeyArrowDown); modifiers=[KeyArrowLeft], action_dispatch=DocumentMessage::NudgeSelectedLayers { delta_x: -NUDGE_AMOUNT, delta_y: NUDGE_AMOUNT }),
entry!(KeyDown(KeyArrowDown); modifiers=[KeyArrowRight], action_dispatch=DocumentMessage::NudgeSelectedLayers { delta_x: NUDGE_AMOUNT, delta_y: NUDGE_AMOUNT }),
entry!(KeyDown(KeyArrowDown); action_dispatch=DocumentMessage::NudgeSelectedLayers { delta_x: 0., delta_y: NUDGE_AMOUNT }),
entry!(KeyDown(KeyArrowLeft); modifiers=[KeyArrowUp], action_dispatch=DocumentMessage::NudgeSelectedLayers { delta_x: -NUDGE_AMOUNT, delta_y: -NUDGE_AMOUNT }),
entry!(KeyDown(KeyArrowLeft); modifiers=[KeyArrowDown], action_dispatch=DocumentMessage::NudgeSelectedLayers { delta_x: -NUDGE_AMOUNT, delta_y: NUDGE_AMOUNT }),
entry!(KeyDown(KeyArrowLeft); action_dispatch=DocumentMessage::NudgeSelectedLayers { delta_x: -NUDGE_AMOUNT, delta_y: 0. }),
entry!(KeyDown(KeyArrowRight); modifiers=[KeyArrowUp], action_dispatch=DocumentMessage::NudgeSelectedLayers { delta_x: NUDGE_AMOUNT, delta_y: -NUDGE_AMOUNT }),
entry!(KeyDown(KeyArrowRight); modifiers=[KeyArrowDown], action_dispatch=DocumentMessage::NudgeSelectedLayers { delta_x: NUDGE_AMOUNT, delta_y: NUDGE_AMOUNT }),
entry!(KeyDown(KeyArrowRight); action_dispatch=DocumentMessage::NudgeSelectedLayers { delta_x: NUDGE_AMOUNT, delta_y: 0. }),
//
// TransformLayerMessage
entry!(KeyDown(KeyG); action_dispatch=TransformLayerMessage::BeginGrab),
entry!(KeyDown(KeyR); action_dispatch=TransformLayerMessage::BeginRotate),
entry!(KeyDown(KeyS); action_dispatch=TransformLayerMessage::BeginScale),
//
// MovementMessage
entry!(KeyDown(Mmb); modifiers=[KeyControl], action_dispatch=MovementMessage::RotateCanvasBegin),
entry!(KeyDown(Mmb); modifiers=[KeyShift], action_dispatch=MovementMessage::ZoomCanvasBegin),
entry!(KeyDown(Mmb); action_dispatch=MovementMessage::TranslateCanvasBegin),
entry!(KeyUp(Mmb); action_dispatch=MovementMessage::TransformCanvasEnd),
entry!(KeyDown(Lmb); modifiers=[KeySpace], action_dispatch=MovementMessage::TranslateCanvasBegin),
entry!(KeyUp(Lmb); modifiers=[KeySpace], action_dispatch=MovementMessage::TransformCanvasEnd),
entry_multiplatform!(
standard!(KeyDown(KeyPlus); modifiers=[KeyControl], action_dispatch=MovementMessage::IncreaseCanvasZoom { center_on_mouse: false }),
mac_only!(KeyDown(KeyPlus); modifiers=[KeyCommand], action_dispatch=MovementMessage::IncreaseCanvasZoom { center_on_mouse: false }),
),
entry_multiplatform!(
standard!(KeyDown(KeyEquals); modifiers=[KeyControl], action_dispatch=MovementMessage::IncreaseCanvasZoom { center_on_mouse: false }),
mac_only!(KeyDown(KeyEquals); modifiers=[KeyCommand], action_dispatch=MovementMessage::IncreaseCanvasZoom { center_on_mouse: false }),
),
entry_multiplatform!(
standard!(KeyDown(KeyMinus); modifiers=[KeyControl], action_dispatch=MovementMessage::DecreaseCanvasZoom { center_on_mouse: false }),
mac_only!(KeyDown(KeyMinus); modifiers=[KeyCommand], action_dispatch=MovementMessage::DecreaseCanvasZoom { center_on_mouse: false }),
),
entry_multiplatform!(
standard!(KeyDown(Key1); modifiers=[KeyControl], action_dispatch=MovementMessage::SetCanvasZoom { zoom_factor: 1. }),
mac_only!(KeyDown(Key1); modifiers=[KeyCommand], action_dispatch=MovementMessage::SetCanvasZoom { zoom_factor: 1. }),
),
entry_multiplatform!(
standard!(KeyDown(Key2); modifiers=[KeyControl], action_dispatch=MovementMessage::SetCanvasZoom { zoom_factor: 2. }),
mac_only!(KeyDown(Key2); modifiers=[KeyCommand], action_dispatch=MovementMessage::SetCanvasZoom { zoom_factor: 2. }),
),
entry!(WheelScroll; modifiers=[KeyControl], action_dispatch=MovementMessage::WheelCanvasZoom),
entry!(WheelScroll; modifiers=[KeyShift], action_dispatch=MovementMessage::WheelCanvasTranslate { use_y_as_x: true }),
entry!(WheelScroll; action_dispatch=MovementMessage::WheelCanvasTranslate { use_y_as_x: false }),
entry!(KeyDown(KeyPageUp); modifiers=[KeyShift], action_dispatch=MovementMessage::TranslateCanvasByViewportFraction { delta: DVec2::new(1., 0.) }),
entry!(KeyDown(KeyPageDown); modifiers=[KeyShift], action_dispatch=MovementMessage::TranslateCanvasByViewportFraction { delta: DVec2::new(-1., 0.) }),
entry!(KeyDown(KeyPageUp); action_dispatch=MovementMessage::TranslateCanvasByViewportFraction { delta: DVec2::new(0., 1.) }),
entry!(KeyDown(KeyPageDown); action_dispatch=MovementMessage::TranslateCanvasByViewportFraction { delta: DVec2::new(0., -1.) }),
//
// PortfolioMessage
entry_multiplatform!(
standard!(KeyDown(KeyO); modifiers=[KeyControl], action_dispatch=PortfolioMessage::OpenDocument),
mac_only!(KeyDown(KeyO); modifiers=[KeyCommand], action_dispatch=PortfolioMessage::OpenDocument),
),
entry_multiplatform!(
standard!(KeyDown(KeyI); modifiers=[KeyControl], action_dispatch=PortfolioMessage::Import),
mac_only!(KeyDown(KeyI); modifiers=[KeyCommand], action_dispatch=PortfolioMessage::Import),
),
entry!(KeyDown(KeyTab); modifiers=[KeyControl], action_dispatch=PortfolioMessage::NextDocument),
entry!(KeyDown(KeyTab); modifiers=[KeyControl, KeyShift], action_dispatch=PortfolioMessage::PrevDocument),
entry_multiplatform!(
standard!(KeyDown(KeyW); modifiers=[KeyControl], action_dispatch=PortfolioMessage::CloseActiveDocumentWithConfirmation),
mac_only!(KeyDown(KeyW); modifiers=[KeyCommand], action_dispatch=PortfolioMessage::CloseActiveDocumentWithConfirmation),
),
entry_multiplatform!(
standard!(KeyDown(KeyX); modifiers=[KeyControl], action_dispatch=PortfolioMessage::Cut { clipboard: Clipboard::Device }),
mac_only!(KeyDown(KeyX); modifiers=[KeyCommand], action_dispatch=PortfolioMessage::Cut { clipboard: Clipboard::Device }),
),
entry_multiplatform!(
standard!(KeyDown(KeyC); modifiers=[KeyControl], action_dispatch=PortfolioMessage::Copy { clipboard: Clipboard::Device }),
mac_only!(KeyDown(KeyC); modifiers=[KeyCommand], action_dispatch=PortfolioMessage::Copy { clipboard: Clipboard::Device }),
),
entry_multiplatform!(
// This shortcut is intercepted in the frontend; it exists here only as a shortcut mapping source
standard!(KeyDown(KeyV); modifiers=[KeyControl], action_dispatch=FrontendMessage::TriggerPaste),
mac_only!(KeyDown(KeyV); modifiers=[KeyCommand], action_dispatch=FrontendMessage::TriggerPaste),
),
//
// DialogMessage
entry_multiplatform!(
standard!(KeyDown(KeyN); modifiers=[KeyControl], action_dispatch=DialogMessage::RequestNewDocumentDialog),
mac_only!(KeyDown(KeyN); modifiers=[KeyCommand], action_dispatch=DialogMessage::RequestNewDocumentDialog),
),
entry_multiplatform!(
standard!(KeyDown(KeyW); modifiers=[KeyControl, KeyAlt], action_dispatch=DialogMessage::CloseAllDocumentsWithConfirmation),
mac_only!(KeyDown(KeyW); modifiers=[KeyCommand, KeyAlt], action_dispatch=DialogMessage::CloseAllDocumentsWithConfirmation),
),
entry_multiplatform!(
standard!(KeyDown(KeyE); modifiers=[KeyControl], action_dispatch=DialogMessage::RequestExportDialog),
mac_only!(KeyDown(KeyE); modifiers=[KeyCommand], action_dispatch=DialogMessage::RequestExportDialog),
),
//
// DebugMessage
entry!(KeyDown(KeyT); modifiers=[KeyAlt], action_dispatch=DebugMessage::ToggleTraceLogs),
entry!(KeyDown(Key0); modifiers=[KeyAlt], action_dispatch=DebugMessage::MessageOff),
entry!(KeyDown(Key1); modifiers=[KeyAlt], action_dispatch=DebugMessage::MessageNames),
entry!(KeyDown(Key2); modifiers=[KeyAlt], action_dispatch=DebugMessage::MessageContents),
];
let (mut key_up, mut key_down, mut double_click, mut wheel_scroll, mut pointer_move) = mappings;
// TODO: Hardcode these 10 lines into 10 lines of declarations, or make this use a macro to do all 10 in one line
const NUMBER_KEYS: [Key; 10] = [Key0, Key1, Key2, Key3, Key4, Key5, Key6, Key7, Key8, Key9];
for (i, key) in NUMBER_KEYS.iter().enumerate() {
key_down[*key as usize].0.insert(
0,
MappingEntry {
action: TransformLayerMessage::TypeDigit { digit: i as u8 }.into(),
input: InputMapperMessage::KeyDown(*key),
platform_layout: None,
modifiers: modifiers!(),
},
);
}
let sort = |list: &mut KeyMappingEntries| list.0.sort_by(|u, v| v.modifiers.ones().cmp(&u.modifiers.ones()));
for list in [&mut key_up, &mut key_down] {
for sublist in list {
sort(sublist);
}
}
sort(&mut double_click);
sort(&mut wheel_scroll);
sort(&mut pointer_move);
Mapping {
key_up,
key_down,
double_click,
wheel_scroll,
pointer_move,
}
}

View File

@@ -1,5 +1,5 @@
use super::keyboard::Key;
use crate::message_prelude::*;
use crate::messages::input_mapper::utility_types::input_keyboard::Key;
use crate::messages::prelude::*;
use serde::{Deserialize, Serialize};

View File

@@ -1,8 +1,7 @@
use super::input_mapper::Mapping;
use super::keyboard::Key;
use super::InputPreprocessorMessageHandler;
use crate::document::utility_types::KeyboardPlatformLayout;
use crate::message_prelude::*;
use super::utility_types::misc::Mapping;
use crate::messages::input_mapper::utility_types::input_keyboard::{self, Key};
use crate::messages::portfolio::document::utility_types::misc::KeyboardPlatformLayout;
use crate::messages::prelude::*;
use std::fmt::Write;
@@ -11,6 +10,17 @@ pub struct InputMapperMessageHandler {
mapping: Mapping,
}
impl MessageHandler<InputMapperMessage, (&InputPreprocessorMessageHandler, KeyboardPlatformLayout, ActionList)> for InputMapperMessageHandler {
fn process_message(&mut self, message: InputMapperMessage, data: (&InputPreprocessorMessageHandler, KeyboardPlatformLayout, ActionList), responses: &mut VecDeque<Message>) {
let (input, keyboard_platform, actions) = data;
if let Some(message) = self.mapping.match_input_message(message, &input.keyboard, actions, keyboard_platform) {
responses.push_back(message);
}
}
advertise_actions!();
}
impl InputMapperMessageHandler {
pub fn hints(&self, actions: ActionList) -> String {
let mut output = String::new();
@@ -58,7 +68,7 @@ impl InputMapperMessageHandler {
.map(|i| {
// TODO: Use a safe solution eventually
assert!(
i < super::keyboard::NUMBER_OF_KEYS,
i < input_keyboard::NUMBER_OF_KEYS,
"Attempting to convert a Key with enum index {}, which is larger than the number of Key enums",
i
);
@@ -87,14 +97,3 @@ impl InputMapperMessageHandler {
.collect::<Vec<_>>()
}
}
impl MessageHandler<InputMapperMessage, (&InputPreprocessorMessageHandler, KeyboardPlatformLayout, ActionList)> for InputMapperMessageHandler {
fn process_action(&mut self, message: InputMapperMessage, data: (&InputPreprocessorMessageHandler, KeyboardPlatformLayout, ActionList), responses: &mut VecDeque<Message>) {
let (input, keyboard_platform, actions) = data;
if let Some(message) = self.mapping.match_input_message(message, &input.keyboard, actions, keyboard_platform) {
responses.push_back(message);
}
}
advertise_actions!();
}

View File

@@ -0,0 +1,10 @@
mod input_mapper_message;
mod input_mapper_message_handler;
pub mod default_mapping;
pub mod utility_types;
#[doc(inline)]
pub use input_mapper_message::{InputMapperMessage, InputMapperMessageDiscriminant};
#[doc(inline)]
pub use input_mapper_message_handler::InputMapperMessageHandler;

View File

@@ -1,5 +1,8 @@
use crate::message_prelude::*;
use crate::messages::prelude::*;
pub use graphene::DocumentResponse;
use bitflags::bitflags;
use serde::{Deserialize, Serialize};
use std::fmt::{self, Display, Formatter};
use std::ops::{BitAnd, BitAndAssign, BitOr, BitOrAssign, BitXor, BitXorAssign};
@@ -15,6 +18,22 @@ const KEY_MASK_STORAGE_LENGTH: usize = (NUMBER_OF_KEYS + STORAGE_SIZE_BITS - 1)
pub type KeyStates = BitVector<KEY_MASK_STORAGE_LENGTH>;
pub enum KeyPosition {
Pressed,
Released,
}
bitflags! {
#[derive(Default, Serialize, Deserialize)]
#[repr(transparent)]
pub struct ModifierKeys: u8 {
const SHIFT = 0b0000_0001;
const ALT = 0b0000_0010;
const CONTROL = 0b0000_0100;
const META_OR_COMMAND = 0b0000_1000;
}
}
// TODO: Consider renaming to `KeyMessage` for consistency with other messages that implement `#[impl_message(..)]`
#[impl_message(Message, InputMapperMessage, KeyDown)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
@@ -106,6 +125,10 @@ impl fmt::Display for Key {
pub const NUMBER_OF_KEYS: usize = Key::NumKeys as usize;
/// Only `Key`s that exist on a physical keyboard should be used.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct KeysGroup(pub Vec<Key>);
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum MouseMotion {
None,

View File

@@ -38,8 +38,8 @@ pub struct ScrollDelta {
}
impl ScrollDelta {
pub fn new(x: i32, y: i32, z: i32) -> ScrollDelta {
ScrollDelta { x, y, z }
pub fn new(x: i32, y: i32, z: i32) -> Self {
Self { x, y, z }
}
pub fn as_dvec2(&self) -> DVec2 {

View File

@@ -150,6 +150,14 @@ macro_rules! mapping {
}};
}
/// Constructs an `ActionKeys` macro with a certain `Action` variant, conveniently wrapped in `Some()`.
macro_rules! action_keys {
($action:expr) => {
Some(crate::messages::input_mapper::utility_types::misc::ActionKeys::Action($action.into()))
};
}
pub(crate) use action_keys;
pub(crate) use entry;
pub(crate) use entry_for_layout;
pub(crate) use entry_multiplatform;

View File

@@ -0,0 +1,148 @@
use crate::messages::input_mapper::default_mapping::default_mapping;
use crate::messages::input_mapper::utility_types::input_keyboard::{Key, KeyStates, NUMBER_OF_KEYS};
use crate::messages::portfolio::document::utility_types::misc::KeyboardPlatformLayout;
use crate::messages::prelude::*;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone)]
pub struct Mapping {
pub key_up: [KeyMappingEntries; NUMBER_OF_KEYS],
pub key_down: [KeyMappingEntries; NUMBER_OF_KEYS],
pub double_click: KeyMappingEntries,
pub wheel_scroll: KeyMappingEntries,
pub pointer_move: KeyMappingEntries,
}
impl Mapping {
pub fn match_input_message(&self, message: InputMapperMessage, keyboard_state: &KeyStates, actions: ActionList, keyboard_platform: KeyboardPlatformLayout) -> Option<Message> {
let list = match message {
InputMapperMessage::KeyDown(key) => &self.key_down[key as usize],
InputMapperMessage::KeyUp(key) => &self.key_up[key as usize],
InputMapperMessage::DoubleClick => &self.double_click,
InputMapperMessage::WheelScroll => &self.wheel_scroll,
InputMapperMessage::PointerMove => &self.pointer_move,
};
list.match_mapping(keyboard_state, actions, keyboard_platform)
}
}
impl Default for Mapping {
fn default() -> Self {
default_mapping()
}
}
#[derive(Debug, Clone)]
pub struct KeyMappingEntries(pub Vec<MappingEntry>);
impl KeyMappingEntries {
pub fn match_mapping(&self, keyboard_state: &KeyStates, actions: ActionList, keyboard_platform: KeyboardPlatformLayout) -> Option<Message> {
for entry in self.0.iter() {
// Skip this entry if it is platform-specific, and for a layout that does not match the user's keyboard platform layout
if let Some(entry_platform_layout) = entry.platform_layout {
if entry_platform_layout != keyboard_platform {
continue;
}
}
// Find which currently pressed keys are also the modifiers in this hotkey entry, then compare those against the required modifiers to see if there are zero missing
let pressed_modifiers = *keyboard_state & entry.modifiers;
let all_modifiers_without_pressed_modifiers = entry.modifiers ^ pressed_modifiers;
let all_required_modifiers_pressed = all_modifiers_without_pressed_modifiers.is_empty();
// 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());
}
}
None
}
pub fn push(&mut self, entry: MappingEntry) {
self.0.push(entry)
}
pub const fn new() -> Self {
Self(Vec::new())
}
pub fn key_array() -> [Self; NUMBER_OF_KEYS] {
const DEFAULT: KeyMappingEntries = KeyMappingEntries::new();
[DEFAULT; NUMBER_OF_KEYS]
}
}
#[derive(PartialEq, Clone, Debug)]
pub struct MappingEntry {
/// Serves two purposes:
/// - This is the message that gets dispatched when the hotkey is matched
/// - This message's discriminant is the action; it must be a currently active action to be considered as a shortcut
pub action: Message,
/// The user input event from an input device which this input mapping matches on
pub input: InputMapperMessage,
/// Any additional keys that must be also pressed for this input mapping to match
pub modifiers: KeyStates,
/// The keyboard platform layout which this mapping is exclusive to, or `None` if it's platform-agnostic
pub platform_layout: Option<KeyboardPlatformLayout>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub enum ActionKeys {
Action(MessageDiscriminant),
#[serde(rename = "keys")]
Keys(Vec<Key>),
}
impl ActionKeys {
pub fn to_keys(&mut self, action_input_mapping: &impl Fn(&MessageDiscriminant) -> Vec<Vec<Key>>) {
match self {
ActionKeys::Action(action) => {
if let Some(keys) = action_input_mapping(action).get_mut(0) {
let mut taken_keys = Vec::new();
std::mem::swap(keys, &mut taken_keys);
*self = ActionKeys::Keys(taken_keys);
} else {
*self = ActionKeys::Keys(Vec::new());
}
}
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());
}
joined
}

View File

@@ -0,0 +1,4 @@
pub mod input_keyboard;
pub mod input_mouse;
pub mod macros;
pub mod misc;

View File

@@ -1,7 +1,6 @@
use super::input_preprocessor::ModifierKeys;
use super::keyboard::Key;
use super::mouse::{EditorMouseState, ViewportBounds};
use crate::message_prelude::*;
use crate::messages::input_mapper::utility_types::input_keyboard::{Key, ModifierKeys};
use crate::messages::input_mapper::utility_types::input_mouse::{EditorMouseState, ViewportBounds};
use crate::messages::prelude::*;
#[doc(inline)]
pub use graphene::DocumentResponse;

View File

@@ -1,8 +1,7 @@
use super::input_preprocessor::ModifierKeys;
use super::keyboard::{Key, KeyStates};
use super::mouse::{MouseKeys, MouseState, ViewportBounds};
use crate::document::utility_types::KeyboardPlatformLayout;
use crate::message_prelude::*;
use crate::messages::input_mapper::utility_types::input_keyboard::{Key, KeyStates, ModifierKeys};
use crate::messages::input_mapper::utility_types::input_mouse::{MouseKeys, MouseState, ViewportBounds};
use crate::messages::portfolio::document::utility_types::misc::KeyboardPlatformLayout;
use crate::messages::prelude::*;
#[doc(inline)]
pub use graphene::DocumentResponse;
@@ -18,7 +17,7 @@ pub struct InputPreprocessorMessageHandler {
impl MessageHandler<InputPreprocessorMessage, KeyboardPlatformLayout> for InputPreprocessorMessageHandler {
#[remain::check]
fn process_action(&mut self, message: InputPreprocessorMessage, data: KeyboardPlatformLayout, responses: &mut VecDeque<Message>) {
fn process_message(&mut self, message: InputPreprocessorMessage, data: KeyboardPlatformLayout, responses: &mut VecDeque<Message>) {
let keyboard_platform = data;
#[remain::sorted]
@@ -168,3 +167,97 @@ impl InputPreprocessorMessageHandler {
[(0., 0.).into(), self.viewport_bounds.bottom_right - self.viewport_bounds.top_left]
}
}
#[cfg(test)]
mod test {
use crate::messages::input_mapper::utility_types::input_keyboard::{Key, ModifierKeys};
use crate::messages::input_mapper::utility_types::input_mouse::EditorMouseState;
use crate::messages::input_mapper::InputMapperMessage;
use crate::messages::portfolio::document::utility_types::misc::KeyboardPlatformLayout;
use crate::messages::prelude::*;
use std::collections::VecDeque;
#[test]
fn process_action_mouse_move_handle_modifier_keys() {
let mut input_preprocessor = InputPreprocessorMessageHandler::default();
let editor_mouse_state = EditorMouseState::from_editor_position(4., 809.);
let modifier_keys = ModifierKeys::ALT;
let message = InputPreprocessorMessage::PointerMove { editor_mouse_state, modifier_keys };
let mut responses = VecDeque::new();
input_preprocessor.process_message(message, KeyboardPlatformLayout::Standard, &mut responses);
assert!(input_preprocessor.keyboard.get(Key::KeyAlt as usize));
assert_eq!(responses.pop_front(), Some(InputMapperMessage::KeyDown(Key::KeyAlt).into()));
}
#[test]
fn process_action_mouse_down_handle_modifier_keys() {
let mut input_preprocessor = InputPreprocessorMessageHandler::default();
let editor_mouse_state = EditorMouseState::new();
let modifier_keys = ModifierKeys::CONTROL;
let message = InputPreprocessorMessage::PointerDown { editor_mouse_state, modifier_keys };
let mut responses = VecDeque::new();
input_preprocessor.process_message(message, KeyboardPlatformLayout::Standard, &mut responses);
assert!(input_preprocessor.keyboard.get(Key::KeyControl as usize));
assert_eq!(responses.pop_front(), Some(InputMapperMessage::KeyDown(Key::KeyControl).into()));
}
#[test]
fn process_action_mouse_up_handle_modifier_keys() {
let mut input_preprocessor = InputPreprocessorMessageHandler::default();
let editor_mouse_state = EditorMouseState::new();
let modifier_keys = ModifierKeys::SHIFT;
let message = InputPreprocessorMessage::PointerUp { editor_mouse_state, modifier_keys };
let mut responses = VecDeque::new();
input_preprocessor.process_message(message, KeyboardPlatformLayout::Standard, &mut responses);
assert!(input_preprocessor.keyboard.get(Key::KeyShift as usize));
assert_eq!(responses.pop_front(), Some(InputMapperMessage::KeyDown(Key::KeyShift).into()));
}
#[test]
fn process_action_key_down_handle_modifier_keys() {
let mut input_preprocessor = InputPreprocessorMessageHandler::default();
input_preprocessor.keyboard.set(Key::KeyControl as usize);
let key = Key::KeyA;
let modifier_keys = ModifierKeys::empty();
let message = InputPreprocessorMessage::KeyDown { key, modifier_keys };
let mut responses = VecDeque::new();
input_preprocessor.process_message(message, KeyboardPlatformLayout::Standard, &mut responses);
assert!(!input_preprocessor.keyboard.get(Key::KeyControl as usize));
assert_eq!(responses.pop_front(), Some(InputMapperMessage::KeyUp(Key::KeyControl).into()));
}
#[test]
fn process_action_key_up_handle_modifier_keys() {
let mut input_preprocessor = InputPreprocessorMessageHandler::default();
let key = Key::KeyS;
let modifier_keys = ModifierKeys::CONTROL | ModifierKeys::SHIFT;
let message = InputPreprocessorMessage::KeyUp { key, modifier_keys };
let mut responses = VecDeque::new();
input_preprocessor.process_message(message, KeyboardPlatformLayout::Standard, &mut responses);
assert!(input_preprocessor.keyboard.get(Key::KeyControl as usize));
assert!(input_preprocessor.keyboard.get(Key::KeyShift as usize));
assert!(responses.contains(&InputMapperMessage::KeyDown(Key::KeyControl).into()));
assert!(responses.contains(&InputMapperMessage::KeyDown(Key::KeyControl).into()));
}
}

View File

@@ -0,0 +1,7 @@
mod input_preprocessor_message;
mod input_preprocessor_message_handler;
#[doc(inline)]
pub use input_preprocessor_message::{InputPreprocessorMessage, InputPreprocessorMessageDiscriminant};
#[doc(inline)]
pub use input_preprocessor_message_handler::InputPreprocessorMessageHandler;

View File

@@ -0,0 +1,14 @@
use super::utility_types::misc::LayoutTarget;
use crate::messages::layout::utility_types::layout_widget::Layout;
use crate::messages::prelude::*;
use serde::{Deserialize, Serialize};
#[remain::sorted]
#[impl_message(Message, Layout)]
#[derive(PartialEq, Clone, Debug, Serialize, Deserialize)]
pub enum LayoutMessage {
RefreshLayout { layout_target: LayoutTarget },
SendLayout { layout: Layout, layout_target: LayoutTarget },
UpdateLayout { layout_target: LayoutTarget, widget_id: u64, value: serde_json::Value },
}

View File

@@ -1,9 +1,9 @@
use super::layout_message::LayoutTarget;
use super::widgets::Layout;
use crate::document::utility_types::KeyboardPlatformLayout;
use crate::input::keyboard::Key;
use crate::layout::widgets::Widget;
use crate::message_prelude::*;
use super::utility_types::misc::LayoutTarget;
use crate::messages::input_mapper::utility_types::input_keyboard::Key;
use crate::messages::layout::utility_types::layout_widget::Layout;
use crate::messages::layout::utility_types::layout_widget::Widget;
use crate::messages::portfolio::document::utility_types::misc::KeyboardPlatformLayout;
use crate::messages::prelude::*;
use graphene::layers::text_layer::Font;
@@ -15,74 +15,14 @@ pub struct LayoutMessageHandler {
layouts: [Layout; LayoutTarget::LayoutTargetLength as usize],
}
impl LayoutMessageHandler {
#[remain::check]
fn send_layout(
&self,
layout_target: LayoutTarget,
responses: &mut VecDeque<Message>,
action_input_mapping: &impl Fn(&MessageDiscriminant) -> Vec<Vec<Key>>,
keyboard_platform: KeyboardPlatformLayout,
) {
let layout = &self.layouts[layout_target as usize];
#[remain::sorted]
let message = match layout_target {
LayoutTarget::DialogDetails => FrontendMessage::UpdateDialogDetails {
layout_target,
layout: layout.clone().unwrap_widget_layout(action_input_mapping, keyboard_platform).layout,
},
LayoutTarget::DocumentBar => FrontendMessage::UpdateDocumentBarLayout {
layout_target,
layout: layout.clone().unwrap_widget_layout(action_input_mapping, keyboard_platform).layout,
},
LayoutTarget::DocumentMode => FrontendMessage::UpdateDocumentModeLayout {
layout_target,
layout: layout.clone().unwrap_widget_layout(action_input_mapping, keyboard_platform).layout,
},
LayoutTarget::LayerTreeOptions => FrontendMessage::UpdateLayerTreeOptionsLayout {
layout_target,
layout: layout.clone().unwrap_widget_layout(action_input_mapping, keyboard_platform).layout,
},
LayoutTarget::MenuBar => FrontendMessage::UpdateMenuBarLayout {
layout_target,
layout: layout.clone().unwrap_menu_layout(action_input_mapping, keyboard_platform).layout,
},
LayoutTarget::PropertiesOptions => FrontendMessage::UpdatePropertyPanelOptionsLayout {
layout_target,
layout: layout.clone().unwrap_widget_layout(action_input_mapping, keyboard_platform).layout,
},
LayoutTarget::PropertiesSections => FrontendMessage::UpdatePropertyPanelSectionsLayout {
layout_target,
layout: layout.clone().unwrap_widget_layout(action_input_mapping, keyboard_platform).layout,
},
LayoutTarget::ToolOptions => FrontendMessage::UpdateToolOptionsLayout {
layout_target,
layout: layout.clone().unwrap_widget_layout(action_input_mapping, keyboard_platform).layout,
},
LayoutTarget::ToolShelf => FrontendMessage::UpdateToolShelfLayout {
layout_target,
layout: layout.clone().unwrap_widget_layout(action_input_mapping, keyboard_platform).layout,
},
LayoutTarget::WorkingColors => FrontendMessage::UpdateWorkingColorsLayout {
layout_target,
layout: layout.clone().unwrap_widget_layout(action_input_mapping, keyboard_platform).layout,
},
#[remain::unsorted]
LayoutTarget::LayoutTargetLength => panic!("`LayoutTargetLength` is not a valid Layout Target and is used for array indexing"),
};
responses.push_back(message.into());
}
}
impl<F: Fn(&MessageDiscriminant) -> Vec<Vec<Key>>> MessageHandler<LayoutMessage, (F, KeyboardPlatformLayout)> for LayoutMessageHandler {
#[remain::check]
fn process_action(&mut self, action: LayoutMessage, data: (F, KeyboardPlatformLayout), responses: &mut std::collections::VecDeque<crate::message_prelude::Message>) {
fn process_message(&mut self, message: LayoutMessage, data: (F, KeyboardPlatformLayout), responses: &mut std::collections::VecDeque<Message>) {
let (action_input_mapping, keyboard_platform) = data;
use LayoutMessage::*;
#[remain::sorted]
match action {
match message {
RefreshLayout { layout_target } => {
self.send_layout(layout_target, responses, &action_input_mapping, keyboard_platform);
}
@@ -148,7 +88,7 @@ impl<F: Fn(&MessageDiscriminant) -> Vec<Vec<Key>>> MessageHandler<LayoutMessage,
responses.push_back(callback_message);
}
Widget::IconLabel(_) => {}
Widget::Invisible(invisible) => {
Widget::InvisibleStandinInput(invisible) => {
let callback_message = (invisible.on_update.callback)(&());
responses.push_back(callback_message);
}
@@ -206,7 +146,67 @@ impl<F: Fn(&MessageDiscriminant) -> Vec<Vec<Key>>> MessageHandler<LayoutMessage,
}
}
fn actions(&self) -> crate::message_prelude::ActionList {
fn actions(&self) -> ActionList {
actions!()
}
}
impl LayoutMessageHandler {
#[remain::check]
fn send_layout(
&self,
layout_target: LayoutTarget,
responses: &mut VecDeque<Message>,
action_input_mapping: &impl Fn(&MessageDiscriminant) -> Vec<Vec<Key>>,
keyboard_platform: KeyboardPlatformLayout,
) {
let layout = &self.layouts[layout_target as usize];
#[remain::sorted]
let message = match layout_target {
LayoutTarget::DialogDetails => FrontendMessage::UpdateDialogDetails {
layout_target,
layout: layout.clone().unwrap_widget_layout(action_input_mapping, keyboard_platform).layout,
},
LayoutTarget::DocumentBar => FrontendMessage::UpdateDocumentBarLayout {
layout_target,
layout: layout.clone().unwrap_widget_layout(action_input_mapping, keyboard_platform).layout,
},
LayoutTarget::DocumentMode => FrontendMessage::UpdateDocumentModeLayout {
layout_target,
layout: layout.clone().unwrap_widget_layout(action_input_mapping, keyboard_platform).layout,
},
LayoutTarget::LayerTreeOptions => FrontendMessage::UpdateLayerTreeOptionsLayout {
layout_target,
layout: layout.clone().unwrap_widget_layout(action_input_mapping, keyboard_platform).layout,
},
LayoutTarget::MenuBar => FrontendMessage::UpdateMenuBarLayout {
layout_target,
layout: layout.clone().unwrap_menu_layout(action_input_mapping, keyboard_platform).layout,
},
LayoutTarget::PropertiesOptions => FrontendMessage::UpdatePropertyPanelOptionsLayout {
layout_target,
layout: layout.clone().unwrap_widget_layout(action_input_mapping, keyboard_platform).layout,
},
LayoutTarget::PropertiesSections => FrontendMessage::UpdatePropertyPanelSectionsLayout {
layout_target,
layout: layout.clone().unwrap_widget_layout(action_input_mapping, keyboard_platform).layout,
},
LayoutTarget::ToolOptions => FrontendMessage::UpdateToolOptionsLayout {
layout_target,
layout: layout.clone().unwrap_widget_layout(action_input_mapping, keyboard_platform).layout,
},
LayoutTarget::ToolShelf => FrontendMessage::UpdateToolShelfLayout {
layout_target,
layout: layout.clone().unwrap_widget_layout(action_input_mapping, keyboard_platform).layout,
},
LayoutTarget::WorkingColors => FrontendMessage::UpdateWorkingColorsLayout {
layout_target,
layout: layout.clone().unwrap_widget_layout(action_input_mapping, keyboard_platform).layout,
},
#[remain::unsorted]
LayoutTarget::LayoutTargetLength => panic!("`LayoutTargetLength` is not a valid Layout Target and is used for array indexing"),
};
responses.push_back(message.into());
}
}

View File

@@ -0,0 +1,9 @@
mod layout_message;
mod layout_message_handler;
pub mod utility_types;
#[doc(inline)]
pub use layout_message::{LayoutMessage, LayoutMessageDiscriminant};
#[doc(inline)]
pub use layout_message_handler::LayoutMessageHandler;

View File

@@ -0,0 +1,292 @@
use super::widgets::button_widgets::*;
use super::widgets::input_widgets::*;
use super::widgets::label_widgets::*;
use super::widgets::menu_widgets::MenuLayout;
use crate::application::generate_uuid;
use crate::messages::input_mapper::utility_types::input_keyboard::Key;
use crate::messages::input_mapper::utility_types::misc::{keys_text_shortcut, ActionKeys};
use crate::messages::layout::utility_types::misc::LayoutTarget;
use crate::messages::layout::LayoutMessage;
use crate::messages::portfolio::document::utility_types::misc::KeyboardPlatformLayout;
use crate::messages::prelude::*;
use serde::{Deserialize, Serialize};
use std::collections::VecDeque;
use std::rc::Rc;
pub trait PropertyHolder {
fn properties(&self) -> Layout {
Layout::WidgetLayout(WidgetLayout::default())
}
fn register_properties(&self, responses: &mut VecDeque<Message>, layout_target: LayoutTarget) {
responses.push_back(
LayoutMessage::SendLayout {
layout: self.properties(),
layout_target,
}
.into(),
)
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub enum Layout {
WidgetLayout(WidgetLayout),
MenuLayout(MenuLayout),
}
impl Layout {
pub fn unwrap_widget_layout(self, action_input_mapping: &impl Fn(&MessageDiscriminant) -> Vec<Vec<Key>>, keyboard_platform: KeyboardPlatformLayout) -> WidgetLayout {
if let Layout::WidgetLayout(mut widget_layout) = self {
// Function used multiple times later in this code block to convert `ActionKeys::Action` to `ActionKeys::Keys` and append its shortcut to the tooltip
let apply_shortcut_to_tooltip = |tooltip_shortcut: &mut ActionKeys, tooltip: &mut String| {
tooltip_shortcut.to_keys(action_input_mapping);
if let ActionKeys::Keys(keys) = tooltip_shortcut {
let shortcut_text = keys_text_shortcut(keys, keyboard_platform);
if !shortcut_text.is_empty() {
if !tooltip.is_empty() {
tooltip.push(' ');
}
tooltip.push('(');
tooltip.push_str(&shortcut_text);
tooltip.push(')');
}
}
};
// Go through each widget to convert `ActionKeys::Action` to `ActionKeys::Keys` and append the key combination to the widget tooltip
for widget_holder in &mut widget_layout.iter_mut() {
// Handle all the widgets that have tooltips
let mut tooltip_shortcut = match &mut widget_holder.widget {
Widget::CheckboxInput(widget) => Some((&mut widget.tooltip, &mut widget.tooltip_shortcut)),
Widget::ColorInput(widget) => Some((&mut widget.tooltip, &mut widget.tooltip_shortcut)),
Widget::IconButton(widget) => Some((&mut widget.tooltip, &mut widget.tooltip_shortcut)),
Widget::OptionalInput(widget) => Some((&mut widget.tooltip, &mut widget.tooltip_shortcut)),
_ => None,
};
if let Some((tooltip, Some(tooltip_shortcut))) = &mut tooltip_shortcut {
apply_shortcut_to_tooltip(tooltip_shortcut, tooltip);
}
// Handle RadioInput separately because its tooltips are children of the widget
if let Widget::RadioInput(radio_input) = &mut widget_holder.widget {
for radio_entry_data in &mut radio_input.entries {
if let RadioEntryData {
tooltip,
tooltip_shortcut: Some(tooltip_shortcut),
..
} = radio_entry_data
{
apply_shortcut_to_tooltip(tooltip_shortcut, tooltip);
}
}
}
}
widget_layout
} else {
panic!("Tried to unwrap layout as WidgetLayout. Got {:?}", self)
}
}
pub fn unwrap_menu_layout(self, action_input_mapping: &impl Fn(&MessageDiscriminant) -> Vec<Vec<Key>>, _keyboard_platform: KeyboardPlatformLayout) -> MenuLayout {
if let Layout::MenuLayout(mut menu_layout) = self {
for menu_column in &mut menu_layout.layout {
menu_column.children.fill_in_shortcut_actions_with_keys(action_input_mapping);
}
menu_layout
} else {
panic!("Tried to unwrap layout as MenuLayout. Got {:?}", self)
}
}
pub fn iter(&self) -> Box<dyn Iterator<Item = &WidgetHolder> + '_> {
match self {
Layout::MenuLayout(menu_layout) => Box::new(menu_layout.iter()),
Layout::WidgetLayout(widget_layout) => Box::new(widget_layout.iter()),
}
}
pub fn iter_mut(&mut self) -> Box<dyn Iterator<Item = &mut WidgetHolder> + '_> {
match self {
Layout::MenuLayout(menu_layout) => Box::new(menu_layout.iter_mut()),
Layout::WidgetLayout(widget_layout) => Box::new(widget_layout.iter_mut()),
}
}
}
impl Default for Layout {
fn default() -> Self {
Layout::WidgetLayout(WidgetLayout::default())
}
}
#[derive(Debug, Default, Clone, Serialize, Deserialize, PartialEq)]
pub struct WidgetLayout {
pub layout: SubLayout,
}
impl WidgetLayout {
pub fn new(layout: SubLayout) -> Self {
Self { layout }
}
pub fn iter(&self) -> WidgetIter<'_> {
WidgetIter {
stack: self.layout.iter().collect(),
current_slice: None,
}
}
pub fn iter_mut(&mut self) -> WidgetIterMut<'_> {
WidgetIterMut {
stack: self.layout.iter_mut().collect(),
current_slice: None,
}
}
}
#[derive(Debug, Default)]
pub struct WidgetIter<'a> {
pub stack: Vec<&'a LayoutGroup>,
pub current_slice: Option<&'a [WidgetHolder]>,
}
impl<'a> Iterator for WidgetIter<'a> {
type Item = &'a WidgetHolder;
fn next(&mut self) -> Option<Self::Item> {
if let Some(item) = self.current_slice.and_then(|slice| slice.first()) {
self.current_slice = Some(&self.current_slice.unwrap()[1..]);
return Some(item);
}
match self.stack.pop() {
Some(LayoutGroup::Column { widgets }) => {
self.current_slice = Some(widgets);
self.next()
}
Some(LayoutGroup::Row { widgets }) => {
self.current_slice = Some(widgets);
self.next()
}
Some(LayoutGroup::Section { name: _, layout }) => {
for layout_row in layout {
self.stack.push(layout_row);
}
self.next()
}
None => None,
}
}
}
#[derive(Debug, Default)]
pub struct WidgetIterMut<'a> {
pub stack: Vec<&'a mut LayoutGroup>,
pub current_slice: Option<&'a mut [WidgetHolder]>,
}
impl<'a> Iterator for WidgetIterMut<'a> {
type Item = &'a mut WidgetHolder;
fn next(&mut self) -> Option<Self::Item> {
if let Some((first, rest)) = self.current_slice.take().and_then(|slice| slice.split_first_mut()) {
self.current_slice = Some(rest);
return Some(first);
};
match self.stack.pop() {
Some(LayoutGroup::Column { widgets }) => {
self.current_slice = Some(widgets);
self.next()
}
Some(LayoutGroup::Row { widgets }) => {
self.current_slice = Some(widgets);
self.next()
}
Some(LayoutGroup::Section { name: _, layout }) => {
for layout_row in layout {
self.stack.push(layout_row);
}
self.next()
}
None => None,
}
}
}
pub type SubLayout = Vec<LayoutGroup>;
#[remain::sorted]
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub enum LayoutGroup {
#[serde(rename = "column")]
Column {
#[serde(rename = "columnWidgets")]
widgets: Vec<WidgetHolder>,
},
#[serde(rename = "row")]
Row {
#[serde(rename = "rowWidgets")]
widgets: Vec<WidgetHolder>,
},
#[serde(rename = "section")]
Section { name: String, layout: SubLayout },
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct WidgetHolder {
#[serde(rename = "widgetId")]
pub widget_id: u64,
pub widget: Widget,
}
impl WidgetHolder {
pub fn new(widget: Widget) -> Self {
Self { widget_id: generate_uuid(), widget }
}
}
#[derive(Clone)]
pub struct WidgetCallback<T> {
pub callback: Rc<dyn Fn(&T) -> Message + 'static>,
}
impl<T> WidgetCallback<T> {
pub fn new(callback: impl Fn(&T) -> Message + 'static) -> Self {
Self { callback: Rc::new(callback) }
}
}
impl<T> Default for WidgetCallback<T> {
fn default() -> Self {
Self::new(|_| Message::NoOp)
}
}
#[remain::sorted]
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub enum Widget {
CheckboxInput(CheckboxInput),
ColorInput(ColorInput),
DropdownInput(DropdownInput),
FontInput(FontInput),
IconButton(IconButton),
IconLabel(IconLabel),
InvisibleStandinInput(InvisibleStandinInput),
NumberInput(NumberInput),
OptionalInput(OptionalInput),
PopoverButton(PopoverButton),
RadioInput(RadioInput),
Separator(Separator),
SwatchPairInput(SwatchPairInput),
TextAreaInput(TextAreaInput),
TextButton(TextButton),
TextInput(TextInput),
TextLabel(TextLabel),
}

View File

@@ -1,17 +1,5 @@
use super::widgets::Layout;
use crate::message_prelude::*;
use serde::{Deserialize, Serialize};
#[remain::sorted]
#[impl_message(Message, Layout)]
#[derive(PartialEq, Clone, Debug, Serialize, Deserialize)]
pub enum LayoutMessage {
RefreshLayout { layout_target: LayoutTarget },
SendLayout { layout: Layout, layout_target: LayoutTarget },
UpdateLayout { layout_target: LayoutTarget, widget_id: u64, value: serde_json::Value },
}
#[remain::sorted]
#[derive(PartialEq, Clone, Debug, Hash, Eq, Copy, Serialize, Deserialize)]
#[repr(u8)]

View File

@@ -0,0 +1,3 @@
pub mod layout_widget;
pub mod misc;
pub mod widgets;

View File

@@ -0,0 +1,55 @@
use crate::messages::input_mapper::utility_types::misc::ActionKeys;
use crate::messages::layout::utility_types::layout_widget::WidgetCallback;
use derivative::*;
use serde::{Deserialize, Serialize};
#[derive(Clone, Default, Derivative, Serialize, Deserialize)]
#[derivative(Debug, PartialEq)]
pub struct IconButton {
pub icon: String,
pub size: u32, // TODO: Convert to an `IconSize` enum
pub active: bool,
pub tooltip: String,
#[serde(skip)]
pub tooltip_shortcut: Option<ActionKeys>,
// Callbacks
#[serde(skip)]
#[derivative(Debug = "ignore", PartialEq = "ignore")]
pub on_update: WidgetCallback<IconButton>,
}
#[derive(Clone, Serialize, Deserialize, Derivative)]
#[derivative(Debug, PartialEq, Default)]
pub struct PopoverButton {
pub icon: Option<String>,
// Body
pub header: String,
pub text: String,
}
#[derive(Clone, Serialize, Deserialize, Derivative, Default)]
#[derivative(Debug, PartialEq)]
#[serde(rename_all(serialize = "camelCase", deserialize = "camelCase"))]
pub struct TextButton {
pub label: String,
pub emphasized: bool,
#[serde(rename = "minWidth")]
pub min_width: u32,
pub disabled: bool,
// Callbacks
#[serde(skip)]
#[derivative(Debug = "ignore", PartialEq = "ignore")]
pub on_update: WidgetCallback<TextButton>,
}

View File

@@ -0,0 +1,268 @@
use crate::messages::input_mapper::utility_types::misc::ActionKeys;
use crate::messages::layout::utility_types::layout_widget::WidgetCallback;
use graphene::color::Color;
use derivative::*;
use serde::{Deserialize, Serialize};
#[derive(Clone, Default, Derivative, Serialize, Deserialize)]
#[derivative(Debug, PartialEq)]
pub struct CheckboxInput {
pub checked: bool,
pub icon: String,
pub tooltip: String,
#[serde(skip)]
pub tooltip_shortcut: Option<ActionKeys>,
// Callbacks
#[serde(skip)]
#[derivative(Debug = "ignore", PartialEq = "ignore")]
pub on_update: WidgetCallback<CheckboxInput>,
}
#[derive(Clone, Derivative, Serialize, Deserialize)]
#[derivative(Debug, PartialEq, Default)]
pub struct ColorInput {
pub value: Option<String>,
pub label: Option<String>,
#[serde(rename = "noTransparency")]
#[derivative(Default(value = "true"))]
pub no_transparency: bool,
pub disabled: bool,
pub tooltip: String,
#[serde(skip)]
pub tooltip_shortcut: Option<ActionKeys>,
// Callbacks
#[serde(skip)]
#[derivative(Debug = "ignore", PartialEq = "ignore")]
pub on_update: WidgetCallback<ColorInput>,
}
#[derive(Clone, Serialize, Deserialize, Derivative)]
#[derivative(Debug, PartialEq, Default)]
pub struct DropdownInput {
pub entries: DropdownInputEntries,
// This uses `u32` instead of `usize` since it will be serialized as a normal JS number (replace this with `usize` after switching to a Rust-based GUI)
#[serde(rename = "selectedIndex")]
pub selected_index: Option<u32>,
#[serde(rename = "drawIcon")]
pub draw_icon: bool,
#[derivative(Default(value = "true"))]
pub interactive: bool,
pub disabled: bool,
//
// Callbacks
// `on_update` exists on the `DropdownEntryData`, not this parent `DropdownInput`
}
pub type DropdownInputEntries = Vec<Vec<DropdownEntryData>>;
#[derive(Clone, Serialize, Deserialize, Derivative, Default)]
#[derivative(Debug, PartialEq)]
pub struct DropdownEntryData {
pub value: String,
pub label: String,
pub icon: String,
pub shortcut: Vec<String>,
#[serde(rename = "shortcutRequiresLock")]
pub shortcut_requires_lock: bool,
pub disabled: bool,
pub children: DropdownInputEntries,
// Callbacks
#[serde(skip)]
#[derivative(Debug = "ignore", PartialEq = "ignore")]
pub on_update: WidgetCallback<()>,
}
#[derive(Clone, Serialize, Deserialize, Derivative)]
#[derivative(Debug, PartialEq, Default)]
pub struct FontInput {
#[serde(rename = "fontFamily")]
pub font_family: String,
#[serde(rename = "fontStyle")]
pub font_style: String,
#[serde(rename = "isStyle")]
pub is_style_picker: bool,
pub disabled: bool,
// Callbacks
#[serde(skip)]
#[derivative(Debug = "ignore", PartialEq = "ignore")]
pub on_update: WidgetCallback<FontInput>,
}
/// This widget allows for the flexible use of the layout system.
/// In a custom layout, one can define a widget that is just used to trigger code on the backend.
/// This is used in MenuLayout to pipe the triggering of messages from the frontend to backend.
#[derive(Clone, Serialize, Deserialize, Derivative, Default)]
#[derivative(Debug, PartialEq)]
pub struct InvisibleStandinInput {
#[serde(skip)]
#[derivative(Debug = "ignore", PartialEq = "ignore")]
pub on_update: WidgetCallback<()>,
}
#[derive(Clone, Serialize, Deserialize, Derivative)]
#[derivative(Debug, PartialEq, Default)]
pub struct NumberInput {
pub label: String,
pub value: Option<f64>,
pub min: Option<f64>,
pub max: Option<f64>,
#[serde(rename = "isInteger")]
pub is_integer: bool,
#[serde(rename = "displayDecimalPlaces")]
#[derivative(Default(value = "3"))]
pub display_decimal_places: u32,
pub unit: String,
#[serde(rename = "unitIsHiddenWhenEditing")]
#[derivative(Default(value = "true"))]
pub unit_is_hidden_when_editing: bool,
#[serde(rename = "incrementBehavior")]
pub increment_behavior: NumberInputIncrementBehavior,
#[serde(rename = "incrementFactor")]
#[derivative(Default(value = "1."))]
pub increment_factor: f64,
pub disabled: bool,
// Callbacks
#[serde(skip)]
#[derivative(Debug = "ignore", PartialEq = "ignore")]
pub on_update: WidgetCallback<NumberInput>,
#[serde(skip)]
#[derivative(Debug = "ignore", PartialEq = "ignore")]
pub increment_callback_increase: WidgetCallback<NumberInput>,
#[serde(skip)]
#[derivative(Debug = "ignore", PartialEq = "ignore")]
pub increment_callback_decrease: WidgetCallback<NumberInput>,
}
#[derive(Clone, Serialize, Deserialize, Debug, Default, PartialEq, Eq)]
pub enum NumberInputIncrementBehavior {
#[default]
Add,
Multiply,
Callback,
}
#[derive(Clone, Default, Derivative, Serialize, Deserialize)]
#[derivative(Debug, PartialEq)]
pub struct OptionalInput {
pub checked: bool,
pub icon: String,
pub tooltip: String,
#[serde(skip)]
pub tooltip_shortcut: Option<ActionKeys>,
// Callbacks
#[serde(skip)]
#[derivative(Debug = "ignore", PartialEq = "ignore")]
pub on_update: WidgetCallback<OptionalInput>,
}
#[derive(Clone, Default, Derivative, Serialize, Deserialize)]
#[derivative(Debug, PartialEq)]
pub struct RadioInput {
pub entries: Vec<RadioEntryData>,
// This uses `u32` instead of `usize` since it will be serialized as a normal JS number (replace this with `usize` after switching to a Rust-based GUI)
#[serde(rename = "selectedIndex")]
pub selected_index: u32,
}
#[derive(Clone, Default, Derivative, Serialize, Deserialize)]
#[derivative(Debug, PartialEq)]
pub struct RadioEntryData {
pub value: String,
pub label: String,
pub icon: String,
pub tooltip: String,
#[serde(skip)]
pub tooltip_shortcut: Option<ActionKeys>,
// Callbacks
#[serde(skip)]
#[derivative(Debug = "ignore", PartialEq = "ignore")]
pub on_update: WidgetCallback<()>,
}
#[derive(Clone, Serialize, Deserialize, Derivative)]
#[derivative(Debug, PartialEq, Default)]
pub struct SwatchPairInput {
pub primary: Color,
pub secondary: Color,
}
#[derive(Clone, Serialize, Deserialize, Derivative)]
#[derivative(Debug, PartialEq, Default)]
pub struct TextAreaInput {
pub value: String,
pub label: Option<String>,
pub disabled: bool,
// Callbacks
#[serde(skip)]
#[derivative(Debug = "ignore", PartialEq = "ignore")]
pub on_update: WidgetCallback<TextAreaInput>,
}
#[derive(Clone, Serialize, Deserialize, Derivative)]
#[derivative(Debug, PartialEq, Default)]
pub struct TextInput {
pub value: String,
pub label: Option<String>,
pub disabled: bool,
// Callbacks
#[serde(skip)]
#[derivative(Debug = "ignore", PartialEq = "ignore")]
pub on_update: WidgetCallback<TextInput>,
}

View File

@@ -0,0 +1,56 @@
use derivative::*;
use serde::{Deserialize, Serialize};
#[derive(Clone, Serialize, Deserialize, Derivative, Debug, Default, PartialEq, Eq)]
pub struct IconLabel {
pub icon: String,
#[serde(rename = "iconStyle")]
pub icon_style: IconStyle,
}
#[derive(Clone, Serialize, Deserialize, Derivative, Debug, Default, PartialEq, Eq)]
pub enum IconStyle {
#[default]
Normal,
Node,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct Separator {
pub direction: SeparatorDirection,
#[serde(rename = "type")]
pub separator_type: SeparatorType,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub enum SeparatorDirection {
Horizontal,
Vertical,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub enum SeparatorType {
Related,
Unrelated,
Section,
List,
}
#[derive(Clone, Serialize, Deserialize, Derivative, Debug, PartialEq, Eq, Default)]
pub struct TextLabel {
pub bold: bool,
pub italic: bool,
#[serde(rename = "tableAlign")]
pub table_align: bool,
pub multiline: bool,
// Body
pub value: String,
}
// TODO: Add UserInputLabel

View File

@@ -0,0 +1,134 @@
use crate::messages::input_mapper::utility_types::input_keyboard::Key;
use crate::messages::input_mapper::utility_types::misc::ActionKeys;
use crate::messages::layout::utility_types::layout_widget::WidgetHolder;
use crate::messages::layout::utility_types::layout_widget::{Widget, WidgetCallback};
use crate::messages::prelude::*;
use serde::{Deserialize, Serialize};
use super::input_widgets::InvisibleStandinInput;
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default)]
pub struct MenuEntryGroups(pub Vec<Vec<MenuEntry>>);
impl MenuEntryGroups {
pub fn empty() -> Self {
Self(Vec::new())
}
pub fn fill_in_shortcut_actions_with_keys(&mut self, action_input_mapping: &impl Fn(&MessageDiscriminant) -> Vec<Vec<Key>>) {
let entries = self.0.iter_mut().flatten();
for entry in entries {
if let Some(action_keys) = &mut entry.shortcut {
action_keys.to_keys(action_input_mapping);
}
// Recursively do this for the children also
entry.children.fill_in_shortcut_actions_with_keys(action_input_mapping);
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct MenuEntry {
pub label: String,
pub icon: Option<String>,
pub children: MenuEntryGroups,
pub action: WidgetHolder,
pub shortcut: Option<ActionKeys>,
}
impl MenuEntry {
pub fn create_action(callback: impl Fn(&()) -> Message + 'static) -> WidgetHolder {
WidgetHolder::new(Widget::InvisibleStandinInput(InvisibleStandinInput {
on_update: WidgetCallback::new(callback),
}))
}
pub fn no_action() -> WidgetHolder {
MenuEntry::create_action(|_| Message::NoOp)
}
}
impl Default for MenuEntry {
fn default() -> Self {
Self {
action: MenuEntry::create_action(|_| DialogMessage::RequestComingSoonDialog { issue: None }.into()),
label: "".into(),
icon: None,
children: MenuEntryGroups::empty(),
shortcut: None,
}
}
}
#[derive(Debug, Default, Clone, Serialize, Deserialize, PartialEq)]
pub struct MenuColumn {
pub label: String,
pub children: MenuEntryGroups,
}
#[derive(Debug, Default, Clone, PartialEq, Serialize, Deserialize)]
pub struct MenuLayout {
pub layout: Vec<MenuColumn>,
}
impl MenuLayout {
pub fn new(layout: Vec<MenuColumn>) -> Self {
Self { layout }
}
pub fn iter(&self) -> impl Iterator<Item = &WidgetHolder> + '_ {
MenuLayoutIter {
stack: self.layout.iter().flat_map(|column| column.children.0.iter()).flat_map(|group| group.iter()).collect(),
}
}
pub fn iter_mut(&mut self) -> impl Iterator<Item = &mut WidgetHolder> + '_ {
MenuLayoutIterMut {
stack: self.layout.iter_mut().flat_map(|column| column.children.0.iter_mut()).flat_map(|group| group.iter_mut()).collect(),
}
}
}
#[derive(Debug, Default)]
pub struct MenuLayoutIter<'a> {
pub stack: Vec<&'a MenuEntry>,
}
impl<'a> Iterator for MenuLayoutIter<'a> {
type Item = &'a WidgetHolder;
fn next(&mut self) -> Option<Self::Item> {
match self.stack.pop() {
Some(menu_entry) => {
let more_entries = menu_entry.children.0.iter().flat_map(|entry| entry.iter());
self.stack.extend(more_entries);
Some(&menu_entry.action)
}
None => None,
}
}
}
pub struct MenuLayoutIterMut<'a> {
pub stack: Vec<&'a mut MenuEntry>,
}
impl<'a> Iterator for MenuLayoutIterMut<'a> {
type Item = &'a mut WidgetHolder;
fn next(&mut self) -> Option<Self::Item> {
match self.stack.pop() {
Some(menu_entry) => {
let more_entries = menu_entry.children.0.iter_mut().flat_map(|entry| entry.iter_mut());
self.stack.extend(more_entries);
Some(&mut menu_entry.action)
}
None => None,
}
}
}

View File

@@ -0,0 +1,4 @@
pub mod button_widgets;
pub mod input_widgets;
pub mod label_widgets;
pub mod menu_widgets;

View File

@@ -1,4 +1,4 @@
use crate::message_prelude::*;
use crate::messages::prelude::*;
use graphite_proc_macros::*;
@@ -6,16 +6,6 @@ use serde::{Deserialize, Serialize};
use std::collections::hash_map::DefaultHasher;
use std::hash::{Hash, Hasher};
pub trait AsMessage: TransitiveChild
where
Self::TopParent: TransitiveChild<Parent = Self::TopParent, TopParent = Self::TopParent> + AsMessage,
{
fn local_name(self) -> String;
fn global_name(self) -> String {
<Self as Into<Self::TopParent>>::into(self).local_name()
}
}
#[remain::sorted]
#[impl_message]
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
@@ -24,6 +14,7 @@ pub enum Message {
NoOp,
#[remain::unsorted]
Init,
#[child]
Broadcast(BroadcastMessage),
#[child]

View File

@@ -0,0 +1,14 @@
//! The root-level messages forming the first layer of the message system architecture.
pub mod broadcast;
pub mod debug;
pub mod dialog;
pub mod frontend;
pub mod input_mapper;
pub mod input_preprocessor;
pub mod layout;
pub mod message;
pub mod portfolio;
pub mod prelude;
pub mod tool;
pub mod workspace;

View File

@@ -1,5 +1,6 @@
use crate::message_prelude::*;
use crate::messages::prelude::*;
use graphene::LayerId;
use graphene::Operation as DocumentOperation;
use serde::{Deserialize, Serialize};

View File

@@ -1,10 +1,12 @@
use crate::message_prelude::*;
use crate::messages::prelude::*;
use crate::application::generate_uuid;
use graphene::color::Color;
use graphene::document::Document as GrapheneDocument;
use graphene::layers::style::{self, Fill, RenderData, ViewMode};
use graphene::layers::text_layer::FontCache;
use graphene::DocumentResponse;
use graphene::LayerId;
use graphene::Operation as DocumentOperation;
use glam::DAffine2;
@@ -17,15 +19,9 @@ pub struct ArtboardMessageHandler {
pub artboard_ids: Vec<LayerId>,
}
impl ArtboardMessageHandler {
pub fn is_infinite_canvas(&self) -> bool {
self.artboard_ids.is_empty()
}
}
impl MessageHandler<ArtboardMessage, &FontCache> for ArtboardMessageHandler {
#[remain::check]
fn process_action(&mut self, message: ArtboardMessage, font_cache: &FontCache, responses: &mut VecDeque<Message>) {
fn process_message(&mut self, message: ArtboardMessage, font_cache: &FontCache, responses: &mut VecDeque<Message>) {
use ArtboardMessage::*;
#[remain::sorted]
@@ -41,7 +37,7 @@ impl MessageHandler<ArtboardMessage, &FontCache> for ArtboardMessageHandler {
DocumentResponse::DocumentChanged => responses.push_back(ArtboardMessage::RenderArtboards.into()),
_ => {}
};
responses.push_back(BroadcastSignal::DocumentIsDirty.into());
responses.push_back(BroadcastEvent::DocumentIsDirty.into());
}
}
Ok(None) => {}
@@ -112,3 +108,9 @@ impl MessageHandler<ArtboardMessage, &FontCache> for ArtboardMessageHandler {
actions!(ArtboardMessageDiscriminant;)
}
}
impl ArtboardMessageHandler {
pub fn is_infinite_canvas(&self) -> bool {
self.artboard_ids.is_empty()
}
}

View File

@@ -0,0 +1,7 @@
mod artboard_message;
mod artboard_message_handler;
#[doc(inline)]
pub use artboard_message::{ArtboardMessage, ArtboardMessageDiscriminant};
#[doc(inline)]
pub use artboard_message_handler::ArtboardMessageHandler;

View File

@@ -1,7 +1,7 @@
use super::layer_panel::LayerMetadata;
use super::utility_types::{AlignAggregate, AlignAxis, FlipAxis};
use crate::frontend::utility_types::{ExportBounds, FileType};
use crate::message_prelude::*;
use crate::messages::frontend::utility_types::{ExportBounds, FileType};
use crate::messages::portfolio::document::utility_types::layer_panel::LayerMetadata;
use crate::messages::portfolio::document::utility_types::misc::{AlignAggregate, AlignAxis, FlipAxis};
use crate::messages::prelude::*;
use graphene::boolean_ops::BooleanOperation as BooleanOperationType;
use graphene::layers::blend_mode::BlendMode;
@@ -29,7 +29,7 @@ pub enum DocumentMessage {
Overlays(OverlaysMessage),
#[remain::unsorted]
#[child]
TransformLayers(TransformLayerMessage),
TransformLayer(TransformLayerMessage),
#[remain::unsorted]
#[child]
PropertiesPanel(PropertiesPanelMessage),

View File

@@ -0,0 +1,14 @@
mod document_message;
mod document_message_handler;
pub mod artboard;
pub mod movement;
pub mod overlays;
pub mod properties_panel;
pub mod transform_layer;
pub mod utility_types;
#[doc(inline)]
pub use document_message::{DocumentMessage, DocumentMessageDiscriminant};
#[doc(inline)]
pub use document_message_handler::DocumentMessageHandler;

View File

@@ -0,0 +1,7 @@
mod movement_message;
mod movement_message_handler;
#[doc(inline)]
pub use movement_message::{MovementMessage, MovementMessageDiscriminant};
#[doc(inline)]
pub use movement_message_handler::MovementMessageHandler;

View File

@@ -1,5 +1,5 @@
use crate::input::keyboard::Key;
use crate::message_prelude::*;
use crate::messages::input_mapper::utility_types::input_keyboard::Key;
use crate::messages::prelude::*;
use glam::DVec2;
use serde::{Deserialize, Serialize};
@@ -8,6 +8,7 @@ use serde::{Deserialize, Serialize};
#[impl_message(Message, DocumentMessage, Movement)]
#[derive(PartialEq, Clone, Debug, Serialize, Deserialize)]
pub enum MovementMessage {
// Messages
DecreaseCanvasZoom {
center_on_mouse: bool,
},

View File

@@ -1,10 +1,9 @@
use crate::consts::{VIEWPORT_ROTATE_SNAP_INTERVAL, VIEWPORT_SCROLL_RATE, VIEWPORT_ZOOM_LEVELS, VIEWPORT_ZOOM_MOUSE_RATE, VIEWPORT_ZOOM_SCALE_MAX, VIEWPORT_ZOOM_SCALE_MIN, VIEWPORT_ZOOM_WHEEL_RATE};
use crate::frontend::utility_types::MouseCursorIcon;
use crate::input::keyboard::Key;
use crate::input::mouse::{ViewportBounds, ViewportPosition};
use crate::input::InputPreprocessorMessageHandler;
use crate::message_prelude::*;
use crate::misc::{HintData, HintGroup, HintInfo, KeysGroup};
use crate::messages::frontend::utility_types::MouseCursorIcon;
use crate::messages::input_mapper::utility_types::input_keyboard::{Key, KeysGroup};
use crate::messages::input_mapper::utility_types::input_mouse::{ViewportBounds, ViewportPosition};
use crate::messages::prelude::*;
use crate::messages::tool::utility_types::{HintData, HintGroup, HintInfo};
use graphene::document::Document;
use graphene::Operation as DocumentOperation;
@@ -50,72 +49,9 @@ impl Default for MovementMessageHandler {
}
}
impl MovementMessageHandler {
pub fn snapped_angle(&self) -> f64 {
let increment_radians: f64 = VIEWPORT_ROTATE_SNAP_INTERVAL.to_radians();
if self.snap_tilt {
(self.tilt / increment_radians).round() * increment_radians
} else {
self.tilt
}
}
pub fn snapped_scale(&self) -> f64 {
if self.snap_zoom {
*VIEWPORT_ZOOM_LEVELS
.iter()
.min_by(|a, b| (**a - self.zoom).abs().partial_cmp(&(**b - self.zoom).abs()).unwrap())
.unwrap_or(&self.zoom)
} else {
self.zoom
}
}
pub fn calculate_offset_transform(&self, offset: DVec2) -> DAffine2 {
// TODO: replace with DAffine2::from_scale_angle_translation and fix the errors
let offset_transform = DAffine2::from_translation(offset);
let scale_transform = DAffine2::from_scale(DVec2::splat(self.snapped_scale()));
let angle_transform = DAffine2::from_angle(self.snapped_angle());
let translation_transform = DAffine2::from_translation(self.pan);
scale_transform * offset_transform * angle_transform * translation_transform
}
fn create_document_transform(&self, viewport_bounds: &ViewportBounds, responses: &mut VecDeque<Message>) {
let half_viewport = viewport_bounds.size() / 2.;
let scaled_half_viewport = half_viewport / self.snapped_scale();
responses.push_back(
DocumentOperation::SetLayerTransform {
path: vec![],
transform: self.calculate_offset_transform(scaled_half_viewport).to_cols_array(),
}
.into(),
);
responses.push_back(
ArtboardMessage::DispatchOperation(
DocumentOperation::SetLayerTransform {
path: vec![],
transform: self.calculate_offset_transform(scaled_half_viewport).to_cols_array(),
}
.into(),
)
.into(),
);
}
pub fn center_zoom(&self, viewport_bounds: DVec2, zoom_factor: f64, mouse: DVec2) -> Message {
let new_viewport_bounds = viewport_bounds / zoom_factor;
let delta_size = viewport_bounds - new_viewport_bounds;
let mouse_fraction = mouse / viewport_bounds;
let delta = delta_size * (DVec2::splat(0.5) - mouse_fraction);
MovementMessage::TranslateCanvas { delta }.into()
}
}
impl MessageHandler<MovementMessage, (&Document, &InputPreprocessorMessageHandler)> for MovementMessageHandler {
#[remain::check]
fn process_action(&mut self, message: MovementMessage, data: (&Document, &InputPreprocessorMessageHandler), responses: &mut VecDeque<Message>) {
fn process_message(&mut self, message: MovementMessage, data: (&Document, &InputPreprocessorMessageHandler), responses: &mut VecDeque<Message>) {
use MovementMessage::*;
let (document, ipp) = data;
@@ -153,7 +89,7 @@ impl MessageHandler<MovementMessage, (&Document, &InputPreprocessorMessageHandle
self.zoom = 1.
}
responses.push_back(BroadcastSignal::DocumentIsDirty.into());
responses.push_back(BroadcastEvent::DocumentIsDirty.into());
responses.push_back(DocumentMessage::DirtyRenderDocumentInOutlineView.into());
responses.push_back(PortfolioMessage::UpdateDocumentWidgets.into());
self.create_document_transform(&ipp.viewport_bounds, responses);
@@ -245,12 +181,12 @@ impl MessageHandler<MovementMessage, (&Document, &InputPreprocessorMessageHandle
SetCanvasRotation { angle_radians } => {
self.tilt = angle_radians;
self.create_document_transform(&ipp.viewport_bounds, responses);
responses.push_back(BroadcastSignal::DocumentIsDirty.into());
responses.push_back(BroadcastEvent::DocumentIsDirty.into());
responses.push_back(PortfolioMessage::UpdateDocumentWidgets.into());
}
SetCanvasZoom { zoom_factor } => {
self.zoom = zoom_factor.clamp(VIEWPORT_ZOOM_SCALE_MIN, VIEWPORT_ZOOM_SCALE_MAX);
responses.push_back(BroadcastSignal::DocumentIsDirty.into());
responses.push_back(BroadcastEvent::DocumentIsDirty.into());
responses.push_back(DocumentMessage::DirtyRenderDocumentInOutlineView.into());
responses.push_back(PortfolioMessage::UpdateDocumentWidgets.into());
self.create_document_transform(&ipp.viewport_bounds, responses);
@@ -258,7 +194,7 @@ impl MessageHandler<MovementMessage, (&Document, &InputPreprocessorMessageHandle
TransformCanvasEnd => {
self.tilt = self.snapped_angle();
self.zoom = self.snapped_scale();
responses.push_back(BroadcastSignal::DocumentIsDirty.into());
responses.push_back(BroadcastEvent::DocumentIsDirty.into());
responses.push_back(ToolMessage::UpdateCursor.into());
responses.push_back(ToolMessage::UpdateHints.into());
self.snap_tilt = false;
@@ -272,7 +208,7 @@ impl MessageHandler<MovementMessage, (&Document, &InputPreprocessorMessageHandle
let transformed_delta = document.root.transform.inverse().transform_vector2(delta);
self.pan += transformed_delta;
responses.push_back(BroadcastSignal::DocumentIsDirty.into());
responses.push_back(BroadcastEvent::DocumentIsDirty.into());
self.create_document_transform(&ipp.viewport_bounds, responses);
}
TranslateCanvasBegin => {
@@ -286,7 +222,7 @@ impl MessageHandler<MovementMessage, (&Document, &InputPreprocessorMessageHandle
let transformed_delta = document.root.transform.inverse().transform_vector2(delta * ipp.viewport_bounds.size());
self.pan += transformed_delta;
responses.push_back(BroadcastSignal::DocumentIsDirty.into());
responses.push_back(BroadcastEvent::DocumentIsDirty.into());
self.create_document_transform(&ipp.viewport_bounds, responses);
}
WheelCanvasTranslate { use_y_as_x } => {
@@ -352,3 +288,66 @@ impl MessageHandler<MovementMessage, (&Document, &InputPreprocessorMessageHandle
common
}
}
impl MovementMessageHandler {
pub fn snapped_angle(&self) -> f64 {
let increment_radians: f64 = VIEWPORT_ROTATE_SNAP_INTERVAL.to_radians();
if self.snap_tilt {
(self.tilt / increment_radians).round() * increment_radians
} else {
self.tilt
}
}
pub fn snapped_scale(&self) -> f64 {
if self.snap_zoom {
*VIEWPORT_ZOOM_LEVELS
.iter()
.min_by(|a, b| (**a - self.zoom).abs().partial_cmp(&(**b - self.zoom).abs()).unwrap())
.unwrap_or(&self.zoom)
} else {
self.zoom
}
}
pub fn calculate_offset_transform(&self, offset: DVec2) -> DAffine2 {
// TODO: replace with DAffine2::from_scale_angle_translation and fix the errors
let offset_transform = DAffine2::from_translation(offset);
let scale_transform = DAffine2::from_scale(DVec2::splat(self.snapped_scale()));
let angle_transform = DAffine2::from_angle(self.snapped_angle());
let translation_transform = DAffine2::from_translation(self.pan);
scale_transform * offset_transform * angle_transform * translation_transform
}
fn create_document_transform(&self, viewport_bounds: &ViewportBounds, responses: &mut VecDeque<Message>) {
let half_viewport = viewport_bounds.size() / 2.;
let scaled_half_viewport = half_viewport / self.snapped_scale();
responses.push_back(
DocumentOperation::SetLayerTransform {
path: vec![],
transform: self.calculate_offset_transform(scaled_half_viewport).to_cols_array(),
}
.into(),
);
responses.push_back(
ArtboardMessage::DispatchOperation(
DocumentOperation::SetLayerTransform {
path: vec![],
transform: self.calculate_offset_transform(scaled_half_viewport).to_cols_array(),
}
.into(),
)
.into(),
);
}
pub fn center_zoom(&self, viewport_bounds: DVec2, zoom_factor: f64, mouse: DVec2) -> Message {
let new_viewport_bounds = viewport_bounds / zoom_factor;
let delta_size = viewport_bounds - new_viewport_bounds;
let mouse_fraction = mouse / viewport_bounds;
let delta = delta_size * (DVec2::splat(0.5) - mouse_fraction);
MovementMessage::TranslateCanvas { delta }.into()
}
}

View File

@@ -0,0 +1,7 @@
mod overlays_message;
mod overlays_message_handler;
#[doc(inline)]
pub use overlays_message::{OverlaysMessage, OverlaysMessageDiscriminant};
#[doc(inline)]
pub use overlays_message_handler::OverlaysMessageHandler;

View File

@@ -1,4 +1,4 @@
use crate::message_prelude::*;
use crate::messages::prelude::*;
use graphene::Operation as DocumentOperation;

View File

@@ -1,5 +1,4 @@
use crate::input::InputPreprocessorMessageHandler;
use crate::message_prelude::*;
use crate::messages::prelude::*;
use graphene::document::Document as GrapheneDocument;
use graphene::layers::style::{RenderData, ViewMode};
@@ -12,7 +11,7 @@ pub struct OverlaysMessageHandler {
impl MessageHandler<OverlaysMessage, (bool, &FontCache, &InputPreprocessorMessageHandler)> for OverlaysMessageHandler {
#[remain::check]
fn process_action(&mut self, message: OverlaysMessage, (overlays_visible, font_cache, ipp): (bool, &FontCache, &InputPreprocessorMessageHandler), responses: &mut VecDeque<Message>) {
fn process_message(&mut self, message: OverlaysMessage, (overlays_visible, font_cache, ipp): (bool, &FontCache, &InputPreprocessorMessageHandler), responses: &mut VecDeque<Message>) {
use OverlaysMessage::*;
#[remain::sorted]

View File

@@ -0,0 +1,10 @@
mod properties_panel_message;
mod properties_panel_message_handler;
pub mod utility_functions;
pub mod utility_types;
#[doc(inline)]
pub use properties_panel_message::{PropertiesPanelMessage, PropertiesPanelMessageDiscriminant};
#[doc(inline)]
pub use properties_panel_message_handler::PropertiesPanelMessageHandler;

View File

@@ -1,14 +1,17 @@
use crate::message_prelude::*;
use super::utility_types::TargetDocument;
use super::utility_types::TransformOp;
use crate::messages::portfolio::document::utility_types::misc::TargetDocument;
use crate::messages::prelude::*;
use graphene::layers::style::{Fill, Stroke};
use graphene::LayerId;
use serde::{Deserialize, Serialize};
#[remain::sorted]
#[impl_message(Message, DocumentMessage, PropertiesPanel)]
#[derive(PartialEq, Clone, Debug, Serialize, Deserialize)]
pub enum PropertiesPanelMessage {
// Messages
CheckSelectedWasDeleted { path: Vec<LayerId> },
CheckSelectedWasUpdated { path: Vec<LayerId> },
ClearSelection,
@@ -24,14 +27,3 @@ pub enum PropertiesPanelMessage {
SetActiveLayers { paths: Vec<Vec<LayerId>>, document: TargetDocument },
UpdateSelectedDocumentProperties,
}
#[derive(PartialEq, Eq, Clone, Copy, Debug, Serialize, Deserialize)]
pub enum TransformOp {
X,
Y,
ScaleX,
ScaleY,
Width,
Height,
Rotation,
}

View File

@@ -0,0 +1,166 @@
use super::utility_functions::{register_artboard_layer_properties, register_artwork_layer_properties};
use super::utility_types::PropertiesPanelMessageHandlerData;
use crate::messages::layout::utility_types::layout_widget::{Layout, WidgetLayout};
use crate::messages::layout::utility_types::misc::LayoutTarget;
use crate::messages::portfolio::document::properties_panel::utility_functions::apply_transform_operation;
use crate::messages::portfolio::document::utility_types::misc::TargetDocument;
use crate::messages::prelude::*;
use graphene::{LayerId, Operation};
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct PropertiesPanelMessageHandler {
active_selection: Option<(Vec<LayerId>, TargetDocument)>,
}
impl<'a> MessageHandler<PropertiesPanelMessage, PropertiesPanelMessageHandlerData<'a>> for PropertiesPanelMessageHandler {
#[remain::check]
fn process_message(&mut self, message: PropertiesPanelMessage, data: PropertiesPanelMessageHandlerData, responses: &mut VecDeque<Message>) {
let PropertiesPanelMessageHandlerData {
artwork_document,
artboard_document,
selected_layers,
font_cache,
} = data;
let get_document = |document_selector: TargetDocument| match document_selector {
TargetDocument::Artboard => artboard_document,
TargetDocument::Artwork => artwork_document,
};
use PropertiesPanelMessage::*;
match message {
SetActiveLayers { paths, document } => {
if paths.len() != 1 {
// TODO: Allow for multiple selected layers
responses.push_back(PropertiesPanelMessage::ClearSelection.into())
} else {
let path = paths.into_iter().next().unwrap();
self.active_selection = Some((path, document));
responses.push_back(PropertiesPanelMessage::ResendActiveProperties.into())
}
}
ClearSelection => {
responses.push_back(
LayoutMessage::SendLayout {
layout: Layout::WidgetLayout(WidgetLayout::new(vec![])),
layout_target: LayoutTarget::PropertiesOptions,
}
.into(),
);
responses.push_back(
LayoutMessage::SendLayout {
layout: Layout::WidgetLayout(WidgetLayout::new(vec![])),
layout_target: LayoutTarget::PropertiesSections,
}
.into(),
);
self.active_selection = None;
}
Deactivate => responses.push_back(
BroadcastMessage::UnsubscribeEvent {
on: BroadcastEvent::SelectionChanged,
message: Box::new(PropertiesPanelMessage::UpdateSelectedDocumentProperties.into()),
}
.into(),
),
Init => responses.push_back(
BroadcastMessage::SubscribeEvent {
on: BroadcastEvent::SelectionChanged,
send: Box::new(PropertiesPanelMessage::UpdateSelectedDocumentProperties.into()),
}
.into(),
),
ModifyFont { font_family, font_style, size } => {
let (path, _) = self.active_selection.clone().expect("Received update for properties panel with no active layer");
responses.push_back(self.create_document_operation(Operation::ModifyFont { path, font_family, font_style, size }));
responses.push_back(ResendActiveProperties.into());
}
ModifyTransform { value, transform_op } => {
let (path, target_document) = self.active_selection.as_ref().expect("Received update for properties panel with no active layer");
let layer = get_document(*target_document).layer(path).unwrap();
let transform = apply_transform_operation(layer, transform_op, value, font_cache);
responses.push_back(self.create_document_operation(Operation::SetLayerTransform { path: path.clone(), transform }));
}
ModifyName { name } => {
let (path, _) = self.active_selection.clone().expect("Received update for properties panel with no active layer");
responses.push_back(self.create_document_operation(Operation::SetLayerName { path, name }))
}
ModifyFill { fill } => {
let (path, _) = self.active_selection.clone().expect("Received update for properties panel with no active layer");
responses.push_back(self.create_document_operation(Operation::SetLayerFill { path, fill }));
}
ModifyStroke { stroke } => {
let (path, _) = self.active_selection.clone().expect("Received update for properties panel with no active layer");
responses.push_back(self.create_document_operation(Operation::SetLayerStroke { path, stroke }))
}
ModifyText { new_text } => {
let (path, _) = self.active_selection.clone().expect("Received update for properties panel with no active layer");
responses.push_back(Operation::SetTextContent { path, new_text }.into())
}
CheckSelectedWasUpdated { path } => {
if self.matches_selected(&path) {
responses.push_back(PropertiesPanelMessage::ResendActiveProperties.into())
}
}
CheckSelectedWasDeleted { path } => {
if self.matches_selected(&path) {
self.active_selection = None;
responses.push_back(
LayoutMessage::SendLayout {
layout_target: LayoutTarget::PropertiesOptions,
layout: Layout::WidgetLayout(WidgetLayout::default()),
}
.into(),
);
responses.push_back(
LayoutMessage::SendLayout {
layout_target: LayoutTarget::PropertiesSections,
layout: Layout::WidgetLayout(WidgetLayout::default()),
}
.into(),
);
}
}
ResendActiveProperties => {
if let Some((path, target_document)) = self.active_selection.clone() {
let layer = get_document(target_document).layer(&path).unwrap();
match target_document {
TargetDocument::Artboard => register_artboard_layer_properties(layer, responses, font_cache),
TargetDocument::Artwork => register_artwork_layer_properties(layer, responses, font_cache),
}
}
}
UpdateSelectedDocumentProperties => responses.push_back(
PropertiesPanelMessage::SetActiveLayers {
paths: selected_layers.map(|path| path.to_vec()).collect(),
document: TargetDocument::Artwork,
}
.into(),
),
}
}
fn actions(&self) -> ActionList {
actions!(PropertiesMessageDiscriminant;)
}
}
impl PropertiesPanelMessageHandler {
fn matches_selected(&self, path: &[LayerId]) -> bool {
let last_active_path_id = self.active_selection.as_ref().and_then(|(v, _)| v.last().copied());
let last_modified = path.last().copied();
matches!((last_active_path_id, last_modified), (Some(active_last), Some(modified_last)) if active_last == modified_last)
}
fn create_document_operation(&self, operation: Operation) -> Message {
let (_, target_document) = self.active_selection.as_ref().unwrap();
match *target_document {
TargetDocument::Artboard => ArtboardMessage::DispatchOperation(Box::new(operation)).into(),
TargetDocument::Artwork => DocumentMessage::DispatchOperation(Box::new(operation)).into(),
}
}
}

View File

@@ -1,270 +1,39 @@
use super::utility_types::TargetDocument;
use crate::document::properties_panel_message::TransformOp;
use crate::layout::layout_message::LayoutTarget;
use crate::layout::widgets::{
ColorInput, FontInput, IconLabel, IconStyle, Layout, LayoutGroup, NumberInput, PopoverButton, RadioEntryData, RadioInput, Separator, SeparatorDirection, SeparatorType, TextAreaInput, TextInput,
TextLabel, Widget, WidgetCallback, WidgetHolder, WidgetLayout,
};
use crate::message_prelude::*;
use super::utility_types::TransformOp;
use crate::messages::layout::utility_types::layout_widget::{Layout, LayoutGroup, Widget, WidgetCallback, WidgetHolder, WidgetLayout};
use crate::messages::layout::utility_types::misc::LayoutTarget;
use crate::messages::layout::utility_types::widgets::button_widgets::PopoverButton;
use crate::messages::layout::utility_types::widgets::input_widgets::{ColorInput, FontInput, NumberInput, RadioEntryData, RadioInput, TextAreaInput, TextInput};
use crate::messages::layout::utility_types::widgets::label_widgets::{IconLabel, IconStyle, Separator, SeparatorDirection, SeparatorType, TextLabel};
use crate::messages::prelude::*;
use glam::{DAffine2, DVec2};
use graphene::color::Color;
use graphene::document::Document as GrapheneDocument;
use graphene::layers::layer_info::{Layer, LayerDataType, LayerDataTypeDiscriminant};
use graphene::layers::style::{Fill, Gradient, GradientType, LineCap, LineJoin, Stroke};
use graphene::layers::text_layer::{FontCache, TextLayer};
use graphene::{LayerId, Operation};
use glam::{DAffine2, DVec2};
use serde::{Deserialize, Serialize};
use std::f64::consts::PI;
use std::rc::Rc;
trait DAffine2Utils {
fn scale_x(&self) -> f64;
fn update_scale_x(self, new_width: f64) -> Self;
fn scale_y(&self) -> f64;
fn update_scale_y(self, new_height: f64) -> Self;
fn x(&self) -> f64;
fn update_x(self, new_x: f64) -> Self;
fn y(&self) -> f64;
fn update_y(self, new_y: f64) -> Self;
fn rotation(&self) -> f64;
fn update_rotation(self, new_rotation: f64) -> Self;
pub fn apply_transform_operation(layer: &Layer, transform_op: TransformOp, value: f64, font_cache: &FontCache) -> [f64; 6] {
let transformation = match transform_op {
TransformOp::X => DAffine2::update_x,
TransformOp::Y => DAffine2::update_y,
TransformOp::ScaleX | TransformOp::Width => DAffine2::update_scale_x,
TransformOp::ScaleY | TransformOp::Height => DAffine2::update_scale_y,
TransformOp::Rotation => DAffine2::update_rotation,
};
let scale = match transform_op {
TransformOp::Width => layer.bounding_transform(font_cache).scale_x() / layer.transform.scale_x(),
TransformOp::Height => layer.bounding_transform(font_cache).scale_y() / layer.transform.scale_y(),
_ => 1.,
};
transformation(layer.transform, value / scale).to_cols_array()
}
impl DAffine2Utils for DAffine2 {
fn scale_x(&self) -> f64 {
self.transform_vector2((1., 0.).into()).length()
}
fn update_scale_x(self, new_width: f64) -> Self {
self * DAffine2::from_scale((new_width / self.scale_x(), 1.).into())
}
fn scale_y(&self) -> f64 {
self.transform_vector2((0., 1.).into()).length()
}
fn update_scale_y(self, new_height: f64) -> Self {
self * DAffine2::from_scale((1., new_height / self.scale_y()).into())
}
fn x(&self) -> f64 {
self.translation.x
}
fn update_x(mut self, new_x: f64) -> Self {
self.translation.x = new_x;
self
}
fn y(&self) -> f64 {
self.translation.y
}
fn update_y(mut self, new_y: f64) -> Self {
self.translation.y = new_y;
self
}
fn rotation(&self) -> f64 {
let cos = self.matrix2.col(0).x / self.scale_x();
let sin = self.matrix2.col(0).y / self.scale_x();
sin.atan2(cos)
}
fn update_rotation(self, new_rotation: f64) -> Self {
let width = self.scale_x();
let height = self.scale_y();
let half_width = width / 2.;
let half_height = height / 2.;
let angle_translation_offset = |angle: f64| DVec2::new(-half_width * angle.cos() + half_height * angle.sin(), -half_width * angle.sin() - half_height * angle.cos());
let angle_translation_adjustment = angle_translation_offset(new_rotation) - angle_translation_offset(self.rotation());
DAffine2::from_scale_angle_translation((width, height).into(), new_rotation, self.translation + angle_translation_adjustment)
}
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct PropertiesPanelMessageHandler {
active_selection: Option<(Vec<LayerId>, TargetDocument)>,
}
impl PropertiesPanelMessageHandler {
fn matches_selected(&self, path: &[LayerId]) -> bool {
let last_active_path_id = self.active_selection.as_ref().and_then(|(v, _)| v.last().copied());
let last_modified = path.last().copied();
matches!((last_active_path_id, last_modified), (Some(active_last), Some(modified_last)) if active_last == modified_last)
}
fn create_document_operation(&self, operation: Operation) -> Message {
let (_, target_document) = self.active_selection.as_ref().unwrap();
match *target_document {
TargetDocument::Artboard => ArtboardMessage::DispatchOperation(Box::new(operation)).into(),
TargetDocument::Artwork => DocumentMessage::DispatchOperation(Box::new(operation)).into(),
}
}
}
pub struct PropertiesPanelMessageHandlerData<'a> {
pub artwork_document: &'a GrapheneDocument,
pub artboard_document: &'a GrapheneDocument,
pub selected_layers: &'a mut dyn Iterator<Item = &'a [LayerId]>,
pub font_cache: &'a FontCache,
}
impl<'a> MessageHandler<PropertiesPanelMessage, PropertiesPanelMessageHandlerData<'a>> for PropertiesPanelMessageHandler {
#[remain::check]
fn process_action(&mut self, message: PropertiesPanelMessage, data: PropertiesPanelMessageHandlerData, responses: &mut VecDeque<Message>) {
let PropertiesPanelMessageHandlerData {
artwork_document,
artboard_document,
selected_layers,
font_cache,
} = data;
let get_document = |document_selector: TargetDocument| match document_selector {
TargetDocument::Artboard => artboard_document,
TargetDocument::Artwork => artwork_document,
};
use PropertiesPanelMessage::*;
match message {
SetActiveLayers { paths, document } => {
if paths.len() != 1 {
// TODO: Allow for multiple selected layers
responses.push_back(PropertiesPanelMessage::ClearSelection.into())
} else {
let path = paths.into_iter().next().unwrap();
self.active_selection = Some((path, document));
responses.push_back(PropertiesPanelMessage::ResendActiveProperties.into())
}
}
ClearSelection => {
responses.push_back(
LayoutMessage::SendLayout {
layout: Layout::WidgetLayout(WidgetLayout::new(vec![])),
layout_target: LayoutTarget::PropertiesOptions,
}
.into(),
);
responses.push_back(
LayoutMessage::SendLayout {
layout: Layout::WidgetLayout(WidgetLayout::new(vec![])),
layout_target: LayoutTarget::PropertiesSections,
}
.into(),
);
self.active_selection = None;
}
Deactivate => responses.push_back(
BroadcastMessage::UnsubscribeSignal {
on: BroadcastSignal::SelectionChanged,
message: Box::new(PropertiesPanelMessage::UpdateSelectedDocumentProperties.into()),
}
.into(),
),
Init => responses.push_back(
BroadcastMessage::SubscribeSignal {
on: BroadcastSignal::SelectionChanged,
send: Box::new(PropertiesPanelMessage::UpdateSelectedDocumentProperties.into()),
}
.into(),
),
ModifyFont { font_family, font_style, size } => {
let (path, _) = self.active_selection.clone().expect("Received update for properties panel with no active layer");
responses.push_back(self.create_document_operation(Operation::ModifyFont { path, font_family, font_style, size }));
responses.push_back(ResendActiveProperties.into());
}
ModifyTransform { value, transform_op } => {
let (path, target_document) = self.active_selection.as_ref().expect("Received update for properties panel with no active layer");
let layer = get_document(*target_document).layer(path).unwrap();
use TransformOp::*;
let action = match transform_op {
X => DAffine2::update_x,
Y => DAffine2::update_y,
ScaleX | Width => DAffine2::update_scale_x,
ScaleY | Height => DAffine2::update_scale_y,
Rotation => DAffine2::update_rotation,
};
let scale = match transform_op {
Width => layer.bounding_transform(font_cache).scale_x() / layer.transform.scale_x(),
Height => layer.bounding_transform(font_cache).scale_y() / layer.transform.scale_y(),
_ => 1.,
};
responses.push_back(self.create_document_operation(Operation::SetLayerTransform {
path: path.clone(),
transform: action(layer.transform, value / scale).to_cols_array(),
}));
}
ModifyName { name } => {
let (path, _) = self.active_selection.clone().expect("Received update for properties panel with no active layer");
responses.push_back(self.create_document_operation(Operation::SetLayerName { path, name }))
}
ModifyFill { fill } => {
let (path, _) = self.active_selection.clone().expect("Received update for properties panel with no active layer");
responses.push_back(self.create_document_operation(Operation::SetLayerFill { path, fill }));
}
ModifyStroke { stroke } => {
let (path, _) = self.active_selection.clone().expect("Received update for properties panel with no active layer");
responses.push_back(self.create_document_operation(Operation::SetLayerStroke { path, stroke }))
}
ModifyText { new_text } => {
let (path, _) = self.active_selection.clone().expect("Received update for properties panel with no active layer");
responses.push_back(Operation::SetTextContent { path, new_text }.into())
}
CheckSelectedWasUpdated { path } => {
if self.matches_selected(&path) {
responses.push_back(PropertiesPanelMessage::ResendActiveProperties.into())
}
}
CheckSelectedWasDeleted { path } => {
if self.matches_selected(&path) {
self.active_selection = None;
responses.push_back(
LayoutMessage::SendLayout {
layout_target: LayoutTarget::PropertiesOptions,
layout: Layout::WidgetLayout(WidgetLayout::default()),
}
.into(),
);
responses.push_back(
LayoutMessage::SendLayout {
layout_target: LayoutTarget::PropertiesSections,
layout: Layout::WidgetLayout(WidgetLayout::default()),
}
.into(),
);
}
}
ResendActiveProperties => {
if let Some((path, target_document)) = self.active_selection.clone() {
let layer = get_document(target_document).layer(&path).unwrap();
match target_document {
TargetDocument::Artboard => register_artboard_layer_properties(layer, responses, font_cache),
TargetDocument::Artwork => register_artwork_layer_properties(layer, responses, font_cache),
}
}
}
UpdateSelectedDocumentProperties => responses.push_back(
PropertiesPanelMessage::SetActiveLayers {
paths: selected_layers.map(|path| path.to_vec()).collect(),
document: TargetDocument::Artwork,
}
.into(),
),
}
}
fn actions(&self) -> ActionList {
actions!(PropertiesMessageDiscriminant;)
}
}
fn register_artboard_layer_properties(layer: &Layer, responses: &mut VecDeque<Message>, font_cache: &FontCache) {
pub fn register_artboard_layer_properties(layer: &Layer, responses: &mut VecDeque<Message>, font_cache: &FontCache) {
let options_bar = vec![LayoutGroup::Row {
widgets: vec![
WidgetHolder::new(Widget::IconLabel(IconLabel {
@@ -448,7 +217,7 @@ fn register_artboard_layer_properties(layer: &Layer, responses: &mut VecDeque<Me
);
}
fn register_artwork_layer_properties(layer: &Layer, responses: &mut VecDeque<Message>, font_cache: &FontCache) {
pub fn register_artwork_layer_properties(layer: &Layer, responses: &mut VecDeque<Message>, font_cache: &FontCache) {
let options_bar = vec![LayoutGroup::Row {
widgets: vec![
match &layer.data {
@@ -1164,3 +933,70 @@ fn node_section_stroke(stroke: &Stroke) -> LayoutGroup {
],
}
}
trait DAffine2Utils {
fn scale_x(&self) -> f64;
fn update_scale_x(self, new_width: f64) -> Self;
fn scale_y(&self) -> f64;
fn update_scale_y(self, new_height: f64) -> Self;
fn x(&self) -> f64;
fn update_x(self, new_x: f64) -> Self;
fn y(&self) -> f64;
fn update_y(self, new_y: f64) -> Self;
fn rotation(&self) -> f64;
fn update_rotation(self, new_rotation: f64) -> Self;
}
impl DAffine2Utils for DAffine2 {
fn scale_x(&self) -> f64 {
self.transform_vector2((1., 0.).into()).length()
}
fn update_scale_x(self, new_width: f64) -> Self {
self * DAffine2::from_scale((new_width / self.scale_x(), 1.).into())
}
fn scale_y(&self) -> f64 {
self.transform_vector2((0., 1.).into()).length()
}
fn update_scale_y(self, new_height: f64) -> Self {
self * DAffine2::from_scale((1., new_height / self.scale_y()).into())
}
fn x(&self) -> f64 {
self.translation.x
}
fn update_x(mut self, new_x: f64) -> Self {
self.translation.x = new_x;
self
}
fn y(&self) -> f64 {
self.translation.y
}
fn update_y(mut self, new_y: f64) -> Self {
self.translation.y = new_y;
self
}
fn rotation(&self) -> f64 {
let cos = self.matrix2.col(0).x / self.scale_x();
let sin = self.matrix2.col(0).y / self.scale_x();
sin.atan2(cos)
}
fn update_rotation(self, new_rotation: f64) -> Self {
let width = self.scale_x();
let height = self.scale_y();
let half_width = width / 2.;
let half_height = height / 2.;
let angle_translation_offset = |angle: f64| DVec2::new(-half_width * angle.cos() + half_height * angle.sin(), -half_width * angle.sin() - half_height * angle.cos());
let angle_translation_adjustment = angle_translation_offset(new_rotation) - angle_translation_offset(self.rotation());
DAffine2::from_scale_angle_translation((width, height).into(), new_rotation, self.translation + angle_translation_adjustment)
}
}

View File

@@ -0,0 +1,23 @@
use graphene::document::Document as GrapheneDocument;
use graphene::layers::text_layer::FontCache;
use graphene::LayerId;
use serde::{Deserialize, Serialize};
pub struct PropertiesPanelMessageHandlerData<'a> {
pub artwork_document: &'a GrapheneDocument,
pub artboard_document: &'a GrapheneDocument,
pub selected_layers: &'a mut dyn Iterator<Item = &'a [LayerId]>,
pub font_cache: &'a FontCache,
}
#[derive(PartialEq, Eq, Clone, Copy, Debug, Serialize, Deserialize)]
pub enum TransformOp {
X,
Y,
ScaleX,
ScaleY,
Width,
Height,
Rotation,
}

View File

@@ -0,0 +1,7 @@
mod transform_layer_message;
mod transform_layer_message_handler;
#[doc(inline)]
pub use transform_layer_message::{TransformLayerMessage, TransformLayerMessageDiscriminant};
#[doc(inline)]
pub use transform_layer_message_handler::TransformLayerMessageHandler;

View File

@@ -1,12 +1,13 @@
use crate::input::keyboard::Key;
use crate::message_prelude::*;
use crate::messages::input_mapper::utility_types::input_keyboard::Key;
use crate::messages::prelude::*;
use serde::{Deserialize, Serialize};
#[remain::sorted]
#[impl_message(Message, DocumentMessage, TransformLayers)]
#[impl_message(Message, DocumentMessage, TransformLayer)]
#[derive(PartialEq, Eq, Clone, Debug, Serialize, Deserialize)]
pub enum TransformLayerMessage {
// Messages
ApplyTransformOperation,
BeginGrab,
BeginRotate,

View File

@@ -1,11 +1,11 @@
use super::layer_panel::LayerMetadata;
use super::transformation::{Axis, OriginalTransforms, Selected, TransformOperation, Typing};
use crate::consts::SLOWING_DIVISOR;
use crate::input::mouse::ViewportPosition;
use crate::input::InputPreprocessorMessageHandler;
use crate::message_prelude::*;
use crate::messages::input_mapper::utility_types::input_mouse::ViewportPosition;
use crate::messages::portfolio::document::utility_types::layer_panel::LayerMetadata;
use crate::messages::portfolio::document::utility_types::transformation::{Axis, OriginalTransforms, Selected, TransformOperation, Typing};
use crate::messages::prelude::*;
use graphene::document::Document;
use graphene::LayerId;
use glam::DVec2;
use graphene::layers::text_layer::FontCache;
@@ -29,7 +29,7 @@ pub struct TransformLayerMessageHandler {
type TransformData<'a> = (&'a mut HashMap<Vec<LayerId>, LayerMetadata>, &'a mut Document, &'a InputPreprocessorMessageHandler, &'a FontCache);
impl<'a> MessageHandler<TransformLayerMessage, TransformData<'a>> for TransformLayerMessageHandler {
#[remain::check]
fn process_action(&mut self, message: TransformLayerMessage, (layer_metadata, document, ipp, font_cache): TransformData, responses: &mut VecDeque<Message>) {
fn process_message(&mut self, message: TransformLayerMessage, (layer_metadata, document, ipp, font_cache): TransformData, responses: &mut VecDeque<Message>) {
use TransformLayerMessage::*;
let selected_layers = layer_metadata.iter().filter_map(|(layer_path, data)| data.selected.then(|| layer_path)).collect::<Vec<_>>();
@@ -55,7 +55,7 @@ impl<'a> MessageHandler<TransformLayerMessage, TransformData<'a>> for TransformL
self.transform_operation = TransformOperation::None;
responses.push_back(BroadcastSignal::DocumentIsDirty.into());
responses.push_back(BroadcastEvent::DocumentIsDirty.into());
}
BeginGrab => {
if let TransformOperation::Grabbing(_) = self.transform_operation {
@@ -66,7 +66,7 @@ impl<'a> MessageHandler<TransformLayerMessage, TransformData<'a>> for TransformL
self.transform_operation = TransformOperation::Grabbing(Default::default());
responses.push_back(BroadcastSignal::DocumentIsDirty.into());
responses.push_back(BroadcastEvent::DocumentIsDirty.into());
}
BeginRotate => {
if let TransformOperation::Rotating(_) = self.transform_operation {
@@ -77,7 +77,7 @@ impl<'a> MessageHandler<TransformLayerMessage, TransformData<'a>> for TransformL
self.transform_operation = TransformOperation::Rotating(Default::default());
responses.push_back(BroadcastSignal::DocumentIsDirty.into());
responses.push_back(BroadcastEvent::DocumentIsDirty.into());
}
BeginScale => {
if let TransformOperation::Scaling(_) = self.transform_operation {
@@ -89,7 +89,7 @@ impl<'a> MessageHandler<TransformLayerMessage, TransformData<'a>> for TransformL
self.transform_operation = TransformOperation::Scaling(Default::default());
self.transform_operation.apply_transform_operation(&mut selected, self.snap);
responses.push_back(BroadcastSignal::DocumentIsDirty.into());
responses.push_back(BroadcastEvent::DocumentIsDirty.into());
}
CancelTransformOperation => {
selected.revert_operation();
@@ -99,7 +99,7 @@ impl<'a> MessageHandler<TransformLayerMessage, TransformData<'a>> for TransformL
self.transform_operation = TransformOperation::None;
responses.push_back(BroadcastSignal::DocumentIsDirty.into());
responses.push_back(BroadcastEvent::DocumentIsDirty.into());
}
ConstrainX => self.transform_operation.constrain_axis(Axis::X, &mut selected, self.snap),
ConstrainY => self.transform_operation.constrain_axis(Axis::Y, &mut selected, self.snap),

View File

@@ -7,53 +7,6 @@ use glam::{DAffine2, DVec2};
use serde::ser::SerializeStruct;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Copy)]
pub struct LayerMetadata {
pub selected: bool,
pub expanded: bool,
}
impl LayerMetadata {
pub fn new(expanded: bool) -> LayerMetadata {
LayerMetadata { selected: false, expanded }
}
}
pub fn layer_panel_entry(layer_metadata: &LayerMetadata, transform: DAffine2, layer: &Layer, path: Vec<LayerId>, font_cache: &FontCache) -> LayerPanelEntry {
let name = layer.name.clone().unwrap_or_else(|| String::from(""));
let arr = layer.data.bounding_box(transform, font_cache).unwrap_or([DVec2::ZERO, DVec2::ZERO]);
let arr = arr.iter().map(|x| (*x).into()).collect::<Vec<(f64, f64)>>();
let mut thumbnail = String::new();
let mut svg_defs = String::new();
let render_data = RenderData::new(ViewMode::Normal, font_cache, None, false);
layer.data.clone().render(&mut thumbnail, &mut svg_defs, &mut vec![transform], render_data);
let transform = transform.to_cols_array().iter().map(ToString::to_string).collect::<Vec<_>>().join(",");
let thumbnail = if let [(x_min, y_min), (x_max, y_max)] = arr.as_slice() {
format!(
r#"<svg xmlns="http://www.w3.org/2000/svg" viewBox="{} {} {} {}"><defs>{}</defs><g transform="matrix({})">{}</g></svg>"#,
x_min,
y_min,
x_max - x_min,
y_max - y_min,
svg_defs,
transform,
thumbnail,
)
} else {
String::new()
};
LayerPanelEntry {
name,
visible: layer.visible,
layer_type: (&layer.data).into(),
layer_metadata: *layer_metadata,
path,
thumbnail,
}
}
#[derive(Debug, Clone, Deserialize, PartialEq, Eq)]
pub struct RawBuffer(Vec<u8>);
@@ -69,11 +22,9 @@ impl From<Vec<u64>> for RawBuffer {
Self(v_from_raw)
}
}
impl Serialize for RawBuffer {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
let mut buffer = serializer.serialize_struct("Buffer", 2)?;
buffer.serialize_field("pointer", &(self.0.as_ptr() as usize))?;
buffer.serialize_field("length", &(self.0.len()))?;
@@ -81,6 +32,18 @@ impl Serialize for RawBuffer {
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Copy)]
pub struct LayerMetadata {
pub selected: bool,
pub expanded: bool,
}
impl LayerMetadata {
pub fn new(expanded: bool) -> Self {
Self { selected: false, expanded }
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct LayerPanelEntry {
pub name: String,
@@ -90,3 +53,40 @@ pub struct LayerPanelEntry {
pub path: Vec<LayerId>,
pub thumbnail: String,
}
impl LayerPanelEntry {
pub fn new(layer_metadata: &LayerMetadata, transform: DAffine2, layer: &Layer, path: Vec<LayerId>, font_cache: &FontCache) -> Self {
let name = layer.name.clone().unwrap_or_else(|| String::from(""));
let arr = layer.data.bounding_box(transform, font_cache).unwrap_or([DVec2::ZERO, DVec2::ZERO]);
let arr = arr.iter().map(|x| (*x).into()).collect::<Vec<(f64, f64)>>();
let mut thumbnail = String::new();
let mut svg_defs = String::new();
let render_data = RenderData::new(ViewMode::Normal, font_cache, None, false);
layer.data.clone().render(&mut thumbnail, &mut svg_defs, &mut vec![transform], render_data);
let transform = transform.to_cols_array().iter().map(ToString::to_string).collect::<Vec<_>>().join(",");
let thumbnail = if let [(x_min, y_min), (x_max, y_max)] = arr.as_slice() {
format!(
r#"<svg xmlns="http://www.w3.org/2000/svg" viewBox="{} {} {} {}"><defs>{}</defs><g transform="matrix({})">{}</g></svg>"#,
x_min,
y_min,
x_max - x_min,
y_max - y_min,
svg_defs,
transform,
thumbnail,
)
} else {
String::new()
};
LayerPanelEntry {
name,
visible: layer.visible,
layer_type: (&layer.data).into(),
layer_metadata: *layer_metadata,
path,
thumbnail,
}
}
}

View File

@@ -1,4 +1,5 @@
pub use super::layer_panel::{layer_panel_entry, LayerMetadata, LayerPanelEntry, RawBuffer};
pub use super::layer_panel::{LayerMetadata, LayerPanelEntry};
use graphene::document::Document as GrapheneDocument;
use graphene::LayerId;

View File

@@ -0,0 +1,6 @@
pub mod clipboards;
pub mod error;
pub mod layer_panel;
pub mod misc;
pub mod transformation;
pub mod vectorize_layer_metadata;

View File

@@ -1,8 +1,9 @@
use crate::consts::{ROTATE_SNAP_ANGLE, SCALE_SNAP_INTERVAL};
use crate::message_prelude::*;
use crate::messages::prelude::*;
use graphene::document::Document;
use graphene::layers::text_layer::FontCache;
use graphene::LayerId;
use graphene::Operation as DocumentOperation;
use glam::{DAffine2, DVec2};
@@ -258,7 +259,7 @@ impl<'a> Selected<'a> {
);
}
self.responses.push_back(BroadcastSignal::DocumentIsDirty.into());
self.responses.push_back(BroadcastEvent::DocumentIsDirty.into());
}
}

View File

@@ -1,4 +1,4 @@
use crate::message_prelude::*;
use crate::messages::prelude::*;
use serde::{Deserialize, Serialize};
@@ -6,5 +6,6 @@ use serde::{Deserialize, Serialize};
#[impl_message(Message, PortfolioMessage, MenuBar)]
#[derive(PartialEq, Eq, Clone, Debug, Hash, Serialize, Deserialize)]
pub enum MenuBarMessage {
// Messages
SendLayout,
}

View File

@@ -1,8 +1,10 @@
use super::MenuBarMessage;
use crate::input::input_mapper::action_keys::action_shortcut;
use crate::layout::layout_message::LayoutTarget;
use crate::layout::widgets::*;
use crate::message_prelude::*;
use crate::messages::input_mapper::utility_types::macros::action_keys;
use crate::messages::layout::utility_types::layout_widget::{Layout, PropertyHolder};
use crate::messages::layout::utility_types::misc::LayoutTarget;
use crate::messages::layout::utility_types::widgets::menu_widgets::{MenuColumn, MenuEntry, MenuEntryGroups, MenuLayout};
use crate::messages::portfolio::document::utility_types::clipboards::Clipboard;
use crate::messages::prelude::*;
use std::collections::VecDeque;
@@ -11,7 +13,7 @@ pub struct MenuBarMessageHandler {}
impl MessageHandler<MenuBarMessage, ()> for MenuBarMessageHandler {
#[remain::check]
fn process_action(&mut self, message: MenuBarMessage, _data: (), responses: &mut VecDeque<Message>) {
fn process_message(&mut self, message: MenuBarMessage, _data: (), responses: &mut VecDeque<Message>) {
use MenuBarMessage::*;
#[remain::sorted]
@@ -36,12 +38,12 @@ impl PropertyHolder for MenuBarMessageHandler {
label: "New…".into(),
icon: Some("File".into()),
action: MenuEntry::create_action(|_| DialogMessage::RequestNewDocumentDialog.into()),
shortcut: action_shortcut!(DialogMessageDiscriminant::RequestNewDocumentDialog),
shortcut: action_keys!(DialogMessageDiscriminant::RequestNewDocumentDialog),
children: MenuEntryGroups::empty(),
},
MenuEntry {
label: "Open…".into(),
shortcut: action_shortcut!(PortfolioMessageDiscriminant::OpenDocument),
shortcut: action_keys!(PortfolioMessageDiscriminant::OpenDocument),
action: MenuEntry::create_action(|_| PortfolioMessage::OpenDocument.into()),
..MenuEntry::default()
},
@@ -90,13 +92,13 @@ impl PropertyHolder for MenuBarMessageHandler {
vec![
MenuEntry {
label: "Close".into(),
shortcut: action_shortcut!(PortfolioMessageDiscriminant::CloseActiveDocumentWithConfirmation),
shortcut: action_keys!(PortfolioMessageDiscriminant::CloseActiveDocumentWithConfirmation),
action: MenuEntry::create_action(|_| PortfolioMessage::CloseActiveDocumentWithConfirmation.into()),
..MenuEntry::default()
},
MenuEntry {
label: "Close All".into(),
shortcut: action_shortcut!(DialogMessageDiscriminant::CloseAllDocumentsWithConfirmation),
shortcut: action_keys!(DialogMessageDiscriminant::CloseAllDocumentsWithConfirmation),
action: MenuEntry::create_action(|_| DialogMessage::CloseAllDocumentsWithConfirmation.into()),
..MenuEntry::default()
},
@@ -104,7 +106,7 @@ impl PropertyHolder for MenuBarMessageHandler {
vec![
MenuEntry {
label: "Save".into(),
shortcut: action_shortcut!(DocumentMessageDiscriminant::SaveDocument),
shortcut: action_keys!(DocumentMessageDiscriminant::SaveDocument),
action: MenuEntry::create_action(|_| DocumentMessage::SaveDocument.into()),
..MenuEntry::default()
},
@@ -127,13 +129,13 @@ impl PropertyHolder for MenuBarMessageHandler {
vec![
MenuEntry {
label: "Import…".into(),
shortcut: action_shortcut!(PortfolioMessageDiscriminant::Import),
shortcut: action_keys!(PortfolioMessageDiscriminant::Import),
action: MenuEntry::create_action(|_| PortfolioMessage::Import.into()),
..MenuEntry::default()
},
MenuEntry {
label: "Export…".into(),
shortcut: action_shortcut!(DialogMessageDiscriminant::RequestExportDialog),
shortcut: action_keys!(DialogMessageDiscriminant::RequestExportDialog),
action: MenuEntry::create_action(|_| DialogMessage::RequestExportDialog.into()),
..MenuEntry::default()
},
@@ -151,13 +153,13 @@ impl PropertyHolder for MenuBarMessageHandler {
vec![
MenuEntry {
label: "Undo".into(),
shortcut: action_shortcut!(DocumentMessageDiscriminant::Undo),
shortcut: action_keys!(DocumentMessageDiscriminant::Undo),
action: MenuEntry::create_action(|_| DocumentMessage::Undo.into()),
..MenuEntry::default()
},
MenuEntry {
label: "Redo".into(),
shortcut: action_shortcut!(DocumentMessageDiscriminant::Redo),
shortcut: action_keys!(DocumentMessageDiscriminant::Redo),
action: MenuEntry::create_action(|_| DocumentMessage::Redo.into()),
..MenuEntry::default()
},
@@ -165,21 +167,21 @@ impl PropertyHolder for MenuBarMessageHandler {
vec![
MenuEntry {
label: "Cut".into(),
shortcut: action_shortcut!(PortfolioMessageDiscriminant::Cut),
shortcut: action_keys!(PortfolioMessageDiscriminant::Cut),
action: MenuEntry::create_action(|_| PortfolioMessage::Cut { clipboard: Clipboard::Device }.into()),
..MenuEntry::default()
},
MenuEntry {
label: "Copy".into(),
icon: Some("Copy".into()),
shortcut: action_shortcut!(PortfolioMessageDiscriminant::Copy),
shortcut: action_keys!(PortfolioMessageDiscriminant::Copy),
action: MenuEntry::create_action(|_| PortfolioMessage::Copy { clipboard: Clipboard::Device }.into()),
..MenuEntry::default()
},
MenuEntry {
label: "Paste".into(),
icon: Some("Paste".into()),
shortcut: action_shortcut!(FrontendMessageDiscriminant::TriggerPaste),
shortcut: action_keys!(FrontendMessageDiscriminant::TriggerPaste),
action: MenuEntry::create_action(|_| FrontendMessage::TriggerPaste.into()),
..MenuEntry::default()
},
@@ -191,13 +193,13 @@ impl PropertyHolder for MenuBarMessageHandler {
children: MenuEntryGroups(vec![vec![
MenuEntry {
label: "Select All".into(),
shortcut: action_shortcut!(DocumentMessageDiscriminant::SelectAllLayers),
shortcut: action_keys!(DocumentMessageDiscriminant::SelectAllLayers),
action: MenuEntry::create_action(|_| DocumentMessage::SelectAllLayers.into()),
..MenuEntry::default()
},
MenuEntry {
label: "Deselect All".into(),
shortcut: action_shortcut!(DocumentMessageDiscriminant::DeselectAllLayers),
shortcut: action_keys!(DocumentMessageDiscriminant::DeselectAllLayers),
action: MenuEntry::create_action(|_| DocumentMessage::DeselectAllLayers.into()),
..MenuEntry::default()
},
@@ -207,25 +209,25 @@ impl PropertyHolder for MenuBarMessageHandler {
children: MenuEntryGroups(vec![vec![
MenuEntry {
label: "Raise To Front".into(),
shortcut: action_shortcut!(DocumentMessageDiscriminant::SelectedLayersRaiseToFront),
shortcut: action_keys!(DocumentMessageDiscriminant::SelectedLayersRaiseToFront),
action: MenuEntry::create_action(|_| DocumentMessage::SelectedLayersRaiseToFront.into()),
..MenuEntry::default()
},
MenuEntry {
label: "Raise".into(),
shortcut: action_shortcut!(DocumentMessageDiscriminant::SelectedLayersRaise),
shortcut: action_keys!(DocumentMessageDiscriminant::SelectedLayersRaise),
action: MenuEntry::create_action(|_| DocumentMessage::SelectedLayersRaise.into()),
..MenuEntry::default()
},
MenuEntry {
label: "Lower".into(),
shortcut: action_shortcut!(DocumentMessageDiscriminant::SelectedLayersLower),
shortcut: action_keys!(DocumentMessageDiscriminant::SelectedLayersLower),
action: MenuEntry::create_action(|_| DocumentMessage::SelectedLayersLower.into()),
..MenuEntry::default()
},
MenuEntry {
label: "Lower to Back".into(),
shortcut: action_shortcut!(DocumentMessageDiscriminant::SelectedLayersLowerToBack),
shortcut: action_keys!(DocumentMessageDiscriminant::SelectedLayersLowerToBack),
action: MenuEntry::create_action(|_| DocumentMessage::SelectedLayersLowerToBack.into()),
..MenuEntry::default()
},
@@ -287,19 +289,19 @@ impl PropertyHolder for MenuBarMessageHandler {
MenuEntry {
label: "Off".into(),
// icon: Some("Checkmark".into()), // TODO: Find a way to set this icon on the active mode
shortcut: action_shortcut!(DebugMessageDiscriminant::MessageOff),
shortcut: action_keys!(DebugMessageDiscriminant::MessageOff),
action: MenuEntry::create_action(|_| DebugMessage::MessageOff.into()),
..MenuEntry::default()
},
MenuEntry {
label: "Only Names".into(),
shortcut: action_shortcut!(DebugMessageDiscriminant::MessageNames),
shortcut: action_keys!(DebugMessageDiscriminant::MessageNames),
action: MenuEntry::create_action(|_| DebugMessage::MessageNames.into()),
..MenuEntry::default()
},
MenuEntry {
label: "Full Contents".into(),
shortcut: action_shortcut!(DebugMessageDiscriminant::MessageContents),
shortcut: action_keys!(DebugMessageDiscriminant::MessageContents),
action: MenuEntry::create_action(|_| DebugMessage::MessageContents.into()),
..MenuEntry::default()
},
@@ -309,13 +311,13 @@ impl PropertyHolder for MenuBarMessageHandler {
MenuEntry {
label: "Debug: Print Trace Logs".into(),
icon: Some(if let log::LevelFilter::Trace = log::max_level() { "CheckboxChecked" } else { "CheckboxUnchecked" }.into()),
shortcut: action_shortcut!(DebugMessageDiscriminant::ToggleTraceLogs),
shortcut: action_keys!(DebugMessageDiscriminant::ToggleTraceLogs),
action: MenuEntry::create_action(|_| DebugMessage::ToggleTraceLogs.into()),
..MenuEntry::default()
},
MenuEntry {
label: "Debug: Print Document".into(),
shortcut: action_shortcut!(DocumentMessageDiscriminant::DebugPrintDocument),
shortcut: action_keys!(DocumentMessageDiscriminant::DebugPrintDocument),
action: MenuEntry::create_action(|_| DocumentMessage::DebugPrintDocument.into()),
..MenuEntry::default()
},

View File

@@ -0,0 +1,7 @@
mod menu_bar_message;
mod menu_bar_message_handler;
#[doc(inline)]
pub use menu_bar_message::{MenuBarMessage, MenuBarMessageDiscriminant};
#[doc(inline)]
pub use menu_bar_message_handler::MenuBarMessageHandler;

Some files were not shown because too many files have changed in this diff Show More