v7
This commit is contained in:
@@ -0,0 +1,183 @@
|
||||
// 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.
|
||||
|
||||
// This sample demonstrates allowing a context menu to be shown in a widget
|
||||
// subtree in response to user gestures.
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
|
||||
void main() => runApp(const ContextMenuControllerExampleApp());
|
||||
|
||||
/// A builder that includes an Offset to draw the context menu at.
|
||||
typedef ContextMenuBuilder = Widget Function(BuildContext context, Offset offset);
|
||||
|
||||
class ContextMenuControllerExampleApp extends StatefulWidget {
|
||||
const ContextMenuControllerExampleApp({super.key});
|
||||
|
||||
@override
|
||||
State<ContextMenuControllerExampleApp> createState() => _ContextMenuControllerExampleAppState();
|
||||
}
|
||||
|
||||
class _ContextMenuControllerExampleAppState extends State<ContextMenuControllerExampleApp> {
|
||||
void _showDialog(BuildContext context) {
|
||||
Navigator.of(context).push(
|
||||
DialogRoute<void>(
|
||||
context: context,
|
||||
builder: (BuildContext context) => const AlertDialog(title: Text('You clicked print!')),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
// On web, disable the browser's context menu since this example uses a custom
|
||||
// Flutter-rendered context menu.
|
||||
if (kIsWeb) {
|
||||
BrowserContextMenu.disableContextMenu();
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
if (kIsWeb) {
|
||||
BrowserContextMenu.enableContextMenu();
|
||||
}
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return MaterialApp(
|
||||
home: Scaffold(
|
||||
appBar: AppBar(
|
||||
title: const Text('Context menu outside of text'),
|
||||
),
|
||||
body: _ContextMenuRegion(
|
||||
contextMenuBuilder: (BuildContext context, Offset offset) {
|
||||
// The custom context menu will look like the default context menu
|
||||
// on the current platform with a single 'Print' button.
|
||||
return AdaptiveTextSelectionToolbar.buttonItems(
|
||||
anchors: TextSelectionToolbarAnchors(
|
||||
primaryAnchor: offset,
|
||||
),
|
||||
buttonItems: <ContextMenuButtonItem>[
|
||||
ContextMenuButtonItem(
|
||||
onPressed: () {
|
||||
ContextMenuController.removeAny();
|
||||
_showDialog(context);
|
||||
},
|
||||
label: 'Print',
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
// In this case this wraps a big open space in a GestureDetector in
|
||||
// order to show the context menu, but it could also wrap a single
|
||||
// widget like an Image to give it a context menu.
|
||||
child: ListView(
|
||||
children: <Widget>[
|
||||
Container(height: 20.0),
|
||||
const Text(
|
||||
'Right click (desktop) or long press (mobile) anywhere, not just on this text, to show the custom menu.'),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Shows and hides the context menu based on user gestures.
|
||||
///
|
||||
/// By default, shows the menu on right clicks and long presses.
|
||||
class _ContextMenuRegion extends StatefulWidget {
|
||||
/// Creates an instance of [_ContextMenuRegion].
|
||||
const _ContextMenuRegion({
|
||||
required this.child,
|
||||
required this.contextMenuBuilder,
|
||||
});
|
||||
|
||||
/// Builds the context menu.
|
||||
final ContextMenuBuilder contextMenuBuilder;
|
||||
|
||||
/// The child widget that will be listened to for gestures.
|
||||
final Widget child;
|
||||
|
||||
@override
|
||||
State<_ContextMenuRegion> createState() => _ContextMenuRegionState();
|
||||
}
|
||||
|
||||
class _ContextMenuRegionState extends State<_ContextMenuRegion> {
|
||||
Offset? _longPressOffset;
|
||||
|
||||
final ContextMenuController _contextMenuController = ContextMenuController();
|
||||
|
||||
static bool get _longPressEnabled {
|
||||
switch (defaultTargetPlatform) {
|
||||
case TargetPlatform.android:
|
||||
case TargetPlatform.iOS:
|
||||
return true;
|
||||
case TargetPlatform.macOS:
|
||||
case TargetPlatform.fuchsia:
|
||||
case TargetPlatform.linux:
|
||||
case TargetPlatform.windows:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
void _onSecondaryTapUp(TapUpDetails details) {
|
||||
_show(details.globalPosition);
|
||||
}
|
||||
|
||||
void _onTap() {
|
||||
if (!_contextMenuController.isShown) {
|
||||
return;
|
||||
}
|
||||
_hide();
|
||||
}
|
||||
|
||||
void _onLongPressStart(LongPressStartDetails details) {
|
||||
_longPressOffset = details.globalPosition;
|
||||
}
|
||||
|
||||
void _onLongPress() {
|
||||
assert(_longPressOffset != null);
|
||||
_show(_longPressOffset!);
|
||||
_longPressOffset = null;
|
||||
}
|
||||
|
||||
void _show(Offset position) {
|
||||
_contextMenuController.show(
|
||||
context: context,
|
||||
contextMenuBuilder: (BuildContext context) {
|
||||
return widget.contextMenuBuilder(context, position);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
void _hide() {
|
||||
_contextMenuController.remove();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_hide();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return GestureDetector(
|
||||
behavior: HitTestBehavior.opaque,
|
||||
onSecondaryTapUp: _onSecondaryTapUp,
|
||||
onTap: _onTap,
|
||||
onLongPress: _longPressEnabled ? _onLongPress : null,
|
||||
onLongPressStart: _longPressEnabled ? _onLongPressStart : null,
|
||||
child: widget.child,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
// 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.
|
||||
|
||||
// This example demonstrates showing the default buttons, but customizing their
|
||||
// appearance.
|
||||
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
|
||||
void main() => runApp(const EditableTextToolbarBuilderExampleApp());
|
||||
|
||||
class EditableTextToolbarBuilderExampleApp extends StatefulWidget {
|
||||
const EditableTextToolbarBuilderExampleApp({super.key});
|
||||
|
||||
@override
|
||||
State<EditableTextToolbarBuilderExampleApp> createState() => _EditableTextToolbarBuilderExampleAppState();
|
||||
}
|
||||
|
||||
class _EditableTextToolbarBuilderExampleAppState extends State<EditableTextToolbarBuilderExampleApp> {
|
||||
final TextEditingController _controller = TextEditingController(
|
||||
text: 'Right click (desktop) or long press (mobile) to see the menu with custom buttons.',
|
||||
);
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
// On web, disable the browser's context menu since this example uses a custom
|
||||
// Flutter-rendered context menu.
|
||||
if (kIsWeb) {
|
||||
BrowserContextMenu.disableContextMenu();
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
if (kIsWeb) {
|
||||
BrowserContextMenu.enableContextMenu();
|
||||
}
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return MaterialApp(
|
||||
home: Scaffold(
|
||||
appBar: AppBar(
|
||||
title: const Text('Custom button appearance'),
|
||||
),
|
||||
body: Center(
|
||||
child: Column(
|
||||
children: <Widget>[
|
||||
const SizedBox(height: 20.0),
|
||||
TextField(
|
||||
controller: _controller,
|
||||
contextMenuBuilder: (BuildContext context, EditableTextState editableTextState) {
|
||||
return AdaptiveTextSelectionToolbar(
|
||||
anchors: editableTextState.contextMenuAnchors,
|
||||
// Build the default buttons, but make them look custom.
|
||||
// In a real project you may want to build different
|
||||
// buttons depending on the platform.
|
||||
children: editableTextState.contextMenuButtonItems.map((ContextMenuButtonItem buttonItem) {
|
||||
return CupertinoButton(
|
||||
borderRadius: null,
|
||||
color: const Color(0xffaaaa00),
|
||||
disabledColor: const Color(0xffaaaaff),
|
||||
onPressed: buttonItem.onPressed,
|
||||
padding: const EdgeInsets.all(10.0),
|
||||
pressedOpacity: 0.7,
|
||||
child: SizedBox(
|
||||
width: 200.0,
|
||||
child: Text(
|
||||
CupertinoTextSelectionToolbarButton.getButtonLabel(context, buttonItem),
|
||||
),
|
||||
),
|
||||
);
|
||||
}).toList(),
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
// 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.
|
||||
|
||||
// This example demonstrates showing a custom context menu only when some
|
||||
// narrowly defined text is selected.
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
|
||||
void main() => runApp(const EditableTextToolbarBuilderExampleApp());
|
||||
|
||||
const String emailAddress = 'me@example.com';
|
||||
const String text = 'Select the email address and open the menu: $emailAddress';
|
||||
|
||||
class EditableTextToolbarBuilderExampleApp extends StatefulWidget {
|
||||
const EditableTextToolbarBuilderExampleApp({super.key});
|
||||
|
||||
@override
|
||||
State<EditableTextToolbarBuilderExampleApp> createState() => _EditableTextToolbarBuilderExampleAppState();
|
||||
}
|
||||
|
||||
class _EditableTextToolbarBuilderExampleAppState extends State<EditableTextToolbarBuilderExampleApp> {
|
||||
final TextEditingController _controller = TextEditingController(
|
||||
text: text,
|
||||
);
|
||||
|
||||
void _showDialog(BuildContext context) {
|
||||
Navigator.of(context).push(
|
||||
DialogRoute<void>(
|
||||
context: context,
|
||||
builder: (BuildContext context) => const AlertDialog(title: Text('You clicked send email!')),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
// On web, disable the browser's context menu since this example uses a custom
|
||||
// Flutter-rendered context menu.
|
||||
if (kIsWeb) {
|
||||
BrowserContextMenu.disableContextMenu();
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
if (kIsWeb) {
|
||||
BrowserContextMenu.enableContextMenu();
|
||||
}
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return MaterialApp(
|
||||
home: Scaffold(
|
||||
appBar: AppBar(
|
||||
title: const Text('Custom button for emails'),
|
||||
),
|
||||
body: Center(
|
||||
child: Column(
|
||||
children: <Widget>[
|
||||
Container(height: 20.0),
|
||||
TextField(
|
||||
controller: _controller,
|
||||
contextMenuBuilder: (BuildContext context, EditableTextState editableTextState) {
|
||||
final List<ContextMenuButtonItem> buttonItems = editableTextState.contextMenuButtonItems;
|
||||
// Here we add an "Email" button to the default TextField
|
||||
// context menu for the current platform, but only if an email
|
||||
// address is currently selected.
|
||||
final TextEditingValue value = _controller.value;
|
||||
if (_isValidEmail(value.selection.textInside(value.text))) {
|
||||
buttonItems.insert(
|
||||
0,
|
||||
ContextMenuButtonItem(
|
||||
label: 'Send email',
|
||||
onPressed: () {
|
||||
ContextMenuController.removeAny();
|
||||
_showDialog(context);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
return AdaptiveTextSelectionToolbar.buttonItems(
|
||||
anchors: editableTextState.contextMenuAnchors,
|
||||
buttonItems: buttonItems,
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
bool _isValidEmail(String text) {
|
||||
return RegExp(
|
||||
r'(?<name>[a-zA-Z0-9]+)'
|
||||
r'@'
|
||||
r'(?<domain>[a-zA-Z0-9]+)'
|
||||
r'\.'
|
||||
r'(?<topLevelDomain>[a-zA-Z0-9]+)',
|
||||
).hasMatch(text);
|
||||
}
|
||||
@@ -0,0 +1,132 @@
|
||||
// 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.
|
||||
|
||||
// This example demonstrates how to create a custom toolbar that retains the
|
||||
// look of the default buttons for the current platform.
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
|
||||
void main() => runApp(const EditableTextToolbarBuilderExampleApp());
|
||||
|
||||
class EditableTextToolbarBuilderExampleApp extends StatefulWidget {
|
||||
const EditableTextToolbarBuilderExampleApp({super.key});
|
||||
|
||||
@override
|
||||
State<EditableTextToolbarBuilderExampleApp> createState() => _EditableTextToolbarBuilderExampleAppState();
|
||||
}
|
||||
|
||||
class _EditableTextToolbarBuilderExampleAppState extends State<EditableTextToolbarBuilderExampleApp> {
|
||||
final TextEditingController _controller = TextEditingController(
|
||||
text: 'Right click (desktop) or long press (mobile) to see the menu with a custom toolbar.',
|
||||
);
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
// On web, disable the browser's context menu since this example uses a custom
|
||||
// Flutter-rendered context menu.
|
||||
if (kIsWeb) {
|
||||
BrowserContextMenu.disableContextMenu();
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
if (kIsWeb) {
|
||||
BrowserContextMenu.enableContextMenu();
|
||||
}
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return MaterialApp(
|
||||
home: Scaffold(
|
||||
appBar: AppBar(
|
||||
title: const Text('Custom toolbar, default-looking buttons'),
|
||||
),
|
||||
body: Center(
|
||||
child: Column(
|
||||
children: <Widget>[
|
||||
const SizedBox(height: 20.0),
|
||||
TextField(
|
||||
controller: _controller,
|
||||
contextMenuBuilder: (BuildContext context, EditableTextState editableTextState) {
|
||||
return _MyTextSelectionToolbar(
|
||||
anchor: editableTextState.contextMenuAnchors.primaryAnchor,
|
||||
// getAdaptiveButtons creates the default button widgets for
|
||||
// the current platform.
|
||||
children: AdaptiveTextSelectionToolbar.getAdaptiveButtons(
|
||||
context,
|
||||
// These buttons just close the menu when clicked.
|
||||
<ContextMenuButtonItem>[
|
||||
ContextMenuButtonItem(
|
||||
label: 'One',
|
||||
onPressed: () => ContextMenuController.removeAny(),
|
||||
),
|
||||
ContextMenuButtonItem(
|
||||
label: 'Two',
|
||||
onPressed: () => ContextMenuController.removeAny(),
|
||||
),
|
||||
ContextMenuButtonItem(
|
||||
label: 'Three',
|
||||
onPressed: () => ContextMenuController.removeAny(),
|
||||
),
|
||||
ContextMenuButtonItem(
|
||||
label: 'Four',
|
||||
onPressed: () => ContextMenuController.removeAny(),
|
||||
),
|
||||
ContextMenuButtonItem(
|
||||
label: 'Five',
|
||||
onPressed: () => ContextMenuController.removeAny(),
|
||||
),
|
||||
],
|
||||
).toList(),
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// A simple, yet totally custom, text selection toolbar.
|
||||
///
|
||||
/// Displays its children in a scrollable grid.
|
||||
class _MyTextSelectionToolbar extends StatelessWidget {
|
||||
const _MyTextSelectionToolbar({
|
||||
required this.anchor,
|
||||
required this.children,
|
||||
});
|
||||
|
||||
final Offset anchor;
|
||||
final List<Widget> children;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Stack(
|
||||
children: <Widget>[
|
||||
Positioned(
|
||||
top: anchor.dy,
|
||||
left: anchor.dx,
|
||||
child: Container(
|
||||
width: 200.0,
|
||||
height: 200.0,
|
||||
color: Colors.cyanAccent.withOpacity(0.5),
|
||||
child: GridView.count(
|
||||
padding: const EdgeInsets.all(12.0),
|
||||
crossAxisCount: 2,
|
||||
children: children,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
// 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.
|
||||
|
||||
// This example demonstrates a custom context menu in non-editable text using
|
||||
// SelectionArea.
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
|
||||
void main() => runApp(const SelectableRegionToolbarBuilderExampleApp());
|
||||
|
||||
const String text =
|
||||
'I am some text inside of SelectionArea. Right click (desktop) or long press (mobile) me to show the customized context menu.';
|
||||
|
||||
class SelectableRegionToolbarBuilderExampleApp extends StatefulWidget {
|
||||
const SelectableRegionToolbarBuilderExampleApp({super.key});
|
||||
|
||||
@override
|
||||
State<SelectableRegionToolbarBuilderExampleApp> createState() => _SelectableRegionToolbarBuilderExampleAppState();
|
||||
}
|
||||
|
||||
class _SelectableRegionToolbarBuilderExampleAppState extends State<SelectableRegionToolbarBuilderExampleApp> {
|
||||
void _showDialog(BuildContext context) {
|
||||
Navigator.of(context).push(
|
||||
DialogRoute<void>(
|
||||
context: context,
|
||||
builder: (BuildContext context) => const AlertDialog(title: Text('You clicked print!')),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
// On web, disable the browser's context menu since this example uses a custom
|
||||
// Flutter-rendered context menu.
|
||||
if (kIsWeb) {
|
||||
BrowserContextMenu.disableContextMenu();
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
if (kIsWeb) {
|
||||
BrowserContextMenu.enableContextMenu();
|
||||
}
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return MaterialApp(
|
||||
home: Scaffold(
|
||||
appBar: AppBar(
|
||||
title: const Text('Context menu anywhere'),
|
||||
),
|
||||
body: Center(
|
||||
child: SizedBox(
|
||||
width: 200.0,
|
||||
child: SelectionArea(
|
||||
contextMenuBuilder: (
|
||||
BuildContext context,
|
||||
SelectableRegionState selectableRegionState,
|
||||
) {
|
||||
return AdaptiveTextSelectionToolbar.buttonItems(
|
||||
anchors: selectableRegionState.contextMenuAnchors,
|
||||
buttonItems: <ContextMenuButtonItem>[
|
||||
...selectableRegionState.contextMenuButtonItems,
|
||||
ContextMenuButtonItem(
|
||||
onPressed: () {
|
||||
ContextMenuController.removeAny();
|
||||
_showDialog(context);
|
||||
},
|
||||
label: 'Print',
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
child: ListView(
|
||||
children: const <Widget>[
|
||||
SizedBox(height: 20.0),
|
||||
Text(text),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user