Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Sign in
Toggle navigation
F
Front-End
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
abdullh.alsoleman
Front-End
Commits
c4b8046d
Unverified
Commit
c4b8046d
authored
Dec 10, 2022
by
Callum Moffat
Committed by
GitHub
Dec 10, 2022
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Floating cursor cleanup (#116746)
* Floating cursor cleanup * Use TextSelection.fromPosition
parent
cbdc763c
Changes
4
Hide whitespace changes
Inline
Side-by-side
Showing
4 changed files
with
237 additions
and
4 deletions
+237
-4
editable.dart
packages/flutter/lib/src/rendering/editable.dart
+1
-1
editable_text.dart
packages/flutter/lib/src/widgets/editable_text.dart
+5
-3
editable_test.dart
packages/flutter/test/rendering/editable_test.dart
+74
-0
editable_text_test.dart
packages/flutter/test/widgets/editable_text_test.dart
+157
-0
No files found.
packages/flutter/lib/src/rendering/editable.dart
View file @
c4b8046d
...
...
@@ -3102,7 +3102,7 @@ class _FloatingCursorPainter extends RenderEditablePainter {
}
canvas
.
drawRRect
(
RRect
.
fromRectAndRadius
(
floatingCursorRect
.
shift
(
renderEditable
.
_paintOffset
)
,
_kFloatingCaretRadius
),
RRect
.
fromRectAndRadius
(
floatingCursorRect
,
_kFloatingCaretRadius
),
floatingCursorPaint
..
color
=
floatingCursorColor
,
);
}
...
...
packages/flutter/lib/src/widgets/editable_text.dart
View file @
c4b8046d
...
...
@@ -2671,7 +2671,7 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien
// we cache the position.
_pointOffsetOrigin
=
point
.
offset
;
final
TextPosition
currentTextPosition
=
TextPosition
(
offset:
renderEditable
.
selection
!.
baseOffset
);
final
TextPosition
currentTextPosition
=
TextPosition
(
offset:
renderEditable
.
selection
!.
baseOffset
,
affinity:
renderEditable
.
selection
!.
affinity
);
_startCaretRect
=
renderEditable
.
getLocalRectForCaret
(
currentTextPosition
);
_lastBoundedOffset
=
_startCaretRect
!.
center
-
_floatingCursorOffset
;
...
...
@@ -2702,9 +2702,11 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien
final
Offset
finalPosition
=
renderEditable
.
getLocalRectForCaret
(
_lastTextPosition
!).
centerLeft
-
_floatingCursorOffset
;
if
(
_floatingCursorResetController
!.
isCompleted
)
{
renderEditable
.
setFloatingCursor
(
FloatingCursorDragState
.
End
,
finalPosition
,
_lastTextPosition
!);
if
(
_lastTextPosition
!.
offset
!=
renderEditable
.
selection
!.
baseOffset
)
{
// Only change if new position is out of current selection range, as the
// selection may have been modified using the iOS keyboard selection gesture.
if
(
_lastTextPosition
!.
offset
<
renderEditable
.
selection
!.
start
||
_lastTextPosition
!.
offset
>=
renderEditable
.
selection
!.
end
)
{
// The cause is technically the force cursor, but the cause is listed as tap as the desired functionality is the same.
_handleSelectionChanged
(
TextSelection
.
collapsed
(
offset:
_lastTextPosition
!.
offset
),
SelectionChangedCause
.
forcePress
);
_handleSelectionChanged
(
TextSelection
.
fromPosition
(
_lastTextPosition
!
),
SelectionChangedCause
.
forcePress
);
}
_startCaretRect
=
null
;
_lastTextPosition
=
null
;
...
...
packages/flutter/test/rendering/editable_test.dart
View file @
c4b8046d
...
...
@@ -12,6 +12,7 @@ import 'package:flutter/foundation.dart';
import
'package:flutter/gestures.dart'
;
import
'package:flutter/material.dart'
;
import
'package:flutter/rendering.dart'
;
import
'package:flutter/src/services/text_input.dart'
;
import
'package:flutter_test/flutter_test.dart'
;
import
'mock_canvas.dart'
;
...
...
@@ -1725,6 +1726,79 @@ void main() {
editable
.
forceLine
=
false
;
expect
(
editable
.
computeDryLayout
(
constraints
).
width
,
lessThan
(
initialWidth
));
});
test
(
'Floating cursor position is independent of viewport offset'
,
()
{
final
TextSelectionDelegate
delegate
=
_FakeEditableTextState
();
final
ValueNotifier
<
bool
>
showCursor
=
ValueNotifier
<
bool
>(
true
);
EditableText
.
debugDeterministicCursor
=
true
;
const
Color
cursorColor
=
Color
.
fromARGB
(
0xFF
,
0xFF
,
0x00
,
0x00
);
final
RenderEditable
editable
=
RenderEditable
(
backgroundCursorColor:
Colors
.
grey
,
textDirection:
TextDirection
.
ltr
,
cursorColor:
cursorColor
,
offset:
ViewportOffset
.
zero
(),
textSelectionDelegate:
delegate
,
text:
const
TextSpan
(
text:
'test'
,
style:
TextStyle
(
height:
1.0
,
fontSize:
10.0
,
fontFamily:
'Ahem'
,
),
),
maxLines:
3
,
startHandleLayerLink:
LayerLink
(),
endHandleLayerLink:
LayerLink
(),
selection:
const
TextSelection
.
collapsed
(
offset:
4
,
affinity:
TextAffinity
.
upstream
,
),
);
layout
(
editable
);
editable
.
layout
(
BoxConstraints
.
loose
(
const
Size
(
100
,
100
)));
// Prepare for painting after layout.
pumpFrame
(
phase:
EnginePhase
.
compositingBits
);
expect
(
editable
,
// Draw no cursor by default.
paintsExactlyCountTimes
(
#drawRect
,
0
),
);
editable
.
showCursor
=
showCursor
;
editable
.
setFloatingCursor
(
FloatingCursorDragState
.
Start
,
const
Offset
(
50
,
50
),
const
TextPosition
(
offset:
4
,
affinity:
TextAffinity
.
upstream
,
));
pumpFrame
(
phase:
EnginePhase
.
compositingBits
);
final
RRect
expectedRRect
=
RRect
.
fromRectAndRadius
(
const
Rect
.
fromLTWH
(
49.5
,
51
,
2
,
8
),
const
Radius
.
circular
(
1
)
);
expect
(
editable
,
paints
..
rrect
(
color:
cursorColor
.
withOpacity
(
0.75
),
rrect:
expectedRRect
));
// Change the text viewport offset.
editable
.
offset
=
ViewportOffset
.
fixed
(
200
);
// Floating cursor should be drawn in the same position.
editable
.
setFloatingCursor
(
FloatingCursorDragState
.
Start
,
const
Offset
(
50
,
50
),
const
TextPosition
(
offset:
4
,
affinity:
TextAffinity
.
upstream
,
));
pumpFrame
(
phase:
EnginePhase
.
compositingBits
);
expect
(
editable
,
paints
..
rrect
(
color:
cursorColor
.
withOpacity
(
0.75
),
rrect:
expectedRRect
));
});
}
class
_TestRenderEditable
extends
RenderEditable
{
...
...
packages/flutter/test/widgets/editable_text_test.dart
View file @
c4b8046d
...
...
@@ -11701,6 +11701,163 @@ void main() {
expect
(
tester
.
hasRunningAnimations
,
isFalse
);
});
testWidgets
(
'Floating cursor affinity'
,
(
WidgetTester
tester
)
async
{
EditableText
.
debugDeterministicCursor
=
true
;
final
FocusNode
focusNode
=
FocusNode
();
final
GlobalKey
key
=
GlobalKey
();
// Set it up so that there will be word-wrap.
final
TextEditingController
controller
=
TextEditingController
(
text:
'ABCDEFGHIJKLMNOPQRSTUVWXYZ abcdefghijklmnopqrstuvwxyz'
);
await
tester
.
pumpWidget
(
MaterialApp
(
home:
Center
(
child:
ConstrainedBox
(
constraints:
const
BoxConstraints
(
maxWidth:
500
,
),
child:
EditableText
(
key:
key
,
autofocus:
true
,
maxLines:
2
,
controller:
controller
,
focusNode:
focusNode
,
style:
textStyle
,
cursorColor:
Colors
.
blue
,
backgroundCursorColor:
Colors
.
grey
,
cursorOpacityAnimates:
true
,
),
),
),
),
);
await
tester
.
pump
();
final
EditableTextState
state
=
tester
.
state
(
find
.
byType
(
EditableText
));
// Select after the first word, with default affinity (downstream).
controller
.
selection
=
const
TextSelection
.
collapsed
(
offset:
27
);
await
tester
.
pump
();
state
.
updateFloatingCursor
(
RawFloatingCursorPoint
(
state:
FloatingCursorDragState
.
Start
,
offset:
Offset
.
zero
));
await
tester
.
pump
();
// The floating cursor should be drawn at the end of the first line.
expect
(
key
.
currentContext
!.
findRenderObject
(),
paints
..
rrect
(
rrect:
RRect
.
fromRectAndRadius
(
const
Rect
.
fromLTWH
(
0.5
,
15
,
3
,
12
),
const
Radius
.
circular
(
1
)
)
));
// Select after the first word, with upstream affinity.
controller
.
selection
=
const
TextSelection
.
collapsed
(
offset:
27
,
affinity:
TextAffinity
.
upstream
);
await
tester
.
pump
();
state
.
updateFloatingCursor
(
RawFloatingCursorPoint
(
state:
FloatingCursorDragState
.
Start
,
offset:
Offset
.
zero
));
await
tester
.
pump
();
// The floating cursor should be drawn at the beginning of the second line.
expect
(
key
.
currentContext
!.
findRenderObject
(),
paints
..
rrect
(
rrect:
RRect
.
fromRectAndRadius
(
const
Rect
.
fromLTWH
(
378.5
,
1
,
3
,
12
),
const
Radius
.
circular
(
1
)
)
));
EditableText
.
debugDeterministicCursor
=
false
;
});
testWidgets
(
'Floating cursor ending with selection'
,
(
WidgetTester
tester
)
async
{
EditableText
.
debugDeterministicCursor
=
true
;
final
FocusNode
focusNode
=
FocusNode
();
final
GlobalKey
key
=
GlobalKey
();
// Set it up so that there will be word-wrap.
final
TextEditingController
controller
=
TextEditingController
(
text:
'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
);
controller
.
selection
=
const
TextSelection
.
collapsed
(
offset:
0
);
await
tester
.
pumpWidget
(
MaterialApp
(
home:
EditableText
(
key:
key
,
autofocus:
true
,
controller:
controller
,
focusNode:
focusNode
,
style:
textStyle
,
cursorColor:
Colors
.
blue
,
backgroundCursorColor:
Colors
.
grey
,
cursorOpacityAnimates:
true
,
),
),
);
await
tester
.
pump
();
final
EditableTextState
state
=
tester
.
state
(
find
.
byType
(
EditableText
));
state
.
updateFloatingCursor
(
RawFloatingCursorPoint
(
state:
FloatingCursorDragState
.
Start
,
offset:
Offset
.
zero
));
await
tester
.
pump
();
// The floating cursor should be drawn at the start of the line.
expect
(
key
.
currentContext
!.
findRenderObject
(),
paints
..
rrect
(
rrect:
RRect
.
fromRectAndRadius
(
const
Rect
.
fromLTWH
(
0.5
,
1
,
3
,
12
),
const
Radius
.
circular
(
1
)
)
));
state
.
updateFloatingCursor
(
RawFloatingCursorPoint
(
state:
FloatingCursorDragState
.
Update
,
offset:
const
Offset
(
50
,
0
)));
await
tester
.
pump
();
// The floating cursor should be drawn somewhere in the middle of the line
expect
(
key
.
currentContext
!.
findRenderObject
(),
paints
..
rrect
(
rrect:
RRect
.
fromRectAndRadius
(
const
Rect
.
fromLTWH
(
50.5
,
1
,
3
,
12
),
const
Radius
.
circular
(
1
)
)
));
state
.
updateFloatingCursor
(
RawFloatingCursorPoint
(
state:
FloatingCursorDragState
.
End
,
offset:
Offset
.
zero
));
await
tester
.
pumpAndSettle
(
const
Duration
(
milliseconds:
125
));
// Floating cursor has an end animation.
// Selection should be updated based on the floating cursor location.
expect
(
controller
.
selection
.
isCollapsed
,
true
);
expect
(
controller
.
selection
.
baseOffset
,
4
);
state
.
updateFloatingCursor
(
RawFloatingCursorPoint
(
state:
FloatingCursorDragState
.
Start
,
offset:
Offset
.
zero
));
await
tester
.
pump
();
// The floating cursor should be drawn near to the previous position.
// It's different because it's snapped to exactly between characters.
expect
(
key
.
currentContext
!.
findRenderObject
(),
paints
..
rrect
(
rrect:
RRect
.
fromRectAndRadius
(
const
Rect
.
fromLTWH
(
56.5
,
1
,
3
,
12
),
const
Radius
.
circular
(
1
)
)
));
state
.
updateFloatingCursor
(
RawFloatingCursorPoint
(
state:
FloatingCursorDragState
.
Update
,
offset:
const
Offset
(-
56
,
0
)));
await
tester
.
pump
();
// The floating cursor should be drawn at the start of the line.
expect
(
key
.
currentContext
!.
findRenderObject
(),
paints
..
rrect
(
rrect:
RRect
.
fromRectAndRadius
(
const
Rect
.
fromLTWH
(
0.5
,
1
,
3
,
12
),
const
Radius
.
circular
(
1
)
)
));
// Simulate UIKit setting the selection using keyboard selection.
controller
.
selection
=
const
TextSelection
(
baseOffset:
0
,
extentOffset:
4
);
await
tester
.
pump
();
state
.
updateFloatingCursor
(
RawFloatingCursorPoint
(
state:
FloatingCursorDragState
.
End
,
offset:
Offset
.
zero
));
await
tester
.
pump
();
// Selection should not be updated as the new position is within the selection range.
expect
(
controller
.
selection
.
isCollapsed
,
false
);
expect
(
controller
.
selection
.
baseOffset
,
0
);
expect
(
controller
.
selection
.
extentOffset
,
4
);
EditableText
.
debugDeterministicCursor
=
false
;
});
group
(
'Selection changed scroll into view'
,
()
{
final
String
text
=
List
<
int
>.
generate
(
64
,
(
int
index
)
=>
index
).
join
(
'
\n
'
);
final
TextEditingController
controller
=
TextEditingController
(
text:
text
);
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment