From 274998c9cdef54ace41e03404701974be6aab71e Mon Sep 17 00:00:00 2001 From: 0SlowPoke0 <govindpratyush@gmail.com> Date: Wed, 12 Mar 2025 22:35:48 +0530 Subject: [PATCH 01/21] added handle_types and refactored the handle_adjustments --- .../messages/tool/tool_messages/pen_tool.rs | 211 ++++++++---------- 1 file changed, 95 insertions(+), 116 deletions(-) diff --git a/editor/src/messages/tool/tool_messages/pen_tool.rs b/editor/src/messages/tool/tool_messages/pen_tool.rs index 50e06ef16c..5f4e497adc 100644 --- a/editor/src/messages/tool/tool_messages/pen_tool.rs +++ b/editor/src/messages/tool/tool_messages/pen_tool.rs @@ -263,6 +263,15 @@ enum HandleMode { ColinearEquidistant, } +#[derive(Clone, Debug, Default)] +enum TargetHandle { + HandleEnd, + PrimaryHandle(SegmentId), + EndHandle(SegmentId), + #[default] + None, +} + #[derive(Clone, Debug, Default)] struct PenToolData { snap_manager: SnapManager, @@ -290,6 +299,7 @@ struct PenToolData { end_point: Option<PointId>, end_point_segment: Option<SegmentId>, draw_mode: DrawMode, + handle_type: TargetHandle, } impl PenToolData { fn latest_point(&self) -> Option<&LastPoint> { @@ -306,6 +316,53 @@ impl PenToolData { self.latest_points.push(point); } + fn update_handle_custom(&mut self, vector_data: &VectorData) { + let Some((point, segment)) = self.end_point.zip(self.end_point_segment) else { + self.update_handle_type(TargetHandle::None); + return; + }; + + if vector_data.segment_start_from_id(segment) == Some(point) { + self.update_handle_type(TargetHandle::PrimaryHandle(segment)); + return; + } + self.update_handle_type(TargetHandle::EndHandle(segment)); + } + + fn update_handle_type(&mut self, handle_type: TargetHandle) { + self.handle_type = handle_type; + } + + fn update_target_handle_pos(&mut self, responses: &mut VecDeque<Message>, delta: DVec2, layer: LayerNodeIdentifier) { + match self.handle_type { + TargetHandle::PrimaryHandle(segment) => { + let relative_position = delta - self.next_point; + let modification_type = VectorModificationType::SetPrimaryHandle { segment, relative_position }; + responses.add(GraphOperationMessage::Vector { layer, modification_type }); + } + TargetHandle::HandleEnd => { + if let Some(handle) = self.handle_end.as_mut() { + *handle = delta; + } + } + TargetHandle::EndHandle(segment) => { + let relative_position = delta - self.next_point; + let modification_type = VectorModificationType::SetEndHandle { segment, relative_position }; + responses.add(GraphOperationMessage::Vector { layer, modification_type }); + } + TargetHandle::None => {} + } + } + + fn target_handle_position(&self, vector_data: &VectorData) -> Option<DVec2> { + match self.handle_type { + TargetHandle::PrimaryHandle(segment) => ManipulatorPointId::PrimaryHandle(segment).get_position(&vector_data), + TargetHandle::EndHandle(segment) => ManipulatorPointId::EndHandle(segment).get_position(&vector_data), + TargetHandle::HandleEnd => self.handle_end, + TargetHandle::None => None, + } + } + // When the vector data transform changes, the positions of the points must be recalculated. fn recalculate_latest_points_position(&mut self, document: &DocumentMessageHandler) { let selected_nodes = document.network_interface.selected_nodes(); @@ -336,6 +393,7 @@ impl PenToolData { let transform = document.metadata().document_to_viewport * transform; let on_top = transform.transform_point2(self.next_point).distance_squared(transform.transform_point2(last_pos)) < crate::consts::SNAP_POINT_TOLERANCE.powi(2); + self.update_handle_type(TargetHandle::HandleEnd); if on_top { self.handle_end = None; self.handle_mode = HandleMode::Free; @@ -435,17 +493,12 @@ impl PenToolData { self.next_handle_start = self.compute_snapped_angle(snap_data, transform, colinear, mouse, Some(self.next_point), false); let Some(layer) = layer else { return Some(PenToolFsmState::DraggingHandle(self.handle_mode)) }; let vector_data = document.network_interface.compute_modified_vector(layer)?; - // Check if the handle is the start of the segment - let mut is_start = false; - if let Some((anchor, segment)) = self.end_point.zip(self.end_point_segment) { - is_start = vector_data.segment_start_from_id(segment) == Some(anchor); - } match self.handle_mode { HandleMode::ColinearLocked | HandleMode::ColinearEquidistant => { self.g1_continuous = true; - self.colinear(responses, layer, self.next_handle_start, self.next_point, &vector_data, is_start); - self.adjust_handle_length(responses, layer, &vector_data, is_start); + self.apply_colinear_constraint(responses, layer, self.next_handle_start, self.next_point, &vector_data); + self.adjust_handle_length(responses, layer, &vector_data); } HandleMode::Free => { self.g1_continuous = false; @@ -458,127 +511,50 @@ impl PenToolData { } /// Makes the opposite handle equidistant or locks its length. - fn adjust_handle_length(&mut self, responses: &mut VecDeque<Message>, layer: LayerNodeIdentifier, vector_data: &VectorData, is_start: bool) { - let Some(latest) = self.latest_point() else { return }; - let anchor_pos = latest.pos; - + fn adjust_handle_length(&mut self, responses: &mut VecDeque<Message>, layer: LayerNodeIdentifier, vector_data: &VectorData) { match self.handle_mode { - HandleMode::ColinearEquidistant => self.adjust_equidistant_handle(anchor_pos, responses, layer, vector_data, is_start), - HandleMode::ColinearLocked => self.adjust_locked_length_handle(anchor_pos, responses, layer, is_start), + HandleMode::ColinearEquidistant => { + if self.modifiers.break_handle { + // Store handle for later restoration only when Alt is first pressed + if !self.alt_press { + self.previous_handle_end_pos = self.target_handle_position(vector_data); + self.alt_press = true; + } + + // Set handle to opposite position of the other handle + let new_position = self.next_point * 2. - self.next_handle_start; + self.update_target_handle_pos(responses, new_position, layer); + } else if self.alt_press { + // Restore the previous handle position when Alt is released + if let Some(previous_handle) = self.previous_handle_end_pos { + self.update_target_handle_pos(responses, previous_handle, layer); + } + self.alt_press = false; + self.previous_handle_end_pos = None; + } + } + HandleMode::ColinearLocked => { + if !self.modifiers.break_handle { + let new_position = self.next_point * 2. - self.next_handle_start; + self.update_target_handle_pos(responses, new_position, layer); + } + } HandleMode::Free => {} // No adjustments needed in free mode } } - fn colinear(&mut self, responses: &mut VecDeque<Message>, layer: LayerNodeIdentifier, handle_start: DVec2, anchor_pos: DVec2, vector_data: &VectorData, is_start: bool) { + fn apply_colinear_constraint(&mut self, responses: &mut VecDeque<Message>, layer: LayerNodeIdentifier, handle_start: DVec2, anchor_pos: DVec2, vector_data: &VectorData) { let Some(direction) = (anchor_pos - handle_start).try_normalize() else { log::trace!("Skipping colinear adjustment: handle_start and anchor_point are too close!"); return; }; - let Some(handle_offset) = self.get_handle_offset(anchor_pos, vector_data, is_start) else { return }; - let new_handle_position = anchor_pos + handle_offset * direction; - - self.update_handle_position(new_handle_position, anchor_pos, responses, layer, is_start); - } - - fn get_handle_offset(&self, anchor_pos: DVec2, vector_data: &VectorData, is_start: bool) -> Option<f64> { - if is_start { - let segment = self.end_point_segment?; - let handle = ManipulatorPointId::PrimaryHandle(segment).get_position(vector_data)?; - return Some((handle - anchor_pos).length()); - } - - if self.draw_mode == DrawMode::ContinuePath { - return self.handle_end.map(|handle| (handle - anchor_pos).length()).or_else(|| { - self.end_point_segment - .and_then(|segment| Some((ManipulatorPointId::EndHandle(segment).get_position(vector_data)? - anchor_pos).length())) - }); - } - - let handle = ManipulatorPointId::EndHandle(self.end_point_segment?).get_position(vector_data); - if let Some(handle) = handle { - return Some((handle - anchor_pos).length()); - } - None - } - - fn adjust_equidistant_handle(&mut self, anchor_pos: DVec2, responses: &mut VecDeque<Message>, layer: LayerNodeIdentifier, vector_data: &VectorData, is_start: bool) { - if self.modifiers.break_handle { - self.store_handle(vector_data, is_start); - self.alt_press = true; - let new_position = self.next_point * 2. - self.next_handle_start; - self.update_handle_position(new_position, anchor_pos, responses, layer, is_start); - } else { - self.restore_previous_handle(anchor_pos, responses, layer, is_start); - } - } - - fn adjust_locked_length_handle(&mut self, anchor_pos: DVec2, responses: &mut VecDeque<Message>, layer: LayerNodeIdentifier, is_start: bool) { - if !self.modifiers.break_handle { - let new_position = self.next_point * 2. - self.next_handle_start; - self.update_handle_position(new_position, anchor_pos, responses, layer, is_start); - } - } - - /// Temporarily stores the opposite handle position to revert back when Alt is released in equidistant mode. - fn store_handle(&mut self, vector_data: &VectorData, is_start: bool) { - if !self.alt_press { - self.previous_handle_end_pos = if is_start { - let Some(segment) = self.end_point_segment else { return }; - ManipulatorPointId::PrimaryHandle(segment).get_position(vector_data) - } else if self.draw_mode == DrawMode::ContinuePath { - self.handle_end.or_else(|| { - let segment = self.end_point_segment?; - ManipulatorPointId::EndHandle(segment).get_position(vector_data) - }) - } else { - let Some(segment) = self.end_point_segment else { return }; - let end_handle = ManipulatorPointId::EndHandle(segment); - end_handle.get_position(vector_data) - }; - } - } - - fn restore_previous_handle(&mut self, anchor_pos: DVec2, responses: &mut VecDeque<Message>, layer: LayerNodeIdentifier, is_start: bool) { - if self.alt_press { - self.alt_press = false; - if let Some(previous_handle) = self.previous_handle_end_pos { - self.update_handle_position(previous_handle, anchor_pos, responses, layer, is_start); - } - self.previous_handle_end_pos = None; // Reset storage - } - } - - fn update_handle_position(&mut self, new_position: DVec2, anchor_pos: DVec2, responses: &mut VecDeque<Message>, layer: LayerNodeIdentifier, is_start: bool) { - let relative_position = new_position - anchor_pos; - - if is_start { - let modification_type = VectorModificationType::SetPrimaryHandle { - segment: self - .end_point_segment - .expect("In update_handle_position(), if `is_start` is true then `end_point_segment` should exist"), - relative_position, - }; - responses.add(GraphOperationMessage::Vector { layer, modification_type }); + let Some(handle_offset) = self.target_handle_position(vector_data).map(|handle| (handle - anchor_pos).length()) else { return; - } - - if self.draw_mode == DrawMode::ContinuePath { - if let Some(handle) = self.handle_end.as_mut() { - *handle = new_position; - return; - } - - let Some(segment) = self.end_point_segment else { return }; - let modification_type = VectorModificationType::SetEndHandle { segment, relative_position }; - responses.add(GraphOperationMessage::Vector { layer, modification_type }); - return; - } - - let Some(segment) = self.end_point_segment else { return }; + }; - let modification_type = VectorModificationType::SetEndHandle { segment, relative_position }; - responses.add(GraphOperationMessage::Vector { layer, modification_type }); + let new_handle_position = anchor_pos + handle_offset * direction; + self.update_target_handle_pos(responses, new_handle_position, layer); } fn place_anchor(&mut self, snap_data: SnapData, transform: DAffine2, mouse: DVec2, preferences: &PreferencesMessageHandler, responses: &mut VecDeque<Message>) -> Option<PenToolFsmState> { @@ -740,6 +716,7 @@ impl PenToolData { tool_options.stroke.apply_stroke(tool_options.line_weight, layer, responses); self.end_point_segment = None; self.draw_mode = DrawMode::ContinuePath; + self.handle_type = TargetHandle::HandleEnd; responses.add(NodeGraphMessage::SelectedNodesSet { nodes: vec![layer.to_node()] }); // This causes the following message to be run only after the next graph evaluation runs and the transforms are updated @@ -820,6 +797,7 @@ impl PenToolData { let vector_data = document.network_interface.compute_modified_vector(layer).unwrap(); let segment = vector_data.all_connected(point).collect::<Vec<_>>().first().map(|s| s.segment); self.end_point_segment = segment; + self.update_handle_custom(&vector_data); } } @@ -1235,6 +1213,7 @@ impl Fsm for PenToolFsmState { colinear, }, ) => { + log::info!("{:?}", tool_data.handle_type); tool_data.modifiers = ModifierState { snap_angle: input.keyboard.key(snap_angle), lock_angle: input.keyboard.key(lock_angle), From 32581403ea3e59f6df90c0d2050addcf37f8018f Mon Sep 17 00:00:00 2001 From: 0SlowPoke0 <govindpratyush@gmail.com> Date: Thu, 13 Mar 2025 10:31:33 +0530 Subject: [PATCH 02/21] anchor move refactor --- .../messages/tool/tool_messages/pen_tool.rs | 78 ++++--------------- 1 file changed, 13 insertions(+), 65 deletions(-) diff --git a/editor/src/messages/tool/tool_messages/pen_tool.rs b/editor/src/messages/tool/tool_messages/pen_tool.rs index ffed73964c..161ce3960e 100644 --- a/editor/src/messages/tool/tool_messages/pen_tool.rs +++ b/editor/src/messages/tool/tool_messages/pen_tool.rs @@ -528,8 +528,8 @@ impl PenToolData { mouse: &DVec2, vector_data: &VectorData, input: &InputPreprocessorMessageHandler, - is_start: bool, ) -> Option<DVec2> { + let handle_pos = self.target_handle_position(vector_data); let snap = &mut self.snap_manager; let snap_data = SnapData::new_snap_cache(snap_data.document, input, &self.snap_cache); @@ -543,34 +543,14 @@ impl PenToolData { let snapped_near_handle_start = snap.free_snap(&snap_data, &handle_start, SnapTypeConfiguration::default()); let snapped_anchor = snap.free_snap(&snap_data, &anchor, SnapTypeConfiguration::default()); - let handle_snap_option = if let Some(handle_end) = self.handle_end { - let handle_offset = transform.transform_point2(handle_end - self.next_handle_start); - let handle_snap = SnapCandidatePoint::handle(document_pos + handle_offset); - Some((handle_end, handle_snap)) - } else { - // Otherwise use either primary or end handle based on is_start flag - if is_start { - let primary_handle_id = ManipulatorPointId::PrimaryHandle(self.end_point_segment.unwrap()); - match primary_handle_id.get_position(vector_data) { - Some(primary_handle) => { - let handle_offset = transform.transform_point2(primary_handle - self.next_handle_start); - let handle_snap = SnapCandidatePoint::handle(document_pos + handle_offset); - Some((primary_handle, handle_snap)) - } - None => None, - } - } else { - let end_handle = self.end_point_segment.and_then(|handle| ManipulatorPointId::EndHandle(handle).get_position(vector_data)); - match end_handle { - Some(end_handle) => { - let handle_offset = transform.transform_point2(end_handle - self.next_handle_start); - let handle_snap = SnapCandidatePoint::handle(document_pos + handle_offset); - Some((end_handle, handle_snap)) - } - None => None, - } + let handle_snap_option = handle_pos.and_then(|handle| match self.handle_type { + TargetHandle::None => None, + _ => { + let handle_offset = transform.transform_point2(handle - self.next_handle_start); + let handle_snap = SnapCandidatePoint::handle(document_pos + handle_offset); + Some((handle, handle_snap)) } - }; + }); let mut delta: DVec2; let best_snapped = if snapped_near_handle_start.other_snap_better(&snapped_anchor) { @@ -609,53 +589,27 @@ impl PenToolData { }; latest.pos += delta; - let modification_type = VectorModificationType::ApplyPointDelta { point: latest.id, delta }; - responses.add(GraphOperationMessage::Vector { layer, modification_type }); responses.add(OverlaysMessage::Draw); Some(PenToolFsmState::DraggingHandle(self.handle_mode)) } - fn move_anchor_and_handles(&mut self, delta: DVec2, layer: LayerNodeIdentifier, responses: &mut VecDeque<Message>, is_start: bool, vector_data: &VectorData) { + fn move_anchor_and_handles(&mut self, delta: DVec2, layer: LayerNodeIdentifier, responses: &mut VecDeque<Message>, vector_data: &VectorData) { if let Some(latest_pt) = self.latest_point_mut() { latest_pt.pos += delta; } let Some(end_point) = self.end_point else { return }; - - // Move anchor point let modification_type_anchor = VectorModificationType::ApplyPointDelta { point: end_point, delta }; - responses.add(GraphOperationMessage::Vector { layer, modification_type: modification_type_anchor, }); - // Check if the opposite handle exist and move it - let Some(segment) = self.end_point_segment else { return }; - // Get handle positions - let handle_end = ManipulatorPointId::EndHandle(segment).get_position(vector_data); - let handle_start = ManipulatorPointId::PrimaryHandle(segment).get_position(vector_data); - - let handle_modification_type: Option<VectorModificationType> = if is_start { - let Some(handle_start) = handle_start else { return }; - Some(VectorModificationType::SetPrimaryHandle { - segment, - relative_position: handle_start + delta - self.next_point, - }) - } else { - let Some(handle_end) = handle_end else { return }; - Some(VectorModificationType::SetEndHandle { - segment, - relative_position: handle_end + delta - self.next_point, - }) - }; - - if let Some(modification_type) = handle_modification_type { - responses.add(GraphOperationMessage::Vector { layer, modification_type }); - } + let Some(handle_pos) = self.target_handle_position(vector_data) else { return }; + self.update_target_handle_pos(responses, handle_pos + delta, layer); } fn drag_handle( @@ -673,14 +627,8 @@ impl PenToolData { let vector_data = document.network_interface.compute_modified_vector(layer)?; let viewport_to_document = document.metadata().document_to_viewport.inverse(); - // Check if the handle is the start of the segment - let mut is_start = false; - if let Some((anchor, segment)) = self.end_point.zip(self.end_point_segment) { - is_start = vector_data.segment_start_from_id(segment) == Some(anchor); - } - if self.modifiers.move_anchor_with_handles { - let Some(delta) = self.space_anchor_handle_snap(&viewport_to_document, &transform, &snap_data, &mouse, &vector_data, input, is_start) else { + let Some(delta) = self.space_anchor_handle_snap(&viewport_to_document, &transform, &snap_data, &mouse, &vector_data, input) else { return Some(PenToolFsmState::DraggingHandle(self.handle_mode)); }; @@ -694,7 +642,7 @@ impl PenToolData { if let Some(handle) = self.handle_end.as_mut() { *handle += delta; } else { - self.move_anchor_and_handles(delta, layer, responses, is_start, &vector_data); + self.move_anchor_and_handles(delta, layer, responses, &vector_data); } responses.add(OverlaysMessage::Draw); return Some(PenToolFsmState::DraggingHandle(self.handle_mode)); From 308031bae545bb48d6d2611b0106a897ef3a5036 Mon Sep 17 00:00:00 2001 From: 0SlowPoke0 <govindpratyush@gmail.com> Date: Thu, 13 Mar 2025 17:35:32 +0530 Subject: [PATCH 03/21] code-todo-fix --- editor/src/messages/input_mapper/input_mappings.rs | 4 ++-- editor/src/messages/tool/tool_messages/pen_tool.rs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/editor/src/messages/input_mapper/input_mappings.rs b/editor/src/messages/input_mapper/input_mappings.rs index 939e85353f..0ac3ee419c 100644 --- a/editor/src/messages/input_mapper/input_mappings.rs +++ b/editor/src/messages/input_mapper/input_mappings.rs @@ -255,8 +255,8 @@ pub fn input_mappings() -> Mapping { entry!(PointerMove; refresh_keys=[Control, Alt, Shift, KeyC], action_dispatch=PenToolMessage::PointerMove { snap_angle: Shift, break_handle: Alt, lock_angle: Control, colinear: KeyC, move_anchor_with_handles: Space }), entry!(KeyDown(MouseLeft); action_dispatch=PenToolMessage::DragStart { append_to_selected: Shift }), entry!(KeyUp(MouseLeft); action_dispatch=PenToolMessage::DragStop), - entry!(KeyDown(MouseRight); action_dispatch=PenToolMessage::Confirm), - entry!(KeyDown(Escape); action_dispatch=PenToolMessage::Confirm), + entry!(KeyDown(MouseRight); action_dispatch=PenToolMessage::Abort), + entry!(KeyDown(Escape); action_dispatch=PenToolMessage::Abort), entry!(KeyDown(Enter); action_dispatch=PenToolMessage::Confirm), entry!(KeyDown(Delete); action_dispatch=PenToolMessage::RemovePreviousHandle), entry!(KeyDown(Backspace); action_dispatch=PenToolMessage::RemovePreviousHandle), diff --git a/editor/src/messages/tool/tool_messages/pen_tool.rs b/editor/src/messages/tool/tool_messages/pen_tool.rs index 161ce3960e..b2b8abcdc5 100644 --- a/editor/src/messages/tool/tool_messages/pen_tool.rs +++ b/editor/src/messages/tool/tool_messages/pen_tool.rs @@ -695,7 +695,7 @@ impl PenToolData { self.update_target_handle_pos(responses, new_position, layer); } } - HandleMode::Free => {} // No adjustments needed in free mode + HandleMode::Free => {} } } From 34f6f4359e608ee984d28dcd6606497ee0646e70 Mon Sep 17 00:00:00 2001 From: 0SlowPoke0 <govindpratyush@gmail.com> Date: Fri, 14 Mar 2025 09:41:42 +0530 Subject: [PATCH 04/21] removed-draw-mode --- editor/src/messages/tool/tool_messages/pen_tool.rs | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/editor/src/messages/tool/tool_messages/pen_tool.rs b/editor/src/messages/tool/tool_messages/pen_tool.rs index b2b8abcdc5..26ec1e3e76 100644 --- a/editor/src/messages/tool/tool_messages/pen_tool.rs +++ b/editor/src/messages/tool/tool_messages/pen_tool.rs @@ -266,14 +266,6 @@ struct LastPoint { in_segment: Option<SegmentId>, handle_start: DVec2, } -#[derive(Clone, Debug, Default, PartialEq, Eq)] -enum DrawMode { - #[default] - /// Modifies the clicked endpoint segment, once you go to the ready mode you need to modify the handles of the next clicked endpoint segment - BreakPath, - /// Modifies the handle_end - ContinuePath, -} #[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] enum HandleMode { @@ -321,7 +313,6 @@ struct PenToolData { /// The point that is being dragged end_point: Option<PointId>, end_point_segment: Option<SegmentId>, - draw_mode: DrawMode, handle_type: TargetHandle, snap_cache: SnapCache, @@ -871,7 +862,6 @@ impl PenToolData { tool_options.fill.apply_fill(layer, responses); tool_options.stroke.apply_stroke(tool_options.line_weight, layer, responses); self.end_point_segment = None; - self.draw_mode = DrawMode::ContinuePath; self.handle_type = TargetHandle::HandleEnd; responses.add(NodeGraphMessage::SelectedNodesSet { nodes: vec![layer.to_node()] }); @@ -1370,7 +1360,6 @@ impl Fsm for PenToolFsmState { } (PenToolFsmState::DraggingHandle(_), PenToolMessage::DragStop) => { tool_data.end_point = None; - tool_data.draw_mode = DrawMode::ContinuePath; tool_data .finish_placing_handle(SnapData::new(document, input), transform, preferences, responses) .unwrap_or(PenToolFsmState::PlacingAnchor) @@ -1547,7 +1536,6 @@ impl Fsm for PenToolFsmState { (PenToolFsmState::DraggingHandle(..) | PenToolFsmState::PlacingAnchor, PenToolMessage::Confirm) => { responses.add(DocumentMessage::EndTransaction); tool_data.handle_end = None; - tool_data.draw_mode = DrawMode::BreakPath; tool_data.latest_points.clear(); tool_data.point_index = 0; tool_data.snap_manager.cleanup(responses); @@ -1559,7 +1547,6 @@ impl Fsm for PenToolFsmState { tool_data.handle_end = None; tool_data.latest_points.clear(); tool_data.point_index = 0; - tool_data.draw_mode = DrawMode::BreakPath; tool_data.snap_manager.cleanup(responses); responses.add(OverlaysMessage::Draw); From 3e86424d79dac3cbbb4707f5346a17640812d6a3 Mon Sep 17 00:00:00 2001 From: 0SlowPoke0 <govindpratyush@gmail.com> Date: Sat, 15 Mar 2025 12:01:57 +0530 Subject: [PATCH 05/21] kind of works need to figure out snapping --- .../messages/input_mapper/input_mappings.rs | 1 + .../messages/tool/tool_messages/pen_tool.rs | 155 +++++++++++++----- 2 files changed, 115 insertions(+), 41 deletions(-) diff --git a/editor/src/messages/input_mapper/input_mappings.rs b/editor/src/messages/input_mapper/input_mappings.rs index 0ac3ee419c..201200ed11 100644 --- a/editor/src/messages/input_mapper/input_mappings.rs +++ b/editor/src/messages/input_mapper/input_mappings.rs @@ -253,6 +253,7 @@ pub fn input_mappings() -> Mapping { // // PenToolMessage entry!(PointerMove; refresh_keys=[Control, Alt, Shift, KeyC], action_dispatch=PenToolMessage::PointerMove { snap_angle: Shift, break_handle: Alt, lock_angle: Control, colinear: KeyC, move_anchor_with_handles: Space }), + entry!(KeyDownNoRepeat(Tab);action_dispatch=PenToolMessage::SwapHandles), entry!(KeyDown(MouseLeft); action_dispatch=PenToolMessage::DragStart { append_to_selected: Shift }), entry!(KeyUp(MouseLeft); action_dispatch=PenToolMessage::DragStop), entry!(KeyDown(MouseRight); action_dispatch=PenToolMessage::Abort), diff --git a/editor/src/messages/tool/tool_messages/pen_tool.rs b/editor/src/messages/tool/tool_messages/pen_tool.rs index 26ec1e3e76..f2b29661a4 100644 --- a/editor/src/messages/tool/tool_messages/pen_tool.rs +++ b/editor/src/messages/tool/tool_messages/pen_tool.rs @@ -88,6 +88,7 @@ pub enum PenToolMessage { FinalPosition { final_position: DVec2, }, + SwapHandles, } #[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] @@ -186,9 +187,36 @@ impl LayoutHolder for PenTool { impl<'a> MessageHandler<ToolMessage, &mut ToolActionHandlerData<'a>> for PenTool { fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque<Message>, tool_data: &mut ToolActionHandlerData<'a>) { let ToolMessage::Pen(PenToolMessage::UpdateOptions(action)) = message else { - self.fsm_state.process_event(message, &mut self.tool_data, tool_data, &self.options, responses, true); - return; + match message { + ToolMessage::Pen(PenToolMessage::SwapHandles) => { + let document = &tool_data.document; + let selected_nodes = document.network_interface.selected_nodes(); + let mut selected_layers = selected_nodes.selected_layers(document.metadata()); + let layer = selected_layers.next().filter(|_| selected_layers.next().is_none()); + let Some(vector_data) = layer.and_then(|layer| document.network_interface.compute_modified_vector(layer)) else { + return; + }; + match self.tool_data.handle_type { + TargetHandle::None => {} + TargetHandle::HandleStart => { + self.tool_data.update_handle_custom(&vector_data); + } + _ => { + let offset: Option<DVec2> = self.tool_data.target_handle_position(&vector_data).map(|handle| handle - self.tool_data.next_handle_start); + self.tool_data.offset_position = offset; + self.tool_data.update_handle_type(TargetHandle::HandleStart); + } + } + responses.add(FrontendMessage::UpdateMouseCursor { cursor: MouseCursorIcon::None }); + return; + } + _ => { + self.fsm_state.process_event(message, &mut self.tool_data, tool_data, &self.options, responses, true); + return; + } + } }; + match action { PenOptionsUpdate::OverlayModeType(overlay_mode_type) => { self.options.pen_overlay_mode = overlay_mode_type; @@ -235,6 +263,7 @@ impl<'a> MessageHandler<ToolMessage, &mut ToolActionHandlerData<'a>> for PenTool Abort, RemovePreviousHandle, GRS, + SwapHandles ), } } @@ -278,9 +307,10 @@ enum HandleMode { ColinearEquidistant, } -#[derive(Clone, Debug, Default)] +#[derive(Clone, Debug, Default, PartialEq)] enum TargetHandle { HandleEnd, + HandleStart, PrimaryHandle(SegmentId), EndHandle(SegmentId), #[default] @@ -314,6 +344,7 @@ struct PenToolData { end_point: Option<PointId>, end_point_segment: Option<SegmentId>, handle_type: TargetHandle, + offset_position: Option<DVec2>, snap_cache: SnapCache, } @@ -333,49 +364,77 @@ impl PenToolData { } fn update_handle_custom(&mut self, vector_data: &VectorData) { - let Some((point, segment)) = self.end_point.zip(self.end_point_segment) else { - self.update_handle_type(TargetHandle::None); - return; + let target_handle = match (self.handle_end, self.end_point, self.end_point_segment) { + (Some(_), _, _) => TargetHandle::HandleEnd, + (None, Some(point), Some(segment)) => { + if vector_data.segment_start_from_id(segment) == Some(point) { + TargetHandle::PrimaryHandle(segment) + } else { + TargetHandle::EndHandle(segment) + } + } + _ => TargetHandle::None, }; - if vector_data.segment_start_from_id(segment) == Some(point) { - self.update_handle_type(TargetHandle::PrimaryHandle(segment)); - return; - } - self.update_handle_type(TargetHandle::EndHandle(segment)); + self.update_handle_type(target_handle); } fn update_handle_type(&mut self, handle_type: TargetHandle) { self.handle_type = handle_type; } - fn update_target_handle_pos(&mut self, responses: &mut VecDeque<Message>, delta: DVec2, layer: LayerNodeIdentifier) { + fn update_target_handle_pos(&mut self, responses: &mut VecDeque<Message>, delta: DVec2, layer: LayerNodeIdentifier, vector_data: &VectorData) { match self.handle_type { - TargetHandle::PrimaryHandle(segment) => { - let relative_position = delta - self.next_point; - let modification_type = VectorModificationType::SetPrimaryHandle { segment, relative_position }; - responses.add(GraphOperationMessage::Vector { layer, modification_type }); - } - TargetHandle::HandleEnd => { - if let Some(handle) = self.handle_end.as_mut() { - *handle = delta; - } - } - TargetHandle::EndHandle(segment) => { - let relative_position = delta - self.next_point; - let modification_type = VectorModificationType::SetEndHandle { segment, relative_position }; - responses.add(GraphOperationMessage::Vector { layer, modification_type }); + TargetHandle::HandleStart => { + self.next_handle_start = delta; } TargetHandle::None => {} + _ => { + self.update_end_targets(responses, delta, layer, vector_data); + } + } + } + + fn update_end_targets(&mut self, responses: &mut VecDeque<Message>, delta: DVec2, layer: LayerNodeIdentifier, vector_data: &VectorData) { + if let Some(handle) = self.handle_end.as_mut() { + *handle = delta; + return; + } + + if let Some((segment, anchor_point)) = self.end_point_segment.zip(self.end_point) { + let relative_position = delta - self.next_point; + let modification_type = if vector_data.segment_start_from_id(segment) == Some(anchor_point) { + VectorModificationType::SetPrimaryHandle { segment, relative_position } + } else { + VectorModificationType::SetEndHandle { segment, relative_position } + }; + responses.add(GraphOperationMessage::Vector { layer, modification_type }); } } fn target_handle_position(&self, vector_data: &VectorData) -> Option<DVec2> { match self.handle_type { - TargetHandle::PrimaryHandle(segment) => ManipulatorPointId::PrimaryHandle(segment).get_position(&vector_data), - TargetHandle::EndHandle(segment) => ManipulatorPointId::EndHandle(segment).get_position(&vector_data), + TargetHandle::PrimaryHandle(segment) => ManipulatorPointId::PrimaryHandle(segment).get_position(vector_data), + TargetHandle::EndHandle(segment) => ManipulatorPointId::EndHandle(segment).get_position(vector_data), TargetHandle::HandleEnd => self.handle_end, + TargetHandle::HandleStart => Some(self.next_handle_start), + TargetHandle::None => None, + } + } + + fn opposite_target_handle_position(&self, vector_data: &VectorData) -> Option<DVec2> { + match self.handle_type { TargetHandle::None => None, + TargetHandle::HandleStart => self.handle_end.or_else(|| { + self.end_point_segment.zip(self.end_point).and_then(|(segment, anchor_point)| { + if vector_data.segment_start_from_id(segment) == Some(anchor_point) { + ManipulatorPointId::PrimaryHandle(segment).get_position(vector_data) + } else { + ManipulatorPointId::EndHandle(segment).get_position(vector_data) + } + }) + }), + _ => Some(self.next_handle_start), } } @@ -441,6 +500,7 @@ impl PenToolData { let next_handle_start = self.next_handle_start; let handle_start = self.latest_point()?.handle_start; let mouse = snap_data.input.mouse.position; + self.offset_position = None; let Some(handle_end) = self.handle_end else { responses.add(DocumentMessage::EndTransaction); self.handle_end = Some(next_handle_start); @@ -600,7 +660,7 @@ impl PenToolData { }); let Some(handle_pos) = self.target_handle_position(vector_data) else { return }; - self.update_target_handle_pos(responses, handle_pos + delta, layer); + self.update_target_handle_pos(responses, handle_pos + delta, layer, vector_data); } fn drag_handle( @@ -617,6 +677,7 @@ impl PenToolData { let Some(layer) = layer else { return Some(PenToolFsmState::DraggingHandle(self.handle_mode)) }; let vector_data = document.network_interface.compute_modified_vector(layer)?; let viewport_to_document = document.metadata().document_to_viewport.inverse(); + let viewport = document.metadata().transform_to_viewport(layer); if self.modifiers.move_anchor_with_handles { let Some(delta) = self.space_anchor_handle_snap(&viewport_to_document, &transform, &snap_data, &mouse, &vector_data, input) else { @@ -638,13 +699,18 @@ impl PenToolData { responses.add(OverlaysMessage::Draw); return Some(PenToolFsmState::DraggingHandle(self.handle_mode)); } - - self.next_handle_start = self.compute_snapped_angle(snap_data.clone(), transform, colinear, mouse, Some(self.next_point), false); + if self.handle_type == TargetHandle::HandleStart { + let mouse_offset = mouse + viewport.transform_point2(self.offset_position.unwrap()); + let mouse_pos = self.compute_snapped_angle(snap_data.clone(), transform, colinear, mouse_offset, Some(self.next_point), false); + self.update_end_targets(responses, mouse_pos, layer, &vector_data); + } else { + self.next_handle_start = self.compute_snapped_angle(snap_data.clone(), transform, colinear, mouse, Some(self.next_point), false); + } match self.handle_mode { HandleMode::ColinearLocked | HandleMode::ColinearEquidistant => { self.g1_continuous = true; - self.apply_colinear_constraint(responses, layer, self.next_handle_start, self.next_point, &vector_data); + self.apply_colinear_constraint(responses, layer, self.next_point, &vector_data); self.adjust_handle_length(responses, layer, &vector_data); } HandleMode::Free => { @@ -669,12 +735,14 @@ impl PenToolData { } // Set handle to opposite position of the other handle - let new_position = self.next_point * 2. - self.next_handle_start; - self.update_target_handle_pos(responses, new_position, layer); + let Some(new_position) = self.opposite_target_handle_position(vector_data).map(|handle| self.next_point * 2. - handle) else { + return; + }; + self.update_target_handle_pos(responses, new_position, layer, vector_data); } else if self.alt_press { // Restore the previous handle position when Alt is released if let Some(previous_handle) = self.previous_handle_end_pos { - self.update_target_handle_pos(responses, previous_handle, layer); + self.update_target_handle_pos(responses, previous_handle, layer, vector_data); } self.alt_press = false; self.previous_handle_end_pos = None; @@ -682,16 +750,22 @@ impl PenToolData { } HandleMode::ColinearLocked => { if !self.modifiers.break_handle { - let new_position = self.next_point * 2. - self.next_handle_start; - self.update_target_handle_pos(responses, new_position, layer); + let Some(new_position) = self.opposite_target_handle_position(vector_data).map(|handle| self.next_point * 2. - handle) else { + return; + }; + self.update_target_handle_pos(responses, new_position, layer, vector_data); } } HandleMode::Free => {} } } - fn apply_colinear_constraint(&mut self, responses: &mut VecDeque<Message>, layer: LayerNodeIdentifier, handle_start: DVec2, anchor_pos: DVec2, vector_data: &VectorData) { - let Some(direction) = (anchor_pos - handle_start).try_normalize() else { + fn apply_colinear_constraint(&mut self, responses: &mut VecDeque<Message>, layer: LayerNodeIdentifier, anchor_pos: DVec2, vector_data: &VectorData) { + let Some(handle) = self.opposite_target_handle_position(vector_data) else { + return; + }; + + let Some(direction) = (anchor_pos - handle).try_normalize() else { log::trace!("Skipping colinear adjustment: handle_start and anchor_point are too close!"); return; }; @@ -701,7 +775,7 @@ impl PenToolData { }; let new_handle_position = anchor_pos + handle_offset * direction; - self.update_target_handle_pos(responses, new_handle_position, layer); + self.update_target_handle_pos(responses, new_handle_position, layer, vector_data); } fn place_anchor(&mut self, snap_data: SnapData, transform: DAffine2, mouse: DVec2, preferences: &PreferencesMessageHandler, responses: &mut VecDeque<Message>) -> Option<PenToolFsmState> { @@ -1374,7 +1448,6 @@ impl Fsm for PenToolFsmState { move_anchor_with_handles, }, ) => { - log::info!("{:?}", tool_data.handle_type); tool_data.modifiers = ModifierState { snap_angle: input.keyboard.key(snap_angle), lock_angle: input.keyboard.key(lock_angle), From 6b7ba1149f127c0be7529f91da9c0ad59d5b0c68 Mon Sep 17 00:00:00 2001 From: 0SlowPoke0 <govindpratyush@gmail.com> Date: Sat, 15 Mar 2025 13:10:31 +0530 Subject: [PATCH 06/21] some refactoring --- .../messages/tool/tool_messages/pen_tool.rs | 101 +++++++++--------- 1 file changed, 53 insertions(+), 48 deletions(-) diff --git a/editor/src/messages/tool/tool_messages/pen_tool.rs b/editor/src/messages/tool/tool_messages/pen_tool.rs index f2b29661a4..22d738463b 100644 --- a/editor/src/messages/tool/tool_messages/pen_tool.rs +++ b/editor/src/messages/tool/tool_messages/pen_tool.rs @@ -202,7 +202,10 @@ impl<'a> MessageHandler<ToolMessage, &mut ToolActionHandlerData<'a>> for PenTool self.tool_data.update_handle_custom(&vector_data); } _ => { - let offset: Option<DVec2> = self.tool_data.target_handle_position(&vector_data).map(|handle| handle - self.tool_data.next_handle_start); + let offset: Option<DVec2> = self + .tool_data + .target_handle_position(self.tool_data.handle_type, &vector_data) + .map(|handle| handle - self.tool_data.next_handle_start); self.tool_data.offset_position = offset; self.tool_data.update_handle_type(TargetHandle::HandleStart); } @@ -307,7 +310,7 @@ enum HandleMode { ColinearEquidistant, } -#[derive(Clone, Debug, Default, PartialEq)] +#[derive(Clone, Debug, Default, PartialEq, Copy)] enum TargetHandle { HandleEnd, HandleStart, @@ -363,8 +366,8 @@ impl PenToolData { self.latest_points.push(point); } - fn update_handle_custom(&mut self, vector_data: &VectorData) { - let target_handle = match (self.handle_end, self.end_point, self.end_point_segment) { + fn check_end_handle_type(&self, vector_data: &VectorData) -> TargetHandle { + match (self.handle_end, self.end_point, self.end_point_segment) { (Some(_), _, _) => TargetHandle::HandleEnd, (None, Some(point), Some(segment)) => { if vector_data.segment_start_from_id(segment) == Some(point) { @@ -374,46 +377,52 @@ impl PenToolData { } } _ => TargetHandle::None, - }; + } + } + fn update_handle_custom(&mut self, vector_data: &VectorData) { + let target_handle = self.check_end_handle_type(vector_data); self.update_handle_type(target_handle); } + fn get_opposite_handle_type(&mut self, vector_data: &VectorData) -> TargetHandle { + match self.handle_type { + TargetHandle::HandleStart => self.check_end_handle_type(vector_data), + TargetHandle::None => TargetHandle::None, + _ => TargetHandle::HandleStart, + } + } + fn update_handle_type(&mut self, handle_type: TargetHandle) { self.handle_type = handle_type; } - fn update_target_handle_pos(&mut self, responses: &mut VecDeque<Message>, delta: DVec2, layer: LayerNodeIdentifier, vector_data: &VectorData) { - match self.handle_type { + fn update_target_handle_pos(&mut self, handle_type: TargetHandle, responses: &mut VecDeque<Message>, delta: DVec2, layer: LayerNodeIdentifier, vector_data: &VectorData) { + match handle_type { TargetHandle::HandleStart => { self.next_handle_start = delta; } - TargetHandle::None => {} - _ => { - self.update_end_targets(responses, delta, layer, vector_data); + TargetHandle::HandleEnd => { + if let Some(handle) = self.handle_end.as_mut() { + *handle = delta; + } } + TargetHandle::EndHandle(segment) => { + let relative_position = delta - self.next_point; + let modification_type = VectorModificationType::SetEndHandle { segment, relative_position }; + responses.add(GraphOperationMessage::Vector { layer, modification_type }); + } + TargetHandle::PrimaryHandle(segment) => { + let relative_position = delta - self.next_point; + let modification_type = VectorModificationType::SetPrimaryHandle { segment, relative_position }; + responses.add(GraphOperationMessage::Vector { layer, modification_type }); + } + TargetHandle::None => {} } } - fn update_end_targets(&mut self, responses: &mut VecDeque<Message>, delta: DVec2, layer: LayerNodeIdentifier, vector_data: &VectorData) { - if let Some(handle) = self.handle_end.as_mut() { - *handle = delta; - return; - } - - if let Some((segment, anchor_point)) = self.end_point_segment.zip(self.end_point) { - let relative_position = delta - self.next_point; - let modification_type = if vector_data.segment_start_from_id(segment) == Some(anchor_point) { - VectorModificationType::SetPrimaryHandle { segment, relative_position } - } else { - VectorModificationType::SetEndHandle { segment, relative_position } - }; - responses.add(GraphOperationMessage::Vector { layer, modification_type }); - } - } - - fn target_handle_position(&self, vector_data: &VectorData) -> Option<DVec2> { - match self.handle_type { + fn target_handle_position(&self, handle_type: TargetHandle, vector_data: &VectorData) -> Option<DVec2> { + match handle_type { TargetHandle::PrimaryHandle(segment) => ManipulatorPointId::PrimaryHandle(segment).get_position(vector_data), TargetHandle::EndHandle(segment) => ManipulatorPointId::EndHandle(segment).get_position(vector_data), TargetHandle::HandleEnd => self.handle_end, @@ -425,15 +434,10 @@ impl PenToolData { fn opposite_target_handle_position(&self, vector_data: &VectorData) -> Option<DVec2> { match self.handle_type { TargetHandle::None => None, - TargetHandle::HandleStart => self.handle_end.or_else(|| { - self.end_point_segment.zip(self.end_point).and_then(|(segment, anchor_point)| { - if vector_data.segment_start_from_id(segment) == Some(anchor_point) { - ManipulatorPointId::PrimaryHandle(segment).get_position(vector_data) - } else { - ManipulatorPointId::EndHandle(segment).get_position(vector_data) - } - }) - }), + TargetHandle::HandleStart => { + let opposite_target_handle = self.check_end_handle_type(vector_data); + self.target_handle_position(opposite_target_handle, vector_data) + } _ => Some(self.next_handle_start), } } @@ -580,7 +584,7 @@ impl PenToolData { vector_data: &VectorData, input: &InputPreprocessorMessageHandler, ) -> Option<DVec2> { - let handle_pos = self.target_handle_position(vector_data); + let handle_pos = self.target_handle_position(self.handle_type, vector_data); let snap = &mut self.snap_manager; let snap_data = SnapData::new_snap_cache(snap_data.document, input, &self.snap_cache); @@ -659,8 +663,8 @@ impl PenToolData { modification_type: modification_type_anchor, }); - let Some(handle_pos) = self.target_handle_position(vector_data) else { return }; - self.update_target_handle_pos(responses, handle_pos + delta, layer, vector_data); + let Some(handle_pos) = self.target_handle_position(self.handle_type, vector_data) else { return }; + self.update_target_handle_pos(self.handle_type, responses, handle_pos + delta, layer, vector_data); } fn drag_handle( @@ -702,7 +706,8 @@ impl PenToolData { if self.handle_type == TargetHandle::HandleStart { let mouse_offset = mouse + viewport.transform_point2(self.offset_position.unwrap()); let mouse_pos = self.compute_snapped_angle(snap_data.clone(), transform, colinear, mouse_offset, Some(self.next_point), false); - self.update_end_targets(responses, mouse_pos, layer, &vector_data); + let opposite_target = self.get_opposite_handle_type(&vector_data); + self.update_target_handle_pos(opposite_target, responses, mouse_pos, layer, &vector_data); } else { self.next_handle_start = self.compute_snapped_angle(snap_data.clone(), transform, colinear, mouse, Some(self.next_point), false); } @@ -730,7 +735,7 @@ impl PenToolData { if self.modifiers.break_handle { // Store handle for later restoration only when Alt is first pressed if !self.alt_press { - self.previous_handle_end_pos = self.target_handle_position(vector_data); + self.previous_handle_end_pos = self.target_handle_position(self.handle_type, vector_data); self.alt_press = true; } @@ -738,11 +743,11 @@ impl PenToolData { let Some(new_position) = self.opposite_target_handle_position(vector_data).map(|handle| self.next_point * 2. - handle) else { return; }; - self.update_target_handle_pos(responses, new_position, layer, vector_data); + self.update_target_handle_pos(self.handle_type, responses, new_position, layer, vector_data); } else if self.alt_press { // Restore the previous handle position when Alt is released if let Some(previous_handle) = self.previous_handle_end_pos { - self.update_target_handle_pos(responses, previous_handle, layer, vector_data); + self.update_target_handle_pos(self.handle_type, responses, previous_handle, layer, vector_data); } self.alt_press = false; self.previous_handle_end_pos = None; @@ -753,7 +758,7 @@ impl PenToolData { let Some(new_position) = self.opposite_target_handle_position(vector_data).map(|handle| self.next_point * 2. - handle) else { return; }; - self.update_target_handle_pos(responses, new_position, layer, vector_data); + self.update_target_handle_pos(self.handle_type, responses, new_position, layer, vector_data); } } HandleMode::Free => {} @@ -770,12 +775,12 @@ impl PenToolData { return; }; - let Some(handle_offset) = self.target_handle_position(vector_data).map(|handle| (handle - anchor_pos).length()) else { + let Some(handle_offset) = self.target_handle_position(self.handle_type, vector_data).map(|handle| (handle - anchor_pos).length()) else { return; }; let new_handle_position = anchor_pos + handle_offset * direction; - self.update_target_handle_pos(responses, new_handle_position, layer, vector_data); + self.update_target_handle_pos(self.handle_type, responses, new_handle_position, layer, vector_data); } fn place_anchor(&mut self, snap_data: SnapData, transform: DAffine2, mouse: DVec2, preferences: &PreferencesMessageHandler, responses: &mut VecDeque<Message>) -> Option<PenToolFsmState> { From 7cabb159d126d1083fb5127abc3edb2559a4918b Mon Sep 17 00:00:00 2001 From: 0SlowPoke0 <govindpratyush@gmail.com> Date: Sun, 16 Mar 2025 12:14:46 +0530 Subject: [PATCH 07/21] refactor+overlays..need to fix the snapping and dragging --- .../messages/tool/tool_messages/pen_tool.rs | 72 +++++++++++++------ 1 file changed, 51 insertions(+), 21 deletions(-) diff --git a/editor/src/messages/tool/tool_messages/pen_tool.rs b/editor/src/messages/tool/tool_messages/pen_tool.rs index 22d738463b..c8ec9534c3 100644 --- a/editor/src/messages/tool/tool_messages/pen_tool.rs +++ b/editor/src/messages/tool/tool_messages/pen_tool.rs @@ -7,6 +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::{self, merge_layers}; +use crate::messages::tool::common_functionality::shape_editor::ShapeState; use crate::messages::tool::common_functionality::snapping::{SnapCache, SnapCandidatePoint, SnapConstraint, SnapData, SnapManager, SnapTypeConfiguration}; use crate::messages::tool::common_functionality::utility_functions::{closest_point, should_extend}; use bezier_rs::{Bezier, BezierHandles}; @@ -200,16 +201,16 @@ impl<'a> MessageHandler<ToolMessage, &mut ToolActionHandlerData<'a>> for PenTool TargetHandle::None => {} TargetHandle::HandleStart => { self.tool_data.update_handle_custom(&vector_data); + self.tool_data.cleanup_target_selections(tool_data.shape_editor, layer, tool_data.document); } _ => { - let offset: Option<DVec2> = self - .tool_data - .target_handle_position(self.tool_data.handle_type, &vector_data) - .map(|handle| handle - self.tool_data.next_handle_start); - self.tool_data.offset_position = offset; + self.tool_data.add_target_selections(tool_data.shape_editor, layer); self.tool_data.update_handle_type(TargetHandle::HandleStart); + let offset = self.tool_data.opposite_target_handle_position(&vector_data).map(|opposite| opposite - self.tool_data.next_handle_start); + self.tool_data.offset_position = offset; } } + responses.add(OverlaysMessage::Draw); responses.add(FrontendMessage::UpdateMouseCursor { cursor: MouseCursorIcon::None }); return; } @@ -385,7 +386,7 @@ impl PenToolData { self.update_handle_type(target_handle); } - fn get_opposite_handle_type(&mut self, vector_data: &VectorData) -> TargetHandle { + fn get_opposite_handle_type(&self, vector_data: &VectorData) -> TargetHandle { match self.handle_type { TargetHandle::HandleStart => self.check_end_handle_type(vector_data), TargetHandle::None => TargetHandle::None, @@ -397,7 +398,7 @@ impl PenToolData { self.handle_type = handle_type; } - fn update_target_handle_pos(&mut self, handle_type: TargetHandle, responses: &mut VecDeque<Message>, delta: DVec2, layer: LayerNodeIdentifier, vector_data: &VectorData) { + fn update_target_handle_pos(&mut self, handle_type: TargetHandle, responses: &mut VecDeque<Message>, delta: DVec2, layer: LayerNodeIdentifier) { match handle_type { TargetHandle::HandleStart => { self.next_handle_start = delta; @@ -432,13 +433,36 @@ impl PenToolData { } fn opposite_target_handle_position(&self, vector_data: &VectorData) -> Option<DVec2> { + let opposite_handle_type = self.get_opposite_handle_type(vector_data); + self.target_handle_position(opposite_handle_type, vector_data) + } + + /// Remove the handles selected when swapping handles + fn cleanup_target_selections(&self, shape_editor: &mut ShapeState, layer: Option<LayerNodeIdentifier>, document: &DocumentMessageHandler) { + let Some(shape_state) = layer.and_then(|layer| shape_editor.selected_shape_state.get_mut(&layer)) else { + return; + }; + + let Some(vector_data) = layer.and_then(|layer| document.network_interface.compute_modified_vector(layer)) else { + return; + }; + + match self.check_end_handle_type(&vector_data) { + TargetHandle::EndHandle(segment) => shape_state.deselect_point(ManipulatorPointId::EndHandle(segment)), + TargetHandle::PrimaryHandle(segment) => shape_state.deselect_point(ManipulatorPointId::PrimaryHandle(segment)), + _ => {} + } + } + + fn add_target_selections(&self, shape_editor: &mut ShapeState, layer: Option<LayerNodeIdentifier>) { + let Some(shape_state) = layer.and_then(|layer| shape_editor.selected_shape_state.get_mut(&layer)) else { + return; + }; + match self.handle_type { - TargetHandle::None => None, - TargetHandle::HandleStart => { - let opposite_target_handle = self.check_end_handle_type(vector_data); - self.target_handle_position(opposite_target_handle, vector_data) - } - _ => Some(self.next_handle_start), + TargetHandle::EndHandle(segment) => shape_state.select_point(ManipulatorPointId::EndHandle(segment)), + TargetHandle::PrimaryHandle(segment) => shape_state.select_point(ManipulatorPointId::PrimaryHandle(segment)), + _ => {} } } @@ -664,7 +688,7 @@ impl PenToolData { }); let Some(handle_pos) = self.target_handle_position(self.handle_type, vector_data) else { return }; - self.update_target_handle_pos(self.handle_type, responses, handle_pos + delta, layer, vector_data); + self.update_target_handle_pos(self.handle_type, responses, handle_pos + delta, layer); } fn drag_handle( @@ -707,7 +731,7 @@ impl PenToolData { let mouse_offset = mouse + viewport.transform_point2(self.offset_position.unwrap()); let mouse_pos = self.compute_snapped_angle(snap_data.clone(), transform, colinear, mouse_offset, Some(self.next_point), false); let opposite_target = self.get_opposite_handle_type(&vector_data); - self.update_target_handle_pos(opposite_target, responses, mouse_pos, layer, &vector_data); + self.update_target_handle_pos(opposite_target, responses, mouse_pos, layer); } else { self.next_handle_start = self.compute_snapped_angle(snap_data.clone(), transform, colinear, mouse, Some(self.next_point), false); } @@ -743,11 +767,11 @@ impl PenToolData { let Some(new_position) = self.opposite_target_handle_position(vector_data).map(|handle| self.next_point * 2. - handle) else { return; }; - self.update_target_handle_pos(self.handle_type, responses, new_position, layer, vector_data); + self.update_target_handle_pos(self.handle_type, responses, new_position, layer); } else if self.alt_press { // Restore the previous handle position when Alt is released if let Some(previous_handle) = self.previous_handle_end_pos { - self.update_target_handle_pos(self.handle_type, responses, previous_handle, layer, vector_data); + self.update_target_handle_pos(self.handle_type, responses, previous_handle, layer); } self.alt_press = false; self.previous_handle_end_pos = None; @@ -758,7 +782,7 @@ impl PenToolData { let Some(new_position) = self.opposite_target_handle_position(vector_data).map(|handle| self.next_point * 2. - handle) else { return; }; - self.update_target_handle_pos(self.handle_type, responses, new_position, layer, vector_data); + self.update_target_handle_pos(self.handle_type, responses, new_position, layer); } } HandleMode::Free => {} @@ -780,7 +804,7 @@ impl PenToolData { }; let new_handle_position = anchor_pos + handle_offset * direction; - self.update_target_handle_pos(self.handle_type, responses, new_handle_position, layer, vector_data); + self.update_target_handle_pos(self.handle_type, responses, new_handle_position, layer); } fn place_anchor(&mut self, snap_data: SnapData, transform: DAffine2, mouse: DVec2, preferences: &PreferencesMessageHandler, responses: &mut VecDeque<Message>) -> Option<PenToolFsmState> { @@ -1328,7 +1352,8 @@ impl Fsm for PenToolFsmState { if self == PenToolFsmState::DraggingHandle(tool_data.handle_mode) && valid(next_anchor, handle_end) { // Draw the handle circle for the currently-being-dragged-out incoming handle (opposite the one currently being dragged out) - overlay_context.manipulator_handle(handle_end, false, None); + let selected = tool_data.handle_type == TargetHandle::HandleStart; + overlay_context.manipulator_handle(handle_end, selected, None); } if valid(anchor_start, handle_start) { @@ -1349,7 +1374,8 @@ impl Fsm for PenToolFsmState { if self == PenToolFsmState::DraggingHandle(tool_data.handle_mode) && valid(next_anchor, next_handle_start) { // Draw the handle circle for the currently-being-dragged-out outgoing handle (the one currently being dragged out, under the user's cursor) - overlay_context.manipulator_handle(next_handle_start, false, None); + let selected = tool_data.handle_type != TargetHandle::HandleStart; + overlay_context.manipulator_handle(next_handle_start, selected, None); } if self == PenToolFsmState::DraggingHandle(tool_data.handle_mode) { @@ -1439,6 +1465,8 @@ impl Fsm for PenToolFsmState { } (PenToolFsmState::DraggingHandle(_), PenToolMessage::DragStop) => { tool_data.end_point = None; + + tool_data.cleanup_target_selections(shape_editor, layer, document); tool_data .finish_placing_handle(SnapData::new(document, input), transform, preferences, responses) .unwrap_or(PenToolFsmState::PlacingAnchor) @@ -1617,6 +1645,7 @@ impl Fsm for PenToolFsmState { tool_data.latest_points.clear(); tool_data.point_index = 0; tool_data.snap_manager.cleanup(responses); + tool_data.cleanup_target_selections(shape_editor, layer, document); PenToolFsmState::Ready } @@ -1626,6 +1655,7 @@ impl Fsm for PenToolFsmState { tool_data.latest_points.clear(); tool_data.point_index = 0; tool_data.snap_manager.cleanup(responses); + tool_data.cleanup_target_selections(shape_editor, layer, document); responses.add(OverlaysMessage::Draw); From f4515103cc187dff8ba9f7230f4123839f60e9c7 Mon Sep 17 00:00:00 2001 From: 0SlowPoke0 <govindpratyush@gmail.com> Date: Sun, 16 Mar 2025 22:30:57 +0530 Subject: [PATCH 08/21] added docs --- .../messages/tool/tool_messages/pen_tool.rs | 70 ++++++++++--------- 1 file changed, 38 insertions(+), 32 deletions(-) diff --git a/editor/src/messages/tool/tool_messages/pen_tool.rs b/editor/src/messages/tool/tool_messages/pen_tool.rs index c8ec9534c3..0427d5a56d 100644 --- a/editor/src/messages/tool/tool_messages/pen_tool.rs +++ b/editor/src/messages/tool/tool_messages/pen_tool.rs @@ -188,37 +188,8 @@ impl LayoutHolder for PenTool { impl<'a> MessageHandler<ToolMessage, &mut ToolActionHandlerData<'a>> for PenTool { fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque<Message>, tool_data: &mut ToolActionHandlerData<'a>) { let ToolMessage::Pen(PenToolMessage::UpdateOptions(action)) = message else { - match message { - ToolMessage::Pen(PenToolMessage::SwapHandles) => { - let document = &tool_data.document; - let selected_nodes = document.network_interface.selected_nodes(); - let mut selected_layers = selected_nodes.selected_layers(document.metadata()); - let layer = selected_layers.next().filter(|_| selected_layers.next().is_none()); - let Some(vector_data) = layer.and_then(|layer| document.network_interface.compute_modified_vector(layer)) else { - return; - }; - match self.tool_data.handle_type { - TargetHandle::None => {} - TargetHandle::HandleStart => { - self.tool_data.update_handle_custom(&vector_data); - self.tool_data.cleanup_target_selections(tool_data.shape_editor, layer, tool_data.document); - } - _ => { - self.tool_data.add_target_selections(tool_data.shape_editor, layer); - self.tool_data.update_handle_type(TargetHandle::HandleStart); - let offset = self.tool_data.opposite_target_handle_position(&vector_data).map(|opposite| opposite - self.tool_data.next_handle_start); - self.tool_data.offset_position = offset; - } - } - responses.add(OverlaysMessage::Draw); - responses.add(FrontendMessage::UpdateMouseCursor { cursor: MouseCursorIcon::None }); - return; - } - _ => { - self.fsm_state.process_event(message, &mut self.tool_data, tool_data, &self.options, responses, true); - return; - } - } + self.fsm_state.process_event(message, &mut self.tool_data, tool_data, &self.options, responses, true); + return; }; match action { @@ -311,6 +282,7 @@ enum HandleMode { ColinearEquidistant, } +/// The handle which is opposite to the currently dragged handle under the cursor #[derive(Clone, Debug, Default, PartialEq, Copy)] enum TargetHandle { HandleEnd, @@ -367,6 +339,7 @@ impl PenToolData { self.latest_points.push(point); } + /// Check whether target handle is primary,end or 'self.handle_end' fn check_end_handle_type(&self, vector_data: &VectorData) -> TargetHandle { match (self.handle_end, self.end_point, self.end_point_segment) { (Some(_), _, _) => TargetHandle::HandleEnd, @@ -454,6 +427,7 @@ impl PenToolData { } } + /// Selects the handle which is currently dragged by the user fn add_target_selections(&self, shape_editor: &mut ShapeState, layer: Option<LayerNodeIdentifier>) { let Some(shape_state) = layer.and_then(|layer| shape_editor.selected_shape_state.get_mut(&layer)) else { return; @@ -728,10 +702,14 @@ impl PenToolData { return Some(PenToolFsmState::DraggingHandle(self.handle_mode)); } if self.handle_type == TargetHandle::HandleStart { - let mouse_offset = mouse + viewport.transform_point2(self.offset_position.unwrap()); + let mouse_offset = mouse + self.offset_position.unwrap(); + log::info!("{:?}", mouse_offset); + log::info!("{:?}", self.handle_end.map(|handle| viewport.transform_point2(handle))); + let mouse_pos = self.compute_snapped_angle(snap_data.clone(), transform, colinear, mouse_offset, Some(self.next_point), false); let opposite_target = self.get_opposite_handle_type(&vector_data); self.update_target_handle_pos(opposite_target, responses, mouse_pos, layer); + responses.add(OverlaysMessage::Draw); } else { self.next_handle_start = self.compute_snapped_angle(snap_data.clone(), transform, colinear, mouse, Some(self.next_point), false); } @@ -1573,6 +1551,34 @@ impl Fsm for PenToolFsmState { state } + (PenToolFsmState::DraggingHandle(_), PenToolMessage::SwapHandles) => { + let Some(vector_data) = layer.and_then(|layer| document.network_interface.compute_modified_vector(layer)) else { + return self; + }; + match tool_data.handle_type { + TargetHandle::None => {} + TargetHandle::HandleStart => { + tool_data.offset_position = layer.map(|layer| document.metadata().transform_to_viewport(layer).transform_point2(tool_data.next_handle_start) - input.mouse.position); + + tool_data.update_handle_custom(&vector_data); + tool_data.cleanup_target_selections(shape_editor, layer, document); + } + _ => { + tool_data.add_target_selections(shape_editor, layer); + + let viewport = layer.map(|layer| document.metadata().transform_to_viewport(layer)); + let offset = tool_data + .target_handle_position(tool_data.handle_type, &vector_data) + .zip(viewport) + .map(|(opposite, transform)| transform.transform_point2(opposite - tool_data.next_handle_start)); + tool_data.offset_position = offset; + tool_data.update_handle_type(TargetHandle::HandleStart); + } + } + responses.add(OverlaysMessage::Draw); + responses.add(FrontendMessage::UpdateMouseCursor { cursor: MouseCursorIcon::None }); + self + } ( PenToolFsmState::Ready, PenToolMessage::PointerMove { From ca346bc11050c0c6cb6a179ec7a830ac4778f260 Mon Sep 17 00:00:00 2001 From: 0SlowPoke0 <govindpratyush@gmail.com> Date: Mon, 17 Mar 2025 19:45:12 +0530 Subject: [PATCH 09/21] got stuck in space move --- .../messages/tool/tool_messages/pen_tool.rs | 45 +++++++++++-------- 1 file changed, 27 insertions(+), 18 deletions(-) diff --git a/editor/src/messages/tool/tool_messages/pen_tool.rs b/editor/src/messages/tool/tool_messages/pen_tool.rs index 0427d5a56d..fd028ae4c6 100644 --- a/editor/src/messages/tool/tool_messages/pen_tool.rs +++ b/editor/src/messages/tool/tool_messages/pen_tool.rs @@ -320,8 +320,8 @@ struct PenToolData { end_point: Option<PointId>, end_point_segment: Option<SegmentId>, handle_type: TargetHandle, - offset_position: Option<DVec2>, - + handle_start_offset: Option<DVec2>, + handle_end_offset: Option<DVec2>, snap_cache: SnapCache, } impl PenToolData { @@ -502,7 +502,8 @@ impl PenToolData { let next_handle_start = self.next_handle_start; let handle_start = self.latest_point()?.handle_start; let mouse = snap_data.input.mouse.position; - self.offset_position = None; + self.handle_end_offset = None; + self.handle_start_offset = None; let Some(handle_end) = self.handle_end else { responses.add(DocumentMessage::EndTransaction); self.handle_end = Some(next_handle_start); @@ -582,25 +583,33 @@ impl PenToolData { vector_data: &VectorData, input: &InputPreprocessorMessageHandler, ) -> Option<DVec2> { - let handle_pos = self.target_handle_position(self.handle_type, vector_data); + let end_handle = self.check_end_handle_type(vector_data); + let end_handle_pos = self.target_handle_position(end_handle, vector_data); let snap = &mut self.snap_manager; let snap_data = SnapData::new_snap_cache(snap_data.document, input, &self.snap_cache); let document_pos = viewport_to_document.transform_point2(*mouse); - let offset = transform.transform_point2(self.next_point - self.next_handle_start); + let offset = transform.transform_point2(self.next_point) - transform.transform_point2(self.next_handle_start); + + let start_offset = self + .handle_start_offset + .map(|offset| viewport_to_document.transform_point2(offset)) + .filter(|vec| !vec.abs().cmple(DVec2::splat(10.0)).all()) + .unwrap_or(DVec2::ZERO); - let handle_start = SnapCandidatePoint::handle(document_pos); - let anchor = SnapCandidatePoint::handle(document_pos + offset); + let handle_start = SnapCandidatePoint::handle(document_pos + start_offset); + let anchor = SnapCandidatePoint::handle(document_pos + offset + start_offset); let snapped_near_handle_start = snap.free_snap(&snap_data, &handle_start, SnapTypeConfiguration::default()); let snapped_anchor = snap.free_snap(&snap_data, &anchor, SnapTypeConfiguration::default()); - let handle_snap_option = handle_pos.and_then(|handle| match self.handle_type { + let handle_snap_option = end_handle_pos.and_then(|handle| match end_handle { TargetHandle::None => None, + TargetHandle::HandleStart => None, _ => { let handle_offset = transform.transform_point2(handle - self.next_handle_start); - let handle_snap = SnapCandidatePoint::handle(document_pos + handle_offset); + let handle_snap = SnapCandidatePoint::handle(document_pos + handle_offset + start_offset); Some((handle, handle_snap)) } }); @@ -679,7 +688,6 @@ impl PenToolData { let Some(layer) = layer else { return Some(PenToolFsmState::DraggingHandle(self.handle_mode)) }; let vector_data = document.network_interface.compute_modified_vector(layer)?; let viewport_to_document = document.metadata().document_to_viewport.inverse(); - let viewport = document.metadata().transform_to_viewport(layer); if self.modifiers.move_anchor_with_handles { let Some(delta) = self.space_anchor_handle_snap(&viewport_to_document, &transform, &snap_data, &mouse, &vector_data, input) else { @@ -702,16 +710,14 @@ impl PenToolData { return Some(PenToolFsmState::DraggingHandle(self.handle_mode)); } if self.handle_type == TargetHandle::HandleStart { - let mouse_offset = mouse + self.offset_position.unwrap(); - log::info!("{:?}", mouse_offset); - log::info!("{:?}", self.handle_end.map(|handle| viewport.transform_point2(handle))); + let mouse_offset = mouse + self.handle_end_offset.unwrap_or(DVec2::ZERO); let mouse_pos = self.compute_snapped_angle(snap_data.clone(), transform, colinear, mouse_offset, Some(self.next_point), false); let opposite_target = self.get_opposite_handle_type(&vector_data); self.update_target_handle_pos(opposite_target, responses, mouse_pos, layer); - responses.add(OverlaysMessage::Draw); } else { - self.next_handle_start = self.compute_snapped_angle(snap_data.clone(), transform, colinear, mouse, Some(self.next_point), false); + let offset = self.handle_start_offset.unwrap_or(DVec2::ZERO); + self.next_handle_start = self.compute_snapped_angle(snap_data.clone(), transform, colinear, mouse + offset, Some(self.next_point), false); } match self.handle_mode { @@ -1558,7 +1564,8 @@ impl Fsm for PenToolFsmState { match tool_data.handle_type { TargetHandle::None => {} TargetHandle::HandleStart => { - tool_data.offset_position = layer.map(|layer| document.metadata().transform_to_viewport(layer).transform_point2(tool_data.next_handle_start) - input.mouse.position); + // will be moving the `next_handle_start` need to check the offset + tool_data.handle_start_offset = layer.map(|layer| document.metadata().transform_to_viewport(layer).transform_point2(tool_data.next_handle_start) - input.mouse.position); tool_data.update_handle_custom(&vector_data); tool_data.cleanup_target_selections(shape_editor, layer, document); @@ -1566,12 +1573,14 @@ impl Fsm for PenToolFsmState { _ => { tool_data.add_target_selections(shape_editor, layer); + // The handle which will be dragged will be the handle end ,need to offset the mouse position let viewport = layer.map(|layer| document.metadata().transform_to_viewport(layer)); let offset = tool_data .target_handle_position(tool_data.handle_type, &vector_data) .zip(viewport) - .map(|(opposite, transform)| transform.transform_point2(opposite - tool_data.next_handle_start)); - tool_data.offset_position = offset; + .map(|(opposite, transform)| transform.transform_point2(opposite) - input.mouse.position); + + tool_data.handle_end_offset = offset; tool_data.update_handle_type(TargetHandle::HandleStart); } } From 1b9ea0eb4177f3d09707af6bb28179f8bb86060c Mon Sep 17 00:00:00 2001 From: 0SlowPoke0 <govindpratyush@gmail.com> Date: Wed, 19 Mar 2025 22:55:43 +0530 Subject: [PATCH 10/21] fixed all issues --- .../messages/tool/tool_messages/pen_tool.rs | 63 ++++++++++++------- 1 file changed, 39 insertions(+), 24 deletions(-) diff --git a/editor/src/messages/tool/tool_messages/pen_tool.rs b/editor/src/messages/tool/tool_messages/pen_tool.rs index fd028ae4c6..f1142aaf71 100644 --- a/editor/src/messages/tool/tool_messages/pen_tool.rs +++ b/editor/src/messages/tool/tool_messages/pen_tool.rs @@ -314,6 +314,7 @@ struct PenToolData { previous_handle_start_pos: DVec2, previous_handle_end_pos: Option<DVec2>, alt_press: bool, + space_press: bool, handle_mode: HandleMode, /// The point that is being dragged @@ -411,7 +412,7 @@ impl PenToolData { } /// Remove the handles selected when swapping handles - fn cleanup_target_selections(&self, shape_editor: &mut ShapeState, layer: Option<LayerNodeIdentifier>, document: &DocumentMessageHandler) { + fn cleanup_target_selections(&self, shape_editor: &mut ShapeState, layer: Option<LayerNodeIdentifier>, document: &DocumentMessageHandler, responses: &mut VecDeque<Message>) { let Some(shape_state) = layer.and_then(|layer| shape_editor.selected_shape_state.get_mut(&layer)) else { return; }; @@ -425,6 +426,7 @@ impl PenToolData { TargetHandle::PrimaryHandle(segment) => shape_state.deselect_point(ManipulatorPointId::PrimaryHandle(segment)), _ => {} } + responses.add(OverlaysMessage::Draw); } /// Selects the handle which is currently dragged by the user @@ -588,18 +590,13 @@ impl PenToolData { let snap = &mut self.snap_manager; let snap_data = SnapData::new_snap_cache(snap_data.document, input, &self.snap_cache); - let document_pos = viewport_to_document.transform_point2(*mouse); - - let offset = transform.transform_point2(self.next_point) - transform.transform_point2(self.next_handle_start); + let handle_start_offset = self.handle_start_offset.unwrap_or(DVec2::ZERO); + let document_pos = viewport_to_document.transform_point2(*mouse + handle_start_offset); - let start_offset = self - .handle_start_offset - .map(|offset| viewport_to_document.transform_point2(offset)) - .filter(|vec| !vec.abs().cmple(DVec2::splat(10.0)).all()) - .unwrap_or(DVec2::ZERO); + let anchor_offset = transform.transform_point2(self.next_point - self.next_handle_start); - let handle_start = SnapCandidatePoint::handle(document_pos + start_offset); - let anchor = SnapCandidatePoint::handle(document_pos + offset + start_offset); + let handle_start = SnapCandidatePoint::handle(document_pos); + let anchor = SnapCandidatePoint::handle(document_pos + anchor_offset); let snapped_near_handle_start = snap.free_snap(&snap_data, &handle_start, SnapTypeConfiguration::default()); let snapped_anchor = snap.free_snap(&snap_data, &anchor, SnapTypeConfiguration::default()); @@ -609,7 +606,7 @@ impl PenToolData { TargetHandle::HandleStart => None, _ => { let handle_offset = transform.transform_point2(handle - self.next_handle_start); - let handle_snap = SnapCandidatePoint::handle(document_pos + handle_offset + start_offset); + let handle_snap = SnapCandidatePoint::handle(document_pos + handle_offset); Some((handle, handle_snap)) } }); @@ -670,8 +667,15 @@ impl PenToolData { modification_type: modification_type_anchor, }); - let Some(handle_pos) = self.target_handle_position(self.handle_type, vector_data) else { return }; - self.update_target_handle_pos(self.handle_type, responses, handle_pos + delta, layer); + // Move the end handle + let end_handle_type = self.check_end_handle_type(vector_data); + match end_handle_type { + TargetHandle::EndHandle(..) | TargetHandle::PrimaryHandle(..) => { + let Some(handle_pos) = self.target_handle_position(end_handle_type, vector_data) else { return }; + self.update_target_handle_pos(end_handle_type, responses, handle_pos + delta, layer); + } + _ => {} + } } fn drag_handle( @@ -689,6 +693,7 @@ impl PenToolData { let vector_data = document.network_interface.compute_modified_vector(layer)?; let viewport_to_document = document.metadata().document_to_viewport.inverse(); + // Handles pressing `space` to drag anchor and its handles if self.modifiers.move_anchor_with_handles { let Some(delta) = self.space_anchor_handle_snap(&viewport_to_document, &transform, &snap_data, &mouse, &vector_data, input) else { return Some(PenToolFsmState::DraggingHandle(self.handle_mode)); @@ -709,15 +714,15 @@ impl PenToolData { responses.add(OverlaysMessage::Draw); return Some(PenToolFsmState::DraggingHandle(self.handle_mode)); } - if self.handle_type == TargetHandle::HandleStart { - let mouse_offset = mouse + self.handle_end_offset.unwrap_or(DVec2::ZERO); + if self.handle_type != TargetHandle::HandleStart { + let offset = self.handle_start_offset.unwrap_or(DVec2::ZERO); + self.next_handle_start = self.compute_snapped_angle(snap_data.clone(), transform, colinear, mouse + offset, Some(self.next_point), false); + } else { + let mouse_offset = mouse + self.handle_end_offset.unwrap_or(DVec2::ZERO); let mouse_pos = self.compute_snapped_angle(snap_data.clone(), transform, colinear, mouse_offset, Some(self.next_point), false); let opposite_target = self.get_opposite_handle_type(&vector_data); self.update_target_handle_pos(opposite_target, responses, mouse_pos, layer); - } else { - let offset = self.handle_start_offset.unwrap_or(DVec2::ZERO); - self.next_handle_start = self.compute_snapped_angle(snap_data.clone(), transform, colinear, mouse + offset, Some(self.next_point), false); } match self.handle_mode { @@ -1450,7 +1455,7 @@ impl Fsm for PenToolFsmState { (PenToolFsmState::DraggingHandle(_), PenToolMessage::DragStop) => { tool_data.end_point = None; - tool_data.cleanup_target_selections(shape_editor, layer, document); + tool_data.cleanup_target_selections(shape_editor, layer, document, responses); tool_data .finish_placing_handle(SnapData::new(document, input), transform, preferences, responses) .unwrap_or(PenToolFsmState::PlacingAnchor) @@ -1481,6 +1486,16 @@ impl Fsm for PenToolFsmState { tool_data.toggle_colinear_debounce = true; } + if tool_data.modifiers.move_anchor_with_handles && !tool_data.space_press { + let handle_start = layer.map(|layer| document.metadata().transform_to_viewport(layer).transform_point2(tool_data.next_handle_start)); + tool_data.handle_start_offset = handle_start.map(|start| start - input.mouse.position); + tool_data.space_press = true; + } + + if !tool_data.modifiers.move_anchor_with_handles { + tool_data.space_press = false; + } + if !tool_data.modifiers.colinear { tool_data.toggle_colinear_debounce = false; } @@ -1568,7 +1583,7 @@ impl Fsm for PenToolFsmState { tool_data.handle_start_offset = layer.map(|layer| document.metadata().transform_to_viewport(layer).transform_point2(tool_data.next_handle_start) - input.mouse.position); tool_data.update_handle_custom(&vector_data); - tool_data.cleanup_target_selections(shape_editor, layer, document); + tool_data.cleanup_target_selections(shape_editor, layer, document, responses); } _ => { tool_data.add_target_selections(shape_editor, layer); @@ -1579,7 +1594,7 @@ impl Fsm for PenToolFsmState { .target_handle_position(tool_data.handle_type, &vector_data) .zip(viewport) .map(|(opposite, transform)| transform.transform_point2(opposite) - input.mouse.position); - + tool_data.handle_start_offset = layer.map(|layer| document.metadata().transform_to_viewport(layer).transform_point2(tool_data.next_handle_start) - input.mouse.position); tool_data.handle_end_offset = offset; tool_data.update_handle_type(TargetHandle::HandleStart); } @@ -1660,7 +1675,7 @@ impl Fsm for PenToolFsmState { tool_data.latest_points.clear(); tool_data.point_index = 0; tool_data.snap_manager.cleanup(responses); - tool_data.cleanup_target_selections(shape_editor, layer, document); + tool_data.cleanup_target_selections(shape_editor, layer, document, responses); PenToolFsmState::Ready } @@ -1670,7 +1685,7 @@ impl Fsm for PenToolFsmState { tool_data.latest_points.clear(); tool_data.point_index = 0; tool_data.snap_manager.cleanup(responses); - tool_data.cleanup_target_selections(shape_editor, layer, document); + tool_data.cleanup_target_selections(shape_editor, layer, document, responses); responses.add(OverlaysMessage::Draw); From 9493749f96af16ad0f76e176fd4ce35cd755eeaa Mon Sep 17 00:00:00 2001 From: 0SlowPoke0 <govindpratyush@gmail.com> Date: Thu, 20 Mar 2025 08:45:38 +0530 Subject: [PATCH 11/21] comments and small fixes --- editor/src/messages/tool/tool_messages/pen_tool.rs | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/editor/src/messages/tool/tool_messages/pen_tool.rs b/editor/src/messages/tool/tool_messages/pen_tool.rs index f67272dcd5..c6df6ca097 100644 --- a/editor/src/messages/tool/tool_messages/pen_tool.rs +++ b/editor/src/messages/tool/tool_messages/pen_tool.rs @@ -363,7 +363,6 @@ impl PenToolData { fn get_opposite_handle_type(&self, vector_data: &VectorData) -> TargetHandle { match self.handle_type { TargetHandle::HandleStart => self.check_end_handle_type(vector_data), - TargetHandle::None => TargetHandle::None, _ => TargetHandle::HandleStart, } } @@ -1453,9 +1452,8 @@ impl Fsm for PenToolFsmState { self } (PenToolFsmState::DraggingHandle(_), PenToolMessage::DragStop) => { - tool_data.end_point = None; - tool_data.cleanup_target_selections(shape_editor, layer, document, responses); + tool_data.end_point = None; tool_data .finish_placing_handle(SnapData::new(document, input), transform, preferences, responses) .unwrap_or(PenToolFsmState::PlacingAnchor) @@ -1579,7 +1577,7 @@ impl Fsm for PenToolFsmState { match tool_data.handle_type { TargetHandle::None => {} TargetHandle::HandleStart => { - // will be moving the `next_handle_start` need to check the offset + // The hanlde which will be dragged will be the `next_handle_start` need to check the offset tool_data.handle_start_offset = layer.map(|layer| document.metadata().transform_to_viewport(layer).transform_point2(tool_data.next_handle_start) - input.mouse.position); tool_data.update_handle_custom(&vector_data); @@ -1625,7 +1623,7 @@ impl Fsm for PenToolFsmState { self } (PenToolFsmState::DraggingHandle(mode), PenToolMessage::PointerOutsideViewport { .. }) => { - // Auto-panning + // Auto-panningF let _ = tool_data.auto_panning.shift_viewport(input, responses); PenToolFsmState::DraggingHandle(mode) From 43f9a7c3d2d0a07341c8017a45e96c7d48cd814e Mon Sep 17 00:00:00 2001 From: 0SlowPoke0 <govindpratyush@gmail.com> Date: Fri, 21 Mar 2025 23:18:30 +0530 Subject: [PATCH 12/21] completed last issue and refactor --- .../messages/tool/tool_messages/pen_tool.rs | 201 +++++++++++------- 1 file changed, 130 insertions(+), 71 deletions(-) diff --git a/editor/src/messages/tool/tool_messages/pen_tool.rs b/editor/src/messages/tool/tool_messages/pen_tool.rs index c6df6ca097..73f0dd0fd8 100644 --- a/editor/src/messages/tool/tool_messages/pen_tool.rs +++ b/editor/src/messages/tool/tool_messages/pen_tool.rs @@ -282,7 +282,7 @@ enum HandleMode { ColinearEquidistant, } -/// The handle which is opposite to the currently dragged handle under the cursor +/// The type of handle which id dragged handle by the cursor (under the cursor) #[derive(Clone, Debug, Default, PartialEq, Copy)] enum TargetHandle { HandleEnd, @@ -315,14 +315,15 @@ struct PenToolData { previous_handle_end_pos: Option<DVec2>, alt_press: bool, space_press: bool, + close_path: bool, handle_mode: HandleMode, - /// The point that is being dragged end_point: Option<PointId>, end_point_segment: Option<SegmentId>, handle_type: TargetHandle, handle_start_offset: Option<DVec2>, handle_end_offset: Option<DVec2>, + snap_cache: SnapCache, } impl PenToolData { @@ -355,15 +356,32 @@ impl PenToolData { } } - fn update_handle_custom(&mut self, vector_data: &VectorData) { - let target_handle = self.check_end_handle_type(vector_data); - self.update_handle_type(target_handle); - } - - fn get_opposite_handle_type(&self, vector_data: &VectorData) -> TargetHandle { - match self.handle_type { + fn get_opposite_handle_type(&self, handle_type: TargetHandle, vector_data: &VectorData) -> TargetHandle { + match handle_type { TargetHandle::HandleStart => self.check_end_handle_type(vector_data), - _ => TargetHandle::HandleStart, + TargetHandle::HandleEnd => { + if self.close_path { + match (self.end_point, self.end_point_segment) { + (Some(point), Some(segment)) => { + if vector_data.segment_start_from_id(segment) == Some(point) { + TargetHandle::PrimaryHandle(segment) + } else { + TargetHandle::EndHandle(segment) + } + } + _ => TargetHandle::None, + } + } else { + TargetHandle::HandleStart + } + } + _ => { + if self.close_path { + TargetHandle::HandleEnd + } else { + TargetHandle::HandleStart + } + } } } @@ -404,12 +422,6 @@ impl PenToolData { TargetHandle::None => None, } } - - fn opposite_target_handle_position(&self, vector_data: &VectorData) -> Option<DVec2> { - let opposite_handle_type = self.get_opposite_handle_type(vector_data); - self.target_handle_position(opposite_handle_type, vector_data) - } - /// Remove the handles selected when swapping handles fn cleanup_target_selections(&self, shape_editor: &mut ShapeState, layer: Option<LayerNodeIdentifier>, document: &DocumentMessageHandler, responses: &mut VecDeque<Message>) { let Some(shape_state) = layer.and_then(|layer| shape_editor.selected_shape_state.get_mut(&layer)) else { @@ -470,20 +482,17 @@ impl PenToolData { let document = snap_data.document; self.next_handle_start = self.next_point; let vector_data = document.network_interface.compute_modified_vector(layer).unwrap(); + self.update_handle_type(TargetHandle::HandleStart); // Break the control let Some((last_pos, id)) = self.latest_point().map(|point| (point.pos, point.id)) else { return }; let transform = document.metadata().document_to_viewport * transform; let on_top = transform.transform_point2(self.next_point).distance_squared(transform.transform_point2(last_pos)) < crate::consts::SNAP_POINT_TOLERANCE.powi(2); - self.update_handle_type(TargetHandle::HandleEnd); if on_top { self.handle_end = None; self.handle_mode = HandleMode::Free; - - // Update `end_point_segment` that was clicked on self.store_clicked_endpoint(document, &transform, snap_data.input, preferences); - if self.modifiers.lock_angle { self.set_lock_angle(&vector_data, id, self.end_point_segment); let last_segment = self.end_point_segment; @@ -496,6 +505,20 @@ impl PenToolData { point.in_segment = None; } } + + for id in vector_data.extendable_points(preferences.vector_meshes).filter(|&point| point != id) { + let Some(pos) = vector_data.point_domain.position_from_id(id) else { continue }; + let transformed_distance_between_squared = transform.transform_point2(pos).distance_squared(transform.transform_point2(self.next_point)); + let snap_point_tolerance_squared = crate::consts::SNAP_POINT_TOLERANCE.powi(2); + if transformed_distance_between_squared < snap_point_tolerance_squared { + self.update_handle_type(TargetHandle::HandleEnd); + self.handle_end_offset = None; + self.close_path = true; + self.next_handle_start = self.next_point; + self.store_clicked_endpoint(document, &transform, snap_data.input, preferences); + self.handle_mode = HandleMode::Free; + } + } } fn finish_placing_handle(&mut self, snap_data: SnapData, transform: DAffine2, preferences: &PreferencesMessageHandler, responses: &mut VecDeque<Message>) -> Option<PenToolFsmState> { @@ -569,6 +592,7 @@ impl PenToolData { handle_start: next_handle_start, }); } + self.close_path = false; responses.add(DocumentMessage::EndTransaction); Some(if close_subpath { PenToolFsmState::Ready } else { PenToolFsmState::PlacingAnchor }) } @@ -584,15 +608,17 @@ impl PenToolData { vector_data: &VectorData, input: &InputPreprocessorMessageHandler, ) -> Option<DVec2> { - let end_handle = self.check_end_handle_type(vector_data); + let reference_handle = if self.close_path { TargetHandle::HandleEnd } else { TargetHandle::HandleStart }; + let end_handle = self.get_opposite_handle_type(reference_handle, vector_data); let end_handle_pos = self.target_handle_position(end_handle, vector_data); + let ref_pos = self.target_handle_position(reference_handle, vector_data).unwrap(); let snap = &mut self.snap_manager; let snap_data = SnapData::new_snap_cache(snap_data.document, input, &self.snap_cache); let handle_start_offset = self.handle_start_offset.unwrap_or(DVec2::ZERO); let document_pos = viewport_to_document.transform_point2(*mouse + handle_start_offset); - let anchor_offset = transform.transform_point2(self.next_point - self.next_handle_start); + let anchor_offset = transform.transform_point2(self.next_point - ref_pos); let handle_start = SnapCandidatePoint::handle(document_pos); let anchor = SnapCandidatePoint::handle(document_pos + anchor_offset); @@ -604,7 +630,7 @@ impl PenToolData { TargetHandle::None => None, TargetHandle::HandleStart => None, _ => { - let handle_offset = transform.transform_point2(handle - self.next_handle_start); + let handle_offset = transform.transform_point2(handle - ref_pos); let handle_snap = SnapCandidatePoint::handle(document_pos + handle_offset); Some((handle, handle_snap)) } @@ -615,7 +641,7 @@ impl PenToolData { delta = snapped_anchor.snapped_point_document - transform.transform_point2(self.next_point); snapped_anchor } else { - delta = snapped_near_handle_start.snapped_point_document - transform.transform_point2(self.next_handle_start); + delta = snapped_near_handle_start.snapped_point_document - transform.transform_point2(ref_pos); snapped_near_handle_start }; @@ -655,8 +681,10 @@ impl PenToolData { } fn move_anchor_and_handles(&mut self, delta: DVec2, layer: LayerNodeIdentifier, responses: &mut VecDeque<Message>, vector_data: &VectorData) { - if let Some(latest_pt) = self.latest_point_mut() { - latest_pt.pos += delta; + if self.handle_end.is_none() { + if let Some(latest_pt) = self.latest_point_mut() { + latest_pt.pos += delta; + } } let Some(end_point) = self.end_point else { return }; @@ -666,8 +694,10 @@ impl PenToolData { modification_type: modification_type_anchor, }); + let reference_handle = if self.close_path { TargetHandle::HandleEnd } else { TargetHandle::HandleStart }; + // Move the end handle - let end_handle_type = self.check_end_handle_type(vector_data); + let end_handle_type = self.get_opposite_handle_type(reference_handle, vector_data); match end_handle_type { TargetHandle::EndHandle(..) | TargetHandle::PrimaryHandle(..) => { let Some(handle_pos) = self.target_handle_position(end_handle_type, vector_data) else { return }; @@ -707,21 +737,23 @@ impl PenToolData { if let Some(handle) = self.handle_end.as_mut() { *handle += delta; - } else { - self.move_anchor_and_handles(delta, layer, responses, &vector_data); } + self.move_anchor_and_handles(delta, layer, responses, &vector_data); + responses.add(OverlaysMessage::Draw); return Some(PenToolFsmState::DraggingHandle(self.handle_mode)); } - if self.handle_type != TargetHandle::HandleStart { - let offset = self.handle_start_offset.unwrap_or(DVec2::ZERO); - self.next_handle_start = self.compute_snapped_angle(snap_data.clone(), transform, colinear, mouse + offset, Some(self.next_point), false); - } else { - let mouse_offset = mouse + self.handle_end_offset.unwrap_or(DVec2::ZERO); - let mouse_pos = self.compute_snapped_angle(snap_data.clone(), transform, colinear, mouse_offset, Some(self.next_point), false); - let opposite_target = self.get_opposite_handle_type(&vector_data); - self.update_target_handle_pos(opposite_target, responses, mouse_pos, layer); + match self.handle_type { + TargetHandle::HandleStart => { + let offset = self.handle_start_offset.unwrap_or(DVec2::ZERO); + self.next_handle_start = self.compute_snapped_angle(snap_data.clone(), transform, colinear, mouse + offset, Some(self.next_point), false); + } + _ => { + let mouse_offset = mouse + self.handle_end_offset.unwrap_or(DVec2::ZERO); + let mouse_pos = self.compute_snapped_angle(snap_data.clone(), transform, colinear, mouse_offset, Some(self.next_point), false); + self.update_target_handle_pos(self.handle_type, responses, mouse_pos, layer); + } } match self.handle_mode { @@ -742,24 +774,25 @@ impl PenToolData { /// Makes the opposite handle equidistant or locks its length. fn adjust_handle_length(&mut self, responses: &mut VecDeque<Message>, layer: LayerNodeIdentifier, vector_data: &VectorData) { + let opposite_handle_type = self.get_opposite_handle_type(self.handle_type, vector_data); match self.handle_mode { HandleMode::ColinearEquidistant => { if self.modifiers.break_handle { // Store handle for later restoration only when Alt is first pressed if !self.alt_press { - self.previous_handle_end_pos = self.target_handle_position(self.handle_type, vector_data); + self.previous_handle_end_pos = self.target_handle_position(opposite_handle_type, vector_data); self.alt_press = true; } // Set handle to opposite position of the other handle - let Some(new_position) = self.opposite_target_handle_position(vector_data).map(|handle| self.next_point * 2. - handle) else { + let Some(new_position) = self.target_handle_position(self.handle_type, vector_data).map(|handle| self.next_point * 2. - handle) else { return; }; - self.update_target_handle_pos(self.handle_type, responses, new_position, layer); + self.update_target_handle_pos(opposite_handle_type, responses, new_position, layer); } else if self.alt_press { // Restore the previous handle position when Alt is released if let Some(previous_handle) = self.previous_handle_end_pos { - self.update_target_handle_pos(self.handle_type, responses, previous_handle, layer); + self.update_target_handle_pos(opposite_handle_type, responses, previous_handle, layer); } self.alt_press = false; self.previous_handle_end_pos = None; @@ -767,10 +800,10 @@ impl PenToolData { } HandleMode::ColinearLocked => { if !self.modifiers.break_handle { - let Some(new_position) = self.opposite_target_handle_position(vector_data).map(|handle| self.next_point * 2. - handle) else { + let Some(new_position) = self.target_handle_position(self.handle_type, vector_data).map(|handle| self.next_point * 2. - handle) else { return; }; - self.update_target_handle_pos(self.handle_type, responses, new_position, layer); + self.update_target_handle_pos(opposite_handle_type, responses, new_position, layer); } } HandleMode::Free => {} @@ -778,7 +811,7 @@ impl PenToolData { } fn apply_colinear_constraint(&mut self, responses: &mut VecDeque<Message>, layer: LayerNodeIdentifier, anchor_pos: DVec2, vector_data: &VectorData) { - let Some(handle) = self.opposite_target_handle_position(vector_data) else { + let Some(handle) = self.target_handle_position(self.handle_type, vector_data) else { return; }; @@ -787,12 +820,13 @@ impl PenToolData { return; }; - let Some(handle_offset) = self.target_handle_position(self.handle_type, vector_data).map(|handle| (handle - anchor_pos).length()) else { + let opposite_handle = self.get_opposite_handle_type(self.handle_type, vector_data); + let Some(handle_offset) = self.target_handle_position(opposite_handle, vector_data).map(|handle| (handle - anchor_pos).length()) else { return; }; let new_handle_position = anchor_pos + handle_offset * direction; - self.update_target_handle_pos(self.handle_type, responses, new_handle_position, layer); + self.update_target_handle_pos(opposite_handle, responses, new_handle_position, layer); } fn place_anchor(&mut self, snap_data: SnapData, transform: DAffine2, mouse: DVec2, preferences: &PreferencesMessageHandler, responses: &mut VecDeque<Message>) -> Option<PenToolFsmState> { @@ -904,6 +938,7 @@ impl PenToolData { 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 viewport = document.metadata().document_to_viewport.transform_point2(snapped.snapped_point_document); + self.handle_type = TargetHandle::HandleStart; let selected_nodes = document.network_interface.selected_nodes(); self.handle_end = None; @@ -953,7 +988,6 @@ impl PenToolData { tool_options.fill.apply_fill(layer, responses); tool_options.stroke.apply_stroke(tool_options.line_weight, layer, responses); self.end_point_segment = None; - self.handle_type = TargetHandle::HandleEnd; responses.add(NodeGraphMessage::SelectedNodesSet { nodes: vec![layer.to_node()] }); // This causes the following message to be run only after the next graph evaluation runs and the transforms are updated @@ -1038,7 +1072,6 @@ impl PenToolData { let vector_data = document.network_interface.compute_modified_vector(layer).unwrap(); let segment = vector_data.all_connected(point).collect::<Vec<_>>().first().map(|s| s.segment); self.end_point_segment = segment; - self.update_handle_custom(&vector_data); layer_manipulators.insert(point); for (&id, &position) in vector_data.point_domain.ids().iter().zip(vector_data.point_domain.positions()) { if id == point { @@ -1340,7 +1373,7 @@ impl Fsm for PenToolFsmState { if self == PenToolFsmState::DraggingHandle(tool_data.handle_mode) && valid(next_anchor, handle_end) { // Draw the handle circle for the currently-being-dragged-out incoming handle (opposite the one currently being dragged out) - let selected = tool_data.handle_type == TargetHandle::HandleStart; + let selected = tool_data.handle_type == TargetHandle::HandleEnd; overlay_context.manipulator_handle(handle_end, selected, None); } @@ -1362,7 +1395,7 @@ impl Fsm for PenToolFsmState { if self == PenToolFsmState::DraggingHandle(tool_data.handle_mode) && valid(next_anchor, next_handle_start) { // Draw the handle circle for the currently-being-dragged-out outgoing handle (the one currently being dragged out, under the user's cursor) - let selected = tool_data.handle_type != TargetHandle::HandleStart; + let selected = tool_data.handle_type == TargetHandle::HandleStart; overlay_context.manipulator_handle(next_handle_start, selected, None); } @@ -1475,6 +1508,7 @@ impl Fsm for PenToolFsmState { colinear: input.keyboard.key(colinear), move_anchor_with_handles: input.keyboard.key(move_anchor_with_handles), }; + let snap_data = SnapData::new(document, input); if tool_data.modifiers.colinear && !tool_data.toggle_colinear_debounce { tool_data.handle_mode = match tool_data.handle_mode { @@ -1484,8 +1518,18 @@ impl Fsm for PenToolFsmState { tool_data.toggle_colinear_debounce = true; } + let Some(vector_data) = layer.and_then(|layer| document.network_interface.compute_modified_vector(layer)) else { + return self; + }; + if tool_data.modifiers.move_anchor_with_handles && !tool_data.space_press { - let handle_start = layer.map(|layer| document.metadata().transform_to_viewport(layer).transform_point2(tool_data.next_handle_start)); + let reference_handle = if tool_data.close_path { TargetHandle::HandleEnd } else { TargetHandle::HandleStart }; + let handle_start = layer.map(|layer| { + document + .metadata() + .transform_to_viewport(layer) + .transform_point2(tool_data.target_handle_position(reference_handle, &vector_data).unwrap()) + }); tool_data.handle_start_offset = handle_start.map(|start| start - input.mouse.position); tool_data.space_press = true; } @@ -1574,29 +1618,44 @@ impl Fsm for PenToolFsmState { let Some(vector_data) = layer.and_then(|layer| document.network_interface.compute_modified_vector(layer)) else { return self; }; - match tool_data.handle_type { - TargetHandle::None => {} - TargetHandle::HandleStart => { - // The hanlde which will be dragged will be the `next_handle_start` need to check the offset - tool_data.handle_start_offset = layer.map(|layer| document.metadata().transform_to_viewport(layer).transform_point2(tool_data.next_handle_start) - input.mouse.position); - - tool_data.update_handle_custom(&vector_data); - tool_data.cleanup_target_selections(shape_editor, layer, document, responses); + + let Some(viewport) = layer.map(|layer| document.metadata().transform_to_viewport(layer)) else { + return self; + }; + + if tool_data.close_path { + match tool_data.handle_type { + TargetHandle::HandleEnd | TargetHandle::PrimaryHandle(..) | TargetHandle::EndHandle(..) => { + let opposite_type = tool_data.get_opposite_handle_type(tool_data.handle_type, &vector_data); + tool_data.handle_end_offset = tool_data + .target_handle_position(opposite_type, &vector_data) + .map(|handle| viewport.transform_point2(handle) - input.mouse.position); + tool_data.cleanup_target_selections(shape_editor, layer, document, responses); + tool_data.update_handle_type(opposite_type); + tool_data.add_target_selections(shape_editor, layer); + } + _ => {} } - _ => { - tool_data.add_target_selections(shape_editor, layer); - - // The handle which will be dragged will be the handle end ,need to offset the mouse position - let viewport = layer.map(|layer| document.metadata().transform_to_viewport(layer)); - let offset = tool_data - .target_handle_position(tool_data.handle_type, &vector_data) - .zip(viewport) - .map(|(opposite, transform)| transform.transform_point2(opposite) - input.mouse.position); - tool_data.handle_start_offset = layer.map(|layer| document.metadata().transform_to_viewport(layer).transform_point2(tool_data.next_handle_start) - input.mouse.position); - tool_data.handle_end_offset = offset; - tool_data.update_handle_type(TargetHandle::HandleStart); + } else { + // Store the offset and swap the handle_types + match tool_data.handle_type { + TargetHandle::None => {} + TargetHandle::HandleStart => { + let opposite_type = tool_data.get_opposite_handle_type(tool_data.handle_type, &vector_data); + tool_data.handle_end_offset = tool_data + .target_handle_position(opposite_type, &vector_data) + .map(|handle| viewport.transform_point2(handle) - input.mouse.position); + tool_data.update_handle_type(opposite_type); + tool_data.add_target_selections(shape_editor, layer); + } + _ => { + tool_data.cleanup_target_selections(shape_editor, layer, document, responses); + tool_data.handle_start_offset = layer.map(|layer| document.metadata().transform_to_viewport(layer).transform_point2(tool_data.next_handle_start) - input.mouse.position); + tool_data.update_handle_type(TargetHandle::HandleStart); + } } } + responses.add(OverlaysMessage::Draw); responses.add(FrontendMessage::UpdateMouseCursor { cursor: MouseCursorIcon::None }); self From 3a7369087bfa88aed4b0c4f796da27a4b107f091 Mon Sep 17 00:00:00 2001 From: 0SlowPoke0 <govindpratyush@gmail.com> Date: Thu, 27 Mar 2025 21:30:17 +0530 Subject: [PATCH 13/21] major fixes and improv --- .../document/overlays/utility_functions.rs | 1 - .../messages/tool/tool_messages/pen_tool.rs | 225 ++++++++++-------- 2 files changed, 131 insertions(+), 95 deletions(-) diff --git a/editor/src/messages/portfolio/document/overlays/utility_functions.rs b/editor/src/messages/portfolio/document/overlays/utility_functions.rs index a6a77b1726..2c585d2afe 100644 --- a/editor/src/messages/portfolio/document/overlays/utility_functions.rs +++ b/editor/src/messages/portfolio/document/overlays/utility_functions.rs @@ -121,7 +121,6 @@ pub fn path_overlays(document: &DocumentMessageHandler, draw_handles: DrawHandle let is_selected = |point: ManipulatorPointId| selected.is_some_and(|selected| selected.is_selected(point)); let opposite_handles_data: Vec<(PointId, SegmentId)> = shape_editor.selected_points().filter_map(|point_id| vector_data.adjacent_segment(point_id)).collect(); - match draw_handles { DrawHandles::All => { vector_data.segment_bezier_iter().for_each(|(segment_id, bezier, _start, _end)| { diff --git a/editor/src/messages/tool/tool_messages/pen_tool.rs b/editor/src/messages/tool/tool_messages/pen_tool.rs index 73f0dd0fd8..2a006d2bf3 100644 --- a/editor/src/messages/tool/tool_messages/pen_tool.rs +++ b/editor/src/messages/tool/tool_messages/pen_tool.rs @@ -282,7 +282,7 @@ enum HandleMode { ColinearEquidistant, } -/// The type of handle which id dragged handle by the cursor (under the cursor) +/// The type of handle which is dragged by the cursor (under the cursor) #[derive(Clone, Debug, Default, PartialEq, Copy)] enum TargetHandle { HandleEnd, @@ -356,6 +356,17 @@ impl PenToolData { } } + fn check_grs_end_handle(&self, vector_data: &VectorData) -> TargetHandle { + let Some(point) = self.latest_point().map(|point| point.id) else { return TargetHandle::None }; + let Some(segment) = self.end_point_segment else { return TargetHandle::None }; + + if vector_data.segment_start_from_id(segment) == Some(point) { + TargetHandle::PrimaryHandle(segment) + } else { + TargetHandle::EndHandle(segment) + } + } + fn get_opposite_handle_type(&self, handle_type: TargetHandle, vector_data: &VectorData) -> TargetHandle { match handle_type { TargetHandle::HandleStart => self.check_end_handle_type(vector_data), @@ -389,7 +400,7 @@ impl PenToolData { self.handle_type = handle_type; } - fn update_target_handle_pos(&mut self, handle_type: TargetHandle, responses: &mut VecDeque<Message>, delta: DVec2, layer: LayerNodeIdentifier) { + fn update_target_handle_pos(&mut self, handle_type: TargetHandle, anchor_pos: DVec2, responses: &mut VecDeque<Message>, delta: DVec2, layer: LayerNodeIdentifier) { match handle_type { TargetHandle::HandleStart => { self.next_handle_start = delta; @@ -400,12 +411,12 @@ impl PenToolData { } } TargetHandle::EndHandle(segment) => { - let relative_position = delta - self.next_point; + let relative_position = delta - anchor_pos; let modification_type = VectorModificationType::SetEndHandle { segment, relative_position }; responses.add(GraphOperationMessage::Vector { layer, modification_type }); } TargetHandle::PrimaryHandle(segment) => { - let relative_position = delta - self.next_point; + let relative_position = delta - anchor_pos; let modification_type = VectorModificationType::SetPrimaryHandle { segment, relative_position }; responses.add(GraphOperationMessage::Vector { layer, modification_type }); } @@ -517,6 +528,9 @@ impl PenToolData { self.next_handle_start = self.next_point; self.store_clicked_endpoint(document, &transform, snap_data.input, preferences); self.handle_mode = HandleMode::Free; + if self.modifiers.lock_angle { + self.set_lock_angle(&vector_data, self.end_point.unwrap(), self.end_point_segment); + } } } } @@ -568,6 +582,19 @@ impl PenToolData { // Store the segment let id = SegmentId::generate(); + if self.close_path && self.handle_mode != HandleMode::Free { + if let Some(handles) = match self.get_opposite_handle_type(TargetHandle::HandleEnd, &vector_data) { + TargetHandle::PrimaryHandle(segment) => Some([HandleId::end(id), HandleId::primary(segment)]), + TargetHandle::EndHandle(segment) => Some([HandleId::end(id), HandleId::end(segment)]), + _ => None, + } { + responses.add(GraphOperationMessage::Vector { + layer, + modification_type: VectorModificationType::SetG1Continuous { handles, enabled: true }, + }); + } + } + self.end_point_segment = Some(id); let points = [start, end]; @@ -575,13 +602,16 @@ impl PenToolData { responses.add(GraphOperationMessage::Vector { layer, modification_type }); // Mirror - if let Some(last_segment) = self.latest_point().and_then(|point| point.in_segment) { + if let Some((last_segment, last_point)) = self.latest_point().and_then(|point| point.in_segment).zip(self.latest_point()) { + let end = vector_data.segment_end_from_id(last_segment) == Some(last_point.id); + let handles = if end { + [HandleId::end(last_segment), HandleId::primary(id)] + } else { + [HandleId::primary(last_segment), HandleId::primary(id)] + }; responses.add(GraphOperationMessage::Vector { layer, - modification_type: VectorModificationType::SetG1Continuous { - handles: [HandleId::end(last_segment), HandleId::primary(id)], - enabled: true, - }, + modification_type: VectorModificationType::SetG1Continuous { handles, enabled: true }, }); } if !close_subpath { @@ -593,6 +623,7 @@ impl PenToolData { }); } self.close_path = false; + self.end_point = None; responses.add(DocumentMessage::EndTransaction); Some(if close_subpath { PenToolFsmState::Ready } else { PenToolFsmState::PlacingAnchor }) } @@ -663,6 +694,57 @@ impl PenToolData { Some(transform.inverse().transform_vector2(delta)) } + // Calculates the offset from the mouse when swapping handles + fn update_offset( + &mut self, + layer: Option<LayerNodeIdentifier>, + document: &DocumentMessageHandler, + shape_editor: &mut ShapeState, + input: &InputPreprocessorMessageHandler, + responses: &mut VecDeque<Message>, + ) { + let Some(vector_data) = layer.and_then(|layer| document.network_interface.compute_modified_vector(layer)) else { + return; + }; + + let Some(viewport) = layer.map(|layer| document.metadata().transform_to_viewport(layer)) else { + return; + }; + + if self.close_path { + match self.handle_type { + TargetHandle::HandleEnd | TargetHandle::PrimaryHandle(..) | TargetHandle::EndHandle(..) => { + let opposite_type = self.get_opposite_handle_type(self.handle_type, &vector_data); + self.handle_end_offset = self + .target_handle_position(opposite_type, &vector_data) + .map(|handle| viewport.transform_point2(handle) - input.mouse.position); + self.cleanup_target_selections(shape_editor, layer, document, responses); + self.update_handle_type(opposite_type); + self.add_target_selections(shape_editor, layer); + } + _ => {} + } + } else { + // Store the offset and swap the handle_types + match self.handle_type { + TargetHandle::None => {} + TargetHandle::HandleStart => { + let opposite_type = self.get_opposite_handle_type(self.handle_type, &vector_data); + self.handle_end_offset = self + .target_handle_position(opposite_type, &vector_data) + .map(|handle| viewport.transform_point2(handle) - input.mouse.position); + self.update_handle_type(opposite_type); + self.add_target_selections(shape_editor, layer); + } + _ => { + self.cleanup_target_selections(shape_editor, layer, document, responses); + self.handle_start_offset = layer.map(|layer| document.metadata().transform_to_viewport(layer).transform_point2(self.next_handle_start) - input.mouse.position); + self.update_handle_type(TargetHandle::HandleStart); + } + } + } + } + /// Handles moving the initially created point fn handle_single_point_path_drag(&mut self, delta: DVec2, layer: LayerNodeIdentifier, responses: &mut VecDeque<Message>) -> Option<PenToolFsmState> { self.next_handle_start += delta; @@ -701,7 +783,7 @@ impl PenToolData { match end_handle_type { TargetHandle::EndHandle(..) | TargetHandle::PrimaryHandle(..) => { let Some(handle_pos) = self.target_handle_position(end_handle_type, vector_data) else { return }; - self.update_target_handle_pos(end_handle_type, responses, handle_pos + delta, layer); + self.update_target_handle_pos(end_handle_type, self.next_point, responses, handle_pos + delta, layer); } _ => {} } @@ -721,7 +803,6 @@ impl PenToolData { let Some(layer) = layer else { return Some(PenToolFsmState::DraggingHandle(self.handle_mode)) }; let vector_data = document.network_interface.compute_modified_vector(layer)?; let viewport_to_document = document.metadata().document_to_viewport.inverse(); - // Handles pressing `space` to drag anchor and its handles if self.modifiers.move_anchor_with_handles { let Some(delta) = self.space_anchor_handle_snap(&viewport_to_document, &transform, &snap_data, &mouse, &vector_data, input) else { @@ -752,7 +833,7 @@ impl PenToolData { _ => { let mouse_offset = mouse + self.handle_end_offset.unwrap_or(DVec2::ZERO); let mouse_pos = self.compute_snapped_angle(snap_data.clone(), transform, colinear, mouse_offset, Some(self.next_point), false); - self.update_target_handle_pos(self.handle_type, responses, mouse_pos, layer); + self.update_target_handle_pos(self.handle_type, self.next_point, responses, mouse_pos, layer); } } @@ -788,11 +869,11 @@ impl PenToolData { let Some(new_position) = self.target_handle_position(self.handle_type, vector_data).map(|handle| self.next_point * 2. - handle) else { return; }; - self.update_target_handle_pos(opposite_handle_type, responses, new_position, layer); + self.update_target_handle_pos(opposite_handle_type, self.next_point, responses, new_position, layer); } else if self.alt_press { // Restore the previous handle position when Alt is released if let Some(previous_handle) = self.previous_handle_end_pos { - self.update_target_handle_pos(opposite_handle_type, responses, previous_handle, layer); + self.update_target_handle_pos(opposite_handle_type, self.next_point, responses, previous_handle, layer); } self.alt_press = false; self.previous_handle_end_pos = None; @@ -803,7 +884,7 @@ impl PenToolData { let Some(new_position) = self.target_handle_position(self.handle_type, vector_data).map(|handle| self.next_point * 2. - handle) else { return; }; - self.update_target_handle_pos(opposite_handle_type, responses, new_position, layer); + self.update_target_handle_pos(opposite_handle_type, self.next_point, responses, new_position, layer); } } HandleMode::Free => {} @@ -826,13 +907,16 @@ impl PenToolData { }; let new_handle_position = anchor_pos + handle_offset * direction; - self.update_target_handle_pos(opposite_handle, responses, new_handle_position, layer); + self.update_target_handle_pos(opposite_handle, self.next_point, responses, new_handle_position, layer); } fn place_anchor(&mut self, snap_data: SnapData, transform: DAffine2, mouse: DVec2, preferences: &PreferencesMessageHandler, responses: &mut VecDeque<Message>) -> Option<PenToolFsmState> { let document = snap_data.document; - let relative = self.latest_point().map(|point| point.pos); + let mut relative = self.latest_point().map(|point| point.pos); + if self.close_path { + relative = None + }; self.next_point = self.compute_snapped_angle(snap_data, transform, false, mouse, relative, true); let selected_nodes = document.network_interface.selected_nodes(); @@ -959,7 +1043,6 @@ impl PenToolData { self.set_lock_angle(&vector_data, point, segment); } } - self.end_point_segment = None; 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 { @@ -974,8 +1057,8 @@ impl PenToolData { let segment = vector_data.all_connected(point).collect::<Vec<_>>().first().map(|s| s.segment); if self.modifiers.lock_angle { - self.set_lock_angle(&vector_data, point, segment); self.handle_mode = HandleMode::Free; + self.set_lock_angle(&vector_data, point, segment); } } @@ -1048,10 +1131,10 @@ impl PenToolData { let vector_data = document.network_interface.compute_modified_vector(layer).unwrap(); let segment = vector_data.all_connected(point).collect::<Vec<_>>().first().map(|s| s.segment); + self.handle_mode = HandleMode::Free; if self.modifiers.lock_angle { self.set_lock_angle(&vector_data, point, segment); } - self.handle_mode = HandleMode::Free; } // Stores the segment and point ID of the clicked endpoint @@ -1191,14 +1274,10 @@ impl Fsm for PenToolFsmState { responses.add(TransformLayerMessage::BeginScalePen { last_point, handle }); } + let vector_data = document.network_interface.compute_modified_vector(layer).unwrap(); tool_data.previous_handle_start_pos = latest.handle_start; - - // Store the handle_end position - let segment = tool_data.end_point_segment; - if let Some(segment) = segment { - let vector_data = document.network_interface.compute_modified_vector(layer).unwrap(); - tool_data.previous_handle_end_pos = ManipulatorPointId::EndHandle(segment).get_position(&vector_data); - } + let opposite_handle = tool_data.check_grs_end_handle(&vector_data); + tool_data.previous_handle_end_pos = tool_data.target_handle_position(opposite_handle, &vector_data); PenToolFsmState::GRSHandle } (PenToolFsmState::GRSHandle, PenToolMessage::FinalPosition { final_position }) => { @@ -1218,7 +1297,7 @@ impl Fsm for PenToolFsmState { match tool_data.handle_mode { HandleMode::Free => {} HandleMode::ColinearEquidistant | HandleMode::ColinearLocked => { - if let Some((latest, segment)) = tool_data.latest_point().zip(tool_data.end_point_segment) { + if let Some(latest) = tool_data.latest_point() { let Some(direction) = (latest.pos - latest.handle_start).try_normalize() else { return PenToolFsmState::GRSHandle; }; @@ -1226,23 +1305,12 @@ impl Fsm for PenToolFsmState { if (latest.pos - latest.handle_start).length_squared() < f64::EPSILON { return PenToolFsmState::GRSHandle; } - - let is_start = vector_data.segment_start_from_id(segment) == Some(latest.id); - - let handle = if is_start { - ManipulatorPointId::PrimaryHandle(segment).get_position(&vector_data) - } else { - ManipulatorPointId::EndHandle(segment).get_position(&vector_data) - }; + let opposite_handle = tool_data.check_grs_end_handle(&vector_data); + let handle = tool_data.target_handle_position(opposite_handle, &vector_data); let Some(handle) = handle else { return PenToolFsmState::GRSHandle }; let relative_distance = (handle - latest.pos).length(); - let relative_position = relative_distance * direction; - let modification_type = if is_start { - VectorModificationType::SetPrimaryHandle { segment, relative_position } - } else { - VectorModificationType::SetEndHandle { segment, relative_position } - }; - responses.add(GraphOperationMessage::Vector { layer, modification_type }); + let relative_position = relative_distance * direction + latest.pos; + tool_data.update_target_handle_pos(opposite_handle, latest.pos, responses, relative_position, layer); } } } @@ -1271,10 +1339,14 @@ impl Fsm for PenToolFsmState { tool_data.next_handle_start = input.mouse.position; let Some(layer) = layer else { return PenToolFsmState::GRSHandle }; + let vector_data = document.network_interface.compute_modified_vector(layer).unwrap(); + let opposite_handle = tool_data.check_grs_end_handle(&vector_data); let previous = tool_data.previous_handle_start_pos; if let Some(latest) = tool_data.latest_point_mut() { latest.handle_start = previous; + } else { + return PenToolFsmState::PlacingAnchor; } responses.add(OverlaysMessage::Draw); @@ -1286,12 +1358,10 @@ impl Fsm for PenToolFsmState { move_anchor_with_handles: Key::Space, }); - // Set the handle-end back to original position - if let Some(((latest, segment), handle_end)) = tool_data.latest_point().zip(tool_data.end_point_segment).zip(tool_data.previous_handle_end_pos) { - let relative = handle_end - latest.pos; - let modification_type = VectorModificationType::SetEndHandle { segment, relative_position: relative }; - responses.add(GraphOperationMessage::Vector { layer, modification_type }); - } + let Some((previous_pos, latest)) = tool_data.previous_handle_end_pos.zip(tool_data.latest_point()) else { + return PenToolFsmState::PlacingAnchor; + }; + tool_data.update_target_handle_pos(opposite_handle, latest.pos, responses, previous_pos, layer); PenToolFsmState::PlacingAnchor } @@ -1486,7 +1556,6 @@ impl Fsm for PenToolFsmState { } (PenToolFsmState::DraggingHandle(_), PenToolMessage::DragStop) => { tool_data.cleanup_target_selections(shape_editor, layer, document, responses); - tool_data.end_point = None; tool_data .finish_placing_handle(SnapData::new(document, input), transform, preferences, responses) .unwrap_or(PenToolFsmState::PlacingAnchor) @@ -1512,7 +1581,13 @@ impl Fsm for PenToolFsmState { let snap_data = SnapData::new(document, input); if tool_data.modifiers.colinear && !tool_data.toggle_colinear_debounce { tool_data.handle_mode = match tool_data.handle_mode { - HandleMode::Free => HandleMode::ColinearEquidistant, + HandleMode::Free => { + let last_segment = tool_data.end_point_segment; + if let Some(latest) = tool_data.latest_point_mut() { + latest.in_segment = last_segment; + } + HandleMode::ColinearEquidistant + } HandleMode::ColinearEquidistant | HandleMode::ColinearLocked => HandleMode::Free, }; tool_data.toggle_colinear_debounce = true; @@ -1615,47 +1690,7 @@ impl Fsm for PenToolFsmState { state } (PenToolFsmState::DraggingHandle(_), PenToolMessage::SwapHandles) => { - let Some(vector_data) = layer.and_then(|layer| document.network_interface.compute_modified_vector(layer)) else { - return self; - }; - - let Some(viewport) = layer.map(|layer| document.metadata().transform_to_viewport(layer)) else { - return self; - }; - - if tool_data.close_path { - match tool_data.handle_type { - TargetHandle::HandleEnd | TargetHandle::PrimaryHandle(..) | TargetHandle::EndHandle(..) => { - let opposite_type = tool_data.get_opposite_handle_type(tool_data.handle_type, &vector_data); - tool_data.handle_end_offset = tool_data - .target_handle_position(opposite_type, &vector_data) - .map(|handle| viewport.transform_point2(handle) - input.mouse.position); - tool_data.cleanup_target_selections(shape_editor, layer, document, responses); - tool_data.update_handle_type(opposite_type); - tool_data.add_target_selections(shape_editor, layer); - } - _ => {} - } - } else { - // Store the offset and swap the handle_types - match tool_data.handle_type { - TargetHandle::None => {} - TargetHandle::HandleStart => { - let opposite_type = tool_data.get_opposite_handle_type(tool_data.handle_type, &vector_data); - tool_data.handle_end_offset = tool_data - .target_handle_position(opposite_type, &vector_data) - .map(|handle| viewport.transform_point2(handle) - input.mouse.position); - tool_data.update_handle_type(opposite_type); - tool_data.add_target_selections(shape_editor, layer); - } - _ => { - tool_data.cleanup_target_selections(shape_editor, layer, document, responses); - tool_data.handle_start_offset = layer.map(|layer| document.metadata().transform_to_viewport(layer).transform_point2(tool_data.next_handle_start) - input.mouse.position); - tool_data.update_handle_type(TargetHandle::HandleStart); - } - } - } - + tool_data.update_offset(layer, document, shape_editor, input, responses); responses.add(OverlaysMessage::Draw); responses.add(FrontendMessage::UpdateMouseCursor { cursor: MouseCursorIcon::None }); self @@ -1796,7 +1831,7 @@ impl Fsm for PenToolFsmState { HintInfo::keys([Key::Enter], "End Path").prepend_slash(), ])); - let toggle_group = match mode { + let mut toggle_group = match mode { HandleMode::Free => { vec![HintInfo::keys([Key::KeyC], "Make Handles Colinear")] } @@ -1804,9 +1839,10 @@ impl Fsm for PenToolFsmState { vec![HintInfo::keys([Key::KeyC], "Break Colinear Handles")] } }; + toggle_group.push(HintInfo::keys([Key::Tab], "Swap Selected Handles")); let mut common_hints = vec![HintInfo::keys([Key::Shift], "15° Increments"), HintInfo::keys([Key::Control], "Lock Angle")]; - let hold_group = match mode { + let mut hold_group = match mode { HandleMode::Free => common_hints, HandleMode::ColinearLocked => { common_hints.push(HintInfo::keys([Key::Alt], "Non-Equidistant Handles")); @@ -1817,6 +1853,7 @@ impl Fsm for PenToolFsmState { common_hints } }; + hold_group.push(HintInfo::keys([Key::Space], "Drag Anchor")); dragging_hint_data.0.push(HintGroup(toggle_group)); dragging_hint_data.0.push(HintGroup(hold_group)); From b228ac905e41c98f925da8b54a7471a2257711bd Mon Sep 17 00:00:00 2001 From: 0SlowPoke0 <govindpratyush@gmail.com> Date: Mon, 31 Mar 2025 04:18:32 +0530 Subject: [PATCH 14/21] fixed edge cases --- .../messages/tool/tool_messages/pen_tool.rs | 240 +++++++++++------- node-graph/gcore/src/vector/vector_data.rs | 12 +- 2 files changed, 156 insertions(+), 96 deletions(-) diff --git a/editor/src/messages/tool/tool_messages/pen_tool.rs b/editor/src/messages/tool/tool_messages/pen_tool.rs index 2a006d2bf3..e48560cfd9 100644 --- a/editor/src/messages/tool/tool_messages/pen_tool.rs +++ b/editor/src/messages/tool/tool_messages/pen_tool.rs @@ -313,6 +313,7 @@ struct PenToolData { previous_handle_start_pos: DVec2, previous_handle_end_pos: Option<DVec2>, + colinear: bool, alt_press: bool, space_press: bool, close_path: bool, @@ -343,9 +344,9 @@ impl PenToolData { /// Check whether target handle is primary,end or 'self.handle_end' fn check_end_handle_type(&self, vector_data: &VectorData) -> TargetHandle { - match (self.handle_end, self.end_point, self.end_point_segment) { - (Some(_), _, _) => TargetHandle::HandleEnd, - (None, Some(point), Some(segment)) => { + match (self.handle_end, self.end_point, self.end_point_segment, self.close_path) { + (Some(_), _, _, false) => TargetHandle::HandleEnd, + (None, Some(point), Some(segment), false) | (Some(_), Some(point), Some(segment), true) => { if vector_data.segment_start_from_id(segment) == Some(point) { TargetHandle::PrimaryHandle(segment) } else { @@ -527,10 +528,10 @@ impl PenToolData { self.close_path = true; self.next_handle_start = self.next_point; self.store_clicked_endpoint(document, &transform, snap_data.input, preferences); - self.handle_mode = HandleMode::Free; if self.modifiers.lock_angle { self.set_lock_angle(&vector_data, self.end_point.unwrap(), self.end_point_segment); } + self.handle_mode = HandleMode::Free; } } } @@ -582,15 +583,26 @@ impl PenToolData { // Store the segment let id = SegmentId::generate(); - if self.close_path && self.handle_mode != HandleMode::Free { - if let Some(handles) = match self.get_opposite_handle_type(TargetHandle::HandleEnd, &vector_data) { - TargetHandle::PrimaryHandle(segment) => Some([HandleId::end(id), HandleId::primary(segment)]), - TargetHandle::EndHandle(segment) => Some([HandleId::end(id), HandleId::end(segment)]), + if self.close_path { + if let Some((handles, handle1_pos)) = match self.get_opposite_handle_type(TargetHandle::HandleEnd, &vector_data) { + TargetHandle::PrimaryHandle(segment) => { + let handles = [HandleId::end(id), HandleId::primary(segment)]; + let handle1_pos = handles[1].to_manipulator_point().get_position(&vector_data); + handle1_pos.map(|pos| (handles, pos)) + } + TargetHandle::EndHandle(segment) => { + let handles = [HandleId::end(id), HandleId::end(segment)]; + let handle1_pos = handles[1].to_manipulator_point().get_position(&vector_data); + handle1_pos.map(|pos| (handles, pos)) + } _ => None, } { + let angle = (handle_end - next_point).angle_to(handle1_pos - next_point); + let pi = std::f64::consts::PI; + let colinear = (angle - pi).abs() < 1e-6 || (angle + pi).abs() < 1e-6; responses.add(GraphOperationMessage::Vector { layer, - modification_type: VectorModificationType::SetG1Continuous { handles, enabled: true }, + modification_type: VectorModificationType::SetG1Continuous { handles, enabled: colinear }, }); } } @@ -609,10 +621,16 @@ impl PenToolData { } else { [HandleId::primary(last_segment), HandleId::primary(id)] }; - responses.add(GraphOperationMessage::Vector { - layer, - modification_type: VectorModificationType::SetG1Continuous { handles, enabled: true }, - }); + + if let Some(h1) = handles[0].to_manipulator_point().get_position(&vector_data) { + let angle = (h1 - last_point.pos).angle_to(last_point.handle_start - last_point.pos); + let pi = std::f64::consts::PI; + let colinear = (angle - pi).abs() < 1e-6 || (angle + pi).abs() < 1e-6; + responses.add(GraphOperationMessage::Vector { + layer, + modification_type: VectorModificationType::SetG1Continuous { handles, enabled: colinear }, + }); + } } if !close_subpath { self.add_point(LastPoint { @@ -694,8 +712,8 @@ impl PenToolData { Some(transform.inverse().transform_vector2(delta)) } - // Calculates the offset from the mouse when swapping handles - fn update_offset( + // Calculates the offset from the mouse when swapping handles and swaps the handles + fn swap_handles( &mut self, layer: Option<LayerNodeIdentifier>, document: &DocumentMessageHandler, @@ -703,6 +721,7 @@ impl PenToolData { input: &InputPreprocessorMessageHandler, responses: &mut VecDeque<Message>, ) { + // Validate necessary data exists let Some(vector_data) = layer.and_then(|layer| document.network_interface.compute_modified_vector(layer)) else { return; }; @@ -710,39 +729,41 @@ impl PenToolData { let Some(viewport) = layer.map(|layer| document.metadata().transform_to_viewport(layer)) else { return; }; + // Determine if we need to swap to opposite handle + let should_swap_to_opposite = self.close_path && matches!(self.handle_type, TargetHandle::HandleEnd | TargetHandle::PrimaryHandle(..) | TargetHandle::EndHandle(..)) + || !self.close_path && matches!(self.handle_type, TargetHandle::HandleStart); - if self.close_path { - match self.handle_type { - TargetHandle::HandleEnd | TargetHandle::PrimaryHandle(..) | TargetHandle::EndHandle(..) => { - let opposite_type = self.get_opposite_handle_type(self.handle_type, &vector_data); - self.handle_end_offset = self - .target_handle_position(opposite_type, &vector_data) - .map(|handle| viewport.transform_point2(handle) - input.mouse.position); - self.cleanup_target_selections(shape_editor, layer, document, responses); - self.update_handle_type(opposite_type); - self.add_target_selections(shape_editor, layer); - } - _ => {} + // Determine if we need to swap to start handle + let should_swap_to_start = !self.close_path && !matches!(self.handle_type, TargetHandle::None | TargetHandle::HandleStart); + + if should_swap_to_opposite { + let opposite_type = self.get_opposite_handle_type(self.handle_type, &vector_data); + // Update offset + let Some(handle_pos) = self.target_handle_position(opposite_type, &vector_data) else { + return; + }; + if (handle_pos - self.next_point).length() < 1e-6 { + return; } - } else { - // Store the offset and swap the handle_types - match self.handle_type { - TargetHandle::None => {} - TargetHandle::HandleStart => { - let opposite_type = self.get_opposite_handle_type(self.handle_type, &vector_data); - self.handle_end_offset = self - .target_handle_position(opposite_type, &vector_data) - .map(|handle| viewport.transform_point2(handle) - input.mouse.position); - self.update_handle_type(opposite_type); - self.add_target_selections(shape_editor, layer); - } - _ => { - self.cleanup_target_selections(shape_editor, layer, document, responses); - self.handle_start_offset = layer.map(|layer| document.metadata().transform_to_viewport(layer).transform_point2(self.next_handle_start) - input.mouse.position); - self.update_handle_type(TargetHandle::HandleStart); - } + self.handle_end_offset = Some(viewport.transform_point2(handle_pos) - input.mouse.position); + // Update selections if in closed path mode + if self.close_path { + self.cleanup_target_selections(shape_editor, layer, document, responses); } + self.update_handle_type(opposite_type); + self.add_target_selections(shape_editor, layer); + } else if should_swap_to_start { + self.cleanup_target_selections(shape_editor, layer, document, responses); + + // Calculate offset from mouse position to next handle start + if let Some(layer_id) = layer { + let transform = document.metadata().transform_to_viewport(layer_id); + self.handle_start_offset = Some(transform.transform_point2(self.next_handle_start) - input.mouse.position); + } + + self.update_handle_type(TargetHandle::HandleStart); } + responses.add(FrontendMessage::UpdateMouseCursor { cursor: MouseCursorIcon::None }); } /// Handles moving the initially created point @@ -803,6 +824,7 @@ impl PenToolData { let Some(layer) = layer else { return Some(PenToolFsmState::DraggingHandle(self.handle_mode)) }; let vector_data = document.network_interface.compute_modified_vector(layer)?; let viewport_to_document = document.metadata().document_to_viewport.inverse(); + let mut mouse_pos = mouse; // Handles pressing `space` to drag anchor and its handles if self.modifiers.move_anchor_with_handles { let Some(delta) = self.space_anchor_handle_snap(&viewport_to_document, &transform, &snap_data, &mouse, &vector_data, input) else { @@ -828,11 +850,12 @@ impl PenToolData { match self.handle_type { TargetHandle::HandleStart => { let offset = self.handle_start_offset.unwrap_or(DVec2::ZERO); - self.next_handle_start = self.compute_snapped_angle(snap_data.clone(), transform, colinear, mouse + offset, Some(self.next_point), false); + mouse_pos += offset; + self.next_handle_start = self.compute_snapped_angle(snap_data.clone(), transform, colinear, mouse_pos, Some(self.next_point), false); } _ => { - let mouse_offset = mouse + self.handle_end_offset.unwrap_or(DVec2::ZERO); - let mouse_pos = self.compute_snapped_angle(snap_data.clone(), transform, colinear, mouse_offset, Some(self.next_point), false); + mouse_pos += self.handle_end_offset.unwrap_or(DVec2::ZERO); + let mouse_pos = self.compute_snapped_angle(snap_data.clone(), transform, colinear, mouse_pos, Some(self.next_point), false); self.update_target_handle_pos(self.handle_type, self.next_point, responses, mouse_pos, layer); } } @@ -848,8 +871,20 @@ impl PenToolData { } } - responses.add(OverlaysMessage::Draw); + let mouse_pos = viewport_to_document.transform_point2(mouse_pos); + let anchor = transform.transform_point2(self.next_point); + let distance = (mouse_pos - anchor).length(); + + if distance < 20. && self.handle_mode == HandleMode::Free && self.modifiers.lock_angle { + self.set_lock_angle(&vector_data, self.end_point.unwrap(), self.end_point_segment); + self.handle_mode = HandleMode::Free; + let last_segment = self.end_point_segment; + if let Some(latest) = self.latest_point_mut() { + latest.in_segment = last_segment; + } + } + responses.add(OverlaysMessage::Draw); Some(PenToolFsmState::DraggingHandle(self.handle_mode)) } @@ -1131,10 +1166,10 @@ impl PenToolData { let vector_data = document.network_interface.compute_modified_vector(layer).unwrap(); let segment = vector_data.all_connected(point).collect::<Vec<_>>().first().map(|s| s.segment); - self.handle_mode = HandleMode::Free; if self.modifiers.lock_angle { self.set_lock_angle(&vector_data, point, segment); } + self.handle_mode = HandleMode::Free; } // Stores the segment and point ID of the clicked endpoint @@ -1174,35 +1209,45 @@ impl PenToolData { self.handle_mode = HandleMode::Free; return; }; + match (self.handle_type, self.close_path) { + (TargetHandle::HandleStart, _) | (TargetHandle::HandleEnd, true) => { + let is_start = |point: PointId, segment: SegmentId| vector_data.segment_start_from_id(segment) == Some(point); - // Closure to check if a point is the start or end of a segment - let is_start = |point: PointId, segment: SegmentId| vector_data.segment_start_from_id(segment) == Some(point); + let end_handle = ManipulatorPointId::EndHandle(segment).get_position(vector_data); + let start_handle = ManipulatorPointId::PrimaryHandle(segment).get_position(vector_data); - let end_handle = ManipulatorPointId::EndHandle(segment).get_position(vector_data); - let start_handle = ManipulatorPointId::PrimaryHandle(segment).get_position(vector_data); - - let start_point = if is_start(anchor, segment) { - vector_data.segment_end_from_id(segment).and_then(|id| vector_data.point_domain.position_from_id(id)) - } else { - vector_data.segment_start_from_id(segment).and_then(|id| vector_data.point_domain.position_from_id(id)) - }; + let start_point = if is_start(anchor, segment) { + vector_data.segment_end_from_id(segment).and_then(|id| vector_data.point_domain.position_from_id(id)) + } else { + vector_data.segment_start_from_id(segment).and_then(|id| vector_data.point_domain.position_from_id(id)) + }; - let required_handle = if is_start(anchor, segment) { - start_handle - .filter(|&handle| handle != anchor_position) - .or(end_handle.filter(|&handle| Some(handle) != start_point)) - .or(start_point) - } else { - end_handle - .filter(|&handle| handle != anchor_position) - .or(start_handle.filter(|&handle| Some(handle) != start_point)) - .or(start_point) - }; + let required_handle = if is_start(anchor, segment) { + start_handle + .filter(|&handle| handle != anchor_position) + .or(end_handle.filter(|&handle| Some(handle) != start_point)) + .or(start_point) + } else { + end_handle + .filter(|&handle| handle != anchor_position) + .or(start_handle.filter(|&handle| Some(handle) != start_point)) + .or(start_point) + }; - if let Some(required_handle) = required_handle { - self.angle = -(required_handle - anchor_position).angle_to(DVec2::X); - self.handle_mode = HandleMode::ColinearEquidistant; + if let Some(required_handle) = required_handle { + self.angle = -(required_handle - anchor_position).angle_to(DVec2::X); + self.handle_mode = HandleMode::ColinearEquidistant; + } + } + (TargetHandle::EndHandle(..) | TargetHandle::PrimaryHandle(..), true) => { + self.angle = -(self.handle_end.unwrap() - anchor_position).angle_to(DVec2::X); + } + _ => { + self.angle = -(self.next_handle_start - anchor_position).angle_to(DVec2::X); + } } + + // Closure to check if a point is the start or end of a segment } fn add_point_layer_position(&mut self, document: &DocumentMessageHandler, responses: &mut VecDeque<Message>, layer: LayerNodeIdentifier, viewport: DVec2) { @@ -1262,6 +1307,9 @@ impl Fsm for PenToolFsmState { return PenToolFsmState::PlacingAnchor; } + let latest_pos = latest.pos; + let latest_handle_start = latest.handle_start; + let viewport = document.metadata().transform_to_viewport(layer); let last_point = viewport.transform_point2(latest.pos); let handle = viewport.transform_point2(latest.handle_start); @@ -1278,6 +1326,14 @@ impl Fsm for PenToolFsmState { tool_data.previous_handle_start_pos = latest.handle_start; let opposite_handle = tool_data.check_grs_end_handle(&vector_data); tool_data.previous_handle_end_pos = tool_data.target_handle_position(opposite_handle, &vector_data); + let handle1 = latest_handle_start - latest_pos; + let Some(opposite_handle_pos) = tool_data.target_handle_position(opposite_handle, &vector_data) else { + return PenToolFsmState::GRSHandle; + }; + let handle2 = opposite_handle_pos - latest_pos; + let pi = std::f64::consts::PI; + let angle = handle1.angle_to(handle2); + tool_data.colinear = angle.abs() < 1e-6 || (angle % pi).abs() < 1e-6; PenToolFsmState::GRSHandle } (PenToolFsmState::GRSHandle, PenToolMessage::FinalPosition { final_position }) => { @@ -1292,27 +1348,24 @@ impl Fsm for PenToolFsmState { } responses.add(OverlaysMessage::Draw); + let Some(latest) = tool_data.latest_point() else { + return PenToolFsmState::GRSHandle; + }; + let opposite_handle = tool_data.check_grs_end_handle(&vector_data); + let Some(opposite_handle_pos) = tool_data.target_handle_position(opposite_handle, &vector_data) else { + return PenToolFsmState::GRSHandle; + }; + if tool_data.colinear { + let Some(direction) = (latest.pos - latest.handle_start).try_normalize() else { + return PenToolFsmState::GRSHandle; + }; - // Making the end handle colinear - match tool_data.handle_mode { - HandleMode::Free => {} - HandleMode::ColinearEquidistant | HandleMode::ColinearLocked => { - if let Some(latest) = tool_data.latest_point() { - let Some(direction) = (latest.pos - latest.handle_start).try_normalize() else { - return PenToolFsmState::GRSHandle; - }; - - if (latest.pos - latest.handle_start).length_squared() < f64::EPSILON { - return PenToolFsmState::GRSHandle; - } - let opposite_handle = tool_data.check_grs_end_handle(&vector_data); - let handle = tool_data.target_handle_position(opposite_handle, &vector_data); - let Some(handle) = handle else { return PenToolFsmState::GRSHandle }; - let relative_distance = (handle - latest.pos).length(); - let relative_position = relative_distance * direction + latest.pos; - tool_data.update_target_handle_pos(opposite_handle, latest.pos, responses, relative_position, layer); - } + if (latest.pos - latest.handle_start).length_squared() < f64::EPSILON { + return PenToolFsmState::GRSHandle; } + let relative_distance = (opposite_handle_pos - latest.pos).length(); + let relative_position = relative_distance * direction + latest.pos; + tool_data.update_target_handle_pos(opposite_handle, latest.pos, responses, relative_position, layer); } responses.add(OverlaysMessage::Draw); @@ -1690,9 +1743,8 @@ impl Fsm for PenToolFsmState { state } (PenToolFsmState::DraggingHandle(_), PenToolMessage::SwapHandles) => { - tool_data.update_offset(layer, document, shape_editor, input, responses); + tool_data.swap_handles(layer, document, shape_editor, input, responses); responses.add(OverlaysMessage::Draw); - responses.add(FrontendMessage::UpdateMouseCursor { cursor: MouseCursorIcon::None }); self } ( diff --git a/node-graph/gcore/src/vector/vector_data.rs b/node-graph/gcore/src/vector/vector_data.rs index a141655fe8..add5b68690 100644 --- a/node-graph/gcore/src/vector/vector_data.rs +++ b/node-graph/gcore/src/vector/vector_data.rs @@ -334,14 +334,22 @@ impl VectorData { let (start_point_id, _, _) = self.segment_points_from_id(*segment_id)?; let start_index = self.point_domain.resolve_id(start_point_id)?; - self.segment_domain.end_connected(start_index).find(|&id| id != *segment_id).map(|id| (start_point_id, id)) + self.segment_domain.end_connected(start_index).find(|&id| id != *segment_id).map(|id| (start_point_id, id)).or(self + .segment_domain + .start_connected(start_index) + .find(|&id| id != *segment_id) + .map(|id| (start_point_id, id))) } ManipulatorPointId::EndHandle(segment_id) => { // For end handle, find segments starting at our end point let (_, end_point_id, _) = self.segment_points_from_id(*segment_id)?; let end_index = self.point_domain.resolve_id(end_point_id)?; - self.segment_domain.start_connected(end_index).find(|&id| id != *segment_id).map(|id| (end_point_id, id)) + self.segment_domain.start_connected(end_index).find(|&id| id != *segment_id).map(|id| (end_point_id, id)).or(self + .segment_domain + .end_connected(end_index) + .find(|&id| id != *segment_id) + .map(|id| (end_point_id, id))) } ManipulatorPointId::Anchor(_) => None, } From a25c0d903f703c5cb5549816bd128c4aec4a0263 Mon Sep 17 00:00:00 2001 From: 0SlowPoke0 <govindpratyush@gmail.com> Date: Mon, 31 Mar 2025 17:51:28 +0530 Subject: [PATCH 15/21] edge cases fixed --- .../messages/tool/tool_messages/pen_tool.rs | 56 +++++++++++++++---- 1 file changed, 44 insertions(+), 12 deletions(-) diff --git a/editor/src/messages/tool/tool_messages/pen_tool.rs b/editor/src/messages/tool/tool_messages/pen_tool.rs index e48560cfd9..38e78259bb 100644 --- a/editor/src/messages/tool/tool_messages/pen_tool.rs +++ b/editor/src/messages/tool/tool_messages/pen_tool.rs @@ -303,7 +303,6 @@ struct PenToolData { next_handle_start: DVec2, g1_continuous: bool, - toggle_colinear_debounce: bool, angle: f64, auto_panning: AutoPanning, @@ -313,9 +312,13 @@ struct PenToolData { previous_handle_start_pos: DVec2, previous_handle_end_pos: Option<DVec2>, + toggle_colinear_debounce: bool, colinear: bool, alt_press: bool, space_press: bool, + lock_toggle: bool, + handle_swap: bool, + angle_locked: bool, close_path: bool, handle_mode: HandleMode, @@ -495,6 +498,7 @@ impl PenToolData { self.next_handle_start = self.next_point; let vector_data = document.network_interface.compute_modified_vector(layer).unwrap(); self.update_handle_type(TargetHandle::HandleStart); + self.handle_mode = HandleMode::ColinearLocked; // Break the control let Some((last_pos, id)) = self.latest_point().map(|point| (point.pos, point.id)) else { return }; @@ -510,6 +514,7 @@ impl PenToolData { let last_segment = self.end_point_segment; let Some(point) = self.latest_point_mut() else { return }; point.in_segment = last_segment; + self.lock_toggle = true; return; } @@ -518,6 +523,7 @@ impl PenToolData { } } + //Closing path for id in vector_data.extendable_points(preferences.vector_meshes).filter(|&point| point != id) { let Some(pos) = vector_data.point_domain.position_from_id(id) else { continue }; let transformed_distance_between_squared = transform.transform_point2(pos).distance_squared(transform.transform_point2(self.next_point)); @@ -528,10 +534,11 @@ impl PenToolData { self.close_path = true; self.next_handle_start = self.next_point; self.store_clicked_endpoint(document, &transform, snap_data.input, preferences); + self.handle_mode = HandleMode::Free; if self.modifiers.lock_angle { self.set_lock_angle(&vector_data, self.end_point.unwrap(), self.end_point_segment); + self.lock_toggle = true; } - self.handle_mode = HandleMode::Free; } } } @@ -541,6 +548,7 @@ impl PenToolData { let next_handle_start = self.next_handle_start; let handle_start = self.latest_point()?.handle_start; let mouse = snap_data.input.mouse.position; + self.handle_swap = false; self.handle_end_offset = None; self.handle_start_offset = None; let Some(handle_end) = self.handle_end else { @@ -860,6 +868,19 @@ impl PenToolData { } } + let mouse_pos = viewport_to_document.transform_point2(mouse_pos); + let anchor = transform.transform_point2(self.next_point); + let distance = (mouse_pos - anchor).length(); + + if self.lock_toggle && !self.modifiers.lock_angle { + self.lock_toggle = false; + self.handle_mode = HandleMode::Free; + } + + if distance > 20. && self.handle_mode == HandleMode::Free && self.modifiers.lock_angle && !self.angle_locked { + self.angle_locked = true + } + match self.handle_mode { HandleMode::ColinearLocked | HandleMode::ColinearEquidistant => { self.g1_continuous = true; @@ -871,13 +892,9 @@ impl PenToolData { } } - let mouse_pos = viewport_to_document.transform_point2(mouse_pos); - let anchor = transform.transform_point2(self.next_point); - let distance = (mouse_pos - anchor).length(); - - if distance < 20. && self.handle_mode == HandleMode::Free && self.modifiers.lock_angle { + if distance < 20. && self.handle_mode == HandleMode::Free && self.modifiers.lock_angle && !self.angle_locked { self.set_lock_angle(&vector_data, self.end_point.unwrap(), self.end_point_segment); - self.handle_mode = HandleMode::Free; + self.lock_toggle = true; let last_segment = self.end_point_segment; if let Some(latest) = self.latest_point_mut() { latest.in_segment = last_segment; @@ -1076,6 +1093,7 @@ impl PenToolData { if self.modifiers.lock_angle { self.set_lock_angle(&vector_data, point, segment); + self.lock_toggle = true; } } let mut selected_layers_except_artboards = selected_nodes.selected_layers_except_artboards(&document.network_interface); @@ -1090,10 +1108,10 @@ impl PenToolData { if let Some((layer, point, _position)) = closest_point(document, viewport, tolerance, document.metadata().all_layers(), |_| false, preferences) { let vector_data = document.network_interface.compute_modified_vector(layer).unwrap(); let segment = vector_data.all_connected(point).collect::<Vec<_>>().first().map(|s| s.segment); - + self.handle_mode = HandleMode::Free; if self.modifiers.lock_angle { - self.handle_mode = HandleMode::Free; self.set_lock_angle(&vector_data, point, segment); + self.lock_toggle = true; } } @@ -1165,11 +1183,11 @@ impl PenToolData { self.next_handle_start = handle_start; let vector_data = document.network_interface.compute_modified_vector(layer).unwrap(); let segment = vector_data.all_connected(point).collect::<Vec<_>>().first().map(|s| s.segment); - + self.handle_mode = HandleMode::Free; if self.modifiers.lock_angle { self.set_lock_angle(&vector_data, point, segment); + self.lock_toggle = true; } - self.handle_mode = HandleMode::Free; } // Stores the segment and point ID of the clicked endpoint @@ -1241,9 +1259,11 @@ impl PenToolData { } (TargetHandle::EndHandle(..) | TargetHandle::PrimaryHandle(..), true) => { self.angle = -(self.handle_end.unwrap() - anchor_position).angle_to(DVec2::X); + self.handle_mode = HandleMode::ColinearEquidistant; } _ => { self.angle = -(self.next_handle_start - anchor_position).angle_to(DVec2::X); + self.handle_mode = HandleMode::ColinearEquidistant; } } @@ -1670,10 +1690,19 @@ impl Fsm for PenToolFsmState { tool_data.toggle_colinear_debounce = false; } + if !tool_data.modifiers.lock_angle { + tool_data.angle_locked = false; + } + let state = tool_data .drag_handle(snap_data, transform, input.mouse.position, responses, layer, input) .unwrap_or(PenToolFsmState::Ready); + // To prevent showing cursor when KeyC is pressed when handles are swapped + if tool_data.handle_swap { + responses.add(FrontendMessage::UpdateMouseCursor { cursor: MouseCursorIcon::None }); + } + // Auto-panning let messages = [ PenToolMessage::PointerOutsideViewport { @@ -1744,6 +1773,9 @@ impl Fsm for PenToolFsmState { } (PenToolFsmState::DraggingHandle(_), PenToolMessage::SwapHandles) => { tool_data.swap_handles(layer, document, shape_editor, input, responses); + if !tool_data.handle_swap { + tool_data.handle_swap = true + }; responses.add(OverlaysMessage::Draw); self } From 8a445d747683334211b0bd744a90a884a684e747 Mon Sep 17 00:00:00 2001 From: 0SlowPoke0 <govindpratyush@gmail.com> Date: Mon, 7 Apr 2025 14:46:25 +0530 Subject: [PATCH 16/21] fix edge cases and add docs --- .../messages/tool/tool_messages/pen_tool.rs | 47 +++++++++++++------ 1 file changed, 32 insertions(+), 15 deletions(-) diff --git a/editor/src/messages/tool/tool_messages/pen_tool.rs b/editor/src/messages/tool/tool_messages/pen_tool.rs index bdd5a4e671..2e07ba3696 100644 --- a/editor/src/messages/tool/tool_messages/pen_tool.rs +++ b/editor/src/messages/tool/tool_messages/pen_tool.rs @@ -285,12 +285,18 @@ enum HandleMode { /// The type of handle which is dragged by the cursor (under the cursor) #[derive(Clone, Debug, Default, PartialEq, Copy)] enum TargetHandle { - HandleEnd, + #[default] + None, + /// The handle that is drawn when an anchor is placed and dragging begins usually `next__handle-_start`, + /// lasting until `Tab` is pressed to swap the handles. HandleStart, + /// The opposite handle that is drawn after placing an anchor and starting to drag + /// the "next handle start", continuing until `Tab` is pressed to swap the handles. + HandleEnd, + /// In the bent handle case,or starting to drag handle from a different segment,the endpoint might be having handle a to it. + /// if the endpoint is a startpoint it would have ManipulatorPointId::PrimaryHandle attached to it if not then ManipulatorPointId::EndHandle PrimaryHandle(SegmentId), EndHandle(SegmentId), - #[default] - None, } #[derive(Clone, Debug, Default)] @@ -316,8 +322,14 @@ struct PenToolData { colinear: bool, alt_press: bool, space_press: bool, - lock_toggle: bool, + /// Tracks whether to switch from `HandleMode::ColinearEquidistant` to `HandleMode::Free` + /// after releasing Ctrl, specifically when Ctrl was held before the handle was dragged from the anchor. + switch_to_free_on_ctrl_release: bool, + ///// To prevent showing cursor when KeyC is pressed when handles are swapped handle_swap: bool, + /// Prevents conflicts when the handle's angle is already locked and it passes near the anchor, + /// avoiding unintended direction changes. Specifically handles the case where a handle is being dragged, + /// and Ctrl is pressed near the anchor to make it colinear with its opposite handle. angle_locked: bool, close_path: bool, @@ -515,7 +527,7 @@ impl PenToolData { let last_segment = self.end_point_segment; let Some(point) = self.latest_point_mut() else { return }; point.in_segment = last_segment; - self.lock_toggle = true; + self.switch_to_free_on_ctrl_release = true; return; } @@ -538,7 +550,7 @@ impl PenToolData { self.handle_mode = HandleMode::Free; if self.modifiers.lock_angle { self.set_lock_angle(&vector_data, self.end_point.unwrap(), self.end_point_segment); - self.lock_toggle = true; + self.switch_to_free_on_ctrl_release = true; } } } @@ -749,9 +761,11 @@ impl PenToolData { let opposite_type = self.get_opposite_handle_type(self.handle_type, &vector_data); // Update offset let Some(handle_pos) = self.target_handle_position(opposite_type, &vector_data) else { + self.handle_swap = false; return; }; if (handle_pos - self.next_point).length() < 1e-6 { + self.handle_swap = false; return; } self.handle_end_offset = Some(viewport.transform_point2(handle_pos) - input.mouse.position); @@ -873,8 +887,8 @@ impl PenToolData { let anchor = transform.transform_point2(self.next_point); let distance = (mouse_pos - anchor).length(); - if self.lock_toggle && !self.modifiers.lock_angle { - self.lock_toggle = false; + if self.switch_to_free_on_ctrl_release && !self.modifiers.lock_angle { + self.switch_to_free_on_ctrl_release = false; self.handle_mode = HandleMode::Free; } @@ -895,7 +909,7 @@ impl PenToolData { if distance < 20. && self.handle_mode == HandleMode::Free && self.modifiers.lock_angle && !self.angle_locked { self.set_lock_angle(&vector_data, self.end_point.unwrap(), self.end_point_segment); - self.lock_toggle = true; + self.switch_to_free_on_ctrl_release = true; let last_segment = self.end_point_segment; if let Some(latest) = self.latest_point_mut() { latest.in_segment = last_segment; @@ -949,6 +963,10 @@ impl PenToolData { return; }; + if (anchor_pos - handle).length() < 1e-6 { + return; + } + let Some(direction) = (anchor_pos - handle).try_normalize() else { log::trace!("Skipping colinear adjustment: handle_start and anchor_point are too close!"); return; @@ -1094,7 +1112,7 @@ impl PenToolData { if self.modifiers.lock_angle { self.set_lock_angle(&vector_data, point, segment); - self.lock_toggle = true; + self.switch_to_free_on_ctrl_release = true; } } let mut selected_layers_except_artboards = selected_nodes.selected_layers_except_artboards(&document.network_interface); @@ -1112,7 +1130,7 @@ impl PenToolData { self.handle_mode = HandleMode::Free; if self.modifiers.lock_angle { self.set_lock_angle(&vector_data, point, segment); - self.lock_toggle = true; + self.switch_to_free_on_ctrl_release = true; } } @@ -1187,7 +1205,7 @@ impl PenToolData { self.handle_mode = HandleMode::Free; if self.modifiers.lock_angle { self.set_lock_angle(&vector_data, point, segment); - self.lock_toggle = true; + self.switch_to_free_on_ctrl_release = true; } } @@ -1354,7 +1372,7 @@ impl Fsm for PenToolFsmState { let handle2 = opposite_handle_pos - latest_pos; let pi = std::f64::consts::PI; let angle = handle1.angle_to(handle2); - tool_data.colinear = angle.abs() < 1e-6 || (angle % pi).abs() < 1e-6; + tool_data.colinear = (angle - pi).abs() < 1e-6 || (angle + pi).abs() < 1e-6; PenToolFsmState::GRSHandle } (PenToolFsmState::GRSHandle, PenToolMessage::FinalPosition { final_position }) => { @@ -1699,7 +1717,6 @@ impl Fsm for PenToolFsmState { .drag_handle(snap_data, transform, input.mouse.position, responses, layer, input) .unwrap_or(PenToolFsmState::Ready); - // To prevent showing cursor when KeyC is pressed when handles are swapped if tool_data.handle_swap { responses.add(FrontendMessage::UpdateMouseCursor { cursor: MouseCursorIcon::None }); } @@ -1773,10 +1790,10 @@ impl Fsm for PenToolFsmState { state } (PenToolFsmState::DraggingHandle(_), PenToolMessage::SwapHandles) => { - tool_data.swap_handles(layer, document, shape_editor, input, responses); if !tool_data.handle_swap { tool_data.handle_swap = true }; + tool_data.swap_handles(layer, document, shape_editor, input, responses); responses.add(OverlaysMessage::Draw); self } From 2b987e764f1b10ff9ffef9ac2cea4f720885b2e6 Mon Sep 17 00:00:00 2001 From: Keavon Chambers <keavon@keavon.com> Date: Mon, 7 Apr 2025 04:12:54 -0700 Subject: [PATCH 17/21] Code review pass --- .../messages/input_mapper/input_mappings.rs | 2 +- .../document/overlays/utility_functions.rs | 1 + .../messages/tool/tool_messages/pen_tool.rs | 85 ++++++++++++------- 3 files changed, 55 insertions(+), 33 deletions(-) diff --git a/editor/src/messages/input_mapper/input_mappings.rs b/editor/src/messages/input_mapper/input_mappings.rs index 3e05c4efe9..eb264dfb3a 100644 --- a/editor/src/messages/input_mapper/input_mappings.rs +++ b/editor/src/messages/input_mapper/input_mappings.rs @@ -254,7 +254,7 @@ pub fn input_mappings() -> Mapping { // // PenToolMessage entry!(PointerMove; refresh_keys=[Control, Alt, Shift, KeyC], action_dispatch=PenToolMessage::PointerMove { snap_angle: Shift, break_handle: Alt, lock_angle: Control, colinear: KeyC, move_anchor_with_handles: Space }), - entry!(KeyDownNoRepeat(Tab);action_dispatch=PenToolMessage::SwapHandles), + entry!(KeyDownNoRepeat(Tab); action_dispatch=PenToolMessage::SwapHandles), entry!(KeyDown(MouseLeft); action_dispatch=PenToolMessage::DragStart { append_to_selected: Shift }), entry!(KeyUp(MouseLeft); action_dispatch=PenToolMessage::DragStop), entry!(KeyDown(MouseRight); action_dispatch=PenToolMessage::Abort), diff --git a/editor/src/messages/portfolio/document/overlays/utility_functions.rs b/editor/src/messages/portfolio/document/overlays/utility_functions.rs index 2c585d2afe..a6a77b1726 100644 --- a/editor/src/messages/portfolio/document/overlays/utility_functions.rs +++ b/editor/src/messages/portfolio/document/overlays/utility_functions.rs @@ -121,6 +121,7 @@ pub fn path_overlays(document: &DocumentMessageHandler, draw_handles: DrawHandle let is_selected = |point: ManipulatorPointId| selected.is_some_and(|selected| selected.is_selected(point)); let opposite_handles_data: Vec<(PointId, SegmentId)> = shape_editor.selected_points().filter_map(|point_id| vector_data.adjacent_segment(point_id)).collect(); + match draw_handles { DrawHandles::All => { vector_data.segment_bezier_iter().for_each(|(segment_id, bezier, _start, _end)| { diff --git a/editor/src/messages/tool/tool_messages/pen_tool.rs b/editor/src/messages/tool/tool_messages/pen_tool.rs index 2e07ba3696..6cbeb3bec7 100644 --- a/editor/src/messages/tool/tool_messages/pen_tool.rs +++ b/editor/src/messages/tool/tool_messages/pen_tool.rs @@ -282,20 +282,33 @@ enum HandleMode { ColinearEquidistant, } -/// The type of handle which is dragged by the cursor (under the cursor) +/// The type of handle which is dragged by the cursor (under the cursor). #[derive(Clone, Debug, Default, PartialEq, Copy)] enum TargetHandle { #[default] None, - /// The handle that is drawn when an anchor is placed and dragging begins usually `next__handle-_start`, - /// lasting until `Tab` is pressed to swap the handles. + /// This is the handle being dragged and represents the primary handle + /// of the next segment that will be placed after the current handle and next anchor are placed. + /// Its position is stored in `tool_data.next_handle_start`. + /// + /// Pressing Tab swaps to the opposite handle type. The swapped handle can be either `HandleEnd` or, + /// in the case of a bent segment, [`ManipulatorPointId::PrimaryHandle`] or [`ManipulatorPointId::EndHandle`]. + /// + /// When closing a path, the handle being dragged becomes the end handle of the currently placed anchor. HandleStart, /// The opposite handle that is drawn after placing an anchor and starting to drag - /// the "next handle start", continuing until `Tab` is pressed to swap the handles. + /// the "next handle start", continuing until Tab is pressed to swap the handles. HandleEnd, - /// In the bent handle case,or starting to drag handle from a different segment,the endpoint might be having handle a to it. - /// if the endpoint is a startpoint it would have ManipulatorPointId::PrimaryHandle attached to it if not then ManipulatorPointId::EndHandle + /// This is the primary handle of the segment from whose endpoint a new handle is being drawn. + /// When closing the path, the handle being dragged will be the [`TargetHandle::HandleEnd`] (see its documentation); + /// otherwise, it will be [`TargetHandle::HandleStart`]. + /// + /// If a handle is dragged from a different endpoint within the same layer, the opposite handle will be + /// `ManipulatorPoint::Primary` if that point is the starting point of its path. PrimaryHandle(SegmentId), + /// This is the end handle of the segment from whose endpoint a new handle is being drawn (same cases apply + /// as mentioned in [`TargetHandle::PrimaryHandle`]). If a handle is dragged from a different endpoint within the same + /// layer, the opposite handle will be `ManipulatorPoint::EndHandle` if that point is the end point of its path. EndHandle(SegmentId), } @@ -325,7 +338,7 @@ struct PenToolData { /// Tracks whether to switch from `HandleMode::ColinearEquidistant` to `HandleMode::Free` /// after releasing Ctrl, specifically when Ctrl was held before the handle was dragged from the anchor. switch_to_free_on_ctrl_release: bool, - ///// To prevent showing cursor when KeyC is pressed when handles are swapped + /// To prevent showing cursor when `KeyC` is pressed when handles are swapped. handle_swap: bool, /// Prevents conflicts when the handle's angle is already locked and it passes near the anchor, /// avoiding unintended direction changes. Specifically handles the case where a handle is being dragged, @@ -358,7 +371,7 @@ impl PenToolData { self.latest_points.push(point); } - /// Check whether target handle is primary,end or 'self.handle_end' + /// Check whether target handle is primary, end, or `self.handle_end` fn check_end_handle_type(&self, vector_data: &VectorData) -> TargetHandle { match (self.handle_end, self.end_point, self.end_point_segment, self.close_path) { (Some(_), _, _, false) => TargetHandle::HandleEnd, @@ -387,22 +400,17 @@ impl PenToolData { fn get_opposite_handle_type(&self, handle_type: TargetHandle, vector_data: &VectorData) -> TargetHandle { match handle_type { TargetHandle::HandleStart => self.check_end_handle_type(vector_data), - TargetHandle::HandleEnd => { - if self.close_path { - match (self.end_point, self.end_point_segment) { - (Some(point), Some(segment)) => { - if vector_data.segment_start_from_id(segment) == Some(point) { - TargetHandle::PrimaryHandle(segment) - } else { - TargetHandle::EndHandle(segment) - } - } - _ => TargetHandle::None, + TargetHandle::HandleEnd => match (self.close_path, self.end_point, self.end_point_segment) { + (true, _, _) => TargetHandle::HandleStart, + (false, Some(point), Some(segment)) => { + if vector_data.segment_start_from_id(segment) == Some(point) { + TargetHandle::PrimaryHandle(segment) + } else { + TargetHandle::EndHandle(segment) } - } else { - TargetHandle::HandleStart } - } + _ => TargetHandle::None, + }, _ => { if self.close_path { TargetHandle::HandleEnd @@ -450,6 +458,7 @@ impl PenToolData { TargetHandle::None => None, } } + /// Remove the handles selected when swapping handles fn cleanup_target_selections(&self, shape_editor: &mut ShapeState, layer: Option<LayerNodeIdentifier>, document: &DocumentMessageHandler, responses: &mut VecDeque<Message>) { let Some(shape_state) = layer.and_then(|layer| shape_editor.selected_shape_state.get_mut(&layer)) else { @@ -468,7 +477,7 @@ impl PenToolData { responses.add(OverlaysMessage::Draw); } - /// Selects the handle which is currently dragged by the user + /// Selects the handle which is currently dragged by the user. fn add_target_selections(&self, shape_editor: &mut ShapeState, layer: Option<LayerNodeIdentifier>) { let Some(shape_state) = layer.and_then(|layer| shape_editor.selected_shape_state.get_mut(&layer)) else { return; @@ -521,7 +530,9 @@ impl PenToolData { if on_top { self.handle_end = None; self.handle_mode = HandleMode::Free; + self.store_clicked_endpoint(document, &transform, snap_data.input, preferences); + if self.modifiers.lock_angle { self.set_lock_angle(&vector_data, id, self.end_point_segment); let last_segment = self.end_point_segment; @@ -536,11 +547,12 @@ impl PenToolData { } } - //Closing path + // Closing path for id in vector_data.extendable_points(preferences.vector_meshes).filter(|&point| point != id) { let Some(pos) = vector_data.point_domain.position_from_id(id) else { continue }; let transformed_distance_between_squared = transform.transform_point2(pos).distance_squared(transform.transform_point2(self.next_point)); let snap_point_tolerance_squared = crate::consts::SNAP_POINT_TOLERANCE.powi(2); + if transformed_distance_between_squared < snap_point_tolerance_squared { self.update_handle_type(TargetHandle::HandleEnd); self.handle_end_offset = None; @@ -733,7 +745,7 @@ impl PenToolData { Some(transform.inverse().transform_vector2(delta)) } - // Calculates the offset from the mouse when swapping handles and swaps the handles + /// Calculates the offset from the mouse when swapping handles, and swaps the handles. fn swap_handles( &mut self, layer: Option<LayerNodeIdentifier>, @@ -750,6 +762,7 @@ impl PenToolData { let Some(viewport) = layer.map(|layer| document.metadata().transform_to_viewport(layer)) else { return; }; + // Determine if we need to swap to opposite handle let should_swap_to_opposite = self.close_path && matches!(self.handle_type, TargetHandle::HandleEnd | TargetHandle::PrimaryHandle(..) | TargetHandle::EndHandle(..)) || !self.close_path && matches!(self.handle_type, TargetHandle::HandleStart); @@ -769,6 +782,7 @@ impl PenToolData { return; } self.handle_end_offset = Some(viewport.transform_point2(handle_pos) - input.mouse.position); + // Update selections if in closed path mode if self.close_path { self.cleanup_target_selections(shape_editor, layer, document, responses); @@ -786,6 +800,7 @@ impl PenToolData { self.update_handle_type(TargetHandle::HandleStart); } + responses.add(FrontendMessage::UpdateMouseCursor { cursor: MouseCursorIcon::None }); } @@ -799,7 +814,9 @@ impl PenToolData { }; latest.pos += delta; + let modification_type = VectorModificationType::ApplyPointDelta { point: latest.id, delta }; + responses.add(GraphOperationMessage::Vector { layer, modification_type }); responses.add(OverlaysMessage::Draw); @@ -814,6 +831,7 @@ impl PenToolData { } let Some(end_point) = self.end_point else { return }; + let modification_type_anchor = VectorModificationType::ApplyPointDelta { point: end_point, delta }; responses.add(GraphOperationMessage::Vector { layer, @@ -848,7 +866,8 @@ impl PenToolData { let vector_data = document.network_interface.compute_modified_vector(layer)?; let viewport_to_document = document.metadata().document_to_viewport.inverse(); let mut mouse_pos = mouse; - // Handles pressing `space` to drag anchor and its handles + + // Handles pressing Space to drag anchor and its handles if self.modifiers.move_anchor_with_handles { let Some(delta) = self.space_anchor_handle_snap(&viewport_to_document, &transform, &snap_data, &mouse, &vector_data, input) else { return Some(PenToolFsmState::DraggingHandle(self.handle_mode)); @@ -864,6 +883,7 @@ impl PenToolData { if let Some(handle) = self.handle_end.as_mut() { *handle += delta; } + self.move_anchor_and_handles(delta, layer, responses, &vector_data); responses.add(OverlaysMessage::Draw); @@ -917,6 +937,7 @@ impl PenToolData { } responses.add(OverlaysMessage::Draw); + Some(PenToolFsmState::DraggingHandle(self.handle_mode)) } @@ -984,10 +1005,7 @@ impl PenToolData { fn place_anchor(&mut self, snap_data: SnapData, transform: DAffine2, mouse: DVec2, preferences: &PreferencesMessageHandler, responses: &mut VecDeque<Message>) -> Option<PenToolFsmState> { let document = snap_data.document; - let mut relative = self.latest_point().map(|point| point.pos); - if self.close_path { - relative = None - }; + let relative = if self.close_path { None } else { self.latest_point().map(|point| point.pos) }; self.next_point = self.compute_snapped_angle(snap_data, transform, false, mouse, relative, true); let selected_nodes = document.network_interface.selected_nodes(); @@ -1203,6 +1221,7 @@ impl PenToolData { let vector_data = document.network_interface.compute_modified_vector(layer).unwrap(); let segment = vector_data.all_connected(point).collect::<Vec<_>>().first().map(|s| s.segment); self.handle_mode = HandleMode::Free; + if self.modifiers.lock_angle { self.set_lock_angle(&vector_data, point, segment); self.switch_to_free_on_ctrl_release = true; @@ -1246,6 +1265,7 @@ impl PenToolData { self.handle_mode = HandleMode::Free; return; }; + match (self.handle_type, self.close_path) { (TargetHandle::HandleStart, _) | (TargetHandle::HandleEnd, true) => { let is_start = |point: PointId, segment: SegmentId| vector_data.segment_start_from_id(segment) == Some(point); @@ -1394,6 +1414,7 @@ impl Fsm for PenToolFsmState { let Some(opposite_handle_pos) = tool_data.target_handle_position(opposite_handle, &vector_data) else { return PenToolFsmState::GRSHandle; }; + if tool_data.colinear { let Some(direction) = (latest.pos - latest.handle_start).try_normalize() else { return PenToolFsmState::GRSHandle; @@ -1792,7 +1813,7 @@ impl Fsm for PenToolFsmState { (PenToolFsmState::DraggingHandle(_), PenToolMessage::SwapHandles) => { if !tool_data.handle_swap { tool_data.handle_swap = true - }; + } tool_data.swap_handles(layer, document, shape_editor, input, responses); responses.add(OverlaysMessage::Draw); self @@ -1819,7 +1840,7 @@ impl Fsm for PenToolFsmState { self } (PenToolFsmState::DraggingHandle(mode), PenToolMessage::PointerOutsideViewport { .. }) => { - // Auto-panningF + // Auto-panning let _ = tool_data.auto_panning.shift_viewport(input, responses); PenToolFsmState::DraggingHandle(mode) From ff344547c1e0e854916e750481744a44ddba0425 Mon Sep 17 00:00:00 2001 From: 0SlowPoke0 <govindpratyush@gmail.com> Date: Tue, 8 Apr 2025 07:03:59 +0530 Subject: [PATCH 18/21] rename ,bug fixes --- .../messages/tool/tool_messages/pen_tool.rs | 215 +++++++++--------- 1 file changed, 112 insertions(+), 103 deletions(-) diff --git a/editor/src/messages/tool/tool_messages/pen_tool.rs b/editor/src/messages/tool/tool_messages/pen_tool.rs index 6cbeb3bec7..3ee6bb9652 100644 --- a/editor/src/messages/tool/tool_messages/pen_tool.rs +++ b/editor/src/messages/tool/tool_messages/pen_tool.rs @@ -291,25 +291,25 @@ enum TargetHandle { /// of the next segment that will be placed after the current handle and next anchor are placed. /// Its position is stored in `tool_data.next_handle_start`. /// - /// Pressing Tab swaps to the opposite handle type. The swapped handle can be either `HandleEnd` or, - /// in the case of a bent segment, [`ManipulatorPointId::PrimaryHandle`] or [`ManipulatorPointId::EndHandle`]. + /// Pressing Tab swaps to the opposite handle type. The swapped handle can be either `PreviewInHandle` or, + /// in the case of a bent segment, [`ManipulatorPointId::EndHandle`] or [`ManipulatorPointId::PrimaryHandle`]. /// /// When closing a path, the handle being dragged becomes the end handle of the currently placed anchor. - HandleStart, + FuturePreviewOutHandle, /// The opposite handle that is drawn after placing an anchor and starting to drag /// the "next handle start", continuing until Tab is pressed to swap the handles. - HandleEnd, + PreviewInHandle, /// This is the primary handle of the segment from whose endpoint a new handle is being drawn. - /// When closing the path, the handle being dragged will be the [`TargetHandle::HandleEnd`] (see its documentation); - /// otherwise, it will be [`TargetHandle::HandleStart`]. + /// When closing the path, the handle being dragged will be the [`TargetHandle::PreviewInHandle`] (see its documentation); + /// otherwise, it will be [`TargetHandle::FuturePreviewOutHandle`]. /// /// If a handle is dragged from a different endpoint within the same layer, the opposite handle will be /// `ManipulatorPoint::Primary` if that point is the starting point of its path. - PrimaryHandle(SegmentId), + PriorOutHandle(SegmentId), /// This is the end handle of the segment from whose endpoint a new handle is being drawn (same cases apply - /// as mentioned in [`TargetHandle::PrimaryHandle`]). If a handle is dragged from a different endpoint within the same + /// as mentioned in [`TargetHandle::PriorOutHandle`]). If a handle is dragged from a different endpoint within the same /// layer, the opposite handle will be `ManipulatorPoint::EndHandle` if that point is the end point of its path. - EndHandle(SegmentId), + PriorInHandle(SegmentId), } #[derive(Clone, Debug, Default)] @@ -333,22 +333,22 @@ struct PenToolData { previous_handle_end_pos: Option<DVec2>, toggle_colinear_debounce: bool, colinear: bool, - alt_press: bool, - space_press: bool, + alt_pressed: bool, + space_pressed: bool, /// Tracks whether to switch from `HandleMode::ColinearEquidistant` to `HandleMode::Free` /// after releasing Ctrl, specifically when Ctrl was held before the handle was dragged from the anchor. switch_to_free_on_ctrl_release: bool, /// To prevent showing cursor when `KeyC` is pressed when handles are swapped. - handle_swap: bool, + handle_swapped: bool, /// Prevents conflicts when the handle's angle is already locked and it passes near the anchor, /// avoiding unintended direction changes. Specifically handles the case where a handle is being dragged, /// and Ctrl is pressed near the anchor to make it colinear with its opposite handle. angle_locked: bool, - close_path: bool, + path_closed: bool, handle_mode: HandleMode, - end_point: Option<PointId>, - end_point_segment: Option<SegmentId>, + prior_segment_endpoint: Option<PointId>, + prior_segment: Option<SegmentId>, handle_type: TargetHandle, handle_start_offset: Option<DVec2>, handle_end_offset: Option<DVec2>, @@ -373,13 +373,13 @@ impl PenToolData { /// Check whether target handle is primary, end, or `self.handle_end` fn check_end_handle_type(&self, vector_data: &VectorData) -> TargetHandle { - match (self.handle_end, self.end_point, self.end_point_segment, self.close_path) { - (Some(_), _, _, false) => TargetHandle::HandleEnd, + match (self.handle_end, self.prior_segment_endpoint, self.prior_segment, self.path_closed) { + (Some(_), _, _, false) => TargetHandle::PreviewInHandle, (None, Some(point), Some(segment), false) | (Some(_), Some(point), Some(segment), true) => { if vector_data.segment_start_from_id(segment) == Some(point) { - TargetHandle::PrimaryHandle(segment) + TargetHandle::PriorOutHandle(segment) } else { - TargetHandle::EndHandle(segment) + TargetHandle::PriorInHandle(segment) } } _ => TargetHandle::None, @@ -388,34 +388,34 @@ impl PenToolData { fn check_grs_end_handle(&self, vector_data: &VectorData) -> TargetHandle { let Some(point) = self.latest_point().map(|point| point.id) else { return TargetHandle::None }; - let Some(segment) = self.end_point_segment else { return TargetHandle::None }; + let Some(segment) = self.prior_segment else { return TargetHandle::None }; if vector_data.segment_start_from_id(segment) == Some(point) { - TargetHandle::PrimaryHandle(segment) + TargetHandle::PriorOutHandle(segment) } else { - TargetHandle::EndHandle(segment) + TargetHandle::PriorInHandle(segment) } } fn get_opposite_handle_type(&self, handle_type: TargetHandle, vector_data: &VectorData) -> TargetHandle { match handle_type { - TargetHandle::HandleStart => self.check_end_handle_type(vector_data), - TargetHandle::HandleEnd => match (self.close_path, self.end_point, self.end_point_segment) { - (true, _, _) => TargetHandle::HandleStart, - (false, Some(point), Some(segment)) => { + TargetHandle::FuturePreviewOutHandle => self.check_end_handle_type(vector_data), + TargetHandle::PreviewInHandle => match (self.path_closed, self.prior_segment_endpoint, self.prior_segment) { + (false, _, _) => TargetHandle::FuturePreviewOutHandle, + (true, Some(point), Some(segment)) => { if vector_data.segment_start_from_id(segment) == Some(point) { - TargetHandle::PrimaryHandle(segment) + TargetHandle::PriorOutHandle(segment) } else { - TargetHandle::EndHandle(segment) + TargetHandle::PriorInHandle(segment) } } _ => TargetHandle::None, }, _ => { - if self.close_path { - TargetHandle::HandleEnd + if self.path_closed { + TargetHandle::PreviewInHandle } else { - TargetHandle::HandleStart + TargetHandle::FuturePreviewOutHandle } } } @@ -427,20 +427,20 @@ impl PenToolData { fn update_target_handle_pos(&mut self, handle_type: TargetHandle, anchor_pos: DVec2, responses: &mut VecDeque<Message>, delta: DVec2, layer: LayerNodeIdentifier) { match handle_type { - TargetHandle::HandleStart => { + TargetHandle::FuturePreviewOutHandle => { self.next_handle_start = delta; } - TargetHandle::HandleEnd => { + TargetHandle::PreviewInHandle => { if let Some(handle) = self.handle_end.as_mut() { *handle = delta; } } - TargetHandle::EndHandle(segment) => { + TargetHandle::PriorInHandle(segment) => { let relative_position = delta - anchor_pos; let modification_type = VectorModificationType::SetEndHandle { segment, relative_position }; responses.add(GraphOperationMessage::Vector { layer, modification_type }); } - TargetHandle::PrimaryHandle(segment) => { + TargetHandle::PriorOutHandle(segment) => { let relative_position = delta - anchor_pos; let modification_type = VectorModificationType::SetPrimaryHandle { segment, relative_position }; responses.add(GraphOperationMessage::Vector { layer, modification_type }); @@ -451,10 +451,10 @@ impl PenToolData { fn target_handle_position(&self, handle_type: TargetHandle, vector_data: &VectorData) -> Option<DVec2> { match handle_type { - TargetHandle::PrimaryHandle(segment) => ManipulatorPointId::PrimaryHandle(segment).get_position(vector_data), - TargetHandle::EndHandle(segment) => ManipulatorPointId::EndHandle(segment).get_position(vector_data), - TargetHandle::HandleEnd => self.handle_end, - TargetHandle::HandleStart => Some(self.next_handle_start), + TargetHandle::PriorOutHandle(segment) => ManipulatorPointId::PrimaryHandle(segment).get_position(vector_data), + TargetHandle::PriorInHandle(segment) => ManipulatorPointId::EndHandle(segment).get_position(vector_data), + TargetHandle::PreviewInHandle => self.handle_end, + TargetHandle::FuturePreviewOutHandle => Some(self.next_handle_start), TargetHandle::None => None, } } @@ -470,8 +470,8 @@ impl PenToolData { }; match self.check_end_handle_type(&vector_data) { - TargetHandle::EndHandle(segment) => shape_state.deselect_point(ManipulatorPointId::EndHandle(segment)), - TargetHandle::PrimaryHandle(segment) => shape_state.deselect_point(ManipulatorPointId::PrimaryHandle(segment)), + TargetHandle::PriorInHandle(segment) => shape_state.deselect_point(ManipulatorPointId::EndHandle(segment)), + TargetHandle::PriorOutHandle(segment) => shape_state.deselect_point(ManipulatorPointId::PrimaryHandle(segment)), _ => {} } responses.add(OverlaysMessage::Draw); @@ -484,8 +484,8 @@ impl PenToolData { }; match self.handle_type { - TargetHandle::EndHandle(segment) => shape_state.select_point(ManipulatorPointId::EndHandle(segment)), - TargetHandle::PrimaryHandle(segment) => shape_state.select_point(ManipulatorPointId::PrimaryHandle(segment)), + TargetHandle::PriorInHandle(segment) => shape_state.select_point(ManipulatorPointId::EndHandle(segment)), + TargetHandle::PriorOutHandle(segment) => shape_state.select_point(ManipulatorPointId::PrimaryHandle(segment)), _ => {} } } @@ -519,7 +519,7 @@ impl PenToolData { let document = snap_data.document; self.next_handle_start = self.next_point; let vector_data = document.network_interface.compute_modified_vector(layer).unwrap(); - self.update_handle_type(TargetHandle::HandleStart); + self.update_handle_type(TargetHandle::FuturePreviewOutHandle); self.handle_mode = HandleMode::ColinearLocked; // Break the control @@ -534,8 +534,8 @@ impl PenToolData { self.store_clicked_endpoint(document, &transform, snap_data.input, preferences); if self.modifiers.lock_angle { - self.set_lock_angle(&vector_data, id, self.end_point_segment); - let last_segment = self.end_point_segment; + self.set_lock_angle(&vector_data, id, self.prior_segment); + let last_segment = self.prior_segment; let Some(point) = self.latest_point_mut() else { return }; point.in_segment = last_segment; self.switch_to_free_on_ctrl_release = true; @@ -554,14 +554,14 @@ impl PenToolData { let snap_point_tolerance_squared = crate::consts::SNAP_POINT_TOLERANCE.powi(2); if transformed_distance_between_squared < snap_point_tolerance_squared { - self.update_handle_type(TargetHandle::HandleEnd); + self.update_handle_type(TargetHandle::PreviewInHandle); self.handle_end_offset = None; - self.close_path = true; + self.path_closed = true; self.next_handle_start = self.next_point; self.store_clicked_endpoint(document, &transform, snap_data.input, preferences); self.handle_mode = HandleMode::Free; - if self.modifiers.lock_angle { - self.set_lock_angle(&vector_data, self.end_point.unwrap(), self.end_point_segment); + if let (true, Some(prior_endpoint)) = (self.modifiers.lock_angle, self.prior_segment_endpoint) { + self.set_lock_angle(&vector_data, prior_endpoint, self.prior_segment); self.switch_to_free_on_ctrl_release = true; } } @@ -573,7 +573,7 @@ impl PenToolData { let next_handle_start = self.next_handle_start; let handle_start = self.latest_point()?.handle_start; let mouse = snap_data.input.mouse.position; - self.handle_swap = false; + self.handle_swapped = false; self.handle_end_offset = None; self.handle_start_offset = None; let Some(handle_end) = self.handle_end else { @@ -616,14 +616,14 @@ impl PenToolData { // Store the segment let id = SegmentId::generate(); - if self.close_path { - if let Some((handles, handle1_pos)) = match self.get_opposite_handle_type(TargetHandle::HandleEnd, &vector_data) { - TargetHandle::PrimaryHandle(segment) => { + if self.path_closed { + if let Some((handles, handle1_pos)) = match self.get_opposite_handle_type(TargetHandle::PreviewInHandle, &vector_data) { + TargetHandle::PriorOutHandle(segment) => { let handles = [HandleId::end(id), HandleId::primary(segment)]; let handle1_pos = handles[1].to_manipulator_point().get_position(&vector_data); handle1_pos.map(|pos| (handles, pos)) } - TargetHandle::EndHandle(segment) => { + TargetHandle::PriorInHandle(segment) => { let handles = [HandleId::end(id), HandleId::end(segment)]; let handle1_pos = handles[1].to_manipulator_point().get_position(&vector_data); handle1_pos.map(|pos| (handles, pos)) @@ -640,7 +640,7 @@ impl PenToolData { } } - self.end_point_segment = Some(id); + self.prior_segment = Some(id); let points = [start, end]; let modification_type = VectorModificationType::InsertSegment { id, points, handles }; @@ -673,8 +673,8 @@ impl PenToolData { handle_start: next_handle_start, }); } - self.close_path = false; - self.end_point = None; + self.path_closed = false; + self.prior_segment_endpoint = None; responses.add(DocumentMessage::EndTransaction); Some(if close_subpath { PenToolFsmState::Ready } else { PenToolFsmState::PlacingAnchor }) } @@ -690,10 +690,10 @@ impl PenToolData { vector_data: &VectorData, input: &InputPreprocessorMessageHandler, ) -> Option<DVec2> { - let reference_handle = if self.close_path { TargetHandle::HandleEnd } else { TargetHandle::HandleStart }; + let reference_handle = if self.path_closed { TargetHandle::PreviewInHandle } else { TargetHandle::FuturePreviewOutHandle }; let end_handle = self.get_opposite_handle_type(reference_handle, vector_data); let end_handle_pos = self.target_handle_position(end_handle, vector_data); - let ref_pos = self.target_handle_position(reference_handle, vector_data).unwrap(); + let ref_pos = self.target_handle_position(reference_handle, vector_data)?; let snap = &mut self.snap_manager; let snap_data = SnapData::new_snap_cache(snap_data.document, input, &self.snap_cache); @@ -710,7 +710,7 @@ impl PenToolData { let handle_snap_option = end_handle_pos.and_then(|handle| match end_handle { TargetHandle::None => None, - TargetHandle::HandleStart => None, + TargetHandle::FuturePreviewOutHandle => None, _ => { let handle_offset = transform.transform_point2(handle - ref_pos); let handle_snap = SnapCandidatePoint::handle(document_pos + handle_offset); @@ -764,27 +764,27 @@ impl PenToolData { }; // Determine if we need to swap to opposite handle - let should_swap_to_opposite = self.close_path && matches!(self.handle_type, TargetHandle::HandleEnd | TargetHandle::PrimaryHandle(..) | TargetHandle::EndHandle(..)) - || !self.close_path && matches!(self.handle_type, TargetHandle::HandleStart); + let should_swap_to_opposite = self.path_closed && matches!(self.handle_type, TargetHandle::PreviewInHandle | TargetHandle::PriorOutHandle(..) | TargetHandle::PriorInHandle(..)) + || !self.path_closed && matches!(self.handle_type, TargetHandle::FuturePreviewOutHandle); // Determine if we need to swap to start handle - let should_swap_to_start = !self.close_path && !matches!(self.handle_type, TargetHandle::None | TargetHandle::HandleStart); + let should_swap_to_start = !self.path_closed && !matches!(self.handle_type, TargetHandle::None | TargetHandle::FuturePreviewOutHandle); if should_swap_to_opposite { let opposite_type = self.get_opposite_handle_type(self.handle_type, &vector_data); // Update offset let Some(handle_pos) = self.target_handle_position(opposite_type, &vector_data) else { - self.handle_swap = false; + self.handle_swapped = false; return; }; if (handle_pos - self.next_point).length() < 1e-6 { - self.handle_swap = false; + self.handle_swapped = false; return; } self.handle_end_offset = Some(viewport.transform_point2(handle_pos) - input.mouse.position); // Update selections if in closed path mode - if self.close_path { + if self.path_closed { self.cleanup_target_selections(shape_editor, layer, document, responses); } self.update_handle_type(opposite_type); @@ -798,7 +798,7 @@ impl PenToolData { self.handle_start_offset = Some(transform.transform_point2(self.next_handle_start) - input.mouse.position); } - self.update_handle_type(TargetHandle::HandleStart); + self.update_handle_type(TargetHandle::FuturePreviewOutHandle); } responses.add(FrontendMessage::UpdateMouseCursor { cursor: MouseCursorIcon::None }); @@ -830,7 +830,7 @@ impl PenToolData { } } - let Some(end_point) = self.end_point else { return }; + let Some(end_point) = self.prior_segment_endpoint else { return }; let modification_type_anchor = VectorModificationType::ApplyPointDelta { point: end_point, delta }; responses.add(GraphOperationMessage::Vector { @@ -838,12 +838,12 @@ impl PenToolData { modification_type: modification_type_anchor, }); - let reference_handle = if self.close_path { TargetHandle::HandleEnd } else { TargetHandle::HandleStart }; + let reference_handle = if self.path_closed { TargetHandle::PreviewInHandle } else { TargetHandle::FuturePreviewOutHandle }; // Move the end handle let end_handle_type = self.get_opposite_handle_type(reference_handle, vector_data); match end_handle_type { - TargetHandle::EndHandle(..) | TargetHandle::PrimaryHandle(..) => { + TargetHandle::PriorInHandle(..) | TargetHandle::PriorOutHandle(..) => { let Some(handle_pos) = self.target_handle_position(end_handle_type, vector_data) else { return }; self.update_target_handle_pos(end_handle_type, self.next_point, responses, handle_pos + delta, layer); } @@ -882,6 +882,10 @@ impl PenToolData { if let Some(handle) = self.handle_end.as_mut() { *handle += delta; + if !self.path_closed { + responses.add(OverlaysMessage::Draw); + return Some(PenToolFsmState::DraggingHandle(self.handle_mode)); + }; } self.move_anchor_and_handles(delta, layer, responses, &vector_data); @@ -891,7 +895,7 @@ impl PenToolData { } match self.handle_type { - TargetHandle::HandleStart => { + TargetHandle::FuturePreviewOutHandle => { let offset = self.handle_start_offset.unwrap_or(DVec2::ZERO); mouse_pos += offset; self.next_handle_start = self.compute_snapped_angle(snap_data.clone(), transform, colinear, mouse_pos, Some(self.next_point), false); @@ -928,9 +932,12 @@ impl PenToolData { } if distance < 20. && self.handle_mode == HandleMode::Free && self.modifiers.lock_angle && !self.angle_locked { - self.set_lock_angle(&vector_data, self.end_point.unwrap(), self.end_point_segment); + let Some(endpoint) = self.prior_segment_endpoint else { + return Some(PenToolFsmState::DraggingHandle(self.handle_mode)); + }; + self.set_lock_angle(&vector_data, endpoint, self.prior_segment); self.switch_to_free_on_ctrl_release = true; - let last_segment = self.end_point_segment; + let last_segment = self.prior_segment; if let Some(latest) = self.latest_point_mut() { latest.in_segment = last_segment; } @@ -948,9 +955,9 @@ impl PenToolData { HandleMode::ColinearEquidistant => { if self.modifiers.break_handle { // Store handle for later restoration only when Alt is first pressed - if !self.alt_press { + if !self.alt_pressed { self.previous_handle_end_pos = self.target_handle_position(opposite_handle_type, vector_data); - self.alt_press = true; + self.alt_pressed = true; } // Set handle to opposite position of the other handle @@ -958,12 +965,12 @@ impl PenToolData { return; }; self.update_target_handle_pos(opposite_handle_type, self.next_point, responses, new_position, layer); - } else if self.alt_press { + } else if self.alt_pressed { // Restore the previous handle position when Alt is released if let Some(previous_handle) = self.previous_handle_end_pos { self.update_target_handle_pos(opposite_handle_type, self.next_point, responses, previous_handle, layer); } - self.alt_press = false; + self.alt_pressed = false; self.previous_handle_end_pos = None; } } @@ -984,20 +991,17 @@ impl PenToolData { return; }; - if (anchor_pos - handle).length() < 1e-6 { + if (anchor_pos - handle).length() < 1e-6 && self.modifiers.lock_angle { return; } let Some(direction) = (anchor_pos - handle).try_normalize() else { - log::trace!("Skipping colinear adjustment: handle_start and anchor_point are too close!"); return; }; - let opposite_handle = self.get_opposite_handle_type(self.handle_type, vector_data); let Some(handle_offset) = self.target_handle_position(opposite_handle, vector_data).map(|handle| (handle - anchor_pos).length()) else { return; }; - let new_handle_position = anchor_pos + handle_offset * direction; self.update_target_handle_pos(opposite_handle, self.next_point, responses, new_handle_position, layer); } @@ -1005,7 +1009,7 @@ impl PenToolData { fn place_anchor(&mut self, snap_data: SnapData, transform: DAffine2, mouse: DVec2, preferences: &PreferencesMessageHandler, responses: &mut VecDeque<Message>) -> Option<PenToolFsmState> { let document = snap_data.document; - let relative = if self.close_path { None } else { self.latest_point().map(|point| point.pos) }; + let relative = if self.path_closed { None } else { self.latest_point().map(|point| point.pos) }; self.next_point = self.compute_snapped_angle(snap_data, transform, false, mouse, relative, true); let selected_nodes = document.network_interface.selected_nodes(); @@ -1111,7 +1115,7 @@ impl PenToolData { 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 viewport = document.metadata().document_to_viewport.transform_point2(snapped.snapped_point_document); - self.handle_type = TargetHandle::HandleStart; + self.handle_type = TargetHandle::FuturePreviewOutHandle; let selected_nodes = document.network_interface.selected_nodes(); self.handle_end = None; @@ -1160,7 +1164,7 @@ impl PenToolData { let layer = graph_modification_utils::new_custom(NodeId::new(), nodes, parent, responses); tool_options.fill.apply_fill(layer, responses); tool_options.stroke.apply_stroke(tool_options.line_weight, layer, responses); - self.end_point_segment = None; + self.prior_segment = None; responses.add(NodeGraphMessage::SelectedNodesSet { nodes: vec![layer.to_node()] }); // This causes the following message to be run only after the next graph evaluation runs and the transforms are updated @@ -1205,7 +1209,7 @@ impl PenToolData { (position, None) }; - let in_segment = if self.modifiers.lock_angle { self.end_point_segment } else { in_segment }; + let in_segment = if self.modifiers.lock_angle { self.prior_segment } else { in_segment }; self.add_point(LastPoint { id: point, @@ -1242,10 +1246,10 @@ impl PenToolData { let tolerance = crate::consts::SNAP_POINT_TOLERANCE; if let Some((layer, point, _position)) = closest_point(document, viewport, tolerance, document.metadata().all_layers(), |_| false, preferences) { - self.end_point = Some(point); + self.prior_segment_endpoint = Some(point); let vector_data = document.network_interface.compute_modified_vector(layer).unwrap(); let segment = vector_data.all_connected(point).collect::<Vec<_>>().first().map(|s| s.segment); - self.end_point_segment = segment; + self.prior_segment = segment; layer_manipulators.insert(point); for (&id, &position) in vector_data.point_domain.ids().iter().zip(vector_data.point_domain.positions()) { if id == point { @@ -1266,8 +1270,8 @@ impl PenToolData { return; }; - match (self.handle_type, self.close_path) { - (TargetHandle::HandleStart, _) | (TargetHandle::HandleEnd, true) => { + match (self.handle_type, self.path_closed) { + (TargetHandle::FuturePreviewOutHandle, _) | (TargetHandle::PreviewInHandle, true) => { let is_start = |point: PointId, segment: SegmentId| vector_data.segment_start_from_id(segment) == Some(point); let end_handle = ManipulatorPointId::EndHandle(segment).get_position(vector_data); @@ -1296,7 +1300,7 @@ impl PenToolData { self.handle_mode = HandleMode::ColinearEquidistant; } } - (TargetHandle::EndHandle(..) | TargetHandle::PrimaryHandle(..), true) => { + (TargetHandle::PriorInHandle(..) | TargetHandle::PriorOutHandle(..), true) => { self.angle = -(self.handle_end.unwrap() - anchor_position).angle_to(DVec2::X); self.handle_mode = HandleMode::ColinearEquidistant; } @@ -1529,7 +1533,7 @@ impl Fsm for PenToolFsmState { path_overlays(document, DrawHandles::All, shape_editor, &mut overlay_context); } PenOverlayMode::FrontierHandles => { - if let Some(latest_segment) = tool_data.end_point_segment { + if let Some(latest_segment) = tool_data.prior_segment { path_overlays(document, DrawHandles::SelectedAnchors(vec![latest_segment]), shape_editor, &mut overlay_context); } else { path_overlays(document, DrawHandles::None, shape_editor, &mut overlay_context); @@ -1556,7 +1560,7 @@ impl Fsm for PenToolFsmState { if self == PenToolFsmState::DraggingHandle(tool_data.handle_mode) && valid(next_anchor, handle_end) { // Draw the handle circle for the currently-being-dragged-out incoming handle (opposite the one currently being dragged out) - let selected = tool_data.handle_type == TargetHandle::HandleEnd; + let selected = tool_data.handle_type == TargetHandle::PreviewInHandle; overlay_context.manipulator_handle(handle_end, selected, None); } @@ -1578,7 +1582,7 @@ impl Fsm for PenToolFsmState { if self == PenToolFsmState::DraggingHandle(tool_data.handle_mode) && valid(next_anchor, next_handle_start) { // Draw the handle circle for the currently-being-dragged-out outgoing handle (the one currently being dragged out, under the user's cursor) - let selected = tool_data.handle_type == TargetHandle::HandleStart; + let selected = tool_data.handle_type == TargetHandle::FuturePreviewOutHandle; overlay_context.manipulator_handle(next_handle_start, selected, None); } @@ -1695,7 +1699,7 @@ impl Fsm for PenToolFsmState { if tool_data.modifiers.colinear && !tool_data.toggle_colinear_debounce { tool_data.handle_mode = match tool_data.handle_mode { HandleMode::Free => { - let last_segment = tool_data.end_point_segment; + let last_segment = tool_data.prior_segment; if let Some(latest) = tool_data.latest_point_mut() { latest.in_segment = last_segment; } @@ -1710,8 +1714,12 @@ impl Fsm for PenToolFsmState { return self; }; - if tool_data.modifiers.move_anchor_with_handles && !tool_data.space_press { - let reference_handle = if tool_data.close_path { TargetHandle::HandleEnd } else { TargetHandle::HandleStart }; + if tool_data.modifiers.move_anchor_with_handles && !tool_data.space_pressed { + let reference_handle = if tool_data.path_closed { + TargetHandle::PreviewInHandle + } else { + TargetHandle::FuturePreviewOutHandle + }; let handle_start = layer.map(|layer| { document .metadata() @@ -1719,11 +1727,11 @@ impl Fsm for PenToolFsmState { .transform_point2(tool_data.target_handle_position(reference_handle, &vector_data).unwrap()) }); tool_data.handle_start_offset = handle_start.map(|start| start - input.mouse.position); - tool_data.space_press = true; + tool_data.space_pressed = true; } if !tool_data.modifiers.move_anchor_with_handles { - tool_data.space_press = false; + tool_data.space_pressed = false; } if !tool_data.modifiers.colinear { @@ -1738,7 +1746,7 @@ impl Fsm for PenToolFsmState { .drag_handle(snap_data, transform, input.mouse.position, responses, layer, input) .unwrap_or(PenToolFsmState::Ready); - if tool_data.handle_swap { + if tool_data.handle_swapped { responses.add(FrontendMessage::UpdateMouseCursor { cursor: MouseCursorIcon::None }); } @@ -1775,7 +1783,8 @@ impl Fsm for PenToolFsmState { move_anchor_with_handles, }, ) => { - tool_data.alt_press = false; + tool_data.switch_to_free_on_ctrl_release = false; + tool_data.alt_pressed = false; tool_data.modifiers = ModifierState { snap_angle: input.keyboard.key(snap_angle), lock_angle: input.keyboard.key(lock_angle), @@ -1811,8 +1820,8 @@ impl Fsm for PenToolFsmState { state } (PenToolFsmState::DraggingHandle(_), PenToolMessage::SwapHandles) => { - if !tool_data.handle_swap { - tool_data.handle_swap = true + if !tool_data.handle_swapped { + tool_data.handle_swapped = true } tool_data.swap_handles(layer, document, shape_editor, input, responses); responses.add(OverlaysMessage::Draw); From 24eb19e8cedf9de3f28a5a5e0dfddcaff72d4f18 Mon Sep 17 00:00:00 2001 From: Keavon Chambers <keavon@keavon.com> Date: Mon, 7 Apr 2025 22:32:44 -0700 Subject: [PATCH 19/21] Add terminology diagram --- .../messages/tool/tool_messages/pen_tool.rs | 25 +++++++++++++------ 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/editor/src/messages/tool/tool_messages/pen_tool.rs b/editor/src/messages/tool/tool_messages/pen_tool.rs index 3ee6bb9652..1b03892218 100644 --- a/editor/src/messages/tool/tool_messages/pen_tool.rs +++ b/editor/src/messages/tool/tool_messages/pen_tool.rs @@ -283,21 +283,26 @@ enum HandleMode { } /// The type of handle which is dragged by the cursor (under the cursor). +/// +///  #[derive(Clone, Debug, Default, PartialEq, Copy)] enum TargetHandle { #[default] None, - /// This is the handle being dragged and represents the primary handle - /// of the next segment that will be placed after the current handle and next anchor are placed. - /// Its position is stored in `tool_data.next_handle_start`. + /// This is the handle being dragged and represents the out handle of the next preview segment that will be placed + /// after the current preview segment is finalized. Its position is stored in `tool_data.next_handle_start`. /// - /// Pressing Tab swaps to the opposite handle type. The swapped handle can be either `PreviewInHandle` or, - /// in the case of a bent segment, [`ManipulatorPointId::EndHandle`] or [`ManipulatorPointId::PrimaryHandle`]. + /// Pressing Tab swaps to the opposite handle type. The swapped handle can be either [`ManipulatorPointId::PreviewInHandle`] + /// or, in the case of a bent segment, [`ManipulatorPointId::EndHandle`] or [`ManipulatorPointId::PrimaryHandle`]. /// /// When closing a path, the handle being dragged becomes the end handle of the currently placed anchor. + /// + ///  FuturePreviewOutHandle, - /// The opposite handle that is drawn after placing an anchor and starting to drag - /// the "next handle start", continuing until Tab is pressed to swap the handles. + /// The opposite handle that is drawn after placing an anchor and starting to drag the "next handle start", + /// continuing until Tab is pressed to swap the handles. + /// + ///  PreviewInHandle, /// This is the primary handle of the segment from whose endpoint a new handle is being drawn. /// When closing the path, the handle being dragged will be the [`TargetHandle::PreviewInHandle`] (see its documentation); @@ -305,10 +310,14 @@ enum TargetHandle { /// /// If a handle is dragged from a different endpoint within the same layer, the opposite handle will be /// `ManipulatorPoint::Primary` if that point is the starting point of its path. + /// + ///  PriorOutHandle(SegmentId), /// This is the end handle of the segment from whose endpoint a new handle is being drawn (same cases apply /// as mentioned in [`TargetHandle::PriorOutHandle`]). If a handle is dragged from a different endpoint within the same /// layer, the opposite handle will be `ManipulatorPoint::EndHandle` if that point is the end point of its path. + /// + ///  PriorInHandle(SegmentId), } @@ -401,7 +410,6 @@ impl PenToolData { match handle_type { TargetHandle::FuturePreviewOutHandle => self.check_end_handle_type(vector_data), TargetHandle::PreviewInHandle => match (self.path_closed, self.prior_segment_endpoint, self.prior_segment) { - (false, _, _) => TargetHandle::FuturePreviewOutHandle, (true, Some(point), Some(segment)) => { if vector_data.segment_start_from_id(segment) == Some(point) { TargetHandle::PriorOutHandle(segment) @@ -409,6 +417,7 @@ impl PenToolData { TargetHandle::PriorInHandle(segment) } } + (false, _, _) => TargetHandle::FuturePreviewOutHandle, _ => TargetHandle::None, }, _ => { From 3054a3186e58bb4d7a8ada839848a1d22c8c179f Mon Sep 17 00:00:00 2001 From: Keavon Chambers <keavon@keavon.com> Date: Mon, 7 Apr 2025 22:59:35 -0700 Subject: [PATCH 20/21] Add Ctrl "Lock Angle" hint --- editor/src/messages/tool/tool_messages/pen_tool.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/editor/src/messages/tool/tool_messages/pen_tool.rs b/editor/src/messages/tool/tool_messages/pen_tool.rs index 1b03892218..40b3647cec 100644 --- a/editor/src/messages/tool/tool_messages/pen_tool.rs +++ b/editor/src/messages/tool/tool_messages/pen_tool.rs @@ -1976,7 +1976,11 @@ impl Fsm for PenToolFsmState { ]), HintGroup(vec![HintInfo::keys([Key::Shift], "15° Increments"), HintInfo::keys([Key::Control], "Lock Angle")]), HintGroup(vec![HintInfo::mouse(MouseMotion::Lmb, "Add Sharp Point"), HintInfo::mouse(MouseMotion::LmbDrag, "Add Smooth Point")]), - HintGroup(vec![HintInfo::mouse(MouseMotion::Lmb, ""), HintInfo::mouse(MouseMotion::LmbDrag, "Bend Prev. Point").prepend_slash()]), + HintGroup(vec![ + HintInfo::mouse(MouseMotion::Lmb, ""), + HintInfo::mouse(MouseMotion::LmbDrag, "Bend Prev. Point").prepend_slash(), + HintInfo::keys([Key::Control], "Lock Angle").prepend_plus(), + ]), ]), PenToolFsmState::DraggingHandle(mode) => { let mut dragging_hint_data = HintData(Vec::new()); From 5ee4239fce009cf4afa6ffac03950eb9be478f29 Mon Sep 17 00:00:00 2001 From: Keavon Chambers <keavon@keavon.com> Date: Mon, 7 Apr 2025 23:17:42 -0700 Subject: [PATCH 21/21] Rename other hint --- editor/src/messages/tool/tool_messages/path_tool.rs | 2 +- editor/src/messages/tool/tool_messages/pen_tool.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/editor/src/messages/tool/tool_messages/path_tool.rs b/editor/src/messages/tool/tool_messages/path_tool.rs index 4aac4f15c3..f724f1d742 100644 --- a/editor/src/messages/tool/tool_messages/path_tool.rs +++ b/editor/src/messages/tool/tool_messages/path_tool.rs @@ -1468,7 +1468,7 @@ impl Fsm for PathToolFsmState { let drag_anchor = HintInfo::keys([Key::Space], "Drag Anchor"); let toggle_group = match dragging_state.point_select_state { PointSelectState::HandleNoPair | PointSelectState::HandleWithPair => { - let mut hints = vec![HintInfo::keys([Key::Tab], "Swap Selected Handles")]; + let mut hints = vec![HintInfo::keys([Key::Tab], "Swap Dragged Handle")]; hints.push(HintInfo::keys( [Key::KeyC], if colinear == ManipulatorAngle::Colinear { diff --git a/editor/src/messages/tool/tool_messages/pen_tool.rs b/editor/src/messages/tool/tool_messages/pen_tool.rs index 40b3647cec..498bd53fb0 100644 --- a/editor/src/messages/tool/tool_messages/pen_tool.rs +++ b/editor/src/messages/tool/tool_messages/pen_tool.rs @@ -1998,7 +1998,7 @@ impl Fsm for PenToolFsmState { vec![HintInfo::keys([Key::KeyC], "Break Colinear Handles")] } }; - toggle_group.push(HintInfo::keys([Key::Tab], "Swap Selected Handles")); + toggle_group.push(HintInfo::keys([Key::Tab], "Swap Dragged Handle")); let mut common_hints = vec![HintInfo::keys([Key::Shift], "15° Increments"), HintInfo::keys([Key::Control], "Lock Angle")]; let mut hold_group = match mode {