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);
|
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
|
@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) &&
|
||||||
|
@ -21,6 +21,10 @@ mixin Selectable<T extends StatefulWidget> on State<T> {
|
|||||||
///
|
///
|
||||||
/// The return result must be an offset of the local coordinate system.
|
/// The return result must be an offset of the local coordinate system.
|
||||||
Position getPositionInOffset(Offset start);
|
Position getPositionInOffset(Offset start);
|
||||||
|
Selection? getWorldBoundaryInOffset(Offset start) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
Rect getCursorRectInPosition(Position position);
|
Rect getCursorRectInPosition(Position position);
|
||||||
|
|
||||||
Offset localToGlobal(Offset offset);
|
Offset localToGlobal(Offset offset);
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
import 'dart:async';
|
||||||
|
|
||||||
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';
|
||||||
@ -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/render/selection/flowy_selection_widget.dart';
|
||||||
import 'package:flowy_editor/extensions/object_extensions.dart';
|
import 'package:flowy_editor/extensions/object_extensions.dart';
|
||||||
import 'package:flowy_editor/extensions/node_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/service/shortcut_service.dart';
|
||||||
import 'package:flowy_editor/editor_state.dart';
|
import 'package:flowy_editor/editor_state.dart';
|
||||||
|
|
||||||
import 'package:flutter/gestures.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
/// Process selection and cursor
|
/// Process selection and cursor
|
||||||
@ -99,6 +101,92 @@ class FlowySelection extends StatefulWidget {
|
|||||||
State<FlowySelection> createState() => _FlowySelectionState();
|
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>
|
class _FlowySelectionState extends State<FlowySelection>
|
||||||
with FlowySelectionService, WidgetsBindingObserver {
|
with FlowySelectionService, WidgetsBindingObserver {
|
||||||
final _cursorKey = GlobalKey(debugLabel: 'cursor');
|
final _cursorKey = GlobalKey(debugLabel: 'cursor');
|
||||||
@ -152,27 +240,12 @@ class _FlowySelectionState extends State<FlowySelection>
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return RawGestureDetector(
|
return _SelectionGestureDetector(
|
||||||
behavior: HitTestBehavior.translucent,
|
onPanStart: _onPanStart,
|
||||||
gestures: {
|
onPanUpdate: _onPanUpdate,
|
||||||
PanGestureRecognizer:
|
onPanEnd: _onPanEnd,
|
||||||
GestureRecognizerFactoryWithHandlers<PanGestureRecognizer>(
|
onTapDown: _onTapDown,
|
||||||
() => PanGestureRecognizer(),
|
onDoubleTapDown: _onDoubleTapDown,
|
||||||
(recognizer) {
|
|
||||||
recognizer
|
|
||||||
..onStart = _onPanStart
|
|
||||||
..onUpdate = _onPanUpdate
|
|
||||||
..onEnd = _onPanEnd;
|
|
||||||
},
|
|
||||||
),
|
|
||||||
TapGestureRecognizer:
|
|
||||||
GestureRecognizerFactoryWithHandlers<TapGestureRecognizer>(
|
|
||||||
() => TapGestureRecognizer(),
|
|
||||||
(recognizer) {
|
|
||||||
recognizer.onTapDown = _onTapDown;
|
|
||||||
},
|
|
||||||
)
|
|
||||||
},
|
|
||||||
child: widget.child,
|
child: widget.child,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -278,6 +351,22 @@ class _FlowySelectionState extends State<FlowySelection>
|
|||||||
return false;
|
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) {
|
void _onTapDown(TapDownDetails details) {
|
||||||
// clear old state.
|
// clear old state.
|
||||||
panStartOffset = null;
|
panStartOffset = null;
|
||||||
|
Loading…
Reference in New Issue
Block a user