Double click to toggle sharpness of an anchor's handles with the Path tool (#815)

* Double click to flip sharpness of anchor's handles

* Fix bad length

* Convert mirror distance to toggle

* Revert "Convert mirror distance to toggle"

This reverts commit 76b565002265feb29c899840c4b4696b80a220b2.

* Fix mirror_distance_between_handles being inverted
This commit is contained in:
0HyperCube 2022-10-24 21:55:31 +01:00 committed by Keavon Chambers
parent 7f9c59dd99
commit 0553cc5100
3 changed files with 114 additions and 4 deletions

View File

@ -323,6 +323,113 @@ impl ShapeEditor {
} }
} }
/// Handles the flipping between sharp corner and smooth (which can be activated by double clicking on an anchor with the Path tool).
pub fn flip_sharp(&self, document: &Document, position: glam::DVec2, tolerance: f64, responses: &mut VecDeque<Message>) -> bool {
let mut process_layer = |layer_path| {
let manipulator_groups = document.layer(layer_path).ok()?.as_subpath()?.manipulator_groups();
let transform_to_screenspace = document.generate_transform_relative_to_viewport(layer_path).ok()?;
let mut result = None;
let mut closest_distance_squared = tolerance * tolerance;
// Find the closest anchor point on the current layer
for (index, (&bezier_id, group)) in manipulator_groups.enumerate().enumerate() {
if let Some(anchor) = &group.points[ManipulatorType::Anchor as usize] {
let screenspace = transform_to_screenspace.transform_point2(anchor.position);
let distance_squared = screenspace.distance_squared(position);
if distance_squared < closest_distance_squared {
closest_distance_squared = distance_squared;
result = Some((anchor.position, index, bezier_id, group));
}
}
}
let (anchor_position, index, bezier_id, group) = result?;
// Check by comparing the handle positions to the anchor if this maniuplator group is a point
let already_sharp = match &group.points {
[_, Some(in_handle), Some(out_handle)] => anchor_position.abs_diff_eq(in_handle.position, f64::EPSILON * 100.) && anchor_position.abs_diff_eq(out_handle.position, f64::EPSILON * 100.),
[_, Some(handle), None] | [_, None, Some(handle)] => anchor_position.abs_diff_eq(handle.position, f64::EPSILON * 100.),
[_, None, None] => true,
};
let (in_handle, out_handle) = if already_sharp {
// Grab the next and previous manipulator groups by simply looking at the next / previous index
// TODO: Wrapping around on a closed path
let previous_position = index.checked_sub(1).and_then(|index| manipulator_groups.by_index(index)).and_then(|group| group.points[0].as_ref());
let next_position = manipulator_groups.by_index(index + 1).and_then(|group| group.points[0].as_ref());
// To find the length of the new tangent we just take the distance to the anchor and divide by 3 (pretty arbitrary)
let length_previous = previous_position.map(|point| (point.position - anchor_position).length() / 3.);
let length_next = next_position.map(|point| (point.position - anchor_position).length() / 3.);
// Use the position relative to the anchor
let relative_previous_normalised = previous_position.map(|point| (point.position - anchor_position).normalize());
let relative_next_normalised = next_position.map(|point| (point.position - anchor_position).normalize());
// The direction of the handles is either the perpendicular vector to the sum of the anchors' positions or just the anchor's position (if only one)
let handle_direction = match (relative_previous_normalised, relative_next_normalised) {
(Some(previous), Some(next)) => DVec2::new(previous.y + next.y, -(previous.x + next.x)),
(None, Some(val)) => -val,
(Some(val), None) => val,
(None, None) => return None,
};
// Mirror the angle but not the distance
responses.push_back(
Operation::SetManipulatorHandleMirroring {
layer_path: layer_path.to_vec(),
id: bezier_id,
mirror_distance: false,
mirror_angle: true,
}
.into(),
);
let mut handle_vector = handle_direction.normalize();
// Flip the vector if it is not facing towards the same direction as the anchor
if relative_previous_normalised.filter(|pos| pos.dot(handle_vector) < 0.).is_some() || relative_next_normalised.filter(|pos| pos.dot(handle_vector) > 0.).is_some() {
handle_vector = -handle_vector;
}
(
length_previous.map(|length| anchor_position + handle_vector * length),
length_next.map(|length| anchor_position - handle_vector * length),
)
} else {
(Some(anchor_position), Some(anchor_position))
};
// Push both in and out handles into the correct position
if let Some(in_handle) = in_handle {
let in_handle = Operation::SetManipulatorPoints {
layer_path: layer_path.to_vec(),
id: bezier_id,
manipulator_type: ManipulatorType::InHandle,
position: Some(in_handle.into()),
};
responses.push_back(in_handle.into());
}
if let Some(out_handle) = out_handle {
let out_handle = Operation::SetManipulatorPoints {
layer_path: layer_path.to_vec(),
id: bezier_id,
manipulator_type: ManipulatorType::OutHandle,
position: Some(out_handle.into()),
};
responses.push_back(out_handle.into());
}
Some(true)
};
for layer_path in &self.selected_layers {
if let Some(result) = process_layer(layer_path) {
return result;
}
}
false
}
fn shape<'a>(&'a self, document: &'a Document, layer_id: &[u64]) -> Option<&'a Subpath> { fn shape<'a>(&'a self, document: &'a Document, layer_id: &[u64]) -> Option<&'a Subpath> {
document.layer(layer_id).ok()?.as_subpath() document.layer(layer_id).ok()?.as_subpath()
} }

View File

@ -281,7 +281,11 @@ impl Fsm for PathToolFsmState {
PathToolFsmState::Ready PathToolFsmState::Ready
} }
(_, PathToolMessage::InsertPoint) => { (_, PathToolMessage::InsertPoint) => {
tool_data.shape_editor.split(&document.graphene_document, input.mouse.position, SELECTION_TOLERANCE, responses); // First we try and flip the sharpness (if they have clicked on an anchor)
if !tool_data.shape_editor.flip_sharp(&document.graphene_document, input.mouse.position, SELECTION_TOLERANCE, responses) {
// If not, then we try and split the path that may have been clicked upon
tool_data.shape_editor.split(&document.graphene_document, input.mouse.position, SELECTION_TOLERANCE, responses);
}
self self
} }

View File

@ -96,8 +96,7 @@ impl ManipulatorGroup {
/// Move the selected points by the provided transform. /// Move the selected points by the provided transform.
pub fn move_selected_points(&mut self, delta: DVec2) { pub fn move_selected_points(&mut self, delta: DVec2) {
let mirror_angle = self.editor_state.mirror_angle_between_handles; let mirror_angle = self.editor_state.mirror_angle_between_handles;
// Invert distance since we want it to start disabled let mirror_distance = self.editor_state.mirror_distance_between_handles;
let mirror_distance = !self.editor_state.mirror_distance_between_handles;
// Move the point absolutely or relatively depending on if the point is under the cursor (the last selected point) // Move the point absolutely or relatively depending on if the point is under the cursor (the last selected point)
let move_point = |point: &mut ManipulatorPoint, delta: DVec2| { let move_point = |point: &mut ManipulatorPoint, delta: DVec2| {
@ -299,7 +298,7 @@ impl Default for ManipulatorGroupEditorState {
fn default() -> Self { fn default() -> Self {
Self { Self {
mirror_angle_between_handles: true, mirror_angle_between_handles: true,
mirror_distance_between_handles: true, mirror_distance_between_handles: false,
} }
} }
} }