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;
export 'src/document/state_tree.dart';
export 'src/document/node.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/render/selection/selectable.dart';
export 'src/editor_state.dart';
export 'src/operation/operation.dart';
export 'src/operation/transaction.dart';
export 'src/operation/transaction_builder.dart';
export 'src/operation/operation.dart';
export 'src/editor_state.dart';
export 'src/render/selection/selectable.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/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/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 {
final Position start;
final Position end;
/// Create a selection with [start], [end].
Selection({
required this.start,
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({
required Path path,
required int startOffset,
@ -18,10 +29,21 @@ class Selection {
}) : start = Position(path: path, offset: startOffset),
end = Position(path: path, offset: endOffset ?? startOffset);
/// Create a collapsed selection with [position].
Selection.collapsed(Position position)
: start = 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}) {
if (atStart) {
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}) {
return Selection(
start: start ?? this.start,
@ -46,13 +61,10 @@ class Selection {
Selection copy() => Selection(start: start, end: end);
@override
String toString() => '[Selection] start = $start, end = $end';
Map<String, dynamic> toJson() {
return {
"start": start.toJson(),
"end": end.toJson(),
'start': start.toJson(),
'end': end.toJson(),
};
}
@ -69,4 +81,7 @@ class Selection {
@override
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: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/rich_text.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/render_plugin_service.dart';
import 'package:flowy_editor/src/service/scroll_service.dart';
@ -34,18 +26,6 @@ NodeWidgetBuilders defaultBuilders = {
'text/quote': QuotedTextNodeWidgetBuilder(),
};
List<FlowyKeyEventHandler> defaultKeyEventHandler = [
deleteTextHandler,
slashShortcutHandler,
flowyDeleteNodesHandler,
arrowKeysHandler,
copyPasteKeysHandler,
redoUndoKeysHandler,
enterWithoutShiftInTextNodesHandler,
updateTextStyleByCommandXHandler,
whiteSpaceHandler,
];
class FlowyEditor extends StatefulWidget {
const FlowyEditor({
Key? key,
@ -98,7 +78,7 @@ class _FlowyEditorState extends State<FlowyEditor> {
child: FlowyKeyboard(
key: editorState.service.keyboardServiceKey,
handlers: [
...defaultKeyEventHandler,
...defaultKeyEventHandlers,
...widget.keyEventHandlers,
],
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/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> {
/// Returns the current [Selection]
/// The current [Selection] in editor.
///
/// The value is null if there is no nodes are selected.
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;
/// Update the selection or cursor.
/// Updates the selection.
///
/// If selection is collapsed, this method will
/// update the position of the cursor.
/// Otherwise, will update the selection.
/// The editor will update selection area and popup list area
/// if the [selection] is not collapsed,
/// otherwise, will update the cursor area.
void updateSelection(Selection selection);
/// Clear the selection or cursor.
/// Clears the selection area, cursor area and the popup list area.
void clearSelection();
/// ------------------ Selection ------------------------
List<Rect> rects();
Position? hitTest(Offset? offset);
///
/// Returns the [Node]s in [Selection].
List<Node> getNodesInSelection(Selection selection);
/// ------------------ Selection ------------------------
/// ------------------ Offset ------------------------
/// Return the [Node] or [Null] in single selection.
/// Returns the [Node] containing to the offset.
///
/// [offset] is under the global coordinate system.
/// [offset] must be under the global coordinate system.
Node? getNodeInOffset(Offset offset);
/// Returns selected [Node]s. Empty list would be returned
/// if no nodes are in range.
///
///
/// [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 ------------------------
// TODO: need to be documented.
List<Rect> rects();
Position? hitTest(Offset? offset);
}
class FlowySelection extends StatefulWidget {
@ -207,36 +200,6 @@ class _FlowySelectionState extends State<FlowySelection>
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) {
final offset = details.globalPosition;
final node = getNodeInOffset(offset);
@ -395,13 +358,13 @@ class _FlowySelectionState extends State<FlowySelection>
// text: ghijkl
// text: mn>opqr
if (index == 0) {
if (selection.isDownward) {
if (selection.isBackward) {
newSelection = selection.copyWith(end: selectable.end());
} else {
newSelection = selection.copyWith(start: selectable.start());
}
} else if (index == nodes.length - 1) {
if (selection.isDownward) {
if (selection.isBackward) {
newSelection = selection.copyWith(start: selectable.start());
} else {
newSelection = selection.copyWith(end: selectable.end());
@ -498,7 +461,7 @@ class _FlowySelectionState extends State<FlowySelection>
/// TODO: It is necessary to calculate the relative speed
/// according to the gap and move forward more gently.
final distance = 10.0;
const distance = 10.0;
if (offset.dy <= topLimit && !isDownward) {
// up
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/render_plugin_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/toolbar_service.dart';
import 'package:flutter/material.dart';
class FlowyService {
// selection service