@@ -11701,6 +11701,163 @@ void main() {
11701
11701
expect (tester.hasRunningAnimations, isFalse);
11702
11702
});
11703
11703
11704
+ testWidgets ('Floating cursor affinity' , (WidgetTester tester) async {
11705
+ EditableText .debugDeterministicCursor = true ;
11706
+ final FocusNode focusNode = FocusNode ();
11707
+ final GlobalKey key = GlobalKey ();
11708
+ // Set it up so that there will be word-wrap.
11709
+ final TextEditingController controller = TextEditingController (text: 'ABCDEFGHIJKLMNOPQRSTUVWXYZ abcdefghijklmnopqrstuvwxyz' );
11710
+ await tester.pumpWidget (
11711
+ MaterialApp (
11712
+ home: Center (
11713
+ child: ConstrainedBox (
11714
+ constraints: const BoxConstraints (
11715
+ maxWidth: 500 ,
11716
+ ),
11717
+ child: EditableText (
11718
+ key: key,
11719
+ autofocus: true ,
11720
+ maxLines: 2 ,
11721
+ controller: controller,
11722
+ focusNode: focusNode,
11723
+ style: textStyle,
11724
+ cursorColor: Colors .blue,
11725
+ backgroundCursorColor: Colors .grey,
11726
+ cursorOpacityAnimates: true ,
11727
+ ),
11728
+ ),
11729
+ ),
11730
+ ),
11731
+ );
11732
+
11733
+ await tester.pump ();
11734
+ final EditableTextState state = tester.state (find.byType (EditableText ));
11735
+
11736
+ // Select after the first word, with default affinity (downstream).
11737
+ controller.selection = const TextSelection .collapsed (offset: 27 );
11738
+ await tester.pump ();
11739
+ state.updateFloatingCursor (RawFloatingCursorPoint (state: FloatingCursorDragState .Start , offset: Offset .zero));
11740
+ await tester.pump ();
11741
+
11742
+ // The floating cursor should be drawn at the end of the first line.
11743
+ expect (key.currentContext! .findRenderObject (), paints..rrect (
11744
+ rrect: RRect .fromRectAndRadius (
11745
+ const Rect .fromLTWH (0.5 , 15 , 3 , 12 ),
11746
+ const Radius .circular (1 )
11747
+ )
11748
+ ));
11749
+
11750
+ // Select after the first word, with upstream affinity.
11751
+ controller.selection = const TextSelection .collapsed (offset: 27 , affinity: TextAffinity .upstream);
11752
+ await tester.pump ();
11753
+
11754
+ state.updateFloatingCursor (RawFloatingCursorPoint (state: FloatingCursorDragState .Start , offset: Offset .zero));
11755
+ await tester.pump ();
11756
+
11757
+ // The floating cursor should be drawn at the beginning of the second line.
11758
+ expect (key.currentContext! .findRenderObject (), paints..rrect (
11759
+ rrect: RRect .fromRectAndRadius (
11760
+ const Rect .fromLTWH (378.5 , 1 , 3 , 12 ),
11761
+ const Radius .circular (1 )
11762
+ )
11763
+ ));
11764
+
11765
+ EditableText .debugDeterministicCursor = false ;
11766
+ });
11767
+
11768
+ testWidgets ('Floating cursor ending with selection' , (WidgetTester tester) async {
11769
+ EditableText .debugDeterministicCursor = true ;
11770
+ final FocusNode focusNode = FocusNode ();
11771
+ final GlobalKey key = GlobalKey ();
11772
+ // Set it up so that there will be word-wrap.
11773
+ final TextEditingController controller = TextEditingController (text: 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' );
11774
+ controller.selection = const TextSelection .collapsed (offset: 0 );
11775
+ await tester.pumpWidget (
11776
+ MaterialApp (
11777
+ home: EditableText (
11778
+ key: key,
11779
+ autofocus: true ,
11780
+ controller: controller,
11781
+ focusNode: focusNode,
11782
+ style: textStyle,
11783
+ cursorColor: Colors .blue,
11784
+ backgroundCursorColor: Colors .grey,
11785
+ cursorOpacityAnimates: true ,
11786
+ ),
11787
+ ),
11788
+ );
11789
+
11790
+ await tester.pump ();
11791
+ final EditableTextState state = tester.state (find.byType (EditableText ));
11792
+
11793
+ state.updateFloatingCursor (RawFloatingCursorPoint (state: FloatingCursorDragState .Start , offset: Offset .zero));
11794
+ await tester.pump ();
11795
+
11796
+ // The floating cursor should be drawn at the start of the line.
11797
+ expect (key.currentContext! .findRenderObject (), paints..rrect (
11798
+ rrect: RRect .fromRectAndRadius (
11799
+ const Rect .fromLTWH (0.5 , 1 , 3 , 12 ),
11800
+ const Radius .circular (1 )
11801
+ )
11802
+ ));
11803
+
11804
+ state.updateFloatingCursor (RawFloatingCursorPoint (state: FloatingCursorDragState .Update , offset: const Offset (50 , 0 )));
11805
+ await tester.pump ();
11806
+
11807
+ // The floating cursor should be drawn somewhere in the middle of the line
11808
+ expect (key.currentContext! .findRenderObject (), paints..rrect (
11809
+ rrect: RRect .fromRectAndRadius (
11810
+ const Rect .fromLTWH (50.5 , 1 , 3 , 12 ),
11811
+ const Radius .circular (1 )
11812
+ )
11813
+ ));
11814
+
11815
+ state.updateFloatingCursor (RawFloatingCursorPoint (state: FloatingCursorDragState .End , offset: Offset .zero));
11816
+ await tester.pumpAndSettle (const Duration (milliseconds: 125 )); // Floating cursor has an end animation.
11817
+
11818
+ // Selection should be updated based on the floating cursor location.
11819
+ expect (controller.selection.isCollapsed, true );
11820
+ expect (controller.selection.baseOffset, 4 );
11821
+
11822
+ state.updateFloatingCursor (RawFloatingCursorPoint (state: FloatingCursorDragState .Start , offset: Offset .zero));
11823
+ await tester.pump ();
11824
+
11825
+ // The floating cursor should be drawn near to the previous position.
11826
+ // It's different because it's snapped to exactly between characters.
11827
+ expect (key.currentContext! .findRenderObject (), paints..rrect (
11828
+ rrect: RRect .fromRectAndRadius (
11829
+ const Rect .fromLTWH (56.5 , 1 , 3 , 12 ),
11830
+ const Radius .circular (1 )
11831
+ )
11832
+ ));
11833
+
11834
+ state.updateFloatingCursor (RawFloatingCursorPoint (state: FloatingCursorDragState .Update , offset: const Offset (- 56 , 0 )));
11835
+ await tester.pump ();
11836
+
11837
+ // The floating cursor should be drawn at the start of the line.
11838
+ expect (key.currentContext! .findRenderObject (), paints..rrect (
11839
+ rrect: RRect .fromRectAndRadius (
11840
+ const Rect .fromLTWH (0.5 , 1 , 3 , 12 ),
11841
+ const Radius .circular (1 )
11842
+ )
11843
+ ));
11844
+
11845
+ // Simulate UIKit setting the selection using keyboard selection.
11846
+ controller.selection = const TextSelection (baseOffset: 0 , extentOffset: 4 );
11847
+ await tester.pump ();
11848
+
11849
+ state.updateFloatingCursor (RawFloatingCursorPoint (state: FloatingCursorDragState .End , offset: Offset .zero));
11850
+ await tester.pump ();
11851
+
11852
+ // Selection should not be updated as the new position is within the selection range.
11853
+ expect (controller.selection.isCollapsed, false );
11854
+ expect (controller.selection.baseOffset, 0 );
11855
+ expect (controller.selection.extentOffset, 4 );
11856
+
11857
+ EditableText .debugDeterministicCursor = false ;
11858
+ });
11859
+
11860
+
11704
11861
group ('Selection changed scroll into view' , () {
11705
11862
final String text = List <int >.generate (64 , (int index) => index).join ('\n ' );
11706
11863
final TextEditingController controller = TextEditingController (text: text);
0 commit comments