mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2024-08-30 18:12:39 +00:00
feat: compute cursor and selection by [Selection] or [Offset]
This commit is contained in:
parent
941671568e
commit
114ae2b45d
@ -1,3 +1,5 @@
|
||||
import 'package:flowy_editor/document/position.dart';
|
||||
import 'package:flowy_editor/document/selection.dart';
|
||||
import 'package:flowy_editor/flowy_editor.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
@ -38,27 +40,27 @@ class __ImageNodeWidgetState extends State<_ImageNodeWidget> with Selectable {
|
||||
String get src => widget.node.attributes['image_src'] as String;
|
||||
|
||||
@override
|
||||
List<Rect> getSelectionRectsInRange(Offset start, Offset end) {
|
||||
final renderBox = context.findRenderObject() as RenderBox;
|
||||
return [Offset.zero & renderBox.size];
|
||||
List<Rect> getRectsInSelection(Selection selection) {
|
||||
// TODO: implement getRectsInSelection
|
||||
throw UnimplementedError();
|
||||
}
|
||||
|
||||
@override
|
||||
Rect getCursorRect(Offset start) {
|
||||
final renderBox = context.findRenderObject() as RenderBox;
|
||||
final size = Size(2, renderBox.size.height);
|
||||
final cursorOffset = Offset(renderBox.size.width, 0);
|
||||
return cursorOffset & size;
|
||||
Selection getSelectionInRange(Offset start, Offset end) {
|
||||
// TODO: implement getSelectionInRange
|
||||
throw UnimplementedError();
|
||||
}
|
||||
|
||||
@override
|
||||
TextSelection? getCurrentTextSelection() {
|
||||
return null;
|
||||
Rect getCursorRectInPosition(Position position) {
|
||||
// TODO: implement getCursorRectInPosition
|
||||
throw UnimplementedError();
|
||||
}
|
||||
|
||||
@override
|
||||
Offset getOffsetByTextSelection(TextSelection textSelection) {
|
||||
return Offset.zero;
|
||||
Position getPositionInOffset(Offset start) {
|
||||
// TODO: implement getPositionInOffset
|
||||
throw UnimplementedError();
|
||||
}
|
||||
|
||||
@override
|
||||
|
@ -1,6 +1,8 @@
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:example/plugin/debuggable_rich_text.dart';
|
||||
import 'package:flowy_editor/document/selection.dart';
|
||||
import 'package:flowy_editor/document/position.dart';
|
||||
import 'package:flowy_editor/flowy_editor.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/gestures.dart';
|
||||
@ -56,49 +58,43 @@ class _SelectedTextNodeWidgetState extends State<_SelectedTextNodeWidget>
|
||||
_textKey.currentContext?.findRenderObject() as RenderParagraph;
|
||||
|
||||
@override
|
||||
List<Rect> getSelectionRectsInRange(Offset start, Offset end) {
|
||||
Selection getSelectionInRange(Offset start, Offset end) {
|
||||
final localStart = _renderParagraph.globalToLocal(start);
|
||||
final localEnd = _renderParagraph.globalToLocal(end);
|
||||
|
||||
var textSelection =
|
||||
TextSelection(baseOffset: 0, extentOffset: node.toRawString().length);
|
||||
// Returns select all if the start or end exceeds the size of the box
|
||||
// TODO: don't need to compute everytime.
|
||||
var rects = _computeSelectionRects(textSelection);
|
||||
_textSelection = textSelection;
|
||||
|
||||
if (localEnd.dy > localStart.dy) {
|
||||
// downward
|
||||
if (localEnd.dy >= rects.last.bottom) {
|
||||
return rects;
|
||||
}
|
||||
} else {
|
||||
// upward
|
||||
if (localEnd.dy <= rects.first.top) {
|
||||
return rects;
|
||||
}
|
||||
}
|
||||
|
||||
final selectionBaseOffset = _getTextPositionAtOffset(localStart).offset;
|
||||
final selectionExtentOffset = _getTextPositionAtOffset(localEnd).offset;
|
||||
textSelection = TextSelection(
|
||||
baseOffset: selectionBaseOffset,
|
||||
extentOffset: selectionExtentOffset,
|
||||
final baseOffset = _getTextPositionAtOffset(localStart).offset;
|
||||
final extentOffset = _getTextPositionAtOffset(localEnd).offset;
|
||||
return Selection.single(
|
||||
path: node.path,
|
||||
startOffset: baseOffset,
|
||||
endOffset: extentOffset,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
List<Rect> getRectsInSelection(Selection selection) {
|
||||
assert(pathEquals(selection.start.path, selection.end.path));
|
||||
assert(pathEquals(selection.start.path, node.path));
|
||||
final textSelection = TextSelection(
|
||||
baseOffset: selection.start.offset,
|
||||
extentOffset: selection.end.offset,
|
||||
);
|
||||
_textSelection = textSelection;
|
||||
return _computeSelectionRects(textSelection);
|
||||
}
|
||||
|
||||
@override
|
||||
Rect getCursorRect(Offset start) {
|
||||
final localStart = _renderParagraph.globalToLocal(start);
|
||||
final selectionBaseOffset = _getTextPositionAtOffset(localStart).offset;
|
||||
final textSelection = TextSelection.collapsed(offset: selectionBaseOffset);
|
||||
Rect getCursorRectInPosition(Position position) {
|
||||
final textSelection = TextSelection.collapsed(offset: position.offset);
|
||||
_textSelection = textSelection;
|
||||
print('text selection = $textSelection');
|
||||
return _computeCursorRect(textSelection.baseOffset);
|
||||
}
|
||||
|
||||
@override
|
||||
Position getPositionInOffset(Offset start) {
|
||||
final localStart = _renderParagraph.globalToLocal(start);
|
||||
final baseOffset = _getTextPositionAtOffset(localStart).offset;
|
||||
return Position(path: node.path, offset: baseOffset);
|
||||
}
|
||||
|
||||
@override
|
||||
TextSelection? getCurrentTextSelection() {
|
||||
return _textSelection;
|
||||
@ -175,8 +171,8 @@ class _SelectedTextNodeWidgetState extends State<_SelectedTextNodeWidget>
|
||||
return _renderParagraph.getPositionForOffset(offset);
|
||||
}
|
||||
|
||||
List<Rect> _computeSelectionRects(TextSelection selection) {
|
||||
final textBoxes = _renderParagraph.getBoxesForSelection(selection);
|
||||
List<Rect> _computeSelectionRects(TextSelection textSelection) {
|
||||
final textBoxes = _renderParagraph.getBoxesForSelection(textSelection);
|
||||
return textBoxes.map((box) => box.toRect()).toList();
|
||||
}
|
||||
|
||||
@ -185,7 +181,6 @@ class _SelectedTextNodeWidgetState extends State<_SelectedTextNodeWidget>
|
||||
final cursorOffset =
|
||||
_renderParagraph.getOffsetForCaret(position, Rect.zero);
|
||||
final cursorHeight = _renderParagraph.getFullHeightForCaret(position);
|
||||
print('offset = $offset, cursorHeight = $cursorHeight');
|
||||
if (cursorHeight != null) {
|
||||
const cursorWidth = 2;
|
||||
return Rect.fromLTWH(
|
||||
|
@ -1,3 +1,5 @@
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
|
||||
typedef Path = List<int>;
|
||||
@ -5,3 +7,27 @@ typedef Path = List<int>;
|
||||
bool pathEquals(Path path1, Path path2) {
|
||||
return listEquals(path1, path2);
|
||||
}
|
||||
|
||||
/// Returns true if path1 >= path2, otherwise returns false.
|
||||
/// TODO: Rename this function.
|
||||
bool pathGreaterOrEquals(Path path1, Path path2) {
|
||||
final length = min(path1.length, path2.length);
|
||||
for (var i = 0; i < length; i++) {
|
||||
if (path1[i] < path2[i]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/// Returns true if path1 <= path2, otherwise returns false.
|
||||
/// TODO: Rename this function.
|
||||
bool pathLessOrEquals(Path path1, Path path2) {
|
||||
final length = min(path1.length, path2.length);
|
||||
for (var i = 0; i < length; i++) {
|
||||
if (path1[i] > path2[i]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
@ -24,4 +24,11 @@ class Position {
|
||||
final pathHash = hashList(path);
|
||||
return Object.hash(pathHash, offset);
|
||||
}
|
||||
|
||||
Position copyWith({Path? path, int? offset}) {
|
||||
return Position(
|
||||
path: path ?? this.path,
|
||||
offset: offset ?? this.offset,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,5 @@
|
||||
import './position.dart';
|
||||
import 'package:flowy_editor/document/path.dart';
|
||||
import 'package:flowy_editor/document/position.dart';
|
||||
|
||||
class Selection {
|
||||
final Position start;
|
||||
@ -9,9 +10,16 @@ class Selection {
|
||||
required this.end,
|
||||
});
|
||||
|
||||
factory Selection.collapsed(Position pos) {
|
||||
return Selection(start: pos, end: pos);
|
||||
}
|
||||
Selection.single({
|
||||
required Path path,
|
||||
required int startOffset,
|
||||
int? endOffset,
|
||||
}) : start = Position(path: path, offset: startOffset),
|
||||
end = Position(path: path, offset: endOffset ?? startOffset);
|
||||
|
||||
Selection.collapsed(Position position)
|
||||
: start = position,
|
||||
end = position;
|
||||
|
||||
Selection collapse({bool atStart = false}) {
|
||||
if (atStart) {
|
||||
@ -24,4 +32,11 @@ class Selection {
|
||||
bool isCollapsed() {
|
||||
return start == end;
|
||||
}
|
||||
|
||||
Selection copyWith({Position? start, Position? end}) {
|
||||
return Selection(
|
||||
start: start ?? this.start,
|
||||
end: end ?? this.end,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -1,3 +1,5 @@
|
||||
import 'package:flowy_editor/document/position.dart';
|
||||
import 'package:flowy_editor/document/selection.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
///
|
||||
@ -9,14 +11,17 @@ mixin Selectable<T extends StatefulWidget> on State<T> {
|
||||
///
|
||||
/// The return result must be a [List] of the [Rect]
|
||||
/// under the local coordinate system.
|
||||
List<Rect> getSelectionRectsInRange(Offset start, Offset end);
|
||||
Selection getSelectionInRange(Offset start, Offset end);
|
||||
|
||||
List<Rect> getRectsInSelection(Selection selection);
|
||||
|
||||
/// Returns a [Rect] for the offset in current widget.
|
||||
///
|
||||
/// [start] is the offset of the global coordination system.
|
||||
///
|
||||
/// The return result must be an offset of the local coordinate system.
|
||||
Rect getCursorRect(Offset start);
|
||||
Position getPositionInOffset(Offset start);
|
||||
Rect getCursorRectInPosition(Position position);
|
||||
|
||||
/// Returns a backward offset of the current offset based on the cause.
|
||||
Offset getBackwardOffset(/* Cause */);
|
||||
@ -30,12 +35,12 @@ mixin Selectable<T extends StatefulWidget> on State<T> {
|
||||
///
|
||||
/// Only the widget rendered by [TextNode] need to implement the detail,
|
||||
/// and the rest can return null.
|
||||
TextSelection? getCurrentTextSelection();
|
||||
TextSelection? getCurrentTextSelection() => null;
|
||||
|
||||
/// For [TextNode] only.
|
||||
///
|
||||
/// Retruns a [Offset].
|
||||
/// Only the widget rendered by [TextNode] need to implement the detail,
|
||||
/// and the rest can return [Offset.zero].
|
||||
Offset getOffsetByTextSelection(TextSelection textSelection);
|
||||
Offset getOffsetByTextSelection(TextSelection textSelection) => Offset.zero;
|
||||
}
|
||||
|
@ -30,7 +30,7 @@ FlowyKeyEventHandler arrowKeysHandler = (editorState, event) {
|
||||
}
|
||||
final selectionService = editorState.service.selectionService;
|
||||
if (offset != null) {
|
||||
selectionService.updateCursor(offset);
|
||||
// selectionService.updateCursor(offset);
|
||||
return KeyEventResult.handled;
|
||||
}
|
||||
return KeyEventResult.ignored;
|
||||
|
@ -37,7 +37,7 @@ FlowyKeyEventHandler deleteSingleTextNodeHandler = (editorState, event) {
|
||||
final newOfset = previousSelectable
|
||||
?.getOffsetByTextSelection(newTextSelection);
|
||||
if (newOfset != null) {
|
||||
selectionService.updateCursor(newOfset);
|
||||
// selectionService.updateCursor(newOfset);
|
||||
}
|
||||
// merge
|
||||
TransactionBuilder(editorState)
|
||||
@ -58,7 +58,7 @@ FlowyKeyEventHandler deleteSingleTextNodeHandler = (editorState, event) {
|
||||
final selectionService = editorState.service.selectionService;
|
||||
final newOfset =
|
||||
selectable.getOffsetByTextSelection(newTextSelection);
|
||||
selectionService.updateCursor(newOfset);
|
||||
// selectionService.updateCursor(newOfset);
|
||||
return KeyEventResult.handled;
|
||||
}
|
||||
}
|
||||
|
@ -18,13 +18,13 @@ FlowyKeyEventHandler slashShortcutHandler = (editorState, event) {
|
||||
final textNode = selectedNodes.first.unwrapOrNull<TextNode>();
|
||||
final selectable = textNode?.key?.currentState?.unwrapOrNull<Selectable>();
|
||||
final textSelection = selectable?.getCurrentTextSelection();
|
||||
if (textNode != null && selectable != null && textSelection != null) {
|
||||
final offset = selectable.getOffsetByTextSelection(textSelection);
|
||||
final rect = selectable.getCursorRect(offset);
|
||||
editorState.service.floatingToolbarService
|
||||
.showInOffset(rect.topLeft, textNode.layerLink);
|
||||
return KeyEventResult.handled;
|
||||
}
|
||||
// if (textNode != null && selectable != null && textSelection != null) {
|
||||
// final offset = selectable.getOffsetByTextSelection(textSelection);
|
||||
// final rect = selectable.getCursorRect(offset);
|
||||
// editorState.service.floatingToolbarService
|
||||
// .showInOffset(rect.topLeft, textNode.layerLink);
|
||||
// return KeyEventResult.handled;
|
||||
// }
|
||||
|
||||
return KeyEventResult.ignored;
|
||||
};
|
||||
|
@ -1,3 +1,6 @@
|
||||
import 'package:flowy_editor/document/path.dart';
|
||||
import 'package:flowy_editor/document/position.dart';
|
||||
import 'package:flowy_editor/document/selection.dart';
|
||||
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';
|
||||
@ -12,11 +15,8 @@ import '../render/selection/selectable.dart';
|
||||
|
||||
/// Process selection and cursor
|
||||
mixin FlowySelectionService<T extends StatefulWidget> on State<T> {
|
||||
/// [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);
|
||||
///
|
||||
void updateSelection(Selection selection);
|
||||
|
||||
/// Returns selected [Node]s. Empty list would be returned
|
||||
/// if no nodes are being selected.
|
||||
@ -26,18 +26,21 @@ mixin FlowySelectionService<T extends StatefulWidget> on State<T> {
|
||||
///
|
||||
/// If end is not null, it means multiple selection,
|
||||
/// otherwise single selection.
|
||||
List<Node> getSelectedNodes(Offset start, [Offset? end]);
|
||||
List<Node> getNodesInRange(Offset start, [Offset? end]);
|
||||
|
||||
///
|
||||
List<Node> getNodesInSelection(Selection selection);
|
||||
|
||||
/// Return the [Node] or [Null] in single selection.
|
||||
///
|
||||
/// [start] is the offset under the global coordinate system.
|
||||
Node? computeSelectedNodeInOffset(Node node, Offset offset);
|
||||
Node? computeNodeInOffset(Node node, Offset offset);
|
||||
|
||||
/// 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<Node> computeSelectedNodesInRange(
|
||||
List<Node> computeNodesInRange(
|
||||
Node node,
|
||||
Offset start,
|
||||
Offset end,
|
||||
@ -93,6 +96,10 @@ class _FlowySelectionState extends State<FlowySelection>
|
||||
|
||||
EditorState get editorState => widget.editorState;
|
||||
|
||||
@override
|
||||
List<Node> getNodesInSelection(Selection selection) =>
|
||||
_selectedNodesInSelection(editorState.document.root, selection);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return RawGestureDetector(
|
||||
@ -121,70 +128,23 @@ class _FlowySelectionState extends State<FlowySelection>
|
||||
}
|
||||
|
||||
@override
|
||||
void updateSelection(Offset start, Offset end) {
|
||||
void updateSelection(Selection selection) {
|
||||
_clearAllOverlayEntries();
|
||||
|
||||
final nodes = getSelectedNodes(start, end);
|
||||
editorState.selectedNodes = nodes;
|
||||
if (nodes.isEmpty) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (final node in nodes) {
|
||||
if (node.key?.currentState is! Selectable) {
|
||||
continue;
|
||||
}
|
||||
final selectable = node.key?.currentState as Selectable;
|
||||
final selectionRects = selectable.getSelectionRectsInRange(start, end);
|
||||
for (final rect in selectionRects) {
|
||||
final overlay = OverlayEntry(
|
||||
builder: ((context) => SelectionWidget(
|
||||
color: widget.selectionColor,
|
||||
layerLink: node.layerLink,
|
||||
rect: rect,
|
||||
)),
|
||||
);
|
||||
_selectionOverlays.add(overlay);
|
||||
}
|
||||
}
|
||||
Overlay.of(context)?.insertAll(_selectionOverlays);
|
||||
}
|
||||
|
||||
@override
|
||||
void updateCursor(Offset start) {
|
||||
_clearAllOverlayEntries();
|
||||
|
||||
final nodes = getSelectedNodes(start);
|
||||
editorState.selectedNodes = nodes;
|
||||
if (nodes.isEmpty) {
|
||||
return;
|
||||
}
|
||||
|
||||
final selectedNode = nodes.first;
|
||||
if (selectedNode.key?.currentState is! Selectable) {
|
||||
return;
|
||||
}
|
||||
final selectable = selectedNode.key?.currentState as Selectable;
|
||||
final rect = selectable.getCursorRect(start);
|
||||
final cursor = OverlayEntry(
|
||||
builder: ((context) => CursorWidget(
|
||||
key: _cursorKey,
|
||||
rect: rect,
|
||||
color: widget.cursorColor,
|
||||
layerLink: selectedNode.layerLink,
|
||||
)),
|
||||
);
|
||||
_cursorOverlays.add(cursor);
|
||||
Overlay.of(context)?.insertAll(_cursorOverlays);
|
||||
}
|
||||
|
||||
@override
|
||||
List<Node> getSelectedNodes(Offset start, [Offset? end]) {
|
||||
if (end != null) {
|
||||
return computeSelectedNodesInRange(editorState.document.root, start, end);
|
||||
// cursor
|
||||
if (selection.isCollapsed()) {
|
||||
_updateCursor(selection.start);
|
||||
} else {
|
||||
final reuslt =
|
||||
computeSelectedNodeInOffset(editorState.document.root, start);
|
||||
_updateSelection(selection);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
List<Node> getNodesInRange(Offset start, [Offset? end]) {
|
||||
if (end != null) {
|
||||
return computeNodesInRange(editorState.document.root, start, end);
|
||||
} else {
|
||||
final reuslt = computeNodeInOffset(editorState.document.root, start);
|
||||
if (reuslt != null) {
|
||||
return [reuslt];
|
||||
}
|
||||
@ -193,9 +153,9 @@ class _FlowySelectionState extends State<FlowySelection>
|
||||
}
|
||||
|
||||
@override
|
||||
Node? computeSelectedNodeInOffset(Node node, Offset offset) {
|
||||
Node? computeNodeInOffset(Node node, Offset offset) {
|
||||
for (final child in node.children) {
|
||||
final result = computeSelectedNodeInOffset(child, offset);
|
||||
final result = computeNodeInOffset(child, offset);
|
||||
if (result != null) {
|
||||
return result;
|
||||
}
|
||||
@ -209,7 +169,7 @@ class _FlowySelectionState extends State<FlowySelection>
|
||||
}
|
||||
|
||||
@override
|
||||
List<Node> computeSelectedNodesInRange(Node node, Offset start, Offset end) {
|
||||
List<Node> computeNodesInRange(Node node, Offset start, Offset end) {
|
||||
List<Node> result = [];
|
||||
if (node.parent != null && node.key != null) {
|
||||
if (isNodeInSelection(node, start, end)) {
|
||||
@ -217,7 +177,7 @@ class _FlowySelectionState extends State<FlowySelection>
|
||||
}
|
||||
}
|
||||
for (final child in node.children) {
|
||||
result.addAll(computeSelectedNodesInRange(child, start, end));
|
||||
result.addAll(computeNodesInRange(child, start, end));
|
||||
}
|
||||
// TODO: sort the result
|
||||
return result;
|
||||
@ -254,7 +214,16 @@ class _FlowySelectionState extends State<FlowySelection>
|
||||
panStartOffset = null;
|
||||
panEndOffset = null;
|
||||
|
||||
updateCursor(tapOffset!);
|
||||
final nodes = getNodesInRange(tapOffset!);
|
||||
if (nodes.isNotEmpty) {
|
||||
assert(nodes.length == 1);
|
||||
final selectable = nodes.first.selectable;
|
||||
if (selectable != null) {
|
||||
final position = selectable.getPositionInOffset(tapOffset!);
|
||||
final selection = Selection.collapsed(position);
|
||||
updateSelection(selection);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void _onPanStart(DragStartDetails details) {
|
||||
@ -271,7 +240,16 @@ class _FlowySelectionState extends State<FlowySelection>
|
||||
panEndOffset = details.globalPosition;
|
||||
tapOffset = null;
|
||||
|
||||
updateSelection(panStartOffset!, panEndOffset!);
|
||||
final nodes = getNodesInRange(panStartOffset!, panEndOffset!);
|
||||
final first = nodes.first.selectable;
|
||||
final last = nodes.last.selectable;
|
||||
if (first != null && last != null) {
|
||||
final selection = Selection(
|
||||
start: first.getSelectionInRange(panStartOffset!, panEndOffset!).start,
|
||||
end: last.getSelectionInRange(panStartOffset!, panEndOffset!).end,
|
||||
);
|
||||
updateSelection(selection);
|
||||
}
|
||||
}
|
||||
|
||||
void _onPanEnd(DragEndDetails details) {
|
||||
@ -302,4 +280,106 @@ class _FlowySelectionState extends State<FlowySelection>
|
||||
?.unwrapOrNull<FlowyFloatingShortcutService>();
|
||||
shortcutService?.hide();
|
||||
}
|
||||
|
||||
void _updateSelection(Selection selection) {
|
||||
final nodes =
|
||||
_selectedNodesInSelection(editorState.document.root, selection);
|
||||
|
||||
var index = 0;
|
||||
for (final node in nodes) {
|
||||
final selectable = node.selectable;
|
||||
if (selectable == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
Selection newSelection;
|
||||
if (node is TextNode) {
|
||||
if (pathEquals(selection.start.path, selection.end.path)) {
|
||||
newSelection = selection.copyWith();
|
||||
} else {
|
||||
if (index == 0) {
|
||||
newSelection = selection.copyWith(
|
||||
/// FIXME: make it better.
|
||||
end: selection.start.copyWith(offset: node.toRawString().length),
|
||||
);
|
||||
} else if (index == nodes.length - 1) {
|
||||
newSelection = selection.copyWith(
|
||||
/// FIXME: make it better.
|
||||
start: selection.end.copyWith(offset: 0),
|
||||
);
|
||||
} else {
|
||||
final position = Position(path: node.path);
|
||||
newSelection = Selection(
|
||||
start: position.copyWith(offset: 0),
|
||||
end: position.copyWith(offset: node.toRawString().length),
|
||||
);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
newSelection = Selection.collapsed(
|
||||
Position(path: node.path),
|
||||
);
|
||||
}
|
||||
|
||||
final rects = selectable.getRectsInSelection(newSelection);
|
||||
|
||||
for (final rect in rects) {
|
||||
final overlay = OverlayEntry(
|
||||
builder: ((context) => SelectionWidget(
|
||||
color: widget.selectionColor,
|
||||
layerLink: node.layerLink,
|
||||
rect: rect,
|
||||
)),
|
||||
);
|
||||
_selectionOverlays.add(overlay);
|
||||
}
|
||||
index += 1;
|
||||
}
|
||||
Overlay.of(context)?.insertAll(_selectionOverlays);
|
||||
}
|
||||
|
||||
void _updateCursor(Position position) {
|
||||
final node = _selectedNodeInPostion(editorState.document.root, position);
|
||||
|
||||
assert(node != null);
|
||||
if (node == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
final selectable = node.selectable;
|
||||
final rect = selectable?.getCursorRectInPosition(position);
|
||||
if (rect != null) {
|
||||
final cursor = OverlayEntry(
|
||||
builder: ((context) => CursorWidget(
|
||||
key: _cursorKey,
|
||||
rect: rect,
|
||||
color: widget.cursorColor,
|
||||
layerLink: node.layerLink,
|
||||
)),
|
||||
);
|
||||
_cursorOverlays.add(cursor);
|
||||
Overlay.of(context)?.insertAll(_cursorOverlays);
|
||||
}
|
||||
}
|
||||
|
||||
List<Node> _selectedNodesInSelection(Node node, Selection selection) {
|
||||
List<Node> result = [];
|
||||
if (node.parent != null) {
|
||||
if (_isNodeInSelection(node, selection)) {
|
||||
result.add(node);
|
||||
}
|
||||
}
|
||||
for (final child in node.children) {
|
||||
result.addAll(_selectedNodesInSelection(child, selection));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
Node? _selectedNodeInPostion(Node node, Position position) =>
|
||||
node.childAtPath(position.path);
|
||||
|
||||
bool _isNodeInSelection(Node node, Selection selection) {
|
||||
return pathGreaterOrEquals(node.path, selection.start.path) &&
|
||||
pathLessOrEquals(node.path, selection.end.path);
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user