Fix the Path tool's segment insertion line overlay having hysteresis with distance away from the cursor (#2677)

* Fix hysteresis in segment insertion

* Update editor/src/messages/tool/common_functionality/shape_editor.rs

* Update editor/src/messages/tool/common_functionality/shape_editor.rs

* Fix insert point overlay

---------

Co-authored-by: Keavon Chambers <keavon@keavon.com>
This commit is contained in:
Adesh Gupta 2025-06-09 05:59:08 +05:30 committed by Keavon Chambers
parent 04d7adb867
commit 33a31b4f17
3 changed files with 12 additions and 16 deletions

View file

@ -102,7 +102,7 @@ pub const MANIPULATOR_GROUP_MARKER_SIZE: f64 = 6.;
pub const SELECTION_THRESHOLD: f64 = 10.;
pub const HIDE_HANDLE_DISTANCE: f64 = 3.;
pub const HANDLE_ROTATE_SNAP_ANGLE: f64 = 15.;
pub const SEGMENT_INSERTION_DISTANCE: f64 = 7.5;
pub const SEGMENT_INSERTION_DISTANCE: f64 = 8.;
pub const SEGMENT_OVERLAY_SIZE: f64 = 10.;
pub const HANDLE_LENGTH_FACTOR: f64 = 0.5;

View file

@ -1,4 +1,4 @@
use super::graph_modification_utils::{self, merge_layers};
use super::graph_modification_utils::merge_layers;
use super::snapping::{SnapCache, SnapCandidatePoint, SnapData, SnapManager, SnappedPoint};
use super::utility_functions::calculate_segment_angle;
use crate::consts::HANDLE_LENGTH_FACTOR;
@ -12,7 +12,6 @@ use crate::messages::tool::common_functionality::utility_functions::is_visible_p
use crate::messages::tool::tool_messages::path_tool::{PathOverlayMode, PointSelectState};
use bezier_rs::{Bezier, BezierHandles, Subpath, TValue};
use glam::{DAffine2, DVec2};
use graphene_core::transform::Transform;
use graphene_core::vector::{ManipulatorPointId, PointId, VectorData, VectorModificationType};
use graphene_std::vector::{HandleId, SegmentId};
@ -149,7 +148,6 @@ pub struct ClosestSegment {
colinear: [Option<HandleId>; 2],
t: f64,
bezier_point_to_viewport: DVec2,
stroke_width: f64,
}
impl ClosestSegment {
@ -169,6 +167,12 @@ impl ClosestSegment {
self.bezier_point_to_viewport
}
pub fn closest_point(&self, document_metadata: &DocumentMetadata) -> DVec2 {
let transform = document_metadata.transform_to_viewport(self.layer);
let bezier_point = self.bezier.evaluate(TValue::Parametric(self.t));
transform.transform_point2(bezier_point)
}
/// Updates this [`ClosestSegment`] with the viewport-space location of the closest point on the segment to the given mouse position.
pub fn update_closest_point(&mut self, document_metadata: &DocumentMetadata, mouse_position: DVec2) {
let transform = document_metadata.transform_to_viewport(self.layer);
@ -186,10 +190,8 @@ impl ClosestSegment {
self.bezier_point_to_viewport.distance_squared(mouse_position)
}
pub fn too_far(&self, mouse_position: DVec2, tolerance: f64, document_metadata: &DocumentMetadata) -> bool {
let dist_sq = self.distance_squared(mouse_position);
let stroke_width = document_metadata.document_to_viewport.decompose_scale().x.max(1.) * self.stroke_width;
(stroke_width + tolerance).powi(2) < dist_sq
pub fn too_far(&self, mouse_position: DVec2, tolerance: f64) -> bool {
tolerance.powi(2) < self.distance_squared(mouse_position)
}
pub fn handle_positions(&self, document_metadata: &DocumentMetadata) -> (Option<DVec2>, Option<DVec2>) {
@ -1449,11 +1451,6 @@ impl ShapeState {
if distance_squared < closest_distance_squared {
closest_distance_squared = distance_squared;
// 0.5 is half the line (center to side) but it's convenient to allow targeting slightly more than half the line width
const STROKE_WIDTH_PERCENT: f64 = 0.7;
let stroke_width = graph_modification_utils::get_stroke_width(layer, network_interface).unwrap_or(1.) as f64 * STROKE_WIDTH_PERCENT;
// Convert to linear if handes are on top of control points
if let bezier_rs::BezierHandles::Cubic { handle_start, handle_end } = bezier.handles {
if handle_start.abs_diff_eq(bezier.start(), f64::EPSILON * 100.) && handle_end.abs_diff_eq(bezier.end(), f64::EPSILON * 100.) {
@ -1474,7 +1471,6 @@ impl ShapeState {
t,
bezier_point_to_viewport: screenspace,
layer,
stroke_width,
});
}
}

View file

@ -1129,7 +1129,7 @@ impl Fsm for PathToolFsmState {
Self::Ready => {
if let Some(closest_segment) = &tool_data.segment {
let perp = closest_segment.calculate_perp(document);
let point = closest_segment.closest_point_to_viewport();
let point = closest_segment.closest_point(document.metadata());
// Draw an X on the segment
if tool_data.delete_segment_pressed {
@ -1400,7 +1400,7 @@ impl Fsm for PathToolFsmState {
// If already hovering on a segment, then recalculate its closest point
else if let Some(closest_segment) = &mut tool_data.segment {
closest_segment.update_closest_point(document.metadata(), input.mouse.position);
if closest_segment.too_far(input.mouse.position, SEGMENT_INSERTION_DISTANCE, document.metadata()) {
if closest_segment.too_far(input.mouse.position, SEGMENT_INSERTION_DISTANCE) {
tool_data.segment = None;
}
responses.add(OverlaysMessage::Draw)