mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2025-08-04 13:30:48 +00:00
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:
parent
107e55e91d
commit
92cb553961
3 changed files with 126 additions and 0 deletions
|
@ -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());
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue