Commit 50a1d91f authored by Anatoly Pulyaevskiy's avatar Anatoly Pulyaevskiy Committed by Ian Hickson

Allow customization of TextSpan in EditableText (#16909)

* Allow customization of TextSpan in EditableText

* Addressed PR comments

* Added test with custom-styled EditableText subclass

* More code style fixes
parent 8b8d368d
......@@ -674,8 +674,8 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien
onPaste: _hasFocus && controls?.canPaste(this) == true ? () => controls.handlePaste(this) : null,
child: new _Editable(
key: _editableKey,
textSpan: buildTextSpan(),
value: _value,
style: widget.style,
cursorColor: widget.cursorColor,
showCursor: _showCursor,
hasFocus: _hasFocus,
......@@ -685,7 +685,6 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien
textAlign: widget.textAlign,
textDirection: _textDirection,
obscureText: widget.obscureText,
obscureShowCharacterAtIndex: _obscureShowCharTicksPending > 0 ? _obscureLatestCharIndex : null,
autocorrect: widget.autocorrect,
offset: offset,
onSelectionChanged: _handleSelectionChanged,
......@@ -697,13 +696,46 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien
},
);
}
/// Builds [TextSpan] from current editing value.
///
/// By default makes text in composing range appear as underlined.
/// Descendants can override this method to customize appearance of text.
TextSpan buildTextSpan() {
if (!widget.obscureText && _value.composing.isValid) {
final TextStyle composingStyle = widget.style.merge(
const TextStyle(decoration: TextDecoration.underline),
);
return new TextSpan(
style: widget.style,
children: <TextSpan>[
new TextSpan(text: _value.composing.textBefore(_value.text)),
new TextSpan(
style: composingStyle,
text: _value.composing.textInside(_value.text),
),
new TextSpan(text: _value.composing.textAfter(_value.text)),
]);
}
String text = _value.text;
if (widget.obscureText) {
text = RenderEditable.obscuringCharacter * text.length;
final int o =
_obscureShowCharTicksPending > 0 ? _obscureLatestCharIndex : null;
if (o != null && o >= 0 && o < text.length)
text = text.replaceRange(o, o + 1, _value.text.substring(o, o + 1));
}
return new TextSpan(style: widget.style, text: text);
}
}
class _Editable extends LeafRenderObjectWidget {
const _Editable({
Key key,
this.textSpan,
this.value,
this.style,
this.cursorColor,
this.showCursor,
this.hasFocus,
......@@ -713,7 +745,6 @@ class _Editable extends LeafRenderObjectWidget {
this.textAlign,
@required this.textDirection,
this.obscureText,
this.obscureShowCharacterAtIndex,
this.autocorrect,
this.offset,
this.onSelectionChanged,
......@@ -723,8 +754,8 @@ class _Editable extends LeafRenderObjectWidget {
assert(rendererIgnoresPointer != null),
super(key: key);
final TextSpan textSpan;
final TextEditingValue value;
final TextStyle style;
final Color cursorColor;
final ValueNotifier<bool> showCursor;
final bool hasFocus;
......@@ -734,7 +765,6 @@ class _Editable extends LeafRenderObjectWidget {
final TextAlign textAlign;
final TextDirection textDirection;
final bool obscureText;
final int obscureShowCharacterAtIndex;
final bool autocorrect;
final ViewportOffset offset;
final SelectionChangedHandler onSelectionChanged;
......@@ -744,7 +774,7 @@ class _Editable extends LeafRenderObjectWidget {
@override
RenderEditable createRenderObject(BuildContext context) {
return new RenderEditable(
text: _styledTextSpan,
text: textSpan,
cursorColor: cursorColor,
showCursor: showCursor,
hasFocus: hasFocus,
......@@ -765,7 +795,7 @@ class _Editable extends LeafRenderObjectWidget {
@override
void updateRenderObject(BuildContext context, RenderEditable renderObject) {
renderObject
..text = _styledTextSpan
..text = textSpan
..cursorColor = cursorColor
..showCursor = showCursor
..hasFocus = hasFocus
......@@ -781,32 +811,4 @@ class _Editable extends LeafRenderObjectWidget {
..ignorePointer = rendererIgnoresPointer
..obscureText = obscureText;
}
TextSpan get _styledTextSpan {
if (!obscureText && value.composing.isValid) {
final TextStyle composingStyle = style.merge(
const TextStyle(decoration: TextDecoration.underline)
);
return new TextSpan(
style: style,
children: <TextSpan>[
new TextSpan(text: value.composing.textBefore(value.text)),
new TextSpan(
style: composingStyle,
text: value.composing.textInside(value.text)
),
new TextSpan(text: value.composing.textAfter(value.text))
]);
}
String text = value.text;
if (obscureText) {
text = RenderEditable.obscuringCharacter * text.length;
final int o = obscureShowCharacterAtIndex;
if (o != null && o >= 0 && o < text.length)
text = text.replaceRange(o, o + 1, value.text.substring(o, o + 1));
}
return new TextSpan(style: style, text: text);
}
}
......@@ -766,6 +766,50 @@ void main() {
});
});
testWidgets('allows customizing text style in subclasses', (WidgetTester tester) async {
controller.text = 'Hello World';
await tester.pumpWidget(new MaterialApp(
home: new CustomStyleEditableText(
controller: controller,
focusNode: focusNode,
style: textStyle,
cursorColor: cursorColor,
),
));
// Simulate selection change via tap to show handles.
final RenderEditable render = tester.allRenderObjects.firstWhere((RenderObject o) => o.runtimeType == RenderEditable);
expect(render.text.style.fontStyle, FontStyle.italic);
});
}
class MockTextSelectionControls extends Mock implements TextSelectionControls {}
class CustomStyleEditableText extends EditableText {
CustomStyleEditableText({
TextEditingController controller,
Color cursorColor,
FocusNode focusNode,
TextStyle style,
}): super(
controller:controller,
cursorColor: cursorColor,
focusNode: focusNode,
style: style,
);
@override
CustomStyleEditableTextState createState() =>
new CustomStyleEditableTextState();
}
class CustomStyleEditableTextState extends EditableTextState {
@override
TextSpan buildTextSpan() {
return new TextSpan(
style: const TextStyle(fontStyle: FontStyle.italic),
text: widget.controller.value.text,
);
}
}
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