1// Copyright 2014 The Flutter Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5import 'package:flutter/foundation.dart';
6import 'package:flutter/material.dart';
7import 'package:flutter/services.dart';
8
9/// Flutter code sample for [MenuAnchor].
10
11void main() => runApp(const ContextMenuApp());
12
13/// An enhanced enum to define the available menus and their shortcuts.
14///
15/// Using an enum for menu definition is not required, but this illustrates how
16/// they could be used for simple menu systems.
17enum MenuEntry {
18 about('About'),
19 showMessage('Show Message', SingleActivator(LogicalKeyboardKey.keyS, control: true)),
20 hideMessage('Hide Message', SingleActivator(LogicalKeyboardKey.keyS, control: true)),
21 colorMenu('Color Menu'),
22 colorRed('Red Background', SingleActivator(LogicalKeyboardKey.keyR, control: true)),
23 colorGreen('Green Background', SingleActivator(LogicalKeyboardKey.keyG, control: true)),
24 colorBlue('Blue Background', SingleActivator(LogicalKeyboardKey.keyB, control: true));
25
26 const MenuEntry(this.label, [this.shortcut]);
27 final String label;
28 final MenuSerializableShortcut? shortcut;
29}
30
31class MyContextMenu extends StatefulWidget {
32 const MyContextMenu({super.key, required this.message});
33
34 final String message;
35
36 @override
37 State<MyContextMenu> createState() => _MyContextMenuState();
38}
39
40class _MyContextMenuState extends State<MyContextMenu> {
41 MenuEntry? _lastSelection;
42 final FocusNode _buttonFocusNode = FocusNode(debugLabel: 'Menu Button');
43 final MenuController _menuController = MenuController();
44 ShortcutRegistryEntry? _shortcutsEntry;
45 bool _menuWasEnabled = false;
46
47 Color get backgroundColor => _backgroundColor;
48 Color _backgroundColor = Colors.red;
49 set backgroundColor(Color value) {
50 if (_backgroundColor != value) {
51 setState(() {
52 _backgroundColor = value;
53 });
54 }
55 }
56
57 bool get showingMessage => _showingMessage;
58 bool _showingMessage = false;
59 set showingMessage(bool value) {
60 if (_showingMessage != value) {
61 setState(() {
62 _showingMessage = value;
63 });
64 }
65 }
66
67 @override
68 void initState() {
69 super.initState();
70 _disableContextMenu();
71 }
72
73 @override
74 void didChangeDependencies() {
75 super.didChangeDependencies();
76 // Dispose of any previously registered shortcuts, since they are about to
77 // be replaced.
78 _shortcutsEntry?.dispose();
79 // Collect the shortcuts from the different menu selections so that they can
80 // be registered to apply to the entire app. Menus don't register their
81 // shortcuts, they only display the shortcut hint text.
82 final Map<ShortcutActivator, Intent> shortcuts = <ShortcutActivator, Intent>{
83 for (final MenuEntry item in MenuEntry.values)
84 if (item.shortcut != null) item.shortcut!: VoidCallbackIntent(() => _activate(item)),
85 };
86 // Register the shortcuts with the ShortcutRegistry so that they are
87 // available to the entire application.
88 _shortcutsEntry = ShortcutRegistry.of(context).addAll(shortcuts);
89 }
90
91 @override
92 void dispose() {
93 _shortcutsEntry?.dispose();
94 _buttonFocusNode.dispose();
95 _reenableContextMenu();
96 super.dispose();
97 }
98
99 Future<void> _disableContextMenu() async {
100 if (!kIsWeb) {
101 // Does nothing on non-web platforms.
102 return;
103 }
104 _menuWasEnabled = BrowserContextMenu.enabled;
105 if (_menuWasEnabled) {
106 await BrowserContextMenu.disableContextMenu();
107 }
108 }
109
110 void _reenableContextMenu() {
111 if (!kIsWeb) {
112 // Does nothing on non-web platforms.
113 return;
114 }
115 if (_menuWasEnabled && !BrowserContextMenu.enabled) {
116 BrowserContextMenu.enableContextMenu();
117 }
118 }
119
120 @override
121 Widget build(BuildContext context) {
122 return Padding(
123 padding: const EdgeInsets.all(50),
124 child: GestureDetector(
125 onTapDown: _handleTapDown,
126 onSecondaryTapDown: _handleSecondaryTapDown,
127 child: MenuAnchor(
128 controller: _menuController,
129 menuChildren: <Widget>[
130 MenuItemButton(
131 child: Text(MenuEntry.about.label),
132 onPressed: () => _activate(MenuEntry.about),
133 ),
134 if (_showingMessage)
135 MenuItemButton(
136 onPressed: () => _activate(MenuEntry.hideMessage),
137 shortcut: MenuEntry.hideMessage.shortcut,
138 child: Text(MenuEntry.hideMessage.label),
139 ),
140 if (!_showingMessage)
141 MenuItemButton(
142 onPressed: () => _activate(MenuEntry.showMessage),
143 shortcut: MenuEntry.showMessage.shortcut,
144 child: Text(MenuEntry.showMessage.label),
145 ),
146 SubmenuButton(
147 menuChildren: <Widget>[
148 MenuItemButton(
149 onPressed: () => _activate(MenuEntry.colorRed),
150 shortcut: MenuEntry.colorRed.shortcut,
151 child: Text(MenuEntry.colorRed.label),
152 ),
153 MenuItemButton(
154 onPressed: () => _activate(MenuEntry.colorGreen),
155 shortcut: MenuEntry.colorGreen.shortcut,
156 child: Text(MenuEntry.colorGreen.label),
157 ),
158 MenuItemButton(
159 onPressed: () => _activate(MenuEntry.colorBlue),
160 shortcut: MenuEntry.colorBlue.shortcut,
161 child: Text(MenuEntry.colorBlue.label),
162 ),
163 ],
164 child: const Text('Background Color'),
165 ),
166 ],
167 child: Container(
168 alignment: Alignment.center,
169 color: backgroundColor,
170 child: Column(
171 mainAxisAlignment: MainAxisAlignment.center,
172 children: <Widget>[
173 const Padding(
174 padding: EdgeInsets.all(8.0),
175 child: Text('Right-click anywhere on the background to show the menu.'),
176 ),
177 Padding(
178 padding: const EdgeInsets.all(12.0),
179 child: Text(
180 showingMessage ? widget.message : '',
181 style: Theme.of(context).textTheme.headlineSmall,
182 ),
183 ),
184 Text(_lastSelection != null ? 'Last Selected: ${_lastSelection!.label}' : ''),
185 ],
186 ),
187 ),
188 ),
189 ),
190 );
191 }
192
193 void _activate(MenuEntry selection) {
194 setState(() {
195 _lastSelection = selection;
196 });
197 switch (selection) {
198 case MenuEntry.about:
199 showAboutDialog(
200 context: context,
201 applicationName: 'MenuBar Sample',
202 applicationVersion: '1.0.0',
203 );
204 case MenuEntry.showMessage:
205 case MenuEntry.hideMessage:
206 showingMessage = !showingMessage;
207 case MenuEntry.colorMenu:
208 break;
209 case MenuEntry.colorRed:
210 backgroundColor = Colors.red;
211 case MenuEntry.colorGreen:
212 backgroundColor = Colors.green;
213 case MenuEntry.colorBlue:
214 backgroundColor = Colors.blue;
215 }
216 }
217
218 void _handleSecondaryTapDown(TapDownDetails details) {
219 _menuController.open(position: details.localPosition);
220 }
221
222 void _handleTapDown(TapDownDetails details) {
223 if (_menuController.isOpen) {
224 _menuController.close();
225 return;
226 }
227 switch (defaultTargetPlatform) {
228 case TargetPlatform.android:
229 case TargetPlatform.fuchsia:
230 case TargetPlatform.linux:
231 case TargetPlatform.windows:
232 // Don't open the menu on these platforms with a Ctrl-tap (or a
233 // tap).
234 break;
235 case TargetPlatform.iOS:
236 case TargetPlatform.macOS:
237 // Only open the menu on these platforms if the control button is down
238 // when the tap occurs.
239 if (HardwareKeyboard.instance.logicalKeysPressed.contains(LogicalKeyboardKey.controlLeft) ||
240 HardwareKeyboard.instance.logicalKeysPressed.contains(LogicalKeyboardKey.controlRight)) {
241 _menuController.open(position: details.localPosition);
242 }
243 }
244 }
245}
246
247class ContextMenuApp extends StatelessWidget {
248 const ContextMenuApp({super.key});
249
250 static const String kMessage = '"Talk less. Smile more." - A. Burr';
251
252 @override
253 Widget build(BuildContext context) {
254 return MaterialApp(
255 theme: ThemeData(useMaterial3: true),
256 home: const Scaffold(body: MyContextMenu(message: kMessage)),
257 );
258 }
259}
260

Provided by KDAB

Privacy Policy
Learn more about Flutter for embedded and desktop on industrialflutter.com