@@ -15,8 +15,8 @@ use crate::messages::tool::common_functionality::shape_editor::{
1515} ;
1616use crate :: messages:: tool:: common_functionality:: snapping:: { SnapCache , SnapCandidatePoint , SnapConstraint , SnapData , SnapManager } ;
1717use 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 } ;
2020use std:: vec;
2121
2222#[ derive( Default ) ]
@@ -65,6 +65,7 @@ pub enum PathToolMessage {
6565 direct_insert_without_sliding : Key ,
6666 extend_selection : Key ,
6767 lasso_select : Key ,
68+ handle_drag_from_anchor : Key ,
6869 } ,
6970 NudgeSelectedPoints {
7071 delta_x : f64 ,
@@ -375,6 +376,8 @@ struct PathToolData {
375376 angle : f64 ,
376377 opposite_handle_position : Option < DVec2 > ,
377378 snapping_axis : Option < Axis > ,
379+ alt_clicked_on_anchor : bool ,
380+ alt_dragging_from_anchor : bool ,
378381}
379382
380383impl PathToolData {
@@ -489,6 +492,7 @@ impl PathToolData {
489492 extend_selection : bool ,
490493 direct_insert_without_sliding : bool ,
491494 lasso_select : bool ,
495+ handle_drag_from_anchor : bool ,
492496 ) -> PathToolFsmState {
493497 self . double_click_handled = false ;
494498 self . opposing_handle_lengths = None ;
@@ -516,6 +520,31 @@ impl PathToolData {
516520 self . saved_points_before_handle_drag = old_selection;
517521 }
518522
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+
519548 self . start_dragging_point ( selected_points, input, document, shape_editor) ;
520549 responses. add ( OverlaysMessage :: Draw ) ;
521550 }
@@ -744,7 +773,7 @@ impl PathToolData {
744773 let drag_start = self . drag_start_pos ;
745774 let opposite_delta = drag_start - current_mouse;
746775
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) ;
748777
749778 // Calculate the projected delta and shift the points along that delta
750779 let delta = current_mouse - drag_start;
@@ -756,7 +785,7 @@ impl PathToolData {
756785 _ => DVec2 :: new ( delta. x , 0. ) ,
757786 } ;
758787
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) ;
760789 }
761790
762791 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 {
772801 _ => DVec2 :: new ( opposite_delta. x , 0. ) ,
773802 } ;
774803
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) ;
776805
777806 // Calculate what actually would have been the original delta for the point, and apply that
778807 let delta = current_mouse - drag_start;
779808
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) ;
781810
782811 self . snapping_axis = None ;
783812 }
784813
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+
785831 #[ allow( clippy:: too_many_arguments) ]
786832 fn drag (
787833 & mut self ,
@@ -829,9 +875,51 @@ impl PathToolData {
829875 let handle_lengths = if equidistant { None } else { self . opposing_handle_lengths . take ( ) } ;
830876 let opposite = if lock_angle { None } else { self . opposite_handle_position } ;
831877 let unsnapped_delta = current_mouse - previous_mouse;
878+ let mut was_alt_dragging = false ;
832879
833880 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) ;
835923 self . previous_mouse_position += document_to_viewport. inverse ( ) . transform_vector2 ( snapped_delta) ;
836924 } else {
837925 let Some ( axis) = self . snapping_axis else { return } ;
@@ -840,7 +928,7 @@ impl PathToolData {
840928 Axis :: Y => DVec2 :: new ( 0. , unsnapped_delta. y ) ,
841929 _ => DVec2 :: new ( unsnapped_delta. x , 0. ) ,
842930 } ;
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) ;
844932 self . previous_mouse_position += document_to_viewport. inverse ( ) . transform_vector2 ( unsnapped_delta) ;
845933 }
846934
@@ -1024,16 +1112,27 @@ impl Fsm for PathToolFsmState {
10241112 direct_insert_without_sliding,
10251113 extend_selection,
10261114 lasso_select,
1115+ handle_drag_from_anchor,
10271116 } ,
10281117 ) => {
10291118 let extend_selection = input. keyboard . get ( extend_selection as usize ) ;
10301119 let lasso_select = input. keyboard . get ( lasso_select as usize ) ;
10311120 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 ) ;
10321122
10331123 tool_data. selection_mode = None ;
10341124 tool_data. lasso_polygon . clear ( ) ;
10351125
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+ )
10371136 }
10381137 (
10391138 PathToolFsmState :: Drawing { selection_shape } ,
@@ -1295,6 +1394,9 @@ impl Fsm for PathToolFsmState {
12951394 tool_data. handle_drag_toggle = false ;
12961395 }
12971396
1397+ tool_data. alt_dragging_from_anchor = false ;
1398+ tool_data. alt_clicked_on_anchor = false ;
1399+
12981400 if tool_data. select_anchor_toggled {
12991401 shape_editor. deselect_all_points ( ) ;
13001402 shape_editor. select_points_by_manipulator_id ( & tool_data. saved_points_before_anchor_select_toggle ) ;
@@ -1385,6 +1487,7 @@ impl Fsm for PathToolFsmState {
13851487 ( delta_x, delta_y) . into ( ) ,
13861488 true ,
13871489 false ,
1490+ false ,
13881491 tool_data. opposite_handle_position ,
13891492 responses,
13901493 ) ;
@@ -1446,7 +1549,11 @@ impl Fsm for PathToolFsmState {
14461549 HintGroup ( vec![ HintInfo :: mouse( MouseMotion :: LmbDrag , "Select Area" ) , HintInfo :: keys( [ Key :: Control ] , "Lasso" ) . prepend_plus( ) ] ) ,
14471550 HintGroup ( vec![ HintInfo :: mouse( MouseMotion :: Lmb , "Insert Point on Segment" ) ] ) ,
14481551 // 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+ ] ) ,
14501557 // TODO: Only show the following hints if at least one point is selected
14511558 HintGroup ( vec![ HintInfo :: mouse( MouseMotion :: LmbDrag , "Drag Selected" ) ] ) ,
14521559 HintGroup ( vec![ HintInfo :: multi_keys( [ [ Key :: KeyG ] , [ Key :: KeyR ] , [ Key :: KeyS ] ] , "Grab/Rotate/Scale Selected" ) ] ) ,
0 commit comments