@@ -409,7 +409,13 @@ class RenderParagraph extends RenderBox with ContainerRenderObjectMixin<RenderBo
409
409
if (end == - 1 ) {
410
410
end = plainText.length;
411
411
}
412
- result.add (_SelectableFragment (paragraph: this , range: TextRange (start: start, end: end), fullText: plainText));
412
+ result.add (
413
+ _SelectableFragment (
414
+ paragraph: this ,
415
+ range: TextRange (start: start, end: end),
416
+ fullText: plainText,
417
+ ),
418
+ );
413
419
start = end;
414
420
}
415
421
start += 1 ;
@@ -1314,7 +1320,7 @@ class RenderParagraph extends RenderBox with ContainerRenderObjectMixin<RenderBo
1314
1320
/// [PlaceholderSpan] . The [RenderParagraph] splits itself on [PlaceholderSpan]
1315
1321
/// to create multiple `_SelectableFragment` s so that they can be selected
1316
1322
/// separately.
1317
- class _SelectableFragment with Selectable , ChangeNotifier implements TextLayoutMetrics {
1323
+ class _SelectableFragment with Selectable , Diagnosticable , ChangeNotifier implements TextLayoutMetrics {
1318
1324
_SelectableFragment ({
1319
1325
required this .paragraph,
1320
1326
required this .fullText,
@@ -1366,7 +1372,6 @@ class _SelectableFragment with Selectable, ChangeNotifier implements TextLayoutM
1366
1372
? startOffsetInParagraphCoordinates
1367
1373
: paragraph._getOffsetForPosition (TextPosition (offset: selectionEnd));
1368
1374
final bool flipHandles = isReversed != (TextDirection .rtl == paragraph.textDirection);
1369
- final Matrix4 paragraphToFragmentTransform = getTransformToParagraph ()..invert ();
1370
1375
final TextSelection selection = TextSelection (
1371
1376
baseOffset: selectionStart,
1372
1377
extentOffset: selectionEnd,
@@ -1377,12 +1382,12 @@ class _SelectableFragment with Selectable, ChangeNotifier implements TextLayoutM
1377
1382
}
1378
1383
return SelectionGeometry (
1379
1384
startSelectionPoint: SelectionPoint (
1380
- localPosition: MatrixUtils . transformPoint (paragraphToFragmentTransform, startOffsetInParagraphCoordinates) ,
1385
+ localPosition: startOffsetInParagraphCoordinates,
1381
1386
lineHeight: paragraph._textPainter.preferredLineHeight,
1382
1387
handleType: flipHandles ? TextSelectionHandleType .right : TextSelectionHandleType .left
1383
1388
),
1384
1389
endSelectionPoint: SelectionPoint (
1385
- localPosition: MatrixUtils . transformPoint (paragraphToFragmentTransform, endOffsetInParagraphCoordinates) ,
1390
+ localPosition: endOffsetInParagraphCoordinates,
1386
1391
lineHeight: paragraph._textPainter.preferredLineHeight,
1387
1392
handleType: flipHandles ? TextSelectionHandleType .left : TextSelectionHandleType .right,
1388
1393
),
@@ -1665,7 +1670,16 @@ class _SelectableFragment with Selectable, ChangeNotifier implements TextLayoutM
1665
1670
// we do not need to look up the word boundary for that position. This is to
1666
1671
// maintain a selectables selection collapsed at 0 when the local position is
1667
1672
// not located inside its rect.
1668
- final _WordBoundaryRecord ? wordBoundary = ! _rect.contains (localPosition) ? null : _getWordBoundaryAtPosition (position);
1673
+ _WordBoundaryRecord ? wordBoundary = _rect.contains (localPosition) ? _getWordBoundaryAtPosition (position) : null ;
1674
+ if (wordBoundary != null
1675
+ && (wordBoundary.wordStart.offset < range.start && wordBoundary.wordEnd.offset <= range.start
1676
+ || wordBoundary.wordStart.offset >= range.end && wordBoundary.wordEnd.offset > range.end)) {
1677
+ // When the position is located at a placeholder inside of the text, then we may compute
1678
+ // a word boundary that does not belong to the current selectable fragment. In this case
1679
+ // we should invalidate the word boundary so that it is not taken into account when
1680
+ // computing the target position.
1681
+ wordBoundary = null ;
1682
+ }
1669
1683
final TextPosition targetPosition = _clampTextPosition (isEnd ? _updateSelectionEndEdgeByWord (wordBoundary, position, existingSelectionStart, existingSelectionEnd) : _updateSelectionStartEdgeByWord (wordBoundary, position, existingSelectionStart, existingSelectionEnd));
1670
1684
1671
1685
_setSelectionPosition (targetPosition, isEnd: isEnd);
@@ -1717,23 +1731,26 @@ class _SelectableFragment with Selectable, ChangeNotifier implements TextLayoutM
1717
1731
}
1718
1732
1719
1733
SelectionResult _handleSelectWord (Offset globalPosition) {
1720
- _selectableContainsOriginWord = true ;
1721
-
1722
1734
final TextPosition position = paragraph.getPositionForOffset (paragraph.globalToLocal (globalPosition));
1723
1735
if (_positionIsWithinCurrentSelection (position) && _textSelectionStart != _textSelectionEnd) {
1724
1736
return SelectionResult .end;
1725
1737
}
1726
1738
final _WordBoundaryRecord wordBoundary = _getWordBoundaryAtPosition (position);
1727
- if (wordBoundary.wordStart.offset < range.start && wordBoundary.wordEnd.offset < range.start) {
1739
+ // This fragment may not contain the word, decide what direction the target
1740
+ // fragment is located in. Because fragments are separated by placeholder
1741
+ // spans, we also check if the beginning or end of the word is touching
1742
+ // either edge of this fragment.
1743
+ if (wordBoundary.wordStart.offset < range.start && wordBoundary.wordEnd.offset <= range.start) {
1728
1744
return SelectionResult .previous;
1729
- } else if (wordBoundary.wordStart.offset > range.end && wordBoundary.wordEnd.offset > range.end) {
1745
+ } else if (wordBoundary.wordStart.offset >= range.end && wordBoundary.wordEnd.offset > range.end) {
1730
1746
return SelectionResult .next;
1731
1747
}
1732
1748
// Fragments are separated by placeholder span, the word boundary shouldn't
1733
1749
// expand across fragments.
1734
1750
assert (wordBoundary.wordStart.offset >= range.start && wordBoundary.wordEnd.offset <= range.end);
1735
1751
_textSelectionStart = wordBoundary.wordStart;
1736
1752
_textSelectionEnd = wordBoundary.wordEnd;
1753
+ _selectableContainsOriginWord = true ;
1737
1754
return SelectionResult .end;
1738
1755
}
1739
1756
@@ -1957,13 +1974,9 @@ class _SelectableFragment with Selectable, ChangeNotifier implements TextLayoutM
1957
1974
}
1958
1975
}
1959
1976
1960
- Matrix4 getTransformToParagraph () {
1961
- return Matrix4 .translationValues (_rect.left, _rect.top, 0.0 );
1962
- }
1963
-
1964
1977
@override
1965
1978
Matrix4 getTransformTo (RenderObject ? ancestor) {
1966
- return getTransformToParagraph ().. multiply ( paragraph.getTransformTo (ancestor) );
1979
+ return paragraph.getTransformTo (ancestor);
1967
1980
}
1968
1981
1969
1982
@override
@@ -1982,6 +1995,28 @@ class _SelectableFragment with Selectable, ChangeNotifier implements TextLayoutM
1982
1995
}
1983
1996
}
1984
1997
1998
+ List <Rect >? _cachedBoundingBoxes;
1999
+ @override
2000
+ List <Rect > get boundingBoxes {
2001
+ if (_cachedBoundingBoxes == null ) {
2002
+ final List <TextBox > boxes = paragraph.getBoxesForSelection (
2003
+ TextSelection (baseOffset: range.start, extentOffset: range.end),
2004
+ );
2005
+ if (boxes.isNotEmpty) {
2006
+ _cachedBoundingBoxes = < Rect > [];
2007
+ for (final TextBox textBox in boxes) {
2008
+ _cachedBoundingBoxes! .add (textBox.toRect ());
2009
+ }
2010
+ } else {
2011
+ final Offset offset = paragraph._getOffsetForPosition (TextPosition (offset: range.start));
2012
+ final Rect rect = Rect .fromPoints (offset, offset.translate (0 , - paragraph._textPainter.preferredLineHeight));
2013
+ _cachedBoundingBoxes = < Rect > [rect];
2014
+ }
2015
+ }
2016
+ return _cachedBoundingBoxes! ;
2017
+ }
2018
+
2019
+ Rect ? _cachedRect;
1985
2020
Rect get _rect {
1986
2021
if (_cachedRect == null ) {
1987
2022
final List <TextBox > boxes = paragraph.getBoxesForSelection (
@@ -2000,7 +2035,6 @@ class _SelectableFragment with Selectable, ChangeNotifier implements TextLayoutM
2000
2035
}
2001
2036
return _cachedRect! ;
2002
2037
}
2003
- Rect ? _cachedRect;
2004
2038
2005
2039
void didChangeParagraphLayout () {
2006
2040
_cachedRect = null ;
@@ -2028,12 +2062,11 @@ class _SelectableFragment with Selectable, ChangeNotifier implements TextLayoutM
2028
2062
textBox.toRect ().shift (offset), selectionPaint);
2029
2063
}
2030
2064
}
2031
- final Matrix4 transform = getTransformToParagraph ();
2032
2065
if (_startHandleLayerLink != null && value.startSelectionPoint != null ) {
2033
2066
context.pushLayer (
2034
2067
LeaderLayer (
2035
2068
link: _startHandleLayerLink! ,
2036
- offset: offset + MatrixUtils . transformPoint (transform, value.startSelectionPoint! .localPosition) ,
2069
+ offset: offset + value.startSelectionPoint! .localPosition,
2037
2070
),
2038
2071
(PaintingContext context, Offset offset) { },
2039
2072
Offset .zero,
@@ -2043,7 +2076,7 @@ class _SelectableFragment with Selectable, ChangeNotifier implements TextLayoutM
2043
2076
context.pushLayer (
2044
2077
LeaderLayer (
2045
2078
link: _endHandleLayerLink! ,
2046
- offset: offset + MatrixUtils . transformPoint (transform, value.endSelectionPoint! .localPosition) ,
2079
+ offset: offset + value.endSelectionPoint! .localPosition,
2047
2080
),
2048
2081
(PaintingContext context, Offset offset) { },
2049
2082
Offset .zero,
@@ -2071,4 +2104,12 @@ class _SelectableFragment with Selectable, ChangeNotifier implements TextLayoutM
2071
2104
2072
2105
@override
2073
2106
TextRange getWordBoundary (TextPosition position) => paragraph.getWordBoundary (position);
2107
+
2108
+ @override
2109
+ void debugFillProperties (DiagnosticPropertiesBuilder properties) {
2110
+ super .debugFillProperties (properties);
2111
+ properties.add (DiagnosticsProperty <String >('textInsideRange' , range.textInside (fullText)));
2112
+ properties.add (DiagnosticsProperty <TextRange >('range' , range));
2113
+ properties.add (DiagnosticsProperty <String >('fullText' , fullText));
2114
+ }
2074
2115
}
0 commit comments