diff --git a/editor/src/messages/tool/tool_messages/path_tool.rs b/editor/src/messages/tool/tool_messages/path_tool.rs index 9b46386e6e..db56ab237f 100644 --- a/editor/src/messages/tool/tool_messages/path_tool.rs +++ b/editor/src/messages/tool/tool_messages/path_tool.rs @@ -544,13 +544,14 @@ pub struct SlidingPointInfo { connected_segments: [SlidingSegmentData; 2], } -#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] +#[derive(Clone, Copy, Debug, Default, PartialEq)] enum PathToolFsmState { #[default] Ready, Dragging(DraggingState), Drawing { selection_shape: SelectionShapeType, + drag_start_document: DVec2, }, SlidingPoint, } @@ -618,13 +619,13 @@ impl PathToolData { PathToolFsmState::Dragging(self.dragging_state) } - pub fn selection_quad(&self, metadata: &DocumentMetadata) -> Quad { - let bbox = self.selection_box(metadata); + pub fn selection_quad(&self, drag_start_document: DVec2, metadata: &DocumentMetadata) -> Quad { + let bbox = self.selection_box(drag_start_document, metadata); Quad::from_box(bbox) } - pub fn calculate_selection_mode_from_direction(&mut self, metadata: &DocumentMetadata) -> SelectionMode { - let bbox = self.selection_box(metadata); + pub fn calculate_selection_mode_from_direction(&mut self, drag_start_document: DVec2, metadata: &DocumentMetadata) -> SelectionMode { + let bbox = self.selection_box(drag_start_document, 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 { @@ -640,15 +641,16 @@ impl PathToolData { self.selection_mode.unwrap_or(SelectionMode::Touched) } - pub fn selection_box(&self, metadata: &DocumentMetadata) -> [DVec2; 2] { - // Convert previous mouse position to viewport space first + pub fn selection_box(&self, drag_start_document: DVec2, metadata: &DocumentMetadata) -> [DVec2; 2] { + // Transform the document-anchored start point to viewport let document_to_viewport = metadata.document_to_viewport; + let start_viewport = document_to_viewport.transform_point2(drag_start_document); let previous_mouse = document_to_viewport.transform_point2(self.previous_mouse_position); - if previous_mouse == self.drag_start_pos { + if previous_mouse == start_viewport { let tolerance = DVec2::splat(SELECTION_TOLERANCE); - [self.drag_start_pos - tolerance, self.drag_start_pos + tolerance] + [start_viewport - tolerance, start_viewport + tolerance] } else { - [self.drag_start_pos, previous_mouse] + [start_viewport, previous_mouse] } } @@ -899,19 +901,21 @@ impl PathToolData { self.started_drawing_from_inside = true; + let drag_start_document = document.metadata().document_to_viewport.inverse().transform_point2(input.mouse.position); self.drag_start_pos = input.mouse.position; - self.previous_mouse_position = document.metadata().document_to_viewport.inverse().transform_point2(input.mouse.position); + self.previous_mouse_position = drag_start_document; let selection_shape = if lasso_select { SelectionShapeType::Lasso } else { SelectionShapeType::Box }; - PathToolFsmState::Drawing { selection_shape } + PathToolFsmState::Drawing { selection_shape, drag_start_document } } // Start drawing else { + let drag_start_document = document.metadata().document_to_viewport.inverse().transform_point2(input.mouse.position); self.drag_start_pos = input.mouse.position; - self.previous_mouse_position = document.metadata().document_to_viewport.inverse().transform_point2(input.mouse.position); + self.previous_mouse_position = drag_start_document; let selection_shape = if lasso_select { SelectionShapeType::Lasso } else { SelectionShapeType::Box }; - PathToolFsmState::Drawing { selection_shape } + PathToolFsmState::Drawing { selection_shape, drag_start_document } } } @@ -1888,22 +1892,24 @@ impl Fsm for PathToolFsmState { overlay_context.outline(outline, layer_to_viewport, Some(color)); } } - Self::Drawing { selection_shape } => { + Self::Drawing { selection_shape, drag_start_document } => { let fill_color = Some(COLOR_OVERLAY_BLUE_05); 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, document.metadata()), selection_mode => selection_mode, }; - let quad = tool_data.selection_quad(document.metadata()); + let quad = tool_data.selection_quad(drag_start_document, document.metadata()); let select_segments = tool_options.path_editing_mode.segment_editing_mode; let select_points = tool_options.path_editing_mode.point_editing_mode; 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 document_to_viewport = document.metadata().document_to_viewport; + let start_viewport = document_to_viewport.transform_point2(drag_start_document); + let previous_mouse = document_to_viewport.transform_point2(tool_data.previous_mouse_position); + let bbox = Rect::new(start_viewport.x, start_viewport.y, previous_mouse.x, previous_mouse.y).abs(); shape_editor.get_inside_points_and_segments( &document.network_interface, SelectionShape::Box(bbox), @@ -2030,7 +2036,7 @@ impl Fsm for PathToolFsmState { ) } ( - PathToolFsmState::Drawing { selection_shape }, + PathToolFsmState::Drawing { selection_shape, drag_start_document }, PathToolMessage::PointerMove { equidistant, toggle_colinear, @@ -2080,7 +2086,7 @@ impl Fsm for PathToolFsmState { ]; tool_data.auto_panning.setup_by_mouse_position(input, viewport, &messages, responses); - PathToolFsmState::Drawing { selection_shape } + PathToolFsmState::Drawing { selection_shape, drag_start_document } } ( PathToolFsmState::Dragging(_), @@ -2241,13 +2247,20 @@ impl Fsm for PathToolFsmState { self } - (PathToolFsmState::Drawing { selection_shape: selection_type }, PathToolMessage::PointerOutsideViewport { .. }) => { + ( + PathToolFsmState::Drawing { + selection_shape: selection_type, + drag_start_document, + }, + 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.auto_panning.shift_viewport(input, viewport, responses); - PathToolFsmState::Drawing { selection_shape: selection_type } + PathToolFsmState::Drawing { + selection_shape: selection_type, + drag_start_document, + } } (PathToolFsmState::Dragging(dragging_state), PathToolMessage::PointerOutsideViewport { .. }) => { // Auto-panning @@ -2299,7 +2312,7 @@ impl Fsm for PathToolFsmState { state } - (PathToolFsmState::Drawing { selection_shape }, PathToolMessage::Enter { extend_selection, shrink_selection }) => { + (PathToolFsmState::Drawing { selection_shape, drag_start_document }, PathToolMessage::Enter { extend_selection, shrink_selection }) => { let extend_selection = input.keyboard.get(extend_selection as usize); let shrink_selection = input.keyboard.get(shrink_selection as usize); @@ -2313,17 +2326,18 @@ 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 { + let start_viewport = document_to_viewport.transform_point2(drag_start_document); + if start_viewport == previous_mouse { responses.add(NodeGraphMessage::SelectedNodesSet { nodes: vec![] }); } else { 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, document.metadata()), selection_mode => selection_mode, }; 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 bbox = Rect::new(start_viewport.x, start_viewport.y, previous_mouse.x, previous_mouse.y).abs(); shape_editor.select_all_in_shape( &document.network_interface, @@ -2336,16 +2350,18 @@ impl Fsm for PathToolFsmState { selection_mode, ); } - SelectionShapeType::Lasso => shape_editor.select_all_in_shape( - &document.network_interface, - SelectionShape::Lasso(&tool_data.lasso_polygon), - selection_change, - tool_options.path_overlay_mode, - tool_data.frontier_handles_info.as_ref(), - tool_options.path_editing_mode.segment_editing_mode, - tool_options.path_editing_mode.point_editing_mode, - selection_mode, - ), + SelectionShapeType::Lasso => { + shape_editor.select_all_in_shape( + &document.network_interface, + SelectionShape::Lasso(&tool_data.lasso_polygon), + selection_change, + tool_options.path_overlay_mode, + tool_data.frontier_handles_info.as_ref(), + tool_options.path_editing_mode.segment_editing_mode, + tool_options.path_editing_mode.point_editing_mode, + selection_mode, + ); + } } } @@ -2388,7 +2404,7 @@ impl Fsm for PathToolFsmState { PathToolFsmState::Ready } // Mouse up - (PathToolFsmState::Drawing { selection_shape }, PathToolMessage::DragStop { extend_selection, shrink_selection }) => { + (PathToolFsmState::Drawing { selection_shape, drag_start_document }, PathToolMessage::DragStop { extend_selection, shrink_selection }) => { let extend_selection = input.keyboard.get(extend_selection as usize); let shrink_selection = input.keyboard.get(shrink_selection as usize); @@ -2404,12 +2420,13 @@ impl Fsm for PathToolFsmState { let previous_mouse = document_to_viewport.transform_point2(tool_data.previous_mouse_position); 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, document.metadata()), selection_mode => selection_mode, }; tool_data.started_drawing_from_inside = false; - if tool_data.drag_start_pos.distance(previous_mouse) < 1e-8 { + let start_viewport = document_to_viewport.transform_point2(drag_start_document); + if start_viewport.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()); @@ -2420,7 +2437,7 @@ 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 bbox = Rect::new(start_viewport.x, start_viewport.y, previous_mouse.x, previous_mouse.y).abs(); shape_editor.select_all_in_shape( &document.network_interface, @@ -2681,7 +2698,7 @@ impl Fsm for PathToolFsmState { let mut end_point_id = None; // Get the merged layer's transform to convert local positions to document space - let layer_transform = document.metadata().transform_to_document(layer); + let layer_transform = document.metadata().transform_to_document_if_feeds(layer, &document.network_interface); for (i, &local_pos) in positions.iter().enumerate() { // Transform the local position to document space for comparison @@ -2740,7 +2757,7 @@ impl Fsm for PathToolFsmState { let Some(old_vector) = document.network_interface.compute_modified_vector(layer) else { continue }; // Also get the transform node that is applied on the layer if it exists - let transform = document.metadata().transform_to_document(layer); + let transform = document.metadata().transform_to_document_if_feeds(layer, &document.network_interface); let mut new_vector = Vector::default(); diff --git a/editor/src/messages/tool/tool_messages/select_tool.rs b/editor/src/messages/tool/tool_messages/select_tool.rs index 61012e6a0c..9dee0a74c2 100644 --- a/editor/src/messages/tool/tool_messages/select_tool.rs +++ b/editor/src/messages/tool/tool_messages/select_tool.rs @@ -344,7 +344,7 @@ impl ToolTransition for SelectTool { } } -#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] +#[derive(Clone, Copy, Debug, PartialEq)] enum SelectToolFsmState { Ready { selection: NestedSelectionBehavior, @@ -352,6 +352,7 @@ enum SelectToolFsmState { Drawing { selection_shape: SelectionShapeType, has_drawn: bool, + drag_start_document: DVec2, }, Dragging { axis: Axis, @@ -418,13 +419,13 @@ impl SelectToolData { } } - pub fn selection_quad(&self) -> Quad { - let bbox = self.selection_box(); + pub fn selection_quad(&self, drag_start_document: DVec2, metadata: &DocumentMetadata) -> Quad { + let bbox = self.selection_box(drag_start_document, 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, drag_start_document: DVec2, metadata: &DocumentMetadata) -> SelectionMode { + let bbox: [DVec2; 2] = self.selection_box(drag_start_document, 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,16 @@ 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, drag_start_document: DVec2, metadata: &DocumentMetadata) -> [DVec2; 2] { + // Transform the document-anchored start point to viewport + let start_viewport = metadata.document_to_viewport.transform_point2(drag_start_document); + let roundtrip_epsilon = 10. * f64::EPSILON; + + if self.drag_current.distance_squared(start_viewport) <= roundtrip_epsilon.powi(2) { 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] } } @@ -921,13 +926,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 has started drawing + if let Self::Drawing { + selection_shape, + drag_start_document, + has_drawn: true, + .. + } = 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(drag_start_document, 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(drag_start_document, document.metadata()), SelectionMode::Touched => SelectionMode::Touched, SelectionMode::Enclosed => SelectionMode::Enclosed, }; @@ -1121,7 +1132,13 @@ impl Fsm for SelectToolFsmState { } } else { let selection_shape = if input.keyboard.key(lasso_select) { SelectionShapeType::Lasso } else { SelectionShapeType::Box }; - SelectToolFsmState::Drawing { selection_shape, has_drawn: false } + // Anchor the drag start to document coordinates so panning/zooming doesn't move the start point + let drag_start_document = document.metadata().document_to_viewport.inverse().transform_point2(tool_data.drag_start); + SelectToolFsmState::Drawing { + selection_shape, + has_drawn: false, + drag_start_document, + } } }; tool_data.non_duplicated_layers = None; @@ -1270,7 +1287,14 @@ impl Fsm for SelectToolFsmState { SelectToolFsmState::DraggingPivot } - (SelectToolFsmState::Drawing { selection_shape, has_drawn }, SelectToolMessage::PointerMove { modifier_keys }) => { + ( + SelectToolFsmState::Drawing { + selection_shape, + has_drawn, + drag_start_document, + }, + SelectToolMessage::PointerMove { modifier_keys }, + ) => { if !has_drawn { responses.add(ToolMessage::UpdateHints); } @@ -1289,7 +1313,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 { + selection_shape, + has_drawn: true, + drag_start_document, + } } (SelectToolFsmState::Ready { .. }, SelectToolMessage::PointerMove { .. }) => { let dragging_bounds = tool_data @@ -1361,10 +1389,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 } @@ -1385,8 +1411,9 @@ 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 drag_start_document = document.metadata().document_to_viewport.inverse().transform_point2(tool_data.drag_start); + let quad = tool_data.selection_quad(drag_start_document, 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 @@ -1484,11 +1511,16 @@ 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(); + ( + SelectToolFsmState::Drawing { + selection_shape, drag_start_document, .. + }, + SelectToolMessage::DragStop { remove_from_selection }, + ) => { + let quad = tool_data.selection_quad(drag_start_document, 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(drag_start_document, document.metadata()), selection_mode => selection_mode, };