Enhance the Navigate Tool zoom behavior (#461)
* Snap zoom * Navigate zoom from centre * Ctrl to snap zoom in navigate * Use ctrl for global snap rotate * Fix the rotation input on snap rotate * Update hint to use ctrl * Fix mouse centre on drag * Click to zoom in * Clean up centre zoom * Update user input hints; tweak some variable names for clarity and standardization Co-authored-by: Keavon Chambers <keavon@keavon.com>
This commit is contained in:
parent
26da229807
commit
e39aa2a501
|
|
@ -859,9 +859,8 @@ impl MessageHandler<DocumentMessage, &InputPreprocessor> for DocumentMessageHand
|
|||
);
|
||||
responses.push_back(ArtboardMessage::RenderArtboards.into());
|
||||
|
||||
let document_transform = &self.movement_handler;
|
||||
|
||||
let scale = 0.5 + ASYMPTOTIC_EFFECT + document_transform.scale * SCALE_EFFECT;
|
||||
let document_transform_scale = self.movement_handler.snapped_scale();
|
||||
let scale = 0.5 + ASYMPTOTIC_EFFECT + document_transform_scale * SCALE_EFFECT;
|
||||
let viewport_size = ipp.viewport_bounds.size();
|
||||
let viewport_mid = ipp.viewport_bounds.center();
|
||||
let [bounds1, bounds2] = self.graphene_document.visible_layers_bounding_box().unwrap_or([viewport_mid; 2]);
|
||||
|
|
@ -872,9 +871,9 @@ impl MessageHandler<DocumentMessage, &InputPreprocessor> for DocumentMessageHand
|
|||
let scrollbar_multiplier = bounds_length - viewport_size;
|
||||
let scrollbar_size = viewport_size / bounds_length;
|
||||
|
||||
let log = document_transform.scale.log2();
|
||||
let log = document_transform_scale.log2();
|
||||
let ruler_interval = if log < 0. { 100. * 2_f64.powf(-log.ceil()) } else { 100. / 2_f64.powf(log.ceil()) };
|
||||
let ruler_spacing = ruler_interval * document_transform.scale;
|
||||
let ruler_spacing = ruler_interval * document_transform_scale;
|
||||
|
||||
let ruler_origin = self.graphene_document.root.transform.transform_point2(DVec2::ZERO);
|
||||
|
||||
|
|
|
|||
|
|
@ -17,16 +17,27 @@ use std::collections::VecDeque;
|
|||
#[impl_message(Message, DocumentMessage, Movement)]
|
||||
#[derive(PartialEq, Clone, Debug, Serialize, Deserialize)]
|
||||
pub enum MovementMessage {
|
||||
MouseMove { snap_angle: Key },
|
||||
MouseMove {
|
||||
snap_angle: Key,
|
||||
wait_for_snap_angle_release: bool,
|
||||
snap_zoom: Key,
|
||||
zoom_from_viewport: Option<DVec2>,
|
||||
},
|
||||
TranslateCanvasBegin,
|
||||
WheelCanvasTranslate { use_y_as_x: bool },
|
||||
WheelCanvasTranslate {
|
||||
use_y_as_x: bool,
|
||||
},
|
||||
RotateCanvasBegin,
|
||||
ZoomCanvasBegin,
|
||||
TransformCanvasEnd,
|
||||
SetCanvasRotation(f64),
|
||||
SetCanvasZoom(f64),
|
||||
IncreaseCanvasZoom,
|
||||
DecreaseCanvasZoom,
|
||||
IncreaseCanvasZoom {
|
||||
center_on_mouse: bool,
|
||||
},
|
||||
DecreaseCanvasZoom {
|
||||
center_on_mouse: bool,
|
||||
},
|
||||
WheelCanvasZoom,
|
||||
ZoomCanvasToFitAll,
|
||||
TranslateCanvas(DVec2),
|
||||
|
|
@ -35,27 +46,37 @@ pub enum MovementMessage {
|
|||
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
pub struct MovementMessageHandler {
|
||||
translating: bool,
|
||||
pub translation: DVec2,
|
||||
rotating: bool,
|
||||
pub rotation: f64,
|
||||
pub pan: DVec2,
|
||||
panning: bool,
|
||||
snap_tilt: bool,
|
||||
snap_tilt_released: bool,
|
||||
|
||||
pub tilt: f64,
|
||||
tilting: bool,
|
||||
|
||||
pub zoom: f64,
|
||||
zooming: bool,
|
||||
pub scale: f64,
|
||||
snap_rotate: bool,
|
||||
mouse_pos: ViewportPosition,
|
||||
snap_zoom: bool,
|
||||
|
||||
mouse_position: ViewportPosition,
|
||||
}
|
||||
|
||||
impl Default for MovementMessageHandler {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
scale: 1.,
|
||||
translating: false,
|
||||
translation: DVec2::ZERO,
|
||||
rotating: false,
|
||||
rotation: 0.,
|
||||
pan: DVec2::ZERO,
|
||||
panning: false,
|
||||
snap_tilt: false,
|
||||
snap_tilt_released: false,
|
||||
|
||||
tilt: 0.,
|
||||
tilting: false,
|
||||
|
||||
zoom: 1.,
|
||||
zooming: false,
|
||||
snap_rotate: false,
|
||||
mouse_pos: ViewportPosition::default(),
|
||||
snap_zoom: false,
|
||||
|
||||
mouse_position: ViewportPosition::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -63,25 +84,36 @@ impl Default for MovementMessageHandler {
|
|||
impl MovementMessageHandler {
|
||||
pub fn snapped_angle(&self) -> f64 {
|
||||
let increment_radians: f64 = VIEWPORT_ROTATE_SNAP_INTERVAL.to_radians();
|
||||
if self.snap_rotate {
|
||||
(self.rotation / increment_radians).round() * increment_radians
|
||||
if self.snap_tilt {
|
||||
(self.tilt / increment_radians).round() * increment_radians
|
||||
} else {
|
||||
self.rotation
|
||||
self.tilt
|
||||
}
|
||||
}
|
||||
|
||||
pub fn snapped_scale(&self) -> f64 {
|
||||
if self.snap_zoom {
|
||||
*VIEWPORT_ZOOM_LEVELS
|
||||
.iter()
|
||||
.min_by(|a, b| (**a - self.zoom).abs().partial_cmp(&(**b - self.zoom).abs()).unwrap())
|
||||
.unwrap_or(&self.zoom)
|
||||
} else {
|
||||
self.zoom
|
||||
}
|
||||
}
|
||||
|
||||
pub fn calculate_offset_transform(&self, offset: DVec2) -> DAffine2 {
|
||||
// TODO: replace with DAffine2::from_scale_angle_translation and fix the errors
|
||||
let offset_transform = DAffine2::from_translation(offset);
|
||||
let scale_transform = DAffine2::from_scale(DVec2::new(self.scale, self.scale));
|
||||
let scale_transform = DAffine2::from_scale(DVec2::splat(self.snapped_scale()));
|
||||
let angle_transform = DAffine2::from_angle(self.snapped_angle());
|
||||
let translation_transform = DAffine2::from_translation(self.translation);
|
||||
let translation_transform = DAffine2::from_translation(self.pan);
|
||||
scale_transform * offset_transform * angle_transform * translation_transform
|
||||
}
|
||||
|
||||
fn create_document_transform(&self, viewport_bounds: &ViewportBounds, responses: &mut VecDeque<Message>) {
|
||||
let half_viewport = viewport_bounds.size() / 2.;
|
||||
let scaled_half_viewport = half_viewport / self.scale;
|
||||
|
||||
let scaled_half_viewport = half_viewport / self.snapped_scale();
|
||||
responses.push_back(
|
||||
DocumentOperation::SetLayerTransform {
|
||||
path: vec![],
|
||||
|
|
@ -101,6 +133,14 @@ impl MovementMessageHandler {
|
|||
.into(),
|
||||
);
|
||||
}
|
||||
pub fn center_zoom(&self, viewport_bounds: DVec2, zoom_factor: f64, mouse: DVec2) -> Message {
|
||||
let new_viewport_bounds = viewport_bounds / zoom_factor;
|
||||
let delta_size = viewport_bounds - new_viewport_bounds;
|
||||
let mouse_fraction = mouse / viewport_bounds;
|
||||
let delta = delta_size * (DVec2::splat(0.5) - mouse_fraction);
|
||||
|
||||
MovementMessage::TranslateCanvas(delta).into()
|
||||
}
|
||||
}
|
||||
|
||||
impl MessageHandler<MovementMessage, (&Document, &InputPreprocessor)> for MovementMessageHandler {
|
||||
|
|
@ -109,128 +149,129 @@ impl MessageHandler<MovementMessage, (&Document, &InputPreprocessor)> for Moveme
|
|||
use MovementMessage::*;
|
||||
match message {
|
||||
TranslateCanvasBegin => {
|
||||
self.translating = true;
|
||||
self.mouse_pos = ipp.mouse.position;
|
||||
self.panning = true;
|
||||
self.mouse_position = ipp.mouse.position;
|
||||
}
|
||||
RotateCanvasBegin => {
|
||||
self.rotating = true;
|
||||
self.mouse_pos = ipp.mouse.position;
|
||||
self.tilting = true;
|
||||
self.mouse_position = ipp.mouse.position;
|
||||
}
|
||||
ZoomCanvasBegin => {
|
||||
self.zooming = true;
|
||||
self.mouse_pos = ipp.mouse.position;
|
||||
self.mouse_position = ipp.mouse.position;
|
||||
}
|
||||
TransformCanvasEnd => {
|
||||
self.rotation = self.snapped_angle();
|
||||
self.tilt = self.snapped_angle();
|
||||
self.zoom = self.snapped_scale();
|
||||
responses.push_back(ToolMessage::DocumentIsDirty.into());
|
||||
self.snap_rotate = false;
|
||||
self.translating = false;
|
||||
self.rotating = false;
|
||||
self.snap_tilt = false;
|
||||
self.snap_tilt_released = false;
|
||||
self.snap_zoom = false;
|
||||
self.panning = false;
|
||||
self.tilting = false;
|
||||
self.zooming = false;
|
||||
}
|
||||
MouseMove { snap_angle } => {
|
||||
if self.translating {
|
||||
let delta = ipp.mouse.position - self.mouse_pos;
|
||||
let transformed_delta = document.root.transform.inverse().transform_vector2(delta);
|
||||
MouseMove {
|
||||
snap_angle,
|
||||
wait_for_snap_angle_release,
|
||||
snap_zoom,
|
||||
zoom_from_viewport,
|
||||
} => {
|
||||
if self.panning {
|
||||
let delta = ipp.mouse.position - self.mouse_position;
|
||||
|
||||
self.translation += transformed_delta;
|
||||
responses.push_back(ToolMessage::DocumentIsDirty.into());
|
||||
self.create_document_transform(&ipp.viewport_bounds, responses);
|
||||
responses.push_back(TranslateCanvas(delta).into());
|
||||
}
|
||||
|
||||
if self.rotating {
|
||||
if self.tilting {
|
||||
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();
|
||||
if !(wait_for_snap_angle_release && new_snap && !self.snap_tilt_released) {
|
||||
// When disabling snap, keep the viewed rotation as it was previously.
|
||||
if !new_snap && self.snap_tilt {
|
||||
self.tilt = self.snapped_angle();
|
||||
}
|
||||
self.snap_tilt = new_snap;
|
||||
self.snap_tilt_released = true;
|
||||
}
|
||||
self.snap_rotate = new_snap;
|
||||
|
||||
let half_viewport = ipp.viewport_bounds.size() / 2.;
|
||||
let rotation = {
|
||||
let start_vec = self.mouse_pos - half_viewport;
|
||||
let start_vec = self.mouse_position - half_viewport;
|
||||
let end_vec = ipp.mouse.position - half_viewport;
|
||||
start_vec.angle_between(end_vec)
|
||||
};
|
||||
|
||||
self.rotation += rotation;
|
||||
responses.push_back(ToolMessage::DocumentIsDirty.into());
|
||||
responses.push_back(FrontendMessage::SetCanvasRotation { new_radians: self.snapped_angle() }.into());
|
||||
self.create_document_transform(&ipp.viewport_bounds, responses);
|
||||
responses.push_back(SetCanvasRotation(self.tilt + rotation).into());
|
||||
}
|
||||
|
||||
if self.zooming {
|
||||
let difference = self.mouse_pos.y as f64 - ipp.mouse.position.y as f64;
|
||||
let zoom_start = self.snapped_scale();
|
||||
|
||||
let new_snap = ipp.keyboard.get(snap_zoom as usize);
|
||||
// When disabling snap, keep the viewed zoom as it was previously
|
||||
if !new_snap && self.snap_zoom {
|
||||
self.zoom = self.snapped_scale();
|
||||
}
|
||||
self.snap_zoom = new_snap;
|
||||
|
||||
let difference = self.mouse_position.y as f64 - ipp.mouse.position.y as f64;
|
||||
let amount = 1. + difference * VIEWPORT_ZOOM_MOUSE_RATE;
|
||||
|
||||
let new = (self.scale * amount).clamp(VIEWPORT_ZOOM_SCALE_MIN, VIEWPORT_ZOOM_SCALE_MAX);
|
||||
self.scale = new;
|
||||
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.zoom *= amount;
|
||||
if let Some(mouse) = zoom_from_viewport {
|
||||
let zoom_factor = self.snapped_scale() / zoom_start;
|
||||
|
||||
responses.push_back(SetCanvasZoom(self.zoom).into());
|
||||
responses.push_back(self.center_zoom(ipp.viewport_bounds.size(), zoom_factor, mouse));
|
||||
} else {
|
||||
responses.push_back(SetCanvasZoom(self.zoom).into());
|
||||
}
|
||||
}
|
||||
self.mouse_pos = ipp.mouse.position;
|
||||
self.mouse_position = ipp.mouse.position;
|
||||
}
|
||||
SetCanvasZoom(new) => {
|
||||
self.scale = new.clamp(VIEWPORT_ZOOM_SCALE_MIN, VIEWPORT_ZOOM_SCALE_MAX);
|
||||
responses.push_back(FrontendMessage::SetCanvasZoom { new_zoom: self.scale }.into());
|
||||
self.zoom = new.clamp(VIEWPORT_ZOOM_SCALE_MIN, VIEWPORT_ZOOM_SCALE_MAX);
|
||||
responses.push_back(FrontendMessage::SetCanvasZoom { new_zoom: self.snapped_scale() }.into());
|
||||
responses.push_back(ToolMessage::DocumentIsDirty.into());
|
||||
responses.push_back(DocumentMessage::DirtyRenderDocumentInOutlineView.into());
|
||||
self.create_document_transform(&ipp.viewport_bounds, responses);
|
||||
}
|
||||
IncreaseCanvasZoom => {
|
||||
// TODO: Eliminate redundant code by making this call SetCanvasZoom
|
||||
self.scale = *VIEWPORT_ZOOM_LEVELS.iter().find(|scale| **scale > self.scale).unwrap_or(&self.scale);
|
||||
responses.push_back(FrontendMessage::SetCanvasZoom { new_zoom: self.scale }.into());
|
||||
responses.push_back(ToolMessage::DocumentIsDirty.into());
|
||||
responses.push_back(DocumentMessage::DirtyRenderDocumentInOutlineView.into());
|
||||
self.create_document_transform(&ipp.viewport_bounds, responses);
|
||||
IncreaseCanvasZoom { center_on_mouse } => {
|
||||
let new_scale = *VIEWPORT_ZOOM_LEVELS.iter().find(|scale| **scale > self.zoom).unwrap_or(&self.zoom);
|
||||
if center_on_mouse {
|
||||
responses.push_back(self.center_zoom(ipp.viewport_bounds.size(), new_scale / self.zoom, ipp.mouse.position));
|
||||
}
|
||||
responses.push_back(SetCanvasZoom(new_scale).into());
|
||||
}
|
||||
DecreaseCanvasZoom => {
|
||||
// TODO: Eliminate redundant code by making this call SetCanvasZoom
|
||||
self.scale = *VIEWPORT_ZOOM_LEVELS.iter().rev().find(|scale| **scale < self.scale).unwrap_or(&self.scale);
|
||||
responses.push_back(FrontendMessage::SetCanvasZoom { new_zoom: self.scale }.into());
|
||||
responses.push_back(ToolMessage::DocumentIsDirty.into());
|
||||
responses.push_back(DocumentMessage::DirtyRenderDocumentInOutlineView.into());
|
||||
self.create_document_transform(&ipp.viewport_bounds, responses);
|
||||
DecreaseCanvasZoom { center_on_mouse } => {
|
||||
let new_scale = *VIEWPORT_ZOOM_LEVELS.iter().rev().find(|scale| **scale < self.zoom).unwrap_or(&self.zoom);
|
||||
if center_on_mouse {
|
||||
responses.push_back(self.center_zoom(ipp.viewport_bounds.size(), new_scale / self.zoom, ipp.mouse.position));
|
||||
}
|
||||
responses.push_back(SetCanvasZoom(new_scale).into());
|
||||
}
|
||||
WheelCanvasZoom => {
|
||||
// TODO: Eliminate redundant code by making this call SetCanvasZoom
|
||||
let scroll = ipp.mouse.scroll_delta.scroll_delta();
|
||||
let mouse = ipp.mouse.position;
|
||||
let viewport_bounds = ipp.viewport_bounds.size();
|
||||
let mut zoom_factor = 1. + scroll.abs() * VIEWPORT_ZOOM_WHEEL_RATE;
|
||||
if ipp.mouse.scroll_delta.y > 0 {
|
||||
zoom_factor = 1. / zoom_factor
|
||||
};
|
||||
let new_viewport_bounds = viewport_bounds / zoom_factor;
|
||||
let delta_size = viewport_bounds - new_viewport_bounds;
|
||||
let mouse_fraction = mouse / viewport_bounds;
|
||||
let delta = delta_size * (DVec2::splat(0.5) - mouse_fraction);
|
||||
|
||||
let transformed_delta = document.root.transform.inverse().transform_vector2(delta);
|
||||
let new = (self.scale * zoom_factor).clamp(VIEWPORT_ZOOM_SCALE_MIN, VIEWPORT_ZOOM_SCALE_MAX);
|
||||
self.scale = new;
|
||||
self.translation += transformed_delta;
|
||||
responses.push_back(FrontendMessage::SetCanvasZoom { new_zoom: self.scale }.into());
|
||||
responses.push_back(ToolMessage::DocumentIsDirty.into());
|
||||
responses.push_back(DocumentMessage::DirtyRenderDocumentInOutlineView.into());
|
||||
self.create_document_transform(&ipp.viewport_bounds, responses);
|
||||
responses.push_back(self.center_zoom(ipp.viewport_bounds.size(), zoom_factor, ipp.mouse.position));
|
||||
responses.push_back(SetCanvasZoom(self.zoom * zoom_factor).into());
|
||||
}
|
||||
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;
|
||||
let transformed_delta = document.root.transform.inverse().transform_vector2(delta);
|
||||
self.translation += transformed_delta;
|
||||
responses.push_back(ToolMessage::DocumentIsDirty.into());
|
||||
self.create_document_transform(&ipp.viewport_bounds, responses);
|
||||
responses.push_back(TranslateCanvas(delta).into());
|
||||
}
|
||||
SetCanvasRotation(new_radians) => {
|
||||
self.rotation = new_radians;
|
||||
self.tilt = new_radians;
|
||||
self.create_document_transform(&ipp.viewport_bounds, responses);
|
||||
responses.push_back(ToolMessage::DocumentIsDirty.into());
|
||||
responses.push_back(FrontendMessage::SetCanvasRotation { new_radians }.into());
|
||||
responses.push_back(FrontendMessage::SetCanvasRotation { new_radians: self.snapped_angle() }.into());
|
||||
}
|
||||
ZoomCanvasToFitAll => {
|
||||
if let Some([pos1, pos2]) = document.visible_layers_bounding_box() {
|
||||
|
|
@ -244,9 +285,9 @@ impl MessageHandler<MovementMessage, (&Document, &InputPreprocessor)> for Moveme
|
|||
let size = 1. / size;
|
||||
let new_scale = size.min_element();
|
||||
|
||||
self.translation += center;
|
||||
self.scale *= new_scale;
|
||||
responses.push_back(FrontendMessage::SetCanvasZoom { new_zoom: self.scale }.into());
|
||||
self.pan += center;
|
||||
self.zoom *= new_scale;
|
||||
responses.push_back(FrontendMessage::SetCanvasZoom { new_zoom: self.zoom }.into());
|
||||
responses.push_back(ToolMessage::DocumentIsDirty.into());
|
||||
responses.push_back(DocumentMessage::DirtyRenderDocumentInOutlineView.into());
|
||||
self.create_document_transform(&ipp.viewport_bounds, responses);
|
||||
|
|
@ -255,14 +296,14 @@ impl MessageHandler<MovementMessage, (&Document, &InputPreprocessor)> for Moveme
|
|||
TranslateCanvas(delta) => {
|
||||
let transformed_delta = document.root.transform.inverse().transform_vector2(delta);
|
||||
|
||||
self.translation += transformed_delta;
|
||||
self.pan += transformed_delta;
|
||||
responses.push_back(ToolMessage::DocumentIsDirty.into());
|
||||
self.create_document_transform(&ipp.viewport_bounds, responses);
|
||||
}
|
||||
TranslateCanvasByViewportFraction(delta) => {
|
||||
let transformed_delta = document.root.transform.inverse().transform_vector2(delta * ipp.viewport_bounds.size());
|
||||
|
||||
self.translation += transformed_delta;
|
||||
self.pan += transformed_delta;
|
||||
responses.push_back(ToolMessage::DocumentIsDirty.into());
|
||||
self.create_document_transform(&ipp.viewport_bounds, responses);
|
||||
}
|
||||
|
|
@ -285,7 +326,7 @@ impl MessageHandler<MovementMessage, (&Document, &InputPreprocessor)> for Moveme
|
|||
TranslateCanvasByViewportFraction,
|
||||
);
|
||||
|
||||
if self.translating || self.rotating || self.zooming {
|
||||
if self.panning || self.tilting || self.zooming {
|
||||
let transforming = actions!(MovementMessageDiscriminant;
|
||||
TransformCanvasEnd,
|
||||
);
|
||||
|
|
|
|||
|
|
@ -146,19 +146,23 @@ impl Default for Mapping {
|
|||
entry! {action=TransformLayerMessage::TypeNegate, key_down=KeyMinus},
|
||||
entry! {action=TransformLayerMessage::TypeDecimalPoint, key_down=KeyComma},
|
||||
entry! {action=TransformLayerMessage::TypeDecimalPoint, key_down=KeyPeriod},
|
||||
entry! {action=TransformLayerMessage::MouseMove{slow_key: KeyShift, snap_key: KeyControl}, triggers=[KeyShift, KeyControl]},
|
||||
entry! {action=TransformLayerMessage::MouseMove { slow_key: KeyShift, snap_key: KeyControl }, triggers=[KeyShift, KeyControl]},
|
||||
// Select
|
||||
entry! {action=SelectMessage::MouseMove{snap_angle: KeyShift}, message=InputMapperMessage::PointerMove},
|
||||
entry! {action=SelectMessage::DragStart{add_to_selection: KeyShift}, key_down=Lmb},
|
||||
entry! {action=SelectMessage::MouseMove { snap_angle: KeyShift }, message=InputMapperMessage::PointerMove},
|
||||
entry! {action=SelectMessage::DragStart { add_to_selection: KeyShift }, key_down=Lmb},
|
||||
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::ClickZoom { zoom_in: false }, key_up=Lmb, modifiers=[KeyShift]},
|
||||
entry! {action=NavigateMessage::ClickZoom { zoom_in: true }, key_up=Lmb},
|
||||
entry! {action=NavigateMessage::MouseMove { snap_angle: KeyControl, snap_zoom: KeyControl }, message=InputMapperMessage::PointerMove},
|
||||
entry! {action=NavigateMessage::TranslateCanvasBegin, key_down=Mmb},
|
||||
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},
|
||||
entry! {action=NavigateMessage::TransformCanvasEnd, key_up=Mmb},
|
||||
// Eyedropper
|
||||
entry! {action=EyedropperMessage::LeftMouseDown, key_down=Lmb},
|
||||
entry! {action=EyedropperMessage::RightMouseDown, key_down=Rmb},
|
||||
|
|
@ -167,25 +171,25 @@ impl Default for Mapping {
|
|||
entry! {action=RectangleMessage::DragStop, key_up=Lmb},
|
||||
entry! {action=RectangleMessage::Abort, key_down=Rmb},
|
||||
entry! {action=RectangleMessage::Abort, key_down=KeyEscape},
|
||||
entry! {action=RectangleMessage::Resize{center: KeyAlt, lock_ratio: KeyShift}, triggers=[KeyAlt, KeyShift]},
|
||||
entry! {action=RectangleMessage::Resize { center: KeyAlt, lock_ratio: KeyShift }, triggers=[KeyAlt, KeyShift]},
|
||||
// Ellipse
|
||||
entry! {action=EllipseMessage::DragStart, key_down=Lmb},
|
||||
entry! {action=EllipseMessage::DragStop, key_up=Lmb},
|
||||
entry! {action=EllipseMessage::Abort, key_down=Rmb},
|
||||
entry! {action=EllipseMessage::Abort, key_down=KeyEscape},
|
||||
entry! {action=EllipseMessage::Resize{center: KeyAlt, lock_ratio: KeyShift}, triggers=[KeyAlt, KeyShift]},
|
||||
entry! {action=EllipseMessage::Resize { center: KeyAlt, lock_ratio: KeyShift }, triggers=[KeyAlt, KeyShift]},
|
||||
// Shape
|
||||
entry! {action=ShapeMessage::DragStart, key_down=Lmb},
|
||||
entry! {action=ShapeMessage::DragStop, key_up=Lmb},
|
||||
entry! {action=ShapeMessage::Abort, key_down=Rmb},
|
||||
entry! {action=ShapeMessage::Abort, key_down=KeyEscape},
|
||||
entry! {action=ShapeMessage::Resize{center: KeyAlt, lock_ratio: KeyShift}, triggers=[KeyAlt, KeyShift]},
|
||||
entry! {action=ShapeMessage::Resize { center: KeyAlt, lock_ratio: KeyShift }, triggers=[KeyAlt, KeyShift]},
|
||||
// Line
|
||||
entry! {action=LineMessage::DragStart, key_down=Lmb},
|
||||
entry! {action=LineMessage::DragStop, key_up=Lmb},
|
||||
entry! {action=LineMessage::Abort, key_down=Rmb},
|
||||
entry! {action=LineMessage::Abort, key_down=KeyEscape},
|
||||
entry! {action=LineMessage::Redraw{center: KeyAlt, lock_angle: KeyControl, snap_angle: KeyShift}, triggers=[KeyAlt, KeyShift, KeyControl]},
|
||||
entry! {action=LineMessage::Redraw { center: KeyAlt, lock_angle: KeyControl, snap_angle: KeyShift }, triggers=[KeyAlt, KeyShift, KeyControl]},
|
||||
// Path
|
||||
entry! {action=PathMessage::DragStart, key_down=Lmb},
|
||||
entry! {action=PathMessage::PointerMove, message=InputMapperMessage::PointerMove},
|
||||
|
|
@ -235,22 +239,22 @@ impl Default for Mapping {
|
|||
entry! {action=TransformLayerMessage::BeginRotate, key_down=KeyR},
|
||||
entry! {action=TransformLayerMessage::BeginScale, key_down=KeyS},
|
||||
// Document movement
|
||||
entry! {action=MovementMessage::MouseMove{snap_angle: KeyShift}, message=InputMapperMessage::PointerMove},
|
||||
entry! {action=MovementMessage::MouseMove { snap_angle: KeyControl, wait_for_snap_angle_release: true, snap_zoom: KeyControl, zoom_from_viewport: None }, message=InputMapperMessage::PointerMove},
|
||||
entry! {action=MovementMessage::RotateCanvasBegin, key_down=Mmb, modifiers=[KeyControl]},
|
||||
entry! {action=MovementMessage::ZoomCanvasBegin, key_down=Mmb, modifiers=[KeyShift]},
|
||||
entry! {action=MovementMessage::TranslateCanvasBegin, key_down=Mmb},
|
||||
entry! {action=MovementMessage::TransformCanvasEnd, key_up=Mmb},
|
||||
entry! {action=MovementMessage::TranslateCanvasBegin, key_down=Lmb, modifiers=[KeySpace]},
|
||||
entry! {action=MovementMessage::TransformCanvasEnd, key_up=Lmb, modifiers=[KeySpace]},
|
||||
entry! {action=MovementMessage::IncreaseCanvasZoom, key_down=KeyPlus, modifiers=[KeyControl]},
|
||||
entry! {action=MovementMessage::IncreaseCanvasZoom, key_down=KeyEquals, modifiers=[KeyControl]},
|
||||
entry! {action=MovementMessage::DecreaseCanvasZoom, key_down=KeyMinus, modifiers=[KeyControl]},
|
||||
entry! {action=MovementMessage::IncreaseCanvasZoom { center_on_mouse: false }, key_down=KeyPlus, modifiers=[KeyControl]},
|
||||
entry! {action=MovementMessage::IncreaseCanvasZoom { center_on_mouse: false }, key_down=KeyEquals, modifiers=[KeyControl]},
|
||||
entry! {action=MovementMessage::DecreaseCanvasZoom { center_on_mouse: false }, 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},
|
||||
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},
|
||||
entry! {action=MovementMessage::TranslateCanvasByViewportFraction(DVec2::new(1., 0.)), key_down=KeyPageUp, modifiers=[KeyShift]},
|
||||
entry! {action=MovementMessage::TranslateCanvasByViewportFraction(DVec2::new(-1., 0.)), key_down=KeyPageDown, modifiers=[KeyShift]},
|
||||
entry! {action=MovementMessage::TranslateCanvasByViewportFraction(DVec2::new(0., 1.)), key_down=KeyPageUp},
|
||||
|
|
|
|||
|
|
@ -2,17 +2,20 @@ 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 glam::DVec2;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct Navigate {
|
||||
fsm_state: NavigateToolFsmState,
|
||||
data: NavigateToolData,
|
||||
}
|
||||
|
||||
#[impl_message(Message, ToolMessage, Navigate)]
|
||||
#[derive(PartialEq, Clone, Debug, Hash, Serialize, Deserialize)]
|
||||
pub enum NavigateMessage {
|
||||
MouseMove { snap_angle: Key },
|
||||
ClickZoom { zoom_in: bool },
|
||||
MouseMove { snap_angle: Key, snap_zoom: Key },
|
||||
TranslateCanvasBegin,
|
||||
RotateCanvasBegin,
|
||||
ZoomCanvasBegin,
|
||||
|
|
@ -27,7 +30,7 @@ impl<'a> MessageHandler<ToolMessage, ToolActionHandlerData<'a>> for Navigate {
|
|||
return;
|
||||
}
|
||||
|
||||
let new_state = self.fsm_state.transition(action, data.0, data.1, &mut (), data.2, responses);
|
||||
let new_state = self.fsm_state.transition(action, data.0, data.1, &mut self.data, data.2, responses);
|
||||
|
||||
if self.fsm_state != new_state {
|
||||
self.fsm_state = new_state;
|
||||
|
|
@ -39,7 +42,7 @@ impl<'a> MessageHandler<ToolMessage, ToolActionHandlerData<'a>> for Navigate {
|
|||
use NavigateToolFsmState::*;
|
||||
match self.fsm_state {
|
||||
Ready => actions!(NavigateMessageDiscriminant; TranslateCanvasBegin, RotateCanvasBegin, ZoomCanvasBegin),
|
||||
_ => actions!(NavigateMessageDiscriminant; MouseMove, TransformCanvasEnd),
|
||||
_ => actions!(NavigateMessageDiscriminant; ClickZoom, MouseMove, TransformCanvasEnd),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -47,8 +50,8 @@ impl<'a> MessageHandler<ToolMessage, ToolActionHandlerData<'a>> for Navigate {
|
|||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
enum NavigateToolFsmState {
|
||||
Ready,
|
||||
Translating,
|
||||
Rotating,
|
||||
Panning,
|
||||
Tilting,
|
||||
Zooming,
|
||||
}
|
||||
|
||||
|
|
@ -58,34 +61,64 @@ impl Default for NavigateToolFsmState {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default)]
|
||||
struct NavigateToolData {
|
||||
drag_start: DVec2,
|
||||
}
|
||||
|
||||
impl Fsm for NavigateToolFsmState {
|
||||
type ToolData = ();
|
||||
type ToolData = NavigateToolData;
|
||||
|
||||
fn transition(
|
||||
self,
|
||||
message: ToolMessage,
|
||||
_document: &crate::document::DocumentMessageHandler,
|
||||
_tool_data: &crate::tool::DocumentToolData,
|
||||
_data: &mut Self::ToolData,
|
||||
_input: &crate::input::InputPreprocessor,
|
||||
data: &mut Self::ToolData,
|
||||
input: &crate::input::InputPreprocessor,
|
||||
messages: &mut VecDeque<Message>,
|
||||
) -> Self {
|
||||
if let ToolMessage::Navigate(navigate) = message {
|
||||
use NavigateMessage::*;
|
||||
match navigate {
|
||||
MouseMove { snap_angle } => {
|
||||
messages.push_front(MovementMessage::MouseMove { snap_angle }.into());
|
||||
ClickZoom { zoom_in } => {
|
||||
messages.push_front(MovementMessage::TransformCanvasEnd.into());
|
||||
|
||||
// Mouse has not moved from mousedown to mouseup
|
||||
if data.drag_start == input.mouse.position {
|
||||
messages.push_front(if zoom_in {
|
||||
MovementMessage::IncreaseCanvasZoom { center_on_mouse: true }.into()
|
||||
} else {
|
||||
MovementMessage::DecreaseCanvasZoom { center_on_mouse: true }.into()
|
||||
});
|
||||
}
|
||||
|
||||
NavigateToolFsmState::Ready
|
||||
}
|
||||
MouseMove { snap_angle, snap_zoom } => {
|
||||
messages.push_front(
|
||||
MovementMessage::MouseMove {
|
||||
snap_angle,
|
||||
wait_for_snap_angle_release: false,
|
||||
snap_zoom,
|
||||
zoom_from_viewport: Some(data.drag_start),
|
||||
}
|
||||
.into(),
|
||||
);
|
||||
self
|
||||
}
|
||||
TranslateCanvasBegin => {
|
||||
data.drag_start = input.mouse.position;
|
||||
messages.push_front(MovementMessage::TranslateCanvasBegin.into());
|
||||
NavigateToolFsmState::Translating
|
||||
NavigateToolFsmState::Panning
|
||||
}
|
||||
RotateCanvasBegin => {
|
||||
data.drag_start = input.mouse.position;
|
||||
messages.push_front(MovementMessage::RotateCanvasBegin.into());
|
||||
NavigateToolFsmState::Rotating
|
||||
NavigateToolFsmState::Tilting
|
||||
}
|
||||
ZoomCanvasBegin => {
|
||||
data.drag_start = input.mouse.position;
|
||||
messages.push_front(MovementMessage::ZoomCanvasBegin.into());
|
||||
NavigateToolFsmState::Zooming
|
||||
}
|
||||
|
|
@ -106,37 +139,65 @@ impl Fsm for NavigateToolFsmState {
|
|||
fn update_hints(&self, responses: &mut VecDeque<Message>) {
|
||||
let hint_data = match self {
|
||||
NavigateToolFsmState::Ready => HintData(vec![
|
||||
HintGroup(vec![
|
||||
HintInfo {
|
||||
key_groups: vec![],
|
||||
mouse: Some(MouseMotion::Lmb),
|
||||
label: String::from("Zoom In"),
|
||||
plus: false,
|
||||
},
|
||||
HintInfo {
|
||||
key_groups: vec![KeysGroup(vec![Key::KeyShift])],
|
||||
mouse: None,
|
||||
label: String::from("Zoom Out"),
|
||||
plus: true,
|
||||
},
|
||||
]),
|
||||
HintGroup(vec![
|
||||
HintInfo {
|
||||
key_groups: vec![],
|
||||
mouse: Some(MouseMotion::LmbDrag),
|
||||
label: String::from("Zoom"),
|
||||
plus: false,
|
||||
},
|
||||
HintInfo {
|
||||
key_groups: vec![KeysGroup(vec![Key::KeyControl])],
|
||||
mouse: None,
|
||||
label: String::from("Snap Increments"),
|
||||
plus: true,
|
||||
},
|
||||
]),
|
||||
HintGroup(vec![HintInfo {
|
||||
key_groups: vec![],
|
||||
mouse: Some(MouseMotion::MmbDrag),
|
||||
label: String::from("Translate"),
|
||||
label: String::from("Pan"),
|
||||
plus: false,
|
||||
}]),
|
||||
HintGroup(vec![
|
||||
HintInfo {
|
||||
key_groups: vec![],
|
||||
mouse: Some(MouseMotion::RmbDrag),
|
||||
label: String::from("Rotate (drag around centre)"),
|
||||
label: String::from("Tilt"),
|
||||
plus: false,
|
||||
},
|
||||
HintInfo {
|
||||
key_groups: vec![KeysGroup(vec![Key::KeyControl])],
|
||||
mouse: None,
|
||||
label: String::from("Snap rotation to 15° increments"),
|
||||
label: String::from("Snap 15°"),
|
||||
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 {
|
||||
NavigateToolFsmState::Tilting => HintData(vec![HintGroup(vec![HintInfo {
|
||||
key_groups: vec![KeysGroup(vec![Key::KeyControl])],
|
||||
mouse: None,
|
||||
label: String::from("Snap to 15° increments"),
|
||||
label: String::from("Snap 15°"),
|
||||
plus: false,
|
||||
}])]),
|
||||
NavigateToolFsmState::Zooming => HintData(vec![HintGroup(vec![HintInfo {
|
||||
key_groups: vec![KeysGroup(vec![Key::KeyControl])],
|
||||
mouse: None,
|
||||
label: String::from("Snap Increments"),
|
||||
plus: false,
|
||||
}])]),
|
||||
_ => HintData(Vec::new()),
|
||||
|
|
|
|||
|
|
@ -438,13 +438,13 @@ impl JsEditorHandle {
|
|||
|
||||
/// Zoom in to the next step
|
||||
pub fn increase_canvas_zoom(&self) {
|
||||
let message = MovementMessage::IncreaseCanvasZoom;
|
||||
let message = MovementMessage::IncreaseCanvasZoom { center_on_mouse: false };
|
||||
self.dispatch(message);
|
||||
}
|
||||
|
||||
/// Zoom out to the next step
|
||||
pub fn decrease_canvas_zoom(&self) {
|
||||
let message = MovementMessage::DecreaseCanvasZoom;
|
||||
let message = MovementMessage::DecreaseCanvasZoom { center_on_mouse: false };
|
||||
self.dispatch(message);
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue