@@ -7,6 +7,7 @@ use crate::messages::portfolio::document::overlays::utility_functions::{path_ove
7
7
use crate :: messages:: portfolio:: document:: overlays:: utility_types:: { DrawHandles , OverlayContext } ;
8
8
use crate :: messages:: portfolio:: document:: utility_types:: document_metadata:: LayerNodeIdentifier ;
9
9
use crate :: messages:: portfolio:: document:: utility_types:: network_interface:: NodeNetworkInterface ;
10
+ use crate :: messages:: portfolio:: document:: utility_types:: transformation:: Axis ;
10
11
use crate :: messages:: preferences:: SelectionMode ;
11
12
use crate :: messages:: tool:: common_functionality:: auto_panning:: AutoPanning ;
12
13
use crate :: messages:: tool:: common_functionality:: shape_editor:: {
@@ -373,6 +374,7 @@ struct PathToolData {
373
374
current_selected_handle_id : Option < ManipulatorPointId > ,
374
375
angle : f64 ,
375
376
opposite_handle_position : Option < DVec2 > ,
377
+ snapping_axis : Option < Axis > ,
376
378
}
377
379
378
380
impl PathToolData {
@@ -736,6 +738,50 @@ impl PathToolData {
736
738
document. metadata ( ) . document_to_viewport . transform_vector2 ( snap_result. snapped_point_document - handle_position)
737
739
}
738
740
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
+
739
785
#[ allow( clippy:: too_many_arguments) ]
740
786
fn drag (
741
787
& mut self ,
@@ -747,6 +793,19 @@ impl PathToolData {
747
793
input : & InputPreprocessorMessageHandler ,
748
794
responses : & mut VecDeque < Message > ,
749
795
) {
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
+
750
809
let document_to_viewport = document. metadata ( ) . document_to_viewport ;
751
810
let previous_mouse = document_to_viewport. transform_point2 ( self . previous_mouse_position ) ;
752
811
let current_mouse = input. mouse . position ;
@@ -769,8 +828,31 @@ impl PathToolData {
769
828
770
829
let handle_lengths = if equidistant { None } else { self . opposing_handle_lengths . take ( ) } ;
771
830
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
+ }
774
856
}
775
857
}
776
858
@@ -872,6 +954,28 @@ impl Fsm for PathToolFsmState {
872
954
}
873
955
Self :: Dragging ( _) => {
874
956
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
+ }
875
979
}
876
980
Self :: InsertPoint => {
877
981
let state = tool_data. update_insertion ( shape_editor, document, responses, input) ;
@@ -1065,19 +1169,10 @@ impl Fsm for PathToolFsmState {
1065
1169
1066
1170
PathToolFsmState :: Drawing { selection_shape : selection_type }
1067
1171
}
1068
- (
1069
- PathToolFsmState :: Dragging ( dragging_state) ,
1070
- PathToolMessage :: PointerOutsideViewport {
1071
- equidistant, snap_angle, lock_angle, ..
1072
- } ,
1073
- ) => {
1172
+ ( PathToolFsmState :: Dragging ( dragging_state) , PathToolMessage :: PointerOutsideViewport { .. } ) => {
1074
1173
// 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;
1081
1176
}
1082
1177
1083
1178
PathToolFsmState :: Dragging ( dragging_state)
@@ -1223,6 +1318,10 @@ impl Fsm for PathToolFsmState {
1223
1318
shape_editor. deselect_all_points ( ) ;
1224
1319
}
1225
1320
1321
+ if tool_data. snapping_axis . is_some ( ) {
1322
+ tool_data. snapping_axis = None ;
1323
+ }
1324
+
1226
1325
responses. add ( DocumentMessage :: EndTransaction ) ;
1227
1326
responses. add ( PathToolMessage :: SelectedPointUpdated ) ;
1228
1327
tool_data. snap_manager . cleanup ( responses) ;
0 commit comments