@@ -84,6 +84,51 @@ const Duration _kCursorBlinkHalfPeriod = Duration(milliseconds: 500);
8484// is shown in an obscured text field.
8585const 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