@@ -941,3 +941,177 @@ fn keyboard_context_menu_sets_initial_focus() {
941941 ContextMenuState :: Hidden => panic ! ( "Context menu should be open" ) ,
942942 }
943943}
944+
945+ #[ test]
946+ fn erase_stroke_samples_sparse_path ( ) {
947+ let mut state = create_test_input_state ( ) ;
948+ state. eraser_size = 4.0 ;
949+ state. eraser_mode = EraserMode :: Stroke ;
950+
951+ let line_id = state. canvas_set . active_frame_mut ( ) . add_shape ( Shape :: Line {
952+ x1 : 0 ,
953+ y1 : 0 ,
954+ x2 : 100 ,
955+ y2 : 0 ,
956+ color : Color {
957+ r : 0.0 ,
958+ g : 0.0 ,
959+ b : 0.0 ,
960+ a : 1.0 ,
961+ } ,
962+ thick : 1.0 ,
963+ } ) ;
964+
965+ let erased = state. erase_strokes_by_points ( & [ ( 0 , -10 ) , ( 100 , 10 ) ] ) ;
966+ assert ! ( erased, "stroke eraser should remove intersected line" ) ;
967+ assert ! ( state. canvas_set. active_frame( ) . shape( line_id) . is_none( ) ) ;
968+ }
969+
970+ #[ test]
971+ fn erase_stroke_includes_release_segment ( ) {
972+ let mut state = create_test_input_state ( ) ;
973+ state. eraser_size = 4.0 ;
974+ state. eraser_mode = EraserMode :: Stroke ;
975+ state. set_tool_override ( Some ( Tool :: Eraser ) ) ;
976+
977+ let line_id = state. canvas_set . active_frame_mut ( ) . add_shape ( Shape :: Line {
978+ x1 : 0 ,
979+ y1 : 0 ,
980+ x2 : 100 ,
981+ y2 : 0 ,
982+ color : Color {
983+ r : 0.0 ,
984+ g : 0.0 ,
985+ b : 0.0 ,
986+ a : 1.0 ,
987+ } ,
988+ thick : 1.0 ,
989+ } ) ;
990+
991+ state. on_mouse_press ( MouseButton :: Left , 0 , -10 ) ;
992+ state. on_mouse_release ( MouseButton :: Left , 100 , 10 ) ;
993+
994+ assert ! ( state. canvas_set. active_frame( ) . shape( line_id) . is_none( ) ) ;
995+ }
996+
997+ #[ test]
998+ fn erase_stroke_samples_randomized_crossings ( ) {
999+ fn next_unit ( seed : & mut u64 ) -> f64 {
1000+ * seed = seed. wrapping_mul ( 6364136223846793005 ) . wrapping_add ( 1 ) ;
1001+ let value = ( ( * seed >> 33 ) as u32 ) as f64 ;
1002+ value / ( u32:: MAX as f64 )
1003+ }
1004+
1005+ let mut seed = 0x1234_5678_9abc_def0u64 ;
1006+ for _ in 0 ..16 {
1007+ let mut state = create_test_input_state ( ) ;
1008+ state. eraser_size = 4.0 ;
1009+ state. eraser_mode = EraserMode :: Stroke ;
1010+
1011+ let line_id = state. canvas_set . active_frame_mut ( ) . add_shape ( Shape :: Line {
1012+ x1 : 0 ,
1013+ y1 : 0 ,
1014+ x2 : 100 ,
1015+ y2 : 0 ,
1016+ color : Color {
1017+ r : 0.0 ,
1018+ g : 0.0 ,
1019+ b : 0.0 ,
1020+ a : 1.0 ,
1021+ } ,
1022+ thick : 1.0 ,
1023+ } ) ;
1024+
1025+ let unit = next_unit ( & mut seed) ;
1026+ let angle = std:: f64:: consts:: PI * ( 0.35 + unit * 0.3 ) ;
1027+ let dx = angle. cos ( ) ;
1028+ let dy = angle. sin ( ) ;
1029+ let length = 80.0 ;
1030+ let x0 = 50.0 - dx * length;
1031+ let y0 = 0.0 - dy * length;
1032+ let x1 = 50.0 + dx * length;
1033+ let y1 = 0.0 + dy * length;
1034+
1035+ let erased = state. erase_strokes_by_points ( & [
1036+ ( x0. round ( ) as i32 , y0. round ( ) as i32 ) ,
1037+ ( x1. round ( ) as i32 , y1. round ( ) as i32 ) ,
1038+ ] ) ;
1039+
1040+ assert ! (
1041+ erased,
1042+ "stroke eraser should remove line at angle {}" ,
1043+ angle
1044+ ) ;
1045+ assert ! ( state. canvas_set. active_frame( ) . shape( line_id) . is_none( ) ) ;
1046+ }
1047+ }
1048+
1049+ #[ test]
1050+ fn erase_stroke_hits_various_shapes ( ) {
1051+ let cases = vec ! [
1052+ (
1053+ Shape :: Rect {
1054+ x: 10 ,
1055+ y: 10 ,
1056+ w: 40 ,
1057+ h: 20 ,
1058+ fill: false ,
1059+ color: Color {
1060+ r: 0.0 ,
1061+ g: 0.0 ,
1062+ b: 0.0 ,
1063+ a: 1.0 ,
1064+ } ,
1065+ thick: 1.0 ,
1066+ } ,
1067+ vec![ ( 0 , 10 ) , ( 100 , 10 ) ] ,
1068+ ) ,
1069+ (
1070+ Shape :: Ellipse {
1071+ cx: 50 ,
1072+ cy: 50 ,
1073+ rx: 20 ,
1074+ ry: 10 ,
1075+ fill: false ,
1076+ color: Color {
1077+ r: 0.0 ,
1078+ g: 0.0 ,
1079+ b: 0.0 ,
1080+ a: 1.0 ,
1081+ } ,
1082+ thick: 1.0 ,
1083+ } ,
1084+ vec![ ( 0 , 40 ) , ( 100 , 40 ) ] ,
1085+ ) ,
1086+ (
1087+ Shape :: Arrow {
1088+ x1: 10 ,
1089+ y1: 90 ,
1090+ x2: 90 ,
1091+ y2: 90 ,
1092+ color: Color {
1093+ r: 0.0 ,
1094+ g: 0.0 ,
1095+ b: 0.0 ,
1096+ a: 1.0 ,
1097+ } ,
1098+ thick: 1.0 ,
1099+ arrow_length: 20.0 ,
1100+ arrow_angle: 30.0 ,
1101+ head_at_end: true ,
1102+ } ,
1103+ vec![ ( 0 , 90 ) , ( 100 , 90 ) ] ,
1104+ ) ,
1105+ ] ;
1106+
1107+ for ( shape, path) in cases {
1108+ let mut state = create_test_input_state ( ) ;
1109+ state. eraser_size = 4.0 ;
1110+ state. eraser_mode = EraserMode :: Stroke ;
1111+ let shape_id = state. canvas_set . active_frame_mut ( ) . add_shape ( shape) ;
1112+
1113+ let erased = state. erase_strokes_by_points ( & path) ;
1114+ assert ! ( erased, "stroke eraser should remove intersected shape" ) ;
1115+ assert ! ( state. canvas_set. active_frame( ) . shape( shape_id) . is_none( ) ) ;
1116+ }
1117+ }
0 commit comments