@@ -15,6 +15,7 @@ use crate::messages::tool::common_functionality::shape_editor::{
1515 ClosestSegment , ManipulatorAngle , OpposingHandleLengths , SelectedPointsInfo , SelectionChange , SelectionShape , SelectionShapeType , ShapeState ,
1616} ;
1717use crate :: messages:: tool:: common_functionality:: snapping:: { SnapCache , SnapCandidatePoint , SnapConstraint , SnapData , SnapManager } ;
18+ use crate :: messages:: tool:: common_functionality:: utility_functions:: calculate_segment_angle;
1819use graphene_core:: renderer:: Quad ;
1920use graphene_core:: vector:: { ManipulatorPointId , PointId , VectorModificationType } ;
2021use graphene_std:: vector:: { HandleId , NoHashBuilder , SegmentId , VectorData } ;
@@ -380,6 +381,8 @@ struct PathToolData {
380381 snapping_axis : Option < Axis > ,
381382 alt_clicked_on_anchor : bool ,
382383 alt_dragging_from_anchor : bool ,
384+ angle_locked : bool ,
385+ temporary_colinear_handles : bool ,
383386}
384387
385388impl PathToolData {
@@ -727,11 +730,38 @@ impl PathToolData {
727730 Some ( ( handle_position_document, anchor_position_document, handle_id) )
728731 }
729732
730- fn calculate_handle_angle ( & mut self , handle_vector : DVec2 , handle_id : ManipulatorPointId , lock_angle : bool , snap_angle : bool ) -> f64 {
733+ #[ allow( clippy:: too_many_arguments) ]
734+ fn calculate_handle_angle (
735+ & mut self ,
736+ shape_editor : & mut ShapeState ,
737+ document : & DocumentMessageHandler ,
738+ responses : & mut VecDeque < Message > ,
739+ relative_vector : DVec2 ,
740+ handle_vector : DVec2 ,
741+ handle_id : ManipulatorPointId ,
742+ lock_angle : bool ,
743+ snap_angle : bool ,
744+ ) -> f64 {
731745 let current_angle = -handle_vector. angle_to ( DVec2 :: X ) ;
732746
747+ if let Some ( vector_data) = shape_editor
748+ . selected_shape_state
749+ . iter ( )
750+ . next ( )
751+ . and_then ( |( layer, _) | document. network_interface . compute_modified_vector ( * layer) )
752+ {
753+ if relative_vector. length ( ) < 25. && lock_angle && !self . angle_locked {
754+ if let Some ( angle) = calculate_lock_angle ( self , shape_editor, responses, document, & vector_data, handle_id) {
755+ self . angle = angle;
756+ return angle;
757+ }
758+ }
759+ }
760+
733761 // When the angle is locked we use the old angle
762+
734763 if self . current_selected_handle_id == Some ( handle_id) && lock_angle {
764+ self . angle_locked = true ;
735765 return self . angle ;
736766 }
737767
@@ -785,7 +815,7 @@ impl PathToolData {
785815 let drag_start = self . drag_start_pos ;
786816 let opposite_delta = drag_start - current_mouse;
787817
788- shape_editor. move_selected_points ( None , document, opposite_delta, false , true , false , None , responses) ;
818+ shape_editor. move_selected_points ( None , document, opposite_delta, false , true , false , None , false , responses) ;
789819
790820 // Calculate the projected delta and shift the points along that delta
791821 let delta = current_mouse - drag_start;
@@ -797,7 +827,7 @@ impl PathToolData {
797827 _ => DVec2 :: new ( delta. x , 0. ) ,
798828 } ;
799829
800- shape_editor. move_selected_points ( None , document, projected_delta, false , true , false , None , responses) ;
830+ shape_editor. move_selected_points ( None , document, projected_delta, false , true , false , None , false , responses) ;
801831 }
802832
803833 fn stop_snap_along_axis ( & mut self , shape_editor : & mut ShapeState , document : & DocumentMessageHandler , input : & InputPreprocessorMessageHandler , responses : & mut VecDeque < Message > ) {
@@ -813,12 +843,12 @@ impl PathToolData {
813843 _ => DVec2 :: new ( opposite_delta. x , 0. ) ,
814844 } ;
815845
816- shape_editor. move_selected_points ( None , document, opposite_projected_delta, false , true , false , None , responses) ;
846+ shape_editor. move_selected_points ( None , document, opposite_projected_delta, false , true , false , None , false , responses) ;
817847
818848 // Calculate what actually would have been the original delta for the point, and apply that
819849 let delta = current_mouse - drag_start;
820850
821- shape_editor. move_selected_points ( None , document, delta, false , true , false , None , responses) ;
851+ shape_editor. move_selected_points ( None , document, delta, false , true , false , None , false , responses) ;
822852
823853 self . snapping_axis = None ;
824854 }
@@ -872,7 +902,7 @@ impl PathToolData {
872902 let snapped_delta = if let Some ( ( handle_pos, anchor_pos, handle_id) ) = self . try_get_selected_handle_and_anchor ( shape_editor, document) {
873903 let cursor_pos = handle_pos + raw_delta;
874904
875- let handle_angle = self . calculate_handle_angle ( cursor_pos - anchor_pos, handle_id, lock_angle, snap_angle) ;
905+ let handle_angle = self . calculate_handle_angle ( shape_editor , document , responses , handle_pos - anchor_pos , cursor_pos - anchor_pos, handle_id, lock_angle, snap_angle) ;
876906
877907 let constrained_direction = DVec2 :: new ( handle_angle. cos ( ) , handle_angle. sin ( ) ) ;
878908 let projected_length = ( cursor_pos - anchor_pos) . dot ( constrained_direction) ;
@@ -931,7 +961,14 @@ impl PathToolData {
931961 self . alt_dragging_from_anchor = false ;
932962 self . alt_clicked_on_anchor = false ;
933963 }
934- shape_editor. move_selected_points ( handle_lengths, document, snapped_delta, equidistant, true , was_alt_dragging, opposite, responses) ;
964+
965+ let mut skip_opposite = false ;
966+ if self . temporary_colinear_handles && !lock_angle {
967+ shape_editor. disable_colinear_handles_state_on_selected ( & document. network_interface , responses) ;
968+ self . temporary_colinear_handles = false ;
969+ skip_opposite = true ;
970+ }
971+ shape_editor. move_selected_points ( handle_lengths, document, snapped_delta, equidistant, true , was_alt_dragging, opposite, skip_opposite, responses) ;
935972 self . previous_mouse_position += document_to_viewport. inverse ( ) . transform_vector2 ( snapped_delta) ;
936973 } else {
937974 let Some ( axis) = self . snapping_axis else { return } ;
@@ -940,7 +977,7 @@ impl PathToolData {
940977 Axis :: Y => DVec2 :: new ( 0. , unsnapped_delta. y ) ,
941978 _ => DVec2 :: new ( unsnapped_delta. x , 0. ) ,
942979 } ;
943- shape_editor. move_selected_points ( handle_lengths, document, projected_delta, equidistant, true , false , opposite, responses) ;
980+ shape_editor. move_selected_points ( handle_lengths, document, projected_delta, equidistant, true , false , opposite, false , responses) ;
944981 self . previous_mouse_position += document_to_viewport. inverse ( ) . transform_vector2 ( unsnapped_delta) ;
945982 }
946983
@@ -1238,6 +1275,10 @@ impl Fsm for PathToolFsmState {
12381275 let lock_angle_state = input. keyboard . get ( lock_angle as usize ) ;
12391276 let snap_angle_state = input. keyboard . get ( snap_angle as usize ) ;
12401277
1278+ if !lock_angle_state {
1279+ tool_data. angle_locked = false ;
1280+ }
1281+
12411282 if !tool_data. update_colinear ( equidistant_state, toggle_colinear_state, tool_action_data. shape_editor , tool_action_data. document , responses) {
12421283 tool_data. drag (
12431284 equidistant_state,
@@ -1412,6 +1453,10 @@ impl Fsm for PathToolFsmState {
14121453 }
14131454 }
14141455
1456+ if tool_data. temporary_colinear_handles {
1457+ tool_data. temporary_colinear_handles = false ;
1458+ }
1459+
14151460 if tool_data. handle_drag_toggle && drag_occurred {
14161461 shape_editor. deselect_all_points ( ) ;
14171462 shape_editor. select_points_by_manipulator_id ( & tool_data. saved_points_before_handle_drag ) ;
@@ -1511,6 +1556,7 @@ impl Fsm for PathToolFsmState {
15111556 false ,
15121557 false ,
15131558 tool_data. opposite_handle_position ,
1559+ false ,
15141560 responses,
15151561 ) ;
15161562
@@ -1749,3 +1795,52 @@ fn get_selection_status(network_interface: &NodeNetworkInterface, shape_state: &
17491795
17501796 SelectionStatus :: None
17511797}
1798+
1799+ fn calculate_lock_angle (
1800+ tool_data : & mut PathToolData ,
1801+ shape_state : & mut ShapeState ,
1802+ responses : & mut VecDeque < Message > ,
1803+ document : & DocumentMessageHandler ,
1804+ vector_data : & VectorData ,
1805+ handle_id : ManipulatorPointId ,
1806+ ) -> Option < f64 > {
1807+ let anchor = handle_id. get_anchor ( vector_data) ?;
1808+ let anchor_position = vector_data. point_domain . position_from_id ( anchor) ;
1809+ let current_segment = handle_id. get_segment ( ) ;
1810+ let points_connected = vector_data. connected_count ( anchor) ;
1811+
1812+ let ( anchor_position, segment) = anchor_position. zip ( current_segment) ?;
1813+ if points_connected == 1 {
1814+ calculate_segment_angle ( anchor, segment, vector_data, false )
1815+ } else {
1816+ let opposite_handle = handle_id
1817+ . get_handle_pair ( vector_data)
1818+ . iter ( )
1819+ . flatten ( )
1820+ . find ( |& h| h. to_manipulator_point ( ) != handle_id)
1821+ . copied ( )
1822+ . map ( |h| h. to_manipulator_point ( ) ) ;
1823+ let opposite_handle_position = opposite_handle. and_then ( |h| h. get_position ( vector_data) ) . filter ( |pos| ( pos - anchor_position) . length ( ) > 1e-6 ) ;
1824+
1825+ if let Some ( opposite_pos) = opposite_handle_position {
1826+ if !vector_data. colinear_manipulators . iter ( ) . flatten ( ) . map ( |h| h. to_manipulator_point ( ) ) . any ( |h| h == handle_id) {
1827+ shape_state. convert_selected_manipulators_to_colinear_handles ( responses, document) ;
1828+ tool_data. temporary_colinear_handles = true ;
1829+ }
1830+ Some ( -( opposite_pos - anchor_position) . angle_to ( DVec2 :: X ) )
1831+ } else {
1832+ let angle_1 = vector_data
1833+ . adjacent_segment ( & handle_id)
1834+ . and_then ( |( _, adjacent_segment) | calculate_segment_angle ( anchor, adjacent_segment, vector_data, false ) ) ;
1835+
1836+ let angle_2 = calculate_segment_angle ( anchor, segment, vector_data, false ) ;
1837+
1838+ match ( angle_1, angle_2) {
1839+ ( Some ( angle_1) , Some ( angle_2) ) => Some ( ( angle_1 + angle_2) / 2.0 ) ,
1840+ ( Some ( angle_1) , None ) => Some ( angle_1) ,
1841+ ( None , Some ( angle_2) ) => Some ( angle_2) ,
1842+ ( None , None ) => None ,
1843+ }
1844+ }
1845+ }
1846+ }
0 commit comments