From 83aa9143078b8fc06a1160543191976bba69588b Mon Sep 17 00:00:00 2001 From: indierusty Date: Thu, 16 Jan 2025 14:47:27 +0530 Subject: [PATCH 01/12] visualize spline end points using overlays --- .../messages/tool/tool_messages/spline_tool.rs | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/editor/src/messages/tool/tool_messages/spline_tool.rs b/editor/src/messages/tool/tool_messages/spline_tool.rs index 94a1182b01..22e41b91f2 100644 --- a/editor/src/messages/tool/tool_messages/spline_tool.rs +++ b/editor/src/messages/tool/tool_messages/spline_tool.rs @@ -1,6 +1,8 @@ use super::tool_prelude::*; use crate::consts::{DEFAULT_STROKE_WIDTH, DRAG_THRESHOLD}; use crate::messages::portfolio::document::node_graph::document_node_definitions::resolve_document_node_type; +use crate::messages::portfolio::document::overlays::utility_functions::path_endpoint_overlays; +use crate::messages::portfolio::document::overlays::utility_types::OverlayContext; 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::color_selector::{ToolColorOptions, ToolColorType}; @@ -38,6 +40,7 @@ impl Default for SplineOptions { #[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize, specta::Type)] pub enum SplineToolMessage { // Standard messages + Overlays(OverlayContext), CanvasTransformed, Abort, WorkingColorChanged, @@ -168,6 +171,7 @@ impl<'a> MessageHandler> for SplineT impl ToolTransition for SplineTool { fn event_to_message_map(&self) -> EventToMessageMap { EventToMessageMap { + overlay_provider: Some(|overlay_context: OverlayContext| SplineToolMessage::Overlays(overlay_context).into()), canvas_transformed: Some(SplineToolMessage::CanvasTransformed.into()), tool_abort: Some(SplineToolMessage::Abort.into()), working_color_changed: Some(SplineToolMessage::WorkingColorChanged.into()), @@ -198,12 +202,21 @@ impl Fsm for SplineToolFsmState { fn transition(self, event: ToolMessage, tool_data: &mut Self::ToolData, tool_action_data: &mut ToolActionHandlerData, tool_options: &Self::ToolOptions, responses: &mut VecDeque) -> Self { let ToolActionHandlerData { - document, global_tool_data, input, .. + document, + global_tool_data, + input, + shape_editor, + .. } = tool_action_data; let ToolMessage::Spline(event) = event else { return self }; match (self, event) { (_, SplineToolMessage::CanvasTransformed) => self, + (_, SplineToolMessage::Overlays(mut overlay_context)) => { + path_endpoint_overlays(document, shape_editor, &mut overlay_context); + + self + } (SplineToolFsmState::Ready, SplineToolMessage::DragStart) => { responses.add(DocumentMessage::StartTransaction); responses.add(DocumentMessage::DeselectAllLayers); From b74c587e01060d293ff65a360b7f72159fd9e04b Mon Sep 17 00:00:00 2001 From: indierusty Date: Fri, 17 Jan 2025 11:33:58 +0530 Subject: [PATCH 02/12] implement for spline tool to extend path by draging end points --- .../tool/tool_messages/spline_tool.rs | 35 +++++++++++++------ 1 file changed, 25 insertions(+), 10 deletions(-) diff --git a/editor/src/messages/tool/tool_messages/spline_tool.rs b/editor/src/messages/tool/tool_messages/spline_tool.rs index 22e41b91f2..8ddc0bd21b 100644 --- a/editor/src/messages/tool/tool_messages/spline_tool.rs +++ b/editor/src/messages/tool/tool_messages/spline_tool.rs @@ -8,6 +8,7 @@ use crate::messages::tool::common_functionality::auto_panning::AutoPanning; use crate::messages::tool::common_functionality::color_selector::{ToolColorOptions, ToolColorType}; use crate::messages::tool::common_functionality::graph_modification_utils; use crate::messages::tool::common_functionality::snapping::SnapManager; +use crate::messages::tool::common_functionality::utility_functions::should_extend; use graph_craft::document::{NodeId, NodeInput}; use graphene_core::Color; @@ -182,8 +183,8 @@ impl ToolTransition for SplineTool { #[derive(Clone, Debug, Default)] struct SplineToolData { - /// Points that are inserted. - points: Vec<(PointId, DVec2)>, + /// Current end point of the spline i.e. either last inserted or end point to be extended. + end_point: Option<(PointId, DVec2)>, /// Point to be inserted. next_point: DVec2, /// Point that was inserted temporarily to show preview. @@ -219,12 +220,26 @@ impl Fsm for SplineToolFsmState { } (SplineToolFsmState::Ready, SplineToolMessage::DragStart) => { responses.add(DocumentMessage::StartTransaction); + + tool_data.weight = tool_options.line_weight; + + // Extend an endpoint of the selected path + let selected_nodes = document.network_interface.selected_nodes(&[]).unwrap(); + if let Some((layer, point, position)) = should_extend(document, input.mouse.position, crate::consts::SNAP_POINT_TOLERANCE, selected_nodes.selected_layers(document.metadata())) { + tool_data.layer = Some(layer); + tool_data.end_point = Some((point, position)); + // update next point to preview current mouse pos instead of pointing last mouse pos when DragStop event occured. + tool_data.next_point = position; + + update_spline(tool_data, true, responses); + + return SplineToolFsmState::Drawing; + } + responses.add(DocumentMessage::DeselectAllLayers); let parent = document.new_layer_bounding_artboard(input); - tool_data.weight = tool_options.line_weight; - let path_node_type = resolve_document_node_type("Path").expect("Path node does not exist"); let path_node = path_node_type.default_node_template(); let spline_node_type = resolve_document_node_type("Splines from Points").expect("Spline from Points node does not exist"); @@ -250,7 +265,7 @@ impl Fsm for SplineToolFsmState { let transform = document.metadata().transform_to_viewport(layer); let pos = transform.inverse().transform_point2(snapped_position); - if tool_data.points.last().map_or(true, |last_pos| last_pos.1.distance(pos) > DRAG_THRESHOLD) { + if tool_data.end_point.map_or(true, |last_pos| last_pos.1.distance(pos) > DRAG_THRESHOLD) { tool_data.next_point = pos; } @@ -289,7 +304,7 @@ impl Fsm for SplineToolFsmState { state } (SplineToolFsmState::Drawing, SplineToolMessage::Confirm | SplineToolMessage::Abort) => { - if tool_data.points.len() >= 2 { + if tool_data.end_point.is_some() { delete_preview(tool_data, responses); responses.add(DocumentMessage::EndTransaction); } else { @@ -299,7 +314,7 @@ impl Fsm for SplineToolFsmState { tool_data.layer = None; tool_data.preview_point = None; tool_data.preview_segment = None; - tool_data.points.clear(); + tool_data.end_point = None; tool_data.snap_manager.cleanup(responses); SplineToolFsmState::Ready @@ -346,8 +361,8 @@ fn update_spline(tool_data: &mut SplineToolData, show_preview: bool, responses: }; responses.add(GraphOperationMessage::Vector { layer, modification_type }); - if let Some((last_point_id, _)) = tool_data.points.last() { - let points = [*last_point_id, next_point_id]; + if let Some((last_point_id, _)) = tool_data.end_point { + let points = [last_point_id, next_point_id]; let id = SegmentId::generate(); let modification_type = VectorModificationType::InsertSegment { id, points, handles: [None, None] }; responses.add(GraphOperationMessage::Vector { layer, modification_type }); @@ -360,7 +375,7 @@ fn update_spline(tool_data: &mut SplineToolData, show_preview: bool, responses: if show_preview { tool_data.preview_point = Some(next_point_id); } else { - tool_data.points.push((next_point_id, next_point_pos)); + tool_data.end_point = Some((next_point_id, next_point_pos)); } } From 345e7a71475e46e36339597a213c5fbc972c5503 Mon Sep 17 00:00:00 2001 From: indierusty Date: Fri, 17 Jan 2025 11:51:42 +0530 Subject: [PATCH 03/12] allow holding Shift to begin drawing a new spline subpath in the same layer --- .../messages/input_mapper/input_mappings.rs | 2 +- .../tool/tool_messages/spline_tool.rs | 28 +++++++++++++++---- 2 files changed, 23 insertions(+), 7 deletions(-) diff --git a/editor/src/messages/input_mapper/input_mappings.rs b/editor/src/messages/input_mapper/input_mappings.rs index a684eba092..43d892059a 100644 --- a/editor/src/messages/input_mapper/input_mappings.rs +++ b/editor/src/messages/input_mapper/input_mappings.rs @@ -270,7 +270,7 @@ pub fn input_mappings() -> Mapping { // // SplineToolMessage entry!(PointerMove; action_dispatch=SplineToolMessage::PointerMove), - entry!(KeyDown(MouseLeft); action_dispatch=SplineToolMessage::DragStart), + entry!(KeyDown(MouseLeft); action_dispatch=SplineToolMessage::DragStart { append_to_selected: Shift }), entry!(KeyUp(MouseLeft); action_dispatch=SplineToolMessage::DragStop), entry!(KeyDown(MouseRight); action_dispatch=SplineToolMessage::Confirm), entry!(KeyDown(Escape); action_dispatch=SplineToolMessage::Confirm), diff --git a/editor/src/messages/tool/tool_messages/spline_tool.rs b/editor/src/messages/tool/tool_messages/spline_tool.rs index 8ddc0bd21b..ca02a28087 100644 --- a/editor/src/messages/tool/tool_messages/spline_tool.rs +++ b/editor/src/messages/tool/tool_messages/spline_tool.rs @@ -48,7 +48,7 @@ pub enum SplineToolMessage { // Tool-specific messages Confirm, - DragStart, + DragStart { append_to_selected: Key }, DragStop, PointerMove, PointerOutsideViewport, @@ -218,7 +218,7 @@ impl Fsm for SplineToolFsmState { self } - (SplineToolFsmState::Ready, SplineToolMessage::DragStart) => { + (SplineToolFsmState::Ready, SplineToolMessage::DragStart { append_to_selected }) => { responses.add(DocumentMessage::StartTransaction); tool_data.weight = tool_options.line_weight; @@ -231,11 +231,27 @@ impl Fsm for SplineToolFsmState { // update next point to preview current mouse pos instead of pointing last mouse pos when DragStop event occured. tool_data.next_point = position; - update_spline(tool_data, true, responses); + extend_spline(tool_data, true, responses); return SplineToolFsmState::Drawing; } + if input.keyboard.key(append_to_selected) { + let mut selected_layers_except_artboards = selected_nodes.selected_layers_except_artboards(&document.network_interface); + let existing_layer = selected_layers_except_artboards.next().filter(|_| selected_layers_except_artboards.next().is_none()); + if let Some(layer) = existing_layer { + tool_data.layer = Some(layer); + + let transform = document.metadata().transform_to_viewport(layer); + let position = transform.inverse().transform_point2(input.mouse.position); + tool_data.next_point = position; + + extend_spline(tool_data, false, responses); + + return SplineToolFsmState::Drawing; + } + } + responses.add(DocumentMessage::DeselectAllLayers); let parent = document.new_layer_bounding_artboard(input); @@ -269,7 +285,7 @@ impl Fsm for SplineToolFsmState { tool_data.next_point = pos; } - update_spline(tool_data, false, responses); + extend_spline(tool_data, false, responses); SplineToolFsmState::Drawing } @@ -282,7 +298,7 @@ impl Fsm for SplineToolFsmState { let pos = transform.inverse().transform_point2(snapped_position); tool_data.next_point = pos; - update_spline(tool_data, true, responses); + extend_spline(tool_data, true, responses); // Auto-panning let messages = [SplineToolMessage::PointerOutsideViewport.into(), SplineToolMessage::PointerMove.into()]; @@ -348,7 +364,7 @@ impl Fsm for SplineToolFsmState { } } -fn update_spline(tool_data: &mut SplineToolData, show_preview: bool, responses: &mut VecDeque) { +fn extend_spline(tool_data: &mut SplineToolData, show_preview: bool, responses: &mut VecDeque) { delete_preview(tool_data, responses); let Some(layer) = tool_data.layer else { return }; From 4d38634f7fdcee677adcd1d2b9f13d0f88b3cd2f Mon Sep 17 00:00:00 2001 From: indierusty Date: Sat, 18 Jan 2025 11:54:21 +0530 Subject: [PATCH 04/12] implement spline tool to join two endpoints --- editor/src/consts.rs | 4 + .../common_functionality/utility_functions.rs | 20 ++++- .../tool/tool_messages/spline_tool.rs | 73 ++++++++++++++----- 3 files changed, 79 insertions(+), 18 deletions(-) diff --git a/editor/src/consts.rs b/editor/src/consts.rs index d14efb2409..1bc8258102 100644 --- a/editor/src/consts.rs +++ b/editor/src/consts.rs @@ -67,6 +67,10 @@ pub const HANDLE_ROTATE_SNAP_ANGLE: f64 = 15.; // Pen tool pub const CREATE_CURVE_THRESHOLD: f64 = 5.; +// Spline tool +pub const PATH_JOIN_THRESHOLD: f64 = 5.; +pub const EXTEND_PATH_THRESHOLD: f64 = 5.; + // Line tool pub const LINE_ROTATE_SNAP_ANGLE: f64 = 15.; diff --git a/editor/src/messages/tool/common_functionality/utility_functions.rs b/editor/src/messages/tool/common_functionality/utility_functions.rs index ba46cebf0d..74ae9edaea 100644 --- a/editor/src/messages/tool/common_functionality/utility_functions.rs +++ b/editor/src/messages/tool/common_functionality/utility_functions.rs @@ -7,14 +7,32 @@ use glam::DVec2; /// Determines if a path should be extended. Goal in viewport space. Returns the path and if it is extending from the start, if applicable. pub fn should_extend(document: &DocumentMessageHandler, goal: DVec2, tolerance: f64, layers: impl Iterator) -> Option<(LayerNodeIdentifier, PointId, DVec2)> { + closest_point(document, goal, tolerance, layers, |_| false) +} + +/// Determine the closest point to the goal point under max_distance. +/// Additionally exclude checking closeness to the point which given to exclude() returns true. +pub fn closest_point( + document: &DocumentMessageHandler, + goal: DVec2, + max_distance: f64, + layers: impl Iterator, + exclude: T, +) -> Option<(LayerNodeIdentifier, PointId, DVec2)> +where + T: Fn(PointId) -> bool, +{ let mut best = None; - let mut best_distance_squared = tolerance * tolerance; + let mut best_distance_squared = max_distance * max_distance; for layer in layers { let viewspace = document.metadata().transform_to_viewport(layer); let Some(vector_data) = document.network_interface.compute_modified_vector(layer) else { continue; }; for id in vector_data.single_connected_points() { + if exclude(id) { + continue; + } let Some(point) = vector_data.point_domain.position_from_id(id) else { continue }; let distance_squared = viewspace.transform_point2(point).distance_squared(goal); diff --git a/editor/src/messages/tool/tool_messages/spline_tool.rs b/editor/src/messages/tool/tool_messages/spline_tool.rs index ca02a28087..7c20a00360 100644 --- a/editor/src/messages/tool/tool_messages/spline_tool.rs +++ b/editor/src/messages/tool/tool_messages/spline_tool.rs @@ -1,5 +1,5 @@ use super::tool_prelude::*; -use crate::consts::{DEFAULT_STROKE_WIDTH, DRAG_THRESHOLD}; +use crate::consts::{DEFAULT_STROKE_WIDTH, DRAG_THRESHOLD, EXTEND_PATH_THRESHOLD, PATH_JOIN_THRESHOLD}; use crate::messages::portfolio::document::node_graph::document_node_definitions::resolve_document_node_type; use crate::messages::portfolio::document::overlays::utility_functions::path_endpoint_overlays; use crate::messages::portfolio::document::overlays::utility_types::OverlayContext; @@ -8,7 +8,7 @@ use crate::messages::tool::common_functionality::auto_panning::AutoPanning; use crate::messages::tool::common_functionality::color_selector::{ToolColorOptions, ToolColorType}; use crate::messages::tool::common_functionality::graph_modification_utils; use crate::messages::tool::common_functionality::snapping::SnapManager; -use crate::messages::tool::common_functionality::utility_functions::should_extend; +use crate::messages::tool::common_functionality::utility_functions::{closest_point, should_extend}; use graph_craft::document::{NodeId, NodeInput}; use graphene_core::Color; @@ -197,6 +197,16 @@ struct SplineToolData { auto_panning: AutoPanning, } +impl SplineToolData { + fn cleanup(&mut self, responses: &mut VecDeque) { + self.layer = None; + self.preview_point = None; + self.preview_segment = None; + self.end_point = None; + self.snap_manager.cleanup(responses); + } +} + impl Fsm for SplineToolFsmState { type ToolData = SplineToolData; type ToolOptions = SplineOptions; @@ -221,11 +231,12 @@ impl Fsm for SplineToolFsmState { (SplineToolFsmState::Ready, SplineToolMessage::DragStart { append_to_selected }) => { responses.add(DocumentMessage::StartTransaction); + tool_data.cleanup(responses); tool_data.weight = tool_options.line_weight; // Extend an endpoint of the selected path let selected_nodes = document.network_interface.selected_nodes(&[]).unwrap(); - if let Some((layer, point, position)) = should_extend(document, input.mouse.position, crate::consts::SNAP_POINT_TOLERANCE, selected_nodes.selected_layers(document.metadata())) { + if let Some((layer, point, position)) = should_extend(document, input.mouse.position, EXTEND_PATH_THRESHOLD, selected_nodes.selected_layers(document.metadata())) { tool_data.layer = Some(layer); tool_data.end_point = Some((point, position)); // update next point to preview current mouse pos instead of pointing last mouse pos when DragStop event occured. @@ -236,6 +247,7 @@ impl Fsm for SplineToolFsmState { return SplineToolFsmState::Drawing; } + // Create new path in the same layer when shift is down if input.keyboard.key(append_to_selected) { let mut selected_layers_except_artboards = selected_nodes.selected_layers_except_artboards(&document.network_interface); let existing_layer = selected_layers_except_artboards.next().filter(|_| selected_layers_except_artboards.next().is_none()); @@ -246,8 +258,6 @@ impl Fsm for SplineToolFsmState { let position = transform.inverse().transform_point2(input.mouse.position); tool_data.next_point = position; - extend_spline(tool_data, false, responses); - return SplineToolFsmState::Drawing; } } @@ -277,14 +287,17 @@ impl Fsm for SplineToolFsmState { let Some(layer) = tool_data.layer else { return SplineToolFsmState::Ready; }; - let snapped_position = input.mouse.position; - let transform = document.metadata().transform_to_viewport(layer); - let pos = transform.inverse().transform_point2(snapped_position); + let pos = get_next_point(document, input, layer); if tool_data.end_point.map_or(true, |last_pos| last_pos.1.distance(pos) > DRAG_THRESHOLD) { tool_data.next_point = pos; } + if join_path(document, input.mouse.position, tool_data, responses) { + responses.add(DocumentMessage::EndTransaction); + return SplineToolFsmState::Ready; + } + extend_spline(tool_data, false, responses); SplineToolFsmState::Drawing @@ -293,10 +306,7 @@ impl Fsm for SplineToolFsmState { let Some(layer) = tool_data.layer else { return SplineToolFsmState::Ready; }; - let snapped_position = input.mouse.position; // tool_data.snap_manager.snap_position(responses, document, input.mouse.position); - let transform = document.metadata().transform_to_viewport(layer); - let pos = transform.inverse().transform_point2(snapped_position); - tool_data.next_point = pos; + tool_data.next_point = get_next_point(document, input, layer); extend_spline(tool_data, true, responses); @@ -327,11 +337,7 @@ impl Fsm for SplineToolFsmState { responses.add(DocumentMessage::AbortTransaction); } - tool_data.layer = None; - tool_data.preview_point = None; - tool_data.preview_segment = None; - tool_data.end_point = None; - tool_data.snap_manager.cleanup(responses); + tool_data.cleanup(responses); SplineToolFsmState::Ready } @@ -364,6 +370,39 @@ impl Fsm for SplineToolFsmState { } } +fn get_next_point(document: &DocumentMessageHandler, input: &InputPreprocessorMessageHandler, layer: LayerNodeIdentifier) -> DVec2 { + let snapped_position = input.mouse.position; + let transform = document.metadata().transform_to_viewport(layer); + let pos = transform.inverse().transform_point2(snapped_position); + pos +} + +/// Return `true` only if new segment is inserted to connect two end points in the selected layer otherwise `false`. +fn join_path(document: &DocumentMessageHandler, mouse_pos: DVec2, tool_data: &mut SplineToolData, responses: &mut VecDeque) -> bool { + let Some((endpoint_id, _)) = tool_data.end_point else { + return false; + }; + // use preview_point to get current dragging position. + let Some(preview_point) = tool_data.preview_point else { + return false; + }; + + let selected_nodes = document.network_interface.selected_nodes(&[]).unwrap(); + let Some((layer, point, _)) = closest_point(document, mouse_pos, PATH_JOIN_THRESHOLD, selected_nodes.selected_layers(document.metadata()), |p| p == preview_point) else { + return false; + }; + + // NOTE: deleting preview point so inserting segement connects endpoints not the preview point which is temporary as last inserted could be preview point. + delete_preview(tool_data, responses); + + let points = [endpoint_id, point]; + let id = SegmentId::generate(); + let modification_type = VectorModificationType::InsertSegment { id, points, handles: [None, None] }; + responses.add(GraphOperationMessage::Vector { layer, modification_type }); + + true +} + fn extend_spline(tool_data: &mut SplineToolData, show_preview: bool, responses: &mut VecDeque) { delete_preview(tool_data, responses); From 8889a64870a35477bf88391c56403a9cb21c7861 Mon Sep 17 00:00:00 2001 From: indierusty Date: Sat, 18 Jan 2025 12:12:21 +0530 Subject: [PATCH 05/12] fix naming --- editor/src/messages/tool/tool_messages/spline_tool.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/editor/src/messages/tool/tool_messages/spline_tool.rs b/editor/src/messages/tool/tool_messages/spline_tool.rs index 7c20a00360..3f98c7584a 100644 --- a/editor/src/messages/tool/tool_messages/spline_tool.rs +++ b/editor/src/messages/tool/tool_messages/spline_tool.rs @@ -287,7 +287,7 @@ impl Fsm for SplineToolFsmState { let Some(layer) = tool_data.layer else { return SplineToolFsmState::Ready; }; - let pos = get_next_point(document, input, layer); + let pos = get_mouse_position(document, input, layer); if tool_data.end_point.map_or(true, |last_pos| last_pos.1.distance(pos) > DRAG_THRESHOLD) { tool_data.next_point = pos; @@ -306,7 +306,7 @@ impl Fsm for SplineToolFsmState { let Some(layer) = tool_data.layer else { return SplineToolFsmState::Ready; }; - tool_data.next_point = get_next_point(document, input, layer); + tool_data.next_point = get_mouse_position(document, input, layer); extend_spline(tool_data, true, responses); @@ -370,7 +370,7 @@ impl Fsm for SplineToolFsmState { } } -fn get_next_point(document: &DocumentMessageHandler, input: &InputPreprocessorMessageHandler, layer: LayerNodeIdentifier) -> DVec2 { +fn get_mouse_position(document: &DocumentMessageHandler, input: &InputPreprocessorMessageHandler, layer: LayerNodeIdentifier) -> DVec2 { let snapped_position = input.mouse.position; let transform = document.metadata().transform_to_viewport(layer); let pos = transform.inverse().transform_point2(snapped_position); From ccd15526da73bc0bba02b4827ba2d3249ebd420d Mon Sep 17 00:00:00 2001 From: indierusty Date: Sun, 19 Jan 2025 18:51:15 +0530 Subject: [PATCH 06/12] refactor spline tool --- .../tool/tool_messages/spline_tool.rs | 34 +++++++++---------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/editor/src/messages/tool/tool_messages/spline_tool.rs b/editor/src/messages/tool/tool_messages/spline_tool.rs index 3f98c7584a..7a8c7bcbd1 100644 --- a/editor/src/messages/tool/tool_messages/spline_tool.rs +++ b/editor/src/messages/tool/tool_messages/spline_tool.rs @@ -7,7 +7,8 @@ use crate::messages::portfolio::document::utility_types::document_metadata::Laye use crate::messages::tool::common_functionality::auto_panning::AutoPanning; use crate::messages::tool::common_functionality::color_selector::{ToolColorOptions, ToolColorType}; use crate::messages::tool::common_functionality::graph_modification_utils; -use crate::messages::tool::common_functionality::snapping::SnapManager; +use crate::messages::tool::common_functionality::snapping::{SnapCandidatePoint, SnapData, SnapManager, SnapTypeConfiguration}; + use crate::messages::tool::common_functionality::utility_functions::{closest_point, should_extend}; use graph_craft::document::{NodeId, NodeInput}; @@ -198,12 +199,17 @@ struct SplineToolData { } impl SplineToolData { - fn cleanup(&mut self, responses: &mut VecDeque) { + fn cleanup(&mut self) { self.layer = None; self.preview_point = None; self.preview_segment = None; self.end_point = None; - self.snap_manager.cleanup(responses); + } + + fn snapping_position(&mut self, document: &DocumentMessageHandler, input: &InputPreprocessorMessageHandler) -> DVec2 { + let point = SnapCandidatePoint::handle(document.metadata().document_to_viewport.inverse().transform_point2(input.mouse.position)); + let snapped = self.snap_manager.free_snap(&SnapData::new(document, input), &point, SnapTypeConfiguration::default()); + snapped.snapped_point_document } } @@ -231,7 +237,7 @@ impl Fsm for SplineToolFsmState { (SplineToolFsmState::Ready, SplineToolMessage::DragStart { append_to_selected }) => { responses.add(DocumentMessage::StartTransaction); - tool_data.cleanup(responses); + tool_data.cleanup(); tool_data.weight = tool_options.line_weight; // Extend an endpoint of the selected path @@ -282,12 +288,12 @@ impl Fsm for SplineToolFsmState { SplineToolFsmState::Drawing } (SplineToolFsmState::Drawing, SplineToolMessage::DragStop) => { - responses.add(DocumentMessage::EndTransaction); + tool_data.snap_manager.cleanup(responses); - let Some(layer) = tool_data.layer else { + if tool_data.layer.is_none() { return SplineToolFsmState::Ready; }; - let pos = get_mouse_position(document, input, layer); + let pos = tool_data.snapping_position(document, input); if tool_data.end_point.map_or(true, |last_pos| last_pos.1.distance(pos) > DRAG_THRESHOLD) { tool_data.next_point = pos; @@ -303,10 +309,10 @@ impl Fsm for SplineToolFsmState { SplineToolFsmState::Drawing } (SplineToolFsmState::Drawing, SplineToolMessage::PointerMove) => { - let Some(layer) = tool_data.layer else { + if tool_data.layer.is_none() { return SplineToolFsmState::Ready; }; - tool_data.next_point = get_mouse_position(document, input, layer); + tool_data.next_point = tool_data.snapping_position(document, input); extend_spline(tool_data, true, responses); @@ -337,7 +343,8 @@ impl Fsm for SplineToolFsmState { responses.add(DocumentMessage::AbortTransaction); } - tool_data.cleanup(responses); + tool_data.snap_manager.cleanup(responses); + tool_data.cleanup(); SplineToolFsmState::Ready } @@ -370,13 +377,6 @@ impl Fsm for SplineToolFsmState { } } -fn get_mouse_position(document: &DocumentMessageHandler, input: &InputPreprocessorMessageHandler, layer: LayerNodeIdentifier) -> DVec2 { - let snapped_position = input.mouse.position; - let transform = document.metadata().transform_to_viewport(layer); - let pos = transform.inverse().transform_point2(snapped_position); - pos -} - /// Return `true` only if new segment is inserted to connect two end points in the selected layer otherwise `false`. fn join_path(document: &DocumentMessageHandler, mouse_pos: DVec2, tool_data: &mut SplineToolData, responses: &mut VecDeque) -> bool { let Some((endpoint_id, _)) = tool_data.end_point else { From 7ded22eb6ee11ae435f2bd3882477775d39981af Mon Sep 17 00:00:00 2001 From: indierusty Date: Sun, 19 Jan 2025 19:56:17 +0530 Subject: [PATCH 07/12] impl spline tool snapping and overlays --- editor/src/messages/tool/tool_messages/spline_tool.rs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/editor/src/messages/tool/tool_messages/spline_tool.rs b/editor/src/messages/tool/tool_messages/spline_tool.rs index 7a8c7bcbd1..e2355d68b1 100644 --- a/editor/src/messages/tool/tool_messages/spline_tool.rs +++ b/editor/src/messages/tool/tool_messages/spline_tool.rs @@ -157,6 +157,7 @@ impl<'a> MessageHandler> for SplineT Undo, DragStart, DragStop, + PointerMove, Confirm, Abort, ), @@ -209,6 +210,7 @@ impl SplineToolData { fn snapping_position(&mut self, document: &DocumentMessageHandler, input: &InputPreprocessorMessageHandler) -> DVec2 { let point = SnapCandidatePoint::handle(document.metadata().document_to_viewport.inverse().transform_point2(input.mouse.position)); let snapped = self.snap_manager.free_snap(&SnapData::new(document, input), &point, SnapTypeConfiguration::default()); + self.snap_manager.update_indicator(snapped.clone()); snapped.snapped_point_document } } @@ -231,6 +233,7 @@ impl Fsm for SplineToolFsmState { (_, SplineToolMessage::CanvasTransformed) => self, (_, SplineToolMessage::Overlays(mut overlay_context)) => { path_endpoint_overlays(document, shape_editor, &mut overlay_context); + tool_data.snap_manager.draw_overlays(SnapData::new(document, input), &mut overlay_context); self } @@ -322,6 +325,12 @@ impl Fsm for SplineToolFsmState { SplineToolFsmState::Drawing } + (_, SplineToolMessage::PointerMove) => { + tool_data.snap_manager.preview_draw(&SnapData::new(document, input), input.mouse.position); + responses.add(OverlaysMessage::Draw); + log::info!("10"); + self + } (SplineToolFsmState::Drawing, SplineToolMessage::PointerOutsideViewport) => { // Auto-panning let _ = tool_data.auto_panning.shift_viewport(input, responses); From 8aba57619ae85e92db9f50f163c874cd164e5ebf Mon Sep 17 00:00:00 2001 From: indierusty Date: Mon, 20 Jan 2025 15:33:12 +0530 Subject: [PATCH 08/12] fix joining path and refactor --- .../tool/tool_messages/spline_tool.rs | 42 ++++++++----------- 1 file changed, 18 insertions(+), 24 deletions(-) diff --git a/editor/src/messages/tool/tool_messages/spline_tool.rs b/editor/src/messages/tool/tool_messages/spline_tool.rs index e2355d68b1..f95fbf695a 100644 --- a/editor/src/messages/tool/tool_messages/spline_tool.rs +++ b/editor/src/messages/tool/tool_messages/spline_tool.rs @@ -7,7 +7,7 @@ use crate::messages::portfolio::document::utility_types::document_metadata::Laye use crate::messages::tool::common_functionality::auto_panning::AutoPanning; use crate::messages::tool::common_functionality::color_selector::{ToolColorOptions, ToolColorType}; use crate::messages::tool::common_functionality::graph_modification_utils; -use crate::messages::tool::common_functionality::snapping::{SnapCandidatePoint, SnapData, SnapManager, SnapTypeConfiguration}; +use crate::messages::tool::common_functionality::snapping::{SnapCandidatePoint, SnapData, SnapManager, SnapTypeConfiguration, SnappedPoint}; use crate::messages::tool::common_functionality::utility_functions::{closest_point, should_extend}; @@ -207,11 +207,10 @@ impl SplineToolData { self.end_point = None; } - fn snapping_position(&mut self, document: &DocumentMessageHandler, input: &InputPreprocessorMessageHandler) -> DVec2 { + fn snapped_point(&mut self, document: &DocumentMessageHandler, input: &InputPreprocessorMessageHandler) -> SnappedPoint { let point = SnapCandidatePoint::handle(document.metadata().document_to_viewport.inverse().transform_point2(input.mouse.position)); let snapped = self.snap_manager.free_snap(&SnapData::new(document, input), &point, SnapTypeConfiguration::default()); - self.snap_manager.update_indicator(snapped.clone()); - snapped.snapped_point_document + snapped } } @@ -234,7 +233,6 @@ impl Fsm for SplineToolFsmState { (_, SplineToolMessage::Overlays(mut overlay_context)) => { path_endpoint_overlays(document, shape_editor, &mut overlay_context); tool_data.snap_manager.draw_overlays(SnapData::new(document, input), &mut overlay_context); - self } (SplineToolFsmState::Ready, SplineToolMessage::DragStart { append_to_selected }) => { @@ -291,23 +289,17 @@ impl Fsm for SplineToolFsmState { SplineToolFsmState::Drawing } (SplineToolFsmState::Drawing, SplineToolMessage::DragStop) => { - tool_data.snap_manager.cleanup(responses); - if tool_data.layer.is_none() { return SplineToolFsmState::Ready; }; - let pos = tool_data.snapping_position(document, input); - - if tool_data.end_point.map_or(true, |last_pos| last_pos.1.distance(pos) > DRAG_THRESHOLD) { - tool_data.next_point = pos; - } - if join_path(document, input.mouse.position, tool_data, responses) { responses.add(DocumentMessage::EndTransaction); return SplineToolFsmState::Ready; } - - extend_spline(tool_data, false, responses); + tool_data.next_point = tool_data.snapped_point(document, input).snapped_point_document; + if tool_data.end_point.map_or(true, |last_pos| last_pos.1.distance(tool_data.next_point) > DRAG_THRESHOLD) { + extend_spline(tool_data, false, responses); + } SplineToolFsmState::Drawing } @@ -315,7 +307,9 @@ impl Fsm for SplineToolFsmState { if tool_data.layer.is_none() { return SplineToolFsmState::Ready; }; - tool_data.next_point = tool_data.snapping_position(document, input); + let snapped_point = tool_data.snapped_point(document, input); + tool_data.next_point = snapped_point.snapped_point_document; + tool_data.snap_manager.update_indicator(snapped_point); extend_spline(tool_data, true, responses); @@ -328,7 +322,6 @@ impl Fsm for SplineToolFsmState { (_, SplineToolMessage::PointerMove) => { tool_data.snap_manager.preview_draw(&SnapData::new(document, input), input.mouse.position); responses.add(OverlaysMessage::Draw); - log::info!("10"); self } (SplineToolFsmState::Drawing, SplineToolMessage::PointerOutsideViewport) => { @@ -388,23 +381,24 @@ impl Fsm for SplineToolFsmState { /// Return `true` only if new segment is inserted to connect two end points in the selected layer otherwise `false`. fn join_path(document: &DocumentMessageHandler, mouse_pos: DVec2, tool_data: &mut SplineToolData, responses: &mut VecDeque) -> bool { - let Some((endpoint_id, _)) = tool_data.end_point else { + let Some((endpoint, _)) = tool_data.end_point else { return false; }; // use preview_point to get current dragging position. - let Some(preview_point) = tool_data.preview_point else { - return false; - }; - + let preview_point = tool_data.preview_point; let selected_nodes = document.network_interface.selected_nodes(&[]).unwrap(); - let Some((layer, point, _)) = closest_point(document, mouse_pos, PATH_JOIN_THRESHOLD, selected_nodes.selected_layers(document.metadata()), |p| p == preview_point) else { + let selected_layers = selected_nodes.selected_layers(document.metadata()); + // get the closest point to mouse position which is not preview_point or end_point. + let Some((layer, point, _)) = closest_point(document, mouse_pos, PATH_JOIN_THRESHOLD, selected_layers, |cp| { + preview_point.is_some_and(|pp| pp == cp) || cp == endpoint + }) else { return false; }; // NOTE: deleting preview point so inserting segement connects endpoints not the preview point which is temporary as last inserted could be preview point. delete_preview(tool_data, responses); - let points = [endpoint_id, point]; + let points = [endpoint, point]; let id = SegmentId::generate(); let modification_type = VectorModificationType::InsertSegment { id, points, handles: [None, None] }; responses.add(GraphOperationMessage::Vector { layer, modification_type }); From 3d0764db7c941e76a3a022786bb6a0907e0466bd Mon Sep 17 00:00:00 2001 From: indierusty Date: Mon, 20 Jan 2025 16:24:53 +0530 Subject: [PATCH 09/12] improve join_path comment --- editor/src/messages/tool/tool_messages/spline_tool.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/editor/src/messages/tool/tool_messages/spline_tool.rs b/editor/src/messages/tool/tool_messages/spline_tool.rs index f95fbf695a..9510ae1e32 100644 --- a/editor/src/messages/tool/tool_messages/spline_tool.rs +++ b/editor/src/messages/tool/tool_messages/spline_tool.rs @@ -395,7 +395,8 @@ fn join_path(document: &DocumentMessageHandler, mouse_pos: DVec2, tool_data: &mu return false; }; - // NOTE: deleting preview point so inserting segement connects endpoints not the preview point which is temporary as last inserted could be preview point. + // NOTE: deleting preview point before joining two endponts because + // last point inserted could be preview point and segment which is after the endpoint delete_preview(tool_data, responses); let points = [endpoint, point]; From 11298eaa998640252661b016254337aa5d207abb Mon Sep 17 00:00:00 2001 From: indierusty Date: Tue, 21 Jan 2025 10:29:01 +0530 Subject: [PATCH 10/12] fix snapping overlays flickering by ignoring snapping in current layer --- editor/src/messages/tool/tool_messages/spline_tool.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/editor/src/messages/tool/tool_messages/spline_tool.rs b/editor/src/messages/tool/tool_messages/spline_tool.rs index 9510ae1e32..e9093302e1 100644 --- a/editor/src/messages/tool/tool_messages/spline_tool.rs +++ b/editor/src/messages/tool/tool_messages/spline_tool.rs @@ -207,9 +207,12 @@ impl SplineToolData { self.end_point = None; } + /// get snapped point but ignoring current layer fn snapped_point(&mut self, document: &DocumentMessageHandler, input: &InputPreprocessorMessageHandler) -> SnappedPoint { let point = SnapCandidatePoint::handle(document.metadata().document_to_viewport.inverse().transform_point2(input.mouse.position)); - let snapped = self.snap_manager.free_snap(&SnapData::new(document, input), &point, SnapTypeConfiguration::default()); + let ignore = if let Some(layer) = self.layer { vec![layer] } else { vec![] }; + let snap_data = SnapData::ignore(document, input, &ignore); + let snapped = self.snap_manager.free_snap(&snap_data, &point, SnapTypeConfiguration::default()); snapped } } From 44cf06ea947ef0a0d1890c6ec4fd8c8ed89b0d42 Mon Sep 17 00:00:00 2001 From: indierusty Date: Tue, 21 Jan 2025 11:17:07 +0530 Subject: [PATCH 11/12] fix inserting single point on aborting spline tool --- .../tool/tool_messages/spline_tool.rs | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/editor/src/messages/tool/tool_messages/spline_tool.rs b/editor/src/messages/tool/tool_messages/spline_tool.rs index e9093302e1..55429e1963 100644 --- a/editor/src/messages/tool/tool_messages/spline_tool.rs +++ b/editor/src/messages/tool/tool_messages/spline_tool.rs @@ -185,8 +185,8 @@ impl ToolTransition for SplineTool { #[derive(Clone, Debug, Default)] struct SplineToolData { - /// Current end point of the spline i.e. either last inserted or end point to be extended. - end_point: Option<(PointId, DVec2)>, + /// list of points inserted. + points: Vec<(PointId, DVec2)>, /// Point to be inserted. next_point: DVec2, /// Point that was inserted temporarily to show preview. @@ -204,7 +204,7 @@ impl SplineToolData { self.layer = None; self.preview_point = None; self.preview_segment = None; - self.end_point = None; + self.points = Vec::new(); } /// get snapped point but ignoring current layer @@ -248,7 +248,7 @@ impl Fsm for SplineToolFsmState { let selected_nodes = document.network_interface.selected_nodes(&[]).unwrap(); if let Some((layer, point, position)) = should_extend(document, input.mouse.position, EXTEND_PATH_THRESHOLD, selected_nodes.selected_layers(document.metadata())) { tool_data.layer = Some(layer); - tool_data.end_point = Some((point, position)); + tool_data.points.push((point, position)); // update next point to preview current mouse pos instead of pointing last mouse pos when DragStop event occured. tool_data.next_point = position; @@ -300,7 +300,7 @@ impl Fsm for SplineToolFsmState { return SplineToolFsmState::Ready; } tool_data.next_point = tool_data.snapped_point(document, input).snapped_point_document; - if tool_data.end_point.map_or(true, |last_pos| last_pos.1.distance(tool_data.next_point) > DRAG_THRESHOLD) { + if tool_data.points.last().map_or(true, |last_pos| last_pos.1.distance(tool_data.next_point) > DRAG_THRESHOLD) { extend_spline(tool_data, false, responses); } @@ -341,7 +341,7 @@ impl Fsm for SplineToolFsmState { state } (SplineToolFsmState::Drawing, SplineToolMessage::Confirm | SplineToolMessage::Abort) => { - if tool_data.end_point.is_some() { + if tool_data.points.len() >= 2 { delete_preview(tool_data, responses); responses.add(DocumentMessage::EndTransaction); } else { @@ -384,7 +384,7 @@ impl Fsm for SplineToolFsmState { /// Return `true` only if new segment is inserted to connect two end points in the selected layer otherwise `false`. fn join_path(document: &DocumentMessageHandler, mouse_pos: DVec2, tool_data: &mut SplineToolData, responses: &mut VecDeque) -> bool { - let Some((endpoint, _)) = tool_data.end_point else { + let Some((endpoint, _)) = tool_data.points.last().map(|p| *p) else { return false; }; // use preview_point to get current dragging position. @@ -423,8 +423,8 @@ fn extend_spline(tool_data: &mut SplineToolData, show_preview: bool, responses: }; responses.add(GraphOperationMessage::Vector { layer, modification_type }); - if let Some((last_point_id, _)) = tool_data.end_point { - let points = [last_point_id, next_point_id]; + if let Some((last_point_id, _)) = tool_data.points.last() { + let points = [*last_point_id, next_point_id]; let id = SegmentId::generate(); let modification_type = VectorModificationType::InsertSegment { id, points, handles: [None, None] }; responses.add(GraphOperationMessage::Vector { layer, modification_type }); @@ -437,7 +437,7 @@ fn extend_spline(tool_data: &mut SplineToolData, show_preview: bool, responses: if show_preview { tool_data.preview_point = Some(next_point_id); } else { - tool_data.end_point = Some((next_point_id, next_point_pos)); + tool_data.points.push((next_point_id, next_point_pos)); } } From 1f68f933fd8f2936a71d1c13c0360f9ea00d11a3 Mon Sep 17 00:00:00 2001 From: indierusty Date: Tue, 21 Jan 2025 11:52:33 +0530 Subject: [PATCH 12/12] add snapping for endpoint even when regular snapping is disabled --- .../messages/tool/tool_messages/spline_tool.rs | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/editor/src/messages/tool/tool_messages/spline_tool.rs b/editor/src/messages/tool/tool_messages/spline_tool.rs index 55429e1963..4c586bf70b 100644 --- a/editor/src/messages/tool/tool_messages/spline_tool.rs +++ b/editor/src/messages/tool/tool_messages/spline_tool.rs @@ -307,12 +307,21 @@ impl Fsm for SplineToolFsmState { SplineToolFsmState::Drawing } (SplineToolFsmState::Drawing, SplineToolMessage::PointerMove) => { - if tool_data.layer.is_none() { + let Some(layer) = tool_data.layer else { return SplineToolFsmState::Ready; }; - let snapped_point = tool_data.snapped_point(document, input); - tool_data.next_point = snapped_point.snapped_point_document; - tool_data.snap_manager.update_indicator(snapped_point); + let ignore = |cp: PointId| tool_data.preview_point.is_some_and(|pp| pp == cp) || tool_data.points.last().is_some_and(|(ep, _)| *ep == cp); + let join_point = closest_point(document, input.mouse.position, PATH_JOIN_THRESHOLD, vec![layer].into_iter(), ignore); + + // endpoints snapping + if let Some((_, _, point)) = join_point { + tool_data.next_point = point; + tool_data.snap_manager.clear_indicator(); + } else { + let snapped_point = tool_data.snapped_point(document, input); + tool_data.next_point = snapped_point.snapped_point_document; + tool_data.snap_manager.update_indicator(snapped_point); + } extend_spline(tool_data, true, responses);