mirror of
https://github.com/flutter/flutter.git
synced 2026-02-20 02:29:02 +08:00
757 lines
26 KiB
Dart
757 lines
26 KiB
Dart
// Copyright 2014 The Flutter Authors. All rights reserved.
|
|
// Use of this source code is governed by a BSD-style license that can be
|
|
// found in the LICENSE file.
|
|
|
|
import 'package:flutter/foundation.dart';
|
|
import 'package:vector_math/vector_math_64.dart';
|
|
|
|
import 'layer.dart';
|
|
import 'object.dart';
|
|
|
|
/// The result after handling a [SelectionEvent].
|
|
///
|
|
/// [SelectionEvent]s are sent from [SelectionRegistrar] to be handled by
|
|
/// [SelectionHandler.dispatchSelectionEvent]. The subclasses of
|
|
/// [SelectionHandler] or [Selectable] must return appropriate
|
|
/// [SelectionResult]s after handling the events.
|
|
///
|
|
/// This is used by the [SelectionContainer] to determine how a selection
|
|
/// expands across its [Selectable] children.
|
|
enum SelectionResult {
|
|
/// There is nothing left to select forward in this [Selectable], and further
|
|
/// selection should extend to the next [Selectable] in screen order.
|
|
///
|
|
/// {@template flutter.rendering.selection.SelectionResult.footNote}
|
|
/// This is used after subclasses [SelectionHandler] or [Selectable] handled
|
|
/// [SelectionEdgeUpdateEvent].
|
|
/// {@endtemplate}
|
|
next,
|
|
/// Selection does not reach this [Selectable] and is located before it in
|
|
/// screen order.
|
|
///
|
|
/// {@macro flutter.rendering.selection.SelectionResult.footNote}
|
|
previous,
|
|
/// Selection ends in this [Selectable].
|
|
///
|
|
/// Part of the [Selectable] may or may not be selected, but there is still
|
|
/// content to select forward or backward.
|
|
///
|
|
/// {@macro flutter.rendering.selection.SelectionResult.footNote}
|
|
end,
|
|
/// The result can't be determined in this frame.
|
|
///
|
|
/// This is typically used when the subtree is scrolling to reveal more
|
|
/// content.
|
|
///
|
|
/// {@macro flutter.rendering.selection.SelectionResult.footNote}
|
|
// See `_SelectableRegionState._triggerSelectionEndEdgeUpdate` for how this
|
|
// result affects the selection.
|
|
pending,
|
|
/// There is no result for the selection event.
|
|
///
|
|
/// This is used when a selection result is not applicable, e.g.
|
|
/// [SelectAllSelectionEvent], [ClearSelectionEvent], and
|
|
/// [SelectWordSelectionEvent].
|
|
none,
|
|
}
|
|
|
|
/// The abstract interface to handle [SelectionEvent]s.
|
|
///
|
|
/// This interface is extended by [Selectable] and [SelectionContainerDelegate]
|
|
/// and is typically not used directly.
|
|
///
|
|
/// {@template flutter.rendering.SelectionHandler}
|
|
/// This class returns a [SelectionGeometry] as its [value], and is responsible
|
|
/// to notify its listener when its selection geometry has changed as the result
|
|
/// of receiving selection events.
|
|
/// {@endtemplate}
|
|
abstract class SelectionHandler implements ValueListenable<SelectionGeometry> {
|
|
/// Marks this handler to be responsible for pushing [LeaderLayer]s for the
|
|
/// selection handles.
|
|
///
|
|
/// This handler is responsible for pushing the leader layers with the
|
|
/// given layer links if they are not null. It is possible that only one layer
|
|
/// is non-null if this handler is only responsible for pushing one layer
|
|
/// link.
|
|
///
|
|
/// The `startHandle` needs to be placed at the visual location of selection
|
|
/// start, the `endHandle` needs to be placed at the visual location of selection
|
|
/// end. Typically, the visual locations should be the same as
|
|
/// [SelectionGeometry.startSelectionPoint] and
|
|
/// [SelectionGeometry.endSelectionPoint].
|
|
void pushHandleLayers(LayerLink? startHandle, LayerLink? endHandle);
|
|
|
|
/// Gets the selected content in this object.
|
|
///
|
|
/// Return `null` if nothing is selected.
|
|
SelectedContent? getSelectedContent();
|
|
|
|
/// Handles the [SelectionEvent] sent to this object.
|
|
///
|
|
/// The subclasses need to update their selections or delegate the
|
|
/// [SelectionEvent]s to their subtrees.
|
|
///
|
|
/// The `event`s are subclasses of [SelectionEvent]. Check
|
|
/// [SelectionEvent.type] to determine what kinds of event are dispatched to
|
|
/// this handler and handle them accordingly.
|
|
///
|
|
/// See also:
|
|
/// * [SelectionEventType], which contains all of the possible types.
|
|
SelectionResult dispatchSelectionEvent(SelectionEvent event);
|
|
}
|
|
|
|
/// The selected content in a [Selectable] or [SelectionHandler].
|
|
// TODO(chunhtai): Add more support for rich content.
|
|
// https://github.com/flutter/flutter/issues/104206.
|
|
class SelectedContent {
|
|
/// Creates a selected content object.
|
|
///
|
|
/// Only supports plain text.
|
|
const SelectedContent({required this.plainText});
|
|
|
|
/// The selected content in plain text format.
|
|
final String plainText;
|
|
}
|
|
|
|
/// A mixin that can be selected by users when under a [SelectionArea] widget.
|
|
///
|
|
/// This object receives selection events and the [value] must reflect the
|
|
/// current selection in this [Selectable]. The object must also notify its
|
|
/// listener if the [value] ever changes.
|
|
///
|
|
/// This object is responsible for drawing the selection highlight.
|
|
///
|
|
/// In order to receive the selection event, the mixer needs to register
|
|
/// itself to [SelectionRegistrar]s. Use
|
|
/// [SelectionContainer.maybeOf] to get the selection registrar, and
|
|
/// mix the [SelectionRegistrant] to subscribe to the [SelectionRegistrar]
|
|
/// automatically.
|
|
///
|
|
/// This mixin is typically mixed by [RenderObject]s. The [RenderObject.paint]
|
|
/// methods are responsible to push the [LayerLink]s provided to
|
|
/// [pushHandleLayers].
|
|
///
|
|
/// {@macro flutter.rendering.SelectionHandler}
|
|
///
|
|
/// See also:
|
|
/// * [SelectionArea], which provides an overview of selection system.
|
|
mixin Selectable implements SelectionHandler {
|
|
/// {@macro flutter.rendering.RenderObject.getTransformTo}
|
|
Matrix4 getTransformTo(RenderObject? ancestor);
|
|
|
|
/// The size of this [Selectable].
|
|
Size get size;
|
|
|
|
/// Disposes resources held by the mixer.
|
|
void dispose();
|
|
}
|
|
|
|
/// A mixin to auto-register the mixer to the [registrar].
|
|
///
|
|
/// To use this mixin, the mixer needs to set the [registrar] to the
|
|
/// [SelectionRegistrar] it wants to register to.
|
|
///
|
|
/// This mixin only registers the mixer with the [registrar] if the
|
|
/// [SelectionGeometry.hasContent] returned by the mixer is true.
|
|
mixin SelectionRegistrant on Selectable {
|
|
/// The [SelectionRegistrar] the mixer will be or is registered to.
|
|
///
|
|
/// This [Selectable] only registers the mixer if the
|
|
/// [SelectionGeometry.hasContent] returned by the [Selectable] is true.
|
|
SelectionRegistrar? get registrar => _registrar;
|
|
SelectionRegistrar? _registrar;
|
|
set registrar(SelectionRegistrar? value) {
|
|
if (value == _registrar) {
|
|
return;
|
|
}
|
|
if (value == null) {
|
|
// When registrar goes from non-null to null;
|
|
removeListener(_updateSelectionRegistrarSubscription);
|
|
} else if (_registrar == null) {
|
|
// When registrar goes from null to non-null;
|
|
addListener(_updateSelectionRegistrarSubscription);
|
|
}
|
|
_removeSelectionRegistrarSubscription();
|
|
_registrar = value;
|
|
_updateSelectionRegistrarSubscription();
|
|
}
|
|
|
|
@override
|
|
void dispose() {
|
|
_removeSelectionRegistrarSubscription();
|
|
super.dispose();
|
|
}
|
|
|
|
bool _subscribedToSelectionRegistrar = false;
|
|
void _updateSelectionRegistrarSubscription() {
|
|
if (_registrar == null) {
|
|
_subscribedToSelectionRegistrar = false;
|
|
return;
|
|
}
|
|
if (_subscribedToSelectionRegistrar && !value.hasContent) {
|
|
_registrar!.remove(this);
|
|
_subscribedToSelectionRegistrar = false;
|
|
} else if (!_subscribedToSelectionRegistrar && value.hasContent) {
|
|
_registrar!.add(this);
|
|
_subscribedToSelectionRegistrar = true;
|
|
}
|
|
}
|
|
|
|
void _removeSelectionRegistrarSubscription() {
|
|
if (_subscribedToSelectionRegistrar) {
|
|
_registrar!.remove(this);
|
|
_subscribedToSelectionRegistrar = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
/// A utility class that provides useful methods for handling selection events.
|
|
abstract final class SelectionUtils {
|
|
/// Determines [SelectionResult] purely based on the target rectangle.
|
|
///
|
|
/// This method returns [SelectionResult.end] if the `point` is inside the
|
|
/// `targetRect`. Returns [SelectionResult.previous] if the `point` is
|
|
/// considered to be lower than `targetRect` in screen order. Returns
|
|
/// [SelectionResult.next] if the point is considered to be higher than
|
|
/// `targetRect` in screen order.
|
|
static SelectionResult getResultBasedOnRect(Rect targetRect, Offset point) {
|
|
if (targetRect.contains(point)) {
|
|
return SelectionResult.end;
|
|
}
|
|
if (point.dy < targetRect.top) {
|
|
return SelectionResult.previous;
|
|
}
|
|
if (point.dy > targetRect.bottom) {
|
|
return SelectionResult.next;
|
|
}
|
|
return point.dx >= targetRect.right
|
|
? SelectionResult.next
|
|
: SelectionResult.previous;
|
|
}
|
|
|
|
/// Adjusts the dragging offset based on the target rect.
|
|
///
|
|
/// This method moves the offsets to be within the target rect in case they are
|
|
/// outside the rect.
|
|
///
|
|
/// This is used in the case where a drag happens outside of the rectangle
|
|
/// of a [Selectable].
|
|
///
|
|
/// The logic works as the following:
|
|
/// 
|
|
///
|
|
/// For points inside the rect:
|
|
/// Their effective locations are unchanged.
|
|
///
|
|
/// For points in Area 1:
|
|
/// Move them to top-left of the rect if text direction is ltr, or top-right
|
|
/// if rtl.
|
|
///
|
|
/// For points in Area 2:
|
|
/// Move them to bottom-right of the rect if text direction is ltr, or
|
|
/// bottom-left if rtl.
|
|
static Offset adjustDragOffset(Rect targetRect, Offset point, {TextDirection direction = TextDirection.ltr}) {
|
|
if (targetRect.contains(point)) {
|
|
return point;
|
|
}
|
|
if (point.dy <= targetRect.top ||
|
|
point.dy <= targetRect.bottom && point.dx <= targetRect.left) {
|
|
// Area 1
|
|
return direction == TextDirection.ltr ? targetRect.topLeft : targetRect.topRight;
|
|
} else {
|
|
// Area 2
|
|
return direction == TextDirection.ltr ? targetRect.bottomRight : targetRect.bottomLeft;
|
|
}
|
|
}
|
|
}
|
|
|
|
/// The type of a [SelectionEvent].
|
|
///
|
|
/// Used by [SelectionEvent.type] to distinguish different types of events.
|
|
enum SelectionEventType {
|
|
/// An event to update the selection start edge.
|
|
///
|
|
/// Used by [SelectionEdgeUpdateEvent].
|
|
startEdgeUpdate,
|
|
|
|
/// An event to update the selection end edge.
|
|
///
|
|
/// Used by [SelectionEdgeUpdateEvent].
|
|
endEdgeUpdate,
|
|
|
|
/// An event to clear the current selection.
|
|
///
|
|
/// Used by [ClearSelectionEvent].
|
|
clear,
|
|
|
|
/// An event to select all the available content.
|
|
///
|
|
/// Used by [SelectAllSelectionEvent].
|
|
selectAll,
|
|
|
|
/// An event to select a word at the location
|
|
/// [SelectWordSelectionEvent.globalPosition].
|
|
///
|
|
/// Used by [SelectWordSelectionEvent].
|
|
selectWord,
|
|
|
|
/// An event that extends the selection by a specific [TextGranularity].
|
|
granularlyExtendSelection,
|
|
|
|
/// An event that extends the selection in a specific direction.
|
|
directionallyExtendSelection,
|
|
}
|
|
|
|
/// The unit of how selection handles move in text.
|
|
///
|
|
/// The [GranularlyExtendSelectionEvent] uses this enum to describe how
|
|
/// [Selectable] should extend its selection.
|
|
enum TextGranularity {
|
|
/// Treats each character as an atomic unit when moving the selection handles.
|
|
character,
|
|
|
|
/// Treats word as an atomic unit when moving the selection handles.
|
|
word,
|
|
|
|
/// Treats each line break as an atomic unit when moving the selection handles.
|
|
line,
|
|
|
|
/// Treats the entire document as an atomic unit when moving the selection handles.
|
|
document,
|
|
}
|
|
|
|
/// An abstract base class for selection events.
|
|
///
|
|
/// This should not be directly used. To handle a selection event, it should
|
|
/// be downcast to a specific subclass. One can use [type] to look up which
|
|
/// subclasses to downcast to.
|
|
///
|
|
/// See also:
|
|
/// * [SelectAllSelectionEvent], for events to select all contents.
|
|
/// * [ClearSelectionEvent], for events to clear selections.
|
|
/// * [SelectWordSelectionEvent], for events to select words at the locations.
|
|
/// * [SelectionEdgeUpdateEvent], for events to update selection edges.
|
|
/// * [SelectionEventType], for determining the subclass types.
|
|
abstract class SelectionEvent {
|
|
const SelectionEvent._(this.type);
|
|
|
|
/// The type of this selection event.
|
|
final SelectionEventType type;
|
|
}
|
|
|
|
/// Selects all selectable contents.
|
|
///
|
|
/// This event can be sent as the result of keyboard select-all, i.e.
|
|
/// ctrl + A, or cmd + A in macOS.
|
|
class SelectAllSelectionEvent extends SelectionEvent {
|
|
/// Creates a select all selection event.
|
|
const SelectAllSelectionEvent(): super._(SelectionEventType.selectAll);
|
|
}
|
|
|
|
/// Clears the selection from the [Selectable] and removes any existing
|
|
/// highlight as if there is no selection at all.
|
|
class ClearSelectionEvent extends SelectionEvent {
|
|
/// Create a clear selection event.
|
|
const ClearSelectionEvent(): super._(SelectionEventType.clear);
|
|
}
|
|
|
|
/// Selects the whole word at the location.
|
|
///
|
|
/// This event can be sent as the result of mobile long press selection.
|
|
class SelectWordSelectionEvent extends SelectionEvent {
|
|
/// Creates a select word event at the [globalPosition].
|
|
const SelectWordSelectionEvent({required this.globalPosition}): super._(SelectionEventType.selectWord);
|
|
|
|
/// The position in global coordinates to select word at.
|
|
final Offset globalPosition;
|
|
}
|
|
|
|
/// Updates a selection edge.
|
|
///
|
|
/// An active selection contains two edges, start and end. Use the [type] to
|
|
/// determine which edge this event applies to. If the [type] is
|
|
/// [SelectionEventType.startEdgeUpdate], the event updates start edge. If the
|
|
/// [type] is [SelectionEventType.endEdgeUpdate], the event updates end edge.
|
|
///
|
|
/// The [globalPosition] contains the new offset of the edge.
|
|
///
|
|
/// This event is dispatched when the framework detects [DragStartDetails] in
|
|
/// [SelectionArea]'s gesture recognizers for mouse devices, or the selection
|
|
/// handles have been dragged to new locations.
|
|
class SelectionEdgeUpdateEvent extends SelectionEvent {
|
|
/// Creates a selection start edge update event.
|
|
///
|
|
/// The [globalPosition] contains the location of the selection start edge.
|
|
const SelectionEdgeUpdateEvent.forStart({
|
|
required this.globalPosition
|
|
}) : super._(SelectionEventType.startEdgeUpdate);
|
|
|
|
/// Creates a selection end edge update event.
|
|
///
|
|
/// The [globalPosition] contains the new location of the selection end edge.
|
|
const SelectionEdgeUpdateEvent.forEnd({
|
|
required this.globalPosition
|
|
}) : super._(SelectionEventType.endEdgeUpdate);
|
|
|
|
/// The new location of the selection edge.
|
|
final Offset globalPosition;
|
|
}
|
|
|
|
/// Extends the start or end of the selection by a given [TextGranularity].
|
|
///
|
|
/// To handle this event, move the associated selection edge, as dictated by
|
|
/// [isEnd], according to the [granularity].
|
|
class GranularlyExtendSelectionEvent extends SelectionEvent {
|
|
/// Creates a [GranularlyExtendSelectionEvent].
|
|
///
|
|
/// All parameters are required and must not be null.
|
|
const GranularlyExtendSelectionEvent({
|
|
required this.forward,
|
|
required this.isEnd,
|
|
required this.granularity,
|
|
}) : super._(SelectionEventType.granularlyExtendSelection);
|
|
|
|
/// Whether to extend the selection forward.
|
|
final bool forward;
|
|
|
|
/// Whether this event is updating the end selection edge.
|
|
final bool isEnd;
|
|
|
|
/// The granularity for which the selection extend.
|
|
final TextGranularity granularity;
|
|
}
|
|
|
|
/// The direction to extend a selection.
|
|
///
|
|
/// The [DirectionallyExtendSelectionEvent] uses this enum to describe how
|
|
/// [Selectable] should extend their selection.
|
|
enum SelectionExtendDirection {
|
|
/// Move one edge of the selection vertically to the previous adjacent line.
|
|
///
|
|
/// For text selection, it should consider both soft and hard linebreak.
|
|
///
|
|
/// See [DirectionallyExtendSelectionEvent.dx] on how to
|
|
/// calculate the horizontal offset.
|
|
previousLine,
|
|
|
|
/// Move one edge of the selection vertically to the next adjacent line.
|
|
///
|
|
/// For text selection, it should consider both soft and hard linebreak.
|
|
///
|
|
/// See [DirectionallyExtendSelectionEvent.dx] on how to
|
|
/// calculate the horizontal offset.
|
|
nextLine,
|
|
|
|
/// Move the selection edges forward to a certain horizontal offset in the
|
|
/// same line.
|
|
///
|
|
/// If there is no on-going selection, the selection must start with the first
|
|
/// line (or equivalence of first line in a non-text selectable) and select
|
|
/// toward the horizontal offset in the same line.
|
|
///
|
|
/// The selectable that receives [DirectionallyExtendSelectionEvent] with this
|
|
/// enum must return [SelectionResult.end].
|
|
///
|
|
/// See [DirectionallyExtendSelectionEvent.dx] on how to
|
|
/// calculate the horizontal offset.
|
|
forward,
|
|
|
|
/// Move the selection edges backward to a certain horizontal offset in the
|
|
/// same line.
|
|
///
|
|
/// If there is no on-going selection, the selection must start with the last
|
|
/// line (or equivalence of last line in a non-text selectable) and select
|
|
/// backward the horizontal offset in the same line.
|
|
///
|
|
/// The selectable that receives [DirectionallyExtendSelectionEvent] with this
|
|
/// enum must return [SelectionResult.end].
|
|
///
|
|
/// See [DirectionallyExtendSelectionEvent.dx] on how to
|
|
/// calculate the horizontal offset.
|
|
backward,
|
|
}
|
|
|
|
/// Extends the current selection with respect to a [direction].
|
|
///
|
|
/// To handle this event, move the associated selection edge, as dictated by
|
|
/// [isEnd], according to the [direction].
|
|
///
|
|
/// The movements are always based on [dx]. The value is in
|
|
/// global coordinates and is the horizontal offset the selection edge should
|
|
/// move to when moving to across lines.
|
|
class DirectionallyExtendSelectionEvent extends SelectionEvent {
|
|
/// Creates a [DirectionallyExtendSelectionEvent].
|
|
///
|
|
/// All parameters are required and must not be null.
|
|
const DirectionallyExtendSelectionEvent({
|
|
required this.dx,
|
|
required this.isEnd,
|
|
required this.direction,
|
|
}) : super._(SelectionEventType.directionallyExtendSelection);
|
|
|
|
/// The horizontal offset the selection should move to.
|
|
///
|
|
/// The offset is in global coordinates.
|
|
final double dx;
|
|
|
|
/// Whether this event is updating the end selection edge.
|
|
final bool isEnd;
|
|
|
|
/// The directional movement of this event.
|
|
///
|
|
/// See also:
|
|
/// * [SelectionExtendDirection], which explains how to handle each enum.
|
|
final SelectionExtendDirection direction;
|
|
|
|
/// Makes a copy of this object with its property replaced with the new
|
|
/// values.
|
|
DirectionallyExtendSelectionEvent copyWith({
|
|
double? dx,
|
|
bool? isEnd,
|
|
SelectionExtendDirection? direction,
|
|
}) {
|
|
return DirectionallyExtendSelectionEvent(
|
|
dx: dx ?? this.dx,
|
|
isEnd: isEnd ?? this.isEnd,
|
|
direction: direction ?? this.direction,
|
|
);
|
|
}
|
|
}
|
|
|
|
/// A registrar that keeps track of [Selectable]s in the subtree.
|
|
///
|
|
/// A [Selectable] is only included in the [SelectableRegion] if they are
|
|
/// registered with a [SelectionRegistrar]. Once a [Selectable] is registered,
|
|
/// it will receive [SelectionEvent]s in
|
|
/// [SelectionHandler.dispatchSelectionEvent].
|
|
///
|
|
/// Use [SelectionContainer.maybeOf] to get the immediate [SelectionRegistrar]
|
|
/// in the ancestor chain above the build context.
|
|
///
|
|
/// See also:
|
|
/// * [SelectableRegion], which provides an overview of the selection system.
|
|
/// * [SelectionRegistrarScope], which hosts the [SelectionRegistrar] for the
|
|
/// subtree.
|
|
/// * [SelectionRegistrant], which auto registers the object with the mixin to
|
|
/// [SelectionRegistrar].
|
|
abstract class SelectionRegistrar {
|
|
/// Adds the [selectable] into the registrar.
|
|
///
|
|
/// A [Selectable] must register with the [SelectionRegistrar] in order to
|
|
/// receive selection events.
|
|
void add(Selectable selectable);
|
|
|
|
/// Removes the [selectable] from the registrar.
|
|
///
|
|
/// A [Selectable] must unregister itself if it is removed from the rendering
|
|
/// tree.
|
|
void remove(Selectable selectable);
|
|
}
|
|
|
|
/// The status that indicates whether there is a selection and whether the
|
|
/// selection is collapsed.
|
|
///
|
|
/// A collapsed selection means the selection starts and ends at the same
|
|
/// location.
|
|
enum SelectionStatus {
|
|
/// The selection is not collapsed.
|
|
///
|
|
/// For example if `{}` represent the selection edges:
|
|
/// 'ab{cd}', the collapsing status is [uncollapsed].
|
|
/// '{abcd}', the collapsing status is [uncollapsed].
|
|
uncollapsed,
|
|
|
|
/// The selection is collapsed.
|
|
///
|
|
/// For example if `{}` represent the selection edges:
|
|
/// 'ab{}cd', the collapsing status is [collapsed].
|
|
/// '{}abcd', the collapsing status is [collapsed].
|
|
/// 'abcd{}', the collapsing status is [collapsed].
|
|
collapsed,
|
|
|
|
/// No selection.
|
|
none,
|
|
}
|
|
|
|
/// The geometry of the current selection.
|
|
///
|
|
/// This includes details such as the locations of the selection start and end,
|
|
/// line height, etc. This information is used for drawing selection controls
|
|
/// for mobile platforms.
|
|
///
|
|
/// The positions in geometry are in local coordinates of the [SelectionHandler]
|
|
/// or [Selectable].
|
|
@immutable
|
|
class SelectionGeometry {
|
|
/// Creates a selection geometry object.
|
|
///
|
|
/// If any of the [startSelectionPoint] and [endSelectionPoint] is not null,
|
|
/// the [status] must not be [SelectionStatus.none].
|
|
const SelectionGeometry({
|
|
this.startSelectionPoint,
|
|
this.endSelectionPoint,
|
|
required this.status,
|
|
required this.hasContent,
|
|
}) : assert((startSelectionPoint == null && endSelectionPoint == null) || status != SelectionStatus.none);
|
|
|
|
/// The geometry information at the selection start.
|
|
///
|
|
/// This information is used for drawing mobile selection controls. The
|
|
/// [SelectionPoint.localPosition] of the selection start is typically at the
|
|
/// start of the selection highlight at where the start selection handle
|
|
/// should be drawn.
|
|
///
|
|
/// The [SelectionPoint.handleType] should be [TextSelectionHandleType.left]
|
|
/// for forward selection or [TextSelectionHandleType.right] for backward
|
|
/// selection in most cases.
|
|
///
|
|
/// Can be null if the selection start is offstage, for example, when the
|
|
/// selection is outside of the viewport or is kept alive by a scrollable.
|
|
final SelectionPoint? startSelectionPoint;
|
|
|
|
/// The geometry information at the selection end.
|
|
///
|
|
/// This information is used for drawing mobile selection controls. The
|
|
/// [SelectionPoint.localPosition] of the selection end is typically at the end
|
|
/// of the selection highlight at where the end selection handle should be
|
|
/// drawn.
|
|
///
|
|
/// The [SelectionPoint.handleType] should be [TextSelectionHandleType.right]
|
|
/// for forward selection or [TextSelectionHandleType.left] for backward
|
|
/// selection in most cases.
|
|
///
|
|
/// Can be null if the selection end is offstage, for example, when the
|
|
/// selection is outside of the viewport or is kept alive by a scrollable.
|
|
final SelectionPoint? endSelectionPoint;
|
|
|
|
/// The status of ongoing selection in the [Selectable] or [SelectionHandler].
|
|
final SelectionStatus status;
|
|
|
|
/// Whether there is any selectable content in the [Selectable] or
|
|
/// [SelectionHandler].
|
|
final bool hasContent;
|
|
|
|
/// Whether there is an ongoing selection.
|
|
bool get hasSelection => status != SelectionStatus.none;
|
|
|
|
/// Makes a copy of this object with the given values updated.
|
|
SelectionGeometry copyWith({
|
|
SelectionPoint? startSelectionPoint,
|
|
SelectionPoint? endSelectionPoint,
|
|
SelectionStatus? status,
|
|
bool? hasContent,
|
|
}) {
|
|
return SelectionGeometry(
|
|
startSelectionPoint: startSelectionPoint ?? this.startSelectionPoint,
|
|
endSelectionPoint: endSelectionPoint ?? this.endSelectionPoint,
|
|
status: status ?? this.status,
|
|
hasContent: hasContent ?? this.hasContent,
|
|
);
|
|
}
|
|
|
|
@override
|
|
bool operator ==(Object other) {
|
|
if (identical(this, other)) {
|
|
return true;
|
|
}
|
|
if (other.runtimeType != runtimeType) {
|
|
return false;
|
|
}
|
|
return other is SelectionGeometry
|
|
&& other.startSelectionPoint == startSelectionPoint
|
|
&& other.endSelectionPoint == endSelectionPoint
|
|
&& other.status == status
|
|
&& other.hasContent == hasContent;
|
|
}
|
|
|
|
@override
|
|
int get hashCode {
|
|
return Object.hash(
|
|
startSelectionPoint,
|
|
endSelectionPoint,
|
|
status,
|
|
hasContent,
|
|
);
|
|
}
|
|
}
|
|
|
|
/// The geometry information of a selection point.
|
|
@immutable
|
|
class SelectionPoint {
|
|
/// Creates a selection point object.
|
|
///
|
|
/// All properties must not be null.
|
|
const SelectionPoint({
|
|
required this.localPosition,
|
|
required this.lineHeight,
|
|
required this.handleType,
|
|
});
|
|
|
|
/// The position of the selection point in the local coordinates of the
|
|
/// containing [Selectable].
|
|
final Offset localPosition;
|
|
|
|
/// The line height at the selection point.
|
|
final double lineHeight;
|
|
|
|
/// The selection handle type that should be used at the selection point.
|
|
///
|
|
/// This is used for building the mobile selection handle.
|
|
final TextSelectionHandleType handleType;
|
|
|
|
@override
|
|
bool operator ==(Object other) {
|
|
if (identical(this, other)) {
|
|
return true;
|
|
}
|
|
if (other.runtimeType != runtimeType) {
|
|
return false;
|
|
}
|
|
return other is SelectionPoint
|
|
&& other.localPosition == localPosition
|
|
&& other.lineHeight == lineHeight
|
|
&& other.handleType == handleType;
|
|
}
|
|
|
|
@override
|
|
int get hashCode {
|
|
return Object.hash(
|
|
localPosition,
|
|
lineHeight,
|
|
handleType,
|
|
);
|
|
}
|
|
}
|
|
|
|
/// The type of selection handle to be displayed.
|
|
///
|
|
/// With mixed-direction text, both handles may be the same type. Examples:
|
|
///
|
|
/// * LTR text: 'the <quick brown> fox':
|
|
///
|
|
/// The '<' is drawn with the [left] type, the '>' with the [right]
|
|
///
|
|
/// * RTL text: 'XOF <NWORB KCIUQ> EHT':
|
|
///
|
|
/// Same as above.
|
|
///
|
|
/// * mixed text: '<the NWOR<B KCIUQ fox'
|
|
///
|
|
/// Here 'the QUICK B' is selected, but 'QUICK BROWN' is RTL. Both are drawn
|
|
/// with the [left] type.
|
|
///
|
|
/// See also:
|
|
///
|
|
/// * [TextDirection], which discusses left-to-right and right-to-left text in
|
|
/// more detail.
|
|
enum TextSelectionHandleType {
|
|
/// The selection handle is to the left of the selection end point.
|
|
left,
|
|
|
|
/// The selection handle is to the right of the selection end point.
|
|
right,
|
|
|
|
/// The start and end of the selection are co-incident at this point.
|
|
collapsed,
|
|
}
|