Implement RawEditorState

pull/13/head
singerdmx 4 years ago
parent 4b9975fb95
commit db2a280d47
  1. 2
      lib/models/documents/document.dart
  2. 6
      lib/widgets/controller.dart
  3. 347
      lib/widgets/editor.dart
  4. 13
      lib/widgets/keyborad_listener.dart
  5. 17
      lib/widgets/text_selection.dart

@ -156,6 +156,8 @@ class Document {
close() {
_observer.close();
}
String toPlainText() => _root.children.map((e) => e.toPlainText()).join('');
}
enum ChangeSource {

@ -17,6 +17,12 @@ class QuillController extends ChangeNotifier {
: assert(document != null),
assert(selection != null);
TextEditingValue get plainTextEditingValue => TextEditingValue(
text: document.toPlainText(),
selection: selection,
composing: TextRange.empty,
);
Style getSelectionStyle() {
return document
.collectStyle(selection.start, selection.end - selection.start)

@ -1,13 +1,44 @@
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter/services.dart';
import 'package:flutter_quill/models/documents/document.dart';
import 'package:flutter_quill/utils/diff_delta.dart';
import 'package:flutter_quill/widgets/text_selection.dart';
import 'controller.dart';
import 'cursor.dart';
import 'delegate.dart';
const Set<int> WHITE_SPACE = {
0x9,
0xA,
0xB,
0xC,
0xD,
0x1C,
0x1D,
0x1E,
0x1F,
0x20,
0xA0,
0x1680,
0x2000,
0x2001,
0x2002,
0x2003,
0x2004,
0x2005,
0x2006,
0x2007,
0x2008,
0x2009,
0x200A,
0x202F,
0x205F,
0x3000
};
abstract class RenderAbstractEditor {
TextSelection selectWordAtPosition(TextPosition position);
@ -282,30 +313,302 @@ class RawEditor extends StatefulWidget {
}
}
class RawEditorState extends EditorState implements TextSelectionDelegate {
class RawEditorState extends EditorState
with
AutomaticKeepAliveClientMixin<RawEditor>,
WidgetsBindingObserver,
TickerProviderStateMixin<RawEditor>
implements TextSelectionDelegate, TextInputClient {
final GlobalKey _editorKey = GlobalKey();
final List<TextEditingValue> _sentRemoteValues = [];
TextInputConnection _textInputConnection;
TextEditingValue _lastKnownRemoteTextEditingValue;
int _cursorResetLocation = -1;
bool _wasSelectingVerticallyWithKeyboard = false;
handleCursorMovement(
LogicalKeyboardKey key,
bool wordModifier,
bool lineModifier,
bool shift,
) {
if (wordModifier && lineModifier) {
return;
}
TextSelection selection = widget.controller.selection;
assert(selection != null);
TextSelection newSelection = widget.controller.selection;
String plainText = textEditingValue.text;
bool rightKey = key == LogicalKeyboardKey.arrowRight,
leftKey = key == LogicalKeyboardKey.arrowLeft,
upKey = key == LogicalKeyboardKey.arrowUp,
downKey = key == LogicalKeyboardKey.arrowDown;
if ((rightKey || leftKey) && !(rightKey && leftKey)) {
newSelection = _jumpToBeginOrEndOfWord(newSelection, wordModifier,
leftKey, rightKey, plainText, lineModifier, shift);
}
if (downKey || upKey) {}
if (!shift) {}
widget.controller.updateSelection(newSelection, ChangeSource.LOCAL);
}
TextSelection _jumpToBeginOrEndOfWord(
TextSelection newSelection,
bool wordModifier,
bool leftKey,
bool rightKey,
String plainText,
bool lineModifier,
bool shift) {
if (wordModifier) {
if (leftKey) {
TextSelection textSelection = getRenderEditor().selectWordAtPosition(
TextPosition(
offset: _previousCharacter(
newSelection.extentOffset, plainText, false)));
return newSelection.copyWith(extentOffset: textSelection.baseOffset);
}
TextSelection textSelection = getRenderEditor().selectWordAtPosition(
TextPosition(
offset:
_nextCharacter(newSelection.extentOffset, plainText, false)));
return newSelection.copyWith(extentOffset: textSelection.extentOffset);
} else if (lineModifier) {
if (leftKey) {
TextSelection textSelection = getRenderEditor().selectLineAtPosition(
TextPosition(
offset: _previousCharacter(
newSelection.extentOffset, plainText, false)));
return newSelection.copyWith(extentOffset: textSelection.baseOffset);
}
int startPoint = newSelection.extentOffset;
if (startPoint < plainText.length) {
TextSelection textSelection = getRenderEditor()
.selectLineAtPosition(TextPosition(offset: startPoint));
return newSelection.copyWith(extentOffset: textSelection.extentOffset);
}
return newSelection;
}
if (rightKey && newSelection.extentOffset < plainText.length) {
int nextExtent =
_nextCharacter(newSelection.extentOffset, plainText, true);
int distance = nextExtent - newSelection.extentOffset;
newSelection = newSelection.copyWith(extentOffset: nextExtent);
if (shift) {
_cursorResetLocation += distance;
}
return newSelection;
}
if (leftKey && newSelection.extentOffset > 0) {
int previousExtent =
_previousCharacter(newSelection.extentOffset, plainText, true);
int distance = newSelection.extentOffset - previousExtent;
newSelection = newSelection.copyWith(extentOffset: previousExtent);
if (shift) {
_cursorResetLocation -= distance;
}
return newSelection;
}
return newSelection;
}
int _nextCharacter(int index, String string, bool includeWhitespace) {
assert(index >= 0 && index <= string.length);
if (index == string.length) {
return string.length;
}
int count = 0;
Characters remain = string.characters.skipWhile((String currentString) {
if (count <= index) {
count += currentString.length;
return true;
}
if (includeWhitespace) {
return false;
}
return WHITE_SPACE.contains(currentString.codeUnitAt(0));
});
return string.length - remain.toString().length;
}
int _previousCharacter(int index, String string, includeWhitespace) {
assert(index >= 0 && index <= string.length);
if (index == 0) {
return 0;
}
int count = 0;
int lastNonWhitespace;
for (String currentString in string.characters) {
if (!includeWhitespace &&
!WHITE_SPACE.contains(
currentString.characters.first.toString().codeUnitAt(0))) {
lastNonWhitespace = count;
}
if (count + currentString.length >= index) {
return includeWhitespace ? count : lastNonWhitespace ?? 0;
}
count += currentString.length;
}
return 0;
}
bool get hasConnection =>
_textInputConnection != null && _textInputConnection.attached;
openConnectionIfNeeded() {
if (widget.readOnly) {
return;
}
if (!hasConnection) {
_lastKnownRemoteTextEditingValue = textEditingValue;
_textInputConnection = TextInput.attach(
this,
TextInputConfiguration(
inputType: TextInputType.multiline,
readOnly: widget.readOnly,
obscureText: false,
autocorrect: false,
inputAction: TextInputAction.newline,
keyboardAppearance: widget.keyboardAppearance,
textCapitalization: widget.textCapitalization,
),
);
_textInputConnection.setEditingState(_lastKnownRemoteTextEditingValue);
_sentRemoteValues.add(_lastKnownRemoteTextEditingValue);
}
_textInputConnection.show();
}
closeConnectionIfNeeded() {
if (!hasConnection) {
return;
}
_textInputConnection.close();
_textInputConnection = null;
_lastKnownRemoteTextEditingValue = null;
_sentRemoteValues.clear();
}
updateRemoteValueIfNeeded() {
if (!hasConnection) {
return;
}
TextEditingValue actualValue = textEditingValue.copyWith(
composing: _lastKnownRemoteTextEditingValue.composing,
);
if (actualValue == _lastKnownRemoteTextEditingValue) {
return;
}
bool shouldRemember =
textEditingValue.text != _lastKnownRemoteTextEditingValue.text;
_lastKnownRemoteTextEditingValue = actualValue;
_textInputConnection.setEditingState(actualValue);
if (shouldRemember) {
_sentRemoteValues.add(actualValue);
}
}
@override
TextEditingValue textEditingValue;
TextEditingValue get currentTextEditingValue =>
_lastKnownRemoteTextEditingValue;
@override
void bringIntoView(TextPosition position) {
// TODO: implement bringIntoView
AutofillScope get currentAutofillScope => null;
@override
void updateEditingValue(TextEditingValue value) {
if (widget.readOnly) {
return;
}
if (_sentRemoteValues.contains(value)) {
_sentRemoteValues.remove(value);
return;
}
if (_lastKnownRemoteTextEditingValue == value) {
return;
}
if (_lastKnownRemoteTextEditingValue.text == value.text &&
_lastKnownRemoteTextEditingValue.selection == value.selection) {
_lastKnownRemoteTextEditingValue = value;
return;
}
TextEditingValue effectiveLastKnownValue = _lastKnownRemoteTextEditingValue;
_lastKnownRemoteTextEditingValue = value;
String oldText = effectiveLastKnownValue.text;
String text = value.text;
int cursorPosition = value.selection.extentOffset;
Diff diff = getDiff(oldText, text, cursorPosition);
widget.controller.replaceText(
diff.start, diff.deleted.length, diff.inserted, value.selection);
}
@override
Widget build(BuildContext context) {
// TODO: implement build
TextEditingValue get textEditingValue {
return widget.controller.plainTextEditingValue;
}
@override
set textEditingValue(TextEditingValue value) {
widget.controller.updateSelection(value.selection, ChangeSource.LOCAL);
}
@override
void performAction(TextInputAction action) {}
@override
void performPrivateCommand(String action, Map<String, dynamic> data) {}
@override
void updateFloatingCursor(RawFloatingCursorPoint point) {
throw UnimplementedError();
}
@override
// TODO: implement copyEnabled
bool get copyEnabled => throw UnimplementedError();
void showAutocorrectionPromptRect(int start, int end) {
throw UnimplementedError();
}
@override
// TODO: implement cutEnabled
bool get cutEnabled => throw UnimplementedError();
void bringIntoView(TextPosition position) {
// TODO: implement bringIntoView
}
@override
void connectionClosed() {
if (!hasConnection) {
return;
}
_textInputConnection.connectionClosedReceived();
_textInputConnection = null;
_lastKnownRemoteTextEditingValue = null;
_sentRemoteValues.clear();
}
@override
Widget build(BuildContext context) {
// TODO: implement build
throw UnimplementedError();
}
@override
RenderEditor getRenderEditor() {
@ -327,22 +630,28 @@ class RawEditorState extends EditorState implements TextSelectionDelegate {
@override
void hideToolbar() {
// TODO: implement hideToolbar
if (getSelectionOverlay()?.toolbar != null) {
getSelectionOverlay()?.hideToolbar();
}
}
@override
// TODO: implement pasteEnabled
bool get pasteEnabled => throw UnimplementedError();
bool get cutEnabled => widget.toolbarOptions.cut && !widget.readOnly;
@override
bool get copyEnabled => widget.toolbarOptions.copy;
@override
bool get pasteEnabled => widget.toolbarOptions.paste && !widget.readOnly;
@override
bool get selectAllEnabled => widget.toolbarOptions.selectAll;
@override
void requestKeyboard() {
// TODO: implement requestKeyboard
}
@override
// TODO: implement selectAllEnabled
bool get selectAllEnabled => throw UnimplementedError();
@override
void setTextEditingValue(TextEditingValue value) {
// TODO: implement setTextEditingValue
@ -353,6 +662,10 @@ class RawEditorState extends EditorState implements TextSelectionDelegate {
// TODO: implement showToolbar
throw UnimplementedError();
}
@override
// TODO: implement wantKeepAlive
bool get wantKeepAlive => throw UnimplementedError();
}
class RenderEditor extends RenderEditableContainerBox

@ -2,8 +2,8 @@ import 'package:flutter/services.dart';
enum InputShortcut { CUT, COPY, PAST, SELECT_ALL }
typedef CursorMoveCallback = void Function(LogicalKeyboardKey key,
{bool wordModifier, bool lineModifier, bool shift});
typedef CursorMoveCallback = void Function(
LogicalKeyboardKey key, bool wordModifier, bool lineModifier, bool shift);
typedef InputShortcutCallback = void Function(InputShortcut shortcut);
typedef OnDeleteCallback = void Function(bool forward);
@ -82,10 +82,11 @@ class KeyboardListener {
}
if (_moveKeys.contains(key)) {
onCursorMove(key,
wordModifier: isMacOS ? event.isAltPressed : event.isControlPressed,
lineModifier: isMacOS ? event.isMetaPressed : event.isAltPressed,
shift: event.isShiftPressed);
onCursorMove(
key,
isMacOS ? event.isAltPressed : event.isControlPressed,
isMacOS ? event.isMetaPressed : event.isAltPressed,
event.isShiftPressed);
} else if (isMacOS
? event.isMetaPressed
: event.isControlPressed && _shortcutKeys.contains(key)) {

@ -20,7 +20,7 @@ class EditorTextSelectionOverlay {
final ClipboardStatusNotifier clipboardStatus;
AnimationController _toolbarController;
List<OverlayEntry> _handles;
OverlayEntry _toolbar;
OverlayEntry toolbar;
EditorTextSelectionOverlay(
this.value,
@ -58,12 +58,19 @@ class EditorTextSelectionOverlay {
_handles = null;
}
hideToolbar() {
assert(toolbar != null);
_toolbarController.stop();
toolbar.remove();
toolbar = null;
}
/// Shows the toolbar by inserting it into the [context]'s overlay.
showToolbar() {
assert(_toolbar == null);
_toolbar = OverlayEntry(builder: _buildToolbar);
assert(toolbar == null);
toolbar = OverlayEntry(builder: _buildToolbar);
Overlay.of(context, rootOverlay: true, debugRequiredFor: debugRequiredFor)
.insert(_toolbar);
.insert(toolbar);
_toolbarController.forward(from: 0.0);
}
@ -78,6 +85,6 @@ class EditorTextSelectionOverlay {
_handles[0].markNeedsBuild();
_handles[1].markNeedsBuild();
}
_toolbar?.markNeedsBuild();
toolbar?.markNeedsBuild();
}
}

Loading…
Cancel
Save