Unverified Commit 21ad7122 authored by Renzo Olivares's avatar Renzo Olivares Committed by GitHub

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
parent 80fb7bd2
...@@ -24,6 +24,9 @@ void main() { ...@@ -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() { ...@@ -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 ...@@ -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 ...@@ -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 ...@@ -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 ...@@ -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 ...@@ -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> { ...@@ -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;
......
...@@ -224,8 +224,14 @@ void main() { ...@@ -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() { ...@@ -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(
......
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