mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2024-08-30 18:12:39 +00:00
feat: support floating selection and delete textnode
This commit is contained in:
parent
f58a6c9523
commit
e1d990e4ae
@ -74,7 +74,7 @@
|
|||||||
"type": "text",
|
"type": "text",
|
||||||
"delta": [
|
"delta": [
|
||||||
{
|
{
|
||||||
"insert": "Click the '?' at the bottom right for help and support."
|
"insert": "1. Click the '?' at the bottom right for help and support."
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"attributes": {}
|
"attributes": {}
|
||||||
@ -83,7 +83,7 @@
|
|||||||
"type": "text",
|
"type": "text",
|
||||||
"delta": [
|
"delta": [
|
||||||
{
|
{
|
||||||
"insert": "Click the '?' at the bottom right for help and support."
|
"insert": "2. Click the '?' at the bottom right for help and support."
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"attributes": {}
|
"attributes": {}
|
||||||
@ -92,7 +92,7 @@
|
|||||||
"type": "text",
|
"type": "text",
|
||||||
"delta": [
|
"delta": [
|
||||||
{
|
{
|
||||||
"insert": "Click the '?' at the bottom right for help and support."
|
"insert": "3. Click the '?' at the bottom right for help and support."
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"attributes": {}
|
"attributes": {}
|
||||||
@ -101,7 +101,7 @@
|
|||||||
"type": "text",
|
"type": "text",
|
||||||
"delta": [
|
"delta": [
|
||||||
{
|
{
|
||||||
"insert": "Click the '?' at the bottom right for help and support.Click the '?' at the bottom right for help and support.Click the '?' at the bottom right for help and support.Click the '?' at the bottom right for help and support.Click the '?' at the bottom right for help and support.Click the '?' at the bottom right for help and support.Click the '?' at the bottom right for help and support."
|
"insert": "4. Click the '?' at the bottom right for help and support.Click the '?' at the bottom right for help and support.Click the '?' at the bottom right for help and support.Click the '?' at the bottom right for help and support.Click the '?' at the bottom right for help and support.Click the '?' at the bottom right for help and support.Click the '?' at the bottom right for help and support."
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"attributes": {}
|
"attributes": {}
|
||||||
@ -110,7 +110,7 @@
|
|||||||
"type": "text",
|
"type": "text",
|
||||||
"delta": [
|
"delta": [
|
||||||
{
|
{
|
||||||
"insert": "Click the '?' at the bottom right for help and support."
|
"insert": "5. Click the '?' at the bottom right for help and support."
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"attributes": {}
|
"attributes": {}
|
||||||
@ -119,7 +119,7 @@
|
|||||||
"type": "text",
|
"type": "text",
|
||||||
"delta": [
|
"delta": [
|
||||||
{
|
{
|
||||||
"insert": "Click the '?' at the bottom right for help and support."
|
"insert": "6. Click the '?' at the bottom right for help and support."
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"attributes": {}
|
"attributes": {}
|
||||||
@ -128,7 +128,7 @@
|
|||||||
"type": "text",
|
"type": "text",
|
||||||
"delta": [
|
"delta": [
|
||||||
{
|
{
|
||||||
"insert": "Click the '?' at the bottom right for help and support."
|
"insert": "7. Click the '?' at the bottom right for help and support."
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"attributes": {}
|
"attributes": {}
|
||||||
@ -137,7 +137,7 @@
|
|||||||
"type": "text",
|
"type": "text",
|
||||||
"delta": [
|
"delta": [
|
||||||
{
|
{
|
||||||
"insert": "Click the '?' at the bottom right for help and support."
|
"insert": "8. Click the '?' at the bottom right for help and support."
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"attributes": {}
|
"attributes": {}
|
||||||
@ -146,7 +146,7 @@
|
|||||||
"type": "text",
|
"type": "text",
|
||||||
"delta": [
|
"delta": [
|
||||||
{
|
{
|
||||||
"insert": "Click the '?' at the bottom right for help and support."
|
"insert": "9. Click the '?' at the bottom right for help and support."
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"attributes": {}
|
"attributes": {}
|
||||||
@ -155,7 +155,7 @@
|
|||||||
"type": "text",
|
"type": "text",
|
||||||
"delta": [
|
"delta": [
|
||||||
{
|
{
|
||||||
"insert": "Click the '?' at the bottom right for help and support."
|
"insert": "10. Click the '?' at the bottom right for help and support."
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"attributes": {}
|
"attributes": {}
|
||||||
@ -164,7 +164,7 @@
|
|||||||
"type": "text",
|
"type": "text",
|
||||||
"delta": [
|
"delta": [
|
||||||
{
|
{
|
||||||
"insert": "Click the '?' at the bottom right for help and support."
|
"insert": "11. Click the '?' at the bottom right for help and support."
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"attributes": {}
|
"attributes": {}
|
||||||
|
@ -96,9 +96,7 @@ class _MyHomePageState extends State<MyHomePage> {
|
|||||||
);
|
);
|
||||||
return FlowyEditor(
|
return FlowyEditor(
|
||||||
editorState: _editorState,
|
editorState: _editorState,
|
||||||
keyEventHandler: [
|
keyEventHandler: const [],
|
||||||
deleteSingleImageNode,
|
|
||||||
],
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -1,17 +1,6 @@
|
|||||||
import 'package:flowy_editor/flowy_editor.dart';
|
import 'package:flowy_editor/flowy_editor.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
FlowyKeyEventHandler deleteSingleImageNode = (editorState, event) {
|
|
||||||
final selectNodes = editorState.selectedNodes;
|
|
||||||
if (selectNodes.length != 1 || selectNodes.first.type != 'image') {
|
|
||||||
return KeyEventResult.ignored;
|
|
||||||
}
|
|
||||||
TransactionBuilder(editorState)
|
|
||||||
..deleteNode(selectNodes.first)
|
|
||||||
..commit();
|
|
||||||
return KeyEventResult.handled;
|
|
||||||
};
|
|
||||||
|
|
||||||
class ImageNodeBuilder extends NodeWidgetBuilder {
|
class ImageNodeBuilder extends NodeWidgetBuilder {
|
||||||
ImageNodeBuilder.create({
|
ImageNodeBuilder.create({
|
||||||
required super.node,
|
required super.node,
|
||||||
@ -67,6 +56,11 @@ class __ImageNodeWidgetState extends State<_ImageNodeWidget> with Selectable {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Offset getOffsetByTextSelection(TextSelection textSelection) {
|
||||||
|
return Offset.zero;
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return _build(context);
|
return _build(context);
|
||||||
|
@ -93,6 +93,7 @@ class _SelectedTextNodeWidgetState extends State<_SelectedTextNodeWidget>
|
|||||||
final selectionBaseOffset = _getTextPositionAtOffset(localStart).offset;
|
final selectionBaseOffset = _getTextPositionAtOffset(localStart).offset;
|
||||||
final textSelection = TextSelection.collapsed(offset: selectionBaseOffset);
|
final textSelection = TextSelection.collapsed(offset: selectionBaseOffset);
|
||||||
_textSelection = textSelection;
|
_textSelection = textSelection;
|
||||||
|
print('text selection = $textSelection');
|
||||||
return _computeCursorRect(textSelection.baseOffset);
|
return _computeCursorRect(textSelection.baseOffset);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -101,6 +102,12 @@ class _SelectedTextNodeWidgetState extends State<_SelectedTextNodeWidget>
|
|||||||
return _textSelection;
|
return _textSelection;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Offset getOffsetByTextSelection(TextSelection textSelection) {
|
||||||
|
final offset = _computeCursorRect(textSelection.baseOffset).center;
|
||||||
|
return _renderParagraph.localToGlobal(offset);
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
Widget richText;
|
Widget richText;
|
||||||
@ -148,6 +155,7 @@ class _SelectedTextNodeWidgetState extends State<_SelectedTextNodeWidget>
|
|||||||
final cursorOffset =
|
final cursorOffset =
|
||||||
_renderParagraph.getOffsetForCaret(position, Rect.zero);
|
_renderParagraph.getOffsetForCaret(position, Rect.zero);
|
||||||
final cursorHeight = _renderParagraph.getFullHeightForCaret(position);
|
final cursorHeight = _renderParagraph.getFullHeightForCaret(position);
|
||||||
|
print('offset = $offset, cursorHeight = $cursorHeight');
|
||||||
if (cursorHeight != null) {
|
if (cursorHeight != null) {
|
||||||
const cursorWidth = 2;
|
const cursorWidth = 2;
|
||||||
return Rect.fromLTWH(
|
return Rect.fromLTWH(
|
||||||
|
@ -19,6 +19,9 @@ class ApplyOptions {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO
|
||||||
|
final selectionServiceKey = GlobalKey();
|
||||||
|
|
||||||
class EditorState {
|
class EditorState {
|
||||||
final StateTree document;
|
final StateTree document;
|
||||||
final RenderPlugins renderPlugins;
|
final RenderPlugins renderPlugins;
|
||||||
|
@ -0,0 +1,8 @@
|
|||||||
|
extension FlowyObjectExtensions on Object {
|
||||||
|
T? unwrapOrNull<T>() {
|
||||||
|
if (this is T) {
|
||||||
|
return this as T;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
@ -11,5 +11,4 @@ export 'package:flowy_editor/operation/transaction.dart';
|
|||||||
export 'package:flowy_editor/operation/transaction_builder.dart';
|
export 'package:flowy_editor/operation/transaction_builder.dart';
|
||||||
export 'package:flowy_editor/operation/operation.dart';
|
export 'package:flowy_editor/operation/operation.dart';
|
||||||
export 'package:flowy_editor/editor_state.dart';
|
export 'package:flowy_editor/editor_state.dart';
|
||||||
export 'package:flowy_editor/service/flowy_editor_service.dart';
|
export 'package:flowy_editor/service/editor_service.dart';
|
||||||
export 'package:flowy_editor/service/flowy_keyboard_service.dart';
|
|
||||||
|
@ -13,4 +13,7 @@ mixin Selectable<T extends StatefulWidget> on State<T> {
|
|||||||
|
|
||||||
/// For [TextNode] only.
|
/// For [TextNode] only.
|
||||||
TextSelection? getTextSelection();
|
TextSelection? getTextSelection();
|
||||||
|
|
||||||
|
/// For [TextNode] only.
|
||||||
|
Offset getOffsetByTextSelection(TextSelection textSelection);
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
import 'package:flowy_editor/service/flowy_keyboard_service.dart';
|
import 'package:flowy_editor/service/flowy_key_event_handlers/delete_nodes_handler.dart';
|
||||||
import 'package:flowy_editor/service/flowy_selection_service.dart';
|
import 'package:flowy_editor/service/flowy_key_event_handlers/delete_single_text_node_handler.dart';
|
||||||
|
import 'package:flowy_editor/service/keyboard_service.dart';
|
||||||
|
import 'package:flowy_editor/service/selection_service.dart';
|
||||||
|
|
||||||
import '../editor_state.dart';
|
import '../editor_state.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
@ -23,11 +25,13 @@ class _FlowyEditorState extends State<FlowyEditor> {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return FlowySelectionService(
|
return FlowySelection(
|
||||||
|
key: selectionServiceKey,
|
||||||
editorState: editorState,
|
editorState: editorState,
|
||||||
child: FlowyKeyboardWidget(
|
child: FlowyKeyboard(
|
||||||
handlers: [
|
handlers: [
|
||||||
flowyDeleteNodesHandler,
|
flowyDeleteNodesHandler,
|
||||||
|
deleteSingleTextNodeHandler,
|
||||||
...widget.keyEventHandler,
|
...widget.keyEventHandler,
|
||||||
],
|
],
|
||||||
editorState: editorState,
|
editorState: editorState,
|
@ -0,0 +1,21 @@
|
|||||||
|
import 'package:flowy_editor/flowy_editor.dart';
|
||||||
|
import 'package:flowy_editor/service/keyboard_service.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
FlowyKeyEventHandler flowyDeleteNodesHandler = (editorState, event) {
|
||||||
|
// Handle delete nodes.
|
||||||
|
final nodes = editorState.selectedNodes;
|
||||||
|
if (nodes.length <= 1) {
|
||||||
|
return KeyEventResult.ignored;
|
||||||
|
}
|
||||||
|
|
||||||
|
debugPrint('delete nodes = $nodes');
|
||||||
|
|
||||||
|
nodes
|
||||||
|
.fold<TransactionBuilder>(
|
||||||
|
TransactionBuilder(editorState),
|
||||||
|
(previousValue, node) => previousValue..deleteNode(node),
|
||||||
|
)
|
||||||
|
.commit();
|
||||||
|
return KeyEventResult.handled;
|
||||||
|
};
|
@ -0,0 +1,73 @@
|
|||||||
|
import 'package:flowy_editor/document/node.dart';
|
||||||
|
import 'package:flowy_editor/editor_state.dart';
|
||||||
|
import 'package:flowy_editor/operation/transaction_builder.dart';
|
||||||
|
import 'package:flowy_editor/render/selection/selectable.dart';
|
||||||
|
import 'package:flowy_editor/service/keyboard_service.dart';
|
||||||
|
import 'package:flowy_editor/extensions/object_extensions.dart';
|
||||||
|
import 'package:flowy_editor/service/selection_service.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter/services.dart';
|
||||||
|
|
||||||
|
// TODO: need to be refactored, just a example code.
|
||||||
|
FlowyKeyEventHandler deleteSingleTextNodeHandler = (editorState, event) {
|
||||||
|
if (event.logicalKey != LogicalKeyboardKey.backspace) {
|
||||||
|
return KeyEventResult.ignored;
|
||||||
|
}
|
||||||
|
|
||||||
|
final selectionNodes = editorState.selectedNodes;
|
||||||
|
if (selectionNodes.length == 1 && selectionNodes.first is TextNode) {
|
||||||
|
final node = selectionNodes.first.unwrapOrNull<TextNode>();
|
||||||
|
final selectable = node?.key?.currentState?.unwrapOrNull<Selectable>();
|
||||||
|
if (selectable != null) {
|
||||||
|
final textSelection = selectable.getTextSelection();
|
||||||
|
if (textSelection != null) {
|
||||||
|
if (textSelection.isCollapsed) {
|
||||||
|
/// Three cases:
|
||||||
|
/// Delete the zero character,
|
||||||
|
/// 1. if there is still text node in front of it, then merge them.
|
||||||
|
/// 2. if not, just ignore
|
||||||
|
/// Delete the non-zero character,
|
||||||
|
/// 3. delete the single character.
|
||||||
|
if (textSelection.baseOffset == 0) {
|
||||||
|
if (node?.previous != null && node?.previous is TextNode) {
|
||||||
|
final previous = node!.previous! as TextNode;
|
||||||
|
final newTextSelection = TextSelection.collapsed(
|
||||||
|
offset: previous.toRawString().length);
|
||||||
|
final selectionService =
|
||||||
|
selectionServiceKey.currentState as FlowySelectionService;
|
||||||
|
final previousSelectable =
|
||||||
|
previous.key?.currentState?.unwrapOrNull<Selectable>();
|
||||||
|
final newOfset = previousSelectable
|
||||||
|
?.getOffsetByTextSelection(newTextSelection);
|
||||||
|
if (newOfset != null) {
|
||||||
|
selectionService.updateCursor(newOfset);
|
||||||
|
}
|
||||||
|
// merge
|
||||||
|
TransactionBuilder(editorState)
|
||||||
|
..deleteNode(node)
|
||||||
|
..insertText(
|
||||||
|
previous, previous.toRawString().length, node.toRawString())
|
||||||
|
..commit();
|
||||||
|
return KeyEventResult.handled;
|
||||||
|
} else {
|
||||||
|
return KeyEventResult.ignored;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
TransactionBuilder(editorState)
|
||||||
|
..deleteText(node!, textSelection.baseOffset - 1, 1)
|
||||||
|
..commit();
|
||||||
|
final newTextSelection =
|
||||||
|
TextSelection.collapsed(offset: textSelection.baseOffset - 1);
|
||||||
|
final selectionService =
|
||||||
|
selectionServiceKey.currentState as FlowySelectionService;
|
||||||
|
final newOfset =
|
||||||
|
selectable.getOffsetByTextSelection(newTextSelection);
|
||||||
|
selectionService.updateCursor(newOfset);
|
||||||
|
return KeyEventResult.handled;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return KeyEventResult.ignored;
|
||||||
|
};
|
@ -1,4 +1,3 @@
|
|||||||
import 'package:flowy_editor/operation/transaction_builder.dart';
|
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
|
|
||||||
import '../editor_state.dart';
|
import '../editor_state.dart';
|
||||||
@ -9,27 +8,9 @@ typedef FlowyKeyEventHandler = KeyEventResult Function(
|
|||||||
RawKeyEvent event,
|
RawKeyEvent event,
|
||||||
);
|
);
|
||||||
|
|
||||||
FlowyKeyEventHandler flowyDeleteNodesHandler = (editorState, event) {
|
|
||||||
// Handle delete nodes.
|
|
||||||
final nodes = editorState.selectedNodes;
|
|
||||||
if (nodes.length <= 1) {
|
|
||||||
return KeyEventResult.ignored;
|
|
||||||
}
|
|
||||||
|
|
||||||
debugPrint('delete nodes = $nodes');
|
|
||||||
|
|
||||||
nodes
|
|
||||||
.fold<TransactionBuilder>(
|
|
||||||
TransactionBuilder(editorState),
|
|
||||||
(previousValue, node) => previousValue..deleteNode(node),
|
|
||||||
)
|
|
||||||
.commit();
|
|
||||||
return KeyEventResult.handled;
|
|
||||||
};
|
|
||||||
|
|
||||||
/// Process keyboard events
|
/// Process keyboard events
|
||||||
class FlowyKeyboardWidget extends StatefulWidget {
|
class FlowyKeyboard extends StatefulWidget {
|
||||||
const FlowyKeyboardWidget({
|
const FlowyKeyboard({
|
||||||
Key? key,
|
Key? key,
|
||||||
required this.handlers,
|
required this.handlers,
|
||||||
required this.editorState,
|
required this.editorState,
|
||||||
@ -41,10 +22,10 @@ class FlowyKeyboardWidget extends StatefulWidget {
|
|||||||
final List<FlowyKeyEventHandler> handlers;
|
final List<FlowyKeyEventHandler> handlers;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<FlowyKeyboardWidget> createState() => _FlowyKeyboardWidgetState();
|
State<FlowyKeyboard> createState() => _FlowyKeyboardState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _FlowyKeyboardWidgetState extends State<FlowyKeyboardWidget> {
|
class _FlowyKeyboardState extends State<FlowyKeyboard> {
|
||||||
final FocusNode focusNode = FocusNode(debugLabel: 'flowy_keyboard_service');
|
final FocusNode focusNode = FocusNode(debugLabel: 'flowy_keyboard_service');
|
||||||
|
|
||||||
@override
|
@override
|
@ -8,7 +8,7 @@ import '../document/node.dart';
|
|||||||
import '../render/selection/selectable.dart';
|
import '../render/selection/selectable.dart';
|
||||||
|
|
||||||
/// Process selection and cursor
|
/// Process selection and cursor
|
||||||
mixin _FlowySelectionService<T extends StatefulWidget> on State<T> {
|
mixin FlowySelectionService<T extends StatefulWidget> on State<T> {
|
||||||
/// [Pan] and [Tap] must be mutually exclusive.
|
/// [Pan] and [Tap] must be mutually exclusive.
|
||||||
/// Pan
|
/// Pan
|
||||||
Offset? panStartOffset;
|
Offset? panStartOffset;
|
||||||
@ -19,20 +19,20 @@ mixin _FlowySelectionService<T extends StatefulWidget> on State<T> {
|
|||||||
|
|
||||||
void updateSelection(Offset start, Offset end);
|
void updateSelection(Offset start, Offset end);
|
||||||
|
|
||||||
void updateCursor(Offset offset);
|
void updateCursor(Offset start);
|
||||||
|
|
||||||
/// Returns selected node(s)
|
/// Returns selected node(s)
|
||||||
/// Returns empty list if no nodes are being selected.
|
/// Returns empty list if no nodes are being selected.
|
||||||
List<Node> get selectedNodes;
|
List<Node> getSelectedNodes(Offset start, [Offset? end]);
|
||||||
|
|
||||||
/// Compute selected node triggered by [Tap]
|
/// Compute selected node triggered by [Tap]
|
||||||
Node? computeSelectedNodeByTap(
|
Node? computeSelectedNodeInOffset(
|
||||||
Node node,
|
Node node,
|
||||||
Offset offset,
|
Offset offset,
|
||||||
);
|
);
|
||||||
|
|
||||||
/// Compute selected nodes triggered by [Pan]
|
/// Compute selected nodes triggered by [Pan]
|
||||||
List<Node> computeSelectedNodesByPan(
|
List<Node> computeSelectedNodesInRange(
|
||||||
Node node,
|
Node node,
|
||||||
Offset start,
|
Offset start,
|
||||||
Offset end,
|
Offset end,
|
||||||
@ -52,8 +52,8 @@ mixin _FlowySelectionService<T extends StatefulWidget> on State<T> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
class FlowySelectionService extends StatefulWidget {
|
class FlowySelection extends StatefulWidget {
|
||||||
const FlowySelectionService({
|
const FlowySelection({
|
||||||
Key? key,
|
Key? key,
|
||||||
required this.editorState,
|
required this.editorState,
|
||||||
required this.child,
|
required this.child,
|
||||||
@ -63,11 +63,11 @@ class FlowySelectionService extends StatefulWidget {
|
|||||||
final Widget child;
|
final Widget child;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<FlowySelectionService> createState() => _FlowySelectionServiceState();
|
State<FlowySelection> createState() => _FlowySelectionState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _FlowySelectionServiceState extends State<FlowySelectionService>
|
class _FlowySelectionState extends State<FlowySelection>
|
||||||
with _FlowySelectionService {
|
with FlowySelectionService {
|
||||||
final _cursorKey = GlobalKey(debugLabel: 'cursor');
|
final _cursorKey = GlobalKey(debugLabel: 'cursor');
|
||||||
|
|
||||||
final List<OverlayEntry> _selectionOverlays = [];
|
final List<OverlayEntry> _selectionOverlays = [];
|
||||||
@ -106,7 +106,7 @@ class _FlowySelectionServiceState extends State<FlowySelectionService>
|
|||||||
void updateSelection(Offset start, Offset end) {
|
void updateSelection(Offset start, Offset end) {
|
||||||
_clearAllOverlayEntries();
|
_clearAllOverlayEntries();
|
||||||
|
|
||||||
final nodes = selectedNodes;
|
final nodes = getSelectedNodes(start, end);
|
||||||
editorState.selectedNodes = nodes;
|
editorState.selectedNodes = nodes;
|
||||||
if (nodes.isEmpty) {
|
if (nodes.isEmpty) {
|
||||||
return;
|
return;
|
||||||
@ -133,10 +133,10 @@ class _FlowySelectionServiceState extends State<FlowySelectionService>
|
|||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void updateCursor(Offset offset) {
|
void updateCursor(Offset start) {
|
||||||
_clearAllOverlayEntries();
|
_clearAllOverlayEntries();
|
||||||
|
|
||||||
final nodes = selectedNodes;
|
final nodes = getSelectedNodes(start);
|
||||||
editorState.selectedNodes = nodes;
|
editorState.selectedNodes = nodes;
|
||||||
if (nodes.isEmpty) {
|
if (nodes.isEmpty) {
|
||||||
return;
|
return;
|
||||||
@ -147,7 +147,7 @@ class _FlowySelectionServiceState extends State<FlowySelectionService>
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
final selectable = selectedNode.key?.currentState as Selectable;
|
final selectable = selectedNode.key?.currentState as Selectable;
|
||||||
final rect = selectable.getCursorRect(offset);
|
final rect = selectable.getCursorRect(start);
|
||||||
final cursor = OverlayEntry(
|
final cursor = OverlayEntry(
|
||||||
builder: ((context) => FlowyCursorWidget(
|
builder: ((context) => FlowyCursorWidget(
|
||||||
key: _cursorKey,
|
key: _cursorKey,
|
||||||
@ -161,13 +161,18 @@ class _FlowySelectionServiceState extends State<FlowySelectionService>
|
|||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
List<Node> get selectedNodes {
|
List<Node> getSelectedNodes(Offset start, [Offset? end]) {
|
||||||
if (panStartOffset != null && panEndOffset != null) {
|
if (end != null) {
|
||||||
return computeSelectedNodesByPan(
|
return computeSelectedNodesInRange(
|
||||||
editorState.document.root, panStartOffset!, panEndOffset!);
|
editorState.document.root,
|
||||||
} else if (tapOffset != null) {
|
start,
|
||||||
final reuslt =
|
end,
|
||||||
computeSelectedNodeByTap(editorState.document.root, tapOffset!);
|
);
|
||||||
|
} else {
|
||||||
|
final reuslt = computeSelectedNodeInOffset(
|
||||||
|
editorState.document.root,
|
||||||
|
start,
|
||||||
|
);
|
||||||
if (reuslt != null) {
|
if (reuslt != null) {
|
||||||
return [reuslt];
|
return [reuslt];
|
||||||
}
|
}
|
||||||
@ -176,13 +181,9 @@ class _FlowySelectionServiceState extends State<FlowySelectionService>
|
|||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Node? computeSelectedNodeByTap(Node node, Offset offset) {
|
Node? computeSelectedNodeInOffset(Node node, Offset offset) {
|
||||||
assert(this.tapOffset != null);
|
|
||||||
final tapOffset = this.tapOffset;
|
|
||||||
if (tapOffset != null) {}
|
|
||||||
|
|
||||||
for (final child in node.children) {
|
for (final child in node.children) {
|
||||||
final result = computeSelectedNodeByTap(child, offset);
|
final result = computeSelectedNodeInOffset(child, offset);
|
||||||
if (result != null) {
|
if (result != null) {
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
@ -198,7 +199,7 @@ class _FlowySelectionServiceState extends State<FlowySelectionService>
|
|||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
List<Node> computeSelectedNodesByPan(Node node, Offset start, Offset end) {
|
List<Node> computeSelectedNodesInRange(Node node, Offset start, Offset end) {
|
||||||
List<Node> result = [];
|
List<Node> result = [];
|
||||||
if (node.parent != null && node.key != null) {
|
if (node.parent != null && node.key != null) {
|
||||||
if (isNodeInSelection(node, start, end)) {
|
if (isNodeInSelection(node, start, end)) {
|
||||||
@ -206,7 +207,7 @@ class _FlowySelectionServiceState extends State<FlowySelectionService>
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
for (final child in node.children) {
|
for (final child in node.children) {
|
||||||
result.addAll(computeSelectedNodesByPan(child, start, end));
|
result.addAll(computeSelectedNodesInRange(child, start, end));
|
||||||
}
|
}
|
||||||
// TODO: sort the result
|
// TODO: sort the result
|
||||||
return result;
|
return result;
|
Loading…
x
Reference in New Issue
Block a user