docs: documentation for selection_service

This commit is contained in:
Lucas.Xu 2022-08-10 15:30:19 +08:00
parent 046faf3880
commit ae0012ba37
6 changed files with 103 additions and 123 deletions

View File

@ -1,15 +1,16 @@
library flowy_editor; library flowy_editor;
export 'src/document/state_tree.dart';
export 'src/document/node.dart'; export 'src/document/node.dart';
export 'src/document/path.dart'; export 'src/document/path.dart';
export 'src/document/position.dart';
export 'src/document/selection.dart';
export 'src/document/state_tree.dart';
export 'src/document/text_delta.dart'; export 'src/document/text_delta.dart';
export 'src/render/selection/selectable.dart'; export 'src/editor_state.dart';
export 'src/operation/operation.dart';
export 'src/operation/transaction.dart'; export 'src/operation/transaction.dart';
export 'src/operation/transaction_builder.dart'; export 'src/operation/transaction_builder.dart';
export 'src/operation/operation.dart'; export 'src/render/selection/selectable.dart';
export 'src/editor_state.dart';
export 'src/service/editor_service.dart'; export 'src/service/editor_service.dart';
export 'src/document/selection.dart';
export 'src/document/position.dart';
export 'src/service/render_plugin_service.dart'; export 'src/service/render_plugin_service.dart';
export 'src/service/service.dart';

View File

@ -2,15 +2,26 @@ import 'package:flowy_editor/src/document/path.dart';
import 'package:flowy_editor/src/document/position.dart'; import 'package:flowy_editor/src/document/position.dart';
import 'package:flowy_editor/src/extensions/path_extensions.dart'; import 'package:flowy_editor/src/extensions/path_extensions.dart';
/// Selection represents the selected area or the cursor area in the editor.
///
/// [Selection] is directional.
///
/// 1. forwardthe end position is before the start position.
/// 2. backward, the end position is after the start position.
/// 3. collapsed, the end position is equal to the start position.
class Selection { class Selection {
final Position start; /// Create a selection with [start], [end].
final Position end;
Selection({ Selection({
required this.start, required this.start,
required this.end, required this.end,
}); });
/// Create a selection with [Path], [startOffset] and [endOffset].
///
/// The [endOffset] is optional.
///
/// This constructor will return a collapsed [Selection] if [endOffset] is null.
///
Selection.single({ Selection.single({
required Path path, required Path path,
required int startOffset, required int startOffset,
@ -18,10 +29,21 @@ class Selection {
}) : start = Position(path: path, offset: startOffset), }) : start = Position(path: path, offset: startOffset),
end = Position(path: path, offset: endOffset ?? startOffset); end = Position(path: path, offset: endOffset ?? startOffset);
/// Create a collapsed selection with [position].
Selection.collapsed(Position position) Selection.collapsed(Position position)
: start = position, : start = position,
end = position; end = position;
final Position start;
final Position end;
bool get isCollapsed => start == end;
bool get isSingle => pathEquals(start.path, end.path);
bool get isForward =>
start.path >= end.path && !pathEquals(start.path, end.path);
bool get isBackward =>
start.path <= end.path && !pathEquals(start.path, end.path);
Selection collapse({bool atStart = false}) { Selection collapse({bool atStart = false}) {
if (atStart) { if (atStart) {
return Selection(start: start, end: start); return Selection(start: start, end: start);
@ -30,13 +52,6 @@ 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 =>
start.path <= end.path && !pathEquals(start.path, end.path);
Selection copyWith({Position? start, Position? end}) { Selection copyWith({Position? start, Position? end}) {
return Selection( return Selection(
start: start ?? this.start, start: start ?? this.start,
@ -46,13 +61,10 @@ class Selection {
Selection copy() => Selection(start: start, end: end); Selection copy() => Selection(start: start, end: end);
@override
String toString() => '[Selection] start = $start, end = $end';
Map<String, dynamic> toJson() { Map<String, dynamic> toJson() {
return { return {
"start": start.toJson(), 'start': start.toJson(),
"end": end.toJson(), 'end': end.toJson(),
}; };
} }
@ -69,4 +81,7 @@ class Selection {
@override @override
int get hashCode => Object.hash(start, end); int get hashCode => Object.hash(start, end);
@override
String toString() => '[Selection] start = $start, end = $end';
} }

View File

@ -1,3 +1,4 @@
import 'package:flowy_editor/src/service/internal_key_event_handlers/default_key_event_handlers.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flowy_editor/src/editor_state.dart'; import 'package:flowy_editor/src/editor_state.dart';
@ -9,15 +10,6 @@ import 'package:flowy_editor/src/render/rich_text/number_list_text.dart';
import 'package:flowy_editor/src/render/rich_text/quoted_text.dart'; import 'package:flowy_editor/src/render/rich_text/quoted_text.dart';
import 'package:flowy_editor/src/render/rich_text/rich_text.dart'; import 'package:flowy_editor/src/render/rich_text/rich_text.dart';
import 'package:flowy_editor/src/service/input_service.dart'; import 'package:flowy_editor/src/service/input_service.dart';
import 'package:flowy_editor/src/service/internal_key_event_handlers/arrow_keys_handler.dart';
import 'package:flowy_editor/src/service/internal_key_event_handlers/copy_paste_handler.dart';
import 'package:flowy_editor/src/service/internal_key_event_handlers/delete_nodes_handler.dart';
import 'package:flowy_editor/src/service/internal_key_event_handlers/delete_text_handler.dart';
import 'package:flowy_editor/src/service/internal_key_event_handlers/enter_without_shift_in_text_node_handler.dart';
import 'package:flowy_editor/src/service/internal_key_event_handlers/slash_handler.dart';
import 'package:flowy_editor/src/service/internal_key_event_handlers/update_text_style_by_command_x_handler.dart';
import 'package:flowy_editor/src/service/internal_key_event_handlers/whitespace_handler.dart';
import 'package:flowy_editor/src/service/internal_key_event_handlers/redo_undo_handler.dart';
import 'package:flowy_editor/src/service/keyboard_service.dart'; import 'package:flowy_editor/src/service/keyboard_service.dart';
import 'package:flowy_editor/src/service/render_plugin_service.dart'; import 'package:flowy_editor/src/service/render_plugin_service.dart';
import 'package:flowy_editor/src/service/scroll_service.dart'; import 'package:flowy_editor/src/service/scroll_service.dart';
@ -34,18 +26,6 @@ NodeWidgetBuilders defaultBuilders = {
'text/quote': QuotedTextNodeWidgetBuilder(), 'text/quote': QuotedTextNodeWidgetBuilder(),
}; };
List<FlowyKeyEventHandler> defaultKeyEventHandler = [
deleteTextHandler,
slashShortcutHandler,
flowyDeleteNodesHandler,
arrowKeysHandler,
copyPasteKeysHandler,
redoUndoKeysHandler,
enterWithoutShiftInTextNodesHandler,
updateTextStyleByCommandXHandler,
whiteSpaceHandler,
];
class FlowyEditor extends StatefulWidget { class FlowyEditor extends StatefulWidget {
const FlowyEditor({ const FlowyEditor({
Key? key, Key? key,
@ -98,7 +78,7 @@ class _FlowyEditorState extends State<FlowyEditor> {
child: FlowyKeyboard( child: FlowyKeyboard(
key: editorState.service.keyboardServiceKey, key: editorState.service.keyboardServiceKey,
handlers: [ handlers: [
...defaultKeyEventHandler, ...defaultKeyEventHandlers,
...widget.keyEventHandlers, ...widget.keyEventHandlers,
], ],
editorState: editorState, editorState: editorState,

View File

@ -0,0 +1,22 @@
import 'package:flowy_editor/src/service/internal_key_event_handlers/arrow_keys_handler.dart';
import 'package:flowy_editor/src/service/internal_key_event_handlers/copy_paste_handler.dart';
import 'package:flowy_editor/src/service/internal_key_event_handlers/delete_nodes_handler.dart';
import 'package:flowy_editor/src/service/internal_key_event_handlers/delete_text_handler.dart';
import 'package:flowy_editor/src/service/internal_key_event_handlers/enter_without_shift_in_text_node_handler.dart';
import 'package:flowy_editor/src/service/internal_key_event_handlers/redo_undo_handler.dart';
import 'package:flowy_editor/src/service/internal_key_event_handlers/slash_handler.dart';
import 'package:flowy_editor/src/service/internal_key_event_handlers/update_text_style_by_command_x_handler.dart';
import 'package:flowy_editor/src/service/internal_key_event_handlers/whitespace_handler.dart';
import 'package:flowy_editor/src/service/keyboard_service.dart';
List<FlowyKeyEventHandler> defaultKeyEventHandlers = [
deleteTextHandler,
slashShortcutHandler,
flowyDeleteNodesHandler,
arrowKeysHandler,
copyPasteKeysHandler,
redoUndoKeysHandler,
enterWithoutShiftInTextNodesHandler,
updateTextStyleByCommandXHandler,
whiteSpaceHandler,
];

View File

@ -15,67 +15,60 @@ import 'package:flowy_editor/src/render/selection/cursor_widget.dart';
import 'package:flowy_editor/src/render/selection/selectable.dart'; import 'package:flowy_editor/src/render/selection/selectable.dart';
import 'package:flowy_editor/src/render/selection/selection_widget.dart'; import 'package:flowy_editor/src/render/selection/selection_widget.dart';
/// Process selection and cursor /// [FlowySelectionService] is responsible for processing
/// the [Selection] changes and updates.
///
/// Usually, this service can be obtained by the following code.
/// ```dart
/// final selectionService = editorState.service.selectionService;
///
/// /** get current selection value*/
/// final selection = selectionService.currentSelection.value;
///
/// /** get current selected nodes*/
/// final nodes = selectionService.currentSelectedNodes;
/// ```
///
mixin FlowySelectionService<T extends StatefulWidget> on State<T> { mixin FlowySelectionService<T extends StatefulWidget> on State<T> {
/// Returns the current [Selection] /// The current [Selection] in editor.
///
/// The value is null if there is no nodes are selected.
ValueNotifier<Selection?> get currentSelection; ValueNotifier<Selection?> get currentSelection;
/// Returns the current selected [Node]s. /// The current selected [Node]s in editor.
/// ///
/// The order of the return is determined according to the selected order. /// The order of the result is determined according to the [currentSelection].
/// The result are ordered from back to front if the selection is forward.
/// The result are ordered from front to back if the selection is backward.
///
/// For example, Here is an array of selected nodes, [n1, n2, n3].
/// The result will be [n3, n2, n1] if the selection is forward,
/// and [n1, n2, n3] if the selection is backward.
///
/// Returns empty result if there is no nodes are selected.
List<Node> get currentSelectedNodes; List<Node> get currentSelectedNodes;
/// Update the selection or cursor. /// Updates the selection.
/// ///
/// If selection is collapsed, this method will /// The editor will update selection area and popup list area
/// update the position of the cursor. /// if the [selection] is not collapsed,
/// Otherwise, will update the selection. /// otherwise, will update the cursor area.
void updateSelection(Selection selection); void updateSelection(Selection selection);
/// Clear the selection or cursor. /// Clears the selection area, cursor area and the popup list area.
void clearSelection(); void clearSelection();
/// ------------------ Selection ------------------------ /// Returns the [Node]s in [Selection].
List<Rect> rects();
Position? hitTest(Offset? offset);
///
List<Node> getNodesInSelection(Selection selection); List<Node> getNodesInSelection(Selection selection);
/// ------------------ Selection ------------------------ /// Returns the [Node] containing to the offset.
/// ------------------ Offset ------------------------
/// Return the [Node] or [Null] in single selection.
/// ///
/// [offset] is under the global coordinate system. /// [offset] must be under the global coordinate system.
Node? getNodeInOffset(Offset offset); Node? getNodeInOffset(Offset offset);
/// Returns selected [Node]s. Empty list would be returned // TODO: need to be documented.
/// if no nodes are in range. List<Rect> rects();
/// Position? hitTest(Offset? offset);
///
/// [start] and [end] are under the global coordinate system.
///
List<Node> getNodeInRange(Offset start, Offset end);
/// Return [bool] to identify the [Node] is in Range or not.
///
/// [start] and [end] are under the global coordinate system.
bool isNodeInRange(
Node node,
Offset start,
Offset end,
);
/// Return [bool] to identify the [Node] contains [Offset] or not.
///
/// [offset] is under the global coordinate system.
bool isNodeInOffset(Node node, Offset offset);
/// ------------------ Offset ------------------------
} }
class FlowySelection extends StatefulWidget { class FlowySelection extends StatefulWidget {
@ -207,36 +200,6 @@ class _FlowySelectionState extends State<FlowySelection>
return _lowerBoundInDocument(offset); return _lowerBoundInDocument(offset);
} }
@override
List<Node> getNodeInRange(Offset start, Offset end) {
final startNode = _lowerBoundInDocument(start);
final endNode = _upperBoundInDocument(end);
return NodeIterator(editorState.document, startNode, endNode).toList();
}
@override
bool isNodeInOffset(Node node, Offset offset) {
final renderBox = node.renderBox;
if (renderBox != null) {
final boxOffset = renderBox.localToGlobal(Offset.zero);
final boxRect = boxOffset & renderBox.size;
return boxRect.contains(offset);
}
return false;
}
@override
bool isNodeInRange(Node node, Offset start, Offset end) {
final renderBox = node.renderBox;
if (renderBox != null) {
final rect = Rect.fromPoints(start, end);
final boxOffset = renderBox.localToGlobal(Offset.zero);
final boxRect = boxOffset & renderBox.size;
return rect.overlaps(boxRect);
}
return false;
}
void _onDoubleTapDown(TapDownDetails details) { void _onDoubleTapDown(TapDownDetails details) {
final offset = details.globalPosition; final offset = details.globalPosition;
final node = getNodeInOffset(offset); final node = getNodeInOffset(offset);
@ -395,13 +358,13 @@ class _FlowySelectionState extends State<FlowySelection>
// text: ghijkl // text: ghijkl
// text: mn>opqr // text: mn>opqr
if (index == 0) { if (index == 0) {
if (selection.isDownward) { if (selection.isBackward) {
newSelection = selection.copyWith(end: selectable.end()); newSelection = selection.copyWith(end: selectable.end());
} else { } else {
newSelection = selection.copyWith(start: selectable.start()); newSelection = selection.copyWith(start: selectable.start());
} }
} else if (index == nodes.length - 1) { } else if (index == nodes.length - 1) {
if (selection.isDownward) { if (selection.isBackward) {
newSelection = selection.copyWith(start: selectable.start()); newSelection = selection.copyWith(start: selectable.start());
} else { } else {
newSelection = selection.copyWith(end: selectable.end()); newSelection = selection.copyWith(end: selectable.end());
@ -498,7 +461,7 @@ class _FlowySelectionState extends State<FlowySelection>
/// TODO: It is necessary to calculate the relative speed /// TODO: It is necessary to calculate the relative speed
/// according to the gap and move forward more gently. /// according to the gap and move forward more gently.
final distance = 10.0; const distance = 10.0;
if (offset.dy <= topLimit && !isDownward) { if (offset.dy <= topLimit && !isDownward) {
// up // up
editorState.service.scrollService?.scrollTo(dy - distance); editorState.service.scrollService?.scrollTo(dy - distance);

View File

@ -1,10 +1,9 @@
import 'package:flutter/material.dart';
import 'package:flowy_editor/src/service/keyboard_service.dart'; import 'package:flowy_editor/src/service/keyboard_service.dart';
import 'package:flowy_editor/src/service/render_plugin_service.dart'; import 'package:flowy_editor/src/service/render_plugin_service.dart';
import 'package:flowy_editor/src/service/scroll_service.dart'; import 'package:flowy_editor/src/service/scroll_service.dart';
import 'package:flowy_editor/src/service/selection_service.dart'; import 'package:flowy_editor/src/service/selection_service.dart';
import 'package:flowy_editor/src/service/toolbar_service.dart'; import 'package:flowy_editor/src/service/toolbar_service.dart';
import 'package:flutter/material.dart';
class FlowyService { class FlowyService {
// selection service // selection service