From db9fe3f827836a0b29657d24bb87be90c5fca2c3 Mon Sep 17 00:00:00 2001 From: sjindel-google Date: Sun, 4 Nov 2018 03:20:44 +0100 Subject: [PATCH] Fix text field clipping when erasing rapidly. (#23894) `RenderEditable.paint` assumes that if the length of the text fits within the visible region, then the text will be rendered at the start of the region and be completely visible. This is not always true, since the text may still be rendered at an offset if an animation is ongoing when the text begins to fit. This fixes #22288 and #14121 --- .../lib/src/material/text_selection.dart | 3 +-- .../flutter/lib/src/rendering/editable.dart | 9 ++++--- .../flutter/test/rendering/editable_test.dart | 26 +++++++++++++++++++ 3 files changed, 33 insertions(+), 5 deletions(-) diff --git a/packages/flutter/lib/src/material/text_selection.dart b/packages/flutter/lib/src/material/text_selection.dart index eba02b4903d..5f0ee5fda9a 100644 --- a/packages/flutter/lib/src/material/text_selection.dart +++ b/packages/flutter/lib/src/material/text_selection.dart @@ -104,8 +104,7 @@ class _TextSelectionToolbarLayout extends SingleChildLayoutDelegate { } } -/// Draws a single text selection handle. The [type] determines where the handle -/// points (e.g. the [left] handle points up and to the right). +/// Draws a single text selection handle which points up and to the left. class _TextSelectionHandlePainter extends CustomPainter { _TextSelectionHandlePainter({ this.color }); diff --git a/packages/flutter/lib/src/rendering/editable.dart b/packages/flutter/lib/src/rendering/editable.dart index 551d3793962..663d3d5efe3 100644 --- a/packages/flutter/lib/src/rendering/editable.dart +++ b/packages/flutter/lib/src/rendering/editable.dart @@ -921,7 +921,11 @@ class RenderEditable extends RenderBox { return null; } - bool _hasVisualOverflow = false; + double _maxScrollExtent = 0; + + // We need to check the paint offset here because during animation, the start of + // the text may position outside the visible region even when the text fits. + bool get _hasVisualOverflow => _maxScrollExtent > 0 || _paintOffset != Offset.zero; /// Returns the local coordinates of the endpoints of the given selection. /// @@ -1146,8 +1150,7 @@ class RenderEditable extends RenderBox { final Size textPainterSize = _textPainter.size; size = Size(constraints.maxWidth, constraints.constrainHeight(_preferredHeight(constraints.maxWidth))); final Size contentSize = Size(textPainterSize.width + _kCaretGap + cursorWidth, textPainterSize.height); - final double _maxScrollExtent = _getMaxScrollExtent(contentSize); - _hasVisualOverflow = _maxScrollExtent > 0.0; + _maxScrollExtent = _getMaxScrollExtent(contentSize); offset.applyViewportDimension(_viewportExtent); offset.applyContentDimensions(0.0, _maxScrollExtent); } diff --git a/packages/flutter/test/rendering/editable_test.dart b/packages/flutter/test/rendering/editable_test.dart index 53b4ff05a47..c682d7b563d 100644 --- a/packages/flutter/test/rendering/editable_test.dart +++ b/packages/flutter/test/rendering/editable_test.dart @@ -6,6 +6,9 @@ import 'package:flutter/rendering.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:flutter/services.dart'; +import '../rendering/mock_canvas.dart'; +import '../rendering/recording_canvas.dart'; + class FakeEditableTextState extends TextSelectionDelegate { @override TextEditingValue get textEditingValue { return const TextEditingValue(); } @@ -65,4 +68,27 @@ void main() { ), ); }); + + // Test that clipping will be used even when the text fits within the visible + // region if the start position of the text is offset (e.g. during scrolling + // animation). + test('correct clipping', () { + final TextSelectionDelegate delegate = FakeEditableTextState(); + final RenderEditable editable = RenderEditable( + text: const TextSpan( + style: TextStyle(height: 1.0, fontSize: 10.0, fontFamily: 'Ahem'), + text: 'A', + ), + textAlign: TextAlign.start, + textDirection: TextDirection.ltr, + locale: const Locale('en', 'US'), + offset: ViewportOffset.fixed(10.0), + textSelectionDelegate: delegate, + ); + editable.layout(BoxConstraints.loose(const Size(1000.0, 1000.0))); + expect( + (Canvas canvas) => editable.paint(TestRecordingPaintingContext(canvas), Offset.zero), + paints..clipRect(rect: Rect.fromLTRB(0.0, 0.0, 1000.0, 10.0)) + ); + }); }