diff --git a/lib/scripts/selectable_region.patch b/lib/scripts/selectable_region.patch index b0abb9bc1..14bb40f0e 100644 --- a/lib/scripts/selectable_region.patch +++ b/lib/scripts/selectable_region.patch @@ -119,10 +119,18 @@ index 09fba7fc4fb..57ee469358e 100644 void computeSemanticsInformation( List collector, { diff --git a/packages/flutter/lib/src/rendering/paragraph.dart b/packages/flutter/lib/src/rendering/paragraph.dart -index 97a41dafb8d..266dd6f0283 100644 +index 97a41dafb8d..15e7ce055e9 100644 --- a/packages/flutter/lib/src/rendering/paragraph.dart +++ b/packages/flutter/lib/src/rendering/paragraph.dart -@@ -502,27 +502,17 @@ class RenderParagraph extends RenderBox +@@ -7,6 +7,7 @@ + /// @docImport 'editable.dart'; + library; + ++import 'dart:io' show Platform; + import 'dart:math' as math; + import 'dart:ui' + as ui +@@ -502,27 +503,17 @@ class RenderParagraph extends RenderBox } List<_SelectableFragment> _getSelectableFragments() { @@ -161,7 +169,7 @@ index 97a41dafb8d..266dd6f0283 100644 } /// Determines whether the given [Selectable] was created by this -@@ -1467,7 +1457,7 @@ class RenderParagraph extends RenderBox +@@ -1467,7 +1458,7 @@ class RenderParagraph extends RenderBox class _SelectableFragment with Selectable, Diagnosticable, ChangeNotifier implements TextLayoutMetrics { @@ -170,7 +178,7 @@ index 97a41dafb8d..266dd6f0283 100644 : assert(range.isValid && !range.isCollapsed && range.isNormalized) { if (kFlutterMemoryAllocationsEnabled) { ChangeNotifier.maybeDispatchObjectCreation(this); -@@ -1478,6 +1468,7 @@ class _SelectableFragment +@@ -1478,6 +1469,7 @@ class _SelectableFragment final TextRange range; final RenderParagraph paragraph; final String fullText; @@ -178,7 +186,7 @@ index 97a41dafb8d..266dd6f0283 100644 TextPosition? _textSelectionStart; TextPosition? _textSelectionEnd; -@@ -1509,18 +1500,36 @@ class _SelectableFragment +@@ -1509,18 +1501,36 @@ class _SelectableFragment final int selectionStart = _textSelectionStart!.offset; final int selectionEnd = _textSelectionEnd!.offset; final bool isReversed = selectionStart > selectionEnd; @@ -222,11 +230,12 @@ index 97a41dafb8d..266dd6f0283 100644 final selectionCollapsed = selectionStart == selectionEnd; final ( TextSelectionHandleType startSelectionHandleType, -@@ -1564,12 +1573,14 @@ class _SelectableFragment +@@ -1564,12 +1574,15 @@ class _SelectableFragment result = _updateSelectionEdge( edgeUpdate.globalPosition, isEnd: edgeUpdate.type == SelectionEventType.endEdgeUpdate, + isSingle: edgeUpdate.isSingle, ++ fromDrag: edgeUpdate.fromDrag, ); case TextGranularity.word: result = _updateSelectionEdgeByTextBoundary( @@ -237,7 +246,7 @@ index 97a41dafb8d..266dd6f0283 100644 ); case TextGranularity.paragraph: result = _updateSelectionEdgeByMultiSelectableTextBoundary( -@@ -1626,9 +1637,33 @@ class _SelectableFragment +@@ -1626,9 +1639,33 @@ class _SelectableFragment if (_textSelectionStart == null || _textSelectionEnd == null) { return null; } @@ -273,7 +282,7 @@ index 97a41dafb8d..266dd6f0283 100644 } @override -@@ -1848,6 +1883,7 @@ class _SelectableFragment +@@ -1848,6 +1885,7 @@ class _SelectableFragment Offset globalPosition, { required bool isEnd, required _TextBoundaryAtPosition getTextBoundary, @@ -281,7 +290,7 @@ index 97a41dafb8d..266dd6f0283 100644 }) { // When the start/end edges are swapped, i.e. the start is after the end, and // the scrollable synthesizes an event for the opposite edge, this will potentially -@@ -1866,6 +1902,7 @@ class _SelectableFragment +@@ -1866,6 +1904,7 @@ class _SelectableFragment _rect, localPosition, direction: paragraph.textDirection, @@ -289,25 +298,26 @@ index 97a41dafb8d..266dd6f0283 100644 ); final TextPosition position = paragraph.getPositionForOffset(adjustedOffset); -@@ -1920,7 +1957,7 @@ class _SelectableFragment +@@ -1920,7 +1959,7 @@ class _SelectableFragment return SelectionUtils.getResultBasedOnRect(_rect, localPosition); } - SelectionResult _updateSelectionEdge(Offset globalPosition, {required bool isEnd}) { -+ SelectionResult _updateSelectionEdge(Offset globalPosition, {required bool isEnd, bool isSingle = false}) { ++ SelectionResult _updateSelectionEdge(Offset globalPosition, {required bool isEnd, bool isSingle = false, bool fromDrag = false}) { _setSelectionPosition(null, isEnd: isEnd); final Matrix4 transform = paragraph.getTransformTo(null); transform.invert(); -@@ -1932,11 +1969,21 @@ class _SelectableFragment +@@ -1932,11 +1971,30 @@ class _SelectableFragment _rect, localPosition, direction: paragraph.textDirection, + isSingle: isSingle, ); - final TextPosition position = _clampTextPosition( - paragraph.getPositionForOffset(adjustedOffset), - ); +- final TextPosition position = _clampTextPosition( +- paragraph.getPositionForOffset(adjustedOffset), +- ); ++ TextPosition position = _clampTextPosition(paragraph.getPositionForOffset(adjustedOffset)); + // if (rawText != null) { + // position = _clampTextPosition( + // adjustedOffset.dx * 2 > _rect.left + _rect.right @@ -317,10 +327,21 @@ index 97a41dafb8d..266dd6f0283 100644 + // } else { + // position = _clampTextPosition(paragraph.getPositionForOffset(adjustedOffset)); + // } ++ if (fromDrag && isSingle && !Platform.isIOS) { ++ if (isEnd) { ++ if (position.offset <= _textSelectionStart!.offset) { ++ position = TextPosition(offset: _textSelectionStart!.offset + 1); ++ } ++ } else { ++ if (position.offset >= _textSelectionEnd!.offset) { ++ position = TextPosition(offset: _textSelectionEnd!.offset - 1); ++ } ++ } ++ } _setSelectionPosition(position, isEnd: isEnd); if (position.offset == range.end) { return SelectionResult.next; -@@ -3568,7 +3615,10 @@ class _SelectableFragment +@@ -3568,7 +3626,10 @@ class _SelectableFragment final selectionPaint = Paint() ..style = PaintingStyle.fill ..color = paragraph.selectionColor!; @@ -333,7 +354,7 @@ index 97a41dafb8d..266dd6f0283 100644 } } diff --git a/packages/flutter/lib/src/rendering/selection.dart b/packages/flutter/lib/src/rendering/selection.dart -index a813e141dc4..e5ef2103645 100644 +index a813e141dc4..d045236d031 100644 --- a/packages/flutter/lib/src/rendering/selection.dart +++ b/packages/flutter/lib/src/rendering/selection.dart @@ -6,6 +6,8 @@ @@ -383,7 +404,7 @@ index a813e141dc4..e5ef2103645 100644 if (point.dy <= targetRect.top || point.dy <= targetRect.bottom && point.dx <= targetRect.left) { // Area 1 -@@ -520,7 +535,7 @@ class SelectionEdgeUpdateEvent extends SelectionEvent { +@@ -520,9 +535,10 @@ class SelectionEdgeUpdateEvent extends SelectionEvent { /// /// The [granularity] contains the granularity which the selection edge should move by. /// This value defaults to [TextGranularity.character]. @@ -391,8 +412,11 @@ index a813e141dc4..e5ef2103645 100644 + SelectionEdgeUpdateEvent.forStart({ required this.globalPosition, TextGranularity? granularity, ++ this.fromDrag = false, }) : granularity = granularity ?? TextGranularity.character, -@@ -532,7 +547,7 @@ class SelectionEdgeUpdateEvent extends SelectionEvent { + super._(SelectionEventType.startEdgeUpdate); + +@@ -532,9 +548,10 @@ class SelectionEdgeUpdateEvent extends SelectionEvent { /// /// The [granularity] contains the granularity which the selection edge should move by. /// This value defaults to [TextGranularity.character]. @@ -400,18 +424,23 @@ index a813e141dc4..e5ef2103645 100644 + SelectionEdgeUpdateEvent.forEnd({ required this.globalPosition, TextGranularity? granularity, ++ this.fromDrag = false, }) : granularity = granularity ?? TextGranularity.character, -@@ -547,6 +562,8 @@ class SelectionEdgeUpdateEvent extends SelectionEvent { + super._(SelectionEventType.endEdgeUpdate); + +@@ -547,6 +564,10 @@ class SelectionEdgeUpdateEvent extends SelectionEvent { /// /// Defaults to [TextGranularity.character]. final TextGranularity granularity; + + bool isSingle = false; ++ ++ final bool fromDrag; } /// Extends the start or end of the selection by a given [TextGranularity]. diff --git a/packages/flutter/lib/src/widgets/scrollable.dart b/packages/flutter/lib/src/widgets/scrollable.dart -index 9df2d633f16..54d30883245 100644 +index 9df2d633f16..c6a43cc97a3 100644 --- a/packages/flutter/lib/src/widgets/scrollable.dart +++ b/packages/flutter/lib/src/widgets/scrollable.dart @@ -1177,6 +1177,9 @@ class _ScrollableSelectionContainerDelegate extends MultiSelectableSelectionCont @@ -424,8 +453,24 @@ index 9df2d633f16..54d30883245 100644 // Pointer drag is a single point, it should not have a size. static const double _kDefaultDragTargetSize = 0; +@@ -1276,6 +1279,7 @@ class _ScrollableSelectionContainerDelegate extends MultiSelectableSelectionCont + event = SelectionEdgeUpdateEvent.forEnd( + globalPosition: endOffset, + granularity: event.granularity, ++ fromDrag: event.fromDrag, + ); + } else { + _currentDragStartRelatedToOrigin = _inferPositionRelatedToOrigin(event.globalPosition); +@@ -1286,6 +1290,7 @@ class _ScrollableSelectionContainerDelegate extends MultiSelectableSelectionCont + event = SelectionEdgeUpdateEvent.forStart( + globalPosition: startOffset, + granularity: event.granularity, ++ fromDrag: event.fromDrag, + ); + } + final SelectionResult result = super.handleSelectionEdgeUpdate(event); diff --git a/packages/flutter/lib/src/widgets/selectable_region.dart b/packages/flutter/lib/src/widgets/selectable_region.dart -index 59de8bae20b..9ce4810aa68 100644 +index 59de8bae20b..2084a40b24d 100644 --- a/packages/flutter/lib/src/widgets/selectable_region.dart +++ b/packages/flutter/lib/src/widgets/selectable_region.dart @@ -401,9 +401,11 @@ class SelectableRegionState extends State @@ -549,7 +594,59 @@ index 59de8bae20b..9ce4810aa68 100644 // Restore _lastSecondaryTapDownPosition since it may be cleared if a user // accesses contextMenuAnchors. _lastSecondaryTapDownPosition = details.globalPosition; -@@ -1957,6 +1929,8 @@ class SelectableRegionState extends State +@@ -1105,7 +1077,7 @@ class SelectableRegionState extends State + /// If the selectable subtree returns a [SelectionResult.pending], this method + /// continues to send [SelectionEdgeUpdateEvent]s every frame until the result + /// is not pending or users end their gestures. +- void _triggerSelectionEndEdgeUpdate({TextGranularity? textGranularity}) { ++ void _triggerSelectionEndEdgeUpdate({TextGranularity? textGranularity, bool fromDrag = false}) { + // This method can be called when the drag is not in progress. This can + // happen if the child scrollable returns SelectionResult.pending, and + // the selection area scheduled a selection update for the next frame, but +@@ -1117,6 +1089,7 @@ class SelectableRegionState extends State + SelectionEdgeUpdateEvent.forEnd( + globalPosition: _selectionEndPosition!, + granularity: textGranularity, ++ fromDrag: fromDrag, + ), + ) == + SelectionResult.pending) { +@@ -1159,7 +1132,7 @@ class SelectableRegionState extends State + /// If the selectable subtree returns a [SelectionResult.pending], this method + /// continues to send [SelectionEdgeUpdateEvent]s every frame until the result + /// is not pending or users end their gestures. +- void _triggerSelectionStartEdgeUpdate({TextGranularity? textGranularity}) { ++ void _triggerSelectionStartEdgeUpdate({TextGranularity? textGranularity, bool fromDrag = false}) { + // This method can be called when the drag is not in progress. This can + // happen if the child scrollable returns SelectionResult.pending, and + // the selection area scheduled a selection update for the next frame, but +@@ -1171,6 +1144,7 @@ class SelectableRegionState extends State + SelectionEdgeUpdateEvent.forStart( + globalPosition: _selectionStartPosition!, + granularity: textGranularity, ++ fromDrag: fromDrag, + ), + ) == + SelectionResult.pending) { +@@ -1216,7 +1190,7 @@ class SelectableRegionState extends State + _selectionStartPosition = + _selectionStartHandleDragPosition - + Offset(0, _selectionDelegate.value.startSelectionPoint!.lineHeight / 2); +- _triggerSelectionStartEdgeUpdate(); ++ _triggerSelectionStartEdgeUpdate(fromDrag: true); + + _selectionOverlay!.updateMagnifier( + _buildInfoForMagnifier(details.globalPosition, _selectionDelegate.value.startSelectionPoint!), +@@ -1244,7 +1218,7 @@ class SelectableRegionState extends State + _selectionEndPosition = + _selectionEndHandleDragPosition - + Offset(0, _selectionDelegate.value.endSelectionPoint!.lineHeight / 2); +- _triggerSelectionEndEdgeUpdate(); ++ _triggerSelectionEndEdgeUpdate(fromDrag: true); + + _selectionOverlay!.updateMagnifier( + _buildInfoForMagnifier(details.globalPosition, _selectionDelegate.value.endSelectionPoint!), +@@ -1957,6 +1931,8 @@ class SelectableRegionState extends State // the region on non-web platforms. if (kIsWeb) { _focusNode.unfocus(); @@ -558,7 +655,7 @@ index 59de8bae20b..9ce4810aa68 100644 } }, child: CompositedTransformTarget( -@@ -2087,6 +2061,11 @@ class _DirectionallyExtendCaretSelectionAction _hasReceivedStartEvent = {}; -@@ -2271,6 +2250,18 @@ class StaticSelectionContainerDelegate extends MultiSelectableSelectionContainer - super.dispose(); - } - -+ void handleSelectionEdgeUpdateEvent(SelectionEdgeUpdateEvent event) { -+ event.isSingle = isSingle; -+ } -+ -+ @override -+ SelectionResult dispatchSelectionEvent(SelectionEvent event) { -+ if (event is SelectionEdgeUpdateEvent) { -+ handleSelectionEdgeUpdateEvent(event); -+ } -+ return super.dispatchSelectionEvent(event); -+ } -+ - @override - SelectionResult dispatchSelectionEventToChild(Selectable selectable, SelectionEvent event) { - switch (event.type) { -@@ -2367,6 +2358,8 @@ abstract class MultiSelectableSelectionContainerDelegate extends SelectionContai +@@ -2367,6 +2348,8 @@ abstract class MultiSelectableSelectionContainerDelegate extends SelectionContai /// Gets the list of [Selectable]s this delegate is managing. List selectables = []; @@ -598,7 +676,7 @@ index 59de8bae20b..9ce4810aa68 100644 /// The number of additional pixels added to the selection handle drawable /// area. /// -@@ -2822,8 +2815,17 @@ abstract class MultiSelectableSelectionContainerDelegate extends SelectionContai +@@ -2822,8 +2805,17 @@ abstract class MultiSelectableSelectionContainerDelegate extends SelectionContai return null; } final buffer = StringBuffer(); @@ -618,6 +696,25 @@ index 59de8bae20b..9ce4810aa68 100644 } return SelectedContent(plainText: buffer.toString()); } +@@ -3129,6 +3121,7 @@ abstract class MultiSelectableSelectionContainerDelegate extends SelectionContai + /// Updates the selection edges. + @protected + SelectionResult handleSelectionEdgeUpdate(SelectionEdgeUpdateEvent event) { ++ handleSelectionEdgeUpdateEvent(event); + if (event.type == SelectionEventType.endEdgeUpdate) { + return currentSelectionEndIndex == -1 + ? _initSelection(event, isEnd: true) +@@ -3139,6 +3132,10 @@ abstract class MultiSelectableSelectionContainerDelegate extends SelectionContai + : _adjustSelection(event, isEnd: false); + } + ++ void handleSelectionEdgeUpdateEvent(SelectionEdgeUpdateEvent event) { ++ event.isSingle = isSingle; ++ } ++ + @override + SelectionResult dispatchSelectionEvent(SelectionEvent event) { + final selectionWillBeInProgress = event is! ClearSelectionEvent; diff --git a/packages/flutter/lib/src/widgets/selection_container.dart b/packages/flutter/lib/src/widgets/selection_container.dart index 5d5a2386a7a..92e7d1eb70a 100644 --- a/packages/flutter/lib/src/widgets/selection_container.dart