Add `contextMenuBuilder` to `RawEditor` (#1105)

pull/1106/head
Adil Hanney 2 years ago committed by GitHub
parent 83ad28bf7b
commit efd09b535a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 9
      lib/src/widgets/editor.dart
  2. 110
      lib/src/widgets/raw_editor.dart
  3. 9
      lib/src/widgets/raw_editor/raw_editor_state_selection_delegate_mixin.dart

@ -467,12 +467,9 @@ class QuillEditorState extends State<QuillEditor>
readOnly: widget.readOnly,
placeholder: widget.placeholder,
onLaunchUrl: widget.onLaunchUrl,
toolbarOptions: ToolbarOptions(
copy: showSelectionToolbar,
cut: showSelectionToolbar,
paste: showSelectionToolbar,
selectAll: showSelectionToolbar,
),
contextMenuBuilder: showSelectionToolbar
? RawEditor.defaultContextMenuBuilder
: null,
showSelectionHandles: isMobile(theme.platform),
showCursor: widget.showCursor,
cursorStyle: CursorStyle(

@ -57,12 +57,7 @@ class RawEditor extends StatefulWidget {
this.readOnly = false,
this.placeholder,
this.onLaunchUrl,
this.toolbarOptions = const ToolbarOptions(
copy: true,
cut: true,
paste: true,
selectAll: true,
),
this.contextMenuBuilder = defaultContextMenuBuilder,
this.showSelectionHandles = false,
bool? showCursor,
this.textCapitalization = TextCapitalization.none,
@ -114,11 +109,24 @@ class RawEditor extends StatefulWidget {
/// a link in the document.
final ValueChanged<String>? onLaunchUrl;
/// Configuration of toolbar options.
/// Builds the text selection toolbar when requested by the user.
///
/// See also:
/// * [EditableText.contextMenuBuilder], which builds the default
/// text selection toolbar for [EditableText].
///
/// By default, all options are enabled. If [readOnly] is true,
/// paste and cut will be disabled regardless.
final ToolbarOptions toolbarOptions;
/// If not provided, no context menu will be shown.
final QuillEditorContextMenuBuilder? contextMenuBuilder;
static Widget defaultContextMenuBuilder(
BuildContext context,
RawEditorState state,
) {
return AdaptiveTextSelectionToolbar.buttonItems(
buttonItems: state.contextMenuButtonItems,
anchors: state.contextMenuAnchors,
);
}
/// Whether to show selection handles.
///
@ -293,6 +301,76 @@ class RawEditorState extends EditorState
TextDirection get _textDirection => Directionality.of(context);
/// Returns the [ContextMenuButtonItem]s representing the buttons in this
/// platform's default selection menu for [RawEditor].
///
/// Copied from [EditableTextState].
List<ContextMenuButtonItem> get contextMenuButtonItems {
return EditableText.getEditableButtonItems(
clipboardStatus: _clipboardStatus.value,
onCopy: copyEnabled
? () => copySelection(SelectionChangedCause.toolbar)
: null,
onCut: cutEnabled
? () => cutSelection(SelectionChangedCause.toolbar)
: null,
onPaste: pasteEnabled
? () => pasteText(SelectionChangedCause.toolbar)
: null,
onSelectAll: selectAllEnabled
? () => selectAll(SelectionChangedCause.toolbar)
: null,
);
}
/// Returns the anchor points for the default context menu.
///
/// Copied from [EditableTextState].
TextSelectionToolbarAnchors get contextMenuAnchors {
final glyphHeights = _getGlyphHeights();
final selection = textEditingValue.selection;
final points = renderEditor.getEndpointsForSelection(selection);
return TextSelectionToolbarAnchors.fromSelection(
renderBox: renderEditor,
startGlyphHeight: glyphHeights.item1,
endGlyphHeight: glyphHeights.item2,
selectionEndpoints: points,
);
}
/// Gets the line heights at the start and end of the selection for the given
/// [RawEditorState].
///
/// Copied from [EditableTextState].
Tuple2<double, double> _getGlyphHeights() {
final selection = textEditingValue.selection;
// Only calculate handle rects if the text in the previous frame
// is the same as the text in the current frame. This is done because
// widget.renderObject contains the renderEditable from the previous frame.
// If the text changed between the current and previous frames then
// widget.renderObject.getRectForComposingRange might fail. In cases where
// the current frame is different from the previous we fall back to
// renderObject.preferredLineHeight.
final prevText = renderEditor.document.toPlainText();
final currText = textEditingValue.text;
if (prevText != currText || !selection.isValid || selection.isCollapsed) {
return Tuple2(
renderEditor.preferredLineHeight(selection.base),
renderEditor.preferredLineHeight(selection.base),
);
}
final startCharacterRect =
renderEditor.getLocalRectForCaret(selection.base);
final endCharacterRect =
renderEditor.getLocalRectForCaret(selection.extent);
return Tuple2(
startCharacterRect.height,
endCharacterRect.height,
);
}
@override
Widget build(BuildContext context) {
assert(debugCheckHasMediaQuery(context));
@ -2333,3 +2411,15 @@ class _ApplyCheckListAction extends Action<ApplyCheckListIntent> {
@override
bool get isActionEnabled => true;
}
/// Signature for a widget builder that builds a context menu for the given
/// [RawEditorState].
///
/// See also:
///
/// * [EditableTextContextMenuBuilder], which performs the same role for
/// [EditableText]
typedef QuillEditorContextMenuBuilder = Widget Function(
BuildContext context,
RawEditorState rawEditorState,
);

@ -150,14 +150,15 @@ mixin RawEditorStateSelectionDelegateMixin on EditorState
}
@override
bool get cutEnabled => widget.toolbarOptions.cut && !widget.readOnly;
bool get cutEnabled => widget.contextMenuBuilder != null && !widget.readOnly;
@override
bool get copyEnabled => widget.toolbarOptions.copy;
bool get copyEnabled => widget.contextMenuBuilder != null;
@override
bool get pasteEnabled => widget.toolbarOptions.paste && !widget.readOnly;
bool get pasteEnabled => widget.contextMenuBuilder != null
&& !widget.readOnly;
@override
bool get selectAllEnabled => widget.toolbarOptions.selectAll;
bool get selectAllEnabled => widget.contextMenuBuilder != null;
}

Loading…
Cancel
Save