From 13f568283faf33a8db7ee268e076e8affbbe7d2d Mon Sep 17 00:00:00 2001 From: Adesh Gupta Date: Sun, 20 Jul 2025 17:10:40 +0530 Subject: [PATCH 1/6] Feat: Point sliding on G G --- .../messages/tool/tool_messages/path_tool.rs | 15 ++++++-- .../transform_layer_message_handler.rs | 37 +++++++++++++++++-- 2 files changed, 45 insertions(+), 7 deletions(-) diff --git a/editor/src/messages/tool/tool_messages/path_tool.rs b/editor/src/messages/tool/tool_messages/path_tool.rs index f5ebc33189..c51a175391 100644 --- a/editor/src/messages/tool/tool_messages/path_tool.rs +++ b/editor/src/messages/tool/tool_messages/path_tool.rs @@ -119,6 +119,7 @@ pub enum PathToolMessage { UpdateSelectedPointsStatus { overlay_context: OverlayContext, }, + StartSlidingPoint, } #[derive(PartialEq, Eq, Hash, Copy, Clone, Debug, Default, serde::Serialize, serde::Deserialize, specta::Type)] @@ -399,6 +400,7 @@ impl<'a> MessageHandler> for Path DeleteAndBreakPath, ClosePath, PointerMove, + StartSlidingPoint, ), PathToolFsmState::Dragging(_) => actions!(PathToolMessageDiscriminant; Escape, @@ -410,6 +412,7 @@ impl<'a> MessageHandler> for Path BreakPath, DeleteAndBreakPath, SwapSelectedHandles, + StartSlidingPoint, ), PathToolFsmState::Drawing { .. } => actions!(PathToolMessageDiscriminant; DoubleClick, @@ -421,6 +424,7 @@ impl<'a> MessageHandler> for Path DeleteAndBreakPath, Escape, RightClick, + StartSlidingPoint, ), PathToolFsmState::SlidingPoint => actions!(PathToolMessageDiscriminant; PointerMove, @@ -1838,10 +1842,6 @@ impl Fsm for PathToolFsmState { } if !tool_data.update_colinear(equidistant_state, toggle_colinear_state, tool_action_data.shape_editor, tool_action_data.document, responses) { - if snap_angle_state && lock_angle_state && tool_data.start_sliding_point(tool_action_data.shape_editor, tool_action_data.document) { - return PathToolFsmState::SlidingPoint; - } - tool_data.drag( equidistant_state, lock_angle_state, @@ -2249,6 +2249,13 @@ impl Fsm for PathToolFsmState { shape_editor.delete_point_and_break_path(document, responses); PathToolFsmState::Ready } + (_, PathToolMessage::StartSlidingPoint) => { + if tool_data.start_sliding_point(shape_editor, document) { + PathToolFsmState::SlidingPoint + } else { + PathToolFsmState::Ready + } + } (_, PathToolMessage::DoubleClick { extend_selection, shrink_selection }) => { // Double-clicked on a point (flip smooth/sharp behavior) let nearest_point = shape_editor.find_nearest_point_indices(&document.network_interface, input.mouse.position, SELECTION_THRESHOLD); diff --git a/editor/src/messages/tool/transform_layer/transform_layer_message_handler.rs b/editor/src/messages/tool/transform_layer/transform_layer_message_handler.rs index 203c84646f..fd6bb43754 100644 --- a/editor/src/messages/tool/transform_layer/transform_layer_message_handler.rs +++ b/editor/src/messages/tool/transform_layer/transform_layer_message_handler.rs @@ -65,6 +65,8 @@ pub struct TransformLayerMessageHandler { // Ghost outlines for Path Tool ghost_outline: Vec<(Vec, DAffine2)>, + + was_grabbing: bool, } impl MessageHandler> for TransformLayerMessageHandler { @@ -122,7 +124,7 @@ impl MessageHandler> for self.local_pivot = document.metadata().document_to_viewport.inverse().transform_point2(*selected.pivot); self.grab_target = self.local_pivot; } - // Here vector data from all layers is not considered which can be a problem in pivot calculation + // TODO: Here vector data from all layers is not considered which can be a problem in pivot calculation else if let Some(vector_data) = selected_layers.first().and_then(|&layer| document.network_interface.compute_modified_vector(layer)) { *selected.original_transforms = OriginalTransforms::default(); @@ -301,6 +303,7 @@ impl MessageHandler> for responses.add(SelectToolMessage::PivotShift { offset: None, flush: true }); if final_transform { + self.was_grabbing = false; responses.add(OverlaysMessage::RemoveProvider(TRANSFORM_GRS_OVERLAY_PROVIDER)); } } @@ -356,10 +359,37 @@ impl MessageHandler> for if (selected_points.is_empty() && selected_segments.is_empty()) || (!using_path_tool && !using_select_tool && !using_pen_tool && !using_shape_tool) || selected_layers.is_empty() - || transform_type.equivalent_to(self.transform_operation) + || (transform_type.equivalent_to(self.transform_operation) && !self.was_grabbing) { return; } + + if using_path_tool && (transform_type == TransformType::Grab) { + // Check if single point selected and iit is colinear point + let single_anchor_selected = shape_editor.selected_points().count() == 1 && shape_editor.selected_points().any(|point| matches!(point, ManipulatorPointId::Anchor(_))); + log::info!("reaching hereee"); + if single_anchor_selected && transform_type.equivalent_to(self.transform_operation) && self.was_grabbing { + let Some(layer) = document.network_interface.selected_nodes().selected_layers(document.metadata()).next() else { + return; + }; + let Some(vector_data) = document.network_interface.compute_modified_vector(layer) else { return }; + + let Some(anchor) = shape_editor.selected_points().next() else { return }; + if vector_data.colinear(*anchor) { + responses.add(TransformLayerMessage::CancelTransformOperation); + + // Start sliding point + responses.add(DocumentMessage::AddTransaction); + responses.add(PathToolMessage::StartSlidingPoint); + return; + } else { + return; + } + } else if transform_type.equivalent_to(self.transform_operation) { + return; + } + self.was_grabbing = true; + } } if let Some(vector_data) = selected_layers.first().and_then(|&layer| document.network_interface.compute_modified_vector(layer)) { @@ -409,6 +439,7 @@ impl MessageHandler> for TransformLayerMessage::CancelTransformOperation => { if using_path_tool { self.ghost_outline.clear(); + self.was_grabbing = false; } if using_pen_tool { @@ -499,7 +530,7 @@ impl MessageHandler> for if self.typing.digits.is_empty() || !self.transform_operation.can_begin_typing() { match self.transform_operation { - TransformOperation::None => unreachable!(), + TransformOperation::None => {} TransformOperation::Grabbing(translation) => { let delta_pos = input.mouse.position - self.mouse_position; let delta_pos = (self.initial_transform * document_to_viewport.inverse()).transform_vector2(delta_pos); From 09b3fca08578170a00d3f6015dd693dd1463e964 Mon Sep 17 00:00:00 2001 From: Keavon Chambers Date: Thu, 24 Jul 2025 18:26:44 -0700 Subject: [PATCH 2/6] Code cleanup --- .../transform_layer_message_handler.rs | 22 ++++++++++--------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/editor/src/messages/tool/transform_layer/transform_layer_message_handler.rs b/editor/src/messages/tool/transform_layer/transform_layer_message_handler.rs index fd6bb43754..c0137d596d 100644 --- a/editor/src/messages/tool/transform_layer/transform_layer_message_handler.rs +++ b/editor/src/messages/tool/transform_layer/transform_layer_message_handler.rs @@ -365,29 +365,31 @@ impl MessageHandler> for } if using_path_tool && (transform_type == TransformType::Grab) { - // Check if single point selected and iit is colinear point + if transform_type.equivalent_to(self.transform_operation) { + return; + } + + // Check if a single point is selected and it's a colinear point let single_anchor_selected = shape_editor.selected_points().count() == 1 && shape_editor.selected_points().any(|point| matches!(point, ManipulatorPointId::Anchor(_))); - log::info!("reaching hereee"); + if single_anchor_selected && transform_type.equivalent_to(self.transform_operation) && self.was_grabbing { - let Some(layer) = document.network_interface.selected_nodes().selected_layers(document.metadata()).next() else { - return; - }; - let Some(vector_data) = document.network_interface.compute_modified_vector(layer) else { return }; + let selected_nodes = &document.network_interface.selected_nodes(); + let Some(layer) = selected_nodes.selected_layers(document.metadata()).next() else { return }; + let Some(vector_data) = document.network_interface.compute_modified_vector(layer) else { return }; let Some(anchor) = shape_editor.selected_points().next() else { return }; + if vector_data.colinear(*anchor) { responses.add(TransformLayerMessage::CancelTransformOperation); // Start sliding point responses.add(DocumentMessage::AddTransaction); responses.add(PathToolMessage::StartSlidingPoint); - return; - } else { - return; } - } else if transform_type.equivalent_to(self.transform_operation) { + return; } + self.was_grabbing = true; } } From d9b4a4ebb47d3497af41e85213a0aef03a389bfb Mon Sep 17 00:00:00 2001 From: Adesh Gupta Date: Sun, 27 Jul 2025 12:53:21 +0530 Subject: [PATCH 3/6] Fix gg sliding behaviour --- editor/src/messages/tool/tool_messages/path_tool.rs | 5 ----- .../tool/transform_layer/transform_layer_message_handler.rs | 6 ++---- 2 files changed, 2 insertions(+), 9 deletions(-) diff --git a/editor/src/messages/tool/tool_messages/path_tool.rs b/editor/src/messages/tool/tool_messages/path_tool.rs index 6ea3e89939..c34e6bfff1 100644 --- a/editor/src/messages/tool/tool_messages/path_tool.rs +++ b/editor/src/messages/tool/tool_messages/path_tool.rs @@ -1168,11 +1168,6 @@ impl PathToolData { return false; }; - // Check that the handles of anchor point are also colinear - if !vector_data.colinear(*anchor) { - return false; - }; - let Some(point_id) = anchor.as_anchor() else { return false }; let mut connected_segments = [None, None]; diff --git a/editor/src/messages/tool/transform_layer/transform_layer_message_handler.rs b/editor/src/messages/tool/transform_layer/transform_layer_message_handler.rs index c0137d596d..bfaf058ac9 100644 --- a/editor/src/messages/tool/transform_layer/transform_layer_message_handler.rs +++ b/editor/src/messages/tool/transform_layer/transform_layer_message_handler.rs @@ -365,10 +365,6 @@ impl MessageHandler> for } if using_path_tool && (transform_type == TransformType::Grab) { - if transform_type.equivalent_to(self.transform_operation) { - return; - } - // Check if a single point is selected and it's a colinear point let single_anchor_selected = shape_editor.selected_points().count() == 1 && shape_editor.selected_points().any(|point| matches!(point, ManipulatorPointId::Anchor(_))); @@ -387,6 +383,8 @@ impl MessageHandler> for responses.add(PathToolMessage::StartSlidingPoint); } + return; + } else if transform_type.equivalent_to(self.transform_operation) { return; } From 110ba05e12e1d3e996750e8d6dffcd8e88316d98 Mon Sep 17 00:00:00 2001 From: Adesh Gupta Date: Wed, 6 Aug 2025 19:55:13 +0000 Subject: [PATCH 4/6] Fix build after merge conflict resolution --- editor/src/messages/tool/tool_messages/path_tool.rs | 1 + .../tool/transform_layer/transform_layer_message_handler.rs | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/editor/src/messages/tool/tool_messages/path_tool.rs b/editor/src/messages/tool/tool_messages/path_tool.rs index 849d352241..219ee411f7 100644 --- a/editor/src/messages/tool/tool_messages/path_tool.rs +++ b/editor/src/messages/tool/tool_messages/path_tool.rs @@ -2567,6 +2567,7 @@ impl Fsm for PathToolFsmState { } else { PathToolFsmState::Ready } + } (_, PathToolMessage::Copy { clipboard }) => { // TODO: Add support for selected segments diff --git a/editor/src/messages/tool/transform_layer/transform_layer_message_handler.rs b/editor/src/messages/tool/transform_layer/transform_layer_message_handler.rs index 692bfa3c03..a2fb0a30cd 100644 --- a/editor/src/messages/tool/transform_layer/transform_layer_message_handler.rs +++ b/editor/src/messages/tool/transform_layer/transform_layer_message_handler.rs @@ -125,7 +125,7 @@ impl MessageHandler> for self.grab_target = self.local_pivot; } // TODO: Here vector data from all layers is not considered which can be a problem in pivot calculation - else if let Some(vector_data) = selected_layers.first().and_then(|&layer| document.network_interface.compute_modified_vector(layer)) { + else if let Some(vector) = selected_layers.first().and_then(|&layer| document.network_interface.compute_modified_vector(layer)) { *selected.original_transforms = OriginalTransforms::default(); let viewspace = document.metadata().transform_to_viewport(selected_layers[0]); From 9d3c099bb73843f6c36f2418f56ff38bc663da68 Mon Sep 17 00:00:00 2001 From: Adesh Gupta Date: Thu, 7 Aug 2025 05:14:55 +0000 Subject: [PATCH 5/6] Fix slide point and add hints --- .../src/messages/tool/tool_messages/path_tool.rs | 16 +++++++++------- .../transform_layer_message_handler.rs | 4 +++- 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/editor/src/messages/tool/tool_messages/path_tool.rs b/editor/src/messages/tool/tool_messages/path_tool.rs index 219ee411f7..dc12921177 100644 --- a/editor/src/messages/tool/tool_messages/path_tool.rs +++ b/editor/src/messages/tool/tool_messages/path_tool.rs @@ -3293,7 +3293,7 @@ fn update_dynamic_hints( } } - let mut drag_selected_hints = vec![HintInfo::mouse(MouseMotion::LmbDrag, "Drag Selected")]; + let drag_selected_hints = vec![HintInfo::mouse(MouseMotion::LmbDrag, "Drag Selected")]; let mut delete_selected_hints = vec![HintInfo::keys([Key::Delete], "Delete Selected")]; if at_least_one_anchor_selected { @@ -3301,10 +3301,6 @@ fn update_dynamic_hints( delete_selected_hints.push(HintInfo::keys([Key::Shift], "Cut Anchor").prepend_plus()); } - if single_colinear_anchor_selected { - drag_selected_hints.push(HintInfo::multi_keys([[Key::Control], [Key::Shift]], "Slide").prepend_plus()); - } - let segment_edit = tool_options.path_editing_mode.segment_editing_mode; let point_edit = tool_options.path_editing_mode.point_editing_mode; @@ -3369,9 +3365,15 @@ fn update_dynamic_hints( let mut groups = vec![ HintGroup(drag_selected_hints), HintGroup(vec![HintInfo::multi_keys([[Key::KeyG], [Key::KeyR], [Key::KeyS]], "Grab/Rotate/Scale Selected")]), - HintGroup(vec![HintInfo::arrow_keys("Nudge Selected"), HintInfo::keys([Key::Shift], "10x").prepend_plus()]), - HintGroup(delete_selected_hints), ]; + + if single_colinear_anchor_selected { + groups.push(HintGroup(vec![HintInfo::multi_keys([[Key::KeyG], [Key::KeyG]], "Slide")])); + } + + groups.push(HintGroup(vec![HintInfo::arrow_keys("Nudge Selected"), HintInfo::keys([Key::Shift], "10x").prepend_plus()])); + groups.push(HintGroup(delete_selected_hints)); + hint_data.append(&mut groups); } diff --git a/editor/src/messages/tool/transform_layer/transform_layer_message_handler.rs b/editor/src/messages/tool/transform_layer/transform_layer_message_handler.rs index a2fb0a30cd..20e5112540 100644 --- a/editor/src/messages/tool/transform_layer/transform_layer_message_handler.rs +++ b/editor/src/messages/tool/transform_layer/transform_layer_message_handler.rs @@ -380,7 +380,9 @@ impl MessageHandler> for // Start sliding point responses.add(DocumentMessage::AddTransaction); - responses.add(PathToolMessage::StartSlidingPoint); + responses.add(DeferMessage::AfterGraphRun { + messages: vec![PathToolMessage::StartSlidingPoint.into()], + }); } return; From 340f7fbbaa03b29963aa99287ce90c4fc48f69f5 Mon Sep 17 00:00:00 2001 From: Adesh Gupta Date: Thu, 7 Aug 2025 10:12:43 +0000 Subject: [PATCH 6/6] Fix history in segment insertion --- .../src/messages/tool/tool_messages/path_tool.rs | 15 ++++++++++----- .../transform_layer_message_handler.rs | 1 - 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/editor/src/messages/tool/tool_messages/path_tool.rs b/editor/src/messages/tool/tool_messages/path_tool.rs index dc12921177..63d5ea2ac0 100644 --- a/editor/src/messages/tool/tool_messages/path_tool.rs +++ b/editor/src/messages/tool/tool_messages/path_tool.rs @@ -2365,6 +2365,8 @@ impl Fsm for PathToolFsmState { tool_data.ghost_outline.clear(); let extend_selection = input.keyboard.get(extend_selection as usize); let drag_occurred = tool_data.drag_start_pos.distance(input.mouse.position) > DRAG_THRESHOLD; + let mut segment_dissolved = false; + let mut point_inserted = false; let nearest_point = shape_editor.find_nearest_visible_point_indices( &document.network_interface, @@ -2385,6 +2387,7 @@ impl Fsm for PathToolFsmState { if tool_data.delete_segment_pressed { if let Some(vector) = document.network_interface.compute_modified_vector(segment.layer()) { shape_editor.dissolve_segment(responses, segment.layer(), &vector, segment.segment(), segment.points()); + segment_dissolved = true; } } else { let is_segment_selected = shape_editor @@ -2393,11 +2396,7 @@ impl Fsm for PathToolFsmState { .is_some_and(|state| state.is_segment_selected(segment.segment())); segment.adjusted_insert_and_select(shape_editor, responses, extend_selection, point_mode, is_segment_selected); - tool_data.segment = None; - tool_data.molding_info = None; - tool_data.molding_segment = false; - tool_data.temporary_adjacent_handles_while_molding = None; - return PathToolFsmState::Ready; + point_inserted = true; } } @@ -2405,6 +2404,11 @@ impl Fsm for PathToolFsmState { tool_data.molding_info = None; tool_data.molding_segment = false; tool_data.temporary_adjacent_handles_while_molding = None; + + if segment_dissolved || point_inserted { + responses.add(DocumentMessage::EndTransaction); + return PathToolFsmState::Ready; + } } let segment_mode = tool_options.path_editing_mode.segment_editing_mode; @@ -2562,6 +2566,7 @@ impl Fsm for PathToolFsmState { PathToolFsmState::Ready } (_, PathToolMessage::StartSlidingPoint) => { + responses.add(DocumentMessage::StartTransaction); if tool_data.start_sliding_point(shape_editor, document) { PathToolFsmState::SlidingPoint } else { diff --git a/editor/src/messages/tool/transform_layer/transform_layer_message_handler.rs b/editor/src/messages/tool/transform_layer/transform_layer_message_handler.rs index 20e5112540..77e00726cd 100644 --- a/editor/src/messages/tool/transform_layer/transform_layer_message_handler.rs +++ b/editor/src/messages/tool/transform_layer/transform_layer_message_handler.rs @@ -379,7 +379,6 @@ impl MessageHandler> for responses.add(TransformLayerMessage::CancelTransformOperation); // Start sliding point - responses.add(DocumentMessage::AddTransaction); responses.add(DeferMessage::AfterGraphRun { messages: vec![PathToolMessage::StartSlidingPoint.into()], });