mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2024-08-30 18:12:39 +00:00
fix: compute wrong upward selection
This commit is contained in:
parent
cde2127dec
commit
c048c8f623
@ -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);
|
||||
|
@ -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) {
|
||||
|
@ -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';
|
||||
}
|
||||
|
@ -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.
|
||||
///
|
||||
|
@ -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;
|
||||
};
|
||||
|
@ -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);
|
||||
|
Loading…
Reference in New Issue
Block a user