@@ -340,3 +340,383 @@ fn extend_path_with_next_segment(tool_data: &mut FreehandToolData, position: DVe
340
340
tool_data. dragged = true ;
341
341
tool_data. end_point = Some ( ( position, id) ) ;
342
342
}
343
+
344
+ #[ cfg( test) ]
345
+ mod test_freehand {
346
+ use crate :: messages:: input_mapper:: utility_types:: input_mouse:: { EditorMouseState , MouseKeys , ScrollDelta } ;
347
+ use crate :: messages:: portfolio:: document:: graph_operation:: utility_types:: TransformIn ;
348
+ use crate :: messages:: tool:: common_functionality:: graph_modification_utils:: get_stroke_width;
349
+ use crate :: messages:: tool:: tool_messages:: freehand_tool:: FreehandOptionsUpdate ;
350
+ use crate :: test_utils:: test_prelude:: * ;
351
+ use glam:: { DAffine2 , DVec2 } ;
352
+ use graphene_core:: vector:: VectorData ;
353
+
354
+ async fn get_vector_data ( editor : & mut EditorTestUtils ) -> Vec < ( VectorData , DAffine2 ) > {
355
+ let document = editor. active_document ( ) ;
356
+ let layers = document. metadata ( ) . all_layers ( ) ;
357
+
358
+ layers
359
+ . filter_map ( |layer| {
360
+ let vector_data = document. network_interface . compute_modified_vector ( layer) ?;
361
+ let transform = document. metadata ( ) . transform_to_viewport ( layer) ;
362
+ Some ( ( vector_data, transform) )
363
+ } )
364
+ . collect ( )
365
+ }
366
+
367
+ fn verify_path_points ( vector_data_list : & [ ( VectorData , DAffine2 ) ] , expected_captured_points : & [ DVec2 ] , tolerance : f64 ) -> Result < ( ) , String > {
368
+ if vector_data_list. len ( ) == 0 {
369
+ return Err ( "No vector data found after drawing" . to_string ( ) ) ;
370
+ }
371
+
372
+ let path_data = vector_data_list. iter ( ) . find ( |( data, _) | data. point_domain . ids ( ) . len ( ) > 0 ) . ok_or ( "Could not find path data" ) ?;
373
+
374
+ let ( vector_data, transform) = path_data;
375
+ let point_count = vector_data. point_domain . ids ( ) . len ( ) ;
376
+ let segment_count = vector_data. segment_domain . ids ( ) . len ( ) ;
377
+
378
+ let actual_positions: Vec < DVec2 > = vector_data
379
+ . point_domain
380
+ . ids ( )
381
+ . iter ( )
382
+ . filter_map ( |& point_id| {
383
+ let position = vector_data. point_domain . position_from_id ( point_id) ?;
384
+ Some ( transform. transform_point2 ( position) )
385
+ } )
386
+ . collect ( ) ;
387
+
388
+ if segment_count != point_count - 1 {
389
+ return Err ( format ! ( "Expected segments to be one less than points, got {} segments for {} points" , segment_count, point_count) ) ;
390
+ }
391
+
392
+ if point_count != expected_captured_points. len ( ) {
393
+ return Err ( format ! ( "Expected {} points, got {}" , expected_captured_points. len( ) , point_count) ) ;
394
+ }
395
+
396
+ for ( i, ( & expected, & actual) ) in expected_captured_points. iter ( ) . zip ( actual_positions. iter ( ) ) . enumerate ( ) {
397
+ let distance = ( expected - actual) . length ( ) ;
398
+ if distance >= tolerance {
399
+ return Err ( format ! ( "Point {} position mismatch: expected {:?}, got {:?} (distance: {})" , i, expected, actual, distance) ) ;
400
+ }
401
+ }
402
+
403
+ Ok ( ( ) )
404
+ }
405
+
406
+ #[ tokio:: test]
407
+ async fn test_freehand_transformed_artboard ( ) {
408
+ let mut editor = EditorTestUtils :: create ( ) ;
409
+ editor. new_document ( ) . await ;
410
+
411
+ editor. drag_tool ( ToolType :: Artboard , 0. , 0. , 500. , 500. , ModifierKeys :: empty ( ) ) . await ;
412
+
413
+ let metadata = editor. active_document ( ) . metadata ( ) ;
414
+ let artboard = metadata. all_layers ( ) . next ( ) . unwrap ( ) ;
415
+
416
+ editor
417
+ . handle_message ( GraphOperationMessage :: TransformSet {
418
+ layer : artboard,
419
+ transform : DAffine2 :: from_scale_angle_translation ( DVec2 :: new ( 1.5 , 0.8 ) , 0.3 , DVec2 :: new ( 10.0 , -5.0 ) ) ,
420
+ transform_in : TransformIn :: Local ,
421
+ skip_rerender : false ,
422
+ } )
423
+ . await ;
424
+
425
+ editor. select_tool ( ToolType :: Freehand ) . await ;
426
+
427
+ let mouse_points = [ DVec2 :: new ( 150.0 , 100.0 ) , DVec2 :: new ( 200.0 , 150.0 ) , DVec2 :: new ( 250.0 , 130.0 ) , DVec2 :: new ( 300.0 , 170.0 ) ] ;
428
+
429
+ // Expected points that will actually be captured by the tool
430
+ let expected_captured_points = & mouse_points[ 1 ..] ;
431
+ editor. drag_path ( & mouse_points, ModifierKeys :: empty ( ) ) . await ;
432
+
433
+ let vector_data_list = get_vector_data ( & mut editor) . await ;
434
+ verify_path_points ( & vector_data_list, expected_captured_points, 1.0 ) . expect ( "Path points verification failed" ) ;
435
+ }
436
+
437
+ #[ tokio:: test]
438
+ async fn test_extend_existing_path ( ) {
439
+ let mut editor = EditorTestUtils :: create ( ) ;
440
+ editor. new_document ( ) . await ;
441
+
442
+ let initial_points = [ DVec2 :: new ( 100.0 , 100.0 ) , DVec2 :: new ( 200.0 , 200.0 ) , DVec2 :: new ( 300.0 , 100.0 ) ] ;
443
+
444
+ editor. select_tool ( ToolType :: Freehand ) . await ;
445
+
446
+ let first_point = initial_points[ 0 ] ;
447
+ editor. move_mouse ( first_point. x , first_point. y , ModifierKeys :: empty ( ) , MouseKeys :: empty ( ) ) . await ;
448
+ editor. left_mousedown ( first_point. x , first_point. y , ModifierKeys :: empty ( ) ) . await ;
449
+
450
+ for & point in & initial_points[ 1 ..] {
451
+ editor. move_mouse ( point. x , point. y , ModifierKeys :: empty ( ) , MouseKeys :: LEFT ) . await ;
452
+ }
453
+
454
+ let last_initial_point = initial_points[ initial_points. len ( ) - 1 ] ;
455
+ editor
456
+ . mouseup (
457
+ EditorMouseState {
458
+ editor_position : last_initial_point,
459
+ mouse_keys : MouseKeys :: empty ( ) ,
460
+ scroll_delta : ScrollDelta :: default ( ) ,
461
+ } ,
462
+ ModifierKeys :: empty ( ) ,
463
+ )
464
+ . await ;
465
+
466
+ let initial_vector_data = get_vector_data ( & mut editor) . await ;
467
+ assert ! ( !initial_vector_data. is_empty( ) , "No vector data found after initial drawing" ) ;
468
+
469
+ let ( initial_data, transform) = & initial_vector_data[ 0 ] ;
470
+ let initial_point_count = initial_data. point_domain . ids ( ) . len ( ) ;
471
+ let initial_segment_count = initial_data. segment_domain . ids ( ) . len ( ) ;
472
+
473
+ assert ! ( initial_point_count >= 2 , "Expected at least 2 points in initial path, found {}" , initial_point_count) ;
474
+ assert_eq ! (
475
+ initial_segment_count,
476
+ initial_point_count - 1 ,
477
+ "Expected {} segments in initial path, found {}" ,
478
+ initial_point_count - 1 ,
479
+ initial_segment_count
480
+ ) ;
481
+
482
+ let extendable_points = initial_data. extendable_points ( false ) . collect :: < Vec < _ > > ( ) ;
483
+ assert ! ( !extendable_points. is_empty( ) , "No extendable points found in the path" ) ;
484
+
485
+ let endpoint_id = extendable_points[ 0 ] ;
486
+ let endpoint_pos_option = initial_data. point_domain . position_from_id ( endpoint_id) ;
487
+ assert ! ( endpoint_pos_option. is_some( ) , "Could not find position for endpoint" ) ;
488
+
489
+ let endpoint_pos = endpoint_pos_option. unwrap ( ) ;
490
+ let endpoint_viewport_pos = transform. transform_point2 ( endpoint_pos) ;
491
+
492
+ assert ! ( endpoint_viewport_pos. is_finite( ) , "Endpoint position is not finite" ) ;
493
+
494
+ let extension_points = [ DVec2 :: new ( 400.0 , 200.0 ) , DVec2 :: new ( 500.0 , 100.0 ) ] ;
495
+
496
+ let layer_node_id = {
497
+ let document = editor. active_document ( ) ;
498
+ let layer = document. metadata ( ) . all_layers ( ) . next ( ) . unwrap ( ) ;
499
+ layer. to_node ( )
500
+ } ;
501
+
502
+ editor. handle_message ( NodeGraphMessage :: SelectedNodesSet { nodes : vec ! [ layer_node_id] } ) . await ;
503
+
504
+ editor. select_tool ( ToolType :: Freehand ) . await ;
505
+
506
+ editor. move_mouse ( endpoint_viewport_pos. x , endpoint_viewport_pos. y , ModifierKeys :: empty ( ) , MouseKeys :: empty ( ) ) . await ;
507
+ editor. left_mousedown ( endpoint_viewport_pos. x , endpoint_viewport_pos. y , ModifierKeys :: empty ( ) ) . await ;
508
+
509
+ for & point in & extension_points {
510
+ editor. move_mouse ( point. x , point. y , ModifierKeys :: empty ( ) , MouseKeys :: LEFT ) . await ;
511
+ }
512
+
513
+ let last_extension_point = extension_points[ extension_points. len ( ) - 1 ] ;
514
+ editor
515
+ . mouseup (
516
+ EditorMouseState {
517
+ editor_position : last_extension_point,
518
+ mouse_keys : MouseKeys :: empty ( ) ,
519
+ scroll_delta : ScrollDelta :: default ( ) ,
520
+ } ,
521
+ ModifierKeys :: empty ( ) ,
522
+ )
523
+ . await ;
524
+
525
+ let extended_vector_data = get_vector_data ( & mut editor) . await ;
526
+ assert ! ( !extended_vector_data. is_empty( ) , "No vector data found after extension" ) ;
527
+
528
+ let ( extended_data, _) = & extended_vector_data[ 0 ] ;
529
+ let extended_point_count = extended_data. point_domain . ids ( ) . len ( ) ;
530
+ let extended_segment_count = extended_data. segment_domain . ids ( ) . len ( ) ;
531
+
532
+ assert ! (
533
+ extended_point_count > initial_point_count,
534
+ "Expected more points after extension, initial: {}, after extension: {}" ,
535
+ initial_point_count,
536
+ extended_point_count
537
+ ) ;
538
+
539
+ assert_eq ! (
540
+ extended_segment_count,
541
+ extended_point_count - 1 ,
542
+ "Expected segments to be one less than points, points: {}, segments: {}" ,
543
+ extended_point_count,
544
+ extended_segment_count
545
+ ) ;
546
+
547
+ let layer_count = {
548
+ let document = editor. active_document ( ) ;
549
+ document. metadata ( ) . all_layers ( ) . count ( )
550
+ } ;
551
+ assert_eq ! ( layer_count, 1 , "Expected only one layer after extending path" ) ;
552
+ }
553
+
554
+ #[ tokio:: test]
555
+ async fn test_append_to_selected_layer_with_shift ( ) {
556
+ let mut editor = EditorTestUtils :: create ( ) ;
557
+ editor. new_document ( ) . await ;
558
+
559
+ editor. select_tool ( ToolType :: Freehand ) . await ;
560
+
561
+ let initial_points = [ DVec2 :: new ( 100.0 , 100.0 ) , DVec2 :: new ( 200.0 , 200.0 ) , DVec2 :: new ( 300.0 , 100.0 ) ] ;
562
+
563
+ let first_point = initial_points[ 0 ] ;
564
+ editor. move_mouse ( first_point. x , first_point. y , ModifierKeys :: empty ( ) , MouseKeys :: empty ( ) ) . await ;
565
+ editor. left_mousedown ( first_point. x , first_point. y , ModifierKeys :: empty ( ) ) . await ;
566
+
567
+ for & point in & initial_points[ 1 ..] {
568
+ editor. move_mouse ( point. x , point. y , ModifierKeys :: empty ( ) , MouseKeys :: LEFT ) . await ;
569
+ }
570
+
571
+ let last_initial_point = initial_points[ initial_points. len ( ) - 1 ] ;
572
+ editor
573
+ . mouseup (
574
+ EditorMouseState {
575
+ editor_position : last_initial_point,
576
+ mouse_keys : MouseKeys :: empty ( ) ,
577
+ scroll_delta : ScrollDelta :: default ( ) ,
578
+ } ,
579
+ ModifierKeys :: empty ( ) ,
580
+ )
581
+ . await ;
582
+
583
+ let initial_vector_data = get_vector_data ( & mut editor) . await ;
584
+ assert ! ( !initial_vector_data. is_empty( ) , "No vector data found after initial drawing" ) ;
585
+
586
+ let ( initial_data, _) = & initial_vector_data[ 0 ] ;
587
+ let initial_point_count = initial_data. point_domain . ids ( ) . len ( ) ;
588
+ let initial_segment_count = initial_data. segment_domain . ids ( ) . len ( ) ;
589
+
590
+ let existing_layer_id = {
591
+ let document = editor. active_document ( ) ;
592
+ let layer = document. metadata ( ) . all_layers ( ) . next ( ) . unwrap ( ) ;
593
+ layer
594
+ } ;
595
+
596
+ editor
597
+ . handle_message ( NodeGraphMessage :: SelectedNodesSet {
598
+ nodes : vec ! [ existing_layer_id. to_node( ) ] ,
599
+ } )
600
+ . await ;
601
+
602
+ let second_path_points = [ DVec2 :: new ( 400.0 , 100.0 ) , DVec2 :: new ( 500.0 , 200.0 ) , DVec2 :: new ( 600.0 , 100.0 ) ] ;
603
+
604
+ let first_second_point = second_path_points[ 0 ] ;
605
+ editor. move_mouse ( first_second_point. x , first_second_point. y , ModifierKeys :: SHIFT , MouseKeys :: empty ( ) ) . await ;
606
+
607
+ editor
608
+ . mousedown (
609
+ EditorMouseState {
610
+ editor_position : first_second_point,
611
+ mouse_keys : MouseKeys :: LEFT ,
612
+ scroll_delta : ScrollDelta :: default ( ) ,
613
+ } ,
614
+ ModifierKeys :: SHIFT ,
615
+ )
616
+ . await ;
617
+
618
+ for & point in & second_path_points[ 1 ..] {
619
+ editor. move_mouse ( point. x , point. y , ModifierKeys :: SHIFT , MouseKeys :: LEFT ) . await ;
620
+ }
621
+
622
+ let last_second_point = second_path_points[ second_path_points. len ( ) - 1 ] ;
623
+ editor
624
+ . mouseup (
625
+ EditorMouseState {
626
+ editor_position : last_second_point,
627
+ mouse_keys : MouseKeys :: empty ( ) ,
628
+ scroll_delta : ScrollDelta :: default ( ) ,
629
+ } ,
630
+ ModifierKeys :: SHIFT ,
631
+ )
632
+ . await ;
633
+
634
+ let final_vector_data = get_vector_data ( & mut editor) . await ;
635
+ assert ! ( !final_vector_data. is_empty( ) , "No vector data found after second drawing" ) ;
636
+
637
+ // Verify we still have only one layer
638
+ let layer_count = {
639
+ let document = editor. active_document ( ) ;
640
+ document. metadata ( ) . all_layers ( ) . count ( )
641
+ } ;
642
+ assert_eq ! ( layer_count, 1 , "Expected only one layer after drawing with Shift key" ) ;
643
+
644
+ let ( final_data, _) = & final_vector_data[ 0 ] ;
645
+ let final_point_count = final_data. point_domain . ids ( ) . len ( ) ;
646
+ let final_segment_count = final_data. segment_domain . ids ( ) . len ( ) ;
647
+
648
+ assert ! (
649
+ final_point_count > initial_point_count,
650
+ "Expected more points after appending to layer, initial: {}, after append: {}" ,
651
+ initial_point_count,
652
+ final_point_count
653
+ ) ;
654
+
655
+ let expected_new_points = second_path_points. len ( ) ;
656
+ let expected_new_segments = expected_new_points - 1 ;
657
+
658
+ assert_eq ! (
659
+ final_point_count,
660
+ initial_point_count + expected_new_points,
661
+ "Expected {} total points after append" ,
662
+ initial_point_count + expected_new_points
663
+ ) ;
664
+
665
+ assert_eq ! (
666
+ final_segment_count,
667
+ initial_segment_count + expected_new_segments,
668
+ "Expected {} total segments after append" ,
669
+ initial_segment_count + expected_new_segments
670
+ ) ;
671
+ }
672
+
673
+ #[ tokio:: test]
674
+ async fn test_line_weight_affects_stroke_width ( ) {
675
+ let mut editor = EditorTestUtils :: create ( ) ;
676
+ editor. new_document ( ) . await ;
677
+
678
+ editor. select_tool ( ToolType :: Freehand ) . await ;
679
+
680
+ let custom_line_weight = 5.0 ;
681
+ editor
682
+ . handle_message ( ToolMessage :: Freehand ( FreehandToolMessage :: UpdateOptions ( FreehandOptionsUpdate :: LineWeight ( custom_line_weight) ) ) )
683
+ . await ;
684
+
685
+ let points = [ DVec2 :: new ( 100.0 , 100.0 ) , DVec2 :: new ( 200.0 , 200.0 ) , DVec2 :: new ( 300.0 , 100.0 ) ] ;
686
+
687
+ let first_point = points[ 0 ] ;
688
+ editor. move_mouse ( first_point. x , first_point. y , ModifierKeys :: empty ( ) , MouseKeys :: empty ( ) ) . await ;
689
+ editor. left_mousedown ( first_point. x , first_point. y , ModifierKeys :: empty ( ) ) . await ;
690
+
691
+ for & point in & points[ 1 ..] {
692
+ editor. move_mouse ( point. x , point. y , ModifierKeys :: empty ( ) , MouseKeys :: LEFT ) . await ;
693
+ }
694
+
695
+ let last_point = points[ points. len ( ) - 1 ] ;
696
+ editor
697
+ . mouseup (
698
+ EditorMouseState {
699
+ editor_position : last_point,
700
+ mouse_keys : MouseKeys :: empty ( ) ,
701
+ scroll_delta : ScrollDelta :: default ( ) ,
702
+ } ,
703
+ ModifierKeys :: empty ( ) ,
704
+ )
705
+ . await ;
706
+
707
+ let document = editor. active_document ( ) ;
708
+ let layer = document. metadata ( ) . all_layers ( ) . next ( ) . unwrap ( ) ;
709
+
710
+ let stroke_width = get_stroke_width ( layer, & document. network_interface ) ;
711
+
712
+ assert ! ( stroke_width. is_some( ) , "Stroke width should be available on the created path" ) ;
713
+
714
+ assert_eq ! (
715
+ stroke_width. unwrap( ) ,
716
+ custom_line_weight,
717
+ "Stroke width should match the custom line weight (expected {}, got {})" ,
718
+ custom_line_weight,
719
+ stroke_width. unwrap( )
720
+ ) ;
721
+ }
722
+ }
0 commit comments