Skip to content

Commit e606efd

Browse files
jsjgdhKeavon
andauthored
Improve the Gradient tool by visualizing color stops (#3698)
* Gradient * improvement * removed unnecessary code * corrected error * Partical changes * Improveded * remove from advertised actions * Mousedown * Partial code review * changes as per recommendation * corrected error * corrected error -2 * changes as per recommendation * error corrected * changes as suggested * Bug Fix * Fix hints --------- Co-authored-by: Keavon Chambers <keavon@keavon.com>
1 parent ea68d62 commit e606efd

8 files changed

Lines changed: 292 additions & 73 deletions

File tree

editor/src/consts.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,7 @@ pub const COLOR_OVERLAY_RED: &str = "#ef5454";
153153
pub const COLOR_OVERLAY_GRAY: &str = "#cccccc";
154154
pub const COLOR_OVERLAY_GRAY_25: &str = "#cccccc40";
155155
pub const COLOR_OVERLAY_WHITE: &str = "#ffffff";
156+
pub const COLOR_OVERLAY_BLACK: &str = "#000000";
156157
pub const COLOR_OVERLAY_BLACK_75: &str = "#000000bf";
157158

158159
// DOCUMENT

editor/src/messages/input_mapper/input_mappings.rs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -178,7 +178,6 @@ pub fn input_mappings(zoom_with_scroll: bool) -> Mapping {
178178
entry!(KeyDown(MouseLeft); action_dispatch=GradientToolMessage::PointerDown),
179179
entry!(PointerMove; refresh_keys=[Shift], action_dispatch=GradientToolMessage::PointerMove { constrain_axis: Shift }),
180180
entry!(KeyUp(MouseLeft); action_dispatch=GradientToolMessage::PointerUp),
181-
entry!(DoubleClick(MouseButton::Left); action_dispatch=GradientToolMessage::InsertStop),
182181
entry!(KeyDown(Delete); action_dispatch=GradientToolMessage::DeleteStop),
183182
entry!(KeyDown(Backspace); action_dispatch=GradientToolMessage::DeleteStop),
184183
entry!(KeyDown(MouseRight); action_dispatch=GradientToolMessage::Abort),

editor/src/messages/portfolio/document/document_message_handler.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -964,6 +964,7 @@ impl MessageHandler<DocumentMessage, DocumentMessageContext<'_>> for DocumentMes
964964
responses.add(DocumentMessage::DocumentHistoryForward);
965965
responses.add(ToolMessage::Redo);
966966
responses.add(OverlaysMessage::Draw);
967+
responses.add(EventMessage::SelectionChanged);
967968
}
968969
DocumentMessage::RenameDocument { new_name } => {
969970
self.name = new_name.clone();
@@ -1431,6 +1432,7 @@ impl MessageHandler<DocumentMessage, DocumentMessageContext<'_>> for DocumentMes
14311432
responses.add(DocumentMessage::DocumentHistoryBackward);
14321433
responses.add(OverlaysMessage::Draw);
14331434
responses.add(ToolMessage::Undo);
1435+
responses.add(EventMessage::SelectionChanged);
14341436
}
14351437
DocumentMessage::UngroupSelectedLayers => {
14361438
if !self.selection_network_path.is_empty() {

editor/src/messages/portfolio/document/node_graph/node_graph_message_handler.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ use crate::messages::tool::tool_messages::tool_prelude::{Key, MouseMotion};
2525
use crate::messages::tool::utility_types::{HintData, HintGroup, HintInfo};
2626
use crate::messages::viewport::Position;
2727
use glam::{DAffine2, DVec2, IVec2};
28+
use graph_craft::document::value::TaggedValue;
2829
use graph_craft::document::{DocumentNodeImplementation, NodeId, NodeInput};
2930
use graphene_std::math::math_ext::QuadExt;
3031
use graphene_std::vector::algorithms::bezpath_algorithms::bezpath_is_inside_bezpath;
@@ -1699,12 +1700,16 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphMessageContext<'a>> for NodeG
16991700
}
17001701
}
17011702
NodeGraphMessage::SetInputValue { node_id, input_index, value } => {
1703+
let is_fill = matches!(value, TaggedValue::Fill(_));
17021704
let input = NodeInput::value(value, false);
17031705
responses.add(NodeGraphMessage::SetInput {
17041706
input_connector: InputConnector::node(node_id, input_index),
17051707
input,
17061708
});
17071709
responses.add(PropertiesPanelMessage::Refresh);
1710+
if is_fill {
1711+
responses.add(OverlaysMessage::Draw);
1712+
}
17081713
if network_interface.connected_to_output(&node_id, selection_network_path) {
17091714
responses.add(NodeGraphMessage::RunDocumentGraph);
17101715
}

editor/src/messages/portfolio/document/overlays/utility_types_native.rs

Lines changed: 39 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
use crate::consts::{
2-
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,
3-
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,
4-
PIVOT_CROSSHAIR_LENGTH, PIVOT_CROSSHAIR_THICKNESS, PIVOT_DIAMETER, RESIZE_HANDLE_SIZE, SKEW_TRIANGLE_OFFSET, SKEW_TRIANGLE_SIZE,
2+
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,
3+
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,
4+
MANIPULATOR_GROUP_MARKER_SIZE, PIVOT_CROSSHAIR_LENGTH, PIVOT_CROSSHAIR_THICKNESS, PIVOT_DIAMETER, RESIZE_HANDLE_SIZE, SKEW_TRIANGLE_OFFSET, SKEW_TRIANGLE_SIZE,
55
};
66
use crate::messages::portfolio::document::overlays::utility_functions::{GLOBAL_FONT_CACHE, GLOBAL_TEXT_CONTEXT};
77
use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier;
@@ -273,6 +273,10 @@ impl OverlayContext {
273273
self.internal().manipulator_anchor(position, selected, color);
274274
}
275275

276+
pub fn gradient_color_stop(&mut self, position: DVec2, selected: bool, color: &str) {
277+
self.internal().gradient_color_stop(position, selected, color);
278+
}
279+
276280
pub fn resize_handle(&mut self, position: DVec2, rotation: f64) {
277281
self.internal().resize_handle(position, rotation);
278282
}
@@ -583,6 +587,29 @@ impl OverlayContextInternal {
583587
self.square(position, None, Some(color_fill), Some(COLOR_OVERLAY_BLUE));
584588
}
585589

590+
fn gradient_color_stop(&mut self, position: DVec2, selected: bool, color: &str) {
591+
let transform = self.get_transform();
592+
let position = position.round() - DVec2::splat(0.5);
593+
594+
let (radius_offset, stroke_width) = if selected { (1., 3.) } else { (0., 1.) };
595+
let radius = MANIPULATOR_GROUP_MARKER_SIZE / 1.5 + 1. + radius_offset;
596+
597+
let mut draw_circle = |radius: f64, width: Option<f64>, color: &str| {
598+
let circle = kurbo::Circle::new((position.x, position.y), radius);
599+
if let Some(width) = width {
600+
self.scene.stroke(&kurbo::Stroke::new(width), transform, Self::parse_color(color), None, &circle);
601+
} else {
602+
self.scene.fill(peniko::Fill::NonZero, transform, Self::parse_color(color), None, &circle);
603+
}
604+
};
605+
// Fill
606+
draw_circle(radius, None, color);
607+
// Stroke (inner)
608+
draw_circle(radius + stroke_width / 2., Some(1.), COLOR_OVERLAY_BLACK);
609+
// Stroke (outer)
610+
draw_circle(radius, Some(stroke_width), COLOR_OVERLAY_WHITE);
611+
}
612+
586613
fn resize_handle(&mut self, position: DVec2, rotation: f64) {
587614
let quad = DAffine2::from_angle_translation(rotation, position) * Quad::from_box([DVec2::splat(-RESIZE_HANDLE_SIZE / 2.), DVec2::splat(RESIZE_HANDLE_SIZE / 2.)]);
588615
self.quad(quad, None, Some(COLOR_OVERLAY_WHITE));
@@ -832,7 +859,7 @@ impl OverlayContextInternal {
832859
path.move_to(kurbo::Point::new(x, y));
833860
path.line_to(kurbo::Point::new(start1_x, start1_y));
834861

835-
let arc1 = kurbo::Arc::new((x, y), (DOWEL_PIN_RADIUS, DOWEL_PIN_RADIUS), start1, FRAC_PI_2, 0.0);
862+
let arc1 = kurbo::Arc::new((x, y), (DOWEL_PIN_RADIUS, DOWEL_PIN_RADIUS), start1, FRAC_PI_2, 0.);
836863
arc1.to_cubic_beziers(0.1, |p1, p2, p| {
837864
path.curve_to(p1, p2, p);
838865
});
@@ -844,7 +871,7 @@ impl OverlayContextInternal {
844871
path.move_to(kurbo::Point::new(x, y));
845872
path.line_to(kurbo::Point::new(start2_x, start2_y));
846873

847-
let arc2 = kurbo::Arc::new((x, y), (DOWEL_PIN_RADIUS, DOWEL_PIN_RADIUS), start2, FRAC_PI_2, 0.0);
874+
let arc2 = kurbo::Arc::new((x, y), (DOWEL_PIN_RADIUS, DOWEL_PIN_RADIUS), start2, FRAC_PI_2, 0.);
848875
arc2.to_cubic_beziers(0.1, |p1, p2, p| {
849876
path.curve_to(p1, p2, p);
850877
});
@@ -891,15 +918,15 @@ impl OverlayContextInternal {
891918
let mut path = BezPath::new();
892919
self.bezier_to_path(bezier, transform, true, &mut path);
893920

894-
self.scene.stroke(&kurbo::Stroke::new(4.0), vello_transform, Self::parse_color(COLOR_OVERLAY_BLUE), None, &path);
921+
self.scene.stroke(&kurbo::Stroke::new(4.), vello_transform, Self::parse_color(COLOR_OVERLAY_BLUE), None, &path);
895922
}
896923

897924
fn outline_overlay_bezier(&mut self, bezier: PathSeg, transform: DAffine2) {
898925
let vello_transform = self.get_transform();
899926
let mut path = BezPath::new();
900927
self.bezier_to_path(bezier, transform, true, &mut path);
901928

902-
self.scene.stroke(&kurbo::Stroke::new(4.0), vello_transform, Self::parse_color(COLOR_OVERLAY_BLUE_50), None, &path);
929+
self.scene.stroke(&kurbo::Stroke::new(4.), vello_transform, Self::parse_color(COLOR_OVERLAY_BLUE_50), None, &path);
903930
}
904931

905932
fn bezier_to_path(&self, bezier: PathSeg, transform: DAffine2, move_to: bool, path: &mut BezPath) {
@@ -1035,16 +1062,16 @@ impl OverlayContextInternal {
10351062

10361063
fn text(&mut self, text: &str, font_color: &str, background_color: Option<&str>, transform: DAffine2, padding: f64, pivot: [Pivot; 2]) {
10371064
// Use the proper text-to-path system for accurate text rendering
1038-
const FONT_SIZE: f64 = 12.0;
1065+
const FONT_SIZE: f64 = 12.;
10391066

10401067
// Create typesetting configuration
10411068
let typesetting = TypesettingConfig {
10421069
font_size: FONT_SIZE,
10431070
line_height_ratio: 1.2,
1044-
character_spacing: 0.0,
1071+
character_spacing: 0.,
10451072
max_width: None,
10461073
max_height: None,
1047-
tilt: 0.0,
1074+
tilt: 0.,
10481075
align: TextAlign::Left, // We'll handle alignment manually via pivot
10491076
};
10501077

@@ -1059,7 +1086,7 @@ impl OverlayContextInternal {
10591086
let text_width = text_size.x;
10601087
let text_height = text_size.y;
10611088
// Create a rect from the size (assuming text starts at origin)
1062-
let text_bounds = kurbo::Rect::new(0.0, 0.0, text_width, text_height);
1089+
let text_bounds = kurbo::Rect::new(0., 0., text_width, text_height);
10631090

10641091
// Convert text to vector paths for rendering
10651092
let text_table = text_context.to_path(text, &font, &GLOBAL_FONT_CACHE, typesetting, false);
@@ -1068,7 +1095,7 @@ impl OverlayContextInternal {
10681095
let mut position = DVec2::ZERO;
10691096
match pivot[0] {
10701097
Pivot::Start => position.x = padding,
1071-
Pivot::Middle => position.x = -text_width / 2.0,
1098+
Pivot::Middle => position.x = -text_width / 2.,
10721099
Pivot::End => position.x = -padding - text_width,
10731100
}
10741101
match pivot[1] {

editor/src/messages/portfolio/document/overlays/utility_types_web.rs

Lines changed: 34 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
use super::utility_functions::overlay_canvas_context;
22
use crate::consts::{
3-
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,
4-
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,
5-
PIVOT_CROSSHAIR_LENGTH, PIVOT_CROSSHAIR_THICKNESS, PIVOT_DIAMETER, RESIZE_HANDLE_SIZE, SEGMENT_SELECTED_THICKNESS, SKEW_TRIANGLE_OFFSET, SKEW_TRIANGLE_SIZE,
3+
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,
4+
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,
5+
MANIPULATOR_GROUP_MARKER_SIZE, PIVOT_CROSSHAIR_LENGTH, PIVOT_CROSSHAIR_THICKNESS, PIVOT_DIAMETER, RESIZE_HANDLE_SIZE, SEGMENT_SELECTED_THICKNESS, SKEW_TRIANGLE_OFFSET, SKEW_TRIANGLE_SIZE,
66
};
77
use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier;
88
use crate::messages::prelude::Message;
@@ -468,6 +468,37 @@ impl OverlayContext {
468468
self.square(position, None, Some(color_fill), Some(color_stroke));
469469
}
470470

471+
pub fn gradient_color_stop(&mut self, position: DVec2, selected: bool, color: &str) {
472+
self.start_dpi_aware_transform();
473+
474+
let position = position.round() - DVec2::splat(0.5);
475+
476+
let (radius_offset, stroke_width) = if selected { (1., 3.) } else { (0., 1.) };
477+
let radius = MANIPULATOR_GROUP_MARKER_SIZE / 1.5 + 1. + radius_offset;
478+
479+
let draw_circle = |radius: f64, width: Option<f64>, color: &str| {
480+
self.render_context.begin_path();
481+
self.render_context.arc(position.x, position.y, radius, 0., TAU).expect("Failed to draw the circle");
482+
483+
if let Some(width) = width {
484+
self.render_context.set_line_width(width);
485+
self.render_context.set_stroke_style_str(color);
486+
self.render_context.stroke();
487+
} else {
488+
self.render_context.set_fill_style_str(color);
489+
self.render_context.fill();
490+
}
491+
};
492+
// Fill
493+
draw_circle(radius, None, color);
494+
// Stroke (inner)
495+
draw_circle(radius + stroke_width / 2., Some(1.), COLOR_OVERLAY_BLACK);
496+
// Stroke (outer)
497+
draw_circle(radius, Some(stroke_width), COLOR_OVERLAY_WHITE);
498+
499+
self.end_dpi_aware_transform();
500+
}
501+
471502
pub fn hover_manipulator_handle(&mut self, position: DVec2, selected: bool) {
472503
self.start_dpi_aware_transform();
473504

0 commit comments

Comments
 (0)