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;
|
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);
|
||||||
|
@ -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) {
|
||||||
|
@ -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';
|
||||||
}
|
}
|
||||||
|
@ -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.
|
||||||
///
|
///
|
||||||
|
@ -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;
|
||||||
};
|
};
|
||||||
|
@ -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);
|
||||||
|
Loading…
Reference in New Issue
Block a user