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;
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
List<Rect> getRectsInSelection(Selection selection) {
// TODO: implement getRectsInSelection
@ -63,16 +75,6 @@ class __ImageNodeWidgetState extends State<_ImageNodeWidget> with Selectable {
throw UnimplementedError();
}
@override
Offset getBackwardOffset() {
return Offset.zero;
}
@override
Offset getForwardOffset() {
return Offset.zero;
}
@override
Widget build(BuildContext context) {
return _build(context);

View File

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

View File

@ -31,6 +31,7 @@ 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 =>
@ -43,6 +44,8 @@ class Selection {
);
}
Selection copy() => Selection(start: start, end: end);
@override
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);
Rect getCursorRectInPosition(Position position);
/// Returns a backward offset of the current offset based on the cause.
Offset getBackwardOffset(/* Cause */);
/// Returns a forward offset of the current offset based on the cause.
Offset getForwardOffset(/* Cause */);
Position start();
Position end();
/// 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:flutter/material.dart';
import 'package:flutter/services.dart';
@ -12,26 +10,5 @@ FlowyKeyEventHandler arrowKeysHandler = (editorState, event) {
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;
};

View File

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