blob: ff297e737ddf6100d4168636723a87097a33541d [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/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;
}