diff --git a/editor/src/messages/tool/common_functionality/shape_editor.rs b/editor/src/messages/tool/common_functionality/shape_editor.rs index 5f00d97111..639464d595 100644 --- a/editor/src/messages/tool/common_functionality/shape_editor.rs +++ b/editor/src/messages/tool/common_functionality/shape_editor.rs @@ -1,5 +1,5 @@ use super::graph_modification_utils::merge_layers; -use super::snapping::{SnapCache, SnapCandidatePoint, SnapData, SnapManager, SnappedPoint}; +use super::snapping::{SnapCache, SnapCandidatePoint, SnapConstraint, SnapData, SnapManager, SnappedPoint}; use super::utility_functions::{adjust_handle_colinearity, calculate_segment_angle, restore_g1_continuity, restore_previous_handle_position}; use crate::consts::HANDLE_LENGTH_FACTOR; use crate::messages::portfolio::document::overlays::utility_functions::selected_segments_for_layer; @@ -502,7 +502,6 @@ impl ShapeState { } } - // Snap, returning a viewport delta pub fn snap( &self, snap_manager: &mut SnapManager, @@ -511,6 +510,8 @@ impl ShapeState { input: &InputPreprocessorMessageHandler, viewport: &ViewportMessageHandler, previous_mouse: DVec2, + constraint: SnapConstraint, + accumulated_offset: DVec2, ) -> DVec2 { let snap_data = SnapData::new_snap_cache(document, input, viewport, snap_cache); @@ -536,7 +537,8 @@ impl ShapeState { }; let Some(position) = selected.get_position(&vector) else { continue }; - let mut point = SnapCandidatePoint::new_source(to_document.transform_point2(position) + mouse_delta, source); + let point_position = to_document.transform_point2(position); + let mut point = SnapCandidatePoint::new_source(point_position + mouse_delta, source); if let Some(id) = selected.as_anchor() { for neighbor in vector.connected_points(id) { @@ -548,14 +550,42 @@ impl ShapeState { } } - let snapped = snap_manager.free_snap(&snap_data, &point, SnapTypeConfiguration::default()); + let snapped = match constraint { + SnapConstraint::Line { origin, direction } => { + let projected_origin = origin + (point_position - origin).project_onto(direction); + let constraint = SnapConstraint::Line { origin: projected_origin, direction }; + snap_manager.constrained_snap(&snap_data, &point, constraint, SnapTypeConfiguration::default()) + } + SnapConstraint::Direction(direction) => { + let origin = point_position - accumulated_offset; + let constraint = SnapConstraint::Line { origin, direction }; + snap_manager.constrained_snap(&snap_data, &point, constraint, SnapTypeConfiguration::default()) + } + _ => snap_manager.free_snap(&snap_data, &point, SnapTypeConfiguration::default()), + }; + if best_snapped.other_snap_better(&snapped) { offset = snapped.snapped_point_document - point.document_point + mouse_delta; best_snapped = snapped; } } } + let is_snapped = best_snapped.is_snapped(); snap_manager.update_indicator(best_snapped); + + // If no magnetic snap occurred but we have a constraint, force the constraint + if !is_snapped { + match constraint { + SnapConstraint::Direction(direction) => { + let constrained_total_offset = (accumulated_offset + mouse_delta).project_onto(direction); + offset = constrained_total_offset - accumulated_offset; + } + SnapConstraint::Line { direction, .. } if direction != DVec2::ZERO => { + offset = mouse_delta.project_onto(direction); + } + _ => {} + } + } document.metadata().document_to_viewport.transform_vector2(offset) } diff --git a/editor/src/messages/tool/tool_messages/path_tool.rs b/editor/src/messages/tool/tool_messages/path_tool.rs index dd0d2865d1..01e8ee1a05 100644 --- a/editor/src/messages/tool/tool_messages/path_tool.rs +++ b/editor/src/messages/tool/tool_messages/path_tool.rs @@ -598,6 +598,8 @@ struct PathToolData { started_drawing_from_inside: bool, first_selected_with_single_click: bool, stored_selection: Option>, + snap_offset: DVec2, + last_drill_through_click_position: Option, drill_through_cycle_index: usize, drill_through_cycle_count: usize, @@ -730,6 +732,7 @@ impl PathToolData { self.opposing_handle_lengths = None; self.drag_start_pos = input.mouse.position; + self.snap_offset = DVec2::ZERO; if input.time - self.last_click_time > DOUBLE_CLICK_MILLISECONDS { self.saved_points_before_anchor_convert_smooth_sharp.clear(); @@ -1143,50 +1146,6 @@ impl PathToolData { document.metadata().document_to_viewport.transform_vector2(snap_result.snapped_point_document - handle_position) } - 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 opposite_delta = drag_start - current_mouse; - - shape_editor.move_selected_points_and_segments(None, document, opposite_delta, false, true, false, None, false, responses); - - // Calculate the projected delta and shift the points along that delta - let delta = current_mouse - drag_start; - let axis = if delta.x.abs() >= delta.y.abs() { Axis::X } else { Axis::Y }; - self.snapping_axis = Some(axis); - let projected_delta = match axis { - Axis::X => DVec2::new(delta.x, 0.), - Axis::Y => DVec2::new(0., delta.y), - _ => DVec2::new(delta.x, 0.), - }; - - shape_editor.move_selected_points_and_segments(None, document, projected_delta, false, true, false, None, false, responses); - } - - 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 opposite_delta = drag_start - current_mouse; - let Some(axis) = self.snapping_axis else { return }; - let opposite_projected_delta = match axis { - Axis::X => DVec2::new(opposite_delta.x, 0.), - Axis::Y => DVec2::new(0., opposite_delta.y), - _ => DVec2::new(opposite_delta.x, 0.), - }; - - shape_editor.move_selected_points_and_segments(None, document, opposite_projected_delta, false, true, false, None, false, responses); - - // Calculate what actually would have been the original delta for the point, and apply that - let delta = current_mouse - drag_start; - - shape_editor.move_selected_points_and_segments(None, document, delta, false, true, false, None, false, responses); - - self.snapping_axis = None; - } - fn get_normalized_tangent(&mut self, point: PointId, segment: SegmentId, vector: &Vector) -> Option { let other_point = vector.other_point(segment, point)?; let position = ManipulatorPointId::Anchor(point).get_position(vector)?; @@ -1404,10 +1363,15 @@ impl PathToolData { .any(|point| matches!(point, ManipulatorPointId::EndHandle(_) | ManipulatorPointId::PrimaryHandle(_))); // This is where it starts snapping along axis - if snap_axis && self.snapping_axis.is_none() && !single_handle_selected { - self.start_snap_along_axis(shape_editor, document, input, responses); - } else if !snap_axis && self.snapping_axis.is_some() { - self.stop_snap_along_axis(shape_editor, document, input, responses); + if snap_axis && !single_handle_selected { + let total_delta = self.drag_start_pos - input.mouse.position; + if total_delta.x.abs() > total_delta.y.abs() { + self.snapping_axis = Some(Axis::X); + } else { + self.snapping_axis = Some(Axis::Y); + } + } else { + self.snapping_axis = None; } let document_to_viewport = document.metadata().document_to_viewport; @@ -1447,87 +1411,74 @@ impl PathToolData { viewport, ) } else { - shape_editor.snap(&mut self.snap_manager, &self.snap_cache, document, input, viewport, previous_mouse) + let constraint = if let Some(axis) = self.snapping_axis { + match axis { + Axis::X => SnapConstraint::Direction(DVec2::X), + Axis::Y => SnapConstraint::Direction(DVec2::Y), + _ => SnapConstraint::None, + } + } else { + SnapConstraint::None + }; + shape_editor.snap(&mut self.snap_manager, &self.snap_cache, document, input, viewport, previous_mouse, constraint, self.snap_offset) }; let handle_lengths = if equidistant { None } else { self.opposing_handle_lengths.take() }; let opposite = if lock_angle { None } else { self.opposite_handle_position }; - let unsnapped_delta = current_mouse - previous_mouse; 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 { - // 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 { + if self.alt_clicked_on_anchor && !self.alt_dragging_from_anchor && self.drag_start_pos.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 { + return; + }; + let Some(vector) = document.network_interface.compute_modified_vector(layer) else { return }; + let Some(point_id) = shape_editor.selected_points().next().unwrap().get_anchor(&vector) else { + return; + }; + + if vector.connected_count(point_id) == 2 { + let connected_segments: Vec = vector.all_connected(point_id).collect(); + let segment1 = connected_segments[0]; + let Some(tangent1) = self.get_normalized_tangent(point_id, segment1.segment, &vector) else { return; }; - let Some(vector) = document.network_interface.compute_modified_vector(layer) else { return }; - let Some(point_id) = shape_editor.selected_points().next().unwrap().get_anchor(&vector) else { + let segment2 = connected_segments[1]; + let Some(tangent2) = self.get_normalized_tangent(point_id, segment2.segment, &vector) else { return; }; - if vector.connected_count(point_id) == 2 { - let connected_segments: Vec = vector.all_connected(point_id).collect(); - let segment1 = connected_segments[0]; - let Some(tangent1) = self.get_normalized_tangent(point_id, segment1.segment, &vector) else { - return; - }; - let segment2 = connected_segments[1]; - let Some(tangent2) = self.get_normalized_tangent(point_id, segment2.segment, &vector) else { - return; - }; - - let delta = input.mouse.position - self.drag_start_pos; - let handle = if delta.dot(tangent1) >= delta.dot(tangent2) { - segment1.to_manipulator_point() - } else { - segment2.to_manipulator_point() - }; - - // Now change the selection to this handle - shape_editor.deselect_all_points(); - shape_editor.select_point_by_layer_and_id(handle, layer); - - responses.add(PathToolMessage::SelectionChanged); - } - } + let delta = input.mouse.position - self.drag_start_pos; + let handle = if delta.dot(tangent1) >= delta.dot(tangent2) { + segment1.to_manipulator_point() + } else { + segment2.to_manipulator_point() + }; - if self.alt_dragging_from_anchor && !equidistant && self.alt_clicked_on_anchor { - was_alt_dragging = true; - self.alt_dragging_from_anchor = false; - self.alt_clicked_on_anchor = false; - } + // Now change the selection to this handle + shape_editor.deselect_all_points(); + shape_editor.select_point_by_layer_and_id(handle, layer); - let mut skip_opposite = false; - if self.temporary_colinear_handles && !lock_angle { - shape_editor.disable_colinear_handles_state_on_selected(&document.network_interface, responses); - self.temporary_colinear_handles = false; - skip_opposite = true; + responses.add(PathToolMessage::SelectionChanged); } - shape_editor.move_selected_points_and_segments(handle_lengths, document, snapped_delta, equidistant, true, was_alt_dragging, opposite, skip_opposite, responses); - self.previous_mouse_position += document_to_viewport.inverse().transform_vector2(snapped_delta); - } else { - let Some(axis) = self.snapping_axis else { return }; - let projected_delta = match axis { - Axis::X => DVec2::new(unsnapped_delta.x, 0.), - Axis::Y => DVec2::new(0., unsnapped_delta.y), - _ => DVec2::new(unsnapped_delta.x, 0.), - }; - shape_editor.move_selected_points_and_segments(handle_lengths, document, projected_delta, equidistant, true, false, opposite, false, responses); - self.previous_mouse_position += document_to_viewport.inverse().transform_vector2(unsnapped_delta); } - // 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; + if self.alt_dragging_from_anchor && !equidistant && self.alt_clicked_on_anchor { + was_alt_dragging = true; + self.alt_dragging_from_anchor = false; + self.alt_clicked_on_anchor = false; + } - 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); - self.start_snap_along_axis(shape_editor, document, input, responses); - } + let mut skip_opposite = false; + if self.temporary_colinear_handles && !lock_angle { + shape_editor.disable_colinear_handles_state_on_selected(&document.network_interface, responses); + self.temporary_colinear_handles = false; + skip_opposite = true; } + shape_editor.move_selected_points_and_segments(handle_lengths, document, snapped_delta, equidistant, true, was_alt_dragging, opposite, skip_opposite, responses); + self.previous_mouse_position += document_to_viewport.inverse().transform_vector2(snapped_delta); + self.snap_offset += document_to_viewport.inverse().transform_vector2(snapped_delta); } fn pivot_gizmo(&self) -> PivotGizmo {