dartlangeditorflutterflutter-appsflutter-examplesflutter-packageflutter-widgetquillquill-deltaquilljsreactquillrich-textrich-text-editorwysiwygwysiwyg-editor
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
383 lines
13 KiB
383 lines
13 KiB
4 years ago
|
import 'package:flutter/cupertino.dart';
|
||
|
import 'package:flutter/gestures.dart';
|
||
|
import 'package:flutter/material.dart';
|
||
3 years ago
|
import 'package:flutter/scheduler.dart';
|
||
4 years ago
|
|
||
9 months ago
|
import '../../common/utils/platform.dart';
|
||
|
import '../../document/attribute.dart';
|
||
|
import '../../document/nodes/leaf.dart';
|
||
|
import '../editor.dart';
|
||
1 year ago
|
import '../raw_editor/raw_editor.dart';
|
||
9 months ago
|
import 'text/text_selection.dart';
|
||
4 years ago
|
|
||
4 years ago
|
typedef CustomStyleBuilder = TextStyle Function(Attribute attribute);
|
||
4 years ago
|
|
||
2 years ago
|
typedef CustomRecognizerBuilder = GestureRecognizer? Function(
|
||
2 years ago
|
Attribute attribute, Leaf leaf);
|
||
2 years ago
|
|
||
3 years ago
|
/// Delegate interface for the [EditorTextSelectionGestureDetectorBuilder].
|
||
|
///
|
||
|
/// The interface is usually implemented by textfield implementations wrapping
|
||
|
/// [EditableText], that use a [EditorTextSelectionGestureDetectorBuilder]
|
||
|
/// to build a [EditorTextSelectionGestureDetector] for their [EditableText].
|
||
|
/// The delegate provides the builder with information about the current state
|
||
|
/// of the textfield.
|
||
|
/// Based on these information, the builder adds the correct gesture handlers
|
||
|
/// to the gesture detector.
|
||
|
///
|
||
|
/// See also:
|
||
|
///
|
||
|
/// * [TextField], which implements this delegate for the Material textfield.
|
||
|
/// * [CupertinoTextField], which implements this delegate for the Cupertino
|
||
|
/// textfield.
|
||
4 years ago
|
abstract class EditorTextSelectionGestureDetectorBuilderDelegate {
|
||
3 years ago
|
/// [GlobalKey] to the [EditableText] for which the
|
||
|
/// [EditorTextSelectionGestureDetectorBuilder] will build
|
||
|
/// a [EditorTextSelectionGestureDetector].
|
||
|
GlobalKey<EditorState> get editableTextKey;
|
||
4 years ago
|
|
||
3 years ago
|
/// Whether the textfield should respond to force presses.
|
||
|
bool get forcePressEnabled;
|
||
4 years ago
|
|
||
3 years ago
|
/// Whether the user may select text in the textfield.
|
||
|
bool get selectionEnabled;
|
||
4 years ago
|
}
|
||
|
|
||
3 years ago
|
/// Builds a [EditorTextSelectionGestureDetector] to wrap an [EditableText].
|
||
|
///
|
||
|
/// The class implements sensible defaults for many user interactions
|
||
|
/// with an [EditableText] (see the documentation of the various gesture handler
|
||
|
/// methods, e.g. [onTapDown], [onForcePressStart], etc.). Subclasses of
|
||
|
/// [EditorTextSelectionGestureDetectorBuilder] can change the behavior
|
||
|
/// performed in responds to these gesture events by overriding
|
||
|
/// the corresponding handler methods of this class.
|
||
|
///
|
||
|
/// The resulting [EditorTextSelectionGestureDetector] to wrap an [EditableText]
|
||
|
/// is obtained by calling [buildGestureDetector].
|
||
|
///
|
||
|
/// See also:
|
||
|
///
|
||
|
/// * [TextField], which uses a subclass to implement the Material-specific
|
||
|
/// gesture logic of an [EditableText].
|
||
|
/// * [CupertinoTextField], which uses a subclass to implement the
|
||
|
/// Cupertino-specific gesture logic of an [EditableText].
|
||
4 years ago
|
class EditorTextSelectionGestureDetectorBuilder {
|
||
3 years ago
|
/// Creates a [EditorTextSelectionGestureDetectorBuilder].
|
||
|
///
|
||
|
/// The [delegate] must not be null.
|
||
2 years ago
|
EditorTextSelectionGestureDetectorBuilder(
|
||
|
{required this.delegate, this.detectWordBoundary = true});
|
||
4 years ago
|
|
||
3 years ago
|
/// The delegate for this [EditorTextSelectionGestureDetectorBuilder].
|
||
|
///
|
||
|
/// The delegate provides the builder with information about what actions can
|
||
|
/// currently be performed on the textfield. Based on this, the builder adds
|
||
|
/// the correct gesture handlers to the gesture detector.
|
||
|
@protected
|
||
4 years ago
|
final EditorTextSelectionGestureDetectorBuilderDelegate delegate;
|
||
3 years ago
|
|
||
|
/// Whether to show the selection toolbar.
|
||
|
///
|
||
|
/// It is based on the signal source when a [onTapDown] is called. This getter
|
||
|
/// will return true if current [onTapDown] event is triggered by a touch or
|
||
|
/// a stylus.
|
||
4 years ago
|
bool shouldShowSelectionToolbar = true;
|
||
10 months ago
|
PointerDeviceKind? kind;
|
||
|
|
||
|
/// Check if the selection toolbar should show.
|
||
|
///
|
||
|
/// If mouse is used, the toolbar should only show when right click.
|
||
|
/// Else, it should show when the selection is enabled.
|
||
|
bool checkSelectionToolbarShouldShow({required bool isAdditionalAction}) {
|
||
|
if (kind != PointerDeviceKind.mouse) {
|
||
|
return shouldShowSelectionToolbar;
|
||
|
}
|
||
|
return shouldShowSelectionToolbar && isAdditionalAction;
|
||
|
}
|
||
4 years ago
|
|
||
2 years ago
|
bool detectWordBoundary = true;
|
||
|
|
||
3 years ago
|
/// The [State] of the [EditableText] for which the builder will provide a
|
||
|
/// [EditorTextSelectionGestureDetector].
|
||
|
@protected
|
||
3 years ago
|
EditorState? get editor => delegate.editableTextKey.currentState;
|
||
4 years ago
|
|
||
3 years ago
|
/// The [RenderObject] of the [EditableText] for which the builder will
|
||
|
/// provide a [EditorTextSelectionGestureDetector].
|
||
|
@protected
|
||
3 years ago
|
RenderEditor? get renderEditor => editor?.renderEditor;
|
||
4 years ago
|
|
||
3 years ago
|
/// Handler for [EditorTextSelectionGestureDetector.onTapDown].
|
||
|
///
|
||
|
/// By default, it forwards the tap to [RenderEditable.handleTapDown] and sets
|
||
|
/// [shouldShowSelectionToolbar] to true if the tap was initiated by a finger
|
||
|
/// or stylus.
|
||
|
///
|
||
|
/// See also:
|
||
|
///
|
||
|
/// * [EditorTextSelectionGestureDetector.onTapDown],
|
||
|
/// which triggers this callback.
|
||
|
@protected
|
||
4 years ago
|
void onTapDown(TapDownDetails details) {
|
||
3 years ago
|
renderEditor!.handleTapDown(details);
|
||
3 years ago
|
// The selection overlay should only be shown when the user is interacting
|
||
|
// through a touch screen (via either a finger or a stylus).
|
||
|
// A mouse shouldn't trigger the selection overlay.
|
||
|
// For backwards-compatibility, we treat a null kind the same as touch.
|
||
10 months ago
|
kind = details.kind;
|
||
4 years ago
|
shouldShowSelectionToolbar = kind == null ||
|
||
3 years ago
|
kind ==
|
||
|
PointerDeviceKind
|
||
3 years ago
|
.mouse || // Enable word selection by mouse double tap
|
||
4 years ago
|
kind == PointerDeviceKind.touch ||
|
||
|
kind == PointerDeviceKind.stylus;
|
||
|
}
|
||
|
|
||
3 years ago
|
/// Handler for [EditorTextSelectionGestureDetector.onForcePressStart].
|
||
|
///
|
||
|
/// By default, it selects the word at the position of the force press,
|
||
|
/// if selection is enabled.
|
||
|
///
|
||
|
/// This callback is only applicable when force press is enabled.
|
||
|
///
|
||
|
/// See also:
|
||
|
///
|
||
|
/// * [EditorTextSelectionGestureDetector.onForcePressStart],
|
||
|
/// which triggers this callback.
|
||
|
@protected
|
||
4 years ago
|
void onForcePressStart(ForcePressDetails details) {
|
||
3 years ago
|
assert(delegate.forcePressEnabled);
|
||
4 years ago
|
shouldShowSelectionToolbar = true;
|
||
3 years ago
|
if (delegate.selectionEnabled) {
|
||
3 years ago
|
renderEditor!.selectWordsInRange(
|
||
4 years ago
|
details.globalPosition,
|
||
|
null,
|
||
|
SelectionChangedCause.forcePress,
|
||
|
);
|
||
|
}
|
||
|
}
|
||
|
|
||
3 years ago
|
/// Handler for [EditorTextSelectionGestureDetector.onForcePressEnd].
|
||
|
///
|
||
|
/// By default, it selects words in the range specified in [details] and shows
|
||
|
/// toolbar if it is necessary.
|
||
|
///
|
||
|
/// This callback is only applicable when force press is enabled.
|
||
|
///
|
||
|
/// See also:
|
||
|
///
|
||
|
/// * [EditorTextSelectionGestureDetector.onForcePressEnd],
|
||
|
/// which triggers this callback.
|
||
|
@protected
|
||
4 years ago
|
void onForcePressEnd(ForcePressDetails details) {
|
||
3 years ago
|
assert(delegate.forcePressEnabled);
|
||
3 years ago
|
renderEditor!.selectWordsInRange(
|
||
4 years ago
|
details.globalPosition,
|
||
|
null,
|
||
|
SelectionChangedCause.forcePress,
|
||
|
);
|
||
10 months ago
|
if (checkSelectionToolbarShouldShow(isAdditionalAction: false)) {
|
||
3 years ago
|
editor!.showToolbar();
|
||
4 years ago
|
}
|
||
|
}
|
||
|
|
||
3 years ago
|
/// Handler for [EditorTextSelectionGestureDetector.onSingleTapUp].
|
||
|
///
|
||
|
/// By default, it selects word edge if selection is enabled.
|
||
|
///
|
||
|
/// See also:
|
||
|
///
|
||
|
/// * [EditorTextSelectionGestureDetector.onSingleTapUp], which triggers
|
||
|
/// this callback.
|
||
|
@protected
|
||
4 years ago
|
void onSingleTapUp(TapUpDetails details) {
|
||
3 years ago
|
if (delegate.selectionEnabled) {
|
||
3 years ago
|
renderEditor!.selectWordEdge(SelectionChangedCause.tap);
|
||
4 years ago
|
}
|
||
|
}
|
||
|
|
||
3 years ago
|
/// onSingleTapUp for mouse right click
|
||
|
@protected
|
||
3 years ago
|
void onSecondarySingleTapUp(TapUpDetails details) {
|
||
|
// added to show toolbar by right click
|
||
10 months ago
|
if (checkSelectionToolbarShouldShow(isAdditionalAction: true)) {
|
||
3 years ago
|
editor!.showToolbar();
|
||
|
}
|
||
|
}
|
||
|
|
||
3 years ago
|
/// Handler for [EditorTextSelectionGestureDetector.onSingleTapCancel].
|
||
|
///
|
||
|
/// By default, it services as place holder to enable subclass override.
|
||
|
///
|
||
|
/// See also:
|
||
|
///
|
||
|
/// * [EditorTextSelectionGestureDetector.onSingleTapCancel], which triggers
|
||
|
/// this callback.
|
||
|
@protected
|
||
|
void onSingleTapCancel() {
|
||
|
/* Subclass should override this method if needed. */
|
||
|
}
|
||
4 years ago
|
|
||
3 years ago
|
/// Handler for [EditorTextSelectionGestureDetector.onSingleLongTapStart].
|
||
|
///
|
||
|
/// By default, it selects text position specified in [details] if selection
|
||
|
/// is enabled.
|
||
|
///
|
||
|
/// See also:
|
||
|
///
|
||
|
/// * [EditorTextSelectionGestureDetector.onSingleLongTapStart],
|
||
|
/// which triggers this callback.
|
||
|
@protected
|
||
4 years ago
|
void onSingleLongTapStart(LongPressStartDetails details) {
|
||
3 years ago
|
if (delegate.selectionEnabled) {
|
||
3 years ago
|
renderEditor!.selectPositionAt(
|
||
3 years ago
|
from: details.globalPosition,
|
||
|
cause: SelectionChangedCause.longPress,
|
||
4 years ago
|
);
|
||
|
}
|
||
|
}
|
||
|
|
||
3 years ago
|
/// Handler for [EditorTextSelectionGestureDetector.onSingleLongTapMoveUpdate]
|
||
|
///
|
||
|
/// By default, it updates the selection location specified in [details] if
|
||
|
/// selection is enabled.
|
||
|
///
|
||
|
/// See also:
|
||
|
///
|
||
|
/// * [EditorTextSelectionGestureDetector.onSingleLongTapMoveUpdate], which
|
||
|
/// triggers this callback.
|
||
|
@protected
|
||
4 years ago
|
void onSingleLongTapMoveUpdate(LongPressMoveUpdateDetails details) {
|
||
3 years ago
|
if (delegate.selectionEnabled) {
|
||
3 years ago
|
renderEditor!.selectPositionAt(
|
||
3 years ago
|
from: details.globalPosition,
|
||
|
cause: SelectionChangedCause.longPress,
|
||
4 years ago
|
);
|
||
|
}
|
||
|
}
|
||
|
|
||
3 years ago
|
/// Handler for [EditorTextSelectionGestureDetector.onSingleLongTapEnd].
|
||
|
///
|
||
|
/// By default, it shows toolbar if necessary.
|
||
|
///
|
||
|
/// See also:
|
||
|
///
|
||
|
/// * [EditorTextSelectionGestureDetector.onSingleLongTapEnd],
|
||
|
/// which triggers this callback.
|
||
|
@protected
|
||
4 years ago
|
void onSingleLongTapEnd(LongPressEndDetails details) {
|
||
10 months ago
|
if (checkSelectionToolbarShouldShow(isAdditionalAction: false)) {
|
||
3 years ago
|
editor!.showToolbar();
|
||
4 years ago
|
}
|
||
|
}
|
||
|
|
||
3 years ago
|
/// Handler for [EditorTextSelectionGestureDetector.onDoubleTapDown].
|
||
|
///
|
||
|
/// By default, it selects a word through [RenderEditable.selectWord] if
|
||
|
/// selectionEnabled and shows toolbar if necessary.
|
||
|
///
|
||
|
/// See also:
|
||
|
///
|
||
|
/// * [EditorTextSelectionGestureDetector.onDoubleTapDown],
|
||
|
/// which triggers this callback.
|
||
|
@protected
|
||
4 years ago
|
void onDoubleTapDown(TapDownDetails details) {
|
||
3 years ago
|
if (delegate.selectionEnabled) {
|
||
3 years ago
|
renderEditor!.selectWord(SelectionChangedCause.tap);
|
||
3 years ago
|
// allow the selection to get updated before trying to bring up
|
||
|
// toolbars.
|
||
|
//
|
||
|
// if double tap happens on an editor that doesn't
|
||
|
// have focus, selection hasn't been set when the toolbars
|
||
|
// get added
|
||
3 years ago
|
SchedulerBinding.instance.addPostFrameCallback((_) {
|
||
10 months ago
|
if (checkSelectionToolbarShouldShow(isAdditionalAction: false)) {
|
||
3 years ago
|
editor!.showToolbar();
|
||
|
}
|
||
|
});
|
||
4 years ago
|
}
|
||
|
}
|
||
|
|
||
3 years ago
|
/// Handler for [EditorTextSelectionGestureDetector.onDragSelectionStart].
|
||
|
///
|
||
|
/// By default, it selects a text position specified in [details].
|
||
|
///
|
||
|
/// See also:
|
||
|
///
|
||
|
/// * [EditorTextSelectionGestureDetector.onDragSelectionStart],
|
||
|
/// which triggers this callback.
|
||
|
@protected
|
||
4 years ago
|
void onDragSelectionStart(DragStartDetails details) {
|
||
3 years ago
|
renderEditor!.handleDragStart(details);
|
||
4 years ago
|
}
|
||
|
|
||
3 years ago
|
/// Handler for [EditorTextSelectionGestureDetector.onDragSelectionUpdate].
|
||
|
///
|
||
|
/// By default, it updates the selection location specified in the provided
|
||
|
/// details objects.
|
||
|
///
|
||
|
/// See also:
|
||
|
///
|
||
|
/// * [EditorTextSelectionGestureDetector.onDragSelectionUpdate],
|
||
|
/// which triggers this callback./lib/src/material/text_field.dart
|
||
|
@protected
|
||
4 years ago
|
void onDragSelectionUpdate(
|
||
2 years ago
|
//DragStartDetails startDetails,
|
||
|
DragUpdateDetails updateDetails) {
|
||
1 year ago
|
renderEditor!.extendSelection(
|
||
|
updateDetails.globalPosition,
|
||
|
cause: SelectionChangedCause.drag,
|
||
|
);
|
||
4 years ago
|
}
|
||
|
|
||
3 years ago
|
/// Handler for [EditorTextSelectionGestureDetector.onDragSelectionEnd].
|
||
|
///
|
||
|
/// By default, it services as place holder to enable subclass override.
|
||
|
///
|
||
|
/// See also:
|
||
|
///
|
||
|
/// * [EditorTextSelectionGestureDetector.onDragSelectionEnd],
|
||
|
/// which triggers this callback.
|
||
|
@protected
|
||
3 years ago
|
void onDragSelectionEnd(DragEndDetails details) {
|
||
3 years ago
|
renderEditor!.handleDragEnd(details);
|
||
1 year ago
|
if (isDesktop(supportWeb: true) &&
|
||
3 years ago
|
delegate.selectionEnabled &&
|
||
10 months ago
|
checkSelectionToolbarShouldShow(isAdditionalAction: false)) {
|
||
3 years ago
|
// added to show selection copy/paste toolbar after drag to select
|
||
3 years ago
|
editor!.showToolbar();
|
||
3 years ago
|
}
|
||
3 years ago
|
}
|
||
4 years ago
|
|
||
3 years ago
|
/// Returns a [EditorTextSelectionGestureDetector] configured with
|
||
|
/// the handlers provided by this builder.
|
||
|
///
|
||
|
/// The [child] or its subtree should contain [EditableText].
|
||
1 year ago
|
Widget build({
|
||
|
required HitTestBehavior behavior,
|
||
|
required Widget child,
|
||
|
Key? key,
|
||
|
bool detectWordBoundary = true,
|
||
|
}) {
|
||
4 years ago
|
return EditorTextSelectionGestureDetector(
|
||
1 year ago
|
key: key,
|
||
|
onTapDown: onTapDown,
|
||
|
onForcePressStart: delegate.forcePressEnabled ? onForcePressStart : null,
|
||
|
onForcePressEnd: delegate.forcePressEnabled ? onForcePressEnd : null,
|
||
|
onSingleTapUp: onSingleTapUp,
|
||
|
onSingleTapCancel: onSingleTapCancel,
|
||
|
onSingleLongTapStart: onSingleLongTapStart,
|
||
|
onSingleLongTapMoveUpdate: onSingleLongTapMoveUpdate,
|
||
|
onSingleLongTapEnd: onSingleLongTapEnd,
|
||
|
onDoubleTapDown: onDoubleTapDown,
|
||
|
onSecondarySingleTapUp: onSecondarySingleTapUp,
|
||
|
onDragSelectionStart: onDragSelectionStart,
|
||
|
onDragSelectionUpdate: onDragSelectionUpdate,
|
||
|
onDragSelectionEnd: onDragSelectionEnd,
|
||
|
behavior: behavior,
|
||
|
detectWordBoundary: detectWordBoundary,
|
||
|
child: child,
|
||
|
);
|
||
4 years ago
|
}
|
||
|
}
|