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
This commit is contained in:
@@ -16,7 +16,6 @@ import 'viewport_offset.dart';
|
||||
|
||||
const double _kCaretGap = 1.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
|
||||
/// (including the cursor location).
|
||||
@@ -134,6 +133,8 @@ class RenderEditable extends RenderBox {
|
||||
this.ignorePointer = false,
|
||||
bool obscureText = false,
|
||||
Locale locale,
|
||||
double cursorWidth = 1.0,
|
||||
Radius cursorRadius,
|
||||
}) : assert(textAlign != null),
|
||||
assert(textDirection != null, 'RenderEditable created without a textDirection.'),
|
||||
assert(maxLines == null || maxLines > 0),
|
||||
@@ -155,6 +156,8 @@ class RenderEditable extends RenderBox {
|
||||
_selectionColor = selectionColor,
|
||||
_selection = selection,
|
||||
_offset = offset,
|
||||
_cursorWidth = cursorWidth,
|
||||
_cursorRadius = cursorRadius,
|
||||
_obscureText = obscureText {
|
||||
assert(_showCursor != null);
|
||||
assert(!_showCursor.value || cursorColor != null);
|
||||
@@ -382,6 +385,26 @@ class RenderEditable extends RenderBox {
|
||||
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
|
||||
void describeSemanticsConfiguration(SemanticsConfiguration config) {
|
||||
super.describeSemanticsConfiguration(config);
|
||||
@@ -546,7 +569,7 @@ class RenderEditable extends RenderBox {
|
||||
_layoutText(constraints.maxWidth);
|
||||
final Offset caretOffset = _textPainter.getOffsetForCaret(caretPosition, _caretPrototype);
|
||||
// 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
|
||||
@@ -683,7 +706,7 @@ class RenderEditable extends RenderBox {
|
||||
assert(constraintWidth != null);
|
||||
if (_textLayoutLastWidth == constraintWidth)
|
||||
return;
|
||||
const double caretMargin = _kCaretGap + _kCaretWidth;
|
||||
final double caretMargin = _kCaretGap + cursorWidth;
|
||||
final double availableWidth = math.max(0.0, constraintWidth - caretMargin);
|
||||
final double maxWidth = _isMultiline ? availableWidth : double.infinity;
|
||||
_textPainter.layout(minWidth: availableWidth, maxWidth: maxWidth);
|
||||
@@ -693,7 +716,7 @@ class RenderEditable extends RenderBox {
|
||||
@override
|
||||
void performLayout() {
|
||||
_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;
|
||||
// We grab _textPainter.size here because assigning to `size` on the next
|
||||
// line will trigger us to validate our intrinsic sizes, which will change
|
||||
@@ -705,7 +728,7 @@ class RenderEditable extends RenderBox {
|
||||
// See also RenderParagraph which has a similar issue.
|
||||
final Size textPainterSize = _textPainter.size;
|
||||
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);
|
||||
_hasVisualOverflow = _maxScrollExtent > 0.0;
|
||||
offset.applyViewportDimension(_viewportExtent);
|
||||
@@ -715,9 +738,18 @@ class RenderEditable extends RenderBox {
|
||||
void _paintCaret(Canvas canvas, Offset effectiveOffset) {
|
||||
assert(_textLayoutLastWidth == constraints.maxWidth);
|
||||
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);
|
||||
canvas.drawRect(caretRect, paint);
|
||||
|
||||
if (cursorRadius == null) {
|
||||
canvas.drawRect(caretRect, paint);
|
||||
} else {
|
||||
final RRect caretRRect = RRect.fromRectAndRadius(caretRect, cursorRadius);
|
||||
canvas.drawRRect(caretRRect, paint);
|
||||
}
|
||||
|
||||
if (caretRect != _lastCaretRect) {
|
||||
_lastCaretRect = caretRect;
|
||||
if (onCaretChanged != null)
|
||||
|
||||
@@ -208,6 +208,8 @@ class EditableText extends StatefulWidget {
|
||||
this.onSelectionChanged,
|
||||
List<TextInputFormatter> inputFormatters,
|
||||
this.rendererIgnoresPointer = false,
|
||||
this.cursorWidth = 1.0,
|
||||
this.cursorRadius,
|
||||
}) : assert(controller != null),
|
||||
assert(focusNode != null),
|
||||
assert(obscureText != null),
|
||||
@@ -353,6 +355,16 @@ class EditableText extends StatefulWidget {
|
||||
/// This property is false by default.
|
||||
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
|
||||
EditableTextState createState() => new EditableTextState();
|
||||
|
||||
@@ -810,6 +822,7 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien
|
||||
FocusScope.of(context).reparentIfNeeded(widget.focusNode);
|
||||
super.build(context); // See AutomaticKeepAliveClientMixin.
|
||||
final TextSelectionControls controls = widget.selectionControls;
|
||||
|
||||
return new Scrollable(
|
||||
excludeFromSemantics: true,
|
||||
axisDirection: _isMultiline ? AxisDirection.down : AxisDirection.right,
|
||||
@@ -841,6 +854,8 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien
|
||||
onSelectionChanged: _handleSelectionChanged,
|
||||
onCaretChanged: _handleCaretChanged,
|
||||
rendererIgnoresPointer: widget.rendererIgnoresPointer,
|
||||
cursorWidth: widget.cursorWidth,
|
||||
cursorRadius: widget.cursorRadius,
|
||||
),
|
||||
),
|
||||
);
|
||||
@@ -902,6 +917,8 @@ class _Editable extends LeafRenderObjectWidget {
|
||||
this.onSelectionChanged,
|
||||
this.onCaretChanged,
|
||||
this.rendererIgnoresPointer = false,
|
||||
this.cursorWidth,
|
||||
this.cursorRadius,
|
||||
}) : assert(textDirection != null),
|
||||
assert(rendererIgnoresPointer != null),
|
||||
super(key: key);
|
||||
@@ -923,6 +940,8 @@ class _Editable extends LeafRenderObjectWidget {
|
||||
final SelectionChangedHandler onSelectionChanged;
|
||||
final CaretChangedHandler onCaretChanged;
|
||||
final bool rendererIgnoresPointer;
|
||||
final double cursorWidth;
|
||||
final Radius cursorRadius;
|
||||
|
||||
@override
|
||||
RenderEditable createRenderObject(BuildContext context) {
|
||||
@@ -943,6 +962,8 @@ class _Editable extends LeafRenderObjectWidget {
|
||||
onCaretChanged: onCaretChanged,
|
||||
ignorePointer: rendererIgnoresPointer,
|
||||
obscureText: obscureText,
|
||||
cursorWidth: cursorWidth,
|
||||
cursorRadius: cursorRadius,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -964,6 +985,8 @@ class _Editable extends LeafRenderObjectWidget {
|
||||
..onSelectionChanged = onSelectionChanged
|
||||
..onCaretChanged = onCaretChanged
|
||||
..ignorePointer = rendererIgnoresPointer
|
||||
..obscureText = obscureText;
|
||||
..obscureText = obscureText
|
||||
..cursorWidth = cursorWidth
|
||||
..cursorRadius = cursorRadius;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -967,6 +967,7 @@ void main() {
|
||||
final Offset center = tester.getCenter(find.text('B'));
|
||||
await tester.startGesture(center);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
await expectLater(
|
||||
find.byType(RepaintBoundary),
|
||||
matchesGoldenFile('segmented_control_test.1.0.png'),
|
||||
|
||||
@@ -1208,7 +1208,7 @@ void main() {
|
||||
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.pump();
|
||||
@@ -1217,7 +1217,7 @@ void main() {
|
||||
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 {
|
||||
@@ -1240,7 +1240,7 @@ void main() {
|
||||
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.pump();
|
||||
@@ -1249,7 +1249,7 @@ void main() {
|
||||
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 {
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user