diff --git a/frontend/app_flowy/packages/flowy_editor/example/lib/main.dart b/frontend/app_flowy/packages/flowy_editor/example/lib/main.dart index 9cc4d8c536..112c1dcd4f 100644 --- a/frontend/app_flowy/packages/flowy_editor/example/lib/main.dart +++ b/frontend/app_flowy/packages/flowy_editor/example/lib/main.dart @@ -96,7 +96,7 @@ class _MyHomePageState extends State { ); return FlowyEditor( editorState: _editorState, - keyEventHandler: const [], + keyEventHandlers: const [], shortcuts: [ // TODO: this won't work, just a example for now. { diff --git a/frontend/app_flowy/packages/flowy_editor/lib/extensions/node_extensions.dart b/frontend/app_flowy/packages/flowy_editor/lib/extensions/node_extensions.dart new file mode 100644 index 0000000000..35cc18cdd2 --- /dev/null +++ b/frontend/app_flowy/packages/flowy_editor/lib/extensions/node_extensions.dart @@ -0,0 +1,11 @@ +import 'package:flowy_editor/document/node.dart'; +import 'package:flowy_editor/extensions/object_extensions.dart'; +import 'package:flowy_editor/render/selection/selectable.dart'; +import 'package:flutter/material.dart'; + +extension NodeExtensions on Node { + RenderBox? get renderBox => + key?.currentContext?.findRenderObject()?.unwrapOrNull(); + + Selectable? get selectable => key?.currentState?.unwrapOrNull(); +} 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 7cd4eaf708..8b40981ccb 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,25 +1,27 @@ import 'package:flowy_editor/render/selection/floating_shortcut_widget.dart'; -import 'package:flowy_editor/service/floating_shortcut_service.dart'; -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/flowy_key_event_handlers/shortcut_handler.dart'; +import 'package:flowy_editor/service/shortcut_service.dart'; +import 'package:flowy_editor/service/internal_key_event_handlers/arrow_keys_handler.dart'; +import 'package:flowy_editor/service/internal_key_event_handlers/delete_nodes_handler.dart'; +import 'package:flowy_editor/service/internal_key_event_handlers/delete_single_text_node_handler.dart'; +import 'package:flowy_editor/service/internal_key_event_handlers/shortcut_handler.dart'; import 'package:flowy_editor/service/keyboard_service.dart'; import 'package:flowy_editor/service/selection_service.dart'; +import 'package:flowy_editor/editor_state.dart'; -import '../editor_state.dart'; import 'package:flutter/material.dart'; class FlowyEditor extends StatefulWidget { const FlowyEditor({ Key? key, required this.editorState, - required this.keyEventHandler, + required this.keyEventHandlers, required this.shortcuts, }) : super(key: key); final EditorState editorState; - final List keyEventHandler; + final List keyEventHandlers; + + /// Shortcusts final FloatingShortcuts shortcuts; @override @@ -41,7 +43,7 @@ class _FlowyEditorState extends State { flowyDeleteNodesHandler, deleteSingleTextNodeHandler, arrowKeysHandler, - ...widget.keyEventHandler, + ...widget.keyEventHandlers, ], editorState: editorState, child: FloatingShortcut( 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/internal_key_event_handlers/arrow_keys_handler.dart similarity index 100% rename from frontend/app_flowy/packages/flowy_editor/lib/service/flowy_key_event_handlers/arrow_keys_handler.dart rename to frontend/app_flowy/packages/flowy_editor/lib/service/internal_key_event_handlers/arrow_keys_handler.dart diff --git a/frontend/app_flowy/packages/flowy_editor/lib/service/flowy_key_event_handlers/delete_nodes_handler.dart b/frontend/app_flowy/packages/flowy_editor/lib/service/internal_key_event_handlers/delete_nodes_handler.dart similarity index 100% rename from frontend/app_flowy/packages/flowy_editor/lib/service/flowy_key_event_handlers/delete_nodes_handler.dart rename to frontend/app_flowy/packages/flowy_editor/lib/service/internal_key_event_handlers/delete_nodes_handler.dart 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/internal_key_event_handlers/delete_single_text_node_handler.dart similarity index 100% rename from frontend/app_flowy/packages/flowy_editor/lib/service/flowy_key_event_handlers/delete_single_text_node_handler.dart rename to frontend/app_flowy/packages/flowy_editor/lib/service/internal_key_event_handlers/delete_single_text_node_handler.dart diff --git a/frontend/app_flowy/packages/flowy_editor/lib/service/flowy_key_event_handlers/shortcut_handler.dart b/frontend/app_flowy/packages/flowy_editor/lib/service/internal_key_event_handlers/shortcut_handler.dart similarity index 100% rename from frontend/app_flowy/packages/flowy_editor/lib/service/flowy_key_event_handlers/shortcut_handler.dart rename to frontend/app_flowy/packages/flowy_editor/lib/service/internal_key_event_handlers/shortcut_handler.dart diff --git a/frontend/app_flowy/packages/flowy_editor/lib/service/selection_service.dart b/frontend/app_flowy/packages/flowy_editor/lib/service/selection_service.dart index 2f4bba86ec..fa71536ecc 100644 --- a/frontend/app_flowy/packages/flowy_editor/lib/service/selection_service.dart +++ b/frontend/app_flowy/packages/flowy_editor/lib/service/selection_service.dart @@ -1,7 +1,8 @@ import 'package:flowy_editor/render/selection/cursor_widget.dart'; import 'package:flowy_editor/render/selection/flowy_selection_widget.dart'; import 'package:flowy_editor/extensions/object_extensions.dart'; -import 'package:flowy_editor/service/floating_shortcut_service.dart'; +import 'package:flowy_editor/extensions/node_extensions.dart'; +import 'package:flowy_editor/service/shortcut_service.dart'; import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; @@ -11,58 +12,65 @@ import '../render/selection/selectable.dart'; /// Process selection and cursor mixin FlowySelectionService on State { - /// [Pan] and [Tap] must be mutually exclusive. - /// Pan - Offset? panStartOffset; - Offset? panEndOffset; - - /// Tap - Offset? tapOffset; - + /// [start] and [end] are the offsets under the global coordinate system. void updateSelection(Offset start, Offset end); + /// [start] is the offset under the global coordinate system. void updateCursor(Offset start); - /// Returns selected node(s) - /// Returns empty list if no nodes are being selected. + /// Returns selected [Node]s. Empty list would be returned + /// if no nodes are being selected. + /// + /// + /// [start] and [end] are the offsets under the global coordinate system. + /// + /// If end is not null, it means multiple selection, + /// otherwise single selection. List getSelectedNodes(Offset start, [Offset? end]); - /// Compute selected node triggered by [Tap] - Node? computeSelectedNodeInOffset( - Node node, - Offset offset, - ); + /// Return the [Node] or [Null] in single selection. + /// + /// [start] is the offset under the global coordinate system. + Node? computeSelectedNodeInOffset(Node node, Offset offset); - /// Compute selected nodes triggered by [Pan] + /// Return the [Node]s in multiple selection. Emtpy list would be returned + /// if no nodes are in range. + /// + /// [start] is the offset under the global coordinate system. List computeSelectedNodesInRange( Node node, Offset start, Offset end, ); - /// Pan + /// Return [bool] to identify the [Node] is in Range or not. + /// + /// [start] and [end] are the offsets under the global coordinate system. bool isNodeInSelection( Node node, Offset start, Offset end, ); - /// Tap - bool isNodeInOffset( - Node node, - Offset offset, - ); + /// Return [bool] to identify the [Node] contains [Offset] or not. + /// + /// [start] is the offset under the global coordinate system. + bool isNodeInOffset(Node node, Offset offset); } class FlowySelection extends StatefulWidget { const FlowySelection({ Key? key, + this.cursorColor = Colors.black, + this.selectionColor = const Color.fromARGB(60, 61, 61, 213), required this.editorState, required this.child, }) : super(key: key); final EditorState editorState; final Widget child; + final Color cursorColor; + final Color selectionColor; @override State createState() => _FlowySelectionState(); @@ -75,6 +83,14 @@ class _FlowySelectionState extends State final List _selectionOverlays = []; final List _cursorOverlays = []; + /// [Pan] and [Tap] must be mutually exclusive. + /// Pan + Offset? panStartOffset; + Offset? panEndOffset; + + /// Tap + Offset? tapOffset; + EditorState get editorState => widget.editorState; @override @@ -123,7 +139,7 @@ class _FlowySelectionState extends State for (final rect in selectionRects) { final overlay = OverlayEntry( builder: ((context) => SelectionWidget( - color: Colors.yellow.withAlpha(100), + color: widget.selectionColor, layerLink: node.layerLink, rect: rect, )), @@ -154,7 +170,7 @@ class _FlowySelectionState extends State builder: ((context) => CursorWidget( key: _cursorKey, rect: rect, - color: Colors.red, + color: widget.cursorColor, layerLink: selectedNode.layerLink, )), ); @@ -165,16 +181,10 @@ class _FlowySelectionState extends State @override List getSelectedNodes(Offset start, [Offset? end]) { if (end != null) { - return computeSelectedNodesInRange( - editorState.document.root, - start, - end, - ); + return computeSelectedNodesInRange(editorState.document.root, start, end); } else { - final reuslt = computeSelectedNodeInOffset( - editorState.document.root, - start, - ); + final reuslt = + computeSelectedNodeInOffset(editorState.document.root, start); if (reuslt != null) { return [reuslt]; } @@ -190,13 +200,11 @@ class _FlowySelectionState extends State return result; } } - if (node.parent != null && node.key != null) { if (isNodeInOffset(node, offset)) { return node; } } - return null; } @@ -217,9 +225,7 @@ class _FlowySelectionState extends State @override bool isNodeInOffset(Node node, Offset offset) { - assert(node.key != null); - final renderBox = - node.key?.currentContext?.findRenderObject() as RenderBox?; + final renderBox = node.renderBox; if (renderBox != null) { final boxOffset = renderBox.localToGlobal(Offset.zero); final boxRect = boxOffset & renderBox.size; @@ -230,9 +236,7 @@ class _FlowySelectionState extends State @override bool isNodeInSelection(Node node, Offset start, Offset end) { - assert(node.key != null); - final renderBox = - node.key?.currentContext?.findRenderObject() as RenderBox?; + final renderBox = node.renderBox; if (renderBox != null) { final rect = Rect.fromPoints(start, end); final boxOffset = renderBox.localToGlobal(Offset.zero); diff --git a/frontend/app_flowy/packages/flowy_editor/lib/service/service.dart b/frontend/app_flowy/packages/flowy_editor/lib/service/service.dart index 7833e6d379..f8cf4a9e5c 100644 --- a/frontend/app_flowy/packages/flowy_editor/lib/service/service.dart +++ b/frontend/app_flowy/packages/flowy_editor/lib/service/service.dart @@ -1,4 +1,4 @@ -import 'package:flowy_editor/service/floating_shortcut_service.dart'; +import 'package:flowy_editor/service/shortcut_service.dart'; import 'package:flowy_editor/service/selection_service.dart'; import 'package:flutter/material.dart'; diff --git a/frontend/app_flowy/packages/flowy_editor/lib/service/floating_shortcut_service.dart b/frontend/app_flowy/packages/flowy_editor/lib/service/shortcut_service.dart similarity index 100% rename from frontend/app_flowy/packages/flowy_editor/lib/service/floating_shortcut_service.dart rename to frontend/app_flowy/packages/flowy_editor/lib/service/shortcut_service.dart