Unverified Commit 581df52a authored by sandrasandeep's avatar sandrasandeep Committed by GitHub

Make EditableText cursor configurable (#18888)

* fixed segmented control golden test

* fixed segmented control golden test

* added cursorWidth, cursorRadius

* added default value for cursorWidth based on Apple specs

* test default cursorWidth

* removed cursorHeight stuff

* added functionality to keep cursor from blinking

* cursor width and radius is configurable + tests

* changed goldens repo version in goldens.version

* working version of configurable cursor (erased debugKeepCursorOn)

* minor changes

* docs

* changed textfield test that was failing due to new default cursorwidth

* added default value of cursorwidth in RenderEditable

* only run golden file tests on Mac

* cursor tests

* the tests are actually there now

* weak warning fixed

* switching to Linux

* changed default cursorWidth: 2.0 -> 1.0

* assorted changes, including changing text field test

* re-paint -> re-layout when changing cursorWidth
parent 989f5741
...@@ -16,7 +16,6 @@ import 'viewport_offset.dart'; ...@@ -16,7 +16,6 @@ import 'viewport_offset.dart';
const double _kCaretGap = 1.0; // pixels const double _kCaretGap = 1.0; // pixels
const double _kCaretHeightOffset = 2.0; // pixels const double _kCaretHeightOffset = 2.0; // pixels
const double _kCaretWidth = 1.0; // pixels
/// Signature for the callback that reports when the user changes the selection /// Signature for the callback that reports when the user changes the selection
/// (including the cursor location). /// (including the cursor location).
...@@ -134,6 +133,8 @@ class RenderEditable extends RenderBox { ...@@ -134,6 +133,8 @@ class RenderEditable extends RenderBox {
this.ignorePointer = false, this.ignorePointer = false,
bool obscureText = false, bool obscureText = false,
Locale locale, Locale locale,
double cursorWidth = 1.0,
Radius cursorRadius,
}) : assert(textAlign != null), }) : assert(textAlign != null),
assert(textDirection != null, 'RenderEditable created without a textDirection.'), assert(textDirection != null, 'RenderEditable created without a textDirection.'),
assert(maxLines == null || maxLines > 0), assert(maxLines == null || maxLines > 0),
...@@ -155,6 +156,8 @@ class RenderEditable extends RenderBox { ...@@ -155,6 +156,8 @@ class RenderEditable extends RenderBox {
_selectionColor = selectionColor, _selectionColor = selectionColor,
_selection = selection, _selection = selection,
_offset = offset, _offset = offset,
_cursorWidth = cursorWidth,
_cursorRadius = cursorRadius,
_obscureText = obscureText { _obscureText = obscureText {
assert(_showCursor != null); assert(_showCursor != null);
assert(!_showCursor.value || cursorColor != null); assert(!_showCursor.value || cursorColor != null);
...@@ -382,6 +385,26 @@ class RenderEditable extends RenderBox { ...@@ -382,6 +385,26 @@ class RenderEditable extends RenderBox {
markNeedsLayout(); markNeedsLayout();
} }
/// How thick the cursor will be.
double get cursorWidth => _cursorWidth;
double _cursorWidth = 1.0;
set cursorWidth(double value) {
if (_cursorWidth == value)
return;
_cursorWidth = value;
markNeedsLayout();
}
/// How rounded the corners of the cursor should be.
Radius get cursorRadius => _cursorRadius;
Radius _cursorRadius;
set cursorRadius(Radius value) {
if (_cursorRadius == value)
return;
_cursorRadius = value;
markNeedsPaint();
}
@override @override
void describeSemanticsConfiguration(SemanticsConfiguration config) { void describeSemanticsConfiguration(SemanticsConfiguration config) {
super.describeSemanticsConfiguration(config); super.describeSemanticsConfiguration(config);
...@@ -546,7 +569,7 @@ class RenderEditable extends RenderBox { ...@@ -546,7 +569,7 @@ class RenderEditable extends RenderBox {
_layoutText(constraints.maxWidth); _layoutText(constraints.maxWidth);
final Offset caretOffset = _textPainter.getOffsetForCaret(caretPosition, _caretPrototype); final Offset caretOffset = _textPainter.getOffsetForCaret(caretPosition, _caretPrototype);
// This rect is the same as _caretPrototype but without the vertical padding. // This rect is the same as _caretPrototype but without the vertical padding.
return new Rect.fromLTWH(0.0, 0.0, _kCaretWidth, preferredLineHeight).shift(caretOffset + _paintOffset); return new Rect.fromLTWH(0.0, 0.0, cursorWidth, preferredLineHeight).shift(caretOffset + _paintOffset);
} }
@override @override
...@@ -683,7 +706,7 @@ class RenderEditable extends RenderBox { ...@@ -683,7 +706,7 @@ class RenderEditable extends RenderBox {
assert(constraintWidth != null); assert(constraintWidth != null);
if (_textLayoutLastWidth == constraintWidth) if (_textLayoutLastWidth == constraintWidth)
return; return;
const double caretMargin = _kCaretGap + _kCaretWidth; final double caretMargin = _kCaretGap + cursorWidth;
final double availableWidth = math.max(0.0, constraintWidth - caretMargin); final double availableWidth = math.max(0.0, constraintWidth - caretMargin);
final double maxWidth = _isMultiline ? availableWidth : double.infinity; final double maxWidth = _isMultiline ? availableWidth : double.infinity;
_textPainter.layout(minWidth: availableWidth, maxWidth: maxWidth); _textPainter.layout(minWidth: availableWidth, maxWidth: maxWidth);
...@@ -693,7 +716,7 @@ class RenderEditable extends RenderBox { ...@@ -693,7 +716,7 @@ class RenderEditable extends RenderBox {
@override @override
void performLayout() { void performLayout() {
_layoutText(constraints.maxWidth); _layoutText(constraints.maxWidth);
_caretPrototype = new Rect.fromLTWH(0.0, _kCaretHeightOffset, _kCaretWidth, preferredLineHeight - 2.0 * _kCaretHeightOffset); _caretPrototype = new Rect.fromLTWH(0.0, _kCaretHeightOffset, cursorWidth, preferredLineHeight - 2.0 * _kCaretHeightOffset);
_selectionRects = null; _selectionRects = null;
// We grab _textPainter.size here because assigning to `size` on the next // We grab _textPainter.size here because assigning to `size` on the next
// line will trigger us to validate our intrinsic sizes, which will change // line will trigger us to validate our intrinsic sizes, which will change
...@@ -705,7 +728,7 @@ class RenderEditable extends RenderBox { ...@@ -705,7 +728,7 @@ class RenderEditable extends RenderBox {
// See also RenderParagraph which has a similar issue. // See also RenderParagraph which has a similar issue.
final Size textPainterSize = _textPainter.size; final Size textPainterSize = _textPainter.size;
size = new Size(constraints.maxWidth, constraints.constrainHeight(_preferredHeight(constraints.maxWidth))); size = new Size(constraints.maxWidth, constraints.constrainHeight(_preferredHeight(constraints.maxWidth)));
final Size contentSize = new Size(textPainterSize.width + _kCaretGap + _kCaretWidth, textPainterSize.height); final Size contentSize = new Size(textPainterSize.width + _kCaretGap + cursorWidth, textPainterSize.height);
final double _maxScrollExtent = _getMaxScrollExtent(contentSize); final double _maxScrollExtent = _getMaxScrollExtent(contentSize);
_hasVisualOverflow = _maxScrollExtent > 0.0; _hasVisualOverflow = _maxScrollExtent > 0.0;
offset.applyViewportDimension(_viewportExtent); offset.applyViewportDimension(_viewportExtent);
...@@ -715,9 +738,18 @@ class RenderEditable extends RenderBox { ...@@ -715,9 +738,18 @@ class RenderEditable extends RenderBox {
void _paintCaret(Canvas canvas, Offset effectiveOffset) { void _paintCaret(Canvas canvas, Offset effectiveOffset) {
assert(_textLayoutLastWidth == constraints.maxWidth); assert(_textLayoutLastWidth == constraints.maxWidth);
final Offset caretOffset = _textPainter.getOffsetForCaret(_selection.extent, _caretPrototype); final Offset caretOffset = _textPainter.getOffsetForCaret(_selection.extent, _caretPrototype);
final Paint paint = new Paint()..color = _cursorColor; final Paint paint = new Paint()
..color = _cursorColor;
final Rect caretRect = _caretPrototype.shift(caretOffset + effectiveOffset); final Rect caretRect = _caretPrototype.shift(caretOffset + effectiveOffset);
if (cursorRadius == null) {
canvas.drawRect(caretRect, paint); canvas.drawRect(caretRect, paint);
} else {
final RRect caretRRect = RRect.fromRectAndRadius(caretRect, cursorRadius);
canvas.drawRRect(caretRRect, paint);
}
if (caretRect != _lastCaretRect) { if (caretRect != _lastCaretRect) {
_lastCaretRect = caretRect; _lastCaretRect = caretRect;
if (onCaretChanged != null) if (onCaretChanged != null)
......
...@@ -208,6 +208,8 @@ class EditableText extends StatefulWidget { ...@@ -208,6 +208,8 @@ class EditableText extends StatefulWidget {
this.onSelectionChanged, this.onSelectionChanged,
List<TextInputFormatter> inputFormatters, List<TextInputFormatter> inputFormatters,
this.rendererIgnoresPointer = false, this.rendererIgnoresPointer = false,
this.cursorWidth = 1.0,
this.cursorRadius,
}) : assert(controller != null), }) : assert(controller != null),
assert(focusNode != null), assert(focusNode != null),
assert(obscureText != null), assert(obscureText != null),
...@@ -353,6 +355,16 @@ class EditableText extends StatefulWidget { ...@@ -353,6 +355,16 @@ class EditableText extends StatefulWidget {
/// This property is false by default. /// This property is false by default.
final bool rendererIgnoresPointer; final bool rendererIgnoresPointer;
/// How thick the cursor will be.
///
/// Defaults to 1.0
final double cursorWidth;
/// How rounded the corners of the cursor should be.
///
/// By default, the cursor has a Radius of zero.
final Radius cursorRadius;
@override @override
EditableTextState createState() => new EditableTextState(); EditableTextState createState() => new EditableTextState();
...@@ -810,6 +822,7 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien ...@@ -810,6 +822,7 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien
FocusScope.of(context).reparentIfNeeded(widget.focusNode); FocusScope.of(context).reparentIfNeeded(widget.focusNode);
super.build(context); // See AutomaticKeepAliveClientMixin. super.build(context); // See AutomaticKeepAliveClientMixin.
final TextSelectionControls controls = widget.selectionControls; final TextSelectionControls controls = widget.selectionControls;
return new Scrollable( return new Scrollable(
excludeFromSemantics: true, excludeFromSemantics: true,
axisDirection: _isMultiline ? AxisDirection.down : AxisDirection.right, axisDirection: _isMultiline ? AxisDirection.down : AxisDirection.right,
...@@ -841,6 +854,8 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien ...@@ -841,6 +854,8 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien
onSelectionChanged: _handleSelectionChanged, onSelectionChanged: _handleSelectionChanged,
onCaretChanged: _handleCaretChanged, onCaretChanged: _handleCaretChanged,
rendererIgnoresPointer: widget.rendererIgnoresPointer, rendererIgnoresPointer: widget.rendererIgnoresPointer,
cursorWidth: widget.cursorWidth,
cursorRadius: widget.cursorRadius,
), ),
), ),
); );
...@@ -902,6 +917,8 @@ class _Editable extends LeafRenderObjectWidget { ...@@ -902,6 +917,8 @@ class _Editable extends LeafRenderObjectWidget {
this.onSelectionChanged, this.onSelectionChanged,
this.onCaretChanged, this.onCaretChanged,
this.rendererIgnoresPointer = false, this.rendererIgnoresPointer = false,
this.cursorWidth,
this.cursorRadius,
}) : assert(textDirection != null), }) : assert(textDirection != null),
assert(rendererIgnoresPointer != null), assert(rendererIgnoresPointer != null),
super(key: key); super(key: key);
...@@ -923,6 +940,8 @@ class _Editable extends LeafRenderObjectWidget { ...@@ -923,6 +940,8 @@ class _Editable extends LeafRenderObjectWidget {
final SelectionChangedHandler onSelectionChanged; final SelectionChangedHandler onSelectionChanged;
final CaretChangedHandler onCaretChanged; final CaretChangedHandler onCaretChanged;
final bool rendererIgnoresPointer; final bool rendererIgnoresPointer;
final double cursorWidth;
final Radius cursorRadius;
@override @override
RenderEditable createRenderObject(BuildContext context) { RenderEditable createRenderObject(BuildContext context) {
...@@ -943,6 +962,8 @@ class _Editable extends LeafRenderObjectWidget { ...@@ -943,6 +962,8 @@ class _Editable extends LeafRenderObjectWidget {
onCaretChanged: onCaretChanged, onCaretChanged: onCaretChanged,
ignorePointer: rendererIgnoresPointer, ignorePointer: rendererIgnoresPointer,
obscureText: obscureText, obscureText: obscureText,
cursorWidth: cursorWidth,
cursorRadius: cursorRadius,
); );
} }
...@@ -964,6 +985,8 @@ class _Editable extends LeafRenderObjectWidget { ...@@ -964,6 +985,8 @@ class _Editable extends LeafRenderObjectWidget {
..onSelectionChanged = onSelectionChanged ..onSelectionChanged = onSelectionChanged
..onCaretChanged = onCaretChanged ..onCaretChanged = onCaretChanged
..ignorePointer = rendererIgnoresPointer ..ignorePointer = rendererIgnoresPointer
..obscureText = obscureText; ..obscureText = obscureText
..cursorWidth = cursorWidth
..cursorRadius = cursorRadius;
} }
} }
...@@ -967,6 +967,7 @@ void main() { ...@@ -967,6 +967,7 @@ void main() {
final Offset center = tester.getCenter(find.text('B')); final Offset center = tester.getCenter(find.text('B'));
await tester.startGesture(center); await tester.startGesture(center);
await tester.pumpAndSettle(); await tester.pumpAndSettle();
await expectLater( await expectLater(
find.byType(RepaintBoundary), find.byType(RepaintBoundary),
matchesGoldenFile('segmented_control_test.1.0.png'), matchesGoldenFile('segmented_control_test.1.0.png'),
......
...@@ -1208,7 +1208,7 @@ void main() { ...@@ -1208,7 +1208,7 @@ void main() {
editable.getLocalRectForCaret(const TextPosition(offset: 0)).topLeft, editable.getLocalRectForCaret(const TextPosition(offset: 0)).topLeft,
); );
expect(topLeft.dx, equals(399.0)); expect(topLeft.dx, equals(399));
await tester.enterText(find.byType(TextField), 'abcd'); await tester.enterText(find.byType(TextField), 'abcd');
await tester.pump(); await tester.pump();
...@@ -1217,7 +1217,7 @@ void main() { ...@@ -1217,7 +1217,7 @@ void main() {
editable.getLocalRectForCaret(const TextPosition(offset: 2)).topLeft, editable.getLocalRectForCaret(const TextPosition(offset: 2)).topLeft,
); );
expect(topLeft.dx, equals(399.0)); expect(topLeft.dx, equals(399));
}); });
testWidgets('Can align to center within center', (WidgetTester tester) async { testWidgets('Can align to center within center', (WidgetTester tester) async {
...@@ -1240,7 +1240,7 @@ void main() { ...@@ -1240,7 +1240,7 @@ void main() {
editable.getLocalRectForCaret(const TextPosition(offset: 0)).topLeft, editable.getLocalRectForCaret(const TextPosition(offset: 0)).topLeft,
); );
expect(topLeft.dx, equals(399.0)); expect(topLeft.dx, equals(399));
await tester.enterText(find.byType(TextField), 'abcd'); await tester.enterText(find.byType(TextField), 'abcd');
await tester.pump(); await tester.pump();
...@@ -1249,7 +1249,7 @@ void main() { ...@@ -1249,7 +1249,7 @@ void main() {
editable.getLocalRectForCaret(const TextPosition(offset: 2)).topLeft, editable.getLocalRectForCaret(const TextPosition(offset: 2)).topLeft,
); );
expect(topLeft.dx, equals(399.0)); expect(topLeft.dx, equals(399));
}); });
testWidgets('Controller can update server', (WidgetTester tester) async { testWidgets('Controller can update server', (WidgetTester tester) async {
......
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