From 0a03aec34311bf7013511eb6255204743b2836f5 Mon Sep 17 00:00:00 2001 From: 0HyperCube <78500760+0HyperCube@users.noreply.github.com> Date: Sat, 28 Jan 2023 08:38:52 +0000 Subject: [PATCH] Reduce tool code duplication and boilerplate (#993) * Reduce tool code duplication * Add doc comments * Fix Mac key variant, which can't be auto-converted without false positives * Rename "key" to "keys" --------- Co-authored-by: Keavon Chambers --- .../utility_types/input_keyboard.rs | 4 + .../input_mapper/utility_types/input_mouse.rs | 11 + .../utility_types/widgets/assist_widgets.rs | 4 +- .../tool/tool_messages/artboard_tool.rs | 92 +---- .../tool/tool_messages/ellipse_tool.rs | 78 +--- .../tool/tool_messages/eyedropper_tool.rs | 53 +-- .../messages/tool/tool_messages/fill_tool.rs | 43 +- .../tool/tool_messages/freehand_tool.rs | 54 +-- .../tool/tool_messages/gradient_tool.rs | 103 ++--- .../tool/tool_messages/imaginate_tool.rs | 78 +--- .../messages/tool/tool_messages/line_tool.rs | 161 ++------ .../tool/tool_messages/navigate_tool.rs | 104 +---- .../tool_messages/node_graph_frame_tool.rs | 76 +--- .../messages/tool/tool_messages/path_tool.rs | 114 +----- .../messages/tool/tool_messages/pen_tool.rs | 96 +---- .../tool/tool_messages/rectangle_tool.rs | 76 +--- .../tool/tool_messages/select_tool.rs | 374 +++++------------- .../messages/tool/tool_messages/shape_tool.rs | 98 +---- .../tool/tool_messages/spline_tool.rs | 72 +--- .../messages/tool/tool_messages/text_tool.rs | 147 +++---- editor/src/messages/tool/utility_types.rs | 115 +++++- 21 files changed, 480 insertions(+), 1473 deletions(-) diff --git a/editor/src/messages/input_mapper/utility_types/input_keyboard.rs b/editor/src/messages/input_mapper/utility_types/input_keyboard.rs index 800130d0c7..2ad42a9fe7 100644 --- a/editor/src/messages/input_mapper/utility_types/input_keyboard.rs +++ b/editor/src/messages/input_mapper/utility_types/input_keyboard.rs @@ -423,6 +423,10 @@ impl BitVector { (self.0[offset] & bit) != 0 } + pub fn key(&self, key: Key) -> bool { + self.get(key as usize) + } + pub fn is_empty(&self) -> bool { let mut result = 0; diff --git a/editor/src/messages/input_mapper/utility_types/input_mouse.rs b/editor/src/messages/input_mapper/utility_types/input_mouse.rs index 23692c3ead..ea9dc5b50d 100644 --- a/editor/src/messages/input_mapper/utility_types/input_mouse.rs +++ b/editor/src/messages/input_mapper/utility_types/input_mouse.rs @@ -1,6 +1,10 @@ +use crate::consts::DRAG_THRESHOLD; +use crate::messages::prelude::*; + use bitflags::bitflags; use glam::DVec2; use serde::{Deserialize, Serialize}; +use std::collections::VecDeque; // Origin is top left pub type ViewportPosition = DVec2; @@ -85,6 +89,13 @@ impl MouseState { scroll_delta: ScrollDelta::default(), } } + + pub fn finish_transaction(&self, drag_start: DVec2, responses: &mut VecDeque) { + match drag_start.distance(self.position) <= DRAG_THRESHOLD { + true => responses.push_back(DocumentMessage::AbortTransaction.into()), + false => responses.push_back(DocumentMessage::CommitTransaction.into()), + } + } } #[derive(Debug, Copy, Clone, Default, PartialEq, Serialize, Deserialize)] diff --git a/editor/src/messages/layout/utility_types/widgets/assist_widgets.rs b/editor/src/messages/layout/utility_types/widgets/assist_widgets.rs index 9da6279f69..5b884ad770 100644 --- a/editor/src/messages/layout/utility_types/widgets/assist_widgets.rs +++ b/editor/src/messages/layout/utility_types/widgets/assist_widgets.rs @@ -1,12 +1,14 @@ use crate::messages::layout::utility_types::layout_widget::WidgetCallback; +use graphite_proc_macros::WidgetBuilder; use derivative::*; use glam::DVec2; use serde::{Deserialize, Serialize}; -#[derive(Clone, Default, Derivative, Serialize, Deserialize)] +#[derive(Clone, Default, Derivative, Serialize, Deserialize, WidgetBuilder)] #[derivative(Debug, PartialEq)] pub struct PivotAssist { + #[widget_builder(constructor)] pub position: PivotPosition, pub disabled: bool, diff --git a/editor/src/messages/tool/tool_messages/artboard_tool.rs b/editor/src/messages/tool/tool_messages/artboard_tool.rs index 5b503aa21c..b588dfa720 100644 --- a/editor/src/messages/tool/tool_messages/artboard_tool.rs +++ b/editor/src/messages/tool/tool_messages/artboard_tool.rs @@ -1,7 +1,7 @@ use crate::application::generate_uuid; use crate::consts::SELECTION_TOLERANCE; use crate::messages::frontend::utility_types::MouseCursorIcon; -use crate::messages::input_mapper::utility_types::input_keyboard::{Key, KeysGroup, MouseMotion}; +use crate::messages::input_mapper::utility_types::input_keyboard::{Key, MouseMotion}; use crate::messages::layout::utility_types::layout_widget::PropertyHolder; use crate::messages::portfolio::document::utility_types::misc::TargetDocument; use crate::messages::prelude::*; @@ -59,23 +59,8 @@ impl ToolMetadata for ArtboardTool { } impl<'a> MessageHandler> for ArtboardTool { - fn process_message(&mut self, message: ToolMessage, data: ToolActionHandlerData<'a>, responses: &mut VecDeque) { - if message == ToolMessage::UpdateHints { - self.fsm_state.update_hints(responses); - return; - } - - if message == ToolMessage::UpdateCursor { - responses.push_back(FrontendMessage::UpdateMouseCursor { cursor: MouseCursorIcon::Default }.into()); - return; - } - - let new_state = self.fsm_state.transition(message, &mut self.data, data, &(), responses); - - if self.fsm_state != new_state { - self.fsm_state = new_state; - self.fsm_state.update_hints(responses); - } + fn process_message(&mut self, message: ToolMessage, transition_data: ToolActionHandlerData<'a>, responses: &mut VecDeque) { + self.fsm_state.process_event(message, &mut self.data, transition_data, &(), responses, false); } advertise_actions!(ArtboardToolMessageDiscriminant; @@ -100,20 +85,15 @@ impl ToolTransition for ArtboardTool { } } -#[derive(Clone, Copy, Debug, PartialEq, Eq)] +#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] enum ArtboardToolFsmState { + #[default] Ready, Drawing, ResizingBounds, Dragging, } -impl Default for ArtboardToolFsmState { - fn default() -> Self { - ArtboardToolFsmState::Ready - } -} - #[derive(Clone, Debug, Default)] struct ArtboardToolData { bounding_box_overlays: Option, @@ -138,14 +118,9 @@ impl Fsm for ArtboardToolFsmState { ) -> Self { if let ToolMessage::Artboard(event) = event { match (self, event) { - (ArtboardToolFsmState::Ready | ArtboardToolFsmState::ResizingBounds | ArtboardToolFsmState::Dragging, ArtboardToolMessage::DocumentIsDirty) => { - match ( - tool_data - .selected_artboard - .map(|path| document.artboard_bounding_box_and_transform(&[path], font_cache)) - .unwrap_or(None), - tool_data.bounding_box_overlays.take(), - ) { + (state, ArtboardToolMessage::DocumentIsDirty) if state != ArtboardToolFsmState::Drawing => { + let current_artboard = tool_data.selected_artboard.and_then(|path| document.artboard_bounding_box_and_transform(&[path], font_cache)); + match (current_artboard, tool_data.bounding_box_overlays.take()) { (None, Some(bounding_box_overlays)) => bounding_box_overlays.delete(responses), (Some((bounds, transform)), paths) => { let mut bounding_box_overlays = paths.unwrap_or_else(|| BoundingBoxOverlays::new(responses)); @@ -443,51 +418,14 @@ impl Fsm for ArtboardToolFsmState { fn update_hints(&self, responses: &mut VecDeque) { let hint_data = match self { ArtboardToolFsmState::Ready => HintData(vec![ - HintGroup(vec![HintInfo { - key_groups: vec![], - key_groups_mac: None, - mouse: Some(MouseMotion::LmbDrag), - label: String::from("Draw Artboard"), - plus: false, - }]), - HintGroup(vec![HintInfo { - key_groups: vec![], - key_groups_mac: None, - mouse: Some(MouseMotion::LmbDrag), - label: String::from("Move Artboard"), - plus: false, - }]), - HintGroup(vec![HintInfo { - key_groups: vec![KeysGroup(vec![Key::Backspace]).into()], - key_groups_mac: None, - mouse: None, - label: String::from("Delete Artboard"), - plus: false, - }]), + HintGroup(vec![HintInfo::mouse(MouseMotion::LmbDrag, "Draw Artboard")]), + HintGroup(vec![HintInfo::mouse(MouseMotion::LmbDrag, "Move Artboard")]), + HintGroup(vec![HintInfo::keys([Key::Backspace], "Delete Artboard")]), ]), - ArtboardToolFsmState::Dragging => HintData(vec![HintGroup(vec![HintInfo { - key_groups: vec![KeysGroup(vec![Key::Shift]).into()], - key_groups_mac: None, - mouse: None, - label: String::from("Constrain to Axis"), - plus: false, - }])]), - ArtboardToolFsmState::Drawing | ArtboardToolFsmState::ResizingBounds => HintData(vec![HintGroup(vec![ - HintInfo { - key_groups: vec![KeysGroup(vec![Key::Shift]).into()], - key_groups_mac: None, - mouse: None, - label: String::from("Constrain Square"), - plus: false, - }, - HintInfo { - key_groups: vec![KeysGroup(vec![Key::Alt]).into()], - key_groups_mac: None, - mouse: None, - label: String::from("From Center"), - plus: false, - }, - ])]), + ArtboardToolFsmState::Dragging => HintData(vec![HintGroup(vec![HintInfo::keys([Key::Shift], "Constrain to Axis")])]), + ArtboardToolFsmState::Drawing | ArtboardToolFsmState::ResizingBounds => { + HintData(vec![HintGroup(vec![HintInfo::keys([Key::Shift], "Constrain Square"), HintInfo::keys([Key::Alt], "From Center")])]) + } }; responses.push_back(FrontendMessage::UpdateInputHints { hint_data }.into()); diff --git a/editor/src/messages/tool/tool_messages/ellipse_tool.rs b/editor/src/messages/tool/tool_messages/ellipse_tool.rs index e26b1865fd..599e392b90 100644 --- a/editor/src/messages/tool/tool_messages/ellipse_tool.rs +++ b/editor/src/messages/tool/tool_messages/ellipse_tool.rs @@ -1,6 +1,5 @@ -use crate::consts::DRAG_THRESHOLD; use crate::messages::frontend::utility_types::MouseCursorIcon; -use crate::messages::input_mapper::utility_types::input_keyboard::{Key, KeysGroup, MouseMotion}; +use crate::messages::input_mapper::utility_types::input_keyboard::{Key, MouseMotion}; use crate::messages::layout::utility_types::layout_widget::PropertyHolder; use crate::messages::prelude::*; use crate::messages::tool::common_functionality::resize::Resize; @@ -52,23 +51,7 @@ impl PropertyHolder for EllipseTool {} impl<'a> MessageHandler> for EllipseTool { fn process_message(&mut self, message: ToolMessage, data: ToolActionHandlerData<'a>, responses: &mut VecDeque) { - if message == ToolMessage::UpdateHints { - self.fsm_state.update_hints(responses); - return; - } - - if message == ToolMessage::UpdateCursor { - self.fsm_state.update_cursor(responses); - return; - } - - let new_state = self.fsm_state.transition(message, &mut self.data, data, &(), responses); - - if self.fsm_state != new_state { - self.fsm_state = new_state; - self.fsm_state.update_hints(responses); - self.fsm_state.update_cursor(responses); - } + self.fsm_state.process_event(message, &mut self.data, data, &(), responses, true); } fn actions(&self) -> ActionList { @@ -97,18 +80,13 @@ impl ToolTransition for EllipseTool { } } -#[derive(Clone, Copy, Debug, PartialEq, Eq)] +#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] enum EllipseToolFsmState { + #[default] Ready, Drawing, } -impl Default for EllipseToolFsmState { - fn default() -> Self { - EllipseToolFsmState::Ready - } -} - #[derive(Clone, Debug, Default)] struct EllipseToolData { data: Resize, @@ -159,12 +137,9 @@ impl Fsm for EllipseToolFsmState { state } (Drawing, DragStop) => { - match shape_data.viewport_drag_start(document).distance(input.mouse.position) <= DRAG_THRESHOLD { - true => responses.push_back(DocumentMessage::AbortTransaction.into()), - false => responses.push_back(DocumentMessage::CommitTransaction.into()), - } - + input.mouse.finish_transaction(shape_data.viewport_drag_start(document), responses); shape_data.cleanup(responses); + Ready } (Drawing, Abort) => { @@ -183,44 +158,11 @@ impl Fsm for EllipseToolFsmState { fn update_hints(&self, responses: &mut VecDeque) { let hint_data = match self { EllipseToolFsmState::Ready => HintData(vec![HintGroup(vec![ - HintInfo { - key_groups: vec![], - key_groups_mac: None, - mouse: Some(MouseMotion::LmbDrag), - label: String::from("Draw Ellipse"), - plus: false, - }, - HintInfo { - key_groups: vec![KeysGroup(vec![Key::Shift]).into()], - key_groups_mac: None, - mouse: None, - label: String::from("Constrain Circular"), - plus: true, - }, - HintInfo { - key_groups: vec![KeysGroup(vec![Key::Alt]).into()], - key_groups_mac: None, - mouse: None, - label: String::from("From Center"), - plus: true, - }, - ])]), - EllipseToolFsmState::Drawing => HintData(vec![HintGroup(vec![ - HintInfo { - key_groups: vec![KeysGroup(vec![Key::Shift]).into()], - key_groups_mac: None, - mouse: None, - label: String::from("Constrain Circular"), - plus: false, - }, - HintInfo { - key_groups: vec![KeysGroup(vec![Key::Alt]).into()], - key_groups_mac: None, - mouse: None, - label: String::from("From Center"), - plus: false, - }, + HintInfo::mouse(MouseMotion::LmbDrag, "Draw Ellipse"), + HintInfo::keys([Key::Shift], "Constrain Circular").prepend_plus(), + HintInfo::keys([Key::Alt], "From Center").prepend_plus(), ])]), + EllipseToolFsmState::Drawing => HintData(vec![HintGroup(vec![HintInfo::keys([Key::Shift], "Constrain Circular"), HintInfo::keys([Key::Alt], "From Center")])]), }; responses.push_back(FrontendMessage::UpdateInputHints { hint_data }.into()); diff --git a/editor/src/messages/tool/tool_messages/eyedropper_tool.rs b/editor/src/messages/tool/tool_messages/eyedropper_tool.rs index 94109d4347..f155496c0f 100644 --- a/editor/src/messages/tool/tool_messages/eyedropper_tool.rs +++ b/editor/src/messages/tool/tool_messages/eyedropper_tool.rs @@ -1,5 +1,5 @@ use crate::messages::frontend::utility_types::MouseCursorIcon; -use crate::messages::input_mapper::utility_types::input_keyboard::{Key, KeysGroup, MouseMotion}; +use crate::messages::input_mapper::utility_types::input_keyboard::{Key, MouseMotion}; use crate::messages::layout::utility_types::layout_widget::PropertyHolder; use crate::messages::prelude::*; use crate::messages::tool::utility_types::{DocumentToolData, EventToMessageMap, Fsm, ToolActionHandlerData, ToolMetadata, ToolTransition, ToolType}; @@ -45,23 +45,7 @@ impl PropertyHolder for EyedropperTool {} impl<'a> MessageHandler> for EyedropperTool { fn process_message(&mut self, message: ToolMessage, data: ToolActionHandlerData<'a>, responses: &mut VecDeque) { - if message == ToolMessage::UpdateHints { - self.fsm_state.update_hints(responses); - return; - } - - if message == ToolMessage::UpdateCursor { - self.fsm_state.update_cursor(responses); - return; - } - - let new_state = self.fsm_state.transition(message, &mut self.data, data, &(), responses); - - if self.fsm_state != new_state { - self.fsm_state = new_state; - self.fsm_state.update_hints(responses); - self.fsm_state.update_cursor(responses); - } + self.fsm_state.process_event(message, &mut self.data, data, &(), responses, true); } advertise_actions!(EyedropperToolMessageDiscriminant; @@ -84,19 +68,14 @@ impl ToolTransition for EyedropperTool { } } -#[derive(Clone, Copy, Debug, PartialEq, Eq)] +#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] enum EyedropperToolFsmState { + #[default] Ready, SamplingPrimary, SamplingSecondary, } -impl Default for EyedropperToolFsmState { - fn default() -> Self { - EyedropperToolFsmState::Ready - } -} - #[derive(Clone, Debug, Default)] struct EyedropperToolData {} @@ -162,28 +141,10 @@ impl Fsm for EyedropperToolFsmState { fn update_hints(&self, responses: &mut VecDeque) { let hint_data = match self { EyedropperToolFsmState::Ready => HintData(vec![HintGroup(vec![ - HintInfo { - key_groups: vec![], - key_groups_mac: None, - mouse: Some(MouseMotion::Lmb), - label: String::from("Sample to Primary"), - plus: false, - }, - HintInfo { - key_groups: vec![], - key_groups_mac: None, - mouse: Some(MouseMotion::Rmb), - label: String::from("Sample to Secondary"), - plus: false, - }, + HintInfo::mouse(MouseMotion::Lmb, "Sample to Primary"), + HintInfo::mouse(MouseMotion::Rmb, "Sample to Secondary"), ])]), - EyedropperToolFsmState::SamplingPrimary | EyedropperToolFsmState::SamplingSecondary => HintData(vec![HintGroup(vec![HintInfo { - key_groups: vec![KeysGroup(vec![Key::Escape]).into()], - key_groups_mac: None, - mouse: None, - label: String::from("Cancel"), - plus: false, - }])]), + EyedropperToolFsmState::SamplingPrimary | EyedropperToolFsmState::SamplingSecondary => HintData(vec![HintGroup(vec![HintInfo::keys([Key::Escape], "Cancel")])]), }; responses.push_back(FrontendMessage::UpdateInputHints { hint_data }.into()); diff --git a/editor/src/messages/tool/tool_messages/fill_tool.rs b/editor/src/messages/tool/tool_messages/fill_tool.rs index 31433f36c1..afff50f563 100644 --- a/editor/src/messages/tool/tool_messages/fill_tool.rs +++ b/editor/src/messages/tool/tool_messages/fill_tool.rs @@ -48,23 +48,7 @@ impl PropertyHolder for FillTool {} impl<'a> MessageHandler> for FillTool { fn process_message(&mut self, message: ToolMessage, data: ToolActionHandlerData<'a>, responses: &mut VecDeque) { - if message == ToolMessage::UpdateHints { - self.fsm_state.update_hints(responses); - return; - } - - if message == ToolMessage::UpdateCursor { - self.fsm_state.update_cursor(responses); - return; - } - - let new_state = self.fsm_state.transition(message, &mut self.data, data, &(), responses); - - if self.fsm_state != new_state { - self.fsm_state = new_state; - self.fsm_state.update_hints(responses); - self.fsm_state.update_cursor(responses); - } + self.fsm_state.process_event(message, &mut self.data, data, &(), responses, true); } advertise_actions!(FillToolMessageDiscriminant; @@ -83,17 +67,12 @@ impl ToolTransition for FillTool { } } -#[derive(Clone, Copy, Debug, PartialEq, Eq)] +#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] enum FillToolFsmState { + #[default] Ready, } -impl Default for FillToolFsmState { - fn default() -> Self { - FillToolFsmState::Ready - } -} - #[derive(Clone, Debug, Default)] struct FillToolData {} @@ -144,20 +123,8 @@ impl Fsm for FillToolFsmState { fn update_hints(&self, responses: &mut VecDeque) { let hint_data = match self { FillToolFsmState::Ready => HintData(vec![HintGroup(vec![ - HintInfo { - key_groups: vec![], - key_groups_mac: None, - mouse: Some(MouseMotion::Lmb), - label: String::from("Fill with Primary"), - plus: false, - }, - HintInfo { - key_groups: vec![], - key_groups_mac: None, - mouse: Some(MouseMotion::Rmb), - label: String::from("Fill with Secondary"), - plus: false, - }, + HintInfo::mouse(MouseMotion::Lmb, "Fill with Primary"), + HintInfo::mouse(MouseMotion::Rmb, "Fill with Secondary"), ])]), }; diff --git a/editor/src/messages/tool/tool_messages/freehand_tool.rs b/editor/src/messages/tool/tool_messages/freehand_tool.rs index 6ab7e3d41c..fba504fc5e 100644 --- a/editor/src/messages/tool/tool_messages/freehand_tool.rs +++ b/editor/src/messages/tool/tool_messages/freehand_tool.rs @@ -1,6 +1,6 @@ use crate::messages::frontend::utility_types::MouseCursorIcon; use crate::messages::input_mapper::utility_types::input_keyboard::MouseMotion; -use crate::messages::layout::utility_types::layout_widget::{Layout, LayoutGroup, PropertyHolder, Widget, WidgetCallback, WidgetHolder, WidgetLayout}; +use crate::messages::layout::utility_types::layout_widget::{Layout, LayoutGroup, PropertyHolder, WidgetLayout}; use crate::messages::layout::utility_types::widgets::input_widgets::NumberInput; use crate::messages::prelude::*; use crate::messages::tool::utility_types::{DocumentToolData, EventToMessageMap, Fsm, ToolActionHandlerData, ToolMetadata, ToolTransition, ToolType}; @@ -51,8 +51,9 @@ pub enum FreehandToolMessageOptionsUpdate { LineWeight(f64), } -#[derive(Clone, Copy, Debug, PartialEq, Eq)] +#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] enum FreehandToolFsmState { + #[default] Ready, Drawing, } @@ -71,32 +72,18 @@ impl ToolMetadata for FreehandTool { impl PropertyHolder for FreehandTool { fn properties(&self) -> Layout { - Layout::WidgetLayout(WidgetLayout::new(vec![LayoutGroup::Row { - widgets: vec![WidgetHolder::new(Widget::NumberInput(NumberInput { - unit: " px".into(), - label: "Weight".into(), - value: Some(self.options.line_weight as f64), - is_integer: false, - min: Some(1.), - on_update: WidgetCallback::new(|number_input: &NumberInput| FreehandToolMessage::UpdateOptions(FreehandToolMessageOptionsUpdate::LineWeight(number_input.value.unwrap())).into()), - ..NumberInput::default() - }))], - }])) + let weight = NumberInput::new(Some(self.options.line_weight)) + .unit(" px") + .label("Weight") + .min(1.) + .on_update(|number_input: &NumberInput| FreehandToolMessage::UpdateOptions(FreehandToolMessageOptionsUpdate::LineWeight(number_input.value.unwrap())).into()) + .widget_holder(); + Layout::WidgetLayout(WidgetLayout::new(vec![LayoutGroup::Row { widgets: vec![weight] }])) } } impl<'a> MessageHandler> for FreehandTool { fn process_message(&mut self, message: ToolMessage, data: ToolActionHandlerData<'a>, responses: &mut VecDeque) { - if message == ToolMessage::UpdateHints { - self.fsm_state.update_hints(responses); - return; - } - - if message == ToolMessage::UpdateCursor { - self.fsm_state.update_cursor(responses); - return; - } - if let ToolMessage::Freehand(FreehandToolMessage::UpdateOptions(action)) = message { match action { FreehandToolMessageOptionsUpdate::LineWeight(line_weight) => self.options.line_weight = line_weight, @@ -104,13 +91,7 @@ impl<'a> MessageHandler> for FreehandTool return; } - let new_state = self.fsm_state.transition(message, &mut self.data, data, &self.options, responses); - - if self.fsm_state != new_state { - self.fsm_state = new_state; - self.fsm_state.update_hints(responses); - self.fsm_state.update_cursor(responses); - } + self.fsm_state.process_event(message, &mut self.data, data, &self.options, responses, true); } fn actions(&self) -> ActionList { @@ -141,11 +122,6 @@ impl ToolTransition for FreehandTool { } } -impl Default for FreehandToolFsmState { - fn default() -> Self { - FreehandToolFsmState::Ready - } -} #[derive(Clone, Debug, Default)] struct FreehandToolData { points: Vec, @@ -222,13 +198,7 @@ impl Fsm for FreehandToolFsmState { fn update_hints(&self, responses: &mut VecDeque) { let hint_data = match self { - FreehandToolFsmState::Ready => HintData(vec![HintGroup(vec![HintInfo { - key_groups: vec![], - key_groups_mac: None, - mouse: Some(MouseMotion::LmbDrag), - label: String::from("Draw Polyline"), - plus: false, - }])]), + FreehandToolFsmState::Ready => HintData(vec![HintGroup(vec![HintInfo::mouse(MouseMotion::LmbDrag, "Draw Polyline")])]), FreehandToolFsmState::Drawing => HintData(vec![]), }; diff --git a/editor/src/messages/tool/tool_messages/gradient_tool.rs b/editor/src/messages/tool/tool_messages/gradient_tool.rs index 30a5c688cb..a7abe66b84 100644 --- a/editor/src/messages/tool/tool_messages/gradient_tool.rs +++ b/editor/src/messages/tool/tool_messages/gradient_tool.rs @@ -1,8 +1,8 @@ use crate::application::generate_uuid; -use crate::consts::{COLOR_ACCENT, DRAG_THRESHOLD, LINE_ROTATE_SNAP_ANGLE, MANIPULATOR_GROUP_MARKER_SIZE, SELECTION_THRESHOLD, SELECTION_TOLERANCE}; +use crate::consts::{COLOR_ACCENT, LINE_ROTATE_SNAP_ANGLE, MANIPULATOR_GROUP_MARKER_SIZE, SELECTION_THRESHOLD, SELECTION_TOLERANCE}; use crate::messages::frontend::utility_types::MouseCursorIcon; -use crate::messages::input_mapper::utility_types::input_keyboard::{Key, KeysGroup, MouseMotion}; -use crate::messages::layout::utility_types::layout_widget::{Layout, LayoutGroup, PropertyHolder, Widget, WidgetCallback, WidgetHolder, WidgetLayout}; +use crate::messages::input_mapper::utility_types::input_keyboard::{Key, MouseMotion}; +use crate::messages::layout::utility_types::layout_widget::{Layout, LayoutGroup, PropertyHolder, WidgetLayout}; use crate::messages::layout::utility_types::widgets::input_widgets::{RadioEntryData, RadioInput}; use crate::messages::prelude::*; use crate::messages::tool::common_functionality::snapping::SnapManager; @@ -78,15 +78,6 @@ impl ToolMetadata for GradientTool { impl<'a> MessageHandler> for GradientTool { fn process_message(&mut self, message: ToolMessage, data: ToolActionHandlerData<'a>, responses: &mut VecDeque) { - if message == ToolMessage::UpdateHints { - self.fsm_state.update_hints(responses); - return; - } - - if message == ToolMessage::UpdateCursor { - self.fsm_state.update_cursor(responses); - return; - } if let ToolMessage::Gradient(GradientToolMessage::UpdateOptions(action)) = message { match action { GradientOptionsUpdate::Type(gradient_type) => { @@ -100,12 +91,7 @@ impl<'a> MessageHandler> for GradientTool return; } - let new_state = self.fsm_state.transition(message, &mut self.data, data, &self.options, responses); - - if self.fsm_state != new_state { - self.fsm_state = new_state; - self.fsm_state.update_hints(responses); - } + self.fsm_state.process_event(message, &mut self.data, data, &self.options, responses, false); } advertise_actions!(GradientToolMessageDiscriminant; @@ -120,47 +106,29 @@ impl<'a> MessageHandler> for GradientTool impl PropertyHolder for GradientTool { fn properties(&self) -> Layout { - Layout::WidgetLayout(WidgetLayout::new(vec![LayoutGroup::Row { - widgets: vec![WidgetHolder::new(Widget::RadioInput(RadioInput { - selected_index: if self.selected_gradient().unwrap_or(self.options.gradient_type) == GradientType::Radial { - 1 - } else { - 0 - }, - entries: vec![ - RadioEntryData { - value: "linear".into(), - label: "Linear".into(), - tooltip: "Linear Gradient".into(), - on_update: WidgetCallback::new(move |_| GradientToolMessage::UpdateOptions(GradientOptionsUpdate::Type(GradientType::Linear)).into()), - ..RadioEntryData::default() - }, - RadioEntryData { - value: "radial".into(), - label: "Radial".into(), - tooltip: "Radial Gradient".into(), - on_update: WidgetCallback::new(move |_| GradientToolMessage::UpdateOptions(GradientOptionsUpdate::Type(GradientType::Radial)).into()), - ..RadioEntryData::default() - }, - ], - ..Default::default() - }))], - }])) + let gradient_type = RadioInput::new(vec![ + RadioEntryData::new("Linear") + .value("linear") + .tooltip("Linear Gradient") + .on_update(move |_| GradientToolMessage::UpdateOptions(GradientOptionsUpdate::Type(GradientType::Linear)).into()), + RadioEntryData::new("Radial") + .value("radial") + .tooltip("Radial Gradient") + .on_update(move |_| GradientToolMessage::UpdateOptions(GradientOptionsUpdate::Type(GradientType::Radial)).into()), + ]) + .selected_index((self.selected_gradient().unwrap_or(self.options.gradient_type) == GradientType::Radial) as u32) + .widget_holder(); + Layout::WidgetLayout(WidgetLayout::new(vec![LayoutGroup::Row { widgets: vec![gradient_type] }])) } } -#[derive(Clone, Copy, Debug, PartialEq, Eq)] +#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] enum GradientToolFsmState { + #[default] Ready, Drawing, } -impl Default for GradientToolFsmState { - fn default() -> Self { - GradientToolFsmState::Ready - } -} - /// Computes the transform from gradient space to layer space (where gradient space is 0..1 in layer space) fn gradient_space_transform(path: &[LayerId], layer: &Layer, document: &DocumentMessageHandler, font_cache: &FontCache) -> DAffine2 { let bounds = layer.aabb_for_transform(DAffine2::IDENTITY, font_cache).unwrap(); @@ -638,14 +606,7 @@ impl Fsm for GradientToolFsmState { } (GradientToolFsmState::Drawing, GradientToolMessage::PointerUp) => { - match tool_data.drag_start.distance(input.mouse.position) <= DRAG_THRESHOLD { - true => { - responses.push_back(DocumentMessage::AbortTransaction.into()); - responses.push_back(GradientToolMessage::DocumentIsDirty.into()); - } - false => responses.push_back(DocumentMessage::CommitTransaction.into()), - } - + input.mouse.finish_transaction(tool_data.drag_start, responses); tool_data.snap_manager.cleanup(responses); GradientToolFsmState::Ready @@ -669,28 +630,10 @@ impl Fsm for GradientToolFsmState { fn update_hints(&self, responses: &mut VecDeque) { let hint_data = match self { GradientToolFsmState::Ready => HintData(vec![HintGroup(vec![ - HintInfo { - key_groups: vec![], - key_groups_mac: None, - mouse: Some(MouseMotion::LmbDrag), - label: String::from("Draw Gradient"), - plus: false, - }, - HintInfo { - key_groups: vec![KeysGroup(vec![Key::Shift]).into()], - key_groups_mac: None, - mouse: None, - label: String::from("Snap 15°"), - plus: true, - }, + HintInfo::mouse(MouseMotion::LmbDrag, "Draw Gradient"), + HintInfo::keys([Key::Shift], "Snap 15°").prepend_plus(), ])]), - GradientToolFsmState::Drawing => HintData(vec![HintGroup(vec![HintInfo { - key_groups: vec![KeysGroup(vec![Key::Shift]).into()], - key_groups_mac: None, - mouse: None, - label: String::from("Snap 15°"), - plus: false, - }])]), + GradientToolFsmState::Drawing => HintData(vec![HintGroup(vec![HintInfo::keys([Key::Shift], "Snap 15°")])]), }; responses.push_back(FrontendMessage::UpdateInputHints { hint_data }.into()); diff --git a/editor/src/messages/tool/tool_messages/imaginate_tool.rs b/editor/src/messages/tool/tool_messages/imaginate_tool.rs index aa67ea93fb..b16b3f42f5 100644 --- a/editor/src/messages/tool/tool_messages/imaginate_tool.rs +++ b/editor/src/messages/tool/tool_messages/imaginate_tool.rs @@ -1,8 +1,7 @@ -use crate::consts::DRAG_THRESHOLD; use crate::messages::frontend::utility_types::MouseCursorIcon; -use crate::messages::input_mapper::utility_types::input_keyboard::{Key, KeysGroup, MouseMotion}; +use crate::messages::input_mapper::utility_types::input_keyboard::{Key, MouseMotion}; use crate::messages::layout::utility_types::layout_widget::PropertyHolder; -use crate::messages::portfolio::document::node_graph::{NodeImplementation, IMAGINATE_NODE}; +use crate::messages::portfolio::document::node_graph::IMAGINATE_NODE; use crate::messages::prelude::*; use crate::messages::tool::common_functionality::resize::Resize; use crate::messages::tool::utility_types::{EventToMessageMap, Fsm, ToolActionHandlerData, ToolMetadata, ToolTransition, ToolType}; @@ -40,23 +39,7 @@ impl PropertyHolder for ImaginateTool {} impl<'a> MessageHandler> for ImaginateTool { fn process_message(&mut self, message: ToolMessage, tool_data: ToolActionHandlerData<'a>, responses: &mut VecDeque) { - if message == ToolMessage::UpdateHints { - self.fsm_state.update_hints(responses); - return; - } - - if message == ToolMessage::UpdateCursor { - self.fsm_state.update_cursor(responses); - return; - } - - let new_state = self.fsm_state.transition(message, &mut self.tool_data, tool_data, &(), responses); - - if self.fsm_state != new_state { - self.fsm_state = new_state; - self.fsm_state.update_hints(responses); - self.fsm_state.update_cursor(responses); - } + self.fsm_state.process_event(message, &mut self.tool_data, tool_data, &(), responses, true); } fn actions(&self) -> ActionList { @@ -97,17 +80,13 @@ impl ToolTransition for ImaginateTool { } } -#[derive(Clone, Copy, Debug, PartialEq, Eq)] +#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] enum ImaginateToolFsmState { + #[default] Ready, Drawing, } -impl Default for ImaginateToolFsmState { - fn default() -> Self { - ImaginateToolFsmState::Ready - } -} #[derive(Clone, Debug, Default)] struct ImaginateToolData { data: Resize, @@ -178,11 +157,7 @@ impl Fsm for ImaginateToolFsmState { state } (Drawing, DragStop) => { - match shape_data.viewport_drag_start(document).distance(input.mouse.position) <= DRAG_THRESHOLD { - true => responses.push_back(DocumentMessage::AbortTransaction.into()), - false => responses.push_back(DocumentMessage::CommitTransaction.into()), - } - + input.mouse.finish_transaction(shape_data.viewport_drag_start(document), responses); responses.push_back(NodeGraphMessage::SetDrawing { new_drawing: false }.into()); shape_data.cleanup(responses); @@ -207,44 +182,11 @@ impl Fsm for ImaginateToolFsmState { fn update_hints(&self, responses: &mut VecDeque) { let hint_data = match self { ImaginateToolFsmState::Ready => HintData(vec![HintGroup(vec![ - HintInfo { - key_groups: vec![], - key_groups_mac: None, - mouse: Some(MouseMotion::LmbDrag), - label: String::from("Draw Repaint Frame"), - plus: false, - }, - HintInfo { - key_groups: vec![KeysGroup(vec![Key::Shift]).into()], - key_groups_mac: None, - mouse: None, - label: String::from("Constrain Square"), - plus: true, - }, - HintInfo { - key_groups: vec![KeysGroup(vec![Key::Alt]).into()], - key_groups_mac: None, - mouse: None, - label: String::from("From Center"), - plus: true, - }, - ])]), - ImaginateToolFsmState::Drawing => HintData(vec![HintGroup(vec![ - HintInfo { - key_groups: vec![KeysGroup(vec![Key::Shift]).into()], - key_groups_mac: None, - mouse: None, - label: String::from("Constrain Square"), - plus: false, - }, - HintInfo { - key_groups: vec![KeysGroup(vec![Key::Alt]).into()], - key_groups_mac: None, - mouse: None, - label: String::from("From Center"), - plus: false, - }, + HintInfo::mouse(MouseMotion::LmbDrag, "Draw Repaint Frame"), + HintInfo::keys([Key::Shift], "Constrain Square").prepend_plus(), + HintInfo::keys([Key::Alt], "From Center").prepend_plus(), ])]), + ImaginateToolFsmState::Drawing => HintData(vec![HintGroup(vec![HintInfo::keys([Key::Shift], "Constrain Square"), HintInfo::keys([Key::Alt], "From Center")])]), }; responses.push_back(FrontendMessage::UpdateInputHints { hint_data }.into()); diff --git a/editor/src/messages/tool/tool_messages/line_tool.rs b/editor/src/messages/tool/tool_messages/line_tool.rs index 984eeaaf42..d387ad3a55 100644 --- a/editor/src/messages/tool/tool_messages/line_tool.rs +++ b/editor/src/messages/tool/tool_messages/line_tool.rs @@ -1,8 +1,8 @@ -use crate::consts::{DRAG_THRESHOLD, LINE_ROTATE_SNAP_ANGLE}; +use crate::consts::LINE_ROTATE_SNAP_ANGLE; use crate::messages::frontend::utility_types::MouseCursorIcon; -use crate::messages::input_mapper::utility_types::input_keyboard::{Key, KeysGroup, MouseMotion}; +use crate::messages::input_mapper::utility_types::input_keyboard::{Key, MouseMotion}; use crate::messages::input_mapper::utility_types::input_mouse::ViewportPosition; -use crate::messages::layout::utility_types::layout_widget::{Layout, LayoutGroup, PropertyHolder, Widget, WidgetCallback, WidgetHolder, WidgetLayout}; +use crate::messages::layout::utility_types::layout_widget::{Layout, LayoutGroup, PropertyHolder, WidgetLayout}; use crate::messages::layout::utility_types::widgets::input_widgets::NumberInput; use crate::messages::prelude::*; use crate::messages::tool::common_functionality::snapping::SnapManager; @@ -72,32 +72,18 @@ impl ToolMetadata for LineTool { impl PropertyHolder for LineTool { fn properties(&self) -> Layout { - Layout::WidgetLayout(WidgetLayout::new(vec![LayoutGroup::Row { - widgets: vec![WidgetHolder::new(Widget::NumberInput(NumberInput { - unit: " px".into(), - label: "Weight".into(), - value: Some(self.options.line_weight as f64), - is_integer: false, - min: Some(0.), - on_update: WidgetCallback::new(|number_input: &NumberInput| LineToolMessage::UpdateOptions(LineOptionsUpdate::LineWeight(number_input.value.unwrap())).into()), - ..NumberInput::default() - }))], - }])) + let weight = NumberInput::new(Some(self.options.line_weight)) + .unit(" px") + .label("Weight") + .min(0.) + .on_update(|number_input: &NumberInput| LineToolMessage::UpdateOptions(LineOptionsUpdate::LineWeight(number_input.value.unwrap())).into()) + .widget_holder(); + Layout::WidgetLayout(WidgetLayout::new(vec![LayoutGroup::Row { widgets: vec![weight] }])) } } impl<'a> MessageHandler> for LineTool { - fn process_message(&mut self, message: ToolMessage, tool_data: ToolActionHandlerData<'a>, responses: &mut VecDeque) { - if message == ToolMessage::UpdateHints { - self.fsm_state.update_hints(responses); - return; - } - - if message == ToolMessage::UpdateCursor { - self.fsm_state.update_cursor(responses); - return; - } - + fn process_message(&mut self, message: ToolMessage, transition_data: ToolActionHandlerData<'a>, messages: &mut VecDeque) { if let ToolMessage::Line(LineToolMessage::UpdateOptions(action)) = message { match action { LineOptionsUpdate::LineWeight(line_weight) => self.options.line_weight = line_weight, @@ -105,27 +91,13 @@ impl<'a> MessageHandler> for LineTool { return; } - let new_state = self.fsm_state.transition(message, &mut self.tool_data, tool_data, &self.options, responses); - - if self.fsm_state != new_state { - self.fsm_state = new_state; - self.fsm_state.update_hints(responses); - self.fsm_state.update_cursor(responses); - } + self.fsm_state.process_event(message, &mut self.tool_data, transition_data, &self.options, messages, true); } fn actions(&self) -> ActionList { - use LineToolFsmState::*; - match self.fsm_state { - Ready => actions!(LineToolMessageDiscriminant; - DragStart, - ), - Drawing => actions!(LineToolMessageDiscriminant; - DragStop, - Redraw, - Abort, - ), + LineToolFsmState::Ready => actions!(LineToolMessageDiscriminant; DragStart), + LineToolFsmState::Drawing => actions!(LineToolMessageDiscriminant; DragStop, Redraw, Abort), } } } @@ -140,18 +112,13 @@ impl ToolTransition for LineTool { } } -#[derive(Clone, Copy, Debug, PartialEq, Eq)] +#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)] enum LineToolFsmState { + #[default] Ready, Drawing, } -impl Default for LineToolFsmState { - fn default() -> Self { - LineToolFsmState::Ready - } -} - #[derive(Clone, Debug, Default)] struct LineToolData { drag_start: ViewportPosition, @@ -205,20 +172,14 @@ impl Fsm for LineToolFsmState { (Drawing, Redraw { center, snap_angle, lock_angle }) => { tool_data.drag_current = tool_data.snap_manager.snap_position(responses, document, input.mouse.position); - let values: Vec<_> = [lock_angle, snap_angle, center].iter().map(|k| input.keyboard.get(*k as usize)).collect(); - responses.push_back(generate_transform(tool_data, values[0], values[1], values[2])); + let keyboard = &input.keyboard; + responses.push_back(generate_transform(tool_data, keyboard.key(lock_angle), keyboard.key(snap_angle), keyboard.key(center))); Drawing } (Drawing, DragStop) => { - tool_data.drag_current = tool_data.snap_manager.snap_position(responses, document, input.mouse.position); tool_data.snap_manager.cleanup(responses); - - match tool_data.drag_start.distance(input.mouse.position) <= DRAG_THRESHOLD { - true => responses.push_back(DocumentMessage::AbortTransaction.into()), - false => responses.push_back(DocumentMessage::CommitTransaction.into()), - } - + input.mouse.finish_transaction(tool_data.drag_start, responses); tool_data.path = None; Ready @@ -239,57 +200,15 @@ impl Fsm for LineToolFsmState { fn update_hints(&self, responses: &mut VecDeque) { let hint_data = match self { LineToolFsmState::Ready => HintData(vec![HintGroup(vec![ - HintInfo { - key_groups: vec![], - key_groups_mac: None, - mouse: Some(MouseMotion::LmbDrag), - label: String::from("Draw Line"), - plus: false, - }, - HintInfo { - key_groups: vec![KeysGroup(vec![Key::Shift]).into()], - key_groups_mac: None, - mouse: None, - label: String::from("Snap 15°"), - plus: true, - }, - HintInfo { - key_groups: vec![KeysGroup(vec![Key::Alt]).into()], - key_groups_mac: None, - mouse: None, - label: String::from("From Center"), - plus: true, - }, - HintInfo { - key_groups: vec![KeysGroup(vec![Key::Control]).into()], - key_groups_mac: None, - mouse: None, - label: String::from("Lock Angle"), - plus: true, - }, + HintInfo::mouse(MouseMotion::LmbDrag, "Draw Line"), + HintInfo::keys([Key::Shift], "Snap 15°").prepend_plus(), + HintInfo::keys([Key::Alt], "From Center").prepend_plus(), + HintInfo::keys([Key::Control], "Lock Angle").prepend_plus(), ])]), LineToolFsmState::Drawing => HintData(vec![HintGroup(vec![ - HintInfo { - key_groups: vec![KeysGroup(vec![Key::Shift]).into()], - key_groups_mac: None, - mouse: None, - label: String::from("Snap 15°"), - plus: false, - }, - HintInfo { - key_groups: vec![KeysGroup(vec![Key::Alt]).into()], - key_groups_mac: None, - mouse: None, - label: String::from("From Center"), - plus: false, - }, - HintInfo { - key_groups: vec![KeysGroup(vec![Key::Control]).into()], - key_groups_mac: None, - mouse: None, - label: String::from("Lock Angle"), - plus: false, - }, + HintInfo::keys([Key::Shift], "Snap 15°"), + HintInfo::keys([Key::Alt], "From Center"), + HintInfo::keys([Key::Control], "Lock Angle"), ])]), }; @@ -301,40 +220,38 @@ impl Fsm for LineToolFsmState { } } -fn generate_transform(tool_data: &mut LineToolData, lock: bool, snap: bool, center: bool) -> Message { +fn generate_transform(tool_data: &mut LineToolData, lock_angle: bool, snap_angle: bool, center: bool) -> Message { let mut start = tool_data.drag_start; - let stop = tool_data.drag_current; + let line_vector = tool_data.drag_current - start; - let dir = stop - start; + let mut angle = -line_vector.angle_between(DVec2::X); - let mut angle = -dir.angle_between(DVec2::X); + if lock_angle { + angle = tool_data.angle; + } - if lock { - angle = tool_data.angle - }; - - if snap { + if snap_angle { let snap_resolution = LINE_ROTATE_SNAP_ANGLE.to_radians(); angle = (angle / snap_resolution).round() * snap_resolution; } tool_data.angle = angle; - let mut scale = dir.length(); + let mut line_length = line_vector.length(); - if lock { + if lock_angle { let angle_vec = DVec2::new(angle.cos(), angle.sin()); - scale = dir.dot(angle_vec); + line_length = line_vector.dot(angle_vec); } if center { - start -= scale * DVec2::new(angle.cos(), angle.sin()); - scale *= 2.; + start -= line_length * DVec2::new(angle.cos(), angle.sin()); + line_length *= 2.; } Operation::SetLayerTransformInViewport { path: tool_data.path.clone().unwrap(), - transform: glam::DAffine2::from_scale_angle_translation(DVec2::new(scale, 1.), angle, start).to_cols_array(), + transform: glam::DAffine2::from_scale_angle_translation(DVec2::new(line_length, 1.), angle, start).to_cols_array(), } .into() } diff --git a/editor/src/messages/tool/tool_messages/navigate_tool.rs b/editor/src/messages/tool/tool_messages/navigate_tool.rs index 1e66775625..8fd8107198 100644 --- a/editor/src/messages/tool/tool_messages/navigate_tool.rs +++ b/editor/src/messages/tool/tool_messages/navigate_tool.rs @@ -1,5 +1,5 @@ use crate::messages::frontend::utility_types::MouseCursorIcon; -use crate::messages::input_mapper::utility_types::input_keyboard::{Key, KeysGroup, MouseMotion}; +use crate::messages::input_mapper::utility_types::input_keyboard::{Key, MouseMotion}; use crate::messages::layout::utility_types::layout_widget::PropertyHolder; use crate::messages::prelude::*; use crate::messages::tool::utility_types::{EventToMessageMap, Fsm, ToolActionHandlerData, ToolMetadata, ToolTransition, ToolType}; @@ -52,23 +52,7 @@ impl PropertyHolder for NavigateTool {} impl<'a> MessageHandler> for NavigateTool { fn process_message(&mut self, message: ToolMessage, tool_data: ToolActionHandlerData<'a>, responses: &mut VecDeque) { - if message == ToolMessage::UpdateHints { - self.fsm_state.update_hints(responses); - return; - } - - if message == ToolMessage::UpdateCursor { - self.fsm_state.update_cursor(responses); - return; - } - - let new_state = self.fsm_state.transition(message, &mut self.tool_data, tool_data, &(), responses); - - if self.fsm_state != new_state { - self.fsm_state = new_state; - self.fsm_state.update_hints(responses); - self.fsm_state.update_cursor(responses); - } + self.fsm_state.process_event(message, &mut self.tool_data, tool_data, &(), responses, true); } fn actions(&self) -> ActionList { @@ -99,20 +83,15 @@ impl ToolTransition for NavigateTool { } } -#[derive(Clone, Copy, Debug, PartialEq, Eq)] +#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] enum NavigateToolFsmState { + #[default] Ready, Panning, Tilting, Zooming, } -impl Default for NavigateToolFsmState { - fn default() -> Self { - NavigateToolFsmState::Ready - } -} - #[derive(Clone, Debug, Default)] struct NavigateToolData { drag_start: DVec2, @@ -192,76 +171,13 @@ impl Fsm for NavigateToolFsmState { fn update_hints(&self, responses: &mut VecDeque) { let hint_data = match self { NavigateToolFsmState::Ready => HintData(vec![ - HintGroup(vec![ - HintInfo { - key_groups: vec![], - key_groups_mac: None, - mouse: Some(MouseMotion::Lmb), - label: String::from("Zoom In"), - plus: false, - }, - HintInfo { - key_groups: vec![KeysGroup(vec![Key::Shift]).into()], - key_groups_mac: None, - mouse: None, - label: String::from("Zoom Out"), - plus: true, - }, - ]), - HintGroup(vec![ - HintInfo { - key_groups: vec![], - key_groups_mac: None, - mouse: Some(MouseMotion::LmbDrag), - label: String::from("Zoom"), - plus: false, - }, - HintInfo { - key_groups: vec![KeysGroup(vec![Key::Control]).into()], - key_groups_mac: None, - mouse: None, - label: String::from("Snap Increments"), - plus: true, - }, - ]), - HintGroup(vec![HintInfo { - key_groups: vec![], - key_groups_mac: None, - mouse: Some(MouseMotion::MmbDrag), - label: String::from("Pan"), - plus: false, - }]), - HintGroup(vec![ - HintInfo { - key_groups: vec![], - key_groups_mac: None, - mouse: Some(MouseMotion::RmbDrag), - label: String::from("Tilt"), - plus: false, - }, - HintInfo { - key_groups: vec![KeysGroup(vec![Key::Control]).into()], - key_groups_mac: None, - mouse: None, - label: String::from("Snap 15°"), - plus: true, - }, - ]), + HintGroup(vec![HintInfo::mouse(MouseMotion::Lmb, "Zoom In"), HintInfo::keys([Key::Shift], "Zoom Out").prepend_plus()]), + HintGroup(vec![HintInfo::mouse(MouseMotion::LmbDrag, "Zoom"), HintInfo::keys([Key::Control], "Snap Increments").prepend_plus()]), + HintGroup(vec![HintInfo::mouse(MouseMotion::MmbDrag, "Pan")]), + HintGroup(vec![HintInfo::mouse(MouseMotion::RmbDrag, "Tilt"), HintInfo::keys([Key::Control], "Snap 15°").prepend_plus()]), ]), - NavigateToolFsmState::Tilting => HintData(vec![HintGroup(vec![HintInfo { - key_groups: vec![KeysGroup(vec![Key::Control]).into()], - key_groups_mac: None, - mouse: None, - label: String::from("Snap 15°"), - plus: false, - }])]), - NavigateToolFsmState::Zooming => HintData(vec![HintGroup(vec![HintInfo { - key_groups: vec![KeysGroup(vec![Key::Control]).into()], - key_groups_mac: None, - mouse: None, - label: String::from("Snap Increments"), - plus: false, - }])]), + NavigateToolFsmState::Tilting => HintData(vec![HintGroup(vec![HintInfo::keys([Key::Control], "Snap 15°")])]), + NavigateToolFsmState::Zooming => HintData(vec![HintGroup(vec![HintInfo::keys([Key::Control], "Snap Increments")])]), _ => HintData(Vec::new()), }; diff --git a/editor/src/messages/tool/tool_messages/node_graph_frame_tool.rs b/editor/src/messages/tool/tool_messages/node_graph_frame_tool.rs index ef0fef7ad8..b066549f5a 100644 --- a/editor/src/messages/tool/tool_messages/node_graph_frame_tool.rs +++ b/editor/src/messages/tool/tool_messages/node_graph_frame_tool.rs @@ -1,6 +1,5 @@ -use crate::consts::DRAG_THRESHOLD; use crate::messages::frontend::utility_types::MouseCursorIcon; -use crate::messages::input_mapper::utility_types::input_keyboard::{Key, KeysGroup, MouseMotion}; +use crate::messages::input_mapper::utility_types::input_keyboard::{Key, MouseMotion}; use crate::messages::layout::utility_types::layout_widget::PropertyHolder; use crate::messages::prelude::*; use crate::messages::tool::common_functionality::resize::Resize; @@ -39,23 +38,7 @@ impl PropertyHolder for NodeGraphFrameTool {} impl<'a> MessageHandler> for NodeGraphFrameTool { fn process_message(&mut self, message: ToolMessage, tool_data: ToolActionHandlerData<'a>, responses: &mut VecDeque) { - if message == ToolMessage::UpdateHints { - self.fsm_state.update_hints(responses); - return; - } - - if message == ToolMessage::UpdateCursor { - self.fsm_state.update_cursor(responses); - return; - } - - let new_state = self.fsm_state.transition(message, &mut self.tool_data, tool_data, &(), responses); - - if self.fsm_state != new_state { - self.fsm_state = new_state; - self.fsm_state.update_hints(responses); - self.fsm_state.update_cursor(responses); - } + self.fsm_state.process_event(message, &mut self.tool_data, tool_data, &(), responses, true); } fn actions(&self) -> ActionList { @@ -96,17 +79,13 @@ impl ToolTransition for NodeGraphFrameTool { } } -#[derive(Clone, Copy, Debug, PartialEq, Eq)] +#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] enum NodeGraphToolFsmState { + #[default] Ready, Drawing, } -impl Default for NodeGraphToolFsmState { - fn default() -> Self { - NodeGraphToolFsmState::Ready - } -} #[derive(Clone, Debug, Default)] struct NodeGraphToolData { data: Resize, @@ -160,11 +139,7 @@ impl Fsm for NodeGraphToolFsmState { state } (Drawing, DragStop) => { - match shape_data.viewport_drag_start(document).distance(input.mouse.position) <= DRAG_THRESHOLD { - true => responses.push_back(DocumentMessage::AbortTransaction.into()), - false => responses.push_back(DocumentMessage::CommitTransaction.into()), - } - + input.mouse.finish_transaction(shape_data.viewport_drag_start(document), responses); responses.push_back(NodeGraphMessage::SetDrawing { new_drawing: false }.into()); shape_data.cleanup(responses); @@ -188,44 +163,11 @@ impl Fsm for NodeGraphToolFsmState { fn update_hints(&self, responses: &mut VecDeque) { let hint_data = match self { NodeGraphToolFsmState::Ready => HintData(vec![HintGroup(vec![ - HintInfo { - key_groups: vec![], - key_groups_mac: None, - mouse: Some(MouseMotion::LmbDrag), - label: String::from("Draw Repaint Frame"), - plus: false, - }, - HintInfo { - key_groups: vec![KeysGroup(vec![Key::Shift]).into()], - key_groups_mac: None, - mouse: None, - label: String::from("Constrain Square"), - plus: true, - }, - HintInfo { - key_groups: vec![KeysGroup(vec![Key::Alt]).into()], - key_groups_mac: None, - mouse: None, - label: String::from("From Center"), - plus: true, - }, - ])]), - NodeGraphToolFsmState::Drawing => HintData(vec![HintGroup(vec![ - HintInfo { - key_groups: vec![KeysGroup(vec![Key::Shift]).into()], - key_groups_mac: None, - mouse: None, - label: String::from("Constrain Square"), - plus: false, - }, - HintInfo { - key_groups: vec![KeysGroup(vec![Key::Alt]).into()], - key_groups_mac: None, - mouse: None, - label: String::from("From Center"), - plus: false, - }, + HintInfo::mouse(MouseMotion::LmbDrag, "Draw Repaint Frame"), + HintInfo::keys([Key::Shift], "Constrain Square").prepend_plus(), + HintInfo::keys([Key::Alt], "From Center").prepend_plus(), ])]), + NodeGraphToolFsmState::Drawing => HintData(vec![HintGroup(vec![HintInfo::keys([Key::Shift], "Constrain Square"), HintInfo::keys([Key::Alt], "From Center")])]), }; responses.push_back(FrontendMessage::UpdateInputHints { hint_data }.into()); diff --git a/editor/src/messages/tool/tool_messages/path_tool.rs b/editor/src/messages/tool/tool_messages/path_tool.rs index 7b0a4fe1ad..6a67c40e57 100644 --- a/editor/src/messages/tool/tool_messages/path_tool.rs +++ b/editor/src/messages/tool/tool_messages/path_tool.rs @@ -1,6 +1,6 @@ use crate::consts::{SELECTION_THRESHOLD, SELECTION_TOLERANCE}; use crate::messages::frontend::utility_types::MouseCursorIcon; -use crate::messages::input_mapper::utility_types::input_keyboard::{Key, KeysGroup, MouseMotion}; +use crate::messages::input_mapper::utility_types::input_keyboard::{Key, MouseMotion}; use crate::messages::layout::utility_types::layout_widget::PropertyHolder; use crate::messages::prelude::*; use crate::messages::tool::common_functionality::overlay_renderer::OverlayRenderer; @@ -62,23 +62,7 @@ impl PropertyHolder for PathTool {} impl<'a> MessageHandler> for PathTool { fn process_message(&mut self, message: ToolMessage, tool_data: ToolActionHandlerData<'a>, responses: &mut VecDeque) { - if message == ToolMessage::UpdateHints { - self.fsm_state.update_hints(responses); - return; - } - - if message == ToolMessage::UpdateCursor { - self.fsm_state.update_cursor(responses); - return; - } - - let new_state = self.fsm_state.transition(message, &mut self.tool_data, tool_data, &(), responses); - - if self.fsm_state != new_state { - self.fsm_state = new_state; - self.fsm_state.update_hints(responses); - self.fsm_state.update_cursor(responses); - } + self.fsm_state.process_event(message, &mut self.tool_data, tool_data, &(), responses, true); } // Different actions depending on state may be wanted: @@ -111,18 +95,13 @@ impl ToolTransition for PathTool { } } -#[derive(Clone, Copy, Debug, PartialEq, Eq)] +#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] enum PathToolFsmState { + #[default] Ready, Dragging, } -impl Default for PathToolFsmState { - fn default() -> Self { - PathToolFsmState::Ready - } -} - #[derive(Default)] struct PathToolData { shape_editor: ShapeEditor, @@ -309,88 +288,23 @@ impl Fsm for PathToolFsmState { let hint_data = match self { PathToolFsmState::Ready => HintData(vec![ HintGroup(vec![ - HintInfo { - key_groups: vec![], - key_groups_mac: None, - mouse: Some(MouseMotion::Lmb), - label: String::from("Select Point"), - plus: false, - }, - HintInfo { - key_groups: vec![KeysGroup(vec![Key::Shift]).into()], - key_groups_mac: None, - mouse: None, - label: String::from("Grow/Shrink Selection"), - plus: true, - }, + HintInfo::mouse(MouseMotion::Lmb, "Select Point"), + HintInfo::keys([Key::Shift], "Grow/Shrink Selection").prepend_plus(), ]), - HintGroup(vec![HintInfo { - key_groups: vec![], - key_groups_mac: None, - mouse: Some(MouseMotion::LmbDrag), - label: String::from("Drag Selected"), - plus: false, - }]), + HintGroup(vec![HintInfo::mouse(MouseMotion::LmbDrag, "Drag Selected")]), HintGroup(vec![ - HintInfo { - key_groups: vec![ - KeysGroup(vec![Key::ArrowUp]).into(), - KeysGroup(vec![Key::ArrowRight]).into(), - KeysGroup(vec![Key::ArrowDown]).into(), - KeysGroup(vec![Key::ArrowLeft]).into(), - ], - key_groups_mac: None, - mouse: None, - label: String::from("Nudge Selected (coming soon)"), - plus: false, - }, - HintInfo { - key_groups: vec![KeysGroup(vec![Key::Shift]).into()], - key_groups_mac: None, - mouse: None, - label: String::from("Big Increment Nudge"), - plus: true, - }, + HintInfo::arrow_keys("Nudge Selected (coming soon)"), + HintInfo::keys([Key::Shift], "Big Increment Nudge").prepend_plus(), ]), HintGroup(vec![ - HintInfo { - key_groups: vec![KeysGroup(vec![Key::KeyG]).into()], - key_groups_mac: None, - mouse: None, - label: String::from("Grab Selected (coming soon)"), - plus: false, - }, - HintInfo { - key_groups: vec![KeysGroup(vec![Key::KeyR]).into()], - key_groups_mac: None, - mouse: None, - label: String::from("Rotate Selected (coming soon)"), - plus: false, - }, - HintInfo { - key_groups: vec![KeysGroup(vec![Key::KeyS]).into()], - key_groups_mac: None, - mouse: None, - label: String::from("Scale Selected (coming soon)"), - plus: false, - }, + HintInfo::keys([Key::KeyG], "Grab Selected (coming soon)"), + HintInfo::keys([Key::KeyR], "Rotate Selected (coming soon)"), + HintInfo::keys([Key::KeyS], "Scale Selected (coming soon)"), ]), ]), PathToolFsmState::Dragging => HintData(vec![HintGroup(vec![ - HintInfo { - key_groups: vec![KeysGroup(vec![Key::Alt]).into()], - key_groups_mac: None, - mouse: None, - label: String::from("Split/Align Handles (Toggle)"), - plus: false, - }, - HintInfo { - key_groups: vec![KeysGroup(vec![Key::Shift]).into()], - key_groups_mac: None, - mouse: None, - label: String::from("Share Lengths of Aligned Handles"), - plus: false, - }, + HintInfo::keys([Key::Alt], "Split/Align Handles (Toggle)"), + HintInfo::keys([Key::Shift], "Share Lengths of Aligned Handles"), ])]), }; diff --git a/editor/src/messages/tool/tool_messages/pen_tool.rs b/editor/src/messages/tool/tool_messages/pen_tool.rs index 32bf2af36e..fe90b97ce7 100644 --- a/editor/src/messages/tool/tool_messages/pen_tool.rs +++ b/editor/src/messages/tool/tool_messages/pen_tool.rs @@ -1,7 +1,7 @@ use crate::consts::LINE_ROTATE_SNAP_ANGLE; use crate::messages::frontend::utility_types::MouseCursorIcon; -use crate::messages::input_mapper::utility_types::input_keyboard::{Key, KeysGroup, MouseMotion}; -use crate::messages::layout::utility_types::layout_widget::{Layout, LayoutGroup, PropertyHolder, Widget, WidgetCallback, WidgetHolder, WidgetLayout}; +use crate::messages::input_mapper::utility_types::input_keyboard::{Key, MouseMotion}; +use crate::messages::layout::utility_types::layout_widget::{Layout, LayoutGroup, PropertyHolder, WidgetLayout}; use crate::messages::layout::utility_types::widgets::input_widgets::NumberInput; use crate::messages::prelude::*; use crate::messages::tool::common_functionality::overlay_renderer::OverlayRenderer; @@ -59,8 +59,9 @@ pub enum PenToolMessage { UpdateOptions(PenOptionsUpdate), } -#[derive(Clone, Copy, Debug, PartialEq, Eq)] +#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] enum PenToolFsmState { + #[default] Ready, DraggingHandle, PlacingAnchor, @@ -86,32 +87,18 @@ impl ToolMetadata for PenTool { impl PropertyHolder for PenTool { fn properties(&self) -> Layout { - Layout::WidgetLayout(WidgetLayout::new(vec![LayoutGroup::Row { - widgets: vec![WidgetHolder::new(Widget::NumberInput(NumberInput { - unit: " px".into(), - label: "Weight".into(), - value: Some(self.options.line_weight), - is_integer: false, - min: Some(0.), - on_update: WidgetCallback::new(|number_input: &NumberInput| PenToolMessage::UpdateOptions(PenOptionsUpdate::LineWeight(number_input.value.unwrap())).into()), - ..NumberInput::default() - }))], - }])) + let weight = NumberInput::new(Some(self.options.line_weight)) + .unit(" px") + .label("Weight") + .min(0.) + .on_update(|number_input: &NumberInput| PenToolMessage::UpdateOptions(PenOptionsUpdate::LineWeight(number_input.value.unwrap())).into()) + .widget_holder(); + Layout::WidgetLayout(WidgetLayout::new(vec![LayoutGroup::Row { widgets: vec![weight] }])) } } impl<'a> MessageHandler> for PenTool { fn process_message(&mut self, message: ToolMessage, tool_data: ToolActionHandlerData<'a>, responses: &mut VecDeque) { - if message == ToolMessage::UpdateHints { - self.fsm_state.update_hints(responses); - return; - } - - if message == ToolMessage::UpdateCursor { - self.fsm_state.update_cursor(responses); - return; - } - if let ToolMessage::Pen(PenToolMessage::UpdateOptions(action)) = message { match action { PenOptionsUpdate::LineWeight(line_weight) => self.options.line_weight = line_weight, @@ -119,13 +106,7 @@ impl<'a> MessageHandler> for PenTool { return; } - let new_state = self.fsm_state.transition(message, &mut self.tool_data, tool_data, &self.options, responses); - - if self.fsm_state != new_state { - self.fsm_state = new_state; - self.fsm_state.update_hints(responses); - self.fsm_state.update_cursor(responses); - } + self.fsm_state.process_event(message, &mut self.tool_data, tool_data, &self.options, responses, true); } fn actions(&self) -> ActionList { @@ -158,11 +139,6 @@ impl ToolTransition for PenTool { } } -impl Default for PenToolFsmState { - fn default() -> Self { - PenToolFsmState::Ready - } -} #[derive(Clone, Debug, Default)] struct PenToolData { weight: f64, @@ -614,49 +590,13 @@ impl Fsm for PenToolFsmState { fn update_hints(&self, responses: &mut VecDeque) { let hint_data = match self { - PenToolFsmState::Ready => HintData(vec![HintGroup(vec![HintInfo { - key_groups: vec![], - key_groups_mac: None, - mouse: Some(MouseMotion::Lmb), - label: String::from("Draw Path"), - plus: false, - }])]), + PenToolFsmState::Ready => HintData(vec![HintGroup(vec![HintInfo::mouse(MouseMotion::Lmb, "Draw Path")])]), PenToolFsmState::DraggingHandle | PenToolFsmState::PlacingAnchor => HintData(vec![ - HintGroup(vec![HintInfo { - key_groups: vec![], - key_groups_mac: None, - mouse: Some(MouseMotion::LmbDrag), - label: String::from("Add Handle"), - plus: false, - }]), - HintGroup(vec![HintInfo { - key_groups: vec![], - key_groups_mac: None, - mouse: Some(MouseMotion::Lmb), - label: String::from("Add Anchor"), - plus: false, - }]), - HintGroup(vec![HintInfo { - key_groups: vec![KeysGroup(vec![Key::Control]).into()], - key_groups_mac: None, - mouse: None, - label: String::from("Snap 15°"), - plus: false, - }]), - HintGroup(vec![HintInfo { - key_groups: vec![KeysGroup(vec![Key::Shift]).into()], - key_groups_mac: None, - mouse: None, - label: String::from("Break Handle"), - plus: false, - }]), - HintGroup(vec![HintInfo { - key_groups: vec![KeysGroup(vec![Key::Enter]).into()], - key_groups_mac: None, - mouse: None, - label: String::from("End Path"), - plus: false, - }]), + HintGroup(vec![HintInfo::mouse(MouseMotion::LmbDrag, "Add Handle")]), + HintGroup(vec![HintInfo::mouse(MouseMotion::Lmb, "Add Anchor")]), + HintGroup(vec![HintInfo::keys([Key::Control], "Snap 15°")]), + HintGroup(vec![HintInfo::keys([Key::Shift], "Break Handle")]), + HintGroup(vec![HintInfo::keys([Key::Enter], "End Path")]), ]), }; diff --git a/editor/src/messages/tool/tool_messages/rectangle_tool.rs b/editor/src/messages/tool/tool_messages/rectangle_tool.rs index 6e4039f49c..e1ee5dba5d 100644 --- a/editor/src/messages/tool/tool_messages/rectangle_tool.rs +++ b/editor/src/messages/tool/tool_messages/rectangle_tool.rs @@ -1,6 +1,5 @@ -use crate::consts::DRAG_THRESHOLD; use crate::messages::frontend::utility_types::MouseCursorIcon; -use crate::messages::input_mapper::utility_types::input_keyboard::{Key, KeysGroup, MouseMotion}; +use crate::messages::input_mapper::utility_types::input_keyboard::{Key, MouseMotion}; use crate::messages::layout::utility_types::layout_widget::PropertyHolder; use crate::messages::prelude::*; use crate::messages::tool::common_functionality::resize::Resize; @@ -40,23 +39,7 @@ impl PropertyHolder for RectangleTool {} impl<'a> MessageHandler> for RectangleTool { fn process_message(&mut self, message: ToolMessage, tool_data: ToolActionHandlerData<'a>, responses: &mut VecDeque) { - if message == ToolMessage::UpdateHints { - self.fsm_state.update_hints(responses); - return; - } - - if message == ToolMessage::UpdateCursor { - self.fsm_state.update_cursor(responses); - return; - } - - let new_state = self.fsm_state.transition(message, &mut self.tool_data, tool_data, &(), responses); - - if self.fsm_state != new_state { - self.fsm_state = new_state; - self.fsm_state.update_hints(responses); - self.fsm_state.update_cursor(responses); - } + self.fsm_state.process_event(message, &mut self.tool_data, tool_data, &(), responses, true); } fn actions(&self) -> ActionList { @@ -97,17 +80,13 @@ impl ToolTransition for RectangleTool { } } -#[derive(Clone, Copy, Debug, PartialEq, Eq)] +#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] enum RectangleToolFsmState { + #[default] Ready, Drawing, } -impl Default for RectangleToolFsmState { - fn default() -> Self { - RectangleToolFsmState::Ready - } -} #[derive(Clone, Debug, Default)] struct RectangleToolData { data: Resize, @@ -158,11 +137,7 @@ impl Fsm for RectangleToolFsmState { state } (Drawing, DragStop) => { - match shape_data.viewport_drag_start(document).distance(input.mouse.position) <= DRAG_THRESHOLD { - true => responses.push_back(DocumentMessage::AbortTransaction.into()), - false => responses.push_back(DocumentMessage::CommitTransaction.into()), - } - + input.mouse.finish_transaction(shape_data.viewport_drag_start(document), responses); shape_data.cleanup(responses); Ready @@ -184,44 +159,11 @@ impl Fsm for RectangleToolFsmState { fn update_hints(&self, responses: &mut VecDeque) { let hint_data = match self { RectangleToolFsmState::Ready => HintData(vec![HintGroup(vec![ - HintInfo { - key_groups: vec![], - key_groups_mac: None, - mouse: Some(MouseMotion::LmbDrag), - label: String::from("Draw Rectangle"), - plus: false, - }, - HintInfo { - key_groups: vec![KeysGroup(vec![Key::Shift]).into()], - key_groups_mac: None, - mouse: None, - label: String::from("Constrain Square"), - plus: true, - }, - HintInfo { - key_groups: vec![KeysGroup(vec![Key::Alt]).into()], - key_groups_mac: None, - mouse: None, - label: String::from("From Center"), - plus: true, - }, - ])]), - RectangleToolFsmState::Drawing => HintData(vec![HintGroup(vec![ - HintInfo { - key_groups: vec![KeysGroup(vec![Key::Shift]).into()], - key_groups_mac: None, - mouse: None, - label: String::from("Constrain Square"), - plus: false, - }, - HintInfo { - key_groups: vec![KeysGroup(vec![Key::Alt]).into()], - key_groups_mac: None, - mouse: None, - label: String::from("From Center"), - plus: false, - }, + HintInfo::mouse(MouseMotion::LmbDrag, "Draw Rectangle"), + HintInfo::keys([Key::Shift], "Constrain Square").prepend_plus(), + HintInfo::keys([Key::Alt], "From Center").prepend_plus(), ])]), + RectangleToolFsmState::Drawing => HintData(vec![HintGroup(vec![HintInfo::keys([Key::Shift], "Constrain Square"), HintInfo::keys([Key::Alt], "From Center")])]), }; responses.push_back(FrontendMessage::UpdateInputHints { hint_data }.into()); diff --git a/editor/src/messages/tool/tool_messages/select_tool.rs b/editor/src/messages/tool/tool_messages/select_tool.rs index aeeb41b638..c895fcc75b 100644 --- a/editor/src/messages/tool/tool_messages/select_tool.rs +++ b/editor/src/messages/tool/tool_messages/select_tool.rs @@ -1,9 +1,9 @@ use crate::application::generate_uuid; use crate::consts::{ROTATE_SNAP_ANGLE, SELECTION_TOLERANCE}; use crate::messages::frontend::utility_types::MouseCursorIcon; -use crate::messages::input_mapper::utility_types::input_keyboard::{Key, KeysGroup, MouseMotion}; +use crate::messages::input_mapper::utility_types::input_keyboard::{Key, MouseMotion}; use crate::messages::input_mapper::utility_types::input_mouse::ViewportPosition; -use crate::messages::layout::utility_types::layout_widget::{Layout, LayoutGroup, PropertyHolder, Widget, WidgetCallback, WidgetHolder, WidgetLayout}; +use crate::messages::layout::utility_types::layout_widget::{Layout, LayoutGroup, PropertyHolder, Widget, WidgetHolder, WidgetLayout}; use crate::messages::layout::utility_types::misc::LayoutTarget; use crate::messages::layout::utility_types::widgets::assist_widgets::{PivotAssist, PivotPosition}; use crate::messages::layout::utility_types::widgets::button_widgets::{IconButton, PopoverButton}; @@ -85,182 +85,112 @@ impl PropertyHolder for SelectTool { fn properties(&self) -> Layout { Layout::WidgetLayout(WidgetLayout::new(vec![LayoutGroup::Row { widgets: vec![ - WidgetHolder::new(Widget::IconButton(IconButton { - icon: "AlignLeft".into(), - tooltip: "Align Left".into(), - size: 24, - on_update: WidgetCallback::new(|_| { + IconButton::new("AlignLeft", 24) + .tooltip("Align Left") + .on_update(|_| { DocumentMessage::AlignSelectedLayers { axis: AlignAxis::X, aggregate: AlignAggregate::Min, } .into() - }), - ..IconButton::default() - })), - WidgetHolder::new(Widget::IconButton(IconButton { - icon: "AlignHorizontalCenter".into(), - tooltip: "Align Horizontal Center".into(), - size: 24, - on_update: WidgetCallback::new(|_| { + }) + .widget_holder(), + IconButton::new("AlignHorizontalCenter", 24) + .tooltip("Align Horizontal Center") + .on_update(|_| { DocumentMessage::AlignSelectedLayers { axis: AlignAxis::X, aggregate: AlignAggregate::Center, } .into() - }), - ..IconButton::default() - })), - WidgetHolder::new(Widget::IconButton(IconButton { - icon: "AlignRight".into(), - tooltip: "Align Right".into(), - size: 24, - on_update: WidgetCallback::new(|_| { + }) + .widget_holder(), + IconButton::new("AlignRight", 24) + .tooltip("Align Right") + .on_update(|_| { DocumentMessage::AlignSelectedLayers { axis: AlignAxis::X, aggregate: AlignAggregate::Max, } .into() - }), - ..IconButton::default() - })), - WidgetHolder::new(Widget::Separator(Separator { - direction: SeparatorDirection::Horizontal, - separator_type: SeparatorType::Unrelated, - })), - WidgetHolder::new(Widget::IconButton(IconButton { - icon: "AlignTop".into(), - tooltip: "Align Top".into(), - size: 24, - on_update: WidgetCallback::new(|_| { + }) + .widget_holder(), + WidgetHolder::unrelated_separator(), + IconButton::new("AlignTop", 24) + .tooltip("Align Top") + .on_update(|_| { DocumentMessage::AlignSelectedLayers { axis: AlignAxis::Y, aggregate: AlignAggregate::Min, } .into() - }), - ..IconButton::default() - })), - WidgetHolder::new(Widget::IconButton(IconButton { - icon: "AlignVerticalCenter".into(), - tooltip: "Align Vertical Center".into(), - size: 24, - on_update: WidgetCallback::new(|_| { + }) + .widget_holder(), + IconButton::new("AlignVerticalCenter", 24) + .tooltip("Align Vertical Center") + .on_update(|_| { DocumentMessage::AlignSelectedLayers { axis: AlignAxis::Y, aggregate: AlignAggregate::Center, } .into() - }), - ..IconButton::default() - })), - WidgetHolder::new(Widget::IconButton(IconButton { - icon: "AlignBottom".into(), - tooltip: "Align Bottom".into(), - size: 24, - on_update: WidgetCallback::new(|_| { + }) + .widget_holder(), + IconButton::new("AlignBottom", 24) + .tooltip("Align Bottom") + .on_update(|_| { DocumentMessage::AlignSelectedLayers { axis: AlignAxis::Y, aggregate: AlignAggregate::Max, } .into() - }), - ..IconButton::default() - })), - WidgetHolder::new(Widget::Separator(Separator { - direction: SeparatorDirection::Horizontal, - separator_type: SeparatorType::Related, - })), - WidgetHolder::new(Widget::PopoverButton(PopoverButton { - header: "Align".into(), - text: "Coming soon".into(), - ..Default::default() - })), - WidgetHolder::new(Widget::Separator(Separator { - direction: SeparatorDirection::Horizontal, - separator_type: SeparatorType::Section, - })), - WidgetHolder::new(Widget::IconButton(IconButton { - icon: "FlipHorizontal".into(), - tooltip: "Flip Horizontal".into(), - size: 24, - on_update: WidgetCallback::new(|_| SelectToolMessage::FlipHorizontal.into()), - ..IconButton::default() - })), - WidgetHolder::new(Widget::IconButton(IconButton { - icon: "FlipVertical".into(), - tooltip: "Flip Vertical".into(), - size: 24, - on_update: WidgetCallback::new(|_| SelectToolMessage::FlipVertical.into()), - ..IconButton::default() - })), - WidgetHolder::new(Widget::Separator(Separator { - direction: SeparatorDirection::Horizontal, - separator_type: SeparatorType::Related, - })), + }) + .widget_holder(), + WidgetHolder::related_separator(), + PopoverButton::new("Align", "Coming soon").widget_holder(), + Separator::new(SeparatorDirection::Horizontal, SeparatorType::Section).widget_holder(), + IconButton::new("FlipHorizontal", 24) + .tooltip("Flip Horizontal") + .on_update(|_| SelectToolMessage::FlipHorizontal.into()) + .widget_holder(), + IconButton::new("FlipVertical", 24) + .tooltip("Flip Vertical") + .on_update(|_| SelectToolMessage::FlipVertical.into()) + .widget_holder(), + WidgetHolder::related_separator(), WidgetHolder::new(Widget::PopoverButton(PopoverButton { header: "Flip".into(), text: "Coming soon".into(), ..Default::default() })), - WidgetHolder::new(Widget::Separator(Separator { - direction: SeparatorDirection::Horizontal, - separator_type: SeparatorType::Section, - })), - WidgetHolder::new(Widget::IconButton(IconButton { - icon: "BooleanUnion".into(), - tooltip: "Boolean Union".into(), - size: 24, - on_update: WidgetCallback::new(|_| DocumentMessage::BooleanOperation(BooleanOperation::Union).into()), - ..IconButton::default() - })), - WidgetHolder::new(Widget::IconButton(IconButton { - icon: "BooleanSubtractFront".into(), - tooltip: "Boolean Subtract Front".into(), - size: 24, - on_update: WidgetCallback::new(|_| DocumentMessage::BooleanOperation(BooleanOperation::SubtractFront).into()), - ..IconButton::default() - })), - WidgetHolder::new(Widget::IconButton(IconButton { - icon: "BooleanSubtractBack".into(), - tooltip: "Boolean Subtract Back".into(), - size: 24, - on_update: WidgetCallback::new(|_| DocumentMessage::BooleanOperation(BooleanOperation::SubtractBack).into()), - ..IconButton::default() - })), - WidgetHolder::new(Widget::IconButton(IconButton { - icon: "BooleanIntersect".into(), - tooltip: "Boolean Intersect".into(), - size: 24, - on_update: WidgetCallback::new(|_| DocumentMessage::BooleanOperation(BooleanOperation::Intersection).into()), - ..IconButton::default() - })), - WidgetHolder::new(Widget::IconButton(IconButton { - icon: "BooleanDifference".into(), - tooltip: "Boolean Difference".into(), - size: 24, - on_update: WidgetCallback::new(|_| DocumentMessage::BooleanOperation(BooleanOperation::Difference).into()), - ..IconButton::default() - })), - WidgetHolder::new(Widget::Separator(Separator { - direction: SeparatorDirection::Horizontal, - separator_type: SeparatorType::Related, - })), - WidgetHolder::new(Widget::PopoverButton(PopoverButton { - header: "Boolean".into(), - text: "Coming soon".into(), - ..Default::default() - })), - WidgetHolder::new(Widget::Separator(Separator { - direction: SeparatorDirection::Horizontal, - separator_type: SeparatorType::Section, - })), + Separator::new(SeparatorDirection::Horizontal, SeparatorType::Section).widget_holder(), + IconButton::new("BooleanUnion", 24) + .tooltip("Boolean Union") + .on_update(|_| DocumentMessage::BooleanOperation(BooleanOperation::Union).into()) + .widget_holder(), + IconButton::new("BooleanSubtractFront", 24) + .tooltip("Boolean Subtract Front") + .on_update(|_| DocumentMessage::BooleanOperation(BooleanOperation::SubtractFront).into()) + .widget_holder(), + IconButton::new("BooleanSubtractBack", 24) + .tooltip("Boolean Subtract Back") + .on_update(|_| DocumentMessage::BooleanOperation(BooleanOperation::SubtractBack).into()) + .widget_holder(), + IconButton::new("BooleanIntersect", 24) + .tooltip("Boolean Intersect") + .on_update(|_| DocumentMessage::BooleanOperation(BooleanOperation::Intersection).into()) + .widget_holder(), + IconButton::new("BooleanDifference", 24) + .tooltip("Boolean Difference") + .on_update(|_| DocumentMessage::BooleanOperation(BooleanOperation::Difference).into()) + .widget_holder(), + WidgetHolder::related_separator(), + PopoverButton::new("Boolean", "Coming soon").widget_holder(), + Separator::new(SeparatorDirection::Horizontal, SeparatorType::Section).widget_holder(), // We'd like this widget to hide and show itself whenever the transformation cage is active or inactive (i.e. when no layers are selected) - WidgetHolder::new(Widget::PivotAssist(PivotAssist { - position: self.tool_data.pivot.to_pivot_position(), - on_update: WidgetCallback::new(|pivot_assist: &PivotAssist| SelectToolMessage::SetPivot { position: pivot_assist.position }.into()), - ..Default::default() - })), + PivotAssist::new(self.tool_data.pivot.to_pivot_position()) + .on_update(|pivot_assist: &PivotAssist| SelectToolMessage::SetPivot { position: pivot_assist.position }.into()) + .widget_holder(), ], }])) } @@ -268,27 +198,12 @@ impl PropertyHolder for SelectTool { impl<'a> MessageHandler> for SelectTool { fn process_message(&mut self, message: ToolMessage, tool_data: ToolActionHandlerData<'a>, responses: &mut VecDeque) { - if message == ToolMessage::UpdateHints { - self.fsm_state.update_hints(responses); - return; - } - - if message == ToolMessage::UpdateCursor { - responses.push_back(FrontendMessage::UpdateMouseCursor { cursor: MouseCursorIcon::Default }.into()); - return; - } - - let new_state = self.fsm_state.transition(message, &mut self.tool_data, tool_data, &(), responses); + self.fsm_state.process_event(message, &mut self.tool_data, tool_data, &(), responses, false); if self.tool_data.pivot.should_refresh_pivot_position() { // Notify the frontend about the updated pivot position (a bit ugly to do it here not in the fsm but that doesn't have SelectTool) self.register_properties(responses, LayoutTarget::ToolOptions); } - - if self.fsm_state != new_state { - self.fsm_state = new_state; - self.fsm_state.update_hints(responses); - } } fn actions(&self) -> ActionList { @@ -321,8 +236,9 @@ impl ToolTransition for SelectTool { } } -#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] +#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)] enum SelectToolFsmState { + #[default] Ready, Dragging, DrawingBox, @@ -331,12 +247,6 @@ enum SelectToolFsmState { DraggingPivot, } -impl Default for SelectToolFsmState { - fn default() -> Self { - SelectToolFsmState::Ready - } -} - #[derive(Clone, Debug, Default)] struct SelectToolData { drag_start: ViewportPosition, @@ -812,138 +722,34 @@ impl Fsm for SelectToolFsmState { fn update_hints(&self, responses: &mut VecDeque) { let hint_data = match self { SelectToolFsmState::Ready => HintData(vec![ - HintGroup(vec![HintInfo { - key_groups: vec![], - key_groups_mac: None, - mouse: Some(MouseMotion::LmbDrag), - label: String::from("Drag Selected"), - plus: false, - }]), + HintGroup(vec![HintInfo::mouse(MouseMotion::LmbDrag, "Drag Selected")]), HintGroup(vec![ - HintInfo { - key_groups: vec![KeysGroup(vec![Key::KeyG]).into()], - key_groups_mac: None, - mouse: None, - label: String::from("Grab Selected"), - plus: false, - }, - HintInfo { - key_groups: vec![KeysGroup(vec![Key::KeyR]).into()], - key_groups_mac: None, - mouse: None, - label: String::from("Rotate Selected"), - plus: false, - }, - HintInfo { - key_groups: vec![KeysGroup(vec![Key::KeyS]).into()], - key_groups_mac: None, - mouse: None, - label: String::from("Scale Selected"), - plus: false, - }, + HintInfo::keys([Key::KeyG], "Grab Selected"), + HintInfo::keys([Key::KeyR], "Rotate Selected"), + HintInfo::keys([Key::KeyS], "Scale Selected"), ]), HintGroup(vec![ - HintInfo { - key_groups: vec![], - key_groups_mac: None, - mouse: Some(MouseMotion::Lmb), - label: String::from("Select Object"), - plus: false, - }, - HintInfo { - key_groups: vec![KeysGroup(vec![Key::Control]).into()], - key_groups_mac: Some(vec![KeysGroup(vec![Key::Command]).into()]), - mouse: None, - label: String::from("Innermost"), - plus: true, - }, - HintInfo { - key_groups: vec![KeysGroup(vec![Key::Shift]).into()], - key_groups_mac: None, - mouse: None, - label: String::from("Grow/Shrink Selection"), - plus: true, - }, + HintInfo::mouse(MouseMotion::Lmb, "Select Object"), + HintInfo::keys([Key::Control], "Innermost").add_mac_keys([Key::Command]).prepend_plus(), + HintInfo::keys([Key::Shift], "Grow/Shrink Selection").prepend_plus(), ]), HintGroup(vec![ - HintInfo { - key_groups: vec![], - key_groups_mac: None, - mouse: Some(MouseMotion::LmbDrag), - label: String::from("Select Area"), - plus: false, - }, - HintInfo { - key_groups: vec![KeysGroup(vec![Key::Shift]).into()], - key_groups_mac: None, - mouse: None, - label: String::from("Grow/Shrink Selection"), - plus: true, - }, + HintInfo::mouse(MouseMotion::LmbDrag, "Select Area"), + HintInfo::keys([Key::Shift], "Grow/Shrink Selection").prepend_plus(), ]), + HintGroup(vec![HintInfo::arrow_keys("Nudge Selected"), HintInfo::keys([Key::Shift], "Big Increment Nudge").prepend_plus()]), HintGroup(vec![ - HintInfo { - key_groups: vec![ - KeysGroup(vec![Key::ArrowUp]).into(), - KeysGroup(vec![Key::ArrowRight]).into(), - KeysGroup(vec![Key::ArrowDown]).into(), - KeysGroup(vec![Key::ArrowLeft]).into(), - ], - key_groups_mac: None, - mouse: None, - label: String::from("Nudge Selected"), - plus: false, - }, - HintInfo { - key_groups: vec![KeysGroup(vec![Key::Shift]).into()], - key_groups_mac: None, - mouse: None, - label: String::from("Big Increment Nudge"), - plus: true, - }, - ]), - HintGroup(vec![ - HintInfo { - key_groups: vec![KeysGroup(vec![Key::Alt]).into()], - key_groups_mac: None, - mouse: Some(MouseMotion::LmbDrag), - label: String::from("Move Duplicate"), - plus: false, - }, - HintInfo { - key_groups: vec![KeysGroup(vec![Key::Control, Key::KeyD]).into()], - key_groups_mac: Some(vec![KeysGroup(vec![Key::Command, Key::KeyD]).into()]), - mouse: None, - label: String::from("Duplicate"), - plus: false, - }, + HintInfo::keys([Key::Alt], "Move Duplicate"), + HintInfo::keys([Key::Control, Key::KeyD], "Duplicate").add_mac_keys([Key::Command, Key::KeyD]), ]), ]), SelectToolFsmState::Dragging => HintData(vec![HintGroup(vec![ - HintInfo { - key_groups: vec![KeysGroup(vec![Key::Shift]).into()], - key_groups_mac: None, - mouse: None, - label: String::from("Constrain to Axis"), - plus: false, - }, - HintInfo { - key_groups: vec![KeysGroup(vec![Key::Control]).into()], - key_groups_mac: None, - mouse: None, - label: String::from("Snap to Points (coming soon)"), - plus: false, - }, + HintInfo::keys([Key::Shift], "Constrain to Axis"), + HintInfo::keys([Key::Control], "Snap to Points (coming soon)"), ])]), SelectToolFsmState::DrawingBox => HintData(vec![]), SelectToolFsmState::ResizingBounds => HintData(vec![]), - SelectToolFsmState::RotatingBounds => HintData(vec![HintGroup(vec![HintInfo { - key_groups: vec![KeysGroup(vec![Key::Control]).into()], - key_groups_mac: None, - mouse: None, - label: String::from("Snap 15°"), - plus: false, - }])]), + SelectToolFsmState::RotatingBounds => HintData(vec![HintGroup(vec![HintInfo::keys([Key::Control], "Snap 15°")])]), SelectToolFsmState::DraggingPivot => HintData(vec![]), }; diff --git a/editor/src/messages/tool/tool_messages/shape_tool.rs b/editor/src/messages/tool/tool_messages/shape_tool.rs index e1a6e0e22b..a9b215ad5d 100644 --- a/editor/src/messages/tool/tool_messages/shape_tool.rs +++ b/editor/src/messages/tool/tool_messages/shape_tool.rs @@ -1,7 +1,6 @@ -use crate::consts::DRAG_THRESHOLD; use crate::messages::frontend::utility_types::MouseCursorIcon; -use crate::messages::input_mapper::utility_types::input_keyboard::{Key, KeysGroup, MouseMotion}; -use crate::messages::layout::utility_types::layout_widget::{Layout, LayoutGroup, PropertyHolder, Widget, WidgetCallback, WidgetHolder, WidgetLayout}; +use crate::messages::input_mapper::utility_types::input_keyboard::{Key, MouseMotion}; +use crate::messages::layout::utility_types::layout_widget::{Layout, LayoutGroup, PropertyHolder, WidgetLayout}; use crate::messages::layout::utility_types::widgets::input_widgets::NumberInput; use crate::messages::prelude::*; use crate::messages::tool::common_functionality::resize::Resize; @@ -69,32 +68,20 @@ impl ToolMetadata for ShapeTool { impl PropertyHolder for ShapeTool { fn properties(&self) -> Layout { - Layout::WidgetLayout(WidgetLayout::new(vec![LayoutGroup::Row { - widgets: vec![WidgetHolder::new(Widget::NumberInput(NumberInput { - label: "Sides".into(), - value: Some(self.options.vertices as f64), - is_integer: true, - min: Some(3.), - max: Some(1000.), - on_update: WidgetCallback::new(|number_input: &NumberInput| ShapeToolMessage::UpdateOptions(ShapeOptionsUpdate::Vertices(number_input.value.unwrap() as u32)).into()), - ..NumberInput::default() - }))], - }])) + let sides = NumberInput::new(Some(self.options.vertices as f64)) + .label("Sides") + .int() + .min(3.) + .max(1000.) + .mode(crate::messages::layout::utility_types::widget_prelude::NumberInputMode::Increment) + .on_update(|number_input: &NumberInput| ShapeToolMessage::UpdateOptions(ShapeOptionsUpdate::Vertices(number_input.value.unwrap() as u32)).into()) + .widget_holder(); + Layout::WidgetLayout(WidgetLayout::new(vec![LayoutGroup::Row { widgets: vec![sides] }])) } } impl<'a> MessageHandler> for ShapeTool { fn process_message(&mut self, message: ToolMessage, tool_data: ToolActionHandlerData<'a>, responses: &mut VecDeque) { - if message == ToolMessage::UpdateHints { - self.fsm_state.update_hints(responses); - return; - } - - if message == ToolMessage::UpdateCursor { - self.fsm_state.update_cursor(responses); - return; - } - if let ToolMessage::Shape(ShapeToolMessage::UpdateOptions(action)) = message { match action { ShapeOptionsUpdate::Vertices(vertices) => self.options.vertices = vertices, @@ -102,13 +89,7 @@ impl<'a> MessageHandler> for ShapeTool { return; } - let new_state = self.fsm_state.transition(message, &mut self.tool_data, tool_data, &self.options, responses); - - if self.fsm_state != new_state { - self.fsm_state = new_state; - self.fsm_state.update_hints(responses); - self.fsm_state.update_cursor(responses); - } + self.fsm_state.process_event(message, &mut self.tool_data, tool_data, &self.options, responses, true); } fn actions(&self) -> ActionList { @@ -137,17 +118,13 @@ impl ToolTransition for ShapeTool { } } -#[derive(Clone, Copy, Debug, PartialEq, Eq)] +#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] enum ShapeToolFsmState { + #[default] Ready, Drawing, } -impl Default for ShapeToolFsmState { - fn default() -> Self { - ShapeToolFsmState::Ready - } -} #[derive(Clone, Debug, Default)] struct ShapeToolData { sides: u32, @@ -201,11 +178,7 @@ impl Fsm for ShapeToolFsmState { state } (Drawing, DragStop) => { - match shape_data.viewport_drag_start(document).distance(input.mouse.position) <= DRAG_THRESHOLD { - true => responses.push_back(DocumentMessage::AbortTransaction.into()), - false => responses.push_back(DocumentMessage::CommitTransaction.into()), - } - + input.mouse.finish_transaction(shape_data.viewport_drag_start(document), responses); shape_data.cleanup(responses); Ready @@ -227,44 +200,11 @@ impl Fsm for ShapeToolFsmState { fn update_hints(&self, responses: &mut VecDeque) { let hint_data = match self { ShapeToolFsmState::Ready => HintData(vec![HintGroup(vec![ - HintInfo { - key_groups: vec![], - key_groups_mac: None, - mouse: Some(MouseMotion::LmbDrag), - label: String::from("Draw Shape"), - plus: false, - }, - HintInfo { - key_groups: vec![KeysGroup(vec![Key::Shift]).into()], - key_groups_mac: None, - mouse: None, - label: String::from("Constrain 1:1 Aspect"), - plus: true, - }, - HintInfo { - key_groups: vec![KeysGroup(vec![Key::Alt]).into()], - key_groups_mac: None, - mouse: None, - label: String::from("From Center"), - plus: true, - }, - ])]), - ShapeToolFsmState::Drawing => HintData(vec![HintGroup(vec![ - HintInfo { - key_groups: vec![KeysGroup(vec![Key::Shift]).into()], - key_groups_mac: None, - mouse: None, - label: String::from("Constrain 1:1 Aspect"), - plus: false, - }, - HintInfo { - key_groups: vec![KeysGroup(vec![Key::Alt]).into()], - key_groups_mac: None, - mouse: None, - label: String::from("From Center"), - plus: false, - }, + HintInfo::mouse(MouseMotion::LmbDrag, "Draw Shape"), + HintInfo::keys([Key::Shift], "Constrain 1:1 Aspect").prepend_plus(), + HintInfo::keys([Key::Alt], "From Center").prepend_plus(), ])]), + ShapeToolFsmState::Drawing => HintData(vec![HintGroup(vec![HintInfo::keys([Key::Shift], "Constrain 1:1 Aspect"), HintInfo::keys([Key::Alt], "From Center")])]), }; responses.push_back(FrontendMessage::UpdateInputHints { hint_data }.into()); diff --git a/editor/src/messages/tool/tool_messages/spline_tool.rs b/editor/src/messages/tool/tool_messages/spline_tool.rs index 3b54799b05..d984551cc7 100644 --- a/editor/src/messages/tool/tool_messages/spline_tool.rs +++ b/editor/src/messages/tool/tool_messages/spline_tool.rs @@ -1,7 +1,7 @@ use crate::consts::DRAG_THRESHOLD; use crate::messages::frontend::utility_types::MouseCursorIcon; -use crate::messages::input_mapper::utility_types::input_keyboard::{Key, KeysGroup, MouseMotion}; -use crate::messages::layout::utility_types::layout_widget::{Layout, LayoutGroup, PropertyHolder, Widget, WidgetCallback, WidgetHolder, WidgetLayout}; +use crate::messages::input_mapper::utility_types::input_keyboard::{Key, MouseMotion}; +use crate::messages::layout::utility_types::layout_widget::{Layout, LayoutGroup, PropertyHolder, WidgetLayout}; use crate::messages::layout::utility_types::widgets::input_widgets::NumberInput; use crate::messages::prelude::*; use crate::messages::tool::common_functionality::snapping::SnapManager; @@ -49,8 +49,9 @@ pub enum SplineToolMessage { UpdateOptions(SplineOptionsUpdate), } -#[derive(Clone, Copy, Debug, PartialEq, Eq)] +#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] enum SplineToolFsmState { + #[default] Ready, Drawing, } @@ -75,32 +76,18 @@ impl ToolMetadata for SplineTool { impl PropertyHolder for SplineTool { fn properties(&self) -> Layout { - Layout::WidgetLayout(WidgetLayout::new(vec![LayoutGroup::Row { - widgets: vec![WidgetHolder::new(Widget::NumberInput(NumberInput { - unit: " px".into(), - label: "Weight".into(), - value: Some(self.options.line_weight), - is_integer: false, - min: Some(0.), - on_update: WidgetCallback::new(|number_input: &NumberInput| SplineToolMessage::UpdateOptions(SplineOptionsUpdate::LineWeight(number_input.value.unwrap())).into()), - ..NumberInput::default() - }))], - }])) + let weight = NumberInput::new(Some(self.options.line_weight)) + .unit(" px") + .label("Weight") + .min(0.) + .on_update(|number_input: &NumberInput| SplineToolMessage::UpdateOptions(SplineOptionsUpdate::LineWeight(number_input.value.unwrap())).into()) + .widget_holder(); + Layout::WidgetLayout(WidgetLayout::new(vec![LayoutGroup::Row { widgets: vec![weight] }])) } } impl<'a> MessageHandler> for SplineTool { fn process_message(&mut self, message: ToolMessage, tool_data: ToolActionHandlerData<'a>, responses: &mut VecDeque) { - if message == ToolMessage::UpdateHints { - self.fsm_state.update_hints(responses); - return; - } - - if message == ToolMessage::UpdateCursor { - self.fsm_state.update_cursor(responses); - return; - } - if let ToolMessage::Spline(SplineToolMessage::UpdateOptions(action)) = message { match action { SplineOptionsUpdate::LineWeight(line_weight) => self.options.line_weight = line_weight, @@ -108,13 +95,7 @@ impl<'a> MessageHandler> for SplineTool { return; } - let new_state = self.fsm_state.transition(message, &mut self.tool_data, tool_data, &self.options, responses); - - if self.fsm_state != new_state { - self.fsm_state = new_state; - self.fsm_state.update_hints(responses); - self.fsm_state.update_cursor(responses); - } + self.fsm_state.process_event(message, &mut self.tool_data, tool_data, &self.options, responses, true); } fn actions(&self) -> ActionList { @@ -148,11 +129,6 @@ impl ToolTransition for SplineTool { } } -impl Default for SplineToolFsmState { - fn default() -> Self { - SplineToolFsmState::Ready - } -} #[derive(Clone, Debug, Default)] struct SplineToolData { points: Vec, @@ -251,28 +227,10 @@ impl Fsm for SplineToolFsmState { fn update_hints(&self, responses: &mut VecDeque) { let hint_data = match self { - SplineToolFsmState::Ready => HintData(vec![HintGroup(vec![HintInfo { - key_groups: vec![], - key_groups_mac: None, - mouse: Some(MouseMotion::Lmb), - label: String::from("Draw Spline"), - plus: false, - }])]), + SplineToolFsmState::Ready => HintData(vec![HintGroup(vec![HintInfo::mouse(MouseMotion::Lmb, "Draw Spline")])]), SplineToolFsmState::Drawing => HintData(vec![ - HintGroup(vec![HintInfo { - key_groups: vec![], - key_groups_mac: None, - mouse: Some(MouseMotion::Lmb), - label: String::from("Extend Spline"), - plus: false, - }]), - HintGroup(vec![HintInfo { - key_groups: vec![KeysGroup(vec![Key::Enter]).into()], - key_groups_mac: None, - mouse: None, - label: String::from("End Spline"), - plus: false, - }]), + HintGroup(vec![HintInfo::mouse(MouseMotion::Lmb, "Extend Spline")]), + HintGroup(vec![HintInfo::keys([Key::Enter], "End Spline")]), ]), }; diff --git a/editor/src/messages/tool/tool_messages/text_tool.rs b/editor/src/messages/tool/tool_messages/text_tool.rs index 33c596b408..f174cfca06 100644 --- a/editor/src/messages/tool/tool_messages/text_tool.rs +++ b/editor/src/messages/tool/tool_messages/text_tool.rs @@ -1,11 +1,10 @@ use crate::application::generate_uuid; use crate::consts::{COLOR_ACCENT, SELECTION_TOLERANCE}; use crate::messages::frontend::utility_types::MouseCursorIcon; -use crate::messages::input_mapper::utility_types::input_keyboard::{Key, KeysGroup, MouseMotion}; -use crate::messages::layout::utility_types::layout_widget::{Layout, LayoutGroup, PropertyHolder, Widget, WidgetCallback, WidgetHolder, WidgetLayout}; +use crate::messages::input_mapper::utility_types::input_keyboard::{Key, MouseMotion}; +use crate::messages::layout::utility_types::layout_widget::{Layout, LayoutGroup, PropertyHolder, WidgetCallback, WidgetHolder, WidgetLayout}; use crate::messages::layout::utility_types::misc::LayoutTarget; use crate::messages::layout::utility_types::widgets::input_widgets::{FontInput, NumberInput}; -use crate::messages::layout::utility_types::widgets::label_widgets::{Separator, SeparatorDirection, SeparatorType}; use crate::messages::prelude::*; use crate::messages::tool::utility_types::{EventToMessageMap, Fsm, ToolActionHandlerData, ToolMetadata, ToolTransition, ToolType}; use crate::messages::tool::utility_types::{HintData, HintGroup, HintInfo}; @@ -86,68 +85,49 @@ impl ToolMetadata for TextTool { impl PropertyHolder for TextTool { fn properties(&self) -> Layout { + let font = FontInput { + is_style_picker: false, + font_family: self.options.font_name.clone(), + font_style: self.options.font_style.clone(), + on_update: WidgetCallback::new(|font_input: &FontInput| { + TextToolMessage::UpdateOptions(TextOptionsUpdate::Font { + family: font_input.font_family.clone(), + style: font_input.font_style.clone(), + }) + .into() + }), + ..Default::default() + } + .widget_holder(); + let style = FontInput { + is_style_picker: true, + font_family: self.options.font_name.clone(), + font_style: self.options.font_style.clone(), + on_update: WidgetCallback::new(|font_input: &FontInput| { + TextToolMessage::UpdateOptions(TextOptionsUpdate::Font { + family: font_input.font_family.clone(), + style: font_input.font_style.clone(), + }) + .into() + }), + ..Default::default() + } + .widget_holder(); + let size = NumberInput::new(Some(self.options.font_size as f64)) + .unit(" px") + .label("Size") + .int() + .min(1.) + .on_update(|number_input: &NumberInput| TextToolMessage::UpdateOptions(TextOptionsUpdate::FontSize(number_input.value.unwrap() as u32)).into()) + .widget_holder(); Layout::WidgetLayout(WidgetLayout::new(vec![LayoutGroup::Row { - widgets: vec![ - WidgetHolder::new(Widget::FontInput(FontInput { - is_style_picker: false, - font_family: self.options.font_name.clone(), - font_style: self.options.font_style.clone(), - on_update: WidgetCallback::new(|font_input: &FontInput| { - TextToolMessage::UpdateOptions(TextOptionsUpdate::Font { - family: font_input.font_family.clone(), - style: font_input.font_style.clone(), - }) - .into() - }), - ..Default::default() - })), - WidgetHolder::new(Widget::Separator(Separator { - direction: SeparatorDirection::Horizontal, - separator_type: SeparatorType::Related, - })), - WidgetHolder::new(Widget::FontInput(FontInput { - is_style_picker: true, - font_family: self.options.font_name.clone(), - font_style: self.options.font_style.clone(), - on_update: WidgetCallback::new(|font_input: &FontInput| { - TextToolMessage::UpdateOptions(TextOptionsUpdate::Font { - family: font_input.font_family.clone(), - style: font_input.font_style.clone(), - }) - .into() - }), - ..Default::default() - })), - WidgetHolder::new(Widget::Separator(Separator { - direction: SeparatorDirection::Horizontal, - separator_type: SeparatorType::Related, - })), - WidgetHolder::new(Widget::NumberInput(NumberInput { - unit: " px".into(), - label: "Size".into(), - value: Some(self.options.font_size as f64), - is_integer: true, - min: Some(1.), - on_update: WidgetCallback::new(|number_input: &NumberInput| TextToolMessage::UpdateOptions(TextOptionsUpdate::FontSize(number_input.value.unwrap() as u32)).into()), - ..NumberInput::default() - })), - ], + widgets: vec![font, WidgetHolder::related_separator(), style, WidgetHolder::related_separator(), size], }])) } } impl<'a> MessageHandler> for TextTool { fn process_message(&mut self, message: ToolMessage, tool_data: ToolActionHandlerData<'a>, responses: &mut VecDeque) { - if message == ToolMessage::UpdateHints { - self.fsm_state.update_hints(responses); - return; - } - - if message == ToolMessage::UpdateCursor { - self.fsm_state.update_cursor(responses); - return; - } - if let ToolMessage::Text(TextToolMessage::UpdateOptions(action)) = message { match action { TextOptionsUpdate::Font { family, style } => { @@ -161,13 +141,7 @@ impl<'a> MessageHandler> for TextTool { return; } - let new_state = self.fsm_state.transition(message, &mut self.tool_data, tool_data, &self.options, responses); - - if self.fsm_state != new_state { - self.fsm_state = new_state; - self.fsm_state.update_hints(responses); - self.fsm_state.update_cursor(responses); - } + self.fsm_state.process_event(message, &mut self.tool_data, tool_data, &self.options, responses, true); } fn actions(&self) -> ActionList { @@ -196,18 +170,12 @@ impl ToolTransition for TextTool { } } -#[derive(Clone, Copy, Debug, PartialEq, Eq)] +#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] enum TextToolFsmState { + #[default] Ready, Editing, } - -impl Default for TextToolFsmState { - fn default() -> Self { - TextToolFsmState::Ready - } -} - #[derive(Clone, Debug, Default)] struct TextToolData { layer_path: Vec, @@ -421,37 +389,10 @@ impl Fsm for TextToolFsmState { fn update_hints(&self, responses: &mut VecDeque) { let hint_data = match self { - TextToolFsmState::Ready => HintData(vec![HintGroup(vec![ - HintInfo { - key_groups: vec![], - key_groups_mac: None, - mouse: Some(MouseMotion::Lmb), - label: String::from("Add Text"), - plus: false, - }, - HintInfo { - key_groups: vec![], - key_groups_mac: None, - mouse: Some(MouseMotion::Lmb), - label: String::from("Edit Text"), - plus: false, - }, - ])]), + TextToolFsmState::Ready => HintData(vec![HintGroup(vec![HintInfo::mouse(MouseMotion::Lmb, "Add Text"), HintInfo::mouse(MouseMotion::Lmb, "Edit Text")])]), TextToolFsmState::Editing => HintData(vec![HintGroup(vec![ - HintInfo { - key_groups: vec![KeysGroup(vec![Key::Control, Key::Enter]).into()], - key_groups_mac: Some(vec![KeysGroup(vec![Key::Command, Key::Enter]).into()]), - mouse: None, - label: String::from("Commit Edit"), - plus: false, - }, - HintInfo { - key_groups: vec![KeysGroup(vec![Key::Escape]).into()], - key_groups_mac: None, - mouse: None, - label: String::from("Discard Edit"), - plus: false, - }, + HintInfo::keys([Key::Control, Key::Enter], "Commit Edit").add_mac_keys([Key::Command, Key::Enter]), + HintInfo::keys([Key::Escape], "Discard Edit"), ])]), }; diff --git a/editor/src/messages/tool/utility_types.rs b/editor/src/messages/tool/utility_types.rs index 9e52157abc..1e889d96ed 100644 --- a/editor/src/messages/tool/utility_types.rs +++ b/editor/src/messages/tool/utility_types.rs @@ -1,6 +1,5 @@ use super::tool_messages::*; -use crate::messages::input_mapper::utility_types::input_keyboard::LayoutKeysGroup; -use crate::messages::input_mapper::utility_types::input_keyboard::MouseMotion; +use crate::messages::input_mapper::utility_types::input_keyboard::{Key, KeysGroup, LayoutKeysGroup, MouseMotion}; use crate::messages::input_mapper::utility_types::macros::action_keys; use crate::messages::input_mapper::utility_types::misc::ActionKeys; use crate::messages::layout::utility_types::layout_widget::{Layout, LayoutGroup, PropertyHolder, Widget, WidgetCallback, WidgetHolder, WidgetLayout}; @@ -23,15 +22,78 @@ impl ToolCommon for T where T: for<'a> MessageHandler) -> Self; + /// Implementing this trait function lets a specific tool provide a list of hints (user input actions presently available) to draw in the footer bar. fn update_hints(&self, responses: &mut VecDeque); + /// Implementing this trait function lets a specific tool set the current mouse cursor icon. fn update_cursor(&self, responses: &mut VecDeque); + + /// If this message is a standard tool message, process it and return true. Standard tool messages are those which are common across every tool. + fn standard_tool_messages(&self, message: &ToolMessage, messages: &mut VecDeque) -> bool { + // Check for standard hits or cursor events + match message { + ToolMessage::UpdateHints => { + self.update_hints(messages); + true + } + ToolMessage::UpdateCursor => { + self.update_cursor(messages); + true + } + _ => false, + } + } + + /// When an event makes the tool change or do something, it is processed here to perform a step (transition) on the tool's finite state machine (FSM). + /// This function is called by the specific tool's message handler when the dispatcher routes a message to the active tool. + fn process_event( + &mut self, + message: ToolMessage, + tool_data: &mut Self::ToolData, + transition_data: ToolActionHandlerData, + options: &Self::ToolOptions, + messages: &mut VecDeque, + update_cursor_on_transition: bool, + ) where + Self: PartialEq + Sized + Copy, + { + // If this message is one of the standard tool messages, process it and exit early + if self.standard_tool_messages(&message, messages) { + return; + } + + // Transition the tool + let new_state = self.transition(message, tool_data, transition_data, options, messages); + + // Update state + if *self != new_state { + *self = new_state; + self.update_hints(messages); + if update_cursor_on_transition { + self.update_cursor(messages); + } + } + } } #[derive(Debug, Clone)] @@ -455,6 +517,55 @@ pub struct HintInfo { pub plus: bool, } +impl HintInfo { + pub fn keys(keys: impl IntoIterator, label: impl Into) -> Self { + let keys: Vec<_> = keys.into_iter().collect(); + Self { + key_groups: vec![KeysGroup(keys).into()], + key_groups_mac: None, + mouse: None, + label: label.into(), + plus: false, + } + } + + pub fn mouse(mouse_motion: MouseMotion, label: impl Into) -> Self { + Self { + key_groups: vec![], + key_groups_mac: None, + mouse: Some(mouse_motion), + label: label.into(), + plus: false, + } + } + + pub fn arrow_keys(label: impl Into) -> Self { + HintInfo { + key_groups: vec![ + KeysGroup(vec![Key::ArrowUp]).into(), + KeysGroup(vec![Key::ArrowRight]).into(), + KeysGroup(vec![Key::ArrowDown]).into(), + KeysGroup(vec![Key::ArrowLeft]).into(), + ], + key_groups_mac: None, + mouse: None, + label: label.into(), + plus: false, + } + } + + pub fn prepend_plus(mut self) -> Self { + self.plus = true; + self + } + + pub fn add_mac_keys(mut self, keys: impl IntoIterator) -> Self { + let mac_keys: Vec<_> = keys.into_iter().collect(); + self.key_groups_mac = Some(vec![KeysGroup(mac_keys).into()]); + self + } +} + #[cfg(test)] mod tool_crash_on_layer_delete_tests { use crate::application::{set_uuid_seed, Editor};