Skip to content

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

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 7 commits into from
Jun 9, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion editor/src/consts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down
22 changes: 9 additions & 13 deletions editor/src/messages/tool/common_functionality/shape_editor.rs
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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};

Expand Down Expand Up @@ -149,7 +148,6 @@ pub struct ClosestSegment {
colinear: [Option<HandleId>; 2],
t: f64,
bezier_point_to_viewport: DVec2,
stroke_width: f64,
}

impl ClosestSegment {
Expand All @@ -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);
Expand All @@ -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>) {
Expand Down Expand Up @@ -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.) {
Expand All @@ -1474,7 +1471,6 @@ impl ShapeState {
t,
bezier_point_to_viewport: screenspace,
layer,
stroke_width,
});
}
}
Expand Down
4 changes: 2 additions & 2 deletions editor/src/messages/tool/tool_messages/path_tool.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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)
Expand Down
Loading