From ec47aa51a16084d5a2141b88eb4845a162ac83af Mon Sep 17 00:00:00 2001 From: "Lucas.Xu" Date: Fri, 5 Aug 2022 20:05:36 +0800 Subject: [PATCH] feat: support IME in macOS --- .../flowy_editor/lib/document/selection.dart | 14 ++++ .../lib/service/editor_service.dart | 9 --- .../lib/service/input_service.dart | 72 ++++++++++++------- 3 files changed, 60 insertions(+), 35 deletions(-) diff --git a/frontend/app_flowy/packages/flowy_editor/lib/document/selection.dart b/frontend/app_flowy/packages/flowy_editor/lib/document/selection.dart index f1fa0682f6..16341cee1a 100644 --- a/frontend/app_flowy/packages/flowy_editor/lib/document/selection.dart +++ b/frontend/app_flowy/packages/flowy_editor/lib/document/selection.dart @@ -55,4 +55,18 @@ class Selection { "end": end.toJson(), }; } + + @override + bool operator ==(Object other) { + if (other is! Selection) { + return false; + } + if (identical(this, other)) { + return true; + } + return start == other.start && end == other.end; + } + + @override + int get hashCode => Object.hash(start, end); } diff --git a/frontend/app_flowy/packages/flowy_editor/lib/service/editor_service.dart b/frontend/app_flowy/packages/flowy_editor/lib/service/editor_service.dart index d1fb4aac9c..5c088b46d6 100644 --- a/frontend/app_flowy/packages/flowy_editor/lib/service/editor_service.dart +++ b/frontend/app_flowy/packages/flowy_editor/lib/service/editor_service.dart @@ -63,8 +63,6 @@ class FlowyEditor extends StatefulWidget { } class _FlowyEditorState extends State { - late ScrollController _scrollController; - EditorState get editorState => widget.editorState; @override @@ -74,13 +72,6 @@ class _FlowyEditorState extends State { editorState.service.renderPluginService = _createRenderPlugin(); } - @override - void dispose() { - _scrollController.dispose(); - - super.dispose(); - } - @override void didUpdateWidget(covariant FlowyEditor oldWidget) { super.didUpdateWidget(oldWidget); diff --git a/frontend/app_flowy/packages/flowy_editor/lib/service/input_service.dart b/frontend/app_flowy/packages/flowy_editor/lib/service/input_service.dart index ee570d902a..0d6f9aabd8 100644 --- a/frontend/app_flowy/packages/flowy_editor/lib/service/input_service.dart +++ b/frontend/app_flowy/packages/flowy_editor/lib/service/input_service.dart @@ -2,14 +2,15 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flowy_editor/document/node.dart'; +import 'package:flowy_editor/document/path.dart'; import 'package:flowy_editor/document/position.dart'; import 'package:flowy_editor/document/selection.dart'; import 'package:flowy_editor/editor_state.dart'; +import 'package:flowy_editor/extensions/node_extensions.dart'; import 'package:flowy_editor/operation/transaction_builder.dart'; mixin FlowyInputService { void attach(TextEditingValue textEditingValue); - void setTextEditingValue(TextEditingValue textEditingValue); void apply(List deltas); void close(); } @@ -33,6 +34,7 @@ class _FlowyInputState extends State with FlowyInputService implements DeltaTextInputClient { TextInputConnection? _textInputConnection; + TextRange? _composingTextRange; EditorState get _editorState => widget.editorState; @@ -46,6 +48,7 @@ class _FlowyInputState extends State @override void dispose() { + close(); _editorState.service.selectionService.currentSelectedNodes .removeListener(_onSelectedNodesChange); @@ -61,11 +64,7 @@ class _FlowyInputState extends State @override void attach(TextEditingValue textEditingValue) { - if (_textInputConnection != null) { - return; - } - - _textInputConnection = TextInput.attach( + _textInputConnection ??= TextInput.attach( this, const TextInputConfiguration( // TODO: customize @@ -75,18 +74,9 @@ class _FlowyInputState extends State ), ); - _textInputConnection - ?..show() - ..setEditingState(textEditingValue); - } - - @override - void setTextEditingValue(TextEditingValue textEditingValue) { - assert(_textInputConnection != null, - 'Must call `attach` before set textEditingValue'); - if (_textInputConnection != null) { - _textInputConnection?.setEditingState(textEditingValue); - } + _textInputConnection! + ..setEditingState(textEditingValue) + ..show(); } @override @@ -94,13 +84,21 @@ class _FlowyInputState extends State // TODO: implement the detail for (final delta in deltas) { if (delta is TextEditingDeltaInsertion) { + if (_composingTextRange != null) { + _composingTextRange = TextRange( + start: _composingTextRange!.start, + end: delta.composing.end, + ); + } else { + _composingTextRange = delta.composing; + } + _applyInsert(delta); } else if (delta is TextEditingDeltaDeletion) { } else if (delta is TextEditingDeltaReplacement) { _applyReplacement(delta); } else if (delta is TextEditingDeltaNonTextUpdate) { - // We don't need to care the [TextEditingDeltaNonTextUpdate]. - // Do nothing. + _composingTextRange = null; } } } @@ -212,12 +210,12 @@ class _FlowyInputState extends State } void _onSelectedNodesChange() { - final nodes = - _editorState.service.selectionService.currentSelectedNodes.value; + final textNodes = _editorState + .service.selectionService.currentSelectedNodes.value + .whereType(); final selection = _editorState.service.selectionService.currentSelection; - // FIXME: upward. - if (nodes.isNotEmpty && selection != null) { - final textNodes = nodes.whereType(); + // FIXME: upward and selection update. + if (textNodes.isNotEmpty && selection != null) { final text = textNodes.fold( '', (sum, textNode) => '$sum${textNode.toRawString()}\n'); attach( @@ -227,10 +225,32 @@ class _FlowyInputState extends State baseOffset: selection.start.offset, extentOffset: selection.end.offset, ), + composing: _composingTextRange ?? const TextRange.collapsed(-1), ), ); + if (textNodes.length == 1) { + _updateCaretPosition(textNodes.first, selection); + } } else { - close(); + // close(); + } + } + + // TODO: support IME in linux / windows / ios / android + // Only support macOS now. + void _updateCaretPosition(TextNode textNode, Selection selection) { + if (!selection.isCollapsed) { + return; + } + final renderBox = textNode.renderBox; + final selectable = textNode.selectable; + if (renderBox != null && selectable != null) { + final size = renderBox.size; + final transform = renderBox.getTransformTo(null); + final rect = selectable.getCursorRectInPosition(selection.end); + _textInputConnection + ?..setEditableSizeAndTransform(size, transform) + ..setCaretRect(rect); } } }