Unverified Commit e70a1d1d authored by Gary Qian's avatar Gary Qian Committed by GitHub

Support WidgetSpan in RenderEditable (#83537)

parent 18b15788
...@@ -18,7 +18,7 @@ import 'object.dart'; ...@@ -18,7 +18,7 @@ import 'object.dart';
const String _kEllipsis = '\u2026'; const String _kEllipsis = '\u2026';
/// Parent data for use with [RenderParagraph]. /// Parent data for use with [RenderParagraph] and [RenderEditable].
class TextParentData extends ContainerBoxParentData<RenderBox> { class TextParentData extends ContainerBoxParentData<RenderBox> {
/// The scaling of the text. /// The scaling of the text.
double? scale; double? scale;
...@@ -434,14 +434,12 @@ class RenderParagraph extends RenderBox ...@@ -434,14 +434,12 @@ class RenderParagraph extends RenderBox
@override @override
bool hitTestChildren(BoxHitTestResult result, { required Offset position }) { bool hitTestChildren(BoxHitTestResult result, { required Offset position }) {
// Hit test text spans. // Hit test text spans.
late final bool hitText; bool hitText = false;
final TextPosition textPosition = _textPainter.getPositionForOffset(position); final TextPosition textPosition = _textPainter.getPositionForOffset(position);
final InlineSpan? span = _textPainter.text!.getSpanForPosition(textPosition); final InlineSpan? span = _textPainter.text!.getSpanForPosition(textPosition);
if (span != null && span is HitTestTarget) { if (span != null && span is HitTestTarget) {
result.add(HitTestEntry(span as HitTestTarget)); result.add(HitTestEntry(span as HitTestTarget));
hitText = true; hitText = true;
} else {
hitText = false;
} }
// Hit test render object children // Hit test render object children
...@@ -545,17 +543,15 @@ class RenderParagraph extends RenderBox ...@@ -545,17 +543,15 @@ class RenderParagraph extends RenderBox
); );
childSize = child.size; childSize = child.size;
switch (_placeholderSpans[childIndex].alignment) { switch (_placeholderSpans[childIndex].alignment) {
case ui.PlaceholderAlignment.baseline: { case ui.PlaceholderAlignment.baseline:
baselineOffset = child.getDistanceToBaseline( baselineOffset = child.getDistanceToBaseline(
_placeholderSpans[childIndex].baseline!, _placeholderSpans[childIndex].baseline!,
); );
break; break;
} default:
default: {
baselineOffset = null; baselineOffset = null;
break; break;
} }
}
} else { } else {
assert(_placeholderSpans[childIndex].alignment != ui.PlaceholderAlignment.baseline); assert(_placeholderSpans[childIndex].alignment != ui.PlaceholderAlignment.baseline);
childSize = child.getDryLayout(boxConstraints); childSize = child.getDryLayout(boxConstraints);
...@@ -597,16 +593,14 @@ class RenderParagraph extends RenderBox ...@@ -597,16 +593,14 @@ class RenderParagraph extends RenderBox
switch (span.alignment) { switch (span.alignment) {
case ui.PlaceholderAlignment.baseline: case ui.PlaceholderAlignment.baseline:
case ui.PlaceholderAlignment.aboveBaseline: case ui.PlaceholderAlignment.aboveBaseline:
case ui.PlaceholderAlignment.belowBaseline: { case ui.PlaceholderAlignment.belowBaseline:
return false; return false;
}
case ui.PlaceholderAlignment.top: case ui.PlaceholderAlignment.top:
case ui.PlaceholderAlignment.middle: case ui.PlaceholderAlignment.middle:
case ui.PlaceholderAlignment.bottom: { case ui.PlaceholderAlignment.bottom:
continue; continue;
} }
} }
}
return true; return true;
} }
......
...@@ -31,6 +31,7 @@ import 'text.dart'; ...@@ -31,6 +31,7 @@ import 'text.dart';
import 'text_editing_action.dart'; import 'text_editing_action.dart';
import 'text_selection.dart'; import 'text_selection.dart';
import 'ticker_provider.dart'; import 'ticker_provider.dart';
import 'widget_span.dart';
export 'package:flutter/services.dart' show SelectionChangedCause, TextEditingValue, TextSelection, TextInputType, SmartQuotesType, SmartDashesType; export 'package:flutter/services.dart' show SelectionChangedCause, TextEditingValue, TextSelection, TextInputType, SmartQuotesType, SmartDashesType;
...@@ -197,9 +198,8 @@ class TextEditingController extends ValueNotifier<TextEditingValue> { ...@@ -197,9 +198,8 @@ class TextEditingController extends ValueNotifier<TextEditingValue> {
if (!value.isComposingRangeValid || !withComposing) { if (!value.isComposingRangeValid || !withComposing) {
return TextSpan(style: style, text: text); return TextSpan(style: style, text: text);
} }
final TextStyle composingStyle = style!.merge( final TextStyle composingStyle = style?.merge(const TextStyle(decoration: TextDecoration.underline))
const TextStyle(decoration: TextDecoration.underline), ?? const TextStyle(decoration: TextDecoration.underline);
);
return TextSpan( return TextSpan(
style: style, style: style,
children: <TextSpan>[ children: <TextSpan>[
...@@ -2651,7 +2651,7 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien ...@@ -2651,7 +2651,7 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien
key: _editableKey, key: _editableKey,
startHandleLayerLink: _startHandleLayerLink, startHandleLayerLink: _startHandleLayerLink,
endHandleLayerLink: _endHandleLayerLink, endHandleLayerLink: _endHandleLayerLink,
textSpan: buildTextSpan(), inlineSpan: buildTextSpan(),
value: _value, value: _value,
cursorColor: _cursorColor, cursorColor: _cursorColor,
backgroundCursorColor: widget.backgroundCursorColor, backgroundCursorColor: widget.backgroundCursorColor,
...@@ -2730,10 +2730,10 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien ...@@ -2730,10 +2730,10 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien
} }
} }
class _Editable extends LeafRenderObjectWidget { class _Editable extends MultiChildRenderObjectWidget {
const _Editable({ _Editable({
Key? key, Key? key,
required this.textSpan, required this.inlineSpan,
required this.value, required this.value,
required this.startHandleLayerLink, required this.startHandleLayerLink,
required this.endHandleLayerLink, required this.endHandleLayerLink,
...@@ -2778,9 +2778,22 @@ class _Editable extends LeafRenderObjectWidget { ...@@ -2778,9 +2778,22 @@ class _Editable extends LeafRenderObjectWidget {
required this.clipBehavior, required this.clipBehavior,
}) : assert(textDirection != null), }) : assert(textDirection != null),
assert(rendererIgnoresPointer != null), assert(rendererIgnoresPointer != null),
super(key: key); super(key: key, children: _extractChildren(inlineSpan));
// Traverses the InlineSpan tree and depth-first collects the list of
// child widgets that are created in WidgetSpans.
static List<Widget> _extractChildren(InlineSpan span) {
final List<Widget> result = <Widget>[];
span.visitChildren((InlineSpan span) {
if (span is WidgetSpan) {
result.add(span.child);
}
return true;
});
return result;
}
final TextSpan textSpan; final InlineSpan inlineSpan;
final TextEditingValue value; final TextEditingValue value;
final Color? cursorColor; final Color? cursorColor;
final LayerLink startHandleLayerLink; final LayerLink startHandleLayerLink;
...@@ -2827,7 +2840,7 @@ class _Editable extends LeafRenderObjectWidget { ...@@ -2827,7 +2840,7 @@ class _Editable extends LeafRenderObjectWidget {
@override @override
RenderEditable createRenderObject(BuildContext context) { RenderEditable createRenderObject(BuildContext context) {
return RenderEditable( return RenderEditable(
text: textSpan, text: inlineSpan,
cursorColor: cursorColor, cursorColor: cursorColor,
startHandleLayerLink: startHandleLayerLink, startHandleLayerLink: startHandleLayerLink,
endHandleLayerLink: endHandleLayerLink, endHandleLayerLink: endHandleLayerLink,
...@@ -2872,7 +2885,7 @@ class _Editable extends LeafRenderObjectWidget { ...@@ -2872,7 +2885,7 @@ class _Editable extends LeafRenderObjectWidget {
@override @override
void updateRenderObject(BuildContext context, RenderEditable renderObject) { void updateRenderObject(BuildContext context, RenderEditable renderObject) {
renderObject renderObject
..text = textSpan ..text = inlineSpan
..cursorColor = cursorColor ..cursorColor = cursorColor
..startHandleLayerLink = startHandleLayerLink ..startHandleLayerLink = startHandleLayerLink
..endHandleLayerLink = endHandleLayerLink ..endHandleLayerLink = endHandleLayerLink
......
...@@ -1056,12 +1056,12 @@ void main() { ...@@ -1056,12 +1056,12 @@ void main() {
await tester.pump(); await tester.pump();
String editText = findRenderEditable(tester).text!.text!; String editText = (findRenderEditable(tester).text! as TextSpan).text!;
expect(editText.substring(editText.length - 1), newChar); expect(editText.substring(editText.length - 1), newChar);
await tester.pump(const Duration(seconds: 2)); await tester.pump(const Duration(seconds: 2));
editText = findRenderEditable(tester).text!.text!; editText = (findRenderEditable(tester).text! as TextSpan).text!;
expect(editText.substring(editText.length - 1), '\u2022'); expect(editText.substring(editText.length - 1), '\u2022');
}, variant: const TargetPlatformVariant(<TargetPlatform>{ TargetPlatform.iOS, TargetPlatform.android })); }, variant: const TargetPlatformVariant(<TargetPlatform>{ TargetPlatform.iOS, TargetPlatform.android }));
...@@ -1096,7 +1096,7 @@ void main() { ...@@ -1096,7 +1096,7 @@ void main() {
await tester.pump(); await tester.pump();
final String editText = findRenderEditable(tester).text!.text!; final String editText = (findRenderEditable(tester).text! as TextSpan).text!;
expect(editText.substring(editText.length - 1), '\u2022'); expect(editText.substring(editText.length - 1), '\u2022');
}, variant: const TargetPlatformVariant(<TargetPlatform>{ }, variant: const TargetPlatformVariant(<TargetPlatform>{
TargetPlatform.macOS, TargetPlatform.macOS,
......
...@@ -2841,7 +2841,7 @@ void main() { ...@@ -2841,7 +2841,7 @@ void main() {
), ),
)); ));
expect(findRenderEditable(tester).text!.text, expectedValue); expect((findRenderEditable(tester).text! as TextSpan).text, expectedValue);
expect( expect(
semantics, semantics,
...@@ -2906,7 +2906,7 @@ void main() { ...@@ -2906,7 +2906,7 @@ void main() {
)); ));
final String expectedValue = obscuringCharacter * originalText.length; final String expectedValue = obscuringCharacter * originalText.length;
expect(findRenderEditable(tester).text!.text, expectedValue); expect((findRenderEditable(tester).text! as TextSpan).text, expectedValue);
}); });
group('a11y copy/cut/paste', () { group('a11y copy/cut/paste', () {
...@@ -3808,17 +3808,17 @@ void main() { ...@@ -3808,17 +3808,17 @@ void main() {
final RenderEditable renderEditable = findRenderEditable(tester); final RenderEditable renderEditable = findRenderEditable(tester);
// The actual text span is split into 3 parts with the middle part underlined. // The actual text span is split into 3 parts with the middle part underlined.
expect(renderEditable.text!.children!.length, 3); expect((renderEditable.text! as TextSpan).children!.length, 3);
final TextSpan textSpan = renderEditable.text!.children![1] as TextSpan; final TextSpan textSpan = (renderEditable.text! as TextSpan).children![1] as TextSpan;
expect(textSpan.text, 'composing'); expect(textSpan.text, 'composing');
expect(textSpan.style!.decoration, TextDecoration.underline); expect(textSpan.style!.decoration, TextDecoration.underline);
focusNode.unfocus(); focusNode.unfocus();
await tester.pump(); await tester.pump();
expect(renderEditable.text!.children, isNull); expect((renderEditable.text! as TextSpan).children, isNull);
// Everything's just formatted the same way now. // Everything's just formated the same way now.
expect(renderEditable.text!.text, 'text composing text'); expect((renderEditable.text! as TextSpan).text, 'text composing text');
expect(renderEditable.text!.style!.decoration, isNull); expect(renderEditable.text!.style!.decoration, isNull);
}); });
...@@ -6137,7 +6137,7 @@ void main() { ...@@ -6137,7 +6137,7 @@ void main() {
)); ));
final RenderEditable renderEditable = findRenderEditable(tester); final RenderEditable renderEditable = findRenderEditable(tester);
final TextSpan textSpan = renderEditable.text!; final TextSpan textSpan = renderEditable.text! as TextSpan;
expect(textSpan.style!.color, color); expect(textSpan.style!.color, color);
}); });
}); });
......
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