Skip to content
This repository was archived by the owner on Feb 22, 2023. It is now read-only.

Commit 1148a2a

Browse files
Migrate EditableTextState from addPostFrameCallbacks to compositionCallbacks (#119359)
* PostFrameCallbacks -> compositionCallbacks * review * review
1 parent 865dc5c commit 1148a2a

File tree

4 files changed

+251
-134
lines changed

4 files changed

+251
-134
lines changed

packages/flutter/lib/src/rendering/layer.dart

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -168,9 +168,7 @@ abstract class Layer extends AbstractNode with DiagnosticableTreeMixin {
168168
assert(delta != 0);
169169
_compositionCallbackCount += delta;
170170
assert(_compositionCallbackCount >= 0);
171-
if (parent != null) {
172-
parent!._updateSubtreeCompositionObserverCount(delta);
173-
}
171+
parent?._updateSubtreeCompositionObserverCount(delta);
174172
}
175173

176174
void _fireCompositionCallbacks({required bool includeChildren}) {

packages/flutter/lib/src/widgets/editable_text.dart

Lines changed: 187 additions & 131 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,51 @@ const Duration _kCursorBlinkHalfPeriod = Duration(milliseconds: 500);
8484
// is shown in an obscured text field.
8585
const int _kObscureShowLatestCharCursorTicks = 3;
8686

87+
class _CompositionCallback extends SingleChildRenderObjectWidget {
88+
const _CompositionCallback({ required this.compositeCallback, required this.enabled, super.child });
89+
final CompositionCallback compositeCallback;
90+
final bool enabled;
91+
92+
@override
93+
RenderObject createRenderObject(BuildContext context) {
94+
return _RenderCompositionCallback(compositeCallback, enabled);
95+
}
96+
@override
97+
void updateRenderObject(BuildContext context, _RenderCompositionCallback renderObject) {
98+
super.updateRenderObject(context, renderObject);
99+
// _EditableTextState always uses the same callback.
100+
assert(renderObject.compositeCallback == compositeCallback);
101+
renderObject.enabled = enabled;
102+
}
103+
}
104+
105+
class _RenderCompositionCallback extends RenderProxyBox {
106+
_RenderCompositionCallback(this.compositeCallback, this._enabled);
107+
108+
final CompositionCallback compositeCallback;
109+
VoidCallback? _cancelCallback;
110+
111+
bool get enabled => _enabled;
112+
bool _enabled = false;
113+
set enabled(bool newValue) {
114+
_enabled = newValue;
115+
if (!newValue) {
116+
_cancelCallback?.call();
117+
_cancelCallback = null;
118+
} else if (_cancelCallback == null) {
119+
markNeedsPaint();
120+
}
121+
}
122+
123+
@override
124+
void paint(PaintingContext context, ui.Offset offset) {
125+
if (enabled) {
126+
_cancelCallback ??= context.addCompositionCallback(compositeCallback);
127+
}
128+
super.paint(context, offset);
129+
}
130+
}
131+
87132
/// A controller for an editable text field.
88133
///
89134
/// Whenever the user modifies a text field with an associated
@@ -2970,8 +3015,7 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien
29703015
? currentAutofillScope!.attach(this, _effectiveAutofillClient.textInputConfiguration)
29713016
: TextInput.attach(this, _effectiveAutofillClient.textInputConfiguration);
29723017
_updateSizeAndTransform();
2973-
_updateComposingRectIfNeeded();
2974-
_updateCaretRectIfNeeded();
3018+
_schedulePeriodicPostFrameCallbacks();
29753019
final TextStyle style = widget.style;
29763020
_textInputConnection!
29773021
..setStyle(
@@ -2999,6 +3043,7 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien
29993043
_textInputConnection!.close();
30003044
_textInputConnection = null;
30013045
_lastKnownRemoteTextEditingValue = null;
3046+
removeTextPlaceholder();
30023047
}
30033048
}
30043049

@@ -3523,6 +3568,33 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien
35233568
updateKeepAlive();
35243569
}
35253570

3571+
void _compositeCallback(Layer layer) {
3572+
// The callback can be invoked when the layer is detached.
3573+
// The input connection can be closed by the platform in which case this
3574+
// widget doesn't rebuild.
3575+
if (!renderEditable.attached || !_hasInputConnection) {
3576+
return;
3577+
}
3578+
assert(mounted);
3579+
assert((context as Element).debugIsActive);
3580+
_updateSizeAndTransform();
3581+
}
3582+
3583+
void _updateSizeAndTransform() {
3584+
final Size size = renderEditable.size;
3585+
final Matrix4 transform = renderEditable.getTransformTo(null);
3586+
_textInputConnection!.setEditableSizeAndTransform(size, transform);
3587+
}
3588+
3589+
void _schedulePeriodicPostFrameCallbacks([Duration? duration]) {
3590+
if (!_hasInputConnection) {
3591+
return;
3592+
}
3593+
_updateSelectionRects();
3594+
_updateComposingRectIfNeeded();
3595+
_updateCaretRectIfNeeded();
3596+
SchedulerBinding.instance.addPostFrameCallback(_schedulePeriodicPostFrameCallbacks);
3597+
}
35263598
_ScribbleCacheKey? _scribbleCacheKey;
35273599

35283600
void _updateSelectionRects({bool force = false}) {
@@ -3585,61 +3657,41 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien
35853657
_textInputConnection!.setSelectionRects(rects);
35863658
}
35873659

3588-
void _updateSizeAndTransform() {
3589-
if (_hasInputConnection) {
3590-
final Size size = renderEditable.size;
3591-
final Matrix4 transform = renderEditable.getTransformTo(null);
3592-
_textInputConnection!.setEditableSizeAndTransform(size, transform);
3593-
_updateSelectionRects();
3594-
SchedulerBinding.instance.addPostFrameCallback((Duration _) => _updateSizeAndTransform());
3595-
} else if (_placeholderLocation != -1) {
3596-
removeTextPlaceholder();
3597-
}
3598-
}
3599-
36003660
// Sends the current composing rect to the iOS text input plugin via the text
36013661
// input channel. We need to keep sending the information even if no text is
36023662
// currently marked, as the information usually lags behind. The text input
36033663
// plugin needs to estimate the composing rect based on the latest caret rect,
36043664
// when the composing rect info didn't arrive in time.
36053665
void _updateComposingRectIfNeeded() {
36063666
final TextRange composingRange = _value.composing;
3607-
if (_hasInputConnection) {
3608-
assert(mounted);
3609-
Rect? composingRect = renderEditable.getRectForComposingRange(composingRange);
3610-
// Send the caret location instead if there's no marked text yet.
3611-
if (composingRect == null) {
3612-
assert(!composingRange.isValid || composingRange.isCollapsed);
3613-
final int offset = composingRange.isValid ? composingRange.start : 0;
3614-
composingRect = renderEditable.getLocalRectForCaret(TextPosition(offset: offset));
3615-
}
3616-
_textInputConnection!.setComposingRect(composingRect);
3617-
SchedulerBinding.instance.addPostFrameCallback((Duration _) => _updateComposingRectIfNeeded());
3667+
assert(mounted);
3668+
Rect? composingRect = renderEditable.getRectForComposingRange(composingRange);
3669+
// Send the caret location instead if there's no marked text yet.
3670+
if (composingRect == null) {
3671+
assert(!composingRange.isValid || composingRange.isCollapsed);
3672+
final int offset = composingRange.isValid ? composingRange.start : 0;
3673+
composingRect = renderEditable.getLocalRectForCaret(TextPosition(offset: offset));
36183674
}
3675+
_textInputConnection!.setComposingRect(composingRect);
36193676
}
36203677

36213678
void _updateCaretRectIfNeeded() {
3622-
if (_hasInputConnection) {
3623-
if (renderEditable.selection != null && renderEditable.selection!.isValid &&
3624-
renderEditable.selection!.isCollapsed) {
3625-
final TextPosition currentTextPosition = TextPosition(offset: renderEditable.selection!.baseOffset);
3626-
final Rect caretRect = renderEditable.getLocalRectForCaret(currentTextPosition);
3627-
_textInputConnection!.setCaretRect(caretRect);
3628-
}
3629-
SchedulerBinding.instance.addPostFrameCallback((Duration _) => _updateCaretRectIfNeeded());
3679+
final TextSelection? selection = renderEditable.selection;
3680+
if (selection == null || !selection.isValid || !selection.isCollapsed) {
3681+
return;
36303682
}
3683+
final TextPosition currentTextPosition = TextPosition(offset: selection.baseOffset);
3684+
final Rect caretRect = renderEditable.getLocalRectForCaret(currentTextPosition);
3685+
_textInputConnection!.setCaretRect(caretRect);
36313686
}
36323687

3633-
TextDirection get _textDirection {
3634-
final TextDirection result = widget.textDirection ?? Directionality.of(context);
3635-
return result;
3636-
}
3688+
TextDirection get _textDirection => widget.textDirection ?? Directionality.of(context);
36373689

36383690
/// The renderer for this widget's descendant.
36393691
///
36403692
/// This property is typically used to notify the renderer of input gestures
36413693
/// when [RenderEditable.ignorePointer] is true.
3642-
RenderEditable get renderEditable => _editableKey.currentContext!.findRenderObject()! as RenderEditable;
3694+
late final RenderEditable renderEditable = _editableKey.currentContext!.findRenderObject()! as RenderEditable;
36433695

36443696
@override
36453697
TextEditingValue get textEditingValue => _value;
@@ -3812,7 +3864,7 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien
38123864

38133865
@override
38143866
void removeTextPlaceholder() {
3815-
if (!widget.scribbleEnabled) {
3867+
if (!widget.scribbleEnabled || _placeholderLocation == -1) {
38163868
return;
38173869
}
38183870

@@ -4243,100 +4295,104 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien
42434295
super.build(context); // See AutomaticKeepAliveClientMixin.
42444296

42454297
final TextSelectionControls? controls = widget.selectionControls;
4246-
return TextFieldTapRegion(
4247-
onTapOutside: widget.onTapOutside ?? _defaultOnTapOutside,
4248-
debugLabel: kReleaseMode ? null : 'EditableText',
4249-
child: MouseRegion(
4250-
cursor: widget.mouseCursor ?? SystemMouseCursors.text,
4251-
child: Actions(
4252-
actions: _actions,
4253-
child: _TextEditingHistory(
4254-
controller: widget.controller,
4255-
onTriggered: (TextEditingValue value) {
4256-
userUpdateTextEditingValue(value, SelectionChangedCause.keyboard);
4257-
},
4258-
child: Focus(
4259-
focusNode: widget.focusNode,
4260-
includeSemantics: false,
4261-
debugLabel: kReleaseMode ? null : 'EditableText',
4262-
child: Scrollable(
4263-
key: _scrollableKey,
4264-
excludeFromSemantics: true,
4265-
axisDirection: _isMultiline ? AxisDirection.down : AxisDirection.right,
4266-
controller: _scrollController,
4267-
physics: widget.scrollPhysics,
4268-
dragStartBehavior: widget.dragStartBehavior,
4269-
restorationId: widget.restorationId,
4270-
// If a ScrollBehavior is not provided, only apply scrollbars when
4271-
// multiline. The overscroll indicator should not be applied in
4272-
// either case, glowing or stretching.
4273-
scrollBehavior: widget.scrollBehavior ?? ScrollConfiguration.of(context).copyWith(
4274-
scrollbars: _isMultiline,
4275-
overscroll: false,
4276-
),
4277-
viewportBuilder: (BuildContext context, ViewportOffset offset) {
4278-
return CompositedTransformTarget(
4279-
link: _toolbarLayerLink,
4280-
child: Semantics(
4281-
onCopy: _semanticsOnCopy(controls),
4282-
onCut: _semanticsOnCut(controls),
4283-
onPaste: _semanticsOnPaste(controls),
4284-
child: _ScribbleFocusable(
4285-
focusNode: widget.focusNode,
4286-
editableKey: _editableKey,
4287-
enabled: widget.scribbleEnabled,
4288-
updateSelectionRects: () {
4289-
_openInputConnection();
4290-
_updateSelectionRects(force: true);
4291-
},
4292-
child: _Editable(
4293-
key: _editableKey,
4294-
startHandleLayerLink: _startHandleLayerLink,
4295-
endHandleLayerLink: _endHandleLayerLink,
4296-
inlineSpan: buildTextSpan(),
4297-
value: _value,
4298-
cursorColor: _cursorColor,
4299-
backgroundCursorColor: widget.backgroundCursorColor,
4300-
showCursor: EditableText.debugDeterministicCursor
4301-
? ValueNotifier<bool>(widget.showCursor)
4302-
: _cursorVisibilityNotifier,
4303-
forceLine: widget.forceLine,
4304-
readOnly: widget.readOnly,
4305-
hasFocus: _hasFocus,
4306-
maxLines: widget.maxLines,
4307-
minLines: widget.minLines,
4308-
expands: widget.expands,
4309-
strutStyle: widget.strutStyle,
4310-
selectionColor: widget.selectionColor,
4311-
textScaleFactor: widget.textScaleFactor ?? MediaQuery.textScaleFactorOf(context),
4312-
textAlign: widget.textAlign,
4313-
textDirection: _textDirection,
4314-
locale: widget.locale,
4315-
textHeightBehavior: widget.textHeightBehavior ?? DefaultTextHeightBehavior.maybeOf(context),
4316-
textWidthBasis: widget.textWidthBasis,
4317-
obscuringCharacter: widget.obscuringCharacter,
4318-
obscureText: widget.obscureText,
4319-
offset: offset,
4320-
onCaretChanged: _handleCaretChanged,
4321-
rendererIgnoresPointer: widget.rendererIgnoresPointer,
4322-
cursorWidth: widget.cursorWidth,
4323-
cursorHeight: widget.cursorHeight,
4324-
cursorRadius: widget.cursorRadius,
4325-
cursorOffset: widget.cursorOffset ?? Offset.zero,
4326-
selectionHeightStyle: widget.selectionHeightStyle,
4327-
selectionWidthStyle: widget.selectionWidthStyle,
4328-
paintCursorAboveText: widget.paintCursorAboveText,
4329-
enableInteractiveSelection: widget._userSelectionEnabled,
4330-
textSelectionDelegate: this,
4331-
devicePixelRatio: _devicePixelRatio,
4332-
promptRectRange: _currentPromptRectRange,
4333-
promptRectColor: widget.autocorrectionTextRectColor,
4334-
clipBehavior: widget.clipBehavior,
4298+
return _CompositionCallback(
4299+
compositeCallback: _compositeCallback,
4300+
enabled: _hasInputConnection,
4301+
child: TextFieldTapRegion(
4302+
onTapOutside: widget.onTapOutside ?? _defaultOnTapOutside,
4303+
debugLabel: kReleaseMode ? null : 'EditableText',
4304+
child: MouseRegion(
4305+
cursor: widget.mouseCursor ?? SystemMouseCursors.text,
4306+
child: Actions(
4307+
actions: _actions,
4308+
child: _TextEditingHistory(
4309+
controller: widget.controller,
4310+
onTriggered: (TextEditingValue value) {
4311+
userUpdateTextEditingValue(value, SelectionChangedCause.keyboard);
4312+
},
4313+
child: Focus(
4314+
focusNode: widget.focusNode,
4315+
includeSemantics: false,
4316+
debugLabel: kReleaseMode ? null : 'EditableText',
4317+
child: Scrollable(
4318+
key: _scrollableKey,
4319+
excludeFromSemantics: true,
4320+
axisDirection: _isMultiline ? AxisDirection.down : AxisDirection.right,
4321+
controller: _scrollController,
4322+
physics: widget.scrollPhysics,
4323+
dragStartBehavior: widget.dragStartBehavior,
4324+
restorationId: widget.restorationId,
4325+
// If a ScrollBehavior is not provided, only apply scrollbars when
4326+
// multiline. The overscroll indicator should not be applied in
4327+
// either case, glowing or stretching.
4328+
scrollBehavior: widget.scrollBehavior ?? ScrollConfiguration.of(context).copyWith(
4329+
scrollbars: _isMultiline,
4330+
overscroll: false,
4331+
),
4332+
viewportBuilder: (BuildContext context, ViewportOffset offset) {
4333+
return CompositedTransformTarget(
4334+
link: _toolbarLayerLink,
4335+
child: Semantics(
4336+
onCopy: _semanticsOnCopy(controls),
4337+
onCut: _semanticsOnCut(controls),
4338+
onPaste: _semanticsOnPaste(controls),
4339+
child: _ScribbleFocusable(
4340+
focusNode: widget.focusNode,
4341+
editableKey: _editableKey,
4342+
enabled: widget.scribbleEnabled,
4343+
updateSelectionRects: () {
4344+
_openInputConnection();
4345+
_updateSelectionRects(force: true);
4346+
},
4347+
child: _Editable(
4348+
key: _editableKey,
4349+
startHandleLayerLink: _startHandleLayerLink,
4350+
endHandleLayerLink: _endHandleLayerLink,
4351+
inlineSpan: buildTextSpan(),
4352+
value: _value,
4353+
cursorColor: _cursorColor,
4354+
backgroundCursorColor: widget.backgroundCursorColor,
4355+
showCursor: EditableText.debugDeterministicCursor
4356+
? ValueNotifier<bool>(widget.showCursor)
4357+
: _cursorVisibilityNotifier,
4358+
forceLine: widget.forceLine,
4359+
readOnly: widget.readOnly,
4360+
hasFocus: _hasFocus,
4361+
maxLines: widget.maxLines,
4362+
minLines: widget.minLines,
4363+
expands: widget.expands,
4364+
strutStyle: widget.strutStyle,
4365+
selectionColor: widget.selectionColor,
4366+
textScaleFactor: widget.textScaleFactor ?? MediaQuery.textScaleFactorOf(context),
4367+
textAlign: widget.textAlign,
4368+
textDirection: _textDirection,
4369+
locale: widget.locale,
4370+
textHeightBehavior: widget.textHeightBehavior ?? DefaultTextHeightBehavior.maybeOf(context),
4371+
textWidthBasis: widget.textWidthBasis,
4372+
obscuringCharacter: widget.obscuringCharacter,
4373+
obscureText: widget.obscureText,
4374+
offset: offset,
4375+
onCaretChanged: _handleCaretChanged,
4376+
rendererIgnoresPointer: widget.rendererIgnoresPointer,
4377+
cursorWidth: widget.cursorWidth,
4378+
cursorHeight: widget.cursorHeight,
4379+
cursorRadius: widget.cursorRadius,
4380+
cursorOffset: widget.cursorOffset ?? Offset.zero,
4381+
selectionHeightStyle: widget.selectionHeightStyle,
4382+
selectionWidthStyle: widget.selectionWidthStyle,
4383+
paintCursorAboveText: widget.paintCursorAboveText,
4384+
enableInteractiveSelection: widget._userSelectionEnabled,
4385+
textSelectionDelegate: this,
4386+
devicePixelRatio: _devicePixelRatio,
4387+
promptRectRange: _currentPromptRectRange,
4388+
promptRectColor: widget.autocorrectionTextRectColor,
4389+
clipBehavior: widget.clipBehavior,
4390+
),
43354391
),
43364392
),
4337-
),
4338-
);
4339-
},
4393+
);
4394+
},
4395+
),
43404396
),
43414397
),
43424398
),

0 commit comments

Comments
 (0)