Feature: add selection completed callback (#576)

* Implement selection completion handlers

* Selection completion customizable via callback
pull/577/head
frmatthew 3 years ago committed by GitHub
parent 1ce8188c47
commit 6c39fcbc54
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 3
      lib/src/widgets/controller.dart
  2. 35
      lib/src/widgets/editor.dart
  3. 12
      lib/src/widgets/raw_editor.dart

@ -20,6 +20,7 @@ class QuillController extends ChangeNotifier {
bool keepStyleOnNewLine = false, bool keepStyleOnNewLine = false,
this.onReplaceText, this.onReplaceText,
this.onDelete, this.onDelete,
this.onSelectionCompleted,
}) : _selection = selection, }) : _selection = selection,
_keepStyleOnNewLine = keepStyleOnNewLine; _keepStyleOnNewLine = keepStyleOnNewLine;
@ -48,6 +49,8 @@ class QuillController extends ChangeNotifier {
/// Custom delete handler /// Custom delete handler
DeleteCallback? onDelete; DeleteCallback? onDelete;
void Function()? onSelectionCompleted;
/// Store any styles attribute that got toggled by the tap of a button /// Store any styles attribute that got toggled by the tap of a button
/// and that has not been applied yet. /// and that has not been applied yet.
/// It gets reset after each format action within the [document]. /// It gets reset after each format action within the [document].

@ -712,11 +712,14 @@ class _QuillEditorSelectionGestureDetectorBuilder
// If `Shift` key is pressed then // If `Shift` key is pressed then
// extend current selection instead. // extend current selection instead.
if (isShiftClick(details.kind)) { if (isShiftClick(details.kind)) {
getRenderEditor()!.extendSelection(details.globalPosition, getRenderEditor()!
cause: SelectionChangedCause.tap); ..extendSelection(details.globalPosition,
cause: SelectionChangedCause.tap)
..onSelectionCompleted();
} else { } else {
getRenderEditor()! getRenderEditor()!
.selectPosition(cause: SelectionChangedCause.tap); ..selectPosition(cause: SelectionChangedCause.tap)
..onSelectionCompleted();
} }
break; break;
@ -725,7 +728,9 @@ class _QuillEditorSelectionGestureDetectorBuilder
// On macOS/iOS/iPadOS a touch tap places the cursor at the edge // On macOS/iOS/iPadOS a touch tap places the cursor at the edge
// of the word. // of the word.
try { try {
getRenderEditor()!.selectWordEdge(SelectionChangedCause.tap); getRenderEditor()!
..selectWordEdge(SelectionChangedCause.tap)
..onSelectionCompleted();
} finally { } finally {
break; break;
} }
@ -736,7 +741,9 @@ class _QuillEditorSelectionGestureDetectorBuilder
case TargetPlatform.linux: case TargetPlatform.linux:
case TargetPlatform.windows: case TargetPlatform.windows:
try { try {
getRenderEditor()!.selectPosition(cause: SelectionChangedCause.tap); getRenderEditor()!
..selectPosition(cause: SelectionChangedCause.tap)
..onSelectionCompleted();
} finally { } finally {
break; break;
} }
@ -788,6 +795,10 @@ class _QuillEditorSelectionGestureDetectorBuilder
details, renderEditor.getPositionForOffset)) { details, renderEditor.getPositionForOffset)) {
return; return;
} }
if (delegate.getSelectionEnabled()) {
renderEditor.onSelectionCompleted();
}
} }
} }
super.onSingleLongTapEnd(details); super.onSingleLongTapEnd(details);
@ -801,6 +812,17 @@ class _QuillEditorSelectionGestureDetectorBuilder
typedef TextSelectionChangedHandler = void Function( typedef TextSelectionChangedHandler = void Function(
TextSelection selection, SelectionChangedCause cause); TextSelection selection, SelectionChangedCause cause);
/// Signature for the callback that reports when a selection action is actually
/// completed and ratified. Completion is defined as when the user input has
/// concluded for an entire selection action. For simple taps and keyboard input
/// events that change the selection, this callback is invoked immediately
/// following the TextSelectionChangedHandler. For long taps, the selection is
/// considered complete at the up event of a long tap. For drag selections, the
/// selection completes once the drag/pan event ends or is interrupted.
///
/// Used by [RenderEditor.onSelectionCompleted].
typedef TextSelectionCompletedHandler = void Function();
// The padding applied to text field. Used to determine the bounds when // The padding applied to text field. Used to determine the bounds when
// moving the floating cursor. // moving the floating cursor.
const EdgeInsets _kFloatingCursorAddedMargin = EdgeInsets.fromLTRB(4, 4, 4, 5); const EdgeInsets _kFloatingCursorAddedMargin = EdgeInsets.fromLTRB(4, 4, 4, 5);
@ -827,6 +849,7 @@ class RenderEditor extends RenderEditableContainerBox
required EdgeInsetsGeometry padding, required EdgeInsetsGeometry padding,
required CursorCont cursorController, required CursorCont cursorController,
required this.onSelectionChanged, required this.onSelectionChanged,
required this.onSelectionCompleted,
required double scrollBottomInset, required double scrollBottomInset,
required this.floatingCursorDisabled, required this.floatingCursorDisabled,
ViewportOffset? offset, ViewportOffset? offset,
@ -859,6 +882,7 @@ class RenderEditor extends RenderEditableContainerBox
/// Called when the selection changes. /// Called when the selection changes.
TextSelectionChangedHandler onSelectionChanged; TextSelectionChangedHandler onSelectionChanged;
TextSelectionCompletedHandler onSelectionCompleted;
final ValueNotifier<bool> _selectionStartInViewport = final ValueNotifier<bool> _selectionStartInViewport =
ValueNotifier<bool>(true); ValueNotifier<bool>(true);
@ -1067,6 +1091,7 @@ class RenderEditor extends RenderEditableContainerBox
void handleDragEnd(DragEndDetails details) { void handleDragEnd(DragEndDetails details) {
_isDragging = false; _isDragging = false;
onSelectionCompleted();
} }
@override @override

@ -288,6 +288,7 @@ class RawEditorState extends EditorState
startHandleLayerLink: _startHandleLayerLink, startHandleLayerLink: _startHandleLayerLink,
endHandleLayerLink: _endHandleLayerLink, endHandleLayerLink: _endHandleLayerLink,
onSelectionChanged: _handleSelectionChanged, onSelectionChanged: _handleSelectionChanged,
onSelectionCompleted: _handleSelectionCompleted,
scrollBottomInset: widget.scrollBottomInset, scrollBottomInset: widget.scrollBottomInset,
padding: widget.padding, padding: widget.padding,
maxContentWidth: widget.maxContentWidth, maxContentWidth: widget.maxContentWidth,
@ -318,6 +319,7 @@ class RawEditorState extends EditorState
startHandleLayerLink: _startHandleLayerLink, startHandleLayerLink: _startHandleLayerLink,
endHandleLayerLink: _endHandleLayerLink, endHandleLayerLink: _endHandleLayerLink,
onSelectionChanged: _handleSelectionChanged, onSelectionChanged: _handleSelectionChanged,
onSelectionCompleted: _handleSelectionCompleted,
scrollBottomInset: widget.scrollBottomInset, scrollBottomInset: widget.scrollBottomInset,
padding: widget.padding, padding: widget.padding,
maxContentWidth: widget.maxContentWidth, maxContentWidth: widget.maxContentWidth,
@ -374,6 +376,10 @@ class RawEditorState extends EditorState
} }
} }
void _handleSelectionCompleted() {
widget.controller.onSelectionCompleted?.call();
}
/// Updates the checkbox positioned at [offset] in document /// Updates the checkbox positioned at [offset] in document
/// by changing its attribute according to [value]. /// by changing its attribute according to [value].
void _handleCheckboxTap(int offset, bool value) { void _handleCheckboxTap(int offset, bool value) {
@ -793,6 +799,9 @@ class RawEditorState extends EditorState
} }
textEditingValue = value; textEditingValue = value;
userUpdateTextEditingValue(value, cause); userUpdateTextEditingValue(value, cause);
// keyboard and text input force a selection completion
_handleSelectionCompleted();
} }
@override @override
@ -914,6 +923,7 @@ class _Editor extends MultiChildRenderObjectWidget {
required this.startHandleLayerLink, required this.startHandleLayerLink,
required this.endHandleLayerLink, required this.endHandleLayerLink,
required this.onSelectionChanged, required this.onSelectionChanged,
required this.onSelectionCompleted,
required this.scrollBottomInset, required this.scrollBottomInset,
required this.cursorController, required this.cursorController,
required this.floatingCursorDisabled, required this.floatingCursorDisabled,
@ -930,6 +940,7 @@ class _Editor extends MultiChildRenderObjectWidget {
final LayerLink startHandleLayerLink; final LayerLink startHandleLayerLink;
final LayerLink endHandleLayerLink; final LayerLink endHandleLayerLink;
final TextSelectionChangedHandler onSelectionChanged; final TextSelectionChangedHandler onSelectionChanged;
final TextSelectionCompletedHandler onSelectionCompleted;
final double scrollBottomInset; final double scrollBottomInset;
final EdgeInsetsGeometry padding; final EdgeInsetsGeometry padding;
final double? maxContentWidth; final double? maxContentWidth;
@ -947,6 +958,7 @@ class _Editor extends MultiChildRenderObjectWidget {
startHandleLayerLink: startHandleLayerLink, startHandleLayerLink: startHandleLayerLink,
endHandleLayerLink: endHandleLayerLink, endHandleLayerLink: endHandleLayerLink,
onSelectionChanged: onSelectionChanged, onSelectionChanged: onSelectionChanged,
onSelectionCompleted: onSelectionCompleted,
cursorController: cursorController, cursorController: cursorController,
padding: padding, padding: padding,
maxContentWidth: maxContentWidth, maxContentWidth: maxContentWidth,

Loading…
Cancel
Save