Skip to content

Commit

Permalink
feat: add dragging handler feature
Browse files Browse the repository at this point in the history
- refactor CursorWidget to show cursor 100% opacity at beginning when it changes
- refactor the MobileSelectionGestureDetector with panDown and panUpdate
  • Loading branch information
hyj1204 committed Aug 29, 2023
1 parent 54293a4 commit f2ee7f8
Show file tree
Hide file tree
Showing 5 changed files with 72 additions and 63 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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<CursorWidgetState>()?.show();
}

void _showContextMenu(TapDownDetails details) {
_clearContextMenu();

Expand Down
Original file line number Diff line number Diff line change
@@ -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,
Expand Down Expand Up @@ -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<EditorState>(
context,
listen: false,
Expand Down Expand Up @@ -90,8 +102,8 @@ class _MobileSelectionServiceWidgetState

@override
void dispose() {
clearSelection();
WidgetsBinding.instance.removeObserver(this);
clearSelection();
editorState.selectionNotifier.removeListener(_updateSelectionLayers);
super.dispose();
}
Expand All @@ -104,7 +116,8 @@ class _MobileSelectionServiceWidgetState
onTap: _onTap,
onDoubleTapDown: _onDoubleTapDown,
onDoubleTap: _onDoubleTap,
onLongPressMoveUpdate: _onLongPressMoveUpdate,
onPanUpdate: _onPanUpdate,
onPanDown: _onPanDown,
child: widget.child,
);
}
Expand Down Expand Up @@ -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<CursorWidgetState>()?.show();
}
}

Expand Down Expand Up @@ -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
Expand Down
9 changes: 9 additions & 0 deletions lib/src/render/selection/cursor_widget.dart
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,15 @@ class CursorWidgetState extends State<CursorWidget> {
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();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@ class MobileSelectionGestureDetector extends StatefulWidget {
this.onTap,
this.onDoubleTapDown,
this.onDoubleTap,
this.onLongPressMoveUpdate,
this.onPanDown,
this.onPanUpdate,
}) : super(key: key);

@override
Expand All @@ -25,45 +26,32 @@ class MobileSelectionGestureDetector extends StatefulWidget {
final GestureTapCallback? onTap;
final GestureTapDownCallback? onDoubleTapDown;
final GestureDoubleTapCallback? onDoubleTap;
final GestureLongPressMoveUpdateCallback? onLongPressMoveUpdate;
final GestureDragDownCallback? onPanDown;
final GestureDragUpdateCallback? onPanUpdate;
}

class MobileSelectionGestureDetectorState
extends State<MobileSelectionGestureDetector> {
@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,
);
}
Expand Down

0 comments on commit f2ee7f8

Please sign in to comment.