@@ -15,8 +15,8 @@ use crate::messages::tool::common_functionality::shape_editor::{
15
15
} ;
16
16
use crate :: messages:: tool:: common_functionality:: snapping:: { SnapCache , SnapCandidatePoint , SnapConstraint , SnapData , SnapManager } ;
17
17
use graphene_core:: renderer:: Quad ;
18
- use graphene_core:: vector:: { ManipulatorPointId , PointId } ;
19
- use graphene_std:: vector:: { NoHashBuilder , SegmentId } ;
18
+ use graphene_core:: vector:: { ManipulatorPointId , PointId , VectorModificationType } ;
19
+ use graphene_std:: vector:: { HandleId , NoHashBuilder , SegmentId , VectorData } ;
20
20
use std:: vec;
21
21
22
22
#[ derive( Default ) ]
@@ -65,6 +65,7 @@ pub enum PathToolMessage {
65
65
direct_insert_without_sliding : Key ,
66
66
extend_selection : Key ,
67
67
lasso_select : Key ,
68
+ handle_drag_from_anchor : Key ,
68
69
} ,
69
70
NudgeSelectedPoints {
70
71
delta_x : f64 ,
@@ -375,6 +376,8 @@ struct PathToolData {
375
376
angle : f64 ,
376
377
opposite_handle_position : Option < DVec2 > ,
377
378
snapping_axis : Option < Axis > ,
379
+ alt_clicked_on_anchor : bool ,
380
+ alt_dragging_from_anchor : bool ,
378
381
}
379
382
380
383
impl PathToolData {
@@ -489,6 +492,7 @@ impl PathToolData {
489
492
extend_selection : bool ,
490
493
direct_insert_without_sliding : bool ,
491
494
lasso_select : bool ,
495
+ handle_drag_from_anchor : bool ,
492
496
) -> PathToolFsmState {
493
497
self . double_click_handled = false ;
494
498
self . opposing_handle_lengths = None ;
@@ -516,6 +520,31 @@ impl PathToolData {
516
520
self . saved_points_before_handle_drag = old_selection;
517
521
}
518
522
523
+ if handle_drag_from_anchor {
524
+ if let Some ( ( layer, point) ) = shape_editor. find_nearest_point_indices ( & document. network_interface , input. mouse . position , SELECTION_THRESHOLD ) {
525
+ // Check that selected point is an anchor
526
+ if let ( Some ( point_id) , Some ( vector_data) ) = ( point. as_anchor ( ) , document. network_interface . compute_modified_vector ( layer) ) {
527
+ let handles = vector_data. all_connected ( point_id) . collect :: < Vec < _ > > ( ) ;
528
+ self . alt_clicked_on_anchor = true ;
529
+ for handle in & handles {
530
+ let modification_type = handle. set_relative_position ( DVec2 :: ZERO ) ;
531
+ responses. add ( GraphOperationMessage :: Vector { layer, modification_type } ) ;
532
+ for & handles in & vector_data. colinear_manipulators {
533
+ if handles. contains ( & handle) {
534
+ let modification_type = VectorModificationType :: SetG1Continuous { handles, enabled : false } ;
535
+ responses. add ( GraphOperationMessage :: Vector { layer, modification_type } ) ;
536
+ }
537
+ }
538
+ }
539
+
540
+ let manipulator_point_id = handles[ 0 ] . to_manipulator_point ( ) ;
541
+ shape_editor. deselect_all_points ( ) ;
542
+ shape_editor. select_points_by_manipulator_id ( & vec ! [ manipulator_point_id] ) ;
543
+ responses. add ( PathToolMessage :: SelectedPointUpdated ) ;
544
+ }
545
+ }
546
+ }
547
+
519
548
self . start_dragging_point ( selected_points, input, document, shape_editor) ;
520
549
responses. add ( OverlaysMessage :: Draw ) ;
521
550
}
@@ -744,7 +773,7 @@ impl PathToolData {
744
773
let drag_start = self . drag_start_pos ;
745
774
let opposite_delta = drag_start - current_mouse;
746
775
747
- shape_editor. move_selected_points ( None , document, opposite_delta, false , true , None , responses) ;
776
+ shape_editor. move_selected_points ( None , document, opposite_delta, false , true , false , None , responses) ;
748
777
749
778
// Calculate the projected delta and shift the points along that delta
750
779
let delta = current_mouse - drag_start;
@@ -756,7 +785,7 @@ impl PathToolData {
756
785
_ => DVec2 :: new ( delta. x , 0. ) ,
757
786
} ;
758
787
759
- shape_editor. move_selected_points ( None , document, projected_delta, false , true , None , responses) ;
788
+ shape_editor. move_selected_points ( None , document, projected_delta, false , true , false , None , responses) ;
760
789
}
761
790
762
791
fn stop_snap_along_axis ( & mut self , shape_editor : & mut ShapeState , document : & DocumentMessageHandler , input : & InputPreprocessorMessageHandler , responses : & mut VecDeque < Message > ) {
@@ -772,16 +801,33 @@ impl PathToolData {
772
801
_ => DVec2 :: new ( opposite_delta. x , 0. ) ,
773
802
} ;
774
803
775
- shape_editor. move_selected_points ( None , document, opposite_projected_delta, false , true , None , responses) ;
804
+ shape_editor. move_selected_points ( None , document, opposite_projected_delta, false , true , false , None , responses) ;
776
805
777
806
// Calculate what actually would have been the original delta for the point, and apply that
778
807
let delta = current_mouse - drag_start;
779
808
780
- shape_editor. move_selected_points ( None , document, delta, false , true , None , responses) ;
809
+ shape_editor. move_selected_points ( None , document, delta, false , true , false , None , responses) ;
781
810
782
811
self . snapping_axis = None ;
783
812
}
784
813
814
+ fn get_normalized_tangent ( & mut self , point : PointId , segment : SegmentId , vector_data : & VectorData ) -> Option < DVec2 > {
815
+ let other_point = vector_data. other_point ( segment, point) ?;
816
+ let position = ManipulatorPointId :: Anchor ( point) . get_position ( vector_data) ?;
817
+
818
+ let mut handles = vector_data. all_connected ( other_point) ;
819
+ let other_handle = handles. find ( |handle| handle. segment == segment) ?;
820
+
821
+ let target_position = if other_handle. length ( vector_data) == 0. {
822
+ ManipulatorPointId :: Anchor ( other_point) . get_position ( vector_data) ?
823
+ } else {
824
+ other_handle. to_manipulator_point ( ) . get_position ( vector_data) ?
825
+ } ;
826
+
827
+ let tangent_vector = target_position - position;
828
+ tangent_vector. try_normalize ( )
829
+ }
830
+
785
831
#[ allow( clippy:: too_many_arguments) ]
786
832
fn drag (
787
833
& mut self ,
@@ -829,9 +875,51 @@ impl PathToolData {
829
875
let handle_lengths = if equidistant { None } else { self . opposing_handle_lengths . take ( ) } ;
830
876
let opposite = if lock_angle { None } else { self . opposite_handle_position } ;
831
877
let unsnapped_delta = current_mouse - previous_mouse;
878
+ let mut was_alt_dragging = false ;
832
879
833
880
if self . snapping_axis . is_none ( ) {
834
- shape_editor. move_selected_points ( handle_lengths, document, snapped_delta, equidistant, true , opposite, responses) ;
881
+ if self . alt_clicked_on_anchor && !self . alt_dragging_from_anchor && self . drag_start_pos . distance ( input. mouse . position ) > DRAG_THRESHOLD {
882
+ // Checking which direction the dragging begins
883
+ self . alt_dragging_from_anchor = true ;
884
+ let Some ( layer) = document. network_interface . selected_nodes ( ) . selected_layers ( document. metadata ( ) ) . next ( ) else {
885
+ return ;
886
+ } ;
887
+ let Some ( vector_data) = document. network_interface . compute_modified_vector ( layer) else { return } ;
888
+ let Some ( point_id) = shape_editor. selected_points ( ) . next ( ) . unwrap ( ) . get_anchor ( & vector_data) else {
889
+ return ;
890
+ } ;
891
+
892
+ if vector_data. connected_count ( point_id) == 2 {
893
+ let connected_segments: Vec < HandleId > = vector_data. all_connected ( point_id) . collect ( ) ;
894
+ let segment1 = connected_segments[ 0 ] ;
895
+ let Some ( tangent1) = self . get_normalized_tangent ( point_id, segment1. segment , & vector_data) else {
896
+ return ;
897
+ } ;
898
+ let segment2 = connected_segments[ 1 ] ;
899
+ let Some ( tangent2) = self . get_normalized_tangent ( point_id, segment2. segment , & vector_data) else {
900
+ return ;
901
+ } ;
902
+
903
+ let delta = input. mouse . position - self . drag_start_pos ;
904
+ let handle = if delta. dot ( tangent1) >= delta. dot ( tangent2) {
905
+ segment1. to_manipulator_point ( )
906
+ } else {
907
+ segment2. to_manipulator_point ( )
908
+ } ;
909
+
910
+ // Now change the selection to this handle
911
+ shape_editor. deselect_all_points ( ) ;
912
+ shape_editor. select_points_by_manipulator_id ( & vec ! [ handle] ) ;
913
+ responses. add ( PathToolMessage :: SelectionChanged ) ;
914
+ }
915
+ }
916
+
917
+ if self . alt_dragging_from_anchor && !equidistant && self . alt_clicked_on_anchor {
918
+ was_alt_dragging = true ;
919
+ self . alt_dragging_from_anchor = false ;
920
+ self . alt_clicked_on_anchor = false ;
921
+ }
922
+ shape_editor. move_selected_points ( handle_lengths, document, snapped_delta, equidistant, true , was_alt_dragging, opposite, responses) ;
835
923
self . previous_mouse_position += document_to_viewport. inverse ( ) . transform_vector2 ( snapped_delta) ;
836
924
} else {
837
925
let Some ( axis) = self . snapping_axis else { return } ;
@@ -840,7 +928,7 @@ impl PathToolData {
840
928
Axis :: Y => DVec2 :: new ( 0. , unsnapped_delta. y ) ,
841
929
_ => DVec2 :: new ( unsnapped_delta. x , 0. ) ,
842
930
} ;
843
- shape_editor. move_selected_points ( handle_lengths, document, projected_delta, equidistant, true , opposite, responses) ;
931
+ shape_editor. move_selected_points ( handle_lengths, document, projected_delta, equidistant, true , false , opposite, responses) ;
844
932
self . previous_mouse_position += document_to_viewport. inverse ( ) . transform_vector2 ( unsnapped_delta) ;
845
933
}
846
934
@@ -1024,16 +1112,27 @@ impl Fsm for PathToolFsmState {
1024
1112
direct_insert_without_sliding,
1025
1113
extend_selection,
1026
1114
lasso_select,
1115
+ handle_drag_from_anchor,
1027
1116
} ,
1028
1117
) => {
1029
1118
let extend_selection = input. keyboard . get ( extend_selection as usize ) ;
1030
1119
let lasso_select = input. keyboard . get ( lasso_select as usize ) ;
1031
1120
let direct_insert_without_sliding = input. keyboard . get ( direct_insert_without_sliding as usize ) ;
1121
+ let handle_drag_from_anchor = input. keyboard . get ( handle_drag_from_anchor as usize ) ;
1032
1122
1033
1123
tool_data. selection_mode = None ;
1034
1124
tool_data. lasso_polygon . clear ( ) ;
1035
1125
1036
- tool_data. mouse_down ( shape_editor, document, input, responses, extend_selection, direct_insert_without_sliding, lasso_select)
1126
+ tool_data. mouse_down (
1127
+ shape_editor,
1128
+ document,
1129
+ input,
1130
+ responses,
1131
+ extend_selection,
1132
+ direct_insert_without_sliding,
1133
+ lasso_select,
1134
+ handle_drag_from_anchor,
1135
+ )
1037
1136
}
1038
1137
(
1039
1138
PathToolFsmState :: Drawing { selection_shape } ,
@@ -1295,6 +1394,9 @@ impl Fsm for PathToolFsmState {
1295
1394
tool_data. handle_drag_toggle = false ;
1296
1395
}
1297
1396
1397
+ tool_data. alt_dragging_from_anchor = false ;
1398
+ tool_data. alt_clicked_on_anchor = false ;
1399
+
1298
1400
if tool_data. select_anchor_toggled {
1299
1401
shape_editor. deselect_all_points ( ) ;
1300
1402
shape_editor. select_points_by_manipulator_id ( & tool_data. saved_points_before_anchor_select_toggle ) ;
@@ -1385,6 +1487,7 @@ impl Fsm for PathToolFsmState {
1385
1487
( delta_x, delta_y) . into ( ) ,
1386
1488
true ,
1387
1489
false ,
1490
+ false ,
1388
1491
tool_data. opposite_handle_position ,
1389
1492
responses,
1390
1493
) ;
@@ -1446,7 +1549,11 @@ impl Fsm for PathToolFsmState {
1446
1549
HintGroup ( vec![ HintInfo :: mouse( MouseMotion :: LmbDrag , "Select Area" ) , HintInfo :: keys( [ Key :: Control ] , "Lasso" ) . prepend_plus( ) ] ) ,
1447
1550
HintGroup ( vec![ HintInfo :: mouse( MouseMotion :: Lmb , "Insert Point on Segment" ) ] ) ,
1448
1551
// TODO: Only show if at least one anchor is selected, and dynamically show either "Smooth" or "Sharp" based on the current state
1449
- HintGroup ( vec![ HintInfo :: mouse( MouseMotion :: LmbDouble , "Make Anchor Smooth/Sharp" ) ] ) ,
1552
+ HintGroup ( vec![
1553
+ HintInfo :: mouse( MouseMotion :: LmbDouble , "Convert Anchor Point" ) ,
1554
+ HintInfo :: keys_and_mouse( [ Key :: Alt ] , MouseMotion :: Lmb , "To Sharp" ) ,
1555
+ HintInfo :: keys_and_mouse( [ Key :: Alt ] , MouseMotion :: LmbDrag , "To Smooth" ) ,
1556
+ ] ) ,
1450
1557
// TODO: Only show the following hints if at least one point is selected
1451
1558
HintGroup ( vec![ HintInfo :: mouse( MouseMotion :: LmbDrag , "Drag Selected" ) ] ) ,
1452
1559
HintGroup ( vec![ HintInfo :: multi_keys( [ [ Key :: KeyG ] , [ Key :: KeyR ] , [ Key :: KeyS ] ] , "Grab/Rotate/Scale Selected" ) ] ) ,
0 commit comments