Unverified Commit f143fd6a authored by chunhtai's avatar chunhtai Committed by GitHub

fix Selection handles position is off (#34665)

parent b8597f0a
...@@ -11,6 +11,7 @@ import 'package:flutter/semantics.dart'; ...@@ -11,6 +11,7 @@ import 'package:flutter/semantics.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'box.dart'; import 'box.dart';
import 'layer.dart';
import 'object.dart'; import 'object.dart';
import 'viewport_offset.dart'; import 'viewport_offset.dart';
...@@ -143,6 +144,8 @@ class RenderEditable extends RenderBox { ...@@ -143,6 +144,8 @@ class RenderEditable extends RenderBox {
Color backgroundCursorColor, Color backgroundCursorColor,
ValueNotifier<bool> showCursor, ValueNotifier<bool> showCursor,
bool hasFocus, bool hasFocus,
@required LayerLink startHandleLayerLink,
@required LayerLink endHandleLayerLink,
int maxLines = 1, int maxLines = 1,
int minLines, int minLines,
bool expands = false, bool expands = false,
...@@ -168,6 +171,8 @@ class RenderEditable extends RenderBox { ...@@ -168,6 +171,8 @@ class RenderEditable extends RenderBox {
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),
assert(minLines == null || minLines > 0), assert(minLines == null || minLines > 0),
assert(startHandleLayerLink != null),
assert(endHandleLayerLink != null),
assert( assert(
(maxLines == null) || (minLines == null) || (maxLines >= minLines), (maxLines == null) || (minLines == null) || (maxLines >= minLines),
'minLines can\'t be greater than maxLines', 'minLines can\'t be greater than maxLines',
...@@ -209,6 +214,8 @@ class RenderEditable extends RenderBox { ...@@ -209,6 +214,8 @@ class RenderEditable extends RenderBox {
_floatingCursorAddedMargin = floatingCursorAddedMargin, _floatingCursorAddedMargin = floatingCursorAddedMargin,
_enableInteractiveSelection = enableInteractiveSelection, _enableInteractiveSelection = enableInteractiveSelection,
_devicePixelRatio = devicePixelRatio, _devicePixelRatio = devicePixelRatio,
_startHandleLayerLink = startHandleLayerLink,
_endHandleLayerLink = endHandleLayerLink,
_obscureText = obscureText { _obscureText = obscureText {
assert(_showCursor != null); assert(_showCursor != null);
assert(!_showCursor.value || cursorColor != null); assert(!_showCursor.value || cursorColor != null);
...@@ -903,6 +910,32 @@ class RenderEditable extends RenderBox { ...@@ -903,6 +910,32 @@ class RenderEditable extends RenderBox {
markNeedsPaint(); markNeedsPaint();
} }
/// The [LayerLink] of start selection handle.
///
/// [RenderEditable] is responsible for calculating the [Offset] of this
/// [LayerLink], which will be used as [CompositedTransformTarget] of start handle.
LayerLink get startHandleLayerLink => _startHandleLayerLink;
LayerLink _startHandleLayerLink;
set startHandleLayerLink(LayerLink value) {
if (_startHandleLayerLink == value)
return;
_startHandleLayerLink = value;
markNeedsPaint();
}
/// The [LayerLink] of end selection handle.
///
/// [RenderEditable] is responsible for calculating the [Offset] of this
/// [LayerLink], which will be used as [CompositedTransformTarget] of end handle.
LayerLink get endHandleLayerLink => _endHandleLayerLink;
LayerLink _endHandleLayerLink;
set endHandleLayerLink(LayerLink value) {
if (_endHandleLayerLink == value)
return;
_endHandleLayerLink = value;
markNeedsPaint();
}
/// The padding applied to text field. Used to determine the bounds when /// The padding applied to text field. Used to determine the bounds when
/// moving the floating cursor. /// moving the floating cursor.
/// ///
...@@ -1765,6 +1798,30 @@ class RenderEditable extends RenderBox { ...@@ -1765,6 +1798,30 @@ class RenderEditable extends RenderBox {
} }
} }
void _paintHandleLayers(PaintingContext context, List<TextSelectionPoint> endpoints) {
Offset startPoint = endpoints[0].point;
startPoint = Offset(
startPoint.dx.clamp(0.0, size.width),
startPoint.dy.clamp(0.0, size.height),
);
context.pushLayer(
LeaderLayer(link: startHandleLayerLink, offset: startPoint),
super.paint,
Offset.zero,
);
if (endpoints.length == 2) {
Offset endPoint = endpoints[1].point;
endPoint = Offset(
endPoint.dx.clamp(0.0, size.width),
endPoint.dy.clamp(0.0, size.height),
);
context.pushLayer(
LeaderLayer(link: endHandleLayerLink, offset: endPoint),
super.paint,
Offset.zero,
);
}
}
@override @override
void paint(PaintingContext context, Offset offset) { void paint(PaintingContext context, Offset offset) {
_layoutText(constraints.maxWidth); _layoutText(constraints.maxWidth);
...@@ -1772,6 +1829,7 @@ class RenderEditable extends RenderBox { ...@@ -1772,6 +1829,7 @@ class RenderEditable extends RenderBox {
context.pushClipRect(needsCompositing, offset, Offset.zero & size, _paintContents); context.pushClipRect(needsCompositing, offset, Offset.zero & size, _paintContents);
else else
_paintContents(context, offset); _paintContents(context, offset);
_paintHandleLayers(context, getEndpointsForSelection(selection));
} }
@override @override
......
...@@ -848,7 +848,10 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien ...@@ -848,7 +848,10 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien
AnimationController _cursorBlinkOpacityController; AnimationController _cursorBlinkOpacityController;
final LayerLink _layerLink = LayerLink(); final LayerLink _toolbarLayerLink = LayerLink();
final LayerLink _startHandleLayerLink = LayerLink();
final LayerLink _endHandleLayerLink = LayerLink();
bool _didAutoFocus = false; bool _didAutoFocus = false;
FocusAttachment _focusAttachment; FocusAttachment _focusAttachment;
...@@ -1226,7 +1229,9 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien ...@@ -1226,7 +1229,9 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien
context: context, context: context,
value: _value, value: _value,
debugRequiredFor: widget, debugRequiredFor: widget,
layerLink: _layerLink, toolbarLayerLink: _toolbarLayerLink,
startHandleLayerLink: _startHandleLayerLink,
endHandleLayerLink: _endHandleLayerLink,
renderObject: renderObject, renderObject: renderObject,
selectionControls: widget.selectionControls, selectionControls: widget.selectionControls,
selectionDelegate: this, selectionDelegate: this,
...@@ -1541,13 +1546,15 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien ...@@ -1541,13 +1546,15 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien
dragStartBehavior: widget.dragStartBehavior, dragStartBehavior: widget.dragStartBehavior,
viewportBuilder: (BuildContext context, ViewportOffset offset) { viewportBuilder: (BuildContext context, ViewportOffset offset) {
return CompositedTransformTarget( return CompositedTransformTarget(
link: _layerLink, link: _toolbarLayerLink,
child: Semantics( child: Semantics(
onCopy: _semanticsOnCopy(controls), onCopy: _semanticsOnCopy(controls),
onCut: _semanticsOnCut(controls), onCut: _semanticsOnCut(controls),
onPaste: _semanticsOnPaste(controls), onPaste: _semanticsOnPaste(controls),
child: _Editable( child: _Editable(
key: _editableKey, key: _editableKey,
startHandleLayerLink: _startHandleLayerLink,
endHandleLayerLink: _endHandleLayerLink,
textSpan: buildTextSpan(), textSpan: buildTextSpan(),
value: _value, value: _value,
cursorColor: _cursorColor, cursorColor: _cursorColor,
...@@ -1624,6 +1631,8 @@ class _Editable extends LeafRenderObjectWidget { ...@@ -1624,6 +1631,8 @@ class _Editable extends LeafRenderObjectWidget {
Key key, Key key,
this.textSpan, this.textSpan,
this.value, this.value,
this.startHandleLayerLink,
this.endHandleLayerLink,
this.cursorColor, this.cursorColor,
this.backgroundCursorColor, this.backgroundCursorColor,
this.showCursor, this.showCursor,
...@@ -1657,6 +1666,8 @@ class _Editable extends LeafRenderObjectWidget { ...@@ -1657,6 +1666,8 @@ class _Editable extends LeafRenderObjectWidget {
final TextSpan textSpan; final TextSpan textSpan;
final TextEditingValue value; final TextEditingValue value;
final Color cursorColor; final Color cursorColor;
final LayerLink startHandleLayerLink;
final LayerLink endHandleLayerLink;
final Color backgroundCursorColor; final Color backgroundCursorColor;
final ValueNotifier<bool> showCursor; final ValueNotifier<bool> showCursor;
final bool hasFocus; final bool hasFocus;
...@@ -1688,6 +1699,8 @@ class _Editable extends LeafRenderObjectWidget { ...@@ -1688,6 +1699,8 @@ class _Editable extends LeafRenderObjectWidget {
return RenderEditable( return RenderEditable(
text: textSpan, text: textSpan,
cursorColor: cursorColor, cursorColor: cursorColor,
startHandleLayerLink: startHandleLayerLink,
endHandleLayerLink: endHandleLayerLink,
backgroundCursorColor: backgroundCursorColor, backgroundCursorColor: backgroundCursorColor,
showCursor: showCursor, showCursor: showCursor,
hasFocus: hasFocus, hasFocus: hasFocus,
...@@ -1721,6 +1734,8 @@ class _Editable extends LeafRenderObjectWidget { ...@@ -1721,6 +1734,8 @@ class _Editable extends LeafRenderObjectWidget {
renderObject renderObject
..text = textSpan ..text = textSpan
..cursorColor = cursorColor ..cursorColor = cursorColor
..startHandleLayerLink = startHandleLayerLink
..endHandleLayerLink = endHandleLayerLink
..showCursor = showCursor ..showCursor = showCursor
..hasFocus = hasFocus ..hasFocus = hasFocus
..maxLines = maxLines ..maxLines = maxLines
......
...@@ -269,7 +269,9 @@ class TextSelectionOverlay { ...@@ -269,7 +269,9 @@ class TextSelectionOverlay {
@required TextEditingValue value, @required TextEditingValue value,
@required this.context, @required this.context,
this.debugRequiredFor, this.debugRequiredFor,
@required this.layerLink, @required this.toolbarLayerLink,
@required this.startHandleLayerLink,
@required this.endHandleLayerLink,
@required this.renderObject, @required this.renderObject,
this.selectionControls, this.selectionControls,
bool handlesVisible = false, bool handlesVisible = false,
...@@ -300,7 +302,15 @@ class TextSelectionOverlay { ...@@ -300,7 +302,15 @@ class TextSelectionOverlay {
/// The object supplied to the [CompositedTransformTarget] that wraps the text /// The object supplied to the [CompositedTransformTarget] that wraps the text
/// field. /// field.
final LayerLink layerLink; final LayerLink toolbarLayerLink;
/// The objects supplied to the [CompositedTransformTarget] that wraps the
/// location of start selection handle.
final LayerLink startHandleLayerLink;
/// The objects supplied to the [CompositedTransformTarget] that wraps the
/// location of end selection handle.
final LayerLink endHandleLayerLink;
// TODO(mpcomplete): what if the renderObject is removed or replaced, or // TODO(mpcomplete): what if the renderObject is removed or replaced, or
// moves? Not sure what cases I need to handle, or how to handle them. // moves? Not sure what cases I need to handle, or how to handle them.
...@@ -499,7 +509,8 @@ class TextSelectionOverlay { ...@@ -499,7 +509,8 @@ class TextSelectionOverlay {
child: _TextSelectionHandleOverlay( child: _TextSelectionHandleOverlay(
onSelectionHandleChanged: (TextSelection newSelection) { _handleSelectionHandleChanged(newSelection, position); }, onSelectionHandleChanged: (TextSelection newSelection) { _handleSelectionHandleChanged(newSelection, position); },
onSelectionHandleTapped: onSelectionHandleTapped, onSelectionHandleTapped: onSelectionHandleTapped,
layerLink: layerLink, startHandleLayerLink: startHandleLayerLink,
endHandleLayerLink: endHandleLayerLink,
renderObject: renderObject, renderObject: renderObject,
selection: _selection, selection: _selection,
selectionControls: selectionControls, selectionControls: selectionControls,
...@@ -539,7 +550,7 @@ class TextSelectionOverlay { ...@@ -539,7 +550,7 @@ class TextSelectionOverlay {
return FadeTransition( return FadeTransition(
opacity: _toolbarOpacity, opacity: _toolbarOpacity,
child: CompositedTransformFollower( child: CompositedTransformFollower(
link: layerLink, link: toolbarLayerLink,
showWhenUnlinked: false, showWhenUnlinked: false,
offset: -editingRegion.topLeft, offset: -editingRegion.topLeft,
child: selectionControls.buildToolbar( child: selectionControls.buildToolbar(
...@@ -575,7 +586,8 @@ class _TextSelectionHandleOverlay extends StatefulWidget { ...@@ -575,7 +586,8 @@ class _TextSelectionHandleOverlay extends StatefulWidget {
Key key, Key key,
@required this.selection, @required this.selection,
@required this.position, @required this.position,
@required this.layerLink, @required this.startHandleLayerLink,
@required this.endHandleLayerLink,
@required this.renderObject, @required this.renderObject,
@required this.onSelectionHandleChanged, @required this.onSelectionHandleChanged,
@required this.onSelectionHandleTapped, @required this.onSelectionHandleTapped,
...@@ -585,7 +597,8 @@ class _TextSelectionHandleOverlay extends StatefulWidget { ...@@ -585,7 +597,8 @@ class _TextSelectionHandleOverlay extends StatefulWidget {
final TextSelection selection; final TextSelection selection;
final _TextSelectionHandlePosition position; final _TextSelectionHandlePosition position;
final LayerLink layerLink; final LayerLink startHandleLayerLink;
final LayerLink endHandleLayerLink;
final RenderEditable renderObject; final RenderEditable renderObject;
final ValueChanged<TextSelection> onSelectionHandleChanged; final ValueChanged<TextSelection> onSelectionHandleChanged;
final VoidCallback onSelectionHandleTapped; final VoidCallback onSelectionHandleTapped;
...@@ -696,30 +709,30 @@ class _TextSelectionHandleOverlayState ...@@ -696,30 +709,30 @@ class _TextSelectionHandleOverlayState
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final List<TextSelectionPoint> endpoints = widget.renderObject.getEndpointsForSelection(widget.selection); LayerLink layerLink;
Offset point;
TextSelectionHandleType type; TextSelectionHandleType type;
switch (widget.position) { switch (widget.position) {
case _TextSelectionHandlePosition.start: case _TextSelectionHandlePosition.start:
point = endpoints[0].point; layerLink = widget.startHandleLayerLink;
type = _chooseType(endpoints[0], TextSelectionHandleType.left, TextSelectionHandleType.right); type = _chooseType(
widget.renderObject.textDirection,
TextSelectionHandleType.left,
TextSelectionHandleType.right,
);
break; break;
case _TextSelectionHandlePosition.end: case _TextSelectionHandlePosition.end:
// [endpoints] will only contain 1 point for collapsed selections, in // For collapsed selections, we shouldn't be building the [end] handle.
// which case we shouldn't be building the [end] handle. assert(!widget.selection.isCollapsed);
assert(endpoints.length == 2); layerLink = widget.endHandleLayerLink;
point = endpoints[1].point; type = _chooseType(
type = _chooseType(endpoints[1], TextSelectionHandleType.right, TextSelectionHandleType.left); widget.renderObject.textDirection,
TextSelectionHandleType.right,
TextSelectionHandleType.left,
);
break; break;
} }
final Size viewport = widget.renderObject.size;
point = Offset(
point.dx.clamp(0.0, viewport.width),
point.dy.clamp(0.0, viewport.height),
);
final Offset handleAnchor = widget.selectionControls.getHandleAnchor( final Offset handleAnchor = widget.selectionControls.getHandleAnchor(
type, type,
widget.renderObject.preferredLineHeight, widget.renderObject.preferredLineHeight,
...@@ -727,10 +740,10 @@ class _TextSelectionHandleOverlayState ...@@ -727,10 +740,10 @@ class _TextSelectionHandleOverlayState
final Size handleSize = widget.selectionControls.getHandleSize( final Size handleSize = widget.selectionControls.getHandleSize(
widget.renderObject.preferredLineHeight, widget.renderObject.preferredLineHeight,
); );
final Rect handleRect = Rect.fromLTWH( final Rect handleRect = Rect.fromLTWH(
// Put handleAnchor on top of point -handleAnchor.dx,
point.dx - handleAnchor.dx, -handleAnchor.dy,
point.dy - handleAnchor.dy,
handleSize.width, handleSize.width,
handleSize.height, handleSize.height,
); );
...@@ -747,7 +760,7 @@ class _TextSelectionHandleOverlayState ...@@ -747,7 +760,7 @@ class _TextSelectionHandleOverlayState
); );
return CompositedTransformFollower( return CompositedTransformFollower(
link: widget.layerLink, link: layerLink,
offset: interactiveRect.topLeft, offset: interactiveRect.topLeft,
showWhenUnlinked: false, showWhenUnlinked: false,
child: FadeTransition( child: FadeTransition(
...@@ -782,15 +795,15 @@ class _TextSelectionHandleOverlayState ...@@ -782,15 +795,15 @@ class _TextSelectionHandleOverlayState
} }
TextSelectionHandleType _chooseType( TextSelectionHandleType _chooseType(
TextSelectionPoint endpoint, TextDirection textDirection,
TextSelectionHandleType ltrType, TextSelectionHandleType ltrType,
TextSelectionHandleType rtlType, TextSelectionHandleType rtlType,
) { ) {
if (widget.selection.isCollapsed) if (widget.selection.isCollapsed)
return TextSelectionHandleType.collapsed; return TextSelectionHandleType.collapsed;
assert(endpoint.direction != null); assert(textDirection != null);
switch (endpoint.direction) { switch (textDirection) {
case TextDirection.ltr: case TextDirection.ltr:
return ltrType; return ltrType;
case TextDirection.rtl: case TextDirection.rtl:
......
...@@ -34,6 +34,8 @@ void main() { ...@@ -34,6 +34,8 @@ void main() {
style: TextStyle(height: 1.0, fontSize: 10.0, fontFamily: 'Ahem'), style: TextStyle(height: 1.0, fontSize: 10.0, fontFamily: 'Ahem'),
text: '12345', text: '12345',
), ),
startHandleLayerLink: LayerLink(),
endHandleLayerLink: LayerLink(),
textAlign: TextAlign.start, textAlign: TextAlign.start,
textDirection: TextDirection.ltr, textDirection: TextDirection.ltr,
locale: const Locale('ja', 'JP'), locale: const Locale('ja', 'JP'),
...@@ -84,11 +86,16 @@ void main() { ...@@ -84,11 +86,16 @@ void main() {
style: TextStyle(height: 1.0, fontSize: 10.0, fontFamily: 'Ahem'), style: TextStyle(height: 1.0, fontSize: 10.0, fontFamily: 'Ahem'),
text: 'A', text: 'A',
), ),
startHandleLayerLink: LayerLink(),
endHandleLayerLink: LayerLink(),
textAlign: TextAlign.start, textAlign: TextAlign.start,
textDirection: TextDirection.ltr, textDirection: TextDirection.ltr,
locale: const Locale('en', 'US'), locale: const Locale('en', 'US'),
offset: ViewportOffset.fixed(10.0), offset: ViewportOffset.fixed(10.0),
textSelectionDelegate: delegate, textSelectionDelegate: delegate,
selection: const TextSelection.collapsed(
offset: 0,
),
); );
editable.layout(BoxConstraints.loose(const Size(1000.0, 1000.0))); editable.layout(BoxConstraints.loose(const Size(1000.0, 1000.0)));
expect( expect(
...@@ -114,6 +121,8 @@ void main() { ...@@ -114,6 +121,8 @@ void main() {
height: 1.0, fontSize: 10.0, fontFamily: 'Ahem', height: 1.0, fontSize: 10.0, fontFamily: 'Ahem',
), ),
), ),
startHandleLayerLink: LayerLink(),
endHandleLayerLink: LayerLink(),
selection: const TextSelection.collapsed( selection: const TextSelection.collapsed(
offset: 4, offset: 4,
affinity: TextAffinity.upstream, affinity: TextAffinity.upstream,
...@@ -187,6 +196,8 @@ void main() { ...@@ -187,6 +196,8 @@ void main() {
height: 1.0, fontSize: 10.0, fontFamily: 'Ahem', height: 1.0, fontSize: 10.0, fontFamily: 'Ahem',
), ),
), ),
startHandleLayerLink: LayerLink(),
endHandleLayerLink: LayerLink(),
selection: const TextSelection.collapsed( selection: const TextSelection.collapsed(
offset: 4, offset: 4,
affinity: TextAffinity.upstream, affinity: TextAffinity.upstream,
...@@ -258,6 +269,8 @@ void main() { ...@@ -258,6 +269,8 @@ void main() {
height: 1.0, fontSize: 10.0, fontFamily: 'Ahem', height: 1.0, fontSize: 10.0, fontFamily: 'Ahem',
), ),
), ),
startHandleLayerLink: LayerLink(),
endHandleLayerLink: LayerLink(),
selection: const TextSelection( selection: const TextSelection(
baseOffset: 0, baseOffset: 0,
extentOffset: 3, extentOffset: 3,
...@@ -297,6 +310,8 @@ void main() { ...@@ -297,6 +310,8 @@ void main() {
height: 1.0, fontSize: 10.0, fontFamily: 'Ahem', height: 1.0, fontSize: 10.0, fontFamily: 'Ahem',
), ),
), ),
startHandleLayerLink: LayerLink(),
endHandleLayerLink: LayerLink(),
selection: const TextSelection.collapsed( selection: const TextSelection.collapsed(
offset: 2, offset: 2,
affinity: TextAffinity.upstream, affinity: TextAffinity.upstream,
...@@ -345,12 +360,17 @@ void main() { ...@@ -345,12 +360,17 @@ void main() {
onSelectionChanged: (TextSelection selection, RenderEditable renderObject, SelectionChangedCause cause) { onSelectionChanged: (TextSelection selection, RenderEditable renderObject, SelectionChangedCause cause) {
currentSelection = selection; currentSelection = selection;
}, },
startHandleLayerLink: LayerLink(),
endHandleLayerLink: LayerLink(),
text: const TextSpan( text: const TextSpan(
text: 'test\ntest', text: 'test\ntest',
style: TextStyle( style: TextStyle(
height: 1.0, fontSize: 10.0, fontFamily: 'Ahem', height: 1.0, fontSize: 10.0, fontFamily: 'Ahem',
), ),
), ),
selection: const TextSelection.collapsed(
offset: 4,
),
); );
layout(editable); layout(editable);
...@@ -429,6 +449,8 @@ void main() { ...@@ -429,6 +449,8 @@ void main() {
height: 1.0, fontSize: 10.0, fontFamily: 'Ahem', height: 1.0, fontSize: 10.0, fontFamily: 'Ahem',
), ),
), ),
startHandleLayerLink: LayerLink(),
endHandleLayerLink: LayerLink(),
); );
layout(editable); layout(editable);
...@@ -461,6 +483,8 @@ void main() { ...@@ -461,6 +483,8 @@ void main() {
selectionChangedCount++; selectionChangedCount++;
updatedSelection = selection; updatedSelection = selection;
}, },
startHandleLayerLink: LayerLink(),
endHandleLayerLink: LayerLink(),
text: text, text: text,
); );
...@@ -483,6 +507,8 @@ void main() { ...@@ -483,6 +507,8 @@ void main() {
updatedSelection = selection; updatedSelection = selection;
}, },
text: text, text: text,
startHandleLayerLink: LayerLink(),
endHandleLayerLink: LayerLink(),
); );
layout(editable2); layout(editable2);
...@@ -510,6 +536,8 @@ void main() { ...@@ -510,6 +536,8 @@ void main() {
offset: ViewportOffset.zero(), offset: ViewportOffset.zero(),
textSelectionDelegate: delegate, textSelectionDelegate: delegate,
hasFocus: true, hasFocus: true,
startHandleLayerLink: LayerLink(),
endHandleLayerLink: LayerLink(),
); );
expect(editable.hasFocus, true); expect(editable.hasFocus, true);
......
...@@ -268,6 +268,66 @@ void main() { ...@@ -268,6 +268,66 @@ void main() {
equals('TextInputAction.newline')); equals('TextInputAction.newline'));
}); });
testWidgets('selection overlay will update when text grow bigger', (WidgetTester tester) async {
final TextEditingController controller = TextEditingController.fromValue(
const TextEditingValue(
text: 'initial value',
)
);
Future<void> pumpEditableTextWithTextStyle(TextStyle style) async {
await tester.pumpWidget(
MaterialApp(
home: EditableText(
backgroundCursorColor: Colors.grey,
controller: controller,
focusNode: focusNode,
style: style,
cursorColor: cursorColor,
selectionControls: materialTextSelectionControls,
showSelectionHandles: true,
),
),
);
}
await pumpEditableTextWithTextStyle(const TextStyle(fontSize: 18));
final EditableTextState state = tester.state<EditableTextState>(find.byType(EditableText));
state.renderEditable.selectWordsInRange(
from: const Offset(0, 0),
cause: SelectionChangedCause.longPress,
);
await tester.pumpAndSettle();
await tester.idle();
List<RenderBox> handles = List<RenderBox>.from(
tester.renderObjectList<RenderBox>(
find.descendant(
of: find.byType(CompositedTransformFollower),
matching: find.byType(GestureDetector),
),
),
);
expect(handles[0].localToGlobal(Offset.zero), const Offset(-35.0, 5.0));
expect(handles[1].localToGlobal(Offset.zero), const Offset(113.0, 5.0));
await pumpEditableTextWithTextStyle(const TextStyle(fontSize: 30));
await tester.pumpAndSettle();
// Handles should be updated with bigger font size.
handles = List<RenderBox>.from(
tester.renderObjectList<RenderBox>(
find.descendant(
of: find.byType(CompositedTransformFollower),
matching: find.byType(GestureDetector),
),
),
);
// First handle should have the same dx but bigger dy.
expect(handles[0].localToGlobal(Offset.zero), const Offset(-35.0, 17.0));
expect(handles[1].localToGlobal(Offset.zero), const Offset(197.0, 17.0));
});
testWidgets('Multiline keyboard with newline action is requested when maxLines = null', (WidgetTester tester) async { testWidgets('Multiline keyboard with newline action is requested when maxLines = null', (WidgetTester tester) async {
await tester.pumpWidget( await tester.pumpWidget(
MediaQuery( MediaQuery(
...@@ -1908,7 +1968,7 @@ void main() { ...@@ -1908,7 +1968,7 @@ void main() {
)); ));
final EditableTextState state = final EditableTextState state =
tester.state<EditableTextState>(find.byType(EditableText)); tester.state<EditableTextState>(find.byType(EditableText));
final RenderEditable renderEditable = state.renderEditable; final RenderEditable renderEditable = state.renderEditable;
final Scrollable scrollable = tester.widget<Scrollable>(find.byType(Scrollable)); final Scrollable scrollable = tester.widget<Scrollable>(find.byType(Scrollable));
...@@ -1963,12 +2023,14 @@ void main() { ...@@ -1963,12 +2023,14 @@ void main() {
// Check that the handles' positions are correct. // Check that the handles' positions are correct.
final List<CompositedTransformFollower> container = final List<RenderBox> handles = List<RenderBox>.from(
find.byType(CompositedTransformFollower) tester.renderObjectList<RenderBox>(
.evaluate() find.descendant(
.map((Element e) => e.widget) of: find.byType(CompositedTransformFollower),
.cast<CompositedTransformFollower>() matching: find.byType(GestureDetector),
.toList(); ),
),
);
final Size viewport = renderEditable.size; final Size viewport = renderEditable.size;
...@@ -2006,8 +2068,8 @@ void main() { ...@@ -2006,8 +2068,8 @@ void main() {
} }
} }
expect(state.selectionOverlay.handlesAreVisible, isTrue); expect(state.selectionOverlay.handlesAreVisible, isTrue);
testPosition(container[0].offset.dx, leftPosition); testPosition(handles[0].localToGlobal(Offset.zero).dx, leftPosition);
testPosition(container[1].offset.dx, rightPosition); testPosition(handles[1].localToGlobal(Offset.zero).dx, rightPosition);
} }
// Select the first word. Both handles should be visible. // Select the first word. Both handles should be visible.
...@@ -2080,21 +2142,23 @@ void main() { ...@@ -2080,21 +2142,23 @@ void main() {
await tester.tapAt(const Offset(20, 10)); await tester.tapAt(const Offset(20, 10));
state.renderEditable.selectWord(cause: SelectionChangedCause.longPress); state.renderEditable.selectWord(cause: SelectionChangedCause.longPress);
await tester.pump(); await tester.pump();
final List<CompositedTransformFollower> container = final List<RenderBox> handles = List<RenderBox>.from(
find.byType(CompositedTransformFollower) tester.renderObjectList<RenderBox>(
.evaluate() find.descendant(
.map((Element e) => e.widget) of: find.byType(CompositedTransformFollower),
.cast<CompositedTransformFollower>() matching: find.byType(GestureDetector),
.toList(); ),
),
);
expect( expect(
container[0].offset.dx, handles[0].localToGlobal(Offset.zero).dx,
inExclusiveRange( inExclusiveRange(
-kMinInteractiveSize, -kMinInteractiveSize,
kMinInteractiveSize, kMinInteractiveSize,
), ),
); );
expect( expect(
container[1].offset.dx, handles[1].localToGlobal(Offset.zero).dx,
inExclusiveRange( inExclusiveRange(
70.0 - kMinInteractiveSize, 70.0 - kMinInteractiveSize,
70.0 + kMinInteractiveSize, 70.0 + kMinInteractiveSize,
...@@ -2188,12 +2252,14 @@ void main() { ...@@ -2188,12 +2252,14 @@ void main() {
// Check that the handles' positions are correct. // Check that the handles' positions are correct.
final List<CompositedTransformFollower> container = final List<RenderBox> handles = List<RenderBox>.from(
find.byType(CompositedTransformFollower) tester.renderObjectList<RenderBox>(
.evaluate() find.descendant(
.map((Element e) => e.widget) of: find.byType(CompositedTransformFollower),
.cast<CompositedTransformFollower>() matching: find.byType(GestureDetector),
.toList(); ),
),
);
final Size viewport = renderEditable.size; final Size viewport = renderEditable.size;
...@@ -2231,8 +2297,8 @@ void main() { ...@@ -2231,8 +2297,8 @@ void main() {
} }
} }
expect(state.selectionOverlay.handlesAreVisible, isTrue); expect(state.selectionOverlay.handlesAreVisible, isTrue);
testPosition(container[0].offset.dx, leftPosition); testPosition(handles[0].localToGlobal(Offset.zero).dx, leftPosition);
testPosition(container[1].offset.dx, rightPosition); testPosition(handles[1].localToGlobal(Offset.zero).dx, rightPosition);
} }
// Select the first word. Both handles should be visible. // Select the first word. Both handles should be visible.
......
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