From 597c96a7db06fe44dbd4dc170511c011c7239073 Mon Sep 17 00:00:00 2001 From: Keavon Chambers Date: Sun, 28 Apr 2024 02:13:18 -0700 Subject: [PATCH] Polish and fix small bugs with tilt and zoom navigation --- .../messages/input_mapper/default_mapping.rs | 75 ++- .../input_preprocessor_message_handler.rs | 2 +- .../document/document_message_handler.rs | 56 ++- .../document/navigation/navigation_message.rs | 60 +-- .../navigation/navigation_message_handler.rs | 444 +++++++++--------- .../document/navigation/utility_types.rs | 15 +- .../menu_bar/menu_bar_message_handler.rs | 18 +- .../portfolio/portfolio_message_handler.rs | 6 +- .../tool/common_functionality/auto_panning.rs | 2 +- .../tool/tool_messages/navigate_tool.rs | 92 ++-- .../src/components/panels/Document.svelte | 16 +- frontend/wasm/src/editor_api.rs | 8 +- 12 files changed, 382 insertions(+), 412 deletions(-) diff --git a/editor/src/messages/input_mapper/default_mapping.rs b/editor/src/messages/input_mapper/default_mapping.rs index eec0b96e95..50e48fc7fe 100644 --- a/editor/src/messages/input_mapper/default_mapping.rs +++ b/editor/src/messages/input_mapper/default_mapping.rs @@ -35,14 +35,16 @@ pub fn default_mapping() -> Mapping { // =============== // // NavigationMessage - entry!( - PointerMove; - refresh_keys=[Control], - action_dispatch=NavigationMessage::PointerMove { snap_angle: Control, wait_for_snap_angle_release: true, snap_zoom: Control, zoom_from_viewport: None }, - ), - entry!(KeyDown(Lmb); action_dispatch=NavigationMessage::TransformFromMenuEnd { commit_key: Lmb }), - entry!(KeyDown(Mmb); action_dispatch=NavigationMessage::TransformFromMenuEnd { commit_key: Mmb }), - entry!(KeyDown(Rmb); action_dispatch=NavigationMessage::TransformFromMenuEnd { commit_key: Rmb }), + entry!(PointerMove; refresh_keys=[Control], action_dispatch=NavigationMessage::PointerMove { snap: Control }), + entry!(KeyUp(Lmb); action_dispatch=NavigationMessage::EndCanvasPTZ { abort_transform: false }), + entry!(KeyUp(Mmb); action_dispatch=NavigationMessage::EndCanvasPTZ { abort_transform: false }), + entry!(KeyUp(Rmb); action_dispatch=NavigationMessage::EndCanvasPTZ { abort_transform: false }), + entry!(KeyDown(Rmb); action_dispatch=NavigationMessage::EndCanvasPTZ { abort_transform: true }), + entry!(KeyDown(Escape); action_dispatch=NavigationMessage::EndCanvasPTZ { abort_transform: true }), + entry!(KeyDown(Lmb); action_dispatch=NavigationMessage::EndCanvasPTZWithClick { commit_key: Lmb }), + entry!(KeyDown(Mmb); action_dispatch=NavigationMessage::EndCanvasPTZWithClick { commit_key: Mmb }), + entry!(KeyDown(Rmb); action_dispatch=NavigationMessage::EndCanvasPTZWithClick { commit_key: Rmb }), + // // =============== // NORMAL PRIORITY // =============== @@ -116,14 +118,11 @@ pub fn default_mapping() -> Mapping { entry!(KeyDown(Escape); action_dispatch=ArtboardToolMessage::Abort), // // NavigateToolMessage - entry!(KeyUp(Lmb); modifiers=[Shift], action_dispatch=NavigateToolMessage::ClickZoom { zoom_in: false }), - entry!(KeyUp(Lmb); action_dispatch=NavigateToolMessage::ClickZoom { zoom_in: true }), - entry!(PointerMove; refresh_keys=[Control], action_dispatch=NavigateToolMessage::PointerMove { snap_angle: Control, snap_zoom: Control }), - entry!(KeyDown(Lmb); modifiers=[Alt], action_dispatch=NavigateToolMessage::RotateCanvasBegin), entry!(KeyDown(Lmb); action_dispatch=NavigateToolMessage::ZoomCanvasBegin), - entry!(KeyUp(Rmb); action_dispatch=NavigateToolMessage::TransformCanvasEnd), - entry!(KeyUp(Lmb); action_dispatch=NavigateToolMessage::TransformCanvasEnd), - entry!(KeyUp(Mmb); action_dispatch=NavigateToolMessage::TransformCanvasEnd), + entry!(KeyDown(Lmb); modifiers=[Alt], action_dispatch=NavigateToolMessage::TiltCanvasBegin), + entry!(PointerMove; refresh_keys=[Control], action_dispatch=NavigateToolMessage::PointerMove { snap: Control }), + entry!(KeyUp(Lmb); action_dispatch=NavigateToolMessage::PointerUp { zoom_in: true }), + entry!(KeyUp(Lmb); modifiers=[Shift], action_dispatch=NavigateToolMessage::PointerUp { zoom_in: false }), // // EyedropperToolMessage entry!(KeyDown(Lmb); action_dispatch=EyedropperToolMessage::SamplePrimaryColorBegin), @@ -351,25 +350,21 @@ pub fn default_mapping() -> Mapping { entry!(KeyDown(Digit9); action_dispatch=TransformLayerMessage::TypeDigit { digit: 9 }), // // NavigationMessage - entry!(KeyDown(Mmb); modifiers=[Alt], action_dispatch=NavigationMessage::RotateCanvasBegin { was_dispatched_from_menu: false }), - entry!(KeyDown(Mmb); modifiers=[Shift], action_dispatch=NavigationMessage::ZoomCanvasBegin), - entry!(KeyDown(Lmb); modifiers=[Shift, Space], action_dispatch=NavigationMessage::ZoomCanvasBegin), - entry!(KeyDown(Mmb); action_dispatch=NavigationMessage::TranslateCanvasBegin), - entry!(KeyUp(Mmb); action_dispatch=NavigationMessage::TransformCanvasEnd { abort_transform: false }), - entry!(KeyDown(Lmb); modifiers=[Space], action_dispatch=NavigationMessage::TranslateCanvasBegin), - entry!(KeyUp(Lmb); action_dispatch=NavigationMessage::TransformCanvasEnd { abort_transform: false }), - entry!(KeyDown(Rmb); action_dispatch=NavigationMessage::TransformCanvasEnd { abort_transform: true }), - entry!(KeyDown(Escape); action_dispatch=NavigationMessage::TransformCanvasEnd { abort_transform: true }), - entry!(KeyDown(NumpadAdd); modifiers=[Accel], action_dispatch=NavigationMessage::IncreaseCanvasZoom { center_on_mouse: false }), - entry!(KeyDown(Equal); modifiers=[Accel], action_dispatch=NavigationMessage::IncreaseCanvasZoom { center_on_mouse: false }), - entry!(KeyDown(Minus); modifiers=[Accel], action_dispatch=NavigationMessage::DecreaseCanvasZoom { center_on_mouse: false }), - entry!(WheelScroll; modifiers=[Control], action_dispatch=NavigationMessage::WheelCanvasZoom), - entry!(WheelScroll; modifiers=[Shift], action_dispatch=NavigationMessage::WheelCanvasTranslate { use_y_as_x: true }), - entry!(WheelScroll; action_dispatch=NavigationMessage::WheelCanvasTranslate { use_y_as_x: false }), - entry!(KeyDown(PageUp); modifiers=[Shift], action_dispatch=NavigationMessage::TranslateCanvasByViewportFraction { delta: DVec2::new(1., 0.) }), - entry!(KeyDown(PageDown); modifiers=[Shift], action_dispatch=NavigationMessage::TranslateCanvasByViewportFraction { delta: DVec2::new(-1., 0.) }), - entry!(KeyDown(PageUp); action_dispatch=NavigationMessage::TranslateCanvasByViewportFraction { delta: DVec2::new(0., 1.) }), - entry!(KeyDown(PageDown); action_dispatch=NavigationMessage::TranslateCanvasByViewportFraction { delta: DVec2::new(0., -1.) }), + entry!(KeyDown(Mmb); modifiers=[Alt], action_dispatch=NavigationMessage::BeginCanvasTilt { was_dispatched_from_menu: false }), + entry!(KeyDown(Mmb); modifiers=[Shift], action_dispatch=NavigationMessage::BeginCanvasZoom), + entry!(KeyDown(Lmb); modifiers=[Shift, Space], action_dispatch=NavigationMessage::BeginCanvasZoom), + entry!(KeyDown(Mmb); action_dispatch=NavigationMessage::BeginCanvasPan), + entry!(KeyDown(Lmb); modifiers=[Space], action_dispatch=NavigationMessage::BeginCanvasPan), + entry!(KeyDown(NumpadAdd); modifiers=[Accel], action_dispatch=NavigationMessage::CanvasZoomIncrease { center_on_mouse: false }), + entry!(KeyDown(Equal); modifiers=[Accel], action_dispatch=NavigationMessage::CanvasZoomIncrease { center_on_mouse: false }), + entry!(KeyDown(Minus); modifiers=[Accel], action_dispatch=NavigationMessage::CanvasZoomDecrease { center_on_mouse: false }), + entry!(WheelScroll; modifiers=[Control], action_dispatch=NavigationMessage::CanvasZoomMouseWheel), + entry!(WheelScroll; modifiers=[Shift], action_dispatch=NavigationMessage::CanvasPanMouseWheel { use_y_as_x: true }), + entry!(WheelScroll; action_dispatch=NavigationMessage::CanvasPanMouseWheel { use_y_as_x: false }), + entry!(KeyDown(PageUp); modifiers=[Shift], action_dispatch=NavigationMessage::CanvasPanByViewportFraction { delta: DVec2::new(1., 0.) }), + entry!(KeyDown(PageDown); modifiers=[Shift], action_dispatch=NavigationMessage::CanvasPanByViewportFraction { delta: DVec2::new(-1., 0.) }), + entry!(KeyDown(PageUp); action_dispatch=NavigationMessage::CanvasPanByViewportFraction { delta: DVec2::new(0., 1.) }), + entry!(KeyDown(PageDown); action_dispatch=NavigationMessage::CanvasPanByViewportFraction { delta: DVec2::new(0., -1.) }), entry!(KeyDown(Period); action_dispatch=NavigationMessage::FitViewportToSelection), // // PortfolioMessage @@ -429,14 +424,14 @@ pub fn zoom_with_scroll() -> Mapping { let mut mapping = default_mapping(); let remove = [ - entry!(WheelScroll; modifiers=[Control], action_dispatch=NavigationMessage::WheelCanvasZoom), - entry!(WheelScroll; modifiers=[Shift], action_dispatch=NavigationMessage::WheelCanvasTranslate { use_y_as_x: true }), - entry!(WheelScroll; action_dispatch=NavigationMessage::WheelCanvasTranslate { use_y_as_x: false }), + entry!(WheelScroll; modifiers=[Control], action_dispatch=NavigationMessage::CanvasZoomMouseWheel), + entry!(WheelScroll; modifiers=[Shift], action_dispatch=NavigationMessage::CanvasPanMouseWheel { use_y_as_x: true }), + entry!(WheelScroll; action_dispatch=NavigationMessage::CanvasPanMouseWheel { use_y_as_x: false }), ]; let add = [ - entry!(WheelScroll; modifiers=[Control], action_dispatch=NavigationMessage::WheelCanvasTranslate { use_y_as_x: true }), - entry!(WheelScroll; modifiers=[Shift], action_dispatch=NavigationMessage::WheelCanvasTranslate { use_y_as_x: false }), - entry!(WheelScroll; action_dispatch=NavigationMessage::WheelCanvasZoom), + entry!(WheelScroll; modifiers=[Control], action_dispatch=NavigationMessage::CanvasPanMouseWheel { use_y_as_x: true }), + entry!(WheelScroll; modifiers=[Shift], action_dispatch=NavigationMessage::CanvasPanMouseWheel { use_y_as_x: false }), + entry!(WheelScroll; action_dispatch=NavigationMessage::CanvasZoomMouseWheel), ]; apply_mapping_patch(&mut mapping, remove, add); diff --git a/editor/src/messages/input_preprocessor/input_preprocessor_message_handler.rs b/editor/src/messages/input_preprocessor/input_preprocessor_message_handler.rs index 4baf85b19e..3742636b95 100644 --- a/editor/src/messages/input_preprocessor/input_preprocessor_message_handler.rs +++ b/editor/src/messages/input_preprocessor/input_preprocessor_message_handler.rs @@ -30,7 +30,7 @@ impl MessageHandler for // TODO: Extend this to multiple viewports instead of setting it to the value of this last loop iteration self.viewport_bounds = bounds; - responses.add(NavigationMessage::TranslateCanvas { delta: DVec2::ZERO }); + responses.add(NavigationMessage::CanvasPan { delta: DVec2::ZERO }); responses.add(FrontendMessage::TriggerViewportResize); } } diff --git a/editor/src/messages/portfolio/document/document_message_handler.rs b/editor/src/messages/portfolio/document/document_message_handler.rs index 8873720962..1c6d5a601b 100644 --- a/editor/src/messages/portfolio/document/document_message_handler.rs +++ b/editor/src/messages/portfolio/document/document_message_handler.rs @@ -2,7 +2,7 @@ use super::utility_types::error::EditorError; use super::utility_types::misc::{BoundingBoxSnapTarget, GeometrySnapTarget, OptionBoundsSnapping, OptionPointSnapping, SnappingOptions, SnappingState}; use super::utility_types::nodes::{CollapsedLayers, SelectedNodes}; use crate::application::{generate_uuid, GRAPHITE_GIT_COMMIT_HASH}; -use crate::consts::{ASYMPTOTIC_EFFECT, DEFAULT_DOCUMENT_NAME, FILE_SAVE_SUFFIX, SCALE_EFFECT, SCROLLBAR_SPACING}; +use crate::consts::{ASYMPTOTIC_EFFECT, DEFAULT_DOCUMENT_NAME, FILE_SAVE_SUFFIX, SCALE_EFFECT, SCROLLBAR_SPACING, VIEWPORT_ROTATE_SNAP_INTERVAL}; use crate::messages::input_mapper::utility_types::macros::action_keys; use crate::messages::layout::utility_types::widget_prelude::*; use crate::messages::portfolio::document::graph_operation::utility_types::TransformIn; @@ -571,7 +571,7 @@ impl MessageHandler> for DocumentMessag responses.add(NodeGraphMessage::UpdateNewNodeGraph); } DocumentMessage::RenderRulers => { - let document_transform_scale = self.navigation_handler.snapped_scale(self.navigation.zoom); + let document_transform_scale = self.navigation_handler.snapped_zoom(self.navigation.zoom); let ruler_origin = self.metadata().document_to_viewport.transform_point2(DVec2::ZERO); let log = document_transform_scale.log2(); @@ -586,7 +586,7 @@ impl MessageHandler> for DocumentMessag }); } DocumentMessage::RenderScrollbars => { - let document_transform_scale = self.navigation_handler.snapped_scale(self.navigation.zoom); + let document_transform_scale = self.navigation_handler.snapped_zoom(self.navigation.zoom); let scale = 0.5 + ASYMPTOTIC_EFFECT + document_transform_scale * SCALE_EFFECT; @@ -849,16 +849,17 @@ impl MessageHandler> for DocumentMessag responses.add(DocumentMessage::RenderRulers); responses.add(DocumentMessage::RenderScrollbars); responses.add(NodeGraphMessage::RunDocumentGraph); + responses.add(PortfolioMessage::UpdateDocumentWidgets); } DocumentMessage::ZoomCanvasTo100Percent => { - responses.add_front(NavigationMessage::SetCanvasZoom { zoom_factor: 1. }); + responses.add_front(NavigationMessage::CanvasZoomSet { zoom_factor: 1. }); } DocumentMessage::ZoomCanvasTo200Percent => { - responses.add_front(NavigationMessage::SetCanvasZoom { zoom_factor: 2. }); + responses.add_front(NavigationMessage::CanvasZoomSet { zoom_factor: 2. }); } DocumentMessage::ZoomCanvasToFitAll => { if let Some(bounds) = self.metadata().document_bounds_document_space(true) { - responses.add(NavigationMessage::SetCanvasTilt { angle_radians: 0. }); + responses.add(NavigationMessage::CanvasTiltSet { angle_radians: 0. }); responses.add(NavigationMessage::FitViewportToBounds { bounds, prevent_zoom_past_100: true }); } } @@ -1362,18 +1363,19 @@ impl DocumentMessageHandler { Separator::new(SeparatorType::Unrelated).widget_holder(), IconButton::new("ZoomIn", 24) .tooltip("Zoom In") - .tooltip_shortcut(action_keys!(NavigationMessageDiscriminant::IncreaseCanvasZoom)) - .on_update(|_| NavigationMessage::IncreaseCanvasZoom { center_on_mouse: false }.into()) + .tooltip_shortcut(action_keys!(NavigationMessageDiscriminant::CanvasZoomIncrease)) + .on_update(|_| NavigationMessage::CanvasZoomIncrease { center_on_mouse: false }.into()) .widget_holder(), IconButton::new("ZoomOut", 24) .tooltip("Zoom Out") - .tooltip_shortcut(action_keys!(NavigationMessageDiscriminant::DecreaseCanvasZoom)) - .on_update(|_| NavigationMessage::DecreaseCanvasZoom { center_on_mouse: false }.into()) + .tooltip_shortcut(action_keys!(NavigationMessageDiscriminant::CanvasZoomDecrease)) + .on_update(|_| NavigationMessage::CanvasZoomDecrease { center_on_mouse: false }.into()) .widget_holder(), IconButton::new("ZoomReset", 24) .tooltip("Reset Tilt and Zoom to 100%") - .tooltip_shortcut(action_keys!(NavigationMessageDiscriminant::ResetCanvasTiltAndZoomTo100Percent)) - .on_update(|_| NavigationMessage::ResetCanvasTiltAndZoomTo100Percent.into()) + .tooltip_shortcut(action_keys!(NavigationMessageDiscriminant::CanvasTiltResetAndZoomTo100Percent)) + .on_update(|_| NavigationMessage::CanvasTiltResetAndZoomTo100Percent.into()) + .disabled(self.navigation.tilt.abs() < 1e-4 && (self.navigation.zoom - 1.).abs() < 1e-4) .widget_holder(), PopoverButton::new() .popover_layout(vec![ @@ -1404,34 +1406,48 @@ impl DocumentMessageHandler { ]) .widget_holder(), Separator::new(SeparatorType::Related).widget_holder(), - NumberInput::new(Some(self.navigation_handler.snapped_scale(self.navigation.zoom) * 100.)) + NumberInput::new(Some(self.navigation_handler.snapped_zoom(self.navigation.zoom) * 100.)) .unit("%") .min(0.000001) .max(1000000.) .tooltip("Document zoom within the viewport") .on_update(|number_input: &NumberInput| { - NavigationMessage::SetCanvasZoom { + NavigationMessage::CanvasZoomSet { zoom_factor: number_input.value.unwrap() / 100., } .into() }) .increment_behavior(NumberInputIncrementBehavior::Callback) - .increment_callback_decrease(|_| NavigationMessage::DecreaseCanvasZoom { center_on_mouse: false }.into()) - .increment_callback_increase(|_| NavigationMessage::IncreaseCanvasZoom { center_on_mouse: false }.into()) + .increment_callback_decrease(|_| NavigationMessage::CanvasZoomDecrease { center_on_mouse: false }.into()) + .increment_callback_increase(|_| NavigationMessage::CanvasZoomIncrease { center_on_mouse: false }.into()) .widget_holder(), ]; - let tilt_value = self.navigation_handler.snapped_angle(self.navigation.tilt) / (std::f64::consts::PI / 180.); + let tilt_value = self.navigation_handler.snapped_tilt(self.navigation.tilt) / (std::f64::consts::PI / 180.); if tilt_value.abs() > 0.00001 { widgets.extend([ Separator::new(SeparatorType::Related).widget_holder(), NumberInput::new(Some(tilt_value)) .unit("°") - .step(15.) + .increment_behavior(NumberInputIncrementBehavior::Callback) + .increment_callback_increase(|number_input: &NumberInput| { + let one = 1. + f64::EPSILON * 100.; + NavigationMessage::CanvasTiltSet { + angle_radians: ((number_input.value.unwrap() / VIEWPORT_ROTATE_SNAP_INTERVAL + one).floor() * VIEWPORT_ROTATE_SNAP_INTERVAL).to_radians(), + } + .into() + }) + .increment_callback_decrease(|number_input: &NumberInput| { + let one = 1. + f64::EPSILON * 100.; + NavigationMessage::CanvasTiltSet { + angle_radians: ((number_input.value.unwrap() / VIEWPORT_ROTATE_SNAP_INTERVAL - one).ceil() * VIEWPORT_ROTATE_SNAP_INTERVAL).to_radians(), + } + .into() + }) .tooltip("Document tilt within the viewport") .on_update(|number_input: &NumberInput| { - NavigationMessage::SetCanvasTilt { - angle_radians: number_input.value.unwrap() * (std::f64::consts::PI / 180.), + NavigationMessage::CanvasTiltSet { + angle_radians: number_input.value.unwrap().to_radians(), } .into() }) diff --git a/editor/src/messages/portfolio/document/navigation/navigation_message.rs b/editor/src/messages/portfolio/document/navigation/navigation_message.rs index d8d5c42793..13e22d274a 100644 --- a/editor/src/messages/portfolio/document/navigation/navigation_message.rs +++ b/editor/src/messages/portfolio/document/navigation/navigation_message.rs @@ -7,49 +7,21 @@ use glam::DVec2; #[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize)] pub enum NavigationMessage { // Messages - DecreaseCanvasZoom { - center_on_mouse: bool, - }, - FitViewportToBounds { - bounds: [DVec2; 2], - prevent_zoom_past_100: bool, - }, + BeginCanvasPan, + BeginCanvasTilt { was_dispatched_from_menu: bool }, + BeginCanvasZoom, + CanvasPan { delta: DVec2 }, + CanvasPanByViewportFraction { delta: DVec2 }, + CanvasPanMouseWheel { use_y_as_x: bool }, + CanvasTiltResetAndZoomTo100Percent, + CanvasTiltSet { angle_radians: f64 }, + CanvasZoomDecrease { center_on_mouse: bool }, + CanvasZoomIncrease { center_on_mouse: bool }, + CanvasZoomMouseWheel, + CanvasZoomSet { zoom_factor: f64 }, + EndCanvasPTZ { abort_transform: bool }, + EndCanvasPTZWithClick { commit_key: Key }, + FitViewportToBounds { bounds: [DVec2; 2], prevent_zoom_past_100: bool }, FitViewportToSelection, - IncreaseCanvasZoom { - center_on_mouse: bool, - }, - PointerMove { - snap_angle: Key, - wait_for_snap_angle_release: bool, - snap_zoom: Key, - zoom_from_viewport: Option, - }, - ResetCanvasTiltAndZoomTo100Percent, - RotateCanvasBegin { - was_dispatched_from_menu: bool, - }, - SetCanvasTilt { - angle_radians: f64, - }, - SetCanvasZoom { - zoom_factor: f64, - }, - TransformCanvasEnd { - abort_transform: bool, - }, - TransformFromMenuEnd { - commit_key: Key, - }, - TranslateCanvas { - delta: DVec2, - }, - TranslateCanvasBegin, - TranslateCanvasByViewportFraction { - delta: DVec2, - }, - WheelCanvasTranslate { - use_y_as_x: bool, - }, - WheelCanvasZoom, - ZoomCanvasBegin, + PointerMove { snap: Key }, } diff --git a/editor/src/messages/portfolio/document/navigation/navigation_message_handler.rs b/editor/src/messages/portfolio/document/navigation/navigation_message_handler.rs index 3c4da299eb..1c8a1f9e2a 100644 --- a/editor/src/messages/portfolio/document/navigation/navigation_message_handler.rs +++ b/editor/src/messages/portfolio/document/navigation/navigation_message_handler.rs @@ -40,12 +40,162 @@ impl MessageHandler> for Navigation let old_zoom = ptz.zoom; match message { - NavigationMessage::DecreaseCanvasZoom { center_on_mouse } => { + NavigationMessage::BeginCanvasPan => { + responses.add(FrontendMessage::UpdateMouseCursor { cursor: MouseCursorIcon::Grabbing }); + + responses.add(FrontendMessage::UpdateInputHints { + hint_data: HintData(vec![HintGroup(vec![HintInfo::mouse(MouseMotion::Rmb, ""), HintInfo::keys([Key::Escape], "Cancel").prepend_slash()])]), + }); + + self.mouse_position = ipp.mouse.position; + self.navigation_operation = NavigationOperation::Pan { pan_original_for_abort: ptz.pan }; + } + NavigationMessage::BeginCanvasTilt { was_dispatched_from_menu } => { + responses.add(FrontendMessage::UpdateMouseCursor { cursor: MouseCursorIcon::Default }); + responses.add(FrontendMessage::UpdateInputHints { + hint_data: HintData(vec![ + HintGroup(vec![HintInfo::mouse(MouseMotion::Rmb, ""), HintInfo::keys([Key::Escape], "Cancel").prepend_slash()]), + HintGroup(vec![HintInfo { + key_groups: vec![KeysGroup(vec![Key::Control]).into()], + key_groups_mac: None, + mouse: None, + label: String::from("Snap 15°"), + plus: false, + slash: false, + }]), + ]), + }); + + self.navigation_operation = NavigationOperation::Tilt { + tilt_original_for_abort: ptz.tilt, + tilt_raw_not_snapped: ptz.tilt, + snap: false, + }; + + self.mouse_position = ipp.mouse.position; + self.finish_operation_with_click = was_dispatched_from_menu; + } + NavigationMessage::BeginCanvasZoom => { + responses.add(FrontendMessage::UpdateMouseCursor { cursor: MouseCursorIcon::ZoomIn }); + responses.add(FrontendMessage::UpdateInputHints { + hint_data: HintData(vec![ + HintGroup(vec![HintInfo::mouse(MouseMotion::Rmb, ""), HintInfo::keys([Key::Escape], "Cancel").prepend_slash()]), + HintGroup(vec![HintInfo { + key_groups: vec![KeysGroup(vec![Key::Control]).into()], + key_groups_mac: None, + mouse: None, + label: String::from("Increments"), + plus: false, + slash: false, + }]), + ]), + }); + + self.navigation_operation = NavigationOperation::Zoom { + zoom_raw_not_snapped: ptz.zoom, + zoom_original_for_abort: ptz.zoom, + snap: false, + }; + self.mouse_position = ipp.mouse.position; + } + NavigationMessage::CanvasPan { delta } => { + let transformed_delta = metadata.document_to_viewport.inverse().transform_vector2(delta); + + ptz.pan += transformed_delta; + responses.add(BroadcastEvent::CanvasTransformed); + self.create_document_transform(ipp.viewport_bounds.center(), ptz, responses); + } + NavigationMessage::CanvasPanByViewportFraction { delta } => { + let transformed_delta = metadata.document_to_viewport.inverse().transform_vector2(delta * ipp.viewport_bounds.size()); + + ptz.pan += transformed_delta; + self.create_document_transform(ipp.viewport_bounds.center(), ptz, responses); + } + NavigationMessage::CanvasPanMouseWheel { use_y_as_x } => { + let delta = match use_y_as_x { + false => -ipp.mouse.scroll_delta.as_dvec2(), + true => (-ipp.mouse.scroll_delta.y as f64, 0.).into(), + } * VIEWPORT_SCROLL_RATE; + responses.add(NavigationMessage::CanvasPan { delta }); + } + NavigationMessage::CanvasTiltResetAndZoomTo100Percent => { + ptz.tilt = 0.; + ptz.zoom = 1.; + responses.add(PortfolioMessage::UpdateDocumentWidgets); + self.create_document_transform(ipp.viewport_bounds.center(), ptz, responses); + } + NavigationMessage::CanvasTiltSet { angle_radians } => { + ptz.tilt = angle_radians; + self.create_document_transform(ipp.viewport_bounds.center(), ptz, responses); + } + NavigationMessage::CanvasZoomDecrease { center_on_mouse } => { let new_scale = *VIEWPORT_ZOOM_LEVELS.iter().rev().find(|scale| **scale < ptz.zoom).unwrap_or(&ptz.zoom); if center_on_mouse { responses.add(self.center_zoom(ipp.viewport_bounds.size(), new_scale / ptz.zoom, ipp.mouse.position)); } - responses.add(NavigationMessage::SetCanvasZoom { zoom_factor: new_scale }); + responses.add(NavigationMessage::CanvasZoomSet { zoom_factor: new_scale }); + } + NavigationMessage::CanvasZoomIncrease { center_on_mouse } => { + let new_scale = *VIEWPORT_ZOOM_LEVELS.iter().find(|scale| **scale > ptz.zoom).unwrap_or(&ptz.zoom); + if center_on_mouse { + responses.add(self.center_zoom(ipp.viewport_bounds.size(), new_scale / ptz.zoom, ipp.mouse.position)); + } + responses.add(NavigationMessage::CanvasZoomSet { zoom_factor: new_scale }); + } + NavigationMessage::CanvasZoomMouseWheel => { + let scroll = ipp.mouse.scroll_delta.scroll_delta(); + let mut zoom_factor = 1. + scroll.abs() * VIEWPORT_ZOOM_WHEEL_RATE; + if ipp.mouse.scroll_delta.y > 0 { + zoom_factor = 1. / zoom_factor + } + zoom_factor *= Self::clamp_zoom(ptz.zoom * zoom_factor, document_bounds, old_zoom, ipp); + + responses.add(self.center_zoom(ipp.viewport_bounds.size(), zoom_factor, ipp.mouse.position)); + responses.add(NavigationMessage::CanvasZoomSet { zoom_factor: ptz.zoom * zoom_factor }); + } + NavigationMessage::CanvasZoomSet { zoom_factor } => { + ptz.zoom = zoom_factor.clamp(VIEWPORT_ZOOM_SCALE_MIN, VIEWPORT_ZOOM_SCALE_MAX); + ptz.zoom *= Self::clamp_zoom(ptz.zoom, document_bounds, old_zoom, ipp); + responses.add(PortfolioMessage::UpdateDocumentWidgets); + self.create_document_transform(ipp.viewport_bounds.center(), ptz, responses); + } + NavigationMessage::EndCanvasPTZ { abort_transform } => { + // If an abort was requested, reset the active PTZ value to its original state + if abort_transform && self.navigation_operation != NavigationOperation::None { + match self.navigation_operation { + NavigationOperation::None => {} + NavigationOperation::Tilt { tilt_original_for_abort, .. } => { + ptz.tilt = tilt_original_for_abort; + } + NavigationOperation::Pan { pan_original_for_abort, .. } => { + ptz.pan = pan_original_for_abort; + } + NavigationOperation::Zoom { zoom_original_for_abort, .. } => { + ptz.zoom = zoom_original_for_abort; + } + } + + self.create_document_transform(ipp.viewport_bounds.center(), ptz, responses); + } + + // Final chance to apply snapping if the key was pressed during this final frame + ptz.tilt = self.snapped_tilt(ptz.tilt); + ptz.zoom = self.snapped_zoom(ptz.zoom); + + // Reset the navigation operation now that it's done + self.navigation_operation = NavigationOperation::None; + + // Send the final messages to close out the operation + responses.add(BroadcastEvent::CanvasTransformed); + responses.add(ToolMessage::UpdateCursor); + responses.add(ToolMessage::UpdateHints); + responses.add(NavigateToolMessage::End); + } + NavigationMessage::EndCanvasPTZWithClick { commit_key } => { + self.finish_operation_with_click = false; + + let abort_transform = commit_key == Key::Rmb; + responses.add(NavigationMessage::EndCanvasPTZ { abort_transform }); } NavigationMessage::FitViewportToBounds { bounds: [pos1, pos2], @@ -80,257 +230,95 @@ impl MessageHandler> for Navigation }) } } - NavigationMessage::IncreaseCanvasZoom { center_on_mouse } => { - let new_scale = *VIEWPORT_ZOOM_LEVELS.iter().find(|scale| **scale > ptz.zoom).unwrap_or(&ptz.zoom); - if center_on_mouse { - responses.add(self.center_zoom(ipp.viewport_bounds.size(), new_scale / ptz.zoom, ipp.mouse.position)); - } - responses.add(NavigationMessage::SetCanvasZoom { zoom_factor: new_scale }); - } - NavigationMessage::PointerMove { - snap_angle, - wait_for_snap_angle_release, - snap_zoom, - zoom_from_viewport, - } => { + NavigationMessage::PointerMove { snap } => { match self.navigation_operation { NavigationOperation::None => {} NavigationOperation::Pan { .. } => { let delta = ipp.mouse.position - self.mouse_position; - responses.add(NavigationMessage::TranslateCanvas { delta }); + responses.add(NavigationMessage::CanvasPan { delta }); } - NavigationOperation::Rotate { - snap_tilt, - snap_tilt_released, - pre_commit_tilt, + NavigationOperation::Tilt { + tilt_raw_not_snapped, + tilt_original_for_abort, + .. } => { - let new_snap = ipp.keyboard.get(snap_angle as usize); - - if !(wait_for_snap_angle_release && new_snap && !snap_tilt_released) { - // When disabling snap, keep the viewed rotation as it was previously. - if !new_snap && snap_tilt { - ptz.tilt = self.snapped_angle(ptz.tilt); - } - self.navigation_operation = NavigationOperation::Rotate { - pre_commit_tilt, - snap_tilt: new_snap, - snap_tilt_released: true, - }; - } - - let half_viewport = ipp.viewport_bounds.size() / 2.; - let rotation = { + let tilt_raw_not_snapped = { + let half_viewport = ipp.viewport_bounds.size() / 2.; let start_offset = self.mouse_position - half_viewport; let end_offset = ipp.mouse.position - half_viewport; - start_offset.angle_between(end_offset) + let angle = start_offset.angle_between(end_offset); + + tilt_raw_not_snapped + angle + }; + ptz.tilt = self.snapped_tilt(tilt_raw_not_snapped); + + let snap = ipp.keyboard.get(snap as usize); + + self.navigation_operation = NavigationOperation::Tilt { + tilt_original_for_abort, + tilt_raw_not_snapped, + snap, }; - responses.add(NavigationMessage::SetCanvasTilt { angle_radians: ptz.tilt + rotation }); + responses.add(NavigationMessage::CanvasTiltSet { angle_radians: ptz.tilt }); } - NavigationOperation::Zoom { snap_zoom_enabled, pre_commit_zoom } => { - let zoom_start = self.snapped_scale(ptz.zoom); + NavigationOperation::Zoom { + zoom_raw_not_snapped, + zoom_original_for_abort, + .. + } => { + let zoom_raw_not_snapped = { + let vertical_delta = self.mouse_position.y - ipp.mouse.position.y; + let amount = vertical_delta * VIEWPORT_ZOOM_MOUSE_RATE; + let updated_zoom = zoom_raw_not_snapped * (1. + amount); - let new_snap = ipp.keyboard.get(snap_zoom as usize); - // When disabling snap, keep the viewed zoom as it was previously - if !new_snap && snap_zoom_enabled { - ptz.zoom = self.snapped_scale(ptz.zoom); - } + updated_zoom * Self::clamp_zoom(updated_zoom, document_bounds, old_zoom, ipp) + }; + ptz.zoom = self.snapped_zoom(zoom_raw_not_snapped); - if snap_zoom_enabled != new_snap { - self.navigation_operation = NavigationOperation::Zoom { - pre_commit_zoom, - snap_zoom_enabled: new_snap, - }; - } + let snap = ipp.keyboard.get(snap as usize); - let difference = self.mouse_position.y - ipp.mouse.position.y; - let amount = 1. + difference * VIEWPORT_ZOOM_MOUSE_RATE; + self.navigation_operation = NavigationOperation::Zoom { + zoom_raw_not_snapped, + zoom_original_for_abort, + snap, + }; - ptz.zoom *= amount; - ptz.zoom *= Self::clamp_zoom(ptz.zoom, document_bounds, old_zoom, ipp); - - if let Some(mouse) = zoom_from_viewport { - let zoom_factor = self.snapped_scale(ptz.zoom) / zoom_start; - - responses.add(NavigationMessage::SetCanvasZoom { zoom_factor: ptz.zoom }); - responses.add(self.center_zoom(ipp.viewport_bounds.size(), zoom_factor, mouse)); - } else { - responses.add(NavigationMessage::SetCanvasZoom { zoom_factor: ptz.zoom }); - } + responses.add(NavigationMessage::CanvasZoomSet { zoom_factor: ptz.zoom }); } } self.mouse_position = ipp.mouse.position; } - NavigationMessage::ResetCanvasTiltAndZoomTo100Percent => { - ptz.tilt = 0.; - ptz.zoom = 1.; - responses.add(PortfolioMessage::UpdateDocumentWidgets); - self.create_document_transform(ipp.viewport_bounds.center(), ptz, responses); - } - NavigationMessage::RotateCanvasBegin { was_dispatched_from_menu } => { - responses.add(FrontendMessage::UpdateMouseCursor { cursor: MouseCursorIcon::Default }); - responses.add(FrontendMessage::UpdateInputHints { - hint_data: HintData(vec![ - // TODO: Fix bug where canceling doesn't work except with the Navigate tool active - HintGroup(vec![HintInfo::mouse(MouseMotion::Rmb, ""), HintInfo::keys([Key::Escape], "Cancel").prepend_slash()]), - HintGroup(vec![HintInfo { - key_groups: vec![KeysGroup(vec![Key::Control]).into()], - key_groups_mac: None, - mouse: None, - label: String::from("Snap 15°"), - plus: false, - slash: false, - }]), - ]), - }); - - self.navigation_operation = NavigationOperation::Rotate { - pre_commit_tilt: ptz.tilt, - snap_tilt_released: false, - snap_tilt: false, - }; - - self.mouse_position = ipp.mouse.position; - self.finish_operation_with_click = was_dispatched_from_menu; - } - NavigationMessage::SetCanvasTilt { angle_radians } => { - ptz.tilt = angle_radians; - self.create_document_transform(ipp.viewport_bounds.center(), ptz, responses); - responses.add(PortfolioMessage::UpdateDocumentWidgets); - } - NavigationMessage::SetCanvasZoom { zoom_factor } => { - ptz.zoom = zoom_factor.clamp(VIEWPORT_ZOOM_SCALE_MIN, VIEWPORT_ZOOM_SCALE_MAX); - ptz.zoom *= Self::clamp_zoom(ptz.zoom, document_bounds, old_zoom, ipp); - responses.add(PortfolioMessage::UpdateDocumentWidgets); - self.create_document_transform(ipp.viewport_bounds.center(), ptz, responses); - } - NavigationMessage::TransformCanvasEnd { abort_transform } => { - if abort_transform { - match self.navigation_operation { - NavigationOperation::None => {} - NavigationOperation::Rotate { pre_commit_tilt, .. } => { - ptz.tilt = pre_commit_tilt; - responses.add(NavigationMessage::SetCanvasTilt { angle_radians: pre_commit_tilt }); - } - NavigationOperation::Pan { pre_commit_pan, .. } => { - ptz.pan = pre_commit_pan; - self.create_document_transform(ipp.viewport_bounds.center(), ptz, responses); - } - NavigationOperation::Zoom { pre_commit_zoom, .. } => { - ptz.zoom = pre_commit_zoom; - responses.add(PortfolioMessage::UpdateDocumentWidgets); - self.create_document_transform(ipp.viewport_bounds.center(), ptz, responses); - } - } - } - - ptz.tilt = self.snapped_angle(ptz.tilt); - ptz.zoom = self.snapped_scale(ptz.zoom); - responses.add(BroadcastEvent::CanvasTransformed); - responses.add(ToolMessage::UpdateCursor); - responses.add(ToolMessage::UpdateHints); - self.navigation_operation = NavigationOperation::None; - } - NavigationMessage::TransformFromMenuEnd { commit_key } => { - let abort_transform = commit_key == Key::Rmb; - self.finish_operation_with_click = false; - responses.add(NavigationMessage::TransformCanvasEnd { abort_transform }); - } - NavigationMessage::TranslateCanvas { delta } => { - let transformed_delta = metadata.document_to_viewport.inverse().transform_vector2(delta); - - ptz.pan += transformed_delta; - responses.add(BroadcastEvent::CanvasTransformed); - self.create_document_transform(ipp.viewport_bounds.center(), ptz, responses); - } - NavigationMessage::TranslateCanvasBegin => { - responses.add(FrontendMessage::UpdateMouseCursor { cursor: MouseCursorIcon::Grabbing }); - - responses.add(FrontendMessage::UpdateInputHints { - // TODO: Fix bug where canceling doesn't work except with the Navigate tool active - hint_data: HintData(vec![HintGroup(vec![HintInfo::mouse(MouseMotion::Rmb, ""), HintInfo::keys([Key::Escape], "Cancel").prepend_slash()])]), - }); - - self.mouse_position = ipp.mouse.position; - self.navigation_operation = NavigationOperation::Pan { pre_commit_pan: ptz.pan }; - } - NavigationMessage::TranslateCanvasByViewportFraction { delta } => { - let transformed_delta = metadata.document_to_viewport.inverse().transform_vector2(delta * ipp.viewport_bounds.size()); - - ptz.pan += transformed_delta; - self.create_document_transform(ipp.viewport_bounds.center(), ptz, responses); - } - NavigationMessage::WheelCanvasTranslate { use_y_as_x } => { - let delta = match use_y_as_x { - false => -ipp.mouse.scroll_delta.as_dvec2(), - true => (-ipp.mouse.scroll_delta.y as f64, 0.).into(), - } * VIEWPORT_SCROLL_RATE; - responses.add(NavigationMessage::TranslateCanvas { delta }); - } - NavigationMessage::WheelCanvasZoom => { - let scroll = ipp.mouse.scroll_delta.scroll_delta(); - let mut zoom_factor = 1. + scroll.abs() * VIEWPORT_ZOOM_WHEEL_RATE; - if ipp.mouse.scroll_delta.y > 0 { - zoom_factor = 1. / zoom_factor - } - zoom_factor *= Self::clamp_zoom(ptz.zoom * zoom_factor, document_bounds, old_zoom, ipp); - - responses.add(self.center_zoom(ipp.viewport_bounds.size(), zoom_factor, ipp.mouse.position)); - responses.add(NavigationMessage::SetCanvasZoom { zoom_factor: ptz.zoom * zoom_factor }); - } - NavigationMessage::ZoomCanvasBegin => { - responses.add(FrontendMessage::UpdateMouseCursor { cursor: MouseCursorIcon::ZoomIn }); - responses.add(FrontendMessage::UpdateInputHints { - hint_data: HintData(vec![ - // TODO: Fix bug where canceling doesn't work except with the Navigate tool active - HintGroup(vec![HintInfo::mouse(MouseMotion::Rmb, ""), HintInfo::keys([Key::Escape], "Cancel").prepend_slash()]), - HintGroup(vec![HintInfo { - key_groups: vec![KeysGroup(vec![Key::Control]).into()], - key_groups_mac: None, - mouse: None, - label: String::from("Increments"), - plus: false, - slash: false, - }]), - ]), - }); - - self.navigation_operation = NavigationOperation::Zoom { - pre_commit_zoom: ptz.zoom, - snap_zoom_enabled: false, - }; - self.mouse_position = ipp.mouse.position; - } } } fn actions(&self) -> ActionList { let mut common = actions!(NavigationMessageDiscriminant; - TranslateCanvasBegin, - RotateCanvasBegin, - ZoomCanvasBegin, - SetCanvasTilt, - WheelCanvasZoom, - IncreaseCanvasZoom, - DecreaseCanvasZoom, - WheelCanvasTranslate, - TranslateCanvas, - TranslateCanvasByViewportFraction, + BeginCanvasPan, + BeginCanvasTilt, + BeginCanvasZoom, + CanvasPan, + CanvasPanByViewportFraction, + CanvasPanMouseWheel, + CanvasTiltSet, + CanvasZoomDecrease, + CanvasZoomIncrease, + CanvasZoomMouseWheel, FitViewportToSelection, ); if self.navigation_operation != NavigationOperation::None { let transforming = actions!(NavigationMessageDiscriminant; + EndCanvasPTZ, PointerMove, - TransformCanvasEnd, ); common.extend(transforming); } if self.finish_operation_with_click { let transforming_from_menu = actions!(NavigationMessageDiscriminant; - TransformFromMenuEnd, + EndCanvasPTZWithClick, ); common.extend(transforming_from_menu); @@ -341,17 +329,17 @@ impl MessageHandler> for Navigation } impl NavigationMessageHandler { - pub fn snapped_angle(&self, tilt: f64) -> f64 { + pub fn snapped_tilt(&self, tilt: f64) -> f64 { let increment_radians: f64 = VIEWPORT_ROTATE_SNAP_INTERVAL.to_radians(); - if let NavigationOperation::Rotate { snap_tilt: true, .. } = self.navigation_operation { + if matches!(self.navigation_operation, NavigationOperation::Tilt { snap: true, .. }) { (tilt / increment_radians).round() * increment_radians } else { tilt } } - pub fn snapped_scale(&self, zoom: f64) -> f64 { - if let NavigationOperation::Zoom { snap_zoom_enabled: true, .. } = self.navigation_operation { + pub fn snapped_zoom(&self, zoom: f64) -> f64 { + if matches!(self.navigation_operation, NavigationOperation::Zoom { snap: true, .. }) { *VIEWPORT_ZOOM_LEVELS.iter().min_by(|a, b| (**a - zoom).abs().partial_cmp(&(**b - zoom).abs()).unwrap()).unwrap_or(&zoom) } else { zoom @@ -359,16 +347,16 @@ impl NavigationMessageHandler { } pub fn calculate_offset_transform(&self, viewport_center: DVec2, pan: DVec2, tilt: f64, zoom: f64) -> DAffine2 { - let scaled_center = viewport_center / self.snapped_scale(zoom); + let scaled_center = viewport_center / self.snapped_zoom(zoom); // Try to avoid fractional coordinates to reduce anti aliasing. - let scale = self.snapped_scale(zoom); + let scale = self.snapped_zoom(zoom); let rounded_pan = ((pan + scaled_center) * scale).round() / scale - scaled_center; // TODO: replace with DAffine2::from_scale_angle_translation and fix the errors let offset_transform = DAffine2::from_translation(scaled_center); let scale_transform = DAffine2::from_scale(DVec2::splat(scale)); - let angle_transform = DAffine2::from_angle(self.snapped_angle(tilt)); + let angle_transform = DAffine2::from_angle(self.snapped_tilt(tilt)); let translation_transform = DAffine2::from_translation(rounded_pan); scale_transform * offset_transform * angle_transform * translation_transform } @@ -384,17 +372,17 @@ impl NavigationMessageHandler { let mouse_fraction = mouse / viewport_bounds; let delta = delta_size * (DVec2::splat(0.5) - mouse_fraction); - NavigationMessage::TranslateCanvas { delta }.into() + NavigationMessage::CanvasPan { delta }.into() } pub fn clamp_zoom(zoom: f64, document_bounds: Option<[DVec2; 2]>, old_zoom: f64, ipp: &InputPreprocessorMessageHandler) -> f64 { let document_size = (document_bounds.map(|[min, max]| max - min).unwrap_or_default() / old_zoom) * zoom; let scale_factor = (document_size / ipp.viewport_bounds.size()).max_element(); - if scale_factor > f64::EPSILON * 100. && scale_factor.is_finite() && scale_factor < VIEWPORT_ZOOM_MIN_FRACTION_COVER { - VIEWPORT_ZOOM_MIN_FRACTION_COVER / scale_factor - } else { - 1. + if scale_factor <= f64::EPSILON * 100. || !scale_factor.is_finite() || scale_factor >= VIEWPORT_ZOOM_MIN_FRACTION_COVER { + return 1.; } + + VIEWPORT_ZOOM_MIN_FRACTION_COVER / scale_factor } } diff --git a/editor/src/messages/portfolio/document/navigation/utility_types.rs b/editor/src/messages/portfolio/document/navigation/utility_types.rs index dca04f27d7..09684b890e 100644 --- a/editor/src/messages/portfolio/document/navigation/utility_types.rs +++ b/editor/src/messages/portfolio/document/navigation/utility_types.rs @@ -5,15 +5,16 @@ pub enum NavigationOperation { #[default] None, Pan { - pre_commit_pan: DVec2, + pan_original_for_abort: DVec2, }, - Rotate { - pre_commit_tilt: f64, - snap_tilt: bool, - snap_tilt_released: bool, + Tilt { + tilt_original_for_abort: f64, + tilt_raw_not_snapped: f64, + snap: bool, }, Zoom { - pre_commit_zoom: f64, - snap_zoom_enabled: bool, + zoom_raw_not_snapped: f64, + zoom_original_for_abort: f64, + snap: bool, }, } diff --git a/editor/src/messages/portfolio/menu_bar/menu_bar_message_handler.rs b/editor/src/messages/portfolio/menu_bar/menu_bar_message_handler.rs index 3cac8fc336..de446bd40d 100644 --- a/editor/src/messages/portfolio/menu_bar/menu_bar_message_handler.rs +++ b/editor/src/messages/portfolio/menu_bar/menu_bar_message_handler.rs @@ -269,15 +269,15 @@ impl LayoutHolder for MenuBarMessageHandler { vec![ MenuBarEntry { label: "Tilt".into(), - shortcut: action_keys!(NavigationMessageDiscriminant::RotateCanvasBegin), - action: MenuBarEntry::create_action(|_| NavigationMessage::RotateCanvasBegin { was_dispatched_from_menu: true }.into()), + shortcut: action_keys!(NavigationMessageDiscriminant::BeginCanvasTilt), + action: MenuBarEntry::create_action(|_| NavigationMessage::BeginCanvasTilt { was_dispatched_from_menu: true }.into()), disabled: no_active_document, ..MenuBarEntry::default() }, MenuBarEntry { label: "Reset Tilt".into(), - shortcut: action_keys!(NavigationMessageDiscriminant::SetCanvasTilt), - action: MenuBarEntry::create_action(|_| NavigationMessage::SetCanvasTilt { angle_radians: 0.into() }.into()), + shortcut: action_keys!(NavigationMessageDiscriminant::CanvasTiltSet), + action: MenuBarEntry::create_action(|_| NavigationMessage::CanvasTiltSet { angle_radians: 0.into() }.into()), disabled: no_active_document, ..MenuBarEntry::default() }, @@ -285,15 +285,17 @@ impl LayoutHolder for MenuBarMessageHandler { vec![ MenuBarEntry { label: "Zoom In".into(), - shortcut: action_keys!(NavigationMessageDiscriminant::IncreaseCanvasZoom), - action: MenuBarEntry::create_action(|_| NavigationMessage::IncreaseCanvasZoom { center_on_mouse: false }.into()), + icon: Some("ZoomIn".into()), + shortcut: action_keys!(NavigationMessageDiscriminant::CanvasZoomIncrease), + action: MenuBarEntry::create_action(|_| NavigationMessage::CanvasZoomIncrease { center_on_mouse: false }.into()), disabled: no_active_document, ..MenuBarEntry::default() }, MenuBarEntry { label: "Zoom Out".into(), - shortcut: action_keys!(NavigationMessageDiscriminant::DecreaseCanvasZoom), - action: MenuBarEntry::create_action(|_| NavigationMessage::DecreaseCanvasZoom { center_on_mouse: false }.into()), + icon: Some("ZoomOut".into()), + shortcut: action_keys!(NavigationMessageDiscriminant::CanvasZoomDecrease), + action: MenuBarEntry::create_action(|_| NavigationMessage::CanvasZoomDecrease { center_on_mouse: false }.into()), disabled: no_active_document, ..MenuBarEntry::default() }, diff --git a/editor/src/messages/portfolio/portfolio_message_handler.rs b/editor/src/messages/portfolio/portfolio_message_handler.rs index ed2753a803..dccd03fae8 100644 --- a/editor/src/messages/portfolio/portfolio_message_handler.rs +++ b/editor/src/messages/portfolio/portfolio_message_handler.rs @@ -319,7 +319,7 @@ impl MessageHandler> for PortfolioMes let document_id = DocumentId(generate_uuid()); if self.active_document().is_some() { responses.add(BroadcastEvent::ToolAbort); - responses.add(NavigationMessage::TranslateCanvas { delta: (0., 0.).into() }); + responses.add(NavigationMessage::CanvasPan { delta: (0., 0.).into() }); } self.load_document(new_document, document_id, responses); @@ -464,7 +464,7 @@ impl MessageHandler> for PortfolioMes responses.add(BroadcastEvent::ToolAbort); responses.add(BroadcastEvent::SelectionChanged); responses.add(PortfolioMessage::UpdateDocumentWidgets); - responses.add(NavigationMessage::TranslateCanvas { delta: (0., 0.).into() }); + responses.add(NavigationMessage::CanvasPan { delta: (0., 0.).into() }); responses.add(NodeGraphMessage::RunDocumentGraph); responses.add(DocumentMessage::GraphViewOverlay { open: node_graph_open }); } @@ -637,7 +637,7 @@ impl PortfolioMessageHandler { responses.add(PortfolioMessage::UpdateDocumentWidgets); responses.add(ToolMessage::InitTools); responses.add(NodeGraphMessage::Init); - responses.add(NavigationMessage::TranslateCanvas { delta: (0., 0.).into() }); + responses.add(NavigationMessage::CanvasPan { delta: (0., 0.).into() }); responses.add(PropertiesPanelMessage::Clear); responses.add(NodeGraphMessage::UpdateNewNodeGraph); } diff --git a/editor/src/messages/tool/common_functionality/auto_panning.rs b/editor/src/messages/tool/common_functionality/auto_panning.rs index ede9f6676a..a13f6706e0 100644 --- a/editor/src/messages/tool/common_functionality/auto_panning.rs +++ b/editor/src/messages/tool/common_functionality/auto_panning.rs @@ -82,7 +82,7 @@ impl AutoPanning { let time_delta = input.frame_time.frame_duration()?.as_secs_f64(); let delta = (shift_percent * DRAG_BEYOND_VIEWPORT_SPEED_FACTOR * viewport_size * time_delta).round(); - responses.add(NavigationMessage::TranslateCanvas { delta }); + responses.add(NavigationMessage::CanvasPan { delta }); Some(delta) } } diff --git a/editor/src/messages/tool/tool_messages/navigate_tool.rs b/editor/src/messages/tool/tool_messages/navigate_tool.rs index 180910f3a8..e6aaa1ffbd 100644 --- a/editor/src/messages/tool/tool_messages/navigate_tool.rs +++ b/editor/src/messages/tool/tool_messages/navigate_tool.rs @@ -13,12 +13,11 @@ pub enum NavigateToolMessage { Abort, // Tool-specific messages - ClickZoom { zoom_in: bool }, - PointerMove { snap_angle: Key, snap_zoom: Key }, - RotateCanvasBegin, - TransformCanvasEnd, - TranslateCanvasBegin, + PointerUp { zoom_in: bool }, + PointerMove { snap: Key }, + TiltCanvasBegin, ZoomCanvasBegin, + End, } impl ToolMetadata for NavigateTool { @@ -47,14 +46,15 @@ impl<'a> MessageHandler> for Navigat fn actions(&self) -> ActionList { match self.fsm_state { NavigateToolFsmState::Ready => actions!(NavigateToolMessageDiscriminant; - TranslateCanvasBegin, - RotateCanvasBegin, + TiltCanvasBegin, ZoomCanvasBegin, ), - _ => actions!(NavigateToolMessageDiscriminant; - ClickZoom, + NavigateToolFsmState::Tilting | NavigateToolFsmState::Zooming => actions!(NavigateToolMessageDiscriminant; + PointerMove, + ), + NavigateToolFsmState::ZoomOrClickZooming => actions!(NavigateToolMessageDiscriminant; + PointerUp, PointerMove, - TransformCanvasEnd, ), } } @@ -73,14 +73,14 @@ impl ToolTransition for NavigateTool { enum NavigateToolFsmState { #[default] Ready, - Panning, Tilting, + ZoomOrClickZooming, Zooming, } #[derive(Clone, Debug, Default)] struct NavigateToolData { - drag_start: DVec2, + drag_start: Option, } impl Fsm for NavigateToolFsmState { @@ -100,50 +100,48 @@ impl Fsm for NavigateToolFsmState { }; match navigate { - NavigateToolMessage::ClickZoom { zoom_in } => { - responses.add_front(NavigationMessage::TransformCanvasEnd { abort_transform: false }); - - // Mouse has not moved from pointerdown to pointerup - if tool_data.drag_start == input.mouse.position { - responses.add_front(if zoom_in { - NavigationMessage::IncreaseCanvasZoom { center_on_mouse: true } - } else { - NavigationMessage::DecreaseCanvasZoom { center_on_mouse: true } - }); + NavigateToolMessage::PointerUp { zoom_in } => { + if self == NavigateToolFsmState::ZoomOrClickZooming { + // Mouse has not moved from pointerdown to pointerup + if tool_data.drag_start == Some(input.mouse.position) { + responses.add_front(if zoom_in { + NavigationMessage::CanvasZoomIncrease { center_on_mouse: true } + } else { + NavigationMessage::CanvasZoomDecrease { center_on_mouse: true } + }); + } + } else { + responses.add_front(NavigationMessage::EndCanvasPTZ { abort_transform: false }); } + tool_data.drag_start = None; NavigateToolFsmState::Ready } - NavigateToolMessage::PointerMove { snap_angle, snap_zoom } => { - responses.add_front(NavigationMessage::PointerMove { - snap_angle, - wait_for_snap_angle_release: false, - snap_zoom, - zoom_from_viewport: Some(tool_data.drag_start), - }); - self + NavigateToolMessage::PointerMove { snap } => { + if self == NavigateToolFsmState::ZoomOrClickZooming { + responses.add_front(NavigationMessage::BeginCanvasZoom); + NavigateToolFsmState::Zooming + } else { + responses.add_front(NavigationMessage::PointerMove { snap }); + self + } } - NavigateToolMessage::TranslateCanvasBegin => { - tool_data.drag_start = input.mouse.position; - responses.add_front(NavigationMessage::TranslateCanvasBegin); - NavigateToolFsmState::Panning - } - NavigateToolMessage::RotateCanvasBegin => { - tool_data.drag_start = input.mouse.position; - responses.add_front(NavigationMessage::RotateCanvasBegin { was_dispatched_from_menu: false }); + NavigateToolMessage::TiltCanvasBegin => { + responses.add_front(NavigationMessage::BeginCanvasTilt { was_dispatched_from_menu: false }); NavigateToolFsmState::Tilting } NavigateToolMessage::ZoomCanvasBegin => { - tool_data.drag_start = input.mouse.position; - responses.add_front(NavigationMessage::ZoomCanvasBegin); - NavigateToolFsmState::Zooming + // Wait to decide between zooming and click zooming based on whether the next event is a PointerMove or PointerUp + tool_data.drag_start = Some(input.mouse.position); + NavigateToolFsmState::ZoomOrClickZooming } - NavigateToolMessage::TransformCanvasEnd => { - responses.add_front(NavigationMessage::TransformCanvasEnd { abort_transform: false }); + NavigateToolMessage::End => { + tool_data.drag_start = None; NavigateToolFsmState::Ready } NavigateToolMessage::Abort => { - responses.add_front(NavigationMessage::TransformCanvasEnd { abort_transform: false }); + responses.add_front(NavigationMessage::EndCanvasPTZ { abort_transform: false }); + tool_data.drag_start = None; NavigateToolFsmState::Ready } } @@ -151,7 +149,7 @@ impl Fsm for NavigateToolFsmState { fn update_hints(&self, responses: &mut VecDeque) { let hint_data = match self { - NavigateToolFsmState::Ready => HintData(vec![ + NavigateToolFsmState::Ready | NavigateToolFsmState::ZoomOrClickZooming => HintData(vec![ HintGroup(vec![ HintInfo::mouse(MouseMotion::MmbDrag, ""), HintInfo::keys_and_mouse([Key::Space], MouseMotion::LmbDrag, "Pan").prepend_slash(), @@ -168,7 +166,6 @@ impl Fsm for NavigateToolFsmState { HintGroup(vec![HintInfo::mouse(MouseMotion::Rmb, ""), HintInfo::keys([Key::Escape], "Cancel").prepend_slash()]), HintGroup(vec![HintInfo::keys([Key::Control], "Increments")]), ]), - _ => HintData(Vec::new()), }; responses.add(FrontendMessage::UpdateInputHints { hint_data }); @@ -177,9 +174,8 @@ impl Fsm for NavigateToolFsmState { fn update_cursor(&self, responses: &mut VecDeque) { let cursor = match *self { NavigateToolFsmState::Ready => MouseCursorIcon::ZoomIn, - NavigateToolFsmState::Panning => MouseCursorIcon::Grabbing, NavigateToolFsmState::Tilting => MouseCursorIcon::Default, - NavigateToolFsmState::Zooming => MouseCursorIcon::ZoomIn, + NavigateToolFsmState::Zooming | NavigateToolFsmState::ZoomOrClickZooming => MouseCursorIcon::ZoomIn, }; responses.add(FrontendMessage::UpdateMouseCursor { cursor }); diff --git a/frontend/src/components/panels/Document.svelte b/frontend/src/components/panels/Document.svelte index e51ba58b18..b3fab5bc31 100644 --- a/frontend/src/components/panels/Document.svelte +++ b/frontend/src/components/panels/Document.svelte @@ -139,26 +139,26 @@ }); } - function translateCanvasX(newValue: number) { + function panCanvasX(newValue: number) { const delta = newValue - scrollbarPos.x; scrollbarPos.x = newValue; - editor.instance.translateCanvas(-delta * scrollbarMultiplier.x, 0); + editor.instance.panCanvas(-delta * scrollbarMultiplier.x, 0); } - function translateCanvasY(newValue: number) { + function panCanvasY(newValue: number) { const delta = newValue - scrollbarPos.y; scrollbarPos.y = newValue; - editor.instance.translateCanvas(0, -delta * scrollbarMultiplier.y); + editor.instance.panCanvas(0, -delta * scrollbarMultiplier.y); } function pageX(delta: number) { const move = delta < 0 ? 1 : -1; - editor.instance.translateCanvasByFraction(move, 0); + editor.instance.panCanvasByFraction(move, 0); } function pageY(delta: number) { const move = delta < 0 ? 1 : -1; - editor.instance.translateCanvasByFraction(0, move); + editor.instance.panCanvasByFraction(0, move); } function canvasPointerDown(e: PointerEvent) { @@ -506,7 +506,7 @@ direction="Vertical" handleLength={scrollbarSize.y} handlePosition={scrollbarPos.y} - on:handlePosition={({ detail }) => translateCanvasY(detail)} + on:handlePosition={({ detail }) => panCanvasY(detail)} on:pressTrack={({ detail }) => pageY(detail)} /> @@ -516,7 +516,7 @@ direction="Horizontal" handleLength={scrollbarSize.x} handlePosition={scrollbarPos.x} - on:handlePosition={({ detail }) => translateCanvasX(detail)} + on:handlePosition={({ detail }) => panCanvasX(detail)} on:pressTrack={({ detail }) => pageX(detail)} /> diff --git a/frontend/wasm/src/editor_api.rs b/frontend/wasm/src/editor_api.rs index d07368dad8..6ec9d978d6 100644 --- a/frontend/wasm/src/editor_api.rs +++ b/frontend/wasm/src/editor_api.rs @@ -633,16 +633,16 @@ impl JsEditorHandle { } /// Translates document (in viewport coords) - #[wasm_bindgen(js_name = translateCanvas)] + #[wasm_bindgen(js_name = panCanvas)] pub fn translate_canvas(&self, delta_x: f64, delta_y: f64) { - let message = NavigationMessage::TranslateCanvas { delta: (delta_x, delta_y).into() }; + let message = NavigationMessage::CanvasPan { delta: (delta_x, delta_y).into() }; self.dispatch(message); } /// Translates document (in viewport coords) - #[wasm_bindgen(js_name = translateCanvasByFraction)] + #[wasm_bindgen(js_name = panCanvasByFraction)] pub fn translate_canvas_by_fraction(&self, delta_x: f64, delta_y: f64) { - let message = NavigationMessage::TranslateCanvasByViewportFraction { delta: (delta_x, delta_y).into() }; + let message = NavigationMessage::CanvasPanByViewportFraction { delta: (delta_x, delta_y).into() }; self.dispatch(message); }