feat: arrow up and down

This commit is contained in:
Vincent Chan 2022-07-27 15:58:25 +08:00
parent e74f5e84dc
commit 53b982e7c9
6 changed files with 98 additions and 17 deletions

View File

@ -63,6 +63,11 @@ class __ImageNodeWidgetState extends State<_ImageNodeWidget> with Selectable {
throw UnimplementedError(); throw UnimplementedError();
} }
@override
Offset localToGlobal(Offset offset) {
throw UnimplementedError();
}
@override @override
Rect getCursorRectInPosition(Position position) { Rect getCursorRectInPosition(Position position) {
// TODO: implement getCursorRectInPosition // TODO: implement getCursorRectInPosition

View File

@ -70,6 +70,11 @@ class _SelectedTextNodeWidgetState extends State<_SelectedTextNodeWidget>
); );
} }
@override
Offset localToGlobal(Offset offset) {
return _renderParagraph.localToGlobal(offset);
}
@override @override
List<Rect> getRectsInSelection(Selection selection) { List<Rect> getRectsInSelection(Selection selection) {
assert(pathEquals(selection.start.path, selection.end.path)); assert(pathEquals(selection.start.path, selection.end.path));

View File

@ -17,10 +17,10 @@ class CursorWidget extends StatefulWidget {
final LayerLink layerLink; final LayerLink layerLink;
@override @override
State<CursorWidget> createState() => _CursorWidgetState(); State<CursorWidget> createState() => CursorWidgetState();
} }
class _CursorWidgetState extends State<CursorWidget> { class CursorWidgetState extends State<CursorWidget> {
bool showCursor = true; bool showCursor = true;
late Timer timer; late Timer timer;
@ -28,7 +28,17 @@ class _CursorWidgetState extends State<CursorWidget> {
void initState() { void initState() {
super.initState(); super.initState();
timer = Timer.periodic( timer = _initTimer();
}
@override
void dispose() {
timer.cancel();
super.dispose();
}
Timer _initTimer() {
return Timer.periodic(
Duration(milliseconds: (widget.blinkingInterval * 1000).toInt()), Duration(milliseconds: (widget.blinkingInterval * 1000).toInt()),
(timer) { (timer) {
setState(() { setState(() {
@ -37,10 +47,13 @@ class _CursorWidgetState extends State<CursorWidget> {
}); });
} }
@override /// force the cursor widget to show for a while
void dispose() { show() {
setState(() {
showCursor = true;
});
timer.cancel(); timer.cancel();
super.dispose(); timer = _initTimer();
} }
@override @override

View File

@ -23,6 +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);
Offset localToGlobal(Offset offset);
Position start(); Position start();
Position end(); Position end();

View File

@ -2,6 +2,7 @@ import 'package:flowy_editor/document/node.dart';
import 'package:flowy_editor/document/position.dart'; import 'package:flowy_editor/document/position.dart';
import 'package:flowy_editor/service/keyboard_service.dart'; import 'package:flowy_editor/service/keyboard_service.dart';
import 'package:flowy_editor/document/selection.dart'; import 'package:flowy_editor/document/selection.dart';
import 'package:flowy_editor/extensions/node_extensions.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
@ -26,7 +27,6 @@ FlowyKeyEventHandler arrowKeysHandler = (editorState, event) {
} }
if (event.logicalKey == LogicalKeyboardKey.arrowLeft) { if (event.logicalKey == LogicalKeyboardKey.arrowLeft) {
// turn left
if (currentSelection.isCollapsed) { if (currentSelection.isCollapsed) {
final end = currentSelection.end; final end = currentSelection.end;
final offset = end.offset; final offset = end.offset;
@ -67,6 +67,26 @@ FlowyKeyEventHandler arrowKeysHandler = (editorState, event) {
editorState.updateCursorSelection(currentSelection.collapse()); editorState.updateCursorSelection(currentSelection.collapse());
} }
return KeyEventResult.handled; return KeyEventResult.handled;
} else if (event.logicalKey == LogicalKeyboardKey.arrowUp) {
final rects = editorState.service.selectionService.rects();
if (rects.isEmpty) {
return KeyEventResult.handled;
}
final first = rects.first;
final firstOffset = Offset(first.left, first.top);
final hitOffset = firstOffset - Offset(0, first.height * 0.5);
editorState.service.selectionService.hit(hitOffset);
return KeyEventResult.handled;
} else if (event.logicalKey == LogicalKeyboardKey.arrowDown) {
final rects = editorState.service.selectionService.rects();
if (rects.isEmpty) {
return KeyEventResult.handled;
}
final first = rects.last;
final firstOffset = Offset(first.right, first.bottom);
final hitOffset = firstOffset + Offset(0, first.height * 0.5);
editorState.service.selectionService.hit(hitOffset);
return KeyEventResult.handled;
} }
return KeyEventResult.ignored; return KeyEventResult.ignored;

View File

@ -1,6 +1,7 @@
import 'package:flowy_editor/document/node.dart'; import 'package:flowy_editor/document/node.dart';
import 'package:flowy_editor/document/position.dart'; import 'package:flowy_editor/document/position.dart';
import 'package:flowy_editor/document/selection.dart'; import 'package:flowy_editor/document/selection.dart';
import 'package:flowy_editor/render/selection/selectable.dart';
import 'package:flowy_editor/render/selection/cursor_widget.dart'; import 'package:flowy_editor/render/selection/cursor_widget.dart';
import 'package:flowy_editor/render/selection/flowy_selection_widget.dart'; import 'package:flowy_editor/render/selection/flowy_selection_widget.dart';
import 'package:flowy_editor/extensions/object_extensions.dart'; import 'package:flowy_editor/extensions/object_extensions.dart';
@ -26,6 +27,10 @@ mixin FlowySelectionService<T extends StatefulWidget> on State<T> {
/// ///
void clearSelection(); void clearSelection();
List<Rect> rects();
hit(Offset? offset);
/// ///
List<Node> getNodesInSelection(Selection selection); List<Node> getNodesInSelection(Selection selection);
@ -108,6 +113,8 @@ class _FlowySelectionState extends State<FlowySelection>
/// Tap /// Tap
Offset? tapOffset; Offset? tapOffset;
final List<Rect> _rects = [];
EditorState get editorState => widget.editorState; EditorState get editorState => widget.editorState;
@override @override
@ -144,8 +151,13 @@ class _FlowySelectionState extends State<FlowySelection>
); );
} }
List<Rect> rects() {
return _rects;
}
@override @override
void updateSelection(Selection selection) { void updateSelection(Selection selection) {
_rects.clear();
_clearSelection(); _clearSelection();
// cursor // cursor
@ -245,19 +257,30 @@ class _FlowySelectionState extends State<FlowySelection>
tapOffset = details.globalPosition; tapOffset = details.globalPosition;
final nodes = getNodesInRange(tapOffset!); hit(tapOffset);
if (nodes.isNotEmpty) { }
@override
hit(Offset? offset) {
if (offset == null) {
editorState.updateCursorSelection(null);
return;
}
final nodes = getNodesInRange(offset);
if (nodes.isEmpty) {
editorState.updateCursorSelection(null);
return;
}
assert(nodes.length == 1); assert(nodes.length == 1);
final selectable = nodes.first.selectable; final selectable = nodes.first.selectable;
if (selectable != null) { if (selectable == null) {
final position = selectable.getPositionInOffset(tapOffset!); editorState.updateCursorSelection(null);
return;
}
final position = selectable.getPositionInOffset(offset);
final selection = Selection.collapsed(position); final selection = Selection.collapsed(position);
editorState.updateCursorSelection(selection); editorState.updateCursorSelection(selection);
} }
} else {
editorState.updateCursorSelection(null);
}
}
void _onPanStart(DragStartDetails details) { void _onPanStart(DragStartDetails details) {
// clear old state. // clear old state.
@ -353,6 +376,7 @@ class _FlowySelectionState extends State<FlowySelection>
final rects = selectable.getRectsInSelection(newSelection); final rects = selectable.getRectsInSelection(newSelection);
for (final rect in rects) { for (final rect in rects) {
_rects.add(_transformRectToGlobal(selectable, rect));
final overlay = OverlayEntry( final overlay = OverlayEntry(
builder: ((context) => SelectionWidget( builder: ((context) => SelectionWidget(
color: widget.selectionColor, color: widget.selectionColor,
@ -367,6 +391,11 @@ class _FlowySelectionState extends State<FlowySelection>
Overlay.of(context)?.insertAll(_selectionOverlays); Overlay.of(context)?.insertAll(_selectionOverlays);
} }
Rect _transformRectToGlobal(Selectable selectable, Rect r) {
final Offset topLeft = selectable.localToGlobal(Offset(r.left, r.top));
return Rect.fromLTWH(topLeft.dx, topLeft.dy, r.width, r.height);
}
void _updateCursor(Position position) { void _updateCursor(Position position) {
final node = editorState.document.root.childAtPath(position.path); final node = editorState.document.root.childAtPath(position.path);
@ -380,6 +409,7 @@ class _FlowySelectionState extends State<FlowySelection>
final selectable = node.selectable; final selectable = node.selectable;
final rect = selectable?.getCursorRectInPosition(position); final rect = selectable?.getCursorRectInPosition(position);
if (rect != null) { if (rect != null) {
_rects.add(_transformRectToGlobal(selectable!, rect));
final cursor = OverlayEntry( final cursor = OverlayEntry(
builder: ((context) => CursorWidget( builder: ((context) => CursorWidget(
key: _cursorKey, key: _cursorKey,
@ -390,9 +420,15 @@ class _FlowySelectionState extends State<FlowySelection>
); );
_cursorOverlays.add(cursor); _cursorOverlays.add(cursor);
Overlay.of(context)?.insertAll(_cursorOverlays); Overlay.of(context)?.insertAll(_cursorOverlays);
_forceShowCursor();
} }
} }
_forceShowCursor() {
final currentState = _cursorKey.currentState as CursorWidgetState?;
currentState?.show();
}
List<Node> _selectedNodesInSelection(Node node, Selection selection) { List<Node> _selectedNodesInSelection(Node node, Selection selection) {
List<Node> result = []; List<Node> result = [];
if (node.parent != null) { if (node.parent != null) {