| // 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'; |
| |
| void main() { |
| runApp( |
| const MaterialApp( |
| title: 'Menu Tester', |
| home: Material( |
| child: Home(), |
| ), |
| ), |
| ); |
| } |
| |
| class Home extends StatefulWidget { |
| const Home({super.key}); |
| |
| @override |
| State<Home> createState() => _HomeState(); |
| } |
| |
| class _HomeState extends State<Home> { |
| final MenuController _controller = MenuController(); |
| VisualDensity _density = VisualDensity.standard; |
| TextDirection _textDirection = TextDirection.ltr; |
| double _extraPadding = 0; |
| bool _addItem = false; |
| bool _transparent = false; |
| bool _funkyTheme = false; |
| |
| @override |
| Widget build(BuildContext context) { |
| final ThemeData theme = Theme.of(context); |
| MenuThemeData menuTheme = MenuTheme.of(context); |
| MenuBarThemeData menuBarTheme = MenuBarTheme.of(context); |
| MenuButtonThemeData menuButtonTheme = MenuButtonTheme.of(context); |
| if (_funkyTheme) { |
| menuTheme = const MenuThemeData( |
| style: MenuStyle( |
| shape: MaterialStatePropertyAll<OutlinedBorder>( |
| RoundedRectangleBorder( |
| borderRadius: BorderRadius.all( |
| Radius.circular(10), |
| ), |
| ), |
| ), |
| backgroundColor: MaterialStatePropertyAll<Color?>(Colors.blue), |
| elevation: MaterialStatePropertyAll<double?>(10), |
| padding: MaterialStatePropertyAll<EdgeInsetsDirectional>( |
| EdgeInsetsDirectional.all(20), |
| ), |
| ), |
| ); |
| menuButtonTheme = const MenuButtonThemeData( |
| style: ButtonStyle( |
| shape: MaterialStatePropertyAll<OutlinedBorder>(StadiumBorder()), |
| backgroundColor: MaterialStatePropertyAll<Color?>(Colors.green), |
| foregroundColor: MaterialStatePropertyAll<Color?>(Colors.white), |
| ), |
| ); |
| menuBarTheme = const MenuBarThemeData( |
| style: MenuStyle( |
| shape: MaterialStatePropertyAll<OutlinedBorder>(RoundedRectangleBorder()), |
| backgroundColor: MaterialStatePropertyAll<Color?>(Colors.blue), |
| elevation: MaterialStatePropertyAll<double?>(10), |
| padding: MaterialStatePropertyAll<EdgeInsetsDirectional>( |
| EdgeInsetsDirectional.all(20), |
| ), |
| ), |
| ); |
| } |
| return SafeArea( |
| child: Padding( |
| padding: EdgeInsets.all(_extraPadding), |
| child: Directionality( |
| textDirection: _textDirection, |
| child: Theme( |
| data: theme.copyWith( |
| visualDensity: _density, |
| menuTheme: _transparent |
| ? MenuThemeData( |
| style: MenuStyle( |
| backgroundColor: MaterialStatePropertyAll<Color>( |
| Colors.blue.withOpacity(0.12), |
| ), |
| elevation: const MaterialStatePropertyAll<double>(0), |
| ), |
| ) |
| : menuTheme, |
| menuBarTheme: menuBarTheme, |
| menuButtonTheme: menuButtonTheme, |
| ), |
| child: Column( |
| crossAxisAlignment: CrossAxisAlignment.start, |
| children: <Widget>[ |
| _TestMenus( |
| menuController: _controller, |
| addItem: _addItem, |
| ), |
| Expanded( |
| child: SingleChildScrollView( |
| child: _Controls( |
| menuController: _controller, |
| density: _density, |
| addItem: _addItem, |
| transparent: _transparent, |
| funkyTheme: _funkyTheme, |
| extraPadding: _extraPadding, |
| textDirection: _textDirection, |
| onDensityChanged: (VisualDensity value) { |
| setState(() { |
| _density = value; |
| }); |
| }, |
| onTextDirectionChanged: (TextDirection value) { |
| setState(() { |
| _textDirection = value; |
| }); |
| }, |
| onExtraPaddingChanged: (double value) { |
| setState(() { |
| _extraPadding = value; |
| }); |
| }, |
| onAddItemChanged: (bool value) { |
| setState(() { |
| _addItem = value; |
| }); |
| }, |
| onTransparentChanged: (bool value) { |
| setState(() { |
| _transparent = value; |
| }); |
| }, |
| onFunkyThemeChanged: (bool value) { |
| setState(() { |
| _funkyTheme = value; |
| }); |
| }, |
| ), |
| ), |
| ), |
| ], |
| ), |
| ), |
| ), |
| ), |
| ); |
| } |
| } |
| |
| class _Controls extends StatefulWidget { |
| const _Controls({ |
| required this.density, |
| required this.textDirection, |
| required this.extraPadding, |
| this.addItem = false, |
| this.transparent = false, |
| this.funkyTheme = false, |
| required this.onDensityChanged, |
| required this.onTextDirectionChanged, |
| required this.onExtraPaddingChanged, |
| required this.onAddItemChanged, |
| required this.onTransparentChanged, |
| required this.onFunkyThemeChanged, |
| required this.menuController, |
| }); |
| |
| final VisualDensity density; |
| final TextDirection textDirection; |
| final double extraPadding; |
| final bool addItem; |
| final bool transparent; |
| final bool funkyTheme; |
| final ValueChanged<VisualDensity> onDensityChanged; |
| final ValueChanged<TextDirection> onTextDirectionChanged; |
| final ValueChanged<double> onExtraPaddingChanged; |
| final ValueChanged<bool> onAddItemChanged; |
| final ValueChanged<bool> onTransparentChanged; |
| final ValueChanged<bool> onFunkyThemeChanged; |
| final MenuController menuController; |
| |
| @override |
| State<_Controls> createState() => _ControlsState(); |
| } |
| |
| class _ControlsState extends State<_Controls> { |
| final FocusNode _focusNode = FocusNode(debugLabel: 'Floating'); |
| |
| @override |
| void dispose() { |
| _focusNode.dispose(); |
| super.dispose(); |
| } |
| |
| @override |
| Widget build(BuildContext context) { |
| return Container( |
| color: Colors.lightBlueAccent, |
| alignment: Alignment.center, |
| child: Column( |
| mainAxisAlignment: MainAxisAlignment.center, |
| children: <Widget>[ |
| MenuAnchor( |
| childFocusNode: _focusNode, |
| style: const MenuStyle(alignment: AlignmentDirectional.topEnd), |
| alignmentOffset: const Offset(100, -8), |
| menuChildren: <Widget>[ |
| MenuItemButton( |
| shortcut: TestMenu.standaloneMenu1.shortcut, |
| onPressed: () { |
| _itemSelected(TestMenu.standaloneMenu1); |
| }, |
| child: Text(TestMenu.standaloneMenu1.label), |
| ), |
| MenuItemButton( |
| leadingIcon: const Icon(Icons.send), |
| trailingIcon: const Icon(Icons.mail), |
| onPressed: () { |
| _itemSelected(TestMenu.standaloneMenu2); |
| }, |
| child: Text(TestMenu.standaloneMenu2.label), |
| ), |
| ], |
| builder: (BuildContext context, MenuController controller, Widget? child) { |
| return TextButton( |
| focusNode: _focusNode, |
| onPressed: () { |
| if (controller.isOpen) { |
| controller.close(); |
| } else { |
| controller.open(); |
| } |
| }, |
| child: child!, |
| ); |
| }, |
| child: const Text('Open Menu'), |
| ), |
| ConstrainedBox( |
| constraints: const BoxConstraints(maxWidth: 400), |
| child: Column( |
| crossAxisAlignment: CrossAxisAlignment.stretch, |
| children: <Widget>[ |
| _ControlSlider( |
| label: 'Extra Padding: ${widget.extraPadding.toStringAsFixed(1)}', |
| value: widget.extraPadding, |
| max: 40, |
| divisions: 20, |
| onChanged: (double value) { |
| widget.onExtraPaddingChanged(value); |
| }, |
| ), |
| _ControlSlider( |
| label: 'Horizontal Density: ${widget.density.horizontal.toStringAsFixed(1)}', |
| value: widget.density.horizontal, |
| max: 4, |
| min: -4, |
| divisions: 12, |
| onChanged: (double value) { |
| widget.onDensityChanged( |
| VisualDensity( |
| horizontal: value, |
| vertical: widget.density.vertical, |
| ), |
| ); |
| }, |
| ), |
| _ControlSlider( |
| label: 'Vertical Density: ${widget.density.vertical.toStringAsFixed(1)}', |
| value: widget.density.vertical, |
| max: 4, |
| min: -4, |
| divisions: 12, |
| onChanged: (double value) { |
| widget.onDensityChanged( |
| VisualDensity( |
| horizontal: widget.density.horizontal, |
| vertical: value, |
| ), |
| ); |
| }, |
| ), |
| ], |
| ), |
| ), |
| Column( |
| crossAxisAlignment: CrossAxisAlignment.start, |
| children: <Widget>[ |
| Row( |
| mainAxisSize: MainAxisSize.min, |
| children: <Widget>[ |
| Checkbox( |
| value: widget.textDirection == TextDirection.rtl, |
| onChanged: (bool? value) { |
| if (value ?? false) { |
| widget.onTextDirectionChanged(TextDirection.rtl); |
| } else { |
| widget.onTextDirectionChanged(TextDirection.ltr); |
| } |
| }, |
| ), |
| const Text('RTL Text') |
| ], |
| ), |
| Row( |
| mainAxisSize: MainAxisSize.min, |
| children: <Widget>[ |
| Checkbox( |
| value: widget.addItem, |
| onChanged: (bool? value) { |
| if (value ?? false) { |
| widget.onAddItemChanged(true); |
| } else { |
| widget.onAddItemChanged(false); |
| } |
| }, |
| ), |
| const Text('Add Item') |
| ], |
| ), |
| Row( |
| mainAxisSize: MainAxisSize.min, |
| children: <Widget>[ |
| Checkbox( |
| value: widget.transparent, |
| onChanged: (bool? value) { |
| if (value ?? false) { |
| widget.onTransparentChanged(true); |
| } else { |
| widget.onTransparentChanged(false); |
| } |
| }, |
| ), |
| const Text('Transparent') |
| ], |
| ), |
| Row( |
| mainAxisSize: MainAxisSize.min, |
| children: <Widget>[ |
| Checkbox( |
| value: widget.funkyTheme, |
| onChanged: (bool? value) { |
| if (value ?? false) { |
| widget.onFunkyThemeChanged(true); |
| } else { |
| widget.onFunkyThemeChanged(false); |
| } |
| }, |
| ), |
| const Text('Funky Theme') |
| ], |
| ), |
| ], |
| ), |
| ], |
| ), |
| ); |
| } |
| |
| void _itemSelected(TestMenu item) { |
| debugPrint('App: Selected item ${item.label}'); |
| } |
| } |
| |
| class _ControlSlider extends StatelessWidget { |
| const _ControlSlider({ |
| required this.label, |
| required this.value, |
| required this.onChanged, |
| this.min = 0, |
| this.max = 1, |
| this.divisions, |
| }); |
| |
| final String label; |
| final double value; |
| final ValueChanged<double> onChanged; |
| final double min; |
| final double max; |
| final int? divisions; |
| |
| @override |
| Widget build(BuildContext context) { |
| return Row( |
| mainAxisAlignment: MainAxisAlignment.end, |
| children: <Widget>[ |
| Container( |
| alignment: AlignmentDirectional.centerEnd, |
| constraints: const BoxConstraints(minWidth: 150), |
| child: Text(label), |
| ), |
| Expanded( |
| child: Slider( |
| value: value, |
| min: min, |
| max: max, |
| divisions: divisions, |
| onChanged: onChanged, |
| ), |
| ), |
| ], |
| ); |
| } |
| } |
| |
| class _TestMenus extends StatefulWidget { |
| const _TestMenus({ |
| required this.menuController, |
| this.addItem = false, |
| }); |
| |
| final MenuController menuController; |
| final bool addItem; |
| |
| @override |
| State<_TestMenus> createState() => _TestMenusState(); |
| } |
| |
| class _TestMenusState extends State<_TestMenus> { |
| final TextEditingController textController = TextEditingController(); |
| bool? checkboxState = false; |
| TestMenu? radioValue; |
| ShortcutRegistryEntry? _shortcutsEntry; |
| |
| void _itemSelected(TestMenu item) { |
| debugPrint('App: Selected item ${item.label}'); |
| } |
| |
| void _openItem(TestMenu item) { |
| debugPrint('App: Opened item ${item.label}'); |
| } |
| |
| void _closeItem(TestMenu item) { |
| debugPrint('App: Closed item ${item.label}'); |
| } |
| |
| void _setRadio(TestMenu item) { |
| debugPrint('App: Set Radio item ${item.label}'); |
| setState(() { |
| radioValue = item; |
| }); |
| } |
| |
| void _setCheck(TestMenu item) { |
| debugPrint('App: Set Checkbox item ${item.label}'); |
| setState(() { |
| switch (checkboxState) { |
| case false: |
| checkboxState = true; |
| break; |
| case true: |
| checkboxState = null; |
| break; |
| case null: |
| checkboxState = false; |
| break; |
| } |
| }); |
| } |
| |
| @override |
| void didChangeDependencies() { |
| super.didChangeDependencies(); |
| _shortcutsEntry?.dispose(); |
| final Map<ShortcutActivator, Intent> shortcuts = <ShortcutActivator, Intent>{}; |
| for (final TestMenu item in TestMenu.values) { |
| if (item.shortcut == null) { |
| continue; |
| } |
| switch (item) { |
| case TestMenu.radioMenu1: |
| case TestMenu.radioMenu2: |
| case TestMenu.radioMenu3: |
| shortcuts[item.shortcut!] = VoidCallbackIntent(() => _setRadio(item)); |
| break; |
| case TestMenu.subMenu1: |
| shortcuts[item.shortcut!] = VoidCallbackIntent(() => _setCheck(item)); |
| break; |
| case TestMenu.mainMenu1: |
| case TestMenu.mainMenu2: |
| case TestMenu.mainMenu3: |
| case TestMenu.mainMenu4: |
| case TestMenu.subMenu2: |
| case TestMenu.subMenu3: |
| case TestMenu.subMenu4: |
| case TestMenu.subMenu5: |
| case TestMenu.subMenu6: |
| case TestMenu.subMenu7: |
| case TestMenu.subMenu8: |
| case TestMenu.subSubMenu1: |
| case TestMenu.subSubMenu2: |
| case TestMenu.subSubMenu3: |
| case TestMenu.subSubSubMenu1: |
| case TestMenu.testButton: |
| case TestMenu.standaloneMenu1: |
| case TestMenu.standaloneMenu2: |
| shortcuts[item.shortcut!] = VoidCallbackIntent(() => _itemSelected(item)); |
| break; |
| } |
| } |
| _shortcutsEntry = ShortcutRegistry.of(context).addAll(shortcuts); |
| } |
| |
| @override |
| void dispose() { |
| _shortcutsEntry?.dispose(); |
| super.dispose(); |
| } |
| |
| @override |
| Widget build(BuildContext context) { |
| return Row( |
| children: <Widget>[ |
| Expanded( |
| child: MenuBar( |
| controller: widget.menuController, |
| children: <Widget>[ |
| SubmenuButton( |
| onOpen: () { |
| _openItem(TestMenu.mainMenu1); |
| }, |
| onClose: () { |
| _closeItem(TestMenu.mainMenu1); |
| }, |
| menuChildren: <Widget>[ |
| CheckboxMenuButton( |
| value: checkboxState, |
| tristate: true, |
| shortcut: TestMenu.subMenu1.shortcut, |
| trailingIcon: const Icon(Icons.assessment), |
| onChanged: (bool? value) { |
| setState(() { |
| checkboxState = value; |
| }); |
| _itemSelected(TestMenu.subMenu1); |
| }, |
| child: Text(TestMenu.subMenu1.label), |
| ), |
| RadioMenuButton<TestMenu>( |
| value: TestMenu.radioMenu1, |
| groupValue: radioValue, |
| toggleable: true, |
| shortcut: TestMenu.radioMenu1.shortcut, |
| trailingIcon: const Icon(Icons.assessment), |
| onChanged: (TestMenu? value) { |
| setState(() { |
| radioValue = value; |
| }); |
| _itemSelected(TestMenu.radioMenu1); |
| }, |
| child: Text(TestMenu.radioMenu1.label), |
| ), |
| RadioMenuButton<TestMenu>( |
| value: TestMenu.radioMenu2, |
| groupValue: radioValue, |
| toggleable: true, |
| shortcut: TestMenu.radioMenu2.shortcut, |
| trailingIcon: const Icon(Icons.assessment), |
| onChanged: (TestMenu? value) { |
| setState(() { |
| radioValue = value; |
| }); |
| _itemSelected(TestMenu.radioMenu2); |
| }, |
| child: Text(TestMenu.radioMenu2.label), |
| ), |
| RadioMenuButton<TestMenu>( |
| value: TestMenu.radioMenu3, |
| groupValue: radioValue, |
| toggleable: true, |
| shortcut: TestMenu.radioMenu3.shortcut, |
| trailingIcon: const Icon(Icons.assessment), |
| onChanged: (TestMenu? value) { |
| setState(() { |
| radioValue = value; |
| }); |
| _itemSelected(TestMenu.radioMenu3); |
| }, |
| child: Text(TestMenu.radioMenu3.label), |
| ), |
| MenuItemButton( |
| leadingIcon: const Icon(Icons.send), |
| trailingIcon: const Icon(Icons.mail), |
| onPressed: () { |
| _itemSelected(TestMenu.subMenu2); |
| }, |
| child: Text(TestMenu.subMenu2.label), |
| ), |
| ], |
| child: Text(TestMenu.mainMenu1.label), |
| ), |
| SubmenuButton( |
| onOpen: () { |
| _openItem(TestMenu.mainMenu2); |
| }, |
| onClose: () { |
| _closeItem(TestMenu.mainMenu2); |
| }, |
| menuChildren: <Widget>[ |
| TextButton( |
| child: const Text('TEST'), |
| onPressed: () { |
| _itemSelected(TestMenu.testButton); |
| widget.menuController.close(); |
| }, |
| ), |
| MenuItemButton( |
| shortcut: TestMenu.subMenu3.shortcut, |
| onPressed: () { |
| _itemSelected(TestMenu.subMenu3); |
| }, |
| child: Text(TestMenu.subMenu3.label), |
| ), |
| ], |
| child: Text(TestMenu.mainMenu2.label), |
| ), |
| SubmenuButton( |
| onOpen: () { |
| _openItem(TestMenu.mainMenu3); |
| }, |
| onClose: () { |
| _closeItem(TestMenu.mainMenu3); |
| }, |
| menuChildren: <Widget>[ |
| MenuItemButton( |
| child: Text(TestMenu.subMenu8.label), |
| onPressed: () { |
| _itemSelected(TestMenu.subMenu8); |
| }, |
| ), |
| ], |
| child: Text(TestMenu.mainMenu3.label), |
| ), |
| SubmenuButton( |
| onOpen: () { |
| _openItem(TestMenu.mainMenu4); |
| }, |
| onClose: () { |
| _closeItem(TestMenu.mainMenu4); |
| }, |
| menuChildren: <Widget>[ |
| Actions( |
| actions: <Type, Action<Intent>>{ |
| ActivateIntent: CallbackAction<ActivateIntent>( |
| onInvoke: (ActivateIntent? intent) { |
| debugPrint('Activated!'); |
| return; |
| }, |
| ) |
| }, |
| child: MenuItemButton( |
| onPressed: () { |
| debugPrint('Activated text input item with ${textController.text} as a value.'); |
| }, |
| child: SizedBox( |
| width: 200, |
| child: TextField( |
| controller: textController, |
| onSubmitted: (String value) { |
| debugPrint('String $value submitted.'); |
| }, |
| ), |
| ), |
| ), |
| ), |
| SubmenuButton( |
| onOpen: () { |
| _openItem(TestMenu.subMenu5); |
| }, |
| onClose: () { |
| _closeItem(TestMenu.subMenu5); |
| }, |
| menuChildren: <Widget>[ |
| MenuItemButton( |
| shortcut: TestMenu.subSubMenu1.shortcut, |
| onPressed: () { |
| _itemSelected(TestMenu.subSubMenu1); |
| }, |
| child: Text(TestMenu.subSubMenu1.label), |
| ), |
| MenuItemButton( |
| child: Text(TestMenu.subSubMenu2.label), |
| onPressed: () { |
| _itemSelected(TestMenu.subSubMenu2); |
| }, |
| ), |
| if (widget.addItem) |
| SubmenuButton( |
| menuChildren: <Widget>[ |
| MenuItemButton( |
| shortcut: TestMenu.subSubSubMenu1.shortcut, |
| onPressed: () { |
| _itemSelected(TestMenu.subSubSubMenu1); |
| }, |
| child: Text(TestMenu.subSubSubMenu1.label), |
| ), |
| ], |
| child: Text(TestMenu.subSubMenu3.label), |
| ), |
| ], |
| child: Text(TestMenu.subMenu5.label), |
| ), |
| MenuItemButton( |
| // Disabled button |
| shortcut: TestMenu.subMenu6.shortcut, |
| child: Text(TestMenu.subMenu6.label), |
| ), |
| MenuItemButton( |
| child: Text(TestMenu.subMenu7.label), |
| onPressed: () { |
| _itemSelected(TestMenu.subMenu7); |
| }, |
| ), |
| MenuItemButton( |
| child: Text(TestMenu.subMenu7.label), |
| onPressed: () { |
| _itemSelected(TestMenu.subMenu7); |
| }, |
| ), |
| MenuItemButton( |
| child: Text(TestMenu.subMenu8.label), |
| onPressed: () { |
| _itemSelected(TestMenu.subMenu8); |
| }, |
| ), |
| ], |
| child: Text(TestMenu.mainMenu4.label), |
| ), |
| ], |
| ), |
| ), |
| ], |
| ); |
| } |
| } |
| |
| enum TestMenu { |
| mainMenu1('Menu 1'), |
| mainMenu2('Menu 2'), |
| mainMenu3('Menu 3'), |
| mainMenu4('Menu 4'), |
| radioMenu1('Radio Menu One', SingleActivator(LogicalKeyboardKey.digit1, control: true)), |
| radioMenu2('Radio Menu Two', SingleActivator(LogicalKeyboardKey.digit2, control: true)), |
| radioMenu3('Radio Menu Three', SingleActivator(LogicalKeyboardKey.digit3, control: true)), |
| subMenu1('Sub Menu 1', SingleActivator(LogicalKeyboardKey.keyB, control: true)), |
| subMenu2('Sub Menu 2'), |
| subMenu3('Sub Menu 3', SingleActivator(LogicalKeyboardKey.enter, control: true)), |
| subMenu4('Sub Menu 4'), |
| subMenu5('Sub Menu 5'), |
| subMenu6('Sub Menu 6', SingleActivator(LogicalKeyboardKey.tab, control: true)), |
| subMenu7('Sub Menu 7'), |
| subMenu8('Sub Menu 8'), |
| subSubMenu1('Sub Sub Menu 1', SingleActivator(LogicalKeyboardKey.f10, control: true)), |
| subSubMenu2('Sub Sub Menu 2'), |
| subSubMenu3('Sub Sub Menu 3'), |
| subSubSubMenu1('Sub Sub Sub Menu 1', SingleActivator(LogicalKeyboardKey.f11, control: true)), |
| testButton('TEST button'), |
| standaloneMenu1('Standalone Menu 1', SingleActivator(LogicalKeyboardKey.keyC, control: true)), |
| standaloneMenu2('Standalone Menu 2'); |
| |
| const TestMenu(this.label, [this.shortcut]); |
| final String label; |
| final MenuSerializableShortcut? shortcut; |
| } |