feat: insert text at cursor

This commit is contained in:
Vincent Chan 2022-07-19 15:24:51 +08:00
parent ee5c9d410b
commit abe0658cd3
2 changed files with 77 additions and 17 deletions

View File

@ -95,11 +95,20 @@ class _TextNodeWidget extends StatefulWidget {
State<_TextNodeWidget> createState() => __TextNodeWidgetState(); State<_TextNodeWidget> createState() => __TextNodeWidgetState();
} }
String _textContentOfDelta(Delta delta) {
return delta.operations.fold("", (previousValue, element) {
if (element is TextInsert) {
return previousValue + element.content;
}
return previousValue;
});
}
class __TextNodeWidgetState extends State<_TextNodeWidget> class __TextNodeWidgetState extends State<_TextNodeWidget>
implements TextInputClient { implements DeltaTextInputClient {
TextNode get node => widget.node as TextNode; TextNode get node => widget.node as TextNode;
EditorState get editorState => widget.editorState; EditorState get editorState => widget.editorState;
TextEditingValue get textEditingValue => const TextEditingValue(); TextSelection? _localSelection;
TextInputConnection? _textInputConnection; TextInputConnection? _textInputConnection;
@ -112,20 +121,22 @@ class __TextNodeWidgetState extends State<_TextNodeWidget>
TextSpan( TextSpan(
children: node.toTextSpans(), children: node.toTextSpans(),
), ),
onTap: () { onSelectionChanged: ((selection, cause) {
_textInputConnection?.close(); _textInputConnection?.close();
_textInputConnection = TextInput.attach( _textInputConnection = TextInput.attach(
this, this,
const TextInputConfiguration( const TextInputConfiguration(
enableDeltaModel: false, enableDeltaModel: true,
inputType: TextInputType.multiline, inputType: TextInputType.multiline,
textCapitalization: TextCapitalization.sentences, textCapitalization: TextCapitalization.sentences,
), ),
); );
debugPrint('selection: $selection');
_textInputConnection _textInputConnection
?..show() ?..show()
..setEditingState(textEditingValue); ..setEditingState(TextEditingValue(
}, text: _textContentOfDelta(node.delta), selection: selection));
}),
), ),
if (node.children.isNotEmpty) if (node.children.isNotEmpty)
...node.children.map( ...node.children.map(
@ -152,7 +163,9 @@ class __TextNodeWidgetState extends State<_TextNodeWidget>
@override @override
// TODO: implement currentTextEditingValue // TODO: implement currentTextEditingValue
TextEditingValue? get currentTextEditingValue => textEditingValue; TextEditingValue? get currentTextEditingValue => TextEditingValue(
text: _textContentOfDelta(node.delta),
selection: _localSelection ?? const TextSelection.collapsed(offset: -1));
@override @override
void insertTextPlaceholder(Size size) { void insertTextPlaceholder(Size size) {
@ -186,7 +199,23 @@ class __TextNodeWidgetState extends State<_TextNodeWidget>
@override @override
void updateEditingValue(TextEditingValue value) { void updateEditingValue(TextEditingValue value) {
debugPrint(value.text); debugPrint('offset: ${value.selection}');
}
@override
void updateEditingValueWithDeltas(List<TextEditingDelta> textEditingDeltas) {
for (final textDelta in textEditingDeltas) {
if (textDelta is TextEditingDeltaInsertion) {
TransactionBuilder(editorState)
..insertText(node, textDelta.insertionOffset, textDelta.textInserted)
..commit();
} else if (textDelta is TextEditingDeltaDeletion) {
TransactionBuilder(editorState)
..deleteText(node, textDelta.deletedRange.start,
textDelta.deletedRange.end - textDelta.deletedRange.start)
..commit();
}
}
} }
@override @override

View File

@ -26,39 +26,70 @@ class TransactionBuilder {
TransactionBuilder(this.state); TransactionBuilder(this.state);
/// Commit the operations to the state
commit() { commit() {
final transaction = _finish(); final transaction = _finish();
state.apply(transaction); state.apply(transaction);
} }
void insertNode(Path path, Node node) { insertNode(Path path, Node node) {
cursorSelection = state.cursorSelection; cursorSelection = state.cursorSelection;
operations.add(InsertOperation(path: path, value: node)); add(InsertOperation(path: path, value: node));
} }
void updateNode(Node node, Attributes attributes) { updateNode(Node node, Attributes attributes) {
cursorSelection = state.cursorSelection; cursorSelection = state.cursorSelection;
operations.add(UpdateOperation( add(UpdateOperation(
path: node.path, path: node.path,
attributes: Attributes.from(node.attributes)..addAll(attributes), attributes: Attributes.from(node.attributes)..addAll(attributes),
oldAttributes: node.attributes, oldAttributes: node.attributes,
)); ));
} }
void deleteNode(Node node) { deleteNode(Node node) {
cursorSelection = state.cursorSelection; cursorSelection = state.cursorSelection;
operations.add(DeleteOperation(path: node.path, removedValue: node)); add(DeleteOperation(path: node.path, removedValue: node));
} }
void textEdit(TextNode node, Delta Function() f) { textEdit(TextNode node, Delta Function() f) {
cursorSelection = state.cursorSelection; cursorSelection = state.cursorSelection;
final path = node.path; final path = node.path;
final delta = f(); final delta = f();
final inverted = delta.invert(node.delta); final inverted = delta.invert(node.delta);
operations
.add(TextEditOperation(path: path, delta: delta, inverted: inverted)); add(TextEditOperation(path: path, delta: delta, inverted: inverted));
}
insertText(TextNode node, int index, String content) {
textEdit(node, () => Delta().retain(index).insert(content));
}
formatText(TextNode node, int index, int length, Attributes attributes) {
textEdit(node, () => Delta().retain(index).retain(length, attributes));
}
deleteText(TextNode node, int index, int length) {
textEdit(node, () => Delta().retain(index).delete(length));
}
add(Operation op) {
final Operation? last = operations.isEmpty ? null : operations.last;
if (last != null) {
if (op is TextEditOperation &&
last is TextEditOperation &&
pathEquals(op.path, last.path)) {
final newOp = TextEditOperation(
path: op.path,
delta: last.delta.compose(op.delta),
inverted: op.inverted.compose(last.inverted),
);
operations[operations.length - 1] = newOp;
return;
}
}
operations.add(op);
} }
Transaction _finish() { Transaction _finish() {