@@ -84,6 +84,51 @@ const Duration _kCursorBlinkHalfPeriod = Duration(milliseconds: 500);
84
84
// is shown in an obscured text field.
85
85
const int _kObscureShowLatestCharCursorTicks = 3 ;
86
86
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
+
87
132
/// A controller for an editable text field.
88
133
///
89
134
/// Whenever the user modifies a text field with an associated
@@ -2970,8 +3015,7 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien
2970
3015
? currentAutofillScope! .attach (this , _effectiveAutofillClient.textInputConfiguration)
2971
3016
: TextInput .attach (this , _effectiveAutofillClient.textInputConfiguration);
2972
3017
_updateSizeAndTransform ();
2973
- _updateComposingRectIfNeeded ();
2974
- _updateCaretRectIfNeeded ();
3018
+ _schedulePeriodicPostFrameCallbacks ();
2975
3019
final TextStyle style = widget.style;
2976
3020
_textInputConnection!
2977
3021
..setStyle (
@@ -2999,6 +3043,7 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien
2999
3043
_textInputConnection! .close ();
3000
3044
_textInputConnection = null ;
3001
3045
_lastKnownRemoteTextEditingValue = null ;
3046
+ removeTextPlaceholder ();
3002
3047
}
3003
3048
}
3004
3049
@@ -3523,6 +3568,33 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien
3523
3568
updateKeepAlive ();
3524
3569
}
3525
3570
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
+ }
3526
3598
_ScribbleCacheKey ? _scribbleCacheKey;
3527
3599
3528
3600
void _updateSelectionRects ({bool force = false }) {
@@ -3585,61 +3657,41 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien
3585
3657
_textInputConnection! .setSelectionRects (rects);
3586
3658
}
3587
3659
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
-
3600
3660
// Sends the current composing rect to the iOS text input plugin via the text
3601
3661
// input channel. We need to keep sending the information even if no text is
3602
3662
// currently marked, as the information usually lags behind. The text input
3603
3663
// plugin needs to estimate the composing rect based on the latest caret rect,
3604
3664
// when the composing rect info didn't arrive in time.
3605
3665
void _updateComposingRectIfNeeded () {
3606
3666
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));
3618
3674
}
3675
+ _textInputConnection! .setComposingRect (composingRect);
3619
3676
}
3620
3677
3621
3678
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 ;
3630
3682
}
3683
+ final TextPosition currentTextPosition = TextPosition (offset: selection.baseOffset);
3684
+ final Rect caretRect = renderEditable.getLocalRectForCaret (currentTextPosition);
3685
+ _textInputConnection! .setCaretRect (caretRect);
3631
3686
}
3632
3687
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);
3637
3689
3638
3690
/// The renderer for this widget's descendant.
3639
3691
///
3640
3692
/// This property is typically used to notify the renderer of input gestures
3641
3693
/// when [RenderEditable.ignorePointer] is true.
3642
- RenderEditable get renderEditable => _editableKey.currentContext! .findRenderObject ()! as RenderEditable ;
3694
+ late final RenderEditable renderEditable = _editableKey.currentContext! .findRenderObject ()! as RenderEditable ;
3643
3695
3644
3696
@override
3645
3697
TextEditingValue get textEditingValue => _value;
@@ -3812,7 +3864,7 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien
3812
3864
3813
3865
@override
3814
3866
void removeTextPlaceholder () {
3815
- if (! widget.scribbleEnabled) {
3867
+ if (! widget.scribbleEnabled || _placeholderLocation == - 1 ) {
3816
3868
return ;
3817
3869
}
3818
3870
@@ -4243,100 +4295,104 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien
4243
4295
super .build (context); // See AutomaticKeepAliveClientMixin.
4244
4296
4245
4297
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
+ ),
4335
4391
),
4336
4392
),
4337
- ),
4338
- );
4339
- } ,
4393
+ );
4394
+ },
4395
+ ) ,
4340
4396
),
4341
4397
),
4342
4398
),
0 commit comments