Skip to content

Commit d080c05

Browse files
4adexKeavon
andauthored
Add Pen tool support for starting and ending segment drawing on existing path edges for vector meshes (#2692)
* Start on segment * Path tool ending on segment * Overlays for feature * Fixed merge build * Fix overlays * Code review --------- Co-authored-by: Keavon Chambers <keavon@keavon.com>
1 parent ca783aa commit d080c05

3 files changed

Lines changed: 126 additions & 11 deletions

File tree

editor/src/messages/tool/common_functionality/graph_modification_utils.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -57,8 +57,10 @@ pub fn merge_layers(document: &DocumentMessageHandler, first_layer: LayerNodeIde
5757
}
5858

5959
// Move the `second_layer` below the `first_layer` for positioning purposes
60-
let first_layer_parent = first_layer.parent(document.metadata()).unwrap();
61-
let first_layer_index = first_layer_parent.children(document.metadata()).position(|child| child == first_layer).unwrap();
60+
let Some(first_layer_parent) = first_layer.parent(document.metadata()) else { return };
61+
let Some(first_layer_index) = first_layer_parent.children(document.metadata()).position(|child| child == first_layer) else {
62+
return;
63+
};
6264
responses.add(NodeGraphMessage::MoveLayerToStack {
6365
layer: second_layer,
6466
parent: first_layer_parent,

editor/src/messages/tool/common_functionality/shape_editor.rs

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,10 @@ impl ClosestSegment {
159159
self.points
160160
}
161161

162+
pub fn closest_point_document(&self) -> DVec2 {
163+
self.bezier.evaluate(TValue::Parametric(self.t))
164+
}
165+
162166
pub fn closest_point_to_viewport(&self) -> DVec2 {
163167
self.bezier_point_to_viewport
164168
}
@@ -204,7 +208,7 @@ impl ClosestSegment {
204208
(first_handle, second_handle)
205209
}
206210

207-
pub fn adjusted_insert(&self, responses: &mut VecDeque<Message>) -> PointId {
211+
pub fn adjusted_insert(&self, responses: &mut VecDeque<Message>) -> (PointId, [SegmentId; 2]) {
208212
let layer = self.layer;
209213
let [first, second] = self.bezier.split(TValue::Parametric(self.t));
210214

@@ -249,11 +253,11 @@ impl ClosestSegment {
249253
responses.add(GraphOperationMessage::Vector { layer, modification_type });
250254
}
251255

252-
midpoint
256+
(midpoint, segment_ids)
253257
}
254258

255259
pub fn adjusted_insert_and_select(&self, shape_editor: &mut ShapeState, responses: &mut VecDeque<Message>, extend_selection: bool) {
256-
let id = self.adjusted_insert(responses);
260+
let (id, _) = self.adjusted_insert(responses);
257261
shape_editor.select_anchor_point_by_id(self.layer, id, extend_selection)
258262
}
259263

editor/src/messages/tool/tool_messages/pen_tool.rs

Lines changed: 115 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
use super::tool_prelude::*;
2-
use crate::consts::{COLOR_OVERLAY_BLUE, DEFAULT_STROKE_WIDTH, HIDE_HANDLE_DISTANCE, LINE_ROTATE_SNAP_ANGLE};
2+
use crate::consts::{COLOR_OVERLAY_BLUE, DEFAULT_STROKE_WIDTH, HIDE_HANDLE_DISTANCE, LINE_ROTATE_SNAP_ANGLE, SEGMENT_OVERLAY_SIZE};
33
use crate::messages::input_mapper::utility_types::input_mouse::MouseKeys;
44
use crate::messages::portfolio::document::node_graph::document_node_definitions::resolve_document_node_type;
55
use crate::messages::portfolio::document::overlays::utility_functions::path_overlays;
@@ -361,6 +361,9 @@ struct PenToolData {
361361
current_layer: Option<LayerNodeIdentifier>,
362362
prior_segment_endpoint: Option<PointId>,
363363
prior_segment: Option<SegmentId>,
364+
365+
/// For vector meshes, storing all the previous segments the last anchor point was connected to
366+
prior_segments: Option<Vec<SegmentId>>,
364367
handle_type: TargetHandle,
365368
handle_start_offset: Option<DVec2>,
366369
handle_end_offset: Option<DVec2>,
@@ -533,7 +536,15 @@ impl PenToolData {
533536
}
534537

535538
/// If the user places the anchor on top of the previous anchor, it becomes sharp and the outgoing handle may be dragged.
536-
fn bend_from_previous_point(&mut self, snap_data: SnapData, transform: DAffine2, layer: LayerNodeIdentifier, preferences: &PreferencesMessageHandler) {
539+
fn bend_from_previous_point(
540+
&mut self,
541+
snap_data: SnapData,
542+
transform: DAffine2,
543+
layer: LayerNodeIdentifier,
544+
preferences: &PreferencesMessageHandler,
545+
shape_editor: &mut ShapeState,
546+
responses: &mut VecDeque<Message>,
547+
) {
537548
self.g1_continuous = true;
538549
let document = snap_data.document;
539550
self.next_handle_start = self.next_point;
@@ -567,6 +578,43 @@ impl PenToolData {
567578
}
568579

569580
// Closing path
581+
let closing_path_on_point = self.close_path_on_point(snap_data, &vector_data, document, preferences, id, &transform);
582+
if !closing_path_on_point && preferences.vector_meshes {
583+
// Attempt to find nearest segment and close path on segment by creating an anchor point on it
584+
let tolerance = crate::consts::SNAP_POINT_TOLERANCE;
585+
if let Some(closest_segment) = shape_editor.upper_closest_segment(&document.network_interface, transform.transform_point2(self.next_point), tolerance) {
586+
let (point, _) = closest_segment.adjusted_insert(responses);
587+
588+
self.update_handle_type(TargetHandle::PreviewInHandle);
589+
self.handle_end_offset = None;
590+
self.path_closed = true;
591+
self.next_handle_start = self.next_point;
592+
593+
self.prior_segment_endpoint = Some(point);
594+
self.prior_segment_layer = Some(closest_segment.layer());
595+
self.prior_segments = None;
596+
self.prior_segment = None;
597+
598+
// Should also update the SnapCache here?
599+
600+
self.handle_mode = HandleMode::Free;
601+
if let (true, Some(prior_endpoint)) = (self.modifiers.lock_angle, self.prior_segment_endpoint) {
602+
self.set_lock_angle(&vector_data, prior_endpoint, self.prior_segment);
603+
self.switch_to_free_on_ctrl_release = true;
604+
}
605+
}
606+
}
607+
}
608+
609+
fn close_path_on_point(
610+
&mut self,
611+
snap_data: SnapData,
612+
vector_data: &VectorData,
613+
document: &DocumentMessageHandler,
614+
preferences: &PreferencesMessageHandler,
615+
id: PointId,
616+
transform: &DAffine2,
617+
) -> bool {
570618
for id in vector_data.extendable_points(preferences.vector_meshes).filter(|&point| point != id) {
571619
let Some(pos) = vector_data.point_domain.position_from_id(id) else { continue };
572620
let transformed_distance_between_squared = transform.transform_point2(pos).distance_squared(transform.transform_point2(self.next_point));
@@ -577,14 +625,16 @@ impl PenToolData {
577625
self.handle_end_offset = None;
578626
self.path_closed = true;
579627
self.next_handle_start = self.next_point;
580-
self.store_clicked_endpoint(document, &transform, snap_data.input, preferences);
628+
self.store_clicked_endpoint(document, transform, snap_data.input, preferences);
581629
self.handle_mode = HandleMode::Free;
582630
if let (true, Some(prior_endpoint)) = (self.modifiers.lock_angle, self.prior_segment_endpoint) {
583-
self.set_lock_angle(&vector_data, prior_endpoint, self.prior_segment);
631+
self.set_lock_angle(vector_data, prior_endpoint, self.prior_segment);
584632
self.switch_to_free_on_ctrl_release = true;
585633
}
634+
return true;
586635
}
587636
}
637+
false
588638
}
589639

590640
fn finish_placing_handle(&mut self, snap_data: SnapData, transform: DAffine2, preferences: &PreferencesMessageHandler, responses: &mut VecDeque<Message>) -> Option<PenToolFsmState> {
@@ -1122,6 +1172,7 @@ impl PenToolData {
11221172
transform.inverse().transform_point2(document_pos)
11231173
}
11241174

1175+
#[allow(clippy::too_many_arguments)]
11251176
fn create_initial_point(
11261177
&mut self,
11271178
document: &DocumentMessageHandler,
@@ -1130,6 +1181,7 @@ impl PenToolData {
11301181
tool_options: &PenOptions,
11311182
append: bool,
11321183
preferences: &PreferencesMessageHandler,
1184+
shape_editor: &mut ShapeState,
11331185
) {
11341186
let point = SnapCandidatePoint::handle(document.metadata().document_to_viewport.inverse().transform_point2(input.mouse.position));
11351187
let snapped = self.snap_manager.free_snap(&SnapData::new(document, input), &point, SnapTypeConfiguration::default());
@@ -1145,6 +1197,20 @@ impl PenToolData {
11451197
self.current_layer = Some(layer);
11461198
self.extend_existing_path(document, layer, point, position);
11471199
return;
1200+
} else if preferences.vector_meshes {
1201+
if let Some(closest_segment) = shape_editor.upper_closest_segment(&document.network_interface, viewport, tolerance) {
1202+
let (point, segments) = closest_segment.adjusted_insert(responses);
1203+
let layer = closest_segment.layer();
1204+
let position = closest_segment.closest_point_document();
1205+
1206+
// Setting any one of the new segments created as the previous segment
1207+
self.prior_segment_endpoint = Some(point);
1208+
self.prior_segment_layer = Some(layer);
1209+
self.prior_segments = Some(segments.to_vec());
1210+
1211+
self.extend_existing_path(document, layer, point, position);
1212+
return;
1213+
}
11481214
}
11491215

11501216
if append {
@@ -1186,6 +1252,7 @@ impl PenToolData {
11861252
tool_options.fill.apply_fill(layer, responses);
11871253
tool_options.stroke.apply_stroke(tool_options.line_weight, layer, responses);
11881254
self.prior_segment = None;
1255+
self.prior_segments = None;
11891256
responses.add(NodeGraphMessage::SelectedNodesSet { nodes: vec![layer.to_node()] });
11901257

11911258
// This causes the following message to be run only after the next graph evaluation runs and the transforms are updated
@@ -1266,6 +1333,7 @@ impl PenToolData {
12661333
self.prior_segment = None;
12671334
self.prior_segment_endpoint = None;
12681335
self.prior_segment_layer = None;
1336+
self.prior_segments = None;
12691337

12701338
if let Some((layer, point, _position)) = closest_point(document, viewport, tolerance, document.metadata().all_layers(), |_| false, preferences) {
12711339
self.prior_segment_endpoint = Some(point);
@@ -1493,6 +1561,22 @@ impl Fsm for PenToolFsmState {
14931561
path_overlays(document, DrawHandles::None, shape_editor, &mut overlay_context);
14941562
}
14951563
}
1564+
// Check if there is an anchor within threshold
1565+
// If not check if there is a closest segment within threshold, if yes then draw overlay
1566+
let tolerance = crate::consts::SNAP_POINT_TOLERANCE;
1567+
let point = SnapCandidatePoint::handle(document.metadata().document_to_viewport.inverse().transform_point2(input.mouse.position));
1568+
let snapped = tool_data.snap_manager.free_snap(&SnapData::new(document, input), &point, SnapTypeConfiguration::default());
1569+
let viewport = document.metadata().document_to_viewport.transform_point2(snapped.snapped_point_document);
1570+
1571+
let close_to_point = closest_point(document, viewport, tolerance, document.metadata().all_layers(), |_| false, preferences).is_some();
1572+
if preferences.vector_meshes && !close_to_point {
1573+
if let Some(closest_segment) = shape_editor.upper_closest_segment(&document.network_interface, viewport, tolerance) {
1574+
let pos = closest_segment.closest_point_to_viewport();
1575+
let perp = closest_segment.calculate_perp(document);
1576+
overlay_context.manipulator_anchor(pos, true, None);
1577+
overlay_context.line(pos - perp * SEGMENT_OVERLAY_SIZE, pos + perp * SEGMENT_OVERLAY_SIZE, Some(COLOR_OVERLAY_BLUE), None);
1578+
}
1579+
}
14961580
tool_data.snap_manager.draw_overlays(SnapData::new(document, input), &mut overlay_context);
14971581
self
14981582
}
@@ -1530,13 +1614,20 @@ impl Fsm for PenToolFsmState {
15301614
// Draw the line between the currently-being-placed anchor and its currently-being-dragged-out outgoing handle (opposite the one currently being dragged out)
15311615
overlay_context.line(next_anchor, next_handle_start, None, None);
15321616
}
1617+
15331618
match tool_options.pen_overlay_mode {
15341619
PenOverlayMode::AllHandles => {
15351620
path_overlays(document, DrawHandles::All, shape_editor, &mut overlay_context);
15361621
}
15371622
PenOverlayMode::FrontierHandles => {
15381623
if let Some(latest_segment) = tool_data.prior_segment {
15391624
path_overlays(document, DrawHandles::SelectedAnchors(vec![latest_segment]), shape_editor, &mut overlay_context);
1625+
}
1626+
// If a vector mesh then there can be more than one prior segments
1627+
else if let Some(segments) = tool_data.prior_segments.clone() {
1628+
if preferences.vector_meshes {
1629+
path_overlays(document, DrawHandles::SelectedAnchors(segments), shape_editor, &mut overlay_context);
1630+
}
15401631
} else {
15411632
path_overlays(document, DrawHandles::None, shape_editor, &mut overlay_context);
15421633
};
@@ -1598,6 +1689,22 @@ impl Fsm for PenToolFsmState {
15981689
overlay_context.manipulator_anchor(next_anchor, false, None);
15991690
}
16001691

1692+
if self == PenToolFsmState::PlacingAnchor && preferences.vector_meshes {
1693+
let tolerance = crate::consts::SNAP_POINT_TOLERANCE;
1694+
let point = SnapCandidatePoint::handle(document.metadata().document_to_viewport.inverse().transform_point2(input.mouse.position));
1695+
let snapped = tool_data.snap_manager.free_snap(&SnapData::new(document, input), &point, SnapTypeConfiguration::default());
1696+
let viewport = document.metadata().document_to_viewport.transform_point2(snapped.snapped_point_document);
1697+
let close_to_point = closest_point(document, viewport, tolerance, document.metadata().all_layers(), |_| false, preferences).is_some();
1698+
if !close_to_point {
1699+
if let Some(closest_segment) = shape_editor.upper_closest_segment(&document.network_interface, viewport, tolerance) {
1700+
let pos = closest_segment.closest_point_to_viewport();
1701+
let perp = closest_segment.calculate_perp(document);
1702+
overlay_context.manipulator_anchor(pos, true, None);
1703+
overlay_context.line(pos - perp * SEGMENT_OVERLAY_SIZE, pos + perp * SEGMENT_OVERLAY_SIZE, Some(COLOR_OVERLAY_BLUE), None);
1704+
}
1705+
}
1706+
}
1707+
16011708
// Display a filled overlay of the shape if the new point closes the path
16021709
if let Some(latest_point) = tool_data.latest_point() {
16031710
let handle_start = latest_point.handle_start;
@@ -1663,8 +1770,10 @@ impl Fsm for PenToolFsmState {
16631770
tool_data.handle_mode = HandleMode::Free;
16641771

16651772
// Get the closest point and the segment it is on
1773+
let append = input.keyboard.key(append_to_selected);
1774+
16661775
tool_data.store_clicked_endpoint(document, &transform, input, preferences);
1667-
tool_data.create_initial_point(document, input, responses, tool_options, input.keyboard.key(append_to_selected), preferences);
1776+
tool_data.create_initial_point(document, input, responses, tool_options, append, preferences, shape_editor);
16681777

16691778
// Enter the dragging handle state while the mouse is held down, allowing the user to move the mouse and position the handle
16701779
PenToolFsmState::DraggingHandle(tool_data.handle_mode)
@@ -1688,7 +1797,7 @@ impl Fsm for PenToolFsmState {
16881797
if let Some(layer) = layer {
16891798
tool_data.buffering_merged_vector = false;
16901799
tool_data.handle_mode = HandleMode::ColinearLocked;
1691-
tool_data.bend_from_previous_point(SnapData::new(document, input), transform, layer, preferences);
1800+
tool_data.bend_from_previous_point(SnapData::new(document, input), transform, layer, preferences, shape_editor, responses);
16921801
tool_data.place_anchor(SnapData::new(document, input), transform, input.mouse.position, preferences, responses);
16931802
}
16941803
tool_data.buffering_merged_vector = false;

0 commit comments

Comments
 (0)