diff --git a/editor/src/communication/dispatcher.rs b/editor/src/communication/dispatcher.rs index 1367d13688..1fad9df0b8 100644 --- a/editor/src/communication/dispatcher.rs +++ b/editor/src/communication/dispatcher.rs @@ -1,26 +1,31 @@ +use crate::document::PortfolioMessageHandler; +use crate::global::GlobalMessageHandler; +use crate::input::{InputMapperMessageHandler, InputPreprocessorMessageHandler}; use crate::message_prelude::*; +use crate::viewport_tools::tool_message_handler::ToolMessageHandler; -pub use crate::document::PortfolioMessageHandler; -pub use crate::input::{InputMapper, InputPreprocessor}; -pub use crate::tool::ToolMessageHandler; - -use crate::global::GlobalMessageHandler; use std::collections::VecDeque; #[derive(Debug, Default)] pub struct Dispatcher { - input_preprocessor: InputPreprocessor, - input_mapper: InputMapper, + message_queue: VecDeque, + pub responses: Vec, + message_handlers: DispatcherMessageHandlers, +} + +#[remain::sorted] +#[derive(Debug, Default)] +struct DispatcherMessageHandlers { global_message_handler: GlobalMessageHandler, - tool_message_handler: ToolMessageHandler, + input_mapper_message_handler: InputMapperMessageHandler, + input_preprocessor_message_handler: InputPreprocessorMessageHandler, portfolio_message_handler: PortfolioMessageHandler, - messages: VecDeque, - pub responses: Vec, + tool_message_handler: ToolMessageHandler, } -// For optimization, these are messages guaranteed to be redundant when repeated -// The last occurrence of the message in the message queue is sufficient to ensure correctness -// In addition, these messages do not change any state in the backend (aside from caches) +// For optimization, these are messages guaranteed to be redundant when repeated. +// The last occurrence of the message in the message queue is sufficient to ensure correct behavior. +// In addition, these messages do not change any state in the backend (aside from caches). const SIDE_EFFECT_FREE_MESSAGES: &[MessageDiscriminant] = &[ MessageDiscriminant::Portfolio(PortfolioMessageDiscriminant::Document(DocumentMessageDiscriminant::RenderDocument)), MessageDiscriminant::Portfolio(PortfolioMessageDiscriminant::Document(DocumentMessageDiscriminant::FolderChanged)), @@ -35,28 +40,54 @@ impl Dispatcher { Self::default() } + #[remain::check] pub fn handle_message>(&mut self, message: T) { - self.messages.push_back(message.into()); + self.message_queue.push_back(message.into()); use Message::*; - while let Some(message) = self.messages.pop_front() { + while let Some(message) = self.message_queue.pop_front() { // Skip processing of this message if it will be processed later - if SIDE_EFFECT_FREE_MESSAGES.contains(&message.to_discriminant()) && self.messages.contains(&message) { + if SIDE_EFFECT_FREE_MESSAGES.contains(&message.to_discriminant()) && self.message_queue.contains(&message) { continue; } + + // Print the message at a verbosity level of `log` self.log_message(&message); + + // Process the action by forwarding it to the relevant message handler, or saving the FrontendMessage to be sent to the frontend + #[remain::sorted] match message { - NoOp => (), - Portfolio(message) => self.portfolio_message_handler.process_action(message, &self.input_preprocessor, &mut self.messages), - Global(message) => self.global_message_handler.process_action(message, (), &mut self.messages), - Tool(message) => self - .tool_message_handler - .process_action(message, (self.portfolio_message_handler.active_document(), &self.input_preprocessor), &mut self.messages), - Frontend(message) => self.responses.push(message), - InputPreprocessor(message) => self.input_preprocessor.process_action(message, (), &mut self.messages), + Frontend(message) => { + // `FrontendMessage`s are saved and will be sent to the frontend after the message queue is done being processed + self.responses.push(message); + } + Global(message) => { + self.message_handlers.global_message_handler.process_action(message, (), &mut self.message_queue); + } InputMapper(message) => { let actions = self.collect_actions(); - self.input_mapper.process_action(message, (&self.input_preprocessor, actions), &mut self.messages) + self.message_handlers + .input_mapper_message_handler + .process_action(message, (&self.message_handlers.input_preprocessor_message_handler, actions), &mut self.message_queue); + } + InputPreprocessor(message) => { + self.message_handlers.input_preprocessor_message_handler.process_action(message, (), &mut self.message_queue); + } + NoOp => {} + Portfolio(message) => { + self.message_handlers + .portfolio_message_handler + .process_action(message, &self.message_handlers.input_preprocessor_message_handler, &mut self.message_queue); + } + Tool(message) => { + self.message_handlers.tool_message_handler.process_action( + message, + ( + self.message_handlers.portfolio_message_handler.active_document(), + &self.message_handlers.input_preprocessor_message_handler, + ), + &mut self.message_queue, + ); } } } @@ -65,11 +96,11 @@ impl Dispatcher { pub fn collect_actions(&self) -> ActionList { // TODO: Reduce the number of heap allocations let mut list = Vec::new(); - list.extend(self.input_preprocessor.actions()); - list.extend(self.input_mapper.actions()); - list.extend(self.global_message_handler.actions()); - list.extend(self.tool_message_handler.actions()); - list.extend(self.portfolio_message_handler.actions()); + list.extend(self.message_handlers.input_preprocessor_message_handler.actions()); + list.extend(self.message_handlers.input_mapper_message_handler.actions()); + list.extend(self.message_handlers.global_message_handler.actions()); + list.extend(self.message_handlers.tool_message_handler.actions()); + list.extend(self.message_handlers.portfolio_message_handler.actions()); list } @@ -82,21 +113,21 @@ impl Dispatcher { ) || MessageDiscriminant::from(message).local_name().ends_with("MouseMove")) { log::trace!("Message: {:?}", message); - // log::trace!("Hints: {:?}", self.input_mapper.hints(self.collect_actions())); + // log::trace!("Hints: {:?}", self.input_mapper_message_handler.hints(self.collect_actions())); } } } #[cfg(test)] mod test { - use crate::{ - communication::set_uuid_seed, - document::{Clipboard::*, DocumentMessageHandler}, - message_prelude::*, - misc::test_utils::EditorTestUtils, - Editor, - }; - use graphene::{color::Color, Operation}; + 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 graphene::color::Color; + use graphene::Operation; fn init_logger() { let _ = env_logger::builder().is_test(true).try_init(); @@ -129,14 +160,14 @@ mod test { init_logger(); let mut editor = create_editor_with_three_layers(); - let document_before_copy = editor.dispatcher.portfolio_message_handler.active_document().graphene_document.clone(); - editor.handle_message(PortfolioMessage::Copy(User)); + let document_before_copy = editor.dispatcher.message_handlers.portfolio_message_handler.active_document().graphene_document.clone(); + editor.handle_message(PortfolioMessage::Copy(Clipboard::User)); editor.handle_message(PortfolioMessage::PasteIntoFolder { - clipboard: User, + clipboard: Clipboard::User, path: vec![], insert_index: -1, }); - let document_after_copy = editor.dispatcher.portfolio_message_handler.active_document().graphene_document.clone(); + let document_after_copy = editor.dispatcher.message_handlers.portfolio_message_handler.active_document().graphene_document.clone(); let layers_before_copy = document_before_copy.root.as_folder().unwrap().layers(); let layers_after_copy = document_after_copy.root.as_folder().unwrap().layers(); @@ -163,18 +194,18 @@ mod test { init_logger(); let mut editor = create_editor_with_three_layers(); - let document_before_copy = editor.dispatcher.portfolio_message_handler.active_document().graphene_document.clone(); + let document_before_copy = editor.dispatcher.message_handlers.portfolio_message_handler.active_document().graphene_document.clone(); let shape_id = document_before_copy.root.as_folder().unwrap().layer_ids[1]; editor.handle_message(DocumentMessage::SetSelectedLayers(vec![vec![shape_id]])); - editor.handle_message(PortfolioMessage::Copy(User)); + editor.handle_message(PortfolioMessage::Copy(Clipboard::User)); editor.handle_message(PortfolioMessage::PasteIntoFolder { - clipboard: User, + clipboard: Clipboard::User, path: vec![], insert_index: -1, }); - let document_after_copy = editor.dispatcher.portfolio_message_handler.active_document().graphene_document.clone(); + let document_after_copy = editor.dispatcher.message_handlers.portfolio_message_handler.active_document().graphene_document.clone(); let layers_before_copy = document_before_copy.root.as_folder().unwrap().layers(); let layers_after_copy = document_after_copy.root.as_folder().unwrap().layers(); @@ -206,7 +237,7 @@ mod test { editor.handle_message(DocumentMessage::CreateEmptyFolder(vec![])); - let document_before_added_shapes = editor.dispatcher.portfolio_message_handler.active_document().graphene_document.clone(); + let document_before_added_shapes = editor.dispatcher.message_handlers.portfolio_message_handler.active_document().graphene_document.clone(); let folder_id = document_before_added_shapes.root.as_folder().unwrap().layer_ids[FOLDER_INDEX]; // TODO: This adding of a Line and Pen should be rewritten using the corresponding functions in EditorTestUtils. @@ -228,22 +259,22 @@ mod test { editor.handle_message(DocumentMessage::SetSelectedLayers(vec![vec![folder_id]])); - let document_before_copy = editor.dispatcher.portfolio_message_handler.active_document().graphene_document.clone(); + let document_before_copy = editor.dispatcher.message_handlers.portfolio_message_handler.active_document().graphene_document.clone(); - editor.handle_message(PortfolioMessage::Copy(User)); + editor.handle_message(PortfolioMessage::Copy(Clipboard::User)); editor.handle_message(DocumentMessage::DeleteSelectedLayers); editor.handle_message(PortfolioMessage::PasteIntoFolder { - clipboard: User, + clipboard: Clipboard::User, path: vec![], insert_index: -1, }); editor.handle_message(PortfolioMessage::PasteIntoFolder { - clipboard: User, + clipboard: Clipboard::User, path: vec![], insert_index: -1, }); - let document_after_copy = editor.dispatcher.portfolio_message_handler.active_document().graphene_document.clone(); + let document_after_copy = editor.dispatcher.message_handlers.portfolio_message_handler.active_document().graphene_document.clone(); let layers_before_copy = document_before_copy.root.as_folder().unwrap().layers(); let layers_after_copy = document_after_copy.root.as_folder().unwrap().layers(); @@ -294,26 +325,26 @@ mod test { const SHAPE_INDEX: usize = 1; const RECT_INDEX: usize = 0; - let document_before_copy = editor.dispatcher.portfolio_message_handler.active_document().graphene_document.clone(); + let document_before_copy = editor.dispatcher.message_handlers.portfolio_message_handler.active_document().graphene_document.clone(); let rect_id = document_before_copy.root.as_folder().unwrap().layer_ids[RECT_INDEX]; let ellipse_id = document_before_copy.root.as_folder().unwrap().layer_ids[ELLIPSE_INDEX]; editor.handle_message(DocumentMessage::SetSelectedLayers(vec![vec![rect_id], vec![ellipse_id]])); - editor.handle_message(PortfolioMessage::Copy(User)); + editor.handle_message(PortfolioMessage::Copy(Clipboard::User)); editor.handle_message(DocumentMessage::DeleteSelectedLayers); editor.draw_rect(0., 800., 12., 200.); editor.handle_message(PortfolioMessage::PasteIntoFolder { - clipboard: User, + clipboard: Clipboard::User, path: vec![], insert_index: -1, }); editor.handle_message(PortfolioMessage::PasteIntoFolder { - clipboard: User, + clipboard: Clipboard::User, path: vec![], insert_index: -1, }); - let document_after_copy = editor.dispatcher.portfolio_message_handler.active_document().graphene_document.clone(); + let document_after_copy = editor.dispatcher.message_handlers.portfolio_message_handler.active_document().graphene_document.clone(); let layers_before_copy = document_before_copy.root.as_folder().unwrap().layers(); let layers_after_copy = document_after_copy.root.as_folder().unwrap().layers(); @@ -343,7 +374,7 @@ mod test { fn map_to_vec(paths: Vec<&[LayerId]>) -> Vec> { paths.iter().map(|layer| layer.to_vec()).collect::>() } - let sorted_layers = map_to_vec(editor.dispatcher.portfolio_message_handler.active_document().all_layers_sorted()); + let sorted_layers = map_to_vec(editor.dispatcher.message_handlers.portfolio_message_handler.active_document().all_layers_sorted()); println!("Sorted layers: {:?}", sorted_layers); let verify_order = |handler: &mut DocumentMessageHandler| { @@ -357,15 +388,15 @@ mod test { editor.handle_message(DocumentMessage::SetSelectedLayers(sorted_layers[..2].to_vec())); editor.handle_message(DocumentMessage::ReorderSelectedLayers(1)); - let (all, non_selected, selected) = verify_order(editor.dispatcher.portfolio_message_handler.active_document_mut()); + let (all, non_selected, selected) = verify_order(editor.dispatcher.message_handlers.portfolio_message_handler.active_document_mut()); assert_eq!(all, non_selected.into_iter().chain(selected.into_iter()).collect::>()); editor.handle_message(DocumentMessage::ReorderSelectedLayers(-1)); - let (all, non_selected, selected) = verify_order(editor.dispatcher.portfolio_message_handler.active_document_mut()); + let (all, non_selected, selected) = verify_order(editor.dispatcher.message_handlers.portfolio_message_handler.active_document_mut()); assert_eq!(all, selected.into_iter().chain(non_selected.into_iter()).collect::>()); editor.handle_message(DocumentMessage::ReorderSelectedLayers(i32::MAX)); - let (all, non_selected, selected) = verify_order(editor.dispatcher.portfolio_message_handler.active_document_mut()); + let (all, non_selected, selected) = verify_order(editor.dispatcher.message_handlers.portfolio_message_handler.active_document_mut()); assert_eq!(all, non_selected.into_iter().chain(selected.into_iter()).collect::>()); } } diff --git a/editor/src/communication/message.rs b/editor/src/communication/message.rs index a3193d7778..c0422506e5 100644 --- a/editor/src/communication/message.rs +++ b/editor/src/communication/message.rs @@ -1,10 +1,10 @@ use crate::message_prelude::*; + use graphite_proc_macros::*; + use serde::{Deserialize, Serialize}; -use std::{ - collections::hash_map::DefaultHasher, - hash::{Hash, Hasher}, -}; +use std::collections::hash_map::DefaultHasher; +use std::hash::{Hash, Hasher}; pub trait AsMessage: TransitiveChild where @@ -16,22 +16,23 @@ where } } +#[remain::sorted] #[impl_message] #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub enum Message { - NoOp, #[child] - Portfolio(PortfolioMessage), + Frontend(FrontendMessage), #[child] Global(GlobalMessage), #[child] - Tool(ToolMessage), - #[child] - Frontend(FrontendMessage), + InputMapper(InputMapperMessage), #[child] InputPreprocessor(InputPreprocessorMessage), + NoOp, #[child] - InputMapper(InputMapperMessage), + Portfolio(PortfolioMessage), + #[child] + Tool(ToolMessage), } impl Message { @@ -39,12 +40,13 @@ impl Message { /// /// # Safety /// This function reads from uninitialized memory!!! - /// Only use if you know what you are doing + /// Only use if you know what you are doing. unsafe fn as_slice(&self) -> &[u8] { core::slice::from_raw_parts(self as *const Message as *const u8, std::mem::size_of::()) } + /// Returns a pseudo hash that should uniquely identify the message. - /// This is needed because `Hash` is not implemented for f64s + /// This is needed because `Hash` is not implemented for `f64`s /// /// # Safety /// This function reads from uninitialized memory but the generated value should be fine. diff --git a/editor/src/communication/message_handler.rs b/editor/src/communication/message_handler.rs new file mode 100644 index 0000000000..a45805d7c8 --- /dev/null +++ b/editor/src/communication/message_handler.rs @@ -0,0 +1,22 @@ +pub use crate::communication::dispatcher::*; +pub use crate::input::InputPreprocessorMessageHandler; +use crate::message_prelude::*; + +use std::collections::VecDeque; + +pub type ActionList = Vec>; + +// TODO: Add Send + Sync requirement +// Use something like rw locks for synchronization +pub trait MessageHandlerData {} + +pub trait MessageHandler +where + A::Discriminant: AsMessage, + ::TopParent: TransitiveChild::TopParent, TopParent = ::TopParent> + AsMessage, +{ + /// Return true if the Action is consumed. + fn process_action(&mut self, action: A, data: T, responses: &mut VecDeque); + + fn actions(&self) -> ActionList; +} diff --git a/editor/src/communication/mod.rs b/editor/src/communication/mod.rs index aeada11990..6392653b83 100644 --- a/editor/src/communication/mod.rs +++ b/editor/src/communication/mod.rs @@ -1,34 +1,17 @@ pub mod dispatcher; pub mod message; -use crate::message_prelude::*; -pub use dispatcher::*; -use rand_chacha::{ - rand_core::{RngCore, SeedableRng}, - ChaCha20Rng, -}; -use spin::Mutex; +pub mod message_handler; -pub use crate::input::InputPreprocessor; -use std::{cell::Cell, collections::VecDeque}; +pub use crate::communication::dispatcher::*; +pub use crate::input::InputPreprocessorMessageHandler; -pub type ActionList = Vec>; +use rand_chacha::rand_core::{RngCore, SeedableRng}; +use rand_chacha::ChaCha20Rng; +use spin::Mutex; +use std::cell::Cell; static RNG: Mutex> = Mutex::new(None); -// TODO: Add Send + Sync requirement -// Use something like rw locks for synchronization -pub trait MessageHandlerData {} - -pub trait MessageHandler -where - A::Discriminant: AsMessage, - ::TopParent: TransitiveChild::TopParent, TopParent = ::TopParent> + AsMessage, -{ - /// Return true if the Action is consumed. - fn process_action(&mut self, action: A, data: T, responses: &mut VecDeque); - fn actions(&self) -> ActionList; -} - thread_local! { pub static UUID_SEED: Cell> = Cell::new(None); } diff --git a/editor/src/consts.rs b/editor/src/consts.rs index 9fe8c1e574..7c436eca44 100644 --- a/editor/src/consts.rs +++ b/editor/src/consts.rs @@ -1,6 +1,6 @@ use graphene::color::Color; -// VIEWPORT +// Viewport pub const VIEWPORT_ZOOM_WHEEL_RATE: f64 = 1. / 600.; pub const VIEWPORT_ZOOM_MOUSE_RATE: f64 = 1. / 400.; pub const VIEWPORT_ZOOM_SCALE_MIN: f64 = 0.000_000_1; @@ -17,22 +17,22 @@ pub const VIEWPORT_ROTATE_SNAP_INTERVAL: f64 = 15.; pub const SNAP_TOLERANCE: f64 = 3.; -// TRANSFORMING LAYER +// Transforming layer pub const ROTATE_SNAP_ANGLE: f64 = 15.; pub const SCALE_SNAP_INTERVAL: f64 = 0.1; pub const SLOWING_DIVISOR: f64 = 10.; -// SELECT TOOL +// Select tool pub const SELECTION_TOLERANCE: f64 = 1.; pub const SELECTION_DRAG_ANGLE: f64 = 90.; -// PATH TOOL +// Path tool pub const VECTOR_MANIPULATOR_ANCHOR_MARKER_SIZE: f64 = 5.; -// LINE TOOL +// Line tool pub const LINE_ROTATE_SNAP_ANGLE: f64 = 15.; -// SCROLLBARS +// Scrollbars pub const SCROLLBAR_SPACING: f64 = 0.1; pub const ASYMPTOTIC_EFFECT: f64 = 0.5; pub const SCALE_EFFECT: f64 = 0.5; @@ -41,7 +41,7 @@ pub const DEFAULT_DOCUMENT_NAME: &str = "Untitled Document"; pub const FILE_SAVE_SUFFIX: &str = ".graphite"; pub const FILE_EXPORT_SUFFIX: &str = ".svg"; -// COLORS +// Colors pub const COLOR_ACCENT: Color = Color::from_unsafe(0x00 as f32 / 255., 0xA8 as f32 / 255., 0xFF as f32 / 255.); // Document diff --git a/editor/src/document/artboard_message.rs b/editor/src/document/artboard_message.rs new file mode 100644 index 0000000000..3d749597c7 --- /dev/null +++ b/editor/src/document/artboard_message.rs @@ -0,0 +1,20 @@ +use crate::message_prelude::*; + +use graphene::Operation as DocumentOperation; + +use serde::{Deserialize, Serialize}; + +#[remain::sorted] +#[impl_message(Message, DocumentMessage, Artboard)] +#[derive(PartialEq, Clone, Debug, Serialize, Deserialize)] +pub enum ArtboardMessage { + AddArtboard { top: f64, left: f64, height: f64, width: f64 }, + DispatchOperation(Box), + RenderArtboards, +} + +impl From for ArtboardMessage { + fn from(operation: DocumentOperation) -> Self { + Self::DispatchOperation(Box::new(operation)) + } +} diff --git a/editor/src/document/artboard_message_handler.rs b/editor/src/document/artboard_message_handler.rs index 7c61acccd1..60fbd70af6 100644 --- a/editor/src/document/artboard_message_handler.rs +++ b/editor/src/document/artboard_message_handler.rs @@ -1,30 +1,16 @@ -pub use crate::document::layer_panel::*; -use crate::document::{DocumentMessage, LayerMetadata}; -use crate::input::InputPreprocessor; +use super::layer_panel::LayerMetadata; +use crate::input::InputPreprocessorMessageHandler; use crate::message_prelude::*; -use glam::{DAffine2, DVec2}; + use graphene::color::Color; use graphene::document::Document as GrapheneDocument; use graphene::layers::style::{self, Fill, ViewMode}; use graphene::Operation as DocumentOperation; + +use glam::{DAffine2, DVec2}; use serde::{Deserialize, Serialize}; use std::collections::VecDeque; -#[remain::sorted] -#[impl_message(Message, DocumentMessage, Artboard)] -#[derive(PartialEq, Clone, Debug, Serialize, Deserialize)] -pub enum ArtboardMessage { - AddArtboard { top: f64, left: f64, height: f64, width: f64 }, - DispatchOperation(Box), - RenderArtboards, -} - -impl From for ArtboardMessage { - fn from(operation: DocumentOperation) -> Self { - Self::DispatchOperation(Box::new(operation)) - } -} - #[derive(Debug, Clone, Default, Serialize, Deserialize)] pub struct ArtboardMessageHandler { pub artboards_graphene_document: GrapheneDocument, @@ -37,9 +23,9 @@ impl ArtboardMessageHandler { } } -impl MessageHandler for ArtboardMessageHandler { +impl MessageHandler for ArtboardMessageHandler { #[remain::check] - fn process_action(&mut self, message: ArtboardMessage, _data: (&mut LayerMetadata, &GrapheneDocument, &InputPreprocessor), responses: &mut VecDeque) { + fn process_action(&mut self, message: ArtboardMessage, _data: (&mut LayerMetadata, &GrapheneDocument, &InputPreprocessorMessageHandler), responses: &mut VecDeque) { // let (layer_metadata, document, ipp) = data; use ArtboardMessage::*; #[remain::sorted] diff --git a/editor/src/document/clipboards.rs b/editor/src/document/clipboards.rs new file mode 100644 index 0000000000..1e7da95934 --- /dev/null +++ b/editor/src/document/clipboards.rs @@ -0,0 +1,21 @@ +use super::layer_panel::LayerMetadata; + +use graphene::layers::layer_info::Layer; + +use serde::{Deserialize, Serialize}; + +#[repr(u8)] +#[derive(Serialize, Deserialize, Clone, Copy, PartialEq, Eq, Debug)] +pub enum Clipboard { + System, + User, + _ClipboardCount, // Keep this as the last entry since it is used for counting the number of enum variants +} + +pub const CLIPBOARD_COUNT: u8 = Clipboard::_ClipboardCount as u8; + +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub struct CopyBufferEntry { + pub layer: Layer, + pub layer_metadata: LayerMetadata, +} diff --git a/editor/src/document/document_message.rs b/editor/src/document/document_message.rs new file mode 100644 index 0000000000..d14ba04019 --- /dev/null +++ b/editor/src/document/document_message.rs @@ -0,0 +1,88 @@ +use super::layer_panel::LayerMetadata; +use super::utility_types::{AlignAggregate, AlignAxis, FlipAxis}; +use crate::message_prelude::*; + +use graphene::layers::blend_mode::BlendMode; +use graphene::layers::style::ViewMode; +use graphene::LayerId; +use graphene::Operation as DocumentOperation; + +use serde::{Deserialize, Serialize}; + +#[remain::sorted] +#[impl_message(Message, PortfolioMessage, Document)] +#[derive(PartialEq, Clone, Debug, Serialize, Deserialize)] +pub enum DocumentMessage { + AbortTransaction, + AddSelectedLayers(Vec>), + AlignSelectedLayers(AlignAxis, AlignAggregate), + #[child] + Artboard(ArtboardMessage), + CommitTransaction, + CreateEmptyFolder(Vec), + DebugPrintDocument, + DeleteLayer(Vec), + DeleteSelectedLayers, + DeselectAllLayers, + DirtyRenderDocument, + DirtyRenderDocumentInOutlineView, + DispatchOperation(Box), + DocumentHistoryBackward, + DocumentHistoryForward, + DocumentStructureChanged, + DuplicateSelectedLayers, + ExportDocument, + FlipSelectedLayers(FlipAxis), + FolderChanged(Vec), + GroupSelectedLayers, + LayerChanged(Vec), + #[child] + Movement(MovementMessage), + MoveSelectedLayersTo { + path: Vec, + insert_index: isize, + }, + NudgeSelectedLayers(f64, f64), + #[child] + Overlays(OverlaysMessage), + Redo, + RenameLayer(Vec, String), + RenderDocument, + ReorderSelectedLayers(i32), // relative_position, + RollbackTransaction, + SaveDocument, + SelectAllLayers, + SelectionChanged, + SelectLayer(Vec, bool, bool), + SetBlendModeForSelectedLayers(BlendMode), + SetLayerExpansion(Vec, bool), + SetOpacityForSelectedLayers(f64), + SetSelectedLayers(Vec>), + SetSnapping(bool), + SetViewMode(ViewMode), + StartTransaction, + ToggleLayerExpansion(Vec), + ToggleLayerVisibility(Vec), + #[child] + TransformLayers(TransformLayerMessage), + Undo, + UngroupLayers(Vec), + UngroupSelectedLayers, + UpdateLayerMetadata { + layer_path: Vec, + layer_metadata: LayerMetadata, + }, + ZoomCanvasToFitAll, +} + +impl From for DocumentMessage { + fn from(operation: DocumentOperation) -> DocumentMessage { + DocumentMessage::DispatchOperation(Box::new(operation)) + } +} + +impl From for Message { + fn from(operation: DocumentOperation) -> Message { + DocumentMessage::DispatchOperation(Box::new(operation)).into() + } +} diff --git a/editor/src/document/document_message_handler.rs b/editor/src/document/document_message_handler.rs index 9cbeb13aee..74158765c6 100644 --- a/editor/src/document/document_message_handler.rs +++ b/editor/src/document/document_message_handler.rs @@ -1,73 +1,27 @@ -use std::collections::HashMap; -use std::collections::VecDeque; - -use super::artboard_message_handler::ArtboardMessage; -use super::artboard_message_handler::ArtboardMessageHandler; -pub use super::layer_panel::*; -use super::movement_handler::{MovementMessage, MovementMessageHandler}; -use super::overlay_message_handler::OverlayMessageHandler; -use super::transform_layer_handler::{TransformLayerMessage, TransformLayerMessageHandler}; +use super::clipboards::Clipboard; +use super::layer_panel::{layer_panel_entry, LayerMetadata, LayerPanelEntry, RawBuffer}; +use super::utility_types::{AlignAggregate, AlignAxis, DocumentSave, FlipAxis, VectorManipulatorSegment, VectorManipulatorShape}; use super::vectorize_layer_metadata; +use super::{ArtboardMessageHandler, MovementMessageHandler, OverlaysMessageHandler, TransformLayerMessageHandler}; use crate::consts::{ ASYMPTOTIC_EFFECT, DEFAULT_DOCUMENT_NAME, FILE_EXPORT_SUFFIX, FILE_SAVE_SUFFIX, GRAPHITE_DOCUMENT_VERSION, SCALE_EFFECT, SCROLLBAR_SPACING, VIEWPORT_ZOOM_TO_FIT_PADDING_SCALE_FACTOR, }; -use crate::document::Clipboard; -use crate::input::InputPreprocessor; +use crate::input::InputPreprocessorMessageHandler; use crate::message_prelude::*; use crate::EditorError; -use graphene::layers::{style::ViewMode, BlendMode, LayerDataType}; -use graphene::{document::Document as GrapheneDocument, DocumentError, LayerId}; -use graphene::{DocumentResponse, Operation as DocumentOperation}; +use graphene::document::Document as GrapheneDocument; +use graphene::layers::folder::Folder; +use graphene::layers::layer_info::LayerDataType; +use graphene::layers::style::ViewMode; +use graphene::{DocumentError, DocumentResponse, LayerId, Operation as DocumentOperation}; use glam::{DAffine2, DVec2}; -use graphene::layers::Folder; use kurbo::PathSeg; use log::warn; use serde::{Deserialize, Serialize}; - -type DocumentSave = (GrapheneDocument, HashMap, LayerMetadata>); - -#[derive(PartialEq, Clone, Debug, Serialize, Deserialize, Hash)] -pub enum FlipAxis { - X, - Y, -} - -#[derive(PartialEq, Clone, Debug, Serialize, Deserialize, Hash)] -pub enum AlignAxis { - X, - Y, -} - -#[derive(PartialEq, Clone, Debug, Serialize, Deserialize, Hash)] -pub enum AlignAggregate { - Min, - Max, - Center, - Average, -} - -#[derive(PartialEq, Clone, Debug)] -pub enum VectorManipulatorSegment { - Line(DVec2, DVec2), - Quad(DVec2, DVec2, DVec2), - Cubic(DVec2, DVec2, DVec2, DVec2), -} - -#[derive(PartialEq, Clone, Debug)] -pub struct VectorManipulatorShape { - /// The path to the layer - pub layer_path: Vec, - /// The outline of the shape - pub path: kurbo::BezPath, - /// The control points / manipulator handles - pub segments: Vec, - /// The compound Bezier curve is closed - pub closed: bool, - /// The transformation matrix to apply - pub transform: DAffine2, -} +use std::collections::HashMap; +use std::collections::VecDeque; #[derive(Clone, Debug, Serialize, Deserialize)] pub struct DocumentMessageHandler { @@ -83,7 +37,7 @@ pub struct DocumentMessageHandler { layer_range_selection_reference: Vec, movement_handler: MovementMessageHandler, #[serde(skip)] - overlay_message_handler: OverlayMessageHandler, + overlays_message_handler: OverlaysMessageHandler, artboard_message_handler: ArtboardMessageHandler, #[serde(skip)] transform_layer_handler: TransformLayerMessageHandler, @@ -98,12 +52,12 @@ impl Default for DocumentMessageHandler { graphene_document: GrapheneDocument::default(), document_undo_history: Vec::new(), document_redo_history: Vec::new(), - name: String::from("Untitled Document"), saved_document_identifier: 0, + name: String::from("Untitled Document"), layer_metadata: vec![(vec![], LayerMetadata::new(true))].into_iter().collect(), layer_range_selection_reference: Vec::new(), movement_handler: MovementMessageHandler::default(), - overlay_message_handler: OverlayMessageHandler::default(), + overlays_message_handler: OverlaysMessageHandler::default(), artboard_message_handler: ArtboardMessageHandler::default(), transform_layer_handler: TransformLayerMessageHandler::default(), snapping_enabled: true, @@ -113,84 +67,6 @@ impl Default for DocumentMessageHandler { } } -#[remain::sorted] -#[impl_message(Message, PortfolioMessage, Document)] -#[derive(PartialEq, Clone, Debug, Serialize, Deserialize)] -pub enum DocumentMessage { - AbortTransaction, - AddSelectedLayers(Vec>), - AlignSelectedLayers(AlignAxis, AlignAggregate), - #[child] - Artboard(ArtboardMessage), - CommitTransaction, - CreateEmptyFolder(Vec), - DebugPrintDocument, - DeleteLayer(Vec), - DeleteSelectedLayers, - DeselectAllLayers, - DirtyRenderDocument, - DirtyRenderDocumentInOutlineView, - DispatchOperation(Box), - DocumentHistoryBackward, - DocumentHistoryForward, - DocumentStructureChanged, - DuplicateSelectedLayers, - ExportDocument, - FlipSelectedLayers(FlipAxis), - FolderChanged(Vec), - GroupSelectedLayers, - LayerChanged(Vec), - #[child] - Movement(MovementMessage), - MoveSelectedLayersTo { - path: Vec, - insert_index: isize, - }, - NudgeSelectedLayers(f64, f64), - #[child] - Overlay(OverlayMessage), - Redo, - RenameLayer(Vec, String), - RenderDocument, - ReorderSelectedLayers(i32), // relative_position, - RollbackTransaction, - SaveDocument, - SelectAllLayers, - SelectionChanged, - SelectLayer(Vec, bool, bool), - SetBlendModeForSelectedLayers(BlendMode), - SetLayerExpansion(Vec, bool), - SetOpacityForSelectedLayers(f64), - SetSelectedLayers(Vec>), - SetSnapping(bool), - SetViewMode(ViewMode), - StartTransaction, - ToggleLayerExpansion(Vec), - ToggleLayerVisibility(Vec), - #[child] - TransformLayers(TransformLayerMessage), - Undo, - UngroupLayers(Vec), - UngroupSelectedLayers, - UpdateLayerMetadata { - layer_path: Vec, - layer_metadata: LayerMetadata, - }, - ZoomCanvasToFitAll, -} - -impl From for DocumentMessage { - fn from(operation: DocumentOperation) -> DocumentMessage { - Self::DispatchOperation(Box::new(operation)) - } -} - -impl From for Message { - fn from(operation: DocumentOperation) -> Message { - DocumentMessage::DispatchOperation(Box::new(operation)).into() - } -} - impl DocumentMessageHandler { pub fn serialize_document(&self) -> String { let val = serde_json::to_string(self); @@ -212,7 +88,7 @@ impl DocumentMessageHandler { } } - pub fn with_name(name: String, ipp: &InputPreprocessor) -> Self { + pub fn with_name(name: String, ipp: &InputPreprocessorMessageHandler) -> Self { let mut document = Self { name, ..Self::default() }; let starting_root_transform = document.movement_handler.calculate_offset_transform(ipp.viewport_bounds.size() / 2.); document.graphene_document.root.transform = starting_root_transform; @@ -256,13 +132,13 @@ impl DocumentMessageHandler { self.graphene_document.combined_viewport_bounding_box(paths) } - // TODO: Consider moving this to some kind of overlay manager in the future + // TODO: Consider moving this to some kind of overlays manager in the future pub fn selected_visible_layers_vector_points(&self) -> Vec { let shapes = self.selected_layers().filter_map(|path_to_shape| { let viewport_transform = self.graphene_document.generate_transform_relative_to_viewport(path_to_shape).ok()?; let layer = self.graphene_document.layer(path_to_shape); - // Filter out the non-visible layers from the filter_map + // Filter out the non-visible layers from the `filter_map` match &layer { Ok(layer) if layer.visible => {} _ => return None, @@ -343,30 +219,38 @@ impl DocumentMessageHandler { structure.push(space | 1 << 63); } - /// Serializes the layer structure into a compressed 1d structure + /// Serializes the layer structure into a condensed 1D structure. /// + /// # Format /// It is a string of numbers broken into three sections: - /// (4),(2,1,-2,-0),(16533113728871998040,3427872634365736244,18115028555707261608,15878401910454357952,449479075714955186) <- Example encoded data - /// L = 4 = structure.len() <- First value in the encoding: L, the length of the structure section - /// structure = 2,1,-2,-0 <- Subsequent L values: structure section - /// data = 16533113728871998040,3427872634365736244,18115028555707261608,15878401910454357952,449479075714955186 <- Remaining values: data section (layer IDs) + /// + /// | Data | Description | Length | + /// |--------------------------------------------------------------------------------------------------------------------------------|----------------------------------------------|------------------| + /// | `4,` `2, 1, -2, -0,` `16533113728871998040,3427872634365736244,18115028555707261608,15878401910454357952,449479075714955186` | Encoded example data | | + /// | `L` = `4` = `structure.len()` | `L`, the length of the **Structure** section | First value | + /// | **Structure** section = `2, 1, -2, -0` | The **Structure** section | Next `L` values | + /// | **Data** section = `16533113728871998040, 3427872634365736244, 18115028555707261608, 15878401910454357952, 449479075714955186` | The **Data** section (layer IDs) | Remaining values | /// /// The data section lists the layer IDs for all folders/layers in the tree as read from top to bottom. - /// The structure section lists signed numbers. The sign indicates a folder indentation change (+ is down a level, - is up a level). - /// the numbers in the structure block encode the indentation, - /// 2 mean read two element from the data section, then place a [ - /// -x means read x elements from the data section and then insert a ] + /// The structure section lists signed numbers. The sign indicates a folder indentation change (`+` is down a level, `-` is up a level). + /// The numbers in the structure block encode the indentation. For example: + /// - `2` means read two element from the data section, then place a `[`. + /// - `-x` means read `x` elements from the data section and then insert a `]`. /// + /// ```text /// 2 V 1 V -2 A -0 A /// 16533113728871998040,3427872634365736244, 18115028555707261608, 15878401910454357952,449479075714955186 /// 16533113728871998040,3427872634365736244,[ 18115028555707261608,[15878401910454357952,449479075714955186] ] + /// ``` /// - /// resulting layer panel: + /// Resulting layer panel: + /// ```text /// 16533113728871998040 /// 3427872634365736244 /// [3427872634365736244,18115028555707261608] /// [3427872634365736244,18115028555707261608,15878401910454357952] /// [3427872634365736244,18115028555707261608,449479075714955186] + /// ``` pub fn serialize_root(&self) -> Vec { let (mut structure, mut data) = (vec![0], Vec::new()); self.serialize_structure(self.graphene_document.root.as_folder().unwrap(), &mut structure, &mut data, &mut vec![]); @@ -552,9 +436,9 @@ impl DocumentMessageHandler { } } -impl MessageHandler for DocumentMessageHandler { +impl MessageHandler for DocumentMessageHandler { #[remain::check] - fn process_action(&mut self, message: DocumentMessage, ipp: &InputPreprocessor, responses: &mut VecDeque) { + fn process_action(&mut self, message: DocumentMessage, ipp: &InputPreprocessorMessageHandler, responses: &mut VecDeque) { use DocumentMessage::*; #[remain::sorted] match message { @@ -798,13 +682,13 @@ impl MessageHandler for DocumentMessageHand } responses.push_back(ToolMessage::DocumentIsDirty.into()); } - Overlay(message) => { - self.overlay_message_handler.process_action( + Overlays(message) => { + self.overlays_message_handler.process_action( message, (Self::layer_metadata_mut_no_borrow_self(&mut self.layer_metadata, &[]), &self.graphene_document, ipp), responses, ); - // responses.push_back(OverlayMessage::RenderOverlays.into()); + // responses.push_back(OverlaysMessage::RenderOverlays.into()); } Redo => { responses.push_back(SelectMessage::Abort.into()); diff --git a/editor/src/document/layer_panel.rs b/editor/src/document/layer_panel.rs index 8bc9841cd4..e0b9978194 100644 --- a/editor/src/document/layer_panel.rs +++ b/editor/src/document/layer_panel.rs @@ -1,10 +1,12 @@ -use graphene::layers::{style::ViewMode, BlendMode, Layer, LayerData, LayerDataType}; +use graphene::layers::blend_mode::BlendMode; +use graphene::layers::layer_info::{Layer, LayerData, LayerDataType}; +use graphene::layers::style::ViewMode; use graphene::LayerId; -use std::fmt; - use glam::{DAffine2, DVec2}; -use serde::{ser::SerializeStruct, Deserialize, Serialize}; +use serde::ser::SerializeStruct; +use serde::{Deserialize, Serialize}; +use std::fmt; #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Copy)] pub struct LayerMetadata { diff --git a/editor/src/document/mod.rs b/editor/src/document/mod.rs index 1cc500d544..18e858003c 100644 --- a/editor/src/document/mod.rs +++ b/editor/src/document/mod.rs @@ -1,29 +1,48 @@ +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 layer_panel; -mod movement_handler; -mod overlay_message_handler; +mod movement_message; +mod movement_message_handler; +mod overlays_message; +mod overlays_message_handler; +mod portfolio_message; mod portfolio_message_handler; -mod transform_layer_handler; -mod vectorize_layer_metadata; +mod transform_layer_message; +mod transform_layer_message_handler; #[doc(inline)] -pub use document_message_handler::{AlignAggregate, AlignAxis, DocumentMessage, DocumentMessageDiscriminant, DocumentMessageHandler, FlipAxis, VectorManipulatorSegment, VectorManipulatorShape}; - +pub use artboard_message::{ArtboardMessage, ArtboardMessageDiscriminant}; #[doc(inline)] -pub use layer_panel::{LayerDataTypeDiscriminant, LayerMetadata, LayerPanelEntry, RawBuffer}; +pub use artboard_message_handler::ArtboardMessageHandler; #[doc(inline)] -pub use movement_handler::{MovementMessage, MovementMessageDiscriminant}; +pub use document_message::{DocumentMessage, DocumentMessageDiscriminant}; +#[doc(inline)] +pub use document_message_handler::DocumentMessageHandler; #[doc(inline)] -pub use overlay_message_handler::{OverlayMessage, OverlayMessageDiscriminant}; +pub use movement_message::{MovementMessage, MovementMessageDiscriminant}; +#[doc(inline)] +pub use movement_message_handler::MovementMessageHandler; #[doc(inline)] -pub use portfolio_message_handler::{Clipboard, PortfolioMessage, PortfolioMessageDiscriminant, PortfolioMessageHandler}; +pub use overlays_message::{OverlaysMessage, OverlaysMessageDiscriminant}; +#[doc(inline)] +pub use overlays_message_handler::OverlaysMessageHandler; #[doc(inline)] -pub use artboard_message_handler::{ArtboardMessage, ArtboardMessageDiscriminant}; +pub use portfolio_message::{PortfolioMessage, PortfolioMessageDiscriminant}; +#[doc(inline)] +pub use portfolio_message_handler::PortfolioMessageHandler; #[doc(inline)] -pub use transform_layer_handler::{TransformLayerMessage, TransformLayerMessageDiscriminant}; +pub use transform_layer_message::{TransformLayerMessage, TransformLayerMessageDiscriminant}; +#[doc(inline)] +pub use transform_layer_message_handler::TransformLayerMessageHandler; diff --git a/editor/src/document/movement_message.rs b/editor/src/document/movement_message.rs new file mode 100644 index 0000000000..b71a5e5169 --- /dev/null +++ b/editor/src/document/movement_message.rs @@ -0,0 +1,40 @@ +use crate::input::keyboard::Key; +use crate::message_prelude::*; + +use glam::DVec2; +use serde::{Deserialize, Serialize}; + +#[remain::sorted] +#[impl_message(Message, DocumentMessage, Movement)] +#[derive(PartialEq, Clone, Debug, Serialize, Deserialize)] +pub enum MovementMessage { + DecreaseCanvasZoom { + center_on_mouse: bool, + }, + FitViewportToBounds { + bounds: [DVec2; 2], + padding_scale_factor: Option, + prevent_zoom_past_100: bool, + }, + IncreaseCanvasZoom { + center_on_mouse: bool, + }, + MouseMove { + snap_angle: Key, + wait_for_snap_angle_release: bool, + snap_zoom: Key, + zoom_from_viewport: Option, + }, + RotateCanvasBegin, + SetCanvasRotation(f64), + SetCanvasZoom(f64), + TransformCanvasEnd, + TranslateCanvas(DVec2), + TranslateCanvasBegin, + TranslateCanvasByViewportFraction(DVec2), + WheelCanvasTranslate { + use_y_as_x: bool, + }, + WheelCanvasZoom, + ZoomCanvasBegin, +} diff --git a/editor/src/document/movement_handler.rs b/editor/src/document/movement_message_handler.rs similarity index 88% rename from editor/src/document/movement_handler.rs rename to editor/src/document/movement_message_handler.rs index 9c597909ea..dbb22bf961 100644 --- a/editor/src/document/movement_handler.rs +++ b/editor/src/document/movement_message_handler.rs @@ -1,12 +1,8 @@ -use crate::consts::VIEWPORT_ROTATE_SNAP_INTERVAL; -pub use crate::document::layer_panel::*; -use crate::document::DocumentMessage; -use crate::input::keyboard::Key; +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::input::mouse::{ViewportBounds, ViewportPosition}; +use crate::input::InputPreprocessorMessageHandler; use crate::message_prelude::*; -use crate::{ - consts::{VIEWPORT_SCROLL_RATE, VIEWPORT_ZOOM_LEVELS, VIEWPORT_ZOOM_MOUSE_RATE, VIEWPORT_ZOOM_SCALE_MAX, VIEWPORT_ZOOM_SCALE_MIN, VIEWPORT_ZOOM_WHEEL_RATE}, - input::{mouse::ViewportBounds, mouse::ViewportPosition, InputPreprocessor}, -}; + use graphene::document::Document; use graphene::Operation as DocumentOperation; @@ -14,41 +10,6 @@ use glam::{DAffine2, DVec2}; use serde::{Deserialize, Serialize}; use std::collections::VecDeque; -#[remain::sorted] -#[impl_message(Message, DocumentMessage, Movement)] -#[derive(PartialEq, Clone, Debug, Serialize, Deserialize)] -pub enum MovementMessage { - DecreaseCanvasZoom { - center_on_mouse: bool, - }, - FitViewportToBounds { - bounds: [DVec2; 2], - padding_scale_factor: Option, - prevent_zoom_past_100: bool, - }, - IncreaseCanvasZoom { - center_on_mouse: bool, - }, - MouseMove { - snap_angle: Key, - wait_for_snap_angle_release: bool, - snap_zoom: Key, - zoom_from_viewport: Option, - }, - RotateCanvasBegin, - SetCanvasRotation(f64), - SetCanvasZoom(f64), - TransformCanvasEnd, - TranslateCanvas(DVec2), - TranslateCanvasBegin, - TranslateCanvasByViewportFraction(DVec2), - WheelCanvasTranslate { - use_y_as_x: bool, - }, - WheelCanvasZoom, - ZoomCanvasBegin, -} - #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub struct MovementMessageHandler { pub pan: DVec2, @@ -138,6 +99,7 @@ impl MovementMessageHandler { .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; @@ -148,9 +110,9 @@ impl MovementMessageHandler { } } -impl MessageHandler for MovementMessageHandler { +impl MessageHandler for MovementMessageHandler { #[remain::check] - fn process_action(&mut self, message: MovementMessage, data: (&Document, &InputPreprocessor), responses: &mut VecDeque) { + fn process_action(&mut self, message: MovementMessage, data: (&Document, &InputPreprocessorMessageHandler), responses: &mut VecDeque) { let (document, ipp) = data; use MovementMessage::*; #[remain::sorted] @@ -325,6 +287,7 @@ impl MessageHandler for Moveme } } } + fn actions(&self) -> ActionList { let mut common = actions!(MovementMessageDiscriminant; MouseMove, diff --git a/editor/src/document/overlay_message_handler.rs b/editor/src/document/overlay_message_handler.rs deleted file mode 100644 index 25176c2013..0000000000 --- a/editor/src/document/overlay_message_handler.rs +++ /dev/null @@ -1,59 +0,0 @@ -pub use crate::document::layer_panel::*; -use crate::document::{DocumentMessage, LayerMetadata}; -use crate::input::InputPreprocessor; -use crate::message_prelude::*; -use graphene::document::Document; -use graphene::Operation as DocumentOperation; - -use graphene::document::Document as GrapheneDocument; -use graphene::layers::style::ViewMode; -use serde::{Deserialize, Serialize}; - -#[remain::sorted] -#[impl_message(Message, DocumentMessage, Overlay)] -#[derive(PartialEq, Clone, Debug, Serialize, Deserialize)] -pub enum OverlayMessage { - ClearAllOverlays, - DispatchOperation(Box), -} - -impl From for OverlayMessage { - fn from(operation: DocumentOperation) -> OverlayMessage { - Self::DispatchOperation(Box::new(operation)) - } -} - -#[derive(Debug, Clone, Default)] -pub struct OverlayMessageHandler { - pub overlays_graphene_document: GrapheneDocument, -} - -impl MessageHandler for OverlayMessageHandler { - #[remain::check] - fn process_action(&mut self, message: OverlayMessage, _data: (&mut LayerMetadata, &Document, &InputPreprocessor), responses: &mut VecDeque) { - // let (layer_metadata, document, ipp) = data; - use OverlayMessage::*; - #[remain::sorted] - match message { - ClearAllOverlays => todo!(), - DispatchOperation(operation) => match self.overlays_graphene_document.handle_operation(&operation) { - Ok(_) => (), - Err(e) => log::error!("OverlayError: {:?}", e), - }, - } - - // Render overlays - responses.push_back( - FrontendMessage::UpdateDocumentOverlays { - svg: self.overlays_graphene_document.render_root(ViewMode::Normal), - } - .into(), - ); - } - - fn actions(&self) -> ActionList { - actions!(OverlayMessageDiscriminant; - ClearAllOverlays - ) - } -} diff --git a/editor/src/document/overlays_message.rs b/editor/src/document/overlays_message.rs new file mode 100644 index 0000000000..54dde40576 --- /dev/null +++ b/editor/src/document/overlays_message.rs @@ -0,0 +1,19 @@ +use crate::message_prelude::*; + +use graphene::Operation as DocumentOperation; + +use serde::{Deserialize, Serialize}; + +#[remain::sorted] +#[impl_message(Message, DocumentMessage, Overlays)] +#[derive(PartialEq, Clone, Debug, Serialize, Deserialize)] +pub enum OverlaysMessage { + ClearAllOverlays, + DispatchOperation(Box), +} + +impl From for OverlaysMessage { + fn from(operation: DocumentOperation) -> OverlaysMessage { + Self::DispatchOperation(Box::new(operation)) + } +} diff --git a/editor/src/document/overlays_message_handler.rs b/editor/src/document/overlays_message_handler.rs new file mode 100644 index 0000000000..d87b4cf090 --- /dev/null +++ b/editor/src/document/overlays_message_handler.rs @@ -0,0 +1,40 @@ +use super::layer_panel::LayerMetadata; +use crate::input::InputPreprocessorMessageHandler; +use crate::message_prelude::*; + +use graphene::document::Document; +use graphene::document::Document as GrapheneDocument; +use graphene::layers::style::ViewMode; + +#[derive(Debug, Clone, Default)] +pub struct OverlaysMessageHandler { + pub overlays_graphene_document: GrapheneDocument, +} + +impl MessageHandler for OverlaysMessageHandler { + #[remain::check] + fn process_action(&mut self, message: OverlaysMessage, _data: (&mut LayerMetadata, &Document, &InputPreprocessorMessageHandler), responses: &mut VecDeque) { + // let (layer_metadata, document, ipp) = data; + use OverlaysMessage::*; + #[remain::sorted] + match message { + ClearAllOverlays => todo!(), + DispatchOperation(operation) => match self.overlays_graphene_document.handle_operation(&operation) { + Ok(_) => (), + Err(e) => log::error!("OverlaysError: {:?}", e), + }, + } + + // Render overlays + responses.push_back( + FrontendMessage::UpdateDocumentOverlays { + svg: self.overlays_graphene_document.render_root(ViewMode::Normal), + } + .into(), + ); + } + + fn actions(&self) -> ActionList { + actions!(OverlaysMessageDiscriminant; ClearAllOverlays) + } +} diff --git a/editor/src/document/portfolio_message.rs b/editor/src/document/portfolio_message.rs new file mode 100644 index 0000000000..71392e0a79 --- /dev/null +++ b/editor/src/document/portfolio_message.rs @@ -0,0 +1,43 @@ +use super::clipboards::Clipboard; +use crate::message_prelude::*; + +use graphene::LayerId; + +use serde::{Deserialize, Serialize}; + +#[remain::sorted] +#[impl_message(Message, Portfolio)] +#[derive(PartialEq, Clone, Debug, Serialize, Deserialize)] +pub enum PortfolioMessage { + AutoSaveActiveDocument, + AutoSaveDocument(u64), + CloseActiveDocumentWithConfirmation, + CloseAllDocuments, + CloseAllDocumentsWithConfirmation, + CloseDocument(u64), + CloseDocumentWithConfirmation(u64), + Copy(Clipboard), + Cut(Clipboard), + #[child] + Document(DocumentMessage), + NewDocument, + NextDocument, + OpenDocument, + OpenDocumentFile(String, String), + OpenDocumentFileWithId { + document: String, + document_name: String, + document_id: u64, + document_is_saved: bool, + }, + Paste(Clipboard), + PasteIntoFolder { + clipboard: Clipboard, + path: Vec, + insert_index: isize, + }, + PrevDocument, + RequestAboutGraphiteDialog, + SelectDocument(u64), + UpdateOpenDocumentsList, +} diff --git a/editor/src/document/portfolio_message_handler.rs b/editor/src/document/portfolio_message_handler.rs index b6eba1e624..2005af481a 100644 --- a/editor/src/document/portfolio_message_handler.rs +++ b/editor/src/document/portfolio_message_handler.rs @@ -1,63 +1,15 @@ -use super::{DocumentMessageHandler, LayerMetadata}; +use super::clipboards::{CopyBufferEntry, CLIPBOARD_COUNT}; +use super::DocumentMessageHandler; use crate::consts::{DEFAULT_DOCUMENT_NAME, GRAPHITE_DOCUMENT_VERSION}; -use crate::frontend::frontend_message_handler::FrontendDocumentDetails; -use crate::input::InputPreprocessor; +use crate::frontend::utility_types::FrontendDocumentDetails; +use crate::input::InputPreprocessorMessageHandler; use crate::message_prelude::*; -use graphene::layers::Layer; -use graphene::{LayerId, Operation as DocumentOperation}; -use log::warn; -use serde::{Deserialize, Serialize}; +use graphene::Operation as DocumentOperation; +use log::warn; use std::collections::{HashMap, VecDeque}; -#[repr(u8)] -#[derive(Serialize, Deserialize, Clone, Copy, PartialEq, Eq, Debug)] -pub enum Clipboard { - System, - User, - _ClipboardCount, -} - -const CLIPBOARD_COUNT: u8 = Clipboard::_ClipboardCount as u8; - -#[remain::sorted] -#[impl_message(Message, Portfolio)] -#[derive(PartialEq, Clone, Debug, Serialize, Deserialize)] -pub enum PortfolioMessage { - AutoSaveActiveDocument, - AutoSaveDocument(u64), - CloseActiveDocumentWithConfirmation, - CloseAllDocuments, - CloseAllDocumentsWithConfirmation, - CloseDocument(u64), - CloseDocumentWithConfirmation(u64), - Copy(Clipboard), - Cut(Clipboard), - #[child] - Document(DocumentMessage), - NewDocument, - NextDocument, - OpenDocument, - OpenDocumentFile(String, String), - OpenDocumentFileWithId { - document: String, - document_name: String, - document_id: u64, - document_is_saved: bool, - }, - Paste(Clipboard), - PasteIntoFolder { - clipboard: Clipboard, - path: Vec, - insert_index: isize, - }, - PrevDocument, - RequestAboutGraphiteDialog, - SelectDocument(u64), - UpdateOpenDocumentsList, -} - #[derive(Debug, Clone)] pub struct PortfolioMessageHandler { documents: HashMap, @@ -66,12 +18,6 @@ pub struct PortfolioMessageHandler { copy_buffer: [Vec; CLIPBOARD_COUNT as usize], } -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] -pub struct CopyBufferEntry { - layer: Layer, - layer_metadata: LayerMetadata, -} - impl PortfolioMessageHandler { pub fn active_document(&self) -> &DocumentMessageHandler { self.documents.get(&self.active_document_id).unwrap() @@ -149,7 +95,7 @@ impl PortfolioMessageHandler { responses.push_back(PortfolioMessage::SelectDocument(document_id).into()); } - // Returns an iterator over the open documents in order + /// Returns an iterator over the open documents in order. pub fn ordered_document_iterator(&self) -> impl Iterator { self.document_ids.iter().map(|id| self.documents.get(id).expect("document id was not found in the document hashmap")) } @@ -176,9 +122,9 @@ impl Default for PortfolioMessageHandler { } } -impl MessageHandler for PortfolioMessageHandler { +impl MessageHandler for PortfolioMessageHandler { #[remain::check] - fn process_action(&mut self, message: PortfolioMessage, ipp: &InputPreprocessor, responses: &mut VecDeque) { + fn process_action(&mut self, message: PortfolioMessage, ipp: &InputPreprocessorMessageHandler, responses: &mut VecDeque) { use DocumentMessage::*; use PortfolioMessage::*; #[remain::sorted] @@ -429,6 +375,7 @@ impl MessageHandler for PortfolioMessageHa } } } + fn actions(&self) -> ActionList { let mut common = actions!(PortfolioMessageDiscriminant; NewDocument, diff --git a/editor/src/document/transform_layer_handler.rs b/editor/src/document/transform_layer_handler.rs deleted file mode 100644 index 13815f68ef..0000000000 --- a/editor/src/document/transform_layer_handler.rs +++ /dev/null @@ -1,550 +0,0 @@ -pub use super::layer_panel::*; - -use super::LayerMetadata; - -use crate::consts::{ROTATE_SNAP_ANGLE, SCALE_SNAP_INTERVAL, SLOWING_DIVISOR}; -use crate::input::keyboard::Key; -use crate::input::{mouse::ViewportPosition, InputPreprocessor}; -use crate::message_prelude::*; -use glam::{DAffine2, DVec2}; -use graphene::document::Document; -use graphene::Operation as DocumentOperation; -use serde::{Deserialize, Serialize}; -use std::collections::{HashMap, VecDeque}; - -type OriginalTransforms = HashMap, DAffine2>; - -struct Selected<'a> { - pub selected: Vec>, - responses: &'a mut VecDeque, - document: &'a mut Document, - original_transforms: &'a mut OriginalTransforms, - pivot: &'a mut DVec2, -} -impl<'a> Selected<'a> { - pub fn new( - original_transforms: &'a mut OriginalTransforms, - pivot: &'a mut DVec2, - layer_metadata: &'a mut HashMap, LayerMetadata>, - responses: &'a mut VecDeque, - document: &'a mut Document, - ) -> Self { - let selected = layer_metadata.iter().filter_map(|(layer_path, data)| data.selected.then(|| layer_path.to_owned())).collect(); - for path in &selected { - if !original_transforms.contains_key::>(path) { - original_transforms.insert(path.clone(), document.layer(path).unwrap().transform); - } - } - Self { - selected, - responses, - document, - original_transforms, - pivot, - } - } - - pub fn calculate_pivot(&mut self) -> DVec2 { - let xy_summation = self - .selected - .iter() - .map(|path| { - let multiplied_transform = self.document.multiply_transforms(path).unwrap(); - - let bounds = self - .document - .layer(path) - .unwrap() - .current_bounding_box_with_transform(multiplied_transform) - .unwrap_or([multiplied_transform.translation; 2]); - - (bounds[0] + bounds[1]) / 2. - }) - .fold(DVec2::ZERO, |summation, next| summation + next); - - xy_summation / self.selected.len() as f64 - } - - pub fn update_transforms(&mut self, delta: DAffine2) { - if !self.selected.is_empty() { - let pivot = DAffine2::from_translation(*self.pivot); - let transformation = pivot * delta * pivot.inverse(); - - for layer_path in &self.selected { - let parent_folder_path = &layer_path[..layer_path.len() - 1]; - let original_layer_transforms = *self.original_transforms.get(layer_path).unwrap(); - - let to = self.document.generate_transform_across_scope(parent_folder_path, None).unwrap(); - let new = to.inverse() * transformation * to * original_layer_transforms; - - self.responses.push_back( - DocumentOperation::SetLayerTransform { - path: layer_path.to_vec(), - transform: new.to_cols_array(), - } - .into(), - ); - } - - self.responses.push_back(ToolMessage::DocumentIsDirty.into()); - } - } - - pub fn revert_operation(&mut self) { - for path in &self.selected { - self.responses.push_back( - DocumentOperation::SetLayerTransform { - path: path.to_vec(), - transform: (*self.original_transforms.get(path).unwrap()).to_cols_array(), - } - .into(), - ); - } - } -} - -#[derive(Debug, Clone, PartialEq, Copy)] -enum Axis { - Both, - X, - Y, -} - -impl Default for Axis { - fn default() -> Self { - Self::Both - } -} - -impl Axis { - pub fn set_or_toggle(&mut self, target: Axis) { - // If constrained to an axis and target is requesting the same axis, toggle back to Both - if *self == target { - *self = Axis::Both; - } - // If current axis is different from the target axis, switch to the target - else { - *self = target; - } - } -} - -#[derive(Default, Debug, Clone, PartialEq, Copy)] -struct Translation { - pub dragged_distance: DVec2, - pub typed_distance: Option, - pub constraint: Axis, -} - -impl Translation { - pub fn to_dvec(self) -> DVec2 { - if let Some(value) = self.typed_distance { - if self.constraint == Axis::Y { - return DVec2::new(0., value); - } else { - return DVec2::new(value, 0.); - } - } - - match self.constraint { - Axis::Both => self.dragged_distance, - Axis::X => DVec2::new(self.dragged_distance.x, 0.), - Axis::Y => DVec2::new(0., self.dragged_distance.y), - } - } - - pub fn increment_amount(self, delta: DVec2) -> Self { - Self { - dragged_distance: self.dragged_distance + delta, - typed_distance: None, - constraint: self.constraint, - } - } -} - -#[derive(Debug, Clone, PartialEq, Copy)] -struct Scale { - pub dragged_factor: f64, - pub typed_factor: Option, - pub constraint: Axis, -} - -impl Default for Scale { - fn default() -> Self { - Self { - dragged_factor: 1., - typed_factor: None, - constraint: Axis::default(), - } - } -} - -impl Scale { - pub fn to_dvec(self, snap: bool) -> DVec2 { - let factor = if let Some(value) = self.typed_factor { value } else { self.dragged_factor }; - let factor = if snap { (factor / SCALE_SNAP_INTERVAL).round() * SCALE_SNAP_INTERVAL } else { factor }; - - match self.constraint { - Axis::Both => DVec2::splat(factor), - Axis::X => DVec2::new(factor, 1.), - Axis::Y => DVec2::new(1., factor), - } - } - - pub fn increment_amount(self, delta: f64) -> Self { - Self { - dragged_factor: self.dragged_factor + delta, - typed_factor: None, - constraint: self.constraint, - } - } -} - -#[derive(Default, Debug, Clone, PartialEq, Copy)] -struct Rotation { - pub dragged_angle: f64, - pub typed_angle: Option, -} - -impl Rotation { - pub fn to_f64(self, snap: bool) -> f64 { - if let Some(value) = self.typed_angle { - value.to_radians() - } else if snap { - let snap_resolution = ROTATE_SNAP_ANGLE.to_radians(); - (self.dragged_angle / snap_resolution).round() * snap_resolution - } else { - self.dragged_angle - } - } - - pub fn increment_amount(self, delta: f64) -> Self { - Self { - dragged_angle: self.dragged_angle + delta, - typed_angle: None, - } - } -} - -#[derive(Debug, Clone, PartialEq, Copy)] -enum Operation { - None, - Grabbing(Translation), - Rotating(Rotation), - Scaling(Scale), -} - -impl Default for Operation { - fn default() -> Self { - Operation::None - } -} - -impl Operation { - pub fn apply_operation(&self, selected: &mut Selected, snapping: bool) { - if self != &Operation::None { - let transformation = match self { - Operation::Grabbing(translation) => DAffine2::from_translation(translation.to_dvec()), - Operation::Rotating(rotation) => DAffine2::from_angle(rotation.to_f64(snapping)), - Operation::Scaling(scale) => DAffine2::from_scale(scale.to_dvec(snapping)), - Operation::None => unreachable!(), - }; - - selected.update_transforms(transformation); - } - } - - pub fn constrain_axis(&mut self, axis: Axis, selected: &mut Selected, snapping: bool) { - match self { - Operation::None => (), - Operation::Grabbing(translation) => translation.constraint.set_or_toggle(axis), - Operation::Rotating(_) => (), - Operation::Scaling(scale) => scale.constraint.set_or_toggle(axis), - }; - - self.apply_operation(selected, snapping); - } - - pub fn handle_typed(&mut self, typed: Option, selected: &mut Selected, snapping: bool) { - match self { - Operation::None => (), - Operation::Grabbing(translation) => translation.typed_distance = typed, - Operation::Rotating(rotation) => rotation.typed_angle = typed, - Operation::Scaling(scale) => scale.typed_factor = typed, - }; - - self.apply_operation(selected, snapping); - } -} - -#[derive(Debug, Clone, PartialEq, Default)] -struct Typing { - digits: Vec, - contains_decimal: bool, - negative: bool, -} - -const DECIMAL_POINT: u8 = 10; - -impl Typing { - pub fn type_number(&mut self, number: u8) -> Option { - self.digits.push(number); - - self.evaluate() - } - - pub fn type_backspace(&mut self) -> Option { - if self.digits.is_empty() { - return None; - } - - match self.digits.pop() { - Some(DECIMAL_POINT) => self.contains_decimal = false, - Some(_) => (), - None => self.negative = false, - } - - self.evaluate() - } - - pub fn type_decimal_point(&mut self) -> Option { - if !self.contains_decimal { - self.contains_decimal = true; - self.digits.push(DECIMAL_POINT); - } - - self.evaluate() - } - - pub fn type_negate(&mut self) -> Option { - self.negative = !self.negative; - - self.evaluate() - } - - pub fn evaluate(&self) -> Option { - if self.digits.is_empty() { - return None; - } - - let mut result = 0_f64; - let mut running_decimal_place = 0_i32; - - for digit in &self.digits { - if *digit == DECIMAL_POINT { - if running_decimal_place == 0 { - running_decimal_place = 1; - } - } else if running_decimal_place == 0 { - result *= 10.; - result += *digit as f64; - } else { - result += *digit as f64 * 0.1_f64.powi(running_decimal_place); - running_decimal_place += 1; - } - } - - if self.negative { - result = -result; - } - - Some(result) - } - - pub fn clear(&mut self) { - self.digits.clear(); - self.contains_decimal = false; - self.negative = false; - } -} - -#[remain::sorted] -#[impl_message(Message, DocumentMessage, TransformLayers)] -#[derive(PartialEq, Clone, Debug, Serialize, Deserialize)] -pub enum TransformLayerMessage { - ApplyOperation, - BeginGrab, - BeginRotate, - BeginScale, - CancelOperation, - ConstrainX, - ConstrainY, - MouseMove { slow_key: Key, snap_key: Key }, - TypeBackspace, - TypeDecimalPoint, - TypeNegate, - TypeNumber(u8), -} - -#[derive(Debug, Clone, Default, PartialEq)] -pub struct TransformLayerMessageHandler { - operation: Operation, - - slow: bool, - snap: bool, - typing: Typing, - - mouse_position: ViewportPosition, - start_mouse: ViewportPosition, - - original_transforms: OriginalTransforms, - pivot: DVec2, -} - -impl MessageHandler, LayerMetadata>, &mut Document, &InputPreprocessor)> for TransformLayerMessageHandler { - #[remain::check] - fn process_action(&mut self, message: TransformLayerMessage, data: (&mut HashMap, LayerMetadata>, &mut Document, &InputPreprocessor), responses: &mut VecDeque) { - use TransformLayerMessage::*; - - let (layer_metadata, document, ipp) = data; - let mut selected = Selected::new(&mut self.original_transforms, &mut self.pivot, layer_metadata, responses, document); - - let mut begin_operation = |operation: Operation, typing: &mut Typing, mouse_position: &mut DVec2, start_mouse: &mut DVec2| { - if !(operation == Operation::None) { - selected.revert_operation(); - typing.clear(); - } else { - *selected.pivot = selected.calculate_pivot(); - } - - *mouse_position = ipp.mouse.position; - *start_mouse = ipp.mouse.position; - }; - - #[remain::sorted] - match message { - ApplyOperation => { - self.original_transforms.clear(); - self.typing.clear(); - - self.operation = Operation::None; - - responses.push_back(ToolMessage::DocumentIsDirty.into()); - } - BeginGrab => { - if let Operation::Grabbing(_) = self.operation { - return; - } - - begin_operation(self.operation, &mut self.typing, &mut self.mouse_position, &mut self.start_mouse); - - self.operation = Operation::Grabbing(Default::default()); - - responses.push_back(ToolMessage::DocumentIsDirty.into()); - } - BeginRotate => { - if let Operation::Rotating(_) = self.operation { - return; - } - - begin_operation(self.operation, &mut self.typing, &mut self.mouse_position, &mut self.start_mouse); - - self.operation = Operation::Rotating(Default::default()); - - responses.push_back(ToolMessage::DocumentIsDirty.into()); - } - BeginScale => { - if let Operation::Scaling(_) = self.operation { - return; - } - - begin_operation(self.operation, &mut self.typing, &mut self.mouse_position, &mut self.start_mouse); - - self.operation = Operation::Scaling(Default::default()); - self.operation.apply_operation(&mut selected, self.snap); - - responses.push_back(ToolMessage::DocumentIsDirty.into()); - } - CancelOperation => { - selected.revert_operation(); - - selected.original_transforms.clear(); - self.typing.clear(); - - self.operation = Operation::None; - - responses.push_back(ToolMessage::DocumentIsDirty.into()); - } - ConstrainX => self.operation.constrain_axis(Axis::X, &mut selected, self.snap), - ConstrainY => self.operation.constrain_axis(Axis::Y, &mut selected, self.snap), - MouseMove { slow_key, snap_key } => { - self.slow = ipp.keyboard.get(slow_key as usize); - - let new_snap = ipp.keyboard.get(snap_key as usize); - if new_snap != self.snap { - self.snap = new_snap; - self.operation.apply_operation(&mut selected, self.snap); - } - - if self.typing.digits.is_empty() { - let delta_pos = ipp.mouse.position - self.mouse_position; - - match self.operation { - Operation::None => unreachable!(), - Operation::Grabbing(translation) => { - let change = if self.slow { delta_pos / SLOWING_DIVISOR } else { delta_pos }; - self.operation = Operation::Grabbing(translation.increment_amount(change)); - self.operation.apply_operation(&mut selected, self.snap); - } - Operation::Rotating(rotation) => { - let selected_pivot = selected.calculate_pivot(); - let angle = { - let start_vec = self.mouse_position - selected_pivot; - let end_vec = ipp.mouse.position - selected_pivot; - - start_vec.angle_between(end_vec) - }; - - let change = if self.slow { angle / SLOWING_DIVISOR } else { angle }; - self.operation = Operation::Rotating(rotation.increment_amount(change)); - self.operation.apply_operation(&mut selected, self.snap); - } - Operation::Scaling(scale) => { - let change = { - let previous_frame_dist = (self.mouse_position - *selected.pivot).length(); - let current_frame_dist = (ipp.mouse.position - *selected.pivot).length(); - let start_transform_dist = (self.start_mouse - *selected.pivot).length(); - - (current_frame_dist - previous_frame_dist) / start_transform_dist - }; - - let change = if self.slow { change / SLOWING_DIVISOR } else { change }; - self.operation = Operation::Scaling(scale.increment_amount(change)); - self.operation.apply_operation(&mut selected, self.snap); - } - }; - } - self.mouse_position = ipp.mouse.position; - } - TypeBackspace => self.operation.handle_typed(self.typing.type_backspace(), &mut selected, self.snap), - TypeDecimalPoint => self.operation.handle_typed(self.typing.type_decimal_point(), &mut selected, self.snap), - TypeNegate => self.operation.handle_typed(self.typing.type_negate(), &mut selected, self.snap), - TypeNumber(number) => self.operation.handle_typed(self.typing.type_number(number), &mut selected, self.snap), - } - } - - fn actions(&self) -> ActionList { - let mut common = actions!(TransformLayerMessageDiscriminant; - BeginGrab, - BeginScale, - BeginRotate, - ); - - if self.operation != Operation::None { - let active = actions!(TransformLayerMessageDiscriminant; - MouseMove, - CancelOperation, - ApplyOperation, - TypeNumber, - TypeBackspace, - TypeDecimalPoint, - TypeNegate, - ConstrainX, - ConstrainY, - ); - common.extend(active); - } - - common - } -} diff --git a/editor/src/document/transform_layer_message.rs b/editor/src/document/transform_layer_message.rs new file mode 100644 index 0000000000..638dc7839e --- /dev/null +++ b/editor/src/document/transform_layer_message.rs @@ -0,0 +1,22 @@ +use crate::input::keyboard::Key; +use crate::message_prelude::*; + +use serde::{Deserialize, Serialize}; + +#[remain::sorted] +#[impl_message(Message, DocumentMessage, TransformLayers)] +#[derive(PartialEq, Clone, Debug, Serialize, Deserialize)] +pub enum TransformLayerMessage { + ApplyTransformOperation, + BeginGrab, + BeginRotate, + BeginScale, + CancelTransformOperation, + ConstrainX, + ConstrainY, + MouseMove { slow_key: Key, snap_key: Key }, + TypeBackspace, + TypeDecimalPoint, + TypeNegate, + TypeNumber(u8), +} diff --git a/editor/src/document/transform_layer_message_handler.rs b/editor/src/document/transform_layer_message_handler.rs new file mode 100644 index 0000000000..c47f7fda06 --- /dev/null +++ b/editor/src/document/transform_layer_message_handler.rs @@ -0,0 +1,189 @@ +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 graphene::document::Document; + +use glam::DVec2; +use std::collections::{HashMap, VecDeque}; + +#[derive(Debug, Clone, Default, PartialEq)] +pub struct TransformLayerMessageHandler { + transform_operation: TransformOperation, + + slow: bool, + snap: bool, + typing: Typing, + + mouse_position: ViewportPosition, + start_mouse: ViewportPosition, + + original_transforms: OriginalTransforms, + pivot: DVec2, +} + +impl MessageHandler, LayerMetadata>, &mut Document, &InputPreprocessorMessageHandler)> for TransformLayerMessageHandler { + #[remain::check] + fn process_action( + &mut self, + message: TransformLayerMessage, + data: (&mut HashMap, LayerMetadata>, &mut Document, &InputPreprocessorMessageHandler), + responses: &mut VecDeque, + ) { + use TransformLayerMessage::*; + + let (layer_metadata, document, ipp) = data; + let mut selected = Selected::new(&mut self.original_transforms, &mut self.pivot, layer_metadata, responses, document); + + let mut begin_operation = |operation: TransformOperation, typing: &mut Typing, mouse_position: &mut DVec2, start_mouse: &mut DVec2| { + if !(operation == TransformOperation::None) { + selected.revert_operation(); + typing.clear(); + } else { + *selected.pivot = selected.calculate_pivot(); + } + + *mouse_position = ipp.mouse.position; + *start_mouse = ipp.mouse.position; + }; + + #[remain::sorted] + match message { + ApplyTransformOperation => { + self.original_transforms.clear(); + self.typing.clear(); + + self.transform_operation = TransformOperation::None; + + responses.push_back(ToolMessage::DocumentIsDirty.into()); + } + BeginGrab => { + if let TransformOperation::Grabbing(_) = self.transform_operation { + return; + } + + begin_operation(self.transform_operation, &mut self.typing, &mut self.mouse_position, &mut self.start_mouse); + + self.transform_operation = TransformOperation::Grabbing(Default::default()); + + responses.push_back(ToolMessage::DocumentIsDirty.into()); + } + BeginRotate => { + if let TransformOperation::Rotating(_) = self.transform_operation { + return; + } + + begin_operation(self.transform_operation, &mut self.typing, &mut self.mouse_position, &mut self.start_mouse); + + self.transform_operation = TransformOperation::Rotating(Default::default()); + + responses.push_back(ToolMessage::DocumentIsDirty.into()); + } + BeginScale => { + if let TransformOperation::Scaling(_) = self.transform_operation { + return; + } + + begin_operation(self.transform_operation, &mut self.typing, &mut self.mouse_position, &mut self.start_mouse); + + self.transform_operation = TransformOperation::Scaling(Default::default()); + self.transform_operation.apply_transform_operation(&mut selected, self.snap); + + responses.push_back(ToolMessage::DocumentIsDirty.into()); + } + CancelTransformOperation => { + selected.revert_operation(); + + selected.original_transforms.clear(); + self.typing.clear(); + + self.transform_operation = TransformOperation::None; + + responses.push_back(ToolMessage::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), + MouseMove { slow_key, snap_key } => { + self.slow = ipp.keyboard.get(slow_key as usize); + + let new_snap = ipp.keyboard.get(snap_key as usize); + if new_snap != self.snap { + self.snap = new_snap; + self.transform_operation.apply_transform_operation(&mut selected, self.snap); + } + + if self.typing.digits.is_empty() { + let delta_pos = ipp.mouse.position - self.mouse_position; + + match self.transform_operation { + TransformOperation::None => unreachable!(), + TransformOperation::Grabbing(translation) => { + let change = if self.slow { delta_pos / SLOWING_DIVISOR } else { delta_pos }; + self.transform_operation = TransformOperation::Grabbing(translation.increment_amount(change)); + self.transform_operation.apply_transform_operation(&mut selected, self.snap); + } + TransformOperation::Rotating(rotation) => { + let selected_pivot = selected.calculate_pivot(); + let angle = { + let start_vec = self.mouse_position - selected_pivot; + let end_vec = ipp.mouse.position - selected_pivot; + + start_vec.angle_between(end_vec) + }; + + let change = if self.slow { angle / SLOWING_DIVISOR } else { angle }; + self.transform_operation = TransformOperation::Rotating(rotation.increment_amount(change)); + self.transform_operation.apply_transform_operation(&mut selected, self.snap); + } + TransformOperation::Scaling(scale) => { + let change = { + let previous_frame_dist = (self.mouse_position - *selected.pivot).length(); + let current_frame_dist = (ipp.mouse.position - *selected.pivot).length(); + let start_transform_dist = (self.start_mouse - *selected.pivot).length(); + + (current_frame_dist - previous_frame_dist) / start_transform_dist + }; + + let change = if self.slow { change / SLOWING_DIVISOR } else { change }; + self.transform_operation = TransformOperation::Scaling(scale.increment_amount(change)); + self.transform_operation.apply_transform_operation(&mut selected, self.snap); + } + }; + } + self.mouse_position = ipp.mouse.position; + } + TypeBackspace => self.transform_operation.handle_typed(self.typing.type_backspace(), &mut selected, self.snap), + TypeDecimalPoint => self.transform_operation.handle_typed(self.typing.type_decimal_point(), &mut selected, self.snap), + TypeNegate => self.transform_operation.handle_typed(self.typing.type_negate(), &mut selected, self.snap), + TypeNumber(number) => self.transform_operation.handle_typed(self.typing.type_number(number), &mut selected, self.snap), + } + } + + fn actions(&self) -> ActionList { + let mut common = actions!(TransformLayerMessageDiscriminant; + BeginGrab, + BeginScale, + BeginRotate, + ); + + if self.transform_operation != TransformOperation::None { + let active = actions!(TransformLayerMessageDiscriminant; + MouseMove, + CancelTransformOperation, + ApplyTransformOperation, + TypeNumber, + TypeBackspace, + TypeDecimalPoint, + TypeNegate, + ConstrainX, + ConstrainY, + ); + common.extend(active); + } + + common + } +} diff --git a/editor/src/document/transformation.rs b/editor/src/document/transformation.rs new file mode 100644 index 0000000000..1593b03f30 --- /dev/null +++ b/editor/src/document/transformation.rs @@ -0,0 +1,356 @@ +use super::layer_panel::LayerMetadata; +use crate::consts::{ROTATE_SNAP_ANGLE, SCALE_SNAP_INTERVAL}; +use crate::message_prelude::*; + +use graphene::document::Document; +use graphene::Operation as DocumentOperation; + +use glam::{DAffine2, DVec2}; +use std::collections::{HashMap, VecDeque}; + +pub type OriginalTransforms = HashMap, DAffine2>; + +#[derive(Debug, Clone, PartialEq, Copy)] +pub enum Axis { + Both, + X, + Y, +} + +impl Default for Axis { + fn default() -> Self { + Self::Both + } +} + +impl Axis { + pub fn set_or_toggle(&mut self, target: Axis) { + // If constrained to an axis and target is requesting the same axis, toggle back to Both + if *self == target { + *self = Axis::Both; + } + // If current axis is different from the target axis, switch to the target + else { + *self = target; + } + } +} + +#[derive(Default, Debug, Clone, PartialEq, Copy)] +pub struct Translation { + pub dragged_distance: DVec2, + pub typed_distance: Option, + pub constraint: Axis, +} + +impl Translation { + pub fn to_dvec(self) -> DVec2 { + if let Some(value) = self.typed_distance { + if self.constraint == Axis::Y { + return DVec2::new(0., value); + } else { + return DVec2::new(value, 0.); + } + } + + match self.constraint { + Axis::Both => self.dragged_distance, + Axis::X => DVec2::new(self.dragged_distance.x, 0.), + Axis::Y => DVec2::new(0., self.dragged_distance.y), + } + } + + pub fn increment_amount(self, delta: DVec2) -> Self { + Self { + dragged_distance: self.dragged_distance + delta, + typed_distance: None, + constraint: self.constraint, + } + } +} + +#[derive(Default, Debug, Clone, PartialEq, Copy)] +pub struct Rotation { + pub dragged_angle: f64, + pub typed_angle: Option, +} + +impl Rotation { + pub fn to_f64(self, snap: bool) -> f64 { + if let Some(value) = self.typed_angle { + value.to_radians() + } else if snap { + let snap_resolution = ROTATE_SNAP_ANGLE.to_radians(); + (self.dragged_angle / snap_resolution).round() * snap_resolution + } else { + self.dragged_angle + } + } + + pub fn increment_amount(self, delta: f64) -> Self { + Self { + dragged_angle: self.dragged_angle + delta, + typed_angle: None, + } + } +} + +#[derive(Debug, Clone, PartialEq, Copy)] +pub struct Scale { + pub dragged_factor: f64, + pub typed_factor: Option, + pub constraint: Axis, +} + +impl Default for Scale { + fn default() -> Self { + Self { + dragged_factor: 1., + typed_factor: None, + constraint: Axis::default(), + } + } +} + +impl Scale { + pub fn to_dvec(self, snap: bool) -> DVec2 { + let factor = if let Some(value) = self.typed_factor { value } else { self.dragged_factor }; + let factor = if snap { (factor / SCALE_SNAP_INTERVAL).round() * SCALE_SNAP_INTERVAL } else { factor }; + + match self.constraint { + Axis::Both => DVec2::splat(factor), + Axis::X => DVec2::new(factor, 1.), + Axis::Y => DVec2::new(1., factor), + } + } + + pub fn increment_amount(self, delta: f64) -> Self { + Self { + dragged_factor: self.dragged_factor + delta, + typed_factor: None, + constraint: self.constraint, + } + } +} + +#[derive(Debug, Clone, PartialEq, Copy)] +pub enum TransformOperation { + None, + Grabbing(Translation), + Rotating(Rotation), + Scaling(Scale), +} + +impl Default for TransformOperation { + fn default() -> Self { + TransformOperation::None + } +} + +impl TransformOperation { + pub fn apply_transform_operation(&self, selected: &mut Selected, snapping: bool) { + if self != &TransformOperation::None { + let transformation = match self { + TransformOperation::Grabbing(translation) => DAffine2::from_translation(translation.to_dvec()), + TransformOperation::Rotating(rotation) => DAffine2::from_angle(rotation.to_f64(snapping)), + TransformOperation::Scaling(scale) => DAffine2::from_scale(scale.to_dvec(snapping)), + TransformOperation::None => unreachable!(), + }; + + selected.update_transforms(transformation); + } + } + + pub fn constrain_axis(&mut self, axis: Axis, selected: &mut Selected, snapping: bool) { + match self { + TransformOperation::None => (), + TransformOperation::Grabbing(translation) => translation.constraint.set_or_toggle(axis), + TransformOperation::Rotating(_) => (), + TransformOperation::Scaling(scale) => scale.constraint.set_or_toggle(axis), + }; + + self.apply_transform_operation(selected, snapping); + } + + pub fn handle_typed(&mut self, typed: Option, selected: &mut Selected, snapping: bool) { + match self { + TransformOperation::None => (), + TransformOperation::Grabbing(translation) => translation.typed_distance = typed, + TransformOperation::Rotating(rotation) => rotation.typed_angle = typed, + TransformOperation::Scaling(scale) => scale.typed_factor = typed, + }; + + self.apply_transform_operation(selected, snapping); + } +} + +pub struct Selected<'a> { + pub selected: Vec>, + pub responses: &'a mut VecDeque, + pub document: &'a mut Document, + pub original_transforms: &'a mut OriginalTransforms, + pub pivot: &'a mut DVec2, +} + +impl<'a> Selected<'a> { + pub fn new( + original_transforms: &'a mut OriginalTransforms, + pivot: &'a mut DVec2, + layer_metadata: &'a mut HashMap, LayerMetadata>, + responses: &'a mut VecDeque, + document: &'a mut Document, + ) -> Self { + let selected = layer_metadata.iter().filter_map(|(layer_path, data)| data.selected.then(|| layer_path.to_owned())).collect(); + for path in &selected { + if !original_transforms.contains_key::>(path) { + original_transforms.insert(path.clone(), document.layer(path).unwrap().transform); + } + } + Self { + selected, + responses, + document, + original_transforms, + pivot, + } + } + + pub fn calculate_pivot(&mut self) -> DVec2 { + let xy_summation = self + .selected + .iter() + .map(|path| { + let multiplied_transform = self.document.multiply_transforms(path).unwrap(); + + let bounds = self + .document + .layer(path) + .unwrap() + .current_bounding_box_with_transform(multiplied_transform) + .unwrap_or([multiplied_transform.translation; 2]); + + (bounds[0] + bounds[1]) / 2. + }) + .fold(DVec2::ZERO, |summation, next| summation + next); + + xy_summation / self.selected.len() as f64 + } + + pub fn update_transforms(&mut self, delta: DAffine2) { + if !self.selected.is_empty() { + let pivot = DAffine2::from_translation(*self.pivot); + let transformation = pivot * delta * pivot.inverse(); + + for layer_path in &self.selected { + let parent_folder_path = &layer_path[..layer_path.len() - 1]; + let original_layer_transforms = *self.original_transforms.get(layer_path).unwrap(); + + let to = self.document.generate_transform_across_scope(parent_folder_path, None).unwrap(); + let new = to.inverse() * transformation * to * original_layer_transforms; + + self.responses.push_back( + DocumentOperation::SetLayerTransform { + path: layer_path.to_vec(), + transform: new.to_cols_array(), + } + .into(), + ); + } + + self.responses.push_back(ToolMessage::DocumentIsDirty.into()); + } + } + + pub fn revert_operation(&mut self) { + for path in &self.selected { + self.responses.push_back( + DocumentOperation::SetLayerTransform { + path: path.to_vec(), + transform: (*self.original_transforms.get(path).unwrap()).to_cols_array(), + } + .into(), + ); + } + } +} + +#[derive(Debug, Clone, PartialEq, Default)] +pub struct Typing { + pub digits: Vec, + pub contains_decimal: bool, + pub negative: bool, +} + +const DECIMAL_POINT: u8 = 10; + +impl Typing { + pub fn type_number(&mut self, number: u8) -> Option { + self.digits.push(number); + + self.evaluate() + } + + pub fn type_backspace(&mut self) -> Option { + if self.digits.is_empty() { + return None; + } + + match self.digits.pop() { + Some(DECIMAL_POINT) => self.contains_decimal = false, + Some(_) => (), + None => self.negative = false, + } + + self.evaluate() + } + + pub fn type_decimal_point(&mut self) -> Option { + if !self.contains_decimal { + self.contains_decimal = true; + self.digits.push(DECIMAL_POINT); + } + + self.evaluate() + } + + pub fn type_negate(&mut self) -> Option { + self.negative = !self.negative; + + self.evaluate() + } + + pub fn evaluate(&self) -> Option { + if self.digits.is_empty() { + return None; + } + + let mut result = 0_f64; + let mut running_decimal_place = 0_i32; + + for digit in &self.digits { + if *digit == DECIMAL_POINT { + if running_decimal_place == 0 { + running_decimal_place = 1; + } + } else if running_decimal_place == 0 { + result *= 10.; + result += *digit as f64; + } else { + result += *digit as f64 * 0.1_f64.powi(running_decimal_place); + running_decimal_place += 1; + } + } + + if self.negative { + result = -result; + } + + Some(result) + } + + pub fn clear(&mut self) { + self.digits.clear(); + self.contains_decimal = false; + self.negative = false; + } +} diff --git a/editor/src/document/utility_types.rs b/editor/src/document/utility_types.rs new file mode 100644 index 0000000000..c013128de6 --- /dev/null +++ b/editor/src/document/utility_types.rs @@ -0,0 +1,51 @@ +pub use super::layer_panel::{layer_panel_entry, LayerMetadata, LayerPanelEntry, RawBuffer}; + +use graphene::document::Document as GrapheneDocument; +use graphene::LayerId; + +use glam::{DAffine2, DVec2}; +use serde::{Deserialize, Serialize}; +use std::collections::HashMap; + +pub type DocumentSave = (GrapheneDocument, HashMap, LayerMetadata>); + +#[derive(PartialEq, Clone, Debug, Serialize, Deserialize, Hash)] +pub enum FlipAxis { + X, + Y, +} + +#[derive(PartialEq, Clone, Debug, Serialize, Deserialize, Hash)] +pub enum AlignAxis { + X, + Y, +} + +#[derive(PartialEq, Clone, Debug, Serialize, Deserialize, Hash)] +pub enum AlignAggregate { + Min, + Max, + Center, + Average, +} + +#[derive(PartialEq, Clone, Debug)] +pub enum VectorManipulatorSegment { + Line(DVec2, DVec2), + Quad(DVec2, DVec2, DVec2), + Cubic(DVec2, DVec2, DVec2, DVec2), +} + +#[derive(PartialEq, Clone, Debug)] +pub struct VectorManipulatorShape { + /// The path to the layer + pub layer_path: Vec, + /// The outline of the shape + pub path: kurbo::BezPath, + /// The control points / manipulator handles + pub segments: Vec, + /// The compound Bezier curve is closed + pub closed: bool, + /// The transformation matrix to apply + pub transform: DAffine2, +} diff --git a/editor/src/document/vectorize_layer_metadata.rs b/editor/src/document/vectorize_layer_metadata.rs index 57a36135a8..d18f826bae 100644 --- a/editor/src/document/vectorize_layer_metadata.rs +++ b/editor/src/document/vectorize_layer_metadata.rs @@ -1,7 +1,7 @@ -/// Necessary because serde can't serialize hashmaps when the keys don't implement display. use serde::{Deserialize, Deserializer, Serialize, Serializer}; use std::iter::FromIterator; +/// Necessary because serde can't serialize hashmaps when the keys don't implement display. pub fn serialize<'a, T, K, V, S>(target: T, ser: S) -> Result where S: Serializer, diff --git a/editor/src/frontend/frontend_message_handler.rs b/editor/src/frontend/frontend_message.rs similarity index 87% rename from editor/src/frontend/frontend_message_handler.rs rename to editor/src/frontend/frontend_message.rs index 110c0af9dd..b54d3f4548 100644 --- a/editor/src/frontend/frontend_message_handler.rs +++ b/editor/src/frontend/frontend_message.rs @@ -1,16 +1,11 @@ -use crate::document::{LayerPanelEntry, RawBuffer}; +use super::utility_types::FrontendDocumentDetails; +use crate::document::layer_panel::{LayerPanelEntry, RawBuffer}; use crate::message_prelude::*; use crate::misc::HintData; -use crate::tool::tool_options::ToolOptions; +use crate::viewport_tools::tool_options::ToolOptions; use crate::Color; -use serde::{Deserialize, Serialize}; -#[derive(PartialEq, Clone, Deserialize, Serialize, Debug)] -pub struct FrontendDocumentDetails { - pub is_saved: bool, - pub name: String, - pub id: u64, -} +use serde::{Deserialize, Serialize}; #[remain::sorted] #[impl_message(Message, Frontend)] diff --git a/editor/src/frontend/mod.rs b/editor/src/frontend/mod.rs index 409a4ef4c0..2b1c077c10 100644 --- a/editor/src/frontend/mod.rs +++ b/editor/src/frontend/mod.rs @@ -1,3 +1,6 @@ -pub mod frontend_message_handler; +pub mod utility_types; -pub use frontend_message_handler::{FrontendMessage, FrontendMessageDiscriminant}; +mod frontend_message; + +#[doc(inline)] +pub use frontend_message::{FrontendMessage, FrontendMessageDiscriminant}; diff --git a/editor/src/frontend/utility_types.rs b/editor/src/frontend/utility_types.rs new file mode 100644 index 0000000000..d52888a3d3 --- /dev/null +++ b/editor/src/frontend/utility_types.rs @@ -0,0 +1,8 @@ +use serde::{Deserialize, Serialize}; + +#[derive(PartialEq, Clone, Deserialize, Serialize, Debug)] +pub struct FrontendDocumentDetails { + pub is_saved: bool, + pub name: String, + pub id: u64, +} diff --git a/editor/src/global/global_message.rs b/editor/src/global/global_message.rs new file mode 100644 index 0000000000..3f7173f456 --- /dev/null +++ b/editor/src/global/global_message.rs @@ -0,0 +1,12 @@ +use crate::message_prelude::*; + +use serde::{Deserialize, Serialize}; + +#[remain::sorted] +#[impl_message(Message, Global)] +#[derive(PartialEq, Clone, Debug, Hash, Serialize, Deserialize)] +pub enum GlobalMessage { + LogDebug, + LogInfo, + LogTrace, +} diff --git a/editor/src/global/global_message_handler.rs b/editor/src/global/global_message_handler.rs index 21a941f4f2..1f64f7cd90 100644 --- a/editor/src/global/global_message_handler.rs +++ b/editor/src/global/global_message_handler.rs @@ -1,15 +1,6 @@ use crate::message_prelude::*; -use serde::{Deserialize, Serialize}; -use std::collections::VecDeque; -#[remain::sorted] -#[impl_message(Message, Global)] -#[derive(PartialEq, Clone, Debug, Hash, Serialize, Deserialize)] -pub enum GlobalMessage { - LogDebug, - LogInfo, - LogTrace, -} +use std::collections::VecDeque; #[derive(Debug, Default)] pub struct GlobalMessageHandler {} @@ -22,17 +13,18 @@ impl MessageHandler for GlobalMessageHandler { match message { LogDebug => { log::set_max_level(log::LevelFilter::Debug); - log::info!("set log verbosity to debug"); + log::info!("Set log verbosity to debug"); } LogInfo => { log::set_max_level(log::LevelFilter::Info); - log::info!("set log verbosity to info"); + log::info!("Set log verbosity to info"); } LogTrace => { log::set_max_level(log::LevelFilter::Trace); - log::info!("set log verbosity to trace"); + log::info!("Set log verbosity to trace"); } } } + advertise_actions!(GlobalMessageDiscriminant; LogInfo, LogDebug, LogTrace); } diff --git a/editor/src/global/mod.rs b/editor/src/global/mod.rs index 6ba0e00ce2..baf1ddea01 100644 --- a/editor/src/global/mod.rs +++ b/editor/src/global/mod.rs @@ -1,3 +1,7 @@ -pub mod global_message_handler; +mod global_message; +mod global_message_handler; -pub use global_message_handler::{GlobalMessage, GlobalMessageDiscriminant, GlobalMessageHandler}; +#[doc(inline)] +pub use global_message::{GlobalMessage, GlobalMessageDiscriminant}; +#[doc(inline)] +pub use global_message_handler::GlobalMessageHandler; diff --git a/editor/src/input/input_mapper.rs b/editor/src/input/input_mapper.rs index 001f8fc682..66a981e87d 100644 --- a/editor/src/input/input_mapper.rs +++ b/editor/src/input/input_mapper.rs @@ -1,146 +1,38 @@ -use glam::DVec2; - -use super::{ - keyboard::{Key, KeyStates, NUMBER_OF_KEYS}, - InputPreprocessor, -}; -use crate::document::Clipboard::*; +use super::keyboard::{Key, KeyStates, NUMBER_OF_KEYS}; +use crate::document::clipboards::Clipboard; use crate::message_prelude::*; -use crate::tool::ToolType; +use crate::viewport_tools::tool::ToolType; -use serde::{Deserialize, Serialize}; -use std::fmt::Write; +use glam::DVec2; const NUDGE_AMOUNT: f64 = 1.; const SHIFT_NUDGE_AMOUNT: f64 = 10.; -#[remain::sorted] -#[impl_message(Message, InputMapper)] -#[derive(PartialEq, Clone, Debug, Hash, Serialize, Deserialize)] -pub enum InputMapperMessage { - #[child] - KeyDown(Key), - #[child] - KeyUp(Key), - MouseScroll, - PointerMove, -} - -#[derive(PartialEq, Clone, Debug)] -struct MappingEntry { - trigger: InputMapperMessage, - modifiers: KeyStates, - action: Message, -} - #[derive(Debug, Clone)] -struct KeyMappingEntries(Vec); - -impl KeyMappingEntries { - fn match_mapping(&self, keys: &KeyStates, actions: ActionList) -> Option { - for entry in self.0.iter() { - let all_required_modifiers_pressed = ((*keys & entry.modifiers) ^ entry.modifiers).is_empty(); - if all_required_modifiers_pressed && actions.iter().flatten().any(|action| entry.action.to_discriminant() == *action) { - 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] - } -} - -impl Default for KeyMappingEntries { - fn default() -> Self { - Self::new() - } -} - -#[derive(Debug, Clone)] -struct Mapping { - key_up: [KeyMappingEntries; NUMBER_OF_KEYS], - key_down: [KeyMappingEntries; NUMBER_OF_KEYS], - pointer_move: KeyMappingEntries, - mouse_scroll: KeyMappingEntries, -} - -macro_rules! modifiers { - ($($m:ident),*) => {{ - #[allow(unused_mut)] - let mut state = KeyStates::new(); - $( - state.set(Key::$m as usize); - )* - state - }}; -} -macro_rules! entry { - {action=$action:expr, key_down=$key:ident $(, modifiers=[$($m:ident),* $(,)?])?} => {{ - entry!{action=$action, message=InputMapperMessage::KeyDown(Key::$key) $(, modifiers=[$($m),*])?} - }}; - {action=$action:expr, key_up=$key:ident $(, modifiers=[$($m:ident),* $(,)?])?} => {{ - entry!{action=$action, message=InputMapperMessage::KeyUp(Key::$key) $(, modifiers=[$($m),* ])?} - }}; - {action=$action:expr, message=$message:expr $(, modifiers=[$($m:ident),* $(,)?])?} => {{ - &[MappingEntry {trigger: $message, modifiers: modifiers!($($($m),*)?), action: $action.into()}] - }}; - {action=$action:expr, triggers=[$($m:ident),* $(,)?]} => {{ - &[ - MappingEntry {trigger:InputMapperMessage::PointerMove, action: $action.into(), modifiers: modifiers!()}, - $( - MappingEntry {trigger:InputMapperMessage::KeyDown(Key::$m), action: $action.into(), modifiers: modifiers!()}, - MappingEntry {trigger:InputMapperMessage::KeyUp(Key::$m), action: $action.into(), modifiers: modifiers!()}, - )* - ] - }}; -} -macro_rules! mapping { - //[$()*] => {{ - [$($entry:expr),* $(,)?] => {{ - let mut key_up = KeyMappingEntries::key_array(); - let mut key_down = KeyMappingEntries::key_array(); - let mut pointer_move: KeyMappingEntries = Default::default(); - let mut mouse_scroll: KeyMappingEntries = Default::default(); - $( - for entry in $entry { - let arr = match entry.trigger { - InputMapperMessage::KeyDown(key) => &mut key_down[key as usize], - InputMapperMessage::KeyUp(key) => &mut key_up[key as usize], - InputMapperMessage::MouseScroll => &mut mouse_scroll, - InputMapperMessage::PointerMove => &mut pointer_move, - }; - arr.push(entry.clone()); - } - )* - (key_up, key_down, pointer_move, mouse_scroll) - }}; +pub struct Mapping { + pub key_up: [KeyMappingEntries; NUMBER_OF_KEYS], + pub key_down: [KeyMappingEntries; NUMBER_OF_KEYS], + pub pointer_move: KeyMappingEntries, + pub mouse_scroll: KeyMappingEntries, } impl Default for Mapping { fn default() -> Self { + use input_mapper_macros::{entry, mapping, modifiers}; use Key::*; + // WARNING! // If a new mapping isn't being handled (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`) + // it as an available action in the respective message handler file (such as the bottom of `document_message_handler.rs`). + let mappings = mapping![ // Higher priority than entries in sections below - entry! {action=PortfolioMessage::Paste(User), key_down=KeyV, modifiers=[KeyControl]}, + entry! {action=PortfolioMessage::Paste(Clipboard::User), key_down=KeyV, modifiers=[KeyControl]}, // Transform layers - entry! {action=TransformLayerMessage::ApplyOperation, key_down=KeyEnter}, - entry! {action=TransformLayerMessage::ApplyOperation, key_down=Lmb}, - entry! {action=TransformLayerMessage::CancelOperation, key_down=KeyEscape}, - entry! {action=TransformLayerMessage::CancelOperation, key_down=Rmb}, + entry! {action=TransformLayerMessage::ApplyTransformOperation, key_down=KeyEnter}, + entry! {action=TransformLayerMessage::ApplyTransformOperation, key_down=Lmb}, + entry! {action=TransformLayerMessage::CancelTransformOperation, key_down=KeyEscape}, + entry! {action=TransformLayerMessage::CancelTransformOperation, key_down=Rmb}, entry! {action=TransformLayerMessage::ConstrainX, key_down=KeyX}, entry! {action=TransformLayerMessage::ConstrainY, key_down=KeyY}, entry! {action=TransformLayerMessage::TypeBackspace, key_down=KeyBackspace}, @@ -222,7 +114,7 @@ impl Default for Mapping { // Editor Actions entry! {action=FrontendMessage::TriggerFileUpload, key_down=KeyO, modifiers=[KeyControl]}, // Document Actions - entry! {action=PortfolioMessage::Paste(User), key_down=KeyV, modifiers=[KeyControl]}, + entry! {action=PortfolioMessage::Paste(Clipboard::User), key_down=KeyV, modifiers=[KeyControl]}, entry! {action=DocumentMessage::Redo, key_down=KeyZ, modifiers=[KeyControl, KeyShift]}, entry! {action=DocumentMessage::Undo, key_down=KeyZ, modifiers=[KeyControl]}, entry! {action=DocumentMessage::DeselectAllLayers, key_down=KeyA, modifiers=[KeyControl, KeyAlt]}, @@ -267,8 +159,8 @@ impl Default for Mapping { entry! {action=PortfolioMessage::CloseAllDocumentsWithConfirmation, key_down=KeyW, modifiers=[KeyControl, KeyAlt]}, entry! {action=PortfolioMessage::CloseActiveDocumentWithConfirmation, key_down=KeyW, modifiers=[KeyControl]}, entry! {action=DocumentMessage::DuplicateSelectedLayers, key_down=KeyD, modifiers=[KeyControl]}, - entry! {action=PortfolioMessage::Copy(User), key_down=KeyC, modifiers=[KeyControl]}, - entry! {action=PortfolioMessage::Cut(User), key_down=KeyX, modifiers=[KeyControl]}, + entry! {action=PortfolioMessage::Copy(Clipboard::User), key_down=KeyC, modifiers=[KeyControl]}, + entry! {action=PortfolioMessage::Cut(Clipboard::User), key_down=KeyX, modifiers=[KeyControl]}, entry! {action=DocumentMessage::GroupSelectedLayers, key_down=KeyG, modifiers=[KeyControl]}, entry! {action=DocumentMessage::UngroupSelectedLayers, key_down=KeyG, modifiers=[KeyControl, KeyShift]}, // Nudging @@ -337,7 +229,7 @@ impl Default for Mapping { } impl Mapping { - fn match_message(&self, message: InputMapperMessage, keys: &KeyStates, actions: ActionList) -> Option { + pub fn match_message(&self, message: InputMapperMessage, keys: &KeyStates, actions: ActionList) -> Option { use InputMapperMessage::*; let list = match message { KeyDown(key) => &self.key_down[key as usize], @@ -349,40 +241,103 @@ impl Mapping { } } -#[derive(Debug, Default)] -pub struct InputMapper { - mapping: Mapping, +#[derive(PartialEq, Clone, Debug)] +pub struct MappingEntry { + pub trigger: InputMapperMessage, + pub modifiers: KeyStates, + pub action: Message, } -impl InputMapper { - pub fn hints(&self, actions: ActionList) -> String { - let mut output = String::new(); - let mut actions = actions - .into_iter() - .flatten() - .filter(|a| !matches!(*a, MessageDiscriminant::Tool(ToolMessageDiscriminant::ActivateTool) | MessageDiscriminant::Global(_))); - self.mapping - .key_down - .iter() - .enumerate() - .filter_map(|(i, m)| { - let ma = m.0.iter().find_map(|m| actions.find_map(|a| (a == m.action.to_discriminant()).then(|| m.action.to_discriminant()))); +#[derive(Debug, Clone)] +pub struct KeyMappingEntries(pub Vec); - ma.map(|a| unsafe { (std::mem::transmute_copy::(&i), a) }) - }) - .for_each(|(k, a)| { - let _ = write!(output, "{}: {}, ", k.to_discriminant().local_name(), a.local_name().split('.').last().unwrap()); - }); - output.replace("Key", "") +impl KeyMappingEntries { + fn match_mapping(&self, keys: &KeyStates, actions: ActionList) -> Option { + for entry in self.0.iter() { + let all_required_modifiers_pressed = ((*keys & entry.modifiers) ^ entry.modifiers).is_empty(); + if all_required_modifiers_pressed && actions.iter().flatten().any(|action| entry.action.to_discriminant() == *action) { + 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] } } -impl MessageHandler for InputMapper { - fn process_action(&mut self, message: InputMapperMessage, data: (&InputPreprocessor, ActionList), responses: &mut VecDeque) { - let (input, actions) = data; - if let Some(message) = self.mapping.match_message(message, &input.keyboard, actions) { - responses.push_back(message); - } +impl Default for KeyMappingEntries { + fn default() -> Self { + Self::new() + } +} + +mod input_mapper_macros { + macro_rules! modifiers { + ($($m:ident),*) => {{ + #[allow(unused_mut)] + let mut state = KeyStates::new(); + $( + state.set(Key::$m as usize); + )* + state + }}; + } + + macro_rules! entry { + {action=$action:expr, key_down=$key:ident $(, modifiers=[$($m:ident),* $(,)?])?} => {{ + entry!{action=$action, message=InputMapperMessage::KeyDown(Key::$key) $(, modifiers=[$($m),*])?} + }}; + {action=$action:expr, key_up=$key:ident $(, modifiers=[$($m:ident),* $(,)?])?} => {{ + entry!{action=$action, message=InputMapperMessage::KeyUp(Key::$key) $(, modifiers=[$($m),* ])?} + }}; + {action=$action:expr, message=$message:expr $(, modifiers=[$($m:ident),* $(,)?])?} => {{ + &[MappingEntry {trigger: $message, modifiers: modifiers!($($($m),*)?), action: $action.into()}] + }}; + {action=$action:expr, triggers=[$($m:ident),* $(,)?]} => {{ + &[ + MappingEntry {trigger:InputMapperMessage::PointerMove, action: $action.into(), modifiers: modifiers!()}, + $( + MappingEntry {trigger:InputMapperMessage::KeyDown(Key::$m), action: $action.into(), modifiers: modifiers!()}, + MappingEntry {trigger:InputMapperMessage::KeyUp(Key::$m), action: $action.into(), modifiers: modifiers!()}, + )* + ] + }}; } - advertise_actions!(); + + macro_rules! mapping { + //[$()*] => {{ + [$($entry:expr),* $(,)?] => {{ + let mut key_up = KeyMappingEntries::key_array(); + let mut key_down = KeyMappingEntries::key_array(); + let mut pointer_move: KeyMappingEntries = Default::default(); + let mut mouse_scroll: KeyMappingEntries = Default::default(); + $( + for entry in $entry { + let arr = match entry.trigger { + InputMapperMessage::KeyDown(key) => &mut key_down[key as usize], + InputMapperMessage::KeyUp(key) => &mut key_up[key as usize], + InputMapperMessage::MouseScroll => &mut mouse_scroll, + InputMapperMessage::PointerMove => &mut pointer_move, + }; + arr.push(entry.clone()); + } + )* + (key_up, key_down, pointer_move, mouse_scroll) + }}; + } + + pub(crate) use entry; + pub(crate) use mapping; + pub(crate) use modifiers; } diff --git a/editor/src/input/input_mapper_message.rs b/editor/src/input/input_mapper_message.rs new file mode 100644 index 0000000000..c41ade5900 --- /dev/null +++ b/editor/src/input/input_mapper_message.rs @@ -0,0 +1,16 @@ +use super::keyboard::Key; +use crate::message_prelude::*; + +use serde::{Deserialize, Serialize}; + +#[remain::sorted] +#[impl_message(Message, InputMapper)] +#[derive(PartialEq, Clone, Debug, Hash, Serialize, Deserialize)] +pub enum InputMapperMessage { + #[child] + KeyDown(Key), + #[child] + KeyUp(Key), + MouseScroll, + PointerMove, +} diff --git a/editor/src/input/input_mapper_message_handler.rs b/editor/src/input/input_mapper_message_handler.rs new file mode 100644 index 0000000000..ee9eabfa2e --- /dev/null +++ b/editor/src/input/input_mapper_message_handler.rs @@ -0,0 +1,44 @@ +use super::input_mapper::Mapping; +use super::keyboard::Key; +use super::InputPreprocessorMessageHandler; +use crate::message_prelude::*; + +use std::fmt::Write; + +#[derive(Debug, Default)] +pub struct InputMapperMessageHandler { + mapping: Mapping, +} + +impl InputMapperMessageHandler { + pub fn hints(&self, actions: ActionList) -> String { + let mut output = String::new(); + let mut actions = actions + .into_iter() + .flatten() + .filter(|a| !matches!(*a, MessageDiscriminant::Tool(ToolMessageDiscriminant::ActivateTool) | MessageDiscriminant::Global(_))); + self.mapping + .key_down + .iter() + .enumerate() + .filter_map(|(i, m)| { + let ma = m.0.iter().find_map(|m| actions.find_map(|a| (a == m.action.to_discriminant()).then(|| m.action.to_discriminant()))); + + ma.map(|a| unsafe { (std::mem::transmute_copy::(&i), a) }) + }) + .for_each(|(k, a)| { + let _ = write!(output, "{}: {}, ", k.to_discriminant().local_name(), a.local_name().split('.').last().unwrap()); + }); + output.replace("Key", "") + } +} + +impl MessageHandler for InputMapperMessageHandler { + fn process_action(&mut self, message: InputMapperMessage, data: (&InputPreprocessorMessageHandler, ActionList), responses: &mut VecDeque) { + let (input, actions) = data; + if let Some(message) = self.mapping.match_message(message, &input.keyboard, actions) { + responses.push_back(message); + } + } + advertise_actions!(); +} diff --git a/editor/src/input/input_preprocessor.rs b/editor/src/input/input_preprocessor.rs index 7ad5a4fa2f..24f9aefb55 100644 --- a/editor/src/input/input_preprocessor.rs +++ b/editor/src/input/input_preprocessor.rs @@ -1,25 +1,12 @@ -use std::usize; - -use super::keyboard::{Key, KeyStates}; -use super::mouse::{EditorMouseState, MouseKeys, MouseState, ViewportBounds}; -use crate::message_prelude::*; -use bitflags::bitflags; - #[doc(inline)] pub use graphene::DocumentResponse; + +use bitflags::bitflags; use serde::{Deserialize, Serialize}; -#[remain::sorted] -#[impl_message(Message, InputPreprocessor)] -#[derive(PartialEq, Clone, Debug, Serialize, Deserialize)] -pub enum InputPreprocessorMessage { - BoundsOfViewports(Vec), - KeyDown(Key, ModifierKeys), - KeyUp(Key, ModifierKeys), - MouseDown(EditorMouseState, ModifierKeys), - MouseMove(EditorMouseState, ModifierKeys), - MouseScroll(EditorMouseState, ModifierKeys), - MouseUp(EditorMouseState, ModifierKeys), +pub enum KeyPosition { + Pressed, + Released, } bitflags! { @@ -32,167 +19,20 @@ bitflags! { } } -#[derive(Debug, Default)] -pub struct InputPreprocessor { - pub keyboard: KeyStates, - pub mouse: MouseState, - pub viewport_bounds: ViewportBounds, -} - -enum KeyPosition { - Pressed, - Released, -} - -impl MessageHandler for InputPreprocessor { - #[remain::check] - fn process_action(&mut self, message: InputPreprocessorMessage, _data: (), responses: &mut VecDeque) { - #[remain::sorted] - match message { - InputPreprocessorMessage::BoundsOfViewports(bounds_of_viewports) => { - assert_eq!(bounds_of_viewports.len(), 1, "Only one viewport is currently supported"); - - for bounds in bounds_of_viewports { - let new_size = bounds.size(); - let existing_size = self.viewport_bounds.size(); - - let translation = (new_size - existing_size) / 2.; - - // TODO: Extend this to multiple viewports instead of setting it to the value of this last loop iteration - self.viewport_bounds = bounds; - - responses.push_back( - graphene::Operation::TransformLayer { - path: vec![], - transform: glam::DAffine2::from_translation(translation).to_cols_array(), - } - .into(), - ); - responses.push_back( - DocumentMessage::Overlay( - graphene::Operation::TransformLayer { - path: vec![], - transform: glam::DAffine2::from_translation(translation).to_cols_array(), - } - .into(), - ) - .into(), - ); - responses.push_back( - DocumentMessage::Artboard( - graphene::Operation::TransformLayer { - path: vec![], - transform: glam::DAffine2::from_translation(translation).to_cols_array(), - } - .into(), - ) - .into(), - ); - } - } - InputPreprocessorMessage::KeyDown(key, modifier_keys) => { - self.handle_modifier_keys(modifier_keys, responses); - self.keyboard.set(key as usize); - responses.push_back(InputMapperMessage::KeyDown(key).into()); - } - InputPreprocessorMessage::KeyUp(key, modifier_keys) => { - self.handle_modifier_keys(modifier_keys, responses); - self.keyboard.unset(key as usize); - responses.push_back(InputMapperMessage::KeyUp(key).into()); - } - InputPreprocessorMessage::MouseDown(editor_mouse_state, modifier_keys) => { - self.handle_modifier_keys(modifier_keys, responses); - - let mouse_state = editor_mouse_state.to_mouse_state(&self.viewport_bounds); - self.mouse.position = mouse_state.position; - - if let Some(message) = self.translate_mouse_event(mouse_state, KeyPosition::Pressed) { - responses.push_back(message); - } - } - InputPreprocessorMessage::MouseMove(editor_mouse_state, modifier_keys) => { - self.handle_modifier_keys(modifier_keys, responses); - - let mouse_state = editor_mouse_state.to_mouse_state(&self.viewport_bounds); - self.mouse.position = mouse_state.position; - - responses.push_back(InputMapperMessage::PointerMove.into()); - } - InputPreprocessorMessage::MouseScroll(editor_mouse_state, modifier_keys) => { - self.handle_modifier_keys(modifier_keys, responses); - - let mouse_state = editor_mouse_state.to_mouse_state(&self.viewport_bounds); - self.mouse.position = mouse_state.position; - self.mouse.scroll_delta = mouse_state.scroll_delta; - - responses.push_back(InputMapperMessage::MouseScroll.into()); - } - InputPreprocessorMessage::MouseUp(editor_mouse_state, modifier_keys) => { - self.handle_modifier_keys(modifier_keys, responses); - - let mouse_state = editor_mouse_state.to_mouse_state(&self.viewport_bounds); - self.mouse.position = mouse_state.position; - - if let Some(message) = self.translate_mouse_event(mouse_state, KeyPosition::Released) { - responses.push_back(message); - } - } - }; - } - // clean user input and if possible reconstruct it - // store the changes in the keyboard if it is a key event - // transform canvas coordinates to document coordinates - advertise_actions!(); -} - -impl InputPreprocessor { - fn translate_mouse_event(&mut self, new_state: MouseState, position: KeyPosition) -> Option { - // Calculate the difference between the two key states (binary xor) - let diff = self.mouse.mouse_keys ^ new_state.mouse_keys; - self.mouse = new_state; - let key = match diff { - MouseKeys::LEFT => Key::Lmb, - MouseKeys::RIGHT => Key::Rmb, - MouseKeys::MIDDLE => Key::Mmb, - MouseKeys::NONE => return None, // self.mouse.mouse_keys was invalid, e.g. when a drag began outside the client - _ => { - log::warn!("The number of buttons modified at the same time was greater than 1. Modification: {:#010b}", diff); - Key::UnknownKey - } - }; - Some(match position { - KeyPosition::Pressed => InputMapperMessage::KeyDown(key).into(), - KeyPosition::Released => InputMapperMessage::KeyUp(key).into(), - }) - } - - fn handle_modifier_keys(&mut self, modifier_keys: ModifierKeys, responses: &mut VecDeque) { - self.handle_modifier_key(Key::KeyControl, modifier_keys.contains(ModifierKeys::CONTROL), responses); - self.handle_modifier_key(Key::KeyShift, modifier_keys.contains(ModifierKeys::SHIFT), responses); - self.handle_modifier_key(Key::KeyAlt, modifier_keys.contains(ModifierKeys::ALT), responses); - } - - fn handle_modifier_key(&mut self, key: Key, key_is_down: bool, responses: &mut VecDeque) { - let key_was_down = self.keyboard.get(key as usize); - if key_was_down && !key_is_down { - self.keyboard.unset(key as usize); - responses.push_back(InputMapperMessage::KeyUp(key).into()); - } else if !key_was_down && key_is_down { - self.keyboard.set(key as usize); - responses.push_back(InputMapperMessage::KeyDown(key).into()); - } - } -} - #[cfg(test)] mod test { - use crate::input::mouse::ViewportPosition; + use crate::input::input_preprocessor::ModifierKeys; + use crate::input::keyboard::Key; + use crate::input::mouse::{EditorMouseState, ViewportPosition}; + use crate::input::InputPreprocessorMessageHandler; + use crate::message_prelude::MessageHandler; + use crate::message_prelude::*; - use super::*; + use std::collections::VecDeque; #[test] fn process_action_mouse_move_handle_modifier_keys() { - let mut input_preprocessor = InputPreprocessor::default(); + let mut input_preprocessor = InputPreprocessorMessageHandler::default(); let mut editor_mouse_state = EditorMouseState::new(); editor_mouse_state.editor_position = ViewportPosition::new(4., 809.); let message = InputPreprocessorMessage::MouseMove(editor_mouse_state, ModifierKeys::ALT); @@ -206,7 +46,7 @@ mod test { #[test] fn process_action_mouse_down_handle_modifier_keys() { - let mut input_preprocessor = InputPreprocessor::default(); + let mut input_preprocessor = InputPreprocessorMessageHandler::default(); let message = InputPreprocessorMessage::MouseDown(EditorMouseState::new(), ModifierKeys::CONTROL); let mut responses = VecDeque::new(); @@ -218,7 +58,7 @@ mod test { #[test] fn process_action_mouse_up_handle_modifier_keys() { - let mut input_preprocessor = InputPreprocessor::default(); + let mut input_preprocessor = InputPreprocessorMessageHandler::default(); let message = InputPreprocessorMessage::MouseUp(EditorMouseState::new(), ModifierKeys::SHIFT); let mut responses = VecDeque::new(); @@ -230,7 +70,7 @@ mod test { #[test] fn process_action_key_down_handle_modifier_keys() { - let mut input_preprocessor = InputPreprocessor::default(); + let mut input_preprocessor = InputPreprocessorMessageHandler::default(); input_preprocessor.keyboard.set(Key::KeyControl as usize); let message = InputPreprocessorMessage::KeyDown(Key::KeyA, ModifierKeys::empty()); let mut responses = VecDeque::new(); @@ -243,7 +83,7 @@ mod test { #[test] fn process_action_key_up_handle_modifier_keys() { - let mut input_preprocessor = InputPreprocessor::default(); + let mut input_preprocessor = InputPreprocessorMessageHandler::default(); let message = InputPreprocessorMessage::KeyUp(Key::KeyS, ModifierKeys::CONTROL | ModifierKeys::SHIFT); let mut responses = VecDeque::new(); diff --git a/editor/src/input/input_preprocessor_message.rs b/editor/src/input/input_preprocessor_message.rs new file mode 100644 index 0000000000..001431a020 --- /dev/null +++ b/editor/src/input/input_preprocessor_message.rs @@ -0,0 +1,22 @@ +use super::input_preprocessor::ModifierKeys; +use super::keyboard::Key; +use super::mouse::{EditorMouseState, ViewportBounds}; +use crate::message_prelude::*; + +#[doc(inline)] +pub use graphene::DocumentResponse; + +use serde::{Deserialize, Serialize}; + +#[remain::sorted] +#[impl_message(Message, InputPreprocessor)] +#[derive(PartialEq, Clone, Debug, Serialize, Deserialize)] +pub enum InputPreprocessorMessage { + BoundsOfViewports(Vec), + KeyDown(Key, ModifierKeys), + KeyUp(Key, ModifierKeys), + MouseDown(EditorMouseState, ModifierKeys), + MouseMove(EditorMouseState, ModifierKeys), + MouseScroll(EditorMouseState, ModifierKeys), + MouseUp(EditorMouseState, ModifierKeys), +} diff --git a/editor/src/input/input_preprocessor_message_handler.rs b/editor/src/input/input_preprocessor_message_handler.rs new file mode 100644 index 0000000000..b426f26b06 --- /dev/null +++ b/editor/src/input/input_preprocessor_message_handler.rs @@ -0,0 +1,159 @@ +use super::input_preprocessor::{KeyPosition, ModifierKeys}; +use super::keyboard::{Key, KeyStates}; +use super::mouse::{MouseKeys, MouseState, ViewportBounds}; +use crate::message_prelude::*; + +#[doc(inline)] +pub use graphene::DocumentResponse; + +#[derive(Debug, Default)] +pub struct InputPreprocessorMessageHandler { + pub keyboard: KeyStates, + pub mouse: MouseState, + pub viewport_bounds: ViewportBounds, +} + +impl MessageHandler for InputPreprocessorMessageHandler { + #[remain::check] + fn process_action(&mut self, message: InputPreprocessorMessage, _data: (), responses: &mut VecDeque) { + #[remain::sorted] + match message { + InputPreprocessorMessage::BoundsOfViewports(bounds_of_viewports) => { + assert_eq!(bounds_of_viewports.len(), 1, "Only one viewport is currently supported"); + + for bounds in bounds_of_viewports { + let new_size = bounds.size(); + let existing_size = self.viewport_bounds.size(); + + let translation = (new_size - existing_size) / 2.; + + // TODO: Extend this to multiple viewports instead of setting it to the value of this last loop iteration + self.viewport_bounds = bounds; + + responses.push_back( + graphene::Operation::TransformLayer { + path: vec![], + transform: glam::DAffine2::from_translation(translation).to_cols_array(), + } + .into(), + ); + responses.push_back( + DocumentMessage::Overlays( + graphene::Operation::TransformLayer { + path: vec![], + transform: glam::DAffine2::from_translation(translation).to_cols_array(), + } + .into(), + ) + .into(), + ); + responses.push_back( + DocumentMessage::Artboard( + graphene::Operation::TransformLayer { + path: vec![], + transform: glam::DAffine2::from_translation(translation).to_cols_array(), + } + .into(), + ) + .into(), + ); + } + } + InputPreprocessorMessage::KeyDown(key, modifier_keys) => { + self.handle_modifier_keys(modifier_keys, responses); + self.keyboard.set(key as usize); + responses.push_back(InputMapperMessage::KeyDown(key).into()); + } + InputPreprocessorMessage::KeyUp(key, modifier_keys) => { + self.handle_modifier_keys(modifier_keys, responses); + self.keyboard.unset(key as usize); + responses.push_back(InputMapperMessage::KeyUp(key).into()); + } + InputPreprocessorMessage::MouseDown(editor_mouse_state, modifier_keys) => { + self.handle_modifier_keys(modifier_keys, responses); + + let mouse_state = editor_mouse_state.to_mouse_state(&self.viewport_bounds); + self.mouse.position = mouse_state.position; + + if let Some(message) = self.translate_mouse_event(mouse_state, KeyPosition::Pressed) { + responses.push_back(message); + } + } + InputPreprocessorMessage::MouseMove(editor_mouse_state, modifier_keys) => { + self.handle_modifier_keys(modifier_keys, responses); + + let mouse_state = editor_mouse_state.to_mouse_state(&self.viewport_bounds); + self.mouse.position = mouse_state.position; + + responses.push_back(InputMapperMessage::PointerMove.into()); + } + InputPreprocessorMessage::MouseScroll(editor_mouse_state, modifier_keys) => { + self.handle_modifier_keys(modifier_keys, responses); + + let mouse_state = editor_mouse_state.to_mouse_state(&self.viewport_bounds); + self.mouse.position = mouse_state.position; + self.mouse.scroll_delta = mouse_state.scroll_delta; + + responses.push_back(InputMapperMessage::MouseScroll.into()); + } + InputPreprocessorMessage::MouseUp(editor_mouse_state, modifier_keys) => { + self.handle_modifier_keys(modifier_keys, responses); + + let mouse_state = editor_mouse_state.to_mouse_state(&self.viewport_bounds); + self.mouse.position = mouse_state.position; + + if let Some(message) = self.translate_mouse_event(mouse_state, KeyPosition::Released) { + responses.push_back(message); + } + } + }; + } + + // Clean user input and if possible reconstruct it. + // Store the changes in the keyboard if it is a key event. + // Transform canvas coordinates to document coordinates. + advertise_actions!(); +} + +impl InputPreprocessorMessageHandler { + fn translate_mouse_event(&mut self, new_state: MouseState, position: KeyPosition) -> Option { + // Calculate the difference between the two key states (binary xor) + let difference = self.mouse.mouse_keys ^ new_state.mouse_keys; + + self.mouse = new_state; + + let key = match difference { + MouseKeys::LEFT => Key::Lmb, + MouseKeys::RIGHT => Key::Rmb, + MouseKeys::MIDDLE => Key::Mmb, + MouseKeys::NONE => return None, // self.mouse.mouse_keys was invalid, e.g. when a drag began outside the client + _ => { + log::warn!("The number of buttons modified at the same time was greater than 1. Modification: {:#010b}", difference); + Key::UnknownKey + } + }; + + Some(match position { + KeyPosition::Pressed => InputMapperMessage::KeyDown(key).into(), + KeyPosition::Released => InputMapperMessage::KeyUp(key).into(), + }) + } + + fn handle_modifier_keys(&mut self, modifier_keys: ModifierKeys, responses: &mut VecDeque) { + self.handle_modifier_key(Key::KeyControl, modifier_keys.contains(ModifierKeys::CONTROL), responses); + self.handle_modifier_key(Key::KeyShift, modifier_keys.contains(ModifierKeys::SHIFT), responses); + self.handle_modifier_key(Key::KeyAlt, modifier_keys.contains(ModifierKeys::ALT), responses); + } + + fn handle_modifier_key(&mut self, key: Key, key_is_down: bool, responses: &mut VecDeque) { + let key_was_down = self.keyboard.get(key as usize); + + if key_was_down && !key_is_down { + self.keyboard.unset(key as usize); + responses.push_back(InputMapperMessage::KeyUp(key).into()); + } else if !key_was_down && key_is_down { + self.keyboard.set(key as usize); + responses.push_back(InputMapperMessage::KeyDown(key).into()); + } + } +} diff --git a/editor/src/input/keyboard.rs b/editor/src/input/keyboard.rs index 3198b8cdb3..eee4cb908c 100644 --- a/editor/src/input/keyboard.rs +++ b/editor/src/input/keyboard.rs @@ -1,15 +1,18 @@ use crate::message_prelude::*; + use serde::{Deserialize, Serialize}; +use std::fmt::{Display, Formatter}; +use std::ops::{BitAnd, BitAndAssign, BitOr, BitOrAssign, BitXor, BitXorAssign}; -pub const NUMBER_OF_KEYS: usize = Key::NumKeys as usize; -// Edit this to specify the storage type used // TODO: Increase size of type +/// Edit this to specify the storage type used. pub type StorageType = u128; -// base 2 logarithm of the storage type used to represents how many bits you need to fully address every bit in that storage type +// Base-2 logarithm of the storage type used to represents how many bits you need to fully address every bit in that storage type const STORAGE_SIZE: u32 = (std::mem::size_of::() * 8).trailing_zeros(); const STORAGE_SIZE_BITS: usize = 1 << STORAGE_SIZE; const KEY_MASK_STORAGE_LENGTH: usize = (NUMBER_OF_KEYS + STORAGE_SIZE_BITS - 1) >> STORAGE_SIZE; + pub type KeyStates = BitVector; #[impl_message(Message, InputMapperMessage, KeyDown)] @@ -84,10 +87,12 @@ pub enum Key { KeyComma, KeyPeriod, - // This has to be the last element in the enum. + // This has to be the last element in the enum NumKeys, } +pub const NUMBER_OF_KEYS: usize = Key::NumKeys as usize; + #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)] pub enum MouseMotion { None, @@ -105,12 +110,6 @@ pub enum MouseMotion { #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub struct BitVector([StorageType; LENGTH]); -use std::{ - fmt::{Display, Formatter}, - ops::{BitAnd, BitAndAssign, BitOr, BitOrAssign, BitXor, BitXorAssign}, - usize, -}; - impl BitVector { #[inline] fn convert_index(bitvector_index: usize) -> (usize, StorageType) { @@ -118,37 +117,48 @@ impl BitVector { let offset = bitvector_index >> STORAGE_SIZE; (offset, bit) } + pub const fn new() -> Self { Self([0; LENGTH]) } + pub fn set(&mut self, bitvector_index: usize) { let (offset, bit) = Self::convert_index(bitvector_index); self.0[offset] |= bit; } + pub fn unset(&mut self, bitvector_index: usize) { let (offset, bit) = Self::convert_index(bitvector_index); self.0[offset] &= !bit; } + pub fn toggle(&mut self, bitvector_index: usize) { let (offset, bit) = Self::convert_index(bitvector_index); self.0[offset] ^= bit; } + pub fn get(&self, bitvector_index: usize) -> bool { let (offset, bit) = Self::convert_index(bitvector_index); (self.0[offset] & bit) != 0 } + pub fn is_empty(&self) -> bool { let mut result = 0; + for storage in self.0.iter() { result |= storage; } + result == 0 } + pub fn ones(&self) -> u32 { let mut result = 0; + for storage in self.0.iter() { result += storage.count_ones(); } + result } } @@ -164,6 +174,7 @@ impl Display for BitVector { for storage in self.0.iter().rev() { write!(f, "{:0width$b}", storage, width = STORAGE_SIZE_BITS)?; } + Ok(()) } } @@ -181,6 +192,7 @@ macro_rules! bit_ops { result } } + impl $op for &BitVector { type Output = BitVector; fn $func(self, right: Self) -> Self::Output { diff --git a/editor/src/input/mod.rs b/editor/src/input/mod.rs index a2c50ffe76..fd6f4d49c3 100644 --- a/editor/src/input/mod.rs +++ b/editor/src/input/mod.rs @@ -3,7 +3,17 @@ pub mod input_preprocessor; pub mod keyboard; pub mod mouse; -pub use { - input_mapper::{InputMapper, InputMapperMessage, InputMapperMessageDiscriminant}, - input_preprocessor::{InputPreprocessor, InputPreprocessorMessage, InputPreprocessorMessageDiscriminant, ModifierKeys}, -}; +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; diff --git a/editor/src/input/mouse.rs b/editor/src/input/mouse.rs index 023a9a3560..cf2de2cf57 100644 --- a/editor/src/input/mouse.rs +++ b/editor/src/input/mouse.rs @@ -36,13 +36,16 @@ pub struct ScrollDelta { pub y: i32, pub z: i32, } + impl ScrollDelta { pub fn new(x: i32, y: i32, z: i32) -> ScrollDelta { ScrollDelta { x, y, z } } + pub fn as_dvec2(&self) -> DVec2 { DVec2::new(self.x as f64, self.y as f64) } + pub fn scroll_delta(&self) -> f64 { let (dx, dy) = (self.x, self.y); dy.signum() as f64 * ((dy * dy + i32::min(dy.abs(), dx.abs()).pow(2)) as f64).sqrt() @@ -70,7 +73,8 @@ impl MouseState { } pub fn from_keys_and_editor_position(keys: u8, position: ViewportPosition) -> Self { - let mouse_keys = MouseKeys::from_bits(keys).expect("invalid modifier keys"); + let mouse_keys = MouseKeys::from_bits(keys).expect("Invalid modifier keys"); + Self { position, mouse_keys, @@ -100,7 +104,8 @@ impl EditorMouseState { } pub fn from_keys_and_editor_position(keys: u8, editor_position: EditorPosition) -> Self { - let mouse_keys = MouseKeys::from_bits(keys).expect("invalid modifier keys"); + let mouse_keys = MouseKeys::from_bits(keys).expect("Invalid modifier keys"); + Self { editor_position, mouse_keys, diff --git a/editor/src/lib.rs b/editor/src/lib.rs index 4ff23362af..26bb19cdcb 100644 --- a/editor/src/lib.rs +++ b/editor/src/lib.rs @@ -1,17 +1,14 @@ -// Since our policy is tabs, we want to stop clippy from warning about that -#![allow(clippy::tabs_in_doc_comments)] - extern crate graphite_proc_macros; pub mod communication; #[macro_use] pub mod misc; pub mod consts; -mod document; -mod frontend; -mod global; +pub mod document; +pub mod frontend; +pub mod global; pub mod input; -pub mod tool; +pub mod viewport_tools; #[doc(inline)] pub use graphene::color::Color; @@ -56,30 +53,35 @@ impl Default for Editor { pub mod message_prelude { pub use crate::communication::generate_uuid; pub use crate::communication::message::{AsMessage, Message, MessageDiscriminant}; - pub use crate::communication::{ActionList, MessageHandler}; - pub use crate::document::Clipboard; + pub use crate::communication::message_handler::{ActionList, MessageHandler}; + + pub use crate::document::clipboards::Clipboard; + pub use crate::LayerId; + pub use crate::document::{ArtboardMessage, ArtboardMessageDiscriminant}; pub use crate::document::{DocumentMessage, DocumentMessageDiscriminant}; pub use crate::document::{MovementMessage, MovementMessageDiscriminant}; - pub use crate::document::{OverlayMessage, OverlayMessageDiscriminant}; + pub use crate::document::{OverlaysMessage, OverlaysMessageDiscriminant}; pub use crate::document::{PortfolioMessage, PortfolioMessageDiscriminant}; pub use crate::document::{TransformLayerMessage, TransformLayerMessageDiscriminant}; pub use crate::frontend::{FrontendMessage, FrontendMessageDiscriminant}; pub use crate::global::{GlobalMessage, GlobalMessageDiscriminant}; pub use crate::input::{InputMapperMessage, InputMapperMessageDiscriminant, InputPreprocessorMessage, InputPreprocessorMessageDiscriminant}; pub use crate::misc::derivable_custom_traits::{ToDiscriminant, TransitiveChild}; - pub use crate::tool::tool_messages::*; - pub use crate::tool::tools::crop::{CropMessage, CropMessageDiscriminant}; - pub use crate::tool::tools::eyedropper::{EyedropperMessage, EyedropperMessageDiscriminant}; - pub use crate::tool::tools::fill::{FillMessage, FillMessageDiscriminant}; - pub use crate::tool::tools::line::{LineMessage, LineMessageDiscriminant}; - pub use crate::tool::tools::navigate::{NavigateMessage, NavigateMessageDiscriminant}; - pub use crate::tool::tools::path::{PathMessage, PathMessageDiscriminant}; - pub use crate::tool::tools::pen::{PenMessage, PenMessageDiscriminant}; - pub use crate::tool::tools::rectangle::{RectangleMessage, RectangleMessageDiscriminant}; - pub use crate::tool::tools::select::{SelectMessage, SelectMessageDiscriminant}; - pub use crate::tool::tools::shape::{ShapeMessage, ShapeMessageDiscriminant}; - pub use crate::LayerId; + pub use crate::viewport_tools::tool_message::{ToolMessage, ToolMessageDiscriminant}; + pub use crate::viewport_tools::tools::crop::{CropMessage, CropMessageDiscriminant}; + pub use crate::viewport_tools::tools::ellipse::{EllipseMessage, EllipseMessageDiscriminant}; + pub use crate::viewport_tools::tools::eyedropper::{EyedropperMessage, EyedropperMessageDiscriminant}; + pub use crate::viewport_tools::tools::fill::{FillMessage, FillMessageDiscriminant}; + pub use crate::viewport_tools::tools::line::{LineMessage, LineMessageDiscriminant}; + pub use crate::viewport_tools::tools::navigate::{NavigateMessage, NavigateMessageDiscriminant}; + pub use crate::viewport_tools::tools::path::{PathMessage, PathMessageDiscriminant}; + pub use crate::viewport_tools::tools::pen::{PenMessage, PenMessageDiscriminant}; + pub use crate::viewport_tools::tools::rectangle::{RectangleMessage, RectangleMessageDiscriminant}; + pub use crate::viewport_tools::tools::select::{SelectMessage, SelectMessageDiscriminant}; + pub use crate::viewport_tools::tools::shape::{ShapeMessage, ShapeMessageDiscriminant}; + pub use graphite_proc_macros::*; + pub use std::collections::VecDeque; } diff --git a/editor/src/misc/error.rs b/editor/src/misc/error.rs index 08e4c40e00..790bb63dff 100644 --- a/editor/src/misc/error.rs +++ b/editor/src/misc/error.rs @@ -1,5 +1,6 @@ -use crate::Color; +use graphene::color::Color; use graphene::DocumentError; + use thiserror::Error; /// The error type used by the Graphite editor. diff --git a/editor/src/misc/hints.rs b/editor/src/misc/hints.rs index 349dbf2fc4..86c382bcc3 100644 --- a/editor/src/misc/hints.rs +++ b/editor/src/misc/hints.rs @@ -1,7 +1,7 @@ -use serde::{Deserialize, Serialize}; - use crate::input::keyboard::{Key, MouseMotion}; +use serde::{Deserialize, Serialize}; + #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct HintData(pub Vec); @@ -13,8 +13,10 @@ pub struct HintInfo { pub key_groups: Vec, pub mouse: Option, pub label: String, - pub plus: bool, // Prepend the "+" symbol indicating that this is a refinement upon a previous entry in the group + /// Prepend the "+" symbol indicating that this is a refinement upon a previous entry in the group. + pub plus: bool, } +/// Only `Key`s that exist on a physical keyboard should be used. #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -pub struct KeysGroup(pub Vec); // Only use `Key`s that exist on a physical keyboard +pub struct KeysGroup(pub Vec); diff --git a/editor/src/misc/macros.rs b/editor/src/misc/macros.rs index ef20f5521a..539c12452d 100644 --- a/editor/src/misc/macros.rs +++ b/editor/src/misc/macros.rs @@ -24,25 +24,25 @@ macro_rules! count_args { /// /// ```ignore /// let tools = gen_tools_hash_map! { -/// Select => select::Select, -/// Crop => crop::Crop, +/// Select => select::Select, +/// Crop => crop::Crop, /// }; /// ``` /// expands to /// ```ignore /// let tools = { -/// let mut hash_map: std::collections::HashMap> = std::collections::HashMap::with_capacity(count_args!(/* Macro args */)); +/// let mut hash_map: std::collections::HashMap> = std::collections::HashMap::with_capacity(count_args!(/* Macro args */)); /// -/// hash_map.insert(crate::tool::ToolType::Select, Box::new(select::Select::default())); -/// hash_map.insert(crate::tool::ToolType::Crop, Box::new(crop::Crop::default())); +/// hash_map.insert(crate::tool::ToolType::Select, Box::new(select::Select::default())); +/// hash_map.insert(crate::tool::ToolType::Crop, Box::new(crop::Crop::default())); /// -/// hash_map +/// hash_map /// }; /// ``` macro_rules! gen_tools_hash_map { ($($enum_variant:ident => $struct_path:ty),* $(,)?) => {{ - let mut hash_map: ::std::collections::HashMap<$crate::tool::ToolType, ::std::boxed::Box $crate::message_prelude::MessageHandler<$crate::tool::tool_messages::ToolMessage,$crate::tool::ToolActionHandlerData<'a>>>> = ::std::collections::HashMap::with_capacity(count_args!($(($enum_variant)),*)); - $(hash_map.insert($crate::tool::ToolType::$enum_variant, ::std::boxed::Box::new(<$struct_path>::default()));)* + let mut hash_map: ::std::collections::HashMap<$crate::viewport_tools::tool::ToolType, ::std::boxed::Box $crate::message_prelude::MessageHandler<$crate::viewport_tools::tool_message::ToolMessage,$crate::viewport_tools::tool::ToolActionHandlerData<'a>>>> = ::std::collections::HashMap::with_capacity(count_args!($(($enum_variant)),*)); + $(hash_map.insert($crate::viewport_tools::tool::ToolType::$enum_variant, ::std::boxed::Box::new(<$struct_path>::default()));)* hash_map }}; @@ -54,8 +54,8 @@ macro_rules! gen_tools_hash_map { /// /// ```ignore /// enum E { -/// A(u8), -/// B +/// A(u8), +/// B /// } /// /// // this line is important @@ -71,8 +71,8 @@ macro_rules! gen_tools_hash_map { /// // ... /// /// let s = match a { -/// A { .. } => "A", -/// B { .. } => "B" +/// A { .. } => "A", +/// B { .. } => "B" /// }; /// ``` macro_rules! match_variant_name { @@ -110,10 +110,11 @@ macro_rules! match_variant_name { /// macro_rules! actions { ($($v:expr),* $(,)?) => {{ - vec![$(vec![$v.into()]),*] + vec![$(vec![$v.into()]),*] }}; + ($name:ident; $($v:ident),* $(,)?) => {{ - vec![vec![$(($name::$v).into()),*]] + vec![vec![$(($name::$v).into()),*]] }}; } @@ -121,18 +122,19 @@ macro_rules! actions { /// /// ```ignore /// fn actions(&self) -> ActionList { -/// actions!(…) +/// actions!(…) /// } /// ``` macro_rules! advertise_actions { ($($v:expr),* $(,)?) => { - fn actions(&self) -> $crate::communication::ActionList { - actions!($($v),*) + fn actions(&self) -> $crate::communication::message_handler::ActionList { + actions!($($v),*) } }; + ($name:ident; $($v:ident),* $(,)?) => { - fn actions(&self) -> $crate::communication::ActionList { - actions!($name; $($v),*) + fn actions(&self) -> $crate::communication::message_handler::ActionList { + actions!($name; $($v),*) } } } diff --git a/editor/src/misc/mod.rs b/editor/src/misc/mod.rs index 875e813b30..bc412fe40a 100644 --- a/editor/src/misc/mod.rs +++ b/editor/src/misc/mod.rs @@ -1,10 +1,11 @@ #[macro_use] pub mod macros; + pub mod derivable_custom_traits; -mod error; pub mod hints; pub mod test_utils; - pub use error::EditorError; pub use hints::*; pub use macros::*; + +mod error; diff --git a/editor/src/misc/test_utils.rs b/editor/src/misc/test_utils.rs index 784fed47fc..83d74e2f89 100644 --- a/editor/src/misc/test_utils.rs +++ b/editor/src/misc/test_utils.rs @@ -1,12 +1,9 @@ -use crate::{ - input::{ - mouse::{EditorMouseState, MouseKeys, ScrollDelta, ViewportPosition}, - InputPreprocessorMessage, ModifierKeys, - }, - message_prelude::{Message, ToolMessage}, - tool::ToolType, - Editor, -}; +use crate::input::input_preprocessor::ModifierKeys; +use crate::input::mouse::{EditorMouseState, MouseKeys, ScrollDelta, ViewportPosition}; +use crate::message_prelude::*; +use crate::viewport_tools::tool::ToolType; +use crate::Editor; + use graphene::color::Color; /// A set of utility functions to make the writing of editor test more declarative diff --git a/editor/src/tool/mod.rs b/editor/src/tool/mod.rs deleted file mode 100644 index bdea681706..0000000000 --- a/editor/src/tool/mod.rs +++ /dev/null @@ -1,204 +0,0 @@ -mod snapping; -pub mod tool_message_handler; -pub mod tool_options; -pub mod tools; - -use crate::document::DocumentMessageHandler; -use crate::input::InputPreprocessor; -use crate::message_prelude::*; -use crate::{ - communication::{message::Message, MessageHandler}, - Color, -}; -use serde::{Deserialize, Serialize}; -use std::collections::VecDeque; -use std::{ - collections::HashMap, - fmt::{self, Debug}, -}; -pub use tool_message_handler::ToolMessageHandler; -use tool_options::ToolOptions; -pub use tool_options::*; -use tools::*; - -pub mod tool_messages { - pub use super::tool_message_handler::{ToolMessage, ToolMessageDiscriminant}; - pub use super::tools::ellipse::{EllipseMessage, EllipseMessageDiscriminant}; - pub use super::tools::rectangle::{RectangleMessage, RectangleMessageDiscriminant}; -} - -pub type ToolActionHandlerData<'a> = (&'a DocumentMessageHandler, &'a DocumentToolData, &'a InputPreprocessor); - -pub trait Fsm { - type ToolData; - - fn transition( - self, - message: ToolMessage, - document: &DocumentMessageHandler, - tool_data: &DocumentToolData, - data: &mut Self::ToolData, - input: &InputPreprocessor, - messages: &mut VecDeque, - ) -> Self; - - fn update_hints(&self, responses: &mut VecDeque); -} - -#[derive(Debug, Clone)] -pub struct DocumentToolData { - pub primary_color: Color, - pub secondary_color: Color, - pub tool_options: HashMap, -} - -type SubToolMessageHandler = dyn for<'a> MessageHandler>; -pub struct ToolData { - pub active_tool_type: ToolType, - pub tools: HashMap>, -} - -impl fmt::Debug for ToolData { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("ToolData").field("active_tool_type", &self.active_tool_type).field("tool_options", &"[…]").finish() - } -} - -impl ToolData { - pub fn active_tool_mut(&mut self) -> &mut Box { - self.tools.get_mut(&self.active_tool_type).expect("The active tool is not initialized") - } - pub fn active_tool(&self) -> &SubToolMessageHandler { - self.tools.get(&self.active_tool_type).map(|x| x.as_ref()).expect("The active tool is not initialized") - } -} - -#[derive(Debug)] -pub struct ToolFsmState { - pub document_tool_data: DocumentToolData, - pub tool_data: ToolData, -} - -impl Default for ToolFsmState { - fn default() -> Self { - ToolFsmState { - tool_data: ToolData { - active_tool_type: ToolType::Select, - tools: gen_tools_hash_map! { - Rectangle => rectangle::Rectangle, - Select => select::Select, - Crop => crop::Crop, - Navigate => navigate::Navigate, - Eyedropper => eyedropper::Eyedropper, - Path => path::Path, - Pen => pen::Pen, - Line => line::Line, - Shape => shape::Shape, - Ellipse => ellipse::Ellipse, - Fill => fill::Fill, - }, - }, - document_tool_data: DocumentToolData { - primary_color: Color::BLACK, - secondary_color: Color::WHITE, - tool_options: default_tool_options(), - }, - } - } -} - -impl ToolFsmState { - pub fn new() -> Self { - Self::default() - } - - pub fn swap_colors(&mut self) { - std::mem::swap(&mut self.document_tool_data.primary_color, &mut self.document_tool_data.secondary_color); - } -} - -fn default_tool_options() -> HashMap { - let tool_init = |tool: ToolType| (tool, tool.default_options()); - [ - tool_init(ToolType::Select), - tool_init(ToolType::Pen), - tool_init(ToolType::Line), - tool_init(ToolType::Ellipse), - tool_init(ToolType::Shape), // TODO: Add more tool defaults - ] - .into_iter() - .collect() -} - -#[repr(usize)] -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)] -pub enum ToolType { - Select, - Crop, - Navigate, - Eyedropper, - Text, - Fill, - Gradient, - Brush, - Heal, - Clone, - Patch, - BlurSharpen, - Relight, - Path, - Pen, - Freehand, - Spline, - Line, - Rectangle, - Ellipse, - Shape, -} - -impl fmt::Display for ToolType { - fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result { - use ToolType::*; - - let name = match_variant_name!(match (self) { - Select, - Crop, - Navigate, - Eyedropper, - Text, - Fill, - Gradient, - Brush, - Heal, - Clone, - Patch, - BlurSharpen, - Relight, - Path, - Pen, - Freehand, - Spline, - Line, - Rectangle, - Ellipse, - Shape - }); - - formatter.write_str(name) - } -} - -impl ToolType { - fn default_options(&self) -> ToolOptions { - match self { - ToolType::Select => ToolOptions::Select { append_mode: SelectAppendMode::New }, - ToolType::Pen => ToolOptions::Pen { weight: 5 }, - ToolType::Line => ToolOptions::Line { weight: 5 }, - ToolType::Ellipse => ToolOptions::Ellipse {}, - ToolType::Shape => ToolOptions::Shape { - shape_type: ShapeType::Polygon { vertices: 6 }, - }, - _ => todo!(), - } - } -} diff --git a/editor/src/viewport_tools/mod.rs b/editor/src/viewport_tools/mod.rs new file mode 100644 index 0000000000..c245ee587c --- /dev/null +++ b/editor/src/viewport_tools/mod.rs @@ -0,0 +1,6 @@ +pub mod snapping; +pub mod tool; +pub mod tool_message; +pub mod tool_message_handler; +pub mod tool_options; +pub mod tools; diff --git a/editor/src/tool/snapping.rs b/editor/src/viewport_tools/snapping.rs similarity index 97% rename from editor/src/tool/snapping.rs rename to editor/src/viewport_tools/snapping.rs index 6519183acd..a25eb28988 100644 --- a/editor/src/tool/snapping.rs +++ b/editor/src/viewport_tools/snapping.rs @@ -1,9 +1,9 @@ -use glam::DVec2; -use graphene::LayerId; - use crate::consts::SNAP_TOLERANCE; +use crate::document::DocumentMessageHandler; -use super::DocumentMessageHandler; +use graphene::LayerId; + +use glam::DVec2; #[derive(Debug, Clone, Default)] pub struct SnapHandler { @@ -99,6 +99,7 @@ impl SnapHandler { } } + /// Removes snap target data. Call this when snapping is done. pub fn cleanup(&mut self) { self.snap_targets = None; } diff --git a/editor/src/viewport_tools/tool.rs b/editor/src/viewport_tools/tool.rs new file mode 100644 index 0000000000..c96e756046 --- /dev/null +++ b/editor/src/viewport_tools/tool.rs @@ -0,0 +1,316 @@ +use super::tool_options::{SelectAppendMode, ShapeType, ToolOptions}; +use super::tools::*; +use crate::communication::message_handler::MessageHandler; +use crate::document::DocumentMessageHandler; +use crate::input::InputPreprocessorMessageHandler; +use crate::message_prelude::*; + +use graphene::color::Color; + +use serde::{Deserialize, Serialize}; +use std::collections::{HashMap, VecDeque}; +use std::fmt::{self, Debug}; + +pub type ToolActionHandlerData<'a> = (&'a DocumentMessageHandler, &'a DocumentToolData, &'a InputPreprocessorMessageHandler); + +pub trait Fsm { + type ToolData; + + fn transition( + self, + message: ToolMessage, + document: &DocumentMessageHandler, + tool_data: &DocumentToolData, + data: &mut Self::ToolData, + input: &InputPreprocessorMessageHandler, + messages: &mut VecDeque, + ) -> Self; + + fn update_hints(&self, responses: &mut VecDeque); +} + +#[derive(Debug, Clone)] +pub struct DocumentToolData { + pub primary_color: Color, + pub secondary_color: Color, + pub tool_options: HashMap, +} + +type SubToolMessageHandler = dyn for<'a> MessageHandler>; + +pub struct ToolData { + pub active_tool_type: ToolType, + pub tools: HashMap>, +} + +impl fmt::Debug for ToolData { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("ToolData").field("active_tool_type", &self.active_tool_type).field("tool_options", &"[…]").finish() + } +} + +impl ToolData { + pub fn active_tool_mut(&mut self) -> &mut Box { + self.tools.get_mut(&self.active_tool_type).expect("The active tool is not initialized") + } + pub fn active_tool(&self) -> &SubToolMessageHandler { + self.tools.get(&self.active_tool_type).map(|x| x.as_ref()).expect("The active tool is not initialized") + } +} + +#[derive(Debug)] +pub struct ToolFsmState { + pub document_tool_data: DocumentToolData, + pub tool_data: ToolData, +} + +impl Default for ToolFsmState { + fn default() -> Self { + ToolFsmState { + tool_data: ToolData { + active_tool_type: ToolType::Select, + tools: gen_tools_hash_map! { + Select => select::Select, + Crop => crop::Crop, + Navigate => navigate::Navigate, + Eyedropper => eyedropper::Eyedropper, + // Text => text::Text, + Fill => fill::Fill, + // Gradient => gradient::Gradient, + // Brush => brush::Brush, + // Heal => heal::Heal, + // Clone => clone::Clone, + // Patch => patch::Patch, + // BlurSharpen => blursharpen::BlurSharpen, + // Relight => relight::Relight, + Path => path::Path, + Pen => pen::Pen, + // Freehand => freehand::Freehand, + // Spline => spline::Spline, + Line => line::Line, + Rectangle => rectangle::Rectangle, + Ellipse => ellipse::Ellipse, + Shape => shape::Shape, + }, + }, + document_tool_data: DocumentToolData { + primary_color: Color::BLACK, + secondary_color: Color::WHITE, + tool_options: default_tool_options(), + }, + } + } +} + +impl ToolFsmState { + pub fn new() -> Self { + Self::default() + } + + pub fn swap_colors(&mut self) { + std::mem::swap(&mut self.document_tool_data.primary_color, &mut self.document_tool_data.secondary_color); + } +} + +fn default_tool_options() -> HashMap { + let tool_init = |tool: ToolType| (tool, tool.default_options()); + [ + tool_init(ToolType::Select), + tool_init(ToolType::Crop), + tool_init(ToolType::Navigate), + tool_init(ToolType::Eyedropper), + tool_init(ToolType::Text), + tool_init(ToolType::Fill), + tool_init(ToolType::Gradient), + tool_init(ToolType::Brush), + tool_init(ToolType::Heal), + tool_init(ToolType::Clone), + tool_init(ToolType::Patch), + tool_init(ToolType::BlurSharpen), + tool_init(ToolType::Relight), + tool_init(ToolType::Path), + tool_init(ToolType::Pen), + tool_init(ToolType::Freehand), + tool_init(ToolType::Spline), + tool_init(ToolType::Line), + tool_init(ToolType::Rectangle), + tool_init(ToolType::Ellipse), + tool_init(ToolType::Shape), + ] + .into_iter() + .collect() +} + +#[repr(usize)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)] +pub enum ToolType { + Select, + Crop, + Navigate, + Eyedropper, + Text, + Fill, + Gradient, + Brush, + Heal, + Clone, + Patch, + BlurSharpen, + Relight, + Path, + Pen, + Freehand, + Spline, + Line, + Rectangle, + Ellipse, + Shape, +} + +impl fmt::Display for ToolType { + fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + use ToolType::*; + + let name = match_variant_name!(match (self) { + Select, + Crop, + Navigate, + Eyedropper, + Text, + Fill, + Gradient, + Brush, + Heal, + Clone, + Patch, + BlurSharpen, + Relight, + Path, + Pen, + Freehand, + Spline, + Line, + Rectangle, + Ellipse, + Shape + }); + + formatter.write_str(name) + } +} + +impl ToolType { + fn default_options(&self) -> ToolOptions { + match self { + ToolType::Select => ToolOptions::Select { append_mode: SelectAppendMode::New }, + ToolType::Crop => ToolOptions::Crop {}, + ToolType::Navigate => ToolOptions::Navigate {}, + ToolType::Eyedropper => ToolOptions::Eyedropper {}, + ToolType::Text => ToolOptions::Text {}, + ToolType::Fill => ToolOptions::Fill {}, + ToolType::Gradient => ToolOptions::Gradient {}, + ToolType::Brush => ToolOptions::Brush {}, + ToolType::Heal => ToolOptions::Heal {}, + ToolType::Clone => ToolOptions::Clone {}, + ToolType::Patch => ToolOptions::Patch {}, + ToolType::BlurSharpen => ToolOptions::BlurSharpen {}, + ToolType::Relight => ToolOptions::Relight {}, + ToolType::Path => ToolOptions::Path {}, + ToolType::Pen => ToolOptions::Pen { weight: 5 }, + ToolType::Freehand => ToolOptions::Freehand {}, + ToolType::Spline => ToolOptions::Spline {}, + ToolType::Line => ToolOptions::Line { weight: 5 }, + ToolType::Rectangle => ToolOptions::Rectangle {}, + ToolType::Ellipse => ToolOptions::Ellipse {}, + ToolType::Shape => ToolOptions::Shape { + shape_type: ShapeType::Polygon { vertices: 6 }, + }, + } + } +} + +pub enum StandardToolMessageType { + Abort, + DocumentIsDirty, +} + +// TODO: Find a nicer way in Rust to make this generic so we don't have to manually map to enum variants +pub fn standard_tool_message(tool: ToolType, message_type: StandardToolMessageType) -> Option { + match message_type { + StandardToolMessageType::DocumentIsDirty => match tool { + ToolType::Select => Some(SelectMessage::DocumentIsDirty.into()), + ToolType::Crop => None, // Some(CropMessage::DocumentIsDirty.into()), + ToolType::Navigate => None, // Some(NavigateMessage::DocumentIsDirty.into()), + ToolType::Eyedropper => None, // Some(EyedropperMessage::DocumentIsDirty.into()), + ToolType::Text => None, // Some(TextMessage::DocumentIsDirty.into()), + ToolType::Fill => None, // Some(FillMessage::DocumentIsDirty.into()), + ToolType::Gradient => None, // Some(GradientMessage::DocumentIsDirty.into()), + ToolType::Brush => None, // Some(BrushMessage::DocumentIsDirty.into()), + ToolType::Heal => None, // Some(HealMessage::DocumentIsDirty.into()), + ToolType::Clone => None, // Some(CloneMessage::DocumentIsDirty.into()), + ToolType::Patch => None, // Some(PatchMessage::DocumentIsDirty.into()), + ToolType::BlurSharpen => None, // Some(BlurSharpenMessage::DocumentIsDirty.into()), + ToolType::Relight => None, // Some(RelightMessage::DocumentIsDirty.into()), + ToolType::Path => Some(PathMessage::DocumentIsDirty.into()), + ToolType::Pen => None, // Some(PenMessage::DocumentIsDirty.into()), + ToolType::Freehand => None, // Some(FreehandMessage::DocumentIsDirty.into()), + ToolType::Spline => None, // Some(SplineMessage::DocumentIsDirty.into()), + ToolType::Line => None, // Some(LineMessage::DocumentIsDirty.into()), + ToolType::Rectangle => None, // Some(RectangleMessage::DocumentIsDirty.into()), + ToolType::Ellipse => None, // Some(EllipseMessage::DocumentIsDirty.into()), + ToolType::Shape => None, // Some(ShapeMessage::DocumentIsDirty.into()), + }, + StandardToolMessageType::Abort => match tool { + ToolType::Select => Some(SelectMessage::Abort.into()), + ToolType::Path => Some(PathMessage::Abort.into()), + ToolType::Navigate => Some(NavigateMessage::Abort.into()), + ToolType::Pen => Some(PenMessage::Abort.into()), + ToolType::Line => Some(LineMessage::Abort.into()), + ToolType::Rectangle => Some(RectangleMessage::Abort.into()), + ToolType::Ellipse => Some(EllipseMessage::Abort.into()), + ToolType::Shape => Some(ShapeMessage::Abort.into()), + ToolType::Eyedropper => Some(EyedropperMessage::Abort.into()), + ToolType::Fill => Some(FillMessage::Abort.into()), + _ => None, + }, + } +} + +pub fn message_to_tool_type(message: &ToolMessage) -> ToolType { + use ToolMessage::*; + + match message { + Select(_) => ToolType::Select, + Crop(_) => ToolType::Crop, + Navigate(_) => ToolType::Navigate, + Eyedropper(_) => ToolType::Eyedropper, + // Text(_) => ToolType::Text, + Fill(_) => ToolType::Fill, + // Gradient(_) => ToolType::Gradient, + // Brush(_) => ToolType::Brush, + // Heal(_) => ToolType::Heal, + // Clone(_) => ToolType::Clone, + // Patch(_) => ToolType::Patch, + // BlurSharpen(_) => ToolType::BlurSharpen, + // Relight(_) => ToolType::Relight, + Path(_) => ToolType::Path, + Pen(_) => ToolType::Pen, + // Freehand(_) => ToolType::Freehand, + // Spline(_) => ToolType::Spline, + Line(_) => ToolType::Line, + Rectangle(_) => ToolType::Rectangle, + Ellipse(_) => ToolType::Ellipse, + Shape(_) => ToolType::Shape, + _ => panic!("Conversion from message to tool type impossible because the given ToolMessage does not belong to a tool"), + } +} + +pub fn update_working_colors(document_data: &DocumentToolData, responses: &mut VecDeque) { + responses.push_back( + FrontendMessage::UpdateWorkingColors { + primary: document_data.primary_color, + secondary: document_data.secondary_color, + } + .into(), + ); +} diff --git a/editor/src/viewport_tools/tool_message.rs b/editor/src/viewport_tools/tool_message.rs new file mode 100644 index 0000000000..6b2e94fa8e --- /dev/null +++ b/editor/src/viewport_tools/tool_message.rs @@ -0,0 +1,44 @@ +use super::tool::ToolType; +use super::tool_options::ToolOptions; +use crate::message_prelude::*; + +use graphene::color::Color; + +use serde::{Deserialize, Serialize}; + +#[remain::sorted] +#[impl_message(Message, Tool)] +#[derive(PartialEq, Clone, Debug, Serialize, Deserialize)] +pub enum ToolMessage { + ActivateTool(ToolType), + #[child] + Crop(CropMessage), + DocumentIsDirty, + #[child] + Ellipse(EllipseMessage), + #[child] + Eyedropper(EyedropperMessage), + #[child] + Fill(FillMessage), + #[child] + Line(LineMessage), + #[child] + Navigate(NavigateMessage), + NoOp, + #[child] + Path(PathMessage), + #[child] + Pen(PenMessage), + #[child] + Rectangle(RectangleMessage), + ResetColors, + #[child] + Select(SelectMessage), + SelectPrimaryColor(Color), + SelectSecondaryColor(Color), + SetToolOptions(ToolType, ToolOptions), + #[child] + Shape(ShapeMessage), + SwapColors, + UpdateHints, +} diff --git a/editor/src/tool/tool_message_handler.rs b/editor/src/viewport_tools/tool_message_handler.rs similarity index 54% rename from editor/src/tool/tool_message_handler.rs rename to editor/src/viewport_tools/tool_message_handler.rs index 9879e7902b..2ccd142cfa 100644 --- a/editor/src/tool/tool_message_handler.rs +++ b/editor/src/viewport_tools/tool_message_handler.rs @@ -1,61 +1,23 @@ +use super::tool::{message_to_tool_type, standard_tool_message, update_working_colors, StandardToolMessageType, ToolFsmState}; +use crate::document::DocumentMessageHandler; +use crate::input::InputPreprocessorMessageHandler; use crate::message_prelude::*; + use graphene::color::Color; -use crate::input::InputPreprocessor; -use crate::{ - document::DocumentMessageHandler, - tool::{tool_options::ToolOptions, DocumentToolData, ToolFsmState, ToolType}, -}; -use serde::{Deserialize, Serialize}; use std::collections::VecDeque; -#[remain::sorted] -#[impl_message(Message, Tool)] -#[derive(PartialEq, Clone, Debug, Serialize, Deserialize)] -pub enum ToolMessage { - ActivateTool(ToolType), - #[child] - Crop(CropMessage), - DocumentIsDirty, - #[child] - Ellipse(EllipseMessage), - #[child] - Eyedropper(EyedropperMessage), - #[child] - Fill(FillMessage), - #[child] - Line(LineMessage), - #[child] - Navigate(NavigateMessage), - NoOp, - #[child] - Path(PathMessage), - #[child] - Pen(PenMessage), - #[child] - Rectangle(RectangleMessage), - ResetColors, - #[child] - Select(SelectMessage), - SelectPrimaryColor(Color), - SelectSecondaryColor(Color), - SetToolOptions(ToolType, ToolOptions), - #[child] - Shape(ShapeMessage), - SwapColors, - UpdateHints, -} - #[derive(Debug, Default)] pub struct ToolMessageHandler { tool_state: ToolFsmState, } -impl MessageHandler for ToolMessageHandler { +impl MessageHandler for ToolMessageHandler { #[remain::check] - fn process_action(&mut self, message: ToolMessage, data: (&DocumentMessageHandler, &InputPreprocessor), responses: &mut VecDeque) { - let (document, input) = data; + fn process_action(&mut self, message: ToolMessage, data: (&DocumentMessageHandler, &InputPreprocessorMessageHandler), responses: &mut VecDeque) { use ToolMessage::*; + + let (document, input) = data; #[remain::sorted] match message { ActivateTool(new_tool) => { @@ -159,69 +121,3 @@ impl MessageHandler list } } - -enum StandardToolMessageType { - Abort, - DocumentIsDirty, -} - -// TODO: Find a nicer way in Rust to make this generic so we don't have to manually map to enum variants -fn standard_tool_message(tool: ToolType, message_type: StandardToolMessageType) -> Option { - match message_type { - StandardToolMessageType::DocumentIsDirty => match tool { - ToolType::Select => Some(SelectMessage::DocumentIsDirty.into()), - ToolType::Path => Some(PathMessage::DocumentIsDirty.into()), - //ToolType::Navigate => Some(NavigateMessage::DocumentIsDirty.into()) - // ToolType::Pen => Some(PenMessage::DocumentIsDirty.into()), - // ToolType::Line => Some(LineMessage::DocumentIsDirty.into()), - // ToolType::Rectangle => Some(RectangleMessage::DocumentIsDirty.into()), - // ToolType::Ellipse => Some(EllipseMessage::DocumentIsDirty.into()), - // ToolType::Shape => Some(ShapeMessage::DocumentIsDirty.into()), - // ToolType::Eyedropper => Some(EyedropperMessage::DocumentIsDirty.into()), - // ToolType::Fill => Some(FillMessage::DocumentIsDirty.into()), - _ => None, - }, - StandardToolMessageType::Abort => match tool { - ToolType::Select => Some(SelectMessage::Abort.into()), - ToolType::Path => Some(PathMessage::Abort.into()), - ToolType::Navigate => Some(NavigateMessage::Abort.into()), - ToolType::Pen => Some(PenMessage::Abort.into()), - ToolType::Line => Some(LineMessage::Abort.into()), - ToolType::Rectangle => Some(RectangleMessage::Abort.into()), - ToolType::Ellipse => Some(EllipseMessage::Abort.into()), - ToolType::Shape => Some(ShapeMessage::Abort.into()), - ToolType::Eyedropper => Some(EyedropperMessage::Abort.into()), - ToolType::Fill => Some(FillMessage::Abort.into()), - _ => None, - }, - } -} - -fn message_to_tool_type(message: &ToolMessage) -> ToolType { - use ToolMessage::*; - - match message { - Fill(_) => ToolType::Fill, - Rectangle(_) => ToolType::Rectangle, - Ellipse(_) => ToolType::Ellipse, - Shape(_) => ToolType::Shape, - Line(_) => ToolType::Line, - Pen(_) => ToolType::Pen, - Select(_) => ToolType::Select, - Crop(_) => ToolType::Crop, - Eyedropper(_) => ToolType::Eyedropper, - Navigate(_) => ToolType::Navigate, - Path(_) => ToolType::Path, - _ => unreachable!(), - } -} - -fn update_working_colors(document_data: &DocumentToolData, responses: &mut VecDeque) { - responses.push_back( - FrontendMessage::UpdateWorkingColors { - primary: document_data.primary_color, - secondary: document_data.secondary_color, - } - .into(), - ); -} diff --git a/editor/src/tool/tool_options.rs b/editor/src/viewport_tools/tool_options.rs similarity index 74% rename from editor/src/tool/tool_options.rs rename to editor/src/viewport_tools/tool_options.rs index f4a586175b..1963885046 100644 --- a/editor/src/tool/tool_options.rs +++ b/editor/src/viewport_tools/tool_options.rs @@ -3,10 +3,26 @@ use serde::{Deserialize, Serialize}; #[derive(Debug, Clone, Copy, Eq, PartialEq, Serialize, Deserialize, Hash)] pub enum ToolOptions { Select { append_mode: SelectAppendMode }, + Crop {}, + Navigate {}, + Eyedropper {}, + Text {}, + Fill {}, + Gradient {}, + Brush {}, + Heal {}, + Clone {}, + Patch {}, + BlurSharpen {}, + Relight {}, + Path {}, + Pen { weight: u32 }, + Freehand {}, + Spline {}, + Line { weight: u32 }, + Rectangle {}, Ellipse {}, Shape { shape_type: ShapeType }, - Line { weight: u32 }, - Pen { weight: u32 }, } #[derive(Debug, Clone, Copy, Eq, PartialEq, Serialize, Deserialize, Hash)] diff --git a/editor/src/tool/tools/crop.rs b/editor/src/viewport_tools/tools/crop.rs similarity index 90% rename from editor/src/tool/tools/crop.rs rename to editor/src/viewport_tools/tools/crop.rs index b92136b3bc..708de39403 100644 --- a/editor/src/tool/tools/crop.rs +++ b/editor/src/viewport_tools/tools/crop.rs @@ -1,5 +1,6 @@ use crate::message_prelude::*; -use crate::tool::ToolActionHandlerData; +use crate::viewport_tools::tool::ToolActionHandlerData; + use serde::{Deserialize, Serialize}; #[derive(Default)] @@ -16,5 +17,6 @@ impl<'a> MessageHandler> for Crop { fn process_action(&mut self, action: ToolMessage, data: ToolActionHandlerData<'a>, responses: &mut VecDeque) { todo!("{}::handle_input {:?} {:?} {:?} ", module_path!(), action, data, responses); } + advertise_actions!(); } diff --git a/editor/src/tool/tools/ellipse.rs b/editor/src/viewport_tools/tools/ellipse.rs similarity index 93% rename from editor/src/tool/tools/ellipse.rs rename to editor/src/viewport_tools/tools/ellipse.rs index a102c71519..495b052a64 100644 --- a/editor/src/tool/tools/ellipse.rs +++ b/editor/src/viewport_tools/tools/ellipse.rs @@ -1,10 +1,15 @@ +use super::shared::resize::Resize; use crate::document::DocumentMessageHandler; -use crate::input::{keyboard::Key, keyboard::MouseMotion, InputPreprocessor}; +use crate::input::keyboard::{Key, MouseMotion}; +use crate::input::InputPreprocessorMessageHandler; use crate::message_prelude::*; use crate::misc::{HintData, HintGroup, HintInfo, KeysGroup}; -use crate::tool::{tools::resize::Resize, DocumentToolData, Fsm, ToolActionHandlerData}; +use crate::viewport_tools::tool::{DocumentToolData, Fsm, ToolActionHandlerData}; + +use graphene::layers::style; +use graphene::Operation; + use glam::DAffine2; -use graphene::{layers::style, Operation}; use serde::{Deserialize, Serialize}; #[derive(Default)] @@ -73,7 +78,7 @@ impl Fsm for EllipseToolFsmState { document: &DocumentMessageHandler, tool_data: &DocumentToolData, data: &mut Self::ToolData, - input: &InputPreprocessor, + input: &InputPreprocessorMessageHandler, responses: &mut VecDeque, ) -> Self { let mut shape_data = &mut data.data; diff --git a/editor/src/tool/tools/eyedropper.rs b/editor/src/viewport_tools/tools/eyedropper.rs similarity index 90% rename from editor/src/tool/tools/eyedropper.rs rename to editor/src/viewport_tools/tools/eyedropper.rs index 0dba1d57af..c1021f58e7 100644 --- a/editor/src/tool/tools/eyedropper.rs +++ b/editor/src/viewport_tools/tools/eyedropper.rs @@ -1,12 +1,15 @@ use crate::consts::SELECTION_TOLERANCE; use crate::document::DocumentMessageHandler; -use crate::input::{keyboard::MouseMotion, InputPreprocessor}; +use crate::input::keyboard::MouseMotion; +use crate::input::InputPreprocessorMessageHandler; use crate::message_prelude::*; use crate::misc::{HintData, HintGroup, HintInfo}; -use crate::tool::{DocumentToolData, Fsm, ToolActionHandlerData, ToolMessage}; +use crate::viewport_tools::tool::{DocumentToolData, Fsm, ToolActionHandlerData}; + +use graphene::intersection::Quad; +use graphene::layers::layer_info::LayerDataType; + use glam::DVec2; -use graphene::layers::LayerDataType; -use graphene::Quad; use serde::{Deserialize, Serialize}; #[derive(Default)] @@ -65,7 +68,7 @@ impl Fsm for EyedropperToolFsmState { document: &DocumentMessageHandler, _tool_data: &DocumentToolData, _data: &mut Self::ToolData, - input: &InputPreprocessor, + input: &InputPreprocessorMessageHandler, responses: &mut VecDeque, ) -> Self { use EyedropperMessage::*; @@ -77,6 +80,7 @@ impl Fsm for EyedropperToolFsmState { let tolerance = DVec2::splat(SELECTION_TOLERANCE); let quad = Quad::from_box([mouse_pos - tolerance, mouse_pos + tolerance]); + // TODO: Destroy this pyramid if let Some(path) = document.graphene_document.intersects_quad_root(quad).last() { if let Ok(layer) = document.graphene_document.layer(path) { if let LayerDataType::Shape(shape) = &layer.data { diff --git a/editor/src/tool/tools/fill.rs b/editor/src/viewport_tools/tools/fill.rs similarity index 91% rename from editor/src/tool/tools/fill.rs rename to editor/src/viewport_tools/tools/fill.rs index 0185ebfe52..5dac494f31 100644 --- a/editor/src/tool/tools/fill.rs +++ b/editor/src/viewport_tools/tools/fill.rs @@ -1,12 +1,15 @@ use crate::consts::SELECTION_TOLERANCE; use crate::document::DocumentMessageHandler; -use crate::input::{keyboard::MouseMotion, InputPreprocessor}; +use crate::input::keyboard::MouseMotion; +use crate::input::InputPreprocessorMessageHandler; use crate::message_prelude::*; use crate::misc::{HintData, HintGroup, HintInfo}; -use crate::tool::ToolActionHandlerData; -use crate::tool::{DocumentToolData, Fsm, ToolMessage}; +use crate::viewport_tools::tool::{DocumentToolData, Fsm, ToolActionHandlerData}; + +use graphene::intersection::Quad; +use graphene::Operation; + use glam::DVec2; -use graphene::{Operation, Quad}; use serde::{Deserialize, Serialize}; #[derive(Default)] @@ -65,7 +68,7 @@ impl Fsm for FillToolFsmState { document: &DocumentMessageHandler, tool_data: &DocumentToolData, _data: &mut Self::ToolData, - input: &InputPreprocessor, + input: &InputPreprocessorMessageHandler, responses: &mut VecDeque, ) -> Self { use FillMessage::*; diff --git a/editor/src/tool/tools/line.rs b/editor/src/viewport_tools/tools/line.rs similarity index 93% rename from editor/src/tool/tools/line.rs rename to editor/src/viewport_tools/tools/line.rs index 215cfc789b..11627baa82 100644 --- a/editor/src/tool/tools/line.rs +++ b/editor/src/viewport_tools/tools/line.rs @@ -1,12 +1,18 @@ use crate::consts::LINE_ROTATE_SNAP_ANGLE; +use crate::document::DocumentMessageHandler; use crate::input::keyboard::{Key, MouseMotion}; -use crate::input::{mouse::ViewportPosition, InputPreprocessor}; +use crate::input::mouse::ViewportPosition; +use crate::input::InputPreprocessorMessageHandler; +use crate::message_prelude::*; use crate::misc::{HintData, HintGroup, HintInfo, KeysGroup}; -use crate::tool::snapping::SnapHandler; -use crate::tool::{DocumentToolData, Fsm, ToolActionHandlerData, ToolOptions, ToolType}; -use crate::{document::DocumentMessageHandler, message_prelude::*}; +use crate::viewport_tools::snapping::SnapHandler; +use crate::viewport_tools::tool::{DocumentToolData, Fsm, ToolActionHandlerData, ToolType}; +use crate::viewport_tools::tool_options::ToolOptions; + +use graphene::layers::style; +use graphene::Operation; + use glam::{DAffine2, DVec2}; -use graphene::{layers::style, Operation}; use serde::{Deserialize, Serialize}; #[derive(Default)] @@ -42,6 +48,7 @@ impl<'a> MessageHandler> for Line { fn actions(&self) -> ActionList { use LineToolFsmState::*; + match self.fsm_state { Ready => actions!(LineMessageDiscriminant; DragStart), Drawing => actions!(LineMessageDiscriminant; DragStop, Redraw, Abort), @@ -60,6 +67,7 @@ impl Default for LineToolFsmState { LineToolFsmState::Ready } } + #[derive(Clone, Debug, Default)] struct LineToolData { drag_start: ViewportPosition, @@ -79,11 +87,12 @@ impl Fsm for LineToolFsmState { document: &DocumentMessageHandler, tool_data: &DocumentToolData, data: &mut Self::ToolData, - input: &InputPreprocessor, + input: &InputPreprocessorMessageHandler, responses: &mut VecDeque, ) -> Self { use LineMessage::*; use LineToolFsmState::*; + if let ToolMessage::Line(event) = event { match (self, event) { (Ready, DragStart) => { diff --git a/editor/src/tool/tools/mod.rs b/editor/src/viewport_tools/tools/mod.rs similarity index 73% rename from editor/src/tool/tools/mod.rs rename to editor/src/viewport_tools/tools/mod.rs index 36119873be..ea454d46fd 100644 --- a/editor/src/tool/tools/mod.rs +++ b/editor/src/viewport_tools/tools/mod.rs @@ -1,15 +1,12 @@ -// already implemented +pub mod crop; pub mod ellipse; +pub mod eyedropper; pub mod fill; pub mod line; -pub mod pen; -pub mod rectangle; -pub mod resize; -pub mod shape; - -// not implemented yet -pub mod crop; -pub mod eyedropper; pub mod navigate; pub mod path; +pub mod pen; +pub mod rectangle; pub mod select; +pub mod shape; +pub mod shared; diff --git a/editor/src/tool/tools/navigate.rs b/editor/src/viewport_tools/tools/navigate.rs similarity index 93% rename from editor/src/tool/tools/navigate.rs rename to editor/src/viewport_tools/tools/navigate.rs index 6f1ac5d049..7d954272ac 100644 --- a/editor/src/tool/tools/navigate.rs +++ b/editor/src/viewport_tools/tools/navigate.rs @@ -1,7 +1,10 @@ -use crate::input::keyboard::MouseMotion; +use crate::document::DocumentMessageHandler; +use crate::input::keyboard::{Key, MouseMotion}; +use crate::input::InputPreprocessorMessageHandler; +use crate::message_prelude::*; use crate::misc::{HintData, HintGroup, HintInfo, KeysGroup}; -use crate::tool::{Fsm, ToolActionHandlerData}; -use crate::{input::keyboard::Key, message_prelude::*}; +use crate::viewport_tools::tool::{DocumentToolData, Fsm, ToolActionHandlerData}; + use glam::DVec2; use serde::{Deserialize, Serialize}; @@ -73,14 +76,15 @@ impl Fsm for NavigateToolFsmState { fn transition( self, message: ToolMessage, - _document: &crate::document::DocumentMessageHandler, - _tool_data: &crate::tool::DocumentToolData, + _document: &DocumentMessageHandler, + _tool_data: &DocumentToolData, data: &mut Self::ToolData, - input: &crate::input::InputPreprocessor, + input: &InputPreprocessorMessageHandler, messages: &mut VecDeque, ) -> Self { if let ToolMessage::Navigate(navigate) = message { use NavigateMessage::*; + match navigate { ClickZoom { zoom_in } => { messages.push_front(MovementMessage::TransformCanvasEnd.into()); diff --git a/editor/src/tool/tools/path.rs b/editor/src/viewport_tools/tools/path.rs similarity index 87% rename from editor/src/tool/tools/path.rs rename to editor/src/viewport_tools/tools/path.rs index b6ee0e9e0b..bec3a441dd 100644 --- a/editor/src/tool/tools/path.rs +++ b/editor/src/viewport_tools/tools/path.rs @@ -1,24 +1,18 @@ -use crate::consts::COLOR_ACCENT; -use crate::consts::VECTOR_MANIPULATOR_ANCHOR_MARKER_SIZE; +use crate::consts::{COLOR_ACCENT, VECTOR_MANIPULATOR_ANCHOR_MARKER_SIZE}; +use crate::document::utility_types::{VectorManipulatorSegment, VectorManipulatorShape}; use crate::document::DocumentMessageHandler; -use crate::document::VectorManipulatorSegment; -use crate::document::VectorManipulatorShape; use crate::input::keyboard::{Key, MouseMotion}; -use crate::input::InputPreprocessor; +use crate::input::InputPreprocessorMessageHandler; use crate::message_prelude::*; use crate::misc::{HintData, HintGroup, HintInfo, KeysGroup}; -use crate::tool::ToolActionHandlerData; -use crate::tool::{DocumentToolData, Fsm}; -use glam::{DAffine2, DVec2}; +use crate::viewport_tools::tool::{DocumentToolData, Fsm, ToolActionHandlerData}; + use graphene::color::Color; -use graphene::layers::style; -use graphene::layers::style::Fill; -use graphene::layers::style::PathStyle; -use graphene::layers::style::Stroke; +use graphene::layers::style::{self, Fill, PathStyle, Stroke}; use graphene::Operation; -use kurbo::BezPath; -use kurbo::PathEl; -use kurbo::Vec2; + +use glam::{DAffine2, DVec2}; +use kurbo::{BezPath, PathEl, Vec2}; use serde::{Deserialize, Serialize}; #[derive(Default)] @@ -88,6 +82,7 @@ struct PathToolData { } impl PathToolData {} + #[derive(Clone, Debug, Default)] struct PathToolSelection { closest_layer_path: Vec, @@ -106,12 +101,13 @@ impl Fsm for PathToolFsmState { document: &DocumentMessageHandler, _tool_data: &DocumentToolData, data: &mut Self::ToolData, - input: &InputPreprocessor, + input: &InputPreprocessorMessageHandler, responses: &mut VecDeque, ) -> Self { if let ToolMessage::Path(event) = event { use PathMessage::*; use PathToolFsmState::*; + match (self, event) { (_, DocumentIsDirty) => { let (mut anchor_i, mut handle_i, mut line_i, mut shape_i) = (0, 0, 0, 0); @@ -133,7 +129,7 @@ impl Fsm for PathToolFsmState { let shape_layer_path = &data.shape_outline_pool[shape_i]; responses.push_back( - DocumentMessage::Overlay( + DocumentMessage::Overlays( Operation::SetShapePathInViewport { path: shape_layer_path.clone(), bez_path: shape_to_draw.path.clone(), @@ -144,7 +140,7 @@ impl Fsm for PathToolFsmState { .into(), ); responses.push_back( - DocumentMessage::Overlay( + DocumentMessage::Overlays( Operation::SetLayerVisibility { path: shape_layer_path.clone(), visible: true, @@ -168,8 +164,8 @@ impl Fsm for PathToolFsmState { let translation = (anchor_handle_line.1 + BIAS).round() + DVec2::splat(0.5); let transform = DAffine2::from_scale_angle_translation(scale, angle, translation).to_cols_array(); - responses.push_back(DocumentMessage::Overlay(Operation::SetLayerTransformInViewport { path: marker.clone(), transform }.into()).into()); - responses.push_back(DocumentMessage::Overlay(Operation::SetLayerVisibility { path: marker.clone(), visible: true }.into()).into()); + responses.push_back(DocumentMessage::Overlays(Operation::SetLayerTransformInViewport { path: marker.clone(), transform }.into()).into()); + responses.push_back(DocumentMessage::Overlays(Operation::SetLayerVisibility { path: marker.clone(), visible: true }.into()).into()); line_i += 1; } @@ -182,8 +178,8 @@ impl Fsm for PathToolFsmState { let transform = DAffine2::from_scale_angle_translation(scale, angle, translation).to_cols_array(); let marker = &data.anchor_marker_pool[anchor_i]; - responses.push_back(DocumentMessage::Overlay(Operation::SetLayerTransformInViewport { path: marker.clone(), transform }.into()).into()); - responses.push_back(DocumentMessage::Overlay(Operation::SetLayerVisibility { path: marker.clone(), visible: true }.into()).into()); + responses.push_back(DocumentMessage::Overlays(Operation::SetLayerTransformInViewport { path: marker.clone(), transform }.into()).into()); + responses.push_back(DocumentMessage::Overlays(Operation::SetLayerVisibility { path: marker.clone(), visible: true }.into()).into()); anchor_i += 1; } @@ -197,8 +193,8 @@ impl Fsm for PathToolFsmState { let translation = (handle - (scale / 2.) + BIAS).round(); let transform = DAffine2::from_scale_angle_translation(scale, angle, translation).to_cols_array(); - responses.push_back(DocumentMessage::Overlay(Operation::SetLayerTransformInViewport { path: marker.clone(), transform }.into()).into()); - responses.push_back(DocumentMessage::Overlay(Operation::SetLayerVisibility { path: marker.clone(), visible: true }.into()).into()); + responses.push_back(DocumentMessage::Overlays(Operation::SetLayerTransformInViewport { path: marker.clone(), transform }.into()).into()); + responses.push_back(DocumentMessage::Overlays(Operation::SetLayerVisibility { path: marker.clone(), visible: true }.into()).into()); handle_i += 1; } @@ -207,19 +203,19 @@ impl Fsm for PathToolFsmState { // Hide the remaining pooled overlays for i in anchor_i..data.anchor_marker_pool.len() { let marker = data.anchor_marker_pool[i].clone(); - responses.push_back(DocumentMessage::Overlay(Operation::SetLayerVisibility { path: marker, visible: false }.into()).into()); + responses.push_back(DocumentMessage::Overlays(Operation::SetLayerVisibility { path: marker, visible: false }.into()).into()); } for i in handle_i..data.handle_marker_pool.len() { let marker = data.handle_marker_pool[i].clone(); - responses.push_back(DocumentMessage::Overlay(Operation::SetLayerVisibility { path: marker, visible: false }.into()).into()); + responses.push_back(DocumentMessage::Overlays(Operation::SetLayerVisibility { path: marker, visible: false }.into()).into()); } for i in line_i..data.anchor_handle_line_pool.len() { let line = data.anchor_handle_line_pool[i].clone(); - responses.push_back(DocumentMessage::Overlay(Operation::SetLayerVisibility { path: line, visible: false }.into()).into()); + responses.push_back(DocumentMessage::Overlays(Operation::SetLayerVisibility { path: line, visible: false }.into()).into()); } for i in shape_i..data.shape_outline_pool.len() { let shape_i = data.shape_outline_pool[i].clone(); - responses.push_back(DocumentMessage::Overlay(Operation::SetLayerVisibility { path: shape_i, visible: false }.into()).into()); + responses.push_back(DocumentMessage::Overlays(Operation::SetLayerVisibility { path: shape_i, visible: false }.into()).into()); } self @@ -335,7 +331,7 @@ impl Fsm for PathToolFsmState { data.selection.overlay_path = path; responses.push_back( - DocumentMessage::Overlay( + DocumentMessage::Overlays( Operation::SetLayerFill { path: data.selection.overlay_path.clone(), color: COLOR_ACCENT, @@ -377,7 +373,7 @@ impl Fsm for PathToolFsmState { (_, DragStop) => { let style = PathStyle::new(Some(Stroke::new(COLOR_ACCENT, 2.0)), Some(Fill::new(Color::WHITE))); responses.push_back( - DocumentMessage::Overlay( + DocumentMessage::Overlays( Operation::SetLayerStyle { path: data.selection.overlay_path.clone(), style, @@ -391,16 +387,16 @@ impl Fsm for PathToolFsmState { (_, Abort) => { // Destory the overlay layer pools while let Some(layer) = data.anchor_marker_pool.pop() { - responses.push_back(DocumentMessage::Overlay(Operation::DeleteLayer { path: layer }.into()).into()); + responses.push_back(DocumentMessage::Overlays(Operation::DeleteLayer { path: layer }.into()).into()); } while let Some(layer) = data.handle_marker_pool.pop() { - responses.push_back(DocumentMessage::Overlay(Operation::DeleteLayer { path: layer }.into()).into()); + responses.push_back(DocumentMessage::Overlays(Operation::DeleteLayer { path: layer }.into()).into()); } while let Some(layer) = data.anchor_handle_line_pool.pop() { - responses.push_back(DocumentMessage::Overlay(Operation::DeleteLayer { path: layer }.into()).into()); + responses.push_back(DocumentMessage::Overlays(Operation::DeleteLayer { path: layer }.into()).into()); } while let Some(layer) = data.shape_outline_pool.pop() { - responses.push_back(DocumentMessage::Overlay(Operation::DeleteLayer { path: layer }.into()).into()); + responses.push_back(DocumentMessage::Overlays(Operation::DeleteLayer { path: layer }.into()).into()); } Ready @@ -573,7 +569,7 @@ fn add_anchor_marker(responses: &mut VecDeque) -> Vec { transform: DAffine2::IDENTITY.to_cols_array(), style: style::PathStyle::new(Some(Stroke::new(COLOR_ACCENT, 2.0)), Some(Fill::new(Color::WHITE))), }; - responses.push_back(DocumentMessage::Overlay(operation.into()).into()); + responses.push_back(DocumentMessage::Overlays(operation.into()).into()); layer_path } @@ -586,7 +582,7 @@ fn add_handle_marker(responses: &mut VecDeque) -> Vec { transform: DAffine2::IDENTITY.to_cols_array(), style: style::PathStyle::new(Some(Stroke::new(COLOR_ACCENT, 2.0)), Some(Fill::new(Color::WHITE))), }; - responses.push_back(DocumentMessage::Overlay(operation.into()).into()); + responses.push_back(DocumentMessage::Overlays(operation.into()).into()); layer_path } @@ -598,7 +594,7 @@ fn add_anchor_handle_line(responses: &mut VecDeque) -> Vec { transform: DAffine2::IDENTITY.to_cols_array(), style: style::PathStyle::new(Some(Stroke::new(COLOR_ACCENT, 1.0)), Some(Fill::none())), }; - responses.push_back(DocumentMessage::Overlay(operation.into()).into()); + responses.push_back(DocumentMessage::Overlays(operation.into()).into()); layer_path } @@ -612,7 +608,7 @@ fn add_shape_outline(responses: &mut VecDeque) -> Vec { style: style::PathStyle::new(Some(Stroke::new(COLOR_ACCENT, 1.0)), Some(Fill::none())), closed: false, }; - responses.push_back(DocumentMessage::Overlay(operation.into()).into()); + responses.push_back(DocumentMessage::Overlays(operation.into()).into()); layer_path } diff --git a/editor/src/tool/tools/pen.rs b/editor/src/viewport_tools/tools/pen.rs similarity index 93% rename from editor/src/tool/tools/pen.rs rename to editor/src/viewport_tools/tools/pen.rs index 3944a9fbfd..f1badb9aa5 100644 --- a/editor/src/tool/tools/pen.rs +++ b/editor/src/viewport_tools/tools/pen.rs @@ -1,11 +1,16 @@ +use crate::document::DocumentMessageHandler; use crate::input::keyboard::{Key, MouseMotion}; -use crate::input::InputPreprocessor; +use crate::input::InputPreprocessorMessageHandler; +use crate::message_prelude::*; use crate::misc::{HintData, HintGroup, HintInfo, KeysGroup}; -use crate::tool::snapping::SnapHandler; -use crate::tool::{DocumentToolData, Fsm, ToolActionHandlerData, ToolOptions, ToolType}; -use crate::{document::DocumentMessageHandler, message_prelude::*}; +use crate::viewport_tools::snapping::SnapHandler; +use crate::viewport_tools::tool::{DocumentToolData, Fsm, ToolActionHandlerData, ToolType}; +use crate::viewport_tools::tool_options::ToolOptions; + +use graphene::layers::style; +use graphene::Operation; + use glam::DAffine2; -use graphene::{layers::style, Operation}; use serde::{Deserialize, Serialize}; #[derive(Default)] @@ -80,7 +85,7 @@ impl Fsm for PenToolFsmState { document: &DocumentMessageHandler, tool_data: &DocumentToolData, data: &mut Self::ToolData, - input: &InputPreprocessor, + input: &InputPreprocessorMessageHandler, responses: &mut VecDeque, ) -> Self { let transform = document.graphene_document.root.transform; diff --git a/editor/src/tool/tools/rectangle.rs b/editor/src/viewport_tools/tools/rectangle.rs similarity index 92% rename from editor/src/tool/tools/rectangle.rs rename to editor/src/viewport_tools/tools/rectangle.rs index 349ae67682..f2febc886e 100644 --- a/editor/src/tool/tools/rectangle.rs +++ b/editor/src/viewport_tools/tools/rectangle.rs @@ -1,14 +1,17 @@ +use super::shared::resize::Resize; +use crate::document::DocumentMessageHandler; use crate::input::keyboard::{Key, MouseMotion}; -use crate::input::InputPreprocessor; +use crate::input::InputPreprocessorMessageHandler; +use crate::message_prelude::*; use crate::misc::{HintData, HintGroup, HintInfo, KeysGroup}; -use crate::tool::{DocumentToolData, Fsm, ToolActionHandlerData}; -use crate::{document::DocumentMessageHandler, message_prelude::*}; +use crate::viewport_tools::tool::{DocumentToolData, Fsm, ToolActionHandlerData}; + +use graphene::layers::style; +use graphene::Operation; + use glam::DAffine2; -use graphene::{layers::style, Operation}; use serde::{Deserialize, Serialize}; -use super::resize::*; - #[derive(Default)] pub struct Rectangle { fsm_state: RectangleToolFsmState, @@ -74,7 +77,7 @@ impl Fsm for RectangleToolFsmState { document: &DocumentMessageHandler, tool_data: &DocumentToolData, data: &mut Self::ToolData, - input: &InputPreprocessor, + input: &InputPreprocessorMessageHandler, responses: &mut VecDeque, ) -> Self { let mut shape_data = &mut data.data; diff --git a/editor/src/tool/tools/select.rs b/editor/src/viewport_tools/tools/select.rs similarity index 90% rename from editor/src/tool/tools/select.rs rename to editor/src/viewport_tools/tools/select.rs index dcaafa7462..22fb296164 100644 --- a/editor/src/tool/tools/select.rs +++ b/editor/src/viewport_tools/tools/select.rs @@ -1,22 +1,18 @@ -use graphene::layers::style; -use graphene::layers::style::Fill; -use graphene::layers::style::Stroke; -use graphene::Operation; -use graphene::Quad; - -use crate::consts::COLOR_ACCENT; -use crate::input::{ - keyboard::{Key, MouseMotion}, - mouse::ViewportPosition, - InputPreprocessor, -}; +use crate::consts::{COLOR_ACCENT, SELECTION_DRAG_ANGLE, SELECTION_TOLERANCE}; +use crate::document::utility_types::{AlignAggregate, AlignAxis, FlipAxis}; +use crate::document::DocumentMessageHandler; +use crate::input::keyboard::{Key, MouseMotion}; +use crate::input::mouse::ViewportPosition; +use crate::input::InputPreprocessorMessageHandler; +use crate::message_prelude::*; use crate::misc::{HintData, HintGroup, HintInfo, KeysGroup}; -use crate::tool::{snapping::SnapHandler, DocumentToolData, Fsm, ToolActionHandlerData}; -use crate::{ - consts::{SELECTION_DRAG_ANGLE, SELECTION_TOLERANCE}, - document::{AlignAggregate, AlignAxis, DocumentMessageHandler, FlipAxis}, - message_prelude::*, -}; +use crate::viewport_tools::snapping::SnapHandler; +use crate::viewport_tools::tool::{DocumentToolData, Fsm, ToolActionHandlerData}; + +use graphene::intersection::Quad; +use graphene::layers::style::{self, Fill, Stroke}; +use graphene::Operation; + use glam::{DAffine2, DVec2}; use serde::{Deserialize, Serialize}; @@ -115,7 +111,7 @@ fn add_bounding_box(responses: &mut Vec) -> Vec { transform: DAffine2::ZERO.to_cols_array(), style: style::PathStyle::new(Some(Stroke::new(COLOR_ACCENT, 1.0)), Some(Fill::none())), }; - responses.push(DocumentMessage::Overlay(operation.into()).into()); + responses.push(DocumentMessage::Overlays(operation.into()).into()); path } @@ -133,7 +129,7 @@ impl Fsm for SelectToolFsmState { document: &DocumentMessageHandler, _tool_data: &DocumentToolData, data: &mut Self::ToolData, - input: &InputPreprocessor, + input: &InputPreprocessorMessageHandler, responses: &mut VecDeque, ) -> Self { use SelectMessage::*; @@ -144,7 +140,7 @@ impl Fsm for SelectToolFsmState { (_, DocumentIsDirty) => { let mut buffer = Vec::new(); let response = match (document.selected_visible_layers_bounding_box(), data.bounding_box_overlay_layer.take()) { - (None, Some(path)) => DocumentMessage::Overlay(Operation::DeleteLayer { path }.into()).into(), + (None, Some(path)) => DocumentMessage::Overlays(Operation::DeleteLayer { path }.into()).into(), (Some([pos1, pos2]), path) => { let path = path.unwrap_or_else(|| add_bounding_box(&mut buffer)); @@ -154,7 +150,7 @@ impl Fsm for SelectToolFsmState { let pos1 = pos1 + half_pixel_offset; let pos2 = pos2 - half_pixel_offset; let transform = transform_from_box(pos1, pos2); - DocumentMessage::Overlay(Operation::SetLayerTransformInViewport { path, transform }.into()).into() + DocumentMessage::Overlays(Operation::SetLayerTransformInViewport { path, transform }.into()).into() } (_, _) => Message::NoOp, }; @@ -195,7 +191,7 @@ impl Fsm for SelectToolFsmState { }; buffer.into_iter().rev().for_each(|message| responses.push_front(message)); - // TODO: Probably delete this now that the overlay system has moved to a separate Graphene document? (@0hypercube) + // TODO: Probably delete this now that the overlays system has moved to a separate Graphene document? (@0hypercube) let ignore_layers = if let Some(bounding_box) = &data.bounding_box_overlay_layer { vec![bounding_box.clone()] } else { @@ -240,7 +236,7 @@ impl Fsm for SelectToolFsmState { let size = data.drag_current - start + half_pixel_offset; responses.push_front( - DocumentMessage::Overlay( + DocumentMessage::Overlays( Operation::SetLayerTransformInViewport { path: data.drag_box_overlay_layer.clone().unwrap(), transform: DAffine2::from_scale_angle_translation(size, 0., start).to_cols_array(), @@ -264,7 +260,7 @@ impl Fsm for SelectToolFsmState { let quad = data.selection_quad(); responses.push_front(DocumentMessage::AddSelectedLayers(document.graphene_document.intersects_quad_root(quad)).into()); responses.push_front( - DocumentMessage::Overlay( + DocumentMessage::Overlays( Operation::DeleteLayer { path: data.drag_box_overlay_layer.take().unwrap(), } @@ -275,7 +271,7 @@ impl Fsm for SelectToolFsmState { Ready } (_, Abort) => { - let mut delete = |path: &mut Option>| path.take().map(|path| responses.push_front(DocumentMessage::Overlay(Operation::DeleteLayer { path }.into()).into())); + let mut delete = |path: &mut Option>| path.take().map(|path| responses.push_front(DocumentMessage::Overlays(Operation::DeleteLayer { path }.into()).into())); delete(&mut data.drag_box_overlay_layer); delete(&mut data.bounding_box_overlay_layer); Ready diff --git a/editor/src/tool/tools/shape.rs b/editor/src/viewport_tools/tools/shape.rs similarity index 91% rename from editor/src/tool/tools/shape.rs rename to editor/src/viewport_tools/tools/shape.rs index 6de2eb9728..7bcc808957 100644 --- a/editor/src/tool/tools/shape.rs +++ b/editor/src/viewport_tools/tools/shape.rs @@ -1,14 +1,18 @@ +use super::shared::resize::Resize; +use crate::document::DocumentMessageHandler; use crate::input::keyboard::{Key, MouseMotion}; -use crate::input::InputPreprocessor; +use crate::input::InputPreprocessorMessageHandler; +use crate::message_prelude::*; use crate::misc::{HintData, HintGroup, HintInfo, KeysGroup}; -use crate::tool::{DocumentToolData, Fsm, ShapeType, ToolActionHandlerData, ToolOptions, ToolType}; -use crate::{document::DocumentMessageHandler, message_prelude::*}; +use crate::viewport_tools::tool::{DocumentToolData, Fsm, ToolActionHandlerData, ToolType}; +use crate::viewport_tools::tool_options::{ShapeType, ToolOptions}; + +use graphene::layers::style; +use graphene::Operation; + use glam::DAffine2; -use graphene::{layers::style, Operation}; use serde::{Deserialize, Serialize}; -use super::resize::*; - #[derive(Default)] pub struct Shape { fsm_state: ShapeToolFsmState, @@ -75,7 +79,7 @@ impl Fsm for ShapeToolFsmState { document: &DocumentMessageHandler, tool_data: &DocumentToolData, data: &mut Self::ToolData, - input: &InputPreprocessor, + input: &InputPreprocessorMessageHandler, responses: &mut VecDeque, ) -> Self { let mut shape_data = &mut data.data; diff --git a/editor/src/viewport_tools/tools/shared/mod.rs b/editor/src/viewport_tools/tools/shared/mod.rs new file mode 100644 index 0000000000..bade02141a --- /dev/null +++ b/editor/src/viewport_tools/tools/shared/mod.rs @@ -0,0 +1 @@ +pub mod resize; diff --git a/editor/src/tool/tools/resize.rs b/editor/src/viewport_tools/tools/shared/resize.rs similarity index 82% rename from editor/src/tool/tools/resize.rs rename to editor/src/viewport_tools/tools/shared/resize.rs index 64f8c7a710..abe6057986 100644 --- a/editor/src/tool/tools/resize.rs +++ b/editor/src/viewport_tools/tools/shared/resize.rs @@ -1,11 +1,14 @@ +use crate::document::DocumentMessageHandler; use crate::input::keyboard::Key; -use crate::input::{mouse::ViewportPosition, InputPreprocessor}; +use crate::input::mouse::ViewportPosition; +use crate::input::InputPreprocessorMessageHandler; use crate::message_prelude::*; -use crate::tool::snapping::SnapHandler; -use crate::tool::DocumentMessageHandler; -use glam::{DAffine2, DVec2, Vec2Swizzles}; +use crate::viewport_tools::snapping::SnapHandler; + use graphene::Operation; +use glam::{DAffine2, DVec2, Vec2Swizzles}; + #[derive(Clone, Debug, Default)] pub struct Resize { pub drag_start: ViewportPosition, @@ -20,7 +23,7 @@ impl Resize { self.drag_start = self.snap_handler.snap_position(document, mouse_position); } - pub fn calculate_transform(&self, document: &DocumentMessageHandler, center: Key, lock_ratio: Key, ipp: &InputPreprocessor) -> Option { + pub fn calculate_transform(&self, document: &DocumentMessageHandler, center: Key, lock_ratio: Key, ipp: &InputPreprocessorMessageHandler) -> Option { if let Some(path) = &self.path { let mut start = self.drag_start; diff --git a/frontend/wasm/src/api.rs b/frontend/wasm/src/api.rs index 069d5726fe..d35c44b795 100644 --- a/frontend/wasm/src/api.rs +++ b/frontend/wasm/src/api.rs @@ -1,28 +1,30 @@ // This file is where functions are defined to be called directly from JS. // It serves as a thin wrapper over the editor backend API that relies // on the dispatcher messaging system and more complex Rust data types. -use std::sync::atomic::Ordering; use crate::helpers::Error; use crate::type_translators::{translate_blend_mode, translate_key, translate_tool_type, translate_view_mode}; use crate::{EDITOR_HAS_CRASHED, EDITOR_INSTANCES}; + use editor::consts::{FILE_SAVE_SUFFIX, GRAPHITE_DOCUMENT_VERSION}; use editor::input::input_preprocessor::ModifierKeys; use editor::input::mouse::{EditorMouseState, ScrollDelta, ViewportBounds}; use editor::message_prelude::*; use editor::misc::EditorError; -use editor::tool::{tool_options::ToolOptions, tools, ToolType}; +use editor::viewport_tools::tool::ToolType; +use editor::viewport_tools::tool_options::ToolOptions; +use editor::viewport_tools::tools; use editor::Color; +use editor::Editor; use editor::LayerId; -use editor::Editor; use serde::Serialize; use serde_wasm_bindgen; +use std::sync::atomic::Ordering; use wasm_bindgen::prelude::*; -// To avoid wasm-bindgen from checking mutable reference issues using WasmRefCell -// we must make all methods take a non mutable reference to self. Not doing this creates -// an issue when rust calls into JS which calls back to rust in the same call stack. +// To avoid wasm-bindgen from checking mutable reference issues using WasmRefCell we must make all methods take a non mutable reference to self. +// Not doing this creates an issue when rust calls into JS which calls back to rust in the same call stack. #[wasm_bindgen] #[derive(Clone)] pub struct JsEditorHandle { diff --git a/frontend/wasm/src/helpers.rs b/frontend/wasm/src/helpers.rs index bd56d36d63..4c8a428438 100644 --- a/frontend/wasm/src/helpers.rs +++ b/frontend/wasm/src/helpers.rs @@ -1,6 +1,6 @@ use wasm_bindgen::prelude::*; -// The JavaScript `Error` type +/// The JavaScript `Error` type #[wasm_bindgen] extern "C" { #[derive(Clone, Debug)] diff --git a/frontend/wasm/src/lib.rs b/frontend/wasm/src/lib.rs index 9bb85edd7b..b1fa9c0236 100644 --- a/frontend/wasm/src/lib.rs +++ b/frontend/wasm/src/lib.rs @@ -1,9 +1,10 @@ pub mod api; -mod helpers; +pub mod helpers; pub mod logging; pub mod type_translators; -use editor::message_prelude::FrontendMessage; +use editor::message_prelude::*; + use logging::WasmLog; use std::cell::RefCell; use std::collections::HashMap; @@ -12,13 +13,12 @@ use std::sync::atomic::AtomicBool; use wasm_bindgen::prelude::*; // Set up the persistent editor backend state -static LOGGER: WasmLog = WasmLog; +pub static EDITOR_HAS_CRASHED: AtomicBool = AtomicBool::new(false); +pub static LOGGER: WasmLog = WasmLog; thread_local! { pub static EDITOR_INSTANCES: RefCell> = RefCell::new(HashMap::new()); } -pub static EDITOR_HAS_CRASHED: AtomicBool = AtomicBool::new(false); - // Initialize the backend #[wasm_bindgen(start)] pub fn init() { diff --git a/frontend/wasm/src/type_translators.rs b/frontend/wasm/src/type_translators.rs index f7aad285f8..76d44241ad 100644 --- a/frontend/wasm/src/type_translators.rs +++ b/frontend/wasm/src/type_translators.rs @@ -1,7 +1,9 @@ use crate::helpers::match_string_to_enum; + use editor::input::keyboard::Key; -use editor::tool::ToolType; -use graphene::layers::{style::ViewMode, BlendMode}; +use editor::viewport_tools::tool::ToolType; +use graphene::layers::blend_mode::BlendMode; +use graphene::layers::style::ViewMode; pub fn translate_tool_type(name: &str) -> Option { use ToolType::*; diff --git a/graphene/src/color.rs b/graphene/src/color.rs index 12b800835f..287bca6f95 100644 --- a/graphene/src/color.rs +++ b/graphene/src/color.rs @@ -1,6 +1,6 @@ use serde::{Deserialize, Serialize}; -/// Structure that represent a color. +/// Structure that represents a color. /// Internally alpha is stored as `f32` that ranges from `0.0` (transparent) to `1.0` (opaque). /// The other components (RGB) are stored as `f32` that range from `0.0` up to `f32::MAX`, /// the values encode the brightness of each channel proportional to the light intensity in cd/m² (nits) in HDR, and `0.0` (black) to `1.0` (white) in SDR color. diff --git a/graphene/src/document.rs b/graphene/src/document.rs index d527ceb9e8..9f3581a6c9 100644 --- a/graphene/src/document.rs +++ b/graphene/src/document.rs @@ -1,16 +1,18 @@ -use std::{ - cmp::max, - collections::hash_map::DefaultHasher, - hash::{Hash, Hasher}, -}; +use crate::intersection::Quad; +use crate::layers; +use crate::layers::folder::Folder; +use crate::layers::layer_info::{Layer, LayerData, LayerDataType}; +use crate::layers::simple_shape::Shape; +use crate::layers::style::ViewMode; +use crate::{DocumentError, DocumentResponse, Operation}; use glam::{DAffine2, DVec2}; use serde::{Deserialize, Serialize}; +use std::cmp::max; +use std::collections::hash_map::DefaultHasher; +use std::hash::{Hash, Hasher}; -use crate::{ - layers::{self, style::ViewMode, Folder, Layer, LayerData, LayerDataType, Shape}, - DocumentError, DocumentResponse, LayerId, Operation, Quad, -}; +pub type LayerId = u64; #[derive(Debug, Clone, Deserialize, Serialize)] pub struct Document { diff --git a/graphene/src/error.rs b/graphene/src/error.rs new file mode 100644 index 0000000000..917c3e17de --- /dev/null +++ b/graphene/src/error.rs @@ -0,0 +1,12 @@ +use super::LayerId; + +#[derive(Debug, Clone, PartialEq)] +pub enum DocumentError { + LayerNotFound(Vec), + InvalidPath, + IndexOutOfBounds, + NotAFolder, + NonReorderableSelection, + NotAShape, + InvalidFile(String), +} diff --git a/graphene/src/intersection.rs b/graphene/src/intersection.rs index c4a940ff74..79d2231be9 100644 --- a/graphene/src/intersection.rs +++ b/graphene/src/intersection.rs @@ -1,7 +1,6 @@ -use std::ops::Mul; - use glam::{DAffine2, DVec2}; use kurbo::{BezPath, Line, PathSeg, Point, Shape}; +use std::ops::Mul; #[derive(Debug, Clone, Default, Copy)] pub struct Quad([DVec2; 4]); diff --git a/graphene/src/layers/folder.rs b/graphene/src/layers/folder.rs index 2f92efa758..ed96d483a9 100644 --- a/graphene/src/layers/folder.rs +++ b/graphene/src/layers/folder.rs @@ -1,9 +1,9 @@ -use glam::DVec2; - -use crate::{layers::style::ViewMode, DocumentError, LayerId, Quad}; - -use super::{Layer, LayerData, LayerDataType}; +use super::layer_info::{Layer, LayerData, LayerDataType}; +use super::style::ViewMode; +use crate::intersection::Quad; +use crate::{DocumentError, LayerId}; +use glam::DVec2; use serde::{Deserialize, Serialize}; use std::fmt::Write; diff --git a/graphene/src/layers/layer_info.rs b/graphene/src/layers/layer_info.rs new file mode 100644 index 0000000000..dbf4a75151 --- /dev/null +++ b/graphene/src/layers/layer_info.rs @@ -0,0 +1,208 @@ +use super::blend_mode::BlendMode; +use super::folder::Folder; +use super::simple_shape::Shape; +use super::style::ViewMode; +use crate::intersection::Quad; +use crate::DocumentError; +use crate::LayerId; + +use glam::{DAffine2, DMat2, DVec2}; +use serde::{Deserialize, Serialize}; +use std::fmt::Write; + +#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)] +pub enum LayerDataType { + Folder(Folder), + Shape(Shape), +} + +impl LayerDataType { + pub fn inner(&self) -> &dyn LayerData { + match self { + LayerDataType::Shape(s) => s, + LayerDataType::Folder(f) => f, + } + } + + pub fn inner_mut(&mut self) -> &mut dyn LayerData { + match self { + LayerDataType::Shape(s) => s, + LayerDataType::Folder(f) => f, + } + } +} + +pub trait LayerData { + fn render(&mut self, svg: &mut String, transforms: &mut Vec, view_mode: ViewMode); + fn intersects_quad(&self, quad: Quad, path: &mut Vec, intersections: &mut Vec>); + fn bounding_box(&self, transform: glam::DAffine2) -> Option<[DVec2; 2]>; +} + +impl LayerData for LayerDataType { + fn render(&mut self, svg: &mut String, transforms: &mut Vec, view_mode: ViewMode) { + self.inner_mut().render(svg, transforms, view_mode) + } + + fn intersects_quad(&self, quad: Quad, path: &mut Vec, intersections: &mut Vec>) { + self.inner().intersects_quad(quad, path, intersections) + } + + fn bounding_box(&self, transform: glam::DAffine2) -> Option<[DVec2; 2]> { + self.inner().bounding_box(transform) + } +} + +#[derive(Serialize, Deserialize)] +#[serde(remote = "glam::DAffine2")] +struct DAffine2Ref { + pub matrix2: DMat2, + pub translation: DVec2, +} + +fn return_true() -> bool { + true +} + +#[derive(Debug, PartialEq, Deserialize, Serialize)] +pub struct Layer { + pub visible: bool, + pub name: Option, + pub data: LayerDataType, + #[serde(with = "DAffine2Ref")] + pub transform: glam::DAffine2, + #[serde(skip)] + pub cache: String, + #[serde(skip)] + pub thumbnail_cache: String, + #[serde(skip, default = "return_true")] + pub cache_dirty: bool, + pub blend_mode: BlendMode, + pub opacity: f64, +} + +impl Layer { + pub fn new(data: LayerDataType, transform: [f64; 6]) -> Self { + Self { + visible: true, + name: None, + data, + transform: glam::DAffine2::from_cols_array(&transform), + cache: String::new(), + thumbnail_cache: String::new(), + cache_dirty: true, + blend_mode: BlendMode::Normal, + opacity: 1., + } + } + + pub fn iter(&self) -> LayerIter<'_> { + LayerIter { stack: vec![self] } + } + + pub fn render(&mut self, transforms: &mut Vec, view_mode: ViewMode) -> &str { + if !self.visible { + return ""; + } + + if self.cache_dirty { + transforms.push(self.transform); + self.thumbnail_cache.clear(); + self.data.render(&mut self.thumbnail_cache, transforms, view_mode); + + self.cache.clear(); + let _ = writeln!(self.cache, r#"{}"#, + self.blend_mode.to_svg_style_name(), + self.opacity, + self.thumbnail_cache.as_str() + ); + transforms.pop(); + self.cache_dirty = false; + } + + self.cache.as_str() + } + + pub fn intersects_quad(&self, quad: Quad, path: &mut Vec, intersections: &mut Vec>) { + if !self.visible { + return; + } + + let transformed_quad = self.transform.inverse() * quad; + self.data.intersects_quad(transformed_quad, path, intersections) + } + + pub fn current_bounding_box_with_transform(&self, transform: DAffine2) -> Option<[DVec2; 2]> { + self.data.bounding_box(transform) + } + + pub fn current_bounding_box(&self) -> Option<[DVec2; 2]> { + self.current_bounding_box_with_transform(self.transform) + } + + pub fn as_folder_mut(&mut self) -> Result<&mut Folder, DocumentError> { + match &mut self.data { + LayerDataType::Folder(f) => Ok(f), + _ => Err(DocumentError::NotAFolder), + } + } + + pub fn as_folder(&self) -> Result<&Folder, DocumentError> { + match &self.data { + LayerDataType::Folder(f) => Ok(f), + _ => Err(DocumentError::NotAFolder), + } + } +} + +impl Clone for Layer { + fn clone(&self) -> Self { + Self { + visible: self.visible, + name: self.name.clone(), + data: self.data.clone(), + transform: self.transform, + cache: String::new(), + thumbnail_cache: String::new(), + cache_dirty: true, + blend_mode: self.blend_mode, + opacity: self.opacity, + } + } +} + +impl<'a> IntoIterator for &'a Layer { + type Item = &'a Layer; + type IntoIter = LayerIter<'a>; + + fn into_iter(self) -> Self::IntoIter { + self.iter() + } +} + +#[derive(Debug, Default)] +pub struct LayerIter<'a> { + pub stack: Vec<&'a Layer>, +} + +impl<'a> Iterator for LayerIter<'a> { + type Item = &'a Layer; + + fn next(&mut self) -> Option { + match self.stack.pop() { + Some(layer) => { + if let LayerDataType::Folder(folder) = &layer.data { + let layers = folder.layers(); + self.stack.extend(layers); + }; + Some(layer) + } + None => None, + } + } +} diff --git a/graphene/src/layers/mod.rs b/graphene/src/layers/mod.rs index 06f8beb2ea..4763c743ea 100644 --- a/graphene/src/layers/mod.rs +++ b/graphene/src/layers/mod.rs @@ -1,212 +1,5 @@ -pub mod style; -use style::ViewMode; - -use glam::DAffine2; -use glam::{DMat2, DVec2}; - pub mod blend_mode; -pub use blend_mode::BlendMode; - -pub mod simple_shape; -pub use simple_shape::Shape; - pub mod folder; -use crate::LayerId; -use crate::{DocumentError, Quad}; -pub use folder::Folder; -use serde::{Deserialize, Serialize}; - -use std::fmt::Write; - -#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)] -pub enum LayerDataType { - Folder(Folder), - Shape(Shape), -} -impl LayerDataType { - pub fn inner(&self) -> &dyn LayerData { - match self { - LayerDataType::Shape(s) => s, - LayerDataType::Folder(f) => f, - } - } - - pub fn inner_mut(&mut self) -> &mut dyn LayerData { - match self { - LayerDataType::Shape(s) => s, - LayerDataType::Folder(f) => f, - } - } -} - -pub trait LayerData { - fn render(&mut self, svg: &mut String, transforms: &mut Vec, view_mode: ViewMode); - fn intersects_quad(&self, quad: Quad, path: &mut Vec, intersections: &mut Vec>); - fn bounding_box(&self, transform: glam::DAffine2) -> Option<[DVec2; 2]>; -} - -impl LayerData for LayerDataType { - fn render(&mut self, svg: &mut String, transforms: &mut Vec, view_mode: ViewMode) { - self.inner_mut().render(svg, transforms, view_mode) - } - - fn intersects_quad(&self, quad: Quad, path: &mut Vec, intersections: &mut Vec>) { - self.inner().intersects_quad(quad, path, intersections) - } - - fn bounding_box(&self, transform: glam::DAffine2) -> Option<[DVec2; 2]> { - self.inner().bounding_box(transform) - } -} - -#[derive(Serialize, Deserialize)] -#[serde(remote = "glam::DAffine2")] -struct DAffine2Ref { - pub matrix2: DMat2, - pub translation: DVec2, -} - -fn return_true() -> bool { - true -} - -#[derive(Debug, PartialEq, Deserialize, Serialize)] -pub struct Layer { - pub visible: bool, - pub name: Option, - pub data: LayerDataType, - #[serde(with = "DAffine2Ref")] - pub transform: glam::DAffine2, - #[serde(skip)] - pub cache: String, - #[serde(skip)] - pub thumbnail_cache: String, - #[serde(skip, default = "return_true")] - pub cache_dirty: bool, - pub blend_mode: BlendMode, - pub opacity: f64, -} - -impl Layer { - pub fn new(data: LayerDataType, transform: [f64; 6]) -> Self { - Self { - visible: true, - name: None, - data, - transform: glam::DAffine2::from_cols_array(&transform), - cache: String::new(), - thumbnail_cache: String::new(), - cache_dirty: true, - blend_mode: BlendMode::Normal, - opacity: 1., - } - } - - pub fn iter(&self) -> LayerIter<'_> { - LayerIter { stack: vec![self] } - } - - pub fn render(&mut self, transforms: &mut Vec, view_mode: ViewMode) -> &str { - if !self.visible { - return ""; - } - if self.cache_dirty { - transforms.push(self.transform); - self.thumbnail_cache.clear(); - self.data.render(&mut self.thumbnail_cache, transforms, view_mode); - - self.cache.clear(); - let _ = writeln!(self.cache, r#"{}"#, - self.blend_mode.to_svg_style_name(), - self.opacity, - self.thumbnail_cache.as_str() - ); - transforms.pop(); - self.cache_dirty = false; - } - self.cache.as_str() - } - - pub fn intersects_quad(&self, quad: Quad, path: &mut Vec, intersections: &mut Vec>) { - if !self.visible { - return; - } - let transformed_quad = self.transform.inverse() * quad; - self.data.intersects_quad(transformed_quad, path, intersections) - } - - pub fn current_bounding_box_with_transform(&self, transform: DAffine2) -> Option<[DVec2; 2]> { - self.data.bounding_box(transform) - } - - pub fn current_bounding_box(&self) -> Option<[DVec2; 2]> { - self.current_bounding_box_with_transform(self.transform) - } - - pub fn as_folder_mut(&mut self) -> Result<&mut Folder, DocumentError> { - match &mut self.data { - LayerDataType::Folder(f) => Ok(f), - _ => Err(DocumentError::NotAFolder), - } - } - - pub fn as_folder(&self) -> Result<&Folder, DocumentError> { - match &self.data { - LayerDataType::Folder(f) => Ok(f), - _ => Err(DocumentError::NotAFolder), - } - } -} - -impl Clone for Layer { - fn clone(&self) -> Self { - Self { - visible: self.visible, - name: self.name.clone(), - data: self.data.clone(), - transform: self.transform, - cache: String::new(), - thumbnail_cache: String::new(), - cache_dirty: true, - blend_mode: self.blend_mode, - opacity: self.opacity, - } - } -} - -impl<'a> IntoIterator for &'a Layer { - type Item = &'a Layer; - type IntoIter = LayerIter<'a>; - - fn into_iter(self) -> Self::IntoIter { - self.iter() - } -} - -#[derive(Debug, Default)] -pub struct LayerIter<'a> { - pub stack: Vec<&'a Layer>, -} - -impl<'a> Iterator for LayerIter<'a> { - type Item = &'a Layer; - - fn next(&mut self) -> Option { - match self.stack.pop() { - Some(layer) => { - if let LayerDataType::Folder(folder) = &layer.data { - let layers = folder.layers(); - self.stack.extend(layers); - }; - Some(layer) - } - None => None, - } - } -} +pub mod layer_info; +pub mod simple_shape; +pub mod style; diff --git a/graphene/src/layers/simple_shape.rs b/graphene/src/layers/simple_shape.rs index 8995e6653d..3b2a9e242e 100644 --- a/graphene/src/layers/simple_shape.rs +++ b/graphene/src/layers/simple_shape.rs @@ -1,19 +1,10 @@ -use glam::DAffine2; -use glam::DMat2; -use glam::DVec2; -use kurbo::Affine; -use kurbo::BezPath; -use kurbo::Shape as KurboShape; - -use crate::intersection::intersect_quad_bez_path; -use crate::layers::{ - style, - style::{PathStyle, ViewMode}, - LayerData, -}; +use super::layer_info::LayerData; +use super::style::{self, PathStyle, ViewMode}; +use crate::intersection::{intersect_quad_bez_path, Quad}; use crate::LayerId; -use crate::Quad; +use glam::{DAffine2, DMat2, DVec2}; +use kurbo::{Affine, BezPath, Shape as KurboShape}; use serde::{Deserialize, Serialize}; use std::fmt::Write; @@ -107,6 +98,7 @@ impl Shape { relative_points.for_each(|p| path.line_to(p)); path.close_path(); + Self { path, style, @@ -146,6 +138,7 @@ impl Shape { .map(|v: DVec2| kurbo::Point { x: v.x, y: v.y }) .enumerate() .for_each(|(i, p)| if i == 0 { path.move_to(p) } else { path.line_to(p) }); + Self { path, style, diff --git a/graphene/src/layers/style/mod.rs b/graphene/src/layers/style/mod.rs index e77f816005..6ecc1feb48 100644 --- a/graphene/src/layers/style/mod.rs +++ b/graphene/src/layers/style/mod.rs @@ -1,5 +1,6 @@ use crate::color::Color; use crate::consts::{LAYER_OUTLINE_STROKE_COLOR, LAYER_OUTLINE_STROKE_WIDTH}; + use serde::{Deserialize, Serialize}; const OPACITY_PRECISION: usize = 3; @@ -18,6 +19,7 @@ pub enum ViewMode { Outline, Pixels, } + impl Default for ViewMode { fn default() -> Self { ViewMode::Normal @@ -29,16 +31,20 @@ impl Default for ViewMode { pub struct Fill { color: Option, } + impl Fill { pub fn new(color: Color) -> Self { Self { color: Some(color) } } + pub fn color(&self) -> Option { self.color } + pub const fn none() -> Self { Self { color: None } } + pub fn render(&self) -> String { match self.color { Some(c) => format!(r##" fill="#{}"{}"##, c.rgb_hex(), format_opacity("fill", c.a())), @@ -58,12 +64,15 @@ impl Stroke { pub const fn new(color: Color, width: f32) -> Self { Self { color, width } } + pub fn color(&self) -> Color { self.color } + pub fn width(&self) -> f32 { self.width } + pub fn render(&self) -> String { format!(r##" stroke="#{}"{} stroke-width="{}""##, self.color.rgb_hex(), format_opacity("stroke", self.color.a()), self.width) } @@ -75,25 +84,32 @@ pub struct PathStyle { stroke: Option, fill: Option, } + impl PathStyle { pub fn new(stroke: Option, fill: Option) -> Self { Self { stroke, fill } } + pub fn fill(&self) -> Option { self.fill } + pub fn stroke(&self) -> Option { self.stroke } + pub fn set_fill(&mut self, fill: Fill) { self.fill = Some(fill); } + pub fn set_stroke(&mut self, stroke: Stroke) { self.stroke = Some(stroke); } + pub fn clear_fill(&mut self) { self.fill = None; } + pub fn clear_stroke(&mut self) { self.stroke = None; } @@ -109,6 +125,7 @@ impl PathStyle { (_, Some(stroke)) => stroke.render(), (_, None) => String::new(), }; + format!("{}{}", fill_attribute, stroke_attribute) } } diff --git a/graphene/src/lib.rs b/graphene/src/lib.rs index c54ea46bc6..398d3e7a43 100644 --- a/graphene/src/lib.rs +++ b/graphene/src/lib.rs @@ -1,24 +1,13 @@ pub mod color; pub mod consts; pub mod document; +pub mod error; pub mod intersection; pub mod layers; pub mod operation; pub mod response; -pub use intersection::Quad; +pub use document::LayerId; +pub use error::DocumentError; pub use operation::Operation; pub use response::DocumentResponse; - -pub type LayerId = u64; - -#[derive(Debug, Clone, PartialEq)] -pub enum DocumentError { - LayerNotFound(Vec), - InvalidPath, - IndexOutOfBounds, - NotAFolder, - NonReorderableSelection, - NotAShape, - InvalidFile(String), -} diff --git a/graphene/src/operation.rs b/graphene/src/operation.rs index 2c20bbe0fa..953c2aa84a 100644 --- a/graphene/src/operation.rs +++ b/graphene/src/operation.rs @@ -1,15 +1,12 @@ -use std::{ - collections::hash_map::DefaultHasher, - hash::{Hash, Hasher}, -}; - -use crate::{ - color::Color, - layers::{style, BlendMode, Layer}, - LayerId, -}; +use crate::color::Color; +use crate::layers::blend_mode::BlendMode; +use crate::layers::layer_info::Layer; +use crate::layers::style; +use crate::LayerId; use serde::{Deserialize, Serialize}; +use std::collections::hash_map::DefaultHasher; +use std::hash::{Hash, Hasher}; #[repr(C)] #[derive(Debug, Clone, Deserialize, Serialize, PartialEq)] diff --git a/graphene/src/response.rs b/graphene/src/response.rs index c2644e5878..849aa6a82c 100644 --- a/graphene/src/response.rs +++ b/graphene/src/response.rs @@ -1,4 +1,5 @@ use crate::LayerId; + use serde::{Deserialize, Serialize}; use std::fmt; diff --git a/proc-macros/src/helpers.rs b/proc-macros/src/helpers.rs index 5f0fe1b5e1..4461e4b4db 100644 --- a/proc-macros/src/helpers.rs +++ b/proc-macros/src/helpers.rs @@ -42,6 +42,7 @@ pub fn two_segment_path(left_ident: Ident, right_ident: Ident) -> Path { #[cfg(test)] mod tests { use super::*; + use quote::ToTokens; use syn::spanned::Spanned; diff --git a/proc-macros/src/lib.rs b/proc-macros/src/lib.rs index 5067a74dbe..ee4a6d769c 100644 --- a/proc-macros/src/lib.rs +++ b/proc-macros/src/lib.rs @@ -12,6 +12,7 @@ use crate::discriminant::derive_discriminant_impl; use crate::helper_structs::AttrInnerSingleString; use crate::hint::derive_hint_impl; use crate::transitive_child::derive_transitive_child_impl; + use proc_macro::TokenStream; use syn::parse_macro_input; @@ -276,6 +277,7 @@ pub fn edge(attr: TokenStream, item: TokenStream) -> TokenStream { #[cfg(test)] mod tests { use super::*; + use proc_macro2::TokenStream as TokenStream2; fn ts_assert_eq(l: TokenStream2, r: TokenStream2) {