diff --git a/example/lib/editor_autocomplete.dart b/example/lib/editor_autocomplete.dart index 7d514d8..6fb00d3 100644 --- a/example/lib/editor_autocomplete.dart +++ b/example/lib/editor_autocomplete.dart @@ -93,7 +93,7 @@ class AutoCompleteEditor extends StatefulWidget { class _AutoCompleteEditorState extends State { - final CodeLineEditingController _controller = CodeLineEditingController(); + final CodeLineEditingController _controller = CodeLineEditingController(contextMenuDelegate: ContextMenuDelegateImpl()); @override void initState() { diff --git a/example/lib/menu.dart b/example/lib/menu.dart index 8778eb1..26ba8d6 100644 --- a/example/lib/menu.dart +++ b/example/lib/menu.dart @@ -35,6 +35,10 @@ class ContextMenuControllerImpl implements SelectionToolbarController { required LayerLink layerLink, required ValueNotifier visibility, }) { + if(controller.contextMenuDelegate != null && !kIsAndroid && !kIsIOS){ + controller.menuController.open(position: anchors.secondaryAnchor); + return; + } showMenu( context: context, position: RelativeRect.fromSize(anchors.primaryAnchor & const Size(150, double.infinity), @@ -62,4 +66,71 @@ class ContextMenuControllerImpl implements SelectionToolbarController { ); } +} + +class ContextMenuDelegateImpl implements ContextMenuDelegate{ + @override + List buildMenuItems({required CodeLineEditingController controller, required BuildContext context}) { + return [ + Padding( + padding: const EdgeInsets.all(6.0), + child: Column(children: [ + + _buildRightMenuItem(context:context, text: 'Cut', onPressed: () {controller.cut();}), + _buildRightMenuItem(context:context, text: 'Copy', onPressed: () {controller.copy();}), + _buildRightMenuItem(context:context, text: 'Paste', onPressed: (){ controller.paste();}), + _buildSubMenu(context: context, text: 'Sub Menu',onPressed: (){}, menuChildren: [_buildRightMenuItem(context:context, text: 'Select All', onPressed: (){ controller.selectAll();})]) + + ],), + ) + ]; + } + + Widget _buildRightMenuItem({ + required BuildContext context, + required String text, + VoidCallback? onPressed, + bool enabled = true, + }){ + final Color textColor = Theme.of(context).brightness == Brightness.light ? const Color.fromRGBO(0, 0, 0, 0.85) : const Color.fromRGBO(255, 255, 255, 0.85); + return MenuItemButton( + style: enabled ? null : Theme.of(context).menuButtonTheme.style?.copyWith(foregroundColor: WidgetStatePropertyAll(textColor.withOpacity(0.5))), + onPressed: enabled ? onPressed : null, + child: SizedBox( + height: 24, + // width: 160, + child: Align( + alignment: Alignment.centerLeft, + child: Text( + text, + strutStyle: const StrutStyle( + fontSize: 11, + leading: 0, + height: 1.1, + // 1.1更居中 + forceStrutHeight: true, // 关键属性 强制改为文字高度 + ), + overflow: TextOverflow.ellipsis, + ),), + ), + ); + } + + Widget _buildSubMenu({ + required BuildContext context, + required String text, + VoidCallback? onPressed, + bool enabled = true, + required List menuChildren, + }) { + return SubmenuButton( + style: Theme.of(context).menuButtonTheme.style?.copyWith( + padding: WidgetStateProperty.all( + const EdgeInsets.only(left: 0.0, right: 10.0), + )), + menuChildren: enabled ? menuChildren : [], + child: Text(text), + ); + } + } \ No newline at end of file diff --git a/lib/src/_code_line.dart b/lib/src/_code_line.dart index d3d8b8c..34c2f35 100644 --- a/lib/src/_code_line.dart +++ b/lib/src/_code_line.dart @@ -19,11 +19,16 @@ class _CodeLineEditingControllerImpl extends ValueNotifier late int _preEditLineIndex; CodeLineEditingValue? _preValue; GlobalKey? _editorKey; + @override + final MenuController menuController = MenuController(); + @override + final ContextMenuDelegate? contextMenuDelegate; _CodeLineEditingControllerImpl({ required CodeLines codeLines, required this.options, this.spanBuilder, + this.contextMenuDelegate, }) : super(CodeLineEditingValue(codeLines: codeLines)) { _cache = _CodeLineEditingCache(this); _preEditLineIndex = -1; @@ -2500,4 +2505,18 @@ class _CodeLineEditingControllerDelegate implements CodeLineEditingController { _delegate.undo(); } + @override + ContextMenuDelegate? get contextMenuDelegate => delegate.contextMenuDelegate; + + @override + MenuController get menuController => delegate.menuController; + +} + +abstract class ContextMenuDelegate { + List buildMenuItems({ + required CodeLineEditingController controller, + required BuildContext context, + }); + } \ No newline at end of file diff --git a/lib/src/_code_selection.dart b/lib/src/_code_selection.dart index 6df7f6d..0077412 100644 --- a/lib/src/_code_selection.dart +++ b/lib/src/_code_selection.dart @@ -60,13 +60,13 @@ class _CodeSelectionGestureDetectorState extends State<_CodeSelectionGestureDete _onMobileLongPressedStart(details.globalPosition); _autoScrollWhenDragging(); } else { - widget.selectionOverlayController.showToolbar(context, details.globalPosition); + widget.selectionOverlayController.showToolbar(context, details.globalPosition,details.localPosition); } widget.selectionOverlayController.showHandle(context); }, onLongPressEnd: (details) { if (_longPressOnSelection != true) { - widget.selectionOverlayController.showToolbar(context, details.globalPosition); + widget.selectionOverlayController.showToolbar(context, details.globalPosition,details.localPosition); } _dragPosition = null; _longPressOnSelection = false; @@ -155,7 +155,13 @@ class _CodeSelectionGestureDetectorState extends State<_CodeSelectionGestureDete _pointerTapPosition = null; }, behavior: widget.behavior ?? HitTestBehavior.translucent, - child: widget.child, + child: MenuAnchor( + consumeOutsideTap: true, + anchorTapClosesMenu: true, + controller: widget.controller.menuController, + menuChildren: widget.controller.contextMenuDelegate == null ? [] : widget.controller.contextMenuDelegate!.buildMenuItems(controller: widget.controller, context: context), + child: widget.child, + ), ), ); } @@ -181,7 +187,7 @@ class _CodeSelectionGestureDetectorState extends State<_CodeSelectionGestureDete kDoubleTapTimeout.inMilliseconds && _pointerTapPosition != null && _pointerTapPosition!.isSamePosition(position)) { _onDoubleTap(position); widget.selectionOverlayController.showHandle(context); - widget.selectionOverlayController.showToolbar(context, position); + widget.selectionOverlayController.showToolbar(context, position ,null); } else { _pointerTapTimestamp = now; _pointerTapPosition = position; @@ -302,7 +308,7 @@ class _CodeSelectionGestureDetectorState extends State<_CodeSelectionGestureDete return; } widget.controller.clearComposing(); - widget.selectionOverlayController.showToolbar(context, details.globalPosition); + widget.selectionOverlayController.showToolbar(context, details.globalPosition,details.localPosition); } void _extendSelection(Offset offset, _SelectionChangedCause cause) { @@ -417,7 +423,7 @@ abstract class _SelectionOverlayController { void hideHandle(); - void showToolbar(BuildContext context, Offset position); + void showToolbar(BuildContext context, Offset position, Offset? localPosition); void hideToolbar(); @@ -451,12 +457,13 @@ class _DesktopSelectionOverlayController implements _SelectionOverlayController } @override - void showToolbar(BuildContext context, Offset? position) { + void showToolbar(BuildContext context, Offset? position ,Offset? localPosition ) { if (position == null) { return; } onShowToolbar(context, TextSelectionToolbarAnchors( - primaryAnchor: position + primaryAnchor: position, + secondaryAnchor: localPosition ), null); } @@ -558,7 +565,7 @@ class _MobileSelectionOverlayController implements _SelectionOverlayController { } @override - void showToolbar(BuildContext context, Offset globalPosition) { + void showToolbar(BuildContext context, Offset globalPosition, Offset? localPosition) { globalPosition = _clampPosition(globalPosition); final Rect editingRegion = Rect.fromPoints( ensureRender.localToGlobal(Offset.zero), @@ -696,7 +703,7 @@ class _MobileSelectionOverlayController implements _SelectionOverlayController { if (position == null) { return; } - showToolbar(_context, position); + showToolbar(_context, position, null); }, onSelectionHandleDragStart: _handleStartHandleDragStart, onSelectionHandleDragUpdate: (details) { @@ -728,7 +735,7 @@ class _MobileSelectionOverlayController implements _SelectionOverlayController { if (position == null) { return; } - showToolbar(_context, position); + showToolbar(_context, position, null); }, onSelectionHandleDragStart: _handleEndHandleDragStart, onSelectionHandleDragUpdate: (details) { @@ -822,7 +829,7 @@ class _MobileSelectionOverlayController implements _SelectionOverlayController { void _handleStartHandleDragEnd(DragEndDetails details) { _startHandleDragging = false; toolbarVisibility.value = true; - showToolbar(_context, _startHandleDragLastPosition); + showToolbar(_context, _startHandleDragLastPosition, null); } void _handleEndHandleDragStart(DragStartDetails details) { @@ -901,7 +908,7 @@ class _MobileSelectionOverlayController implements _SelectionOverlayController { void _handleEndHandleDragEnd(DragEndDetails details) { _endHandleDragging = false; toolbarVisibility.value = true; - showToolbar(_context, _endHandleDragLastPosition); + showToolbar(_context, _endHandleDragLastPosition, null); } void _autoScrollWhenStartHandleDragging() { diff --git a/lib/src/code_line.dart b/lib/src/code_line.dart index 63e86a8..1d5c67e 100644 --- a/lib/src/code_line.dart +++ b/lib/src/code_line.dart @@ -53,10 +53,12 @@ abstract class CodeLineEditingController extends ValueNotifier _CodeLineEditingControllerImpl( codeLines: codeLines, options: options, spanBuilder: spanBuilder, + contextMenuDelegate: contextMenuDelegate ); /// Creates a controller for a given text. @@ -373,6 +375,11 @@ abstract class CodeLineEditingController extends ValueNotifier kCodeShortcutIntents = { CodeShortcutType.selectAll: CodeShortcutSelectAllIntent(), @@ -270,6 +287,10 @@ const Map kCodeShortcutIntents = { CodeShortcutType.replace: CodeShortcutReplaceIntent(), CodeShortcutType.save: CodeShortcutSaveIntent(), CodeShortcutType.esc: CodeShortcutEscIntent(), + CodeShortcutType.execute: CodeShortcutExecuteIntent(), + CodeShortcutType.executeNewTab: CodeShortcutExecuteNewTabIntent(), + CodeShortcutType.executeScript: CodeShortcutExecuteScriptIntent(), + CodeShortcutType.format: CodeShortcutFormatIntent(), }; const Map> _kDefaultMacCodeShortcutsActivators = { @@ -434,6 +455,18 @@ const Map> _kDefaultMacCodeShortcutsAc CodeShortcutType.esc: [ SingleActivator(LogicalKeyboardKey.escape) ], + CodeShortcutType.execute: [ + SingleActivator(LogicalKeyboardKey.enter,meta: true) + ], + CodeShortcutType.executeNewTab: [ + SingleActivator(LogicalKeyboardKey.backslash,meta: true) + ], + CodeShortcutType.executeScript: [ + SingleActivator(LogicalKeyboardKey.keyX,alt: true) + ], + CodeShortcutType.format: [ + SingleActivator(LogicalKeyboardKey.keyF,meta: true,shift: true) + ], }; const Map> _kDefaultCommonCodeShortcutsActivators = { @@ -594,4 +627,16 @@ const Map> _kDefaultCommonCodeShortcut CodeShortcutType.esc: [ SingleActivator(LogicalKeyboardKey.escape) ], + CodeShortcutType.execute: [ + SingleActivator(LogicalKeyboardKey.enter,control: true) + ], + CodeShortcutType.executeNewTab: [ + SingleActivator(LogicalKeyboardKey.backslash,control: true) + ], + CodeShortcutType.executeScript: [ + SingleActivator(LogicalKeyboardKey.keyX,alt: true) + ], + CodeShortcutType.format: [ + SingleActivator(LogicalKeyboardKey.keyF,control: true,shift: true) + ], }; \ No newline at end of file