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>
This commit is contained in:
parent
ea68d62ec4
commit
e606efd008
|
|
@ -153,6 +153,7 @@ pub const COLOR_OVERLAY_RED: &str = "#ef5454";
|
||||||
pub const COLOR_OVERLAY_GRAY: &str = "#cccccc";
|
pub const COLOR_OVERLAY_GRAY: &str = "#cccccc";
|
||||||
pub const COLOR_OVERLAY_GRAY_25: &str = "#cccccc40";
|
pub const COLOR_OVERLAY_GRAY_25: &str = "#cccccc40";
|
||||||
pub const COLOR_OVERLAY_WHITE: &str = "#ffffff";
|
pub const COLOR_OVERLAY_WHITE: &str = "#ffffff";
|
||||||
|
pub const COLOR_OVERLAY_BLACK: &str = "#000000";
|
||||||
pub const COLOR_OVERLAY_BLACK_75: &str = "#000000bf";
|
pub const COLOR_OVERLAY_BLACK_75: &str = "#000000bf";
|
||||||
|
|
||||||
// DOCUMENT
|
// DOCUMENT
|
||||||
|
|
|
||||||
|
|
@ -178,7 +178,6 @@ pub fn input_mappings(zoom_with_scroll: bool) -> Mapping {
|
||||||
entry!(KeyDown(MouseLeft); action_dispatch=GradientToolMessage::PointerDown),
|
entry!(KeyDown(MouseLeft); action_dispatch=GradientToolMessage::PointerDown),
|
||||||
entry!(PointerMove; refresh_keys=[Shift], action_dispatch=GradientToolMessage::PointerMove { constrain_axis: Shift }),
|
entry!(PointerMove; refresh_keys=[Shift], action_dispatch=GradientToolMessage::PointerMove { constrain_axis: Shift }),
|
||||||
entry!(KeyUp(MouseLeft); action_dispatch=GradientToolMessage::PointerUp),
|
entry!(KeyUp(MouseLeft); action_dispatch=GradientToolMessage::PointerUp),
|
||||||
entry!(DoubleClick(MouseButton::Left); action_dispatch=GradientToolMessage::InsertStop),
|
|
||||||
entry!(KeyDown(Delete); action_dispatch=GradientToolMessage::DeleteStop),
|
entry!(KeyDown(Delete); action_dispatch=GradientToolMessage::DeleteStop),
|
||||||
entry!(KeyDown(Backspace); action_dispatch=GradientToolMessage::DeleteStop),
|
entry!(KeyDown(Backspace); action_dispatch=GradientToolMessage::DeleteStop),
|
||||||
entry!(KeyDown(MouseRight); action_dispatch=GradientToolMessage::Abort),
|
entry!(KeyDown(MouseRight); action_dispatch=GradientToolMessage::Abort),
|
||||||
|
|
|
||||||
|
|
@ -964,6 +964,7 @@ impl MessageHandler<DocumentMessage, DocumentMessageContext<'_>> for DocumentMes
|
||||||
responses.add(DocumentMessage::DocumentHistoryForward);
|
responses.add(DocumentMessage::DocumentHistoryForward);
|
||||||
responses.add(ToolMessage::Redo);
|
responses.add(ToolMessage::Redo);
|
||||||
responses.add(OverlaysMessage::Draw);
|
responses.add(OverlaysMessage::Draw);
|
||||||
|
responses.add(EventMessage::SelectionChanged);
|
||||||
}
|
}
|
||||||
DocumentMessage::RenameDocument { new_name } => {
|
DocumentMessage::RenameDocument { new_name } => {
|
||||||
self.name = new_name.clone();
|
self.name = new_name.clone();
|
||||||
|
|
@ -1431,6 +1432,7 @@ impl MessageHandler<DocumentMessage, DocumentMessageContext<'_>> for DocumentMes
|
||||||
responses.add(DocumentMessage::DocumentHistoryBackward);
|
responses.add(DocumentMessage::DocumentHistoryBackward);
|
||||||
responses.add(OverlaysMessage::Draw);
|
responses.add(OverlaysMessage::Draw);
|
||||||
responses.add(ToolMessage::Undo);
|
responses.add(ToolMessage::Undo);
|
||||||
|
responses.add(EventMessage::SelectionChanged);
|
||||||
}
|
}
|
||||||
DocumentMessage::UngroupSelectedLayers => {
|
DocumentMessage::UngroupSelectedLayers => {
|
||||||
if !self.selection_network_path.is_empty() {
|
if !self.selection_network_path.is_empty() {
|
||||||
|
|
|
||||||
|
|
@ -25,6 +25,7 @@ use crate::messages::tool::tool_messages::tool_prelude::{Key, MouseMotion};
|
||||||
use crate::messages::tool::utility_types::{HintData, HintGroup, HintInfo};
|
use crate::messages::tool::utility_types::{HintData, HintGroup, HintInfo};
|
||||||
use crate::messages::viewport::Position;
|
use crate::messages::viewport::Position;
|
||||||
use glam::{DAffine2, DVec2, IVec2};
|
use glam::{DAffine2, DVec2, IVec2};
|
||||||
|
use graph_craft::document::value::TaggedValue;
|
||||||
use graph_craft::document::{DocumentNodeImplementation, NodeId, NodeInput};
|
use graph_craft::document::{DocumentNodeImplementation, NodeId, NodeInput};
|
||||||
use graphene_std::math::math_ext::QuadExt;
|
use graphene_std::math::math_ext::QuadExt;
|
||||||
use graphene_std::vector::algorithms::bezpath_algorithms::bezpath_is_inside_bezpath;
|
use graphene_std::vector::algorithms::bezpath_algorithms::bezpath_is_inside_bezpath;
|
||||||
|
|
@ -1699,12 +1700,16 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphMessageContext<'a>> for NodeG
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
NodeGraphMessage::SetInputValue { node_id, input_index, value } => {
|
NodeGraphMessage::SetInputValue { node_id, input_index, value } => {
|
||||||
|
let is_fill = matches!(value, TaggedValue::Fill(_));
|
||||||
let input = NodeInput::value(value, false);
|
let input = NodeInput::value(value, false);
|
||||||
responses.add(NodeGraphMessage::SetInput {
|
responses.add(NodeGraphMessage::SetInput {
|
||||||
input_connector: InputConnector::node(node_id, input_index),
|
input_connector: InputConnector::node(node_id, input_index),
|
||||||
input,
|
input,
|
||||||
});
|
});
|
||||||
responses.add(PropertiesPanelMessage::Refresh);
|
responses.add(PropertiesPanelMessage::Refresh);
|
||||||
|
if is_fill {
|
||||||
|
responses.add(OverlaysMessage::Draw);
|
||||||
|
}
|
||||||
if network_interface.connected_to_output(&node_id, selection_network_path) {
|
if network_interface.connected_to_output(&node_id, selection_network_path) {
|
||||||
responses.add(NodeGraphMessage::RunDocumentGraph);
|
responses.add(NodeGraphMessage::RunDocumentGraph);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
use crate::consts::{
|
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,
|
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,
|
||||||
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,
|
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,
|
||||||
PIVOT_CROSSHAIR_LENGTH, PIVOT_CROSSHAIR_THICKNESS, PIVOT_DIAMETER, RESIZE_HANDLE_SIZE, SKEW_TRIANGLE_OFFSET, SKEW_TRIANGLE_SIZE,
|
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::overlays::utility_functions::{GLOBAL_FONT_CACHE, GLOBAL_TEXT_CONTEXT};
|
||||||
use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier;
|
use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier;
|
||||||
|
|
@ -273,6 +273,10 @@ impl OverlayContext {
|
||||||
self.internal().manipulator_anchor(position, selected, color);
|
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 resize_handle(&mut self, position: DVec2, rotation: f64) {
|
pub fn resize_handle(&mut self, position: DVec2, rotation: f64) {
|
||||||
self.internal().resize_handle(position, rotation);
|
self.internal().resize_handle(position, rotation);
|
||||||
}
|
}
|
||||||
|
|
@ -583,6 +587,29 @@ impl OverlayContextInternal {
|
||||||
self.square(position, None, Some(color_fill), Some(COLOR_OVERLAY_BLUE));
|
self.square(position, None, Some(color_fill), Some(COLOR_OVERLAY_BLUE));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn gradient_color_stop(&mut self, position: DVec2, selected: bool, color: &str) {
|
||||||
|
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 mut draw_circle = |radius: f64, width: Option<f64>, color: &str| {
|
||||||
|
let circle = kurbo::Circle::new((position.x, position.y), radius);
|
||||||
|
if let Some(width) = width {
|
||||||
|
self.scene.stroke(&kurbo::Stroke::new(width), transform, Self::parse_color(color), None, &circle);
|
||||||
|
} else {
|
||||||
|
self.scene.fill(peniko::Fill::NonZero, transform, Self::parse_color(color), None, &circle);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
// Fill
|
||||||
|
draw_circle(radius, None, color);
|
||||||
|
// Stroke (inner)
|
||||||
|
draw_circle(radius + stroke_width / 2., Some(1.), COLOR_OVERLAY_BLACK);
|
||||||
|
// Stroke (outer)
|
||||||
|
draw_circle(radius, Some(stroke_width), COLOR_OVERLAY_WHITE);
|
||||||
|
}
|
||||||
|
|
||||||
fn resize_handle(&mut self, position: DVec2, rotation: f64) {
|
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.)]);
|
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));
|
self.quad(quad, None, Some(COLOR_OVERLAY_WHITE));
|
||||||
|
|
@ -832,7 +859,7 @@ impl OverlayContextInternal {
|
||||||
path.move_to(kurbo::Point::new(x, y));
|
path.move_to(kurbo::Point::new(x, y));
|
||||||
path.line_to(kurbo::Point::new(start1_x, start1_y));
|
path.line_to(kurbo::Point::new(start1_x, start1_y));
|
||||||
|
|
||||||
let arc1 = kurbo::Arc::new((x, y), (DOWEL_PIN_RADIUS, DOWEL_PIN_RADIUS), start1, FRAC_PI_2, 0.0);
|
let arc1 = kurbo::Arc::new((x, y), (DOWEL_PIN_RADIUS, DOWEL_PIN_RADIUS), start1, FRAC_PI_2, 0.);
|
||||||
arc1.to_cubic_beziers(0.1, |p1, p2, p| {
|
arc1.to_cubic_beziers(0.1, |p1, p2, p| {
|
||||||
path.curve_to(p1, p2, p);
|
path.curve_to(p1, p2, p);
|
||||||
});
|
});
|
||||||
|
|
@ -844,7 +871,7 @@ impl OverlayContextInternal {
|
||||||
path.move_to(kurbo::Point::new(x, y));
|
path.move_to(kurbo::Point::new(x, y));
|
||||||
path.line_to(kurbo::Point::new(start2_x, start2_y));
|
path.line_to(kurbo::Point::new(start2_x, start2_y));
|
||||||
|
|
||||||
let arc2 = kurbo::Arc::new((x, y), (DOWEL_PIN_RADIUS, DOWEL_PIN_RADIUS), start2, FRAC_PI_2, 0.0);
|
let arc2 = kurbo::Arc::new((x, y), (DOWEL_PIN_RADIUS, DOWEL_PIN_RADIUS), start2, FRAC_PI_2, 0.);
|
||||||
arc2.to_cubic_beziers(0.1, |p1, p2, p| {
|
arc2.to_cubic_beziers(0.1, |p1, p2, p| {
|
||||||
path.curve_to(p1, p2, p);
|
path.curve_to(p1, p2, p);
|
||||||
});
|
});
|
||||||
|
|
@ -891,7 +918,7 @@ impl OverlayContextInternal {
|
||||||
let mut path = BezPath::new();
|
let mut path = BezPath::new();
|
||||||
self.bezier_to_path(bezier, transform, true, &mut path);
|
self.bezier_to_path(bezier, transform, true, &mut path);
|
||||||
|
|
||||||
self.scene.stroke(&kurbo::Stroke::new(4.0), vello_transform, Self::parse_color(COLOR_OVERLAY_BLUE), None, &path);
|
self.scene.stroke(&kurbo::Stroke::new(4.), vello_transform, Self::parse_color(COLOR_OVERLAY_BLUE), None, &path);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn outline_overlay_bezier(&mut self, bezier: PathSeg, transform: DAffine2) {
|
fn outline_overlay_bezier(&mut self, bezier: PathSeg, transform: DAffine2) {
|
||||||
|
|
@ -899,7 +926,7 @@ impl OverlayContextInternal {
|
||||||
let mut path = BezPath::new();
|
let mut path = BezPath::new();
|
||||||
self.bezier_to_path(bezier, transform, true, &mut path);
|
self.bezier_to_path(bezier, transform, true, &mut path);
|
||||||
|
|
||||||
self.scene.stroke(&kurbo::Stroke::new(4.0), vello_transform, Self::parse_color(COLOR_OVERLAY_BLUE_50), None, &path);
|
self.scene.stroke(&kurbo::Stroke::new(4.), vello_transform, Self::parse_color(COLOR_OVERLAY_BLUE_50), None, &path);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn bezier_to_path(&self, bezier: PathSeg, transform: DAffine2, move_to: bool, path: &mut BezPath) {
|
fn bezier_to_path(&self, bezier: PathSeg, transform: DAffine2, move_to: bool, path: &mut BezPath) {
|
||||||
|
|
@ -1035,16 +1062,16 @@ impl OverlayContextInternal {
|
||||||
|
|
||||||
fn text(&mut self, text: &str, font_color: &str, background_color: Option<&str>, transform: DAffine2, padding: f64, pivot: [Pivot; 2]) {
|
fn text(&mut self, text: &str, font_color: &str, background_color: Option<&str>, transform: DAffine2, padding: f64, pivot: [Pivot; 2]) {
|
||||||
// Use the proper text-to-path system for accurate text rendering
|
// Use the proper text-to-path system for accurate text rendering
|
||||||
const FONT_SIZE: f64 = 12.0;
|
const FONT_SIZE: f64 = 12.;
|
||||||
|
|
||||||
// Create typesetting configuration
|
// Create typesetting configuration
|
||||||
let typesetting = TypesettingConfig {
|
let typesetting = TypesettingConfig {
|
||||||
font_size: FONT_SIZE,
|
font_size: FONT_SIZE,
|
||||||
line_height_ratio: 1.2,
|
line_height_ratio: 1.2,
|
||||||
character_spacing: 0.0,
|
character_spacing: 0.,
|
||||||
max_width: None,
|
max_width: None,
|
||||||
max_height: None,
|
max_height: None,
|
||||||
tilt: 0.0,
|
tilt: 0.,
|
||||||
align: TextAlign::Left, // We'll handle alignment manually via pivot
|
align: TextAlign::Left, // We'll handle alignment manually via pivot
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -1059,7 +1086,7 @@ impl OverlayContextInternal {
|
||||||
let text_width = text_size.x;
|
let text_width = text_size.x;
|
||||||
let text_height = text_size.y;
|
let text_height = text_size.y;
|
||||||
// Create a rect from the size (assuming text starts at origin)
|
// Create a rect from the size (assuming text starts at origin)
|
||||||
let text_bounds = kurbo::Rect::new(0.0, 0.0, text_width, text_height);
|
let text_bounds = kurbo::Rect::new(0., 0., text_width, text_height);
|
||||||
|
|
||||||
// Convert text to vector paths for rendering
|
// Convert text to vector paths for rendering
|
||||||
let text_table = text_context.to_path(text, &font, &GLOBAL_FONT_CACHE, typesetting, false);
|
let text_table = text_context.to_path(text, &font, &GLOBAL_FONT_CACHE, typesetting, false);
|
||||||
|
|
@ -1068,7 +1095,7 @@ impl OverlayContextInternal {
|
||||||
let mut position = DVec2::ZERO;
|
let mut position = DVec2::ZERO;
|
||||||
match pivot[0] {
|
match pivot[0] {
|
||||||
Pivot::Start => position.x = padding,
|
Pivot::Start => position.x = padding,
|
||||||
Pivot::Middle => position.x = -text_width / 2.0,
|
Pivot::Middle => position.x = -text_width / 2.,
|
||||||
Pivot::End => position.x = -padding - text_width,
|
Pivot::End => position.x = -padding - text_width,
|
||||||
}
|
}
|
||||||
match pivot[1] {
|
match pivot[1] {
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
use super::utility_functions::overlay_canvas_context;
|
use super::utility_functions::overlay_canvas_context;
|
||||||
use crate::consts::{
|
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,
|
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,
|
||||||
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,
|
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,
|
||||||
PIVOT_CROSSHAIR_LENGTH, PIVOT_CROSSHAIR_THICKNESS, PIVOT_DIAMETER, RESIZE_HANDLE_SIZE, SEGMENT_SELECTED_THICKNESS, SKEW_TRIANGLE_OFFSET, SKEW_TRIANGLE_SIZE,
|
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::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier;
|
||||||
use crate::messages::prelude::Message;
|
use crate::messages::prelude::Message;
|
||||||
|
|
@ -468,6 +468,37 @@ impl OverlayContext {
|
||||||
self.square(position, None, Some(color_fill), Some(color_stroke));
|
self.square(position, None, Some(color_fill), Some(color_stroke));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn gradient_color_stop(&mut self, position: DVec2, selected: bool, color: &str) {
|
||||||
|
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 draw_circle = |radius: f64, width: Option<f64>, color: &str| {
|
||||||
|
self.render_context.begin_path();
|
||||||
|
self.render_context.arc(position.x, position.y, radius, 0., TAU).expect("Failed to draw the circle");
|
||||||
|
|
||||||
|
if let Some(width) = width {
|
||||||
|
self.render_context.set_line_width(width);
|
||||||
|
self.render_context.set_stroke_style_str(color);
|
||||||
|
self.render_context.stroke();
|
||||||
|
} else {
|
||||||
|
self.render_context.set_fill_style_str(color);
|
||||||
|
self.render_context.fill();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
// Fill
|
||||||
|
draw_circle(radius, None, color);
|
||||||
|
// Stroke (inner)
|
||||||
|
draw_circle(radius + stroke_width / 2., Some(1.), COLOR_OVERLAY_BLACK);
|
||||||
|
// Stroke (outer)
|
||||||
|
draw_circle(radius, Some(stroke_width), COLOR_OVERLAY_WHITE);
|
||||||
|
|
||||||
|
self.end_dpi_aware_transform();
|
||||||
|
}
|
||||||
|
|
||||||
pub fn hover_manipulator_handle(&mut self, position: DVec2, selected: bool) {
|
pub fn hover_manipulator_handle(&mut self, position: DVec2, selected: bool) {
|
||||||
self.start_dpi_aware_transform();
|
self.start_dpi_aware_transform();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
use super::tool_prelude::*;
|
use super::tool_prelude::*;
|
||||||
use crate::consts::{LINE_ROTATE_SNAP_ANGLE, MANIPULATOR_GROUP_MARKER_SIZE, SELECTION_THRESHOLD};
|
use crate::consts::{COLOR_OVERLAY_BLUE, DRAG_THRESHOLD, LINE_ROTATE_SNAP_ANGLE, MANIPULATOR_GROUP_MARKER_SIZE, SEGMENT_INSERTION_DISTANCE, SEGMENT_OVERLAY_SIZE, SELECTION_THRESHOLD};
|
||||||
use crate::messages::portfolio::document::overlays::utility_types::OverlayContext;
|
use crate::messages::portfolio::document::overlays::utility_types::OverlayContext;
|
||||||
use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier;
|
use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier;
|
||||||
use crate::messages::tool::common_functionality::auto_panning::AutoPanning;
|
use crate::messages::tool::common_functionality::auto_panning::AutoPanning;
|
||||||
|
|
@ -25,6 +25,7 @@ pub enum GradientToolMessage {
|
||||||
// Standard messages
|
// Standard messages
|
||||||
Abort,
|
Abort,
|
||||||
Overlays { context: OverlayContext },
|
Overlays { context: OverlayContext },
|
||||||
|
SelectionChanged,
|
||||||
|
|
||||||
// Tool-specific messages
|
// Tool-specific messages
|
||||||
DeleteStop,
|
DeleteStop,
|
||||||
|
|
@ -76,30 +77,29 @@ impl<'a> MessageHandler<ToolMessage, &mut ToolActionMessageContext<'a>> for Grad
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(mut gradient) = get_gradient(layer, &context.document.network_interface) {
|
if let Some(mut gradient) = get_gradient(layer, &context.document.network_interface)
|
||||||
if gradient.gradient_type != gradient_type {
|
&& gradient.gradient_type != gradient_type
|
||||||
if !transaction_started {
|
{
|
||||||
responses.add(DocumentMessage::StartTransaction);
|
if !transaction_started {
|
||||||
transaction_started = true;
|
responses.add(DocumentMessage::StartTransaction);
|
||||||
}
|
transaction_started = true;
|
||||||
gradient.gradient_type = gradient_type;
|
|
||||||
responses.add(GraphOperationMessage::FillSet {
|
|
||||||
layer,
|
|
||||||
fill: Fill::Gradient(gradient),
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
gradient.gradient_type = gradient_type;
|
||||||
|
responses.add(GraphOperationMessage::FillSet {
|
||||||
|
layer,
|
||||||
|
fill: Fill::Gradient(gradient),
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if transaction_started {
|
if transaction_started {
|
||||||
responses.add(DocumentMessage::AddTransaction);
|
responses.add(DocumentMessage::AddTransaction);
|
||||||
}
|
}
|
||||||
if let Some(selected_gradient) = &mut self.data.selected_gradient {
|
if let Some(selected_gradient) = &mut self.data.selected_gradient
|
||||||
if let Some(layer) = selected_gradient.layer {
|
&& let Some(layer) = selected_gradient.layer
|
||||||
if !NodeGraphLayer::is_raster_layer(layer, &mut context.document.network_interface) {
|
&& !NodeGraphLayer::is_raster_layer(layer, &mut context.document.network_interface)
|
||||||
selected_gradient.gradient.gradient_type = gradient_type;
|
{
|
||||||
}
|
selected_gradient.gradient.gradient_type = gradient_type;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
responses.add(ToolMessage::UpdateHints);
|
responses.add(ToolMessage::UpdateHints);
|
||||||
responses.add(PropertiesPanelMessage::Refresh);
|
responses.add(PropertiesPanelMessage::Refresh);
|
||||||
|
|
@ -114,7 +114,6 @@ impl<'a> MessageHandler<ToolMessage, &mut ToolActionMessageContext<'a>> for Grad
|
||||||
PointerUp,
|
PointerUp,
|
||||||
PointerMove,
|
PointerMove,
|
||||||
Abort,
|
Abort,
|
||||||
InsertStop,
|
|
||||||
DeleteStop,
|
DeleteStop,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -142,13 +141,18 @@ impl LayoutHolder for GradientTool {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
|
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||||
enum GradientToolFsmState {
|
enum GradientToolFsmState {
|
||||||
#[default]
|
Ready { hover_insertion: bool },
|
||||||
Ready,
|
|
||||||
Drawing,
|
Drawing,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Default for GradientToolFsmState {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::Ready { hover_insertion: false }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Computes the transform from gradient space to viewport space (where gradient space is 0..1)
|
/// Computes the transform from gradient space to viewport space (where gradient space is 0..1)
|
||||||
fn gradient_space_transform(layer: LayerNodeIdentifier, document: &DocumentMessageHandler) -> DAffine2 {
|
fn gradient_space_transform(layer: LayerNodeIdentifier, document: &DocumentMessageHandler) -> DAffine2 {
|
||||||
let bounds = document.metadata().nonzero_bounding_box(layer);
|
let bounds = document.metadata().nonzero_bounding_box(layer);
|
||||||
|
|
@ -165,6 +169,7 @@ pub enum GradientDragTarget {
|
||||||
#[default]
|
#[default]
|
||||||
End,
|
End,
|
||||||
Step(usize),
|
Step(usize),
|
||||||
|
New,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Contains information about the selected gradient handle
|
/// Contains information about the selected gradient handle
|
||||||
|
|
@ -174,6 +179,28 @@ struct SelectedGradient {
|
||||||
transform: DAffine2,
|
transform: DAffine2,
|
||||||
gradient: Gradient,
|
gradient: Gradient,
|
||||||
dragging: GradientDragTarget,
|
dragging: GradientDragTarget,
|
||||||
|
initial_gradient: Gradient,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn calculate_insertion(start: DVec2, end: DVec2, stops: &[(f64, graphene_std::Color)], mouse: DVec2) -> Option<f64> {
|
||||||
|
let distance = (end - start).angle_to(mouse - start).sin() * (mouse - start).length();
|
||||||
|
let projection = ((end - start).angle_to(mouse - start)).cos() * start.distance(mouse) / start.distance(end);
|
||||||
|
|
||||||
|
if distance.abs() < SEGMENT_INSERTION_DISTANCE && (0. ..=1.).contains(&projection) {
|
||||||
|
for (position, _) in stops {
|
||||||
|
let stop_pos = start.lerp(end, *position);
|
||||||
|
if stop_pos.distance_squared(mouse) < (MANIPULATOR_GROUP_MARKER_SIZE * 2.).powi(2) {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if start.distance_squared(mouse) < (MANIPULATOR_GROUP_MARKER_SIZE * 2.).powi(2) || end.distance_squared(mouse) < (MANIPULATOR_GROUP_MARKER_SIZE * 2.).powi(2) {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Some(projection);
|
||||||
|
}
|
||||||
|
|
||||||
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SelectedGradient {
|
impl SelectedGradient {
|
||||||
|
|
@ -182,22 +209,26 @@ impl SelectedGradient {
|
||||||
Self {
|
Self {
|
||||||
layer: Some(layer),
|
layer: Some(layer),
|
||||||
transform,
|
transform,
|
||||||
gradient,
|
gradient: gradient.clone(),
|
||||||
dragging: GradientDragTarget::End,
|
dragging: GradientDragTarget::End,
|
||||||
|
initial_gradient: gradient,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn with_gradient_start(mut self, start: DVec2) -> Self {
|
pub fn update_gradient(&mut self, mut mouse: DVec2, responses: &mut VecDeque<Message>, snap_rotate: bool, gradient_type: GradientType, drag_start: DVec2) {
|
||||||
self.gradient.start = self.transform.inverse().transform_point2(start);
|
if mouse.distance(drag_start) < DRAG_THRESHOLD {
|
||||||
self
|
self.gradient = self.initial_gradient.clone();
|
||||||
}
|
self.render_gradient(responses);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
pub fn update_gradient(&mut self, mut mouse: DVec2, responses: &mut VecDeque<Message>, snap_rotate: bool, gradient_type: GradientType) {
|
|
||||||
self.gradient.gradient_type = gradient_type;
|
self.gradient.gradient_type = gradient_type;
|
||||||
|
|
||||||
if snap_rotate && matches!(self.dragging, GradientDragTarget::End | GradientDragTarget::Start) {
|
if snap_rotate && matches!(self.dragging, GradientDragTarget::End | GradientDragTarget::Start | GradientDragTarget::New) {
|
||||||
let point = if self.dragging == GradientDragTarget::Start {
|
let point = if self.dragging == GradientDragTarget::Start {
|
||||||
self.transform.transform_point2(self.gradient.end)
|
self.transform.transform_point2(self.gradient.end)
|
||||||
|
} else if self.dragging == GradientDragTarget::New {
|
||||||
|
drag_start
|
||||||
} else {
|
} else {
|
||||||
self.transform.transform_point2(self.gradient.start)
|
self.transform.transform_point2(self.gradient.start)
|
||||||
};
|
};
|
||||||
|
|
@ -219,6 +250,10 @@ impl SelectedGradient {
|
||||||
match self.dragging {
|
match self.dragging {
|
||||||
GradientDragTarget::Start => self.gradient.start = transformed_mouse,
|
GradientDragTarget::Start => self.gradient.start = transformed_mouse,
|
||||||
GradientDragTarget::End => self.gradient.end = transformed_mouse,
|
GradientDragTarget::End => self.gradient.end = transformed_mouse,
|
||||||
|
GradientDragTarget::New => {
|
||||||
|
self.gradient.start = self.transform.inverse().transform_point2(drag_start);
|
||||||
|
self.gradient.end = transformed_mouse;
|
||||||
|
}
|
||||||
GradientDragTarget::Step(s) => {
|
GradientDragTarget::Step(s) => {
|
||||||
let (start, end) = (self.transform.transform_point2(self.gradient.start), self.transform.transform_point2(self.gradient.end));
|
let (start, end) = (self.transform.transform_point2(self.gradient.start), self.transform.transform_point2(self.gradient.end));
|
||||||
|
|
||||||
|
|
@ -259,6 +294,7 @@ impl ToolTransition for GradientTool {
|
||||||
fn event_to_message_map(&self) -> EventToMessageMap {
|
fn event_to_message_map(&self) -> EventToMessageMap {
|
||||||
EventToMessageMap {
|
EventToMessageMap {
|
||||||
tool_abort: Some(GradientToolMessage::Abort.into()),
|
tool_abort: Some(GradientToolMessage::Abort.into()),
|
||||||
|
selection_changed: Some(GradientToolMessage::SelectionChanged.into()),
|
||||||
overlay_provider: Some(|context| GradientToolMessage::Overlays { context }.into()),
|
overlay_provider: Some(|context| GradientToolMessage::Overlays { context }.into()),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
}
|
}
|
||||||
|
|
@ -297,6 +333,7 @@ impl Fsm for GradientToolFsmState {
|
||||||
match (self, event) {
|
match (self, event) {
|
||||||
(_, GradientToolMessage::Overlays { context: mut overlay_context }) => {
|
(_, GradientToolMessage::Overlays { context: mut overlay_context }) => {
|
||||||
let selected = tool_data.selected_gradient.as_ref();
|
let selected = tool_data.selected_gradient.as_ref();
|
||||||
|
let mouse = input.mouse.position;
|
||||||
|
|
||||||
for layer in document.network_interface.selected_nodes().selected_visible_layers(&document.network_interface) {
|
for layer in document.network_interface.selected_nodes().selected_visible_layers(&document.network_interface) {
|
||||||
let Some(gradient) = get_gradient(layer, &document.network_interface) else { continue };
|
let Some(gradient) = get_gradient(layer, &document.network_interface) else { continue };
|
||||||
|
|
@ -305,25 +342,49 @@ impl Fsm for GradientToolFsmState {
|
||||||
.filter(|selected| selected.layer.is_some_and(|selected_layer| selected_layer == layer))
|
.filter(|selected| selected.layer.is_some_and(|selected_layer| selected_layer == layer))
|
||||||
.map(|selected| selected.dragging);
|
.map(|selected| selected.dragging);
|
||||||
|
|
||||||
|
let gradient = if dragging.is_some()
|
||||||
|
&& let Some(selected_gradient) = selected.filter(|s| s.layer == Some(layer))
|
||||||
|
{
|
||||||
|
&selected_gradient.gradient
|
||||||
|
} else {
|
||||||
|
&gradient
|
||||||
|
};
|
||||||
|
|
||||||
let Gradient { start, end, stops, .. } = gradient;
|
let Gradient { start, end, stops, .. } = gradient;
|
||||||
let (start, end) = (transform.transform_point2(start), transform.transform_point2(end));
|
let (start, end) = (transform.transform_point2(*start), transform.transform_point2(*end));
|
||||||
|
|
||||||
|
fn color_to_hex(color: graphene_std::Color) -> String {
|
||||||
|
format!("#{}", color.with_alpha(1.).to_rgba_hex_srgb())
|
||||||
|
}
|
||||||
|
|
||||||
|
let start_hex = stops.first().map(|(_, c)| color_to_hex(*c)).unwrap_or(String::from(COLOR_OVERLAY_BLUE));
|
||||||
|
let end_hex = stops.last().map(|(_, c)| color_to_hex(*c)).unwrap_or(String::from(COLOR_OVERLAY_BLUE));
|
||||||
|
|
||||||
overlay_context.line(start, end, None, None);
|
overlay_context.line(start, end, None, None);
|
||||||
overlay_context.manipulator_handle(start, dragging == Some(GradientDragTarget::Start), None);
|
overlay_context.gradient_color_stop(start, dragging == Some(GradientDragTarget::Start), &start_hex);
|
||||||
overlay_context.manipulator_handle(end, dragging == Some(GradientDragTarget::End), None);
|
overlay_context.gradient_color_stop(end, dragging == Some(GradientDragTarget::End), &end_hex);
|
||||||
|
|
||||||
for (index, (position, _)) in stops.into_iter().enumerate() {
|
for (index, (position, color)) in stops.clone().into_iter().enumerate() {
|
||||||
if position.abs() < f64::EPSILON * 1000. || (1. - position).abs() < f64::EPSILON * 1000. {
|
if position.abs() < f64::EPSILON * 1000. || (1. - position).abs() < f64::EPSILON * 1000. {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
overlay_context.gradient_color_stop(start.lerp(end, position), dragging == Some(GradientDragTarget::Step(index)), &color_to_hex(color));
|
||||||
|
}
|
||||||
|
|
||||||
overlay_context.manipulator_handle(start.lerp(end, position), dragging == Some(GradientDragTarget::Step(index)), None);
|
if let (Some(projection), Some(dir)) = (calculate_insertion(start, end, stops, mouse), (end - start).try_normalize()) {
|
||||||
|
let perp = dir.perp();
|
||||||
|
let point = start.lerp(end, projection);
|
||||||
|
overlay_context.line(point - perp * SEGMENT_OVERLAY_SIZE, point + perp * SEGMENT_OVERLAY_SIZE, Some(COLOR_OVERLAY_BLUE), Some(1.));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
(GradientToolFsmState::Ready, GradientToolMessage::DeleteStop) => {
|
(GradientToolFsmState::Ready { .. }, GradientToolMessage::SelectionChanged) => {
|
||||||
|
tool_data.selected_gradient = None;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
(GradientToolFsmState::Ready { .. }, GradientToolMessage::DeleteStop) => {
|
||||||
let Some(selected_gradient) = &mut tool_data.selected_gradient else {
|
let Some(selected_gradient) = &mut tool_data.selected_gradient else {
|
||||||
return self;
|
return self;
|
||||||
};
|
};
|
||||||
|
|
@ -333,7 +394,7 @@ impl Fsm for GradientToolFsmState {
|
||||||
return self;
|
return self;
|
||||||
}
|
}
|
||||||
|
|
||||||
responses.add(DocumentMessage::AddTransaction);
|
responses.add(DocumentMessage::StartTransaction);
|
||||||
|
|
||||||
// Remove the selected point
|
// Remove the selected point
|
||||||
match selected_gradient.dragging {
|
match selected_gradient.dragging {
|
||||||
|
|
@ -346,6 +407,7 @@ impl Fsm for GradientToolFsmState {
|
||||||
GradientDragTarget::Step(index) => {
|
GradientDragTarget::Step(index) => {
|
||||||
selected_gradient.gradient.stops.remove(index);
|
selected_gradient.gradient.stops.remove(index);
|
||||||
}
|
}
|
||||||
|
GradientDragTarget::New => {}
|
||||||
};
|
};
|
||||||
|
|
||||||
// The gradient has only one point and so should become a fill
|
// The gradient has only one point and so should become a fill
|
||||||
|
|
@ -356,6 +418,8 @@ impl Fsm for GradientToolFsmState {
|
||||||
fill: Fill::Solid(selected_gradient.gradient.stops[0].1),
|
fill: Fill::Solid(selected_gradient.gradient.stops[0].1),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
responses.add(DocumentMessage::CommitTransaction);
|
||||||
|
responses.add(PropertiesPanelMessage::Refresh);
|
||||||
return self;
|
return self;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -377,6 +441,9 @@ impl Fsm for GradientToolFsmState {
|
||||||
|
|
||||||
// Render the new gradient
|
// Render the new gradient
|
||||||
selected_gradient.render_gradient(responses);
|
selected_gradient.render_gradient(responses);
|
||||||
|
responses.add(DocumentMessage::CommitTransaction);
|
||||||
|
responses.add(PropertiesPanelMessage::Refresh);
|
||||||
|
tool_data.selected_gradient = None;
|
||||||
|
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
@ -395,7 +462,7 @@ impl Fsm for GradientToolFsmState {
|
||||||
if distance < (SELECTION_THRESHOLD * 2.) {
|
if distance < (SELECTION_THRESHOLD * 2.) {
|
||||||
// Try and insert the new stop
|
// Try and insert the new stop
|
||||||
if let Some(index) = gradient.insert_stop(mouse, transform) {
|
if let Some(index) = gradient.insert_stop(mouse, transform) {
|
||||||
responses.add(DocumentMessage::AddTransaction);
|
responses.add(DocumentMessage::StartTransaction);
|
||||||
|
|
||||||
let mut selected_gradient = SelectedGradient::new(gradient, layer, document);
|
let mut selected_gradient = SelectedGradient::new(gradient, layer, document);
|
||||||
|
|
||||||
|
|
@ -406,7 +473,7 @@ impl Fsm for GradientToolFsmState {
|
||||||
selected_gradient.render_gradient(responses);
|
selected_gradient.render_gradient(responses);
|
||||||
|
|
||||||
tool_data.selected_gradient = Some(selected_gradient);
|
tool_data.selected_gradient = Some(selected_gradient);
|
||||||
|
responses.add(DocumentMessage::CommitTransaction);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -414,12 +481,13 @@ impl Fsm for GradientToolFsmState {
|
||||||
|
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
(GradientToolFsmState::Ready, GradientToolMessage::PointerDown) => {
|
(GradientToolFsmState::Ready { .. }, GradientToolMessage::PointerDown) => {
|
||||||
let mouse = input.mouse.position;
|
let mouse = input.mouse.position;
|
||||||
tool_data.drag_start = mouse;
|
tool_data.drag_start = mouse;
|
||||||
let tolerance = (MANIPULATOR_GROUP_MARKER_SIZE * 2.).powi(2);
|
let tolerance = (MANIPULATOR_GROUP_MARKER_SIZE * 2.).powi(2);
|
||||||
|
|
||||||
let mut dragging = false;
|
let mut dragging = false;
|
||||||
|
let mut transaction_started = false;
|
||||||
for layer in document.network_interface.selected_nodes().selected_visible_layers(&document.network_interface) {
|
for layer in document.network_interface.selected_nodes().selected_visible_layers(&document.network_interface) {
|
||||||
let Some(gradient) = get_gradient(layer, &document.network_interface) else { continue };
|
let Some(gradient) = get_gradient(layer, &document.network_interface) else { continue };
|
||||||
let transform = gradient_space_transform(layer, document);
|
let transform = gradient_space_transform(layer, document);
|
||||||
|
|
@ -433,6 +501,7 @@ impl Fsm for GradientToolFsmState {
|
||||||
transform,
|
transform,
|
||||||
gradient: gradient.clone(),
|
gradient: gradient.clone(),
|
||||||
dragging: GradientDragTarget::Step(index),
|
dragging: GradientDragTarget::Step(index),
|
||||||
|
initial_gradient: gradient.clone(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -447,9 +516,34 @@ impl Fsm for GradientToolFsmState {
|
||||||
transform,
|
transform,
|
||||||
gradient: gradient.clone(),
|
gradient: gradient.clone(),
|
||||||
dragging: dragging_target,
|
dragging: dragging_target,
|
||||||
|
initial_gradient: gradient.clone(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Insert stop if clicking on line
|
||||||
|
if !dragging {
|
||||||
|
let (start, end) = (transform.transform_point2(gradient.start), transform.transform_point2(gradient.end));
|
||||||
|
let distance = (end - start).angle_to(mouse - start).sin() * (mouse - start).length();
|
||||||
|
let projection = ((end - start).angle_to(mouse - start)).cos() * start.distance(mouse) / start.distance(end);
|
||||||
|
|
||||||
|
if distance.abs() < SEGMENT_INSERTION_DISTANCE
|
||||||
|
&& (0. ..=1.).contains(&projection)
|
||||||
|
&& let Some(index) = gradient.clone().insert_stop(mouse, transform)
|
||||||
|
{
|
||||||
|
responses.add(DocumentMessage::StartTransaction);
|
||||||
|
transaction_started = true;
|
||||||
|
let mut new_gradient = gradient.clone();
|
||||||
|
new_gradient.insert_stop(mouse, transform);
|
||||||
|
|
||||||
|
let mut selected_gradient = SelectedGradient::new(new_gradient, layer, document);
|
||||||
|
selected_gradient.dragging = GradientDragTarget::Step(index);
|
||||||
|
// No offset when inserting a new stop, it should be exactly under the mouse
|
||||||
|
selected_gradient.render_gradient(responses);
|
||||||
|
tool_data.selected_gradient = Some(selected_gradient);
|
||||||
|
dragging = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let gradient_state = if dragging {
|
let gradient_state = if dragging {
|
||||||
|
|
@ -461,7 +555,7 @@ impl Fsm for GradientToolFsmState {
|
||||||
if let Some(layer) = selected_layer {
|
if let Some(layer) = selected_layer {
|
||||||
// Add check for raster layer
|
// Add check for raster layer
|
||||||
if NodeGraphLayer::is_raster_layer(layer, &mut document.network_interface) {
|
if NodeGraphLayer::is_raster_layer(layer, &mut document.network_interface) {
|
||||||
return GradientToolFsmState::Ready;
|
return GradientToolFsmState::Ready { hover_insertion: false };
|
||||||
}
|
}
|
||||||
if !document.network_interface.selected_nodes().selected_layers_contains(layer, document.metadata()) {
|
if !document.network_interface.selected_nodes().selected_layers_contains(layer, document.metadata()) {
|
||||||
let nodes = vec![layer.to_node()];
|
let nodes = vec![layer.to_node()];
|
||||||
|
|
@ -476,22 +570,35 @@ impl Fsm for GradientToolFsmState {
|
||||||
// Generate a new gradient
|
// Generate a new gradient
|
||||||
Gradient::new(DVec2::ZERO, global_tool_data.secondary_color, DVec2::ONE, global_tool_data.primary_color, tool_options.gradient_type)
|
Gradient::new(DVec2::ZERO, global_tool_data.secondary_color, DVec2::ONE, global_tool_data.primary_color, tool_options.gradient_type)
|
||||||
};
|
};
|
||||||
let selected_gradient = SelectedGradient::new(gradient, layer, document).with_gradient_start(input.mouse.position);
|
let mut selected_gradient = SelectedGradient::new(gradient, layer, document);
|
||||||
|
selected_gradient.dragging = GradientDragTarget::New;
|
||||||
|
|
||||||
tool_data.selected_gradient = Some(selected_gradient);
|
tool_data.selected_gradient = Some(selected_gradient);
|
||||||
|
|
||||||
GradientToolFsmState::Drawing
|
GradientToolFsmState::Drawing
|
||||||
} else {
|
} else {
|
||||||
GradientToolFsmState::Ready
|
GradientToolFsmState::Ready { hover_insertion: false }
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
responses.add(DocumentMessage::StartTransaction);
|
|
||||||
|
if gradient_state == GradientToolFsmState::Drawing && !transaction_started {
|
||||||
|
responses.add(DocumentMessage::StartTransaction);
|
||||||
|
}
|
||||||
|
|
||||||
|
responses.add(OverlaysMessage::Draw);
|
||||||
|
|
||||||
gradient_state
|
gradient_state
|
||||||
}
|
}
|
||||||
(GradientToolFsmState::Drawing, GradientToolMessage::PointerMove { constrain_axis }) => {
|
(GradientToolFsmState::Drawing, GradientToolMessage::PointerMove { constrain_axis }) => {
|
||||||
if let Some(selected_gradient) = &mut tool_data.selected_gradient {
|
if let Some(selected_gradient) = &mut tool_data.selected_gradient {
|
||||||
let mouse = input.mouse.position; // tool_data.snap_manager.snap_position(responses, document, input.mouse.position);
|
let mouse = input.mouse.position; // tool_data.snap_manager.snap_position(responses, document, input.mouse.position);
|
||||||
selected_gradient.update_gradient(mouse, responses, input.keyboard.get(constrain_axis as usize), selected_gradient.gradient.gradient_type);
|
selected_gradient.update_gradient(
|
||||||
|
mouse,
|
||||||
|
responses,
|
||||||
|
input.keyboard.get(constrain_axis as usize),
|
||||||
|
selected_gradient.gradient.gradient_type,
|
||||||
|
tool_data.drag_start,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Auto-panning
|
// Auto-panning
|
||||||
|
|
@ -501,6 +608,8 @@ impl Fsm for GradientToolFsmState {
|
||||||
];
|
];
|
||||||
tool_data.auto_panning.setup_by_mouse_position(input, viewport, &messages, responses);
|
tool_data.auto_panning.setup_by_mouse_position(input, viewport, &messages, responses);
|
||||||
|
|
||||||
|
responses.add(OverlaysMessage::Draw);
|
||||||
|
|
||||||
GradientToolFsmState::Drawing
|
GradientToolFsmState::Drawing
|
||||||
}
|
}
|
||||||
(GradientToolFsmState::Drawing, GradientToolMessage::PointerOutsideViewport { .. }) => {
|
(GradientToolFsmState::Drawing, GradientToolMessage::PointerOutsideViewport { .. }) => {
|
||||||
|
|
@ -524,7 +633,7 @@ impl Fsm for GradientToolFsmState {
|
||||||
state
|
state
|
||||||
}
|
}
|
||||||
(GradientToolFsmState::Drawing, GradientToolMessage::PointerUp) => {
|
(GradientToolFsmState::Drawing, GradientToolMessage::PointerUp) => {
|
||||||
input.mouse.finish_transaction(tool_data.drag_start, responses);
|
responses.add(DocumentMessage::EndTransaction);
|
||||||
tool_data.snap_manager.cleanup(responses);
|
tool_data.snap_manager.cleanup(responses);
|
||||||
let was_dragging = tool_data.selected_gradient.is_some();
|
let was_dragging = tool_data.selected_gradient.is_some();
|
||||||
|
|
||||||
|
|
@ -534,27 +643,51 @@ impl Fsm for GradientToolFsmState {
|
||||||
{
|
{
|
||||||
tool_data.selected_gradient = Some(SelectedGradient::new(gradient, selected_layer, document));
|
tool_data.selected_gradient = Some(SelectedGradient::new(gradient, selected_layer, document));
|
||||||
}
|
}
|
||||||
GradientToolFsmState::Ready
|
GradientToolFsmState::Ready { hover_insertion: false }
|
||||||
|
}
|
||||||
|
(GradientToolFsmState::Ready { .. }, GradientToolMessage::PointerMove { .. }) => {
|
||||||
|
let mut hover_insertion = false;
|
||||||
|
let mouse = input.mouse.position;
|
||||||
|
|
||||||
|
for layer in document.network_interface.selected_nodes().selected_visible_layers(&document.network_interface) {
|
||||||
|
let Some(gradient) = get_gradient(layer, &document.network_interface) else { continue };
|
||||||
|
let transform = gradient_space_transform(layer, document);
|
||||||
|
let start = transform.transform_point2(gradient.start);
|
||||||
|
let end = transform.transform_point2(gradient.end);
|
||||||
|
|
||||||
|
if calculate_insertion(start, end, &gradient.stops, mouse).is_some() {
|
||||||
|
hover_insertion = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
responses.add(OverlaysMessage::Draw);
|
||||||
|
GradientToolFsmState::Ready { hover_insertion }
|
||||||
}
|
}
|
||||||
|
|
||||||
(GradientToolFsmState::Drawing, GradientToolMessage::Abort) => {
|
(GradientToolFsmState::Drawing, GradientToolMessage::Abort) => {
|
||||||
responses.add(DocumentMessage::AbortTransaction);
|
responses.add(DocumentMessage::AbortTransaction);
|
||||||
tool_data.snap_manager.cleanup(responses);
|
tool_data.snap_manager.cleanup(responses);
|
||||||
|
tool_data.selected_gradient = None;
|
||||||
responses.add(OverlaysMessage::Draw);
|
responses.add(OverlaysMessage::Draw);
|
||||||
|
|
||||||
GradientToolFsmState::Ready
|
GradientToolFsmState::Ready { hover_insertion: false }
|
||||||
}
|
}
|
||||||
(_, GradientToolMessage::Abort) => GradientToolFsmState::Ready,
|
(_, GradientToolMessage::Abort) => GradientToolFsmState::Ready { hover_insertion: false },
|
||||||
_ => self,
|
_ => self,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update_hints(&self, responses: &mut VecDeque<Message>) {
|
fn update_hints(&self, responses: &mut VecDeque<Message>) {
|
||||||
let hint_data = match self {
|
let hint_data = match self {
|
||||||
GradientToolFsmState::Ready => HintData(vec![HintGroup(vec![
|
GradientToolFsmState::Ready { hover_insertion } => {
|
||||||
HintInfo::mouse(MouseMotion::LmbDrag, "Draw Gradient"),
|
let hints = if *hover_insertion {
|
||||||
HintInfo::keys([Key::Shift], "15° Increments").prepend_plus(),
|
vec![HintInfo::mouse(MouseMotion::Lmb, "Insert Color Stop")]
|
||||||
])]),
|
} else {
|
||||||
|
vec![HintInfo::mouse(MouseMotion::LmbDrag, "Draw Gradient"), HintInfo::keys([Key::Shift], "15° Increments").prepend_plus()]
|
||||||
|
};
|
||||||
|
HintData(vec![HintGroup(hints)])
|
||||||
|
}
|
||||||
GradientToolFsmState::Drawing => HintData(vec![
|
GradientToolFsmState::Drawing => HintData(vec![
|
||||||
HintGroup(vec![HintInfo::mouse(MouseMotion::Rmb, ""), HintInfo::keys([Key::Escape], "Cancel").prepend_slash()]),
|
HintGroup(vec![HintInfo::mouse(MouseMotion::Rmb, ""), HintInfo::keys([Key::Escape], "Cancel").prepend_slash()]),
|
||||||
HintGroup(vec![HintInfo::keys([Key::Shift], "15° Increments")]),
|
HintGroup(vec![HintInfo::keys([Key::Shift], "15° Increments")]),
|
||||||
|
|
@ -724,7 +857,7 @@ mod test_gradient {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn double_click_insert_stop() {
|
async fn click_to_insert_stop() {
|
||||||
let mut editor = EditorTestUtils::create();
|
let mut editor = EditorTestUtils::create();
|
||||||
editor.new_document().await;
|
editor.new_document().await;
|
||||||
|
|
||||||
|
|
@ -738,7 +871,9 @@ mod test_gradient {
|
||||||
assert_eq!(initial_gradient.stops.len(), 2, "Expected 2 stops, found {}", initial_gradient.stops.len());
|
assert_eq!(initial_gradient.stops.len(), 2, "Expected 2 stops, found {}", initial_gradient.stops.len());
|
||||||
|
|
||||||
editor.select_tool(ToolType::Gradient).await;
|
editor.select_tool(ToolType::Gradient).await;
|
||||||
editor.double_click(DVec2::new(50., 0.)).await;
|
editor.move_mouse(50., 0., ModifierKeys::empty(), MouseKeys::empty()).await;
|
||||||
|
editor.left_mousedown(50., 0., ModifierKeys::empty()).await;
|
||||||
|
editor.left_mouseup(50., 0., ModifierKeys::empty()).await;
|
||||||
|
|
||||||
// Check that a new stop has been added
|
// Check that a new stop has been added
|
||||||
let (updated_gradient, _) = get_gradient(&mut editor).await;
|
let (updated_gradient, _) = get_gradient(&mut editor).await;
|
||||||
|
|
@ -831,7 +966,9 @@ mod test_gradient {
|
||||||
editor.select_tool(ToolType::Gradient).await;
|
editor.select_tool(ToolType::Gradient).await;
|
||||||
|
|
||||||
// Add a middle stop at 50%
|
// Add a middle stop at 50%
|
||||||
editor.double_click(DVec2::new(50., 0.)).await;
|
editor.move_mouse(50., 0., ModifierKeys::empty(), MouseKeys::empty()).await;
|
||||||
|
editor.left_mousedown(50., 0., ModifierKeys::empty()).await;
|
||||||
|
editor.left_mouseup(50., 0., ModifierKeys::empty()).await;
|
||||||
|
|
||||||
let (initial_gradient, _) = get_gradient(&mut editor).await;
|
let (initial_gradient, _) = get_gradient(&mut editor).await;
|
||||||
assert_eq!(initial_gradient.stops.len(), 3, "Expected 3 stops, found {}", initial_gradient.stops.len());
|
assert_eq!(initial_gradient.stops.len(), 3, "Expected 3 stops, found {}", initial_gradient.stops.len());
|
||||||
|
|
@ -906,8 +1043,13 @@ mod test_gradient {
|
||||||
editor.select_tool(ToolType::Gradient).await;
|
editor.select_tool(ToolType::Gradient).await;
|
||||||
|
|
||||||
// Add two middle stops
|
// Add two middle stops
|
||||||
editor.double_click(DVec2::new(25., 0.)).await;
|
editor.move_mouse(25., 0., ModifierKeys::empty(), MouseKeys::empty()).await;
|
||||||
editor.double_click(DVec2::new(75., 0.)).await;
|
editor.left_mousedown(25., 0., ModifierKeys::empty()).await;
|
||||||
|
editor.left_mouseup(25., 0., ModifierKeys::empty()).await;
|
||||||
|
|
||||||
|
editor.move_mouse(75., 0., ModifierKeys::empty(), MouseKeys::empty()).await;
|
||||||
|
editor.left_mousedown(75., 0., ModifierKeys::empty()).await;
|
||||||
|
editor.left_mouseup(75., 0., ModifierKeys::empty()).await;
|
||||||
|
|
||||||
let (updated_gradient, _) = get_gradient(&mut editor).await;
|
let (updated_gradient, _) = get_gradient(&mut editor).await;
|
||||||
assert_eq!(updated_gradient.stops.len(), 4, "Expected 4 stops, found {}", updated_gradient.stops.len());
|
assert_eq!(updated_gradient.stops.len(), 4, "Expected 4 stops, found {}", updated_gradient.stops.len());
|
||||||
|
|
|
||||||
|
|
@ -210,6 +210,18 @@ impl EditorTestUtils {
|
||||||
.await;
|
.await;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn left_mouseup(&mut self, x: f64, y: f64, modifier_keys: ModifierKeys) {
|
||||||
|
self.mouseup(
|
||||||
|
EditorMouseState {
|
||||||
|
editor_position: (x, y).into(),
|
||||||
|
mouse_keys: MouseKeys::empty(),
|
||||||
|
scroll_delta: ScrollDelta::default(),
|
||||||
|
},
|
||||||
|
modifier_keys,
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn input(&mut self, message: InputPreprocessorMessage) {
|
pub async fn input(&mut self, message: InputPreprocessorMessage) {
|
||||||
self.handle_message(Message::InputPreprocessor(message)).await;
|
self.handle_message(Message::InputPreprocessor(message)).await;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue