// 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/material.dart'; import 'package:flutter/services.dart'; /// Flutter code sample for [MenuAnchor]. void main() => runApp(const ContextMenuApp()); /// An enhanced enum to define the available menus and their shortcuts. /// /// Using an enum for menu definition is not required, but this illustrates how /// they could be used for simple menu systems. enum MenuEntry { about('About'), showMessage('Show Message', SingleActivator(LogicalKeyboardKey.keyS, control: true)), hideMessage('Hide Message', SingleActivator(LogicalKeyboardKey.keyS, control: true)), colorMenu('Color Menu'), colorRed('Red Background', SingleActivator(LogicalKeyboardKey.keyR, control: true)), colorGreen('Green Background', SingleActivator(LogicalKeyboardKey.keyG, control: true)), colorBlue('Blue Background', SingleActivator(LogicalKeyboardKey.keyB, control: true)); const MenuEntry(this.label, [this.shortcut]); final String label; final MenuSerializableShortcut? shortcut; } class MyContextMenu extends StatefulWidget { const MyContextMenu({super.key, required this.message}); final String message; @override State<MyContextMenu> createState() => _MyContextMenuState(); } class _MyContextMenuState extends State<MyContextMenu> { MenuEntry? _lastSelection; final FocusNode _buttonFocusNode = FocusNode(debugLabel: 'Menu Button'); final MenuController _menuController = MenuController(); ShortcutRegistryEntry? _shortcutsEntry; Color get backgroundColor => _backgroundColor; Color _backgroundColor = Colors.red; set backgroundColor(Color value) { if (_backgroundColor != value) { setState(() { _backgroundColor = value; }); } } bool get showingMessage => _showingMessage; bool _showingMessage = false; set showingMessage(bool value) { if (_showingMessage != value) { setState(() { _showingMessage = value; }); } } @override void didChangeDependencies() { super.didChangeDependencies(); // Dispose of any previously registered shortcuts, since they are about to // be replaced. _shortcutsEntry?.dispose(); // Collect the shortcuts from the different menu selections so that they can // be registered to apply to the entire app. Menus don't register their // shortcuts, they only display the shortcut hint text. final Map<ShortcutActivator, Intent> shortcuts = <ShortcutActivator, Intent>{ for (final MenuEntry item in MenuEntry.values) if (item.shortcut != null) item.shortcut!: VoidCallbackIntent(() => _activate(item)), }; // Register the shortcuts with the ShortcutRegistry so that they are // available to the entire application. _shortcutsEntry = ShortcutRegistry.of(context).addAll(shortcuts); } @override void dispose() { _shortcutsEntry?.dispose(); _buttonFocusNode.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return Padding( padding: const EdgeInsets.all(50), child: GestureDetector( onTapDown: _handleTapDown, child: MenuAnchor( controller: _menuController, anchorTapClosesMenu: true, menuChildren: <Widget>[ MenuItemButton( child: Text(MenuEntry.about.label), onPressed: () => _activate(MenuEntry.about), ), if (_showingMessage) MenuItemButton( onPressed: () => _activate(MenuEntry.hideMessage), shortcut: MenuEntry.hideMessage.shortcut, child: Text(MenuEntry.hideMessage.label), ), if (!_showingMessage) MenuItemButton( onPressed: () => _activate(MenuEntry.showMessage), shortcut: MenuEntry.showMessage.shortcut, child: Text(MenuEntry.showMessage.label), ), SubmenuButton( menuChildren: <Widget>[ MenuItemButton( onPressed: () => _activate(MenuEntry.colorRed), shortcut: MenuEntry.colorRed.shortcut, child: Text(MenuEntry.colorRed.label), ), MenuItemButton( onPressed: () => _activate(MenuEntry.colorGreen), shortcut: MenuEntry.colorGreen.shortcut, child: Text(MenuEntry.colorGreen.label), ), MenuItemButton( onPressed: () => _activate(MenuEntry.colorBlue), shortcut: MenuEntry.colorBlue.shortcut, child: Text(MenuEntry.colorBlue.label), ), ], child: const Text('Background Color'), ), ], child: Container( alignment: Alignment.center, color: backgroundColor, child: Column( mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[ const Padding( padding: EdgeInsets.all(8.0), child: Text('Ctrl-click anywhere on the background to show the menu.'), ), Padding( padding: const EdgeInsets.all(12.0), child: Text( showingMessage ? widget.message : '', style: Theme.of(context).textTheme.headlineSmall, ), ), Text(_lastSelection != null ? 'Last Selected: ${_lastSelection!.label}' : ''), ], ), ), ), ), ); } void _activate(MenuEntry selection) { setState(() { _lastSelection = selection; }); switch (selection) { case MenuEntry.about: showAboutDialog( context: context, applicationName: 'MenuBar Sample', applicationVersion: '1.0.0', ); case MenuEntry.showMessage: case MenuEntry.hideMessage: showingMessage = !showingMessage; case MenuEntry.colorMenu: break; case MenuEntry.colorRed: backgroundColor = Colors.red; case MenuEntry.colorGreen: backgroundColor = Colors.green; case MenuEntry.colorBlue: backgroundColor = Colors.blue; } } void _handleTapDown(TapDownDetails details) { if (!HardwareKeyboard.instance.logicalKeysPressed.contains(LogicalKeyboardKey.controlLeft) && !HardwareKeyboard.instance.logicalKeysPressed.contains(LogicalKeyboardKey.controlRight)) { return; } _menuController.open(position: details.localPosition); } } class ContextMenuApp extends StatelessWidget { const ContextMenuApp({super.key}); static const String kMessage = '"Talk less. Smile more." - A. Burr'; @override Widget build(BuildContext context) { return const MaterialApp( home: Scaffold(body: MyContextMenu(message: kMessage)), ); } }