diff --git a/editor/src/frontend/frontend_message.rs b/editor/src/frontend/frontend_message.rs index b54d3f4548..6bcaff992c 100644 --- a/editor/src/frontend/frontend_message.rs +++ b/editor/src/frontend/frontend_message.rs @@ -1,4 +1,4 @@ -use super::utility_types::FrontendDocumentDetails; +use super::utility_types::{FrontendDocumentDetails, MouseCursorIcon}; use crate::document::layer_panel::{LayerPanelEntry, RawBuffer}; use crate::message_prelude::*; use crate::misc::HintData; @@ -37,6 +37,7 @@ pub enum FrontendMessage { UpdateDocumentRulers { origin: (f64, f64), spacing: f64, interval: f64 }, UpdateDocumentScrollbars { position: (f64, f64), size: (f64, f64), multiplier: (f64, f64) }, UpdateInputHints { hint_data: HintData }, + UpdateMouseCursor { cursor: MouseCursorIcon }, UpdateOpenDocumentsList { open_documents: Vec }, UpdateWorkingColors { primary: Color, secondary: Color }, } diff --git a/editor/src/frontend/utility_types.rs b/editor/src/frontend/utility_types.rs index d52888a3d3..6bc51118f0 100644 --- a/editor/src/frontend/utility_types.rs +++ b/editor/src/frontend/utility_types.rs @@ -6,3 +6,12 @@ pub struct FrontendDocumentDetails { pub name: String, pub id: u64, } + +#[derive(Clone, Copy, Debug, Eq, Deserialize, PartialEq, Serialize)] +pub enum MouseCursorIcon { + Default, + ZoomIn, + ZoomOut, + Grabbing, + Crosshair, +} diff --git a/editor/src/viewport_tools/tool.rs b/editor/src/viewport_tools/tool.rs index c96e756046..f575287271 100644 --- a/editor/src/viewport_tools/tool.rs +++ b/editor/src/viewport_tools/tool.rs @@ -27,6 +27,7 @@ pub trait Fsm { ) -> Self; fn update_hints(&self, responses: &mut VecDeque); + fn update_cursor(&self, responses: &mut VecDeque); } #[derive(Debug, Clone)] diff --git a/editor/src/viewport_tools/tool_message.rs b/editor/src/viewport_tools/tool_message.rs index 2b7a8986d7..34ed73d080 100644 --- a/editor/src/viewport_tools/tool_message.rs +++ b/editor/src/viewport_tools/tool_message.rs @@ -49,5 +49,6 @@ pub enum ToolMessage { #[child] Shape(ShapeMessage), SwapColors, + UpdateCursor, UpdateHints, } diff --git a/editor/src/viewport_tools/tool_message_handler.rs b/editor/src/viewport_tools/tool_message_handler.rs index 896318c773..7936c239b6 100644 --- a/editor/src/viewport_tools/tool_message_handler.rs +++ b/editor/src/viewport_tools/tool_message_handler.rs @@ -31,12 +31,13 @@ impl MessageHandler MessageHandler> for Ellipse { return; } + if action == ToolMessage::UpdateCursor { + self.fsm_state.update_cursor(responses); + return; + } + 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; self.fsm_state.update_hints(responses); + self.fsm_state.update_cursor(responses); } } @@ -177,4 +184,8 @@ impl Fsm for EllipseToolFsmState { responses.push_back(FrontendMessage::UpdateInputHints { hint_data }.into()); } + + fn update_cursor(&self, responses: &mut VecDeque) { + responses.push_back(FrontendMessage::UpdateMouseCursor { cursor: MouseCursorIcon::Crosshair }.into()); + } } diff --git a/editor/src/viewport_tools/tools/eyedropper.rs b/editor/src/viewport_tools/tools/eyedropper.rs index 48561b76b8..803b67e9d0 100644 --- a/editor/src/viewport_tools/tools/eyedropper.rs +++ b/editor/src/viewport_tools/tools/eyedropper.rs @@ -1,5 +1,6 @@ use crate::consts::SELECTION_TOLERANCE; use crate::document::DocumentMessageHandler; +use crate::frontend::utility_types::MouseCursorIcon; use crate::input::keyboard::MouseMotion; use crate::input::InputPreprocessorMessageHandler; use crate::message_prelude::*; @@ -34,11 +35,17 @@ impl<'a> MessageHandler> for Eyedropper { return; } + if action == ToolMessage::UpdateCursor { + self.fsm_state.update_cursor(responses); + return; + } + 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; self.fsm_state.update_hints(responses); + self.fsm_state.update_cursor(responses); } } @@ -127,4 +134,8 @@ impl Fsm for EyedropperToolFsmState { responses.push_back(FrontendMessage::UpdateInputHints { hint_data }.into()); } + + fn update_cursor(&self, responses: &mut VecDeque) { + responses.push_back(FrontendMessage::UpdateMouseCursor { cursor: MouseCursorIcon::Default }.into()); + } } diff --git a/editor/src/viewport_tools/tools/fill.rs b/editor/src/viewport_tools/tools/fill.rs index 2727d06af2..79872a197a 100644 --- a/editor/src/viewport_tools/tools/fill.rs +++ b/editor/src/viewport_tools/tools/fill.rs @@ -1,5 +1,6 @@ use crate::consts::SELECTION_TOLERANCE; use crate::document::DocumentMessageHandler; +use crate::frontend::utility_types::MouseCursorIcon; use crate::input::keyboard::MouseMotion; use crate::input::InputPreprocessorMessageHandler; use crate::message_prelude::*; @@ -34,11 +35,17 @@ impl<'a> MessageHandler> for Fill { return; } + if action == ToolMessage::UpdateCursor { + self.fsm_state.update_cursor(responses); + return; + } + 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; self.fsm_state.update_hints(responses); + self.fsm_state.update_cursor(responses); } } @@ -121,4 +128,8 @@ impl Fsm for FillToolFsmState { responses.push_back(FrontendMessage::UpdateInputHints { hint_data }.into()); } + + fn update_cursor(&self, responses: &mut VecDeque) { + responses.push_back(FrontendMessage::UpdateMouseCursor { cursor: MouseCursorIcon::Default }.into()); + } } diff --git a/editor/src/viewport_tools/tools/line.rs b/editor/src/viewport_tools/tools/line.rs index 11627baa82..119208383f 100644 --- a/editor/src/viewport_tools/tools/line.rs +++ b/editor/src/viewport_tools/tools/line.rs @@ -1,5 +1,6 @@ use crate::consts::LINE_ROTATE_SNAP_ANGLE; use crate::document::DocumentMessageHandler; +use crate::frontend::utility_types::MouseCursorIcon; use crate::input::keyboard::{Key, MouseMotion}; use crate::input::mouse::ViewportPosition; use crate::input::InputPreprocessorMessageHandler; @@ -38,11 +39,17 @@ impl<'a> MessageHandler> for Line { return; } + if action == ToolMessage::UpdateCursor { + self.fsm_state.update_cursor(responses); + return; + } + 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; self.fsm_state.update_hints(responses); + self.fsm_state.update_cursor(responses); } } @@ -207,6 +214,10 @@ impl Fsm for LineToolFsmState { responses.push_back(FrontendMessage::UpdateInputHints { hint_data }.into()); } + + fn update_cursor(&self, responses: &mut VecDeque) { + responses.push_back(FrontendMessage::UpdateMouseCursor { cursor: MouseCursorIcon::Crosshair }.into()); + } } fn generate_transform(data: &mut LineToolData, lock: bool, snap: bool, center: bool) -> Message { diff --git a/editor/src/viewport_tools/tools/navigate.rs b/editor/src/viewport_tools/tools/navigate.rs index 527d3e9299..6fb5ef7bf8 100644 --- a/editor/src/viewport_tools/tools/navigate.rs +++ b/editor/src/viewport_tools/tools/navigate.rs @@ -1,4 +1,5 @@ use crate::document::DocumentMessageHandler; +use crate::frontend::utility_types::MouseCursorIcon; use crate::input::keyboard::{Key, MouseMotion}; use crate::input::InputPreprocessorMessageHandler; use crate::message_prelude::*; @@ -34,11 +35,17 @@ impl<'a> MessageHandler> for Navigate { return; } + if action == ToolMessage::UpdateCursor { + self.fsm_state.update_cursor(responses); + return; + } + 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; self.fsm_state.update_hints(responses); + self.fsm_state.update_cursor(responses); } } @@ -211,4 +218,15 @@ impl Fsm for NavigateToolFsmState { responses.push_back(FrontendMessage::UpdateInputHints { hint_data }.into()); } + + fn update_cursor(&self, responses: &mut VecDeque) { + let cursor = match *self { + NavigateToolFsmState::Ready => MouseCursorIcon::ZoomIn, + NavigateToolFsmState::Panning => MouseCursorIcon::Grabbing, + NavigateToolFsmState::Tilting => MouseCursorIcon::Default, + NavigateToolFsmState::Zooming => MouseCursorIcon::ZoomIn, + }; + + responses.push_back(FrontendMessage::UpdateMouseCursor { cursor }.into()); + } } diff --git a/editor/src/viewport_tools/tools/path.rs b/editor/src/viewport_tools/tools/path.rs index b0c6f5fa0d..1132cdcd93 100644 --- a/editor/src/viewport_tools/tools/path.rs +++ b/editor/src/viewport_tools/tools/path.rs @@ -1,6 +1,7 @@ use crate::consts::{COLOR_ACCENT, VECTOR_MANIPULATOR_ANCHOR_MARKER_SIZE}; use crate::document::utility_types::{VectorManipulatorSegment, VectorManipulatorShape}; use crate::document::DocumentMessageHandler; +use crate::frontend::utility_types::MouseCursorIcon; use crate::input::keyboard::{Key, MouseMotion}; use crate::input::InputPreprocessorMessageHandler; use crate::message_prelude::*; @@ -41,11 +42,17 @@ impl<'a> MessageHandler> for Path { return; } + if action == ToolMessage::UpdateCursor { + self.fsm_state.update_cursor(responses); + return; + } + 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; self.fsm_state.update_hints(responses); + self.fsm_state.update_cursor(responses); } } @@ -476,6 +483,10 @@ impl Fsm for PathToolFsmState { responses.push_back(FrontendMessage::UpdateInputHints { hint_data }.into()); } + + fn update_cursor(&self, responses: &mut VecDeque) { + responses.push_back(FrontendMessage::UpdateMouseCursor { cursor: MouseCursorIcon::Default }.into()); + } } struct VectorManipulatorTypes { diff --git a/editor/src/viewport_tools/tools/pen.rs b/editor/src/viewport_tools/tools/pen.rs index 22bd6cc614..cfbe80522f 100644 --- a/editor/src/viewport_tools/tools/pen.rs +++ b/editor/src/viewport_tools/tools/pen.rs @@ -1,4 +1,5 @@ use crate::document::DocumentMessageHandler; +use crate::frontend::utility_types::MouseCursorIcon; use crate::input::keyboard::{Key, MouseMotion}; use crate::input::InputPreprocessorMessageHandler; use crate::message_prelude::*; @@ -44,11 +45,17 @@ impl<'a> MessageHandler> for Pen { return; } + if action == ToolMessage::UpdateCursor { + self.fsm_state.update_cursor(responses); + return; + } + 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; self.fsm_state.update_hints(responses); + self.fsm_state.update_cursor(responses); } } @@ -193,6 +200,10 @@ impl Fsm for PenToolFsmState { responses.push_back(FrontendMessage::UpdateInputHints { hint_data }.into()); } + + fn update_cursor(&self, responses: &mut VecDeque) { + responses.push_back(FrontendMessage::UpdateMouseCursor { cursor: MouseCursorIcon::Default }.into()); + } } fn remove_preview(data: &PenToolData) -> Message { diff --git a/editor/src/viewport_tools/tools/rectangle.rs b/editor/src/viewport_tools/tools/rectangle.rs index 3687995d4a..bff9592404 100644 --- a/editor/src/viewport_tools/tools/rectangle.rs +++ b/editor/src/viewport_tools/tools/rectangle.rs @@ -1,5 +1,6 @@ use super::shared::resize::Resize; use crate::document::DocumentMessageHandler; +use crate::frontend::utility_types::MouseCursorIcon; use crate::input::keyboard::{Key, MouseMotion}; use crate::input::InputPreprocessorMessageHandler; use crate::message_prelude::*; @@ -35,11 +36,17 @@ impl<'a> MessageHandler> for Rectangle { return; } + if action == ToolMessage::UpdateCursor { + self.fsm_state.update_cursor(responses); + return; + } + 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; self.fsm_state.update_hints(responses); + self.fsm_state.update_cursor(responses); } } @@ -177,4 +184,8 @@ impl Fsm for RectangleToolFsmState { responses.push_back(FrontendMessage::UpdateInputHints { hint_data }.into()); } + + fn update_cursor(&self, responses: &mut VecDeque) { + responses.push_back(FrontendMessage::UpdateMouseCursor { cursor: MouseCursorIcon::Crosshair }.into()); + } } diff --git a/editor/src/viewport_tools/tools/select.rs b/editor/src/viewport_tools/tools/select.rs index 4823332516..7f0d40e27d 100644 --- a/editor/src/viewport_tools/tools/select.rs +++ b/editor/src/viewport_tools/tools/select.rs @@ -1,6 +1,7 @@ use crate::consts::{COLOR_ACCENT, SELECTION_DRAG_ANGLE, SELECTION_TOLERANCE}; use crate::document::utility_types::{AlignAggregate, AlignAxis, FlipAxis}; use crate::document::DocumentMessageHandler; +use crate::frontend::utility_types::MouseCursorIcon; use crate::input::keyboard::{Key, MouseMotion}; use crate::input::mouse::ViewportPosition; use crate::input::InputPreprocessorMessageHandler; @@ -46,11 +47,17 @@ impl<'a> MessageHandler> for Select { return; } + if action == ToolMessage::UpdateCursor { + self.fsm_state.update_cursor(responses); + return; + } + 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; self.fsm_state.update_hints(responses); + self.fsm_state.update_cursor(responses); } } @@ -420,4 +427,8 @@ impl Fsm for SelectToolFsmState { responses.push_back(FrontendMessage::UpdateInputHints { hint_data }.into()); } + + fn update_cursor(&self, responses: &mut VecDeque) { + responses.push_back(FrontendMessage::UpdateMouseCursor { cursor: MouseCursorIcon::Default }.into()); + } } diff --git a/editor/src/viewport_tools/tools/shape.rs b/editor/src/viewport_tools/tools/shape.rs index 49178bf75c..75d842268c 100644 --- a/editor/src/viewport_tools/tools/shape.rs +++ b/editor/src/viewport_tools/tools/shape.rs @@ -1,5 +1,6 @@ use super::shared::resize::Resize; use crate::document::DocumentMessageHandler; +use crate::frontend::utility_types::MouseCursorIcon; use crate::input::keyboard::{Key, MouseMotion}; use crate::input::InputPreprocessorMessageHandler; use crate::message_prelude::*; @@ -36,11 +37,17 @@ impl<'a> MessageHandler> for Shape { return; } + if action == ToolMessage::UpdateCursor { + self.fsm_state.update_cursor(responses); + return; + } + 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; self.fsm_state.update_hints(responses); + self.fsm_state.update_cursor(responses); } } @@ -185,4 +192,8 @@ impl Fsm for ShapeToolFsmState { responses.push_back(FrontendMessage::UpdateInputHints { hint_data }.into()); } + + fn update_cursor(&self, responses: &mut VecDeque) { + responses.push_back(FrontendMessage::UpdateMouseCursor { cursor: MouseCursorIcon::Crosshair }.into()); + } } diff --git a/frontend/src/components/panels/Document.vue b/frontend/src/components/panels/Document.vue index e9a2bb986c..adf022d757 100644 --- a/frontend/src/components/panels/Document.vue +++ b/frontend/src/components/panels/Document.vue @@ -123,7 +123,7 @@ -
+
@@ -261,6 +261,7 @@ import { UpdateCanvasRotation, ToolName, UpdateDocumentArtboards, + UpdateMouseCursor, } from "@/dispatcher/js-messages"; import LayoutCol from "@/components/layout/LayoutCol.vue"; @@ -338,6 +339,10 @@ export default defineComponent({ resetWorkingColors() { this.editor.instance.reset_colors(); }, + canvasPointerDown(e: PointerEvent) { + const canvas = this.$refs.canvas as HTMLElement; + canvas.setPointerCapture(e.pointerId); + }, }, mounted() { this.editor.dispatcher.subscribeJsMessage(UpdateDocumentArtwork, (UpdateDocumentArtwork) => { @@ -378,6 +383,10 @@ export default defineComponent({ this.documentRotation = (360 + (newRotation % 360)) % 360; }); + this.editor.dispatcher.subscribeJsMessage(UpdateMouseCursor, (updateMouseCursor) => { + this.canvasCursor = updateMouseCursor.cursor; + }); + window.addEventListener("resize", this.viewportResize); window.addEventListener("DOMContentLoaded", this.viewportResize); }, @@ -401,6 +410,7 @@ export default defineComponent({ overlaysSvg: "", canvasSvgWidth: "100%", canvasSvgHeight: "100%", + canvasCursor: "default", activeTool: "Select" as ToolName, activeToolOptions: {}, documentModeEntries, diff --git a/frontend/src/dispatcher/js-messages.ts b/frontend/src/dispatcher/js-messages.ts index 671ea7017f..88826ee790 100644 --- a/frontend/src/dispatcher/js-messages.ts +++ b/frontend/src/dispatcher/js-messages.ts @@ -201,6 +201,24 @@ export class UpdateDocumentRulers extends JsMessage { readonly interval!: number; } +export type MouseCursorIcon = "default" | "zoom-in" | "zoom-out" | "grabbing" | "crosshair"; + +const ToCssCursorProperty = Transform(({ value }) => { + const cssNames: Record = { + ZoomIn: "zoom-in", + ZoomOut: "zoom-out", + Grabbing: "grabbing", + Crosshair: "crosshair", + }; + + return cssNames[value] || "default"; +}); + +export class UpdateMouseCursor extends JsMessage { + @ToCssCursorProperty + readonly cursor!: MouseCursorIcon; +} + export class TriggerFileDownload extends JsMessage { readonly document!: string; @@ -378,6 +396,7 @@ export const messageConstructors: Record = { UpdateWorkingColors, UpdateCanvasZoom, UpdateCanvasRotation, + UpdateMouseCursor, DisplayDialogError, DisplayDialogPanic, DisplayConfirmationToCloseDocument,