opt text selection

Signed-off-by: dom <githubaccount56556@proton.me>
This commit is contained in:
dom
2026-07-28 12:14:13 +08:00
parent e4e70370d1
commit 91e7899858

View File

@@ -119,10 +119,18 @@ index 09fba7fc4fb..57ee469358e 100644
void computeSemanticsInformation( void computeSemanticsInformation(
List<InlineSpanSemanticsInformation> collector, { List<InlineSpanSemanticsInformation> collector, {
diff --git a/packages/flutter/lib/src/rendering/paragraph.dart b/packages/flutter/lib/src/rendering/paragraph.dart 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 --- a/packages/flutter/lib/src/rendering/paragraph.dart
+++ b/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() { List<_SelectableFragment> _getSelectableFragments() {
@@ -161,7 +169,7 @@ index 97a41dafb8d..266dd6f0283 100644
} }
/// Determines whether the given [Selectable] was created by this /// 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 class _SelectableFragment
with Selectable, Diagnosticable, ChangeNotifier with Selectable, Diagnosticable, ChangeNotifier
implements TextLayoutMetrics { implements TextLayoutMetrics {
@@ -170,7 +178,7 @@ index 97a41dafb8d..266dd6f0283 100644
: assert(range.isValid && !range.isCollapsed && range.isNormalized) { : assert(range.isValid && !range.isCollapsed && range.isNormalized) {
if (kFlutterMemoryAllocationsEnabled) { if (kFlutterMemoryAllocationsEnabled) {
ChangeNotifier.maybeDispatchObjectCreation(this); ChangeNotifier.maybeDispatchObjectCreation(this);
@@ -1478,6 +1468,7 @@ class _SelectableFragment @@ -1478,6 +1469,7 @@ class _SelectableFragment
final TextRange range; final TextRange range;
final RenderParagraph paragraph; final RenderParagraph paragraph;
final String fullText; final String fullText;
@@ -178,7 +186,7 @@ index 97a41dafb8d..266dd6f0283 100644
TextPosition? _textSelectionStart; TextPosition? _textSelectionStart;
TextPosition? _textSelectionEnd; TextPosition? _textSelectionEnd;
@@ -1509,18 +1500,36 @@ class _SelectableFragment @@ -1509,18 +1501,36 @@ class _SelectableFragment
final int selectionStart = _textSelectionStart!.offset; final int selectionStart = _textSelectionStart!.offset;
final int selectionEnd = _textSelectionEnd!.offset; final int selectionEnd = _textSelectionEnd!.offset;
final bool isReversed = selectionStart > selectionEnd; final bool isReversed = selectionStart > selectionEnd;
@@ -222,11 +230,12 @@ index 97a41dafb8d..266dd6f0283 100644
final selectionCollapsed = selectionStart == selectionEnd; final selectionCollapsed = selectionStart == selectionEnd;
final ( final (
TextSelectionHandleType startSelectionHandleType, TextSelectionHandleType startSelectionHandleType,
@@ -1564,12 +1573,14 @@ class _SelectableFragment @@ -1564,12 +1574,15 @@ class _SelectableFragment
result = _updateSelectionEdge( result = _updateSelectionEdge(
edgeUpdate.globalPosition, edgeUpdate.globalPosition,
isEnd: edgeUpdate.type == SelectionEventType.endEdgeUpdate, isEnd: edgeUpdate.type == SelectionEventType.endEdgeUpdate,
+ isSingle: edgeUpdate.isSingle, + isSingle: edgeUpdate.isSingle,
+ fromDrag: edgeUpdate.fromDrag,
); );
case TextGranularity.word: case TextGranularity.word:
result = _updateSelectionEdgeByTextBoundary( result = _updateSelectionEdgeByTextBoundary(
@@ -237,7 +246,7 @@ index 97a41dafb8d..266dd6f0283 100644
); );
case TextGranularity.paragraph: case TextGranularity.paragraph:
result = _updateSelectionEdgeByMultiSelectableTextBoundary( result = _updateSelectionEdgeByMultiSelectableTextBoundary(
@@ -1626,9 +1637,33 @@ class _SelectableFragment @@ -1626,9 +1639,33 @@ class _SelectableFragment
if (_textSelectionStart == null || _textSelectionEnd == null) { if (_textSelectionStart == null || _textSelectionEnd == null) {
return null; return null;
} }
@@ -273,7 +282,7 @@ index 97a41dafb8d..266dd6f0283 100644
} }
@override @override
@@ -1848,6 +1883,7 @@ class _SelectableFragment @@ -1848,6 +1885,7 @@ class _SelectableFragment
Offset globalPosition, { Offset globalPosition, {
required bool isEnd, required bool isEnd,
required _TextBoundaryAtPosition getTextBoundary, 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 // 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 // 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, _rect,
localPosition, localPosition,
direction: paragraph.textDirection, direction: paragraph.textDirection,
@@ -289,25 +298,26 @@ index 97a41dafb8d..266dd6f0283 100644
); );
final TextPosition position = paragraph.getPositionForOffset(adjustedOffset); final TextPosition position = paragraph.getPositionForOffset(adjustedOffset);
@@ -1920,7 +1957,7 @@ class _SelectableFragment @@ -1920,7 +1959,7 @@ class _SelectableFragment
return SelectionUtils.getResultBasedOnRect(_rect, localPosition); return SelectionUtils.getResultBasedOnRect(_rect, localPosition);
} }
- SelectionResult _updateSelectionEdge(Offset globalPosition, {required bool isEnd}) { - 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); _setSelectionPosition(null, isEnd: isEnd);
final Matrix4 transform = paragraph.getTransformTo(null); final Matrix4 transform = paragraph.getTransformTo(null);
transform.invert(); transform.invert();
@@ -1932,11 +1969,21 @@ class _SelectableFragment @@ -1932,11 +1971,30 @@ class _SelectableFragment
_rect, _rect,
localPosition, localPosition,
direction: paragraph.textDirection, direction: paragraph.textDirection,
+ isSingle: isSingle, + isSingle: isSingle,
); );
final TextPosition position = _clampTextPosition( - final TextPosition position = _clampTextPosition(
paragraph.getPositionForOffset(adjustedOffset), - paragraph.getPositionForOffset(adjustedOffset),
); - );
+ TextPosition position = _clampTextPosition(paragraph.getPositionForOffset(adjustedOffset));
+ // if (rawText != null) { + // if (rawText != null) {
+ // position = _clampTextPosition( + // position = _clampTextPosition(
+ // adjustedOffset.dx * 2 > _rect.left + _rect.right + // adjustedOffset.dx * 2 > _rect.left + _rect.right
@@ -317,10 +327,21 @@ index 97a41dafb8d..266dd6f0283 100644
+ // } else { + // } else {
+ // position = _clampTextPosition(paragraph.getPositionForOffset(adjustedOffset)); + // 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); _setSelectionPosition(position, isEnd: isEnd);
if (position.offset == range.end) { if (position.offset == range.end) {
return SelectionResult.next; return SelectionResult.next;
@@ -3568,7 +3615,10 @@ class _SelectableFragment @@ -3568,7 +3626,10 @@ class _SelectableFragment
final selectionPaint = Paint() final selectionPaint = Paint()
..style = PaintingStyle.fill ..style = PaintingStyle.fill
..color = paragraph.selectionColor!; ..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 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 --- a/packages/flutter/lib/src/rendering/selection.dart
+++ b/packages/flutter/lib/src/rendering/selection.dart +++ b/packages/flutter/lib/src/rendering/selection.dart
@@ -6,6 +6,8 @@ @@ -6,6 +6,8 @@
@@ -383,7 +404,7 @@ index a813e141dc4..e5ef2103645 100644
if (point.dy <= targetRect.top || if (point.dy <= targetRect.top ||
point.dy <= targetRect.bottom && point.dx <= targetRect.left) { point.dy <= targetRect.bottom && point.dx <= targetRect.left) {
// Area 1 // 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. /// The [granularity] contains the granularity which the selection edge should move by.
/// This value defaults to [TextGranularity.character]. /// This value defaults to [TextGranularity.character].
@@ -391,8 +412,11 @@ index a813e141dc4..e5ef2103645 100644
+ SelectionEdgeUpdateEvent.forStart({ + SelectionEdgeUpdateEvent.forStart({
required this.globalPosition, required this.globalPosition,
TextGranularity? granularity, TextGranularity? granularity,
+ this.fromDrag = false,
}) : granularity = granularity ?? TextGranularity.character, }) : 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. /// The [granularity] contains the granularity which the selection edge should move by.
/// This value defaults to [TextGranularity.character]. /// This value defaults to [TextGranularity.character].
@@ -400,18 +424,23 @@ index a813e141dc4..e5ef2103645 100644
+ SelectionEdgeUpdateEvent.forEnd({ + SelectionEdgeUpdateEvent.forEnd({
required this.globalPosition, required this.globalPosition,
TextGranularity? granularity, TextGranularity? granularity,
+ this.fromDrag = false,
}) : granularity = granularity ?? TextGranularity.character, }) : 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]. /// Defaults to [TextGranularity.character].
final TextGranularity granularity; final TextGranularity granularity;
+ +
+ bool isSingle = false; + bool isSingle = false;
+
+ final bool fromDrag;
} }
/// Extends the start or end of the selection by a given [TextGranularity]. /// 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 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 --- a/packages/flutter/lib/src/widgets/scrollable.dart
+++ b/packages/flutter/lib/src/widgets/scrollable.dart +++ b/packages/flutter/lib/src/widgets/scrollable.dart
@@ -1177,6 +1177,9 @@ class _ScrollableSelectionContainerDelegate extends MultiSelectableSelectionCont @@ -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. // Pointer drag is a single point, it should not have a size.
static const double _kDefaultDragTargetSize = 0; 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 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 --- a/packages/flutter/lib/src/widgets/selectable_region.dart
+++ b/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<SelectableRegion> @@ -401,9 +401,11 @@ class SelectableRegionState extends State<SelectableRegion>
@@ -549,7 +594,59 @@ index 59de8bae20b..9ce4810aa68 100644
// Restore _lastSecondaryTapDownPosition since it may be cleared if a user // Restore _lastSecondaryTapDownPosition since it may be cleared if a user
// accesses contextMenuAnchors. // accesses contextMenuAnchors.
_lastSecondaryTapDownPosition = details.globalPosition; _lastSecondaryTapDownPosition = details.globalPosition;
@@ -1957,6 +1929,8 @@ class SelectableRegionState extends State<SelectableRegion> @@ -1105,7 +1077,7 @@ class SelectableRegionState extends State<SelectableRegion>
/// 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<SelectableRegion>
SelectionEdgeUpdateEvent.forEnd(
globalPosition: _selectionEndPosition!,
granularity: textGranularity,
+ fromDrag: fromDrag,
),
) ==
SelectionResult.pending) {
@@ -1159,7 +1132,7 @@ class SelectableRegionState extends State<SelectableRegion>
/// 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<SelectableRegion>
SelectionEdgeUpdateEvent.forStart(
globalPosition: _selectionStartPosition!,
granularity: textGranularity,
+ fromDrag: fromDrag,
),
) ==
SelectionResult.pending) {
@@ -1216,7 +1190,7 @@ class SelectableRegionState extends State<SelectableRegion>
_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<SelectableRegion>
_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<SelectableRegion>
// the region on non-web platforms. // the region on non-web platforms.
if (kIsWeb) { if (kIsWeb) {
_focusNode.unfocus(); _focusNode.unfocus();
@@ -558,7 +655,7 @@ index 59de8bae20b..9ce4810aa68 100644
} }
}, },
child: CompositedTransformTarget( child: CompositedTransformTarget(
@@ -2087,6 +2061,11 @@ class _DirectionallyExtendCaretSelectionAction<T extends DirectionalCaretMovemen @@ -2087,6 +2063,11 @@ class _DirectionallyExtendCaretSelectionAction<T extends DirectionalCaretMovemen
/// * [MultiSelectableSelectionContainerDelegate], for the class that provides /// * [MultiSelectableSelectionContainerDelegate], for the class that provides
/// the main implementation details of this [SelectionContainerDelegate]. /// the main implementation details of this [SelectionContainerDelegate].
class StaticSelectionContainerDelegate extends MultiSelectableSelectionContainerDelegate { class StaticSelectionContainerDelegate extends MultiSelectableSelectionContainerDelegate {
@@ -570,26 +667,7 @@ index 59de8bae20b..9ce4810aa68 100644
/// The set of [Selectable]s that have received start events. /// The set of [Selectable]s that have received start events.
final Set<Selectable> _hasReceivedStartEvent = <Selectable>{}; final Set<Selectable> _hasReceivedStartEvent = <Selectable>{};
@@ -2271,6 +2250,18 @@ class StaticSelectionContainerDelegate extends MultiSelectableSelectionContainer @@ -2367,6 +2348,8 @@ abstract class MultiSelectableSelectionContainerDelegate extends SelectionContai
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
/// Gets the list of [Selectable]s this delegate is managing. /// Gets the list of [Selectable]s this delegate is managing.
List<Selectable> selectables = <Selectable>[]; List<Selectable> selectables = <Selectable>[];
@@ -598,7 +676,7 @@ index 59de8bae20b..9ce4810aa68 100644
/// The number of additional pixels added to the selection handle drawable /// The number of additional pixels added to the selection handle drawable
/// area. /// area.
/// ///
@@ -2822,8 +2815,17 @@ abstract class MultiSelectableSelectionContainerDelegate extends SelectionContai @@ -2822,8 +2805,17 @@ abstract class MultiSelectableSelectionContainerDelegate extends SelectionContai
return null; return null;
} }
final buffer = StringBuffer(); final buffer = StringBuffer();
@@ -618,6 +696,25 @@ index 59de8bae20b..9ce4810aa68 100644
} }
return SelectedContent(plainText: buffer.toString()); 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 diff --git a/packages/flutter/lib/src/widgets/selection_container.dart b/packages/flutter/lib/src/widgets/selection_container.dart
index 5d5a2386a7a..92e7d1eb70a 100644 index 5d5a2386a7a..92e7d1eb70a 100644
--- a/packages/flutter/lib/src/widgets/selection_container.dart --- a/packages/flutter/lib/src/widgets/selection_container.dart