Migrate EditableTextState from addPostFrameCallbacks to compositionCallbacks (#119359)
* PostFrameCallbacks -> compositionCallbacks * review * review
This commit is contained in:
parent
865dc5c510
commit
1148a2a8ba
@ -168,9 +168,7 @@ abstract class Layer extends AbstractNode with DiagnosticableTreeMixin {
|
|||||||
assert(delta != 0);
|
assert(delta != 0);
|
||||||
_compositionCallbackCount += delta;
|
_compositionCallbackCount += delta;
|
||||||
assert(_compositionCallbackCount >= 0);
|
assert(_compositionCallbackCount >= 0);
|
||||||
if (parent != null) {
|
parent?._updateSubtreeCompositionObserverCount(delta);
|
||||||
parent!._updateSubtreeCompositionObserverCount(delta);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void _fireCompositionCallbacks({required bool includeChildren}) {
|
void _fireCompositionCallbacks({required bool includeChildren}) {
|
||||||
|
@ -84,6 +84,51 @@ const Duration _kCursorBlinkHalfPeriod = Duration(milliseconds: 500);
|
|||||||
// is shown in an obscured text field.
|
// is shown in an obscured text field.
|
||||||
const int _kObscureShowLatestCharCursorTicks = 3;
|
const int _kObscureShowLatestCharCursorTicks = 3;
|
||||||
|
|
||||||
|
class _CompositionCallback extends SingleChildRenderObjectWidget {
|
||||||
|
const _CompositionCallback({ required this.compositeCallback, required this.enabled, super.child });
|
||||||
|
final CompositionCallback compositeCallback;
|
||||||
|
final bool enabled;
|
||||||
|
|
||||||
|
@override
|
||||||
|
RenderObject createRenderObject(BuildContext context) {
|
||||||
|
return _RenderCompositionCallback(compositeCallback, enabled);
|
||||||
|
}
|
||||||
|
@override
|
||||||
|
void updateRenderObject(BuildContext context, _RenderCompositionCallback renderObject) {
|
||||||
|
super.updateRenderObject(context, renderObject);
|
||||||
|
// _EditableTextState always uses the same callback.
|
||||||
|
assert(renderObject.compositeCallback == compositeCallback);
|
||||||
|
renderObject.enabled = enabled;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _RenderCompositionCallback extends RenderProxyBox {
|
||||||
|
_RenderCompositionCallback(this.compositeCallback, this._enabled);
|
||||||
|
|
||||||
|
final CompositionCallback compositeCallback;
|
||||||
|
VoidCallback? _cancelCallback;
|
||||||
|
|
||||||
|
bool get enabled => _enabled;
|
||||||
|
bool _enabled = false;
|
||||||
|
set enabled(bool newValue) {
|
||||||
|
_enabled = newValue;
|
||||||
|
if (!newValue) {
|
||||||
|
_cancelCallback?.call();
|
||||||
|
_cancelCallback = null;
|
||||||
|
} else if (_cancelCallback == null) {
|
||||||
|
markNeedsPaint();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void paint(PaintingContext context, ui.Offset offset) {
|
||||||
|
if (enabled) {
|
||||||
|
_cancelCallback ??= context.addCompositionCallback(compositeCallback);
|
||||||
|
}
|
||||||
|
super.paint(context, offset);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// A controller for an editable text field.
|
/// A controller for an editable text field.
|
||||||
///
|
///
|
||||||
/// Whenever the user modifies a text field with an associated
|
/// Whenever the user modifies a text field with an associated
|
||||||
@ -2970,8 +3015,7 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien
|
|||||||
? currentAutofillScope!.attach(this, _effectiveAutofillClient.textInputConfiguration)
|
? currentAutofillScope!.attach(this, _effectiveAutofillClient.textInputConfiguration)
|
||||||
: TextInput.attach(this, _effectiveAutofillClient.textInputConfiguration);
|
: TextInput.attach(this, _effectiveAutofillClient.textInputConfiguration);
|
||||||
_updateSizeAndTransform();
|
_updateSizeAndTransform();
|
||||||
_updateComposingRectIfNeeded();
|
_schedulePeriodicPostFrameCallbacks();
|
||||||
_updateCaretRectIfNeeded();
|
|
||||||
final TextStyle style = widget.style;
|
final TextStyle style = widget.style;
|
||||||
_textInputConnection!
|
_textInputConnection!
|
||||||
..setStyle(
|
..setStyle(
|
||||||
@ -2999,6 +3043,7 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien
|
|||||||
_textInputConnection!.close();
|
_textInputConnection!.close();
|
||||||
_textInputConnection = null;
|
_textInputConnection = null;
|
||||||
_lastKnownRemoteTextEditingValue = null;
|
_lastKnownRemoteTextEditingValue = null;
|
||||||
|
removeTextPlaceholder();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -3523,6 +3568,33 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien
|
|||||||
updateKeepAlive();
|
updateKeepAlive();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void _compositeCallback(Layer layer) {
|
||||||
|
// The callback can be invoked when the layer is detached.
|
||||||
|
// The input connection can be closed by the platform in which case this
|
||||||
|
// widget doesn't rebuild.
|
||||||
|
if (!renderEditable.attached || !_hasInputConnection) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
assert(mounted);
|
||||||
|
assert((context as Element).debugIsActive);
|
||||||
|
_updateSizeAndTransform();
|
||||||
|
}
|
||||||
|
|
||||||
|
void _updateSizeAndTransform() {
|
||||||
|
final Size size = renderEditable.size;
|
||||||
|
final Matrix4 transform = renderEditable.getTransformTo(null);
|
||||||
|
_textInputConnection!.setEditableSizeAndTransform(size, transform);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _schedulePeriodicPostFrameCallbacks([Duration? duration]) {
|
||||||
|
if (!_hasInputConnection) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_updateSelectionRects();
|
||||||
|
_updateComposingRectIfNeeded();
|
||||||
|
_updateCaretRectIfNeeded();
|
||||||
|
SchedulerBinding.instance.addPostFrameCallback(_schedulePeriodicPostFrameCallbacks);
|
||||||
|
}
|
||||||
_ScribbleCacheKey? _scribbleCacheKey;
|
_ScribbleCacheKey? _scribbleCacheKey;
|
||||||
|
|
||||||
void _updateSelectionRects({bool force = false}) {
|
void _updateSelectionRects({bool force = false}) {
|
||||||
@ -3585,18 +3657,6 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien
|
|||||||
_textInputConnection!.setSelectionRects(rects);
|
_textInputConnection!.setSelectionRects(rects);
|
||||||
}
|
}
|
||||||
|
|
||||||
void _updateSizeAndTransform() {
|
|
||||||
if (_hasInputConnection) {
|
|
||||||
final Size size = renderEditable.size;
|
|
||||||
final Matrix4 transform = renderEditable.getTransformTo(null);
|
|
||||||
_textInputConnection!.setEditableSizeAndTransform(size, transform);
|
|
||||||
_updateSelectionRects();
|
|
||||||
SchedulerBinding.instance.addPostFrameCallback((Duration _) => _updateSizeAndTransform());
|
|
||||||
} else if (_placeholderLocation != -1) {
|
|
||||||
removeTextPlaceholder();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sends the current composing rect to the iOS text input plugin via the text
|
// Sends the current composing rect to the iOS text input plugin via the text
|
||||||
// input channel. We need to keep sending the information even if no text is
|
// input channel. We need to keep sending the information even if no text is
|
||||||
// currently marked, as the information usually lags behind. The text input
|
// currently marked, as the information usually lags behind. The text input
|
||||||
@ -3604,42 +3664,34 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien
|
|||||||
// when the composing rect info didn't arrive in time.
|
// when the composing rect info didn't arrive in time.
|
||||||
void _updateComposingRectIfNeeded() {
|
void _updateComposingRectIfNeeded() {
|
||||||
final TextRange composingRange = _value.composing;
|
final TextRange composingRange = _value.composing;
|
||||||
if (_hasInputConnection) {
|
assert(mounted);
|
||||||
assert(mounted);
|
Rect? composingRect = renderEditable.getRectForComposingRange(composingRange);
|
||||||
Rect? composingRect = renderEditable.getRectForComposingRange(composingRange);
|
// Send the caret location instead if there's no marked text yet.
|
||||||
// Send the caret location instead if there's no marked text yet.
|
if (composingRect == null) {
|
||||||
if (composingRect == null) {
|
assert(!composingRange.isValid || composingRange.isCollapsed);
|
||||||
assert(!composingRange.isValid || composingRange.isCollapsed);
|
final int offset = composingRange.isValid ? composingRange.start : 0;
|
||||||
final int offset = composingRange.isValid ? composingRange.start : 0;
|
composingRect = renderEditable.getLocalRectForCaret(TextPosition(offset: offset));
|
||||||
composingRect = renderEditable.getLocalRectForCaret(TextPosition(offset: offset));
|
|
||||||
}
|
|
||||||
_textInputConnection!.setComposingRect(composingRect);
|
|
||||||
SchedulerBinding.instance.addPostFrameCallback((Duration _) => _updateComposingRectIfNeeded());
|
|
||||||
}
|
}
|
||||||
|
_textInputConnection!.setComposingRect(composingRect);
|
||||||
}
|
}
|
||||||
|
|
||||||
void _updateCaretRectIfNeeded() {
|
void _updateCaretRectIfNeeded() {
|
||||||
if (_hasInputConnection) {
|
final TextSelection? selection = renderEditable.selection;
|
||||||
if (renderEditable.selection != null && renderEditable.selection!.isValid &&
|
if (selection == null || !selection.isValid || !selection.isCollapsed) {
|
||||||
renderEditable.selection!.isCollapsed) {
|
return;
|
||||||
final TextPosition currentTextPosition = TextPosition(offset: renderEditable.selection!.baseOffset);
|
|
||||||
final Rect caretRect = renderEditable.getLocalRectForCaret(currentTextPosition);
|
|
||||||
_textInputConnection!.setCaretRect(caretRect);
|
|
||||||
}
|
|
||||||
SchedulerBinding.instance.addPostFrameCallback((Duration _) => _updateCaretRectIfNeeded());
|
|
||||||
}
|
}
|
||||||
|
final TextPosition currentTextPosition = TextPosition(offset: selection.baseOffset);
|
||||||
|
final Rect caretRect = renderEditable.getLocalRectForCaret(currentTextPosition);
|
||||||
|
_textInputConnection!.setCaretRect(caretRect);
|
||||||
}
|
}
|
||||||
|
|
||||||
TextDirection get _textDirection {
|
TextDirection get _textDirection => widget.textDirection ?? Directionality.of(context);
|
||||||
final TextDirection result = widget.textDirection ?? Directionality.of(context);
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The renderer for this widget's descendant.
|
/// The renderer for this widget's descendant.
|
||||||
///
|
///
|
||||||
/// This property is typically used to notify the renderer of input gestures
|
/// This property is typically used to notify the renderer of input gestures
|
||||||
/// when [RenderEditable.ignorePointer] is true.
|
/// when [RenderEditable.ignorePointer] is true.
|
||||||
RenderEditable get renderEditable => _editableKey.currentContext!.findRenderObject()! as RenderEditable;
|
late final RenderEditable renderEditable = _editableKey.currentContext!.findRenderObject()! as RenderEditable;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
TextEditingValue get textEditingValue => _value;
|
TextEditingValue get textEditingValue => _value;
|
||||||
@ -3812,7 +3864,7 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
void removeTextPlaceholder() {
|
void removeTextPlaceholder() {
|
||||||
if (!widget.scribbleEnabled) {
|
if (!widget.scribbleEnabled || _placeholderLocation == -1) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -4243,100 +4295,104 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien
|
|||||||
super.build(context); // See AutomaticKeepAliveClientMixin.
|
super.build(context); // See AutomaticKeepAliveClientMixin.
|
||||||
|
|
||||||
final TextSelectionControls? controls = widget.selectionControls;
|
final TextSelectionControls? controls = widget.selectionControls;
|
||||||
return TextFieldTapRegion(
|
return _CompositionCallback(
|
||||||
onTapOutside: widget.onTapOutside ?? _defaultOnTapOutside,
|
compositeCallback: _compositeCallback,
|
||||||
debugLabel: kReleaseMode ? null : 'EditableText',
|
enabled: _hasInputConnection,
|
||||||
child: MouseRegion(
|
child: TextFieldTapRegion(
|
||||||
cursor: widget.mouseCursor ?? SystemMouseCursors.text,
|
onTapOutside: widget.onTapOutside ?? _defaultOnTapOutside,
|
||||||
child: Actions(
|
debugLabel: kReleaseMode ? null : 'EditableText',
|
||||||
actions: _actions,
|
child: MouseRegion(
|
||||||
child: _TextEditingHistory(
|
cursor: widget.mouseCursor ?? SystemMouseCursors.text,
|
||||||
controller: widget.controller,
|
child: Actions(
|
||||||
onTriggered: (TextEditingValue value) {
|
actions: _actions,
|
||||||
userUpdateTextEditingValue(value, SelectionChangedCause.keyboard);
|
child: _TextEditingHistory(
|
||||||
},
|
controller: widget.controller,
|
||||||
child: Focus(
|
onTriggered: (TextEditingValue value) {
|
||||||
focusNode: widget.focusNode,
|
userUpdateTextEditingValue(value, SelectionChangedCause.keyboard);
|
||||||
includeSemantics: false,
|
},
|
||||||
debugLabel: kReleaseMode ? null : 'EditableText',
|
child: Focus(
|
||||||
child: Scrollable(
|
focusNode: widget.focusNode,
|
||||||
key: _scrollableKey,
|
includeSemantics: false,
|
||||||
excludeFromSemantics: true,
|
debugLabel: kReleaseMode ? null : 'EditableText',
|
||||||
axisDirection: _isMultiline ? AxisDirection.down : AxisDirection.right,
|
child: Scrollable(
|
||||||
controller: _scrollController,
|
key: _scrollableKey,
|
||||||
physics: widget.scrollPhysics,
|
excludeFromSemantics: true,
|
||||||
dragStartBehavior: widget.dragStartBehavior,
|
axisDirection: _isMultiline ? AxisDirection.down : AxisDirection.right,
|
||||||
restorationId: widget.restorationId,
|
controller: _scrollController,
|
||||||
// If a ScrollBehavior is not provided, only apply scrollbars when
|
physics: widget.scrollPhysics,
|
||||||
// multiline. The overscroll indicator should not be applied in
|
dragStartBehavior: widget.dragStartBehavior,
|
||||||
// either case, glowing or stretching.
|
restorationId: widget.restorationId,
|
||||||
scrollBehavior: widget.scrollBehavior ?? ScrollConfiguration.of(context).copyWith(
|
// If a ScrollBehavior is not provided, only apply scrollbars when
|
||||||
scrollbars: _isMultiline,
|
// multiline. The overscroll indicator should not be applied in
|
||||||
overscroll: false,
|
// either case, glowing or stretching.
|
||||||
),
|
scrollBehavior: widget.scrollBehavior ?? ScrollConfiguration.of(context).copyWith(
|
||||||
viewportBuilder: (BuildContext context, ViewportOffset offset) {
|
scrollbars: _isMultiline,
|
||||||
return CompositedTransformTarget(
|
overscroll: false,
|
||||||
link: _toolbarLayerLink,
|
),
|
||||||
child: Semantics(
|
viewportBuilder: (BuildContext context, ViewportOffset offset) {
|
||||||
onCopy: _semanticsOnCopy(controls),
|
return CompositedTransformTarget(
|
||||||
onCut: _semanticsOnCut(controls),
|
link: _toolbarLayerLink,
|
||||||
onPaste: _semanticsOnPaste(controls),
|
child: Semantics(
|
||||||
child: _ScribbleFocusable(
|
onCopy: _semanticsOnCopy(controls),
|
||||||
focusNode: widget.focusNode,
|
onCut: _semanticsOnCut(controls),
|
||||||
editableKey: _editableKey,
|
onPaste: _semanticsOnPaste(controls),
|
||||||
enabled: widget.scribbleEnabled,
|
child: _ScribbleFocusable(
|
||||||
updateSelectionRects: () {
|
focusNode: widget.focusNode,
|
||||||
_openInputConnection();
|
editableKey: _editableKey,
|
||||||
_updateSelectionRects(force: true);
|
enabled: widget.scribbleEnabled,
|
||||||
},
|
updateSelectionRects: () {
|
||||||
child: _Editable(
|
_openInputConnection();
|
||||||
key: _editableKey,
|
_updateSelectionRects(force: true);
|
||||||
startHandleLayerLink: _startHandleLayerLink,
|
},
|
||||||
endHandleLayerLink: _endHandleLayerLink,
|
child: _Editable(
|
||||||
inlineSpan: buildTextSpan(),
|
key: _editableKey,
|
||||||
value: _value,
|
startHandleLayerLink: _startHandleLayerLink,
|
||||||
cursorColor: _cursorColor,
|
endHandleLayerLink: _endHandleLayerLink,
|
||||||
backgroundCursorColor: widget.backgroundCursorColor,
|
inlineSpan: buildTextSpan(),
|
||||||
showCursor: EditableText.debugDeterministicCursor
|
value: _value,
|
||||||
? ValueNotifier<bool>(widget.showCursor)
|
cursorColor: _cursorColor,
|
||||||
: _cursorVisibilityNotifier,
|
backgroundCursorColor: widget.backgroundCursorColor,
|
||||||
forceLine: widget.forceLine,
|
showCursor: EditableText.debugDeterministicCursor
|
||||||
readOnly: widget.readOnly,
|
? ValueNotifier<bool>(widget.showCursor)
|
||||||
hasFocus: _hasFocus,
|
: _cursorVisibilityNotifier,
|
||||||
maxLines: widget.maxLines,
|
forceLine: widget.forceLine,
|
||||||
minLines: widget.minLines,
|
readOnly: widget.readOnly,
|
||||||
expands: widget.expands,
|
hasFocus: _hasFocus,
|
||||||
strutStyle: widget.strutStyle,
|
maxLines: widget.maxLines,
|
||||||
selectionColor: widget.selectionColor,
|
minLines: widget.minLines,
|
||||||
textScaleFactor: widget.textScaleFactor ?? MediaQuery.textScaleFactorOf(context),
|
expands: widget.expands,
|
||||||
textAlign: widget.textAlign,
|
strutStyle: widget.strutStyle,
|
||||||
textDirection: _textDirection,
|
selectionColor: widget.selectionColor,
|
||||||
locale: widget.locale,
|
textScaleFactor: widget.textScaleFactor ?? MediaQuery.textScaleFactorOf(context),
|
||||||
textHeightBehavior: widget.textHeightBehavior ?? DefaultTextHeightBehavior.maybeOf(context),
|
textAlign: widget.textAlign,
|
||||||
textWidthBasis: widget.textWidthBasis,
|
textDirection: _textDirection,
|
||||||
obscuringCharacter: widget.obscuringCharacter,
|
locale: widget.locale,
|
||||||
obscureText: widget.obscureText,
|
textHeightBehavior: widget.textHeightBehavior ?? DefaultTextHeightBehavior.maybeOf(context),
|
||||||
offset: offset,
|
textWidthBasis: widget.textWidthBasis,
|
||||||
onCaretChanged: _handleCaretChanged,
|
obscuringCharacter: widget.obscuringCharacter,
|
||||||
rendererIgnoresPointer: widget.rendererIgnoresPointer,
|
obscureText: widget.obscureText,
|
||||||
cursorWidth: widget.cursorWidth,
|
offset: offset,
|
||||||
cursorHeight: widget.cursorHeight,
|
onCaretChanged: _handleCaretChanged,
|
||||||
cursorRadius: widget.cursorRadius,
|
rendererIgnoresPointer: widget.rendererIgnoresPointer,
|
||||||
cursorOffset: widget.cursorOffset ?? Offset.zero,
|
cursorWidth: widget.cursorWidth,
|
||||||
selectionHeightStyle: widget.selectionHeightStyle,
|
cursorHeight: widget.cursorHeight,
|
||||||
selectionWidthStyle: widget.selectionWidthStyle,
|
cursorRadius: widget.cursorRadius,
|
||||||
paintCursorAboveText: widget.paintCursorAboveText,
|
cursorOffset: widget.cursorOffset ?? Offset.zero,
|
||||||
enableInteractiveSelection: widget._userSelectionEnabled,
|
selectionHeightStyle: widget.selectionHeightStyle,
|
||||||
textSelectionDelegate: this,
|
selectionWidthStyle: widget.selectionWidthStyle,
|
||||||
devicePixelRatio: _devicePixelRatio,
|
paintCursorAboveText: widget.paintCursorAboveText,
|
||||||
promptRectRange: _currentPromptRectRange,
|
enableInteractiveSelection: widget._userSelectionEnabled,
|
||||||
promptRectColor: widget.autocorrectionTextRectColor,
|
textSelectionDelegate: this,
|
||||||
clipBehavior: widget.clipBehavior,
|
devicePixelRatio: _devicePixelRatio,
|
||||||
|
promptRectRange: _currentPromptRectRange,
|
||||||
|
promptRectColor: widget.autocorrectionTextRectColor,
|
||||||
|
clipBehavior: widget.clipBehavior,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
);
|
||||||
);
|
},
|
||||||
},
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -179,6 +179,9 @@ class TestRecordingPaintingContext extends ClipContext implements PaintingContex
|
|||||||
painter(this, offset);
|
painter(this, offset);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
VoidCallback addCompositionCallback(CompositionCallback callback) => () {};
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void noSuchMethod(Invocation invocation) { }
|
void noSuchMethod(Invocation invocation) { }
|
||||||
}
|
}
|
||||||
|
@ -14611,6 +14611,66 @@ testWidgets('Floating cursor ending with selection', (WidgetTester tester) async
|
|||||||
// Shouldn't crash.
|
// Shouldn't crash.
|
||||||
state.didChangeMetrics();
|
state.didChangeMetrics();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
testWidgets('_CompositionCallback widget does not skip frames', (WidgetTester tester) async {
|
||||||
|
EditableText.debugDeterministicCursor = true;
|
||||||
|
final FocusNode focusNode = FocusNode();
|
||||||
|
final TextEditingController controller = TextEditingController.fromValue(
|
||||||
|
const TextEditingValue(selection: TextSelection.collapsed(offset: 0)),
|
||||||
|
);
|
||||||
|
|
||||||
|
Offset offset = Offset.zero;
|
||||||
|
late StateSetter setState;
|
||||||
|
|
||||||
|
await tester.pumpWidget(
|
||||||
|
MaterialApp(
|
||||||
|
home: StatefulBuilder(
|
||||||
|
builder: (BuildContext context, StateSetter stateSetter) {
|
||||||
|
setState = stateSetter;
|
||||||
|
return Transform.translate(
|
||||||
|
offset: offset,
|
||||||
|
// The EditableText is configured in a way that the it doesn't
|
||||||
|
// explicitly request repaint on focus change.
|
||||||
|
child: TickerMode(
|
||||||
|
enabled: false,
|
||||||
|
child: RepaintBoundary(
|
||||||
|
child: EditableText(
|
||||||
|
controller: controller,
|
||||||
|
focusNode: focusNode,
|
||||||
|
style: const TextStyle(),
|
||||||
|
showCursor: false,
|
||||||
|
cursorColor: Colors.blue,
|
||||||
|
backgroundCursorColor: Colors.grey,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
focusNode.requestFocus();
|
||||||
|
await tester.pump();
|
||||||
|
tester.testTextInput.log.clear();
|
||||||
|
|
||||||
|
// The composition callback should be registered. To verify, change the
|
||||||
|
// parent layer's transform.
|
||||||
|
setState(() { offset = const Offset(42, 0); });
|
||||||
|
await tester.pump();
|
||||||
|
|
||||||
|
expect(
|
||||||
|
tester.testTextInput.log,
|
||||||
|
contains(
|
||||||
|
matchesMethodCall(
|
||||||
|
'TextInput.setEditableSizeAndTransform',
|
||||||
|
args: containsPair('transform', Matrix4.translationValues(offset.dx, offset.dy, 0).storage),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
EditableText.debugDeterministicCursor = false;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
class UnsettableController extends TextEditingController {
|
class UnsettableController extends TextEditingController {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user