ソースを参照

feat: handle arrow keys

Vincent Chan 2 年 前
コミット
e74f5e84dc

+ 1 - 1
frontend/app_flowy/packages/flowy_editor/example/lib/plugin/text_node_widget.dart

@@ -126,7 +126,7 @@ class __TextNodeWidgetState extends State<_TextNodeWidget>
         textCapitalization: TextCapitalization.sentences,
       ),
     );
-    editorState.cursorSelection = _localSelectionToGlobal(node, selection);
+    editorState.updateCursorSelection(_localSelectionToGlobal(node, selection));
     _textInputConnection
       ?..show()
       ..setEditingState(

+ 17 - 2
frontend/app_flowy/packages/flowy_editor/lib/editor_state.dart

@@ -31,7 +31,22 @@ class EditorState {
   final service = FlowyService();
 
   final UndoManager undoManager = UndoManager();
-  Selection? cursorSelection;
+  Selection? _cursorSelection;
+
+  Selection? get cursorSelection {
+    return _cursorSelection;
+  }
+
+  /// add the set reason in the future, don't use setter
+  updateCursorSelection(Selection? cursorSelection) {
+    // broadcast to other users here
+    if (cursorSelection == null) {
+      service.selectionService.clearSelection();
+    } else {
+      service.selectionService.updateSelection(cursorSelection);
+    }
+    _cursorSelection = cursorSelection;
+  }
 
   Timer? _debouncedSealHistoryItemTimer;
 
@@ -58,7 +73,7 @@ class EditorState {
     for (final op in transaction.operations) {
       _applyOperation(op);
     }
-    cursorSelection = transaction.afterSelection;
+    updateCursorSelection(transaction.afterSelection);
 
     if (options.recordUndo) {
       final undoItem = undoManager.getUndoHistoryItem();

+ 59 - 0
frontend/app_flowy/packages/flowy_editor/lib/service/internal_key_event_handlers/arrow_keys_handler.dart

@@ -1,7 +1,17 @@
+import 'package:flowy_editor/document/node.dart';
+import 'package:flowy_editor/document/position.dart';
 import 'package:flowy_editor/service/keyboard_service.dart';
+import 'package:flowy_editor/document/selection.dart';
 import 'package:flutter/material.dart';
 import 'package:flutter/services.dart';
 
+int _endOffsetOfNode(Node node) {
+  if (node is TextNode) {
+    return node.delta.length;
+  }
+  return 0;
+}
+
 FlowyKeyEventHandler arrowKeysHandler = (editorState, event) {
   if (event.logicalKey != LogicalKeyboardKey.arrowUp &&
       event.logicalKey != LogicalKeyboardKey.arrowDown &&
@@ -10,5 +20,54 @@ FlowyKeyEventHandler arrowKeysHandler = (editorState, event) {
     return KeyEventResult.ignored;
   }
 
+  final currentSelection = editorState.cursorSelection;
+  if (currentSelection == null) {
+    return KeyEventResult.ignored;
+  }
+
+  if (event.logicalKey == LogicalKeyboardKey.arrowLeft) {
+    // turn left
+    if (currentSelection.isCollapsed) {
+      final end = currentSelection.end;
+      final offset = end.offset;
+      if (offset == 0) {
+        final node = editorState.document.nodeAtPath(end.path)!;
+        final prevNode = node.previous;
+        if (prevNode != null) {
+          editorState.updateCursorSelection(Selection.collapsed(Position(
+              path: prevNode.path, offset: _endOffsetOfNode(prevNode))));
+        }
+        return KeyEventResult.handled;
+      }
+      editorState.updateCursorSelection(
+          Selection.collapsed(Position(path: end.path, offset: offset - 1)));
+    } else {
+      editorState
+          .updateCursorSelection(currentSelection.collapse(atStart: true));
+    }
+    return KeyEventResult.handled;
+  } else if (event.logicalKey == LogicalKeyboardKey.arrowRight) {
+    if (currentSelection.isCollapsed) {
+      final end = currentSelection.end;
+      final offset = end.offset;
+      final node = editorState.document.nodeAtPath(end.path)!;
+      final lengthOfNode = _endOffsetOfNode(node);
+      if (offset >= lengthOfNode) {
+        final nextNode = node.next;
+        if (nextNode != null) {
+          editorState.updateCursorSelection(
+              Selection.collapsed(Position(path: nextNode.path, offset: 0)));
+        }
+        return KeyEventResult.handled;
+      }
+
+      editorState.updateCursorSelection(
+          Selection.collapsed(Position(path: end.path, offset: offset + 1)));
+    } else {
+      editorState.updateCursorSelection(currentSelection.collapse());
+    }
+    return KeyEventResult.handled;
+  }
+
   return KeyEventResult.ignored;
 };

+ 11 - 10
frontend/app_flowy/packages/flowy_editor/lib/service/selection_service.dart

@@ -1,4 +1,3 @@
-import 'package:flowy_editor/document/path.dart';
 import 'package:flowy_editor/document/node.dart';
 import 'package:flowy_editor/document/position.dart';
 import 'package:flowy_editor/document/selection.dart';
@@ -49,7 +48,7 @@ mixin FlowySelectionService<T extends StatefulWidget> on State<T> {
   /// [start] is the offset under the global coordinate system.
   Node? computeNodeInOffset(Node node, Offset offset);
 
-  /// Return the [Node]s in multiple selection. Emtpy list would be returned
+  /// Return the [Node]s in multiple selection. Empty list would be returned
   ///   if no nodes are in range.
   ///
   /// [start] is the offset under the global coordinate system.
@@ -136,8 +135,8 @@ class _FlowySelectionState extends State<FlowySelection>
         TapGestureRecognizer:
             GestureRecognizerFactoryWithHandlers<TapGestureRecognizer>(
           () => TapGestureRecognizer(),
-          (recongizer) {
-            recongizer.onTapDown = _onTapDown;
+          (recognizer) {
+            recognizer.onTapDown = _onTapDown;
           },
         )
       },
@@ -167,9 +166,9 @@ class _FlowySelectionState extends State<FlowySelection>
     if (end != null) {
       return computeNodesInRange(editorState.document.root, start, end);
     } else {
-      final reuslt = computeNodeInOffset(editorState.document.root, start);
-      if (reuslt != null) {
-        return [reuslt];
+      final result = computeNodeInOffset(editorState.document.root, start);
+      if (result != null) {
+        return [result];
       }
     }
     return [];
@@ -253,8 +252,10 @@ class _FlowySelectionState extends State<FlowySelection>
       if (selectable != null) {
         final position = selectable.getPositionInOffset(tapOffset!);
         final selection = Selection.collapsed(position);
-        updateSelection(selection);
+        editorState.updateCursorSelection(selection);
       }
+    } else {
+      editorState.updateCursorSelection(null);
     }
   }
 
@@ -283,7 +284,7 @@ class _FlowySelectionState extends State<FlowySelection>
       final selection = Selection(
           start: isDownward ? start : end, end: isDownward ? end : start);
       debugPrint('[_onPanUpdate] $selection');
-      updateSelection(selection);
+      editorState.updateCursorSelection(selection);
     }
   }
 
@@ -302,7 +303,7 @@ class _FlowySelectionState extends State<FlowySelection>
     _cursorOverlays
       ..forEach((overlay) => overlay.remove())
       ..clear();
-    // clear floating shortcusts
+    // clear floating shortcuts
     editorState.service.floatingShortcutServiceKey.currentState
         ?.unwrapOrNull<FlowyFloatingShortcutService>()
         ?.hide();