From 95995d8abc8cdf4fa324840d219821358ff3f418 Mon Sep 17 00:00:00 2001 From: afrdbaig7 Date: Mon, 6 Apr 2026 19:47:38 +0530 Subject: [PATCH] Fix PTZ coordinate drift during drag operations This shifts all dragging offset calculations in TransformLayer, SelectTool, and PathTool to document space, removing any dependency on cached viewport projections for state logic. Both BoundingBoxManager's interactions and custom Path selection behaviors now properly invalidate and lazily reload viewport layouts on CanvasTransformed updates, resolving issues with geometry moving opposite the viewport during pan/tilt/zoom. --- .../transformation_cage.rs | 48 ++++ editor/src/messages/tool/tool_messages/mod.rs | 2 +- .../messages/tool/tool_messages/path_tool.rs | 104 +++++--- .../tool/tool_messages/select_tool.rs | 239 +++++++++++++----- .../transform_layer_message_handler.rs | 57 ++--- editor/src/messages/tool/utility_types.rs | 40 +++ 6 files changed, 367 insertions(+), 123 deletions(-) diff --git a/editor/src/messages/tool/common_functionality/transformation_cage.rs b/editor/src/messages/tool/common_functionality/transformation_cage.rs index 5f0ff7a1e1..d59cd74cf4 100644 --- a/editor/src/messages/tool/common_functionality/transformation_cage.rs +++ b/editor/src/messages/tool/common_functionality/transformation_cage.rs @@ -368,6 +368,52 @@ pub fn snap_drag(start: DVec2, current: DVec2, snap_to_axis: bool, axis: Axis, s document.metadata().document_to_viewport.transform_vector2(offset) } +/// Snaps a dragging event using document-space drag state so PTZ changes do not invalidate the drag anchor. +pub fn snap_drag_from_document(start: DVec2, current: DVec2, snap_to_axis: bool, axis: Axis, snap_data: SnapData, snap_manager: &mut SnapManager, candidates: &[SnapCandidatePoint]) -> DVec2 { + let document = snap_data.document; + let document_to_viewport = document.metadata().document_to_viewport; + let start_viewport = document_to_viewport.transform_point2(start); + let mouse_position = axis_align_drag(snap_to_axis, axis, snap_data.input.mouse.position, start_viewport); + let aligned_document = document_to_viewport.inverse().transform_point2(mouse_position); + let total_mouse_delta_document = aligned_document - start; + let mut offset = aligned_document - current; + let mut best_snap = SnappedPoint::infinite_snap(aligned_document); + + let bbox = Rect::point_iter(candidates.iter().map(|candidate| candidate.document_point + total_mouse_delta_document)); + + for (index, point) in candidates.iter().enumerate() { + let config = SnapTypeConfiguration { + bbox, + accept_distribution: true, + use_existing_candidates: index != 0, + ..Default::default() + }; + + let mut point = point.clone(); + point.document_point += total_mouse_delta_document; + + let constrained_along_axis = snap_to_axis || axis.is_constraint(); + let snapped = if constrained_along_axis { + let constraint = SnapConstraint::Line { + origin: point.document_point, + direction: total_mouse_delta_document.try_normalize().unwrap_or(DVec2::X), + }; + snap_manager.constrained_snap(&snap_data, &point, constraint, config) + } else { + snap_manager.free_snap(&snap_data, &point, config) + }; + + if best_snap.other_snap_better(&snapped) { + offset = snapped.snapped_point_document - point.document_point + (aligned_document - current); + best_snap = snapped; + } + } + + snap_manager.update_indicator(best_snap); + + offset +} + /// Contains info on the overlays for the bounding box and transform handles #[derive(Clone, Debug, Default)] pub struct BoundingBoxManager { @@ -379,10 +425,12 @@ pub struct BoundingBoxManager { pub transform_tampered: bool, /// The transform to viewport space for the bounds co-ordinates when the transformation was started. pub original_bound_transform: DAffine2, + pub original_bounds_to_document: DAffine2, pub selected_edges: Option, pub original_transforms: OriginalTransforms, pub opposite_pivot: DVec2, pub center_of_transformation: DVec2, + pub center_of_transformation_doc: DVec2, } impl BoundingBoxManager { diff --git a/editor/src/messages/tool/tool_messages/mod.rs b/editor/src/messages/tool/tool_messages/mod.rs index 6d29ad81a9..cf66adce21 100644 --- a/editor/src/messages/tool/tool_messages/mod.rs +++ b/editor/src/messages/tool/tool_messages/mod.rs @@ -17,7 +17,7 @@ pub mod tool_prelude { pub use crate::messages::input_mapper::utility_types::input_keyboard::{Key, MouseMotion}; pub use crate::messages::layout::utility_types::widget_prelude::*; pub use crate::messages::prelude::*; - pub use crate::messages::tool::utility_types::{EventToMessageMap, Fsm, ToolActionMessageContext, ToolMetadata, ToolTransition, ToolType}; + pub use crate::messages::tool::utility_types::{DragState, EventToMessageMap, Fsm, ToolActionMessageContext, ToolMetadata, ToolTransition, ToolType}; pub use crate::messages::tool::utility_types::{HintData, HintGroup, HintInfo}; pub use glam::{DAffine2, DVec2}; } diff --git a/editor/src/messages/tool/tool_messages/path_tool.rs b/editor/src/messages/tool/tool_messages/path_tool.rs index 80f0a6faa4..7649c80c93 100644 --- a/editor/src/messages/tool/tool_messages/path_tool.rs +++ b/editor/src/messages/tool/tool_messages/path_tool.rs @@ -55,6 +55,7 @@ pub struct PathToolOptions { pub enum PathToolMessage { // Standard messages Abort, + CanvasTransformed, SelectionChanged, Overlays { context: OverlayContext, @@ -511,6 +512,7 @@ impl ToolTransition for PathTool { fn event_to_message_map(&self) -> EventToMessageMap { EventToMessageMap { tool_abort: Some(PathToolMessage::Abort.into()), + canvas_transformed: Some(PathToolMessage::CanvasTransformed.into()), selection_changed: Some(PathToolMessage::SelectionChanged.into()), overlay_provider: Some(|context| PathToolMessage::Overlays { context }.into()), ..Default::default() @@ -561,7 +563,7 @@ struct PathToolData { snap_manager: SnapManager, lasso_polygon: Vec, selection_mode: Option, - drag_start_pos: DVec2, + drag_start_doc: DVec2, previous_mouse_position: DVec2, toggle_colinear_debounce: bool, opposing_handle_lengths: Option, @@ -645,14 +647,19 @@ impl PathToolData { // Convert previous mouse position to viewport space first let document_to_viewport = metadata.document_to_viewport; let previous_mouse = document_to_viewport.transform_point2(self.previous_mouse_position); - if previous_mouse == self.drag_start_pos { + let drag_start_vp = document_to_viewport.transform_point2(self.drag_start_doc); + if previous_mouse == drag_start_vp { let tolerance = DVec2::splat(SELECTION_TOLERANCE); - [self.drag_start_pos - tolerance, self.drag_start_pos + tolerance] + [drag_start_vp - tolerance, drag_start_vp + tolerance] } else { - [self.drag_start_pos, previous_mouse] + [drag_start_vp, previous_mouse] } } + fn drag_start_viewport(&self, document: &DocumentMessageHandler) -> DVec2 { + document.metadata().document_to_viewport.transform_point2(self.drag_start_doc) + } + fn update_selection_status(&mut self, shape_editor: &mut ShapeState, document: &DocumentMessageHandler) { let selection_status = get_selection_status(&document.network_interface, shape_editor); @@ -736,7 +743,7 @@ impl PathToolData { self.double_click_handled = false; self.opposing_handle_lengths = None; - self.drag_start_pos = input.mouse.position; + self.drag_start_doc = document.metadata().document_to_viewport.inverse().transform_point2(input.mouse.position); if input.time - self.last_click_time > DOUBLE_CLICK_MILLISECONDS { self.saved_points_before_anchor_convert_smooth_sharp.clear(); @@ -784,7 +791,7 @@ impl PathToolData { } if let Some(selected_points) = selection_info { - self.drag_start_pos = input.mouse.position; + self.drag_start_doc = document.metadata().document_to_viewport.inverse().transform_point2(input.mouse.position); // If selected points contain only handles and there was some selection before, then it is stored and becomes restored upon release let mut dragging_only_handles = true; @@ -871,7 +878,7 @@ impl PathToolData { // TODO: If the segment connected to one of the endpoints is also selected then select that point } - self.drag_start_pos = input.mouse.position; + self.drag_start_doc = document.metadata().document_to_viewport.inverse().transform_point2(input.mouse.position); let viewport_to_document = document.metadata().document_to_viewport.inverse(); self.previous_mouse_position = viewport_to_document.transform_point2(input.mouse.position); @@ -900,7 +907,7 @@ impl PathToolData { self.started_drawing_from_inside = true; - self.drag_start_pos = input.mouse.position; + self.drag_start_doc = document.metadata().document_to_viewport.inverse().transform_point2(input.mouse.position); self.previous_mouse_position = document.metadata().document_to_viewport.inverse().transform_point2(input.mouse.position); let selection_shape = if lasso_select { SelectionShapeType::Lasso } else { SelectionShapeType::Box }; @@ -908,7 +915,7 @@ impl PathToolData { } // Start drawing else { - self.drag_start_pos = input.mouse.position; + self.drag_start_doc = document.metadata().document_to_viewport.inverse().transform_point2(input.mouse.position); self.previous_mouse_position = document.metadata().document_to_viewport.inverse().transform_point2(input.mouse.position); let selection_shape = if lasso_select { SelectionShapeType::Lasso } else { SelectionShapeType::Box }; @@ -1153,7 +1160,7 @@ impl PathToolData { fn start_snap_along_axis(&mut self, shape_editor: &mut ShapeState, document: &DocumentMessageHandler, input: &InputPreprocessorMessageHandler, responses: &mut VecDeque) { // Find the negative delta to take the point to the drag start position let current_mouse = input.mouse.position; - let drag_start = self.drag_start_pos; + let drag_start = self.drag_start_viewport(document); let opposite_delta = drag_start - current_mouse; shape_editor.move_selected_points_and_segments(None, document, opposite_delta, false, true, false, None, false, responses); @@ -1174,7 +1181,7 @@ impl PathToolData { fn stop_snap_along_axis(&mut self, shape_editor: &mut ShapeState, document: &DocumentMessageHandler, input: &InputPreprocessorMessageHandler, responses: &mut VecDeque) { // Calculate the negative delta of the selection and move it back to the drag start let current_mouse = input.mouse.position; - let drag_start = self.drag_start_pos; + let drag_start = self.drag_start_viewport(document); let opposite_delta = drag_start - current_mouse; let Some(axis) = self.snapping_axis else { return }; @@ -1463,7 +1470,7 @@ impl PathToolData { let mut was_alt_dragging = false; if self.snapping_axis.is_none() { - if self.alt_clicked_on_anchor && !self.alt_dragging_from_anchor && self.drag_start_pos.distance(input.mouse.position) > DRAG_THRESHOLD { + if self.alt_clicked_on_anchor && !self.alt_dragging_from_anchor && self.drag_start_viewport(document).distance(input.mouse.position) > DRAG_THRESHOLD { // Checking which direction the dragging begins self.alt_dragging_from_anchor = true; let Some(layer) = document.network_interface.selected_nodes().selected_layers(document.metadata()).next() else { @@ -1485,7 +1492,7 @@ impl PathToolData { return; }; - let delta = input.mouse.position - self.drag_start_pos; + let delta = input.mouse.position - self.drag_start_viewport(document); let handle = if delta.dot(tangent1) >= delta.dot(tangent2) { segment1.to_manipulator_point() } else { @@ -1528,7 +1535,7 @@ impl PathToolData { // Constantly checking and changing the snapping axis based on current mouse position if snap_axis && self.snapping_axis.is_some() { let Some(current_axis) = self.snapping_axis else { return }; - let total_delta = self.drag_start_pos - input.mouse.position; + let total_delta = self.drag_start_viewport(document) - input.mouse.position; if (total_delta.x.abs() > total_delta.y.abs() && current_axis == Axis::Y) || (total_delta.y.abs() > total_delta.x.abs() && current_axis == Axis::X) { self.stop_snap_along_axis(shape_editor, document, input, responses); @@ -1706,7 +1713,7 @@ impl Fsm for PathToolFsmState { } (_, PathToolMessage::Overlays { context: mut overlay_context }) => { // Set this to show ghost line only if drag actually happened - if matches!(self, Self::Dragging(_)) && tool_data.drag_start_pos.distance(input.mouse.position) > DRAG_THRESHOLD { + if matches!(self, Self::Dragging(_)) && tool_data.drag_start_viewport(document).distance(input.mouse.position) > DRAG_THRESHOLD { for (outline, layer) in &tool_data.ghost_outline { let transform = document.metadata().transform_to_viewport(*layer); overlay_context.outline(outline.iter(), transform, Some(COLOR_OVERLAY_GRAY)); @@ -1904,7 +1911,8 @@ impl Fsm for PathToolFsmState { let (points_inside, segments_inside) = match selection_shape { SelectionShapeType::Box => { let previous_mouse = document.metadata().document_to_viewport.transform_point2(tool_data.previous_mouse_position); - let bbox = Rect::new(tool_data.drag_start_pos.x, tool_data.drag_start_pos.y, previous_mouse.x, previous_mouse.y).abs(); + let drag_start_vp = tool_data.drag_start_viewport(document); + let bbox = Rect::new(drag_start_vp.x, drag_start_vp.y, previous_mouse.x, previous_mouse.y).abs(); shape_editor.get_inside_points_and_segments( &document.network_interface, SelectionShape::Box(bbox), @@ -1971,7 +1979,7 @@ impl Fsm for PathToolFsmState { // Draw the snapping axis lines if tool_data.snapping_axis.is_some() { let Some(axis) = tool_data.snapping_axis else { return self }; - let origin = tool_data.drag_start_pos; + let origin = tool_data.drag_start_viewport(document); let viewport_diagonal = viewport.size().into_dvec2().length(); match axis { @@ -2099,11 +2107,11 @@ impl Fsm for PathToolFsmState { let selected_only_handles = !shape_editor.selected_points().any(|point| matches!(point, ManipulatorPointId::Anchor(_))); tool_data.stored_selection = None; - if !tool_data.saved_selection_before_handle_drag.is_empty() && (tool_data.drag_start_pos.distance(input.mouse.position) > DRAG_THRESHOLD) && (selected_only_handles) { + if !tool_data.saved_selection_before_handle_drag.is_empty() && (tool_data.drag_start_viewport(document).distance(input.mouse.position) > DRAG_THRESHOLD) && (selected_only_handles) { tool_data.handle_drag_toggle = true; } - if tool_data.drag_start_pos.distance(input.mouse.position) > DRAG_THRESHOLD { + if tool_data.drag_start_viewport(document).distance(input.mouse.position) > DRAG_THRESHOLD { tool_data.molding_segment = true; } @@ -2245,7 +2253,7 @@ impl Fsm for PathToolFsmState { (PathToolFsmState::Drawing { selection_shape: selection_type }, PathToolMessage::PointerOutsideViewport { .. }) => { // Auto-panning if let Some(offset) = tool_data.auto_panning.shift_viewport(input, viewport, responses) { - tool_data.drag_start_pos += offset; + tool_data.drag_start_doc += document.metadata().document_to_viewport.inverse().transform_vector2(offset); } PathToolFsmState::Drawing { selection_shape: selection_type } @@ -2253,7 +2261,7 @@ impl Fsm for PathToolFsmState { (PathToolFsmState::Dragging(dragging_state), PathToolMessage::PointerOutsideViewport { .. }) => { // Auto-panning if let Some(offset) = tool_data.auto_panning.shift_viewport(input, viewport, responses) { - tool_data.drag_start_pos += offset; + tool_data.drag_start_doc += document.metadata().document_to_viewport.inverse().transform_vector2(offset); } PathToolFsmState::Dragging(dragging_state) @@ -2314,7 +2322,7 @@ impl Fsm for PathToolFsmState { let document_to_viewport = document.metadata().document_to_viewport; let previous_mouse = document_to_viewport.transform_point2(tool_data.previous_mouse_position); - if tool_data.drag_start_pos == previous_mouse { + if tool_data.drag_start_viewport(document) == previous_mouse { responses.add(NodeGraphMessage::SelectedNodesSet { nodes: vec![] }); } else { let selection_mode = match tool_action_data.preferences.get_selection_mode() { @@ -2324,7 +2332,8 @@ impl Fsm for PathToolFsmState { match selection_shape { SelectionShapeType::Box => { - let bbox = Rect::new(tool_data.drag_start_pos.x, tool_data.drag_start_pos.y, previous_mouse.x, previous_mouse.y).abs(); + let drag_start_vp = tool_data.drag_start_viewport(document); + let bbox = Rect::new(drag_start_vp.x, drag_start_vp.y, previous_mouse.x, previous_mouse.y).abs(); shape_editor.select_all_in_shape( &document.network_interface, @@ -2355,7 +2364,7 @@ impl Fsm for PathToolFsmState { PathToolFsmState::Ready } (PathToolFsmState::Dragging { .. }, PathToolMessage::Escape | PathToolMessage::RightClick) => { - if tool_data.handle_drag_toggle && tool_data.drag_start_pos.distance(input.mouse.position) > DRAG_THRESHOLD { + if tool_data.handle_drag_toggle && tool_data.drag_start_viewport(document).distance(input.mouse.position) > DRAG_THRESHOLD { shape_editor.deselect_all_points(); shape_editor.deselect_all_segments(); @@ -2410,7 +2419,7 @@ impl Fsm for PathToolFsmState { }; tool_data.started_drawing_from_inside = false; - if tool_data.drag_start_pos.distance(previous_mouse) < 1e-8 { + if tool_data.drag_start_viewport(document).distance(previous_mouse) < 1e-8 { // Clicked inside or outside the shape then deselect all of the points/segments if document.click(input, viewport).is_some() && tool_data.stored_selection.is_none() { tool_data.stored_selection = Some(shape_editor.selected_shape_state.clone()); @@ -2421,7 +2430,8 @@ impl Fsm for PathToolFsmState { } else { match selection_shape { SelectionShapeType::Box => { - let bbox = Rect::new(tool_data.drag_start_pos.x, tool_data.drag_start_pos.y, previous_mouse.x, previous_mouse.y).abs(); + let drag_start_vp = tool_data.drag_start_viewport(document); + let bbox = Rect::new(drag_start_vp.x, drag_start_vp.y, previous_mouse.x, previous_mouse.y).abs(); shape_editor.select_all_in_shape( &document.network_interface, @@ -2454,7 +2464,7 @@ impl Fsm for PathToolFsmState { (_, PathToolMessage::DragStop { extend_selection, .. }) => { tool_data.ghost_outline.clear(); let extend_selection = input.keyboard.get(extend_selection as usize); - let drag_occurred = tool_data.drag_start_pos.distance(input.mouse.position) > DRAG_THRESHOLD; + let drag_occurred = tool_data.drag_start_viewport(document).distance(input.mouse.position) > DRAG_THRESHOLD; let mut segment_dissolved = false; let mut point_inserted = false; @@ -2585,7 +2595,7 @@ impl Fsm for PathToolFsmState { } } // Deselect all points if the user clicks the filled region of the shape - else if tool_data.drag_start_pos.distance(input.mouse.position) <= DRAG_THRESHOLD { + else if tool_data.drag_start_viewport(document).distance(input.mouse.position) <= DRAG_THRESHOLD { shape_editor.deselect_all_points(); shape_editor.deselect_all_segments(); } @@ -3037,7 +3047,7 @@ impl Fsm for PathToolFsmState { if nearest_point.is_some() { // Flip the selected point between smooth and sharp - if !tool_data.double_click_handled && tool_data.drag_start_pos.distance(input.mouse.position) <= DRAG_THRESHOLD { + if !tool_data.double_click_handled && tool_data.drag_start_viewport(document).distance(input.mouse.position) <= DRAG_THRESHOLD { responses.add(DocumentMessage::StartTransaction); shape_editor.select_points_by_layer_and_id(&tool_data.saved_points_before_anchor_convert_smooth_sharp); @@ -3119,6 +3129,42 @@ impl Fsm for PathToolFsmState { PathToolFsmState::Ready } + // PTZ handling: re-emit PointerMove to recompute positions with updated document_to_viewport + (PathToolFsmState::Dragging(dragging_state), PathToolMessage::CanvasTransformed) => { + let modifier_keys = PathToolMessage::PointerMove { + equidistant: Key::Alt, + toggle_colinear: Key::KeyC, + move_anchor_with_handles: Key::Space, + snap_angle: Key::Control, + lock_angle: Key::Shift, + delete_segment: Key::Backspace, + break_colinear_molding: Key::Tab, + segment_editing_modifier: Key::Alt, + }; + responses.add(modifier_keys); + responses.add(OverlaysMessage::Draw); + PathToolFsmState::Dragging(dragging_state) + } + (PathToolFsmState::Drawing { selection_shape }, PathToolMessage::CanvasTransformed) => { + let modifier_keys = PathToolMessage::PointerMove { + equidistant: Key::Alt, + toggle_colinear: Key::KeyC, + move_anchor_with_handles: Key::Space, + snap_angle: Key::Control, + lock_angle: Key::Shift, + delete_segment: Key::Backspace, + break_colinear_molding: Key::Tab, + segment_editing_modifier: Key::Alt, + }; + responses.add(modifier_keys); + responses.add(OverlaysMessage::Draw); + PathToolFsmState::Drawing { selection_shape } + } + (PathToolFsmState::SlidingPoint, PathToolMessage::CanvasTransformed) => { + responses.add(OverlaysMessage::Draw); + PathToolFsmState::SlidingPoint + } + (_, PathToolMessage::CanvasTransformed) => self, (_, PathToolMessage::Abort) => { responses.add(OverlaysMessage::Draw); PathToolFsmState::Ready diff --git a/editor/src/messages/tool/tool_messages/select_tool.rs b/editor/src/messages/tool/tool_messages/select_tool.rs index b8c6751565..b024e33fdb 100644 --- a/editor/src/messages/tool/tool_messages/select_tool.rs +++ b/editor/src/messages/tool/tool_messages/select_tool.rs @@ -2,7 +2,6 @@ use super::tool_prelude::*; use crate::consts::*; -use crate::messages::input_mapper::utility_types::input_mouse::ViewportPosition; use crate::messages::portfolio::document::graph_operation::utility_types::TransformIn; use crate::messages::portfolio::document::node_graph::document_node_definitions::DefinitionIdentifier; use crate::messages::portfolio::document::overlays::utility_types::OverlayContext; @@ -83,6 +82,7 @@ pub struct SelectToolPointerKeys { pub enum SelectToolMessage { // Standard messages Abort, + CanvasTransformed, Overlays { context: OverlayContext, }, @@ -323,6 +323,7 @@ impl<'a> MessageHandler> for Sele let mut common = actions!(SelectToolMessageDiscriminant; PointerMove, Abort, + CanvasTransformed, EditLayer, EditLayerExec, Enter, @@ -341,6 +342,7 @@ impl<'a> MessageHandler> for Sele impl ToolTransition for SelectTool { fn event_to_message_map(&self) -> EventToMessageMap { EventToMessageMap { + canvas_transformed: Some(SelectToolMessage::CanvasTransformed.into()), tool_abort: Some(SelectToolMessage::Abort.into()), overlay_provider: Some(|context| SelectToolMessage::Overlays { context }.into()), ..Default::default() @@ -381,9 +383,8 @@ impl Default for SelectToolFsmState { #[derive(Clone, Debug, Default)] struct SelectToolData { - drag_start: ViewportPosition, - drag_current: ViewportPosition, - lasso_polygon: Vec, + drag_state: DragState, + lasso_polygon: Vec, selection_mode: Option, layers_dragging: Vec, // Unordered, often used as temporary buffer ordered_layers: Vec, // Ordered list of layers @@ -391,6 +392,7 @@ struct SelectToolData { select_single_layer: Option, axis_align: bool, non_duplicated_layers: Option>, + duplicate_paused_by_ptz: bool, bounding_box_manager: Option, snap_manager: SnapManager, cursor: MouseCursorIcon, @@ -398,14 +400,13 @@ struct SelectToolData { pivot_gizmo_start: Option, pivot_gizmo_shift: Option, compass_rose: CompassRose, - line_center: DVec2, skew_edge: EdgeBool, nested_selection_behavior: NestedSelectionBehavior, selected_layers_count: usize, selected_layers_changed: bool, snap_candidates: Vec, auto_panning: AutoPanning, - drag_start_center: ViewportPosition, + drag_start_center: DVec2, } impl SelectToolData { @@ -422,13 +423,21 @@ impl SelectToolData { } } - pub fn selection_quad(&self) -> Quad { - let bbox = self.selection_box(); + pub fn drag_start_viewport(&self, document: &DocumentMessageHandler) -> DVec2 { + self.drag_state.start_viewport(document) + } + + pub fn drag_current_viewport(&self, document: &DocumentMessageHandler) -> DVec2 { + self.drag_state.current_viewport(document) + } + + pub fn selection_quad(&self, document: &DocumentMessageHandler) -> Quad { + let bbox = self.selection_box(document); Quad::from_box(bbox) } - pub fn calculate_selection_mode_from_direction(&mut self) -> SelectionMode { - let bbox: [DVec2; 2] = self.selection_box(); + pub fn calculate_selection_mode_from_direction(&mut self, document: &DocumentMessageHandler) -> SelectionMode { + let bbox: [DVec2; 2] = self.selection_box(document); let above_threshold = bbox[1].distance_squared(bbox[0]) > DRAG_DIRECTION_MODE_DETERMINATION_THRESHOLD.powi(2); if self.selection_mode.is_none() && above_threshold { @@ -444,12 +453,13 @@ impl SelectToolData { self.selection_mode.unwrap_or(SelectionMode::Touched) } - pub fn selection_box(&self) -> [DVec2; 2] { - if self.drag_current == self.drag_start { + pub fn selection_box(&self, document: &DocumentMessageHandler) -> [DVec2; 2] { + let start = self.drag_start_viewport(document); + if self.drag_state.current_doc == self.drag_state.start_doc { let tolerance = DVec2::splat(SELECTION_TOLERANCE); - [self.drag_start - tolerance, self.drag_start + tolerance] + [start - tolerance, start + tolerance] } else { - [self.drag_start, self.drag_current] + [start, self.drag_current_viewport(document)] } } @@ -457,7 +467,13 @@ impl SelectToolData { if self.lasso_polygon.len() < 2 { return Vec::new(); } - let polygon = Subpath::from_anchors(self.lasso_polygon.clone(), true); + let polygon = Subpath::from_anchors( + self.lasso_polygon + .iter() + .map(|point| document.metadata().document_to_viewport.transform_point2(*point)) + .collect::>(), + true, + ); document.intersect_polygon_no_artboards(polygon, viewport).collect() } @@ -465,14 +481,22 @@ impl SelectToolData { if self.lasso_polygon.len() < 2 { return false; } - let polygon = Subpath::from_anchors(self.lasso_polygon.clone(), true); + let polygon = Subpath::from_anchors( + self.lasso_polygon + .iter() + .map(|point| document.metadata().document_to_viewport.transform_point2(*point)) + .collect::>(), + true, + ); document.is_layer_fully_inside_polygon(layer, viewport, polygon) } /// Duplicates the currently dragging layers. Called when Alt is pressed and the layers have not yet been duplicated. fn start_duplicates(&mut self, document: &mut DocumentMessageHandler, responses: &mut VecDeque) { + self.duplicate_paused_by_ptz = false; self.non_duplicated_layers = Some(self.layers_dragging.clone()); let mut new_dragging = Vec::new(); + let drag_offset = self.drag_state.start_viewport(document) - self.drag_state.current_viewport(document); // Get the shallowest unique layers and sort by their index relative to parent for ordered processing let mut layers = document.network_interface.shallowest_unique_layers(&[]).collect::>(); @@ -488,7 +512,7 @@ impl SelectToolData { // Moves the layer back to its starting position. responses.add(GraphOperationMessage::TransformChange { layer, - transform: DAffine2::from_translation(self.drag_start - self.drag_current), + transform: DAffine2::from_translation(drag_offset), transform_in: TransformIn::Viewport, skip_rerender: true, }); @@ -541,7 +565,7 @@ impl SelectToolData { for &layer in &original { responses.add(GraphOperationMessage::TransformChange { layer, - transform: DAffine2::from_translation(self.drag_current - self.drag_start), + transform: DAffine2::from_translation(self.drag_state.viewport_delta(document)), transform_in: TransformIn::Viewport, skip_rerender: true, }); @@ -854,7 +878,7 @@ impl Fsm for SelectToolFsmState { .pivot_gizmo_start .map(|offset| { if tool_data.pivot_gizmo.pivot_disconnected() { - tool_data.drag_current - offset + tool_data.drag_current_viewport(document) - document.metadata().document_to_viewport.transform_point2(offset) } else { Default::default() } @@ -868,9 +892,6 @@ impl Fsm for SelectToolFsmState { if overlay_context.visibility_settings.compass_rose() { tool_data.compass_rose.refresh_position(document); let compass_center = tool_data.compass_rose.compass_rose_position(); - if !matches!(self, Self::Dragging { .. }) { - tool_data.line_center = compass_center; - } overlay_context.compass_rose(compass_center, angle, show_compass_with_ring); @@ -899,17 +920,17 @@ impl Fsm for SelectToolFsmState { let viewport_diagonal = viewport.size().into_dvec2().length(); let color = if !hover { color } else { color_faded }; - let line_center = tool_data.line_center; + let line_center = compass_center; overlay_context.line(line_center - direction * viewport_diagonal, line_center + direction * viewport_diagonal, Some(color), None); } if axis_state.is_none_or(|(axis, _)| !axis.is_constraint()) && tool_data.axis_align { - let mouse_position = mouse_position - tool_data.drag_start; + let mouse_position = mouse_position - tool_data.drag_start_viewport(document); let snap_resolution = SELECTION_DRAG_ANGLE.to_radians(); let angle = -mouse_position.angle_to(DVec2::X); let snapped_angle = (angle / snap_resolution).round() * snap_resolution; - let origin = tool_data.drag_start_center; + let origin = document.metadata().document_to_viewport.transform_point2(tool_data.drag_start_center); let viewport_diagonal = viewport.size().into_dvec2().length(); let edge = DVec2::from_angle(snapped_angle).normalize_or(DVec2::X); @@ -928,10 +949,10 @@ impl Fsm for SelectToolFsmState { // Check if the tool is in selection mode if let Self::Drawing { selection_shape, .. } = self { // Get the updated selection box bounds - let quad = Quad::from_box([tool_data.drag_start, tool_data.drag_current]); + let quad = tool_data.selection_quad(document); let current_selection_mode = match tool_action_data.preferences.get_selection_mode() { - SelectionMode::Directional => tool_data.calculate_selection_mode_from_direction(), + SelectionMode::Directional => tool_data.calculate_selection_mode_from_direction(document), SelectionMode::Touched => SelectionMode::Touched, SelectionMode::Enclosed => SelectionMode::Enclosed, }; @@ -963,23 +984,19 @@ impl Fsm for SelectToolFsmState { // Update the selection box let fill_color = Some(COLOR_OVERLAY_BLUE_05); - - let polygon = &tool_data.lasso_polygon; + let polygon: Vec<_> = tool_data.lasso_polygon.iter().map(|point| document.metadata().document_to_viewport.transform_point2(*point)).collect(); match (selection_shape, current_selection_mode) { (SelectionShapeType::Box, SelectionMode::Enclosed) => overlay_context.dashed_quad(quad, None, fill_color, Some(4.), Some(4.), Some(0.5)), - (SelectionShapeType::Lasso, SelectionMode::Enclosed) => overlay_context.dashed_polygon(polygon, None, fill_color, Some(4.), Some(4.), Some(0.5)), + (SelectionShapeType::Lasso, SelectionMode::Enclosed) => overlay_context.dashed_polygon(&polygon, None, fill_color, Some(4.), Some(4.), Some(0.5)), (SelectionShapeType::Box, _) => overlay_context.quad(quad, None, fill_color), - (SelectionShapeType::Lasso, _) => overlay_context.polygon(polygon, None, fill_color), + (SelectionShapeType::Lasso, _) => overlay_context.polygon(&polygon, None, fill_color), } } if let Self::Dragging { .. } = self { - let quad = Quad::from_box([tool_data.drag_start, tool_data.drag_current]); - let document_start = document.metadata().document_to_viewport.inverse().transform_point2(quad.top_left()); - let document_current = document.metadata().document_to_viewport.inverse().transform_point2(quad.bottom_right()); - - overlay_context.translation_box(document_current - document_start, quad, None); + let quad = tool_data.selection_quad(document); + overlay_context.translation_box(tool_data.drag_state.current_doc - tool_data.drag_state.start_doc, quad, None); } self @@ -1010,8 +1027,7 @@ impl Fsm for SelectToolFsmState { .. }, ) => { - tool_data.drag_start = input.mouse.position; - tool_data.drag_current = input.mouse.position; + tool_data.drag_state = DragState::new(document, input.mouse.position); tool_data.selection_mode = None; let mut selected: Vec<_> = document.network_interface.selected_nodes().selected_visible_and_unlocked_layers(&document.network_interface).collect(); @@ -1021,7 +1037,7 @@ impl Fsm for SelectToolFsmState { let position = tool_data.pivot_gizmo().position(document); let (resize, rotate, skew) = transforming_transform_cage(document, &mut tool_data.bounding_box_manager, input, responses, &mut tool_data.layers_dragging, Some(position)); - tool_data.drag_start_center = position; + tool_data.drag_start_center = document.metadata().document_to_viewport.inverse().transform_point2(position); // If the user is dragging the bounding box bounds, go into ResizingBounds mode. // If the user is dragging the rotate trigger, go into RotatingBounds mode. @@ -1073,7 +1089,7 @@ impl Fsm for SelectToolFsmState { (axis_state.unwrap_or_default(), axis_state.is_some()) }; - tool_data.pivot_gizmo_start = Some(tool_data.drag_current); + tool_data.pivot_gizmo_start = Some(tool_data.drag_state.current_doc); SelectToolFsmState::Dragging { axis, @@ -1114,7 +1130,7 @@ impl Fsm for SelectToolFsmState { responses.add(DocumentMessage::StartTransaction); - tool_data.pivot_gizmo_start = Some(tool_data.drag_current); + tool_data.pivot_gizmo_start = Some(tool_data.drag_state.current_doc); SelectToolFsmState::Dragging { axis: Axis::None, @@ -1129,6 +1145,7 @@ impl Fsm for SelectToolFsmState { } }; tool_data.non_duplicated_layers = None; + tool_data.duplicate_paused_by_ptz = false; state } @@ -1151,7 +1168,10 @@ impl Fsm for SelectToolFsmState { if !has_dragged { responses.add(ToolMessage::UpdateHints); } - if input.keyboard.key(modifier_keys.duplicate) && tool_data.non_duplicated_layers.is_none() { + if !input.keyboard.key(modifier_keys.duplicate) { + tool_data.duplicate_paused_by_ptz = false; + } + if input.keyboard.key(modifier_keys.duplicate) && tool_data.non_duplicated_layers.is_none() && !tool_data.duplicate_paused_by_ptz { tool_data.start_duplicates(document, responses); } else if !input.keyboard.key(modifier_keys.duplicate) && tool_data.non_duplicated_layers.is_some() { tool_data.stop_duplicates(document, responses); @@ -1164,14 +1184,16 @@ impl Fsm for SelectToolFsmState { let ignore = tool_data.non_duplicated_layers.as_ref().filter(|_| !layers_exist).unwrap_or(&tool_data.layers_dragging); let snap_data = SnapData::ignore(document, input, viewport, ignore); - let (start, current) = (tool_data.drag_start, tool_data.drag_current); + let start = tool_data.drag_state.start_doc; + let current = tool_data.drag_state.current_doc; let e0 = tool_data .bounding_box_manager .as_ref() .map(|bounding_box_manager| bounding_box_manager.transform * Quad::from_box(bounding_box_manager.bounds)) .map_or(DVec2::X, |quad| (quad.top_left() - quad.top_right()).normalize_or(DVec2::X)); - let mouse_delta = snap_drag(start, current, tool_data.axis_align, axis, snap_data, &mut tool_data.snap_manager, &tool_data.snap_candidates); + let mouse_delta_document = snap_drag_from_document(start, current, tool_data.axis_align, axis, snap_data, &mut tool_data.snap_manager, &tool_data.snap_candidates); + let mouse_delta = document.metadata().document_to_viewport.transform_vector2(mouse_delta_document); let mouse_delta = match axis { Axis::X => mouse_delta.project_onto(e0), Axis::Y => mouse_delta.project_onto(e0.perp()), @@ -1187,7 +1209,7 @@ impl Fsm for SelectToolFsmState { skip_rerender: false, }); } - tool_data.drag_current += mouse_delta; + tool_data.drag_state.current_doc += document.metadata().document_to_viewport.inverse().transform_vector2(mouse_delta); // Auto-panning let messages = [ @@ -1206,6 +1228,8 @@ impl Fsm for SelectToolFsmState { } (SelectToolFsmState::ResizingBounds, SelectToolMessage::PointerMove { modifier_keys }) => { if let Some(bounds) = &mut tool_data.bounding_box_manager { + bounds.original_bound_transform = document.metadata().document_to_viewport * bounds.original_bounds_to_document; + bounds.center_of_transformation = document.metadata().document_to_viewport.transform_point2(bounds.center_of_transformation_doc); resize_bounds( document, responses, @@ -1229,6 +1253,8 @@ impl Fsm for SelectToolFsmState { } (SelectToolFsmState::SkewingBounds { skew }, SelectToolMessage::PointerMove { .. }) => { if let Some(bounds) = &mut tool_data.bounding_box_manager { + bounds.original_bound_transform = document.metadata().document_to_viewport * bounds.original_bounds_to_document; + bounds.center_of_transformation = document.metadata().document_to_viewport.transform_point2(bounds.center_of_transformation_doc); skew_bounds( document, responses, @@ -1242,13 +1268,16 @@ impl Fsm for SelectToolFsmState { SelectToolFsmState::SkewingBounds { skew } } (SelectToolFsmState::RotatingBounds, SelectToolMessage::PointerMove { .. }) => { + let drag_start_viewport = tool_data.drag_start_viewport(document); if let Some(bounds) = &mut tool_data.bounding_box_manager { + bounds.original_bound_transform = document.metadata().document_to_viewport * bounds.original_bounds_to_document; + bounds.center_of_transformation = document.metadata().document_to_viewport.transform_point2(bounds.center_of_transformation_doc); rotate_bounds( document, responses, bounds, &mut tool_data.layers_dragging, - tool_data.drag_start, + drag_start_viewport, input.mouse.position, input.keyboard.key(Key::Shift), ToolType::Select, @@ -1279,11 +1308,11 @@ impl Fsm for SelectToolFsmState { responses.add(ToolMessage::UpdateHints); } - tool_data.drag_current = input.mouse.position; + tool_data.drag_state.set_current_viewport(document, input.mouse.position); responses.add(OverlaysMessage::Draw); if selection_shape == SelectionShapeType::Lasso { - extend_lasso(&mut tool_data.lasso_polygon, tool_data.drag_current); + extend_lasso(&mut tool_data.lasso_polygon, tool_data.drag_state.current_doc); } // Auto-panning @@ -1331,13 +1360,100 @@ impl Fsm for SelectToolFsmState { deepest, remove, }, - SelectToolMessage::PointerOutsideViewport { .. }, + SelectToolMessage::CanvasTransformed, ) => { - // Auto-panning - if let Some(shift) = tool_data.auto_panning.shift_viewport(input, viewport, responses) { - tool_data.drag_current += shift; - tool_data.drag_start += shift; + if tool_data.non_duplicated_layers.is_some() { + tool_data.stop_duplicates(document, responses); + tool_data.duplicate_paused_by_ptz = true; } + // Re-emit PointerMove to recompute drag delta with updated document_to_viewport + let modifier_keys = SelectToolPointerKeys { + axis_align: Key::Shift, + snap_angle: Key::Control, + center: Key::Alt, + duplicate: Key::Alt, + }; + responses.add(SelectToolMessage::PointerMove { modifier_keys }); + responses.add(OverlaysMessage::Draw); + SelectToolFsmState::Dragging { + axis, + using_compass, + has_dragged, + deepest, + remove, + } + } + (SelectToolFsmState::Drawing { selection_shape, has_drawn }, SelectToolMessage::CanvasTransformed) => { + // Re-emit PointerMove to recompute selection box with updated document_to_viewport + let modifier_keys = SelectToolPointerKeys { + axis_align: Key::Shift, + snap_angle: Key::Control, + center: Key::Alt, + duplicate: Key::Alt, + }; + responses.add(SelectToolMessage::PointerMove { modifier_keys }); + responses.add(OverlaysMessage::Draw); + SelectToolFsmState::Drawing { selection_shape, has_drawn } + } + (SelectToolFsmState::ResizingBounds, SelectToolMessage::CanvasTransformed) => { + // Re-emit PointerMove to recompute resize with updated document_to_viewport + let modifier_keys = SelectToolPointerKeys { + axis_align: Key::Shift, + snap_angle: Key::Control, + center: Key::Alt, + duplicate: Key::Alt, + }; + responses.add(SelectToolMessage::PointerMove { modifier_keys }); + responses.add(OverlaysMessage::Draw); + SelectToolFsmState::ResizingBounds + } + (SelectToolFsmState::RotatingBounds, SelectToolMessage::CanvasTransformed) => { + // Re-emit PointerMove to recompute rotation with updated document_to_viewport + let modifier_keys = SelectToolPointerKeys { + axis_align: Key::Shift, + snap_angle: Key::Control, + center: Key::Alt, + duplicate: Key::Alt, + }; + responses.add(SelectToolMessage::PointerMove { modifier_keys }); + responses.add(OverlaysMessage::Draw); + SelectToolFsmState::RotatingBounds + } + (SelectToolFsmState::SkewingBounds { skew }, SelectToolMessage::CanvasTransformed) => { + // Re-emit PointerMove to recompute skew with updated document_to_viewport + let modifier_keys = SelectToolPointerKeys { + axis_align: Key::Shift, + snap_angle: Key::Control, + center: Key::Alt, + duplicate: Key::Alt, + }; + responses.add(SelectToolMessage::PointerMove { modifier_keys }); + responses.add(OverlaysMessage::Draw); + SelectToolFsmState::SkewingBounds { skew } + } + (SelectToolFsmState::DraggingPivot, SelectToolMessage::CanvasTransformed) => { + let modifier_keys = SelectToolPointerKeys { + axis_align: Key::Shift, + snap_angle: Key::Control, + center: Key::Alt, + duplicate: Key::Alt, + }; + responses.add(SelectToolMessage::PointerMove { modifier_keys }); + responses.add(OverlaysMessage::Draw); + SelectToolFsmState::DraggingPivot + } + (state, SelectToolMessage::CanvasTransformed) => state, + ( + SelectToolFsmState::Dragging { + axis, + using_compass, + has_dragged, + deepest, + remove, + }, + SelectToolMessage::PointerOutsideViewport { .. }, + ) => { + let _ = tool_data.auto_panning.shift_viewport(input, viewport, responses); SelectToolFsmState::Dragging { axis, @@ -1365,10 +1481,7 @@ impl Fsm for SelectToolFsmState { self } (SelectToolFsmState::Drawing { .. }, SelectToolMessage::PointerOutsideViewport { .. }) => { - // Auto-panning - if let Some(shift) = tool_data.auto_panning.shift_viewport(input, viewport, responses) { - tool_data.drag_start += shift; - } + let _ = tool_data.auto_panning.shift_viewport(input, viewport, responses); self } @@ -1389,7 +1502,7 @@ impl Fsm for SelectToolFsmState { if !has_dragged && input.keyboard.key(remove_from_selection) && tool_data.layer_selected_on_start.is_none() { // When you click on the layer with remove from selection key (shift) pressed, we deselect all nodes that are children. - let quad = tool_data.selection_quad(); + let quad = tool_data.selection_quad(document); let intersection = document.intersect_quad_no_artboards(quad, viewport); if let Some(path) = intersection.last() { @@ -1448,12 +1561,12 @@ impl Fsm for SelectToolFsmState { if let Some(start) = tool_data.pivot_gizmo_start { let offset = if tool_data.pivot_gizmo.pivot_disconnected() { - tool_data.drag_current - start + tool_data.drag_state.current_doc - start } else { Default::default() }; if let Some(v) = tool_data.pivot_gizmo.pivot.pivot.as_mut() { - *v += offset; + *v += document.metadata().document_to_viewport.transform_vector2(offset); } } tool_data.pivot_gizmo_start = None; @@ -1468,7 +1581,7 @@ impl Fsm for SelectToolFsmState { SelectToolFsmState::ResizingBounds | SelectToolFsmState::SkewingBounds { .. } | SelectToolFsmState::RotatingBounds | SelectToolFsmState::DraggingPivot, SelectToolMessage::DragStop { .. } | SelectToolMessage::Enter, ) => { - let drag_too_small = input.mouse.position.distance(tool_data.drag_start) < 10. * f64::EPSILON; + let drag_too_small = input.mouse.position.distance(tool_data.drag_start_viewport(document)) < 10. * f64::EPSILON; let response = if drag_too_small { DocumentMessage::AbortTransaction } else { DocumentMessage::EndTransaction }; let pivot_gizmo = tool_data.pivot_gizmo(); @@ -1489,10 +1602,10 @@ impl Fsm for SelectToolFsmState { SelectToolFsmState::Ready { selection } } (SelectToolFsmState::Drawing { selection_shape, .. }, SelectToolMessage::DragStop { remove_from_selection }) => { - let quad = tool_data.selection_quad(); + let quad = tool_data.selection_quad(document); let selection_mode = match tool_action_data.preferences.get_selection_mode() { - SelectionMode::Directional => tool_data.calculate_selection_mode_from_direction(), + SelectionMode::Directional => tool_data.calculate_selection_mode_from_direction(document), selection_mode => selection_mode, }; diff --git a/editor/src/messages/tool/transform_layer/transform_layer_message_handler.rs b/editor/src/messages/tool/transform_layer/transform_layer_message_handler.rs index 5c784b556e..df1bb6f8c8 100644 --- a/editor/src/messages/tool/transform_layer/transform_layer_message_handler.rs +++ b/editor/src/messages/tool/transform_layer/transform_layer_message_handler.rs @@ -3,7 +3,6 @@ use crate::messages::input_mapper::utility_types::input_mouse::{DocumentPosition use crate::messages::portfolio::document::overlays::utility_functions::text_width; use crate::messages::portfolio::document::overlays::utility_types::{OverlayProvider, Pivot}; use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier; -use crate::messages::portfolio::document::utility_types::misc::PTZ; use crate::messages::portfolio::document::utility_types::transformation::{Axis, OriginalTransforms, Selected, TransformOperation, TransformType, Typing}; use crate::messages::prelude::*; use crate::messages::tool::common_functionality::pivot::{PivotGizmo, PivotGizmoType}; @@ -74,15 +73,14 @@ pub struct TransformLayerMessageHandler { slow: bool, layer_bounding_box: Quad, typing: Typing, - mouse_position: ViewportPosition, - start_mouse: ViewportPosition, + mouse_position: DocumentPosition, + start_mouse: DocumentPosition, original_transforms: OriginalTransforms, pivot_gizmo: PivotGizmo, pivot: ViewportPosition, path_bounds: Option<[DVec2; 2]>, local_mouse_start: DocumentPosition, grab_target: DocumentPosition, - ptz: PTZ, initial_transform: DAffine2, operation_count: usize, was_grabbing: bool, @@ -93,7 +91,7 @@ pub struct TransformLayerMessageHandler { grs_pen_handle: bool, // Path tool (ghost outlines showing pre-transform geometry) - ghost_outline: Vec<(Vec, DAffine2)>, + ghost_outline: Vec<(Vec, LayerNodeIdentifier)>, } #[message_handler_data] @@ -189,10 +187,11 @@ impl MessageHandler> for } } - *mouse_position = input.mouse.position; - *start_mouse = input.mouse.position; + let mouse_doc = document.metadata().document_to_viewport.inverse().transform_point2(input.mouse.position); + *mouse_position = mouse_doc; + *start_mouse = mouse_doc; *transform = document_to_viewport; - self.local_mouse_start = document.metadata().document_to_viewport.inverse().transform_point2(input.mouse.position); + self.local_mouse_start = mouse_doc; selected.original_transforms.clear(); @@ -207,8 +206,8 @@ impl MessageHandler> for } if using_path_tool { - for (outline, transform) in &self.ghost_outline { - overlay_context.outline(outline.iter(), *transform, Some(COLOR_OVERLAY_GRAY)); + for (outline, layer) in &self.ghost_outline { + overlay_context.outline(outline.iter(), document.metadata().transform_to_viewport(*layer), Some(COLOR_OVERLAY_GRAY)); } } @@ -357,8 +356,9 @@ impl MessageHandler> for self.last_point = last_point; self.handle = handle; self.grs_pen_handle = true; - self.mouse_position = input.mouse.position; - self.start_mouse = input.mouse.position; + let mouse_doc = document.metadata().document_to_viewport.inverse().transform_point2(input.mouse.position); + self.mouse_position = mouse_doc; + self.start_mouse = mouse_doc; let top_left = DVec2::new(last_point.x, handle.y); let bottom_right = DVec2::new(handle.x, last_point.y); @@ -369,7 +369,7 @@ impl MessageHandler> for self.grab_target = document.metadata().document_to_viewport.inverse().transform_point2(handle); let pivot = last_point; self.state.document_space_pivot = document.metadata().document_to_viewport.inverse().transform_point2(pivot); - self.local_mouse_start = document.metadata().document_to_viewport.inverse().transform_point2(self.start_mouse); + self.local_mouse_start = mouse_doc; self.handle = handle; // Operation-specific logic @@ -520,12 +520,6 @@ impl MessageHandler> for } TransformLayerMessage::PointerMove { slow_key, increments_key } => { self.slow = input.keyboard.get(slow_key as usize); - let old_ptz = self.ptz; - self.ptz = document.document_ptz; - if old_ptz != self.ptz { - self.mouse_position = input.mouse.position; - return; - } let new_increments = input.keyboard.get(increments_key as usize); if new_increments != self.state.is_rounded_to_intervals { @@ -533,19 +527,22 @@ impl MessageHandler> for self.transform_operation.apply_transform_operation(&mut selected, &self.state, document); } + let current_mouse_doc = document_to_viewport.inverse().transform_point2(input.mouse.position); + if self.typing.digits.is_empty() || !self.transform_operation.can_begin_typing() { match self.transform_operation { TransformOperation::None => {} TransformOperation::Grabbing(translation) => { - let delta_pos = input.mouse.position - self.mouse_position; - let delta_pos = (self.initial_transform * document_to_viewport.inverse()).transform_vector2(delta_pos); - let delta_viewport = if self.slow { delta_pos / SLOWING_DIVISOR } else { delta_pos }; - let delta_scaled = delta_viewport / document_to_viewport.y_axis.length(); // Values are local to the viewport but scaled so values are relative to the current scale. + let delta_doc = current_mouse_doc - self.mouse_position; + let delta_pos = self.initial_transform.transform_vector2(delta_doc); + let delta_pos = if self.slow { delta_pos / SLOWING_DIVISOR } else { delta_pos }; + let delta_scaled = delta_pos / document_to_viewport.y_axis.length(); self.transform_operation = TransformOperation::Grabbing(translation.increment_amount(delta_scaled)); self.transform_operation.apply_transform_operation(&mut selected, &self.state, document); } TransformOperation::Rotating(rotation) => { - let start_offset = self.state.pivot_viewport(document) - self.mouse_position; + let prev_mouse_viewport = document_to_viewport.transform_point2(self.mouse_position); + let start_offset = self.state.pivot_viewport(document) - prev_mouse_viewport; let end_offset = self.state.pivot_viewport(document) - input.mouse.position; let angle = start_offset.angle_to(end_offset); @@ -556,9 +553,10 @@ impl MessageHandler> for } TransformOperation::Scaling(mut scale) => { let axis_constraint = scale.constraint; - let to_mouse_final = self.mouse_position - self.state.pivot_viewport(document); + let prev_mouse_viewport = document_to_viewport.transform_point2(self.mouse_position); + let to_mouse_final = prev_mouse_viewport - self.state.pivot_viewport(document); let to_mouse_final_old = input.mouse.position - self.state.pivot_viewport(document); - let to_mouse_start = self.start_mouse - self.state.pivot_viewport(document); + let to_mouse_start = document_to_viewport.transform_point2(self.local_mouse_start) - self.state.pivot_viewport(document); let to_mouse_final = self.state.project_onto_constrained(to_mouse_final, axis_constraint); let to_mouse_final_old = self.state.project_onto_constrained(to_mouse_final_old, axis_constraint); @@ -580,7 +578,7 @@ impl MessageHandler> for }; } - self.mouse_position = input.mouse.position; + self.mouse_position = current_mouse_doc; } TransformLayerMessage::SelectionChanged => { let target_layers = document.network_interface.selected_nodes().selected_layers(document.metadata()).collect(); @@ -650,13 +648,12 @@ impl TransformLayerMessageHandler { self.transform_operation.hints(responses, self.state.is_transforming_in_local_space); } - fn set_ghost_outline(ghost_outline: &mut Vec<(Vec, DAffine2)>, shape_editor: &ShapeState, document: &DocumentMessageHandler) { + fn set_ghost_outline(ghost_outline: &mut Vec<(Vec, LayerNodeIdentifier)>, shape_editor: &ShapeState, document: &DocumentMessageHandler) { ghost_outline.clear(); for &layer in shape_editor.selected_shape_state.keys() { // We probably need to collect here let outline = document.metadata().layer_with_free_points_outline(layer).cloned().collect(); - let transform = document.metadata().transform_to_viewport(layer); - ghost_outline.push((outline, transform)); + ghost_outline.push((outline, layer)); } } } diff --git a/editor/src/messages/tool/utility_types.rs b/editor/src/messages/tool/utility_types.rs index c54f3c1cd3..7f6943505e 100644 --- a/editor/src/messages/tool/utility_types.rs +++ b/editor/src/messages/tool/utility_types.rs @@ -14,6 +14,7 @@ use crate::messages::preferences::PreferencesMessageHandler; use crate::messages::prelude::*; use crate::messages::tool::common_functionality::shapes::shape_utility::ShapeType; use crate::node_graph_executor::NodeGraphExecutor; +use glam::DVec2; use graphene_std::raster::color::Color; use std::borrow::Cow; use std::fmt::{self, Debug}; @@ -163,6 +164,45 @@ pub struct EventToMessageMap { pub overlay_provider: Option, } +#[derive(Clone, Copy, Debug, Default, PartialEq)] +pub struct DragState { + pub start_doc: DVec2, + pub current_doc: DVec2, + pub pivot_doc: Option, +} + +impl DragState { + pub fn new(document: &DocumentMessageHandler, viewport_position: DVec2) -> Self { + let position = document.metadata().document_to_viewport.inverse().transform_point2(viewport_position); + Self { + start_doc: position, + current_doc: position, + pivot_doc: None, + } + } + + pub fn with_current_viewport(mut self, document: &DocumentMessageHandler, viewport_position: DVec2) -> Self { + self.set_current_viewport(document, viewport_position); + self + } + + pub fn set_current_viewport(&mut self, document: &DocumentMessageHandler, viewport_position: DVec2) { + self.current_doc = document.metadata().document_to_viewport.inverse().transform_point2(viewport_position); + } + + pub fn start_viewport(&self, document: &DocumentMessageHandler) -> DVec2 { + document.metadata().document_to_viewport.transform_point2(self.start_doc) + } + + pub fn current_viewport(&self, document: &DocumentMessageHandler) -> DVec2 { + document.metadata().document_to_viewport.transform_point2(self.current_doc) + } + + pub fn viewport_delta(&self, document: &DocumentMessageHandler) -> DVec2 { + document.metadata().document_to_viewport.transform_vector2(self.current_doc - self.start_doc) + } +} + pub trait ToolTransition { fn event_to_message_map(&self) -> EventToMessageMap;