Rework navigation tool hints and navigation shortcuts (#1419)

* Added Pan,Tilt,Zoom to View menu + Tilt shortcut changes

* formating

* fixed menus, added action for when dispatch happens from menu

* set key mappings to spec

* fix labels for navigation tool

* fix some spacing

* add action that locks spacebar for navigation when using it as a modifier so the node overlay does not toggle

* escape from the node-graph-overlay, set toggle to ctrl+space, fix demo art

* move all transform state into an enum

* rename confusing flag

* fix demo art and tests

---------

Co-authored-by: Ezbaze <rybitwamateusz3@gmail.com>
This commit is contained in:
mobile-bungalow
2023-09-10 23:42:27 +01:00
committed by GitHub
co-authored by Ezbaze
parent ad9ccaa800
commit b29acbd784
10 changed files with 249 additions and 133 deletions
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+1 -1
View File
@@ -79,7 +79,7 @@ pub const DEFAULT_FONT_FAMILY: &str = "Merriweather";
pub const DEFAULT_FONT_STYLE: &str = "Normal (400)"; pub const DEFAULT_FONT_STYLE: &str = "Normal (400)";
// Document // Document
pub const GRAPHITE_DOCUMENT_VERSION: &str = "0.0.17"; // Remember to update the demo artwork in /demos with both this version number and the contents so it remains editable pub const GRAPHITE_DOCUMENT_VERSION: &str = "0.0.18"; // Remember to update the demo artwork in /demos with both this version number and the contents so it remains editable
pub const DEFAULT_DOCUMENT_NAME: &str = "Untitled Document"; pub const DEFAULT_DOCUMENT_NAME: &str = "Untitled Document";
pub const FILE_SAVE_SUFFIX: &str = ".graphite"; pub const FILE_SAVE_SUFFIX: &str = ".graphite";
pub const MAX_UNDO_HISTORY_LEN: usize = 100; // TODO: Add this to user preferences pub const MAX_UNDO_HISTORY_LEN: usize = 100; // TODO: Add this to user preferences
@@ -37,6 +37,9 @@ pub fn default_mapping() -> Mapping {
refresh_keys=[Control], refresh_keys=[Control],
action_dispatch=NavigationMessage::PointerMove { snap_angle: Control, wait_for_snap_angle_release: true, snap_zoom: Control, zoom_from_viewport: None }, 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: Key::Lmb }),
entry!(KeyDown(Mmb); action_dispatch=NavigationMessage::TransformFromMenuEnd { commit_key: Key::Mmb }),
entry!(KeyDown(Rmb); action_dispatch=NavigationMessage::TransformFromMenuEnd { commit_key: Key::Rmb }),
// NORMAL PRIORITY: // NORMAL PRIORITY:
// //
// NodeGraphMessage // NodeGraphMessage
@@ -106,8 +109,7 @@ pub fn default_mapping() -> Mapping {
entry!(KeyUp(Lmb); modifiers=[Shift], action_dispatch=NavigateToolMessage::ClickZoom { zoom_in: false }), entry!(KeyUp(Lmb); modifiers=[Shift], action_dispatch=NavigateToolMessage::ClickZoom { zoom_in: false }),
entry!(KeyUp(Lmb); action_dispatch=NavigateToolMessage::ClickZoom { zoom_in: true }), 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!(PointerMove; refresh_keys=[Control], action_dispatch=NavigateToolMessage::PointerMove { snap_angle: Control, snap_zoom: Control }),
entry!(KeyDown(Mmb); action_dispatch=NavigateToolMessage::TranslateCanvasBegin), entry!(KeyDown(Lmb); modifiers=[Alt], action_dispatch=NavigateToolMessage::RotateCanvasBegin),
entry!(KeyDown(Rmb); action_dispatch=NavigateToolMessage::RotateCanvasBegin),
entry!(KeyDown(Lmb); action_dispatch=NavigateToolMessage::ZoomCanvasBegin), entry!(KeyDown(Lmb); action_dispatch=NavigateToolMessage::ZoomCanvasBegin),
entry!(KeyUp(Rmb); action_dispatch=NavigateToolMessage::TransformCanvasEnd), entry!(KeyUp(Rmb); action_dispatch=NavigateToolMessage::TransformCanvasEnd),
entry!(KeyUp(Lmb); action_dispatch=NavigateToolMessage::TransformCanvasEnd), entry!(KeyUp(Lmb); action_dispatch=NavigateToolMessage::TransformCanvasEnd),
@@ -315,12 +317,15 @@ pub fn default_mapping() -> Mapping {
entry!(KeyDown(KeyS); action_dispatch=TransformLayerMessage::BeginScale), entry!(KeyDown(KeyS); action_dispatch=TransformLayerMessage::BeginScale),
// //
// NavigationMessage // NavigationMessage
entry!(KeyDown(Mmb); modifiers=[Control], action_dispatch=NavigationMessage::RotateCanvasBegin), entry!(KeyDown(Lmb); modifiers=[Alt], action_dispatch=NavigationMessage::RotateCanvasBegin { was_dispatched_from_menu: false }),
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(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!(KeyDown(Mmb); action_dispatch=NavigationMessage::TranslateCanvasBegin),
entry!(KeyUp(Mmb); action_dispatch=NavigationMessage::TransformCanvasEnd), entry!(KeyUp(Mmb); action_dispatch=NavigationMessage::TransformCanvasEnd { abort_transform: false }),
entry!(KeyDown(Lmb); modifiers=[Space], action_dispatch=NavigationMessage::TranslateCanvasBegin), entry!(KeyDown(Lmb); modifiers=[Space], action_dispatch=NavigationMessage::TranslateCanvasBegin),
entry!(KeyUp(Lmb); action_dispatch=NavigationMessage::TransformCanvasEnd), entry!(KeyUp(Lmb); action_dispatch=NavigationMessage::TransformCanvasEnd { abort_transform: false }),
entry!(KeyUp(Rmb); action_dispatch=NavigationMessage::TransformCanvasEnd { abort_transform: true }),
entry!(KeyDown(NumpadAdd); modifiers=[Accel], action_dispatch=NavigationMessage::IncreaseCanvasZoom { center_on_mouse: false }), 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(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!(KeyDown(Minus); modifiers=[Accel], action_dispatch=NavigationMessage::DecreaseCanvasZoom { center_on_mouse: false }),
@@ -334,8 +339,8 @@ pub fn default_mapping() -> Mapping {
entry!(KeyDown(Period); action_dispatch=NavigationMessage::FitViewportToSelection), entry!(KeyDown(Period); action_dispatch=NavigationMessage::FitViewportToSelection),
// //
// PortfolioMessage // PortfolioMessage
entry!(KeyUp(Space); action_dispatch=PortfolioMessage::GraphViewOverlayToggle), entry!(KeyDown(Space); modifiers=[Control], action_dispatch=PortfolioMessage::GraphViewOverlayToggle),
entry!(KeyDownNoRepeat(Space); action_dispatch=PortfolioMessage::GraphViewOverlayToggleDisabled { disabled: false }), entry!(KeyUp(Escape); action_dispatch=PortfolioMessage::GraphViewOverlay { open: false }),
entry!(KeyDown(Tab); modifiers=[Control], action_dispatch=PortfolioMessage::NextDocument), entry!(KeyDown(Tab); modifiers=[Control], action_dispatch=PortfolioMessage::NextDocument),
entry!(KeyDown(Tab); modifiers=[Control, Shift], action_dispatch=PortfolioMessage::PrevDocument), entry!(KeyDown(Tab); modifiers=[Control, Shift], action_dispatch=PortfolioMessage::PrevDocument),
entry!(KeyDown(KeyW); modifiers=[Accel], action_dispatch=PortfolioMessage::CloseActiveDocumentWithConfirmation), entry!(KeyDown(KeyW); modifiers=[Accel], action_dispatch=PortfolioMessage::CloseActiveDocumentWithConfirmation),
@@ -27,14 +27,21 @@ pub enum NavigationMessage {
snap_zoom: Key, snap_zoom: Key,
zoom_from_viewport: Option<DVec2>, zoom_from_viewport: Option<DVec2>,
}, },
RotateCanvasBegin, RotateCanvasBegin {
was_dispatched_from_menu: bool,
},
SetCanvasRotation { SetCanvasRotation {
angle_radians: f64, angle_radians: f64,
}, },
SetCanvasZoom { SetCanvasZoom {
zoom_factor: f64, zoom_factor: f64,
}, },
TransformCanvasEnd, TransformCanvasEnd {
abort_transform: bool,
},
TransformFromMenuEnd {
commit_key: Key,
},
TranslateCanvas { TranslateCanvas {
delta: DVec2, delta: DVec2,
}, },
@@ -3,7 +3,7 @@ use crate::consts::{
VIEWPORT_ZOOM_TO_FIT_PADDING_SCALE_FACTOR, VIEWPORT_ZOOM_WHEEL_RATE, VIEWPORT_ZOOM_TO_FIT_PADDING_SCALE_FACTOR, VIEWPORT_ZOOM_WHEEL_RATE,
}; };
use crate::messages::frontend::utility_types::MouseCursorIcon; use crate::messages::frontend::utility_types::MouseCursorIcon;
use crate::messages::input_mapper::utility_types::input_keyboard::{Key, KeysGroup}; use crate::messages::input_mapper::utility_types::input_keyboard::{Key, KeysGroup, MouseMotion};
use crate::messages::input_mapper::utility_types::input_mouse::{ViewportBounds, ViewportPosition}; use crate::messages::input_mapper::utility_types::input_mouse::{ViewportBounds, ViewportPosition};
use crate::messages::prelude::*; use crate::messages::prelude::*;
use crate::messages::tool::utility_types::{HintData, HintGroup, HintInfo}; use crate::messages::tool::utility_types::{HintData, HintGroup, HintInfo};
@@ -15,39 +15,35 @@ use graphene_core::renderer::format_transform_matrix;
use glam::{DAffine2, DVec2}; use glam::{DAffine2, DVec2};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
enum TransformOperation {
None,
Pan { pre_commit_pan: DVec2 },
Rotate { pre_commit_tilt: f64, snap_tilt: bool, snap_tilt_released: bool },
Zoom { pre_commit_zoom: f64, snap_zoom_enabled: bool },
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct NavigationMessageHandler { pub struct NavigationMessageHandler {
pub pan: DVec2, pub pan: DVec2,
panning: bool,
snap_tilt: bool,
snap_tilt_released: bool,
pub tilt: f64, pub tilt: f64,
tilting: bool,
pub zoom: f64, pub zoom: f64,
zooming: bool,
snap_zoom: bool,
transform_operation: TransformOperation,
mouse_position: ViewportPosition, mouse_position: ViewportPosition,
finish_operation_with_click: bool,
} }
impl Default for NavigationMessageHandler { impl Default for NavigationMessageHandler {
fn default() -> Self { fn default() -> Self {
Self { Self {
pan: DVec2::ZERO, pan: DVec2::ZERO,
panning: false,
snap_tilt: false,
snap_tilt_released: false,
tilt: 0., tilt: 0.,
tilting: false,
zoom: 1., zoom: 1.,
zooming: false,
snap_zoom: false,
mouse_position: ViewportPosition::default(), mouse_position: ViewportPosition::default(),
finish_operation_with_click: false,
transform_operation: TransformOperation::None,
} }
} }
} }
@@ -124,75 +120,99 @@ impl MessageHandler<NavigationMessage, (&Document, Option<[DVec2; 2]>, &InputPre
snap_zoom, snap_zoom,
zoom_from_viewport, zoom_from_viewport,
} => { } => {
if self.panning { match self.transform_operation {
let delta = ipp.mouse.position - self.mouse_position; TransformOperation::None => {}
TransformOperation::Pan { .. } => {
let delta = ipp.mouse.position - self.mouse_position;
responses.add(TranslateCanvas { delta });
}
TransformOperation::Rotate {
snap_tilt,
snap_tilt_released,
pre_commit_tilt,
} => {
let new_snap = ipp.keyboard.get(snap_angle as usize);
responses.add(TranslateCanvas { delta }); 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 {
if self.tilting { self.tilt = self.snapped_angle();
let new_snap = ipp.keyboard.get(snap_angle as usize); }
if !(wait_for_snap_angle_release && new_snap && !self.snap_tilt_released) { self.transform_operation = TransformOperation::Rotate {
// When disabling snap, keep the viewed rotation as it was previously. pre_commit_tilt,
if !new_snap && self.snap_tilt { snap_tilt: new_snap,
self.tilt = self.snapped_angle(); snap_tilt_released: true,
};
} }
self.snap_tilt = new_snap;
self.snap_tilt_released = true; let half_viewport = ipp.viewport_bounds.size() / 2.;
let rotation = {
let start_offset = self.mouse_position - half_viewport;
let end_offset = ipp.mouse.position - half_viewport;
start_offset.angle_between(end_offset)
};
responses.add(SetCanvasRotation { angle_radians: self.tilt + rotation });
} }
TransformOperation::Zoom { snap_zoom_enabled, pre_commit_zoom } => {
let zoom_start = self.snapped_scale();
let half_viewport = ipp.viewport_bounds.size() / 2.; let new_snap = ipp.keyboard.get(snap_zoom as usize);
let rotation = { // When disabling snap, keep the viewed zoom as it was previously
let start_offset = self.mouse_position - half_viewport; if !new_snap && snap_zoom_enabled {
let end_offset = ipp.mouse.position - half_viewport; self.zoom = self.snapped_scale();
start_offset.angle_between(end_offset) }
};
responses.add(SetCanvasRotation { angle_radians: self.tilt + rotation }); if snap_zoom_enabled != new_snap {
} self.transform_operation = TransformOperation::Zoom {
pre_commit_zoom,
snap_zoom_enabled: new_snap,
};
}
if self.zooming { let difference = self.mouse_position.y - ipp.mouse.position.y;
let zoom_start = self.snapped_scale(); let amount = 1. + difference * VIEWPORT_ZOOM_MOUSE_RATE;
let new_snap = ipp.keyboard.get(snap_zoom as usize); self.zoom *= amount;
// When disabling snap, keep the viewed zoom as it was previously self.zoom *= Self::clamp_zoom(self.zoom, document_bounds, old_zoom, ipp);
if !new_snap && self.snap_zoom {
self.zoom = self.snapped_scale();
}
self.snap_zoom = new_snap;
let difference = self.mouse_position.y - ipp.mouse.position.y; if let Some(mouse) = zoom_from_viewport {
let amount = 1. + difference * VIEWPORT_ZOOM_MOUSE_RATE; let zoom_factor = self.snapped_scale() / zoom_start;
self.zoom *= amount; responses.add(SetCanvasZoom { zoom_factor: self.zoom });
self.zoom *= Self::clamp_zoom(self.zoom, document_bounds, old_zoom, ipp); responses.add(self.center_zoom(ipp.viewport_bounds.size(), zoom_factor, mouse));
} else {
if let Some(mouse) = zoom_from_viewport { responses.add(SetCanvasZoom { zoom_factor: self.zoom });
let zoom_factor = self.snapped_scale() / zoom_start; }
responses.add(SetCanvasZoom { zoom_factor: self.zoom });
responses.add(self.center_zoom(ipp.viewport_bounds.size(), zoom_factor, mouse));
} else {
responses.add(SetCanvasZoom { zoom_factor: self.zoom });
} }
} }
self.mouse_position = ipp.mouse.position; self.mouse_position = ipp.mouse.position;
} }
RotateCanvasBegin => { RotateCanvasBegin { was_dispatched_from_menu } => {
responses.add(FrontendMessage::UpdateMouseCursor { cursor: MouseCursorIcon::Default }); responses.add(FrontendMessage::UpdateMouseCursor { cursor: MouseCursorIcon::Default });
responses.add(FrontendMessage::UpdateInputHints { responses.add(FrontendMessage::UpdateInputHints {
hint_data: HintData(vec![HintGroup(vec![HintInfo { hint_data: HintData(vec![
key_groups: vec![KeysGroup(vec![Key::Control]).into()], HintGroup(vec![HintInfo {
key_groups_mac: None, key_groups: vec![KeysGroup(vec![Key::Control]).into()],
mouse: None, key_groups_mac: None,
label: String::from("Snap 15°"), mouse: None,
plus: false, label: String::from("Snap 15°"),
}])]), plus: false,
}]),
HintGroup(vec![HintInfo::mouse(MouseMotion::Lmb, "Confirm")]),
HintGroup(vec![HintInfo::mouse(MouseMotion::Rmb, "Abort")]),
]),
}); });
self.tilting = true; self.transform_operation = TransformOperation::Rotate {
pre_commit_tilt: self.tilt,
snap_tilt_released: false,
snap_tilt: false,
};
self.mouse_position = ipp.mouse.position; self.mouse_position = ipp.mouse.position;
self.finish_operation_with_click = was_dispatched_from_menu;
} }
SetCanvasRotation { angle_radians } => { SetCanvasRotation { angle_radians } => {
self.tilt = angle_radians; self.tilt = angle_radians;
@@ -208,19 +228,38 @@ impl MessageHandler<NavigationMessage, (&Document, Option<[DVec2; 2]>, &InputPre
responses.add(PortfolioMessage::UpdateDocumentWidgets); responses.add(PortfolioMessage::UpdateDocumentWidgets);
self.create_document_transform(&ipp.viewport_bounds, responses); self.create_document_transform(&ipp.viewport_bounds, responses);
} }
TransformCanvasEnd => { TransformCanvasEnd { abort_transform } => {
if abort_transform {
match self.transform_operation {
TransformOperation::None => {}
TransformOperation::Rotate { pre_commit_tilt, .. } => {
responses.add(SetCanvasRotation { angle_radians: pre_commit_tilt });
}
TransformOperation::Pan { pre_commit_pan, .. } => {
self.pan = pre_commit_pan;
self.create_document_transform(&ipp.viewport_bounds, responses);
}
TransformOperation::Zoom { pre_commit_zoom, .. } => {
self.zoom = pre_commit_zoom;
responses.add(PortfolioMessage::UpdateDocumentWidgets);
self.create_document_transform(&ipp.viewport_bounds, responses);
}
}
}
self.tilt = self.snapped_angle(); self.tilt = self.snapped_angle();
self.zoom = self.snapped_scale(); self.zoom = self.snapped_scale();
responses.add(BroadcastEvent::CanvasTransformed); responses.add(BroadcastEvent::CanvasTransformed);
responses.add(BroadcastEvent::DocumentIsDirty); responses.add(BroadcastEvent::DocumentIsDirty);
responses.add(ToolMessage::UpdateCursor); responses.add(ToolMessage::UpdateCursor);
responses.add(ToolMessage::UpdateHints); responses.add(ToolMessage::UpdateHints);
self.snap_tilt = false; self.transform_operation = TransformOperation::None;
self.snap_tilt_released = false; responses.add(PortfolioMessage::GraphViewOverlayToggleDisabled { disabled: false });
self.snap_zoom = false; }
self.panning = false; TransformFromMenuEnd { commit_key } => {
self.tilting = false; let abort_transform = commit_key == Key::Rmb;
self.zooming = false; self.finish_operation_with_click = false;
responses.add(TransformCanvasEnd { abort_transform });
} }
TranslateCanvas { delta } => { TranslateCanvas { delta } => {
let transformed_delta = document.root.transform.inverse().transform_vector2(delta); let transformed_delta = document.root.transform.inverse().transform_vector2(delta);
@@ -232,13 +271,16 @@ impl MessageHandler<NavigationMessage, (&Document, Option<[DVec2; 2]>, &InputPre
} }
TranslateCanvasBegin => { TranslateCanvasBegin => {
responses.add(FrontendMessage::UpdateMouseCursor { cursor: MouseCursorIcon::Grabbing }); responses.add(FrontendMessage::UpdateMouseCursor { cursor: MouseCursorIcon::Grabbing });
responses.add(FrontendMessage::UpdateInputHints { hint_data: HintData(Vec::new()) });
responses.add(FrontendMessage::UpdateInputHints {
hint_data: HintData(vec![HintGroup(vec![HintInfo::mouse(MouseMotion::Rmb, "Abort")])]),
});
// Because the pan key shares the Spacebar with toggling the graph view overlay, now that we've begun panning, // Because the pan key shares the Spacebar with toggling the graph view overlay, now that we've begun panning,
// we need to prevent the graph view overlay from toggling when the Spacebar is released. // we need to prevent the graph view overlay from toggling when the control key is pressed.
responses.add(PortfolioMessage::GraphViewOverlayToggleDisabled { disabled: true }); responses.add(PortfolioMessage::GraphViewOverlayToggleDisabled { disabled: true });
self.panning = true;
self.mouse_position = ipp.mouse.position; self.mouse_position = ipp.mouse.position;
self.transform_operation = TransformOperation::Pan { pre_commit_pan: self.pan };
} }
TranslateCanvasByViewportFraction { delta } => { TranslateCanvasByViewportFraction { delta } => {
let transformed_delta = document.root.transform.inverse().transform_vector2(delta * ipp.viewport_bounds.size()); let transformed_delta = document.root.transform.inverse().transform_vector2(delta * ipp.viewport_bounds.size());
@@ -268,17 +310,27 @@ impl MessageHandler<NavigationMessage, (&Document, Option<[DVec2; 2]>, &InputPre
ZoomCanvasBegin => { ZoomCanvasBegin => {
responses.add(FrontendMessage::UpdateMouseCursor { cursor: MouseCursorIcon::ZoomIn }); responses.add(FrontendMessage::UpdateMouseCursor { cursor: MouseCursorIcon::ZoomIn });
responses.add(FrontendMessage::UpdateInputHints { responses.add(FrontendMessage::UpdateInputHints {
hint_data: HintData(vec![HintGroup(vec![HintInfo { hint_data: HintData(vec![
key_groups: vec![KeysGroup(vec![Key::Control]).into()], HintGroup(vec![HintInfo {
key_groups_mac: None, key_groups: vec![KeysGroup(vec![Key::Control]).into()],
mouse: None, key_groups_mac: None,
label: String::from("Snap Increments"), mouse: None,
plus: false, label: String::from("Snap Increments"),
}])]), plus: false,
}]),
HintGroup(vec![HintInfo::mouse(MouseMotion::Rmb, "Abort")]),
]),
}); });
self.zooming = true; self.transform_operation = TransformOperation::Zoom {
pre_commit_zoom: self.zoom,
snap_zoom_enabled: false,
};
self.mouse_position = ipp.mouse.position; self.mouse_position = ipp.mouse.position;
// Because the zoom key shares the Spacebar with toggling the graph view overlay, now that we've begun zooming,
// we need to prevent the graph view overlay from toggling when the control key is pressed.
responses.add(PortfolioMessage::GraphViewOverlayToggleDisabled { disabled: true });
} }
} }
} }
@@ -298,13 +350,22 @@ impl MessageHandler<NavigationMessage, (&Document, Option<[DVec2; 2]>, &InputPre
FitViewportToSelection, FitViewportToSelection,
); );
if self.panning || self.tilting || self.zooming { if self.transform_operation != TransformOperation::None {
let transforming = actions!(NavigationMessageDiscriminant; let transforming = actions!(NavigationMessageDiscriminant;
PointerMove, PointerMove,
TransformCanvasEnd, TransformCanvasEnd,
); );
common.extend(transforming); common.extend(transforming);
} }
if self.finish_operation_with_click {
let transforming_from_menu = actions!(NavigationMessageDiscriminant;
TransformFromMenuEnd,
);
common.extend(transforming_from_menu);
}
common common
} }
} }
@@ -312,7 +373,7 @@ impl MessageHandler<NavigationMessage, (&Document, Option<[DVec2; 2]>, &InputPre
impl NavigationMessageHandler { impl NavigationMessageHandler {
pub fn snapped_angle(&self) -> f64 { pub fn snapped_angle(&self) -> f64 {
let increment_radians: f64 = VIEWPORT_ROTATE_SNAP_INTERVAL.to_radians(); let increment_radians: f64 = VIEWPORT_ROTATE_SNAP_INTERVAL.to_radians();
if self.snap_tilt { if let TransformOperation::Rotate { snap_tilt: true, .. } = self.transform_operation {
(self.tilt / increment_radians).round() * increment_radians (self.tilt / increment_radians).round() * increment_radians
} else { } else {
self.tilt self.tilt
@@ -320,7 +381,7 @@ impl NavigationMessageHandler {
} }
pub fn snapped_scale(&self) -> f64 { pub fn snapped_scale(&self) -> f64 {
if self.snap_zoom { if let TransformOperation::Zoom { snap_zoom_enabled: true, .. } = self.transform_operation {
*VIEWPORT_ZOOM_LEVELS *VIEWPORT_ZOOM_LEVELS
.iter() .iter()
.min_by(|a, b| (**a - self.zoom).abs().partial_cmp(&(**b - self.zoom).abs()).unwrap()) .min_by(|a, b| (**a - self.zoom).abs().partial_cmp(&(**b - self.zoom).abs()).unwrap())
@@ -257,36 +257,68 @@ impl LayoutHolder for MenuBarMessageHandler {
MenuBarEntry::new_root( MenuBarEntry::new_root(
"View".into(), "View".into(),
no_active_document, no_active_document,
MenuBarEntryChildren(vec![vec![ MenuBarEntryChildren(vec![
MenuBarEntry { vec![
label: "Zoom to Selected".into(), MenuBarEntry {
label: "Tilt".into(),
shortcut: action_keys!(NavigationMessageDiscriminant::RotateCanvasBegin),
action: MenuBarEntry::create_action(|_| NavigationMessage::RotateCanvasBegin { was_dispatched_from_menu: true }.into()),
disabled: no_active_document,
..MenuBarEntry::default()
},
MenuBarEntry {
label: "Reset Tilt".into(),
shortcut: action_keys!(NavigationMessageDiscriminant::SetCanvasRotation),
action: MenuBarEntry::create_action(|_| NavigationMessage::SetCanvasRotation { angle_radians: 0.into() }.into()),
disabled: no_active_document,
..MenuBarEntry::default()
},
],
vec![
MenuBarEntry {
label: "Zoom In".into(),
shortcut: action_keys!(NavigationMessageDiscriminant::IncreaseCanvasZoom),
action: MenuBarEntry::create_action(|_| NavigationMessage::IncreaseCanvasZoom { 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()),
disabled: no_active_document,
..MenuBarEntry::default()
},
MenuBarEntry {
label: "Zoom to Fit".into(),
shortcut: action_keys!(DocumentMessageDiscriminant::ZoomCanvasToFitAll),
action: MenuBarEntry::create_action(|_| DocumentMessage::ZoomCanvasToFitAll.into()),
disabled: no_active_document,
..MenuBarEntry::default()
},
MenuBarEntry {
label: "Zoom to 100%".into(),
shortcut: action_keys!(DocumentMessageDiscriminant::ZoomCanvasTo100Percent),
action: MenuBarEntry::create_action(|_| DocumentMessage::ZoomCanvasTo100Percent.into()),
disabled: no_active_document,
..MenuBarEntry::default()
},
MenuBarEntry {
label: "Zoom to 200%".into(),
shortcut: action_keys!(DocumentMessageDiscriminant::ZoomCanvasTo200Percent),
action: MenuBarEntry::create_action(|_| DocumentMessage::ZoomCanvasTo200Percent.into()),
disabled: no_active_document,
..MenuBarEntry::default()
},
],
vec![MenuBarEntry {
label: "Frame Selected".into(),
shortcut: action_keys!(NavigationMessageDiscriminant::FitViewportToSelection), shortcut: action_keys!(NavigationMessageDiscriminant::FitViewportToSelection),
action: MenuBarEntry::create_action(|_| NavigationMessage::FitViewportToSelection.into()), action: MenuBarEntry::create_action(|_| NavigationMessage::FitViewportToSelection.into()),
disabled: no_active_document, disabled: no_active_document,
..MenuBarEntry::default() ..MenuBarEntry::default()
}, }],
MenuBarEntry { ]),
label: "Zoom to Fit".into(),
shortcut: action_keys!(DocumentMessageDiscriminant::ZoomCanvasToFitAll),
action: MenuBarEntry::create_action(|_| DocumentMessage::ZoomCanvasToFitAll.into()),
disabled: no_active_document,
..MenuBarEntry::default()
},
MenuBarEntry {
label: "Zoom to 100%".into(),
shortcut: action_keys!(DocumentMessageDiscriminant::ZoomCanvasTo100Percent),
action: MenuBarEntry::create_action(|_| DocumentMessage::ZoomCanvasTo100Percent.into()),
disabled: no_active_document,
..MenuBarEntry::default()
},
MenuBarEntry {
label: "Zoom to 200%".into(),
shortcut: action_keys!(DocumentMessageDiscriminant::ZoomCanvasTo200Percent),
action: MenuBarEntry::create_action(|_| DocumentMessage::ZoomCanvasTo200Percent.into()),
disabled: no_active_document,
..MenuBarEntry::default()
},
]]),
), ),
MenuBarEntry::new_root( MenuBarEntry::new_root(
"Help".into(), "Help".into(),
@@ -557,6 +557,11 @@ impl MessageHandler<PortfolioMessage, (&InputPreprocessorMessageHandler, &Prefer
PrevDocument, PrevDocument,
); );
if self.graph_view_overlay_open {
let escape = actions!(PortfolioMessageDiscriminant; GraphViewOverlay);
common.extend(escape);
}
if let Some(document) = self.active_document() { if let Some(document) = self.active_document() {
if document.layer_metadata.values().any(|data| data.selected) { if document.layer_metadata.values().any(|data| data.selected) {
let select = actions!(PortfolioMessageDiscriminant; let select = actions!(PortfolioMessageDiscriminant;
@@ -117,7 +117,7 @@ impl Fsm for NavigateToolFsmState {
match navigate { match navigate {
ClickZoom { zoom_in } => { ClickZoom { zoom_in } => {
responses.add_front(NavigationMessage::TransformCanvasEnd); responses.add_front(NavigationMessage::TransformCanvasEnd { abort_transform: false });
// Mouse has not moved from pointerdown to pointerup // Mouse has not moved from pointerdown to pointerup
if tool_data.drag_start == input.mouse.position { if tool_data.drag_start == input.mouse.position {
@@ -146,7 +146,7 @@ impl Fsm for NavigateToolFsmState {
} }
RotateCanvasBegin => { RotateCanvasBegin => {
tool_data.drag_start = input.mouse.position; tool_data.drag_start = input.mouse.position;
responses.add_front(NavigationMessage::RotateCanvasBegin); responses.add_front(NavigationMessage::RotateCanvasBegin { was_dispatched_from_menu: false });
NavigateToolFsmState::Tilting NavigateToolFsmState::Tilting
} }
ZoomCanvasBegin => { ZoomCanvasBegin => {
@@ -155,11 +155,11 @@ impl Fsm for NavigateToolFsmState {
NavigateToolFsmState::Zooming NavigateToolFsmState::Zooming
} }
TransformCanvasEnd => { TransformCanvasEnd => {
responses.add_front(NavigationMessage::TransformCanvasEnd); responses.add_front(NavigationMessage::TransformCanvasEnd { abort_transform: false });
NavigateToolFsmState::Ready NavigateToolFsmState::Ready
} }
Abort => { Abort => {
responses.add_front(NavigationMessage::TransformCanvasEnd); responses.add_front(NavigationMessage::TransformCanvasEnd { abort_transform: false });
NavigateToolFsmState::Ready NavigateToolFsmState::Ready
} }
} }
@@ -173,8 +173,12 @@ impl Fsm for NavigateToolFsmState {
NavigateToolFsmState::Ready => HintData(vec![ NavigateToolFsmState::Ready => HintData(vec![
HintGroup(vec![HintInfo::mouse(MouseMotion::Lmb, "Zoom In"), HintInfo::keys([Key::Shift], "Zoom Out").prepend_plus()]), 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::LmbDrag, "Zoom"), HintInfo::keys([Key::Control], "Snap Increments").prepend_plus()]),
HintGroup(vec![HintInfo::mouse(MouseMotion::MmbDrag, "Pan")]), HintGroup(vec![
HintGroup(vec![HintInfo::mouse(MouseMotion::RmbDrag, "Tilt"), HintInfo::keys([Key::Control], "Snap 15°").prepend_plus()]), HintInfo::mouse(MouseMotion::LmbDrag, " "),
HintInfo::keys([Key::Space], "Or").prepend_plus(),
HintInfo::mouse(MouseMotion::MmbDrag, "Pan"),
]),
HintGroup(vec![HintInfo::mouse(MouseMotion::LmbDrag, ""), HintInfo::keys([Key::Alt], "Tilt").prepend_plus()]),
]), ]),
NavigateToolFsmState::Tilting => HintData(vec![HintGroup(vec![HintInfo::keys([Key::Control], "Snap 15°")])]), NavigateToolFsmState::Tilting => HintData(vec![HintGroup(vec![HintInfo::keys([Key::Control], "Snap 15°")])]),
NavigateToolFsmState::Zooming => HintData(vec![HintGroup(vec![HintInfo::keys([Key::Control], "Snap Increments")])]), NavigateToolFsmState::Zooming => HintData(vec![HintGroup(vec![HintInfo::keys([Key::Control], "Snap Increments")])]),