@@ -705,7 +705,7 @@ mod test_transform_layer {
705
705
use crate :: messages:: portfolio:: document:: graph_operation:: transform_utils;
706
706
use crate :: test_utils:: test_prelude:: * ;
707
707
// Use ModifyInputsContext to locate the transform node
708
- use crate :: messages:: portfolio:: document:: graph_operation:: utility_types:: ModifyInputsContext ;
708
+ use crate :: messages:: portfolio:: document:: graph_operation:: utility_types:: { ModifyInputsContext , TransformIn } ;
709
709
use crate :: messages:: prelude:: Message ;
710
710
use glam:: DAffine2 ;
711
711
use std:: collections:: VecDeque ;
@@ -908,4 +908,160 @@ mod test_transform_layer {
908
908
let translation_diff = ( after_cancel. translation - original_transform. translation ) . length ( ) ;
909
909
assert ! ( translation_diff < 1.0 , "Translation component changed too much: {}" , translation_diff) ;
910
910
}
911
+
912
+ #[ tokio:: test]
913
+ async fn test_grab_rotate_scale_chained ( ) {
914
+ let mut editor = EditorTestUtils :: create ( ) ;
915
+ editor. new_document ( ) . await ;
916
+ editor. drag_tool ( ToolType :: Rectangle , 0. , 0. , 100. , 100. , ModifierKeys :: empty ( ) ) . await ;
917
+ let document = editor. active_document ( ) ;
918
+ let layer = document. metadata ( ) . all_layers ( ) . next ( ) . unwrap ( ) ;
919
+ editor. handle_message ( NodeGraphMessage :: SelectedNodesSet { nodes : vec ! [ layer. to_node( ) ] } ) . await ;
920
+ let original_transform = get_layer_transform ( & mut editor, layer) . await . unwrap ( ) ;
921
+
922
+ editor. handle_message ( TransformLayerMessage :: BeginGrab ) . await ;
923
+ editor. move_mouse ( 150.0 , 130.0 , ModifierKeys :: empty ( ) , MouseKeys :: NONE ) . await ;
924
+ editor
925
+ . handle_message ( TransformLayerMessage :: PointerMove {
926
+ slow_key : Key :: Shift ,
927
+ increments_key : Key :: Control ,
928
+ } )
929
+ . await ;
930
+
931
+ let after_grab_transform = get_layer_transform ( & mut editor, layer) . await . unwrap ( ) ;
932
+ let expected_translation = DVec2 :: new ( 50.0 , 30.0 ) ;
933
+ let actual_translation = after_grab_transform. translation - original_transform. translation ;
934
+ assert ! (
935
+ ( actual_translation - expected_translation) . length( ) < 1e-5 ,
936
+ "Expected translation of {:?}, got {:?}" ,
937
+ expected_translation,
938
+ actual_translation
939
+ ) ;
940
+
941
+ // 2. Chain to rotation - from current position to create ~45 degree rotation
942
+ editor. handle_message ( TransformLayerMessage :: BeginRotate ) . await ;
943
+ editor. move_mouse ( 190.0 , 90.0 , ModifierKeys :: empty ( ) , MouseKeys :: NONE ) . await ;
944
+ editor
945
+ . handle_message ( TransformLayerMessage :: PointerMove {
946
+ slow_key : Key :: Shift ,
947
+ increments_key : Key :: Control ,
948
+ } )
949
+ . await ;
950
+ let after_rotate_transform = get_layer_transform ( & mut editor, layer) . await . unwrap ( ) ;
951
+ // Checking for off-diagonal elements close to 0.707, which corresponds to cos(45°) and sin(45°)
952
+ assert ! (
953
+ !after_rotate_transform. matrix2. abs_diff_eq( after_grab_transform. matrix2, 1e-5 ) &&
954
+ ( after_rotate_transform. matrix2. x_axis. y. abs( ) - 0.707 ) . abs( ) < 0.1 && // Check for off-diagonal elements close to 0.707
955
+ ( after_rotate_transform. matrix2. y_axis. x. abs( ) - 0.707 ) . abs( ) < 0.1 , // that would indicate ~45° rotation
956
+ "Rotation should change matrix components with approximately 45° rotation"
957
+ ) ;
958
+
959
+ // 3. Chain to scaling - scale(area) up by 2x
960
+ editor. handle_message ( TransformLayerMessage :: BeginScale ) . await ;
961
+ editor. move_mouse ( 250.0 , 200.0 , ModifierKeys :: empty ( ) , MouseKeys :: NONE ) . await ;
962
+ editor
963
+ . handle_message ( TransformLayerMessage :: PointerMove {
964
+ slow_key : Key :: Shift ,
965
+ increments_key : Key :: Control ,
966
+ } )
967
+ . await ;
968
+
969
+ let after_scale_transform = get_layer_transform ( & mut editor, layer) . await . unwrap ( ) ;
970
+ let before_scale_det = after_rotate_transform. matrix2 . determinant ( ) ;
971
+ let after_scale_det = after_scale_transform. matrix2 . determinant ( ) ;
972
+ assert ! (
973
+ after_scale_det >= 2.0 * before_scale_det,
974
+ "Scale should increase the determinant of the matrix (before: {}, after: {})" ,
975
+ before_scale_det,
976
+ after_scale_det
977
+ ) ;
978
+
979
+ editor. handle_message ( TransformLayerMessage :: ApplyTransformOperation { final_transform : true } ) . await ;
980
+ let final_transform = get_layer_transform ( & mut editor, layer) . await . unwrap ( ) ;
981
+
982
+ assert ! ( final_transform. abs_diff_eq( after_scale_transform, 1e-5 ) , "Final transform should match the transform before committing" ) ;
983
+ assert ! ( !final_transform. abs_diff_eq( original_transform, 1e-5 ) , "Final transform should be different from original transform" ) ;
984
+ }
985
+
986
+ #[ tokio:: test]
987
+ async fn test_scale_with_panned_view ( ) {
988
+ let mut editor = EditorTestUtils :: create ( ) ;
989
+ editor. new_document ( ) . await ;
990
+ editor. drag_tool ( ToolType :: Rectangle , 0. , 0. , 100. , 100. , ModifierKeys :: empty ( ) ) . await ;
991
+ let document = editor. active_document ( ) ;
992
+ let layer = document. metadata ( ) . all_layers ( ) . next ( ) . unwrap ( ) ;
993
+
994
+ let original_transform = get_layer_transform ( & mut editor, layer) . await . unwrap ( ) ;
995
+
996
+ let pan_amount = DVec2 :: new ( 200.0 , 150.0 ) ;
997
+ editor. handle_message ( NavigationMessage :: CanvasPan { delta : pan_amount } ) . await ;
998
+
999
+ editor. handle_message ( TransformLayerMessage :: BeginScale ) . await ;
1000
+ editor. handle_message ( TransformLayerMessage :: TypeDigit { digit : 2 } ) . await ;
1001
+ editor. handle_message ( TransformLayerMessage :: ApplyTransformOperation { final_transform : true } ) . await ;
1002
+
1003
+ let final_transform = get_layer_transform ( & mut editor, layer) . await . unwrap ( ) ;
1004
+
1005
+ let scale_x = final_transform. matrix2 . x_axis . length ( ) / original_transform. matrix2 . x_axis . length ( ) ;
1006
+ let scale_y = final_transform. matrix2 . y_axis . length ( ) / original_transform. matrix2 . y_axis . length ( ) ;
1007
+
1008
+ assert ! ( ( scale_x - 2.0 ) . abs( ) < 0.1 , "Expected scale factor X of 2.0, got: {}" , scale_x) ;
1009
+ assert ! ( ( scale_y - 2.0 ) . abs( ) < 0.1 , "Expected scale factor Y of 2.0, got: {}" , scale_y) ;
1010
+ }
1011
+
1012
+ #[ tokio:: test]
1013
+ async fn test_scale_with_zoomed_view ( ) {
1014
+ let mut editor = EditorTestUtils :: create ( ) ;
1015
+ editor. new_document ( ) . await ;
1016
+ editor. drag_tool ( ToolType :: Rectangle , 0. , 0. , 100. , 100. , ModifierKeys :: empty ( ) ) . await ;
1017
+ let document = editor. active_document ( ) ;
1018
+ let layer = document. metadata ( ) . all_layers ( ) . next ( ) . unwrap ( ) ;
1019
+
1020
+ let original_transform = get_layer_transform ( & mut editor, layer) . await . unwrap ( ) ;
1021
+
1022
+ editor. handle_message ( NavigationMessage :: CanvasZoomIncrease { center_on_mouse : false } ) . await ;
1023
+ editor. handle_message ( NavigationMessage :: CanvasZoomIncrease { center_on_mouse : false } ) . await ;
1024
+
1025
+ editor. handle_message ( TransformLayerMessage :: BeginScale ) . await ;
1026
+ editor. handle_message ( TransformLayerMessage :: TypeDigit { digit : 2 } ) . await ;
1027
+ editor. handle_message ( TransformLayerMessage :: ApplyTransformOperation { final_transform : true } ) . await ;
1028
+
1029
+ let final_transform = get_layer_transform ( & mut editor, layer) . await . unwrap ( ) ;
1030
+
1031
+ let scale_x = final_transform. matrix2 . x_axis . length ( ) / original_transform. matrix2 . x_axis . length ( ) ;
1032
+ let scale_y = final_transform. matrix2 . y_axis . length ( ) / original_transform. matrix2 . y_axis . length ( ) ;
1033
+
1034
+ assert ! ( ( scale_x - 2.0 ) . abs( ) < 0.1 , "Expected scale factor X of 2.0, got: {}" , scale_x) ;
1035
+ assert ! ( ( scale_y - 2.0 ) . abs( ) < 0.1 , "Expected scale factor Y of 2.0, got: {}" , scale_y) ;
1036
+ }
1037
+
1038
+ #[ tokio:: test]
1039
+ async fn test_rotate_with_rotated_view ( ) {
1040
+ let mut editor = EditorTestUtils :: create ( ) ;
1041
+ editor. new_document ( ) . await ;
1042
+ editor. drag_tool ( ToolType :: Rectangle , 0. , 0. , 100. , 100. , ModifierKeys :: empty ( ) ) . await ;
1043
+ let document = editor. active_document ( ) ;
1044
+ let layer = document. metadata ( ) . all_layers ( ) . next ( ) . unwrap ( ) ;
1045
+
1046
+ let original_transform = get_layer_transform ( & mut editor, layer) . await . unwrap ( ) ;
1047
+
1048
+ // Rotate the document view (45 degrees)
1049
+ editor. handle_message ( NavigationMessage :: BeginCanvasTilt { was_dispatched_from_menu : false } ) . await ;
1050
+ editor. handle_message ( NavigationMessage :: CanvasTiltSet { angle_radians : 45.0_f64 . to_radians ( ) } ) . await ;
1051
+ editor. handle_message ( TransformLayerMessage :: BeginRotate ) . await ;
1052
+
1053
+ editor. handle_message ( TransformLayerMessage :: TypeDigit { digit : 9 } ) . await ;
1054
+ editor. handle_message ( TransformLayerMessage :: TypeDigit { digit : 0 } ) . await ;
1055
+ editor. handle_message ( TransformLayerMessage :: ApplyTransformOperation { final_transform : true } ) . await ;
1056
+
1057
+ let final_transform = get_layer_transform ( & mut editor, layer) . await . unwrap ( ) ;
1058
+
1059
+ let original_angle = original_transform. to_scale_angle_translation ( ) . 1 ;
1060
+ let final_angle = final_transform. to_scale_angle_translation ( ) . 1 ;
1061
+ let angle_change = ( final_angle - original_angle) . to_degrees ( ) ;
1062
+
1063
+ // Normalize angle between 0 and 360
1064
+ let angle_change = ( ( angle_change % 360.0 ) + 360.0 ) % 360.0 ;
1065
+ assert ! ( ( angle_change - 90.0 ) . abs( ) < 0.1 , "Expected rotation of 90 degrees, got: {}" , angle_change) ;
1066
+ }
911
1067
}
0 commit comments