Skip to content

Commit

Permalink
feat: expose method to render drop target
Browse files Browse the repository at this point in the history
  • Loading branch information
Xazin committed Jul 25, 2024
1 parent 61d4363 commit a0add4b
Show file tree
Hide file tree
Showing 5 changed files with 122 additions and 16 deletions.
23 changes: 18 additions & 5 deletions example/lib/pages/editor.dart
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import 'dart:convert';

import 'package:appflowy_editor/appflowy_editor.dart';
import 'package:flutter/material.dart';

import 'package:example/pages/desktop_editor.dart';
import 'package:example/pages/mobile_editor.dart';
import 'package:flutter/material.dart';

import 'package:appflowy_editor/appflowy_editor.dart';

class Editor extends StatefulWidget {
const Editor({
Expand Down Expand Up @@ -110,9 +112,20 @@ class _EditorState extends State<Editor> {
}

if (PlatformExtension.isDesktopOrWeb) {
return DesktopEditor(
editorState: editorState!,
textDirection: widget.textDirection,
return GestureDetector(
behavior: HitTestBehavior.opaque,
onPanStart: (details) {
editorState?.selectionService
.renderDropTargetForOffset(details.localPosition);
},
onPanUpdate: (details) {
editorState?.selectionService
.renderDropTargetForOffset(details.localPosition);
},
child: DesktopEditor(
editorState: editorState!,
textDirection: widget.textDirection,
),
);
} else if (PlatformExtension.isMobile) {
return MobileEditor(editorState: editorState!);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';

import 'package:provider/provider.dart';

import 'package:appflowy_editor/appflowy_editor.dart';
import 'package:appflowy_editor/src/editor/editor_component/service/selection/mobile_selection_service.dart';
import 'package:appflowy_editor/src/editor/editor_component/service/selection/shared.dart';
import 'package:appflowy_editor/src/flutter/overlay.dart';
import 'package:appflowy_editor/src/service/selection/selection_gesture.dart';
import 'package:flutter/material.dart' hide Overlay, OverlayEntry;
import 'package:flutter/services.dart';
import 'package:provider/provider.dart';

class DesktopSelectionServiceWidget extends StatefulWidget {
const DesktopSelectionServiceWidget({
Expand Down Expand Up @@ -50,6 +51,8 @@ class _DesktopSelectionServiceWidgetState

Position? _panStartPosition;

OverlayEntry? _dropTargetEntry;

late EditorState editorState = Provider.of<EditorState>(
context,
listen: false,
Expand Down Expand Up @@ -83,7 +86,7 @@ class _DesktopSelectionServiceWidgetState
WidgetsBinding.instance.removeObserver(this);
editorState.selectionNotifier.removeListener(_updateSelection);
currentSelection.dispose();

removeDropTarget();
super.dispose();
}

Expand Down Expand Up @@ -403,7 +406,7 @@ class _DesktopSelectionServiceWidgetState
);

_contextMenuAreas.add(contextMenu);
Overlay.of(context, rootOverlay: true)?.insert(contextMenu);
Overlay.of(context, rootOverlay: true).insert(contextMenu);
}

@override
Expand All @@ -415,4 +418,62 @@ class _DesktopSelectionServiceWidgetState
void unregisterGestureInterceptor(String key) {
_interceptors.removeWhere((element) => element.key == key);
}

@override
void removeDropTarget() {
_dropTargetEntry?.remove();
_dropTargetEntry = null;
}

@override
Node? renderDropTargetForOffset(Offset offset) {
removeDropTarget();

final node = getNodeInOffset(offset);
final selectable = node?.selectable;
if (selectable == null) {
return null;
}

final blockRect = selectable.getBlockRect();
final startRect = blockRect.topLeft;
final endRect = blockRect.bottomLeft;

final startDistance = (startRect - offset).distance;
final endDistance = (endRect - offset).distance;

final isCloserToStart = startDistance < endDistance;

_dropTargetEntry = OverlayEntry(
builder: (context) {
final overlayRenderBox =
Overlay.of(context).context.findRenderObject() as RenderBox;
final editorRenderBox =
selectable.context.findRenderObject() as RenderBox;

final editorOffset = editorRenderBox.localToGlobal(
Offset.zero,
ancestor: overlayRenderBox,
);

final indicatorTop =
(isCloserToStart ? startRect.dy : endRect.dy) + editorOffset.dy;

final width = blockRect.topRight.dx - startRect.dx;
return Positioned(
top: indicatorTop,
left: startRect.dx + editorOffset.dx,
child: Container(
height: 3,
width: width,
color: Colors.blue,
),
);
},
);

Overlay.of(context).insert(_dropTargetEntry!);

return isCloserToStart ? node : node!.next ?? node;
}
}
Original file line number Diff line number Diff line change
@@ -1,16 +1,18 @@
import 'dart:async';
import 'dart:io';

import 'package:flutter/material.dart' hide Overlay, OverlayEntry;
import 'package:flutter/services.dart';

import 'package:provider/provider.dart';

import 'package:appflowy_editor/appflowy_editor.dart';
import 'package:appflowy_editor/src/editor/editor_component/service/selection/mobile_magnifier.dart';
import 'package:appflowy_editor/src/editor/editor_component/service/selection/shared.dart';
import 'package:appflowy_editor/src/render/selection/mobile_basic_handle.dart';
import 'package:appflowy_editor/src/render/selection/mobile_collapsed_handle.dart';
import 'package:appflowy_editor/src/render/selection/mobile_selection_handle.dart';
import 'package:appflowy_editor/src/service/selection/mobile_selection_gesture.dart';
import 'package:flutter/material.dart' hide Overlay, OverlayEntry;
import 'package:flutter/services.dart';
import 'package:provider/provider.dart';

/// only used in mobile
///
Expand Down Expand Up @@ -825,4 +827,12 @@ class _MobileSelectionServiceWidgetState
}
return false;
}

@override
void removeDropTarget() {
// Do nothing on mobile
}

@override
Node? renderDropTargetForOffset(Offset offset) => null;
}
13 changes: 13 additions & 0 deletions lib/src/editor/editor_component/service/selection_service.dart
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,19 @@ abstract class AppFlowySelectionService {
DragEndDetails details,
MobileSelectionDragMode mode,
);

/// Draws a horizontal line between the nearest nodes to the [offset].
///
/// The [offset] must be under the global coordinate system.
/// Additionally if any is found, it returns the [Node] that is closest to the [offset].
///
/// Should call [removeDropTarget] to remove the line once drop is done.
///
Node? renderDropTargetForOffset(Offset offset);

/// Removes the horizontal line drawn by [renderDropTargetForOffset].
///
void removeDropTarget();
}

class SelectionGestureInterceptor {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import 'package:flutter/material.dart' hide Overlay, OverlayEntry;

import 'package:provider/provider.dart';

import 'package:appflowy_editor/appflowy_editor.dart';
import 'package:appflowy_editor/src/editor/editor_component/service/selection/desktop_selection_service.dart';
import 'package:appflowy_editor/src/editor/editor_component/service/selection/mobile_selection_service.dart';
import 'package:flutter/material.dart' hide Overlay, OverlayEntry;
import 'package:provider/provider.dart';

class SelectionServiceWidget extends StatefulWidget {
const SelectionServiceWidget({
Expand Down Expand Up @@ -114,4 +116,11 @@ class _SelectionServiceWidgetState extends State<SelectionServiceWidget>
MobileSelectionDragMode mode,
) =>
forward.onPanEnd(details, mode);

@override
void removeDropTarget() => forward.removeDropTarget();

@override
Node? renderDropTargetForOffset(Offset offset) =>
forward.renderDropTargetForOffset(offset);
}

0 comments on commit a0add4b

Please sign in to comment.