Add snapping to endpoints and stops in the Gradient tool (#3732)
* snapping * Cleanup * fix * Fix snapping failing sometimes on newly drawn gradient lines * Code cleanup --------- Co-authored-by: Keavon Chambers <keavon@keavon.com>
This commit is contained in:
parent
691d965bcf
commit
3b91d02fff
|
|
@ -484,6 +484,7 @@ impl OverlayContext {
|
|||
self.render_context.set_line_width(width);
|
||||
self.render_context.set_stroke_style_str(color);
|
||||
self.render_context.stroke();
|
||||
self.render_context.set_line_width(1.);
|
||||
} else {
|
||||
self.render_context.set_fill_style_str(color);
|
||||
self.render_context.fill();
|
||||
|
|
|
|||
|
|
@ -307,6 +307,19 @@ pub enum PathSnapSource {
|
|||
IntersectionPoint,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum GradientSnapSource {
|
||||
Endpoint,
|
||||
}
|
||||
|
||||
impl fmt::Display for GradientSnapSource {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
GradientSnapSource::Endpoint => write!(f, "Gradient: Endpoint"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for PathSnapSource {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
|
|
@ -347,6 +360,7 @@ pub enum SnapSource {
|
|||
Artboard(ArtboardSnapSource),
|
||||
Path(PathSnapSource),
|
||||
Alignment(AlignmentSnapSource),
|
||||
Gradient(GradientSnapSource),
|
||||
}
|
||||
|
||||
impl SnapSource {
|
||||
|
|
@ -377,6 +391,7 @@ impl fmt::Display for SnapSource {
|
|||
SnapSource::Artboard(artboard_snap_source) => write!(f, "{artboard_snap_source}"),
|
||||
SnapSource::Path(path_snap_source) => write!(f, "{path_snap_source}"),
|
||||
SnapSource::Alignment(alignment_snap_source) => write!(f, "{alignment_snap_source}"),
|
||||
SnapSource::Gradient(gradient_snap_source) => write!(f, "{gradient_snap_source}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -251,15 +251,23 @@ impl SnapManager {
|
|||
pub fn update_indicator(&mut self, snapped_point: SnappedPoint) {
|
||||
self.indicator = snapped_point.is_snapped().then_some(snapped_point);
|
||||
}
|
||||
|
||||
pub fn clear_indicator(&mut self) {
|
||||
self.indicator = None;
|
||||
}
|
||||
|
||||
pub fn preview_draw(&mut self, snap_data: &SnapData, mouse: DVec2) {
|
||||
let point = SnapCandidatePoint::handle(snap_data.document.metadata().document_to_viewport.inverse().transform_point2(mouse));
|
||||
let snapped = self.free_snap(snap_data, &point, SnapTypeConfiguration::default());
|
||||
self.update_indicator(snapped);
|
||||
}
|
||||
|
||||
pub fn preview_draw_gradient(&mut self, snap_data: &SnapData, mouse: DVec2) {
|
||||
let point = SnapCandidatePoint::gradient_handle(snap_data.document.metadata().document_to_viewport.inverse().transform_point2(mouse));
|
||||
let snapped = self.free_snap(snap_data, &point, SnapTypeConfiguration::default());
|
||||
self.update_indicator(snapped);
|
||||
}
|
||||
|
||||
pub fn indicator_pos(&self) -> Option<DVec2> {
|
||||
self.indicator.as_ref().map(|point| point.snapped_point_document)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -453,6 +453,10 @@ impl SnapCandidatePoint {
|
|||
Self::new_source(document_point, SnapSource::Path(PathSnapSource::AnchorPointWithFreeHandles))
|
||||
}
|
||||
|
||||
pub fn gradient_handle(document_point: DVec2) -> Self {
|
||||
Self::new_source(document_point, SnapSource::Gradient(GradientSnapSource::Endpoint))
|
||||
}
|
||||
|
||||
pub fn handle_neighbors(document_point: DVec2, neighbors: impl Into<Vec<DVec2>>) -> Self {
|
||||
let mut point = Self::new_source(document_point, SnapSource::Path(PathSnapSource::AnchorPointWithFreeHandles));
|
||||
point.neighbors = neighbors.into();
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ use crate::messages::portfolio::document::overlays::utility_types::OverlayContex
|
|||
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::graph_modification_utils::{NodeGraphLayer, get_gradient};
|
||||
use crate::messages::tool::common_functionality::snapping::SnapManager;
|
||||
use crate::messages::tool::common_functionality::snapping::{SnapCandidatePoint, SnapConstraint, SnapData, SnapManager, SnapTypeConfiguration};
|
||||
use graphene_std::vector::style::{Fill, Gradient, GradientStops, GradientType};
|
||||
|
||||
#[derive(Default, ExtractField)]
|
||||
|
|
@ -215,7 +215,17 @@ impl SelectedGradient {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn update_gradient(&mut self, mut mouse: DVec2, responses: &mut VecDeque<Message>, snap_rotate: bool, gradient_type: GradientType, drag_start: DVec2) {
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn update_gradient(
|
||||
&mut self,
|
||||
mut mouse: DVec2,
|
||||
responses: &mut VecDeque<Message>,
|
||||
snap_rotate: bool,
|
||||
gradient_type: GradientType,
|
||||
drag_start: DVec2,
|
||||
snap_data: SnapData,
|
||||
snap_manager: &mut SnapManager,
|
||||
) {
|
||||
if mouse.distance(drag_start) < DRAG_THRESHOLD {
|
||||
self.gradient = self.initial_gradient.clone();
|
||||
self.render_gradient(responses);
|
||||
|
|
@ -243,22 +253,60 @@ impl SelectedGradient {
|
|||
|
||||
let rotated = DVec2::new(length * angle.cos(), length * angle.sin());
|
||||
mouse = point - rotated;
|
||||
} else {
|
||||
// Basic point snapping when not angle-constraining
|
||||
let document_to_viewport = snap_data.document.metadata().document_to_viewport;
|
||||
let document_mouse = document_to_viewport.inverse().transform_point2(mouse);
|
||||
let point_candidate = SnapCandidatePoint::gradient_handle(document_mouse);
|
||||
let snapped = snap_manager.free_snap(&snap_data, &point_candidate, SnapTypeConfiguration::default());
|
||||
if snapped.is_snapped() {
|
||||
mouse = document_to_viewport.transform_point2(snapped.snapped_point_document);
|
||||
}
|
||||
snap_manager.update_indicator(snapped);
|
||||
}
|
||||
|
||||
let transformed_mouse = self.transform.inverse().transform_point2(mouse);
|
||||
|
||||
match self.dragging {
|
||||
GradientDragTarget::Start => self.gradient.start = transformed_mouse,
|
||||
GradientDragTarget::End => self.gradient.end = transformed_mouse,
|
||||
GradientDragTarget::Start => {
|
||||
self.gradient.start = 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) => {
|
||||
let (start, end) = (self.transform.transform_point2(self.gradient.start), self.transform.transform_point2(self.gradient.end));
|
||||
let document_to_viewport = snap_data.document.metadata().document_to_viewport;
|
||||
|
||||
let (viewport_start, viewport_end) = (self.transform.transform_point2(self.gradient.start), self.transform.transform_point2(self.gradient.end));
|
||||
let (document_start, document_end) = (
|
||||
document_to_viewport.inverse().transform_point2(viewport_start),
|
||||
document_to_viewport.inverse().transform_point2(viewport_end),
|
||||
);
|
||||
|
||||
let constraint = SnapConstraint::Line {
|
||||
origin: document_start,
|
||||
direction: document_end - document_start,
|
||||
};
|
||||
|
||||
let document_mouse = document_to_viewport.inverse().transform_point2(mouse);
|
||||
let point_candidate = SnapCandidatePoint::gradient_handle(document_mouse);
|
||||
|
||||
let snapped = snap_manager.constrained_snap(&snap_data, &point_candidate, constraint, SnapTypeConfiguration::default());
|
||||
|
||||
let projected_mouse_document = if snapped.is_snapped() {
|
||||
snapped.snapped_point_document
|
||||
} else {
|
||||
constraint.projection(document_mouse)
|
||||
};
|
||||
let projected_mouse = document_to_viewport.transform_point2(projected_mouse_document);
|
||||
snap_manager.update_indicator(snapped);
|
||||
|
||||
// Calculate the new position by finding the closest point on the line
|
||||
let new_pos = ((end - start).angle_to(mouse - start)).cos() * start.distance(mouse) / start.distance(end);
|
||||
let new_pos = ((viewport_end - viewport_start).angle_to(projected_mouse - viewport_start)).cos() * viewport_start.distance(projected_mouse) / viewport_start.distance(viewport_end);
|
||||
|
||||
// Should not go off end but can swap
|
||||
let clamped = new_pos.clamp(0., 1.);
|
||||
|
|
@ -379,6 +427,9 @@ impl Fsm for GradientToolFsmState {
|
|||
}
|
||||
}
|
||||
|
||||
let snap_data = SnapData::new(document, input, viewport);
|
||||
tool_data.snap_manager.draw_overlays(snap_data, &mut overlay_context);
|
||||
|
||||
self
|
||||
}
|
||||
(GradientToolFsmState::Ready { .. }, GradientToolMessage::SelectionChanged) => {
|
||||
|
|
@ -483,7 +534,18 @@ impl Fsm for GradientToolFsmState {
|
|||
self
|
||||
}
|
||||
(GradientToolFsmState::Ready { .. }, GradientToolMessage::PointerDown) => {
|
||||
let mouse = input.mouse.position;
|
||||
let document_to_viewport = document.metadata().document_to_viewport;
|
||||
|
||||
let mut mouse = input.mouse.position;
|
||||
|
||||
let snap_data = SnapData::new(document, input, viewport);
|
||||
let point = SnapCandidatePoint::gradient_handle(document_to_viewport.inverse().transform_point2(mouse));
|
||||
let snapped = tool_data.snap_manager.free_snap(&snap_data, &point, SnapTypeConfiguration::default());
|
||||
|
||||
if snapped.is_snapped() {
|
||||
mouse = document_to_viewport.transform_point2(snapped.snapped_point_document);
|
||||
}
|
||||
|
||||
tool_data.drag_start = mouse;
|
||||
let tolerance = (MANIPULATOR_GROUP_MARKER_SIZE * 2.).powi(2);
|
||||
|
||||
|
|
@ -528,21 +590,19 @@ impl Fsm for GradientToolFsmState {
|
|||
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;
|
||||
if distance.abs() < SEGMENT_INSERTION_DISTANCE && (0. ..=1.).contains(&projection) {
|
||||
let mut new_gradient = gradient.clone();
|
||||
new_gradient.insert_stop(mouse, transform);
|
||||
if let Some(index) = new_gradient.insert_stop(mouse, transform) {
|
||||
responses.add(DocumentMessage::StartTransaction);
|
||||
transaction_started = true;
|
||||
|
||||
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 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -550,7 +610,8 @@ impl Fsm for GradientToolFsmState {
|
|||
let gradient_state = if dragging {
|
||||
GradientToolFsmState::Drawing
|
||||
} else {
|
||||
let selected_layer = document.click(input, viewport);
|
||||
let document_mouse = document.metadata().document_to_viewport.inverse().transform_point2(mouse);
|
||||
let selected_layer = document.click_based_on_position(document_mouse);
|
||||
|
||||
// Apply the gradient to the selected layer
|
||||
if let Some(layer) = selected_layer {
|
||||
|
|
@ -592,13 +653,17 @@ impl Fsm for GradientToolFsmState {
|
|||
}
|
||||
(GradientToolFsmState::Drawing, GradientToolMessage::PointerMove { constrain_axis }) => {
|
||||
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;
|
||||
let snap_data = SnapData::new(document, input, viewport);
|
||||
|
||||
selected_gradient.update_gradient(
|
||||
mouse,
|
||||
responses,
|
||||
input.keyboard.get(constrain_axis as usize),
|
||||
selected_gradient.gradient.gradient_type,
|
||||
tool_data.drag_start,
|
||||
snap_data,
|
||||
&mut tool_data.snap_manager,
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -662,6 +727,9 @@ impl Fsm for GradientToolFsmState {
|
|||
}
|
||||
}
|
||||
|
||||
let snap_data = SnapData::new(document, input, viewport);
|
||||
tool_data.snap_manager.preview_draw_gradient(&snap_data, mouse);
|
||||
|
||||
responses.add(OverlaysMessage::Draw);
|
||||
GradientToolFsmState::Ready { hover_insertion }
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue