Disable blinking cursor when EditableText.showCursor
is false (#127562)
Fixes https://github.com/flutter/flutter/issues/108187
This commit is contained in:
parent
9707001c02
commit
37f20c268b
@ -2610,12 +2610,10 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien
|
|||||||
final bool newTickerEnabled = TickerMode.of(context);
|
final bool newTickerEnabled = TickerMode.of(context);
|
||||||
if (_tickersEnabled != newTickerEnabled) {
|
if (_tickersEnabled != newTickerEnabled) {
|
||||||
_tickersEnabled = newTickerEnabled;
|
_tickersEnabled = newTickerEnabled;
|
||||||
if (_tickersEnabled && _cursorActive) {
|
if (_showBlinkingCursor) {
|
||||||
_startCursorBlink();
|
_startCursorBlink();
|
||||||
} else if (!_tickersEnabled && _cursorTimer != null) {
|
} else if (!_tickersEnabled && _cursorTimer != null) {
|
||||||
// Cannot use _stopCursorBlink because it would reset _cursorActive.
|
_stopCursorBlink();
|
||||||
_cursorTimer!.cancel();
|
|
||||||
_cursorTimer = null;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2707,6 +2705,10 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (widget.showCursor != oldWidget.showCursor) {
|
||||||
|
_startOrStopCursorTimerIfNeeded();
|
||||||
|
}
|
||||||
final bool canPaste = widget.selectionControls is TextSelectionHandleControls
|
final bool canPaste = widget.selectionControls is TextSelectionHandleControls
|
||||||
? pasteEnabled
|
? pasteEnabled
|
||||||
: widget.selectionControls?.canPaste(this) ?? false;
|
: widget.selectionControls?.canPaste(this) ?? false;
|
||||||
@ -3655,6 +3657,8 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien
|
|||||||
_cursorVisibilityNotifier.value = widget.showCursor && _cursorBlinkOpacityController.value > 0;
|
_cursorVisibilityNotifier.value = widget.showCursor && _cursorBlinkOpacityController.value > 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool get _showBlinkingCursor => _hasFocus && _value.selection.isCollapsed && widget.showCursor && _tickersEnabled;
|
||||||
|
|
||||||
/// Whether the blinking cursor is actually visible at this precise moment
|
/// Whether the blinking cursor is actually visible at this precise moment
|
||||||
/// (it's hidden half the time, since it blinks).
|
/// (it's hidden half the time, since it blinks).
|
||||||
@visibleForTesting
|
@visibleForTesting
|
||||||
@ -3673,13 +3677,11 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien
|
|||||||
int _obscureShowCharTicksPending = 0;
|
int _obscureShowCharTicksPending = 0;
|
||||||
int? _obscureLatestCharIndex;
|
int? _obscureLatestCharIndex;
|
||||||
|
|
||||||
// Indicates whether the cursor should be blinking right now (but it may
|
|
||||||
// actually not blink because it's disabled via TickerMode.of(context)).
|
|
||||||
bool _cursorActive = false;
|
|
||||||
|
|
||||||
void _startCursorBlink() {
|
void _startCursorBlink() {
|
||||||
assert(!(_cursorTimer?.isActive ?? false) || !(_backingCursorBlinkOpacityController?.isAnimating ?? false));
|
assert(!(_cursorTimer?.isActive ?? false) || !(_backingCursorBlinkOpacityController?.isAnimating ?? false));
|
||||||
_cursorActive = true;
|
if (!widget.showCursor) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (!_tickersEnabled) {
|
if (!_tickersEnabled) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -3719,7 +3721,6 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien
|
|||||||
}
|
}
|
||||||
|
|
||||||
void _stopCursorBlink({ bool resetCharTicks = true }) {
|
void _stopCursorBlink({ bool resetCharTicks = true }) {
|
||||||
_cursorActive = false;
|
|
||||||
_cursorBlinkOpacityController.value = 0.0;
|
_cursorBlinkOpacityController.value = 0.0;
|
||||||
_cursorTimer?.cancel();
|
_cursorTimer?.cancel();
|
||||||
_cursorTimer = null;
|
_cursorTimer = null;
|
||||||
@ -3729,11 +3730,10 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien
|
|||||||
}
|
}
|
||||||
|
|
||||||
void _startOrStopCursorTimerIfNeeded() {
|
void _startOrStopCursorTimerIfNeeded() {
|
||||||
if (_cursorTimer == null && _hasFocus && _value.selection.isCollapsed) {
|
if (!_showBlinkingCursor) {
|
||||||
_startCursorBlink();
|
|
||||||
}
|
|
||||||
else if (_cursorActive && (!_hasFocus || !_value.selection.isCollapsed)) {
|
|
||||||
_stopCursorBlink();
|
_stopCursorBlink();
|
||||||
|
} else if (_cursorTimer == null) {
|
||||||
|
_startCursorBlink();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -771,6 +771,54 @@ void main() {
|
|||||||
await checkCursorBlinking();
|
await checkCursorBlinking();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
testWidgets('Turning showCursor off stops the cursor', (WidgetTester tester) async {
|
||||||
|
// Regression test for https://github.com/flutter/flutter/issues/108187.
|
||||||
|
final bool debugDeterministicCursor = EditableText.debugDeterministicCursor;
|
||||||
|
// This doesn't really matter.
|
||||||
|
EditableText.debugDeterministicCursor = false;
|
||||||
|
addTearDown(() { EditableText.debugDeterministicCursor = debugDeterministicCursor; });
|
||||||
|
const Key key = Key('EditableText');
|
||||||
|
|
||||||
|
Widget buildEditableText({ required bool showCursor }) {
|
||||||
|
return MediaQuery(
|
||||||
|
data: const MediaQueryData(),
|
||||||
|
child: Directionality(
|
||||||
|
textDirection: TextDirection.ltr,
|
||||||
|
child: EditableText(
|
||||||
|
key: key,
|
||||||
|
backgroundCursorColor: Colors.grey,
|
||||||
|
// Use animation controller to animate cursor blink for testing.
|
||||||
|
cursorOpacityAnimates: true,
|
||||||
|
controller: controller,
|
||||||
|
focusNode: focusNode,
|
||||||
|
style: textStyle,
|
||||||
|
cursorColor: cursorColor,
|
||||||
|
showCursor: showCursor,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
late final EditableTextState editableTextState = tester.state(find.byKey(key));
|
||||||
|
await tester.pumpWidget(buildEditableText(showCursor: false));
|
||||||
|
await tester.tap(find.byKey(key));
|
||||||
|
await tester.pump();
|
||||||
|
|
||||||
|
// No cursor even when focused.
|
||||||
|
expect(editableTextState.cursorCurrentlyVisible, false);
|
||||||
|
|
||||||
|
// The EditableText still has focus, so the cursor should starts blinking.
|
||||||
|
await tester.pumpWidget(buildEditableText(showCursor: true));
|
||||||
|
expect(editableTextState.cursorCurrentlyVisible, true);
|
||||||
|
await tester.pump();
|
||||||
|
expect(editableTextState.cursorCurrentlyVisible, true);
|
||||||
|
|
||||||
|
// readOnly disables blinking cursor.
|
||||||
|
await tester.pumpWidget(buildEditableText(showCursor: false));
|
||||||
|
expect(editableTextState.cursorCurrentlyVisible, false);
|
||||||
|
await tester.pump();
|
||||||
|
expect(editableTextState.cursorCurrentlyVisible, false);
|
||||||
|
});
|
||||||
|
|
||||||
// Regression test for https://github.com/flutter/flutter/pull/30475.
|
// Regression test for https://github.com/flutter/flutter/pull/30475.
|
||||||
testWidgets('Trying to select with the floating cursor does not crash', (WidgetTester tester) async {
|
testWidgets('Trying to select with the floating cursor does not crash', (WidgetTester tester) async {
|
||||||
const String text = 'hello world this is fun and cool and awesome!';
|
const String text = 'hello world this is fun and cool and awesome!';
|
||||||
|
Loading…
x
Reference in New Issue
Block a user