mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2024-08-30 18:12:39 +00:00
docs: documentation for selection_service
This commit is contained in:
parent
046faf3880
commit
ae0012ba37
@ -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';
|
||||
|
@ -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. forward,the 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';
|
||||
}
|
||||
|
@ -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,
|
||||
|
@ -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,
|
||||
];
|
@ -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);
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user