Explorar o código

feat: compute cursor and selection by [Selection] or [Offset]

Lucas.Xu %!s(int64=2) %!d(string=hai) anos
pai
achega
cde2127dec

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

@@ -326,7 +326,7 @@ TextSelection? _globalSelectionToLocal(Node node, Selection? globalSel) {
   if (!pathEquals(nodePath, globalSel.start.path)) {
     return null;
   }
-  if (globalSel.isCollapsed()) {
+  if (globalSel.isCollapsed) {
     return TextSelection(
         baseOffset: globalSel.start.offset, extentOffset: globalSel.end.offset);
   } else {

+ 0 - 24
frontend/app_flowy/packages/flowy_editor/lib/document/path.dart

@@ -7,27 +7,3 @@ typedef Path = List<int>;
 bool pathEquals(Path path1, Path path2) {
   return listEquals(path1, path2);
 }
-
-/// Returns true if path1 >= path2, otherwise returns false.
-/// TODO: Rename this function.
-bool pathGreaterOrEquals(Path path1, Path path2) {
-  final length = min(path1.length, path2.length);
-  for (var i = 0; i < length; i++) {
-    if (path1[i] < path2[i]) {
-      return false;
-    }
-  }
-  return true;
-}
-
-/// Returns true if path1 <= path2, otherwise returns false.
-/// TODO: Rename this function.
-bool pathLessOrEquals(Path path1, Path path2) {
-  final length = min(path1.length, path2.length);
-  for (var i = 0; i < length; i++) {
-    if (path1[i] > path2[i]) {
-      return false;
-    }
-  }
-  return true;
-}

+ 3 - 0
frontend/app_flowy/packages/flowy_editor/lib/document/position.dart

@@ -31,4 +31,7 @@ class Position {
       offset: offset ?? this.offset,
     );
   }
+
+  @override
+  String toString() => 'path = $path, offset = $offset';
 }

+ 9 - 3
frontend/app_flowy/packages/flowy_editor/lib/document/selection.dart

@@ -1,5 +1,6 @@
 import 'package:flowy_editor/document/path.dart';
 import 'package:flowy_editor/document/position.dart';
+import 'package:flowy_editor/extensions/path_extensions.dart';
 
 class Selection {
   final Position start;
@@ -29,9 +30,11 @@ class Selection {
     }
   }
 
-  bool isCollapsed() {
-    return start == end;
-  }
+  bool get isCollapsed => start == end;
+  bool get isUpward =>
+      start.path >= end.path && !pathEquals(start.path, end.path);
+  bool get isDownward =>
+      start.path <= end.path && !pathEquals(start.path, end.path);
 
   Selection copyWith({Position? start, Position? end}) {
     return Selection(
@@ -39,4 +42,7 @@ class Selection {
       end: end ?? this.end,
     );
   }
+
+  @override
+  String toString() => '[Selection] start = $start, end = $end';
 }

+ 12 - 0
frontend/app_flowy/packages/flowy_editor/lib/extensions/node_extensions.dart

@@ -1,5 +1,9 @@
+import 'dart:math';
+
 import 'package:flowy_editor/document/node.dart';
+import 'package:flowy_editor/document/selection.dart';
 import 'package:flowy_editor/extensions/object_extensions.dart';
+import 'package:flowy_editor/extensions/path_extensions.dart';
 import 'package:flowy_editor/render/selection/selectable.dart';
 import 'package:flutter/material.dart';
 
@@ -8,4 +12,12 @@ extension NodeExtensions on Node {
       key?.currentContext?.findRenderObject()?.unwrapOrNull<RenderBox>();
 
   Selectable? get selectable => key?.currentState?.unwrapOrNull<Selectable>();
+
+  bool inSelection(Selection selection) {
+    if (selection.start.path <= selection.end.path) {
+      return selection.start.path <= path && path <= selection.end.path;
+    } else {
+      return selection.end.path <= path && path <= selection.start.path;
+    }
+  }
 }

+ 25 - 0
frontend/app_flowy/packages/flowy_editor/lib/extensions/path_extensions.dart

@@ -0,0 +1,25 @@
+import 'package:flowy_editor/document/path.dart';
+
+import 'dart:math';
+
+extension PathExtensions on Path {
+  bool operator >=(Path other) {
+    final length = min(this.length, other.length);
+    for (var i = 0; i < length; i++) {
+      if (this[i] < other[i]) {
+        return false;
+      }
+    }
+    return true;
+  }
+
+  bool operator <=(Path other) {
+    final length = min(this.length, other.length);
+    for (var i = 0; i < length; i++) {
+      if (this[i] > other[i]) {
+        return false;
+      }
+    }
+    return true;
+  }
+}

+ 80 - 47
frontend/app_flowy/packages/flowy_editor/lib/service/selection_service.dart

@@ -1,4 +1,5 @@
 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';
 import 'package:flowy_editor/render/selection/cursor_widget.dart';
@@ -6,18 +7,22 @@ import 'package:flowy_editor/render/selection/flowy_selection_widget.dart';
 import 'package:flowy_editor/extensions/object_extensions.dart';
 import 'package:flowy_editor/extensions/node_extensions.dart';
 import 'package:flowy_editor/service/shortcut_service.dart';
+import 'package:flowy_editor/editor_state.dart';
+
 import 'package:flutter/gestures.dart';
 import 'package:flutter/material.dart';
 
-import '../editor_state.dart';
-import '../document/node.dart';
-import '../render/selection/selectable.dart';
-
 /// Process selection and cursor
 mixin FlowySelectionService<T extends StatefulWidget> on State<T> {
+  ///
+  List<Node> get currentSelectedNodes;
+
   ///
   void updateSelection(Selection selection);
 
+  ///
+  void clearSelection();
+
   /// Returns selected [Node]s. Empty list would be returned
   ///   if no nodes are being selected.
   ///
@@ -49,7 +54,7 @@ mixin FlowySelectionService<T extends StatefulWidget> on State<T> {
   /// Return [bool] to identify the [Node] is in Range or not.
   ///
   /// [start] and [end] are the offsets under the global coordinate system.
-  bool isNodeInSelection(
+  bool isNodeInRange(
     Node node,
     Offset start,
     Offset end,
@@ -96,6 +101,12 @@ class _FlowySelectionState extends State<FlowySelection>
 
   EditorState get editorState => widget.editorState;
 
+  Node? _selectedNodeInPostion(Node node, Position position) =>
+      node.childAtPath(position.path);
+
+  @override
+  List<Node> currentSelectedNodes = [];
+
   @override
   List<Node> getNodesInSelection(Selection selection) =>
       _selectedNodesInSelection(editorState.document.root, selection);
@@ -129,16 +140,21 @@ class _FlowySelectionState extends State<FlowySelection>
 
   @override
   void updateSelection(Selection selection) {
-    _clearAllOverlayEntries();
+    _clearSelection();
 
     // cursor
-    if (selection.isCollapsed()) {
+    if (selection.isCollapsed) {
       _updateCursor(selection.start);
     } else {
       _updateSelection(selection);
     }
   }
 
+  @override
+  void clearSelection() {
+    _clearSelection();
+  }
+
   @override
   List<Node> getNodesInRange(Offset start, [Offset? end]) {
     if (end != null) {
@@ -172,7 +188,7 @@ class _FlowySelectionState extends State<FlowySelection>
   List<Node> computeNodesInRange(Node node, Offset start, Offset end) {
     List<Node> result = [];
     if (node.parent != null && node.key != null) {
-      if (isNodeInSelection(node, start, end)) {
+      if (isNodeInRange(node, start, end)) {
         result.add(node);
       }
     }
@@ -195,7 +211,7 @@ class _FlowySelectionState extends State<FlowySelection>
   }
 
   @override
-  bool isNodeInSelection(Node node, Offset start, Offset end) {
+  bool isNodeInRange(Node node, Offset start, Offset end) {
     final renderBox = node.renderBox;
     if (renderBox != null) {
       final rect = Rect.fromPoints(start, end);
@@ -244,10 +260,21 @@ class _FlowySelectionState extends State<FlowySelection>
     final first = nodes.first.selectable;
     final last = nodes.last.selectable;
     if (first != null && last != null) {
-      final selection = Selection(
-        start: first.getSelectionInRange(panStartOffset!, panEndOffset!).start,
-        end: last.getSelectionInRange(panStartOffset!, panEndOffset!).end,
-      );
+      final Selection selection;
+      if (panStartOffset!.dy <= panEndOffset!.dy) {
+        // down
+        selection = Selection(
+          start:
+              first.getSelectionInRange(panStartOffset!, panEndOffset!).start,
+          end: last.getSelectionInRange(panStartOffset!, panEndOffset!).end,
+        );
+      } else {
+        // up
+        selection = Selection(
+          start: last.getSelectionInRange(panStartOffset!, panEndOffset!).end,
+          end: first.getSelectionInRange(panStartOffset!, panEndOffset!).start,
+        );
+      }
       updateSelection(selection);
     }
   }
@@ -256,35 +283,29 @@ class _FlowySelectionState extends State<FlowySelection>
     // do nothing
   }
 
-  void _clearAllOverlayEntries() {
-    _clearSelection();
-    _clearCursor();
-    _clearFloatingShorts();
-  }
-
   void _clearSelection() {
+    currentSelectedNodes = [];
+
+    // clear selection
     _selectionOverlays
       ..forEach((overlay) => overlay.remove())
       ..clear();
-  }
-
-  void _clearCursor() {
+    // clear cursors
     _cursorOverlays
       ..forEach((overlay) => overlay.remove())
       ..clear();
-  }
-
-  void _clearFloatingShorts() {
-    final shortcutService = editorState
-        .service.floatingShortcutServiceKey.currentState
-        ?.unwrapOrNull<FlowyFloatingShortcutService>();
-    shortcutService?.hide();
+    // clear floating shortcusts
+    editorState.service.floatingShortcutServiceKey.currentState
+        ?.unwrapOrNull<FlowyFloatingShortcutService>()
+        ?.hide();
   }
 
   void _updateSelection(Selection selection) {
     final nodes =
         _selectedNodesInSelection(editorState.document.root, selection);
 
+    currentSelectedNodes = nodes;
+
     var index = 0;
     for (final node in nodes) {
       final selectable = node.selectable;
@@ -293,20 +314,38 @@ class _FlowySelectionState extends State<FlowySelection>
       }
 
       Selection newSelection;
+      // TODO: too complicate, need to refactor.
       if (node is TextNode) {
         if (pathEquals(selection.start.path, selection.end.path)) {
           newSelection = selection.copyWith();
         } else {
           if (index == 0) {
-            newSelection = selection.copyWith(
-              /// FIXME: make it better.
-              end: selection.start.copyWith(offset: node.toRawString().length),
-            );
+            if (selection.isUpward) {
+              newSelection = selection.copyWith(
+                /// FIXME: make it better.
+                start: selection.end.copyWith(),
+                end: selection.end.copyWith(offset: node.toRawString().length),
+              );
+            } else {
+              newSelection = selection.copyWith(
+                /// FIXME: make it better.
+                end:
+                    selection.start.copyWith(offset: node.toRawString().length),
+              );
+            }
           } else if (index == nodes.length - 1) {
-            newSelection = selection.copyWith(
-              /// FIXME: make it better.
-              start: selection.end.copyWith(offset: 0),
-            );
+            if (selection.isUpward) {
+              newSelection = selection.copyWith(
+                /// FIXME: make it better.
+                start: selection.start.copyWith(offset: 0),
+                end: selection.start.copyWith(),
+              );
+            } else {
+              newSelection = selection.copyWith(
+                /// FIXME: make it better.
+                start: selection.end.copyWith(offset: 0),
+              );
+            }
           } else {
             final position = Position(path: node.path);
             newSelection = Selection(
@@ -339,13 +378,15 @@ class _FlowySelectionState extends State<FlowySelection>
   }
 
   void _updateCursor(Position position) {
-    final node = _selectedNodeInPostion(editorState.document.root, position);
+    final node = editorState.document.root.childAtPath(position.path);
 
     assert(node != null);
     if (node == null) {
       return;
     }
 
+    currentSelectedNodes = [node];
+
     final selectable = node.selectable;
     final rect = selectable?.getCursorRectInPosition(position);
     if (rect != null) {
@@ -365,7 +406,7 @@ class _FlowySelectionState extends State<FlowySelection>
   List<Node> _selectedNodesInSelection(Node node, Selection selection) {
     List<Node> result = [];
     if (node.parent != null) {
-      if (_isNodeInSelection(node, selection)) {
+      if (node.inSelection(selection)) {
         result.add(node);
       }
     }
@@ -374,12 +415,4 @@ class _FlowySelectionState extends State<FlowySelection>
     }
     return result;
   }
-
-  Node? _selectedNodeInPostion(Node node, Position position) =>
-      node.childAtPath(position.path);
-
-  bool _isNodeInSelection(Node node, Selection selection) {
-    return pathGreaterOrEquals(node.path, selection.start.path) &&
-        pathLessOrEquals(node.path, selection.end.path);
-  }
 }

+ 1 - 1
frontend/app_flowy/packages/flowy_editor/test/flowy_editor_test.dart

@@ -127,7 +127,7 @@ void main() {
     final pos = Position(path: [0], offset: 0);
     final sel = Selection.collapsed(pos);
     expect(sel.start, sel.end);
-    expect(sel.isCollapsed(), true);
+    expect(sel.isCollapsed, true);
   });
 
   test('test selection collapse', () {