From 2f86cac8af9343b3812bfca42f6edfbe003c20a7 Mon Sep 17 00:00:00 2001 From: "Lucas.Xu" Date: Mon, 25 Jul 2022 15:58:57 +0800 Subject: [PATCH] feat: Implement arrow up/down/left/right event handler. #708 --- .../example/lib/plugin/image_node_widget.dart | 10 +++++ .../lib/plugin/selected_text_node_widget.dart | 30 +++++++++++++++ .../flowy_editor/lib/editor_state.dart | 8 ++-- .../lib/render/selection/selectable.dart | 6 +++ .../lib/service/editor_service.dart | 5 ++- .../arrow_keys_handler.dart | 37 +++++++++++++++++++ .../delete_single_text_node_handler.dart | 6 +-- .../lib/service/keyboard_service.dart | 2 +- .../flowy_editor/lib/service/service.dart | 15 ++++++++ 9 files changed, 110 insertions(+), 9 deletions(-) create mode 100644 frontend/app_flowy/packages/flowy_editor/lib/service/flowy_key_event_handlers/arrow_keys_handler.dart create mode 100644 frontend/app_flowy/packages/flowy_editor/lib/service/service.dart diff --git a/frontend/app_flowy/packages/flowy_editor/example/lib/plugin/image_node_widget.dart b/frontend/app_flowy/packages/flowy_editor/example/lib/plugin/image_node_widget.dart index 389bfed320..193cc879c9 100644 --- a/frontend/app_flowy/packages/flowy_editor/example/lib/plugin/image_node_widget.dart +++ b/frontend/app_flowy/packages/flowy_editor/example/lib/plugin/image_node_widget.dart @@ -61,6 +61,16 @@ class __ImageNodeWidgetState extends State<_ImageNodeWidget> with Selectable { return Offset.zero; } + @override + Offset getLeftOfOffset() { + return Offset.zero; + } + + @override + Offset getRightOfOffset() { + return Offset.zero; + } + @override Widget build(BuildContext context) { return _build(context); diff --git a/frontend/app_flowy/packages/flowy_editor/example/lib/plugin/selected_text_node_widget.dart b/frontend/app_flowy/packages/flowy_editor/example/lib/plugin/selected_text_node_widget.dart index 1124ec3cbb..d827fdfde8 100644 --- a/frontend/app_flowy/packages/flowy_editor/example/lib/plugin/selected_text_node_widget.dart +++ b/frontend/app_flowy/packages/flowy_editor/example/lib/plugin/selected_text_node_widget.dart @@ -1,3 +1,5 @@ +import 'dart:math'; + import 'package:example/plugin/debuggable_rich_text.dart'; import 'package:flowy_editor/flowy_editor.dart'; import 'package:flutter/foundation.dart'; @@ -108,6 +110,30 @@ class _SelectedTextNodeWidgetState extends State<_SelectedTextNodeWidget> return _renderParagraph.localToGlobal(offset); } + @override + Offset getLeftOfOffset() { + final textSelection = _textSelection; + if (textSelection != null) { + final leftTextSelection = TextSelection.collapsed( + offset: max(0, textSelection.baseOffset - 1), + ); + return getOffsetByTextSelection(leftTextSelection); + } + return Offset.zero; + } + + @override + Offset getRightOfOffset() { + final textSelection = _textSelection; + if (textSelection != null) { + final leftTextSelection = TextSelection.collapsed( + offset: min(node.toRawString().length, textSelection.extentOffset + 1), + ); + return getOffsetByTextSelection(leftTextSelection); + } + return Offset.zero; + } + @override Widget build(BuildContext context) { Widget richText; @@ -117,6 +143,10 @@ class _SelectedTextNodeWidgetState extends State<_SelectedTextNodeWidget> richText = RichText(key: _textKey, text: node.toTextSpan()); } + if (node.children.isEmpty) { + return richText; + } + return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ diff --git a/frontend/app_flowy/packages/flowy_editor/lib/editor_state.dart b/frontend/app_flowy/packages/flowy_editor/lib/editor_state.dart index 04a5721ed9..000badf02a 100644 --- a/frontend/app_flowy/packages/flowy_editor/lib/editor_state.dart +++ b/frontend/app_flowy/packages/flowy_editor/lib/editor_state.dart @@ -1,4 +1,5 @@ import 'dart:async'; +import 'package:flowy_editor/service/service.dart'; import 'package:flutter/material.dart'; import 'package:flowy_editor/document/node.dart'; @@ -19,13 +20,14 @@ class ApplyOptions { }); } -// TODO -final selectionServiceKey = GlobalKey(); - class EditorState { final StateTree document; final RenderPlugins renderPlugins; List selectedNodes = []; + + // Service reference. + final service = FlowyService(); + final UndoManager undoManager = UndoManager(); Selection? cursorSelection; diff --git a/frontend/app_flowy/packages/flowy_editor/lib/render/selection/selectable.dart b/frontend/app_flowy/packages/flowy_editor/lib/render/selection/selectable.dart index 59849c1a6a..098c246569 100644 --- a/frontend/app_flowy/packages/flowy_editor/lib/render/selection/selectable.dart +++ b/frontend/app_flowy/packages/flowy_editor/lib/render/selection/selectable.dart @@ -11,6 +11,12 @@ mixin Selectable on State { /// The return result must be an local offset. Rect getCursorRect(Offset start); + /// Returns one unit offset to the left of the offset + Offset getLeftOfOffset(/* Cause */); + + /// Returns one unit offset to the right of the offset + Offset getRightOfOffset(/* Cause */); + /// For [TextNode] only. TextSelection? getTextSelection(); 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 d0efac2a0f..2ebb9ce14a 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 @@ -1,3 +1,4 @@ +import 'package:flowy_editor/service/flowy_key_event_handlers/arrow_keys_handler.dart'; import 'package:flowy_editor/service/flowy_key_event_handlers/delete_nodes_handler.dart'; import 'package:flowy_editor/service/flowy_key_event_handlers/delete_single_text_node_handler.dart'; import 'package:flowy_editor/service/keyboard_service.dart'; @@ -26,12 +27,14 @@ class _FlowyEditorState extends State { @override Widget build(BuildContext context) { return FlowySelection( - key: selectionServiceKey, + key: editorState.service.selectionServiceKey, editorState: editorState, child: FlowyKeyboard( + key: editorState.service.keyboardServiceKey, handlers: [ flowyDeleteNodesHandler, deleteSingleTextNodeHandler, + arrowKeysHandler, ...widget.keyEventHandler, ], editorState: editorState, diff --git a/frontend/app_flowy/packages/flowy_editor/lib/service/flowy_key_event_handlers/arrow_keys_handler.dart b/frontend/app_flowy/packages/flowy_editor/lib/service/flowy_key_event_handlers/arrow_keys_handler.dart new file mode 100644 index 0000000000..4de5b61968 --- /dev/null +++ b/frontend/app_flowy/packages/flowy_editor/lib/service/flowy_key_event_handlers/arrow_keys_handler.dart @@ -0,0 +1,37 @@ +import 'package:flowy_editor/extensions/object_extensions.dart'; +import 'package:flowy_editor/flowy_editor.dart'; +import 'package:flowy_editor/service/keyboard_service.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; + +FlowyKeyEventHandler arrowKeysHandler = (editorState, event) { + if (event.logicalKey != LogicalKeyboardKey.arrowUp && + event.logicalKey != LogicalKeyboardKey.arrowDown && + event.logicalKey != LogicalKeyboardKey.arrowLeft && + event.logicalKey != LogicalKeyboardKey.arrowRight) { + return KeyEventResult.ignored; + } + + // TODO: Up and Down + + // Left and Right + final selectedNodes = editorState.selectedNodes; + if (selectedNodes.length != 1) { + return KeyEventResult.ignored; + } + + final node = selectedNodes.first.unwrapOrNull(); + final selectable = node?.key?.currentState?.unwrapOrNull(); + Offset? offset; + if (event.logicalKey == LogicalKeyboardKey.arrowLeft) { + offset = selectable?.getLeftOfOffset(); + } else if (event.logicalKey == LogicalKeyboardKey.arrowRight) { + offset = selectable?.getRightOfOffset(); + } + final selectionService = editorState.service.selectionService; + if (offset != null) { + selectionService.updateCursor(offset); + return KeyEventResult.handled; + } + return KeyEventResult.ignored; +}; diff --git a/frontend/app_flowy/packages/flowy_editor/lib/service/flowy_key_event_handlers/delete_single_text_node_handler.dart b/frontend/app_flowy/packages/flowy_editor/lib/service/flowy_key_event_handlers/delete_single_text_node_handler.dart index 3c1c1c9e95..5affb8800f 100644 --- a/frontend/app_flowy/packages/flowy_editor/lib/service/flowy_key_event_handlers/delete_single_text_node_handler.dart +++ b/frontend/app_flowy/packages/flowy_editor/lib/service/flowy_key_event_handlers/delete_single_text_node_handler.dart @@ -33,8 +33,7 @@ FlowyKeyEventHandler deleteSingleTextNodeHandler = (editorState, event) { final previous = node!.previous! as TextNode; final newTextSelection = TextSelection.collapsed( offset: previous.toRawString().length); - final selectionService = - selectionServiceKey.currentState as FlowySelectionService; + final selectionService = editorState.service.selectionService; final previousSelectable = previous.key?.currentState?.unwrapOrNull(); final newOfset = previousSelectable @@ -58,8 +57,7 @@ FlowyKeyEventHandler deleteSingleTextNodeHandler = (editorState, event) { ..commit(); final newTextSelection = TextSelection.collapsed(offset: textSelection.baseOffset - 1); - final selectionService = - selectionServiceKey.currentState as FlowySelectionService; + final selectionService = editorState.service.selectionService; final newOfset = selectable.getOffsetByTextSelection(newTextSelection); selectionService.updateCursor(newOfset); diff --git a/frontend/app_flowy/packages/flowy_editor/lib/service/keyboard_service.dart b/frontend/app_flowy/packages/flowy_editor/lib/service/keyboard_service.dart index 060a9c98fb..ebd66894a7 100644 --- a/frontend/app_flowy/packages/flowy_editor/lib/service/keyboard_service.dart +++ b/frontend/app_flowy/packages/flowy_editor/lib/service/keyboard_service.dart @@ -46,7 +46,7 @@ class _FlowyKeyboardState extends State { } for (final handler in widget.handlers) { - debugPrint('handle keyboard event $event by $handler'); + // debugPrint('handle keyboard event $event by $handler'); KeyEventResult result = handler(widget.editorState, event); diff --git a/frontend/app_flowy/packages/flowy_editor/lib/service/service.dart b/frontend/app_flowy/packages/flowy_editor/lib/service/service.dart new file mode 100644 index 0000000000..8c436cee7f --- /dev/null +++ b/frontend/app_flowy/packages/flowy_editor/lib/service/service.dart @@ -0,0 +1,15 @@ +import 'package:flowy_editor/service/selection_service.dart'; +import 'package:flutter/material.dart'; + +class FlowyService { + // selection service + final selectionServiceKey = GlobalKey(debugLabel: 'flowy_selection_service'); + FlowySelectionService get selectionService { + assert(selectionServiceKey.currentState != null && + selectionServiceKey.currentState is FlowySelectionService); + return selectionServiceKey.currentState! as FlowySelectionService; + } + + // keyboard service + final keyboardServiceKey = GlobalKey(debugLabel: 'flowy_keyboard_service'); +}