mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-10-02 06:18:11 +08:00
Remap Path tool point sliding to G G (#2913)
* Feat: Point sliding on G G * Code cleanup * Fix gg sliding behaviour * Fix build after merge conflict resolution * Fix slide point and add hints * Fix history in segment insertion * Code review --------- Co-authored-by: Keavon Chambers <keavon@keavon.com>
This commit is contained in:
co-authored by
Keavon Chambers
parent
3bcec37493
commit
52174fa4c2
@@ -127,6 +127,7 @@ pub enum PathToolMessage {
|
|||||||
UpdateSelectedPointsStatus {
|
UpdateSelectedPointsStatus {
|
||||||
overlay_context: OverlayContext,
|
overlay_context: OverlayContext,
|
||||||
},
|
},
|
||||||
|
StartSlidingPoint,
|
||||||
Copy {
|
Copy {
|
||||||
clipboard: Clipboard,
|
clipboard: Clipboard,
|
||||||
},
|
},
|
||||||
@@ -420,6 +421,7 @@ impl<'a> MessageHandler<ToolMessage, &mut ToolActionMessageContext<'a>> for Path
|
|||||||
DeleteAndBreakPath,
|
DeleteAndBreakPath,
|
||||||
ClosePath,
|
ClosePath,
|
||||||
PointerMove,
|
PointerMove,
|
||||||
|
StartSlidingPoint,
|
||||||
Copy,
|
Copy,
|
||||||
Cut,
|
Cut,
|
||||||
DeleteSelected,
|
DeleteSelected,
|
||||||
@@ -438,6 +440,7 @@ impl<'a> MessageHandler<ToolMessage, &mut ToolActionMessageContext<'a>> for Path
|
|||||||
BreakPath,
|
BreakPath,
|
||||||
DeleteAndBreakPath,
|
DeleteAndBreakPath,
|
||||||
SwapSelectedHandles,
|
SwapSelectedHandles,
|
||||||
|
StartSlidingPoint,
|
||||||
Copy,
|
Copy,
|
||||||
Cut,
|
Cut,
|
||||||
DeleteSelected,
|
DeleteSelected,
|
||||||
@@ -456,6 +459,7 @@ impl<'a> MessageHandler<ToolMessage, &mut ToolActionMessageContext<'a>> for Path
|
|||||||
DeleteAndBreakPath,
|
DeleteAndBreakPath,
|
||||||
Escape,
|
Escape,
|
||||||
RightClick,
|
RightClick,
|
||||||
|
StartSlidingPoint,
|
||||||
TogglePointEditing,
|
TogglePointEditing,
|
||||||
ToggleSegmentEditing
|
ToggleSegmentEditing
|
||||||
),
|
),
|
||||||
@@ -1208,11 +1212,6 @@ impl PathToolData {
|
|||||||
};
|
};
|
||||||
let Some(vector) = document.network_interface.compute_modified_vector(layer) else { return false };
|
let Some(vector) = document.network_interface.compute_modified_vector(layer) else { return false };
|
||||||
|
|
||||||
// Check that the handles of anchor point are also colinear
|
|
||||||
if !vector.colinear(*anchor) {
|
|
||||||
return false;
|
|
||||||
};
|
|
||||||
|
|
||||||
let Some(point_id) = anchor.as_anchor() else { return false };
|
let Some(point_id) = anchor.as_anchor() else { return false };
|
||||||
|
|
||||||
let mut connected_segments = [None, None];
|
let mut connected_segments = [None, None];
|
||||||
@@ -2083,10 +2082,6 @@ impl Fsm for PathToolFsmState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if !tool_data.update_colinear(equidistant_state, toggle_colinear_state, tool_action_data.shape_editor, tool_action_data.document, responses) {
|
if !tool_data.update_colinear(equidistant_state, toggle_colinear_state, tool_action_data.shape_editor, tool_action_data.document, responses) {
|
||||||
if snap_angle_state && lock_angle_state && tool_data.start_sliding_point(tool_action_data.shape_editor, tool_action_data.document) {
|
|
||||||
return PathToolFsmState::SlidingPoint;
|
|
||||||
}
|
|
||||||
|
|
||||||
tool_data.drag(
|
tool_data.drag(
|
||||||
equidistant_state,
|
equidistant_state,
|
||||||
lock_angle_state,
|
lock_angle_state,
|
||||||
@@ -2382,6 +2377,8 @@ impl Fsm for PathToolFsmState {
|
|||||||
tool_data.ghost_outline.clear();
|
tool_data.ghost_outline.clear();
|
||||||
let extend_selection = input.keyboard.get(extend_selection as usize);
|
let extend_selection = input.keyboard.get(extend_selection as usize);
|
||||||
let drag_occurred = tool_data.drag_start_pos.distance(input.mouse.position) > DRAG_THRESHOLD;
|
let drag_occurred = tool_data.drag_start_pos.distance(input.mouse.position) > DRAG_THRESHOLD;
|
||||||
|
let mut segment_dissolved = false;
|
||||||
|
let mut point_inserted = false;
|
||||||
|
|
||||||
let nearest_point = shape_editor.find_nearest_visible_point_indices(
|
let nearest_point = shape_editor.find_nearest_visible_point_indices(
|
||||||
&document.network_interface,
|
&document.network_interface,
|
||||||
@@ -2402,6 +2399,7 @@ impl Fsm for PathToolFsmState {
|
|||||||
if tool_data.delete_segment_pressed {
|
if tool_data.delete_segment_pressed {
|
||||||
if let Some(vector) = document.network_interface.compute_modified_vector(segment.layer()) {
|
if let Some(vector) = document.network_interface.compute_modified_vector(segment.layer()) {
|
||||||
shape_editor.dissolve_segment(responses, segment.layer(), &vector, segment.segment(), segment.points());
|
shape_editor.dissolve_segment(responses, segment.layer(), &vector, segment.segment(), segment.points());
|
||||||
|
segment_dissolved = true;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
let is_segment_selected = shape_editor
|
let is_segment_selected = shape_editor
|
||||||
@@ -2410,11 +2408,7 @@ impl Fsm for PathToolFsmState {
|
|||||||
.is_some_and(|state| state.is_segment_selected(segment.segment()));
|
.is_some_and(|state| state.is_segment_selected(segment.segment()));
|
||||||
|
|
||||||
segment.adjusted_insert_and_select(shape_editor, responses, extend_selection, point_mode, is_segment_selected);
|
segment.adjusted_insert_and_select(shape_editor, responses, extend_selection, point_mode, is_segment_selected);
|
||||||
tool_data.segment = None;
|
point_inserted = true;
|
||||||
tool_data.molding_info = None;
|
|
||||||
tool_data.molding_segment = false;
|
|
||||||
tool_data.temporary_adjacent_handles_while_molding = None;
|
|
||||||
return PathToolFsmState::Ready;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2422,6 +2416,11 @@ impl Fsm for PathToolFsmState {
|
|||||||
tool_data.molding_info = None;
|
tool_data.molding_info = None;
|
||||||
tool_data.molding_segment = false;
|
tool_data.molding_segment = false;
|
||||||
tool_data.temporary_adjacent_handles_while_molding = None;
|
tool_data.temporary_adjacent_handles_while_molding = None;
|
||||||
|
|
||||||
|
if segment_dissolved || point_inserted {
|
||||||
|
responses.add(DocumentMessage::EndTransaction);
|
||||||
|
return PathToolFsmState::Ready;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let segment_mode = tool_options.path_editing_mode.segment_editing_mode;
|
let segment_mode = tool_options.path_editing_mode.segment_editing_mode;
|
||||||
@@ -2578,6 +2577,14 @@ impl Fsm for PathToolFsmState {
|
|||||||
shape_editor.delete_point_and_break_path(document, responses);
|
shape_editor.delete_point_and_break_path(document, responses);
|
||||||
PathToolFsmState::Ready
|
PathToolFsmState::Ready
|
||||||
}
|
}
|
||||||
|
(_, PathToolMessage::StartSlidingPoint) => {
|
||||||
|
responses.add(DocumentMessage::StartTransaction);
|
||||||
|
if tool_data.start_sliding_point(shape_editor, document) {
|
||||||
|
PathToolFsmState::SlidingPoint
|
||||||
|
} else {
|
||||||
|
PathToolFsmState::Ready
|
||||||
|
}
|
||||||
|
}
|
||||||
(_, PathToolMessage::Copy { clipboard }) => {
|
(_, PathToolMessage::Copy { clipboard }) => {
|
||||||
// TODO: Add support for selected segments
|
// TODO: Add support for selected segments
|
||||||
|
|
||||||
@@ -3297,7 +3304,7 @@ fn update_dynamic_hints(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut drag_selected_hints = vec![HintInfo::mouse(MouseMotion::LmbDrag, "Drag Selected")];
|
let drag_selected_hints = vec![HintInfo::mouse(MouseMotion::LmbDrag, "Drag Selected")];
|
||||||
let mut delete_selected_hints = vec![HintInfo::keys([Key::Delete], "Delete Selected")];
|
let mut delete_selected_hints = vec![HintInfo::keys([Key::Delete], "Delete Selected")];
|
||||||
|
|
||||||
if at_least_one_anchor_selected {
|
if at_least_one_anchor_selected {
|
||||||
@@ -3305,10 +3312,6 @@ fn update_dynamic_hints(
|
|||||||
delete_selected_hints.push(HintInfo::keys([Key::Shift], "Cut Anchor").prepend_plus());
|
delete_selected_hints.push(HintInfo::keys([Key::Shift], "Cut Anchor").prepend_plus());
|
||||||
}
|
}
|
||||||
|
|
||||||
if single_colinear_anchor_selected {
|
|
||||||
drag_selected_hints.push(HintInfo::multi_keys([[Key::Control], [Key::Shift]], "Slide").prepend_plus());
|
|
||||||
}
|
|
||||||
|
|
||||||
let segment_edit = tool_options.path_editing_mode.segment_editing_mode;
|
let segment_edit = tool_options.path_editing_mode.segment_editing_mode;
|
||||||
let point_edit = tool_options.path_editing_mode.point_editing_mode;
|
let point_edit = tool_options.path_editing_mode.point_editing_mode;
|
||||||
|
|
||||||
@@ -3373,9 +3376,15 @@ fn update_dynamic_hints(
|
|||||||
let mut groups = vec![
|
let mut groups = vec![
|
||||||
HintGroup(drag_selected_hints),
|
HintGroup(drag_selected_hints),
|
||||||
HintGroup(vec![HintInfo::multi_keys([[Key::KeyG], [Key::KeyR], [Key::KeyS]], "Grab/Rotate/Scale Selected")]),
|
HintGroup(vec![HintInfo::multi_keys([[Key::KeyG], [Key::KeyR], [Key::KeyS]], "Grab/Rotate/Scale Selected")]),
|
||||||
HintGroup(vec![HintInfo::arrow_keys("Nudge Selected"), HintInfo::keys([Key::Shift], "10x").prepend_plus()]),
|
|
||||||
HintGroup(delete_selected_hints),
|
|
||||||
];
|
];
|
||||||
|
|
||||||
|
if single_colinear_anchor_selected {
|
||||||
|
groups.push(HintGroup(vec![HintInfo::multi_keys([[Key::KeyG], [Key::KeyG]], "Slide")]));
|
||||||
|
}
|
||||||
|
|
||||||
|
groups.push(HintGroup(vec![HintInfo::arrow_keys("Nudge Selected"), HintInfo::keys([Key::Shift], "10x").prepend_plus()]));
|
||||||
|
groups.push(HintGroup(delete_selected_hints));
|
||||||
|
|
||||||
hint_data.append(&mut groups);
|
hint_data.append(&mut groups);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -65,6 +65,8 @@ pub struct TransformLayerMessageHandler {
|
|||||||
|
|
||||||
// Ghost outlines for Path Tool
|
// Ghost outlines for Path Tool
|
||||||
ghost_outline: Vec<(Vec<ClickTargetType>, DAffine2)>,
|
ghost_outline: Vec<(Vec<ClickTargetType>, DAffine2)>,
|
||||||
|
|
||||||
|
was_grabbing: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MessageHandler<TransformLayerMessage, TransformLayerMessageContext<'_>> for TransformLayerMessageHandler {
|
impl MessageHandler<TransformLayerMessage, TransformLayerMessageContext<'_>> for TransformLayerMessageHandler {
|
||||||
@@ -122,7 +124,7 @@ impl MessageHandler<TransformLayerMessage, TransformLayerMessageContext<'_>> for
|
|||||||
self.local_pivot = document.metadata().document_to_viewport.inverse().transform_point2(*selected.pivot);
|
self.local_pivot = document.metadata().document_to_viewport.inverse().transform_point2(*selected.pivot);
|
||||||
self.grab_target = self.local_pivot;
|
self.grab_target = self.local_pivot;
|
||||||
}
|
}
|
||||||
// Here vector data from all layers is not considered which can be a problem in pivot calculation
|
// TODO: Here vector data from all layers is not considered which can be a problem in pivot calculation
|
||||||
else if let Some(vector) = selected_layers.first().and_then(|&layer| document.network_interface.compute_modified_vector(layer)) {
|
else if let Some(vector) = selected_layers.first().and_then(|&layer| document.network_interface.compute_modified_vector(layer)) {
|
||||||
*selected.original_transforms = OriginalTransforms::default();
|
*selected.original_transforms = OriginalTransforms::default();
|
||||||
|
|
||||||
@@ -301,6 +303,7 @@ impl MessageHandler<TransformLayerMessage, TransformLayerMessageContext<'_>> for
|
|||||||
responses.add(SelectToolMessage::PivotShift { offset: None, flush: true });
|
responses.add(SelectToolMessage::PivotShift { offset: None, flush: true });
|
||||||
|
|
||||||
if final_transform {
|
if final_transform {
|
||||||
|
self.was_grabbing = false;
|
||||||
responses.add(OverlaysMessage::RemoveProvider(TRANSFORM_GRS_OVERLAY_PROVIDER));
|
responses.add(OverlaysMessage::RemoveProvider(TRANSFORM_GRS_OVERLAY_PROVIDER));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -356,34 +359,64 @@ impl MessageHandler<TransformLayerMessage, TransformLayerMessageContext<'_>> for
|
|||||||
if (selected_points.is_empty() && selected_segments.is_empty())
|
if (selected_points.is_empty() && selected_segments.is_empty())
|
||||||
|| (!using_path_tool && !using_select_tool && !using_pen_tool && !using_shape_tool)
|
|| (!using_path_tool && !using_select_tool && !using_pen_tool && !using_shape_tool)
|
||||||
|| selected_layers.is_empty()
|
|| selected_layers.is_empty()
|
||||||
|| transform_type.equivalent_to(self.transform_operation)
|
|| (transform_type.equivalent_to(self.transform_operation) && !self.was_grabbing)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if using_path_tool && transform_type == TransformType::Grab {
|
||||||
|
// Check if a single point is selected and it's a colinear point
|
||||||
|
let single_anchor_selected = shape_editor.selected_points().count() == 1 && shape_editor.selected_points().any(|point| point.as_anchor().is_some());
|
||||||
|
|
||||||
|
if single_anchor_selected && transform_type.equivalent_to(self.transform_operation) && self.was_grabbing {
|
||||||
|
let selected_nodes = &document.network_interface.selected_nodes();
|
||||||
|
|
||||||
|
let Some(layer) = selected_nodes.selected_layers(document.metadata()).next() else { return };
|
||||||
|
let Some(vector_data) = document.network_interface.compute_modified_vector(layer) else { return };
|
||||||
|
let Some(anchor) = shape_editor.selected_points().next() else { return };
|
||||||
|
|
||||||
|
if vector_data.colinear(*anchor) {
|
||||||
|
responses.add(TransformLayerMessage::CancelTransformOperation);
|
||||||
|
|
||||||
|
// Start sliding point
|
||||||
|
responses.add(DeferMessage::AfterGraphRun {
|
||||||
|
messages: vec![PathToolMessage::StartSlidingPoint.into()],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if transform_type.equivalent_to(self.transform_operation) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.was_grabbing = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(vector) = selected_layers.first().and_then(|&layer| document.network_interface.compute_modified_vector(layer)) {
|
if let Some(vector) = selected_layers.first().and_then(|&layer| document.network_interface.compute_modified_vector(layer))
|
||||||
if let [point] = selected_points.as_slice() {
|
&& let [point] = selected_points.as_slice()
|
||||||
if matches!(point, ManipulatorPointId::Anchor(_)) {
|
{
|
||||||
if let Some([handle1, handle2]) = point.get_handle_pair(&vector) {
|
if matches!(point, ManipulatorPointId::Anchor(_)) {
|
||||||
let handle1_length = handle1.length(&vector);
|
if let Some([handle1, handle2]) = point.get_handle_pair(&vector) {
|
||||||
let handle2_length = handle2.length(&vector);
|
let handle1_length = handle1.length(&vector);
|
||||||
|
let handle2_length = handle2.length(&vector);
|
||||||
|
|
||||||
if (handle1_length == 0. && handle2_length == 0. && !using_select_tool) || (handle1_length == f64::MAX && handle2_length == f64::MAX && !using_select_tool) {
|
if (handle1_length == 0. && handle2_length == 0. && !using_select_tool) || (handle1_length == f64::MAX && handle2_length == f64::MAX && !using_select_tool) {
|
||||||
// G should work for this point but not R and S
|
// G should work for this point but not R and S
|
||||||
if matches!(transform_type, TransformType::Rotate | TransformType::Scale) {
|
if matches!(transform_type, TransformType::Rotate | TransformType::Scale) {
|
||||||
selected.original_transforms.clear();
|
selected.original_transforms.clear();
|
||||||
return;
|
return;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
}
|
||||||
let handle_length = point.as_handle().map(|handle| handle.length(&vector));
|
} else {
|
||||||
|
let handle_length = point.as_handle().map(|handle| handle.length(&vector));
|
||||||
|
|
||||||
if handle_length == Some(0.) {
|
if handle_length == Some(0.) {
|
||||||
selected.original_transforms.clear();
|
selected.original_transforms.clear();
|
||||||
return;
|
return;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -409,6 +442,7 @@ impl MessageHandler<TransformLayerMessage, TransformLayerMessageContext<'_>> for
|
|||||||
TransformLayerMessage::CancelTransformOperation => {
|
TransformLayerMessage::CancelTransformOperation => {
|
||||||
if using_path_tool {
|
if using_path_tool {
|
||||||
self.ghost_outline.clear();
|
self.ghost_outline.clear();
|
||||||
|
self.was_grabbing = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if using_pen_tool {
|
if using_pen_tool {
|
||||||
@@ -499,7 +533,7 @@ impl MessageHandler<TransformLayerMessage, TransformLayerMessageContext<'_>> for
|
|||||||
|
|
||||||
if self.typing.digits.is_empty() || !self.transform_operation.can_begin_typing() {
|
if self.typing.digits.is_empty() || !self.transform_operation.can_begin_typing() {
|
||||||
match self.transform_operation {
|
match self.transform_operation {
|
||||||
TransformOperation::None => unreachable!(),
|
TransformOperation::None => {}
|
||||||
TransformOperation::Grabbing(translation) => {
|
TransformOperation::Grabbing(translation) => {
|
||||||
let delta_pos = input.mouse.position - self.mouse_position;
|
let delta_pos = input.mouse.position - self.mouse_position;
|
||||||
let delta_pos = (self.initial_transform * document_to_viewport.inverse()).transform_vector2(delta_pos);
|
let delta_pos = (self.initial_transform * document_to_viewport.inverse()).transform_vector2(delta_pos);
|
||||||
|
|||||||
@@ -182,10 +182,10 @@ pub fn bezpath_to_manipulator_groups(bezpath: &BezPath) -> (Vec<ManipulatorGroup
|
|||||||
ManipulatorGroup::new(point_to_dvec2(point2), Some(point_to_dvec2(point1)), None)
|
ManipulatorGroup::new(point_to_dvec2(point2), Some(point_to_dvec2(point1)), None)
|
||||||
}
|
}
|
||||||
kurbo::PathEl::ClosePath => {
|
kurbo::PathEl::ClosePath => {
|
||||||
if let Some(last_manipulators) = manipulator_groups.pop() {
|
if let Some(last_manipulators) = manipulator_groups.pop()
|
||||||
if let Some(first_manipulators) = manipulator_groups.first_mut() {
|
&& let Some(first_manipulators) = manipulator_groups.first_mut()
|
||||||
first_manipulators.out_handle = last_manipulators.in_handle;
|
{
|
||||||
}
|
first_manipulators.out_handle = last_manipulators.in_handle;
|
||||||
}
|
}
|
||||||
is_closed = true;
|
is_closed = true;
|
||||||
break;
|
break;
|
||||||
|
|||||||
Reference in New Issue
Block a user