mirror of
https://github.com/flutter/flutter.git
synced 2025-06-03 00:51:18 +00:00
Implement SelectionArea single click/tap gestures (#132682)
This change collapses the selection at the clicked/tapped location on single click down for desktop platforms, and on single click/tap up for mobile platforms to match native. This is a change from how `SelectionArea` previously worked. Before this change a single click down would clear the selection. From observing a native browser it looks like when tapping on static text the selection is not cleared but collapsed. A user can still attain the selection from static text using the `window.getSelection` API. https://jsfiddle.net/juepasn3/11/ You can try this demo out here to observe this behavior yourself. When clicking on static text the selection will change. This change also allows `Paragraph.selections` to return selections that are collapsed. This for testing purposes to confirm where the selection has been collapsed. Partially fixes: #129583
This commit is contained in:
parent
80fb7bd206
commit
21ad7122a1
@ -24,6 +24,9 @@ void main() {
|
|||||||
|
|
||||||
// Right clicking the Text in the SelectionArea shows the custom context
|
// Right clicking the Text in the SelectionArea shows the custom context
|
||||||
// menu.
|
// menu.
|
||||||
|
final TestGesture primaryMouseButtonGesture = await tester.createGesture(
|
||||||
|
kind: PointerDeviceKind.mouse,
|
||||||
|
);
|
||||||
final TestGesture gesture = await tester.startGesture(
|
final TestGesture gesture = await tester.startGesture(
|
||||||
tester.getCenter(find.text(example.text)),
|
tester.getCenter(find.text(example.text)),
|
||||||
kind: PointerDeviceKind.mouse,
|
kind: PointerDeviceKind.mouse,
|
||||||
@ -37,7 +40,9 @@ void main() {
|
|||||||
expect(find.text('Print'), findsOneWidget);
|
expect(find.text('Print'), findsOneWidget);
|
||||||
|
|
||||||
// Tap to dismiss.
|
// Tap to dismiss.
|
||||||
await tester.tapAt(tester.getCenter(find.byType(Scaffold)));
|
await primaryMouseButtonGesture.down(tester.getCenter(find.byType(Scaffold)));
|
||||||
|
await tester.pump();
|
||||||
|
await primaryMouseButtonGesture.up();
|
||||||
await tester.pumpAndSettle();
|
await tester.pumpAndSettle();
|
||||||
|
|
||||||
expect(find.byType(AdaptiveTextSelectionToolbar), findsNothing);
|
expect(find.byType(AdaptiveTextSelectionToolbar), findsNothing);
|
||||||
|
@ -132,7 +132,7 @@ mixin RenderInlineChildrenContainerDefaults on RenderBox, ContainerRenderObjectM
|
|||||||
ui.PlaceholderAlignment.belowBaseline ||
|
ui.PlaceholderAlignment.belowBaseline ||
|
||||||
ui.PlaceholderAlignment.bottom ||
|
ui.PlaceholderAlignment.bottom ||
|
||||||
ui.PlaceholderAlignment.middle ||
|
ui.PlaceholderAlignment.middle ||
|
||||||
ui.PlaceholderAlignment.top => null,
|
ui.PlaceholderAlignment.top => null,
|
||||||
ui.PlaceholderAlignment.baseline => child.getDistanceToBaseline(span.baseline!),
|
ui.PlaceholderAlignment.baseline => child.getDistanceToBaseline(span.baseline!),
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
@ -351,8 +351,7 @@ class RenderParagraph extends RenderBox with ContainerRenderObjectMixin<RenderBo
|
|||||||
final List<TextSelection> results = <TextSelection>[];
|
final List<TextSelection> results = <TextSelection>[];
|
||||||
for (final _SelectableFragment fragment in _lastSelectableFragments!) {
|
for (final _SelectableFragment fragment in _lastSelectableFragments!) {
|
||||||
if (fragment._textSelectionStart != null &&
|
if (fragment._textSelectionStart != null &&
|
||||||
fragment._textSelectionEnd != null &&
|
fragment._textSelectionEnd != null) {
|
||||||
fragment._textSelectionStart!.offset != fragment._textSelectionEnd!.offset) {
|
|
||||||
results.add(
|
results.add(
|
||||||
TextSelection(
|
TextSelection(
|
||||||
baseOffset: fragment._textSelectionStart!.offset,
|
baseOffset: fragment._textSelectionStart!.offset,
|
||||||
@ -1309,9 +1308,9 @@ class RenderParagraph extends RenderBox with ContainerRenderObjectMixin<RenderBo
|
|||||||
|
|
||||||
/// A continuous, selectable piece of paragraph.
|
/// A continuous, selectable piece of paragraph.
|
||||||
///
|
///
|
||||||
/// Since the selections in [PlaceHolderSpan] are handled independently in its
|
/// Since the selections in [PlaceholderSpan] are handled independently in its
|
||||||
/// subtree, a selection in [RenderParagraph] can't continue across a
|
/// subtree, a selection in [RenderParagraph] can't continue across a
|
||||||
/// [PlaceHolderSpan]. The [RenderParagraph] splits itself on [PlaceHolderSpan]
|
/// [PlaceholderSpan]. The [RenderParagraph] splits itself on [PlaceholderSpan]
|
||||||
/// to create multiple `_SelectableFragment`s so that they can be selected
|
/// to create multiple `_SelectableFragment`s so that they can be selected
|
||||||
/// separately.
|
/// separately.
|
||||||
class _SelectableFragment with Selectable, ChangeNotifier implements TextLayoutMetrics {
|
class _SelectableFragment with Selectable, ChangeNotifier implements TextLayoutMetrics {
|
||||||
@ -1720,7 +1719,7 @@ class _SelectableFragment with Selectable, ChangeNotifier implements TextLayoutM
|
|||||||
_selectableContainsOriginWord = true;
|
_selectableContainsOriginWord = true;
|
||||||
|
|
||||||
final TextPosition position = paragraph.getPositionForOffset(paragraph.globalToLocal(globalPosition));
|
final TextPosition position = paragraph.getPositionForOffset(paragraph.globalToLocal(globalPosition));
|
||||||
if (_positionIsWithinCurrentSelection(position)) {
|
if (_positionIsWithinCurrentSelection(position) && _textSelectionStart != _textSelectionEnd) {
|
||||||
return SelectionResult.end;
|
return SelectionResult.end;
|
||||||
}
|
}
|
||||||
final _WordBoundaryRecord wordBoundary = _getWordBoundaryAtPosition(position);
|
final _WordBoundaryRecord wordBoundary = _getWordBoundaryAtPosition(position);
|
||||||
|
@ -670,7 +670,7 @@ class ScrollableState extends State<Scrollable> with TickerProviderStateMixin, R
|
|||||||
if (oldWidget.controller == null) {
|
if (oldWidget.controller == null) {
|
||||||
// The old controller was null, meaning the fallback cannot be null.
|
// The old controller was null, meaning the fallback cannot be null.
|
||||||
// Dispose of the fallback.
|
// Dispose of the fallback.
|
||||||
assert(_fallbackScrollController != null);
|
assert(_fallbackScrollController != null);
|
||||||
assert(widget.controller != null);
|
assert(widget.controller != null);
|
||||||
_fallbackScrollController!.detach(position);
|
_fallbackScrollController!.detach(position);
|
||||||
_fallbackScrollController!.dispose();
|
_fallbackScrollController!.dispose();
|
||||||
@ -1954,7 +1954,7 @@ class TwoDimensionalScrollableState extends State<TwoDimensionalScrollable> {
|
|||||||
if (oldWidget.horizontalDetails.controller == null) {
|
if (oldWidget.horizontalDetails.controller == null) {
|
||||||
// The old controller was null, meaning the fallback cannot be null.
|
// The old controller was null, meaning the fallback cannot be null.
|
||||||
// Dispose of the fallback.
|
// Dispose of the fallback.
|
||||||
assert(_horizontalFallbackController != null);
|
assert(_horizontalFallbackController != null);
|
||||||
assert(widget.horizontalDetails.controller != null);
|
assert(widget.horizontalDetails.controller != null);
|
||||||
_horizontalFallbackController!.dispose();
|
_horizontalFallbackController!.dispose();
|
||||||
_horizontalFallbackController = null;
|
_horizontalFallbackController = null;
|
||||||
|
@ -352,7 +352,8 @@ class SelectableRegionState extends State<SelectableRegion> with TextSelectionDe
|
|||||||
_showToolbar(location: details.globalPosition);
|
_showToolbar(location: details.globalPosition);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
_clearSelection();
|
hideToolbar();
|
||||||
|
_collapseSelectionAt(offset: details.globalPosition);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
instance.onSecondaryTapDown = _handleRightClickDown;
|
instance.onSecondaryTapDown = _handleRightClickDown;
|
||||||
@ -472,6 +473,7 @@ class SelectableRegionState extends State<SelectableRegion> with TextSelectionDe
|
|||||||
(TapAndPanGestureRecognizer instance) {
|
(TapAndPanGestureRecognizer instance) {
|
||||||
instance
|
instance
|
||||||
..onTapDown = _startNewMouseSelectionGesture
|
..onTapDown = _startNewMouseSelectionGesture
|
||||||
|
..onTapUp = _handleMouseTapUp
|
||||||
..onDragStart = _handleMouseDragStart
|
..onDragStart = _handleMouseDragStart
|
||||||
..onDragUpdate = _handleMouseDragUpdate
|
..onDragUpdate = _handleMouseDragUpdate
|
||||||
..onDragEnd = _handleMouseDragEnd
|
..onDragEnd = _handleMouseDragEnd
|
||||||
@ -498,7 +500,17 @@ class SelectableRegionState extends State<SelectableRegion> with TextSelectionDe
|
|||||||
case 1:
|
case 1:
|
||||||
widget.focusNode.requestFocus();
|
widget.focusNode.requestFocus();
|
||||||
hideToolbar();
|
hideToolbar();
|
||||||
_clearSelection();
|
switch (defaultTargetPlatform) {
|
||||||
|
case TargetPlatform.android:
|
||||||
|
case TargetPlatform.fuchsia:
|
||||||
|
case TargetPlatform.iOS:
|
||||||
|
// On mobile platforms the selection is set on tap up.
|
||||||
|
break;
|
||||||
|
case TargetPlatform.macOS:
|
||||||
|
case TargetPlatform.linux:
|
||||||
|
case TargetPlatform.windows:
|
||||||
|
_collapseSelectionAt(offset: details.globalPosition);
|
||||||
|
}
|
||||||
case 2:
|
case 2:
|
||||||
_selectWordAt(offset: details.globalPosition);
|
_selectWordAt(offset: details.globalPosition);
|
||||||
}
|
}
|
||||||
@ -528,6 +540,24 @@ class SelectableRegionState extends State<SelectableRegion> with TextSelectionDe
|
|||||||
_updateSelectedContentIfNeeded();
|
_updateSelectedContentIfNeeded();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void _handleMouseTapUp(TapDragUpDetails details) {
|
||||||
|
switch (_getEffectiveConsecutiveTapCount(details.consecutiveTapCount)) {
|
||||||
|
case 1:
|
||||||
|
switch (defaultTargetPlatform) {
|
||||||
|
case TargetPlatform.android:
|
||||||
|
case TargetPlatform.fuchsia:
|
||||||
|
case TargetPlatform.iOS:
|
||||||
|
_collapseSelectionAt(offset: details.globalPosition);
|
||||||
|
case TargetPlatform.macOS:
|
||||||
|
case TargetPlatform.linux:
|
||||||
|
case TargetPlatform.windows:
|
||||||
|
// On desktop platforms the selection is set on tap down.
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_updateSelectedContentIfNeeded();
|
||||||
|
}
|
||||||
|
|
||||||
void _updateSelectedContentIfNeeded() {
|
void _updateSelectedContentIfNeeded() {
|
||||||
if (_lastSelectedContent?.plainText != _selectable?.getSelectedContent()?.plainText) {
|
if (_lastSelectedContent?.plainText != _selectable?.getSelectedContent()?.plainText) {
|
||||||
_lastSelectedContent = _selectable?.getSelectedContent();
|
_lastSelectedContent = _selectable?.getSelectedContent();
|
||||||
@ -586,8 +616,7 @@ class SelectableRegionState extends State<SelectableRegion> with TextSelectionDe
|
|||||||
// keep the current selection, if not then collapse it.
|
// keep the current selection, if not then collapse it.
|
||||||
final bool lastSecondaryTapDownPositionWasOnActiveSelection = _positionIsOnActiveSelection(globalPosition: details.globalPosition);
|
final bool lastSecondaryTapDownPositionWasOnActiveSelection = _positionIsOnActiveSelection(globalPosition: details.globalPosition);
|
||||||
if (!lastSecondaryTapDownPositionWasOnActiveSelection) {
|
if (!lastSecondaryTapDownPositionWasOnActiveSelection) {
|
||||||
_selectStartTo(offset: lastSecondaryTapDownPosition!);
|
_collapseSelectionAt(offset: lastSecondaryTapDownPosition!);
|
||||||
_selectEndTo(offset: lastSecondaryTapDownPosition!);
|
|
||||||
}
|
}
|
||||||
_showHandles();
|
_showHandles();
|
||||||
_showToolbar(location: lastSecondaryTapDownPosition);
|
_showToolbar(location: lastSecondaryTapDownPosition);
|
||||||
@ -612,8 +641,7 @@ class SelectableRegionState extends State<SelectableRegion> with TextSelectionDe
|
|||||||
// keep the current selection, if not then collapse it.
|
// keep the current selection, if not then collapse it.
|
||||||
final bool lastSecondaryTapDownPositionWasOnActiveSelection = _positionIsOnActiveSelection(globalPosition: details.globalPosition);
|
final bool lastSecondaryTapDownPositionWasOnActiveSelection = _positionIsOnActiveSelection(globalPosition: details.globalPosition);
|
||||||
if (!lastSecondaryTapDownPositionWasOnActiveSelection) {
|
if (!lastSecondaryTapDownPositionWasOnActiveSelection) {
|
||||||
_selectStartTo(offset: lastSecondaryTapDownPosition!);
|
_collapseSelectionAt(offset: lastSecondaryTapDownPosition!);
|
||||||
_selectEndTo(offset: lastSecondaryTapDownPosition!);
|
|
||||||
}
|
}
|
||||||
_showHandles();
|
_showHandles();
|
||||||
_showToolbar(location: lastSecondaryTapDownPosition);
|
_showToolbar(location: lastSecondaryTapDownPosition);
|
||||||
@ -925,8 +953,9 @@ class SelectableRegionState extends State<SelectableRegion> with TextSelectionDe
|
|||||||
/// See also:
|
/// See also:
|
||||||
/// * [_selectStartTo], which sets or updates selection start edge.
|
/// * [_selectStartTo], which sets or updates selection start edge.
|
||||||
/// * [_finalizeSelection], which stops the `continuous` updates.
|
/// * [_finalizeSelection], which stops the `continuous` updates.
|
||||||
/// * [_clearSelection], which clear the ongoing selection.
|
/// * [_clearSelection], which clears the ongoing selection.
|
||||||
/// * [_selectWordAt], which selects a whole word at the location.
|
/// * [_selectWordAt], which selects a whole word at the location.
|
||||||
|
/// * [_collapseSelectionAt], which collapses the selection at the location.
|
||||||
/// * [selectAll], which selects the entire content.
|
/// * [selectAll], which selects the entire content.
|
||||||
void _selectEndTo({required Offset offset, bool continuous = false, TextGranularity? textGranularity}) {
|
void _selectEndTo({required Offset offset, bool continuous = false, TextGranularity? textGranularity}) {
|
||||||
if (!continuous) {
|
if (!continuous) {
|
||||||
@ -964,8 +993,9 @@ class SelectableRegionState extends State<SelectableRegion> with TextSelectionDe
|
|||||||
/// See also:
|
/// See also:
|
||||||
/// * [_selectEndTo], which sets or updates selection end edge.
|
/// * [_selectEndTo], which sets or updates selection end edge.
|
||||||
/// * [_finalizeSelection], which stops the `continuous` updates.
|
/// * [_finalizeSelection], which stops the `continuous` updates.
|
||||||
/// * [_clearSelection], which clear the ongoing selection.
|
/// * [_clearSelection], which clears the ongoing selection.
|
||||||
/// * [_selectWordAt], which selects a whole word at the location.
|
/// * [_selectWordAt], which selects a whole word at the location.
|
||||||
|
/// * [_collapseSelectionAt], which collapses the selection at the location.
|
||||||
/// * [selectAll], which selects the entire content.
|
/// * [selectAll], which selects the entire content.
|
||||||
void _selectStartTo({required Offset offset, bool continuous = false, TextGranularity? textGranularity}) {
|
void _selectStartTo({required Offset offset, bool continuous = false, TextGranularity? textGranularity}) {
|
||||||
if (!continuous) {
|
if (!continuous) {
|
||||||
@ -978,6 +1008,20 @@ class SelectableRegionState extends State<SelectableRegion> with TextSelectionDe
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Collapses the selection at the given `offset` location.
|
||||||
|
///
|
||||||
|
/// See also:
|
||||||
|
/// * [_selectStartTo], which sets or updates selection start edge.
|
||||||
|
/// * [_selectEndTo], which sets or updates selection end edge.
|
||||||
|
/// * [_finalizeSelection], which stops the `continuous` updates.
|
||||||
|
/// * [_clearSelection], which clears the ongoing selection.
|
||||||
|
/// * [_selectWordAt], which selects a whole word at the location.
|
||||||
|
/// * [selectAll], which selects the entire content.
|
||||||
|
void _collapseSelectionAt({required Offset offset}) {
|
||||||
|
_selectStartTo(offset: offset);
|
||||||
|
_selectEndTo(offset: offset);
|
||||||
|
}
|
||||||
|
|
||||||
/// Selects a whole word at the `offset` location.
|
/// Selects a whole word at the `offset` location.
|
||||||
///
|
///
|
||||||
/// If the whole word is already in the current selection, selection won't
|
/// If the whole word is already in the current selection, selection won't
|
||||||
@ -991,7 +1035,8 @@ class SelectableRegionState extends State<SelectableRegion> with TextSelectionDe
|
|||||||
/// * [_selectStartTo], which sets or updates selection start edge.
|
/// * [_selectStartTo], which sets or updates selection start edge.
|
||||||
/// * [_selectEndTo], which sets or updates selection end edge.
|
/// * [_selectEndTo], which sets or updates selection end edge.
|
||||||
/// * [_finalizeSelection], which stops the `continuous` updates.
|
/// * [_finalizeSelection], which stops the `continuous` updates.
|
||||||
/// * [_clearSelection], which clear the ongoing selection.
|
/// * [_clearSelection], which clears the ongoing selection.
|
||||||
|
/// * [_collapseSelectionAt], which collapses the selection at the location.
|
||||||
/// * [selectAll], which selects the entire content.
|
/// * [selectAll], which selects the entire content.
|
||||||
void _selectWordAt({required Offset offset}) {
|
void _selectWordAt({required Offset offset}) {
|
||||||
// There may be other selection ongoing.
|
// There may be other selection ongoing.
|
||||||
@ -1881,7 +1926,7 @@ abstract class MultiSelectableSelectionContainerDelegate extends SelectionContai
|
|||||||
|
|
||||||
SelectionPoint? startPoint;
|
SelectionPoint? startPoint;
|
||||||
if (startGeometry.startSelectionPoint != null) {
|
if (startGeometry.startSelectionPoint != null) {
|
||||||
final Matrix4 startTransform = getTransformFrom(selectables[startIndexWalker]);
|
final Matrix4 startTransform = getTransformFrom(selectables[startIndexWalker]);
|
||||||
final Offset start = MatrixUtils.transformPoint(startTransform, startGeometry.startSelectionPoint!.localPosition);
|
final Offset start = MatrixUtils.transformPoint(startTransform, startGeometry.startSelectionPoint!.localPosition);
|
||||||
// It can be NaN if it is detached or off-screen.
|
// It can be NaN if it is detached or off-screen.
|
||||||
if (start.isFinite) {
|
if (start.isFinite) {
|
||||||
@ -1902,7 +1947,7 @@ abstract class MultiSelectableSelectionContainerDelegate extends SelectionContai
|
|||||||
}
|
}
|
||||||
SelectionPoint? endPoint;
|
SelectionPoint? endPoint;
|
||||||
if (endGeometry.endSelectionPoint != null) {
|
if (endGeometry.endSelectionPoint != null) {
|
||||||
final Matrix4 endTransform = getTransformFrom(selectables[endIndexWalker]);
|
final Matrix4 endTransform = getTransformFrom(selectables[endIndexWalker]);
|
||||||
final Offset end = MatrixUtils.transformPoint(endTransform, endGeometry.endSelectionPoint!.localPosition);
|
final Offset end = MatrixUtils.transformPoint(endTransform, endGeometry.endSelectionPoint!.localPosition);
|
||||||
// It can be NaN if it is detached or off-screen.
|
// It can be NaN if it is detached or off-screen.
|
||||||
if (end.isFinite) {
|
if (end.isFinite) {
|
||||||
@ -1986,8 +2031,8 @@ abstract class MultiSelectableSelectionContainerDelegate extends SelectionContai
|
|||||||
final Rect? drawableArea = hasSize ? Rect
|
final Rect? drawableArea = hasSize ? Rect
|
||||||
.fromLTWH(0, 0, containerSize.width, containerSize.height)
|
.fromLTWH(0, 0, containerSize.width, containerSize.height)
|
||||||
.inflate(_kSelectionHandleDrawableAreaPadding) : null;
|
.inflate(_kSelectionHandleDrawableAreaPadding) : null;
|
||||||
final bool hideStartHandle = value.startSelectionPoint == null || drawableArea == null || !drawableArea.contains(value.startSelectionPoint!.localPosition);
|
final bool hideStartHandle = value.startSelectionPoint == null || drawableArea == null || !drawableArea.contains(value.startSelectionPoint!.localPosition);
|
||||||
final bool hideEndHandle = value.endSelectionPoint == null || drawableArea == null|| !drawableArea.contains(value.endSelectionPoint!.localPosition);
|
final bool hideEndHandle = value.endSelectionPoint == null || drawableArea == null|| !drawableArea.contains(value.endSelectionPoint!.localPosition);
|
||||||
effectiveStartHandle = hideStartHandle ? null : _startHandleLayer;
|
effectiveStartHandle = hideStartHandle ? null : _startHandleLayer;
|
||||||
effectiveEndHandle = hideEndHandle ? null : _endHandleLayer;
|
effectiveEndHandle = hideEndHandle ? null : _endHandleLayer;
|
||||||
}
|
}
|
||||||
@ -2047,6 +2092,34 @@ abstract class MultiSelectableSelectionContainerDelegate extends SelectionContai
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Clears the selection on all selectables not in the range of
|
||||||
|
// currentSelectionStartIndex..currentSelectionEndIndex.
|
||||||
|
//
|
||||||
|
// If one of the edges does not exist, then this method will clear the selection
|
||||||
|
// in all selectables except the existing edge.
|
||||||
|
//
|
||||||
|
// If neither of the edges exist this method immediately returns.
|
||||||
|
void _flushInactiveSelections() {
|
||||||
|
if (currentSelectionStartIndex == -1 && currentSelectionEndIndex == -1) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (currentSelectionStartIndex == -1 || currentSelectionEndIndex == -1) {
|
||||||
|
final int skipIndex = currentSelectionStartIndex == -1 ? currentSelectionEndIndex : currentSelectionStartIndex;
|
||||||
|
selectables
|
||||||
|
.where((Selectable target) => target != selectables[skipIndex])
|
||||||
|
.forEach((Selectable target) => dispatchSelectionEventToChild(target, const ClearSelectionEvent()));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
final int skipStart = min(currentSelectionStartIndex, currentSelectionEndIndex);
|
||||||
|
final int skipEnd = max(currentSelectionStartIndex, currentSelectionEndIndex);
|
||||||
|
for (int index = 0; index < selectables.length; index += 1) {
|
||||||
|
if (index >= skipStart && index <= skipEnd) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
dispatchSelectionEventToChild(selectables[index], const ClearSelectionEvent());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Selects all contents of all selectables.
|
/// Selects all contents of all selectables.
|
||||||
@protected
|
@protected
|
||||||
SelectionResult handleSelectAll(SelectAllSelectionEvent event) {
|
SelectionResult handleSelectAll(SelectAllSelectionEvent event) {
|
||||||
@ -2290,7 +2363,7 @@ abstract class MultiSelectableSelectionContainerDelegate extends SelectionContai
|
|||||||
bool hasFoundEdgeIndex = false;
|
bool hasFoundEdgeIndex = false;
|
||||||
SelectionResult? result;
|
SelectionResult? result;
|
||||||
for (int index = 0; index < selectables.length && !hasFoundEdgeIndex; index += 1) {
|
for (int index = 0; index < selectables.length && !hasFoundEdgeIndex; index += 1) {
|
||||||
final Selectable child = selectables[index];
|
final Selectable child = selectables[index];
|
||||||
final SelectionResult childResult = dispatchSelectionEventToChild(child, event);
|
final SelectionResult childResult = dispatchSelectionEventToChild(child, event);
|
||||||
switch (childResult) {
|
switch (childResult) {
|
||||||
case SelectionResult.next:
|
case SelectionResult.next:
|
||||||
@ -2323,6 +2396,7 @@ abstract class MultiSelectableSelectionContainerDelegate extends SelectionContai
|
|||||||
} else {
|
} else {
|
||||||
currentSelectionStartIndex = newIndex;
|
currentSelectionStartIndex = newIndex;
|
||||||
}
|
}
|
||||||
|
_flushInactiveSelections();
|
||||||
// The result can only be null if the loop went through the entire list
|
// The result can only be null if the loop went through the entire list
|
||||||
// without any of the selection returned end or previous. In this case, the
|
// without any of the selection returned end or previous. In this case, the
|
||||||
// caller of this method needs to find the next selectable in their list.
|
// caller of this method needs to find the next selectable in their list.
|
||||||
@ -2345,13 +2419,39 @@ abstract class MultiSelectableSelectionContainerDelegate extends SelectionContai
|
|||||||
return true;
|
return true;
|
||||||
}());
|
}());
|
||||||
SelectionResult? finalResult;
|
SelectionResult? finalResult;
|
||||||
int newIndex = isEnd ? currentSelectionEndIndex : currentSelectionStartIndex;
|
// Determines if the edge being adjusted is within the current viewport.
|
||||||
|
// - If so, we begin the search for the new selection edge position at the
|
||||||
|
// currentSelectionEndIndex/currentSelectionStartIndex.
|
||||||
|
// - If not, we attempt to locate the new selection edge starting from
|
||||||
|
// the opposite end.
|
||||||
|
// - If neither edge is in the current viewport, the search for the new
|
||||||
|
// selection edge position begins at 0.
|
||||||
|
//
|
||||||
|
// This can happen when there is a scrollable child and the edge being adjusted
|
||||||
|
// has been scrolled out of view.
|
||||||
|
final bool isCurrentEdgeWithinViewport = isEnd ? _selectionGeometry.endSelectionPoint != null : _selectionGeometry.startSelectionPoint != null;
|
||||||
|
final bool isOppositeEdgeWithinViewport = isEnd ? _selectionGeometry.startSelectionPoint != null : _selectionGeometry.endSelectionPoint != null;
|
||||||
|
int newIndex = switch ((isEnd, isCurrentEdgeWithinViewport, isOppositeEdgeWithinViewport)) {
|
||||||
|
(true, true, true) => currentSelectionEndIndex,
|
||||||
|
(true, true, false) => currentSelectionEndIndex,
|
||||||
|
(true, false, true) => currentSelectionStartIndex,
|
||||||
|
(true, false, false) => 0,
|
||||||
|
(false, true, true) => currentSelectionStartIndex,
|
||||||
|
(false, true, false) => currentSelectionStartIndex,
|
||||||
|
(false, false, true) => currentSelectionEndIndex,
|
||||||
|
(false, false, false) => 0,
|
||||||
|
};
|
||||||
bool? forward;
|
bool? forward;
|
||||||
late SelectionResult currentSelectableResult;
|
late SelectionResult currentSelectableResult;
|
||||||
// This loop sends the selection event to the
|
// This loop sends the selection event to one of the following to determine
|
||||||
// currentSelectionEndIndex/currentSelectionStartIndex to determine the
|
// the direction of the search.
|
||||||
// direction of the search. If the result is `SelectionResult.next`, this
|
// - currentSelectionEndIndex/currentSelectionStartIndex if the current edge
|
||||||
// loop look backward. Otherwise, it looks forward.
|
// is in the current viewport.
|
||||||
|
// - The opposite edge index if the current edge is not in the current viewport.
|
||||||
|
// - Index 0 if neither edge is in the current viewport.
|
||||||
|
//
|
||||||
|
// If the result is `SelectionResult.next`, this loop look backward.
|
||||||
|
// Otherwise, it looks forward.
|
||||||
//
|
//
|
||||||
// The terminate condition are:
|
// The terminate condition are:
|
||||||
// 1. the selectable returns end, pending, none.
|
// 1. the selectable returns end, pending, none.
|
||||||
@ -2391,6 +2491,7 @@ abstract class MultiSelectableSelectionContainerDelegate extends SelectionContai
|
|||||||
} else {
|
} else {
|
||||||
currentSelectionStartIndex = newIndex;
|
currentSelectionStartIndex = newIndex;
|
||||||
}
|
}
|
||||||
|
_flushInactiveSelections();
|
||||||
return finalResult!;
|
return finalResult!;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -224,8 +224,14 @@ void main() {
|
|||||||
|
|
||||||
// Backwards selection.
|
// Backwards selection.
|
||||||
await gesture.down(textOffsetToPosition(paragraph, 3));
|
await gesture.down(textOffsetToPosition(paragraph, 3));
|
||||||
await tester.pumpAndSettle();
|
await tester.pump();
|
||||||
expect(content, isNull);
|
await gesture.up();
|
||||||
|
await tester.pumpAndSettle(kDoubleTapTimeout);
|
||||||
|
expect(content, isNotNull);
|
||||||
|
expect(content!.plainText, '');
|
||||||
|
|
||||||
|
await gesture.down(textOffsetToPosition(paragraph, 3));
|
||||||
|
await tester.pump();
|
||||||
await gesture.moveTo(textOffsetToPosition(paragraph, 0));
|
await gesture.moveTo(textOffsetToPosition(paragraph, 0));
|
||||||
await gesture.up();
|
await gesture.up();
|
||||||
await tester.pump();
|
await tester.pump();
|
||||||
|
@ -978,7 +978,8 @@ void main() {
|
|||||||
granularity: TextGranularity.word,
|
granularity: TextGranularity.word,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
expect(paragraph.selections.length, 0); // how []are you
|
expect(paragraph.selections.length, 1); // how []are you
|
||||||
|
expect(paragraph.selections[0], const TextSelection.collapsed(offset: 4));
|
||||||
|
|
||||||
// Equivalent to sending shift + alt + arrow-left.
|
// Equivalent to sending shift + alt + arrow-left.
|
||||||
registrar.selectables[0].dispatchSelectionEvent(
|
registrar.selectables[0].dispatchSelectionEvent(
|
||||||
|
@ -281,7 +281,7 @@ void main() {
|
|||||||
semantics.dispose();
|
semantics.dispose();
|
||||||
});
|
});
|
||||||
|
|
||||||
testWidgetsWithLeakTracking('mouse selection always cancels previous selection', (WidgetTester tester) async {
|
testWidgets('mouse single-click selection collapses the selection', (WidgetTester tester) async {
|
||||||
final UniqueKey spy = UniqueKey();
|
final UniqueKey spy = UniqueKey();
|
||||||
final FocusNode focusNode = FocusNode();
|
final FocusNode focusNode = FocusNode();
|
||||||
addTearDown(focusNode.dispose);
|
addTearDown(focusNode.dispose);
|
||||||
@ -300,9 +300,14 @@ void main() {
|
|||||||
final RenderSelectionSpy renderSelectionSpy = tester.renderObject<RenderSelectionSpy>(find.byKey(spy));
|
final RenderSelectionSpy renderSelectionSpy = tester.renderObject<RenderSelectionSpy>(find.byKey(spy));
|
||||||
final TestGesture gesture = await tester.startGesture(const Offset(200.0, 200.0), kind: PointerDeviceKind.mouse);
|
final TestGesture gesture = await tester.startGesture(const Offset(200.0, 200.0), kind: PointerDeviceKind.mouse);
|
||||||
addTearDown(gesture.removePointer);
|
addTearDown(gesture.removePointer);
|
||||||
|
await tester.pump();
|
||||||
|
await gesture.up();
|
||||||
await tester.pumpAndSettle();
|
await tester.pumpAndSettle();
|
||||||
expect(renderSelectionSpy.events.length, 1);
|
expect(renderSelectionSpy.events.length, 2);
|
||||||
expect(renderSelectionSpy.events[0], isA<ClearSelectionEvent>());
|
expect(renderSelectionSpy.events[0], isA<SelectionEdgeUpdateEvent>());
|
||||||
|
expect((renderSelectionSpy.events[0] as SelectionEdgeUpdateEvent).type, SelectionEventType.startEdgeUpdate);
|
||||||
|
expect(renderSelectionSpy.events[1], isA<SelectionEdgeUpdateEvent>());
|
||||||
|
expect((renderSelectionSpy.events[1] as SelectionEdgeUpdateEvent).type, SelectionEventType.endEdgeUpdate);
|
||||||
}, skip: kIsWeb); // https://github.com/flutter/flutter/issues/102410.
|
}, skip: kIsWeb); // https://github.com/flutter/flutter/issues/102410.
|
||||||
|
|
||||||
testWidgetsWithLeakTracking('touch long press sends select-word event', (WidgetTester tester) async {
|
testWidgetsWithLeakTracking('touch long press sends select-word event', (WidgetTester tester) async {
|
||||||
@ -474,7 +479,7 @@ void main() {
|
|||||||
await tester.pump(const Duration(milliseconds: 500));
|
await tester.pump(const Duration(milliseconds: 500));
|
||||||
await gesture.up();
|
await gesture.up();
|
||||||
expect(
|
expect(
|
||||||
renderSelectionSpy.events.every((SelectionEvent element) => element is ClearSelectionEvent),
|
renderSelectionSpy.events.every((SelectionEvent element) => element is SelectionEdgeUpdateEvent),
|
||||||
isTrue,
|
isTrue,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
@ -543,7 +548,7 @@ void main() {
|
|||||||
}, variant: TargetPlatformVariant.all());
|
}, variant: TargetPlatformVariant.all());
|
||||||
|
|
||||||
group('SelectionArea integration', () {
|
group('SelectionArea integration', () {
|
||||||
testWidgetsWithLeakTracking('mouse can select single text', (WidgetTester tester) async {
|
testWidgets('mouse can select single text on desktop platforms', (WidgetTester tester) async {
|
||||||
final FocusNode focusNode = FocusNode();
|
final FocusNode focusNode = FocusNode();
|
||||||
addTearDown(focusNode.dispose);
|
addTearDown(focusNode.dispose);
|
||||||
|
|
||||||
@ -574,13 +579,17 @@ void main() {
|
|||||||
// Check backward selection.
|
// Check backward selection.
|
||||||
await gesture.moveTo(textOffsetToPosition(paragraph, 1));
|
await gesture.moveTo(textOffsetToPosition(paragraph, 1));
|
||||||
await tester.pump();
|
await tester.pump();
|
||||||
|
expect(paragraph.selections.isEmpty, isFalse);
|
||||||
expect(paragraph.selections[0], const TextSelection(baseOffset: 2, extentOffset: 1));
|
expect(paragraph.selections[0], const TextSelection(baseOffset: 2, extentOffset: 1));
|
||||||
|
|
||||||
// Start a new drag.
|
// Start a new drag.
|
||||||
await gesture.up();
|
await gesture.up();
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
|
||||||
await gesture.down(textOffsetToPosition(paragraph, 5));
|
await gesture.down(textOffsetToPosition(paragraph, 5));
|
||||||
await tester.pumpAndSettle();
|
await tester.pumpAndSettle();
|
||||||
expect(paragraph.selections.isEmpty, isTrue);
|
expect(paragraph.selections.isEmpty, isFalse);
|
||||||
|
expect(paragraph.selections[0], const TextSelection.collapsed(offset: 5));
|
||||||
|
|
||||||
// Selecting across line should select to the end.
|
// Selecting across line should select to the end.
|
||||||
await gesture.moveTo(textOffsetToPosition(paragraph, 5) + const Offset(0.0, 200.0));
|
await gesture.moveTo(textOffsetToPosition(paragraph, 5) + const Offset(0.0, 200.0));
|
||||||
@ -588,7 +597,60 @@ void main() {
|
|||||||
expect(paragraph.selections[0], const TextSelection(baseOffset: 5, extentOffset: 11));
|
expect(paragraph.selections[0], const TextSelection(baseOffset: 5, extentOffset: 11));
|
||||||
|
|
||||||
await gesture.up();
|
await gesture.up();
|
||||||
});
|
}, variant: TargetPlatformVariant.desktop());
|
||||||
|
|
||||||
|
testWidgetsWithLeakTracking('mouse can select single text on mobile platforms', (WidgetTester tester) async {
|
||||||
|
final FocusNode focusNode = FocusNode();
|
||||||
|
addTearDown(focusNode.dispose);
|
||||||
|
|
||||||
|
await tester.pumpWidget(
|
||||||
|
MaterialApp(
|
||||||
|
home: SelectableRegion(
|
||||||
|
focusNode: focusNode,
|
||||||
|
selectionControls: materialTextSelectionControls,
|
||||||
|
child: const Center(
|
||||||
|
child: Text('How are you'),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
final RenderParagraph paragraph = tester.renderObject<RenderParagraph>(find.descendant(of: find.text('How are you'), matching: find.byType(RichText)));
|
||||||
|
final TestGesture gesture = await tester.startGesture(textOffsetToPosition(paragraph, 2), kind: PointerDeviceKind.mouse);
|
||||||
|
addTearDown(gesture.removePointer);
|
||||||
|
await tester.pump();
|
||||||
|
|
||||||
|
await gesture.moveTo(textOffsetToPosition(paragraph, 4));
|
||||||
|
await tester.pump();
|
||||||
|
expect(paragraph.selections[0], const TextSelection(baseOffset: 2, extentOffset: 4));
|
||||||
|
|
||||||
|
await gesture.moveTo(textOffsetToPosition(paragraph, 6));
|
||||||
|
await tester.pump();
|
||||||
|
expect(paragraph.selections[0], const TextSelection(baseOffset: 2, extentOffset: 6));
|
||||||
|
|
||||||
|
// Check backward selection.
|
||||||
|
await gesture.moveTo(textOffsetToPosition(paragraph, 1));
|
||||||
|
await tester.pump();
|
||||||
|
expect(paragraph.selections.isEmpty, isFalse);
|
||||||
|
expect(paragraph.selections[0], const TextSelection(baseOffset: 2, extentOffset: 1));
|
||||||
|
|
||||||
|
// Start a new drag.
|
||||||
|
await gesture.up();
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
|
||||||
|
await gesture.down(textOffsetToPosition(paragraph, 5));
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
await gesture.moveTo(textOffsetToPosition(paragraph, 6));
|
||||||
|
await tester.pump();
|
||||||
|
expect(paragraph.selections.isEmpty, isFalse);
|
||||||
|
expect(paragraph.selections[0], const TextSelection(baseOffset: 5, extentOffset: 6));
|
||||||
|
|
||||||
|
// Selecting across line should select to the end.
|
||||||
|
await gesture.moveTo(textOffsetToPosition(paragraph, 5) + const Offset(0.0, 200.0));
|
||||||
|
await tester.pump();
|
||||||
|
expect(paragraph.selections[0], const TextSelection(baseOffset: 5, extentOffset: 11));
|
||||||
|
|
||||||
|
await gesture.up();
|
||||||
|
}, variant: TargetPlatformVariant.mobile());
|
||||||
|
|
||||||
testWidgetsWithLeakTracking('mouse can select word-by-word on double click drag', (WidgetTester tester) async {
|
testWidgetsWithLeakTracking('mouse can select word-by-word on double click drag', (WidgetTester tester) async {
|
||||||
final FocusNode focusNode = FocusNode();
|
final FocusNode focusNode = FocusNode();
|
||||||
@ -643,7 +705,8 @@ void main() {
|
|||||||
await gesture.down(textOffsetToPosition(paragraph, 5));
|
await gesture.down(textOffsetToPosition(paragraph, 5));
|
||||||
await tester.pump();
|
await tester.pump();
|
||||||
await gesture.up();
|
await gesture.up();
|
||||||
expect(paragraph.selections.isEmpty, isTrue);
|
expect(paragraph.selections.isEmpty, isFalse);
|
||||||
|
expect(paragraph.selections[0], const TextSelection.collapsed(offset: 5));
|
||||||
await tester.pump(kDoubleTapTimeout);
|
await tester.pump(kDoubleTapTimeout);
|
||||||
|
|
||||||
// Double-click.
|
// Double-click.
|
||||||
@ -761,13 +824,13 @@ void main() {
|
|||||||
// Should clear the selection on paragraph 3.
|
// Should clear the selection on paragraph 3.
|
||||||
expect(paragraph1.selections[0], const TextSelection(baseOffset: 0, extentOffset: 12));
|
expect(paragraph1.selections[0], const TextSelection(baseOffset: 0, extentOffset: 12));
|
||||||
expect(paragraph2.selections[0], const TextSelection(baseOffset: 0, extentOffset: 6));
|
expect(paragraph2.selections[0], const TextSelection(baseOffset: 0, extentOffset: 6));
|
||||||
expect(paragraph3.selections.isEmpty, true);
|
expect(paragraph3.selections.isEmpty, isTrue);
|
||||||
|
|
||||||
await gesture.moveTo(textOffsetToPosition(paragraph1, 4));
|
await gesture.moveTo(textOffsetToPosition(paragraph1, 4));
|
||||||
// Should clear the selection on paragraph 2.
|
// Should clear the selection on paragraph 2.
|
||||||
expect(paragraph1.selections[0], const TextSelection(baseOffset: 0, extentOffset: 7));
|
expect(paragraph1.selections[0], const TextSelection(baseOffset: 0, extentOffset: 7));
|
||||||
expect(paragraph2.selections.isEmpty, true);
|
expect(paragraph2.selections.isEmpty, isTrue);
|
||||||
expect(paragraph3.selections.isEmpty, true);
|
expect(paragraph3.selections.isEmpty, isTrue);
|
||||||
|
|
||||||
await gesture.up();
|
await gesture.up();
|
||||||
}, skip: kIsWeb); // https://github.com/flutter/flutter/issues/125582.
|
}, skip: kIsWeb); // https://github.com/flutter/flutter/issues/125582.
|
||||||
@ -863,6 +926,52 @@ void main() {
|
|||||||
await gesture.up();
|
await gesture.up();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
testWidgetsWithLeakTracking('collapsing selection should clear selection of all other selectables', (WidgetTester tester) async {
|
||||||
|
final FocusNode focusNode = FocusNode();
|
||||||
|
addTearDown(focusNode.dispose);
|
||||||
|
|
||||||
|
await tester.pumpWidget(
|
||||||
|
MaterialApp(
|
||||||
|
home: SelectableRegion(
|
||||||
|
focusNode: focusNode,
|
||||||
|
selectionControls: materialTextSelectionControls,
|
||||||
|
child: const Column(
|
||||||
|
children: <Widget>[
|
||||||
|
Text('How are you?'),
|
||||||
|
Text('Good, and you?'),
|
||||||
|
Text('Fine, thank you.'),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
final RenderParagraph paragraph1 = tester.renderObject<RenderParagraph>(find.descendant(of: find.text('How are you?'), matching: find.byType(RichText)));
|
||||||
|
final TestGesture gesture = await tester.startGesture(textOffsetToPosition(paragraph1, 2), kind: PointerDeviceKind.mouse);
|
||||||
|
addTearDown(gesture.removePointer);
|
||||||
|
await tester.pump();
|
||||||
|
await gesture.up();
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
expect(paragraph1.selections[0], const TextSelection.collapsed(offset: 2));
|
||||||
|
|
||||||
|
final RenderParagraph paragraph2 = tester.renderObject<RenderParagraph>(find.descendant(of: find.text('Good, and you?'), matching: find.byType(RichText)));
|
||||||
|
await gesture.down(textOffsetToPosition(paragraph2, 5));
|
||||||
|
await tester.pump();
|
||||||
|
await gesture.up();
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
expect(paragraph1.selections.isEmpty, isTrue);
|
||||||
|
expect(paragraph2.selections[0], const TextSelection.collapsed(offset: 5));
|
||||||
|
|
||||||
|
final RenderParagraph paragraph3 = tester.renderObject<RenderParagraph>(find.descendant(of: find.text('Fine, thank you.'), matching: find.byType(RichText)));
|
||||||
|
await gesture.down(textOffsetToPosition(paragraph3, 13));
|
||||||
|
await tester.pump();
|
||||||
|
await gesture.up();
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
|
||||||
|
expect(paragraph1.selections.isEmpty, isTrue);
|
||||||
|
expect(paragraph2.selections.isEmpty, isTrue);
|
||||||
|
expect(paragraph3.selections[0], const TextSelection.collapsed(offset: 13));
|
||||||
|
});
|
||||||
|
|
||||||
testWidgetsWithLeakTracking('mouse can work with disabled container', (WidgetTester tester) async {
|
testWidgetsWithLeakTracking('mouse can work with disabled container', (WidgetTester tester) async {
|
||||||
final FocusNode focusNode = FocusNode();
|
final FocusNode focusNode = FocusNode();
|
||||||
addTearDown(focusNode.dispose);
|
addTearDown(focusNode.dispose);
|
||||||
@ -1108,10 +1217,11 @@ void main() {
|
|||||||
expect(buttonTypes, contains(ContextMenuButtonType.selectAll));
|
expect(buttonTypes, contains(ContextMenuButtonType.selectAll));
|
||||||
expect(find.byKey(toolbarKey), findsOneWidget);
|
expect(find.byKey(toolbarKey), findsOneWidget);
|
||||||
|
|
||||||
// Clear selection.
|
// Collapse selection.
|
||||||
await tester.tapAt(textOffsetToPosition(paragraph, 9));
|
await tester.tapAt(textOffsetToPosition(paragraph, 9));
|
||||||
await tester.pump();
|
await tester.pump();
|
||||||
expect(paragraph.selections.isEmpty, true);
|
expect(paragraph.selections.isEmpty, isFalse);
|
||||||
|
expect(paragraph.selections[0], const TextSelection.collapsed(offset: 9));
|
||||||
expect(find.byKey(toolbarKey), findsNothing);
|
expect(find.byKey(toolbarKey), findsNothing);
|
||||||
},
|
},
|
||||||
variant: TargetPlatformVariant.only(TargetPlatform.iOS),
|
variant: TargetPlatformVariant.only(TargetPlatform.iOS),
|
||||||
@ -1151,7 +1261,9 @@ void main() {
|
|||||||
expect(find.byKey(toolbarKey), findsNothing);
|
expect(find.byKey(toolbarKey), findsNothing);
|
||||||
|
|
||||||
final RenderParagraph paragraph = tester.renderObject<RenderParagraph>(find.descendant(of: find.text('How are you'), matching: find.byType(RichText)));
|
final RenderParagraph paragraph = tester.renderObject<RenderParagraph>(find.descendant(of: find.text('How are you'), matching: find.byType(RichText)));
|
||||||
|
final TestGesture primaryMouseButtonGesture = await tester.createGesture(kind: PointerDeviceKind.mouse);
|
||||||
final TestGesture gesture = await tester.startGesture(textOffsetToPosition(paragraph, 2), kind: PointerDeviceKind.mouse, buttons: kSecondaryMouseButton);
|
final TestGesture gesture = await tester.startGesture(textOffsetToPosition(paragraph, 2), kind: PointerDeviceKind.mouse, buttons: kSecondaryMouseButton);
|
||||||
|
addTearDown(primaryMouseButtonGesture.removePointer);
|
||||||
addTearDown(gesture.removePointer);
|
addTearDown(gesture.removePointer);
|
||||||
await tester.pump();
|
await tester.pump();
|
||||||
expect(paragraph.selections[0], const TextSelection(baseOffset: 0, extentOffset: 3));
|
expect(paragraph.selections[0], const TextSelection(baseOffset: 0, extentOffset: 3));
|
||||||
@ -1185,10 +1297,14 @@ void main() {
|
|||||||
expect(buttonTypes, contains(ContextMenuButtonType.selectAll));
|
expect(buttonTypes, contains(ContextMenuButtonType.selectAll));
|
||||||
expect(find.byKey(toolbarKey), findsOneWidget);
|
expect(find.byKey(toolbarKey), findsOneWidget);
|
||||||
|
|
||||||
// Clear selection.
|
// Collapse selection.
|
||||||
await tester.tapAt(textOffsetToPosition(paragraph, 1));
|
await primaryMouseButtonGesture.down(textOffsetToPosition(paragraph, 1));
|
||||||
await tester.pump();
|
await tester.pump();
|
||||||
expect(paragraph.selections.isEmpty, true);
|
await primaryMouseButtonGesture.up();
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
// Selection is collapsed.
|
||||||
|
expect(paragraph.selections.isEmpty, false);
|
||||||
|
expect(paragraph.selections[0], const TextSelection.collapsed(offset: 1));
|
||||||
expect(find.byKey(toolbarKey), findsNothing);
|
expect(find.byKey(toolbarKey), findsNothing);
|
||||||
},
|
},
|
||||||
variant: const TargetPlatformVariant(<TargetPlatform>{ TargetPlatform.iOS, TargetPlatform.macOS }),
|
variant: const TargetPlatformVariant(<TargetPlatform>{ TargetPlatform.iOS, TargetPlatform.macOS }),
|
||||||
@ -1229,6 +1345,8 @@ void main() {
|
|||||||
|
|
||||||
final RenderParagraph paragraph = tester.renderObject<RenderParagraph>(find.descendant(of: find.text('How are you'), matching: find.byType(RichText)));
|
final RenderParagraph paragraph = tester.renderObject<RenderParagraph>(find.descendant(of: find.text('How are you'), matching: find.byType(RichText)));
|
||||||
final TestGesture gesture = await tester.startGesture(textOffsetToPosition(paragraph, 2), kind: PointerDeviceKind.mouse, buttons: kSecondaryMouseButton);
|
final TestGesture gesture = await tester.startGesture(textOffsetToPosition(paragraph, 2), kind: PointerDeviceKind.mouse, buttons: kSecondaryMouseButton);
|
||||||
|
final TestGesture primaryMouseButtonGesture = await tester.createGesture(kind: PointerDeviceKind.mouse);
|
||||||
|
addTearDown(primaryMouseButtonGesture.removePointer);
|
||||||
addTearDown(gesture.removePointer);
|
addTearDown(gesture.removePointer);
|
||||||
await tester.pump();
|
await tester.pump();
|
||||||
expect(paragraph.selections[0], const TextSelection(baseOffset: 0, extentOffset: 3));
|
expect(paragraph.selections[0], const TextSelection(baseOffset: 0, extentOffset: 3));
|
||||||
@ -1286,10 +1404,14 @@ void main() {
|
|||||||
expect(buttonTypes, contains(ContextMenuButtonType.selectAll));
|
expect(buttonTypes, contains(ContextMenuButtonType.selectAll));
|
||||||
expect(find.byKey(toolbarKey), findsOneWidget);
|
expect(find.byKey(toolbarKey), findsOneWidget);
|
||||||
|
|
||||||
// Clear selection.
|
// Collapse selection.
|
||||||
await tester.tapAt(textOffsetToPosition(paragraph, 1));
|
await primaryMouseButtonGesture.down(textOffsetToPosition(paragraph, 1));
|
||||||
await tester.pump();
|
await tester.pump();
|
||||||
expect(paragraph.selections.isEmpty, true);
|
await primaryMouseButtonGesture.up();
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
// Selection is collapsed.
|
||||||
|
expect(paragraph.selections.isEmpty, false);
|
||||||
|
expect(paragraph.selections[0], const TextSelection.collapsed(offset: 1));
|
||||||
expect(find.byKey(toolbarKey), findsNothing);
|
expect(find.byKey(toolbarKey), findsNothing);
|
||||||
},
|
},
|
||||||
variant: TargetPlatformVariant.only(TargetPlatform.macOS),
|
variant: TargetPlatformVariant.only(TargetPlatform.macOS),
|
||||||
@ -1330,21 +1452,24 @@ void main() {
|
|||||||
|
|
||||||
final RenderParagraph paragraph = tester.renderObject<RenderParagraph>(find.descendant(of: find.text('How are you'), matching: find.byType(RichText)));
|
final RenderParagraph paragraph = tester.renderObject<RenderParagraph>(find.descendant(of: find.text('How are you'), matching: find.byType(RichText)));
|
||||||
final TestGesture gesture = await tester.startGesture(textOffsetToPosition(paragraph, 2), kind: PointerDeviceKind.mouse, buttons: kSecondaryMouseButton);
|
final TestGesture gesture = await tester.startGesture(textOffsetToPosition(paragraph, 2), kind: PointerDeviceKind.mouse, buttons: kSecondaryMouseButton);
|
||||||
|
final TestGesture primaryMouseButtonGesture = await tester.createGesture(kind: PointerDeviceKind.mouse);
|
||||||
|
addTearDown(primaryMouseButtonGesture.removePointer);
|
||||||
addTearDown(gesture.removePointer);
|
addTearDown(gesture.removePointer);
|
||||||
await tester.pump();
|
await tester.pump();
|
||||||
// Selection is collapsed so none is reported.
|
|
||||||
expect(paragraph.selections.isEmpty, true);
|
|
||||||
|
|
||||||
await gesture.up();
|
await gesture.up();
|
||||||
await tester.pump();
|
await tester.pump();
|
||||||
|
|
||||||
|
// Selection is collapsed.
|
||||||
|
expect(paragraph.selections.isEmpty, false);
|
||||||
|
expect(paragraph.selections[0], const TextSelection.collapsed(offset: 2));
|
||||||
expect(buttonTypes.length, 1);
|
expect(buttonTypes.length, 1);
|
||||||
expect(buttonTypes, contains(ContextMenuButtonType.selectAll));
|
expect(buttonTypes, contains(ContextMenuButtonType.selectAll));
|
||||||
expect(find.byKey(toolbarKey), findsOneWidget);
|
expect(find.byKey(toolbarKey), findsOneWidget);
|
||||||
|
|
||||||
await gesture.down(textOffsetToPosition(paragraph, 6));
|
await gesture.down(textOffsetToPosition(paragraph, 6));
|
||||||
await tester.pump();
|
await tester.pump();
|
||||||
expect(paragraph.selections.isEmpty, true);
|
expect(paragraph.selections.isEmpty, false);
|
||||||
|
expect(paragraph.selections[0], const TextSelection.collapsed(offset: 6));
|
||||||
|
|
||||||
await gesture.up();
|
await gesture.up();
|
||||||
await tester.pump();
|
await tester.pump();
|
||||||
@ -1355,7 +1480,8 @@ void main() {
|
|||||||
|
|
||||||
await gesture.down(textOffsetToPosition(paragraph, 9));
|
await gesture.down(textOffsetToPosition(paragraph, 9));
|
||||||
await tester.pump();
|
await tester.pump();
|
||||||
expect(paragraph.selections.isEmpty, true);
|
expect(paragraph.selections.isEmpty, false);
|
||||||
|
expect(paragraph.selections[0], const TextSelection.collapsed(offset: 9));
|
||||||
|
|
||||||
await gesture.up();
|
await gesture.up();
|
||||||
await tester.pump();
|
await tester.pump();
|
||||||
@ -1364,20 +1490,23 @@ void main() {
|
|||||||
expect(buttonTypes, contains(ContextMenuButtonType.selectAll));
|
expect(buttonTypes, contains(ContextMenuButtonType.selectAll));
|
||||||
expect(find.byKey(toolbarKey), findsOneWidget);
|
expect(find.byKey(toolbarKey), findsOneWidget);
|
||||||
|
|
||||||
// Clear selection.
|
// Collapse selection.
|
||||||
await tester.tapAt(textOffsetToPosition(paragraph, 1));
|
await primaryMouseButtonGesture.down(textOffsetToPosition(paragraph, 1));
|
||||||
await tester.pump();
|
await tester.pump();
|
||||||
expect(paragraph.selections.isEmpty, true);
|
await primaryMouseButtonGesture.up();
|
||||||
|
await tester.pumpAndSettle(kDoubleTapTimeout);
|
||||||
|
// Selection is collapsed.
|
||||||
|
expect(paragraph.selections.isEmpty, false);
|
||||||
|
expect(paragraph.selections[0], const TextSelection.collapsed(offset: 1));
|
||||||
expect(find.byKey(toolbarKey), findsNothing);
|
expect(find.byKey(toolbarKey), findsNothing);
|
||||||
|
|
||||||
// Create an uncollapsed selection by dragging.
|
// Create an uncollapsed selection by dragging.
|
||||||
final TestGesture dragGesture = await tester.startGesture(textOffsetToPosition(paragraph, 0), kind: PointerDeviceKind.mouse);
|
await primaryMouseButtonGesture.down(textOffsetToPosition(paragraph, 0));
|
||||||
addTearDown(dragGesture.removePointer);
|
|
||||||
await tester.pump();
|
await tester.pump();
|
||||||
await dragGesture.moveTo(textOffsetToPosition(paragraph, 5));
|
await primaryMouseButtonGesture.moveTo(textOffsetToPosition(paragraph, 5));
|
||||||
await tester.pump();
|
await tester.pump();
|
||||||
expect(paragraph.selections[0], const TextSelection(baseOffset: 0, extentOffset: 5));
|
expect(paragraph.selections[0], const TextSelection(baseOffset: 0, extentOffset: 5));
|
||||||
await dragGesture.up();
|
await primaryMouseButtonGesture.up();
|
||||||
await tester.pump();
|
await tester.pump();
|
||||||
|
|
||||||
// Right click on previous selection should not collapse the selection.
|
// Right click on previous selection should not collapse the selection.
|
||||||
@ -1394,13 +1523,18 @@ void main() {
|
|||||||
await tester.pump();
|
await tester.pump();
|
||||||
await gesture.up();
|
await gesture.up();
|
||||||
await tester.pump();
|
await tester.pump();
|
||||||
expect(paragraph.selections.isEmpty, true);
|
expect(paragraph.selections.isEmpty, false);
|
||||||
|
expect(paragraph.selections[0], const TextSelection.collapsed(offset: 7));
|
||||||
expect(find.byKey(toolbarKey), findsOneWidget);
|
expect(find.byKey(toolbarKey), findsOneWidget);
|
||||||
|
|
||||||
// Clear selection.
|
// Collapse selection.
|
||||||
await tester.tapAt(textOffsetToPosition(paragraph, 1));
|
await primaryMouseButtonGesture.down(textOffsetToPosition(paragraph, 1));
|
||||||
await tester.pump();
|
await tester.pump();
|
||||||
expect(paragraph.selections.isEmpty, true);
|
await primaryMouseButtonGesture.up();
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
// Selection is collapsed.
|
||||||
|
expect(paragraph.selections.isEmpty, false);
|
||||||
|
expect(paragraph.selections[0], const TextSelection.collapsed(offset: 1));
|
||||||
expect(find.byKey(toolbarKey), findsNothing);
|
expect(find.byKey(toolbarKey), findsNothing);
|
||||||
},
|
},
|
||||||
variant: const TargetPlatformVariant(<TargetPlatform>{ TargetPlatform.android, TargetPlatform.fuchsia, TargetPlatform.windows }),
|
variant: const TargetPlatformVariant(<TargetPlatform>{ TargetPlatform.android, TargetPlatform.fuchsia, TargetPlatform.windows }),
|
||||||
@ -1441,13 +1575,15 @@ void main() {
|
|||||||
|
|
||||||
final RenderParagraph paragraph = tester.renderObject<RenderParagraph>(find.descendant(of: find.text('How are you'), matching: find.byType(RichText)));
|
final RenderParagraph paragraph = tester.renderObject<RenderParagraph>(find.descendant(of: find.text('How are you'), matching: find.byType(RichText)));
|
||||||
final TestGesture gesture = await tester.startGesture(textOffsetToPosition(paragraph, 2), kind: PointerDeviceKind.mouse, buttons: kSecondaryMouseButton);
|
final TestGesture gesture = await tester.startGesture(textOffsetToPosition(paragraph, 2), kind: PointerDeviceKind.mouse, buttons: kSecondaryMouseButton);
|
||||||
|
final TestGesture primaryMouseButtonGesture = await tester.createGesture(kind: PointerDeviceKind.mouse);
|
||||||
|
addTearDown(primaryMouseButtonGesture.removePointer);
|
||||||
addTearDown(gesture.removePointer);
|
addTearDown(gesture.removePointer);
|
||||||
await tester.pump();
|
await tester.pump();
|
||||||
// Selection is collapsed so none is reported.
|
|
||||||
expect(paragraph.selections.isEmpty, true);
|
|
||||||
|
|
||||||
await gesture.up();
|
await gesture.up();
|
||||||
await tester.pump();
|
await tester.pump();
|
||||||
|
// Selection is collapsed.
|
||||||
|
expect(paragraph.selections.isEmpty, false);
|
||||||
|
expect(paragraph.selections[0], const TextSelection.collapsed(offset: 2));
|
||||||
|
|
||||||
// Context menu toggled on.
|
// Context menu toggled on.
|
||||||
expect(buttonTypes.length, 1);
|
expect(buttonTypes.length, 1);
|
||||||
@ -1456,17 +1592,18 @@ void main() {
|
|||||||
|
|
||||||
await gesture.down(textOffsetToPosition(paragraph, 6));
|
await gesture.down(textOffsetToPosition(paragraph, 6));
|
||||||
await tester.pump();
|
await tester.pump();
|
||||||
expect(paragraph.selections.isEmpty, true);
|
|
||||||
|
|
||||||
await gesture.up();
|
await gesture.up();
|
||||||
await tester.pump();
|
await tester.pump();
|
||||||
|
expect(paragraph.selections.isEmpty, false);
|
||||||
|
expect(paragraph.selections[0], const TextSelection.collapsed(offset: 2));
|
||||||
|
|
||||||
// Context menu toggled off.
|
// Context menu toggled off. Selection remains the same.
|
||||||
expect(find.byKey(toolbarKey), findsNothing);
|
expect(find.byKey(toolbarKey), findsNothing);
|
||||||
|
|
||||||
await gesture.down(textOffsetToPosition(paragraph, 9));
|
await gesture.down(textOffsetToPosition(paragraph, 9));
|
||||||
await tester.pump();
|
await tester.pump();
|
||||||
expect(paragraph.selections.isEmpty, true);
|
expect(paragraph.selections.isEmpty, false);
|
||||||
|
expect(paragraph.selections[0], const TextSelection.collapsed(offset: 9));
|
||||||
|
|
||||||
await gesture.up();
|
await gesture.up();
|
||||||
await tester.pump();
|
await tester.pump();
|
||||||
@ -1476,19 +1613,22 @@ void main() {
|
|||||||
expect(buttonTypes, contains(ContextMenuButtonType.selectAll));
|
expect(buttonTypes, contains(ContextMenuButtonType.selectAll));
|
||||||
expect(find.byKey(toolbarKey), findsOneWidget);
|
expect(find.byKey(toolbarKey), findsOneWidget);
|
||||||
|
|
||||||
// Clear selection.
|
// Collapse selection.
|
||||||
await tester.tapAt(textOffsetToPosition(paragraph, 1));
|
await primaryMouseButtonGesture.down(textOffsetToPosition(paragraph, 1));
|
||||||
await tester.pump();
|
await tester.pump();
|
||||||
expect(paragraph.selections.isEmpty, true);
|
await primaryMouseButtonGesture.up();
|
||||||
|
await tester.pumpAndSettle(kDoubleTapTimeout);
|
||||||
|
// Selection is collapsed.
|
||||||
|
expect(paragraph.selections.isEmpty, false);
|
||||||
|
expect(paragraph.selections[0], const TextSelection.collapsed(offset: 1));
|
||||||
expect(find.byKey(toolbarKey), findsNothing);
|
expect(find.byKey(toolbarKey), findsNothing);
|
||||||
|
|
||||||
final TestGesture dragGesture = await tester.startGesture(textOffsetToPosition(paragraph, 0), kind: PointerDeviceKind.mouse);
|
await primaryMouseButtonGesture.down(textOffsetToPosition(paragraph, 0));
|
||||||
addTearDown(dragGesture.removePointer);
|
|
||||||
await tester.pump();
|
await tester.pump();
|
||||||
await dragGesture.moveTo(textOffsetToPosition(paragraph, 5));
|
await primaryMouseButtonGesture.moveTo(textOffsetToPosition(paragraph, 5));
|
||||||
await tester.pump();
|
await tester.pump();
|
||||||
expect(paragraph.selections[0], const TextSelection(baseOffset: 0, extentOffset: 5));
|
expect(paragraph.selections[0], const TextSelection(baseOffset: 0, extentOffset: 5));
|
||||||
await dragGesture.up();
|
await primaryMouseButtonGesture.up();
|
||||||
await tester.pump();
|
await tester.pump();
|
||||||
|
|
||||||
// Right click on previous selection should not collapse the selection.
|
// Right click on previous selection should not collapse the selection.
|
||||||
@ -1514,13 +1654,18 @@ void main() {
|
|||||||
await tester.pump();
|
await tester.pump();
|
||||||
await gesture.up();
|
await gesture.up();
|
||||||
await tester.pump();
|
await tester.pump();
|
||||||
expect(paragraph.selections.isEmpty, true);
|
expect(paragraph.selections.isEmpty, false);
|
||||||
|
expect(paragraph.selections[0], const TextSelection.collapsed(offset: 7));
|
||||||
expect(find.byKey(toolbarKey), findsOneWidget);
|
expect(find.byKey(toolbarKey), findsOneWidget);
|
||||||
|
|
||||||
// Clear selection.
|
// Collapse selection.
|
||||||
await tester.tapAt(textOffsetToPosition(paragraph, 1));
|
await primaryMouseButtonGesture.down(textOffsetToPosition(paragraph, 1));
|
||||||
await tester.pump();
|
await tester.pump();
|
||||||
expect(paragraph.selections.isEmpty, true);
|
await primaryMouseButtonGesture.up();
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
// Selection is collapsed.
|
||||||
|
expect(paragraph.selections.isEmpty, false);
|
||||||
|
expect(paragraph.selections[0], const TextSelection.collapsed(offset: 1));
|
||||||
expect(find.byKey(toolbarKey), findsNothing);
|
expect(find.byKey(toolbarKey), findsNothing);
|
||||||
},
|
},
|
||||||
variant: TargetPlatformVariant.only(TargetPlatform.linux),
|
variant: TargetPlatformVariant.only(TargetPlatform.linux),
|
||||||
@ -2414,7 +2559,9 @@ void main() {
|
|||||||
expect(paragraph1.selections.length, 1);
|
expect(paragraph1.selections.length, 1);
|
||||||
expect(paragraph1.selections[0].start, 2);
|
expect(paragraph1.selections[0].start, 2);
|
||||||
expect(paragraph1.selections[0].end, 12);
|
expect(paragraph1.selections[0].end, 12);
|
||||||
expect(paragraph2.selections.length, 0);
|
expect(paragraph2.selections.length, 1);
|
||||||
|
expect(paragraph2.selections[0].start, 0);
|
||||||
|
expect(paragraph2.selections[0].end, 0);
|
||||||
|
|
||||||
await sendKeyCombination(tester, SingleActivator(LogicalKeyboardKey.arrowLeft, shift: true, alt: alt, control: control));
|
await sendKeyCombination(tester, SingleActivator(LogicalKeyboardKey.arrowLeft, shift: true, alt: alt, control: control));
|
||||||
await tester.pump();
|
await tester.pump();
|
||||||
@ -2424,7 +2571,9 @@ void main() {
|
|||||||
expect(paragraph1.selections.length, 1);
|
expect(paragraph1.selections.length, 1);
|
||||||
expect(paragraph1.selections[0].start, 2);
|
expect(paragraph1.selections[0].start, 2);
|
||||||
expect(paragraph1.selections[0].end, 8);
|
expect(paragraph1.selections[0].end, 8);
|
||||||
expect(paragraph2.selections.length, 0);
|
expect(paragraph2.selections.length, 1);
|
||||||
|
expect(paragraph2.selections[0].start, 0);
|
||||||
|
expect(paragraph2.selections[0].end, 0);
|
||||||
}, variant: TargetPlatformVariant.all());
|
}, variant: TargetPlatformVariant.all());
|
||||||
|
|
||||||
testWidgetsWithLeakTracking('can use keyboard to granularly extend selection - line', (WidgetTester tester) async {
|
testWidgetsWithLeakTracking('can use keyboard to granularly extend selection - line', (WidgetTester tester) async {
|
||||||
@ -2507,7 +2656,9 @@ void main() {
|
|||||||
expect(paragraph1.selections.length, 1);
|
expect(paragraph1.selections.length, 1);
|
||||||
expect(paragraph1.selections[0].start, 2);
|
expect(paragraph1.selections[0].start, 2);
|
||||||
expect(paragraph1.selections[0].end, 12);
|
expect(paragraph1.selections[0].end, 12);
|
||||||
expect(paragraph2.selections.length, 0);
|
expect(paragraph2.selections.length, 1);
|
||||||
|
expect(paragraph2.selections[0].start, 0);
|
||||||
|
expect(paragraph2.selections[0].end, 0);
|
||||||
|
|
||||||
await sendKeyCombination(tester, SingleActivator(LogicalKeyboardKey.arrowLeft, shift: true, alt: alt, meta: meta));
|
await sendKeyCombination(tester, SingleActivator(LogicalKeyboardKey.arrowLeft, shift: true, alt: alt, meta: meta));
|
||||||
await tester.pump();
|
await tester.pump();
|
||||||
@ -2594,8 +2745,12 @@ void main() {
|
|||||||
expect(paragraph1.selections.length, 1);
|
expect(paragraph1.selections.length, 1);
|
||||||
expect(paragraph1.selections[0].start, 0);
|
expect(paragraph1.selections[0].start, 0);
|
||||||
expect(paragraph1.selections[0].end, 2);
|
expect(paragraph1.selections[0].end, 2);
|
||||||
expect(paragraph2.selections.length, 0);
|
expect(paragraph2.selections.length, 1);
|
||||||
expect(paragraph3.selections.length, 0);
|
expect(paragraph2.selections[0].start, 0);
|
||||||
|
expect(paragraph2.selections[0].end, 0);
|
||||||
|
expect(paragraph3.selections.length, 1);
|
||||||
|
expect(paragraph3.selections[0].start, 0);
|
||||||
|
expect(paragraph3.selections[0].end, 0);
|
||||||
}, variant: TargetPlatformVariant.all());
|
}, variant: TargetPlatformVariant.all());
|
||||||
|
|
||||||
testWidgetsWithLeakTracking('can use keyboard to directionally extend selection', (WidgetTester tester) async {
|
testWidgetsWithLeakTracking('can use keyboard to directionally extend selection', (WidgetTester tester) async {
|
||||||
@ -2666,7 +2821,9 @@ void main() {
|
|||||||
expect(paragraph2.selections.length, 1);
|
expect(paragraph2.selections.length, 1);
|
||||||
expect(paragraph2.selections[0].start, 2);
|
expect(paragraph2.selections[0].start, 2);
|
||||||
expect(paragraph2.selections[0].end, 6);
|
expect(paragraph2.selections[0].end, 6);
|
||||||
expect(paragraph3.selections.length, 0);
|
expect(paragraph3.selections.length, 1);
|
||||||
|
expect(paragraph3.selections[0].start, 0);
|
||||||
|
expect(paragraph3.selections[0].end, 0);
|
||||||
|
|
||||||
await sendKeyCombination(tester, const SingleActivator(LogicalKeyboardKey.arrowUp, shift: true));
|
await sendKeyCombination(tester, const SingleActivator(LogicalKeyboardKey.arrowUp, shift: true));
|
||||||
await tester.pump();
|
await tester.pump();
|
||||||
@ -3017,8 +3174,14 @@ void main() {
|
|||||||
|
|
||||||
// Backwards selection.
|
// Backwards selection.
|
||||||
await mouseGesture.down(textOffsetToPosition(paragraph, 3));
|
await mouseGesture.down(textOffsetToPosition(paragraph, 3));
|
||||||
await tester.pumpAndSettle();
|
await tester.pump();
|
||||||
expect(content, isNull);
|
await mouseGesture.up();
|
||||||
|
await tester.pumpAndSettle(kDoubleTapTimeout);
|
||||||
|
expect(content, isNotNull);
|
||||||
|
expect(content!.plainText, '');
|
||||||
|
|
||||||
|
await mouseGesture.down(textOffsetToPosition(paragraph, 3));
|
||||||
|
await tester.pump();
|
||||||
|
|
||||||
await mouseGesture.moveTo(textOffsetToPosition(paragraph, 0));
|
await mouseGesture.moveTo(textOffsetToPosition(paragraph, 0));
|
||||||
await tester.pumpAndSettle();
|
await tester.pumpAndSettle();
|
||||||
@ -3045,9 +3208,10 @@ void main() {
|
|||||||
// Called on tap.
|
// Called on tap.
|
||||||
await mouseGesture.down(textOffsetToPosition(paragraph, 0));
|
await mouseGesture.down(textOffsetToPosition(paragraph, 0));
|
||||||
await tester.pumpAndSettle();
|
await tester.pumpAndSettle();
|
||||||
expect(content, isNull);
|
|
||||||
await mouseGesture.up();
|
await mouseGesture.up();
|
||||||
await tester.pumpAndSettle();
|
await tester.pumpAndSettle(kDoubleTapTimeout);
|
||||||
|
expect(content, isNotNull);
|
||||||
|
expect(content!.plainText, '');
|
||||||
|
|
||||||
// With touch gestures.
|
// With touch gestures.
|
||||||
|
|
||||||
@ -3224,7 +3388,7 @@ void main() {
|
|||||||
expect(paragraph2.selections.length, 1);
|
expect(paragraph2.selections.length, 1);
|
||||||
expect(paragraph2.selections[0].start, 0);
|
expect(paragraph2.selections[0].start, 0);
|
||||||
expect(paragraph2.selections[0].end, 8);
|
expect(paragraph2.selections[0].end, 8);
|
||||||
expect(paragraph3.selections.length, 0);
|
expect(paragraph3.selections.length, 1);
|
||||||
expect(content, isNotNull);
|
expect(content, isNotNull);
|
||||||
expect(content!.plainText, 'w are you?Good, an');
|
expect(content!.plainText, 'w are you?Good, an');
|
||||||
|
|
||||||
@ -3233,8 +3397,8 @@ void main() {
|
|||||||
expect(paragraph1.selections.length, 1);
|
expect(paragraph1.selections.length, 1);
|
||||||
expect(paragraph1.selections[0].start, 2);
|
expect(paragraph1.selections[0].start, 2);
|
||||||
expect(paragraph1.selections[0].end, 7);
|
expect(paragraph1.selections[0].end, 7);
|
||||||
expect(paragraph2.selections.length, 0);
|
expect(paragraph2.selections.length, 1);
|
||||||
expect(paragraph3.selections.length, 0);
|
expect(paragraph3.selections.length, 1);
|
||||||
expect(content, isNotNull);
|
expect(content, isNotNull);
|
||||||
expect(content!.plainText, 'w are');
|
expect(content!.plainText, 'w are');
|
||||||
|
|
||||||
@ -3243,8 +3407,8 @@ void main() {
|
|||||||
expect(paragraph1.selections.length, 1);
|
expect(paragraph1.selections.length, 1);
|
||||||
expect(paragraph1.selections[0].start, 0);
|
expect(paragraph1.selections[0].start, 0);
|
||||||
expect(paragraph1.selections[0].end, 2);
|
expect(paragraph1.selections[0].end, 2);
|
||||||
expect(paragraph2.selections.length, 0);
|
expect(paragraph2.selections.length, 1);
|
||||||
expect(paragraph3.selections.length, 0);
|
expect(paragraph3.selections.length, 1);
|
||||||
expect(content, isNotNull);
|
expect(content, isNotNull);
|
||||||
expect(content!.plainText, 'Ho');
|
expect(content!.plainText, 'Ho');
|
||||||
});
|
});
|
||||||
|
Loading…
Reference in New Issue
Block a user