Browse Source

feat: add keyboard example

Lucas.Xu 2 years ago
parent
commit
c643c02887

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

@@ -1,5 +1,4 @@
 import 'package:flowy_editor/flowy_editor.dart';
-import 'package:flutter/gestures.dart';
 import 'package:flutter/material.dart';
 
 class EditorNodeWidgetBuilder extends NodeWidgetBuilder {

+ 5 - 0
frontend/app_flowy/packages/flowy_editor/example/lib/plugin/image_node_widget.dart

@@ -57,6 +57,11 @@ class __ImageNodeWidgetState extends State<_ImageNodeWidget>
     return cursorOffset & size;
   }
 
+  @override
+  TextSelection? getTextSelection() {
+    return null;
+  }
+
   @override
   KeyEventResult onKeyDown(RawKeyEvent event) {
     if (event.logicalKey == LogicalKeyboardKey.backspace) {

+ 8 - 3
frontend/app_flowy/packages/flowy_editor/example/lib/plugin/selected_text_node_widget.dart

@@ -94,6 +94,11 @@ class _SelectedTextNodeWidgetState extends State<_SelectedTextNodeWidget>
     return _computeCursorRect(textSelection.baseOffset);
   }
 
+  @override
+  TextSelection? getTextSelection() {
+    return _textSelection;
+  }
+
   @override
   KeyEventResult onKeyDown(RawKeyEvent event) {
     if (event.logicalKey == LogicalKeyboardKey.backspace) {
@@ -111,9 +116,9 @@ class _SelectedTextNodeWidgetState extends State<_SelectedTextNodeWidget>
             TransactionBuilder(editorState)
               ..deleteText(node, textSelection.start - 1, 1)
               ..commit();
-            final rect = _computeCursorRect(textSelection.baseOffset - 1);
-            editorState.tapOffset = rect.center;
-            editorState.updateCursor();
+            // final rect = _computeCursorRect(textSelection.baseOffset - 1);
+            // editorState.tapOffset = rect.center;
+            // editorState.updateCursor();
           }
         } else {
           TransactionBuilder(editorState)

+ 1 - 148
frontend/app_flowy/packages/flowy_editor/lib/editor_state.dart

@@ -15,10 +15,7 @@ import './render/render_plugins.dart';
 class EditorState {
   final StateTree document;
   final RenderPlugins renderPlugins;
-
-  Offset? tapOffset;
-  Offset? panStartOffset;
-  Offset? panEndOffset;
+  List<Node> selectedNodes = [];
 
   Selection? cursorSelection;
 
@@ -59,148 +56,4 @@ class EditorState {
       document.textEdit(op.path, op.delta);
     }
   }
-
-  List<OverlayEntry> selectionOverlays = [];
-
-  void updateCursor() {
-    selectionOverlays
-      ..forEach((element) => element.remove())
-      ..clear();
-
-    if (tapOffset == null) {
-      return;
-    }
-
-    // TODO: upward and backward
-    final selectedNode = _calculateSelectedNode(document.root, tapOffset!);
-    if (selectedNode.isEmpty) {
-      return;
-    }
-    final key = selectedNode.first.key;
-    if (key != null && key.currentState is Selectable) {
-      final selectable = key.currentState as Selectable;
-      final rect = selectable.getCursorRect(tapOffset!);
-      final overlay = OverlayEntry(builder: ((context) {
-        return Positioned.fromRect(
-          rect: rect,
-          child: Container(
-            color: Colors.red,
-          ),
-        );
-      }));
-      selectionOverlays.add(overlay);
-      Overlay.of(selectable.context)?.insert(overlay);
-    }
-  }
-
-  void updateSelection() {
-    selectionOverlays
-      ..forEach((element) => element.remove())
-      ..clear();
-
-    final selectedNodes = this.selectedNodes;
-    if (selectedNodes.isEmpty ||
-        panStartOffset == null ||
-        panEndOffset == null) {
-      return;
-    }
-
-    for (final node in selectedNodes) {
-      final key = node.key;
-      if (key != null && key.currentState is Selectable) {
-        final selectable = key.currentState as Selectable;
-        final overlayRects = selectable.getSelectionRectsInSelection(
-            panStartOffset!, panEndOffset!);
-        for (final rect in overlayRects) {
-          // TODO: refactor overlay implement.
-          final overlay = OverlayEntry(builder: ((context) {
-            return Positioned.fromRect(
-              rect: rect,
-              child: Container(
-                color: Colors.yellow.withAlpha(100),
-              ),
-            );
-          }));
-          selectionOverlays.add(overlay);
-          Overlay.of(selectable.context)?.insert(overlay);
-        }
-      }
-    }
-  }
-
-  List<Node> get selectedNodes {
-    if (panStartOffset != null && panEndOffset != null) {
-      return _calculateSelectedNodes(
-          document.root, panStartOffset!, panEndOffset!);
-    }
-    if (tapOffset != null) {
-      return _calculateSelectedNode(document.root, tapOffset!);
-    }
-    return [];
-  }
-
-  List<Node> _calculateSelectedNode(Node node, Offset offset) {
-    List<Node> result = [];
-
-    /// Skip the node without parent because it is the topmost node.
-    /// Skip the node without key because it cannot get the [RenderObject].
-    if (node.parent != null && node.key != null) {
-      if (_isNodeInOffset(node, offset)) {
-        result.add(node);
-      }
-    }
-
-    ///
-    for (final child in node.children) {
-      result.addAll(_calculateSelectedNode(child, offset));
-    }
-
-    return result;
-  }
-
-  bool _isNodeInOffset(Node node, Offset offset) {
-    assert(node.key != null);
-    final renderBox =
-        node.key?.currentContext?.findRenderObject() as RenderBox?;
-    if (renderBox == null) {
-      return false;
-    }
-    final boxOffset = renderBox.localToGlobal(Offset.zero);
-    final boxRect = boxOffset & renderBox.size;
-    return boxRect.contains(offset);
-  }
-
-  List<Node> _calculateSelectedNodes(Node node, Offset start, Offset end) {
-    List<Node> result = [];
-
-    /// Skip the node without parent because it is the topmost node.
-    /// Skip the node without key because it cannot get the [RenderObject].
-    if (node.parent != null && node.key != null) {
-      if (_isNodeInRange(node, start, end)) {
-        result.add(node);
-      }
-    }
-
-    ///
-    for (final child in node.children) {
-      result.addAll(_calculateSelectedNodes(child, start, end));
-    }
-
-    return result;
-  }
-
-  bool _isNodeInRange(Node node, Offset start, Offset end) {
-    assert(node.key != null);
-    final renderBox =
-        node.key?.currentContext?.findRenderObject() as RenderBox?;
-
-    /// Return false directly if the [RenderBox] cannot found.
-    if (renderBox == null) {
-      return false;
-    }
-
-    final rect = Rect.fromPoints(start, end);
-    final boxOffset = renderBox.localToGlobal(Offset.zero);
-    return rect.overlaps(boxOffset & renderBox.size);
-  }
 }

+ 5 - 1
frontend/app_flowy/packages/flowy_editor/lib/flowy_editor_service.dart

@@ -24,7 +24,11 @@ class _FlowyEditorState extends State<FlowyEditor> {
     return FlowySelectionWidget(
       editorState: editorState,
       child: FlowyKeyboardWidget(
-        handlers: const [],
+        handlers: [
+          FlowyKeyboradBackSpaceHandler(
+            editorState: editorState,
+          )
+        ],
         editorState: editorState,
         child: editorState.build(context),
       ),

+ 40 - 4
frontend/app_flowy/packages/flowy_editor/lib/flowy_keyboard_service.dart

@@ -1,3 +1,7 @@
+import 'package:flowy_editor/document/node.dart';
+import 'package:flowy_editor/operation/transaction.dart';
+import 'package:flowy_editor/operation/transaction_builder.dart';
+import 'package:flowy_editor/render/selectable.dart';
 import 'package:flutter/services.dart';
 
 import 'editor_state.dart';
@@ -5,14 +9,44 @@ import 'package:flutter/material.dart';
 
 abstract class FlowyKeyboardHandler {
   final EditorState editorState;
-  final RawKeyEvent rawKeyEvent;
 
   FlowyKeyboardHandler({
     required this.editorState,
-    required this.rawKeyEvent,
   });
 
-  KeyEventResult onKeyDown();
+  KeyEventResult onKeyDown(RawKeyEvent event);
+}
+
+class FlowyKeyboradBackSpaceHandler extends FlowyKeyboardHandler {
+  FlowyKeyboradBackSpaceHandler({
+    required super.editorState,
+  });
+
+  @override
+  KeyEventResult onKeyDown(RawKeyEvent event) {
+    final selectedNodes = editorState.selectedNodes;
+    if (selectedNodes.isNotEmpty) {
+      // handle delete text
+      // TODO: type: cursor or selection
+      if (selectedNodes.length == 1) {
+        final node = selectedNodes.first;
+        if (node is TextNode) {
+          final selectable = node.key?.currentState as Selectable?;
+          final textSelection = selectable?.getTextSelection();
+          if (textSelection != null) {
+            if (textSelection.isCollapsed) {
+              TransactionBuilder(editorState)
+                ..deleteText(node, textSelection.start - 1, 1)
+                ..commit();
+              // TODO: update selection??
+            }
+          }
+        }
+      }
+      return KeyEventResult.handled;
+    }
+    return KeyEventResult.ignored;
+  }
 }
 
 /// Process keyboard events
@@ -46,6 +80,8 @@ class _FlowyKeyboardWidgetState extends State<FlowyKeyboardWidget> {
   }
 
   KeyEventResult _onKey(FocusNode node, RawKeyEvent event) {
+    debugPrint('on keyboard event $event');
+
     if (event is! RawKeyDownEvent) {
       return KeyEventResult.ignored;
     }
@@ -53,7 +89,7 @@ class _FlowyKeyboardWidgetState extends State<FlowyKeyboardWidget> {
     for (final handler in widget.handlers) {
       debugPrint('handle keyboard event $event by $handler');
 
-      KeyEventResult result = handler.onKeyDown();
+      KeyEventResult result = handler.onKeyDown(event);
 
       switch (result) {
         case KeyEventResult.handled:

+ 2 - 0
frontend/app_flowy/packages/flowy_editor/lib/flowy_selection_service.dart

@@ -102,6 +102,7 @@ class _FlowySelectionWidgetState extends State<FlowySelectionWidget>
     _clearOverlay();
 
     final nodes = selectedNodes;
+    editorState.selectedNodes = nodes;
     if (nodes.isEmpty || panStartOffset == null || panEndOffset == null) {
       assert(panStartOffset == null);
       assert(panEndOffset == null);
@@ -139,6 +140,7 @@ class _FlowySelectionWidgetState extends State<FlowySelectionWidget>
     }
 
     final nodes = selectedNodes;
+    editorState.selectedNodes = nodes;
     if (nodes.isEmpty) {
       return;
     }

+ 14 - 14
frontend/app_flowy/packages/flowy_editor/lib/keyboard.dart

@@ -26,20 +26,20 @@ class Keyboard extends StatelessWidget {
   }
 
   KeyEventResult _onKey(FocusNode node, RawKeyEvent event) {
-    if (event is! RawKeyDownEvent) {
-      return KeyEventResult.ignored;
-    }
-    List<KeyEventResult> result = [];
-    for (final node in editorState.selectedNodes) {
-      if (node.key != null &&
-          node.key?.currentState is KeyboardEventsRespondable) {
-        final respondable = node.key!.currentState as KeyboardEventsRespondable;
-        result.add(respondable.onKeyDown(event));
-      }
-    }
-    if (result.contains(KeyEventResult.handled)) {
-      return KeyEventResult.handled;
-    }
+    // if (event is! RawKeyDownEvent) {
+    //   return KeyEventResult.ignored;
+    // }
+    // List<KeyEventResult> result = [];
+    // for (final node in editorState.selectedNodes) {
+    //   if (node.key != null &&
+    //       node.key?.currentState is KeyboardEventsRespondable) {
+    //     final respondable = node.key!.currentState as KeyboardEventsRespondable;
+    //     result.add(respondable.onKeyDown(event));
+    //   }
+    // }
+    // if (result.contains(KeyEventResult.handled)) {
+    //   return KeyEventResult.handled;
+    // }
     return KeyEventResult.ignored;
   }
 }

+ 4 - 1
frontend/app_flowy/packages/flowy_editor/lib/render/selectable.dart

@@ -6,8 +6,11 @@ mixin Selectable<T extends StatefulWidget> on State<T> {
   /// [start] and [end] are global offsets.
   List<Rect> getSelectionRectsInSelection(Offset start, Offset end);
 
-  /// Returns a [Rect] for cursor
+  /// Returns a [Rect] for cursor.
   Rect getCursorRect(Offset start);
+
+  /// For [TextNode] only.
+  TextSelection? getTextSelection();
 }
 
 mixin KeyboardEventsRespondable<T extends StatefulWidget> on State<T> {