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;