Skip to content

Commit 4275eaf

Browse files
4adexKeavon
andauthored
Add Path tool support for dragging along an axis when Shift is held (#2449)
* Initial logic for snapping * Solved switching of axes * Solved conflict * Fixed autopanning issue * Autopanning issue * cleared comments * Code review --------- Co-authored-by: Keavon Chambers <keavon@keavon.com>
1 parent 7e7e88f commit 4275eaf

File tree

1 file changed

+113
-14
lines changed

1 file changed

+113
-14
lines changed

Diff for: editor/src/messages/tool/tool_messages/path_tool.rs

+113-14
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ use crate::messages::portfolio::document::overlays::utility_functions::{path_ove
77
use crate::messages::portfolio::document::overlays::utility_types::{DrawHandles, OverlayContext};
88
use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier;
99
use crate::messages::portfolio::document::utility_types::network_interface::NodeNetworkInterface;
10+
use crate::messages::portfolio::document::utility_types::transformation::Axis;
1011
use crate::messages::preferences::SelectionMode;
1112
use crate::messages::tool::common_functionality::auto_panning::AutoPanning;
1213
use crate::messages::tool::common_functionality::shape_editor::{
@@ -373,6 +374,7 @@ struct PathToolData {
373374
current_selected_handle_id: Option<ManipulatorPointId>,
374375
angle: f64,
375376
opposite_handle_position: Option<DVec2>,
377+
snapping_axis: Option<Axis>,
376378
}
377379

378380
impl PathToolData {
@@ -736,6 +738,50 @@ impl PathToolData {
736738
document.metadata().document_to_viewport.transform_vector2(snap_result.snapped_point_document - handle_position)
737739
}
738740

741+
fn start_snap_along_axis(&mut self, shape_editor: &mut ShapeState, document: &DocumentMessageHandler, input: &InputPreprocessorMessageHandler, responses: &mut VecDeque<Message>) {
742+
// Find the negative delta to take the point to the drag start position
743+
let current_mouse = input.mouse.position;
744+
let drag_start = self.drag_start_pos;
745+
let opposite_delta = drag_start - current_mouse;
746+
747+
shape_editor.move_selected_points(None, document, opposite_delta, false, true, None, responses);
748+
749+
// Calculate the projected delta and shift the points along that delta
750+
let delta = current_mouse - drag_start;
751+
let axis = if delta.x.abs() >= delta.y.abs() { Axis::X } else { Axis::Y };
752+
self.snapping_axis = Some(axis);
753+
let projected_delta = match axis {
754+
Axis::X => DVec2::new(delta.x, 0.),
755+
Axis::Y => DVec2::new(0., delta.y),
756+
_ => DVec2::new(delta.x, 0.),
757+
};
758+
759+
shape_editor.move_selected_points(None, document, projected_delta, false, true, None, responses);
760+
}
761+
762+
fn stop_snap_along_axis(&mut self, shape_editor: &mut ShapeState, document: &DocumentMessageHandler, input: &InputPreprocessorMessageHandler, responses: &mut VecDeque<Message>) {
763+
// Calculate the negative delta of the selection and move it back to the drag start
764+
let current_mouse = input.mouse.position;
765+
let drag_start = self.drag_start_pos;
766+
767+
let opposite_delta = drag_start - current_mouse;
768+
let Some(axis) = self.snapping_axis else { return };
769+
let opposite_projected_delta = match axis {
770+
Axis::X => DVec2::new(opposite_delta.x, 0.),
771+
Axis::Y => DVec2::new(0., opposite_delta.y),
772+
_ => DVec2::new(opposite_delta.x, 0.),
773+
};
774+
775+
shape_editor.move_selected_points(None, document, opposite_projected_delta, false, true, None, responses);
776+
777+
// Calculate what actually would have been the original delta for the point, and apply that
778+
let delta = current_mouse - drag_start;
779+
780+
shape_editor.move_selected_points(None, document, delta, false, true, None, responses);
781+
782+
self.snapping_axis = None;
783+
}
784+
739785
#[allow(clippy::too_many_arguments)]
740786
fn drag(
741787
&mut self,
@@ -747,6 +793,19 @@ impl PathToolData {
747793
input: &InputPreprocessorMessageHandler,
748794
responses: &mut VecDeque<Message>,
749795
) {
796+
// First check if selection is not just a single handle point
797+
let selected_points = shape_editor.selected_points();
798+
let single_handle_selected = selected_points.count() == 1
799+
&& shape_editor
800+
.selected_points()
801+
.any(|point| matches!(point, ManipulatorPointId::EndHandle(_) | ManipulatorPointId::PrimaryHandle(_)));
802+
803+
if snap_angle && self.snapping_axis.is_none() && !single_handle_selected {
804+
self.start_snap_along_axis(shape_editor, document, input, responses);
805+
} else if !snap_angle && self.snapping_axis.is_some() {
806+
self.stop_snap_along_axis(shape_editor, document, input, responses);
807+
}
808+
750809
let document_to_viewport = document.metadata().document_to_viewport;
751810
let previous_mouse = document_to_viewport.transform_point2(self.previous_mouse_position);
752811
let current_mouse = input.mouse.position;
@@ -769,8 +828,31 @@ impl PathToolData {
769828

770829
let handle_lengths = if equidistant { None } else { self.opposing_handle_lengths.take() };
771830
let opposite = if lock_angle { None } else { self.opposite_handle_position };
772-
shape_editor.move_selected_points(handle_lengths, document, snapped_delta, equidistant, true, opposite, responses);
773-
self.previous_mouse_position += document_to_viewport.inverse().transform_vector2(snapped_delta);
831+
let unsnapped_delta = current_mouse - previous_mouse;
832+
833+
if self.snapping_axis.is_none() {
834+
shape_editor.move_selected_points(handle_lengths, document, snapped_delta, equidistant, true, opposite, responses);
835+
self.previous_mouse_position += document_to_viewport.inverse().transform_vector2(snapped_delta);
836+
} else {
837+
let Some(axis) = self.snapping_axis else { return };
838+
let projected_delta = match axis {
839+
Axis::X => DVec2::new(unsnapped_delta.x, 0.),
840+
Axis::Y => DVec2::new(0., unsnapped_delta.y),
841+
_ => DVec2::new(unsnapped_delta.x, 0.),
842+
};
843+
shape_editor.move_selected_points(handle_lengths, document, projected_delta, equidistant, true, opposite, responses);
844+
self.previous_mouse_position += document_to_viewport.inverse().transform_vector2(unsnapped_delta);
845+
}
846+
847+
if snap_angle && self.snapping_axis.is_some() {
848+
let Some(current_axis) = self.snapping_axis else { return };
849+
let total_delta = self.drag_start_pos - input.mouse.position;
850+
851+
if (total_delta.x.abs() > total_delta.y.abs() && current_axis == Axis::Y) || (total_delta.y.abs() > total_delta.x.abs() && current_axis == Axis::X) {
852+
self.stop_snap_along_axis(shape_editor, document, input, responses);
853+
self.start_snap_along_axis(shape_editor, document, input, responses);
854+
}
855+
}
774856
}
775857
}
776858

@@ -872,6 +954,28 @@ impl Fsm for PathToolFsmState {
872954
}
873955
Self::Dragging(_) => {
874956
tool_data.snap_manager.draw_overlays(SnapData::new(document, input), &mut overlay_context);
957+
958+
// Draw the snapping axis lines
959+
if tool_data.snapping_axis.is_some() {
960+
let Some(axis) = tool_data.snapping_axis else { return self };
961+
let origin = tool_data.drag_start_pos;
962+
let viewport_diagonal = input.viewport_bounds.size().length();
963+
964+
let mut faded_blue = graphene_std::Color::from_rgb_str(COLOR_OVERLAY_BLUE.strip_prefix('#').unwrap()).unwrap().with_alpha(0.25).rgba_hex();
965+
faded_blue.insert(0, '#');
966+
let other = faded_blue.as_str();
967+
968+
match axis {
969+
Axis::Y => {
970+
overlay_context.line(origin - DVec2::Y * viewport_diagonal, origin + DVec2::Y * viewport_diagonal, Some(COLOR_OVERLAY_BLUE));
971+
overlay_context.line(origin - DVec2::X * viewport_diagonal, origin + DVec2::X * viewport_diagonal, Some(other));
972+
}
973+
Axis::X | Axis::Both => {
974+
overlay_context.line(origin - DVec2::X * viewport_diagonal, origin + DVec2::X * viewport_diagonal, Some(COLOR_OVERLAY_BLUE));
975+
overlay_context.line(origin - DVec2::Y * viewport_diagonal, origin + DVec2::Y * viewport_diagonal, Some(other));
976+
}
977+
}
978+
}
875979
}
876980
Self::InsertPoint => {
877981
let state = tool_data.update_insertion(shape_editor, document, responses, input);
@@ -1065,19 +1169,10 @@ impl Fsm for PathToolFsmState {
10651169

10661170
PathToolFsmState::Drawing { selection_shape: selection_type }
10671171
}
1068-
(
1069-
PathToolFsmState::Dragging(dragging_state),
1070-
PathToolMessage::PointerOutsideViewport {
1071-
equidistant, snap_angle, lock_angle, ..
1072-
},
1073-
) => {
1172+
(PathToolFsmState::Dragging(dragging_state), PathToolMessage::PointerOutsideViewport { .. }) => {
10741173
// Auto-panning
1075-
if tool_data.auto_panning.shift_viewport(input, responses).is_some() {
1076-
let equidistant = input.keyboard.get(equidistant as usize);
1077-
let snap_angle = input.keyboard.get(snap_angle as usize);
1078-
let lock_angle = input.keyboard.get(lock_angle as usize);
1079-
1080-
tool_data.drag(equidistant, lock_angle, snap_angle, shape_editor, document, input, responses);
1174+
if let Some(offset) = tool_data.auto_panning.shift_viewport(input, responses) {
1175+
tool_data.drag_start_pos += offset;
10811176
}
10821177

10831178
PathToolFsmState::Dragging(dragging_state)
@@ -1223,6 +1318,10 @@ impl Fsm for PathToolFsmState {
12231318
shape_editor.deselect_all_points();
12241319
}
12251320

1321+
if tool_data.snapping_axis.is_some() {
1322+
tool_data.snapping_axis = None;
1323+
}
1324+
12261325
responses.add(DocumentMessage::EndTransaction);
12271326
responses.add(PathToolMessage::SelectedPointUpdated);
12281327
tool_data.snap_manager.cleanup(responses);

0 commit comments

Comments
 (0)