blob: f2b029dd111e1e89315a12164328a26b49eba4b3 [file] [log] [blame]
// 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 clampDouble;
import 'package:flutter/widgets.dart';
import 'debug.dart';
import 'desktop_text_selection_toolbar.dart';
import 'desktop_text_selection_toolbar_button.dart';
import 'material_localizations.dart';
/// Desktop Material styled text selection handle controls.
///
/// Specifically does not manage the toolbar, which is left to
/// [EditableText.contextMenuBuilder].
class _DesktopTextSelectionHandleControls extends DesktopTextSelectionControls with TextSelectionHandleControls {
}
/// Desktop Material styled text selection controls.
///
/// The [desktopTextSelectionControls] global variable has a
/// suitable instance of this class.
class DesktopTextSelectionControls extends TextSelectionControls {
/// Desktop has no text selection handles.
@override
Size getHandleSize(double textLineHeight) {
return Size.zero;
}
/// Builder for the Material-style desktop 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,
ClipboardStatusNotifier? clipboardStatus,
Offset? lastSecondaryTapDownPosition,
) {
return _DesktopTextSelectionControlsToolbar(
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
bool canSelectAll(TextSelectionDelegate delegate) {
// Allow SelectAll when selection is not collapsed, unless everything has
// already been selected. Same behavior as Android.
final TextEditingValue value = delegate.textEditingValue;
return delegate.selectAllEnabled &&
value.text.isNotEmpty &&
!(value.selection.start == 0 && value.selection.end == value.text.length);
}
@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();
}
}
/// Desktop text selection handle controls that loosely follow Material design
/// conventions.
@Deprecated(
'Use `desktopTextSelectionControls` instead. '
'This feature was deprecated after v3.3.0-0.5.pre.',
)
final TextSelectionControls desktopTextSelectionHandleControls =
_DesktopTextSelectionHandleControls();
/// Desktop text selection controls that loosely follow Material design
/// conventions.
final TextSelectionControls desktopTextSelectionControls =
DesktopTextSelectionControls();
// Generates the child that's passed into DesktopTextSelectionToolbar.
class _DesktopTextSelectionControlsToolbar extends StatefulWidget {
const _DesktopTextSelectionControlsToolbar({
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 ClipboardStatusNotifier? 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
_DesktopTextSelectionControlsToolbarState createState() => _DesktopTextSelectionControlsToolbarState();
}
class _DesktopTextSelectionControlsToolbarState extends State<_DesktopTextSelectionControlsToolbar> {
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(_DesktopTextSelectionControlsToolbar 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) {
assert(debugCheckHasMaterialLocalizations(context));
assert(debugCheckHasMediaQuery(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();
}
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 MaterialLocalizations localizations = MaterialLocalizations.of(context);
final List<Widget> items = <Widget>[];
void addToolbarButton(
String text,
VoidCallback onPressed,
) {
items.add(DesktopTextSelectionToolbarButton.text(
context: context,
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 DesktopTextSelectionToolbar(
anchor: widget.lastSecondaryTapDownPosition ?? midpointAnchor,
children: items,
);
}
}