mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2024-08-30 18:12:39 +00:00
feat: double tap on text
This commit is contained in:
parent
583d838344
commit
2a09f69bec
@ -108,6 +108,16 @@ class _FlowyRichTextState extends State<FlowyRichText> with Selectable {
|
||||
return Position(path: _textNode.path, offset: baseOffset);
|
||||
}
|
||||
|
||||
@override
|
||||
Selection? getWorldBoundaryInOffset(Offset offset) {
|
||||
final localOffset = _renderParagraph.globalToLocal(offset);
|
||||
final textPosition = _renderParagraph.getPositionForOffset(localOffset);
|
||||
final textRange = _renderParagraph.getWordBoundary(textPosition);
|
||||
final start = Position(path: _textNode.path, offset: textRange.start);
|
||||
final end = Position(path: _textNode.path, offset: textRange.end);
|
||||
return Selection(start: start, end: end);
|
||||
}
|
||||
|
||||
@override
|
||||
List<Rect> getRectsInSelection(Selection selection) {
|
||||
assert(pathEquals(selection.start.path, selection.end.path) &&
|
||||
|
@ -21,6 +21,10 @@ mixin Selectable<T extends StatefulWidget> on State<T> {
|
||||
///
|
||||
/// The return result must be an offset of the local coordinate system.
|
||||
Position getPositionInOffset(Offset start);
|
||||
Selection? getWorldBoundaryInOffset(Offset start) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Rect getCursorRectInPosition(Position position);
|
||||
|
||||
Offset localToGlobal(Offset offset);
|
||||
|
@ -1,3 +1,5 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:flowy_editor/document/node.dart';
|
||||
import 'package:flowy_editor/document/position.dart';
|
||||
import 'package:flowy_editor/document/selection.dart';
|
||||
@ -6,10 +8,10 @@ import 'package:flowy_editor/render/selection/cursor_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/node_extensions.dart';
|
||||
import 'package:flutter/gestures.dart';
|
||||
import 'package:flowy_editor/service/shortcut_service.dart';
|
||||
import 'package:flowy_editor/editor_state.dart';
|
||||
|
||||
import 'package:flutter/gestures.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
/// Process selection and cursor
|
||||
@ -99,6 +101,92 @@ class FlowySelection extends StatefulWidget {
|
||||
State<FlowySelection> createState() => _FlowySelectionState();
|
||||
}
|
||||
|
||||
/// Because the flutter's [DoubleTapGestureRecognizer] will block the [TapGestureRecognizer]
|
||||
/// for a while. So we need to implement our own GestureDetector.
|
||||
@immutable
|
||||
class _SelectionGestureDetector extends StatefulWidget {
|
||||
const _SelectionGestureDetector(
|
||||
{Key? key,
|
||||
this.child,
|
||||
this.onTapDown,
|
||||
this.onDoubleTapDown,
|
||||
this.onPanStart,
|
||||
this.onPanUpdate,
|
||||
this.onPanEnd})
|
||||
: super(key: key);
|
||||
|
||||
@override
|
||||
State<_SelectionGestureDetector> createState() =>
|
||||
_SelectionGestureDetectorState();
|
||||
|
||||
final Widget? child;
|
||||
|
||||
final GestureTapDownCallback? onTapDown;
|
||||
final GestureTapDownCallback? onDoubleTapDown;
|
||||
final GestureDragStartCallback? onPanStart;
|
||||
final GestureDragUpdateCallback? onPanUpdate;
|
||||
final GestureDragEndCallback? onPanEnd;
|
||||
}
|
||||
|
||||
class _SelectionGestureDetectorState extends State<_SelectionGestureDetector> {
|
||||
bool _isDoubleTap = false;
|
||||
Timer? _doubleTapTimer;
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return RawGestureDetector(
|
||||
behavior: HitTestBehavior.translucent,
|
||||
gestures: {
|
||||
PanGestureRecognizer:
|
||||
GestureRecognizerFactoryWithHandlers<PanGestureRecognizer>(
|
||||
() => PanGestureRecognizer(),
|
||||
(recognizer) {
|
||||
recognizer
|
||||
..onStart = widget.onPanStart
|
||||
..onUpdate = widget.onPanUpdate
|
||||
..onEnd = widget.onPanEnd;
|
||||
},
|
||||
),
|
||||
TapGestureRecognizer:
|
||||
GestureRecognizerFactoryWithHandlers<TapGestureRecognizer>(
|
||||
() => TapGestureRecognizer(),
|
||||
(recognizer) {
|
||||
recognizer.onTapDown = _tapDownDelegate;
|
||||
},
|
||||
),
|
||||
},
|
||||
child: widget.child,
|
||||
);
|
||||
}
|
||||
|
||||
_tapDownDelegate(TapDownDetails tapDownDetails) {
|
||||
if (_isDoubleTap) {
|
||||
_isDoubleTap = false;
|
||||
_doubleTapTimer?.cancel();
|
||||
_doubleTapTimer = null;
|
||||
if (widget.onDoubleTapDown != null) {
|
||||
widget.onDoubleTapDown!(tapDownDetails);
|
||||
}
|
||||
} else {
|
||||
if (widget.onTapDown != null) {
|
||||
widget.onTapDown!(tapDownDetails);
|
||||
}
|
||||
|
||||
_isDoubleTap = true;
|
||||
_doubleTapTimer?.cancel();
|
||||
_doubleTapTimer = Timer(kDoubleTapTimeout, () {
|
||||
_isDoubleTap = false;
|
||||
_doubleTapTimer = null;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_doubleTapTimer?.cancel();
|
||||
super.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
class _FlowySelectionState extends State<FlowySelection>
|
||||
with FlowySelectionService, WidgetsBindingObserver {
|
||||
final _cursorKey = GlobalKey(debugLabel: 'cursor');
|
||||
@ -152,27 +240,12 @@ class _FlowySelectionState extends State<FlowySelection>
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return RawGestureDetector(
|
||||
behavior: HitTestBehavior.translucent,
|
||||
gestures: {
|
||||
PanGestureRecognizer:
|
||||
GestureRecognizerFactoryWithHandlers<PanGestureRecognizer>(
|
||||
() => PanGestureRecognizer(),
|
||||
(recognizer) {
|
||||
recognizer
|
||||
..onStart = _onPanStart
|
||||
..onUpdate = _onPanUpdate
|
||||
..onEnd = _onPanEnd;
|
||||
},
|
||||
),
|
||||
TapGestureRecognizer:
|
||||
GestureRecognizerFactoryWithHandlers<TapGestureRecognizer>(
|
||||
() => TapGestureRecognizer(),
|
||||
(recognizer) {
|
||||
recognizer.onTapDown = _onTapDown;
|
||||
},
|
||||
)
|
||||
},
|
||||
return _SelectionGestureDetector(
|
||||
onPanStart: _onPanStart,
|
||||
onPanUpdate: _onPanUpdate,
|
||||
onPanEnd: _onPanEnd,
|
||||
onTapDown: _onTapDown,
|
||||
onDoubleTapDown: _onDoubleTapDown,
|
||||
child: widget.child,
|
||||
);
|
||||
}
|
||||
@ -278,6 +351,22 @@ class _FlowySelectionState extends State<FlowySelection>
|
||||
return false;
|
||||
}
|
||||
|
||||
void _onDoubleTapDown(TapDownDetails details) {
|
||||
final offset = details.globalPosition;
|
||||
final nodes = getNodesInRange(offset);
|
||||
if (nodes.isEmpty) {
|
||||
editorState.updateCursorSelection(null);
|
||||
return;
|
||||
}
|
||||
final selectable = nodes.first.selectable;
|
||||
if (selectable == null) {
|
||||
editorState.updateCursorSelection(null);
|
||||
return;
|
||||
}
|
||||
editorState
|
||||
.updateCursorSelection(selectable.getWorldBoundaryInOffset(offset));
|
||||
}
|
||||
|
||||
void _onTapDown(TapDownDetails details) {
|
||||
// clear old state.
|
||||
panStartOffset = null;
|
||||
|
Loading…
Reference in New Issue
Block a user