1// Copyright 2014 The Flutter Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5import 'package:flutter/foundation.dart';
6import 'package:vector_math/vector_math_64.dart';
7
8import 'layer.dart';
9import 'object.dart';
10
11/// The result after handling a [SelectionEvent].
12///
13/// [SelectionEvent]s are sent from [SelectionRegistrar] to be handled by
14/// [SelectionHandler.dispatchSelectionEvent]. The subclasses of
15/// [SelectionHandler] or [Selectable] must return appropriate
16/// [SelectionResult]s after handling the events.
17///
18/// This is used by the [SelectionContainer] to determine how a selection
19/// expands across its [Selectable] children.
20enum SelectionResult {
21 /// There is nothing left to select forward in this [Selectable], and further
22 /// selection should extend to the next [Selectable] in screen order.
23 ///
24 /// {@template flutter.rendering.selection.SelectionResult.footNote}
25 /// This is used after subclasses [SelectionHandler] or [Selectable] handled
26 /// [SelectionEdgeUpdateEvent].
27 /// {@endtemplate}
28 next,
29 /// Selection does not reach this [Selectable] and is located before it in
30 /// screen order.
31 ///
32 /// {@macro flutter.rendering.selection.SelectionResult.footNote}
33 previous,
34 /// Selection ends in this [Selectable].
35 ///
36 /// Part of the [Selectable] may or may not be selected, but there is still
37 /// content to select forward or backward.
38 ///
39 /// {@macro flutter.rendering.selection.SelectionResult.footNote}
40 end,
41 /// The result can't be determined in this frame.
42 ///
43 /// This is typically used when the subtree is scrolling to reveal more
44 /// content.
45 ///
46 /// {@macro flutter.rendering.selection.SelectionResult.footNote}
47 // See `_SelectableRegionState._triggerSelectionEndEdgeUpdate` for how this
48 // result affects the selection.
49 pending,
50 /// There is no result for the selection event.
51 ///
52 /// This is used when a selection result is not applicable, e.g.
53 /// [SelectAllSelectionEvent], [ClearSelectionEvent], and
54 /// [SelectWordSelectionEvent].
55 none,
56}
57
58/// The abstract interface to handle [SelectionEvent]s.
59///
60/// This interface is extended by [Selectable] and [SelectionContainerDelegate]
61/// and is typically not used directly.
62///
63/// {@template flutter.rendering.SelectionHandler}
64/// This class returns a [SelectionGeometry] as its [value], and is responsible
65/// to notify its listener when its selection geometry has changed as the result
66/// of receiving selection events.
67/// {@endtemplate}
68abstract class SelectionHandler implements ValueListenable<SelectionGeometry> {
69 /// Marks this handler to be responsible for pushing [LeaderLayer]s for the
70 /// selection handles.
71 ///
72 /// This handler is responsible for pushing the leader layers with the
73 /// given layer links if they are not null. It is possible that only one layer
74 /// is non-null if this handler is only responsible for pushing one layer
75 /// link.
76 ///
77 /// The `startHandle` needs to be placed at the visual location of selection
78 /// start, the `endHandle` needs to be placed at the visual location of selection
79 /// end. Typically, the visual locations should be the same as
80 /// [SelectionGeometry.startSelectionPoint] and
81 /// [SelectionGeometry.endSelectionPoint].
82 void pushHandleLayers(LayerLink? startHandle, LayerLink? endHandle);
83
84 /// Gets the selected content in this object.
85 ///
86 /// Return `null` if nothing is selected.
87 SelectedContent? getSelectedContent();
88
89 /// Handles the [SelectionEvent] sent to this object.
90 ///
91 /// The subclasses need to update their selections or delegate the
92 /// [SelectionEvent]s to their subtrees.
93 ///
94 /// The `event`s are subclasses of [SelectionEvent]. Check
95 /// [SelectionEvent.type] to determine what kinds of event are dispatched to
96 /// this handler and handle them accordingly.
97 ///
98 /// See also:
99 /// * [SelectionEventType], which contains all of the possible types.
100 SelectionResult dispatchSelectionEvent(SelectionEvent event);
101}
102
103/// The selected content in a [Selectable] or [SelectionHandler].
104// TODO(chunhtai): Add more support for rich content.
105// https://github.com/flutter/flutter/issues/104206.
106class SelectedContent {
107 /// Creates a selected content object.
108 ///
109 /// Only supports plain text.
110 const SelectedContent({required this.plainText});
111
112 /// The selected content in plain text format.
113 final String plainText;
114}
115
116/// A mixin that can be selected by users when under a [SelectionArea] widget.
117///
118/// This object receives selection events and the [value] must reflect the
119/// current selection in this [Selectable]. The object must also notify its
120/// listener if the [value] ever changes.
121///
122/// This object is responsible for drawing the selection highlight.
123///
124/// In order to receive the selection event, the mixer needs to register
125/// itself to [SelectionRegistrar]s. Use
126/// [SelectionContainer.maybeOf] to get the selection registrar, and
127/// mix the [SelectionRegistrant] to subscribe to the [SelectionRegistrar]
128/// automatically.
129///
130/// This mixin is typically mixed by [RenderObject]s. The [RenderObject.paint]
131/// methods are responsible to push the [LayerLink]s provided to
132/// [pushHandleLayers].
133///
134/// {@macro flutter.rendering.SelectionHandler}
135///
136/// See also:
137/// * [SelectionArea], which provides an overview of selection system.
138mixin Selectable implements SelectionHandler {
139 /// {@macro flutter.rendering.RenderObject.getTransformTo}
140 Matrix4 getTransformTo(RenderObject? ancestor);
141
142 /// The size of this [Selectable].
143 Size get size;
144
145 /// A list of [Rect]s that represent the bounding box of this [Selectable]
146 /// in local coordinates.
147 List<Rect> get boundingBoxes;
148
149 /// Disposes resources held by the mixer.
150 void dispose();
151}
152
153/// A mixin to auto-register the mixer to the [registrar].
154///
155/// To use this mixin, the mixer needs to set the [registrar] to the
156/// [SelectionRegistrar] it wants to register to.
157///
158/// This mixin only registers the mixer with the [registrar] if the
159/// [SelectionGeometry.hasContent] returned by the mixer is true.
160mixin SelectionRegistrant on Selectable {
161 /// The [SelectionRegistrar] the mixer will be or is registered to.
162 ///
163 /// This [Selectable] only registers the mixer if the
164 /// [SelectionGeometry.hasContent] returned by the [Selectable] is true.
165 SelectionRegistrar? get registrar => _registrar;
166 SelectionRegistrar? _registrar;
167 set registrar(SelectionRegistrar? value) {
168 if (value == _registrar) {
169 return;
170 }
171 if (value == null) {
172 // When registrar goes from non-null to null;
173 removeListener(_updateSelectionRegistrarSubscription);
174 } else if (_registrar == null) {
175 // When registrar goes from null to non-null;
176 addListener(_updateSelectionRegistrarSubscription);
177 }
178 _removeSelectionRegistrarSubscription();
179 _registrar = value;
180 _updateSelectionRegistrarSubscription();
181 }
182
183 @override
184 void dispose() {
185 _removeSelectionRegistrarSubscription();
186 super.dispose();
187 }
188
189 bool _subscribedToSelectionRegistrar = false;
190 void _updateSelectionRegistrarSubscription() {
191 if (_registrar == null) {
192 _subscribedToSelectionRegistrar = false;
193 return;
194 }
195 if (_subscribedToSelectionRegistrar && !value.hasContent) {
196 _registrar!.remove(this);
197 _subscribedToSelectionRegistrar = false;
198 } else if (!_subscribedToSelectionRegistrar && value.hasContent) {
199 _registrar!.add(this);
200 _subscribedToSelectionRegistrar = true;
201 }
202 }
203
204 void _removeSelectionRegistrarSubscription() {
205 if (_subscribedToSelectionRegistrar) {
206 _registrar!.remove(this);
207 _subscribedToSelectionRegistrar = false;
208 }
209 }
210}
211
212/// A utility class that provides useful methods for handling selection events.
213abstract final class SelectionUtils {
214 /// Determines [SelectionResult] purely based on the target rectangle.
215 ///
216 /// This method returns [SelectionResult.end] if the `point` is inside the
217 /// `targetRect`. Returns [SelectionResult.previous] if the `point` is
218 /// considered to be lower than `targetRect` in screen order. Returns
219 /// [SelectionResult.next] if the point is considered to be higher than
220 /// `targetRect` in screen order.
221 static SelectionResult getResultBasedOnRect(Rect targetRect, Offset point) {
222 if (targetRect.contains(point)) {
223 return SelectionResult.end;
224 }
225 if (point.dy < targetRect.top) {
226 return SelectionResult.previous;
227 }
228 if (point.dy > targetRect.bottom) {
229 return SelectionResult.next;
230 }
231 return point.dx >= targetRect.right
232 ? SelectionResult.next
233 : SelectionResult.previous;
234 }
235
236 /// Adjusts the dragging offset based on the target rect.
237 ///
238 /// This method moves the offsets to be within the target rect in case they are
239 /// outside the rect.
240 ///
241 /// This is used in the case where a drag happens outside of the rectangle
242 /// of a [Selectable].
243 ///
244 /// The logic works as the following:
245 /// ![](https://flutter.github.io/assets-for-api-docs/assets/rendering/adjust_drag_offset.png)
246 ///
247 /// For points inside the rect:
248 /// Their effective locations are unchanged.
249 ///
250 /// For points in Area 1:
251 /// Move them to top-left of the rect if text direction is ltr, or top-right
252 /// if rtl.
253 ///
254 /// For points in Area 2:
255 /// Move them to bottom-right of the rect if text direction is ltr, or
256 /// bottom-left if rtl.
257 static Offset adjustDragOffset(Rect targetRect, Offset point, {TextDirection direction = TextDirection.ltr}) {
258 if (targetRect.contains(point)) {
259 return point;
260 }
261 if (point.dy <= targetRect.top ||
262 point.dy <= targetRect.bottom && point.dx <= targetRect.left) {
263 // Area 1
264 return direction == TextDirection.ltr ? targetRect.topLeft : targetRect.topRight;
265 } else {
266 // Area 2
267 return direction == TextDirection.ltr ? targetRect.bottomRight : targetRect.bottomLeft;
268 }
269 }
270}
271
272/// The type of a [SelectionEvent].
273///
274/// Used by [SelectionEvent.type] to distinguish different types of events.
275enum SelectionEventType {
276 /// An event to update the selection start edge.
277 ///
278 /// Used by [SelectionEdgeUpdateEvent].
279 startEdgeUpdate,
280
281 /// An event to update the selection end edge.
282 ///
283 /// Used by [SelectionEdgeUpdateEvent].
284 endEdgeUpdate,
285
286 /// An event to clear the current selection.
287 ///
288 /// Used by [ClearSelectionEvent].
289 clear,
290
291 /// An event to select all the available content.
292 ///
293 /// Used by [SelectAllSelectionEvent].
294 selectAll,
295
296 /// An event to select a word at the location
297 /// [SelectWordSelectionEvent.globalPosition].
298 ///
299 /// Used by [SelectWordSelectionEvent].
300 selectWord,
301
302 /// An event that extends the selection by a specific [TextGranularity].
303 granularlyExtendSelection,
304
305 /// An event that extends the selection in a specific direction.
306 directionallyExtendSelection,
307}
308
309/// The unit of how selection handles move in text.
310///
311/// The [GranularlyExtendSelectionEvent] uses this enum to describe how
312/// [Selectable] should extend its selection.
313enum TextGranularity {
314 /// Treats each character as an atomic unit when moving the selection handles.
315 character,
316
317 /// Treats word as an atomic unit when moving the selection handles.
318 word,
319
320 /// Treats each line break as an atomic unit when moving the selection handles.
321 line,
322
323 /// Treats the entire document as an atomic unit when moving the selection handles.
324 document,
325}
326
327/// An abstract base class for selection events.
328///
329/// This should not be directly used. To handle a selection event, it should
330/// be downcast to a specific subclass. One can use [type] to look up which
331/// subclasses to downcast to.
332///
333/// See also:
334/// * [SelectAllSelectionEvent], for events to select all contents.
335/// * [ClearSelectionEvent], for events to clear selections.
336/// * [SelectWordSelectionEvent], for events to select words at the locations.
337/// * [SelectionEdgeUpdateEvent], for events to update selection edges.
338/// * [SelectionEventType], for determining the subclass types.
339abstract class SelectionEvent {
340 const SelectionEvent._(this.type);
341
342 /// The type of this selection event.
343 final SelectionEventType type;
344}
345
346/// Selects all selectable contents.
347///
348/// This event can be sent as the result of keyboard select-all, i.e.
349/// ctrl + A, or cmd + A in macOS.
350class SelectAllSelectionEvent extends SelectionEvent {
351 /// Creates a select all selection event.
352 const SelectAllSelectionEvent(): super._(SelectionEventType.selectAll);
353}
354
355/// Clears the selection from the [Selectable] and removes any existing
356/// highlight as if there is no selection at all.
357class ClearSelectionEvent extends SelectionEvent {
358 /// Create a clear selection event.
359 const ClearSelectionEvent(): super._(SelectionEventType.clear);
360}
361
362/// Selects the whole word at the location.
363///
364/// This event can be sent as the result of mobile long press selection.
365class SelectWordSelectionEvent extends SelectionEvent {
366 /// Creates a select word event at the [globalPosition].
367 const SelectWordSelectionEvent({required this.globalPosition}): super._(SelectionEventType.selectWord);
368
369 /// The position in global coordinates to select word at.
370 final Offset globalPosition;
371}
372
373/// Updates a selection edge.
374///
375/// An active selection contains two edges, start and end. Use the [type] to
376/// determine which edge this event applies to. If the [type] is
377/// [SelectionEventType.startEdgeUpdate], the event updates start edge. If the
378/// [type] is [SelectionEventType.endEdgeUpdate], the event updates end edge.
379///
380/// The [globalPosition] contains the new offset of the edge.
381///
382/// The [granularity] contains the granularity that the selection edge should move by.
383/// Only [TextGranularity.character] and [TextGranularity.word] are currently supported.
384///
385/// This event is dispatched when the framework detects [TapDragStartDetails] in
386/// [SelectionArea]'s gesture recognizers for mouse devices, or the selection
387/// handles have been dragged to new locations.
388class SelectionEdgeUpdateEvent extends SelectionEvent {
389 /// Creates a selection start edge update event.
390 ///
391 /// The [globalPosition] contains the location of the selection start edge.
392 ///
393 /// The [granularity] contains the granularity which the selection edge should move by.
394 /// This value defaults to [TextGranularity.character].
395 const SelectionEdgeUpdateEvent.forStart({
396 required this.globalPosition,
397 TextGranularity? granularity
398 }) : granularity = granularity ?? TextGranularity.character, super._(SelectionEventType.startEdgeUpdate);
399
400 /// Creates a selection end edge update event.
401 ///
402 /// The [globalPosition] contains the new location of the selection end edge.
403 ///
404 /// The [granularity] contains the granularity which the selection edge should move by.
405 /// This value defaults to [TextGranularity.character].
406 const SelectionEdgeUpdateEvent.forEnd({
407 required this.globalPosition,
408 TextGranularity? granularity
409 }) : granularity = granularity ?? TextGranularity.character, super._(SelectionEventType.endEdgeUpdate);
410
411 /// The new location of the selection edge.
412 final Offset globalPosition;
413
414 /// The granularity for which the selection moves.
415 ///
416 /// Only [TextGranularity.character] and [TextGranularity.word] are currently supported.
417 ///
418 /// Defaults to [TextGranularity.character].
419 final TextGranularity granularity;
420}
421
422/// Extends the start or end of the selection by a given [TextGranularity].
423///
424/// To handle this event, move the associated selection edge, as dictated by
425/// [isEnd], according to the [granularity].
426class GranularlyExtendSelectionEvent extends SelectionEvent {
427 /// Creates a [GranularlyExtendSelectionEvent].
428 const GranularlyExtendSelectionEvent({
429 required this.forward,
430 required this.isEnd,
431 required this.granularity,
432 }) : super._(SelectionEventType.granularlyExtendSelection);
433
434 /// Whether to extend the selection forward.
435 final bool forward;
436
437 /// Whether this event is updating the end selection edge.
438 final bool isEnd;
439
440 /// The granularity for which the selection extend.
441 final TextGranularity granularity;
442}
443
444/// The direction to extend a selection.
445///
446/// The [DirectionallyExtendSelectionEvent] uses this enum to describe how
447/// [Selectable] should extend their selection.
448enum SelectionExtendDirection {
449 /// Move one edge of the selection vertically to the previous adjacent line.
450 ///
451 /// For text selection, it should consider both soft and hard linebreak.
452 ///
453 /// See [DirectionallyExtendSelectionEvent.dx] on how to
454 /// calculate the horizontal offset.
455 previousLine,
456
457 /// Move one edge of the selection vertically to the next adjacent line.
458 ///
459 /// For text selection, it should consider both soft and hard linebreak.
460 ///
461 /// See [DirectionallyExtendSelectionEvent.dx] on how to
462 /// calculate the horizontal offset.
463 nextLine,
464
465 /// Move the selection edges forward to a certain horizontal offset in the
466 /// same line.
467 ///
468 /// If there is no on-going selection, the selection must start with the first
469 /// line (or equivalence of first line in a non-text selectable) and select
470 /// toward the horizontal offset in the same line.
471 ///
472 /// The selectable that receives [DirectionallyExtendSelectionEvent] with this
473 /// enum must return [SelectionResult.end].
474 ///
475 /// See [DirectionallyExtendSelectionEvent.dx] on how to
476 /// calculate the horizontal offset.
477 forward,
478
479 /// Move the selection edges backward to a certain horizontal offset in the
480 /// same line.
481 ///
482 /// If there is no on-going selection, the selection must start with the last
483 /// line (or equivalence of last line in a non-text selectable) and select
484 /// backward the horizontal offset in the same line.
485 ///
486 /// The selectable that receives [DirectionallyExtendSelectionEvent] with this
487 /// enum must return [SelectionResult.end].
488 ///
489 /// See [DirectionallyExtendSelectionEvent.dx] on how to
490 /// calculate the horizontal offset.
491 backward,
492}
493
494/// Extends the current selection with respect to a [direction].
495///
496/// To handle this event, move the associated selection edge, as dictated by
497/// [isEnd], according to the [direction].
498///
499/// The movements are always based on [dx]. The value is in
500/// global coordinates and is the horizontal offset the selection edge should
501/// move to when moving to across lines.
502class DirectionallyExtendSelectionEvent extends SelectionEvent {
503 /// Creates a [DirectionallyExtendSelectionEvent].
504 const DirectionallyExtendSelectionEvent({
505 required this.dx,
506 required this.isEnd,
507 required this.direction,
508 }) : super._(SelectionEventType.directionallyExtendSelection);
509
510 /// The horizontal offset the selection should move to.
511 ///
512 /// The offset is in global coordinates.
513 final double dx;
514
515 /// Whether this event is updating the end selection edge.
516 final bool isEnd;
517
518 /// The directional movement of this event.
519 ///
520 /// See also:
521 /// * [SelectionExtendDirection], which explains how to handle each enum.
522 final SelectionExtendDirection direction;
523
524 /// Makes a copy of this object with its property replaced with the new
525 /// values.
526 DirectionallyExtendSelectionEvent copyWith({
527 double? dx,
528 bool? isEnd,
529 SelectionExtendDirection? direction,
530 }) {
531 return DirectionallyExtendSelectionEvent(
532 dx: dx ?? this.dx,
533 isEnd: isEnd ?? this.isEnd,
534 direction: direction ?? this.direction,
535 );
536 }
537}
538
539/// A registrar that keeps track of [Selectable]s in the subtree.
540///
541/// A [Selectable] is only included in the [SelectableRegion] if they are
542/// registered with a [SelectionRegistrar]. Once a [Selectable] is registered,
543/// it will receive [SelectionEvent]s in
544/// [SelectionHandler.dispatchSelectionEvent].
545///
546/// Use [SelectionContainer.maybeOf] to get the immediate [SelectionRegistrar]
547/// in the ancestor chain above the build context.
548///
549/// See also:
550/// * [SelectableRegion], which provides an overview of the selection system.
551/// * [SelectionRegistrarScope], which hosts the [SelectionRegistrar] for the
552/// subtree.
553/// * [SelectionRegistrant], which auto registers the object with the mixin to
554/// [SelectionRegistrar].
555abstract class SelectionRegistrar {
556 /// Adds the [selectable] into the registrar.
557 ///
558 /// A [Selectable] must register with the [SelectionRegistrar] in order to
559 /// receive selection events.
560 void add(Selectable selectable);
561
562 /// Removes the [selectable] from the registrar.
563 ///
564 /// A [Selectable] must unregister itself if it is removed from the rendering
565 /// tree.
566 void remove(Selectable selectable);
567}
568
569/// The status that indicates whether there is a selection and whether the
570/// selection is collapsed.
571///
572/// A collapsed selection means the selection starts and ends at the same
573/// location.
574enum SelectionStatus {
575 /// The selection is not collapsed.
576 ///
577 /// For example if `{}` represent the selection edges:
578 /// 'ab{cd}', the collapsing status is [uncollapsed].
579 /// '{abcd}', the collapsing status is [uncollapsed].
580 uncollapsed,
581
582 /// The selection is collapsed.
583 ///
584 /// For example if `{}` represent the selection edges:
585 /// 'ab{}cd', the collapsing status is [collapsed].
586 /// '{}abcd', the collapsing status is [collapsed].
587 /// 'abcd{}', the collapsing status is [collapsed].
588 collapsed,
589
590 /// No selection.
591 none,
592}
593
594/// The geometry of the current selection.
595///
596/// This includes details such as the locations of the selection start and end,
597/// line height, the rects that encompass the selection, etc. This information
598/// is used for drawing selection controls for mobile platforms.
599///
600/// The positions in geometry are in local coordinates of the [SelectionHandler]
601/// or [Selectable].
602@immutable
603class SelectionGeometry {
604 /// Creates a selection geometry object.
605 ///
606 /// If any of the [startSelectionPoint] and [endSelectionPoint] is not null,
607 /// the [status] must not be [SelectionStatus.none].
608 const SelectionGeometry({
609 this.startSelectionPoint,
610 this.endSelectionPoint,
611 this.selectionRects = const <Rect>[],
612 required this.status,
613 required this.hasContent,
614 }) : assert((startSelectionPoint == null && endSelectionPoint == null) || status != SelectionStatus.none);
615
616 /// The geometry information at the selection start.
617 ///
618 /// This information is used for drawing mobile selection controls. The
619 /// [SelectionPoint.localPosition] of the selection start is typically at the
620 /// start of the selection highlight at where the start selection handle
621 /// should be drawn.
622 ///
623 /// The [SelectionPoint.handleType] should be [TextSelectionHandleType.left]
624 /// for forward selection or [TextSelectionHandleType.right] for backward
625 /// selection in most cases.
626 ///
627 /// Can be null if the selection start is offstage, for example, when the
628 /// selection is outside of the viewport or is kept alive by a scrollable.
629 final SelectionPoint? startSelectionPoint;
630
631 /// The geometry information at the selection end.
632 ///
633 /// This information is used for drawing mobile selection controls. The
634 /// [SelectionPoint.localPosition] of the selection end is typically at the end
635 /// of the selection highlight at where the end selection handle should be
636 /// drawn.
637 ///
638 /// The [SelectionPoint.handleType] should be [TextSelectionHandleType.right]
639 /// for forward selection or [TextSelectionHandleType.left] for backward
640 /// selection in most cases.
641 ///
642 /// Can be null if the selection end is offstage, for example, when the
643 /// selection is outside of the viewport or is kept alive by a scrollable.
644 final SelectionPoint? endSelectionPoint;
645
646 /// The status of ongoing selection in the [Selectable] or [SelectionHandler].
647 final SelectionStatus status;
648
649 /// The rects in the local coordinates of the containing [Selectable] that
650 /// represent the selection if there is any.
651 final List<Rect> selectionRects;
652
653 /// Whether there is any selectable content in the [Selectable] or
654 /// [SelectionHandler].
655 final bool hasContent;
656
657 /// Whether there is an ongoing selection.
658 bool get hasSelection => status != SelectionStatus.none;
659
660 /// Makes a copy of this object with the given values updated.
661 SelectionGeometry copyWith({
662 SelectionPoint? startSelectionPoint,
663 SelectionPoint? endSelectionPoint,
664 List<Rect>? selectionRects,
665 SelectionStatus? status,
666 bool? hasContent,
667 }) {
668 return SelectionGeometry(
669 startSelectionPoint: startSelectionPoint ?? this.startSelectionPoint,
670 endSelectionPoint: endSelectionPoint ?? this.endSelectionPoint,
671 selectionRects: selectionRects ?? this.selectionRects,
672 status: status ?? this.status,
673 hasContent: hasContent ?? this.hasContent,
674 );
675 }
676
677 @override
678 bool operator ==(Object other) {
679 if (identical(this, other)) {
680 return true;
681 }
682 if (other.runtimeType != runtimeType) {
683 return false;
684 }
685 return other is SelectionGeometry
686 && other.startSelectionPoint == startSelectionPoint
687 && other.endSelectionPoint == endSelectionPoint
688 && other.selectionRects == selectionRects
689 && other.status == status
690 && other.hasContent == hasContent;
691 }
692
693 @override
694 int get hashCode {
695 return Object.hash(
696 startSelectionPoint,
697 endSelectionPoint,
698 selectionRects,
699 status,
700 hasContent,
701 );
702 }
703}
704
705/// The geometry information of a selection point.
706@immutable
707class SelectionPoint with Diagnosticable {
708 /// Creates a selection point object.
709 const SelectionPoint({
710 required this.localPosition,
711 required this.lineHeight,
712 required this.handleType,
713 });
714
715 /// The position of the selection point in the local coordinates of the
716 /// containing [Selectable].
717 final Offset localPosition;
718
719 /// The line height at the selection point.
720 final double lineHeight;
721
722 /// The selection handle type that should be used at the selection point.
723 ///
724 /// This is used for building the mobile selection handle.
725 final TextSelectionHandleType handleType;
726
727 @override
728 bool operator ==(Object other) {
729 if (identical(this, other)) {
730 return true;
731 }
732 if (other.runtimeType != runtimeType) {
733 return false;
734 }
735 return other is SelectionPoint
736 && other.localPosition == localPosition
737 && other.lineHeight == lineHeight
738 && other.handleType == handleType;
739 }
740
741 @override
742 int get hashCode {
743 return Object.hash(
744 localPosition,
745 lineHeight,
746 handleType,
747 );
748 }
749
750 @override
751 void debugFillProperties(DiagnosticPropertiesBuilder properties) {
752 super.debugFillProperties(properties);
753 properties.add(DiagnosticsProperty<Offset>('localPosition', localPosition));
754 properties.add(DoubleProperty('lineHeight', lineHeight));
755 properties.add(EnumProperty<TextSelectionHandleType>('handleType', handleType));
756 }
757}
758
759/// The type of selection handle to be displayed.
760///
761/// With mixed-direction text, both handles may be the same type. Examples:
762///
763/// * LTR text: 'the <quick brown> fox':
764///
765/// The '<' is drawn with the [left] type, the '>' with the [right]
766///
767/// * RTL text: 'XOF <NWORB KCIUQ> EHT':
768///
769/// Same as above.
770///
771/// * mixed text: '<the NWOR<B KCIUQ fox'
772///
773/// Here 'the QUICK B' is selected, but 'QUICK BROWN' is RTL. Both are drawn
774/// with the [left] type.
775///
776/// See also:
777///
778/// * [TextDirection], which discusses left-to-right and right-to-left text in
779/// more detail.
780enum TextSelectionHandleType {
781 /// The selection handle is to the left of the selection end point.
782 left,
783
784 /// The selection handle is to the right of the selection end point.
785 right,
786
787 /// The start and end of the selection are co-incident at this point.
788 collapsed,
789}
790