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:
parent
7f9c59dd99
commit
0553cc5100
|
|
@ -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> {
|
||||
document.layer(layer_id).ok()?.as_subpath()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -281,7 +281,11 @@ impl Fsm for PathToolFsmState {
|
|||
PathToolFsmState::Ready
|
||||
}
|
||||
(_, 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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -96,8 +96,7 @@ impl ManipulatorGroup {
|
|||
/// Move the selected points by the provided transform.
|
||||
pub fn move_selected_points(&mut self, delta: DVec2) {
|
||||
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)
|
||||
let move_point = |point: &mut ManipulatorPoint, delta: DVec2| {
|
||||
|
|
@ -299,7 +298,7 @@ impl Default for ManipulatorGroupEditorState {
|
|||
fn default() -> Self {
|
||||
Self {
|
||||
mirror_angle_between_handles: true,
|
||||
mirror_distance_between_handles: true,
|
||||
mirror_distance_between_handles: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue