Restore handle length when Shift is released while dragging opposite handle with Path tool (#1009)

* Restore path handle length to previous length when shift is released while dragging

* Reset previous opposing handle length on Delete and Abort messages

* Move the opposing handle length state to the path tool

* Use MoveManipulatorPoint messages instead of modifying MoveSelectedManipulatorPoints + break handle mirroring on dragging both handles without anchor

* Handle alt + improve reset logic + comments

---------

Co-authored-by: Keavon Chambers <keavon@keavon.com>
This commit is contained in:
Shouvik Ghosh 2023-02-22 05:21:46 +05:30 committed by Keavon Chambers
parent 107e55e91d
commit 92cb553961
3 changed files with 126 additions and 0 deletions

View file

@ -212,6 +212,114 @@ impl ShapeEditor {
}
}
/// The opposing handle lengths.
pub fn opposing_handle_lengths(&self, document: &Document) -> HashMap<Vec<LayerId>, HashMap<u64, f64>> {
self.selected_layers()
.iter()
.filter_map(|path| document.layer(path).ok().map(|layer| (path, layer)))
.filter_map(|(path, shape)| shape.as_subpath().map(|subpath| (path, subpath)))
.map(|(path, shape)| {
let opposing_handle_lengths = shape
.manipulator_groups()
.enumerate()
.filter_map(|(id, manipulator_group)| {
// We will keep track of the opposing handle length when:
// i) Both handles exist and exactly one is selected.
// ii) The anchor is not selected.
// iii) We have to mirror the angle between handles.
if !manipulator_group.editor_state.mirror_angle_between_handles {
return None;
}
let mut selected_handles = manipulator_group.selected_handles();
let handle = selected_handles.next()?;
// Check that handle is the only selected handle.
if selected_handles.next().is_none() {
let opposing_handle_position = manipulator_group.opposing_handle(handle)?.position;
let anchor = manipulator_group.points[ManipulatorType::Anchor].as_ref()?;
if !anchor.is_selected() {
let opposing_handle_length = opposing_handle_position.distance(anchor.position);
Some((*id, opposing_handle_length))
} else {
None
}
} else {
None
}
})
.collect::<HashMap<_, _>>();
(path.clone(), opposing_handle_lengths)
})
.collect::<HashMap<_, _>>()
}
/// Reset the opposing handle lengths.
pub fn reset_opposing_handle_lengths(&self, document: &Document, opposing_handle_lengths: &HashMap<Vec<LayerId>, HashMap<u64, f64>>, responses: &mut VecDeque<Message>) {
self.selected_layers()
.iter()
.filter_map(|path| document.layer(path).ok().map(|layer| (path, layer)))
.filter_map(|(path, shape)| shape.as_subpath().map(|subpath| (path, subpath)))
.filter_map(|(path, shape)| opposing_handle_lengths.get(path).map(|layer_opposing_handle_lengths| (path, shape, layer_opposing_handle_lengths)))
.flat_map(|(path, shape, layer_opposing_handle_lengths)| {
shape
.manipulator_groups()
.enumerate()
.map(move |(id, manipulator_group)| (path, layer_opposing_handle_lengths, id, manipulator_group))
})
.for_each(|(path, layer_opposing_handle_lengths, id, manipulator_group)| {
if !manipulator_group.editor_state.mirror_angle_between_handles {
return;
}
let opposing_handle_length = if let Some(length) = layer_opposing_handle_lengths.get(id) {
length
} else {
return;
};
let mut selected_handles = manipulator_group.selected_handles();
let handle = if let Some(handle) = selected_handles.next() {
handle
} else {
return;
};
// Check that handle is the only selected handle.
if selected_handles.next().is_none() {
let opposing_handle = if let Some(opposing_handle) = manipulator_group.opposing_handle(handle) {
opposing_handle
} else {
return;
};
let anchor = if let Some(anchor) = manipulator_group.points[ManipulatorType::Anchor].as_ref() {
anchor
} else {
return;
};
if anchor.is_selected() {
return;
}
if let Some(offset) = (opposing_handle.position - anchor.position).try_normalize() {
let new_opposing_handle_position = anchor.position + offset * (*opposing_handle_length);
assert!(new_opposing_handle_position.is_finite(), "Opposing handle not finite!");
responses.push_back(
Operation::MoveManipulatorPoint {
layer_path: path.clone(),
id: *id,
manipulator_type: opposing_handle.manipulator_type,
position: new_opposing_handle_position.into(),
}
.into(),
);
}
}
});
}
/// Dissolve the selected points.
pub fn delete_selected_points(&self, responses: &mut VecDeque<Message>) {
responses.push_back(DocumentMessage::DeleteSelectedManipulatorPoints.into());

View file

@ -9,6 +9,7 @@ use crate::messages::tool::common_functionality::snapping::SnapManager;
use crate::messages::tool::utility_types::{EventToMessageMap, Fsm, HintData, HintGroup, HintInfo, ToolActionHandlerData, ToolMetadata, ToolTransition, ToolType};
use document_legacy::intersection::Quad;
use document_legacy::LayerId;
use graphene_std::vector::consts::ManipulatorType;
use glam::DVec2;
@ -112,6 +113,7 @@ struct PathToolData {
drag_start_pos: DVec2,
previous_mouse_position: DVec2,
alt_debounce: bool,
opposing_handle_lengths: Option<HashMap<Vec<LayerId>, HashMap<u64, f64>>>,
}
impl Fsm for PathToolFsmState {
@ -142,6 +144,7 @@ impl Fsm for PathToolFsmState {
tool_data.overlay_renderer.render_subpath_overlays(&document.document_legacy, layer_path.to_vec(), responses);
}
tool_data.opposing_handle_lengths = None;
// This can happen in any state (which is why we return self)
self
}
@ -158,6 +161,8 @@ impl Fsm for PathToolFsmState {
(_, PathToolMessage::DragStart { add_to_selection }) => {
let shift_pressed = input.keyboard.get(add_to_selection as usize);
tool_data.opposing_handle_lengths = None;
// Select the first point within the threshold (in pixels)
if let Some(mut selected_points) = tool_data
.shape_editor
@ -240,6 +245,7 @@ impl Fsm for PathToolFsmState {
tool_data.alt_debounce = alt_pressed;
// Only on alt down
if alt_pressed {
tool_data.opposing_handle_lengths = None;
tool_data.shape_editor.toggle_handle_mirroring_on_selected(true, responses);
}
}
@ -247,6 +253,17 @@ impl Fsm for PathToolFsmState {
// Determine when shift state changes
let shift_pressed = input.keyboard.get(shift_mirror_distance as usize);
if shift_pressed {
if tool_data.opposing_handle_lengths.is_none() {
tool_data.opposing_handle_lengths = Some(tool_data.shape_editor.opposing_handle_lengths(&document.document_legacy));
}
} else {
if let Some(opposing_handle_lengths) = &tool_data.opposing_handle_lengths {
tool_data.shape_editor.reset_opposing_handle_lengths(&document.document_legacy, opposing_handle_lengths, responses);
tool_data.opposing_handle_lengths = None;
}
}
// Move the selected points by the mouse position
let snapped_position = tool_data.snap_manager.snap_position(responses, document, input.mouse.position);
tool_data

View file

@ -146,6 +146,7 @@ impl ManipulatorGroup {
// If the anchor isn't selected, but both handles are, drag only handles
if self.both_handles_selected() {
self.editor_state.mirror_angle_between_handles = false;
for point in self.selected_handles_mut() {
move_point(point, delta);
}