11use super :: tool_prelude:: * ;
2- use crate :: consts:: { COLOR_OVERLAY_YELLOW , DRAG_THRESHOLD , INSERT_POINT_ON_SEGMENT_TOO_FAR_DISTANCE , SELECTION_THRESHOLD , SELECTION_TOLERANCE } ;
2+ use crate :: consts:: { COLOR_OVERLAY_YELLOW , DRAG_THRESHOLD , HANDLE_ROTATE_SNAP_ANGLE , INSERT_POINT_ON_SEGMENT_TOO_FAR_DISTANCE , SELECTION_THRESHOLD , SELECTION_TOLERANCE } ;
33use crate :: messages:: portfolio:: document:: overlays:: utility_functions:: path_overlays;
44use crate :: messages:: portfolio:: document:: overlays:: utility_types:: OverlayContext ;
55use crate :: messages:: portfolio:: document:: utility_types:: document_metadata:: LayerNodeIdentifier ;
66use crate :: messages:: portfolio:: document:: utility_types:: network_interface:: NodeNetworkInterface ;
77use crate :: messages:: tool:: common_functionality:: auto_panning:: AutoPanning ;
88use crate :: messages:: tool:: common_functionality:: shape_editor:: { ClosestSegment , ManipulatorAngle , OpposingHandleLengths , SelectedPointsInfo , ShapeState } ;
9- use crate :: messages:: tool:: common_functionality:: snapping:: { SnapCache , SnapCandidatePoint , SnapData , SnapManager } ;
9+ use crate :: messages:: tool:: common_functionality:: snapping:: { SnapCache , SnapCandidatePoint , SnapConstraint , SnapData , SnapManager } ;
1010
1111use graphene_core:: renderer:: Quad ;
1212use graphene_core:: vector:: ManipulatorPointId ;
@@ -59,11 +59,15 @@ pub enum PathToolMessage {
5959 equidistant : Key ,
6060 toggle_colinear : Key ,
6161 move_anchor_with_handles : Key ,
62+ snap_angle : Key ,
63+ lock_angle : Key ,
6264 } ,
6365 PointerOutsideViewport {
6466 equidistant : Key ,
6567 toggle_colinear : Key ,
6668 move_anchor_with_handles : Key ,
69+ snap_angle : Key ,
70+ lock_angle : Key ,
6771 } ,
6872 RightClick ,
6973 SelectAllAnchors ,
@@ -294,6 +298,7 @@ struct PathToolData {
294298 saved_points_before_anchor_select_toggle : Vec < ManipulatorPointId > ,
295299 select_anchor_toggled : bool ,
296300 dragging_state : DraggingState ,
301+ angle : f64 ,
297302}
298303
299304impl PathToolData {
@@ -466,13 +471,114 @@ impl PathToolData {
466471 false
467472 }
468473
469- fn drag ( & mut self , equidistant : bool , shape_editor : & mut ShapeState , document : & DocumentMessageHandler , input : & InputPreprocessorMessageHandler , responses : & mut VecDeque < Message > ) {
470- // Move the selected points with the mouse
471- let previous_mouse = document. metadata ( ) . document_to_viewport . transform_point2 ( self . previous_mouse_position ) ;
472- let snapped_delta = shape_editor. snap ( & mut self . snap_manager , & self . snap_cache , document, input, previous_mouse) ;
474+ /// Attempts to get a single selected handle. Also retrieves the position of the anchor it is connected to. Used for the purpose of snapping the angle.
475+ fn try_get_selected_handle_and_anchor ( & self , shape_editor : & ShapeState , document : & DocumentMessageHandler ) -> Option < ( DVec2 , DVec2 ) > {
476+ // Only count selections of a single layer
477+ let ( layer, selection) = shape_editor. selected_shape_state . iter ( ) . next ( ) ?;
478+
479+ // Do not allow selections of multiple points to count
480+ if selection. selected_points_count ( ) != 1 {
481+ return None ;
482+ }
483+
484+ // Only count selected handles
485+ let selected_handle = selection. selected ( ) . next ( ) ?. as_handle ( ) ?;
486+
487+ let layer_to_document = document. metadata ( ) . transform_to_document ( * layer) ;
488+ let vector_data = document. network_interface . compute_modified_vector ( * layer) ?;
489+
490+ let handle_position_local = selected_handle. to_manipulator_point ( ) . get_position ( & vector_data) ?;
491+ let anchor_id = selected_handle. to_manipulator_point ( ) . get_anchor ( & vector_data) ?;
492+ let anchor_position_local = vector_data. point_domain . position_from_id ( anchor_id) ?;
493+
494+ let handle_position_document = layer_to_document. transform_point2 ( handle_position_local) ;
495+ let anchor_position_document = layer_to_document. transform_point2 ( anchor_position_local) ;
496+
497+ Some ( ( handle_position_document, anchor_position_document) )
498+ }
499+
500+ fn calculate_handle_angle ( & mut self , handle_vector : DVec2 , lock_angle : bool , snap_angle : bool ) -> f64 {
501+ let mut handle_angle = -handle_vector. angle_to ( DVec2 :: X ) ;
502+
503+ // When the angle is locked we use the old angle
504+ if lock_angle {
505+ handle_angle = self . angle
506+ }
507+
508+ // Round the angle to the closest increment
509+ if snap_angle {
510+ let snap_resolution = HANDLE_ROTATE_SNAP_ANGLE . to_radians ( ) ;
511+ handle_angle = ( handle_angle / snap_resolution) . round ( ) * snap_resolution;
512+ }
513+
514+ // Cache the old handle angle for the lock angle.
515+ self . angle = handle_angle;
516+
517+ handle_angle
518+ }
519+
520+ fn apply_snapping (
521+ & mut self ,
522+ handle_direction : DVec2 ,
523+ new_handle_position : DVec2 ,
524+ anchor_position : DVec2 ,
525+ using_angle_constraints : bool ,
526+ handle_position : DVec2 ,
527+ document : & DocumentMessageHandler ,
528+ input : & InputPreprocessorMessageHandler ,
529+ ) -> DVec2 {
530+ let snap_data = SnapData :: new ( document, input) ;
531+ let snap_point = SnapCandidatePoint :: handle_neighbors ( new_handle_position, [ anchor_position] ) ;
532+
533+ let snap_result = match using_angle_constraints {
534+ true => {
535+ let snap_constraint = SnapConstraint :: Line {
536+ origin : anchor_position,
537+ direction : handle_direction. normalize_or_zero ( ) ,
538+ } ;
539+ self . snap_manager . constrained_snap ( & snap_data, & snap_point, snap_constraint, Default :: default ( ) )
540+ }
541+ false => self . snap_manager . free_snap ( & snap_data, & snap_point, Default :: default ( ) ) ,
542+ } ;
543+
544+ self . snap_manager . update_indicator ( snap_result. clone ( ) ) ;
545+
546+ document. metadata ( ) . document_to_viewport . transform_vector2 ( snap_result. snapped_point_document - handle_position)
547+ }
548+
549+ fn drag (
550+ & mut self ,
551+ equidistant : bool ,
552+ lock_angle : bool ,
553+ snap_angle : bool ,
554+ shape_editor : & mut ShapeState ,
555+ document : & DocumentMessageHandler ,
556+ input : & InputPreprocessorMessageHandler ,
557+ responses : & mut VecDeque < Message > ,
558+ ) {
559+ let document_to_viewport = document. metadata ( ) . document_to_viewport ;
560+ let previous_mouse = document_to_viewport. transform_point2 ( self . previous_mouse_position ) ;
561+ let current_mouse = input. mouse . position ;
562+ let raw_delta = document_to_viewport. inverse ( ) . transform_vector2 ( current_mouse - previous_mouse) ;
563+
564+ let snapped_delta = if let Some ( ( handle_pos, anchor_pos) ) = self . try_get_selected_handle_and_anchor ( shape_editor, document) {
565+ let cursor_pos = handle_pos + raw_delta;
566+
567+ let handle_angle = self . calculate_handle_angle ( cursor_pos - anchor_pos, lock_angle, snap_angle) ;
568+
569+ let constrained_direction = DVec2 :: new ( handle_angle. cos ( ) , handle_angle. sin ( ) ) ;
570+ let projected_length = ( cursor_pos - anchor_pos) . dot ( constrained_direction) ;
571+ let constrained_target = anchor_pos + constrained_direction * projected_length;
572+ let constrained_delta = constrained_target - handle_pos;
573+
574+ self . apply_snapping ( constrained_direction, handle_pos + constrained_delta, anchor_pos, lock_angle || snap_angle, handle_pos, document, input)
575+ } else {
576+ shape_editor. snap ( & mut self . snap_manager , & self . snap_cache , document, input, previous_mouse)
577+ } ;
578+
473579 let handle_lengths = if equidistant { None } else { self . opposing_handle_lengths . take ( ) } ;
474580 shape_editor. move_selected_points ( handle_lengths, document, snapped_delta, equidistant, responses, true ) ;
475- self . previous_mouse_position += document . metadata ( ) . document_to_viewport . inverse ( ) . transform_vector2 ( snapped_delta) ;
581+ self . previous_mouse_position += document_to_viewport. inverse ( ) . transform_vector2 ( snapped_delta) ;
476582 }
477583}
478584
@@ -574,6 +680,8 @@ impl Fsm for PathToolFsmState {
574680 equidistant,
575681 toggle_colinear,
576682 move_anchor_with_handles,
683+ snap_angle,
684+ lock_angle,
577685 } ,
578686 ) => {
579687 tool_data. previous_mouse_position = input. mouse . position ;
@@ -585,12 +693,16 @@ impl Fsm for PathToolFsmState {
585693 equidistant,
586694 toggle_colinear,
587695 move_anchor_with_handles,
696+ snap_angle,
697+ lock_angle,
588698 }
589699 . into ( ) ,
590700 PathToolMessage :: PointerMove {
591701 equidistant,
592702 toggle_colinear,
593703 move_anchor_with_handles,
704+ snap_angle,
705+ lock_angle,
594706 }
595707 . into ( ) ,
596708 ] ;
@@ -604,6 +716,8 @@ impl Fsm for PathToolFsmState {
604716 equidistant,
605717 toggle_colinear,
606718 move_anchor_with_handles,
719+ snap_angle,
720+ lock_angle,
607721 } ,
608722 ) => {
609723 if tool_data. selection_status . is_none ( ) {
@@ -631,8 +745,19 @@ impl Fsm for PathToolFsmState {
631745
632746 let toggle_colinear_state = input. keyboard . get ( toggle_colinear as usize ) ;
633747 let equidistant_state = input. keyboard . get ( equidistant as usize ) ;
634- if !tool_data. update_colinear ( equidistant_state, toggle_colinear_state, shape_editor, document, responses) {
635- tool_data. drag ( equidistant_state, shape_editor, document, input, responses) ;
748+ let lock_angle_state = input. keyboard . get ( lock_angle as usize ) ;
749+ let snap_angle_state = input. keyboard . get ( snap_angle as usize ) ;
750+
751+ if !tool_data. update_colinear ( equidistant_state, toggle_colinear_state, tool_action_data. shape_editor , tool_action_data. document , responses) {
752+ tool_data. drag (
753+ equidistant_state,
754+ lock_angle_state,
755+ snap_angle_state,
756+ tool_action_data. shape_editor ,
757+ tool_action_data. document ,
758+ input,
759+ responses,
760+ ) ;
636761 }
637762
638763 // Auto-panning
@@ -641,12 +766,16 @@ impl Fsm for PathToolFsmState {
641766 toggle_colinear,
642767 equidistant,
643768 move_anchor_with_handles,
769+ snap_angle,
770+ lock_angle,
644771 }
645772 . into ( ) ,
646773 PathToolMessage :: PointerMove {
647774 toggle_colinear,
648775 equidistant,
649776 move_anchor_with_handles,
777+ snap_angle,
778+ lock_angle,
650779 }
651780 . into ( ) ,
652781 ] ;
@@ -662,11 +791,19 @@ impl Fsm for PathToolFsmState {
662791
663792 PathToolFsmState :: DrawingBox
664793 }
665- ( PathToolFsmState :: Dragging ( dragging_state) , PathToolMessage :: PointerOutsideViewport { equidistant, .. } ) => {
794+ (
795+ PathToolFsmState :: Dragging ( dragging_state) ,
796+ PathToolMessage :: PointerOutsideViewport {
797+ equidistant, snap_angle, lock_angle, ..
798+ } ,
799+ ) => {
666800 // Auto-panning
667801 if tool_data. auto_panning . shift_viewport ( input, responses) . is_some ( ) {
668802 let equidistant = input. keyboard . get ( equidistant as usize ) ;
669- tool_data. drag ( equidistant, shape_editor, document, input, responses) ;
803+ let snap_angle = input. keyboard . get ( snap_angle as usize ) ;
804+ let lock_angle = input. keyboard . get ( lock_angle as usize ) ;
805+
806+ tool_data. drag ( equidistant, lock_angle, snap_angle, shape_editor, document, input, responses) ;
670807 }
671808
672809 PathToolFsmState :: Dragging ( dragging_state)
@@ -677,6 +814,8 @@ impl Fsm for PathToolFsmState {
677814 equidistant,
678815 toggle_colinear,
679816 move_anchor_with_handles,
817+ snap_angle,
818+ lock_angle,
680819 } ,
681820 ) => {
682821 // Auto-panning
@@ -685,12 +824,16 @@ impl Fsm for PathToolFsmState {
685824 equidistant,
686825 toggle_colinear,
687826 move_anchor_with_handles,
827+ snap_angle,
828+ lock_angle,
688829 }
689830 . into ( ) ,
690831 PathToolMessage :: PointerMove {
691832 equidistant,
692833 toggle_colinear,
693834 move_anchor_with_handles,
835+ snap_angle,
836+ lock_angle,
694837 }
695838 . into ( ) ,
696839 ] ;
@@ -890,7 +1033,12 @@ impl Fsm for PathToolFsmState {
8901033
8911034 let drag_anchor = HintInfo :: keys ( [ Key :: Space ] , "Drag Anchor" ) ;
8921035 let point_select_state_hint_group = match dragging_state. point_select_state {
893- PointSelectState :: HandleNoPair => vec ! [ drag_anchor] ,
1036+ PointSelectState :: HandleNoPair => {
1037+ let mut hints = vec ! [ drag_anchor] ;
1038+ hints. push ( HintInfo :: keys ( [ Key :: Shift ] , "Snap 15°" ) ) ;
1039+ hints. push ( HintInfo :: keys ( [ Key :: Control ] , "Lock Angle" ) ) ;
1040+ hints
1041+ }
8941042 PointSelectState :: HandleWithPair => {
8951043 let mut hints = vec ! [ drag_anchor] ;
8961044 hints. push ( HintInfo :: keys ( [ Key :: Tab ] , "Swap Selected Handles" ) ) ;
@@ -905,6 +1053,8 @@ impl Fsm for PathToolFsmState {
9051053 if colinear != ManipulatorAngle :: Free {
9061054 hints. push ( HintInfo :: keys ( [ Key :: Alt ] , "Equidistant Handles" ) ) ;
9071055 }
1056+ hints. push ( HintInfo :: keys ( [ Key :: Shift ] , "Snap 15°" ) ) ;
1057+ hints. push ( HintInfo :: keys ( [ Key :: Control ] , "Lock Angle" ) ) ;
9081058 hints
9091059 }
9101060 PointSelectState :: Anchor => Vec :: new ( ) ,
0 commit comments