Improve the Path tool's segment editing mode and make hovering manipulators react contextually (#2860)

* Improve path editing mode

* Code review

* Tidy up UI

* Update path selection behaviour

* Fix linting

* Remove frozen flag

* Code review

* Fix segment split

* Fix deleting segments

* Add requred methods in vello overlay context

---------

Co-authored-by: Keavon Chambers <keavon@keavon.com>
This commit is contained in:
Adesh Gupta
2025-08-01 23:58:35 -07:00
committed by GitHub
co-authored by Keavon Chambers
parent 523132da17
commit 668acd3c30
7 changed files with 555 additions and 95 deletions
@@ -2,7 +2,7 @@ use super::utility_functions::overlay_canvas_context;
use crate::consts::{
ARC_SWEEP_GIZMO_RADIUS, COLOR_OVERLAY_BLUE, COLOR_OVERLAY_BLUE_50, COLOR_OVERLAY_GREEN, COLOR_OVERLAY_RED, COLOR_OVERLAY_WHITE, COLOR_OVERLAY_YELLOW, COLOR_OVERLAY_YELLOW_DULL,
COMPASS_ROSE_ARROW_SIZE, COMPASS_ROSE_HOVER_RING_DIAMETER, COMPASS_ROSE_MAIN_RING_DIAMETER, COMPASS_ROSE_RING_INNER_DIAMETER, DOWEL_PIN_RADIUS, MANIPULATOR_GROUP_MARKER_SIZE,
PIVOT_CROSSHAIR_LENGTH, PIVOT_CROSSHAIR_THICKNESS, PIVOT_DIAMETER,
PIVOT_CROSSHAIR_LENGTH, PIVOT_CROSSHAIR_THICKNESS, PIVOT_DIAMETER, SEGMENT_SELECTED_THICKNESS,
};
use crate::messages::prelude::Message;
use bezier_rs::{Bezier, Subpath};
@@ -460,6 +460,42 @@ impl OverlayContext {
self.square(position, None, Some(color_fill), Some(color_stroke));
}
pub fn hover_manipulator_handle(&mut self, position: DVec2, selected: bool) {
self.start_dpi_aware_transform();
let position = position.round() - DVec2::splat(0.5);
self.render_context.begin_path();
self.render_context
.arc(position.x, position.y, (MANIPULATOR_GROUP_MARKER_SIZE + 2.) / 2., 0., TAU)
.expect("Failed to draw the circle");
self.render_context.set_fill_style_str(COLOR_OVERLAY_BLUE_50);
self.render_context.set_stroke_style_str(COLOR_OVERLAY_BLUE_50);
self.render_context.fill();
self.render_context.stroke();
self.render_context.begin_path();
self.render_context
.arc(position.x, position.y, MANIPULATOR_GROUP_MARKER_SIZE / 2., 0., TAU)
.expect("Failed to draw the circle");
let color_fill = if selected { COLOR_OVERLAY_BLUE } else { COLOR_OVERLAY_WHITE };
self.render_context.set_fill_style_str(color_fill);
self.render_context.set_stroke_style_str(COLOR_OVERLAY_BLUE);
self.render_context.fill();
self.render_context.stroke();
self.end_dpi_aware_transform();
}
pub fn hover_manipulator_anchor(&mut self, position: DVec2, selected: bool) {
self.square(position, Some(MANIPULATOR_GROUP_MARKER_SIZE + 2.), Some(COLOR_OVERLAY_BLUE_50), Some(COLOR_OVERLAY_BLUE_50));
let color_fill = if selected { COLOR_OVERLAY_BLUE } else { COLOR_OVERLAY_WHITE };
self.square(position, None, Some(color_fill), Some(COLOR_OVERLAY_BLUE));
}
/// Transforms the canvas context to adjust for DPI scaling
///
/// Overwrites all existing tranforms. This operation can be reversed with [`Self::reset_transform`].
@@ -758,7 +794,7 @@ impl OverlayContext {
self.render_context.begin_path();
self.bezier_command(bezier, transform, true);
self.render_context.set_stroke_style_str(COLOR_OVERLAY_BLUE);
self.render_context.set_line_width(4.);
self.render_context.set_line_width(SEGMENT_SELECTED_THICKNESS);
self.render_context.stroke();
self.render_context.set_line_width(1.);
@@ -772,7 +808,7 @@ impl OverlayContext {
self.render_context.begin_path();
self.bezier_command(bezier, transform, true);
self.render_context.set_stroke_style_str(COLOR_OVERLAY_BLUE_50);
self.render_context.set_line_width(4.);
self.render_context.set_line_width(SEGMENT_SELECTED_THICKNESS);
self.render_context.stroke();
self.render_context.set_line_width(1.);
@@ -293,12 +293,36 @@ impl OverlayContext {
.stroke(&kurbo::Stroke::new(1.0), transform, Self::parse_color(color.unwrap_or(COLOR_OVERLAY_BLUE)), None, &circle);
}
pub fn hover_manipulator_handle(&mut self, position: DVec2, selected: bool) {
let transform = self.get_transform();
let position = position.round() - DVec2::splat(0.5);
let circle = kurbo::Circle::new((position.x, position.y), (MANIPULATOR_GROUP_MARKER_SIZE + 2.) / 2.);
let fill = COLOR_OVERLAY_BLUE_50;
self.scene.fill(peniko::Fill::NonZero, transform, Self::parse_color(fill), None, &circle);
self.scene.stroke(&kurbo::Stroke::new(1.0), transform, Self::parse_color(COLOR_OVERLAY_BLUE_50), None, &circle);
let inner_circle = kurbo::Circle::new((position.x, position.y), MANIPULATOR_GROUP_MARKER_SIZE / 2.);
let color_fill = if selected { COLOR_OVERLAY_BLUE } else { COLOR_OVERLAY_WHITE };
self.scene.fill(peniko::Fill::NonZero, transform, Self::parse_color(color_fill), None, &circle);
self.scene.stroke(&kurbo::Stroke::new(1.0), transform, Self::parse_color(COLOR_OVERLAY_BLUE), None, &inner_circle);
}
pub fn manipulator_anchor(&mut self, position: DVec2, selected: bool, color: Option<&str>) {
let color_stroke = color.unwrap_or(COLOR_OVERLAY_BLUE);
let color_fill = if selected { color_stroke } else { COLOR_OVERLAY_WHITE };
self.square(position, None, Some(color_fill), Some(color_stroke));
}
pub fn hover_manipulator_anchor(&mut self, position: DVec2, selected: bool) {
self.square(position, Some(MANIPULATOR_GROUP_MARKER_SIZE + 2.), Some(COLOR_OVERLAY_BLUE_50), Some(COLOR_OVERLAY_BLUE_50));
let color_fill = if selected { COLOR_OVERLAY_BLUE } else { COLOR_OVERLAY_WHITE };
self.square(position, None, Some(color_fill), Some(COLOR_OVERLAY_BLUE));
}
fn get_transform(&self) -> kurbo::Affine {
kurbo::Affine::scale(self.device_pixel_ratio)
}