diff --git a/editor/src/messages/portfolio/document/overlays/utility_types_web.rs b/editor/src/messages/portfolio/document/overlays/utility_types_web.rs index 4fbaa5878d..a80166f787 100644 --- a/editor/src/messages/portfolio/document/overlays/utility_types_web.rs +++ b/editor/src/messages/portfolio/document/overlays/utility_types_web.rs @@ -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(); diff --git a/editor/src/messages/portfolio/document/utility_types/misc.rs b/editor/src/messages/portfolio/document/utility_types/misc.rs index c474bf9665..2394dc73b0 100644 --- a/editor/src/messages/portfolio/document/utility_types/misc.rs +++ b/editor/src/messages/portfolio/document/utility_types/misc.rs @@ -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}"), } } } diff --git a/editor/src/messages/tool/common_functionality/snapping.rs b/editor/src/messages/tool/common_functionality/snapping.rs index 9b53cce626..ad732978c3 100644 --- a/editor/src/messages/tool/common_functionality/snapping.rs +++ b/editor/src/messages/tool/common_functionality/snapping.rs @@ -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 { self.indicator.as_ref().map(|point| point.snapped_point_document) } diff --git a/editor/src/messages/tool/common_functionality/snapping/layer_snapper.rs b/editor/src/messages/tool/common_functionality/snapping/layer_snapper.rs index 9919855716..18a190ac20 100644 --- a/editor/src/messages/tool/common_functionality/snapping/layer_snapper.rs +++ b/editor/src/messages/tool/common_functionality/snapping/layer_snapper.rs @@ -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>) -> Self { let mut point = Self::new_source(document_point, SnapSource::Path(PathSnapSource::AnchorPointWithFreeHandles)); point.neighbors = neighbors.into(); diff --git a/editor/src/messages/tool/tool_messages/gradient_tool.rs b/editor/src/messages/tool/tool_messages/gradient_tool.rs index 9969256b6f..607caafcaf 100644 --- a/editor/src/messages/tool/tool_messages/gradient_tool.rs +++ b/editor/src/messages/tool/tool_messages/gradient_tool.rs @@ -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, 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, + 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); - - 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; + 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; + } } } } @@ -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 } }