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(
List<InlineSpanSemanticsInformation> 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<SelectableRegion>
@@ -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<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.
if (kIsWeb) {
_focusNode.unfocus();
@@ -558,7 +655,7 @@ index 59de8bae20b..9ce4810aa68 100644
}
},
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
/// the main implementation details of this [SelectionContainerDelegate].
class StaticSelectionContainerDelegate extends MultiSelectableSelectionContainerDelegate {
@@ -570,26 +667,7 @@ index 59de8bae20b..9ce4810aa68 100644
/// The set of [Selectable]s that have received start events.
final Set<Selectable> _hasReceivedStartEvent = <Selectable>{};
@@ -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<Selectable> selectables = <Selectable>[];
@@ -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