|
|
|
@ -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, |
|
|
|
|
); |
|
|
|
|