diff --git a/editor/src/document/movement_handler.rs b/editor/src/document/movement_handler.rs index 5f51f2fc18..e5d625bf82 100644 --- a/editor/src/document/movement_handler.rs +++ b/editor/src/document/movement_handler.rs @@ -1,6 +1,7 @@ use crate::consts::VIEWPORT_ROTATE_SNAP_INTERVAL; pub use crate::document::layer_panel::*; use crate::document::DocumentMessage; +use crate::input::keyboard::Key; use crate::message_prelude::*; use crate::{ consts::{VIEWPORT_SCROLL_RATE, VIEWPORT_ZOOM_LEVELS, VIEWPORT_ZOOM_MOUSE_RATE, VIEWPORT_ZOOM_SCALE_MAX, VIEWPORT_ZOOM_SCALE_MIN, VIEWPORT_ZOOM_WHEEL_RATE}, @@ -16,12 +17,10 @@ use std::collections::VecDeque; #[impl_message(Message, DocumentMessage, Movement)] #[derive(PartialEq, Clone, Debug, Serialize, Deserialize)] pub enum MovementMessage { - MouseMove, + MouseMove { snap_angle: Key }, TranslateCanvasBegin, WheelCanvasTranslate { use_y_as_x: bool }, - RotateCanvasBegin { snap: bool }, - EnableSnapping, - DisableSnapping, + RotateCanvasBegin, ZoomCanvasBegin, TransformCanvasEnd, SetCanvasRotation(f64), @@ -101,16 +100,10 @@ impl MessageHandler for Moveme self.translating = true; self.mouse_pos = ipp.mouse.position; } - RotateCanvasBegin { snap } => { + RotateCanvasBegin => { self.rotating = true; - self.snap_rotate = snap; self.mouse_pos = ipp.mouse.position; } - EnableSnapping => self.snap_rotate = true, - DisableSnapping => { - self.rotation = self.snapped_angle(); - self.snap_rotate = false - } ZoomCanvasBegin => { self.zooming = true; self.mouse_pos = ipp.mouse.position; @@ -123,7 +116,7 @@ impl MessageHandler for Moveme self.rotating = false; self.zooming = false; } - MouseMove => { + MouseMove { snap_angle } => { if self.translating { let delta = ipp.mouse.position - self.mouse_pos; let transformed_delta = document.root.transform.inverse().transform_vector2(delta); @@ -132,7 +125,15 @@ impl MessageHandler for Moveme responses.push_back(ToolMessage::DocumentIsDirty.into()); self.create_document_transform(&ipp.viewport_bounds, responses); } + if self.rotating { + let new_snap = ipp.keyboard.get(snap_angle as usize); + // When disabling snap, keep the viewed rotation as it was previously. + if !new_snap && self.snap_rotate { + self.rotation = self.snapped_angle(); + } + self.snap_rotate = new_snap; + let half_viewport = ipp.viewport_bounds.size() / 2.; let rotation = { let start_vec = self.mouse_pos - half_viewport; @@ -151,8 +152,8 @@ impl MessageHandler for Moveme let new = (self.scale * amount).clamp(VIEWPORT_ZOOM_SCALE_MIN, VIEWPORT_ZOOM_SCALE_MAX); self.scale = new; - responses.push_back(FrontendMessage::SetCanvasZoom { new_zoom: self.scale }.into()); responses.push_back(ToolMessage::DocumentIsDirty.into()); + responses.push_back(FrontendMessage::SetCanvasZoom { new_zoom: self.scale }.into()); self.create_document_transform(&ipp.viewport_bounds, responses); } self.mouse_pos = ipp.mouse.position; @@ -213,11 +214,11 @@ impl MessageHandler for Moveme responses.push_back(ToolMessage::DocumentIsDirty.into()); self.create_document_transform(&ipp.viewport_bounds, responses); } - SetCanvasRotation(new) => { - self.rotation = new; + SetCanvasRotation(new_radians) => { + self.rotation = new_radians; self.create_document_transform(&ipp.viewport_bounds, responses); responses.push_back(ToolMessage::DocumentIsDirty.into()); - responses.push_back(FrontendMessage::SetCanvasRotation { new_radians: new }.into()); + responses.push_back(FrontendMessage::SetCanvasRotation { new_radians }.into()); } ZoomCanvasToFitAll => { if let Some([pos1, pos2]) = document.visible_layers_bounding_box() { @@ -272,13 +273,6 @@ impl MessageHandler for Moveme TranslateCanvasByViewportFraction, ); - if self.rotating { - let snapping = actions!(MovementMessageDiscriminant; - EnableSnapping, - DisableSnapping, - ); - common.extend(snapping); - } if self.translating || self.rotating || self.zooming { let transforming = actions!(MovementMessageDiscriminant; TransformCanvasEnd, diff --git a/editor/src/input/input_mapper.rs b/editor/src/input/input_mapper.rs index 79aa07a0fb..fdc5eac129 100644 --- a/editor/src/input/input_mapper.rs +++ b/editor/src/input/input_mapper.rs @@ -135,8 +135,6 @@ impl Default for Mapping { let mappings = mapping![ // Higher priority than entries in sections below entry! {action=DocumentsMessage::Paste(User), key_down=KeyV, modifiers=[KeyControl]}, - entry! {action=MovementMessage::EnableSnapping, key_down=KeyShift}, - entry! {action=MovementMessage::DisableSnapping, key_up=KeyShift}, // Transform layers entry! {action=TransformLayerMessage::ApplyOperation, key_down=KeyEnter}, entry! {action=TransformLayerMessage::ApplyOperation, key_down=Lmb}, @@ -155,6 +153,12 @@ impl Default for Mapping { entry! {action=SelectMessage::DragStop, key_up=Lmb}, entry! {action=SelectMessage::Abort, key_down=Rmb}, entry! {action=SelectMessage::Abort, key_down=KeyEscape}, + // Navigate + entry! {action=NavigateMessage::MouseMove{snap_angle: KeyControl}, message=InputMapperMessage::PointerMove}, + entry! {action=NavigateMessage::RotateCanvasBegin, key_down=Rmb}, + entry! {action=NavigateMessage::ZoomCanvasBegin, key_down=Lmb}, + entry! {action=NavigateMessage::TransformCanvasEnd, key_up=Rmb}, + entry! {action=NavigateMessage::TransformCanvasEnd, key_up=Lmb}, // Eyedropper entry! {action=EyedropperMessage::LeftMouseDown, key_down=Lmb}, entry! {action=EyedropperMessage::RightMouseDown, key_down=Rmb}, @@ -194,6 +198,7 @@ impl Default for Mapping { entry! {action=FillMessage::RightMouseDown, key_down=Rmb}, // Tool Actions entry! {action=ToolMessage::ActivateTool(ToolType::Select), key_down=KeyV}, + entry! {action=ToolMessage::ActivateTool(ToolType::Navigate), key_down=KeyZ}, entry! {action=ToolMessage::ActivateTool(ToolType::Eyedropper), key_down=KeyI}, entry! {action=ToolMessage::ActivateTool(ToolType::Fill), key_down=KeyF}, entry! {action=ToolMessage::ActivateTool(ToolType::Path), key_down=KeyA}, @@ -226,11 +231,9 @@ impl Default for Mapping { entry! {action=TransformLayerMessage::BeginRotate, key_down=KeyR}, entry! {action=TransformLayerMessage::BeginScale, key_down=KeyS}, // Document movement - entry! {action=MovementMessage::MouseMove, message=InputMapperMessage::PointerMove}, - entry! {action=MovementMessage::RotateCanvasBegin{snap:false}, key_down=Mmb, modifiers=[KeyControl]}, - entry! {action=MovementMessage::RotateCanvasBegin{snap:true}, key_down=Mmb, modifiers=[KeyControl, KeyShift]}, + entry! {action=MovementMessage::MouseMove{snap_angle: KeyShift}, message=InputMapperMessage::PointerMove}, + entry! {action=MovementMessage::RotateCanvasBegin, key_down=Mmb, modifiers=[KeyControl]}, entry! {action=MovementMessage::ZoomCanvasBegin, key_down=Mmb, modifiers=[KeyShift]}, - entry! {action=MovementMessage::ZoomCanvasToFitAll, key_down=Key0, modifiers=[KeyControl]}, entry! {action=MovementMessage::TranslateCanvasBegin, key_down=Mmb}, entry! {action=MovementMessage::TransformCanvasEnd, key_up=Mmb}, entry! {action=MovementMessage::TranslateCanvasBegin, key_down=Lmb, modifiers=[KeySpace]}, @@ -240,6 +243,7 @@ impl Default for Mapping { entry! {action=MovementMessage::DecreaseCanvasZoom, key_down=KeyMinus, modifiers=[KeyControl]}, entry! {action=MovementMessage::SetCanvasZoom(1.), key_down=Key1, modifiers=[KeyControl]}, entry! {action=MovementMessage::SetCanvasZoom(2.), key_down=Key2, modifiers=[KeyControl]}, + entry! {action=MovementMessage::ZoomCanvasToFitAll, key_down=Key0, modifiers=[KeyControl]}, entry! {action=MovementMessage::WheelCanvasZoom, message=InputMapperMessage::MouseScroll, modifiers=[KeyControl]}, entry! {action=MovementMessage::WheelCanvasTranslate{use_y_as_x: true}, message=InputMapperMessage::MouseScroll, modifiers=[KeyShift]}, entry! {action=MovementMessage::WheelCanvasTranslate{use_y_as_x: false}, message=InputMapperMessage::MouseScroll}, diff --git a/editor/src/tool/tool_message_handler.rs b/editor/src/tool/tool_message_handler.rs index b8c7104d14..b67294b471 100644 --- a/editor/src/tool/tool_message_handler.rs +++ b/editor/src/tool/tool_message_handler.rs @@ -166,6 +166,7 @@ fn standard_tool_message(tool: ToolType, message_type: StandardToolMessageType) StandardToolMessageType::DocumentIsDirty => match tool { ToolType::Select => Some(SelectMessage::DocumentIsDirty.into()), ToolType::Path => Some(PathMessage::DocumentIsDirty.into()), + //ToolType::Navigate => Some(NavigateMessage::DocumentIsDirty.into()) // ToolType::Pen => Some(PenMessage::DocumentIsDirty.into()), // ToolType::Line => Some(LineMessage::DocumentIsDirty.into()), // ToolType::Rectangle => Some(RectangleMessage::DocumentIsDirty.into()), @@ -178,6 +179,7 @@ fn standard_tool_message(tool: ToolType, message_type: StandardToolMessageType) StandardToolMessageType::Abort => match tool { ToolType::Select => Some(SelectMessage::Abort.into()), ToolType::Path => Some(PathMessage::Abort.into()), + ToolType::Navigate => Some(NavigateMessage::Abort.into()), ToolType::Pen => Some(PenMessage::Abort.into()), ToolType::Line => Some(LineMessage::Abort.into()), ToolType::Rectangle => Some(RectangleMessage::Abort.into()), diff --git a/editor/src/tool/tools/navigate.rs b/editor/src/tool/tools/navigate.rs index 136b149cc2..6e584f3ad9 100644 --- a/editor/src/tool/tools/navigate.rs +++ b/editor/src/tool/tools/navigate.rs @@ -1,20 +1,147 @@ -use crate::message_prelude::*; -use crate::tool::ToolActionHandlerData; +use crate::input::keyboard::MouseMotion; +use crate::misc::{HintData, HintGroup, HintInfo, KeysGroup}; +use crate::tool::{Fsm, ToolActionHandlerData}; +use crate::{input::keyboard::Key, message_prelude::*}; use serde::{Deserialize, Serialize}; #[derive(Default)] -pub struct Navigate; +pub struct Navigate { + fsm_state: NavigateToolFsmState, +} #[impl_message(Message, ToolMessage, Navigate)] #[derive(PartialEq, Clone, Debug, Hash, Serialize, Deserialize)] pub enum NavigateMessage { - MouseMove, + MouseMove { snap_angle: Key }, + TranslateCanvasBegin, + RotateCanvasBegin, + ZoomCanvasBegin, + TransformCanvasEnd, + Abort, } impl<'a> MessageHandler> for Navigate { fn process_action(&mut self, action: ToolMessage, data: ToolActionHandlerData<'a>, responses: &mut VecDeque) { - todo!("{}::handle_input {:?} {:?} {:?} ", module_path!(), action, data, responses); + if action == ToolMessage::UpdateHints { + self.fsm_state.update_hints(responses); + return; + } + + let new_state = self.fsm_state.transition(action, data.0, data.1, &mut (), data.2, responses); + + if self.fsm_state != new_state { + self.fsm_state = new_state; + self.fsm_state.update_hints(responses); + } + } + + fn actions(&self) -> ActionList { + use NavigateToolFsmState::*; + match self.fsm_state { + Ready => actions!(NavigateMessageDiscriminant; TranslateCanvasBegin, RotateCanvasBegin, ZoomCanvasBegin), + _ => actions!(NavigateMessageDiscriminant; MouseMove, TransformCanvasEnd), + } + } +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +enum NavigateToolFsmState { + Ready, + Translating, + Rotating, + Zooming, +} + +impl Default for NavigateToolFsmState { + fn default() -> Self { + NavigateToolFsmState::Ready + } +} + +impl Fsm for NavigateToolFsmState { + type ToolData = (); + + fn transition( + self, + message: ToolMessage, + _document: &crate::document::DocumentMessageHandler, + _tool_data: &crate::tool::DocumentToolData, + _data: &mut Self::ToolData, + _input: &crate::input::InputPreprocessor, + messages: &mut VecDeque, + ) -> Self { + if let ToolMessage::Navigate(navigate) = message { + use NavigateMessage::*; + match navigate { + MouseMove { snap_angle } => { + messages.push_front(MovementMessage::MouseMove { snap_angle }.into()); + self + } + TranslateCanvasBegin => { + messages.push_front(MovementMessage::TranslateCanvasBegin.into()); + NavigateToolFsmState::Translating + } + RotateCanvasBegin => { + messages.push_front(MovementMessage::RotateCanvasBegin.into()); + NavigateToolFsmState::Rotating + } + ZoomCanvasBegin => { + messages.push_front(MovementMessage::ZoomCanvasBegin.into()); + NavigateToolFsmState::Zooming + } + TransformCanvasEnd => { + messages.push_front(MovementMessage::TransformCanvasEnd.into()); + NavigateToolFsmState::Ready + } + Abort => { + messages.push_front(MovementMessage::TransformCanvasEnd.into()); + NavigateToolFsmState::Ready + } + } + } else { + self + } } - advertise_actions!(); + fn update_hints(&self, responses: &mut VecDeque) { + let hint_data = match self { + NavigateToolFsmState::Ready => HintData(vec![ + HintGroup(vec![HintInfo { + key_groups: vec![], + mouse: Some(MouseMotion::MmbDrag), + label: String::from("Translate"), + plus: false, + }]), + HintGroup(vec![ + HintInfo { + key_groups: vec![], + mouse: Some(MouseMotion::RmbDrag), + label: String::from("Rotate (drag around centre)"), + plus: false, + }, + HintInfo { + key_groups: vec![KeysGroup(vec![Key::KeyControl])], + mouse: None, + label: String::from("Snap rotation to 15° increments"), + plus: true, + }, + ]), + HintGroup(vec![HintInfo { + key_groups: vec![], + mouse: Some(MouseMotion::LmbDrag), + label: String::from("Zoom in and out (drag up and down)"), + plus: false, + }]), + ]), + NavigateToolFsmState::Rotating => HintData(vec![HintGroup(vec![HintInfo { + key_groups: vec![KeysGroup(vec![Key::KeyControl])], + mouse: None, + label: String::from("Snap to 15° increments"), + plus: false, + }])]), + _ => HintData(Vec::new()), + }; + + responses.push_back(FrontendMessage::UpdateInputHints { hint_data }.into()); + } } diff --git a/frontend/src/components/panels/Document.vue b/frontend/src/components/panels/Document.vue index aa36e2879c..5348e227a2 100644 --- a/frontend/src/components/panels/Document.vue +++ b/frontend/src/components/panels/Document.vue @@ -71,7 +71,7 @@
- +