fix: compute wrong upward selection

This commit is contained in:
Lucas.Xu 2022-07-26 23:28:51 +08:00
parent cde2127dec
commit c048c8f623
6 changed files with 83 additions and 132 deletions

View File

@ -39,6 +39,18 @@ class __ImageNodeWidgetState extends State<_ImageNodeWidget> with Selectable {
EditorState get editorState => widget.editorState; EditorState get editorState => widget.editorState;
String get src => widget.node.attributes['image_src'] as String; String get src => widget.node.attributes['image_src'] as String;
@override
Position end() {
// TODO: implement end
throw UnimplementedError();
}
@override
Position start() {
// TODO: implement start
throw UnimplementedError();
}
@override @override
List<Rect> getRectsInSelection(Selection selection) { List<Rect> getRectsInSelection(Selection selection) {
// TODO: implement getRectsInSelection // TODO: implement getRectsInSelection
@ -63,16 +75,6 @@ class __ImageNodeWidgetState extends State<_ImageNodeWidget> with Selectable {
throw UnimplementedError(); throw UnimplementedError();
} }
@override
Offset getBackwardOffset() {
return Offset.zero;
}
@override
Offset getForwardOffset() {
return Offset.zero;
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return _build(context); return _build(context);

View File

@ -107,28 +107,11 @@ class _SelectedTextNodeWidgetState extends State<_SelectedTextNodeWidget>
} }
@override @override
Offset getBackwardOffset() { Position start() => Position(path: node.path, offset: 0);
final textSelection = _textSelection;
if (textSelection != null) {
final leftTextSelection = TextSelection.collapsed(
offset: max(0, textSelection.baseOffset - 1),
);
return getOffsetByTextSelection(leftTextSelection);
}
return Offset.zero;
}
@override @override
Offset getForwardOffset() { Position end() =>
final textSelection = _textSelection; Position(path: node.path, offset: node.toRawString().length);
if (textSelection != null) {
final leftTextSelection = TextSelection.collapsed(
offset: min(node.toRawString().length, textSelection.extentOffset + 1),
);
return getOffsetByTextSelection(leftTextSelection);
}
return Offset.zero;
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {

View File

@ -31,6 +31,7 @@ class Selection {
} }
bool get isCollapsed => start == end; bool get isCollapsed => start == end;
bool get isSingle => pathEquals(start.path, end.path);
bool get isUpward => bool get isUpward =>
start.path >= end.path && !pathEquals(start.path, end.path); start.path >= end.path && !pathEquals(start.path, end.path);
bool get isDownward => bool get isDownward =>
@ -43,6 +44,8 @@ class Selection {
); );
} }
Selection copy() => Selection(start: start, end: end);
@override @override
String toString() => '[Selection] start = $start, end = $end'; String toString() => '[Selection] start = $start, end = $end';
} }

View File

@ -23,11 +23,8 @@ mixin Selectable<T extends StatefulWidget> on State<T> {
Position getPositionInOffset(Offset start); Position getPositionInOffset(Offset start);
Rect getCursorRectInPosition(Position position); Rect getCursorRectInPosition(Position position);
/// Returns a backward offset of the current offset based on the cause. Position start();
Offset getBackwardOffset(/* Cause */); Position end();
/// Returns a forward offset of the current offset based on the cause.
Offset getForwardOffset(/* Cause */);
/// For [TextNode] only. /// For [TextNode] only.
/// ///

View File

@ -1,5 +1,3 @@
import 'package:flowy_editor/extensions/object_extensions.dart';
import 'package:flowy_editor/flowy_editor.dart';
import 'package:flowy_editor/service/keyboard_service.dart'; import 'package:flowy_editor/service/keyboard_service.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
@ -12,26 +10,5 @@ FlowyKeyEventHandler arrowKeysHandler = (editorState, event) {
return KeyEventResult.ignored; return KeyEventResult.ignored;
} }
// TODO: Up and Down
// Left and Right
final selectedNodes = editorState.selectedNodes;
if (selectedNodes.length != 1) {
return KeyEventResult.ignored;
}
final node = selectedNodes.first.unwrapOrNull<TextNode>();
final selectable = node?.key?.currentState?.unwrapOrNull<Selectable>();
Offset? offset;
if (event.logicalKey == LogicalKeyboardKey.arrowLeft) {
offset = selectable?.getBackwardOffset();
} else if (event.logicalKey == LogicalKeyboardKey.arrowRight) {
offset = selectable?.getForwardOffset();
}
final selectionService = editorState.service.selectionService;
if (offset != null) {
// selectionService.updateCursor(offset);
return KeyEventResult.handled;
}
return KeyEventResult.ignored; return KeyEventResult.ignored;
}; };

View File

@ -14,15 +14,26 @@ import 'package:flutter/material.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> {
/// Returns the currently selected [Node]s.
/// ///
/// The order of the return is determined according to the selected order.
List<Node> get currentSelectedNodes; List<Node> get currentSelectedNodes;
/// ------------------ Selection ------------------------
/// ///
void updateSelection(Selection selection); void updateSelection(Selection selection);
/// ///
void clearSelection(); void clearSelection();
///
List<Node> getNodesInSelection(Selection selection);
/// ------------------ Selection ------------------------
/// ------------------ Offset ------------------------
/// Returns selected [Node]s. Empty list would be returned /// Returns selected [Node]s. Empty list would be returned
/// if no nodes are being selected. /// if no nodes are being selected.
/// ///
@ -33,9 +44,6 @@ mixin FlowySelectionService<T extends StatefulWidget> on State<T> {
/// otherwise single selection. /// otherwise single selection.
List<Node> getNodesInRange(Offset start, [Offset? end]); List<Node> getNodesInRange(Offset start, [Offset? end]);
///
List<Node> getNodesInSelection(Selection selection);
/// Return the [Node] or [Null] in single selection. /// Return the [Node] or [Null] in single selection.
/// ///
/// [start] is the offset under the global coordinate system. /// [start] is the offset under the global coordinate system.
@ -64,6 +72,8 @@ mixin FlowySelectionService<T extends StatefulWidget> on State<T> {
/// ///
/// [start] is the offset under the global coordinate system. /// [start] is the offset under the global coordinate system.
bool isNodeInOffset(Node node, Offset offset); bool isNodeInOffset(Node node, Offset offset);
/// ------------------ Offset ------------------------
} }
class FlowySelection extends StatefulWidget { class FlowySelection extends StatefulWidget {
@ -101,9 +111,6 @@ class _FlowySelectionState extends State<FlowySelection>
EditorState get editorState => widget.editorState; EditorState get editorState => widget.editorState;
Node? _selectedNodeInPostion(Node node, Position position) =>
node.childAtPath(position.path);
@override @override
List<Node> currentSelectedNodes = []; List<Node> currentSelectedNodes = [];
@ -186,6 +193,17 @@ class _FlowySelectionState extends State<FlowySelection>
@override @override
List<Node> computeNodesInRange(Node node, Offset start, Offset end) { List<Node> computeNodesInRange(Node node, Offset start, Offset end) {
final result = _computeNodesInRange(node, start, end);
if (start.dy <= end.dy) {
// downward
return result;
} else {
// upward
return result.reversed.toList(growable: false);
}
}
List<Node> _computeNodesInRange(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 (isNodeInRange(node, start, end)) { if (isNodeInRange(node, start, end)) {
@ -195,7 +213,6 @@ class _FlowySelectionState extends State<FlowySelection>
for (final child in node.children) { for (final child in node.children) {
result.addAll(computeNodesInRange(child, start, end)); result.addAll(computeNodesInRange(child, start, end));
} }
// TODO: sort the result
return result; return result;
} }
@ -223,13 +240,12 @@ class _FlowySelectionState extends State<FlowySelection>
} }
void _onTapDown(TapDownDetails details) { void _onTapDown(TapDownDetails details) {
debugPrint('on tap down'); // clear old state.
// TODO: use setter to make them exclusive??
tapOffset = details.globalPosition;
panStartOffset = null; panStartOffset = null;
panEndOffset = null; panEndOffset = null;
tapOffset = details.globalPosition;
final nodes = getNodesInRange(tapOffset!); final nodes = getNodesInRange(tapOffset!);
if (nodes.isNotEmpty) { if (nodes.isNotEmpty) {
assert(nodes.length == 1); assert(nodes.length == 1);
@ -243,38 +259,30 @@ class _FlowySelectionState extends State<FlowySelection>
} }
void _onPanStart(DragStartDetails details) { void _onPanStart(DragStartDetails details) {
debugPrint('on pan start'); // clear old state.
panStartOffset = details.globalPosition;
panEndOffset = null; panEndOffset = null;
tapOffset = null; tapOffset = null;
clearSelection();
panStartOffset = details.globalPosition;
} }
void _onPanUpdate(DragUpdateDetails details) { void _onPanUpdate(DragUpdateDetails details) {
// debugPrint('on pan update');
panEndOffset = details.globalPosition; panEndOffset = details.globalPosition;
tapOffset = null;
final nodes = getNodesInRange(panStartOffset!, panEndOffset!); final nodes = getNodesInRange(panStartOffset!, panEndOffset!);
final first = nodes.first.selectable; final first = nodes.first.selectable;
final last = nodes.last.selectable; final last = nodes.last.selectable;
// compute the selection in range.
if (first != null && last != null) { if (first != null && last != null) {
final Selection selection; bool isDownward = panStartOffset!.dy <= panEndOffset!.dy;
if (panStartOffset!.dy <= panEndOffset!.dy) { final start =
// down first.getSelectionInRange(panStartOffset!, panEndOffset!).start;
selection = Selection( final end = last.getSelectionInRange(panStartOffset!, panEndOffset!).end;
start: final selection = Selection(
first.getSelectionInRange(panStartOffset!, panEndOffset!).start, start: isDownward ? start : end, end: isDownward ? end : start);
end: last.getSelectionInRange(panStartOffset!, panEndOffset!).end, debugPrint('[_onPanUpdate] $selection');
);
} else {
// up
selection = Selection(
start: last.getSelectionInRange(panStartOffset!, panEndOffset!).end,
end: first.getSelectionInRange(panStartOffset!, panEndOffset!).start,
);
}
updateSelection(selection); updateSelection(selection);
} }
} }
@ -313,51 +321,32 @@ class _FlowySelectionState extends State<FlowySelection>
continue; continue;
} }
Selection newSelection; var newSelection = selection.copy();
// TODO: too complicate, need to refactor. // In the case of multiple selections,
if (node is TextNode) { // we need to return a new selection for each selected node individually.
if (pathEquals(selection.start.path, selection.end.path)) { if (!selection.isSingle) {
newSelection = selection.copyWith(); // <> means selected.
} else { // text: abcd<ef
if (index == 0) { // text: ghijkl
if (selection.isUpward) { // text: mn>opqr
newSelection = selection.copyWith( if (index == 0) {
/// FIXME: make it better. if (selection.isDownward) {
start: selection.end.copyWith(), newSelection = selection.copyWith(end: selectable.end());
end: selection.end.copyWith(offset: node.toRawString().length),
);
} else {
newSelection = selection.copyWith(
/// FIXME: make it better.
end:
selection.start.copyWith(offset: node.toRawString().length),
);
}
} else if (index == nodes.length - 1) {
if (selection.isUpward) {
newSelection = selection.copyWith(
/// FIXME: make it better.
start: selection.start.copyWith(offset: 0),
end: selection.start.copyWith(),
);
} else {
newSelection = selection.copyWith(
/// FIXME: make it better.
start: selection.end.copyWith(offset: 0),
);
}
} else { } else {
final position = Position(path: node.path); newSelection = selection.copyWith(start: selectable.start());
newSelection = Selection(
start: position.copyWith(offset: 0),
end: position.copyWith(offset: node.toRawString().length),
);
} }
} else if (index == nodes.length - 1) {
if (selection.isDownward) {
newSelection = selection.copyWith(start: selectable.start());
} else {
newSelection = selection.copyWith(end: selectable.end());
}
} else {
newSelection = selection.copyWith(
start: selectable.start(),
end: selectable.end(),
);
} }
} else {
newSelection = Selection.collapsed(
Position(path: node.path),
);
} }
final rects = selectable.getRectsInSelection(newSelection); final rects = selectable.getRectsInSelection(newSelection);