From 5aae6090174ff711cc2a3993b2e490c2524d1221 Mon Sep 17 00:00:00 2001 From: krVatsal Date: Wed, 4 Feb 2026 23:49:36 +0530 Subject: [PATCH 1/2] fixed Box selection with the Select tool and Path tool to follow canvas while panning --- .../tool/tool_messages/select_tool.rs | 47 ++++++++++++------- 1 file changed, 29 insertions(+), 18 deletions(-) diff --git a/editor/src/messages/tool/tool_messages/select_tool.rs b/editor/src/messages/tool/tool_messages/select_tool.rs index e5738e7770..fdbbd8057b 100644 --- a/editor/src/messages/tool/tool_messages/select_tool.rs +++ b/editor/src/messages/tool/tool_messages/select_tool.rs @@ -402,6 +402,7 @@ struct SelectToolData { snap_candidates: Vec, auto_panning: AutoPanning, drag_start_center: ViewportPosition, + drag_start_document: Option, } impl SelectToolData { @@ -418,13 +419,13 @@ impl SelectToolData { } } - pub fn selection_quad(&self) -> Quad { - let bbox = self.selection_box(); + pub fn selection_quad(&self, metadata: &DocumentMetadata) -> Quad { + let bbox = self.selection_box(metadata); 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, metadata: &DocumentMetadata) -> SelectionMode { + let bbox: [DVec2; 2] = self.selection_box(metadata); let above_threshold = bbox[1].distance_squared(bbox[0]) > DRAG_DIRECTION_MODE_DETERMINATION_THRESHOLD.powi(2); if self.selection_mode.is_none() && above_threshold { @@ -440,12 +441,19 @@ 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, metadata: &DocumentMetadata) -> [DVec2; 2] { + // If we have a document-anchored start point, transform it to viewport + let start_viewport = if let Some(start_doc) = self.drag_start_document { + metadata.document_to_viewport.transform_point2(start_doc) + } else { + self.drag_start + }; + + if self.drag_current == start_viewport { let tolerance = DVec2::splat(SELECTION_TOLERANCE); - [self.drag_start - tolerance, self.drag_start + tolerance] + [start_viewport - tolerance, start_viewport + tolerance] } else { - [self.drag_start, self.drag_current] + [start_viewport, self.drag_current] } } @@ -938,10 +946,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.metadata()); 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.metadata()), SelectionMode::Touched => SelectionMode::Touched, SelectionMode::Enclosed => SelectionMode::Enclosed, }; @@ -1140,6 +1148,8 @@ impl Fsm for SelectToolFsmState { } } else { let selection_shape = if input.keyboard.key(lasso_select) { SelectionShapeType::Lasso } else { SelectionShapeType::Box }; + // Anchor the drag start to document coordinates so panning/zooming doesn't move the start point + tool_data.drag_start_document = Some(document.metadata().document_to_viewport.inverse().transform_point2(tool_data.drag_start)); SelectToolFsmState::Drawing { selection_shape, has_drawn: false } } }; @@ -1380,10 +1390,8 @@ 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; - } + // Auto-panning - the start is anchored to document space, so no need to shift `drag_start` here + let _ = tool_data.auto_panning.shift_viewport(input, viewport, responses); self } @@ -1404,8 +1412,8 @@ 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 intersection = document.intersect_quad_no_artboards(quad, viewport); + let quad = tool_data.selection_quad(document.metadata()); + let intersection: Vec<_> = document.intersect_quad_no_artboards(quad, viewport).collect(); if let Some(path) = intersection.last() { let replacement_selected_layers: Vec<_> = document @@ -1504,10 +1512,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.metadata()); 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.metadata()), selection_mode => selection_mode, }; @@ -1572,6 +1580,9 @@ impl Fsm for SelectToolFsmState { tool_data.lasso_polygon.clear(); + // Clear the document-anchored drag start when finishing drawing + tool_data.drag_start_document = None; + responses.add(OverlaysMessage::Draw); let selection = tool_data.nested_selection_behavior; From b7eb431e520c2b8a8578db66d7277fa7cce829c1 Mon Sep 17 00:00:00 2001 From: krVatsal Date: Sat, 14 Feb 2026 12:19:21 +0530 Subject: [PATCH 2/2] simplified the approach and fixed tiny square drawing problem --- .../tool/tool_messages/select_tool.rs | 238 ++++++++++++------ 1 file changed, 167 insertions(+), 71 deletions(-) diff --git a/editor/src/messages/tool/tool_messages/select_tool.rs b/editor/src/messages/tool/tool_messages/select_tool.rs index fdbbd8057b..1ea6026ffc 100644 --- a/editor/src/messages/tool/tool_messages/select_tool.rs +++ b/editor/src/messages/tool/tool_messages/select_tool.rs @@ -344,28 +344,68 @@ impl ToolTransition for SelectTool { } } +#[derive(Clone, Copy, Debug, PartialEq)] +pub struct SelectToolDragStart { + pub viewport: DVec2, + pub document: DVec2, +} + +impl Eq for SelectToolDragStart {} + +impl std::hash::Hash for SelectToolDragStart { + fn hash(&self, state: &mut H) { + self.viewport.x.to_bits().hash(state); + self.viewport.y.to_bits().hash(state); + self.document.x.to_bits().hash(state); + self.document.y.to_bits().hash(state); + } +} + #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] enum SelectToolFsmState { Ready { selection: NestedSelectionBehavior, }, Drawing { + drag_start: SelectToolDragStart, selection_shape: SelectionShapeType, has_drawn: bool, }, Dragging { + drag_start: SelectToolDragStart, axis: Axis, using_compass: bool, has_dragged: bool, deepest: bool, remove: bool, }, - ResizingBounds, + ResizingBounds { + drag_start: SelectToolDragStart, + }, SkewingBounds { + drag_start: SelectToolDragStart, skew: Key, }, - RotatingBounds, - DraggingPivot, + RotatingBounds { + drag_start: SelectToolDragStart, + }, + DraggingPivot { + drag_start: SelectToolDragStart, + }, +} + +impl SelectToolFsmState { + pub fn drag_start(&self) -> Option<&SelectToolDragStart> { + match self { + Self::Drawing { drag_start, .. } + | Self::Dragging { drag_start, .. } + | Self::ResizingBounds { drag_start } + | Self::SkewingBounds { drag_start, .. } + | Self::RotatingBounds { drag_start } + | Self::DraggingPivot { drag_start } => Some(drag_start), + _ => None, + } + } } impl Default for SelectToolFsmState { @@ -377,7 +417,6 @@ impl Default for SelectToolFsmState { #[derive(Clone, Debug, Default)] struct SelectToolData { - drag_start: ViewportPosition, drag_current: ViewportPosition, lasso_polygon: Vec, selection_mode: Option, @@ -402,7 +441,6 @@ struct SelectToolData { snap_candidates: Vec, auto_panning: AutoPanning, drag_start_center: ViewportPosition, - drag_start_document: Option, } impl SelectToolData { @@ -419,13 +457,13 @@ impl SelectToolData { } } - pub fn selection_quad(&self, metadata: &DocumentMetadata) -> Quad { - let bbox = self.selection_box(metadata); + pub fn selection_quad(&self, drag_start: SelectToolDragStart, metadata: &DocumentMetadata) -> Quad { + let bbox = self.selection_box(drag_start, metadata); Quad::from_box(bbox) } - pub fn calculate_selection_mode_from_direction(&mut self, metadata: &DocumentMetadata) -> SelectionMode { - let bbox: [DVec2; 2] = self.selection_box(metadata); + pub fn calculate_selection_mode_from_direction(&mut self, drag_start: SelectToolDragStart, metadata: &DocumentMetadata) -> SelectionMode { + let bbox: [DVec2; 2] = self.selection_box(drag_start, metadata); let above_threshold = bbox[1].distance_squared(bbox[0]) > DRAG_DIRECTION_MODE_DETERMINATION_THRESHOLD.powi(2); if self.selection_mode.is_none() && above_threshold { @@ -441,13 +479,9 @@ impl SelectToolData { self.selection_mode.unwrap_or(SelectionMode::Touched) } - pub fn selection_box(&self, metadata: &DocumentMetadata) -> [DVec2; 2] { - // If we have a document-anchored start point, transform it to viewport - let start_viewport = if let Some(start_doc) = self.drag_start_document { - metadata.document_to_viewport.transform_point2(start_doc) - } else { - self.drag_start - }; + pub fn selection_box(&self, drag_start: SelectToolDragStart, metadata: &DocumentMetadata) -> [DVec2; 2] { + // Transform the document-anchored start point to viewport + let start_viewport = metadata.document_to_viewport.transform_point2(drag_start.document); if self.drag_current == start_viewport { let tolerance = DVec2::splat(SELECTION_TOLERANCE); @@ -474,7 +508,7 @@ impl SelectToolData { } /// 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) { + fn start_duplicates(&mut self, document: &mut DocumentMessageHandler, drag_start: SelectToolDragStart, responses: &mut VecDeque) { self.non_duplicated_layers = Some(self.layers_dragging.clone()); let mut new_dragging = Vec::new(); @@ -490,9 +524,10 @@ impl SelectToolData { let Some(parent) = layer.parent(document.metadata()) else { continue }; // Moves the layer back to its starting position. + let start_viewport = document.metadata().document_to_viewport.transform_point2(drag_start.document); responses.add(GraphOperationMessage::TransformChange { layer, - transform: DAffine2::from_translation(self.drag_start - self.drag_current), + transform: DAffine2::from_translation(start_viewport - self.drag_current), transform_in: TransformIn::Viewport, skip_rerender: true, }); @@ -529,7 +564,7 @@ impl SelectToolData { } /// Removes the duplicated layers. Called when Alt is released and the layers have previously been duplicated. - fn stop_duplicates(&mut self, document: &DocumentMessageHandler, responses: &mut VecDeque) { + fn stop_duplicates(&mut self, document: &DocumentMessageHandler, drag_start: SelectToolDragStart, responses: &mut VecDeque) { let Some(original) = self.non_duplicated_layers.take() else { return; }; @@ -543,9 +578,10 @@ impl SelectToolData { } for &layer in &original { + let start_viewport = document.metadata().document_to_viewport.transform_point2(drag_start.document); responses.add(GraphOperationMessage::TransformChange { layer, - transform: DAffine2::from_translation(self.drag_current - self.drag_start), + transform: DAffine2::from_translation(self.drag_current - start_viewport), transform_in: TransformIn::Viewport, skip_rerender: true, }); @@ -568,13 +604,20 @@ impl SelectToolData { self.layers_dragging = original; } - fn state_from_pivot_gizmo(&self, mouse: DVec2) -> Option { + fn state_from_pivot_gizmo(&self, mouse: DVec2, drag_start: SelectToolDragStart) -> Option { match self.pivot_gizmo.state.gizmo_type { - PivotGizmoType::Pivot if self.pivot_gizmo.state.is_pivot() => self.pivot_gizmo.pivot.is_over(mouse).then_some(SelectToolFsmState::DraggingPivot), + PivotGizmoType::Pivot if self.pivot_gizmo.state.is_pivot() => self.pivot_gizmo.pivot.is_over(mouse).then_some(SelectToolFsmState::DraggingPivot { drag_start }), _ => None, } } + fn is_over_pivot(&self, mouse: DVec2) -> bool { + match self.pivot_gizmo.state.gizmo_type { + PivotGizmoType::Pivot if self.pivot_gizmo.state.is_pivot() => self.pivot_gizmo.pivot.is_over(mouse), + _ => false, + } + } + fn pivot_gizmo(&self) -> PivotGizmo { self.pivot_gizmo.clone() } @@ -616,6 +659,16 @@ impl Fsm for SelectToolFsmState { let ToolMessage::Select(event) = event else { return self }; match (self, event) { (_, SelectToolMessage::Overlays { context: mut overlay_context }) => { + let drag_start = match self { + Self::Drawing { drag_start, .. } + | Self::Dragging { drag_start, .. } + | Self::ResizingBounds { drag_start } + | Self::SkewingBounds { drag_start, .. } + | Self::RotatingBounds { drag_start } + | Self::DraggingPivot { drag_start } => Some(drag_start), + _ => None, + }; + tool_data.snap_manager.draw_overlays(SnapData::new(document, input, viewport), &mut overlay_context); crate::messages::tool::common_functionality::layer_origin_cross::draw_for_selected_layers(&mut overlay_context, document); @@ -777,7 +830,10 @@ impl Fsm for SelectToolFsmState { .map(|bounding_box| bounding_box.check_rotate(input.mouse.position)) .unwrap_or_default(); - let is_resizing_or_rotating = matches!(self, SelectToolFsmState::ResizingBounds | SelectToolFsmState::SkewingBounds { .. } | SelectToolFsmState::RotatingBounds); + let is_resizing_or_rotating = matches!( + self, + SelectToolFsmState::ResizingBounds { .. } | SelectToolFsmState::SkewingBounds { .. } | SelectToolFsmState::RotatingBounds { .. } + ); if overlay_context.visibility_settings.transform_cage() && let Some(bounds) = tool_data.bounding_box_manager.as_mut() @@ -919,7 +975,8 @@ impl Fsm for SelectToolFsmState { } if axis_state.is_none_or(|(axis, _)| !axis.is_constraint()) && tool_data.axis_align { - let mouse_position = mouse_position - tool_data.drag_start; + let start_viewport = drag_start.map(|s| document.metadata().document_to_viewport.transform_point2(s.document)).unwrap_or(mouse_position); + let mouse_position = mouse_position - start_viewport; 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; @@ -943,13 +1000,19 @@ impl Fsm for SelectToolFsmState { } } - // Check if the tool is in selection mode - if let Self::Drawing { selection_shape, .. } = self { + // Check if the tool is in selection mode and the pointer has moved since the initial click + if let Self::Drawing { + drag_start, + selection_shape, + has_drawn: true, + .. + } = self + { // Get the updated selection box bounds - let quad = tool_data.selection_quad(document.metadata()); + let quad = tool_data.selection_quad(drag_start, document.metadata()); let current_selection_mode = match tool_action_data.preferences.get_selection_mode() { - SelectionMode::Directional => tool_data.calculate_selection_mode_from_direction(document.metadata()), + SelectionMode::Directional => tool_data.calculate_selection_mode_from_direction(drag_start, document.metadata()), SelectionMode::Touched => SelectionMode::Touched, SelectionMode::Enclosed => SelectionMode::Enclosed, }; @@ -997,8 +1060,9 @@ impl Fsm for SelectToolFsmState { } } - if let Self::Dragging { .. } = self { - let quad = Quad::from_box([tool_data.drag_start, tool_data.drag_current]); + if let Self::Dragging { drag_start, .. } = self { + let drag_start_viewport = document.metadata().document_to_viewport.transform_point2(drag_start.document); + let quad = Quad::from_box([drag_start_viewport, 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()); @@ -1033,8 +1097,11 @@ impl Fsm for SelectToolFsmState { .. }, ) => { - tool_data.drag_start = input.mouse.position; tool_data.drag_current = input.mouse.position; + let drag_start = SelectToolDragStart { + viewport: input.mouse.position, + document: document.metadata().document_to_viewport.inverse().transform_point2(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(); @@ -1063,7 +1130,7 @@ impl Fsm for SelectToolFsmState { let show_compass = bounds.is_some_and(|quad| quad.all_sides_at_least_width(COMPASS_ROSE_HOVER_RING_DIAMETER) && quad.contains(mouse_position)); let can_grab_compass_rose = compass_rose_state.can_grab() && (show_compass || bounds.is_none()); - let state = if let Some(state) = tool_data.state_from_pivot_gizmo(input.mouse.position) { + let state = if let Some(state) = tool_data.state_from_pivot_gizmo(input.mouse.position, drag_start) { responses.add(DocumentMessage::StartTransaction); // tool_data.snap_manager.start_snap(document, input, document.bounding_boxes(), true, true); @@ -1074,10 +1141,10 @@ impl Fsm for SelectToolFsmState { // Dragging one (or two, forming a corner) of the transform cage bounding box edges else if resize { tool_data.get_snap_candidates(document, input, viewport); - SelectToolFsmState::ResizingBounds + SelectToolFsmState::ResizingBounds { drag_start } } else if skew { tool_data.get_snap_candidates(document, input, viewport); - SelectToolFsmState::SkewingBounds { skew: Key::Control } + SelectToolFsmState::SkewingBounds { drag_start, skew: Key::Control } } // Dragging the selected layers around to transform them else if can_grab_compass_rose || intersection.is_some_and(|intersection| selected.iter().any(|selected_layer| intersection.starts_with(*selected_layer, document.metadata()))) { @@ -1099,6 +1166,7 @@ impl Fsm for SelectToolFsmState { tool_data.pivot_gizmo_start = Some(tool_data.drag_current); SelectToolFsmState::Dragging { + drag_start, axis, using_compass, has_dragged: false, @@ -1108,7 +1176,7 @@ impl Fsm for SelectToolFsmState { } // Dragging near the transform cage bounding box to rotate it else if rotate { - SelectToolFsmState::RotatingBounds + SelectToolFsmState::RotatingBounds { drag_start } } // Dragging a selection box else { @@ -1140,6 +1208,7 @@ impl Fsm for SelectToolFsmState { tool_data.pivot_gizmo_start = Some(tool_data.drag_current); SelectToolFsmState::Dragging { + drag_start, axis: Axis::None, using_compass: false, has_dragged: false, @@ -1148,16 +1217,18 @@ impl Fsm for SelectToolFsmState { } } else { let selection_shape = if input.keyboard.key(lasso_select) { SelectionShapeType::Lasso } else { SelectionShapeType::Box }; - // Anchor the drag start to document coordinates so panning/zooming doesn't move the start point - tool_data.drag_start_document = Some(document.metadata().document_to_viewport.inverse().transform_point2(tool_data.drag_start)); - SelectToolFsmState::Drawing { selection_shape, has_drawn: false } + SelectToolFsmState::Drawing { + drag_start, + selection_shape, + has_drawn: false, + } } }; tool_data.non_duplicated_layers = None; state } - (SelectToolFsmState::DraggingPivot, SelectToolMessage::Abort) => { + (SelectToolFsmState::DraggingPivot { .. }, SelectToolMessage::Abort) => { responses.add(DocumentMessage::AbortTransaction); let selection = tool_data.nested_selection_behavior; @@ -1165,6 +1236,7 @@ impl Fsm for SelectToolFsmState { } ( SelectToolFsmState::Dragging { + drag_start, axis, using_compass, has_dragged, @@ -1177,9 +1249,9 @@ impl Fsm for SelectToolFsmState { responses.add(ToolMessage::UpdateHints); } if input.keyboard.key(modifier_keys.duplicate) && tool_data.non_duplicated_layers.is_none() { - tool_data.start_duplicates(document, responses); + tool_data.start_duplicates(document, drag_start, responses); } else if !input.keyboard.key(modifier_keys.duplicate) && tool_data.non_duplicated_layers.is_some() { - tool_data.stop_duplicates(document, responses); + tool_data.stop_duplicates(document, drag_start, responses); } tool_data.axis_align = input.keyboard.key(modifier_keys.axis_align); @@ -1189,7 +1261,7 @@ 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, current) = (document.metadata().document_to_viewport.transform_point2(drag_start.document), tool_data.drag_current); let e0 = tool_data .bounding_box_manager .as_ref() @@ -1222,6 +1294,7 @@ impl Fsm for SelectToolFsmState { tool_data.auto_panning.setup_by_mouse_position(input, viewport, &messages, responses); SelectToolFsmState::Dragging { + drag_start, axis, using_compass, has_dragged: true, @@ -1229,7 +1302,7 @@ impl Fsm for SelectToolFsmState { remove, } } - (SelectToolFsmState::ResizingBounds, SelectToolMessage::PointerMove { modifier_keys }) => { + (SelectToolFsmState::ResizingBounds { drag_start }, SelectToolMessage::PointerMove { modifier_keys }) => { if let Some(bounds) = &mut tool_data.bounding_box_manager { resize_bounds( document, @@ -1250,9 +1323,9 @@ impl Fsm for SelectToolFsmState { ]; tool_data.auto_panning.setup_by_mouse_position(input, viewport, &messages, responses); } - SelectToolFsmState::ResizingBounds + SelectToolFsmState::ResizingBounds { drag_start } } - (SelectToolFsmState::SkewingBounds { skew }, SelectToolMessage::PointerMove { .. }) => { + (SelectToolFsmState::SkewingBounds { drag_start, skew }, SelectToolMessage::PointerMove { .. }) => { if let Some(bounds) = &mut tool_data.bounding_box_manager { skew_bounds( document, @@ -1264,25 +1337,26 @@ impl Fsm for SelectToolFsmState { ToolType::Select, ); } - SelectToolFsmState::SkewingBounds { skew } + SelectToolFsmState::SkewingBounds { drag_start, skew } } - (SelectToolFsmState::RotatingBounds, SelectToolMessage::PointerMove { .. }) => { + (SelectToolFsmState::RotatingBounds { drag_start }, SelectToolMessage::PointerMove { .. }) => { if let Some(bounds) = &mut tool_data.bounding_box_manager { + let drag_start_viewport = document.metadata().document_to_viewport.transform_point2(drag_start.document); 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, ); } - SelectToolFsmState::RotatingBounds + SelectToolFsmState::RotatingBounds { drag_start } } - (SelectToolFsmState::DraggingPivot, SelectToolMessage::PointerMove { modifier_keys }) => { + (SelectToolFsmState::DraggingPivot { drag_start }, SelectToolMessage::PointerMove { modifier_keys }) => { let mouse_position = input.mouse.position; let snapped_mouse_position = mouse_position; @@ -1297,9 +1371,16 @@ impl Fsm for SelectToolFsmState { ]; tool_data.auto_panning.setup_by_mouse_position(input, viewport, &messages, responses); - SelectToolFsmState::DraggingPivot + SelectToolFsmState::DraggingPivot { drag_start } } - (SelectToolFsmState::Drawing { selection_shape, has_drawn }, SelectToolMessage::PointerMove { modifier_keys }) => { + ( + SelectToolFsmState::Drawing { + drag_start, + selection_shape, + has_drawn, + }, + SelectToolMessage::PointerMove { modifier_keys }, + ) => { if !has_drawn { responses.add(ToolMessage::UpdateHints); } @@ -1318,7 +1399,11 @@ impl Fsm for SelectToolFsmState { ]; tool_data.auto_panning.setup_by_mouse_position(input, viewport, &messages, responses); - SelectToolFsmState::Drawing { selection_shape, has_drawn: true } + SelectToolFsmState::Drawing { + drag_start, + selection_shape, + has_drawn: true, + } } (SelectToolFsmState::Ready { .. }, SelectToolMessage::PointerMove { .. }) => { let dragging_bounds = tool_data @@ -1333,7 +1418,7 @@ impl Fsm for SelectToolFsmState { .map_or(MouseCursorIcon::Default, |bounds| bounds.get_cursor(input, true, dragging_bounds, Some(tool_data.skew_edge))); // Dragging the pivot overrules the other operations - if tool_data.state_from_pivot_gizmo(input.mouse.position).is_some() { + if tool_data.is_over_pivot(input.mouse.position) { cursor = MouseCursorIcon::Move; } @@ -1350,6 +1435,7 @@ impl Fsm for SelectToolFsmState { } ( SelectToolFsmState::Dragging { + mut drag_start, axis, using_compass, has_dragged, @@ -1361,10 +1447,11 @@ impl Fsm for SelectToolFsmState { // 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; + drag_start.viewport += shift; } SelectToolFsmState::Dragging { + drag_start, axis, using_compass, has_dragged, @@ -1372,7 +1459,7 @@ impl Fsm for SelectToolFsmState { remove, } } - (SelectToolFsmState::ResizingBounds | SelectToolFsmState::SkewingBounds { .. }, SelectToolMessage::PointerOutsideViewport { .. }) => { + (SelectToolFsmState::ResizingBounds { .. } | SelectToolFsmState::SkewingBounds { .. }, SelectToolMessage::PointerOutsideViewport { .. }) => { // Auto-panning if let Some(shift) = tool_data.auto_panning.shift_viewport(input, viewport, responses) && let Some(bounds) = &mut tool_data.bounding_box_manager @@ -1383,7 +1470,7 @@ impl Fsm for SelectToolFsmState { self } - (SelectToolFsmState::DraggingPivot, SelectToolMessage::PointerOutsideViewport { .. }) => { + (SelectToolFsmState::DraggingPivot { .. }, SelectToolMessage::PointerOutsideViewport { .. }) => { // Auto-panning let _ = tool_data.auto_panning.shift_viewport(input, viewport, responses); @@ -1405,14 +1492,23 @@ impl Fsm for SelectToolFsmState { state } - (SelectToolFsmState::Dragging { has_dragged, remove, deepest, .. }, SelectToolMessage::DragStop { remove_from_selection }) => { + ( + SelectToolFsmState::Dragging { + drag_start, + has_dragged, + remove, + deepest, + .. + }, + SelectToolMessage::DragStop { remove_from_selection }, + ) => { // Deselect layer if not snap dragging responses.add(DocumentMessage::EndTransaction); tool_data.axis_align = false; 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(document.metadata()); + let quad = tool_data.selection_quad(drag_start, document.metadata()); let intersection: Vec<_> = document.intersect_quad_no_artboards(quad, viewport).collect(); if let Some(path) = intersection.last() { @@ -1488,10 +1584,13 @@ impl Fsm for SelectToolFsmState { SelectToolFsmState::Ready { selection } } ( - SelectToolFsmState::ResizingBounds | SelectToolFsmState::SkewingBounds { .. } | SelectToolFsmState::RotatingBounds | SelectToolFsmState::DraggingPivot, + state @ (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 = state + .drag_start() + .map(|drag_start| input.mouse.position.distance(drag_start.viewport) < 10. * f64::EPSILON) + .unwrap_or(true); let response = if drag_too_small { DocumentMessage::AbortTransaction } else { DocumentMessage::EndTransaction }; let pivot_gizmo = tool_data.pivot_gizmo(); @@ -1502,7 +1601,7 @@ impl Fsm for SelectToolFsmState { tool_data.axis_align = false; tool_data.snap_manager.cleanup(responses); - if !matches!(self, SelectToolFsmState::DraggingPivot) + if !matches!(state, SelectToolFsmState::DraggingPivot { .. }) && let Some(bounds) = &mut tool_data.bounding_box_manager { bounds.original_transforms.clear(); @@ -1511,11 +1610,11 @@ impl Fsm for SelectToolFsmState { let selection = tool_data.nested_selection_behavior; SelectToolFsmState::Ready { selection } } - (SelectToolFsmState::Drawing { selection_shape, .. }, SelectToolMessage::DragStop { remove_from_selection }) => { - let quad = tool_data.selection_quad(document.metadata()); + (SelectToolFsmState::Drawing { drag_start, selection_shape, .. }, SelectToolMessage::DragStop { remove_from_selection }) => { + let quad = tool_data.selection_quad(drag_start, document.metadata()); let selection_mode = match tool_action_data.preferences.get_selection_mode() { - SelectionMode::Directional => tool_data.calculate_selection_mode_from_direction(document.metadata()), + SelectionMode::Directional => tool_data.calculate_selection_mode_from_direction(drag_start, document.metadata()), selection_mode => selection_mode, }; @@ -1580,9 +1679,6 @@ impl Fsm for SelectToolFsmState { tool_data.lasso_polygon.clear(); - // Clear the document-anchored drag start when finishing drawing - tool_data.drag_start_document = None; - responses.add(OverlaysMessage::Draw); let selection = tool_data.nested_selection_behavior; @@ -1760,14 +1856,14 @@ impl Fsm for SelectToolFsmState { hint_data.send_layout(responses); } SelectToolFsmState::Drawing { .. } | SelectToolFsmState::Dragging { .. } => {} - SelectToolFsmState::ResizingBounds => { + SelectToolFsmState::ResizingBounds { .. } => { let hint_data = HintData(vec![ HintGroup(vec![HintInfo::mouse(MouseMotion::Rmb, ""), HintInfo::keys([Key::Escape], "Cancel").prepend_slash()]), HintGroup(vec![HintInfo::keys([Key::Alt], "From Pivot"), HintInfo::keys([Key::Shift], "Preserve Aspect Ratio")]), ]); hint_data.send_layout(responses); } - SelectToolFsmState::RotatingBounds => { + SelectToolFsmState::RotatingBounds { .. } => { let hint_data = HintData(vec![ HintGroup(vec![HintInfo::mouse(MouseMotion::Rmb, ""), HintInfo::keys([Key::Escape], "Cancel").prepend_slash()]), HintGroup(vec![HintInfo::keys([Key::Shift], "15° Increments")]), @@ -1781,7 +1877,7 @@ impl Fsm for SelectToolFsmState { ]); hint_data.send_layout(responses); } - SelectToolFsmState::DraggingPivot => { + SelectToolFsmState::DraggingPivot { .. } => { let hint_data = HintData(vec![HintGroup(vec![HintInfo::mouse(MouseMotion::Rmb, ""), HintInfo::keys([Key::Escape], "Cancel").prepend_slash()])]); hint_data.send_layout(responses); }