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 ...@@ -674,8 +674,8 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien
onPaste: _hasFocus && controls?.canPaste(this) == true ? () => controls.handlePaste(this) : null, onPaste: _hasFocus && controls?.canPaste(this) == true ? () => controls.handlePaste(this) : null,
child: new _Editable( child: new _Editable(
key: _editableKey, key: _editableKey,
textSpan: buildTextSpan(),
value: _value, value: _value,
style: widget.style,
cursorColor: widget.cursorColor, cursorColor: widget.cursorColor,
showCursor: _showCursor, showCursor: _showCursor,
hasFocus: _hasFocus, hasFocus: _hasFocus,
...@@ -685,7 +685,6 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien ...@@ -685,7 +685,6 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien
textAlign: widget.textAlign, textAlign: widget.textAlign,
textDirection: _textDirection, textDirection: _textDirection,
obscureText: widget.obscureText, obscureText: widget.obscureText,
obscureShowCharacterAtIndex: _obscureShowCharTicksPending > 0 ? _obscureLatestCharIndex : null,
autocorrect: widget.autocorrect, autocorrect: widget.autocorrect,
offset: offset, offset: offset,
onSelectionChanged: _handleSelectionChanged, onSelectionChanged: _handleSelectionChanged,
...@@ -697,13 +696,46 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien ...@@ -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 { class _Editable extends LeafRenderObjectWidget {
const _Editable({ const _Editable({
Key key, Key key,
this.textSpan,
this.value, this.value,
this.style,
this.cursorColor, this.cursorColor,
this.showCursor, this.showCursor,
this.hasFocus, this.hasFocus,
...@@ -713,7 +745,6 @@ class _Editable extends LeafRenderObjectWidget { ...@@ -713,7 +745,6 @@ class _Editable extends LeafRenderObjectWidget {
this.textAlign, this.textAlign,
@required this.textDirection, @required this.textDirection,
this.obscureText, this.obscureText,
this.obscureShowCharacterAtIndex,
this.autocorrect, this.autocorrect,
this.offset, this.offset,
this.onSelectionChanged, this.onSelectionChanged,
...@@ -723,8 +754,8 @@ class _Editable extends LeafRenderObjectWidget { ...@@ -723,8 +754,8 @@ class _Editable extends LeafRenderObjectWidget {
assert(rendererIgnoresPointer != null), assert(rendererIgnoresPointer != null),
super(key: key); super(key: key);
final TextSpan textSpan;
final TextEditingValue value; final TextEditingValue value;
final TextStyle style;
final Color cursorColor; final Color cursorColor;
final ValueNotifier<bool> showCursor; final ValueNotifier<bool> showCursor;
final bool hasFocus; final bool hasFocus;
...@@ -734,7 +765,6 @@ class _Editable extends LeafRenderObjectWidget { ...@@ -734,7 +765,6 @@ class _Editable extends LeafRenderObjectWidget {
final TextAlign textAlign; final TextAlign textAlign;
final TextDirection textDirection; final TextDirection textDirection;
final bool obscureText; final bool obscureText;
final int obscureShowCharacterAtIndex;
final bool autocorrect; final bool autocorrect;
final ViewportOffset offset; final ViewportOffset offset;
final SelectionChangedHandler onSelectionChanged; final SelectionChangedHandler onSelectionChanged;
...@@ -744,7 +774,7 @@ class _Editable extends LeafRenderObjectWidget { ...@@ -744,7 +774,7 @@ class _Editable extends LeafRenderObjectWidget {
@override @override
RenderEditable createRenderObject(BuildContext context) { RenderEditable createRenderObject(BuildContext context) {
return new RenderEditable( return new RenderEditable(
text: _styledTextSpan, text: textSpan,
cursorColor: cursorColor, cursorColor: cursorColor,
showCursor: showCursor, showCursor: showCursor,
hasFocus: hasFocus, hasFocus: hasFocus,
...@@ -765,7 +795,7 @@ class _Editable extends LeafRenderObjectWidget { ...@@ -765,7 +795,7 @@ class _Editable extends LeafRenderObjectWidget {
@override @override
void updateRenderObject(BuildContext context, RenderEditable renderObject) { void updateRenderObject(BuildContext context, RenderEditable renderObject) {
renderObject renderObject
..text = _styledTextSpan ..text = textSpan
..cursorColor = cursorColor ..cursorColor = cursorColor
..showCursor = showCursor ..showCursor = showCursor
..hasFocus = hasFocus ..hasFocus = hasFocus
...@@ -781,32 +811,4 @@ class _Editable extends LeafRenderObjectWidget { ...@@ -781,32 +811,4 @@ class _Editable extends LeafRenderObjectWidget {
..ignorePointer = rendererIgnoresPointer ..ignorePointer = rendererIgnoresPointer
..obscureText = obscureText; ..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() { ...@@ -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 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