@@ -379,6 +379,7 @@ struct PathToolData {
379
379
alt_dragging_from_anchor : bool ,
380
380
angle_locked : bool ,
381
381
temporary_colinear_handles : bool ,
382
+ adjacent_anchor_offset : Option < DVec2 > ,
382
383
}
383
384
384
385
impl PathToolData {
@@ -726,18 +727,39 @@ impl PathToolData {
726
727
) -> f64 {
727
728
let current_angle = -handle_vector. angle_to ( DVec2 :: X ) ;
728
729
729
- if let Some ( vector_data) = shape_editor
730
+ if let Some ( ( vector_data, layer ) ) = shape_editor
730
731
. selected_shape_state
731
732
. iter ( )
732
733
. next ( )
733
- . and_then ( |( layer, _) | document. network_interface . compute_modified_vector ( * layer) )
734
+ . and_then ( |( layer, _) | document. network_interface . compute_modified_vector ( * layer) . map ( |vector_data| ( vector_data , layer ) ) )
734
735
{
736
+ let adjacent_anchor = check_handle_over_adjacent_anchor ( handle_id, & vector_data) ;
737
+ let mut required_angle = None ;
738
+
739
+ // If the handle is dragged over one of its adjacent anchors while holding down the Ctrl key, compute the angle based on the tangent formed with the neighboring anchor points.
740
+ if adjacent_anchor. is_some ( ) && lock_angle && !self . angle_locked {
741
+ let anchor = handle_id. get_anchor ( & vector_data) ;
742
+ let ( angle, anchor_position) = calculate_adjacent_anchor_tangent ( handle_id, anchor, adjacent_anchor, & vector_data) ;
743
+
744
+ let layer_to_document = document. metadata ( ) . transform_to_document ( * layer) ;
745
+
746
+ self . adjacent_anchor_offset = handle_id
747
+ . get_anchor_position ( & vector_data)
748
+ . and_then ( |handle_anchor| anchor_position. map ( |adjacent_anchor| layer_to_document. transform_point2 ( adjacent_anchor) - layer_to_document. transform_point2 ( handle_anchor) ) ) ;
749
+
750
+ required_angle = angle;
751
+ }
752
+
753
+ // If the handle is dragged near its adjacent anchors while holding down the Ctrl key, compute the angle using the tangent direction of neighboring segments.
735
754
if relative_vector. length ( ) < 25. && lock_angle && !self . angle_locked {
736
- if let Some ( angle) = calculate_lock_angle ( self , shape_editor, responses, document, & vector_data, handle_id, tangent_to_neighboring_tangents) {
737
- self . angle = angle;
738
- self . angle_locked = true ;
739
- return angle;
740
- }
755
+ required_angle = calculate_lock_angle ( self , shape_editor, responses, document, & vector_data, handle_id, tangent_to_neighboring_tangents) ;
756
+ }
757
+
758
+ // Finalize and apply angle locking if a valid target angle was determined.
759
+ if let Some ( angle) = required_angle {
760
+ self . angle = angle;
761
+ self . angle_locked = true ;
762
+ return angle;
741
763
}
742
764
}
743
765
@@ -885,27 +907,36 @@ impl PathToolData {
885
907
let current_mouse = input. mouse . position ;
886
908
let raw_delta = document_to_viewport. inverse ( ) . transform_vector2 ( current_mouse - previous_mouse) ;
887
909
888
- let snapped_delta = if let Some ( ( handle_pos , anchor_pos , handle_id) ) = self . try_get_selected_handle_and_anchor ( shape_editor, document) {
889
- let cursor_pos = handle_pos + raw_delta;
910
+ let snapped_delta = if let Some ( ( handle_position , anchor_position , handle_id) ) = self . try_get_selected_handle_and_anchor ( shape_editor, document) {
911
+ let cursor_position = handle_position + raw_delta;
890
912
891
913
let handle_angle = self . calculate_handle_angle (
892
914
shape_editor,
893
915
document,
894
916
responses,
895
- handle_pos - anchor_pos ,
896
- cursor_pos - anchor_pos ,
917
+ handle_position - anchor_position ,
918
+ cursor_position - anchor_position ,
897
919
handle_id,
898
920
lock_angle,
899
921
snap_angle,
900
922
equidistant,
901
923
) ;
902
924
925
+ let adjacent_anchor_offset = self . adjacent_anchor_offset . unwrap_or ( DVec2 :: ZERO ) ;
903
926
let constrained_direction = DVec2 :: new ( handle_angle. cos ( ) , handle_angle. sin ( ) ) ;
904
- let projected_length = ( cursor_pos - anchor_pos) . dot ( constrained_direction) ;
905
- let constrained_target = anchor_pos + constrained_direction * projected_length;
906
- let constrained_delta = constrained_target - handle_pos;
907
-
908
- self . apply_snapping ( constrained_direction, handle_pos + constrained_delta, anchor_pos, lock_angle || snap_angle, handle_pos, document, input)
927
+ let projected_length = ( cursor_position - anchor_position - adjacent_anchor_offset) . dot ( constrained_direction) ;
928
+ let constrained_target = anchor_position + adjacent_anchor_offset + constrained_direction * projected_length;
929
+ let constrained_delta = constrained_target - handle_position;
930
+
931
+ self . apply_snapping (
932
+ constrained_direction,
933
+ handle_position + constrained_delta,
934
+ anchor_position + adjacent_anchor_offset,
935
+ lock_angle || snap_angle,
936
+ handle_position,
937
+ document,
938
+ input,
939
+ )
909
940
} else {
910
941
shape_editor. snap ( & mut self . snap_manager , & self . snap_cache , document, input, previous_mouse)
911
942
} ;
@@ -1265,6 +1296,7 @@ impl Fsm for PathToolFsmState {
1265
1296
1266
1297
if !lock_angle_state {
1267
1298
tool_data. angle_locked = false ;
1299
+ tool_data. adjacent_anchor_offset = None ;
1268
1300
}
1269
1301
1270
1302
if !tool_data. update_colinear ( equidistant_state, toggle_colinear_state, tool_action_data. shape_editor , tool_action_data. document , responses) {
@@ -1311,6 +1343,10 @@ impl Fsm for PathToolFsmState {
1311
1343
tool_data. saved_points_before_anchor_convert_smooth_sharp . clear ( ) ;
1312
1344
}
1313
1345
1346
+ if tool_data. adjacent_anchor_offset . is_some ( ) {
1347
+ tool_data. adjacent_anchor_offset = None ;
1348
+ }
1349
+
1314
1350
// If there is a point nearby, then remove the overlay
1315
1351
if shape_editor
1316
1352
. find_nearest_point_indices ( & document. network_interface , input. mouse . position , SELECTION_THRESHOLD )
@@ -1882,3 +1918,82 @@ fn calculate_lock_angle(
1882
1918
}
1883
1919
}
1884
1920
}
1921
+
1922
+ fn check_handle_over_adjacent_anchor ( handle_id : ManipulatorPointId , vector_data : & VectorData ) -> Option < PointId > {
1923
+ let Some ( ( anchor, handle_position) ) = handle_id. get_anchor ( & vector_data) . zip ( handle_id. get_position ( vector_data) ) else {
1924
+ return None ;
1925
+ } ;
1926
+
1927
+ let check_if_close = |point_id : & PointId | {
1928
+ let Some ( anchor_position) = vector_data. point_domain . position_from_id ( * point_id) else {
1929
+ return false ;
1930
+ } ;
1931
+ ( anchor_position - handle_position) . length ( ) < 10.
1932
+ } ;
1933
+
1934
+ vector_data. connected_points ( anchor) . find ( |point| check_if_close ( point) )
1935
+ }
1936
+ fn calculate_adjacent_anchor_tangent (
1937
+ currently_dragged_handle : ManipulatorPointId ,
1938
+ anchor : Option < PointId > ,
1939
+ adjacent_anchor : Option < PointId > ,
1940
+ vector_data : & VectorData ,
1941
+ ) -> ( Option < f64 > , Option < DVec2 > ) {
1942
+ // Early return if no anchor or no adjacent anchors
1943
+
1944
+ let Some ( ( dragged_handle_anchor, adjacent_anchor) ) = anchor. zip ( adjacent_anchor) else {
1945
+ return ( None , None ) ;
1946
+ } ;
1947
+ let adjacent_anchor_position = vector_data. point_domain . position_from_id ( adjacent_anchor) ;
1948
+
1949
+ let handles: Vec < _ > = vector_data. all_connected ( adjacent_anchor) . filter ( |handle| handle. length ( vector_data) > 1e-6 ) . collect ( ) ;
1950
+
1951
+ match handles. len ( ) {
1952
+ 0 => {
1953
+ // Find non-shared segments
1954
+ let non_shared_segment: Vec < _ > = vector_data
1955
+ . segment_bezier_iter ( )
1956
+ . filter_map ( |( segment_id, _, start, end) | {
1957
+ let touches_adjacent = start == adjacent_anchor || end == adjacent_anchor;
1958
+ let shares_with_dragged = start == dragged_handle_anchor || end == dragged_handle_anchor;
1959
+
1960
+ if touches_adjacent && !shares_with_dragged { Some ( segment_id) } else { None }
1961
+ } )
1962
+ . collect ( ) ;
1963
+
1964
+ match non_shared_segment. first ( ) {
1965
+ Some ( & segment) => {
1966
+ let angle = calculate_segment_angle ( adjacent_anchor, segment, vector_data, true ) ;
1967
+ ( angle, adjacent_anchor_position)
1968
+ }
1969
+ None => ( None , None ) ,
1970
+ }
1971
+ }
1972
+
1973
+ 1 => {
1974
+ let segment = handles[ 0 ] . segment ;
1975
+ let angle = calculate_segment_angle ( adjacent_anchor, segment, vector_data, true ) ;
1976
+ ( angle, adjacent_anchor_position)
1977
+ }
1978
+
1979
+ 2 => {
1980
+ // Use the angle formed by the handle of the shared segment relative to its associated anchor point.
1981
+ let Some ( shared_segment_handle) = handles
1982
+ . iter ( )
1983
+ . find ( |handle| handle. opposite ( ) . to_manipulator_point ( ) == currently_dragged_handle)
1984
+ . map ( |handle| handle. to_manipulator_point ( ) )
1985
+ else {
1986
+ return ( None , None ) ;
1987
+ } ;
1988
+
1989
+ let angle = shared_segment_handle
1990
+ . get_position ( & vector_data)
1991
+ . zip ( adjacent_anchor_position)
1992
+ . map ( |( handle, anchor) | -( handle - anchor) . angle_to ( DVec2 :: X ) ) ;
1993
+
1994
+ ( angle, adjacent_anchor_position)
1995
+ }
1996
+
1997
+ _ => ( None , None ) ,
1998
+ }
1999
+ }
0 commit comments