feat: render drop target (#848)
* feat: expose method to render drop target * feat: add style to drop target * feat: add DropTargetRenderData for more concise control * fix: split up rendering and data fetching * chore: clean unused code * chore: remove unused example code for testing
This commit is contained in:
parent
61d4363b46
commit
819e579afd
8 changed files with 240 additions and 15 deletions
|
|
@ -1,9 +1,11 @@
|
||||||
import 'dart:convert';
|
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/desktop_editor.dart';
|
||||||
import 'package:example/pages/mobile_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 {
|
class Editor extends StatefulWidget {
|
||||||
const Editor({
|
const Editor({
|
||||||
|
|
|
||||||
|
|
@ -27,3 +27,4 @@ export 'style/default_text_span_decorator_for_attribute.dart';
|
||||||
// style
|
// style
|
||||||
export 'style/editor_style.dart';
|
export 'style/editor_style.dart';
|
||||||
export 'style/mobile_text_span_decorator_for_attribute.dart';
|
export 'style/mobile_text_span_decorator_for_attribute.dart';
|
||||||
|
export 'style/drop_target_style.dart';
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,12 @@
|
||||||
import 'dart:math';
|
import 'dart:math';
|
||||||
|
|
||||||
|
import 'package:flutter/material.dart' hide Overlay, OverlayEntry;
|
||||||
|
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
import 'package:appflowy_editor/appflowy_editor.dart';
|
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||||
import 'package:appflowy_editor/src/flutter/overlay.dart';
|
import 'package:appflowy_editor/src/flutter/overlay.dart';
|
||||||
import 'package:appflowy_editor/src/service/context_menu/built_in_context_menu_item.dart';
|
import 'package:appflowy_editor/src/service/context_menu/built_in_context_menu_item.dart';
|
||||||
import 'package:flutter/material.dart' hide Overlay, OverlayEntry;
|
|
||||||
import 'package:provider/provider.dart';
|
|
||||||
|
|
||||||
// workaround for the issue:
|
// workaround for the issue:
|
||||||
// the popover will grab the focus even if it's inside the editor
|
// the popover will grab the focus even if it's inside the editor
|
||||||
|
|
@ -40,6 +42,7 @@ class AppFlowyEditor extends StatefulWidget {
|
||||||
this.focusNode,
|
this.focusNode,
|
||||||
this.enableAutoComplete = false,
|
this.enableAutoComplete = false,
|
||||||
this.autoCompleteTextProvider,
|
this.autoCompleteTextProvider,
|
||||||
|
this.dropTargetStyle,
|
||||||
}) : blockComponentBuilders =
|
}) : blockComponentBuilders =
|
||||||
blockComponentBuilders ?? standardBlockComponentBuilderMap,
|
blockComponentBuilders ?? standardBlockComponentBuilderMap,
|
||||||
characterShortcutEvents =
|
characterShortcutEvents =
|
||||||
|
|
@ -177,6 +180,21 @@ class AppFlowyEditor extends StatefulWidget {
|
||||||
/// {@macro flutter.widgets.editableText.contentInsertionConfiguration}
|
/// {@macro flutter.widgets.editableText.contentInsertionConfiguration}
|
||||||
final ContentInsertionConfiguration? contentInsertionConfiguration;
|
final ContentInsertionConfiguration? contentInsertionConfiguration;
|
||||||
|
|
||||||
|
/// The style of the drop target.
|
||||||
|
///
|
||||||
|
/// Defaults to [AppFlowyDropTargetStyle].
|
||||||
|
///
|
||||||
|
/// The drop target is rendered in the [AppFlowyEditor] using the [DesktopSelectionService]
|
||||||
|
/// specifically the [renderDropTargetForOffset] method.
|
||||||
|
///
|
||||||
|
/// The drop target is a horizontal line that is drawn between the nearest nodes to the [offset].
|
||||||
|
///
|
||||||
|
/// Should call [removeDropTarget] to remove the line once the drop action is done.
|
||||||
|
///
|
||||||
|
/// only works on desktop.
|
||||||
|
///
|
||||||
|
final AppFlowyDropTargetStyle? dropTargetStyle;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<AppFlowyEditor> createState() => _AppFlowyEditorState();
|
State<AppFlowyEditor> createState() => _AppFlowyEditorState();
|
||||||
}
|
}
|
||||||
|
|
@ -283,6 +301,7 @@ class _AppFlowyEditorState extends State<AppFlowyEditor> {
|
||||||
selectionColor: widget.editorStyle.selectionColor,
|
selectionColor: widget.editorStyle.selectionColor,
|
||||||
showMagnifier: widget.showMagnifier,
|
showMagnifier: widget.showMagnifier,
|
||||||
contextMenuItems: widget.contextMenuItems,
|
contextMenuItems: widget.contextMenuItems,
|
||||||
|
dropTargetStyle: widget.dropTargetStyle,
|
||||||
child: child,
|
child: child,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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/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/mobile_selection_service.dart';
|
||||||
import 'package:appflowy_editor/src/editor/editor_component/service/selection/shared.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: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 {
|
class DesktopSelectionServiceWidget extends StatefulWidget {
|
||||||
const DesktopSelectionServiceWidget({
|
const DesktopSelectionServiceWidget({
|
||||||
|
|
@ -14,12 +15,14 @@ class DesktopSelectionServiceWidget extends StatefulWidget {
|
||||||
this.selectionColor = const Color(0xFF00BCF0),
|
this.selectionColor = const Color(0xFF00BCF0),
|
||||||
this.contextMenuItems,
|
this.contextMenuItems,
|
||||||
required this.child,
|
required this.child,
|
||||||
|
this.dropTargetStyle = const AppFlowyDropTargetStyle(),
|
||||||
});
|
});
|
||||||
|
|
||||||
final Widget child;
|
final Widget child;
|
||||||
final Color cursorColor;
|
final Color cursorColor;
|
||||||
final Color selectionColor;
|
final Color selectionColor;
|
||||||
final List<List<ContextMenuItem>>? contextMenuItems;
|
final List<List<ContextMenuItem>>? contextMenuItems;
|
||||||
|
final AppFlowyDropTargetStyle dropTargetStyle;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<DesktopSelectionServiceWidget> createState() =>
|
State<DesktopSelectionServiceWidget> createState() =>
|
||||||
|
|
@ -50,6 +53,8 @@ class _DesktopSelectionServiceWidgetState
|
||||||
|
|
||||||
Position? _panStartPosition;
|
Position? _panStartPosition;
|
||||||
|
|
||||||
|
OverlayEntry? _dropTargetEntry;
|
||||||
|
|
||||||
late EditorState editorState = Provider.of<EditorState>(
|
late EditorState editorState = Provider.of<EditorState>(
|
||||||
context,
|
context,
|
||||||
listen: false,
|
listen: false,
|
||||||
|
|
@ -83,7 +88,7 @@ class _DesktopSelectionServiceWidgetState
|
||||||
WidgetsBinding.instance.removeObserver(this);
|
WidgetsBinding.instance.removeObserver(this);
|
||||||
editorState.selectionNotifier.removeListener(_updateSelection);
|
editorState.selectionNotifier.removeListener(_updateSelection);
|
||||||
currentSelection.dispose();
|
currentSelection.dispose();
|
||||||
|
removeDropTarget();
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -403,7 +408,7 @@ class _DesktopSelectionServiceWidgetState
|
||||||
);
|
);
|
||||||
|
|
||||||
_contextMenuAreas.add(contextMenu);
|
_contextMenuAreas.add(contextMenu);
|
||||||
Overlay.of(context, rootOverlay: true)?.insert(contextMenu);
|
Overlay.of(context, rootOverlay: true).insert(contextMenu);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|
@ -415,4 +420,89 @@ class _DesktopSelectionServiceWidgetState
|
||||||
void unregisterGestureInterceptor(String key) {
|
void unregisterGestureInterceptor(String key) {
|
||||||
_interceptors.removeWhere((element) => element.key == key);
|
_interceptors.removeWhere((element) => element.key == key);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void removeDropTarget() {
|
||||||
|
_dropTargetEntry?.remove();
|
||||||
|
_dropTargetEntry = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void renderDropTargetForOffset(Offset offset) {
|
||||||
|
removeDropTarget();
|
||||||
|
|
||||||
|
final node = getNodeInOffset(offset);
|
||||||
|
final selectable = node?.selectable;
|
||||||
|
if (selectable == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
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: widget.dropTargetStyle.height,
|
||||||
|
width: width,
|
||||||
|
margin: widget.dropTargetStyle.margin,
|
||||||
|
constraints: widget.dropTargetStyle.constraints,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
borderRadius:
|
||||||
|
BorderRadius.circular(widget.dropTargetStyle.borderRadius),
|
||||||
|
color: widget.dropTargetStyle.color,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
Overlay.of(context).insert(_dropTargetEntry!);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
DropTargetRenderData? getDropTargetRenderData(Offset offset) {
|
||||||
|
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;
|
||||||
|
|
||||||
|
return DropTargetRenderData(
|
||||||
|
dropTarget: isCloserToStart ? node : node?.next ?? node,
|
||||||
|
cursorNode: node,
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,11 @@
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'dart:io';
|
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/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/mobile_magnifier.dart';
|
||||||
import 'package:appflowy_editor/src/editor/editor_component/service/selection/shared.dart';
|
import 'package:appflowy_editor/src/editor/editor_component/service/selection/shared.dart';
|
||||||
|
|
@ -8,9 +13,6 @@ 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_collapsed_handle.dart';
|
||||||
import 'package:appflowy_editor/src/render/selection/mobile_selection_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: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
|
/// only used in mobile
|
||||||
///
|
///
|
||||||
|
|
@ -825,4 +827,17 @@ class _MobileSelectionServiceWidgetState
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void removeDropTarget() {
|
||||||
|
// Do nothing on mobile
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void renderDropTargetForOffset(Offset offset) {
|
||||||
|
// Do nothing on mobile
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
DropTargetRenderData? getDropTargetRenderData(Offset offset) => null;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -82,6 +82,22 @@ abstract class AppFlowySelectionService {
|
||||||
DragEndDetails details,
|
DragEndDetails details,
|
||||||
MobileSelectionDragMode mode,
|
MobileSelectionDragMode mode,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
/// Draws a horizontal line between the nearest nodes to the [offset].
|
||||||
|
///
|
||||||
|
/// The [offset] must be under the global coordinate system.
|
||||||
|
///
|
||||||
|
/// Should call [removeDropTarget] to remove the line once drop is done.
|
||||||
|
///
|
||||||
|
void renderDropTargetForOffset(Offset offset);
|
||||||
|
|
||||||
|
/// Removes the horizontal line drawn by [renderDropTargetForOffset].
|
||||||
|
///
|
||||||
|
void removeDropTarget();
|
||||||
|
|
||||||
|
/// Returns the [DropTargetRenderData] for the [offset].
|
||||||
|
///
|
||||||
|
DropTargetRenderData? getDropTargetRenderData(Offset offset);
|
||||||
}
|
}
|
||||||
|
|
||||||
class SelectionGestureInterceptor {
|
class SelectionGestureInterceptor {
|
||||||
|
|
@ -102,3 +118,28 @@ class SelectionGestureInterceptor {
|
||||||
bool Function(DragUpdateDetails details)? canPanUpdate;
|
bool Function(DragUpdateDetails details)? canPanUpdate;
|
||||||
bool Function(DragEndDetails details)? canPanEnd;
|
bool Function(DragEndDetails details)? canPanEnd;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Data returned when calling [renderDropTargetForOffset]
|
||||||
|
///
|
||||||
|
/// Includes the [Node] which the drop target is rendered for
|
||||||
|
/// and the [Node] which the cursor is directly hovering over.
|
||||||
|
///
|
||||||
|
class DropTargetRenderData {
|
||||||
|
const DropTargetRenderData({this.dropTarget, this.cursorNode});
|
||||||
|
|
||||||
|
/// The [Node] which the drop is rendered for,
|
||||||
|
/// this is also the [Node] in which any content should be
|
||||||
|
/// inserted into.
|
||||||
|
///
|
||||||
|
final Node? dropTarget;
|
||||||
|
|
||||||
|
/// The [Node] which the cursor is directly hovering over,
|
||||||
|
/// this might be the same as [dropTarget] but might also
|
||||||
|
/// be another [Node] if the cursor is between two [Node]s.
|
||||||
|
///
|
||||||
|
/// This is useful in case you want to cancel or pause the drop
|
||||||
|
/// for specific [Node]s, in case they as example implement their
|
||||||
|
/// own drop logic.
|
||||||
|
///
|
||||||
|
final Node? cursorNode;
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -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/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/desktop_selection_service.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/mobile_selection_service.dart';
|
||||||
import 'package:flutter/material.dart' hide Overlay, OverlayEntry;
|
|
||||||
import 'package:provider/provider.dart';
|
|
||||||
|
|
||||||
class SelectionServiceWidget extends StatefulWidget {
|
class SelectionServiceWidget extends StatefulWidget {
|
||||||
const SelectionServiceWidget({
|
const SelectionServiceWidget({
|
||||||
|
|
@ -11,6 +13,7 @@ class SelectionServiceWidget extends StatefulWidget {
|
||||||
this.selectionColor = const Color.fromARGB(53, 111, 201, 231),
|
this.selectionColor = const Color.fromARGB(53, 111, 201, 231),
|
||||||
this.showMagnifier = true,
|
this.showMagnifier = true,
|
||||||
this.contextMenuItems,
|
this.contextMenuItems,
|
||||||
|
this.dropTargetStyle,
|
||||||
required this.child,
|
required this.child,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -18,6 +21,7 @@ class SelectionServiceWidget extends StatefulWidget {
|
||||||
final Color cursorColor;
|
final Color cursorColor;
|
||||||
final Color selectionColor;
|
final Color selectionColor;
|
||||||
final List<List<ContextMenuItem>>? contextMenuItems;
|
final List<List<ContextMenuItem>>? contextMenuItems;
|
||||||
|
final AppFlowyDropTargetStyle? dropTargetStyle;
|
||||||
|
|
||||||
/// Show the magnifier or not.
|
/// Show the magnifier or not.
|
||||||
///
|
///
|
||||||
|
|
@ -45,6 +49,8 @@ class _SelectionServiceWidgetState extends State<SelectionServiceWidget>
|
||||||
cursorColor: widget.cursorColor,
|
cursorColor: widget.cursorColor,
|
||||||
selectionColor: widget.selectionColor,
|
selectionColor: widget.selectionColor,
|
||||||
contextMenuItems: widget.contextMenuItems,
|
contextMenuItems: widget.contextMenuItems,
|
||||||
|
dropTargetStyle:
|
||||||
|
widget.dropTargetStyle ?? const AppFlowyDropTargetStyle(),
|
||||||
child: widget.child,
|
child: widget.child,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -114,4 +120,15 @@ class _SelectionServiceWidgetState extends State<SelectionServiceWidget>
|
||||||
MobileSelectionDragMode mode,
|
MobileSelectionDragMode mode,
|
||||||
) =>
|
) =>
|
||||||
forward.onPanEnd(details, mode);
|
forward.onPanEnd(details, mode);
|
||||||
|
|
||||||
|
@override
|
||||||
|
void removeDropTarget() => forward.removeDropTarget();
|
||||||
|
|
||||||
|
@override
|
||||||
|
void renderDropTargetForOffset(Offset offset) =>
|
||||||
|
forward.renderDropTargetForOffset(offset);
|
||||||
|
|
||||||
|
@override
|
||||||
|
DropTargetRenderData? getDropTargetRenderData(Offset offset) =>
|
||||||
|
forward.getDropTargetRenderData(offset);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
40
lib/src/editor/editor_component/style/drop_target_style.dart
Normal file
40
lib/src/editor/editor_component/style/drop_target_style.dart
Normal file
|
|
@ -0,0 +1,40 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
/// Style for the Drop target which is rendered in the [AppFlowyEditor]
|
||||||
|
/// using the [DesktopSelectionService] specifically the [renderDropTargetForOffset] method.
|
||||||
|
///
|
||||||
|
class AppFlowyDropTargetStyle {
|
||||||
|
const AppFlowyDropTargetStyle({
|
||||||
|
this.margin,
|
||||||
|
this.constraints = const BoxConstraints(),
|
||||||
|
this.color,
|
||||||
|
this.borderRadius = 8,
|
||||||
|
this.height = 2,
|
||||||
|
});
|
||||||
|
|
||||||
|
/// The margin to apply to the drop target.
|
||||||
|
///
|
||||||
|
/// Useful if you want to add some padding around the drop target.
|
||||||
|
///
|
||||||
|
final EdgeInsets? margin;
|
||||||
|
|
||||||
|
/// Constraints of the drop target
|
||||||
|
///
|
||||||
|
/// Defaults to default [BoxConstraints]
|
||||||
|
///
|
||||||
|
final BoxConstraints constraints;
|
||||||
|
|
||||||
|
/// The color of the drop target (horizontal line)
|
||||||
|
///
|
||||||
|
/// Defaults to [ThemeData.colorScheme.primary]
|
||||||
|
///
|
||||||
|
final Color? color;
|
||||||
|
|
||||||
|
/// Defaults to 8
|
||||||
|
///
|
||||||
|
final double borderRadius;
|
||||||
|
|
||||||
|
/// Defaults to 2
|
||||||
|
///
|
||||||
|
final double height;
|
||||||
|
}
|
||||||
Loading…
Reference in a new issue