Skip to content

Commit 6effb2d

Browse files
bakayuKeavon
andauthored
In Path tool when dragging a handle, make Alt recover the opposing handle if it's not cubic rather than zero-length (#2196)
* follow up for PR #2160 * now `alt` restores zero-length handles * Update editor/src/messages/tool/tool_messages/path_tool.rs --------- Co-authored-by: Keavon Chambers <keavon@keavon.com>
1 parent 37db9b1 commit 6effb2d

File tree

2 files changed

+63
-18
lines changed

2 files changed

+63
-18
lines changed

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

+5-2
Original file line numberDiff line numberDiff line change
@@ -293,18 +293,21 @@ fn generate_transform(tool_data: &mut LineToolData, snap_data: SnapData, lock_an
293293

294294
let mut angle = -(document_points[1] - document_points[0]).angle_to(DVec2::X);
295295
let mut line_length = (document_points[1] - document_points[0]).length();
296+
296297
if lock_angle {
297298
angle = tool_data.angle;
298-
}
299-
if snap_angle {
299+
} else if snap_angle {
300300
let snap_resolution = LINE_ROTATE_SNAP_ANGLE.to_radians();
301301
angle = (angle / snap_resolution).round() * snap_resolution;
302302
}
303303

304+
tool_data.angle = angle;
305+
304306
if lock_angle {
305307
let angle_vec = DVec2::new(angle.cos(), angle.sin());
306308
line_length = (document_points[1] - document_points[0]).dot(angle_vec);
307309
}
310+
308311
document_points[1] = document_points[0] + line_length * DVec2::new(angle.cos(), angle.sin());
309312

310313
let constrained = snap_angle || lock_angle;

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

+58-16
Original file line numberDiff line numberDiff line change
@@ -301,6 +301,7 @@ struct PathToolData {
301301
saved_points_before_anchor_select_toggle: Vec<ManipulatorPointId>,
302302
select_anchor_toggled: bool,
303303
dragging_state: DraggingState,
304+
current_selected_handle_id: Option<ManipulatorPointId>,
304305
angle: f64,
305306
}
306307

@@ -450,15 +451,20 @@ impl PathToolData {
450451
}
451452

452453
fn update_colinear(&mut self, equidistant: bool, toggle_colinear: bool, shape_editor: &mut ShapeState, document: &DocumentMessageHandler, responses: &mut VecDeque<Message>) -> bool {
454+
// Check handle colinear state
455+
let is_colinear = self
456+
.selection_status
457+
.angle()
458+
.map(|angle| match angle {
459+
ManipulatorAngle::Colinear => true,
460+
ManipulatorAngle::Free | ManipulatorAngle::Mixed => false,
461+
})
462+
.unwrap_or(false);
463+
453464
// Check if the toggle_colinear key has just been pressed
454465
if toggle_colinear && !self.toggle_colinear_debounce {
455466
self.opposing_handle_lengths = None;
456-
let colinear = self.selection_status.angle().is_some_and(|angle| match angle {
457-
ManipulatorAngle::Colinear => true,
458-
ManipulatorAngle::Free => false,
459-
ManipulatorAngle::Mixed => false,
460-
});
461-
if colinear {
467+
if is_colinear {
462468
shape_editor.disable_colinear_handles_state_on_selected(&document.network_interface, responses);
463469
} else {
464470
shape_editor.convert_selected_manipulators_to_colinear_handles(responses, document);
@@ -469,13 +475,46 @@ impl PathToolData {
469475
self.toggle_colinear_debounce = toggle_colinear;
470476

471477
if equidistant && self.opposing_handle_lengths.is_none() {
478+
if !is_colinear {
479+
// Try to get selected handle info
480+
let Some((_, _, selected_handle_id)) = self.try_get_selected_handle_and_anchor(shape_editor, document) else {
481+
self.opposing_handle_lengths = Some(shape_editor.opposing_handle_lengths(document));
482+
return false;
483+
};
484+
485+
let Some((layer, _)) = shape_editor.selected_shape_state.iter().next() else {
486+
self.opposing_handle_lengths = Some(shape_editor.opposing_handle_lengths(document));
487+
return false;
488+
};
489+
490+
let Some(vector_data) = document.network_interface.compute_modified_vector(*layer) else {
491+
self.opposing_handle_lengths = Some(shape_editor.opposing_handle_lengths(document));
492+
return false;
493+
};
494+
495+
// Check if handle has a pair (to ignore handles of edges of open paths)
496+
if let Some(handle_pair) = selected_handle_id.get_handle_pair(&vector_data) {
497+
let opposite_handle_length = handle_pair.iter().filter(|&&h| h.to_manipulator_point() != selected_handle_id).find_map(|&h| {
498+
let opp_handle_pos = h.to_manipulator_point().get_position(&vector_data)?;
499+
let opp_anchor_id = h.to_manipulator_point().get_anchor(&vector_data)?;
500+
let opp_anchor_pos = vector_data.point_domain.position_from_id(opp_anchor_id)?;
501+
Some((opp_handle_pos - opp_anchor_pos).length())
502+
});
503+
504+
// Make handles colinear if opposite handle is zero length
505+
if opposite_handle_length.map_or(false, |l| l == 0.) {
506+
shape_editor.convert_selected_manipulators_to_colinear_handles(responses, document);
507+
return true;
508+
}
509+
}
510+
}
472511
self.opposing_handle_lengths = Some(shape_editor.opposing_handle_lengths(document));
473512
}
474513
false
475514
}
476515

477516
/// Attempts to get a single selected handle. Also retrieves the position of the anchor it is connected to. Used for the purpose of snapping the angle.
478-
fn try_get_selected_handle_and_anchor(&self, shape_editor: &ShapeState, document: &DocumentMessageHandler) -> Option<(DVec2, DVec2)> {
517+
fn try_get_selected_handle_and_anchor(&self, shape_editor: &ShapeState, document: &DocumentMessageHandler) -> Option<(DVec2, DVec2, ManipulatorPointId)> {
479518
// Only count selections of a single layer
480519
let (layer, selection) = shape_editor.selected_shape_state.iter().next()?;
481520

@@ -486,6 +525,7 @@ impl PathToolData {
486525

487526
// Only count selected handles
488527
let selected_handle = selection.selected().next()?.as_handle()?;
528+
let handle_id = selected_handle.to_manipulator_point();
489529

490530
let layer_to_document = document.metadata().transform_to_document(*layer);
491531
let vector_data = document.network_interface.compute_modified_vector(*layer)?;
@@ -497,24 +537,26 @@ impl PathToolData {
497537
let handle_position_document = layer_to_document.transform_point2(handle_position_local);
498538
let anchor_position_document = layer_to_document.transform_point2(anchor_position_local);
499539

500-
Some((handle_position_document, anchor_position_document))
540+
Some((handle_position_document, anchor_position_document, handle_id))
501541
}
502542

503-
fn calculate_handle_angle(&mut self, handle_vector: DVec2, lock_angle: bool, snap_angle: bool) -> f64 {
504-
let mut handle_angle = -handle_vector.angle_to(DVec2::X);
543+
fn calculate_handle_angle(&mut self, handle_vector: DVec2, handle_id: ManipulatorPointId, lock_angle: bool, snap_angle: bool) -> f64 {
544+
let current_angle = -handle_vector.angle_to(DVec2::X);
505545

506546
// When the angle is locked we use the old angle
507-
if lock_angle {
508-
handle_angle = self.angle
547+
if self.current_selected_handle_id == Some(handle_id) && lock_angle {
548+
return self.angle;
509549
}
510550

511551
// Round the angle to the closest increment
512-
if snap_angle {
552+
let mut handle_angle = current_angle;
553+
if snap_angle && !lock_angle {
513554
let snap_resolution = HANDLE_ROTATE_SNAP_ANGLE.to_radians();
514555
handle_angle = (handle_angle / snap_resolution).round() * snap_resolution;
515556
}
516557

517-
// Cache the old handle angle for the lock angle.
558+
// Cache the angle and handle id for lock angle
559+
self.current_selected_handle_id = Some(handle_id);
518560
self.angle = handle_angle;
519561

520562
handle_angle
@@ -566,10 +608,10 @@ impl PathToolData {
566608
let current_mouse = input.mouse.position;
567609
let raw_delta = document_to_viewport.inverse().transform_vector2(current_mouse - previous_mouse);
568610

569-
let snapped_delta = if let Some((handle_pos, anchor_pos)) = self.try_get_selected_handle_and_anchor(shape_editor, document) {
611+
let snapped_delta = if let Some((handle_pos, anchor_pos, handle_id)) = self.try_get_selected_handle_and_anchor(shape_editor, document) {
570612
let cursor_pos = handle_pos + raw_delta;
571613

572-
let handle_angle = self.calculate_handle_angle(cursor_pos - anchor_pos, lock_angle, snap_angle);
614+
let handle_angle = self.calculate_handle_angle(cursor_pos - anchor_pos, handle_id, lock_angle, snap_angle);
573615

574616
let constrained_direction = DVec2::new(handle_angle.cos(), handle_angle.sin());
575617
let projected_length = (cursor_pos - anchor_pos).dot(constrained_direction);

0 commit comments

Comments
 (0)