mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-09-15 22:28:10 +08:00
Restructure the entire editor codebase to consistently match the message hierarchy
Closes #744
This commit is contained in:
@@ -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
83
editor/src/application.rs
Normal 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"))
|
||||
}
|
||||
@@ -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,
|
||||
}
|
||||
@@ -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![]
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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};
|
||||
@@ -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::*;
|
||||
@@ -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(),
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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()));
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
@@ -1,5 +0,0 @@
|
||||
pub mod layout_message;
|
||||
pub mod layout_message_handler;
|
||||
pub mod widgets;
|
||||
|
||||
pub use layout_message::{LayoutMessage, LayoutMessageDiscriminant};
|
||||
@@ -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,
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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),*)
|
||||
}
|
||||
}
|
||||
11
editor/src/messages/broadcast/broadcast_event.rs
Normal file
11
editor/src/messages/broadcast/broadcast_event.rs
Normal 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,
|
||||
}
|
||||
23
editor/src/messages/broadcast/broadcast_message.rs
Normal file
23
editor/src/messages/broadcast/broadcast_message.rs
Normal 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>,
|
||||
},
|
||||
}
|
||||
32
editor/src/messages/broadcast/broadcast_message_handler.rs
Normal file
32
editor/src/messages/broadcast/broadcast_message_handler.rs
Normal 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![]
|
||||
}
|
||||
}
|
||||
9
editor/src/messages/broadcast/mod.rs
Normal file
9
editor/src/messages/broadcast/mod.rs
Normal 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;
|
||||
@@ -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,
|
||||
}
|
||||
@@ -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());
|
||||
@@ -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)]
|
||||
7
editor/src/messages/debug/utility_types.rs
Normal file
7
editor/src/messages/debug/utility_types.rs
Normal file
@@ -0,0 +1,7 @@
|
||||
#[derive(Debug, Default, Clone, Copy)]
|
||||
pub enum MessageLoggingVerbosity {
|
||||
#[default]
|
||||
Off,
|
||||
Names,
|
||||
Contents,
|
||||
}
|
||||
@@ -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,
|
||||
@@ -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),
|
||||
@@ -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,
|
||||
}
|
||||
@@ -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;}
|
||||
}
|
||||
7
editor/src/messages/dialog/export_dialog/mod.rs
Normal file
7
editor/src/messages/dialog/export_dialog/mod.rs
Normal 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;
|
||||
18
editor/src/messages/dialog/mod.rs
Normal file
18
editor/src/messages/dialog/mod.rs
Normal 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;
|
||||
7
editor/src/messages/dialog/new_document_dialog/mod.rs
Normal file
7
editor/src/messages/dialog/new_document_dialog/mod.rs
Normal 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;
|
||||
@@ -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,
|
||||
}
|
||||
@@ -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;}
|
||||
}
|
||||
@@ -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"),
|
||||
@@ -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 {
|
||||
@@ -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()
|
||||
}),
|
||||
@@ -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 {
|
||||
@@ -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 {
|
||||
11
editor/src/messages/dialog/simple_dialogs/mod.rs
Normal file
11
editor/src/messages/dialog/simple_dialogs/mod.rs
Normal 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;
|
||||
@@ -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};
|
||||
@@ -1,6 +1,6 @@
|
||||
pub mod utility_types;
|
||||
|
||||
mod frontend_message;
|
||||
|
||||
pub mod utility_types;
|
||||
|
||||
#[doc(inline)]
|
||||
pub use frontend_message::{FrontendMessage, FrontendMessageDiscriminant};
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
387
editor/src/messages/input_mapper/default_mapping.rs
Normal file
387
editor/src/messages/input_mapper/default_mapping.rs
Normal 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,
|
||||
}
|
||||
}
|
||||
@@ -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};
|
||||
|
||||
@@ -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!();
|
||||
}
|
||||
10
editor/src/messages/input_mapper/mod.rs
Normal file
10
editor/src/messages/input_mapper/mod.rs
Normal 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;
|
||||
@@ -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,
|
||||
@@ -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 {
|
||||
@@ -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;
|
||||
148
editor/src/messages/input_mapper/utility_types/misc.rs
Normal file
148
editor/src/messages/input_mapper/utility_types/misc.rs
Normal 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
|
||||
}
|
||||
4
editor/src/messages/input_mapper/utility_types/mod.rs
Normal file
4
editor/src/messages/input_mapper/utility_types/mod.rs
Normal file
@@ -0,0 +1,4 @@
|
||||
pub mod input_keyboard;
|
||||
pub mod input_mouse;
|
||||
pub mod macros;
|
||||
pub mod misc;
|
||||
@@ -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;
|
||||
@@ -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()));
|
||||
}
|
||||
}
|
||||
7
editor/src/messages/input_preprocessor/mod.rs
Normal file
7
editor/src/messages/input_preprocessor/mod.rs
Normal 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;
|
||||
14
editor/src/messages/layout/layout_message.rs
Normal file
14
editor/src/messages/layout/layout_message.rs
Normal 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 },
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
9
editor/src/messages/layout/mod.rs
Normal file
9
editor/src/messages/layout/mod.rs
Normal 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;
|
||||
292
editor/src/messages/layout/utility_types/layout_widget.rs
Normal file
292
editor/src/messages/layout/utility_types/layout_widget.rs
Normal 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),
|
||||
}
|
||||
@@ -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)]
|
||||
3
editor/src/messages/layout/utility_types/mod.rs
Normal file
3
editor/src/messages/layout/utility_types/mod.rs
Normal file
@@ -0,0 +1,3 @@
|
||||
pub mod layout_widget;
|
||||
pub mod misc;
|
||||
pub mod widgets;
|
||||
@@ -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>,
|
||||
}
|
||||
@@ -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>,
|
||||
}
|
||||
@@ -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
|
||||
134
editor/src/messages/layout/utility_types/widgets/menu_widgets.rs
Normal file
134
editor/src/messages/layout/utility_types/widgets/menu_widgets.rs
Normal 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,
|
||||
}
|
||||
}
|
||||
}
|
||||
4
editor/src/messages/layout/utility_types/widgets/mod.rs
Normal file
4
editor/src/messages/layout/utility_types/widgets/mod.rs
Normal file
@@ -0,0 +1,4 @@
|
||||
pub mod button_widgets;
|
||||
pub mod input_widgets;
|
||||
pub mod label_widgets;
|
||||
pub mod menu_widgets;
|
||||
@@ -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]
|
||||
14
editor/src/messages/mod.rs
Normal file
14
editor/src/messages/mod.rs
Normal 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;
|
||||
@@ -1,5 +1,6 @@
|
||||
use crate::message_prelude::*;
|
||||
use crate::messages::prelude::*;
|
||||
|
||||
use graphene::LayerId;
|
||||
use graphene::Operation as DocumentOperation;
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
7
editor/src/messages/portfolio/document/artboard/mod.rs
Normal file
7
editor/src/messages/portfolio/document/artboard/mod.rs
Normal 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;
|
||||
@@ -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),
|
||||
File diff suppressed because it is too large
Load Diff
14
editor/src/messages/portfolio/document/mod.rs
Normal file
14
editor/src/messages/portfolio/document/mod.rs
Normal 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;
|
||||
7
editor/src/messages/portfolio/document/movement/mod.rs
Normal file
7
editor/src/messages/portfolio/document/movement/mod.rs
Normal 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;
|
||||
@@ -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,
|
||||
},
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
7
editor/src/messages/portfolio/document/overlays/mod.rs
Normal file
7
editor/src/messages/portfolio/document/overlays/mod.rs
Normal 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;
|
||||
@@ -1,4 +1,4 @@
|
||||
use crate::message_prelude::*;
|
||||
use crate::messages::prelude::*;
|
||||
|
||||
use graphene::Operation as DocumentOperation;
|
||||
|
||||
@@ -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]
|
||||
@@ -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;
|
||||
@@ -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,
|
||||
}
|
||||
@@ -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(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
}
|
||||
@@ -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;
|
||||
@@ -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,
|
||||
@@ -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),
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
@@ -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()
|
||||
},
|
||||
7
editor/src/messages/portfolio/menu_bar/mod.rs
Normal file
7
editor/src/messages/portfolio/menu_bar/mod.rs
Normal 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
Reference in New Issue
Block a user