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>
This commit is contained in:
parent
37db9b1392
commit
6effb2d6e6
|
|
@ -293,18 +293,21 @@ fn generate_transform(tool_data: &mut LineToolData, snap_data: SnapData, lock_an
|
||||||
|
|
||||||
let mut angle = -(document_points[1] - document_points[0]).angle_to(DVec2::X);
|
let mut angle = -(document_points[1] - document_points[0]).angle_to(DVec2::X);
|
||||||
let mut line_length = (document_points[1] - document_points[0]).length();
|
let mut line_length = (document_points[1] - document_points[0]).length();
|
||||||
|
|
||||||
if lock_angle {
|
if lock_angle {
|
||||||
angle = tool_data.angle;
|
angle = tool_data.angle;
|
||||||
}
|
} else if snap_angle {
|
||||||
if snap_angle {
|
|
||||||
let snap_resolution = LINE_ROTATE_SNAP_ANGLE.to_radians();
|
let snap_resolution = LINE_ROTATE_SNAP_ANGLE.to_radians();
|
||||||
angle = (angle / snap_resolution).round() * snap_resolution;
|
angle = (angle / snap_resolution).round() * snap_resolution;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
tool_data.angle = angle;
|
||||||
|
|
||||||
if lock_angle {
|
if lock_angle {
|
||||||
let angle_vec = DVec2::new(angle.cos(), angle.sin());
|
let angle_vec = DVec2::new(angle.cos(), angle.sin());
|
||||||
line_length = (document_points[1] - document_points[0]).dot(angle_vec);
|
line_length = (document_points[1] - document_points[0]).dot(angle_vec);
|
||||||
}
|
}
|
||||||
|
|
||||||
document_points[1] = document_points[0] + line_length * DVec2::new(angle.cos(), angle.sin());
|
document_points[1] = document_points[0] + line_length * DVec2::new(angle.cos(), angle.sin());
|
||||||
|
|
||||||
let constrained = snap_angle || lock_angle;
|
let constrained = snap_angle || lock_angle;
|
||||||
|
|
|
||||||
|
|
@ -301,6 +301,7 @@ struct PathToolData {
|
||||||
saved_points_before_anchor_select_toggle: Vec<ManipulatorPointId>,
|
saved_points_before_anchor_select_toggle: Vec<ManipulatorPointId>,
|
||||||
select_anchor_toggled: bool,
|
select_anchor_toggled: bool,
|
||||||
dragging_state: DraggingState,
|
dragging_state: DraggingState,
|
||||||
|
current_selected_handle_id: Option<ManipulatorPointId>,
|
||||||
angle: f64,
|
angle: f64,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -450,15 +451,20 @@ impl PathToolData {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update_colinear(&mut self, equidistant: bool, toggle_colinear: bool, shape_editor: &mut ShapeState, document: &DocumentMessageHandler, responses: &mut VecDeque<Message>) -> bool {
|
fn update_colinear(&mut self, equidistant: bool, toggle_colinear: bool, shape_editor: &mut ShapeState, document: &DocumentMessageHandler, responses: &mut VecDeque<Message>) -> bool {
|
||||||
|
// Check handle colinear state
|
||||||
|
let is_colinear = self
|
||||||
|
.selection_status
|
||||||
|
.angle()
|
||||||
|
.map(|angle| match angle {
|
||||||
|
ManipulatorAngle::Colinear => true,
|
||||||
|
ManipulatorAngle::Free | ManipulatorAngle::Mixed => false,
|
||||||
|
})
|
||||||
|
.unwrap_or(false);
|
||||||
|
|
||||||
// Check if the toggle_colinear key has just been pressed
|
// Check if the toggle_colinear key has just been pressed
|
||||||
if toggle_colinear && !self.toggle_colinear_debounce {
|
if toggle_colinear && !self.toggle_colinear_debounce {
|
||||||
self.opposing_handle_lengths = None;
|
self.opposing_handle_lengths = None;
|
||||||
let colinear = self.selection_status.angle().is_some_and(|angle| match angle {
|
if is_colinear {
|
||||||
ManipulatorAngle::Colinear => true,
|
|
||||||
ManipulatorAngle::Free => false,
|
|
||||||
ManipulatorAngle::Mixed => false,
|
|
||||||
});
|
|
||||||
if colinear {
|
|
||||||
shape_editor.disable_colinear_handles_state_on_selected(&document.network_interface, responses);
|
shape_editor.disable_colinear_handles_state_on_selected(&document.network_interface, responses);
|
||||||
} else {
|
} else {
|
||||||
shape_editor.convert_selected_manipulators_to_colinear_handles(responses, document);
|
shape_editor.convert_selected_manipulators_to_colinear_handles(responses, document);
|
||||||
|
|
@ -469,13 +475,46 @@ impl PathToolData {
|
||||||
self.toggle_colinear_debounce = toggle_colinear;
|
self.toggle_colinear_debounce = toggle_colinear;
|
||||||
|
|
||||||
if equidistant && self.opposing_handle_lengths.is_none() {
|
if equidistant && self.opposing_handle_lengths.is_none() {
|
||||||
|
if !is_colinear {
|
||||||
|
// Try to get selected handle info
|
||||||
|
let Some((_, _, selected_handle_id)) = self.try_get_selected_handle_and_anchor(shape_editor, document) else {
|
||||||
|
self.opposing_handle_lengths = Some(shape_editor.opposing_handle_lengths(document));
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
let Some((layer, _)) = shape_editor.selected_shape_state.iter().next() else {
|
||||||
|
self.opposing_handle_lengths = Some(shape_editor.opposing_handle_lengths(document));
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
let Some(vector_data) = document.network_interface.compute_modified_vector(*layer) else {
|
||||||
|
self.opposing_handle_lengths = Some(shape_editor.opposing_handle_lengths(document));
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Check if handle has a pair (to ignore handles of edges of open paths)
|
||||||
|
if let Some(handle_pair) = selected_handle_id.get_handle_pair(&vector_data) {
|
||||||
|
let opposite_handle_length = handle_pair.iter().filter(|&&h| h.to_manipulator_point() != selected_handle_id).find_map(|&h| {
|
||||||
|
let opp_handle_pos = h.to_manipulator_point().get_position(&vector_data)?;
|
||||||
|
let opp_anchor_id = h.to_manipulator_point().get_anchor(&vector_data)?;
|
||||||
|
let opp_anchor_pos = vector_data.point_domain.position_from_id(opp_anchor_id)?;
|
||||||
|
Some((opp_handle_pos - opp_anchor_pos).length())
|
||||||
|
});
|
||||||
|
|
||||||
|
// Make handles colinear if opposite handle is zero length
|
||||||
|
if opposite_handle_length.map_or(false, |l| l == 0.) {
|
||||||
|
shape_editor.convert_selected_manipulators_to_colinear_handles(responses, document);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
self.opposing_handle_lengths = Some(shape_editor.opposing_handle_lengths(document));
|
self.opposing_handle_lengths = Some(shape_editor.opposing_handle_lengths(document));
|
||||||
}
|
}
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 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.
|
/// 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.
|
||||||
fn try_get_selected_handle_and_anchor(&self, shape_editor: &ShapeState, document: &DocumentMessageHandler) -> Option<(DVec2, DVec2)> {
|
fn try_get_selected_handle_and_anchor(&self, shape_editor: &ShapeState, document: &DocumentMessageHandler) -> Option<(DVec2, DVec2, ManipulatorPointId)> {
|
||||||
// Only count selections of a single layer
|
// Only count selections of a single layer
|
||||||
let (layer, selection) = shape_editor.selected_shape_state.iter().next()?;
|
let (layer, selection) = shape_editor.selected_shape_state.iter().next()?;
|
||||||
|
|
||||||
|
|
@ -486,6 +525,7 @@ impl PathToolData {
|
||||||
|
|
||||||
// Only count selected handles
|
// Only count selected handles
|
||||||
let selected_handle = selection.selected().next()?.as_handle()?;
|
let selected_handle = selection.selected().next()?.as_handle()?;
|
||||||
|
let handle_id = selected_handle.to_manipulator_point();
|
||||||
|
|
||||||
let layer_to_document = document.metadata().transform_to_document(*layer);
|
let layer_to_document = document.metadata().transform_to_document(*layer);
|
||||||
let vector_data = document.network_interface.compute_modified_vector(*layer)?;
|
let vector_data = document.network_interface.compute_modified_vector(*layer)?;
|
||||||
|
|
@ -497,24 +537,26 @@ impl PathToolData {
|
||||||
let handle_position_document = layer_to_document.transform_point2(handle_position_local);
|
let handle_position_document = layer_to_document.transform_point2(handle_position_local);
|
||||||
let anchor_position_document = layer_to_document.transform_point2(anchor_position_local);
|
let anchor_position_document = layer_to_document.transform_point2(anchor_position_local);
|
||||||
|
|
||||||
Some((handle_position_document, anchor_position_document))
|
Some((handle_position_document, anchor_position_document, handle_id))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn calculate_handle_angle(&mut self, handle_vector: DVec2, lock_angle: bool, snap_angle: bool) -> f64 {
|
fn calculate_handle_angle(&mut self, handle_vector: DVec2, handle_id: ManipulatorPointId, lock_angle: bool, snap_angle: bool) -> f64 {
|
||||||
let mut handle_angle = -handle_vector.angle_to(DVec2::X);
|
let current_angle = -handle_vector.angle_to(DVec2::X);
|
||||||
|
|
||||||
// When the angle is locked we use the old angle
|
// When the angle is locked we use the old angle
|
||||||
if lock_angle {
|
if self.current_selected_handle_id == Some(handle_id) && lock_angle {
|
||||||
handle_angle = self.angle
|
return self.angle;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Round the angle to the closest increment
|
// Round the angle to the closest increment
|
||||||
if snap_angle {
|
let mut handle_angle = current_angle;
|
||||||
|
if snap_angle && !lock_angle {
|
||||||
let snap_resolution = HANDLE_ROTATE_SNAP_ANGLE.to_radians();
|
let snap_resolution = HANDLE_ROTATE_SNAP_ANGLE.to_radians();
|
||||||
handle_angle = (handle_angle / snap_resolution).round() * snap_resolution;
|
handle_angle = (handle_angle / snap_resolution).round() * snap_resolution;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Cache the old handle angle for the lock angle.
|
// Cache the angle and handle id for lock angle
|
||||||
|
self.current_selected_handle_id = Some(handle_id);
|
||||||
self.angle = handle_angle;
|
self.angle = handle_angle;
|
||||||
|
|
||||||
handle_angle
|
handle_angle
|
||||||
|
|
@ -566,10 +608,10 @@ impl PathToolData {
|
||||||
let current_mouse = input.mouse.position;
|
let current_mouse = input.mouse.position;
|
||||||
let raw_delta = document_to_viewport.inverse().transform_vector2(current_mouse - previous_mouse);
|
let raw_delta = document_to_viewport.inverse().transform_vector2(current_mouse - previous_mouse);
|
||||||
|
|
||||||
let snapped_delta = if let Some((handle_pos, anchor_pos)) = self.try_get_selected_handle_and_anchor(shape_editor, document) {
|
let snapped_delta = if let Some((handle_pos, anchor_pos, handle_id)) = self.try_get_selected_handle_and_anchor(shape_editor, document) {
|
||||||
let cursor_pos = handle_pos + raw_delta;
|
let cursor_pos = handle_pos + raw_delta;
|
||||||
|
|
||||||
let handle_angle = self.calculate_handle_angle(cursor_pos - anchor_pos, lock_angle, snap_angle);
|
let handle_angle = self.calculate_handle_angle(cursor_pos - anchor_pos, handle_id, lock_angle, snap_angle);
|
||||||
|
|
||||||
let constrained_direction = DVec2::new(handle_angle.cos(), handle_angle.sin());
|
let constrained_direction = DVec2::new(handle_angle.cos(), handle_angle.sin());
|
||||||
let projected_length = (cursor_pos - anchor_pos).dot(constrained_direction);
|
let projected_length = (cursor_pos - anchor_pos).dot(constrained_direction);
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue