| // Copyright 2014 The Flutter Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| import 'package:flutter/foundation.dart' show ValueListenable, clampDouble; |
| import 'package:flutter/widgets.dart'; |
| |
| import 'desktop_text_selection_toolbar.dart'; |
| import 'desktop_text_selection_toolbar_button.dart'; |
| import 'localizations.dart'; |
| |
| /// MacOS Cupertino styled text selection handle controls. |
| /// |
| /// Specifically does not manage the toolbar, which is left to |
| /// [EditableText.contextMenuBuilder]. |
| class _CupertinoDesktopTextSelectionHandleControls extends CupertinoDesktopTextSelectionControls with TextSelectionHandleControls { |
| } |
| |
| /// Desktop Cupertino styled text selection controls. |
| /// |
| /// The [cupertinoDesktopTextSelectionControls] global variable has a |
| /// suitable instance of this class. |
| class CupertinoDesktopTextSelectionControls extends TextSelectionControls { |
| /// Desktop has no text selection handles. |
| @override |
| Size getHandleSize(double textLineHeight) { |
| return Size.zero; |
| } |
| |
| /// Builder for the MacOS-style copy/paste text selection toolbar. |
| @Deprecated( |
| 'Use `contextMenuBuilder` instead. ' |
| 'This feature was deprecated after v3.3.0-0.5.pre.', |
| ) |
| @override |
| Widget buildToolbar( |
| BuildContext context, |
| Rect globalEditableRegion, |
| double textLineHeight, |
| Offset selectionMidpoint, |
| List<TextSelectionPoint> endpoints, |
| TextSelectionDelegate delegate, |
| ValueListenable<ClipboardStatus>? clipboardStatus, |
| Offset? lastSecondaryTapDownPosition, |
| ) { |
| return _CupertinoDesktopTextSelectionControlsToolbar( |
| clipboardStatus: clipboardStatus, |
| endpoints: endpoints, |
| globalEditableRegion: globalEditableRegion, |
| handleCut: canCut(delegate) ? () => handleCut(delegate) : null, |
| handleCopy: canCopy(delegate) ? () => handleCopy(delegate) : null, |
| handlePaste: canPaste(delegate) ? () => handlePaste(delegate) : null, |
| handleSelectAll: canSelectAll(delegate) ? () => handleSelectAll(delegate) : null, |
| selectionMidpoint: selectionMidpoint, |
| lastSecondaryTapDownPosition: lastSecondaryTapDownPosition, |
| textLineHeight: textLineHeight, |
| ); |
| } |
| |
| /// Builds the text selection handles, but desktop has none. |
| @override |
| Widget buildHandle(BuildContext context, TextSelectionHandleType type, double textLineHeight, [VoidCallback? onTap]) { |
| return const SizedBox.shrink(); |
| } |
| |
| /// Gets the position for the text selection handles, but desktop has none. |
| @override |
| Offset getHandleAnchor(TextSelectionHandleType type, double textLineHeight) { |
| return Offset.zero; |
| } |
| |
| @Deprecated( |
| 'Use `contextMenuBuilder` instead. ' |
| 'This feature was deprecated after v3.3.0-0.5.pre.', |
| ) |
| @override |
| void handleSelectAll(TextSelectionDelegate delegate) { |
| super.handleSelectAll(delegate); |
| delegate.hideToolbar(); |
| } |
| } |
| |
| // TODO(justinmc): Deprecate this after TextSelectionControls.buildToolbar is |
| // deleted, when users should migrate back to |
| // cupertinoDesktopTextSelectionControls. |
| // See https://github.com/flutter/flutter/pull/124262 |
| /// Text selection handle controls that follow MacOS design conventions. |
| final TextSelectionControls cupertinoDesktopTextSelectionHandleControls = |
| _CupertinoDesktopTextSelectionHandleControls(); |
| |
| /// Text selection controls that follows MacOS design conventions. |
| final TextSelectionControls cupertinoDesktopTextSelectionControls = |
| CupertinoDesktopTextSelectionControls(); |
| |
| // Generates the child that's passed into CupertinoDesktopTextSelectionToolbar. |
| class _CupertinoDesktopTextSelectionControlsToolbar extends StatefulWidget { |
| const _CupertinoDesktopTextSelectionControlsToolbar({ |
| required this.clipboardStatus, |
| required this.endpoints, |
| required this.globalEditableRegion, |
| required this.handleCopy, |
| required this.handleCut, |
| required this.handlePaste, |
| required this.handleSelectAll, |
| required this.selectionMidpoint, |
| required this.textLineHeight, |
| required this.lastSecondaryTapDownPosition, |
| }); |
| |
| final ValueListenable<ClipboardStatus>? clipboardStatus; |
| final List<TextSelectionPoint> endpoints; |
| final Rect globalEditableRegion; |
| final VoidCallback? handleCopy; |
| final VoidCallback? handleCut; |
| final VoidCallback? handlePaste; |
| final VoidCallback? handleSelectAll; |
| final Offset? lastSecondaryTapDownPosition; |
| final Offset selectionMidpoint; |
| final double textLineHeight; |
| |
| @override |
| _CupertinoDesktopTextSelectionControlsToolbarState createState() => _CupertinoDesktopTextSelectionControlsToolbarState(); |
| } |
| |
| class _CupertinoDesktopTextSelectionControlsToolbarState extends State<_CupertinoDesktopTextSelectionControlsToolbar> { |
| void _onChangedClipboardStatus() { |
| setState(() { |
| // Inform the widget that the value of clipboardStatus has changed. |
| }); |
| } |
| |
| @override |
| void initState() { |
| super.initState(); |
| widget.clipboardStatus?.addListener(_onChangedClipboardStatus); |
| } |
| |
| @override |
| void didUpdateWidget(_CupertinoDesktopTextSelectionControlsToolbar oldWidget) { |
| super.didUpdateWidget(oldWidget); |
| if (oldWidget.clipboardStatus != widget.clipboardStatus) { |
| oldWidget.clipboardStatus?.removeListener(_onChangedClipboardStatus); |
| widget.clipboardStatus?.addListener(_onChangedClipboardStatus); |
| } |
| } |
| |
| @override |
| void dispose() { |
| widget.clipboardStatus?.removeListener(_onChangedClipboardStatus); |
| super.dispose(); |
| } |
| |
| @override |
| Widget build(BuildContext context) { |
| // Don't render the menu until the state of the clipboard is known. |
| if (widget.handlePaste != null && widget.clipboardStatus?.value == ClipboardStatus.unknown) { |
| return const SizedBox.shrink(); |
| } |
| |
| assert(debugCheckHasMediaQuery(context)); |
| final EdgeInsets mediaQueryPadding = MediaQuery.paddingOf(context); |
| |
| final Offset midpointAnchor = Offset( |
| clampDouble(widget.selectionMidpoint.dx - widget.globalEditableRegion.left, |
| mediaQueryPadding.left, |
| MediaQuery.sizeOf(context).width - mediaQueryPadding.right, |
| ), |
| widget.selectionMidpoint.dy - widget.globalEditableRegion.top, |
| ); |
| |
| final List<Widget> items = <Widget>[]; |
| final CupertinoLocalizations localizations = CupertinoLocalizations.of(context); |
| final Widget onePhysicalPixelVerticalDivider = |
| SizedBox(width: 1.0 / MediaQuery.devicePixelRatioOf(context)); |
| |
| void addToolbarButton( |
| String text, |
| VoidCallback onPressed, |
| ) { |
| if (items.isNotEmpty) { |
| items.add(onePhysicalPixelVerticalDivider); |
| } |
| |
| items.add(CupertinoDesktopTextSelectionToolbarButton.text( |
| onPressed: onPressed, |
| text: text, |
| )); |
| } |
| |
| if (widget.handleCut != null) { |
| addToolbarButton(localizations.cutButtonLabel, widget.handleCut!); |
| } |
| if (widget.handleCopy != null) { |
| addToolbarButton(localizations.copyButtonLabel, widget.handleCopy!); |
| } |
| if (widget.handlePaste != null |
| && widget.clipboardStatus?.value == ClipboardStatus.pasteable) { |
| addToolbarButton(localizations.pasteButtonLabel, widget.handlePaste!); |
| } |
| if (widget.handleSelectAll != null) { |
| addToolbarButton(localizations.selectAllButtonLabel, widget.handleSelectAll!); |
| } |
| |
| // If there is no option available, build an empty widget. |
| if (items.isEmpty) { |
| return const SizedBox.shrink(); |
| } |
| |
| return CupertinoDesktopTextSelectionToolbar( |
| anchor: widget.lastSecondaryTapDownPosition ?? midpointAnchor, |
| children: items, |
| ); |
| } |
| } |