Browse Source

fix: compute wrong upward selection

Lucas.Xu 2 years ago
parent
commit
c048c8f623

+ 12 - 10
frontend/app_flowy/packages/flowy_editor/example/lib/plugin/image_node_widget.dart

@@ -39,6 +39,18 @@ class __ImageNodeWidgetState extends State<_ImageNodeWidget> with Selectable {
   EditorState get editorState => widget.editorState;
   String get src => widget.node.attributes['image_src'] as String;
 
+  @override
+  Position end() {
+    // TODO: implement end
+    throw UnimplementedError();
+  }
+
+  @override
+  Position start() {
+    // TODO: implement start
+    throw UnimplementedError();
+  }
+
   @override
   List<Rect> getRectsInSelection(Selection selection) {
     // TODO: implement getRectsInSelection
@@ -63,16 +75,6 @@ class __ImageNodeWidgetState extends State<_ImageNodeWidget> with Selectable {
     throw UnimplementedError();
   }
 
-  @override
-  Offset getBackwardOffset() {
-    return Offset.zero;
-  }
-
-  @override
-  Offset getForwardOffset() {
-    return Offset.zero;
-  }
-
   @override
   Widget build(BuildContext context) {
     return _build(context);

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

@@ -107,28 +107,11 @@ class _SelectedTextNodeWidgetState extends State<_SelectedTextNodeWidget>
   }
 
   @override
-  Offset getBackwardOffset() {
-    final textSelection = _textSelection;
-    if (textSelection != null) {
-      final leftTextSelection = TextSelection.collapsed(
-        offset: max(0, textSelection.baseOffset - 1),
-      );
-      return getOffsetByTextSelection(leftTextSelection);
-    }
-    return Offset.zero;
-  }
+  Position start() => Position(path: node.path, offset: 0);
 
   @override
-  Offset getForwardOffset() {
-    final textSelection = _textSelection;
-    if (textSelection != null) {
-      final leftTextSelection = TextSelection.collapsed(
-        offset: min(node.toRawString().length, textSelection.extentOffset + 1),
-      );
-      return getOffsetByTextSelection(leftTextSelection);
-    }
-    return Offset.zero;
-  }
+  Position end() =>
+      Position(path: node.path, offset: node.toRawString().length);
 
   @override
   Widget build(BuildContext context) {

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

@@ -31,6 +31,7 @@ class Selection {
   }
 
   bool get isCollapsed => start == end;
+  bool get isSingle => pathEquals(start.path, end.path);
   bool get isUpward =>
       start.path >= end.path && !pathEquals(start.path, end.path);
   bool get isDownward =>
@@ -43,6 +44,8 @@ class Selection {
     );
   }
 
+  Selection copy() => Selection(start: start, end: end);
+
   @override
   String toString() => '[Selection] start = $start, end = $end';
 }

+ 2 - 5
frontend/app_flowy/packages/flowy_editor/lib/render/selection/selectable.dart

@@ -23,11 +23,8 @@ mixin Selectable<T extends StatefulWidget> on State<T> {
   Position getPositionInOffset(Offset start);
   Rect getCursorRectInPosition(Position position);
 
-  /// Returns a backward offset of the current offset based on the cause.
-  Offset getBackwardOffset(/* Cause */);
-
-  /// Returns a forward offset of the current offset based on the cause.
-  Offset getForwardOffset(/* Cause */);
+  Position start();
+  Position end();
 
   /// For [TextNode] only.
   ///

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

@@ -1,5 +1,3 @@
-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';
@@ -12,26 +10,5 @@ FlowyKeyEventHandler arrowKeysHandler = (editorState, event) {
     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<TextNode>();
-  final selectable = node?.key?.currentState?.unwrapOrNull<Selectable>();
-  Offset? offset;
-  if (event.logicalKey == LogicalKeyboardKey.arrowLeft) {
-    offset = selectable?.getBackwardOffset();
-  } else if (event.logicalKey == LogicalKeyboardKey.arrowRight) {
-    offset = selectable?.getForwardOffset();
-  }
-  final selectionService = editorState.service.selectionService;
-  if (offset != null) {
-    // selectionService.updateCursor(offset);
-    return KeyEventResult.handled;
-  }
   return KeyEventResult.ignored;
 };

+ 63 - 74
frontend/app_flowy/packages/flowy_editor/lib/service/selection_service.dart

@@ -14,15 +14,26 @@ import 'package:flutter/material.dart';
 
 /// Process selection and cursor
 mixin FlowySelectionService<T extends StatefulWidget> on State<T> {
+  /// Returns the currently selected [Node]s.
   ///
+  /// The order of the return is determined according to the selected order.
   List<Node> get currentSelectedNodes;
 
+  /// ------------------ Selection ------------------------
+
   ///
   void updateSelection(Selection selection);
 
   ///
   void clearSelection();
 
+  ///
+  List<Node> getNodesInSelection(Selection selection);
+
+  /// ------------------ Selection ------------------------
+
+  /// ------------------ Offset ------------------------
+
   /// Returns selected [Node]s. Empty list would be returned
   ///   if no nodes are being selected.
   ///
@@ -33,9 +44,6 @@ mixin FlowySelectionService<T extends StatefulWidget> on State<T> {
   ///   otherwise single selection.
   List<Node> getNodesInRange(Offset start, [Offset? end]);
 
-  ///
-  List<Node> getNodesInSelection(Selection selection);
-
   /// Return the [Node] or [Null] in single selection.
   ///
   /// [start] is the offset under the global coordinate system.
@@ -64,6 +72,8 @@ mixin FlowySelectionService<T extends StatefulWidget> on State<T> {
   ///
   /// [start] is the offset under the global coordinate system.
   bool isNodeInOffset(Node node, Offset offset);
+
+  /// ------------------ Offset ------------------------
 }
 
 class FlowySelection extends StatefulWidget {
@@ -101,9 +111,6 @@ class _FlowySelectionState extends State<FlowySelection>
 
   EditorState get editorState => widget.editorState;
 
-  Node? _selectedNodeInPostion(Node node, Position position) =>
-      node.childAtPath(position.path);
-
   @override
   List<Node> currentSelectedNodes = [];
 
@@ -186,6 +193,17 @@ class _FlowySelectionState extends State<FlowySelection>
 
   @override
   List<Node> computeNodesInRange(Node node, Offset start, Offset end) {
+    final result = _computeNodesInRange(node, start, end);
+    if (start.dy <= end.dy) {
+      // downward
+      return result;
+    } else {
+      // upward
+      return result.reversed.toList(growable: false);
+    }
+  }
+
+  List<Node> _computeNodesInRange(Node node, Offset start, Offset end) {
     List<Node> result = [];
     if (node.parent != null && node.key != null) {
       if (isNodeInRange(node, start, end)) {
@@ -195,7 +213,6 @@ class _FlowySelectionState extends State<FlowySelection>
     for (final child in node.children) {
       result.addAll(computeNodesInRange(child, start, end));
     }
-    // TODO: sort the result
     return result;
   }
 
@@ -223,13 +240,12 @@ class _FlowySelectionState extends State<FlowySelection>
   }
 
   void _onTapDown(TapDownDetails details) {
-    debugPrint('on tap down');
-
-    // TODO: use setter to make them exclusive??
-    tapOffset = details.globalPosition;
+    // clear old state.
     panStartOffset = null;
     panEndOffset = null;
 
+    tapOffset = details.globalPosition;
+
     final nodes = getNodesInRange(tapOffset!);
     if (nodes.isNotEmpty) {
       assert(nodes.length == 1);
@@ -243,38 +259,30 @@ class _FlowySelectionState extends State<FlowySelection>
   }
 
   void _onPanStart(DragStartDetails details) {
-    debugPrint('on pan start');
-
-    panStartOffset = details.globalPosition;
+    // clear old state.
     panEndOffset = null;
     tapOffset = null;
+    clearSelection();
+
+    panStartOffset = details.globalPosition;
   }
 
   void _onPanUpdate(DragUpdateDetails details) {
-    // debugPrint('on pan update');
-
     panEndOffset = details.globalPosition;
-    tapOffset = null;
 
     final nodes = getNodesInRange(panStartOffset!, panEndOffset!);
     final first = nodes.first.selectable;
     final last = nodes.last.selectable;
+
+    // compute the selection in range.
     if (first != null && last != null) {
-      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,
-        );
-      }
+      bool isDownward = panStartOffset!.dy <= panEndOffset!.dy;
+      final start =
+          first.getSelectionInRange(panStartOffset!, panEndOffset!).start;
+      final end = last.getSelectionInRange(panStartOffset!, panEndOffset!).end;
+      final selection = Selection(
+          start: isDownward ? start : end, end: isDownward ? end : start);
+      debugPrint('[_onPanUpdate] $selection');
       updateSelection(selection);
     }
   }
@@ -313,51 +321,32 @@ class _FlowySelectionState extends State<FlowySelection>
         continue;
       }
 
-      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) {
-            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) {
-            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),
-              );
-            }
+      var newSelection = selection.copy();
+      // In the case of multiple selections,
+      //  we need to return a new selection for each selected node individually.
+      if (!selection.isSingle) {
+        // <> means selected.
+        // text: abcd<ef
+        // text: ghijkl
+        // text: mn>opqr
+        if (index == 0) {
+          if (selection.isDownward) {
+            newSelection = selection.copyWith(end: selectable.end());
           } else {
-            final position = Position(path: node.path);
-            newSelection = Selection(
-              start: position.copyWith(offset: 0),
-              end: position.copyWith(offset: node.toRawString().length),
-            );
+            newSelection = selection.copyWith(start: selectable.start());
           }
+        } else if (index == nodes.length - 1) {
+          if (selection.isDownward) {
+            newSelection = selection.copyWith(start: selectable.start());
+          } else {
+            newSelection = selection.copyWith(end: selectable.end());
+          }
+        } else {
+          newSelection = selection.copyWith(
+            start: selectable.start(),
+            end: selectable.end(),
+          );
         }
-      } else {
-        newSelection = Selection.collapsed(
-          Position(path: node.path),
-        );
       }
 
       final rects = selectable.getRectsInSelection(newSelection);