Unverified Commit c3242a16 authored by Michael Goderbauer's avatar Michael Goderbauer Committed by GitHub

Blinking cursor respects TickerMode (#92614)

parent 6bd02372
......@@ -1674,6 +1674,10 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien
_cursorVisibilityNotifier.value = widget.showCursor;
}
// Whether `TickerMode.of(context)` is true and animations (like blinking the
// cursor) are supposed to run.
bool _tickersEnabled = true;
@override
void didChangeDependencies() {
super.didChangeDependencies();
......@@ -1693,6 +1697,19 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien
}
});
}
// Restart or stop the blinking cursor when TickerMode changes.
final bool newTickerEnabled = TickerMode.of(context);
if (_tickersEnabled != newTickerEnabled) {
_tickersEnabled = newTickerEnabled;
if (_tickersEnabled && _cursorActive) {
_startCursorTimer();
} else if (!_tickersEnabled && _cursorTimer != null) {
// Cannot use _stopCursorTimer because it would reset _cursorActive.
_cursorTimer!.cancel();
_cursorTimer = null;
}
}
}
@override
......@@ -2535,8 +2552,16 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien
_cursorTimer = Timer.periodic(_kCursorBlinkHalfPeriod, _cursorTick);
}
// 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 _startCursorTimer() {
assert(_cursorTimer == null);
_cursorActive = true;
if (!_tickersEnabled) {
return;
}
_targetCursorVisibility = true;
_cursorBlinkOpacityController.value = 1.0;
if (EditableText.debugDeterministicCursor)
......@@ -2549,6 +2574,7 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien
}
void _stopCursorTimer({ bool resetCharTicks = true }) {
_cursorActive = false;
_cursorTimer?.cancel();
_cursorTimer = null;
_targetCursorVisibility = false;
......@@ -2566,7 +2592,7 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien
void _startOrStopCursorTimerIfNeeded() {
if (_cursorTimer == null && _hasFocus && _value.selection.isCollapsed)
_startCursorTimer();
else if (_cursorTimer != null && (!_hasFocus || !_value.selection.isCollapsed))
else if (_cursorActive && (!_hasFocus || !_value.selection.isCollapsed))
_stopCursorTimer();
}
......
......@@ -10036,4 +10036,84 @@ void main() {
expect(tester.layers.any((Layer layer) => layer.debugSubtreeNeedsAddToScene!), isFalse);
}, skip: isContextMenuProvidedByPlatform); // [intended] only applies to platforms where we supply the context menu.
testWidgets('cursor blinking respects TickerMode', (WidgetTester tester) async {
final FocusNode focusNode = FocusNode();
Widget builder({required bool tickerMode}) {
return MaterialApp(
home: Material(
child: Center(
child: TickerMode(enabled: tickerMode, child: TextField(focusNode: focusNode)),
),
),
);
}
// TickerMode is on, cursor is blinking.
await tester.pumpWidget(builder(tickerMode: true));
await tester.showKeyboard(find.byType(TextField));
final EditableTextState state = tester.state<EditableTextState>(find.byType(EditableText));
final RenderEditable editable = state.renderEditable;
expect(editable.showCursor.value, isTrue);
await tester.pump(state.cursorBlinkInterval);
expect(editable.showCursor.value, isFalse);
await tester.pump(state.cursorBlinkInterval);
expect(editable.showCursor.value, isTrue);
await tester.pump(state.cursorBlinkInterval);
expect(editable.showCursor.value, isFalse);
// TickerMode is off, cursor does not blink.
await tester.pumpWidget(builder(tickerMode: false));
expect(editable.showCursor.value, isFalse);
await tester.pump(state.cursorBlinkInterval);
expect(editable.showCursor.value, isFalse);
await tester.pump(state.cursorBlinkInterval);
expect(editable.showCursor.value, isFalse);
await tester.pump(state.cursorBlinkInterval);
expect(editable.showCursor.value, isFalse);
// TickerMode is on, cursor blinks again.
await tester.pumpWidget(builder(tickerMode: true));
expect(editable.showCursor.value, isTrue);
await tester.pump(state.cursorBlinkInterval);
expect(editable.showCursor.value, isFalse);
await tester.pump(state.cursorBlinkInterval);
expect(editable.showCursor.value, isTrue);
await tester.pump(state.cursorBlinkInterval);
expect(editable.showCursor.value, isFalse);
// Dismissing focus while tickerMode is off does not start cursor blinking
// when tickerMode is turned on again.
await tester.pumpWidget(builder(tickerMode: false));
focusNode.unfocus();
await tester.pump();
expect(editable.showCursor.value, isFalse);
await tester.pump(state.cursorBlinkInterval);
expect(editable.showCursor.value, isFalse);
await tester.pump(state.cursorBlinkInterval);
expect(editable.showCursor.value, isFalse);
await tester.pumpWidget(builder(tickerMode: true));
expect(editable.showCursor.value, isFalse);
await tester.pump(state.cursorBlinkInterval);
expect(editable.showCursor.value, isFalse);
await tester.pump(state.cursorBlinkInterval);
expect(editable.showCursor.value, isFalse);
// Focusing while tickerMode is off does not start cursor blinking...
await tester.pumpWidget(builder(tickerMode: false));
await tester.showKeyboard(find.byType(TextField));
expect(editable.showCursor.value, isFalse);
await tester.pump(state.cursorBlinkInterval);
expect(editable.showCursor.value, isFalse);
await tester.pump(state.cursorBlinkInterval);
expect(editable.showCursor.value, isFalse);
// ... but it does start when tickerMode is switched on again.
await tester.pumpWidget(builder(tickerMode: true));
expect(editable.showCursor.value, isTrue);
await tester.pump(state.cursorBlinkInterval);
expect(editable.showCursor.value, isFalse);
await tester.pump(state.cursorBlinkInterval);
expect(editable.showCursor.value, isTrue);
});
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment