Add draggable diamond midpoint gizmos to the Gradient tool (#3826)

This commit is contained in:
Keavon Chambers
2026-02-24 20:25:15 -08:00
committed by GitHub
parent 7250b091d5
commit 4a6cdffd84
6 changed files with 401 additions and 75 deletions
@@ -1,7 +1,7 @@
use crate::consts::{
ARC_SWEEP_GIZMO_RADIUS, COLOR_OVERLAY_BLACK, 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, RESIZE_HANDLE_SIZE, SKEW_TRIANGLE_OFFSET, SKEW_TRIANGLE_SIZE,
GRADIENT_MIDPOINT_DIAMOND_RADIUS, MANIPULATOR_GROUP_MARKER_SIZE, PIVOT_CROSSHAIR_LENGTH, PIVOT_CROSSHAIR_THICKNESS, PIVOT_DIAMETER, RESIZE_HANDLE_SIZE, SKEW_TRIANGLE_OFFSET, SKEW_TRIANGLE_SIZE,
};
use crate::messages::portfolio::document::overlays::utility_functions::{GLOBAL_FONT_CACHE, GLOBAL_TEXT_CONTEXT};
use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier;
@@ -273,8 +273,12 @@ impl OverlayContext {
self.internal().manipulator_anchor(position, selected, color);
}
pub fn gradient_color_stop(&mut self, position: DVec2, selected: bool, color: &str) {
self.internal().gradient_color_stop(position, selected, color);
pub fn gradient_color_stop(&mut self, position: DVec2, selected: bool, color: &str, small: bool) {
self.internal().gradient_color_stop(position, selected, color, small);
}
pub fn gradient_midpoint(&mut self, position: DVec2, selected: bool, angle: f64) {
self.internal().gradient_midpoint(position, selected, angle);
}
pub fn resize_handle(&mut self, position: DVec2, rotation: f64) {
@@ -587,12 +591,12 @@ impl OverlayContextInternal {
self.square(position, None, Some(color_fill), Some(COLOR_OVERLAY_BLUE));
}
fn gradient_color_stop(&mut self, position: DVec2, selected: bool, color: &str) {
fn gradient_color_stop(&mut self, position: DVec2, selected: bool, color: &str, small: bool) {
let transform = self.get_transform();
let position = position.round() - DVec2::splat(0.5);
let (radius_offset, stroke_width) = if selected { (1., 3.) } else { (0., 1.) };
let radius = MANIPULATOR_GROUP_MARKER_SIZE / 1.5 + 1. + radius_offset;
let radius = (MANIPULATOR_GROUP_MARKER_SIZE / 1.5 + 1. + radius_offset) * if small { 0.5 } else { 1. };
let mut draw_circle = |radius: f64, width: Option<f64>, color: &str| {
let circle = kurbo::Circle::new((position.x, position.y), radius);
@@ -610,6 +614,31 @@ impl OverlayContextInternal {
draw_circle(radius, Some(stroke_width), COLOR_OVERLAY_WHITE);
}
fn gradient_midpoint(&mut self, position: DVec2, selected: bool, angle: f64) {
let transform = self.get_transform();
let position = position.round() - DVec2::splat(0.5);
// Rotate diamond points by the gradient line angle
let (sin, cos) = angle.sin_cos();
let rotate = |dx: f64, dy: f64| DVec2::new(dx * cos - dy * sin, dx * sin + dy * cos);
let top = rotate(0., -GRADIENT_MIDPOINT_DIAMOND_RADIUS);
let right = rotate(GRADIENT_MIDPOINT_DIAMOND_RADIUS, 0.);
let bottom = rotate(0., GRADIENT_MIDPOINT_DIAMOND_RADIUS);
let left = rotate(-GRADIENT_MIDPOINT_DIAMOND_RADIUS, 0.);
let mut path = BezPath::new();
path.move_to(kurbo::Point::new(position.x + top.x, position.y + top.y));
path.line_to(kurbo::Point::new(position.x + right.x, position.y + right.y));
path.line_to(kurbo::Point::new(position.x + bottom.x, position.y + bottom.y));
path.line_to(kurbo::Point::new(position.x + left.x, position.y + left.y));
path.close_path();
let fill = if selected { COLOR_OVERLAY_BLUE } else { COLOR_OVERLAY_WHITE };
self.scene.fill(peniko::Fill::NonZero, transform, Self::parse_color(fill), None, &path);
self.scene.stroke(&kurbo::Stroke::new(1.), transform, Self::parse_color(COLOR_OVERLAY_BLUE), None, &path);
}
fn resize_handle(&mut self, position: DVec2, rotation: f64) {
let quad = DAffine2::from_angle_translation(rotation, position) * Quad::from_box([DVec2::splat(-RESIZE_HANDLE_SIZE / 2.), DVec2::splat(RESIZE_HANDLE_SIZE / 2.)]);
self.quad(quad, None, Some(COLOR_OVERLAY_WHITE));
@@ -2,7 +2,8 @@ use super::utility_functions::overlay_canvas_context;
use crate::consts::{
ARC_SWEEP_GIZMO_RADIUS, COLOR_OVERLAY_BLACK, 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, RESIZE_HANDLE_SIZE, SEGMENT_SELECTED_THICKNESS, SKEW_TRIANGLE_OFFSET, SKEW_TRIANGLE_SIZE,
GRADIENT_MIDPOINT_DIAMOND_RADIUS, MANIPULATOR_GROUP_MARKER_SIZE, PIVOT_CROSSHAIR_LENGTH, PIVOT_CROSSHAIR_THICKNESS, PIVOT_DIAMETER, RESIZE_HANDLE_SIZE, SEGMENT_SELECTED_THICKNESS,
SKEW_TRIANGLE_OFFSET, SKEW_TRIANGLE_SIZE,
};
use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier;
use crate::messages::prelude::Message;
@@ -468,13 +469,13 @@ impl OverlayContext {
self.square(position, None, Some(color_fill), Some(color_stroke));
}
pub fn gradient_color_stop(&mut self, position: DVec2, selected: bool, color: &str) {
pub fn gradient_color_stop(&mut self, position: DVec2, selected: bool, color: &str, small: bool) {
self.start_dpi_aware_transform();
let position = position.round() - DVec2::splat(0.5);
let (radius_offset, stroke_width) = if selected { (1., 3.) } else { (0., 1.) };
let radius = MANIPULATOR_GROUP_MARKER_SIZE / 1.5 + 1. + radius_offset;
let radius = (MANIPULATOR_GROUP_MARKER_SIZE / 1.5 + 1. + radius_offset) * if small { 0.5 } else { 1. };
let draw_circle = |radius: f64, width: Option<f64>, color: &str| {
self.render_context.begin_path();
@@ -500,6 +501,36 @@ impl OverlayContext {
self.end_dpi_aware_transform();
}
pub fn gradient_midpoint(&mut self, position: DVec2, selected: bool, angle: f64) {
self.start_dpi_aware_transform();
let position = position.round() - DVec2::splat(0.5);
// Rotate diamond points by the gradient line angle
let (sin, cos) = angle.sin_cos();
let rotate = |dx: f64, dy: f64| DVec2::new(dx * cos - dy * sin, dx * sin + dy * cos);
let top = rotate(0., -GRADIENT_MIDPOINT_DIAMOND_RADIUS);
let right = rotate(GRADIENT_MIDPOINT_DIAMOND_RADIUS, 0.);
let bottom = rotate(0., GRADIENT_MIDPOINT_DIAMOND_RADIUS);
let left = rotate(-GRADIENT_MIDPOINT_DIAMOND_RADIUS, 0.);
self.render_context.begin_path();
self.render_context.move_to(position.x + top.x, position.y + top.y);
self.render_context.line_to(position.x + right.x, position.y + right.y);
self.render_context.line_to(position.x + bottom.x, position.y + bottom.y);
self.render_context.line_to(position.x + left.x, position.y + left.y);
self.render_context.close_path();
let fill = if selected { COLOR_OVERLAY_BLUE } else { COLOR_OVERLAY_WHITE };
self.render_context.set_fill_style_str(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_handle(&mut self, position: DVec2, selected: bool) {
self.start_dpi_aware_transform();