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_line_width(width);
|
||||||
self.render_context.set_stroke_style_str(color);
|
self.render_context.set_stroke_style_str(color);
|
||||||
self.render_context.stroke();
|
self.render_context.stroke();
|
||||||
|
self.render_context.set_line_width(1.);
|
||||||
} else {
|
} else {
|
||||||
self.render_context.set_fill_style_str(color);
|
self.render_context.set_fill_style_str(color);
|
||||||
self.render_context.fill();
|
self.render_context.fill();
|
||||||
|
|
|
||||||
|
|
@ -307,6 +307,19 @@ pub enum PathSnapSource {
|
||||||
IntersectionPoint,
|
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 {
|
impl fmt::Display for PathSnapSource {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
match self {
|
match self {
|
||||||
|
|
@ -347,6 +360,7 @@ pub enum SnapSource {
|
||||||
Artboard(ArtboardSnapSource),
|
Artboard(ArtboardSnapSource),
|
||||||
Path(PathSnapSource),
|
Path(PathSnapSource),
|
||||||
Alignment(AlignmentSnapSource),
|
Alignment(AlignmentSnapSource),
|
||||||
|
Gradient(GradientSnapSource),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SnapSource {
|
impl SnapSource {
|
||||||
|
|
@ -377,6 +391,7 @@ impl fmt::Display for SnapSource {
|
||||||
SnapSource::Artboard(artboard_snap_source) => write!(f, "{artboard_snap_source}"),
|
SnapSource::Artboard(artboard_snap_source) => write!(f, "{artboard_snap_source}"),
|
||||||
SnapSource::Path(path_snap_source) => write!(f, "{path_snap_source}"),
|
SnapSource::Path(path_snap_source) => write!(f, "{path_snap_source}"),
|
||||||
SnapSource::Alignment(alignment_snap_source) => write!(f, "{alignment_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) {
|
pub fn update_indicator(&mut self, snapped_point: SnappedPoint) {
|
||||||
self.indicator = snapped_point.is_snapped().then_some(snapped_point);
|
self.indicator = snapped_point.is_snapped().then_some(snapped_point);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn clear_indicator(&mut self) {
|
pub fn clear_indicator(&mut self) {
|
||||||
self.indicator = None;
|
self.indicator = None;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn preview_draw(&mut self, snap_data: &SnapData, mouse: DVec2) {
|
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 point = SnapCandidatePoint::handle(snap_data.document.metadata().document_to_viewport.inverse().transform_point2(mouse));
|
||||||
let snapped = self.free_snap(snap_data, &point, SnapTypeConfiguration::default());
|
let snapped = self.free_snap(snap_data, &point, SnapTypeConfiguration::default());
|
||||||
self.update_indicator(snapped);
|
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> {
|
pub fn indicator_pos(&self) -> Option<DVec2> {
|
||||||
self.indicator.as_ref().map(|point| point.snapped_point_document)
|
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))
|
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 {
|
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));
|
let mut point = Self::new_source(document_point, SnapSource::Path(PathSnapSource::AnchorPointWithFreeHandles));
|
||||||
point.neighbors = neighbors.into();
|
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::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;
|
||||||
use crate::messages::tool::common_functionality::graph_modification_utils::{NodeGraphLayer, get_gradient};
|
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};
|
use graphene_std::vector::style::{Fill, Gradient, GradientStops, GradientType};
|
||||||
|
|
||||||
#[derive(Default, ExtractField)]
|
#[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 {
|
if mouse.distance(drag_start) < DRAG_THRESHOLD {
|
||||||
self.gradient = self.initial_gradient.clone();
|
self.gradient = self.initial_gradient.clone();
|
||||||
self.render_gradient(responses);
|
self.render_gradient(responses);
|
||||||
|
|
@ -243,22 +253,60 @@ impl SelectedGradient {
|
||||||
|
|
||||||
let rotated = DVec2::new(length * angle.cos(), length * angle.sin());
|
let rotated = DVec2::new(length * angle.cos(), length * angle.sin());
|
||||||
mouse = point - rotated;
|
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);
|
let transformed_mouse = self.transform.inverse().transform_point2(mouse);
|
||||||
|
|
||||||
match self.dragging {
|
match self.dragging {
|
||||||
GradientDragTarget::Start => self.gradient.start = transformed_mouse,
|
GradientDragTarget::Start => {
|
||||||
GradientDragTarget::End => self.gradient.end = transformed_mouse,
|
self.gradient.start = transformed_mouse;
|
||||||
|
}
|
||||||
|
GradientDragTarget::End => {
|
||||||
|
self.gradient.end = transformed_mouse;
|
||||||
|
}
|
||||||
GradientDragTarget::New => {
|
GradientDragTarget::New => {
|
||||||
self.gradient.start = self.transform.inverse().transform_point2(drag_start);
|
self.gradient.start = self.transform.inverse().transform_point2(drag_start);
|
||||||
self.gradient.end = transformed_mouse;
|
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 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
|
// 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
|
// Should not go off end but can swap
|
||||||
let clamped = new_pos.clamp(0., 1.);
|
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
|
self
|
||||||
}
|
}
|
||||||
(GradientToolFsmState::Ready { .. }, GradientToolMessage::SelectionChanged) => {
|
(GradientToolFsmState::Ready { .. }, GradientToolMessage::SelectionChanged) => {
|
||||||
|
|
@ -483,7 +534,18 @@ impl Fsm for GradientToolFsmState {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
(GradientToolFsmState::Ready { .. }, GradientToolMessage::PointerDown) => {
|
(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;
|
tool_data.drag_start = mouse;
|
||||||
let tolerance = (MANIPULATOR_GROUP_MARKER_SIZE * 2.).powi(2);
|
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 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);
|
let projection = ((end - start).angle_to(mouse - start)).cos() * start.distance(mouse) / start.distance(end);
|
||||||
|
|
||||||
if distance.abs() < SEGMENT_INSERTION_DISTANCE
|
if distance.abs() < SEGMENT_INSERTION_DISTANCE && (0. ..=1.).contains(&projection) {
|
||||||
&& (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();
|
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);
|
let mut selected_gradient = SelectedGradient::new(new_gradient, layer, document);
|
||||||
selected_gradient.dragging = GradientDragTarget::Step(index);
|
selected_gradient.dragging = GradientDragTarget::Step(index);
|
||||||
// No offset when inserting a new stop, it should be exactly under the mouse
|
// No offset when inserting a new stop, it should be exactly under the mouse
|
||||||
selected_gradient.render_gradient(responses);
|
selected_gradient.render_gradient(responses);
|
||||||
tool_data.selected_gradient = Some(selected_gradient);
|
tool_data.selected_gradient = Some(selected_gradient);
|
||||||
dragging = true;
|
dragging = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -550,7 +610,8 @@ impl Fsm for GradientToolFsmState {
|
||||||
let gradient_state = if dragging {
|
let gradient_state = if dragging {
|
||||||
GradientToolFsmState::Drawing
|
GradientToolFsmState::Drawing
|
||||||
} else {
|
} 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
|
// Apply the gradient to the selected layer
|
||||||
if let Some(layer) = selected_layer {
|
if let Some(layer) = selected_layer {
|
||||||
|
|
@ -592,13 +653,17 @@ impl Fsm for GradientToolFsmState {
|
||||||
}
|
}
|
||||||
(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;
|
||||||
|
let snap_data = SnapData::new(document, input, viewport);
|
||||||
|
|
||||||
selected_gradient.update_gradient(
|
selected_gradient.update_gradient(
|
||||||
mouse,
|
mouse,
|
||||||
responses,
|
responses,
|
||||||
input.keyboard.get(constrain_axis as usize),
|
input.keyboard.get(constrain_axis as usize),
|
||||||
selected_gradient.gradient.gradient_type,
|
selected_gradient.gradient.gradient_type,
|
||||||
tool_data.drag_start,
|
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);
|
responses.add(OverlaysMessage::Draw);
|
||||||
GradientToolFsmState::Ready { hover_insertion }
|
GradientToolFsmState::Ready { hover_insertion }
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue