From d780602ecda33bf34069683c52904c2e8caec818 Mon Sep 17 00:00:00 2001 From: Elbert Ronnie <103196773+elbertronnie@users.noreply.github.com> Date: Sat, 9 Mar 2024 10:02:46 +0530 Subject: [PATCH] Add auto-panning to the Artboard tool (#1652) * Move panning functionality to auto_panning.rs * Add auto-panning to Artboard tool * Hide debug messages containing AnimationFrame --- editor/src/dispatcher.rs | 4 +- .../tool/common_functionality/auto_panning.rs | 79 ++++++++++ .../messages/tool/common_functionality/mod.rs | 1 + .../tool/tool_messages/artboard_tool.rs | 75 ++++++++- .../tool/tool_messages/select_tool.rs | 144 +++++++----------- 5 files changed, 209 insertions(+), 94 deletions(-) create mode 100644 editor/src/messages/tool/common_functionality/auto_panning.rs diff --git a/editor/src/dispatcher.rs b/editor/src/dispatcher.rs index 6ff20238..596b43af 100644 --- a/editor/src/dispatcher.rs +++ b/editor/src/dispatcher.rs @@ -222,7 +222,9 @@ impl Dispatcher { /// Logs a message that is about to be executed, /// either as a tree with a discriminant or the entire payload (depending on settings) fn log_message(&self, message: &Message, queues: &[VecDeque], message_logging_verbosity: MessageLoggingVerbosity) { - if !MessageDiscriminant::from(message).local_name().ends_with("PointerMove") { + let message_name = MessageDiscriminant::from(message).local_name(); + + if !(message_name.ends_with("PointerMove") || message_name.ends_with("AnimationFrame")) { match message_logging_verbosity { MessageLoggingVerbosity::Off => {} MessageLoggingVerbosity::Names => { diff --git a/editor/src/messages/tool/common_functionality/auto_panning.rs b/editor/src/messages/tool/common_functionality/auto_panning.rs new file mode 100644 index 00000000..b0747cfe --- /dev/null +++ b/editor/src/messages/tool/common_functionality/auto_panning.rs @@ -0,0 +1,79 @@ +use crate::consts::{DRAG_BEYOND_VIEWPORT_MAX_OVEREXTENSION_PIXELS, DRAG_BEYOND_VIEWPORT_SPEED_FACTOR}; +use crate::messages::prelude::*; +use crate::messages::tool::tool_messages::tool_prelude::*; + +#[derive(Clone, Debug, Default)] +pub struct AutoPanning { + subscribed_to_animation_frame: bool, +} + +impl AutoPanning { + pub fn start(&mut self, messages: &[Message], responses: &mut VecDeque) { + if !self.subscribed_to_animation_frame { + self.subscribed_to_animation_frame = true; + + for message in messages { + responses.add(BroadcastMessage::SubscribeEvent { + on: BroadcastEvent::AnimationFrame, + send: Box::new(message.clone()), + }); + } + } + } + + pub fn stop(&mut self, messages: &[Message], responses: &mut VecDeque) { + if self.subscribed_to_animation_frame { + self.subscribed_to_animation_frame = false; + + for message in messages { + responses.add(BroadcastMessage::UnsubscribeEvent { + on: BroadcastEvent::AnimationFrame, + message: Box::new(message.clone()), + }); + } + } + } + + pub fn setup_by_mouse_position(&mut self, mouse_position: DVec2, viewport_size: DVec2, messages: &[Message], responses: &mut VecDeque) { + let is_pointer_outside_edge = mouse_position.x < 0. || mouse_position.x > viewport_size.x || mouse_position.y < 0. || mouse_position.y > viewport_size.y; + + match is_pointer_outside_edge { + true => self.start(messages, responses), + false => self.stop(messages, responses), + } + } + + /// Shifts the viewport when the mouse reaches the edge of the viewport. + /// + /// If the mouse was beyond any edge, it returns the amount shifted. Otherwise it returns None. + /// The shift is proportional to the distance between edge and mouse. It is also guaranteed to be integral. + pub fn shift_viewport(mouse_position: DVec2, viewport_size: DVec2, responses: &mut VecDeque) -> Option { + let mouse_position = mouse_position.clamp( + DVec2::ZERO - DVec2::splat(DRAG_BEYOND_VIEWPORT_MAX_OVEREXTENSION_PIXELS), + viewport_size + DVec2::splat(DRAG_BEYOND_VIEWPORT_MAX_OVEREXTENSION_PIXELS), + ); + let mouse_position_percent = mouse_position / viewport_size; + + let mut shift_percent = DVec2::ZERO; + + if mouse_position_percent.x < 0. { + shift_percent.x = -mouse_position_percent.x; + } else if mouse_position_percent.x > 1. { + shift_percent.x = 1. - mouse_position_percent.x; + } + + if mouse_position_percent.y < 0. { + shift_percent.y = -mouse_position_percent.y; + } else if mouse_position_percent.y > 1. { + shift_percent.y = 1. - mouse_position_percent.y; + } + + if shift_percent.x == 0. && shift_percent.y == 0. { + return None; + } + + let delta = (shift_percent * DRAG_BEYOND_VIEWPORT_SPEED_FACTOR * viewport_size).round(); + responses.add(NavigationMessage::TranslateCanvas { delta }); + Some(delta) + } +} diff --git a/editor/src/messages/tool/common_functionality/mod.rs b/editor/src/messages/tool/common_functionality/mod.rs index 02c20780..c7a29d58 100644 --- a/editor/src/messages/tool/common_functionality/mod.rs +++ b/editor/src/messages/tool/common_functionality/mod.rs @@ -1,3 +1,4 @@ +pub mod auto_panning; pub mod color_selector; pub mod graph_modification_utils; pub mod pivot; diff --git a/editor/src/messages/tool/tool_messages/artboard_tool.rs b/editor/src/messages/tool/tool_messages/artboard_tool.rs index b4ac74e5..fe9c9822 100644 --- a/editor/src/messages/tool/tool_messages/artboard_tool.rs +++ b/editor/src/messages/tool/tool_messages/artboard_tool.rs @@ -2,6 +2,7 @@ use super::tool_prelude::*; use crate::application::generate_uuid; use crate::messages::portfolio::document::overlays::utility_types::OverlayContext; use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier; +use crate::messages::tool::common_functionality::auto_panning::AutoPanning; use crate::messages::tool::common_functionality::graph_modification_utils::is_layer_fed_by_node_of_name; use crate::messages::tool::common_functionality::snapping::SnapManager; use crate::messages::tool::common_functionality::transformation_cage::*; @@ -27,6 +28,7 @@ pub enum ArtboardToolMessage { NudgeSelected { delta_x: f64, delta_y: f64 }, PointerDown, PointerMove { constrain_axis_or_aspect: Key, center: Key }, + PointerOutsideViewport { constrain_axis_or_aspect: Key, center: Key }, PointerUp, } @@ -90,6 +92,7 @@ struct ArtboardToolData { cursor: MouseCursorIcon, drag_start: DVec2, drag_current: DVec2, + auto_panning: AutoPanning, } impl ArtboardToolData { @@ -201,10 +204,20 @@ impl Fsm for ArtboardToolFsmState { let mouse_position = input.mouse.position; tool_data.resize_artboard(responses, document, mouse_position, from_center, constrain_square); + tool_data.auto_panning.setup_by_mouse_position( + mouse_position, + input.viewport_bounds.size(), + &[ + ArtboardToolMessage::PointerOutsideViewport { constrain_axis_or_aspect, center }.into(), + ArtboardToolMessage::PointerMove { constrain_axis_or_aspect, center }.into(), + ], + responses, + ); + ArtboardToolFsmState::ResizingBounds } - (ArtboardToolFsmState::Dragging, ArtboardToolMessage::PointerMove { constrain_axis_or_aspect, .. }) => { - if let Some(bounds) = &tool_data.bounding_box_manager { + (ArtboardToolFsmState::Dragging, ArtboardToolMessage::PointerMove { constrain_axis_or_aspect, center }) => { + if let Some(ref mut bounds) = &mut tool_data.bounding_box_manager { let axis_align = input.keyboard.get(constrain_axis_or_aspect as usize); let mouse_position = axis_align_drag(axis_align, input.mouse.position, tool_data.drag_start); let size = bounds.bounds[1] - bounds.bounds[0]; @@ -216,7 +229,22 @@ impl Fsm for ArtboardToolFsmState { dimensions: size.round().as_ivec2(), }); - tool_data.drag_current = mouse_position; + // The second term is added to prevent the slow change in position due to rounding errors. + tool_data.drag_current = mouse_position + bounds.transform.transform_vector2(position.round() - position); + + // Update bounds if another `PointerMove` message comes before `ResizeArtboard` is finished. + bounds.bounds[0] = position.round(); + bounds.bounds[1] = position.round() + size.round(); + + tool_data.auto_panning.setup_by_mouse_position( + mouse_position, + input.viewport_bounds.size(), + &[ + ArtboardToolMessage::PointerOutsideViewport { constrain_axis_or_aspect, center }.into(), + ArtboardToolMessage::PointerMove { constrain_axis_or_aspect, center }.into(), + ], + responses, + ); } ArtboardToolFsmState::Dragging } @@ -266,6 +294,16 @@ impl Fsm for ArtboardToolFsmState { }) } + tool_data.auto_panning.setup_by_mouse_position( + mouse_position, + input.viewport_bounds.size(), + &[ + ArtboardToolMessage::PointerOutsideViewport { constrain_axis_or_aspect, center }.into(), + ArtboardToolMessage::PointerMove { constrain_axis_or_aspect, center }.into(), + ], + responses, + ); + ArtboardToolFsmState::Drawing } (ArtboardToolFsmState::Ready, ArtboardToolMessage::PointerMove { .. }) => { @@ -278,6 +316,37 @@ impl Fsm for ArtboardToolFsmState { ArtboardToolFsmState::Ready } + (ArtboardToolFsmState::ResizingBounds, ArtboardToolMessage::PointerOutsideViewport { .. }) => { + let _ = AutoPanning::shift_viewport(input.mouse.position, input.viewport_bounds.size(), responses); + + ArtboardToolFsmState::ResizingBounds + } + (ArtboardToolFsmState::Dragging, ArtboardToolMessage::PointerOutsideViewport { .. }) => { + if let Some(shift) = AutoPanning::shift_viewport(input.mouse.position, input.viewport_bounds.size(), responses) { + tool_data.drag_current += shift; + tool_data.drag_start += shift; + } + + ArtboardToolFsmState::Dragging + } + (ArtboardToolFsmState::Drawing, ArtboardToolMessage::PointerOutsideViewport { .. }) => { + if let Some(shift) = AutoPanning::shift_viewport(input.mouse.position, input.viewport_bounds.size(), responses) { + tool_data.drag_start += shift; + } + + ArtboardToolFsmState::Drawing + } + (state, ArtboardToolMessage::PointerOutsideViewport { constrain_axis_or_aspect, center }) => { + tool_data.auto_panning.stop( + &[ + ArtboardToolMessage::PointerOutsideViewport { constrain_axis_or_aspect, center }.into(), + ArtboardToolMessage::PointerMove { constrain_axis_or_aspect, center }.into(), + ], + responses, + ); + + state + } (ArtboardToolFsmState::ResizingBounds, ArtboardToolMessage::PointerUp) => { tool_data.snap_manager.cleanup(responses); diff --git a/editor/src/messages/tool/tool_messages/select_tool.rs b/editor/src/messages/tool/tool_messages/select_tool.rs index bd4806cc..cbe3f316 100644 --- a/editor/src/messages/tool/tool_messages/select_tool.rs +++ b/editor/src/messages/tool/tool_messages/select_tool.rs @@ -2,12 +2,13 @@ use super::tool_prelude::*; use crate::application::generate_uuid; -use crate::consts::{DRAG_BEYOND_VIEWPORT_MAX_OVEREXTENSION_PIXELS, DRAG_BEYOND_VIEWPORT_SPEED_FACTOR, ROTATE_SNAP_ANGLE, SELECTION_TOLERANCE}; +use crate::consts::{ROTATE_SNAP_ANGLE, SELECTION_TOLERANCE}; use crate::messages::input_mapper::utility_types::input_mouse::ViewportPosition; use crate::messages::portfolio::document::overlays::utility_types::OverlayContext; use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier; use crate::messages::portfolio::document::utility_types::misc::{AlignAggregate, AlignAxis, FlipAxis}; use crate::messages::portfolio::document::utility_types::transformation::Selected; +use crate::messages::tool::common_functionality::auto_panning::AutoPanning; use crate::messages::tool::common_functionality::graph_modification_utils::is_layer_fed_by_node_of_name; use crate::messages::tool::common_functionality::pivot::Pivot; use crate::messages::tool::common_functionality::snapping::{self, SnapCandidatePoint, SnapConstraint, SnapData, SnapManager, SnappedPoint}; @@ -75,7 +76,6 @@ pub enum SelectToolMessage { PointerOutsideViewport(SelectToolPointerKeys), SelectOptions(SelectOptionsUpdate), SetPivot { position: PivotPosition }, - ShiftViewport, } impl ToolMetadata for SelectTool { @@ -263,7 +263,7 @@ struct SelectToolData { selected_layers_count: usize, selected_layers_changed: bool, snap_candidates: Vec, - subscribed_to_animation_frame: bool, + auto_panning: AutoPanning, } impl SelectToolData { @@ -645,7 +645,15 @@ impl Fsm for SelectToolFsmState { } tool_data.drag_current += mouse_delta; - setup_pointer_outside_edge_event(input.mouse.position, input.viewport_bounds.size(), tool_data, modifier_keys, responses); + tool_data.auto_panning.setup_by_mouse_position( + input.mouse.position, + input.viewport_bounds.size(), + &[ + SelectToolMessage::PointerOutsideViewport(modifier_keys.clone()).into(), + SelectToolMessage::PointerMove(modifier_keys).into(), + ], + responses, + ); SelectToolFsmState::Dragging } @@ -681,7 +689,15 @@ impl Fsm for SelectToolFsmState { selected.apply_transformation(bounds.original_bound_transform * transformation * bounds.original_bound_transform.inverse()); - setup_pointer_outside_edge_event(input.mouse.position, input.viewport_bounds.size(), tool_data, modifier_keys, responses); + tool_data.auto_panning.setup_by_mouse_position( + input.mouse.position, + input.viewport_bounds.size(), + &[ + SelectToolMessage::PointerOutsideViewport(modifier_keys.clone()).into(), + SelectToolMessage::PointerMove(modifier_keys).into(), + ], + responses, + ); } } SelectToolFsmState::ResizingBounds @@ -726,7 +742,15 @@ impl Fsm for SelectToolFsmState { let snapped_mouse_position = mouse_position; //tool_data.snap_manager.snap_position(responses, document, mouse_position); tool_data.pivot.set_viewport_position(snapped_mouse_position, document, responses); - setup_pointer_outside_edge_event(mouse_position, input.viewport_bounds.size(), tool_data, modifier_keys, responses); + tool_data.auto_panning.setup_by_mouse_position( + input.mouse.position, + input.viewport_bounds.size(), + &[ + SelectToolMessage::PointerOutsideViewport(modifier_keys.clone()).into(), + SelectToolMessage::PointerMove(modifier_keys).into(), + ], + responses, + ); SelectToolFsmState::DraggingPivot } @@ -734,7 +758,15 @@ impl Fsm for SelectToolFsmState { tool_data.drag_current = input.mouse.position; responses.add(OverlaysMessage::Draw); - setup_pointer_outside_edge_event(input.mouse.position, input.viewport_bounds.size(), tool_data, modifier_keys, responses); + tool_data.auto_panning.setup_by_mouse_position( + input.mouse.position, + input.viewport_bounds.size(), + &[ + SelectToolMessage::PointerOutsideViewport(modifier_keys.clone()).into(), + SelectToolMessage::PointerMove(modifier_keys).into(), + ], + responses, + ); SelectToolFsmState::DrawingBox } @@ -756,25 +788,16 @@ impl Fsm for SelectToolFsmState { SelectToolFsmState::Ready } - (SelectToolFsmState::Dragging, SelectToolMessage::PointerOutsideViewport(modifier_keys)) => { - responses.add(SelectToolMessage::PointerMove(modifier_keys)); - - if let Some(shift) = shift_viewport_if_mouse_beyond_edge(input.mouse.position, input.viewport_bounds.size(), responses) { + (SelectToolFsmState::Dragging, SelectToolMessage::PointerOutsideViewport(_)) => { + if let Some(shift) = AutoPanning::shift_viewport(input.mouse.position, input.viewport_bounds.size(), responses) { tool_data.drag_current += shift; tool_data.drag_start += shift; } SelectToolFsmState::Dragging } - (SelectToolFsmState::ResizingBounds | SelectToolFsmState::DraggingPivot | SelectToolFsmState::DrawingBox, SelectToolMessage::PointerOutsideViewport(modifier_keys)) => { - responses.add(SelectToolMessage::PointerMove(modifier_keys)); - - responses.add(SelectToolMessage::ShiftViewport); - - self - } - (SelectToolFsmState::ResizingBounds, SelectToolMessage::ShiftViewport) => { - if let Some(shift) = shift_viewport_if_mouse_beyond_edge(input.mouse.position, input.viewport_bounds.size(), responses) { + (SelectToolFsmState::ResizingBounds, SelectToolMessage::PointerOutsideViewport(_)) => { + if let Some(shift) = AutoPanning::shift_viewport(input.mouse.position, input.viewport_bounds.size(), responses) { if let Some(ref mut bounds) = &mut tool_data.bounding_box_manager { bounds.center_of_transformation += shift; bounds.original_bound_transform.translation += shift; @@ -783,20 +806,26 @@ impl Fsm for SelectToolFsmState { self } - (SelectToolFsmState::DraggingPivot, SelectToolMessage::ShiftViewport) => { - let _ = shift_viewport_if_mouse_beyond_edge(input.mouse.position, input.viewport_bounds.size(), responses); + (SelectToolFsmState::DraggingPivot, SelectToolMessage::PointerOutsideViewport(_)) => { + let _ = AutoPanning::shift_viewport(input.mouse.position, input.viewport_bounds.size(), responses); self } - (SelectToolFsmState::DrawingBox, SelectToolMessage::ShiftViewport) => { - if let Some(shift) = shift_viewport_if_mouse_beyond_edge(input.mouse.position, input.viewport_bounds.size(), responses) { + (SelectToolFsmState::DrawingBox, SelectToolMessage::PointerOutsideViewport(_)) => { + if let Some(shift) = AutoPanning::shift_viewport(input.mouse.position, input.viewport_bounds.size(), responses) { tool_data.drag_start += shift; } self } (state, SelectToolMessage::PointerOutsideViewport(modifier_keys)) => { - unsubscribe_animation_frame(tool_data, modifier_keys, responses); + tool_data.auto_panning.stop( + &[ + SelectToolMessage::PointerOutsideViewport(modifier_keys.clone()).into(), + SelectToolMessage::PointerMove(modifier_keys).into(), + ], + responses, + ); state } @@ -1064,68 +1093,3 @@ fn edit_layer_deepest_manipulation(layer: LayerNodeIdentifier, document_network: responses.add_front(ToolMessage::ActivateTool { tool_type: ToolType::Path }); } } - -/// Shifts the viewport when the mouse reaches the edge of the viewport. -/// -/// If the mouse was beyond any edge, it returns the amount shifted. Otherwise it returns None. -/// The shift is proportional to the distance between edge and mouse. It is also guaranteed to be integral. -fn shift_viewport_if_mouse_beyond_edge(mouse_position: DVec2, viewport_size: DVec2, responses: &mut VecDeque) -> Option { - let mouse_position = mouse_position.clamp( - DVec2::ZERO - DVec2::splat(DRAG_BEYOND_VIEWPORT_MAX_OVEREXTENSION_PIXELS), - viewport_size + DVec2::splat(DRAG_BEYOND_VIEWPORT_MAX_OVEREXTENSION_PIXELS), - ); - let mouse_position_percent = mouse_position / viewport_size; - - let mut shift_percent = DVec2::ZERO; - - if mouse_position_percent.x < 0. { - shift_percent.x = -mouse_position_percent.x; - } else if mouse_position_percent.x > 1. { - shift_percent.x = 1. - mouse_position_percent.x; - } - - if mouse_position_percent.y < 0. { - shift_percent.y = -mouse_position_percent.y; - } else if mouse_position_percent.y > 1. { - shift_percent.y = 1. - mouse_position_percent.y; - } - - if shift_percent.x == 0. && shift_percent.y == 0. { - return None; - } - - let delta = (shift_percent * DRAG_BEYOND_VIEWPORT_SPEED_FACTOR * viewport_size).round(); - responses.add(NavigationMessage::TranslateCanvas { delta }); - Some(delta) -} - -fn setup_pointer_outside_edge_event(mouse_position: DVec2, viewport_size: DVec2, tool_data: &mut SelectToolData, modifier_keys: SelectToolPointerKeys, responses: &mut VecDeque) { - let is_pointer_outside_edge = mouse_position.x < 0. || mouse_position.x > viewport_size.x || mouse_position.y < 0. || mouse_position.y > viewport_size.y; - - match is_pointer_outside_edge { - true => subscribe_animation_frame(tool_data, modifier_keys, responses), - false => unsubscribe_animation_frame(tool_data, modifier_keys, responses), - } -} - -fn subscribe_animation_frame(tool_data: &mut SelectToolData, modifier_keys: SelectToolPointerKeys, responses: &mut VecDeque) { - if !tool_data.subscribed_to_animation_frame { - tool_data.subscribed_to_animation_frame = true; - - responses.add(BroadcastMessage::SubscribeEvent { - on: BroadcastEvent::AnimationFrame, - send: Box::new(SelectToolMessage::PointerOutsideViewport(modifier_keys).into()), - }); - } -} - -fn unsubscribe_animation_frame(tool_data: &mut SelectToolData, modifier_keys: SelectToolPointerKeys, responses: &mut VecDeque) { - if tool_data.subscribed_to_animation_frame { - tool_data.subscribed_to_animation_frame = false; - - responses.add(BroadcastMessage::UnsubscribeEvent { - on: BroadcastEvent::AnimationFrame, - message: Box::new(SelectToolMessage::PointerOutsideViewport(modifier_keys).into()), - }); - } -}