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:
parent
7ab127c3ae
commit
d0dcc0e42f
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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},
|
||||
|
|
|
|||
|
|
@ -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()),
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
advertise_actions!();
|
||||
let new_state = self.fsm_state.transition(action, data.0, data.1, &mut (), data.2, responses);
|
||||
|
||||
if self.fsm_state != new_state {
|
||||
self.fsm_state = new_state;
|
||||
self.fsm_state.update_hints(responses);
|
||||
}
|
||||
}
|
||||
|
||||
fn actions(&self) -> ActionList {
|
||||
use NavigateToolFsmState::*;
|
||||
match self.fsm_state {
|
||||
Ready => actions!(NavigateMessageDiscriminant; TranslateCanvasBegin, RotateCanvasBegin, ZoomCanvasBegin),
|
||||
_ => actions!(NavigateMessageDiscriminant; MouseMove, TransformCanvasEnd),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
enum NavigateToolFsmState {
|
||||
Ready,
|
||||
Translating,
|
||||
Rotating,
|
||||
Zooming,
|
||||
}
|
||||
|
||||
impl Default for NavigateToolFsmState {
|
||||
fn default() -> Self {
|
||||
NavigateToolFsmState::Ready
|
||||
}
|
||||
}
|
||||
|
||||
impl Fsm for NavigateToolFsmState {
|
||||
type ToolData = ();
|
||||
|
||||
fn transition(
|
||||
self,
|
||||
message: ToolMessage,
|
||||
_document: &crate::document::DocumentMessageHandler,
|
||||
_tool_data: &crate::tool::DocumentToolData,
|
||||
_data: &mut Self::ToolData,
|
||||
_input: &crate::input::InputPreprocessor,
|
||||
messages: &mut VecDeque<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());
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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" />
|
||||
|
|
|
|||
Loading…
Reference in New Issue