Add navigate tool (#441)

* Remove transformations from layerdata

* Clean up snap rotate

* Enable the navigate tool

* Implement navigate tool

Co-authored-by: otdavies <oliver@psyfer.io>
This commit is contained in:
0HyperCube 2021-12-31 03:43:26 +00:00 committed by Keavon Chambers
parent 7ab127c3ae
commit d0dcc0e42f
5 changed files with 163 additions and 36 deletions

View File

@ -1,6 +1,7 @@
use crate::consts::VIEWPORT_ROTATE_SNAP_INTERVAL;
pub use crate::document::layer_panel::*;
use crate::document::DocumentMessage;
use crate::input::keyboard::Key;
use crate::message_prelude::*;
use crate::{
consts::{VIEWPORT_SCROLL_RATE, VIEWPORT_ZOOM_LEVELS, VIEWPORT_ZOOM_MOUSE_RATE, VIEWPORT_ZOOM_SCALE_MAX, VIEWPORT_ZOOM_SCALE_MIN, VIEWPORT_ZOOM_WHEEL_RATE},
@ -16,12 +17,10 @@ use std::collections::VecDeque;
#[impl_message(Message, DocumentMessage, Movement)]
#[derive(PartialEq, Clone, Debug, Serialize, Deserialize)]
pub enum MovementMessage {
MouseMove,
MouseMove { snap_angle: Key },
TranslateCanvasBegin,
WheelCanvasTranslate { use_y_as_x: bool },
RotateCanvasBegin { snap: bool },
EnableSnapping,
DisableSnapping,
RotateCanvasBegin,
ZoomCanvasBegin,
TransformCanvasEnd,
SetCanvasRotation(f64),
@ -101,16 +100,10 @@ impl MessageHandler<MovementMessage, (&Document, &InputPreprocessor)> for Moveme
self.translating = true;
self.mouse_pos = ipp.mouse.position;
}
RotateCanvasBegin { snap } => {
RotateCanvasBegin => {
self.rotating = true;
self.snap_rotate = snap;
self.mouse_pos = ipp.mouse.position;
}
EnableSnapping => self.snap_rotate = true,
DisableSnapping => {
self.rotation = self.snapped_angle();
self.snap_rotate = false
}
ZoomCanvasBegin => {
self.zooming = true;
self.mouse_pos = ipp.mouse.position;
@ -123,7 +116,7 @@ impl MessageHandler<MovementMessage, (&Document, &InputPreprocessor)> for Moveme
self.rotating = false;
self.zooming = false;
}
MouseMove => {
MouseMove { snap_angle } => {
if self.translating {
let delta = ipp.mouse.position - self.mouse_pos;
let transformed_delta = document.root.transform.inverse().transform_vector2(delta);
@ -132,7 +125,15 @@ impl MessageHandler<MovementMessage, (&Document, &InputPreprocessor)> for Moveme
responses.push_back(ToolMessage::DocumentIsDirty.into());
self.create_document_transform(&ipp.viewport_bounds, responses);
}
if self.rotating {
let new_snap = ipp.keyboard.get(snap_angle as usize);
// When disabling snap, keep the viewed rotation as it was previously.
if !new_snap && self.snap_rotate {
self.rotation = self.snapped_angle();
}
self.snap_rotate = new_snap;
let half_viewport = ipp.viewport_bounds.size() / 2.;
let rotation = {
let start_vec = self.mouse_pos - half_viewport;
@ -151,8 +152,8 @@ impl MessageHandler<MovementMessage, (&Document, &InputPreprocessor)> for Moveme
let new = (self.scale * amount).clamp(VIEWPORT_ZOOM_SCALE_MIN, VIEWPORT_ZOOM_SCALE_MAX);
self.scale = new;
responses.push_back(FrontendMessage::SetCanvasZoom { new_zoom: self.scale }.into());
responses.push_back(ToolMessage::DocumentIsDirty.into());
responses.push_back(FrontendMessage::SetCanvasZoom { new_zoom: self.scale }.into());
self.create_document_transform(&ipp.viewport_bounds, responses);
}
self.mouse_pos = ipp.mouse.position;
@ -213,11 +214,11 @@ impl MessageHandler<MovementMessage, (&Document, &InputPreprocessor)> for Moveme
responses.push_back(ToolMessage::DocumentIsDirty.into());
self.create_document_transform(&ipp.viewport_bounds, responses);
}
SetCanvasRotation(new) => {
self.rotation = new;
SetCanvasRotation(new_radians) => {
self.rotation = new_radians;
self.create_document_transform(&ipp.viewport_bounds, responses);
responses.push_back(ToolMessage::DocumentIsDirty.into());
responses.push_back(FrontendMessage::SetCanvasRotation { new_radians: new }.into());
responses.push_back(FrontendMessage::SetCanvasRotation { new_radians }.into());
}
ZoomCanvasToFitAll => {
if let Some([pos1, pos2]) = document.visible_layers_bounding_box() {
@ -272,13 +273,6 @@ impl MessageHandler<MovementMessage, (&Document, &InputPreprocessor)> for Moveme
TranslateCanvasByViewportFraction,
);
if self.rotating {
let snapping = actions!(MovementMessageDiscriminant;
EnableSnapping,
DisableSnapping,
);
common.extend(snapping);
}
if self.translating || self.rotating || self.zooming {
let transforming = actions!(MovementMessageDiscriminant;
TransformCanvasEnd,

View File

@ -135,8 +135,6 @@ impl Default for Mapping {
let mappings = mapping![
// Higher priority than entries in sections below
entry! {action=DocumentsMessage::Paste(User), key_down=KeyV, modifiers=[KeyControl]},
entry! {action=MovementMessage::EnableSnapping, key_down=KeyShift},
entry! {action=MovementMessage::DisableSnapping, key_up=KeyShift},
// Transform layers
entry! {action=TransformLayerMessage::ApplyOperation, key_down=KeyEnter},
entry! {action=TransformLayerMessage::ApplyOperation, key_down=Lmb},
@ -155,6 +153,12 @@ impl Default for Mapping {
entry! {action=SelectMessage::DragStop, key_up=Lmb},
entry! {action=SelectMessage::Abort, key_down=Rmb},
entry! {action=SelectMessage::Abort, key_down=KeyEscape},
// Navigate
entry! {action=NavigateMessage::MouseMove{snap_angle: KeyControl}, message=InputMapperMessage::PointerMove},
entry! {action=NavigateMessage::RotateCanvasBegin, key_down=Rmb},
entry! {action=NavigateMessage::ZoomCanvasBegin, key_down=Lmb},
entry! {action=NavigateMessage::TransformCanvasEnd, key_up=Rmb},
entry! {action=NavigateMessage::TransformCanvasEnd, key_up=Lmb},
// Eyedropper
entry! {action=EyedropperMessage::LeftMouseDown, key_down=Lmb},
entry! {action=EyedropperMessage::RightMouseDown, key_down=Rmb},
@ -194,6 +198,7 @@ impl Default for Mapping {
entry! {action=FillMessage::RightMouseDown, key_down=Rmb},
// Tool Actions
entry! {action=ToolMessage::ActivateTool(ToolType::Select), key_down=KeyV},
entry! {action=ToolMessage::ActivateTool(ToolType::Navigate), key_down=KeyZ},
entry! {action=ToolMessage::ActivateTool(ToolType::Eyedropper), key_down=KeyI},
entry! {action=ToolMessage::ActivateTool(ToolType::Fill), key_down=KeyF},
entry! {action=ToolMessage::ActivateTool(ToolType::Path), key_down=KeyA},
@ -226,11 +231,9 @@ impl Default for Mapping {
entry! {action=TransformLayerMessage::BeginRotate, key_down=KeyR},
entry! {action=TransformLayerMessage::BeginScale, key_down=KeyS},
// Document movement
entry! {action=MovementMessage::MouseMove, message=InputMapperMessage::PointerMove},
entry! {action=MovementMessage::RotateCanvasBegin{snap:false}, key_down=Mmb, modifiers=[KeyControl]},
entry! {action=MovementMessage::RotateCanvasBegin{snap:true}, key_down=Mmb, modifiers=[KeyControl, KeyShift]},
entry! {action=MovementMessage::MouseMove{snap_angle: KeyShift}, message=InputMapperMessage::PointerMove},
entry! {action=MovementMessage::RotateCanvasBegin, key_down=Mmb, modifiers=[KeyControl]},
entry! {action=MovementMessage::ZoomCanvasBegin, key_down=Mmb, modifiers=[KeyShift]},
entry! {action=MovementMessage::ZoomCanvasToFitAll, key_down=Key0, modifiers=[KeyControl]},
entry! {action=MovementMessage::TranslateCanvasBegin, key_down=Mmb},
entry! {action=MovementMessage::TransformCanvasEnd, key_up=Mmb},
entry! {action=MovementMessage::TranslateCanvasBegin, key_down=Lmb, modifiers=[KeySpace]},
@ -240,6 +243,7 @@ impl Default for Mapping {
entry! {action=MovementMessage::DecreaseCanvasZoom, key_down=KeyMinus, modifiers=[KeyControl]},
entry! {action=MovementMessage::SetCanvasZoom(1.), key_down=Key1, modifiers=[KeyControl]},
entry! {action=MovementMessage::SetCanvasZoom(2.), key_down=Key2, modifiers=[KeyControl]},
entry! {action=MovementMessage::ZoomCanvasToFitAll, key_down=Key0, modifiers=[KeyControl]},
entry! {action=MovementMessage::WheelCanvasZoom, message=InputMapperMessage::MouseScroll, modifiers=[KeyControl]},
entry! {action=MovementMessage::WheelCanvasTranslate{use_y_as_x: true}, message=InputMapperMessage::MouseScroll, modifiers=[KeyShift]},
entry! {action=MovementMessage::WheelCanvasTranslate{use_y_as_x: false}, message=InputMapperMessage::MouseScroll},

View File

@ -166,6 +166,7 @@ fn standard_tool_message(tool: ToolType, message_type: StandardToolMessageType)
StandardToolMessageType::DocumentIsDirty => match tool {
ToolType::Select => Some(SelectMessage::DocumentIsDirty.into()),
ToolType::Path => Some(PathMessage::DocumentIsDirty.into()),
//ToolType::Navigate => Some(NavigateMessage::DocumentIsDirty.into())
// ToolType::Pen => Some(PenMessage::DocumentIsDirty.into()),
// ToolType::Line => Some(LineMessage::DocumentIsDirty.into()),
// ToolType::Rectangle => Some(RectangleMessage::DocumentIsDirty.into()),
@ -178,6 +179,7 @@ fn standard_tool_message(tool: ToolType, message_type: StandardToolMessageType)
StandardToolMessageType::Abort => match tool {
ToolType::Select => Some(SelectMessage::Abort.into()),
ToolType::Path => Some(PathMessage::Abort.into()),
ToolType::Navigate => Some(NavigateMessage::Abort.into()),
ToolType::Pen => Some(PenMessage::Abort.into()),
ToolType::Line => Some(LineMessage::Abort.into()),
ToolType::Rectangle => Some(RectangleMessage::Abort.into()),

View File

@ -1,20 +1,147 @@
use crate::message_prelude::*;
use crate::tool::ToolActionHandlerData;
use crate::input::keyboard::MouseMotion;
use crate::misc::{HintData, HintGroup, HintInfo, KeysGroup};
use crate::tool::{Fsm, ToolActionHandlerData};
use crate::{input::keyboard::Key, message_prelude::*};
use serde::{Deserialize, Serialize};
#[derive(Default)]
pub struct Navigate;
pub struct Navigate {
fsm_state: NavigateToolFsmState,
}
#[impl_message(Message, ToolMessage, Navigate)]
#[derive(PartialEq, Clone, Debug, Hash, Serialize, Deserialize)]
pub enum NavigateMessage {
MouseMove,
MouseMove { snap_angle: Key },
TranslateCanvasBegin,
RotateCanvasBegin,
ZoomCanvasBegin,
TransformCanvasEnd,
Abort,
}
impl<'a> MessageHandler<ToolMessage, ToolActionHandlerData<'a>> for Navigate {
fn process_action(&mut self, action: ToolMessage, data: ToolActionHandlerData<'a>, responses: &mut VecDeque<Message>) {
todo!("{}::handle_input {:?} {:?} {:?} ", module_path!(), action, data, responses);
if action == ToolMessage::UpdateHints {
self.fsm_state.update_hints(responses);
return;
}
let new_state = self.fsm_state.transition(action, data.0, data.1, &mut (), data.2, responses);
if self.fsm_state != new_state {
self.fsm_state = new_state;
self.fsm_state.update_hints(responses);
}
}
advertise_actions!();
fn actions(&self) -> ActionList {
use NavigateToolFsmState::*;
match self.fsm_state {
Ready => actions!(NavigateMessageDiscriminant; TranslateCanvasBegin, RotateCanvasBegin, ZoomCanvasBegin),
_ => actions!(NavigateMessageDiscriminant; MouseMove, TransformCanvasEnd),
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
enum NavigateToolFsmState {
Ready,
Translating,
Rotating,
Zooming,
}
impl Default for NavigateToolFsmState {
fn default() -> Self {
NavigateToolFsmState::Ready
}
}
impl Fsm for NavigateToolFsmState {
type ToolData = ();
fn transition(
self,
message: ToolMessage,
_document: &crate::document::DocumentMessageHandler,
_tool_data: &crate::tool::DocumentToolData,
_data: &mut Self::ToolData,
_input: &crate::input::InputPreprocessor,
messages: &mut VecDeque<Message>,
) -> Self {
if let ToolMessage::Navigate(navigate) = message {
use NavigateMessage::*;
match navigate {
MouseMove { snap_angle } => {
messages.push_front(MovementMessage::MouseMove { snap_angle }.into());
self
}
TranslateCanvasBegin => {
messages.push_front(MovementMessage::TranslateCanvasBegin.into());
NavigateToolFsmState::Translating
}
RotateCanvasBegin => {
messages.push_front(MovementMessage::RotateCanvasBegin.into());
NavigateToolFsmState::Rotating
}
ZoomCanvasBegin => {
messages.push_front(MovementMessage::ZoomCanvasBegin.into());
NavigateToolFsmState::Zooming
}
TransformCanvasEnd => {
messages.push_front(MovementMessage::TransformCanvasEnd.into());
NavigateToolFsmState::Ready
}
Abort => {
messages.push_front(MovementMessage::TransformCanvasEnd.into());
NavigateToolFsmState::Ready
}
}
} else {
self
}
}
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::MmbDrag),
label: String::from("Translate"),
plus: false,
}]),
HintGroup(vec![
HintInfo {
key_groups: vec![],
mouse: Some(MouseMotion::RmbDrag),
label: String::from("Rotate (drag around centre)"),
plus: false,
},
HintInfo {
key_groups: vec![KeysGroup(vec![Key::KeyControl])],
mouse: None,
label: String::from("Snap rotation to 15° increments"),
plus: true,
},
]),
HintGroup(vec![HintInfo {
key_groups: vec![],
mouse: Some(MouseMotion::LmbDrag),
label: String::from("Zoom in and out (drag up and down)"),
plus: false,
}]),
]),
NavigateToolFsmState::Rotating => HintData(vec![HintGroup(vec![HintInfo {
key_groups: vec![KeysGroup(vec![Key::KeyControl])],
mouse: None,
label: String::from("Snap to 15° increments"),
plus: false,
}])]),
_ => HintData(Vec::new()),
};
responses.push_back(FrontendMessage::UpdateInputHints { hint_data }.into());
}
}

View File

@ -71,7 +71,7 @@
<div class="tools scrollable-y">
<ShelfItemInput icon="LayoutSelectTool" title="Select Tool (V)" :active="activeTool === 'Select'" :action="() => selectTool('Select')" />
<ShelfItemInput icon="LayoutCropTool" title="Crop Tool" :active="activeTool === 'Crop'" :action="() => dialog.comingSoon(289) && selectTool('Crop')" />
<ShelfItemInput icon="LayoutNavigateTool" title="Navigate Tool (Z)" :active="activeTool === 'Navigate'" :action="() => dialog.comingSoon(155) && selectTool('Navigate')" />
<ShelfItemInput icon="LayoutNavigateTool" title="Navigate Tool (Z)" :active="activeTool === 'Navigate'" :action="() => selectTool('Navigate')" />
<ShelfItemInput icon="LayoutEyedropperTool" title="Eyedropper Tool (I)" :active="activeTool === 'Eyedropper'" :action="() => selectTool('Eyedropper')" />
<Separator :type="SeparatorType.Section" :direction="SeparatorDirection.Vertical" />