Delete handle on dragging it into its anchor (#1202)

* Delete handles when dragged to anchor + create mirror handle even if handle is deleted

* Handle different zoom levels + check handle mirroring for removal

* Handle different zoom levels + check handle mirroring for removal

* Add an anchor check

* Code review changes

---------

Co-authored-by: Keavon Chambers <keavon@keavon.com>
This commit is contained in:
Shouvik Ghosh 2023-05-20 12:27:48 +05:30 committed by Keavon Chambers
parent 1039f76502
commit da6261aa75
2 changed files with 95 additions and 18 deletions

View File

@ -1,3 +1,4 @@
use crate::consts::DRAG_THRESHOLD;
use crate::messages::portfolio::document::node_graph::VectorDataModification;
use crate::messages::prelude::*;
@ -41,6 +42,8 @@ pub struct ManipulatorPointInfo<'a> {
pub point_id: ManipulatorPointId,
}
pub type OpposingHandleLengths = HashMap<Vec<LayerId>, HashMap<ManipulatorGroupId, Option<f64>>>;
// TODO Consider keeping a list of selected manipulators to minimize traversals of the layers
impl ShapeState {
/// Select the first point within the selection threshold.
@ -178,26 +181,89 @@ impl ShapeState {
move_point(ManipulatorPointId::new(point.group, SelectedType::OutHandle));
}
if mirror_distance && point.manipulator_type != SelectedType::Anchor && vector_data.mirror_angle.contains(&point.group) {
let Some(mut original_handle_position) = point.manipulator_type.get_position(group) else { continue };
original_handle_position += delta;
if mirror_distance && point.manipulator_type != SelectedType::Anchor {
let mut mirror = vector_data.mirror_angle.contains(&point.group);
let point = ManipulatorPointId::new(point.group, point.manipulator_type.opposite());
if state.is_selected(point) {
continue;
// If there is no opposing handle, we mirror even if mirror_angle doesn't contain the group
// and set angle mirroring to true.
if !mirror && point.manipulator_type.opposite().get_position(group).is_none() {
responses.add(GraphOperationMessage::Vector {
layer: layer_path.clone(),
modification: VectorDataModification::SetManipulatorHandleMirroring { id: group.id, mirror_angle: true },
});
mirror = true;
}
let position = group.anchor - (original_handle_position - group.anchor);
if mirror {
let Some(mut original_handle_position) = point.manipulator_type.get_position(group) else { continue };
original_handle_position += delta;
let point = ManipulatorPointId::new(point.group, point.manipulator_type.opposite());
if state.is_selected(point) {
continue;
}
let position = group.anchor - (original_handle_position - group.anchor);
responses.add(GraphOperationMessage::Vector {
layer: layer_path.clone(),
modification: VectorDataModification::SetManipulatorPosition { point, position },
});
}
}
}
}
}
/// Delete selected and mirrored handles with zero length when the drag stops.
pub fn delete_selected_handles_with_zero_length(&self, document: &Document, opposing_handle_lengths: &Option<OpposingHandleLengths>, responses: &mut VecDeque<Message>) {
for (layer_path, state) in &self.selected_shape_state {
let Ok(layer) = document.layer(layer_path) else { continue };
let Some(vector_data) = layer.as_vector_data() else { continue };
let opposing_handle_lengths = opposing_handle_lengths.as_ref().map(|lengths| lengths.get(layer_path)).flatten();
let transform = document.multiply_transforms(layer_path).unwrap_or(glam::DAffine2::IDENTITY);
for &point in state.selected_points.iter() {
let anchor = ManipulatorPointId::new(point.group, SelectedType::Anchor);
if !point.manipulator_type.is_handle() || state.is_selected(anchor) {
continue;
}
let Some(group) = vector_data.manipulator_from_id(point.group) else { continue };
let anchor_position = transform.transform_point2(group.anchor);
let point_position = if let Some(position) = point.manipulator_type.get_position(group) {
transform.transform_point2(position)
} else {
continue;
};
if (anchor_position - point_position).length() < DRAG_THRESHOLD {
responses.add(GraphOperationMessage::Vector {
layer: layer_path.clone(),
modification: VectorDataModification::SetManipulatorPosition { point, position },
modification: VectorDataModification::RemoveManipulatorPoint { point },
});
// Remove opposing handle if it is not selected and is mirrored.
let opposite_point = ManipulatorPointId::new(point.group, point.manipulator_type.opposite());
if !state.is_selected(opposite_point) && vector_data.mirror_angle.contains(&point.group) {
if let Some(lengths) = opposing_handle_lengths {
if lengths.contains_key(&point.group) {
responses.add(GraphOperationMessage::Vector {
layer: layer_path.clone(),
modification: VectorDataModification::RemoveManipulatorPoint { point: opposite_point },
});
}
}
}
}
}
}
}
/// The opposing handle lengths.
pub fn opposing_handle_lengths(&self, document: &Document) -> HashMap<Vec<LayerId>, HashMap<ManipulatorGroupId, f64>> {
pub fn opposing_handle_lengths(&self, document: &Document) -> OpposingHandleLengths {
self.selected_shape_state
.iter()
.filter_map(|(path, state)| {
@ -209,9 +275,8 @@ impl ShapeState {
.flat_map(|subpath| {
subpath.manipulator_groups().iter().filter_map(|manipulator_group| {
// We will keep track of the opposing handle length when:
// i) Both handles exist and exactly one is selected.
// i) Exactly one handle is selected.
// ii) The anchor is not selected.
// iii) We have to mirror the angle between handles.
let in_handle_selected = state.is_selected(ManipulatorPointId::new(manipulator_group.id, SelectedType::InHandle));
let out_handle_selected = state.is_selected(ManipulatorPointId::new(manipulator_group.id, SelectedType::OutHandle));
@ -227,10 +292,12 @@ impl ShapeState {
_ => return None,
};
let opposing_handle_position = single_selected_handle.opposite().get_position(manipulator_group)?;
let Some(opposing_handle_position) = single_selected_handle.opposite().get_position(manipulator_group) else {
return Some((manipulator_group.id, None));
};
let opposing_handle_length = opposing_handle_position.distance(manipulator_group.anchor);
Some((manipulator_group.id, opposing_handle_length))
Some((manipulator_group.id, Some(opposing_handle_length)))
})
})
.collect::<HashMap<_, _>>();
@ -240,7 +307,7 @@ impl ShapeState {
}
/// Reset the opposing handle lengths.
pub fn reset_opposing_handle_lengths(&self, document: &Document, opposing_handle_lengths: &HashMap<Vec<LayerId>, HashMap<ManipulatorGroupId, f64>>, responses: &mut VecDeque<Message>) {
pub fn reset_opposing_handle_lengths(&self, document: &Document, opposing_handle_lengths: &OpposingHandleLengths, responses: &mut VecDeque<Message>) {
for (path, state) in &self.selected_shape_state {
let Ok(layer) = document.layer(path) else { continue };
let Some(vector_data) = layer.as_vector_data() else { continue };
@ -268,6 +335,16 @@ impl ShapeState {
_ => continue,
};
let Some(opposing_handle_length) = opposing_handle_length else {
responses.add(GraphOperationMessage::Vector {
layer: path.to_vec(),
modification: VectorDataModification::RemoveManipulatorPoint {
point: ManipulatorPointId::new(manipulator_group.id, single_selected_handle.opposite()),
},
});
continue;
};
let Some(opposing_handle) = single_selected_handle.opposite().get_position(manipulator_group) else { continue };
let Some(offset) = (opposing_handle - manipulator_group.anchor).try_normalize() else { continue };

View File

@ -4,13 +4,11 @@ use crate::messages::input_mapper::utility_types::input_keyboard::{Key, MouseMot
use crate::messages::layout::utility_types::layout_widget::PropertyHolder;
use crate::messages::prelude::*;
use crate::messages::tool::common_functionality::overlay_renderer::OverlayRenderer;
use crate::messages::tool::common_functionality::shape_editor::{ManipulatorPointInfo, ShapeState};
use crate::messages::tool::common_functionality::shape_editor::{ManipulatorPointInfo, OpposingHandleLengths, ShapeState};
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_core::uuid::ManipulatorGroupId;
use graphene_core::vector::{ManipulatorPointId, SelectedType};
use glam::DVec2;
@ -117,7 +115,7 @@ struct PathToolData {
drag_start_pos: DVec2,
previous_mouse_position: DVec2,
alt_debounce: bool,
opposing_handle_lengths: Option<HashMap<Vec<LayerId>, HashMap<ManipulatorGroupId, f64>>>,
opposing_handle_lengths: Option<OpposingHandleLengths>,
}
impl PathToolData {
@ -293,6 +291,8 @@ impl Fsm for PathToolFsmState {
.map(|(_, nearest_point)| nearest_point);
let shift_pressed = input.keyboard.get(shift_mirror_distance as usize);
shape_editor.delete_selected_handles_with_zero_length(&document.document_legacy, &tool_data.opposing_handle_lengths, responses);
if tool_data.drag_start_pos.distance(input.mouse.position) <= DRAG_THRESHOLD && !shift_pressed {
let clicked_selected = shape_editor.selected_points().any(|&point| nearest_point == Some(point));
if clicked_selected {