From a6c91204d621bb6878c1b55a8f49f2541f0b8665 Mon Sep 17 00:00:00 2001 From: mfish33 <32677537+mfish33@users.noreply.github.com> Date: Sun, 3 Jul 2022 08:06:27 -0600 Subject: [PATCH] Event broadcasting system (#692) * broadcast system implemented but not everywhere * unused types * code review with keavon * - optional signal mappings - tool.rs simplification * Cleanup * reduced code duplication in `tool.rs` * ran cargo fmt * code review changes * fix merge error Co-authored-by: Keavon Chambers --- editor/src/communication/broadcast_message.rs | 26 ++ .../broadcast_message_handler.rs | 27 ++ editor/src/communication/dispatcher.rs | 5 +- editor/src/communication/message.rs | 2 + editor/src/communication/mod.rs | 2 + .../src/document/artboard_message_handler.rs | 2 +- editor/src/document/document_message.rs | 1 - .../src/document/document_message_handler.rs | 46 +-- .../src/document/movement_message_handler.rs | 12 +- .../src/document/portfolio_message_handler.rs | 15 +- .../src/document/properties_panel_message.rs | 2 + .../properties_panel_message_handler.rs | 16 + .../transform_layer_message_handler.rs | 10 +- editor/src/document/transformation.rs | 2 +- editor/src/lib.rs | 1 + editor/src/misc/macros.rs | 87 ---- editor/src/viewport_tools/tool.rs | 379 ++++++------------ editor/src/viewport_tools/tool_message.rs | 4 - .../viewport_tools/tool_message_handler.rs | 68 ++-- .../src/viewport_tools/tools/artboard_tool.rs | 36 +- .../src/viewport_tools/tools/ellipse_tool.rs | 24 +- .../viewport_tools/tools/eyedropper_tool.rs | 24 +- editor/src/viewport_tools/tools/fill_tool.rs | 24 +- .../src/viewport_tools/tools/freehand_tool.rs | 24 +- .../src/viewport_tools/tools/gradient_tool.rs | 26 +- editor/src/viewport_tools/tools/line_tool.rs | 24 +- .../src/viewport_tools/tools/navigate_tool.rs | 24 +- editor/src/viewport_tools/tools/path_tool.rs | 24 +- editor/src/viewport_tools/tools/pen_tool.rs | 24 +- .../viewport_tools/tools/rectangle_tool.rs | 24 +- .../src/viewport_tools/tools/select_tool.rs | 25 +- editor/src/viewport_tools/tools/shape_tool.rs | 24 +- .../src/viewport_tools/tools/spline_tool.rs | 24 +- editor/src/viewport_tools/tools/text_tool.rs | 24 +- frontend/wasm/src/editor_api.rs | 3 + 35 files changed, 634 insertions(+), 451 deletions(-) create mode 100644 editor/src/communication/broadcast_message.rs create mode 100644 editor/src/communication/broadcast_message_handler.rs diff --git a/editor/src/communication/broadcast_message.rs b/editor/src/communication/broadcast_message.rs new file mode 100644 index 00000000..ac542fdf --- /dev/null +++ b/editor/src/communication/broadcast_message.rs @@ -0,0 +1,26 @@ +use crate::message_prelude::*; + +use serde::{Deserialize, Serialize}; + +#[impl_message(Message, Broadcast)] +#[derive(PartialEq, Clone, Debug, Serialize, Deserialize)] +pub enum BroadcastMessage { + SubscribeSignal { + on: BroadcastSignal, + send: Box, + }, + UnsubscribeSignal { + on: BroadcastSignal, + message: Box, + }, + #[child] + TriggerSignal(BroadcastSignal), +} + +#[derive(PartialEq, Eq, Clone, Debug, Serialize, Deserialize, Hash)] +#[impl_message(Message, BroadcastMessage, TriggerSignal)] +pub enum BroadcastSignal { + DocumentIsDirty, + ToolAbort, + SelectionChanged, +} diff --git a/editor/src/communication/broadcast_message_handler.rs b/editor/src/communication/broadcast_message_handler.rs new file mode 100644 index 00000000..278c931a --- /dev/null +++ b/editor/src/communication/broadcast_message_handler.rs @@ -0,0 +1,27 @@ +use crate::message_prelude::*; + +use std::collections::HashMap; + +#[derive(Debug, Clone, Default)] +pub struct BroadcastMessageHandler { + listeners: HashMap>, +} + +impl MessageHandler for BroadcastMessageHandler { + fn process_action(&mut self, action: BroadcastMessage, _data: (), responses: &mut VecDeque) { + use BroadcastMessage::*; + match action { + SubscribeSignal { on, send } => self.listeners.entry(on).or_default().push(*send), + UnsubscribeSignal { on, message } => self.listeners.entry(on).or_default().retain(|msg| *msg != *message), + TriggerSignal(signal) => { + for message in self.listeners.entry(signal).or_default() { + responses.push_front(message.clone()) + } + } + } + } + + fn actions(&self) -> ActionList { + vec![] + } +} diff --git a/editor/src/communication/dispatcher.rs b/editor/src/communication/dispatcher.rs index 60354e7a..6defb93d 100644 --- a/editor/src/communication/dispatcher.rs +++ b/editor/src/communication/dispatcher.rs @@ -1,3 +1,4 @@ +use super::broadcast_message_handler::BroadcastMessageHandler; use crate::document::PortfolioMessageHandler; use crate::global::GlobalMessageHandler; use crate::input::{InputMapperMessageHandler, InputPreprocessorMessageHandler}; @@ -18,6 +19,7 @@ pub struct Dispatcher { #[remain::sorted] #[derive(Debug, Default)] struct DispatcherMessageHandlers { + broadcast_message_handler: BroadcastMessageHandler, dialog_message_handler: DialogMessageHandler, global_message_handler: GlobalMessageHandler, input_mapper_message_handler: InputMapperMessageHandler, @@ -43,7 +45,7 @@ const SIDE_EFFECT_FREE_MESSAGES: &[MessageDiscriminant] = &[ MessageDiscriminant::Frontend(FrontendMessageDiscriminant::UpdateActiveDocument), MessageDiscriminant::Frontend(FrontendMessageDiscriminant::UpdateOpenDocumentsList), MessageDiscriminant::Frontend(FrontendMessageDiscriminant::TriggerFontLoad), - MessageDiscriminant::Tool(ToolMessageDiscriminant::DocumentIsDirty), + MessageDiscriminant::Broadcast(BroadcastMessageDiscriminant::TriggerSignal(BroadcastSignalDiscriminant::DocumentIsDirty)), ]; impl Dispatcher { @@ -85,6 +87,7 @@ impl Dispatcher { match message { #[remain::unsorted] NoOp => {} + Broadcast(message) => self.message_handlers.broadcast_message_handler.process_action(message, (), &mut queue), Dialog(message) => { self.message_handlers .dialog_message_handler diff --git a/editor/src/communication/message.rs b/editor/src/communication/message.rs index 4c6eaa89..d7d2afce 100644 --- a/editor/src/communication/message.rs +++ b/editor/src/communication/message.rs @@ -23,6 +23,8 @@ pub enum Message { #[remain::unsorted] NoOp, #[child] + Broadcast(BroadcastMessage), + #[child] Dialog(DialogMessage), #[child] Frontend(FrontendMessage), diff --git a/editor/src/communication/mod.rs b/editor/src/communication/mod.rs index 6392653b..1009ff89 100644 --- a/editor/src/communication/mod.rs +++ b/editor/src/communication/mod.rs @@ -1,3 +1,5 @@ +pub mod broadcast_message; +pub mod broadcast_message_handler; pub mod dispatcher; pub mod message; pub mod message_handler; diff --git a/editor/src/document/artboard_message_handler.rs b/editor/src/document/artboard_message_handler.rs index 044aed1b..fd52f5d6 100644 --- a/editor/src/document/artboard_message_handler.rs +++ b/editor/src/document/artboard_message_handler.rs @@ -41,7 +41,7 @@ impl MessageHandler for ArtboardMessageHandler { DocumentResponse::DocumentChanged => responses.push_back(ArtboardMessage::RenderArtboards.into()), _ => {} }; - responses.push_back(ToolMessage::DocumentIsDirty.into()); + responses.push_back(BroadcastSignal::DocumentIsDirty.into()); } } Ok(None) => {} diff --git a/editor/src/document/document_message.rs b/editor/src/document/document_message.rs index 2a189951..fdea7349 100644 --- a/editor/src/document/document_message.rs +++ b/editor/src/document/document_message.rs @@ -102,7 +102,6 @@ pub enum DocumentMessage { RollbackTransaction, SaveDocument, SelectAllLayers, - SelectionChanged, SelectLayer { layer_path: Vec, ctrl: bool, diff --git a/editor/src/document/document_message_handler.rs b/editor/src/document/document_message_handler.rs index ef40f5b7..bdfe1ebe 100644 --- a/editor/src/document/document_message_handler.rs +++ b/editor/src/document/document_message_handler.rs @@ -1,8 +1,8 @@ use super::clipboards::Clipboard; use super::layer_panel::{layer_panel_entry, LayerMetadata, LayerPanelEntry, RawBuffer}; use super::properties_panel_message_handler::PropertiesPanelMessageHandlerData; +use super::utility_types::DocumentMode; use super::utility_types::{AlignAggregate, AlignAxis, DocumentSave, FlipAxis}; -use super::utility_types::{DocumentMode, TargetDocument}; use super::{vectorize_layer_metadata, PropertiesPanelMessageHandler}; use super::{ArtboardMessageHandler, MovementMessageHandler, OverlaysMessageHandler, TransformLayerMessageHandler}; use crate::consts::{ASYMPTOTIC_EFFECT, DEFAULT_DOCUMENT_NAME, FILE_SAVE_SUFFIX, GRAPHITE_DOCUMENT_VERSION, SCALE_EFFECT, SCROLLBAR_SPACING, VIEWPORT_ZOOM_TO_FIT_PADDING_SCALE_FACTOR}; @@ -864,7 +864,7 @@ impl MessageHandler responses.push_back(RenderDocument.into()), }; - responses.push_back(ToolMessage::DocumentIsDirty.into()); + responses.push_back(BroadcastSignal::DocumentIsDirty.into()); } } Err(e) => log::error!("DocumentError: {:?}", e), @@ -894,6 +894,7 @@ impl MessageHandler> = self.selected_layers().map(|path| path.to_vec()).collect(); - if selected_paths.is_empty() { - responses.push_back(PropertiesPanelMessage::ClearSelection.into()) - } else { - responses.push_back( - PropertiesPanelMessage::SetActiveLayers { - paths: selected_paths, - document: TargetDocument::Artwork, - } - .into(), - ) - } - // TODO: Correctly update layer panel in clear_selection instead of here responses.push_back(FolderChanged { affected_folder_path: vec![] }.into()); - responses.push_back(DocumentMessage::SelectionChanged.into()); + responses.push_back(BroadcastSignal::SelectionChanged.into()); self.update_layer_tree_options_bar_widgets(responses, font_cache); } @@ -963,7 +951,7 @@ impl MessageHandler { @@ -997,7 +985,7 @@ impl MessageHandler { responses.push_front(DocumentOperation::DeleteLayer { path: layer_path.clone() }.into()); - responses.push_front(ToolMessage::AbortCurrentTool.into()); + responses.push_front(BroadcastSignal::ToolAbort.into()); responses.push_back(PropertiesPanelMessage::CheckSelectedWasDeleted { path: layer_path }.into()); } DeleteSelectedLayers => { @@ -1007,7 +995,8 @@ impl MessageHandler { responses.push_front(SetSelectedLayers { replacement_selected_layers: vec![] }.into()); @@ -1106,7 +1095,7 @@ impl MessageHandler { @@ -1183,7 +1172,7 @@ impl MessageHandler { let path = vec![generate_uuid()]; @@ -1221,7 +1210,7 @@ impl MessageHandler { responses.push_back(SelectToolMessage::Abort.into()); responses.push_back(DocumentHistoryForward.into()); - responses.push_back(ToolMessage::DocumentIsDirty.into()); + responses.push_back(BroadcastSignal::DocumentIsDirty.into()); responses.push_back(RenderDocument.into()); responses.push_back(FolderChanged { affected_folder_path: vec![] }.into()); } @@ -1353,11 +1342,6 @@ impl MessageHandler { - // TODO: Hoist this duplicated code into wider system - responses.push_back(ToolMessage::SelectionChanged.into()); - responses.push_back(ToolMessage::DocumentIsDirty.into()); - } SelectLayer { layer_path, ctrl, shift } => { let mut paths = vec![]; let last_selection_exists = !self.layer_range_selection_reference.is_empty(); @@ -1382,7 +1366,7 @@ impl MessageHandler { responses.push_back(DocumentOperation::ToggleLayerVisibility { path: layer_path }.into()); - responses.push_back(ToolMessage::DocumentIsDirty.into()); + responses.push_back(BroadcastSignal::DocumentIsDirty.into()); } Undo => { - responses.push_back(ToolMessage::AbortCurrentTool.into()); + responses.push_back(BroadcastSignal::ToolAbort.into()); responses.push_back(DocumentHistoryBackward.into()); - responses.push_back(ToolMessage::DocumentIsDirty.into()); + responses.push_back(BroadcastSignal::DocumentIsDirty.into()); responses.push_back(RenderDocument.into()); responses.push_back(FolderChanged { affected_folder_path: vec![] }.into()); } diff --git a/editor/src/document/movement_message_handler.rs b/editor/src/document/movement_message_handler.rs index bf60e46a..93ba9b4a 100644 --- a/editor/src/document/movement_message_handler.rs +++ b/editor/src/document/movement_message_handler.rs @@ -153,7 +153,7 @@ impl MessageHandler { self.tilt = angle_radians; self.create_document_transform(&ipp.viewport_bounds, responses); - responses.push_back(ToolMessage::DocumentIsDirty.into()); + responses.push_back(BroadcastSignal::DocumentIsDirty.into()); responses.push_back(PortfolioMessage::UpdateDocumentWidgets.into()); } SetCanvasZoom { zoom_factor } => { self.zoom = zoom_factor.clamp(VIEWPORT_ZOOM_SCALE_MIN, VIEWPORT_ZOOM_SCALE_MAX); - responses.push_back(ToolMessage::DocumentIsDirty.into()); + responses.push_back(BroadcastSignal::DocumentIsDirty.into()); responses.push_back(DocumentMessage::DirtyRenderDocumentInOutlineView.into()); responses.push_back(PortfolioMessage::UpdateDocumentWidgets.into()); self.create_document_transform(&ipp.viewport_bounds, responses); @@ -256,7 +256,7 @@ impl MessageHandler { self.tilt = self.snapped_angle(); self.zoom = self.snapped_scale(); - responses.push_back(ToolMessage::DocumentIsDirty.into()); + responses.push_back(BroadcastSignal::DocumentIsDirty.into()); responses.push_back(ToolMessage::UpdateCursor.into()); responses.push_back(ToolMessage::UpdateHints.into()); self.snap_tilt = false; @@ -270,7 +270,7 @@ impl MessageHandler { @@ -284,7 +284,7 @@ impl MessageHandler { diff --git a/editor/src/document/portfolio_message_handler.rs b/editor/src/document/portfolio_message_handler.rs index 53f6094f..59ccd1a7 100644 --- a/editor/src/document/portfolio_message_handler.rs +++ b/editor/src/document/portfolio_message_handler.rs @@ -58,7 +58,7 @@ impl PortfolioMessageHandler { fn load_document(&mut self, new_document: DocumentMessageHandler, document_id: u64, replace_first_empty: bool, responses: &mut VecDeque) { // Special case when loading a document on an empty page if replace_first_empty && self.active_document().is_unmodified_default() { - responses.push_back(ToolMessage::AbortCurrentTool.into()); + responses.push_back(BroadcastSignal::ToolAbort.into()); responses.push_back(PortfolioMessage::CloseDocument { document_id: self.active_document_id }.into()); let active_document_index = self @@ -168,7 +168,7 @@ impl MessageHandler for Port self.document_ids.push(new_document_id); self.active_document_id = new_document_id; - responses.push_back(ToolMessage::AbortCurrentTool.into()); + responses.push_back(BroadcastSignal::ToolAbort.into()); responses.push_back(PortfolioMessage::UpdateOpenDocumentsList.into()); responses.push_back(PortfolioMessage::SelectDocument { document_id: new_document_id }.into()) } @@ -208,7 +208,7 @@ impl MessageHandler for Port CloseDocumentWithConfirmation { document_id } => { let target_document = self.documents.get(&document_id).unwrap(); if target_document.is_saved() { - responses.push_back(ToolMessage::AbortCurrentTool.into()); + responses.push_back(BroadcastSignal::ToolAbort.into()); responses.push_back(PortfolioMessage::CloseDocument { document_id }.into()); } else { let dialog = dialog::CloseDocument { @@ -274,13 +274,13 @@ impl MessageHandler for Port let name = self.generate_new_document_name(); let new_document = DocumentMessageHandler::with_name(name, ipp); let document_id = generate_uuid(); - responses.push_back(ToolMessage::AbortCurrentTool.into()); + responses.push_back(BroadcastSignal::ToolAbort.into()); self.load_document(new_document, document_id, false, responses); } NewDocumentWithName { name } => { let new_document = DocumentMessageHandler::with_name(name, ipp); let document_id = generate_uuid(); - responses.push_back(ToolMessage::AbortCurrentTool.into()); + responses.push_back(BroadcastSignal::ToolAbort.into()); self.load_document(new_document, document_id, false, responses); } NextDocument => { @@ -431,7 +431,7 @@ impl MessageHandler for Port if !active_document.is_saved() { responses.push_back(PortfolioMessage::AutoSaveDocument { document_id: self.active_document_id }.into()); } - responses.push_back(ToolMessage::AbortCurrentTool.into()); + responses.push_back(BroadcastSignal::ToolAbort.into()); responses.push_back(SetActiveDocument { document_id }.into()); responses.push_back(FrontendMessage::UpdateActiveDocument { document_id }.into()); @@ -440,7 +440,8 @@ impl MessageHandler for Port for layer in self.documents.get(&document_id).unwrap().layer_metadata.keys() { responses.push_back(DocumentMessage::LayerChanged { affected_layer_path: layer.clone() }.into()); } - responses.push_back(ToolMessage::DocumentIsDirty.into()); + responses.push_back(BroadcastSignal::SelectionChanged.into()); + responses.push_back(BroadcastSignal::DocumentIsDirty.into()); responses.push_back(PortfolioMessage::UpdateDocumentWidgets.into()); } SetActiveDocument { document_id } => { diff --git a/editor/src/document/properties_panel_message.rs b/editor/src/document/properties_panel_message.rs index 844b5499..5eae8fd3 100644 --- a/editor/src/document/properties_panel_message.rs +++ b/editor/src/document/properties_panel_message.rs @@ -12,6 +12,7 @@ pub enum PropertiesPanelMessage { CheckSelectedWasDeleted { path: Vec }, CheckSelectedWasUpdated { path: Vec }, ClearSelection, + Init, ModifyFill { fill: Fill }, ModifyFont { font_family: String, font_style: String, size: f64 }, ModifyName { name: String }, @@ -20,6 +21,7 @@ pub enum PropertiesPanelMessage { ModifyTransform { value: f64, transform_op: TransformOp }, ResendActiveProperties, SetActiveLayers { paths: Vec>, document: TargetDocument }, + UpdateSelectedDocumentProperties, } #[derive(PartialEq, Eq, Clone, Copy, Debug, Serialize, Deserialize)] diff --git a/editor/src/document/properties_panel_message_handler.rs b/editor/src/document/properties_panel_message_handler.rs index 0470c927..88e6853b 100644 --- a/editor/src/document/properties_panel_message_handler.rs +++ b/editor/src/document/properties_panel_message_handler.rs @@ -110,6 +110,7 @@ impl PropertiesPanelMessageHandler { pub struct PropertiesPanelMessageHandlerData<'a> { pub artwork_document: &'a GrapheneDocument, pub artboard_document: &'a GrapheneDocument, + pub selected_layers: &'a mut dyn Iterator, pub font_cache: &'a FontCache, } @@ -119,6 +120,7 @@ impl<'a> MessageHandler MessageHandler responses.push_back( + BroadcastMessage::SubscribeSignal { + on: BroadcastSignal::SelectionChanged, + send: Box::new(PropertiesPanelMessage::UpdateSelectedDocumentProperties.into()), + } + .into(), + ), ModifyFont { font_family, font_style, size } => { let (path, _) = self.active_selection.clone().expect("Received update for properties panel with no active layer"); @@ -233,6 +242,13 @@ impl<'a> MessageHandler responses.push_back( + PropertiesPanelMessage::SetActiveLayers { + paths: selected_layers.map(|path| path.to_vec()).collect(), + document: TargetDocument::Artwork, + } + .into(), + ), } } diff --git a/editor/src/document/transform_layer_message_handler.rs b/editor/src/document/transform_layer_message_handler.rs index b7a309ef..2fdd12d8 100644 --- a/editor/src/document/transform_layer_message_handler.rs +++ b/editor/src/document/transform_layer_message_handler.rs @@ -55,7 +55,7 @@ impl<'a> MessageHandler> for TransformL self.transform_operation = TransformOperation::None; - responses.push_back(ToolMessage::DocumentIsDirty.into()); + responses.push_back(BroadcastSignal::DocumentIsDirty.into()); } BeginGrab => { if let TransformOperation::Grabbing(_) = self.transform_operation { @@ -66,7 +66,7 @@ impl<'a> MessageHandler> for TransformL self.transform_operation = TransformOperation::Grabbing(Default::default()); - responses.push_back(ToolMessage::DocumentIsDirty.into()); + responses.push_back(BroadcastSignal::DocumentIsDirty.into()); } BeginRotate => { if let TransformOperation::Rotating(_) = self.transform_operation { @@ -77,7 +77,7 @@ impl<'a> MessageHandler> for TransformL self.transform_operation = TransformOperation::Rotating(Default::default()); - responses.push_back(ToolMessage::DocumentIsDirty.into()); + responses.push_back(BroadcastSignal::DocumentIsDirty.into()); } BeginScale => { if let TransformOperation::Scaling(_) = self.transform_operation { @@ -89,7 +89,7 @@ impl<'a> MessageHandler> for TransformL self.transform_operation = TransformOperation::Scaling(Default::default()); self.transform_operation.apply_transform_operation(&mut selected, self.snap); - responses.push_back(ToolMessage::DocumentIsDirty.into()); + responses.push_back(BroadcastSignal::DocumentIsDirty.into()); } CancelTransformOperation => { selected.revert_operation(); @@ -99,7 +99,7 @@ impl<'a> MessageHandler> for TransformL self.transform_operation = TransformOperation::None; - responses.push_back(ToolMessage::DocumentIsDirty.into()); + responses.push_back(BroadcastSignal::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), diff --git a/editor/src/document/transformation.rs b/editor/src/document/transformation.rs index f198c9c9..9134574e 100644 --- a/editor/src/document/transformation.rs +++ b/editor/src/document/transformation.rs @@ -258,7 +258,7 @@ impl<'a> Selected<'a> { ); } - self.responses.push_back(ToolMessage::DocumentIsDirty.into()); + self.responses.push_back(BroadcastSignal::DocumentIsDirty.into()); } } diff --git a/editor/src/lib.rs b/editor/src/lib.rs index 07fe8fab..edb99403 100644 --- a/editor/src/lib.rs +++ b/editor/src/lib.rs @@ -54,6 +54,7 @@ impl Default for Editor { } pub mod message_prelude { + pub use crate::communication::broadcast_message::{BroadcastMessage, BroadcastMessageDiscriminant, BroadcastSignal, BroadcastSignalDiscriminant}; pub use crate::communication::generate_uuid; pub use crate::communication::message::{AsMessage, Message, MessageDiscriminant}; pub use crate::communication::message_handler::{ActionList, MessageHandler}; diff --git a/editor/src/misc/macros.rs b/editor/src/misc/macros.rs index b3a6f99a..702efffc 100644 --- a/editor/src/misc/macros.rs +++ b/editor/src/misc/macros.rs @@ -1,90 +1,3 @@ -/// Counts args in the macro invocation by adding `+ 1` for every arg. -/// -/// # Example -/// -/// ```ignore -/// let x = count_args!(("example1"), (10), (25)); -/// assert_eq!(x, 3); -/// ``` -/// expands to -/// ```ignore -/// let x = 0 + 1 + 1 + 1; -/// assert_eq!(x, 3); -/// ``` -macro_rules! count_args { - (@one $($t:tt)*) => { 1 }; - ($(($($x:tt)*)),*$(,)?) => { - 0 $(+ count_args!(@one $($x)*))* - }; -} - -/// Generates a [`std::collections::HashMap`] for `ToolState`'s `tools` variable. -/// -/// # Example -/// -/// ```ignore -/// let tools = gen_tools_hash_map! { -/// Select => select::Select, -/// Artboard => artboard::Artboard, -/// }; -/// ``` -/// expands to -/// ```ignore -/// let tools = { -/// 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::Artboard, Box::new(artboard::Artboard::default())); -/// -/// hash_map -/// }; -/// ``` -macro_rules! gen_tools_hash_map { - ($($enum_variant:ident => $struct_path:ty),* $(,)?) => {{ - let mut hash_map: ::std::collections::HashMap<$crate::viewport_tools::tool::ToolType, ::std::boxed::Box<$crate::viewport_tools::tool::Tool>> = ::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 - }}; -} - -/// Creates a string representation of an enum value that exactly matches the given name of each enum variant -/// -/// # Example -/// -/// ```ignore -/// enum E { -/// A(u8), -/// B -/// } -/// -/// // this line is important -/// use E::*; -/// -/// let a = E::A(7); -/// let s = match_variant_name!(match (a) { A, B }); -/// ``` -/// -/// expands to -/// -/// ```ignore -/// // ... -/// -/// let s = match a { -/// A { .. } => "A", -/// B { .. } => "B" -/// }; -/// ``` -macro_rules! match_variant_name { - (match ($e:expr) { $($v:ident),* $(,)? }) => { - match $e { - $( - $v { .. } => stringify!($v) - ),* - } - }; -} - /// Syntax sugar for initializing an `ActionList` /// /// # Example diff --git a/editor/src/viewport_tools/tool.rs b/editor/src/viewport_tools/tool.rs index b86155da..f2e39688 100644 --- a/editor/src/viewport_tools/tool.rs +++ b/editor/src/viewport_tools/tool.rs @@ -31,8 +31,61 @@ pub struct DocumentToolData { pub secondary_color: Color, } -pub trait ToolCommon: for<'a> MessageHandler> + PropertyHolder {} -impl ToolCommon for T where T: for<'a> MessageHandler> + PropertyHolder {} +#[derive(Clone, Debug)] +pub struct SignalToMessageMap { + pub document_dirty: Option, + pub selection_changed: Option, + pub tool_abort: Option, +} + +pub trait ToolTransition { + fn signal_to_message_map(&self) -> SignalToMessageMap; + fn activate(&self, responses: &mut VecDeque) { + let mut subscribe_message = |broadcast_to_tool_mapping: Option, signal: BroadcastSignal| { + if let Some(mapping) = broadcast_to_tool_mapping { + responses.push_back( + BroadcastMessage::SubscribeSignal { + on: signal, + send: Box::new(mapping.into()), + } + .into(), + ); + }; + }; + + let signal_to_tool_map = self.signal_to_message_map(); + subscribe_message(signal_to_tool_map.document_dirty, BroadcastSignal::DocumentIsDirty); + subscribe_message(signal_to_tool_map.tool_abort, BroadcastSignal::ToolAbort); + subscribe_message(signal_to_tool_map.selection_changed, BroadcastSignal::SelectionChanged); + } + + fn deactivate(&self, responses: &mut VecDeque) { + let mut unsubscribe_message = |broadcast_to_tool_mapping: Option, signal: BroadcastSignal| { + if let Some(mapping) = broadcast_to_tool_mapping { + responses.push_back( + BroadcastMessage::UnsubscribeSignal { + on: signal, + message: Box::new(mapping.into()), + } + .into(), + ); + }; + }; + + let signal_to_tool_map = self.signal_to_message_map(); + unsubscribe_message(signal_to_tool_map.document_dirty, BroadcastSignal::DocumentIsDirty); + unsubscribe_message(signal_to_tool_map.tool_abort, BroadcastSignal::ToolAbort); + unsubscribe_message(signal_to_tool_map.selection_changed, BroadcastSignal::SelectionChanged); + } +} +pub trait ToolMetadata { + fn icon_name(&self) -> String; + fn tooltip(&self) -> String; + fn tool_type(&self) -> ToolType; +} + +pub trait ToolCommon: for<'a> MessageHandler> + PropertyHolder + ToolTransition + ToolMetadata {} +impl ToolCommon for T where T: for<'a> MessageHandler> + PropertyHolder + ToolTransition + ToolMetadata {} type Tool = dyn ToolCommon; @@ -57,24 +110,33 @@ impl ToolData { } } +#[derive(Debug)] +pub struct ToolBarMetadataGroup { + pub tooltip: String, + pub icon_name: String, + pub tool_type: ToolType, +} + impl PropertyHolder for ToolData { fn properties(&self) -> Layout { - let tool_groups_layout = ToolType::list_tools_in_groups() + let tool_groups_layout = list_tools_in_groups() .iter() + .map(|tool_group| tool_group.iter().map(|tool| ToolBarMetadataGroup {tooltip: tool.tooltip(), icon_name: tool.icon_name(), tool_type: tool.tool_type()}).collect::>()) + .chain(coming_soon_tools()) .flat_map(|group| { let separator = std::iter::once(WidgetHolder::new(Widget::Separator(Separator { direction: SeparatorDirection::Vertical, separator_type: SeparatorType::Section, }))); - let buttons = group.iter().map(|tool_type| { + let buttons = group.into_iter().map(|ToolBarMetadataGroup {tooltip, tool_type, icon_name}| { WidgetHolder::new(Widget::IconButton(IconButton { - icon: tool_type.icon_name(), + icon: icon_name, size: 32, - tooltip: tool_type.tooltip(), - active: self.active_tool_type == *tool_type, - on_update: WidgetCallback::new(|_| { - if !tool_type.tooltip().contains("Coming Soon") { - ToolMessage::ActivateTool { tool_type: *tool_type }.into() + tooltip: tooltip.clone(), + active: self.active_tool_type == tool_type, + on_update: WidgetCallback::new(move |_| { + if !tooltip.contains("Coming Soon") { + ToolMessage::ActivateTool { tool_type }.into() } else { DialogMessage::RequestComingSoonDialog { issue: None }.into() } @@ -105,34 +167,7 @@ impl Default for ToolFsmState { ToolFsmState { tool_data: ToolData { active_tool_type: ToolType::Select, - tools: gen_tools_hash_map! { - // General - Select => select_tool::SelectTool, - Artboard => artboard_tool::ArtboardTool, - Navigate => navigate_tool::NavigateTool, - Eyedropper => eyedropper_tool::EyedropperTool, - Fill => fill_tool::FillTool, - Gradient => gradient_tool::GradientTool, - - // Vector - Path => path_tool::PathTool, - Pen => pen_tool::PenTool, - Freehand => freehand_tool::FreehandTool, - Spline => spline_tool::SplineTool, - Line => line_tool::LineTool, - Rectangle => rectangle_tool::RectangleTool, - Ellipse => ellipse_tool::EllipseTool, - Shape => shape_tool::ShapeTool, - Text => text_tool::TextTool, - - // Raster - // Brush => brush_tool::BrushTool, - // Heal => heal_tool::HealTool, - // Clone => clone_tool:::CloneTool, - // Patch => patch_tool:::PatchTool, - // Relight => relight_tool:::RelightTool, - // Detail => detail_tool:::DetailTool, - }, + tools: list_tools_in_groups().into_iter().flatten().map(|tool| (tool.tool_type(), tool)).collect(), }, document_tool_data: DocumentToolData { primary_color: Color::BLACK, @@ -183,213 +218,66 @@ pub enum ToolType { Relight, } -impl ToolType { - /// List of all the tools in their conventional ordering and grouping. - pub fn list_tools_in_groups() -> [&'static [ToolType]; 3] { - [ - &[ - // General tool group - ToolType::Select, - ToolType::Artboard, - ToolType::Navigate, - ToolType::Eyedropper, - ToolType::Fill, - ToolType::Gradient, - ], - &[ - // Vector tool group - ToolType::Path, - ToolType::Pen, - ToolType::Freehand, - ToolType::Spline, - ToolType::Line, - ToolType::Rectangle, - ToolType::Ellipse, - ToolType::Shape, - ToolType::Text, - ], - &[ - // Raster tool group - ToolType::Brush, - ToolType::Heal, - ToolType::Clone, - ToolType::Patch, - ToolType::Detail, - ToolType::Relight, - ], - ] - } - - pub fn icon_name(&self) -> String { - match self { +/// List of all the tools in their conventional ordering and grouping. +pub fn list_tools_in_groups() -> Vec>> { + vec![ + vec![ // General tool group - ToolType::Select => "GeneralSelectTool".into(), - ToolType::Artboard => "GeneralArtboardTool".into(), - ToolType::Navigate => "GeneralNavigateTool".into(), - ToolType::Eyedropper => "GeneralEyedropperTool".into(), - ToolType::Fill => "GeneralFillTool".into(), - ToolType::Gradient => "GeneralGradientTool".into(), - + Box::new(select_tool::SelectTool::default()), + Box::new(artboard_tool::ArtboardTool::default()), + Box::new(navigate_tool::NavigateTool::default()), + Box::new(eyedropper_tool::EyedropperTool::default()), + Box::new(fill_tool::FillTool::default()), + Box::new(gradient_tool::GradientTool::default()), + ], + vec![ // Vector tool group - ToolType::Path => "VectorPathTool".into(), - ToolType::Pen => "VectorPenTool".into(), - ToolType::Freehand => "VectorFreehandTool".into(), - ToolType::Spline => "VectorSplineTool".into(), - ToolType::Line => "VectorLineTool".into(), - ToolType::Rectangle => "VectorRectangleTool".into(), - ToolType::Ellipse => "VectorEllipseTool".into(), - ToolType::Shape => "VectorShapeTool".into(), - ToolType::Text => "VectorTextTool".into(), - - // Raster tool group - ToolType::Brush => "RasterBrushTool".into(), - ToolType::Heal => "RasterHealTool".into(), - ToolType::Clone => "RasterCloneTool".into(), - ToolType::Patch => "RasterPatchTool".into(), - ToolType::Detail => "RasterDetailTool".into(), - ToolType::Relight => "RasterRelightTool".into(), - } - } - - pub fn tooltip(&self) -> String { - match self { - // General tool group - ToolType::Select => "Select Tool (V)".into(), - ToolType::Artboard => "Artboard Tool".into(), - ToolType::Navigate => "Navigate Tool (Z)".into(), - ToolType::Eyedropper => "Eyedropper Tool (I)".into(), - ToolType::Fill => "Fill Tool (F)".into(), - ToolType::Gradient => "Gradient Tool (H)".into(), - - // Vector tool group - ToolType::Path => "Path Tool (A)".into(), - ToolType::Pen => "Pen Tool (P)".into(), - ToolType::Freehand => "Freehand Tool (N)".into(), - ToolType::Spline => "Spline Tool".into(), - ToolType::Line => "Line Tool (L)".into(), - ToolType::Rectangle => "Rectangle Tool (M)".into(), - ToolType::Ellipse => "Ellipse Tool (E)".into(), - ToolType::Shape => "Shape Tool (Y)".into(), - ToolType::Text => "Text Tool (T)".into(), - - // Raster tool group - ToolType::Brush => "Coming Soon: Brush Tool (B)".into(), - ToolType::Heal => "Coming Soon: Heal Tool (J)".into(), - ToolType::Clone => "Coming Soon: Clone Tool (C)".into(), - ToolType::Patch => "Coming Soon: Patch Tool".into(), - ToolType::Detail => "Coming Soon: Detail Tool (D)".into(), - ToolType::Relight => "Coming Soon: Relight Tool (O)".into(), - } - } + Box::new(path_tool::PathTool::default()), + Box::new(pen_tool::PenTool::default()), + Box::new(freehand_tool::FreehandTool::default()), + Box::new(spline_tool::SplineTool::default()), + Box::new(line_tool::LineTool::default()), + Box::new(rectangle_tool::RectangleTool::default()), + Box::new(ellipse_tool::EllipseTool::default()), + Box::new(shape_tool::ShapeTool::default()), + Box::new(text_tool::TextTool::default()), + ], + ] } -impl fmt::Display for ToolType { - fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result { - use ToolType::*; - - let name = match_variant_name!(match (self) { - // General tool group - Select, - Artboard, - Navigate, - Eyedropper, - Fill, - Gradient, - - // Vector tool group - Path, - Pen, - Freehand, - Spline, - Line, - Rectangle, - Ellipse, - Shape, - Text, - - // Raster tool group - Brush, - Heal, - Clone, - Patch, - Detail, - Relight, - }); - - formatter.write_str(name) - } -} - -pub enum StandardToolMessageType { - Abort, - DocumentIsDirty, - SelectionChanged, -} - -// 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 { - // General tool group - ToolType::Select => Some(SelectToolMessage::DocumentIsDirty.into()), - ToolType::Artboard => Some(ArtboardToolMessage::DocumentIsDirty.into()), - ToolType::Navigate => None, // Some(NavigateToolMessage::DocumentIsDirty.into()), - ToolType::Eyedropper => None, // Some(EyedropperToolMessage::DocumentIsDirty.into()), - ToolType::Fill => None, // Some(FillToolMessage::DocumentIsDirty.into()), - ToolType::Gradient => Some(GradientToolMessage::DocumentIsDirty.into()), - - // Vector tool group - ToolType::Path => Some(PathToolMessage::DocumentIsDirty.into()), - ToolType::Pen => Some(PenToolMessage::DocumentIsDirty.into()), - ToolType::Freehand => None, // Some(FreehandToolMessage::DocumentIsDirty.into()), - ToolType::Spline => None, // Some(SplineToolMessage::DocumentIsDirty.into()), - ToolType::Line => None, // Some(LineToolMessage::DocumentIsDirty.into()), - ToolType::Rectangle => None, // Some(RectangleToolMessage::DocumentIsDirty.into()), - ToolType::Ellipse => None, // Some(EllipseToolMessage::DocumentIsDirty.into()), - ToolType::Shape => None, // Some(ShapeToolMessage::DocumentIsDirty.into()), - ToolType::Text => Some(TextMessage::DocumentIsDirty.into()), - - // Raster tool group - 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::Detail => None, // Some(DetailToolMessage::DocumentIsDirty.into()), - ToolType::Relight => None, // Some(RelightMessage::DocumentIsDirty.into()), +pub fn coming_soon_tools() -> Vec> { + vec![vec![ + ToolBarMetadataGroup { + tool_type: ToolType::Brush, + icon_name: "RasterBrushTool".into(), + tooltip: "Coming Soon: Brush Tool (B)".into(), }, - StandardToolMessageType::Abort => match tool { - // General tool group - ToolType::Select => Some(SelectToolMessage::Abort.into()), - ToolType::Artboard => Some(ArtboardToolMessage::Abort.into()), - ToolType::Navigate => Some(NavigateToolMessage::Abort.into()), - ToolType::Eyedropper => Some(EyedropperToolMessage::Abort.into()), - ToolType::Fill => Some(FillToolMessage::Abort.into()), - ToolType::Gradient => Some(GradientToolMessage::Abort.into()), - - // Vector tool group - ToolType::Path => Some(PathToolMessage::Abort.into()), - ToolType::Pen => Some(PenToolMessage::Abort.into()), - ToolType::Freehand => Some(FreehandToolMessage::Abort.into()), - ToolType::Spline => Some(SplineToolMessage::Abort.into()), - ToolType::Line => Some(LineToolMessage::Abort.into()), - ToolType::Rectangle => Some(RectangleToolMessage::Abort.into()), - ToolType::Ellipse => Some(EllipseToolMessage::Abort.into()), - ToolType::Shape => Some(ShapeToolMessage::Abort.into()), - ToolType::Text => Some(TextMessage::Abort.into()), - - // Raster tool group - ToolType::Brush => None, // Some(BrushMessage::Abort.into()), - ToolType::Heal => None, // Some(HealMessage::Abort.into()), - ToolType::Clone => None, // Some(CloneMessage::Abort.into()), - ToolType::Patch => None, // Some(PatchMessage::Abort.into()), - ToolType::Detail => None, // Some(DetailToolMessage::Abort.into()), - ToolType::Relight => None, // Some(RelightMessage::Abort.into()), + ToolBarMetadataGroup { + tool_type: ToolType::Heal, + icon_name: "RasterHealTool".into(), + tooltip: "Coming Soon: Heal Tool (J)".into(), }, - StandardToolMessageType::SelectionChanged => match tool { - ToolType::Path => Some(PathToolMessage::SelectionChanged.into()), - _ => None, + ToolBarMetadataGroup { + tool_type: ToolType::Clone, + icon_name: "RasterCloneTool".into(), + tooltip: "Coming Soon: Clone Tool (C))".into(), }, - } + ToolBarMetadataGroup { + tool_type: ToolType::Patch, + icon_name: "RasterPatchTool".into(), + tooltip: "Coming Soon: Patch Tool".into(), + }, + ToolBarMetadataGroup { + tool_type: ToolType::Detail, + icon_name: "RasterDetailTool".into(), + tooltip: "Coming Soon: Detail Tool (D)".into(), + }, + ToolBarMetadataGroup { + tool_type: ToolType::Relight, + icon_name: "RasterRelightTool".into(), + tooltip: "Coming Soon: Relight Tool (O".into(), + }, + ]] } pub fn message_to_tool_type(message: &ToolMessage) -> ToolType { @@ -422,16 +310,9 @@ pub fn message_to_tool_type(message: &ToolMessage) -> ToolType { // Patch(_) => ToolType::Patch, // Detail(_) => ToolType::Detail, // Relight(_) => ToolType::Relight, - _ => panic!("Conversion from message to tool type impossible because the given ToolMessage does not belong to a tool"), + _ => panic!( + "Conversion from message to tool type impossible because the given ToolMessage does not belong to a tool. Got: {:?}", + message + ), } } - -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 index 31e027cb..570d2a4c 100644 --- a/editor/src/viewport_tools/tool_message.rs +++ b/editor/src/viewport_tools/tool_message.rs @@ -76,15 +76,11 @@ pub enum ToolMessage { // Messages #[remain::unsorted] - NoOp, - AbortCurrentTool, ActivateTool { tool_type: ToolType, }, - DocumentIsDirty, InitTools, ResetColors, - SelectionChanged, SelectPrimaryColor { color: Color, }, diff --git a/editor/src/viewport_tools/tool_message_handler.rs b/editor/src/viewport_tools/tool_message_handler.rs index 7cae2542..d23f281b 100644 --- a/editor/src/viewport_tools/tool_message_handler.rs +++ b/editor/src/viewport_tools/tool_message_handler.rs @@ -1,4 +1,4 @@ -use super::tool::{message_to_tool_type, standard_tool_message, update_working_colors, StandardToolMessageType, ToolFsmState}; +use super::tool::{message_to_tool_type, DocumentToolData, ToolFsmState}; use crate::document::DocumentMessageHandler; use crate::input::InputPreprocessorMessageHandler; use crate::layout::layout_message::LayoutTarget; @@ -24,11 +24,6 @@ impl MessageHandler { - if let Some(tool_message) = standard_tool_message(self.tool_state.tool_data.active_tool_type, StandardToolMessageType::Abort) { - responses.push_front(tool_message.into()); - } - } ActivateTool { tool_type } => { let tool_data = &mut self.tool_state.tool_data; let document_data = &self.tool_state.document_tool_data; @@ -40,9 +35,11 @@ impl MessageHandler { - // Send the DocumentIsDirty message to the active tool's sub-tool message handler - let active_tool = self.tool_state.tool_data.active_tool_type; - if let Some(message) = standard_tool_message(active_tool, StandardToolMessageType::DocumentIsDirty) { - responses.push_back(message.into()); - } - } InitTools => { let tool_data = &mut self.tool_state.tool_data; let document_data = &self.tool_state.document_tool_data; let active_tool = &tool_data.active_tool_type; + // subscribe tool to broadcast messages + tool_data.tools.get(active_tool).unwrap().activate(responses); + // Register initial properties tool_data.tools.get(active_tool).unwrap().register_properties(responses, LayoutTarget::ToolOptions); @@ -111,12 +103,6 @@ impl MessageHandler { - let active_tool = self.tool_state.tool_data.active_tool_type; - if let Some(message) = standard_tool_message(active_tool, StandardToolMessageType::SelectionChanged) { - responses.push_back(message.into()); - } - } SelectPrimaryColor { color } => { let document_data = &mut self.tool_state.document_tool_data; document_data.primary_color = color; @@ -176,3 +162,13 @@ impl MessageHandler) { + responses.push_back( + FrontendMessage::UpdateWorkingColors { + primary: document_data.primary_color, + secondary: document_data.secondary_color, + } + .into(), + ); +} diff --git a/editor/src/viewport_tools/tools/artboard_tool.rs b/editor/src/viewport_tools/tools/artboard_tool.rs index b5af50fc..6696ddfb 100644 --- a/editor/src/viewport_tools/tools/artboard_tool.rs +++ b/editor/src/viewport_tools/tools/artboard_tool.rs @@ -6,7 +6,7 @@ use crate::layout::widgets::PropertyHolder; use crate::message_prelude::*; use crate::misc::{HintData, HintGroup, HintInfo, KeysGroup}; use crate::viewport_tools::snapping::SnapHandler; -use crate::viewport_tools::tool::{Fsm, ToolActionHandlerData}; +use crate::viewport_tools::tool::{Fsm, SignalToMessageMap, ToolActionHandlerData, ToolMetadata, ToolTransition, ToolType}; use graphene::intersection::Quad; @@ -41,6 +41,18 @@ pub enum ArtboardToolMessage { PointerUp, } +impl ToolMetadata for ArtboardTool { + fn icon_name(&self) -> String { + "GeneralArtboardTool".into() + } + fn tooltip(&self) -> String { + "Artboard Tool".into() + } + fn tool_type(&self) -> crate::viewport_tools::tool::ToolType { + ToolType::Artboard + } +} + impl<'a> MessageHandler> for ArtboardTool { fn process_action(&mut self, action: ToolMessage, data: ToolActionHandlerData<'a>, responses: &mut VecDeque) { if action == ToolMessage::UpdateHints { @@ -66,6 +78,16 @@ impl<'a> MessageHandler> for ArtboardTool impl PropertyHolder for ArtboardTool {} +impl ToolTransition for ArtboardTool { + fn signal_to_message_map(&self) -> SignalToMessageMap { + SignalToMessageMap { + document_dirty: Some(ArtboardToolMessage::DocumentIsDirty.into()), + tool_abort: Some(ArtboardToolMessage::Abort.into()), + selection_changed: None, + } + } +} + #[derive(Clone, Copy, Debug, PartialEq, Eq)] enum ArtboardToolFsmState { Ready, @@ -168,7 +190,7 @@ impl Fsm for ArtboardToolFsmState { let quad = Quad::from_box([input.mouse.position - tolerance, input.mouse.position + tolerance]); let intersection = document.artboard_message_handler.artboards_graphene_document.intersects_quad_root(quad, font_cache); - responses.push_back(ToolMessage::DocumentIsDirty.into()); + responses.push_back(BroadcastSignal::DocumentIsDirty.into()); if let Some(intersection) = intersection.last() { tool_data.selected_board = Some(intersection[0]); @@ -229,7 +251,7 @@ impl Fsm for ArtboardToolFsmState { .into(), ); - responses.push_back(ToolMessage::DocumentIsDirty.into()); + responses.push_back(BroadcastSignal::DocumentIsDirty.into()); } } ArtboardToolFsmState::ResizingBounds @@ -257,7 +279,7 @@ impl Fsm for ArtboardToolFsmState { .into(), ); - responses.push_back(ToolMessage::DocumentIsDirty.into()); + responses.push_back(BroadcastSignal::DocumentIsDirty.into()); tool_data.drag_current = mouse_position + closest_move; } @@ -303,7 +325,7 @@ impl Fsm for ArtboardToolFsmState { .into(), ); - responses.push_back(ToolMessage::DocumentIsDirty.into()); + responses.push_back(BroadcastSignal::DocumentIsDirty.into()); ArtboardToolFsmState::Drawing } @@ -333,7 +355,7 @@ impl Fsm for ArtboardToolFsmState { bounds.original_transforms.clear(); } - responses.push_back(ToolMessage::DocumentIsDirty.into()); + responses.push_back(BroadcastSignal::DocumentIsDirty.into()); ArtboardToolFsmState::Ready } @@ -349,7 +371,7 @@ impl Fsm for ArtboardToolFsmState { (_, ArtboardToolMessage::DeleteSelected) => { if let Some(artboard) = tool_data.selected_board.take() { responses.push_back(ArtboardMessage::DeleteArtboard { artboard }.into()); - responses.push_back(ToolMessage::DocumentIsDirty.into()); + responses.push_back(BroadcastSignal::DocumentIsDirty.into()); } ArtboardToolFsmState::Ready } diff --git a/editor/src/viewport_tools/tools/ellipse_tool.rs b/editor/src/viewport_tools/tools/ellipse_tool.rs index 820b7fd2..eb4db7b3 100644 --- a/editor/src/viewport_tools/tools/ellipse_tool.rs +++ b/editor/src/viewport_tools/tools/ellipse_tool.rs @@ -5,7 +5,7 @@ use crate::input::keyboard::{Key, MouseMotion}; use crate::layout::widgets::PropertyHolder; use crate::message_prelude::*; use crate::misc::{HintData, HintGroup, HintInfo, KeysGroup}; -use crate::viewport_tools::tool::{Fsm, ToolActionHandlerData}; +use crate::viewport_tools::tool::{Fsm, SignalToMessageMap, ToolActionHandlerData, ToolMetadata, ToolTransition, ToolType}; use graphene::layers::style; use graphene::Operation; @@ -36,6 +36,18 @@ pub enum EllipseToolMessage { }, } +impl ToolMetadata for EllipseTool { + fn icon_name(&self) -> String { + "VectorEllipseTool".into() + } + fn tooltip(&self) -> String { + "Ellipse Tool (E)".into() + } + fn tool_type(&self) -> crate::viewport_tools::tool::ToolType { + ToolType::Ellipse + } +} + impl PropertyHolder for EllipseTool {} impl<'a> MessageHandler> for EllipseTool { @@ -69,6 +81,16 @@ impl<'a> MessageHandler> for EllipseTool } } +impl ToolTransition for EllipseTool { + fn signal_to_message_map(&self) -> SignalToMessageMap { + SignalToMessageMap { + document_dirty: None, + tool_abort: Some(EllipseToolMessage::Abort.into()), + selection_changed: None, + } + } +} + #[derive(Clone, Copy, Debug, PartialEq, Eq)] enum EllipseToolFsmState { Ready, diff --git a/editor/src/viewport_tools/tools/eyedropper_tool.rs b/editor/src/viewport_tools/tools/eyedropper_tool.rs index a3ef40bc..b903055b 100644 --- a/editor/src/viewport_tools/tools/eyedropper_tool.rs +++ b/editor/src/viewport_tools/tools/eyedropper_tool.rs @@ -4,7 +4,7 @@ use crate::input::keyboard::MouseMotion; use crate::layout::widgets::PropertyHolder; use crate::message_prelude::*; use crate::misc::{HintData, HintGroup, HintInfo}; -use crate::viewport_tools::tool::{Fsm, ToolActionHandlerData}; +use crate::viewport_tools::tool::{Fsm, SignalToMessageMap, ToolActionHandlerData, ToolMetadata, ToolTransition, ToolType}; use graphene::intersection::Quad; use graphene::layers::layer_info::LayerDataType; @@ -31,6 +31,18 @@ pub enum EyedropperToolMessage { RightMouseDown, } +impl ToolMetadata for EyedropperTool { + fn icon_name(&self) -> String { + "GeneralEyedropperTool".into() + } + fn tooltip(&self) -> String { + "Eyedropper Tool (I)".into() + } + fn tool_type(&self) -> crate::viewport_tools::tool::ToolType { + ToolType::Eyedropper + } +} + impl PropertyHolder for EyedropperTool {} impl<'a> MessageHandler> for EyedropperTool { @@ -57,6 +69,16 @@ impl<'a> MessageHandler> for EyedropperTo advertise_actions!(EyedropperToolMessageDiscriminant; LeftMouseDown, RightMouseDown); } +impl ToolTransition for EyedropperTool { + fn signal_to_message_map(&self) -> SignalToMessageMap { + SignalToMessageMap { + document_dirty: None, + tool_abort: Some(EyedropperToolMessage::Abort.into()), + selection_changed: None, + } + } +} + #[derive(Clone, Copy, Debug, PartialEq, Eq)] enum EyedropperToolFsmState { Ready, diff --git a/editor/src/viewport_tools/tools/fill_tool.rs b/editor/src/viewport_tools/tools/fill_tool.rs index 7eaac712..100cae7f 100644 --- a/editor/src/viewport_tools/tools/fill_tool.rs +++ b/editor/src/viewport_tools/tools/fill_tool.rs @@ -4,7 +4,7 @@ use crate::input::keyboard::MouseMotion; use crate::layout::widgets::PropertyHolder; use crate::message_prelude::*; use crate::misc::{HintData, HintGroup, HintInfo}; -use crate::viewport_tools::tool::{Fsm, ToolActionHandlerData}; +use crate::viewport_tools::tool::{Fsm, SignalToMessageMap, ToolActionHandlerData, ToolMetadata, ToolTransition, ToolType}; use graphene::intersection::Quad; use graphene::Operation; @@ -32,6 +32,18 @@ pub enum FillToolMessage { RightMouseDown, } +impl ToolMetadata for FillTool { + fn icon_name(&self) -> String { + "GeneralFillTool".into() + } + fn tooltip(&self) -> String { + "Fill Tool (F)".into() + } + fn tool_type(&self) -> crate::viewport_tools::tool::ToolType { + ToolType::Fill + } +} + impl PropertyHolder for FillTool {} impl<'a> MessageHandler> for FillTool { @@ -58,6 +70,16 @@ impl<'a> MessageHandler> for FillTool { advertise_actions!(FillToolMessageDiscriminant; LeftMouseDown, RightMouseDown); } +impl ToolTransition for FillTool { + fn signal_to_message_map(&self) -> SignalToMessageMap { + SignalToMessageMap { + document_dirty: None, + tool_abort: Some(FillToolMessage::Abort.into()), + selection_changed: None, + } + } +} + #[derive(Clone, Copy, Debug, PartialEq, Eq)] enum FillToolFsmState { Ready, diff --git a/editor/src/viewport_tools/tools/freehand_tool.rs b/editor/src/viewport_tools/tools/freehand_tool.rs index b5826613..f7361b72 100644 --- a/editor/src/viewport_tools/tools/freehand_tool.rs +++ b/editor/src/viewport_tools/tools/freehand_tool.rs @@ -3,7 +3,7 @@ use crate::input::keyboard::MouseMotion; use crate::layout::widgets::{Layout, LayoutGroup, NumberInput, PropertyHolder, Widget, WidgetCallback, WidgetHolder, WidgetLayout}; use crate::message_prelude::*; use crate::misc::{HintData, HintGroup, HintInfo}; -use crate::viewport_tools::tool::{DocumentToolData, Fsm, ToolActionHandlerData}; +use crate::viewport_tools::tool::{DocumentToolData, Fsm, SignalToMessageMap, ToolActionHandlerData, ToolMetadata, ToolTransition, ToolType}; use graphene::layers::style; use graphene::Operation; @@ -55,6 +55,18 @@ enum FreehandToolFsmState { Drawing, } +impl ToolMetadata for FreehandTool { + fn icon_name(&self) -> String { + "VectorFreehandTool".into() + } + fn tooltip(&self) -> String { + "Freehand Tool (N)".into() + } + fn tool_type(&self) -> crate::viewport_tools::tool::ToolType { + ToolType::Freehand + } +} + impl PropertyHolder for FreehandTool { fn properties(&self) -> Layout { Layout::WidgetLayout(WidgetLayout::new(vec![LayoutGroup::Row { @@ -109,6 +121,16 @@ impl<'a> MessageHandler> for FreehandTool } } +impl ToolTransition for FreehandTool { + fn signal_to_message_map(&self) -> SignalToMessageMap { + SignalToMessageMap { + document_dirty: None, + tool_abort: Some(FreehandToolMessage::Abort.into()), + selection_changed: None, + } + } +} + impl Default for FreehandToolFsmState { fn default() -> Self { FreehandToolFsmState::Ready diff --git a/editor/src/viewport_tools/tools/gradient_tool.rs b/editor/src/viewport_tools/tools/gradient_tool.rs index 545502bd..3e0c22fe 100644 --- a/editor/src/viewport_tools/tools/gradient_tool.rs +++ b/editor/src/viewport_tools/tools/gradient_tool.rs @@ -6,7 +6,7 @@ use crate::layout::widgets::{Layout, LayoutGroup, PropertyHolder, RadioEntryData use crate::message_prelude::*; use crate::misc::{HintData, HintGroup, HintInfo, KeysGroup}; use crate::viewport_tools::snapping::SnapHandler; -use crate::viewport_tools::tool::{Fsm, ToolActionHandlerData}; +use crate::viewport_tools::tool::{Fsm, SignalToMessageMap, ToolActionHandlerData, ToolMetadata, ToolTransition, ToolType}; use graphene::color::Color; use graphene::intersection::Quad; @@ -60,6 +60,18 @@ pub enum GradientOptionsUpdate { Type(GradientType), } +impl ToolMetadata for GradientTool { + fn icon_name(&self) -> String { + "GeneralGradientTool".into() + } + fn tooltip(&self) -> String { + "Gradient Tool (H))".into() + } + fn tool_type(&self) -> crate::viewport_tools::tool::ToolType { + ToolType::Gradient + } +} + impl<'a> MessageHandler> for GradientTool { fn process_action(&mut self, action: ToolMessage, data: ToolActionHandlerData<'a>, responses: &mut VecDeque) { if action == ToolMessage::UpdateHints { @@ -284,6 +296,16 @@ impl SelectedGradient { } } +impl ToolTransition for GradientTool { + fn signal_to_message_map(&self) -> SignalToMessageMap { + SignalToMessageMap { + document_dirty: Some(GradientToolMessage::DocumentIsDirty.into()), + tool_abort: Some(GradientToolMessage::Abort.into()), + selection_changed: None, + } + } +} + #[derive(Clone, Debug, Default)] struct GradientToolData { gradient_overlays: Vec, @@ -332,7 +354,7 @@ impl Fsm for GradientToolFsmState { self } (GradientToolFsmState::Ready, GradientToolMessage::PointerDown) => { - responses.push_back(ToolMessage::DocumentIsDirty.into()); + responses.push_back(BroadcastSignal::DocumentIsDirty.into()); let mouse = input.mouse.position; let tolerance = VECTOR_MANIPULATOR_ANCHOR_MARKER_SIZE.powi(2); diff --git a/editor/src/viewport_tools/tools/line_tool.rs b/editor/src/viewport_tools/tools/line_tool.rs index 905b190f..1a85149c 100644 --- a/editor/src/viewport_tools/tools/line_tool.rs +++ b/editor/src/viewport_tools/tools/line_tool.rs @@ -6,7 +6,7 @@ use crate::layout::widgets::{Layout, LayoutGroup, NumberInput, PropertyHolder, W use crate::message_prelude::*; use crate::misc::{HintData, HintGroup, HintInfo, KeysGroup}; use crate::viewport_tools::snapping::SnapHandler; -use crate::viewport_tools::tool::{Fsm, ToolActionHandlerData}; +use crate::viewport_tools::tool::{Fsm, SignalToMessageMap, ToolActionHandlerData, ToolMetadata, ToolTransition, ToolType}; use graphene::layers::style; use graphene::Operation; @@ -56,6 +56,18 @@ pub enum LineOptionsUpdate { LineWeight(f64), } +impl ToolMetadata for LineTool { + fn icon_name(&self) -> String { + "VectorLineTool".into() + } + fn tooltip(&self) -> String { + "Line Tool (L)".into() + } + fn tool_type(&self) -> crate::viewport_tools::tool::ToolType { + ToolType::Line + } +} + impl PropertyHolder for LineTool { fn properties(&self) -> Layout { Layout::WidgetLayout(WidgetLayout::new(vec![LayoutGroup::Row { @@ -110,6 +122,16 @@ impl<'a> MessageHandler> for LineTool { } } +impl ToolTransition for LineTool { + fn signal_to_message_map(&self) -> SignalToMessageMap { + SignalToMessageMap { + document_dirty: None, + tool_abort: Some(LineToolMessage::Abort.into()), + selection_changed: None, + } + } +} + #[derive(Clone, Copy, Debug, PartialEq, Eq)] enum LineToolFsmState { Ready, diff --git a/editor/src/viewport_tools/tools/navigate_tool.rs b/editor/src/viewport_tools/tools/navigate_tool.rs index a277f8be..20d970de 100644 --- a/editor/src/viewport_tools/tools/navigate_tool.rs +++ b/editor/src/viewport_tools/tools/navigate_tool.rs @@ -3,7 +3,7 @@ use crate::input::keyboard::{Key, MouseMotion}; use crate::layout::widgets::PropertyHolder; use crate::message_prelude::*; use crate::misc::{HintData, HintGroup, HintInfo, KeysGroup}; -use crate::viewport_tools::tool::{Fsm, ToolActionHandlerData}; +use crate::viewport_tools::tool::{Fsm, SignalToMessageMap, ToolActionHandlerData, ToolMetadata, ToolTransition, ToolType}; use glam::DVec2; use serde::{Deserialize, Serialize}; @@ -36,6 +36,18 @@ pub enum NavigateToolMessage { ZoomCanvasBegin, } +impl ToolMetadata for NavigateTool { + fn icon_name(&self) -> String { + "GeneralNavigateTool".into() + } + fn tooltip(&self) -> String { + "Navigate Tool (Z)".into() + } + fn tool_type(&self) -> crate::viewport_tools::tool::ToolType { + ToolType::Navigate + } +} + impl PropertyHolder for NavigateTool {} impl<'a> MessageHandler> for NavigateTool { @@ -69,6 +81,16 @@ impl<'a> MessageHandler> for NavigateTool } } +impl ToolTransition for NavigateTool { + fn signal_to_message_map(&self) -> SignalToMessageMap { + SignalToMessageMap { + document_dirty: None, + tool_abort: Some(NavigateToolMessage::Abort.into()), + selection_changed: None, + } + } +} + #[derive(Clone, Copy, Debug, PartialEq, Eq)] enum NavigateToolFsmState { Ready, diff --git a/editor/src/viewport_tools/tools/path_tool.rs b/editor/src/viewport_tools/tools/path_tool.rs index 5b0e626f..eeb8667e 100644 --- a/editor/src/viewport_tools/tools/path_tool.rs +++ b/editor/src/viewport_tools/tools/path_tool.rs @@ -5,7 +5,7 @@ use crate::layout::widgets::PropertyHolder; use crate::message_prelude::*; use crate::misc::{HintData, HintGroup, HintInfo, KeysGroup}; use crate::viewport_tools::snapping::SnapHandler; -use crate::viewport_tools::tool::{Fsm, ToolActionHandlerData}; +use crate::viewport_tools::tool::{Fsm, SignalToMessageMap, ToolActionHandlerData, ToolMetadata, ToolTransition, ToolType}; use crate::viewport_tools::vector_editor::shape_editor::ShapeEditor; use graphene::intersection::Quad; @@ -42,6 +42,18 @@ pub enum PathToolMessage { }, } +impl ToolMetadata for PathTool { + fn icon_name(&self) -> String { + "VectorPathTool".into() + } + fn tooltip(&self) -> String { + "Path Tool (A)".into() + } + fn tool_type(&self) -> crate::viewport_tools::tool::ToolType { + ToolType::Path + } +} + impl PropertyHolder for PathTool {} impl<'a> MessageHandler> for PathTool { @@ -76,6 +88,16 @@ impl<'a> MessageHandler> for PathTool { } } +impl ToolTransition for PathTool { + fn signal_to_message_map(&self) -> SignalToMessageMap { + SignalToMessageMap { + document_dirty: Some(PathToolMessage::DocumentIsDirty.into()), + tool_abort: Some(PathToolMessage::Abort.into()), + selection_changed: Some(PathToolMessage::SelectionChanged.into()), + } + } +} + #[derive(Clone, Copy, Debug, PartialEq, Eq)] enum PathToolFsmState { Ready, diff --git a/editor/src/viewport_tools/tools/pen_tool.rs b/editor/src/viewport_tools/tools/pen_tool.rs index 8e106ab8..0b85fe02 100644 --- a/editor/src/viewport_tools/tools/pen_tool.rs +++ b/editor/src/viewport_tools/tools/pen_tool.rs @@ -7,7 +7,7 @@ use crate::layout::widgets::{Layout, LayoutGroup, NumberInput, PropertyHolder, W use crate::message_prelude::*; use crate::misc::{HintData, HintGroup, HintInfo, KeysGroup}; use crate::viewport_tools::snapping::SnapHandler; -use crate::viewport_tools::tool::{Fsm, ToolActionHandlerData}; +use crate::viewport_tools::tool::{Fsm, SignalToMessageMap, ToolActionHandlerData, ToolMetadata, ToolTransition, ToolType}; use crate::viewport_tools::vector_editor::constants::ControlPointType; use crate::viewport_tools::vector_editor::shape_editor::ShapeEditor; use crate::viewport_tools::vector_editor::vector_shape::VectorShape; @@ -67,6 +67,18 @@ pub enum PenOptionsUpdate { LineWeight(f64), } +impl ToolMetadata for PenTool { + fn icon_name(&self) -> String { + "VectorPenTool".into() + } + fn tooltip(&self) -> String { + "Pen Tool (P)".into() + } + fn tool_type(&self) -> crate::viewport_tools::tool::ToolType { + ToolType::Pen + } +} + impl PropertyHolder for PenTool { fn properties(&self) -> Layout { Layout::WidgetLayout(WidgetLayout::new(vec![LayoutGroup::Row { @@ -121,6 +133,16 @@ impl<'a> MessageHandler> for PenTool { } } +impl ToolTransition for PenTool { + fn signal_to_message_map(&self) -> SignalToMessageMap { + SignalToMessageMap { + document_dirty: Some(PenToolMessage::DocumentIsDirty.into()), + tool_abort: Some(PenToolMessage::Abort.into()), + selection_changed: None, + } + } +} + impl Default for PenToolFsmState { fn default() -> Self { PenToolFsmState::Ready diff --git a/editor/src/viewport_tools/tools/rectangle_tool.rs b/editor/src/viewport_tools/tools/rectangle_tool.rs index a19d6144..4de0cbbb 100644 --- a/editor/src/viewport_tools/tools/rectangle_tool.rs +++ b/editor/src/viewport_tools/tools/rectangle_tool.rs @@ -5,7 +5,7 @@ use crate::input::keyboard::{Key, MouseMotion}; use crate::layout::widgets::PropertyHolder; use crate::message_prelude::*; use crate::misc::{HintData, HintGroup, HintInfo, KeysGroup}; -use crate::viewport_tools::tool::{Fsm, ToolActionHandlerData}; +use crate::viewport_tools::tool::{Fsm, SignalToMessageMap, ToolActionHandlerData, ToolMetadata, ToolTransition, ToolType}; use graphene::layers::style; use graphene::Operation; @@ -69,6 +69,28 @@ impl<'a> MessageHandler> for RectangleToo } } +impl ToolMetadata for RectangleTool { + fn icon_name(&self) -> String { + "VectorRectangleTool".into() + } + fn tooltip(&self) -> String { + "Rectangle Tool (M)".into() + } + fn tool_type(&self) -> crate::viewport_tools::tool::ToolType { + ToolType::Rectangle + } +} + +impl ToolTransition for RectangleTool { + fn signal_to_message_map(&self) -> SignalToMessageMap { + SignalToMessageMap { + document_dirty: None, + tool_abort: Some(RectangleToolMessage::Abort.into()), + selection_changed: None, + } + } +} + #[derive(Clone, Copy, Debug, PartialEq, Eq)] enum RectangleToolFsmState { Ready, diff --git a/editor/src/viewport_tools/tools/select_tool.rs b/editor/src/viewport_tools/tools/select_tool.rs index e3f18f5b..7cf8bd27 100644 --- a/editor/src/viewport_tools/tools/select_tool.rs +++ b/editor/src/viewport_tools/tools/select_tool.rs @@ -8,7 +8,7 @@ use crate::layout::widgets::{IconButton, Layout, LayoutGroup, PopoverButton, Pro use crate::message_prelude::*; use crate::misc::{HintData, HintGroup, HintInfo, KeysGroup}; use crate::viewport_tools::snapping::{self, SnapHandler}; -use crate::viewport_tools::tool::{Fsm, ToolActionHandlerData, ToolType}; +use crate::viewport_tools::tool::{Fsm, SignalToMessageMap, ToolActionHandlerData, ToolMetadata, ToolTransition, ToolType}; use graphene::boolean_ops::BooleanOperation; use graphene::document::Document; use graphene::intersection::Quad; @@ -56,6 +56,18 @@ pub enum SelectToolMessage { }, } +impl ToolMetadata for SelectTool { + fn icon_name(&self) -> String { + "GeneralSelectTool".into() + } + fn tooltip(&self) -> String { + "Select Tool (V)".into() + } + fn tool_type(&self) -> crate::viewport_tools::tool::ToolType { + ToolType::Select + } +} + impl PropertyHolder for SelectTool { fn properties(&self) -> Layout { Layout::WidgetLayout(WidgetLayout::new(vec![LayoutGroup::Row { @@ -258,6 +270,16 @@ impl<'a> MessageHandler> for SelectTool { } } +impl ToolTransition for SelectTool { + fn signal_to_message_map(&self) -> SignalToMessageMap { + SignalToMessageMap { + document_dirty: Some(SelectToolMessage::DocumentIsDirty.into()), + tool_abort: Some(SelectToolMessage::Abort.into()), + selection_changed: None, + } + } +} + #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] enum SelectToolFsmState { Ready, @@ -317,6 +339,7 @@ impl Fsm for SelectToolFsmState { use SelectToolMessage::*; if let ToolMessage::Select(event) = event { + log::debug!("self: {:?}, even: {:?}", self, event); match (self, event) { (_, DocumentIsDirty) => { let mut buffer = Vec::new(); diff --git a/editor/src/viewport_tools/tools/shape_tool.rs b/editor/src/viewport_tools/tools/shape_tool.rs index 97b29b43..0ac1f4d6 100644 --- a/editor/src/viewport_tools/tools/shape_tool.rs +++ b/editor/src/viewport_tools/tools/shape_tool.rs @@ -5,7 +5,7 @@ use crate::input::keyboard::{Key, MouseMotion}; use crate::layout::widgets::{Layout, LayoutGroup, NumberInput, PropertyHolder, Widget, WidgetCallback, WidgetHolder, WidgetLayout}; use crate::message_prelude::*; use crate::misc::{HintData, HintGroup, HintInfo, KeysGroup}; -use crate::viewport_tools::tool::{Fsm, ToolActionHandlerData}; +use crate::viewport_tools::tool::{Fsm, SignalToMessageMap, ToolActionHandlerData, ToolMetadata, ToolTransition, ToolType}; use graphene::layers::style; use graphene::Operation; @@ -54,6 +54,18 @@ pub enum ShapeOptionsUpdate { Vertices(u32), } +impl ToolMetadata for ShapeTool { + fn icon_name(&self) -> String { + "VectorShapeTool".into() + } + fn tooltip(&self) -> String { + "Shape Tool (Y)".into() + } + fn tool_type(&self) -> crate::viewport_tools::tool::ToolType { + ToolType::Shape + } +} + impl PropertyHolder for ShapeTool { fn properties(&self) -> Layout { Layout::WidgetLayout(WidgetLayout::new(vec![LayoutGroup::Row { @@ -108,6 +120,16 @@ impl<'a> MessageHandler> for ShapeTool { } } +impl ToolTransition for ShapeTool { + fn signal_to_message_map(&self) -> SignalToMessageMap { + SignalToMessageMap { + document_dirty: None, + tool_abort: Some(ShapeToolMessage::Abort.into()), + selection_changed: None, + } + } +} + #[derive(Clone, Copy, Debug, PartialEq, Eq)] enum ShapeToolFsmState { Ready, diff --git a/editor/src/viewport_tools/tools/spline_tool.rs b/editor/src/viewport_tools/tools/spline_tool.rs index 8bd5d04a..0bac70b0 100644 --- a/editor/src/viewport_tools/tools/spline_tool.rs +++ b/editor/src/viewport_tools/tools/spline_tool.rs @@ -5,7 +5,7 @@ use crate::layout::widgets::{Layout, LayoutGroup, NumberInput, PropertyHolder, W use crate::message_prelude::*; use crate::misc::{HintData, HintGroup, HintInfo, KeysGroup}; use crate::viewport_tools::snapping::SnapHandler; -use crate::viewport_tools::tool::{DocumentToolData, Fsm, ToolActionHandlerData}; +use crate::viewport_tools::tool::{DocumentToolData, Fsm, SignalToMessageMap, ToolActionHandlerData, ToolMetadata, ToolTransition, ToolType}; use graphene::layers::style; use graphene::Operation; @@ -59,6 +59,18 @@ pub enum SplineOptionsUpdate { LineWeight(f64), } +impl ToolMetadata for SplineTool { + fn icon_name(&self) -> String { + "VectorSplineTool".into() + } + fn tooltip(&self) -> String { + "Spline Tool".into() + } + fn tool_type(&self) -> crate::viewport_tools::tool::ToolType { + ToolType::Spline + } +} + impl PropertyHolder for SplineTool { fn properties(&self) -> Layout { Layout::WidgetLayout(WidgetLayout::new(vec![LayoutGroup::Row { @@ -113,6 +125,16 @@ impl<'a> MessageHandler> for SplineTool { } } +impl ToolTransition for SplineTool { + fn signal_to_message_map(&self) -> SignalToMessageMap { + SignalToMessageMap { + document_dirty: None, + tool_abort: Some(SplineToolMessage::Abort.into()), + selection_changed: None, + } + } +} + impl Default for SplineToolFsmState { fn default() -> Self { SplineToolFsmState::Ready diff --git a/editor/src/viewport_tools/tools/text_tool.rs b/editor/src/viewport_tools/tools/text_tool.rs index 97fd5dc2..fa91ba09 100644 --- a/editor/src/viewport_tools/tools/text_tool.rs +++ b/editor/src/viewport_tools/tools/text_tool.rs @@ -6,7 +6,7 @@ use crate::layout::layout_message::LayoutTarget; use crate::layout::widgets::{FontInput, Layout, LayoutGroup, NumberInput, PropertyHolder, Separator, SeparatorDirection, SeparatorType, Widget, WidgetCallback, WidgetHolder, WidgetLayout}; use crate::message_prelude::*; use crate::misc::{HintData, HintGroup, HintInfo, KeysGroup}; -use crate::viewport_tools::tool::{Fsm, ToolActionHandlerData}; +use crate::viewport_tools::tool::{Fsm, SignalToMessageMap, ToolActionHandlerData, ToolMetadata, ToolTransition, ToolType}; use graphene::intersection::Quad; use graphene::layers::style::{self, Fill, Stroke}; @@ -70,6 +70,18 @@ pub enum TextOptionsUpdate { FontSize(u32), } +impl ToolMetadata for TextTool { + fn icon_name(&self) -> String { + "VectorTextTool".into() + } + fn tooltip(&self) -> String { + "Text Tool (T)".into() + } + fn tool_type(&self) -> crate::viewport_tools::tool::ToolType { + ToolType::Text + } +} + impl PropertyHolder for TextTool { fn properties(&self) -> Layout { Layout::WidgetLayout(WidgetLayout::new(vec![LayoutGroup::Row { @@ -164,6 +176,16 @@ impl<'a> MessageHandler> for TextTool { } } +impl ToolTransition for TextTool { + fn signal_to_message_map(&self) -> SignalToMessageMap { + SignalToMessageMap { + document_dirty: Some(TextMessage::DocumentIsDirty.into()), + tool_abort: Some(TextMessage::Abort.into()), + selection_changed: None, + } + } +} + #[derive(Clone, Copy, Debug, PartialEq, Eq)] enum TextToolFsmState { Ready, diff --git a/frontend/wasm/src/editor_api.rs b/frontend/wasm/src/editor_api.rs index eee3e508..dc567cf4 100644 --- a/frontend/wasm/src/editor_api.rs +++ b/frontend/wasm/src/editor_api.rs @@ -112,6 +112,9 @@ impl JsEditorHandle { let message = PortfolioMessage::UpdateDocumentWidgets; self.dispatch(message); + let message = PropertiesPanelMessage::Init; + self.dispatch(message); + let message = ToolMessage::InitTools; self.dispatch(message);