diff --git a/lib/src/editor/editor_component/service/selection/desktop_selection_service.dart b/lib/src/editor/editor_component/service/selection/desktop_selection_service.dart index 26c91e70f..be1460ae7 100644 --- a/lib/src/editor/editor_component/service/selection/desktop_selection_service.dart +++ b/lib/src/editor/editor_component/service/selection/desktop_selection_service.dart @@ -6,7 +6,7 @@ import 'package:flutter/material.dart' hide Overlay, OverlayEntry; import 'package:appflowy_editor/src/render/selection/cursor_widget.dart'; import 'package:appflowy_editor/src/render/selection/selection_widget.dart'; -import 'package:appflowy_editor/src/service/selection/desktop_selection_gesture_detector.dart'; +import 'package:appflowy_editor/src/service/selection_gesture_detector/desktop_selection_gesture_detector.dart'; import 'package:provider/provider.dart'; class DesktopSelectionServiceWidget extends StatefulWidget { @@ -478,15 +478,9 @@ class _DesktopSelectionServiceWidgetState _cursorAreas.add(cursorArea); selectionRects.add(selectable.transformRectToGlobal(cursorRect)); Overlay.of(context)?.insertAll(_cursorAreas); - - _forceShowCursor(); } } - void _forceShowCursor() { - _cursorKey.currentState?.unwrapOrNull()?.show(); - } - void _showContextMenu(TapDownDetails details) { _clearContextMenu(); diff --git a/lib/src/editor/editor_component/service/selection/mobile_selection_service.dart b/lib/src/editor/editor_component/service/selection/mobile_selection_service.dart index ce8ae96d2..6c78e5cd9 100644 --- a/lib/src/editor/editor_component/service/selection/mobile_selection_service.dart +++ b/lib/src/editor/editor_component/service/selection/mobile_selection_service.dart @@ -1,12 +1,21 @@ import 'package:appflowy_editor/appflowy_editor.dart'; import 'package:appflowy_editor/src/flutter/overlay.dart'; import 'package:appflowy_editor/src/render/selection/mobile_selection_widget.dart'; -import 'package:appflowy_editor/src/service/selection/mobile_selection_gesture_detector.dart'; +import 'package:appflowy_editor/src/service/selection_gesture_detector/mobile_selection_gesture_detector.dart'; import 'package:flutter/material.dart' hide Overlay, OverlayEntry; import 'package:appflowy_editor/src/render/selection/cursor_widget.dart'; import 'package:provider/provider.dart'; +enum DragMode { + // Dragging cursor + cursor, + // Dragging the left handler + leftHandler, + // Dragging the right handler + rightHandler, +} + class MobileSelectionServiceWidget extends StatefulWidget { const MobileSelectionServiceWidget({ super.key, @@ -62,6 +71,9 @@ class _MobileSelectionServiceWidgetState /// The local Rect for building the right handler Rect? _rightHandlerRect; + /// The enum to decide which widget to move. It is based on the location where user start dragging. + DragMode? _dragMode; + late EditorState editorState = Provider.of( context, listen: false, @@ -90,8 +102,8 @@ class _MobileSelectionServiceWidgetState @override void dispose() { - clearSelection(); WidgetsBinding.instance.removeObserver(this); + clearSelection(); editorState.selectionNotifier.removeListener(_updateSelectionLayers); super.dispose(); } @@ -104,7 +116,8 @@ class _MobileSelectionServiceWidgetState onTap: _onTap, onDoubleTapDown: _onDoubleTapDown, onDoubleTap: _onDoubleTap, - onLongPressMoveUpdate: _onLongPressMoveUpdate, + onPanUpdate: _onPanUpdate, + onPanDown: _onPanDown, child: widget.child, ); } @@ -328,9 +341,6 @@ class _MobileSelectionServiceWidgetState ); _cursorOverlayEntry = cursorEntry; Overlay.of(context)?.insert(cursorEntry); - - // Force cursor always show 100% opacity at the begining - _cursorKey.currentState?.unwrapOrNull()?.show(); } } @@ -421,38 +431,46 @@ class _MobileSelectionServiceWidgetState _doubleTapDownOffset = null; } - void _onLongPressMoveUpdate(LongPressMoveUpdateDetails details) { - Log.selection.debug( - 'onLongPressMoveUpdate global: ${details.globalPosition} local :${details.localPosition}', - ); - - final offset = details.globalPosition; + void _onPanUpdate(DragUpdateDetails details) { + // Utilize _dragMode(record from [onPanStart]) to decide which widget to move(cursor, left handler, right handler) + Log.selection.debug('onPanUpdate: ${details.globalPosition}'); + Log.selection.debug('onPanUpdate: _dragMode: $_dragMode'); + // If user is not dragging, we don't need to do anything. + if (_dragMode == null) return; + // otherwise, move the corresponding widget base on selection final selection = editorState.selection; if (selection == null) return; - // TODO(yijing):Fix the cursor didn't update when dragging in the beginning of the line. - if (selection.isCollapsed) { - if (_isOverCursor(offset) == true) { - final position = getPositionInOffset(offset); - if (position == null) return; - editorState.selection = Selection.collapsed(position); - return; - } - } - if (_isOverLeftHandler(offset) == true) { - final position = getPositionInOffset(offset); + final panPosition = getPositionInOffset(details.globalPosition); + if (panPosition == null) return; + + // Update the selection in editor state base on user's behavior + if (_dragMode == DragMode.cursor && selection.isCollapsed) { + editorState.selection = Selection.collapsed(panPosition); + } else if (_dragMode == DragMode.leftHandler && !selection.isCollapsed) { editorState.selection = editorState.selection!.copyWith( - start: position, + start: panPosition, ); - } - if (_isOverRightHandler(offset) == true) { - final position = getPositionInOffset(offset); - + } else if (_dragMode == DragMode.rightHandler && !selection.isCollapsed) { editorState.selection = editorState.selection!.copyWith( - end: position, + end: panPosition, ); } } + void _onPanDown(DragDownDetails details) { + // set the drag mode base on the user's start dragging position + final offset = details.globalPosition; + if (_isOverCursor(offset) == true) { + _dragMode = DragMode.cursor; + } + if (_isOverLeftHandler(offset) == true) { + _dragMode = DragMode.leftHandler; + } + if (_isOverRightHandler(offset) == true) { + _dragMode = DragMode.rightHandler; + } + } + // The following methods decide if the current position(Offset) is over certain widget(cursor, left handler, right handler) /// Check the point offset is over cursor diff --git a/lib/src/render/selection/cursor_widget.dart b/lib/src/render/selection/cursor_widget.dart index a49f8344d..6bdeb4af3 100644 --- a/lib/src/render/selection/cursor_widget.dart +++ b/lib/src/render/selection/cursor_widget.dart @@ -36,6 +36,15 @@ class CursorWidgetState extends State { timer = _initTimer(); } + @override + void didUpdateWidget(covariant CursorWidget oldWidget) { + super.didUpdateWidget(oldWidget); + // when the cursor position changes(like moving round texts), we need to show the cursor(start from 100% opacity all the time) + if (widget.rect != oldWidget.rect) { + show(); + } + } + @override void dispose() { timer.cancel(); diff --git a/lib/src/service/selection/desktop_selection_gesture_detector.dart b/lib/src/service/selection_gesture_detector/desktop_selection_gesture_detector.dart similarity index 100% rename from lib/src/service/selection/desktop_selection_gesture_detector.dart rename to lib/src/service/selection_gesture_detector/desktop_selection_gesture_detector.dart diff --git a/lib/src/service/selection/mobile_selection_gesture_detector.dart b/lib/src/service/selection_gesture_detector/mobile_selection_gesture_detector.dart similarity index 53% rename from lib/src/service/selection/mobile_selection_gesture_detector.dart rename to lib/src/service/selection_gesture_detector/mobile_selection_gesture_detector.dart index 8556393da..11ed9f43d 100644 --- a/lib/src/service/selection/mobile_selection_gesture_detector.dart +++ b/lib/src/service/selection_gesture_detector/mobile_selection_gesture_detector.dart @@ -12,7 +12,8 @@ class MobileSelectionGestureDetector extends StatefulWidget { this.onTap, this.onDoubleTapDown, this.onDoubleTap, - this.onLongPressMoveUpdate, + this.onPanDown, + this.onPanUpdate, }) : super(key: key); @override @@ -25,7 +26,8 @@ class MobileSelectionGestureDetector extends StatefulWidget { final GestureTapCallback? onTap; final GestureTapDownCallback? onDoubleTapDown; final GestureDoubleTapCallback? onDoubleTap; - final GestureLongPressMoveUpdateCallback? onLongPressMoveUpdate; + final GestureDragDownCallback? onPanDown; + final GestureDragUpdateCallback? onPanUpdate; } class MobileSelectionGestureDetectorState @@ -33,37 +35,23 @@ class MobileSelectionGestureDetectorState @override Widget build(BuildContext context) { // TODO(yijing): Needs to refactor to add triple tap guesture, temporarily use GestureDetector here - // All the unused gesture is filled with debuging info for now. return GestureDetector( behavior: HitTestBehavior.translucent, - onTapDown: (tapdetail) => Log.selection.debug( - 'onTapDown global: ${tapdetail.globalPosition} local :${tapdetail.localPosition} }', - ), + // onTapDown: (tapdetail) => Log.selection.debug( + // 'onTapDown global: ${tapdetail.globalPosition} local :${tapdetail.localPosition} }', + // ), onTapUp: widget.onTapUp, onTap: widget.onTap, onDoubleTapDown: widget.onDoubleTapDown, onDoubleTap: widget.onDoubleTap, - onDoubleTapCancel: () => Log.selection.debug('onDoubleTapCancel'), - onLongPressMoveUpdate: widget.onLongPressMoveUpdate, - onPanStart: (details) { - Log.selection.debug( - 'onPanStart global: ${details.globalPosition} local :${details.localPosition} }', - ); - }, - onPanUpdate: (details) { - Log.selection.debug( - 'onPanUpdate global: ${details.globalPosition} local :${details.localPosition}', - ); - }, - onPanDown: (details) => Log.selection.debug( - 'onPanDown global: ${details.globalPosition} local :${details.localPosition} }', - ), - onPanEnd: (details) => Log.selection.debug( - 'onPanEnd velocity: ${details.velocity}, local :${details.primaryVelocity}', - ), - onLongPress: () { - Log.selection.debug('onLongPress'); - }, + onPanUpdate: widget.onPanUpdate, + onPanDown: widget.onPanDown, + // onPanEnd: (details) => Log.selection.debug( + // 'onPanEnd velocity: ${details.velocity}, local :${details.primaryVelocity}', + // ), + // onLongPress: () { + // Log.selection.debug('onLongPress'); + // }, child: widget.child, ); }