Skip to content

Commit 1fb7448

Browse files
committed
Version 0.2.9
* Removed from SelectableController the deprecated properties `text`, `selectionStart`, `selectionEnd`, and `rects`. * Added the ability to customize how the rectangles of selected text is converted to selection rectangles via the new SelectableController `setCustomRectifier` method. See the included example app for an example of its use.
1 parent bcd565a commit 1fb7448

14 files changed

Lines changed: 155 additions & 157 deletions

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
# CHANGELOG
22

3+
## [0.2.9] - December 30, 2022
4+
5+
* Removed from SelectableController the deprecated properties `text`, `selectionStart`, `selectionEnd`, and `rects`.
6+
* Added the ability to customize how the rectangles of selected text is converted to selection rectangles via the new SelectableController `setCustomRectifier` method. See the included example app for an example of its use.
7+
38
## [0.2.8] - December 10, 2022
49

510
* Added `int? get startIndex` and `int? get endIndex` to the Selection class.

example/android/app/build.gradle

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ apply plugin: 'kotlin-android'
2626
apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
2727

2828
android {
29-
compileSdkVersion 31
29+
compileSdkVersion 33
3030

3131
compileOptions {
3232
sourceCompatibility JavaVersion.VERSION_1_8

example/lib/main.dart

Lines changed: 30 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -49,8 +49,12 @@ class _MyHomePageState extends State<MyHomePage> {
4949
super.initState();
5050

5151
_selectionController
52-
// ..setCustomPainter(MySelectionPainter())
53-
.addListener(_selectionChangedListener);
52+
// ..setCustomPainter(MySelectionPainter())
53+
// ..setCustomRectifier(SelectionRectifiers.merged)
54+
..setCustomRectifier((rects) => rects
55+
.map((r) => Rect.fromLTRB(r.left - 2, r.top, r.right + 2, r.bottom))
56+
.toList())
57+
..addListener(_selectionChangedListener);
5458

5559
_timer =
5660
Timer.periodic(const Duration(seconds: 1), (_) => _selectRandomWord());
@@ -87,6 +91,16 @@ class _MyHomePageState extends State<MyHomePage> {
8791
});
8892
}
8993

94+
void _toggleRectifier() {
95+
setState(() {
96+
_selectionController.setCustomRectifier(
97+
_selectionController.getCustomRectifier() ==
98+
SelectionRectifiers.identity
99+
? SelectionRectifiers.merged
100+
: SelectionRectifiers.identity);
101+
});
102+
}
103+
90104
void _selectRandomWord() {
91105
// final text = _selectionController.getContainedText();
92106
// if (text.isNotEmpty) {
@@ -173,9 +187,20 @@ class _MyHomePageState extends State<MyHomePage> {
173187
],
174188
),
175189
floatingActionButton: _isTextSelected
176-
? FloatingActionButton.extended(
177-
onPressed: _toggleShowHideSelection,
178-
label: Text(_showSelection ? 'hide selection' : 'show selection'),
190+
? Column(
191+
mainAxisSize: MainAxisSize.min,
192+
children: [
193+
FloatingActionButton.extended(
194+
onPressed: _toggleShowHideSelection,
195+
label: Text(
196+
_showSelection ? 'hide selection' : 'show selection'),
197+
),
198+
const SizedBox(height: 8),
199+
FloatingActionButton.extended(
200+
onPressed: _toggleRectifier,
201+
label: const Text('switch rectifier'),
202+
),
203+
],
179204
)
180205
: null,
181206
);

example/pubspec.lock

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -148,7 +148,7 @@ packages:
148148
path: ".."
149149
relative: true
150150
source: path
151-
version: "0.2.8"
151+
version: "0.2.9"
152152
sky_engine:
153153
dependency: transitive
154154
description: flutter

lib/selectable.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
library selectable;
66

7+
export 'src/common.dart' show SelectionRectifiers;
78
export 'src/ignore_selectable.dart';
89
export 'src/inline_span_ext.dart';
910
export 'src/selectable.dart';

lib/src/common.dart

Lines changed: 7 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,13 @@ extension SelectableExtOnListOfRect on List<Rect> {
100100
);
101101
}
102102

103+
// ignore: avoid_classes_with_only_static_members
104+
class SelectionRectifiers {
105+
static List<Rect> identity(List<Rect> rects) => rects;
106+
107+
static List<Rect> merged(List<Rect> rects) => rects.mergedToSelectionRects();
108+
}
109+
103110
///
104111
/// Iterable<Rect> extensions
105112
///
@@ -125,10 +132,6 @@ extension SelectableExtOnIterableOfRect on Iterable<Rect> {
125132
/// └──────────────────────┘
126133
/// ```
127134
List<Rect> mergedToSelectionRects() {
128-
//
129-
// IMPORTANT: If this algorithm is changed, please also change the
130-
// algorithm in Iterable<TextBox>.mergedToSelectionRects.
131-
//
132135
Rect? firstLine;
133136
Rect? lastLine;
134137
final rect = fold<Rect?>(
@@ -172,72 +175,6 @@ extension SelectableExtOnIterableOfRect on Iterable<Rect> {
172175
rect.bottom.roundToDouble()));
173176
}
174177

175-
///
176-
/// Iterable<TextBox> extensions
177-
///
178-
extension SelectableExtOnIterableOfTextBox on Iterable<TextBox> {
179-
/// Merges the text boxes into, at most, three rects, where the first rect
180-
/// is the bounding box containing all the rects in the first line, the
181-
/// second rect is the bounding box of lines 1 through N - 1 (where N is
182-
/// the number of lines), and the third rect is the bounding box of the
183-
/// last line.
184-
///
185-
/// Assumes that the text boxes are in 'reading order', i.e. left to right,
186-
/// top to bottom. Does not support languages that have an alternate order.
187-
///
188-
/// For example:
189-
/// ```
190-
/// ┌─────────────────┐
191-
/// │ first line │
192-
/// ┌───────────────┴─────────────────┤
193-
/// │ middle line(s) │
194-
/// | |
195-
/// ├──────────────────────┬──────────┘
196-
/// │ last line │
197-
/// └──────────────────────┘
198-
/// ```
199-
List<Rect> mergedToSelectionRects() {
200-
//
201-
// IMPORTANT: If this algorithm is changed, please also change the
202-
// algorithm in Iterable<Rect>.mergedToSelectionRects.
203-
//
204-
Rect? firstLine;
205-
Rect? lastLine;
206-
final rect = fold<Rect?>(
207-
null,
208-
(previous, r) {
209-
if (firstLine == null) {
210-
firstLine = r.toRect();
211-
} else if (lastLine == null &&
212-
(r.vCenter < firstLine!.bottom || firstLine!.vCenter > r.top)) {
213-
firstLine = firstLine!.expandToIncludeTextBox(r);
214-
} else if (lastLine == null ||
215-
(r.vCenter > lastLine!.bottom && lastLine!.vCenter < r.top)) {
216-
lastLine = r.toRect();
217-
} else {
218-
lastLine = lastLine!.expandToIncludeTextBox(r);
219-
}
220-
return Rect.fromLTRB(
221-
math.min(previous?.left ?? r.left, r.left),
222-
math.min(previous?.top ?? r.top, r.top),
223-
math.max(previous?.right ?? r.right, r.right),
224-
math.max(previous?.bottom ?? r.bottom, r.bottom),
225-
);
226-
},
227-
);
228-
if (firstLine == null) return [];
229-
if (lastLine == null) return [rect!];
230-
if (firstLine!.bottom >= lastLine!.top) return [firstLine!, lastLine!];
231-
return [
232-
Rect.fromLTRB(
233-
firstLine!.left, firstLine!.top, rect!.right, firstLine!.bottom),
234-
Rect.fromLTRB(rect.left, firstLine!.bottom, rect.right, lastLine!.top),
235-
Rect.fromLTRB(
236-
rect.left, lastLine!.top, lastLine!.right, lastLine!.bottom),
237-
];
238-
}
239-
}
240-
241178
///
242179
/// Rect extensions
243180
///

lib/src/selectable.dart

Lines changed: 30 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@ import 'selection_controls.dart';
1818
import 'selection_painter.dart';
1919
import 'selection_paragraph.dart';
2020
import 'selections.dart';
21-
import 'tagged_text.dart';
2221

2322
part 'selectable_controller.dart';
2423

@@ -155,11 +154,11 @@ class _SelectableState extends State<Selectable>
155154
final sc = _selectionController!;
156155

157156
if (sc.isTextSelected &&
158-
_selections.main.isHidden != sc.getSelection()!.isHidden) {
157+
(_selections.main?.isHidden ?? false) != sc.getSelection()!.isHidden) {
159158
// ignore: avoid_positional_boolean_parameters
160159
// String bToStr(bool isHidden) => isHidden ? 'hidden' : 'visible';
161160
// dmPrint('Selection state changed from '
162-
// '${bToStr(_selections.main.isHidden)} to '
161+
// '${bToStr(_selections.main?.isHidden ?? false)} to '
163162
// '${bToStr(sc.isHidden)}.');
164163
_selectionIsHidden = sc.getSelection()!.isHidden;
165164
if (_selectionIsHidden) {
@@ -227,7 +226,8 @@ class _SelectableState extends State<Selectable>
227226
if (isScrolling != _buildHelper.isScrolling) {
228227
// dmPrint(isScrolling ? 'STARTED SCROLLING...' : 'STOPPED SCROLLING.');
229228
_buildHelper.isScrolling = isScrolling;
230-
if (_selections.main.isTextSelected && _buildHelper.showPopupMenu) {
229+
if ((_selections.main?.isTextSelected ?? false) &&
230+
_buildHelper.showPopupMenu) {
231231
_refresh();
232232
}
233233
}
@@ -259,8 +259,8 @@ class _SelectableState extends State<Selectable>
259259

260260
// Ignore taps if text is not selected, because the child might want to
261261
// handle them.
262-
final ignoreTap =
263-
!(widget.showSelectionControls && _selections.main.isTextSelected);
262+
final ignoreTap = !(widget.showSelectionControls &&
263+
(_selections.main?.isTextSelected ?? false));
264264

265265
// This is how the selection color is set in the Flutter 2.5.2
266266
// version of src/material/selectable_text.dart, except that
@@ -300,33 +300,33 @@ class _SelectableState extends State<Selectable>
300300
? _selectionController?.getCustomPainter() ??
301301
DefaultSelectionPainter(
302302
color: selectionColor,
303-
opacityAnimation:
304-
_selectionIsHidden == _selections.main.isHidden
305-
? _selectionOpacityController
306-
: _selections.main.isHidden
307-
? kAlwaysDismissedAnimation
308-
: kAlwaysCompleteAnimation,
303+
opacityAnimation: _selectionIsHidden ==
304+
(_selections.main?.isHidden ?? false)
305+
? _selectionOpacityController
306+
: (_selections.main?.isHidden ?? false)
307+
? kAlwaysDismissedAnimation
308+
: kAlwaysCompleteAnimation,
309309
)
310310
: null,
311311
child: IgnorePointer(
312312
// Ignore gestures (e.g. taps) on the child if text is selected.
313313
ignoring: widget.showSelectionControls &&
314314
(_selections.dragInfo.isSelectingWordOrDraggingHandle ||
315-
_selections.main.isTextSelected),
315+
(_selections.main?.isTextSelected ?? false)),
316316
child: widget.child,
317317
),
318318
),
319319
),
320320
if (widget.showSelection &&
321321
(_selections.dragInfo.isSelectingWordOrDraggingHandle ||
322-
_selections.main.isTextSelected ||
322+
(_selections.main?.isTextSelected ?? false) ||
323323
_buildHelper.showParagraphRects))
324324
Positioned.fill(
325325
child: LayoutBuilder(
326326
builder: (context, constraints) {
327327
// If text is selected, and a handle is being dragged,
328328
// autoscroll if necessary.
329-
if (_selections.main.isTextSelected &&
329+
if ((_selections.main?.isTextSelected ?? false) &&
330330
_selections.dragInfo.isDraggingHandle) {
331331
final paragraphs = _selections.cachedParagraphs.list;
332332
assert(paragraphs.isNotEmpty);
@@ -346,16 +346,17 @@ class _SelectableState extends State<Selectable>
346346
}
347347

348348
// dmPrint('selection.update resulted in '
349-
// '${_selections.main.rects?.length ?? 0} selection rects');
349+
// '${_selections.main?.rects?.length ?? 0} selection rects');
350350
_selections.dragInfo
351351
..selectionPt = null
352352
..handleType = null;
353353

354-
if ((_selections.main.rects?.isNotEmpty ?? false) ||
354+
if ((_selections.main?.rects?.isNotEmpty ?? false) ||
355355
_buildHelper.showParagraphRects) {
356356
return AnimatedOpacity(
357-
opacity: _selections.main.isHidden ? 0.0 : 1.0,
358-
duration: _selections.main.animationDuration,
357+
opacity: (_selections.main?.isHidden ?? false) ? 0.0 : 1.0,
358+
duration:
359+
(_selections.main?.animationDuration ?? Duration.zero),
359360
child: Stack(
360361
children: [
361362
// if (_selections.main.rects?.isNotEmpty ?? false)
@@ -416,8 +417,13 @@ class _SelectableState extends State<Selectable>
416417
if (!mounted) return;
417418
final pt = localPosition;
418419
// dmPrint('onLongPressOrDoubleTap at: $pt');
419-
if (pt != null && !_selections.main.containsPoint(pt)) {
420+
if (pt != null && !(_selections.main?.containsPoint(pt) ?? false)) {
420421
_refresh(() {
422+
if (_selections.main == null) {
423+
// Create the main selection object, if needed.
424+
_selections[0] =
425+
_selectionController?.getSelection() ?? const Selection();
426+
}
421427
_buildHelper.showPopupMenu = widget.showPopup;
422428
_selections.dragInfo
423429
..selectionPt = pt
@@ -431,15 +437,15 @@ class _SelectableState extends State<Selectable>
431437
if (!mounted) return;
432438
final pt = localPosition;
433439
// dmPrint('onTap at: $pt');
434-
if (pt != null && _selections.main.isTextSelected) {
440+
if (pt != null && (_selections.main?.isTextSelected ?? false)) {
435441
_refresh(() {
436442
if (_buildHelper.usingCupertinoControls &&
437-
_selections.main.containsPoint(pt)) {
443+
(_selections.main?.containsPoint(pt) ?? false)) {
438444
if (widget.showPopup) {
439445
_buildHelper.showPopupMenu = !_buildHelper.showPopupMenu;
440446
}
441-
} else {
442-
_selections[0] = _selections.main.cleared();
447+
} else if (_selections.main != null) {
448+
_selections[0] = _selections.main!.cleared();
443449
}
444450
});
445451
}

lib/src/selectable_build_helper.dart

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ class SelectableBuildHelper {
8282

8383
/// Builds the selection handles and optionally the popup menu.
8484
List<Widget> buildSelectionControls(
85-
Selection selection,
85+
Selection? selection,
8686
BuildContext context,
8787
BoxConstraints constraints,
8888
SelectionDelegate selectionDelegate,
@@ -91,7 +91,7 @@ class SelectableBuildHelper {
9191
double topOverlayHeight,
9292
) {
9393
// If there is no selection, return an empty list.
94-
if (!selection.isTextSelected) return []; //---------------------------->
94+
if (selection == null || !selection.isTextSelected) return []; //------->
9595

9696
final leftLineHeight = selection.rects!.first.height;
9797
final rightLineHeight = selection.rects!.last.height;

0 commit comments

Comments
 (0)