mirror of
https://github.com/flutter/flutter
synced 2024-10-12 11:12:54 +00:00
Implements a PlatformMenuBar widget and associated data structures (#100274)
Implements a PlatformMenuBar widget and associated data structures for defining menu bars that use native APIs for rendering. This PR includes: A PlatformMenuBar class, which is a widget that menu bar data can be attached to for sending to the platform. A PlatformMenuDelegate base, which is the type taken by a new WidgetsBinding.platformMenuDelegate. An implementation of the above in DefaultPlatformMenuDelegate that talks to the built-in "flutter/menu" channel to talk to the built-in platform implementation. The delegate is so that a plugin could override with its own delegate and provide other platforms with native menu support using the same widgets to define the menus. This is the framework part of the implementation. The engine part will be in flutter/engine#32080 (and flutter/engine#32358)
This commit is contained in:
parent
2af2c9a68d
commit
2d9ad26086
|
@ -0,0 +1,138 @@
|
|||
// 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.
|
||||
|
||||
// Flutter code sample for PlatformMenuBar
|
||||
|
||||
////////////////////////////////////
|
||||
// THIS SAMPLE ONLY WORKS ON MACOS.
|
||||
////////////////////////////////////
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
|
||||
void main() => runApp(const SampleApp());
|
||||
|
||||
enum MenuSelection {
|
||||
about,
|
||||
showMessage,
|
||||
}
|
||||
|
||||
class SampleApp extends StatelessWidget {
|
||||
const SampleApp({Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return const MaterialApp(
|
||||
home: Scaffold(body: MyMenuBarApp()),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class MyMenuBarApp extends StatefulWidget {
|
||||
const MyMenuBarApp({Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
State<MyMenuBarApp> createState() => _MyMenuBarAppState();
|
||||
}
|
||||
|
||||
class _MyMenuBarAppState extends State<MyMenuBarApp> {
|
||||
String _message = 'Hello';
|
||||
bool _showMessage = false;
|
||||
|
||||
void _handleMenuSelection(MenuSelection value) {
|
||||
switch (value) {
|
||||
case MenuSelection.about:
|
||||
showAboutDialog(
|
||||
context: context,
|
||||
applicationName: 'MenuBar Sample',
|
||||
applicationVersion: '1.0.0',
|
||||
);
|
||||
break;
|
||||
case MenuSelection.showMessage:
|
||||
setState(() {
|
||||
_showMessage = !_showMessage;
|
||||
});
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
////////////////////////////////////
|
||||
// THIS SAMPLE ONLY WORKS ON MACOS.
|
||||
////////////////////////////////////
|
||||
|
||||
// This builds a menu hierarchy that looks like this:
|
||||
// Flutter API Sample
|
||||
// ├ About
|
||||
// ├ ──────── (group divider)
|
||||
// ├ Hide/Show Message
|
||||
// ├ Messages
|
||||
// │ ├ I am not throwing away my shot.
|
||||
// │ └ There's a million things I haven't done, but just you wait.
|
||||
// └ Quit
|
||||
return PlatformMenuBar(
|
||||
menus: <MenuItem>[
|
||||
PlatformMenu(
|
||||
label: 'Flutter API Sample',
|
||||
menus: <MenuItem>[
|
||||
PlatformMenuItemGroup(
|
||||
members: <MenuItem>[
|
||||
PlatformMenuItem(
|
||||
label: 'About',
|
||||
onSelected: () {
|
||||
_handleMenuSelection(MenuSelection.about);
|
||||
},
|
||||
)
|
||||
],
|
||||
),
|
||||
PlatformMenuItemGroup(
|
||||
members: <MenuItem>[
|
||||
PlatformMenuItem(
|
||||
onSelected: () {
|
||||
_handleMenuSelection(MenuSelection.showMessage);
|
||||
},
|
||||
shortcut: const CharacterActivator('m'),
|
||||
label: _showMessage ? 'Hide Message' : 'Show Message',
|
||||
),
|
||||
PlatformMenu(
|
||||
label: 'Messages',
|
||||
menus: <MenuItem>[
|
||||
PlatformMenuItem(
|
||||
label: 'I am not throwing away my shot.',
|
||||
shortcut: const SingleActivator(LogicalKeyboardKey.digit1, meta: true),
|
||||
onSelected: () {
|
||||
setState(() {
|
||||
_message = 'I am not throwing away my shot.';
|
||||
});
|
||||
},
|
||||
),
|
||||
PlatformMenuItem(
|
||||
label: "There's a million things I haven't done, but just you wait.",
|
||||
shortcut: const SingleActivator(LogicalKeyboardKey.digit2, meta: true),
|
||||
onSelected: () {
|
||||
setState(() {
|
||||
_message = "There's a million things I haven't done, but just you wait.";
|
||||
});
|
||||
},
|
||||
),
|
||||
],
|
||||
)
|
||||
],
|
||||
),
|
||||
if (PlatformProvidedMenuItem.hasMenu(PlatformProvidedMenuItemType.quit))
|
||||
const PlatformProvidedMenuItem(type: PlatformProvidedMenuItemType.quit),
|
||||
],
|
||||
),
|
||||
],
|
||||
body: Center(
|
||||
child: Text(_showMessage
|
||||
? _message
|
||||
: 'This space intentionally left blank.\n'
|
||||
'Show a message here using the menu.'),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
|
@ -28,7 +28,7 @@ class ChildLayoutHelper {
|
|||
/// This method calls [RenderBox.getDryLayout] on the given [RenderBox].
|
||||
///
|
||||
/// This method should only be called by the parent of the provided
|
||||
/// [RenderBox] child as it bounds parent and child together (if the child
|
||||
/// [RenderBox] child as it binds parent and child together (if the child
|
||||
/// is marked as dirty, the child will also be marked as dirty).
|
||||
///
|
||||
/// See also:
|
||||
|
@ -46,7 +46,7 @@ class ChildLayoutHelper {
|
|||
/// `parentUsesSize` set to true to receive its [Size].
|
||||
///
|
||||
/// This method should only be called by the parent of the provided
|
||||
/// [RenderBox] child as it bounds parent and child together (if the child
|
||||
/// [RenderBox] child as it binds parent and child together (if the child
|
||||
/// is marked as dirty, the child will also be marked as dirty).
|
||||
///
|
||||
/// See also:
|
||||
|
|
|
@ -392,4 +392,53 @@ class SystemChannels {
|
|||
'flutter/localization',
|
||||
JSONMethodCodec(),
|
||||
);
|
||||
|
||||
/// A [MethodChannel] for platform menu specification and control.
|
||||
///
|
||||
/// The following outgoing method is defined for this channel (invoked using
|
||||
/// [OptionalMethodChannel.invokeMethod]):
|
||||
///
|
||||
/// * `Menu.setMenu`: sends the configuration of the platform menu, including
|
||||
/// labels, enable/disable information, and unique integer identifiers for
|
||||
/// each menu item. The configuration is sent as a `Map<String, Object?>`
|
||||
/// encoding the list of top level menu items in window "0", which each
|
||||
/// have a hierarchy of `Map<String, Object?>` containing the required
|
||||
/// data, sent via a [StandardMessageCodec]. It is typically generated from
|
||||
/// a list of [MenuItem]s, and ends up looking like this example:
|
||||
///
|
||||
/// ```dart
|
||||
/// List<Map<String, Object?>> menu = <String, Object?>{
|
||||
/// '0': <Map<String, Object?>>[
|
||||
/// <String, Object?>{
|
||||
/// 'id': 1,
|
||||
/// 'label': 'First Menu Label',
|
||||
/// 'enabled': true,
|
||||
/// 'children': <Map<String, Object?>>[
|
||||
/// <String, Object?>{
|
||||
/// 'id': 2,
|
||||
/// 'label': 'Sub Menu Label',
|
||||
/// 'enabled': true,
|
||||
/// },
|
||||
/// ],
|
||||
/// },
|
||||
/// ],
|
||||
/// };
|
||||
/// ```
|
||||
///
|
||||
/// The following incoming methods are defined for this channel (registered
|
||||
/// using [MethodChannel.setMethodCallHandler]).
|
||||
///
|
||||
/// * `Menu.selectedCallback`: Called when a menu item is selected, along
|
||||
/// with the unique ID of the menu item selected.
|
||||
///
|
||||
/// * `Menu.opened`: Called when a submenu is opened, along with the unique
|
||||
/// ID of the submenu.
|
||||
///
|
||||
/// * `Menu.closed`: Called when a submenu is closed, along with the unique
|
||||
/// ID of the submenu.
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * [DefaultPlatformMenuDelegate], which uses this channel.
|
||||
static const MethodChannel menu = OptionalMethodChannel('flutter/menu');
|
||||
}
|
||||
|
|
|
@ -16,6 +16,7 @@ import 'app.dart';
|
|||
import 'debug.dart';
|
||||
import 'focus_manager.dart';
|
||||
import 'framework.dart';
|
||||
import 'platform_menu_bar.dart';
|
||||
import 'router.dart';
|
||||
import 'widget_inspector.dart';
|
||||
|
||||
|
@ -294,6 +295,7 @@ mixin WidgetsBinding on BindingBase, ServicesBinding, SchedulerBinding, GestureB
|
|||
FlutterErrorDetails.propertiesTransformers.add(debugTransformDebugCreator);
|
||||
return true;
|
||||
}());
|
||||
platformMenuDelegate = DefaultPlatformMenuDelegate();
|
||||
}
|
||||
|
||||
/// The current [WidgetsBinding], if one has been created.
|
||||
|
@ -523,6 +525,13 @@ mixin WidgetsBinding on BindingBase, ServicesBinding, SchedulerBinding, GestureB
|
|||
/// See [FocusManager] for more details.
|
||||
FocusManager get focusManager => _buildOwner!.focusManager;
|
||||
|
||||
/// A delegate that communicates with a platform plugin for serializing and
|
||||
/// managing platform-rendered menu bars created by [PlatformMenuBar].
|
||||
///
|
||||
/// This is set by default to a [DefaultPlatformMenuDelegate] instance in
|
||||
/// [initInstances].
|
||||
late PlatformMenuDelegate platformMenuDelegate;
|
||||
|
||||
final List<WidgetsBindingObserver> _observers = <WidgetsBindingObserver>[];
|
||||
|
||||
/// Registers the given object as a binding observer. Binding
|
||||
|
|
1003
packages/flutter/lib/src/widgets/platform_menu_bar.dart
Normal file
1003
packages/flutter/lib/src/widgets/platform_menu_bar.dart
Normal file
File diff suppressed because it is too large
Load diff
|
@ -12,6 +12,7 @@ import 'focus_manager.dart';
|
|||
import 'focus_scope.dart';
|
||||
import 'framework.dart';
|
||||
import 'inherited_notifier.dart';
|
||||
import 'platform_menu_bar.dart';
|
||||
|
||||
/// A set of [KeyboardKey]s that can be used as the keys in a [Map].
|
||||
///
|
||||
|
@ -397,7 +398,7 @@ class ShortcutMapProperty extends DiagnosticsProperty<Map<ShortcutActivator, Int
|
|||
///
|
||||
/// * [CharacterActivator], an activator that represents key combinations
|
||||
/// that result in the specified character, such as question mark.
|
||||
class SingleActivator with Diagnosticable implements ShortcutActivator {
|
||||
class SingleActivator with Diagnosticable, MenuSerializableShortcut implements ShortcutActivator {
|
||||
/// Triggered when the [trigger] key is pressed while the modifiers are held.
|
||||
///
|
||||
/// The `trigger` should be the non-modifier key that is pressed after all the
|
||||
|
@ -517,6 +518,17 @@ class SingleActivator with Diagnosticable implements ShortcutActivator {
|
|||
&& (meta == (pressed.contains(LogicalKeyboardKey.metaLeft) || pressed.contains(LogicalKeyboardKey.metaRight)));
|
||||
}
|
||||
|
||||
@override
|
||||
ShortcutSerialization serializeForMenu() {
|
||||
return ShortcutSerialization.modifier(
|
||||
trigger,
|
||||
shift: shift,
|
||||
alt: alt,
|
||||
meta: meta,
|
||||
control: control,
|
||||
);
|
||||
}
|
||||
|
||||
/// Returns a short and readable description of the key combination.
|
||||
///
|
||||
/// Intended to be used in debug mode for logging purposes. In release mode,
|
||||
|
@ -572,7 +584,7 @@ class SingleActivator with Diagnosticable implements ShortcutActivator {
|
|||
///
|
||||
/// * [SingleActivator], an activator that represents a single key combined
|
||||
/// with modifiers, such as `Ctrl+C`.
|
||||
class CharacterActivator with Diagnosticable implements ShortcutActivator {
|
||||
class CharacterActivator with Diagnosticable, MenuSerializableShortcut implements ShortcutActivator {
|
||||
/// Create a [CharacterActivator] from the triggering character.
|
||||
const CharacterActivator(this.character);
|
||||
|
||||
|
@ -608,6 +620,11 @@ class CharacterActivator with Diagnosticable implements ShortcutActivator {
|
|||
return result;
|
||||
}
|
||||
|
||||
@override
|
||||
ShortcutSerialization serializeForMenu() {
|
||||
return ShortcutSerialization.character(character);
|
||||
}
|
||||
|
||||
@override
|
||||
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
|
||||
super.debugFillProperties(properties);
|
||||
|
|
|
@ -83,6 +83,7 @@ export 'src/widgets/page_view.dart';
|
|||
export 'src/widgets/pages.dart';
|
||||
export 'src/widgets/performance_overlay.dart';
|
||||
export 'src/widgets/placeholder.dart';
|
||||
export 'src/widgets/platform_menu_bar.dart';
|
||||
export 'src/widgets/platform_view.dart';
|
||||
export 'src/widgets/preferred_size.dart';
|
||||
export 'src/widgets/primary_scroll_controller.dart';
|
||||
|
|
400
packages/flutter/test/widgets/platform_menu_bar_test.dart
Normal file
400
packages/flutter/test/widgets/platform_menu_bar_test.dart
Normal file
|
@ -0,0 +1,400 @@
|
|||
// 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/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/rendering.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter/src/foundation/diagnostics.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
|
||||
void main() {
|
||||
TestWidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
late FakeMenuChannel fakeMenuChannel;
|
||||
late PlatformMenuDelegate originalDelegate;
|
||||
late DefaultPlatformMenuDelegate delegate;
|
||||
final List<String> activated = <String>[];
|
||||
final List<String> opened = <String>[];
|
||||
final List<String> closed = <String>[];
|
||||
|
||||
void onActivate(String item) {
|
||||
activated.add(item);
|
||||
}
|
||||
|
||||
void onOpen(String item) {
|
||||
opened.add(item);
|
||||
}
|
||||
|
||||
void onClose(String item) {
|
||||
closed.add(item);
|
||||
}
|
||||
|
||||
setUp(() {
|
||||
fakeMenuChannel = FakeMenuChannel((MethodCall call) async {});
|
||||
delegate = DefaultPlatformMenuDelegate(channel: fakeMenuChannel);
|
||||
originalDelegate = WidgetsBinding.instance.platformMenuDelegate;
|
||||
WidgetsBinding.instance.platformMenuDelegate = delegate;
|
||||
activated.clear();
|
||||
opened.clear();
|
||||
closed.clear();
|
||||
});
|
||||
|
||||
tearDown(() {
|
||||
WidgetsBinding.instance.platformMenuDelegate = originalDelegate;
|
||||
});
|
||||
|
||||
group('PlatformMenuBar', () {
|
||||
testWidgets('basic menu structure is transmitted to platform', (WidgetTester tester) async {
|
||||
await tester.pumpWidget(
|
||||
MaterialApp(
|
||||
home: Material(
|
||||
child: PlatformMenuBar(
|
||||
body: const Center(child: Text('Body')),
|
||||
menus: createTestMenus(
|
||||
onActivate: onActivate,
|
||||
onOpen: onOpen,
|
||||
onClose: onClose,
|
||||
shortcuts: <String, MenuSerializableShortcut>{
|
||||
subSubMenu10[0]: const SingleActivator(LogicalKeyboardKey.keyA, control: true),
|
||||
subSubMenu10[1]: const SingleActivator(LogicalKeyboardKey.keyB, shift: true),
|
||||
subSubMenu10[2]: const SingleActivator(LogicalKeyboardKey.keyC, alt: true),
|
||||
subSubMenu10[3]: const SingleActivator(LogicalKeyboardKey.keyD, meta: true),
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
expect(fakeMenuChannel.outgoingCalls.last.method, equals('Menu.setMenu'));
|
||||
expect(
|
||||
fakeMenuChannel.outgoingCalls.last.arguments,
|
||||
equals(
|
||||
<String, Object?>{
|
||||
'0': <Map<String, Object?>>[
|
||||
<String, Object?>{
|
||||
'id': 2,
|
||||
'label': 'Menu 0',
|
||||
'enabled': true,
|
||||
'children': <Map<String, Object?>>[
|
||||
<String, Object?>{
|
||||
'id': 1,
|
||||
'label': 'Sub Menu 00',
|
||||
'enabled': true,
|
||||
}
|
||||
]
|
||||
},
|
||||
<String, Object?>{
|
||||
'id': 12,
|
||||
'label': 'Menu 1',
|
||||
'enabled': true,
|
||||
'children': <Map<String, Object?>>[
|
||||
<String, Object?>{
|
||||
'id': 3,
|
||||
'label': 'Sub Menu 10',
|
||||
'enabled': true,
|
||||
},
|
||||
<String, Object?>{
|
||||
'id': 4,
|
||||
'isDivider': true,
|
||||
},
|
||||
<String, Object?>{
|
||||
'id': 10,
|
||||
'label': 'Sub Menu 11',
|
||||
'enabled': true,
|
||||
'children': <Map<String, Object?>>[
|
||||
<String, Object?>{
|
||||
'id': 5,
|
||||
'label': 'Sub Sub Menu 100',
|
||||
'enabled': true,
|
||||
'shortcutTrigger': 97,
|
||||
'shortcutModifiers': 8
|
||||
},
|
||||
<String, Object?>{
|
||||
'id': 6,
|
||||
'isDivider': true,
|
||||
},
|
||||
<String, Object?>{
|
||||
'id': 7,
|
||||
'label': 'Sub Sub Menu 101',
|
||||
'enabled': true,
|
||||
'shortcutTrigger': 98,
|
||||
'shortcutModifiers': 2
|
||||
},
|
||||
<String, Object?>{
|
||||
'id': 8,
|
||||
'label': 'Sub Sub Menu 102',
|
||||
'enabled': true,
|
||||
'shortcutTrigger': 99,
|
||||
'shortcutModifiers': 4
|
||||
},
|
||||
<String, Object?>{
|
||||
'id': 9,
|
||||
'label': 'Sub Sub Menu 103',
|
||||
'enabled': true,
|
||||
'shortcutTrigger': 100,
|
||||
'shortcutModifiers': 1
|
||||
}
|
||||
]
|
||||
},
|
||||
<String, Object?>{
|
||||
'id': 11,
|
||||
'label': 'Sub Menu 12',
|
||||
'enabled': true,
|
||||
}
|
||||
]
|
||||
},
|
||||
<String, Object?>{
|
||||
'id': 14,
|
||||
'label': 'Menu 2',
|
||||
'enabled': true,
|
||||
'children': <Map<String, Object?>>[
|
||||
<String, Object?>{
|
||||
'id': 13,
|
||||
'label': 'Sub Menu 20',
|
||||
'enabled': false,
|
||||
}
|
||||
]
|
||||
},
|
||||
<String, Object?>{
|
||||
'id': 15,
|
||||
'label': 'Menu 3',
|
||||
'enabled': false,
|
||||
'children': <Map<String, Object?>>[],
|
||||
},
|
||||
],
|
||||
},
|
||||
),
|
||||
);
|
||||
});
|
||||
testWidgets('asserts when more than one has locked the delegate', (WidgetTester tester) async {
|
||||
await tester.pumpWidget(
|
||||
const MaterialApp(
|
||||
home: Material(
|
||||
child: PlatformMenuBar(
|
||||
body: PlatformMenuBar(
|
||||
body: SizedBox(),
|
||||
menus: <MenuItem>[],
|
||||
),
|
||||
menus: <MenuItem>[],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
expect(tester.takeException(), isA<AssertionError>());
|
||||
});
|
||||
testWidgets('diagnostics', (WidgetTester tester) async {
|
||||
const PlatformMenuItem item = PlatformMenuItem(
|
||||
label: 'label2',
|
||||
shortcut: SingleActivator(LogicalKeyboardKey.keyA),
|
||||
);
|
||||
const PlatformMenuBar menuBar = PlatformMenuBar(
|
||||
body: SizedBox(),
|
||||
menus: <MenuItem>[item],
|
||||
);
|
||||
|
||||
await tester.pumpWidget(
|
||||
const MaterialApp(
|
||||
home: Material(
|
||||
child: menuBar,
|
||||
),
|
||||
),
|
||||
);
|
||||
await tester.pump();
|
||||
|
||||
expect(
|
||||
menuBar.toStringDeep(),
|
||||
equalsIgnoringHashCodes(
|
||||
'PlatformMenuBar#00000\n'
|
||||
' └─PlatformMenuItem#00000\n'
|
||||
' label: "label2"\n'
|
||||
' shortcut: SingleActivator#00000(keys: Key A)\n'
|
||||
' DISABLED\n',
|
||||
),
|
||||
);
|
||||
});
|
||||
});
|
||||
group('PlatformMenuBarItem', () {
|
||||
testWidgets('diagnostics', (WidgetTester tester) async {
|
||||
const PlatformMenuItem childItem = PlatformMenuItem(
|
||||
label: 'label',
|
||||
);
|
||||
const PlatformMenu item = PlatformMenu(
|
||||
label: 'label',
|
||||
menus: <MenuItem>[childItem],
|
||||
);
|
||||
|
||||
final DiagnosticPropertiesBuilder builder = DiagnosticPropertiesBuilder();
|
||||
item.debugFillProperties(builder);
|
||||
|
||||
final List<String> description = builder.properties
|
||||
.where((DiagnosticsNode node) => !node.isFiltered(DiagnosticLevel.info))
|
||||
.map((DiagnosticsNode node) => node.toString())
|
||||
.toList();
|
||||
|
||||
expect(description, <String>[
|
||||
'label: "label"',
|
||||
]);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
const List<String> mainMenu = <String>[
|
||||
'Menu 0',
|
||||
'Menu 1',
|
||||
'Menu 2',
|
||||
'Menu 3',
|
||||
];
|
||||
|
||||
const List<String> subMenu0 = <String>[
|
||||
'Sub Menu 00',
|
||||
];
|
||||
|
||||
const List<String> subMenu1 = <String>[
|
||||
'Sub Menu 10',
|
||||
'Sub Menu 11',
|
||||
'Sub Menu 12',
|
||||
];
|
||||
|
||||
const List<String> subSubMenu10 = <String>[
|
||||
'Sub Sub Menu 100',
|
||||
'Sub Sub Menu 101',
|
||||
'Sub Sub Menu 102',
|
||||
'Sub Sub Menu 103',
|
||||
];
|
||||
|
||||
const List<String> subMenu2 = <String>[
|
||||
'Sub Menu 20',
|
||||
];
|
||||
|
||||
List<MenuItem> createTestMenus({
|
||||
void Function(String)? onActivate,
|
||||
void Function(String)? onOpen,
|
||||
void Function(String)? onClose,
|
||||
Map<String, MenuSerializableShortcut> shortcuts = const <String, MenuSerializableShortcut>{},
|
||||
bool includeStandard = false,
|
||||
}) {
|
||||
final List<MenuItem> result = <MenuItem>[
|
||||
PlatformMenu(
|
||||
label: mainMenu[0],
|
||||
onOpen: onOpen != null ? () => onOpen(mainMenu[0]) : null,
|
||||
onClose: onClose != null ? () => onClose(mainMenu[0]) : null,
|
||||
menus: <MenuItem>[
|
||||
PlatformMenuItem(
|
||||
label: subMenu0[0],
|
||||
onSelected: onActivate != null ? () => onActivate(subMenu0[0]) : null,
|
||||
shortcut: shortcuts[subMenu0[0]],
|
||||
),
|
||||
],
|
||||
),
|
||||
PlatformMenu(
|
||||
label: mainMenu[1],
|
||||
onOpen: onOpen != null ? () => onOpen(mainMenu[1]) : null,
|
||||
onClose: onClose != null ? () => onClose(mainMenu[1]) : null,
|
||||
menus: <MenuItem>[
|
||||
PlatformMenuItemGroup(
|
||||
members: <MenuItem>[
|
||||
PlatformMenuItem(
|
||||
label: subMenu1[0],
|
||||
onSelected: onActivate != null ? () => onActivate(subMenu1[0]) : null,
|
||||
shortcut: shortcuts[subMenu1[0]],
|
||||
),
|
||||
],
|
||||
),
|
||||
PlatformMenu(
|
||||
label: subMenu1[1],
|
||||
onOpen: onOpen != null ? () => onOpen(subMenu1[1]) : null,
|
||||
onClose: onClose != null ? () => onClose(subMenu1[1]) : null,
|
||||
menus: <MenuItem>[
|
||||
PlatformMenuItemGroup(
|
||||
members: <MenuItem>[
|
||||
PlatformMenuItem(
|
||||
label: subSubMenu10[0],
|
||||
onSelected: onActivate != null ? () => onActivate(subSubMenu10[0]) : null,
|
||||
shortcut: shortcuts[subSubMenu10[0]],
|
||||
),
|
||||
],
|
||||
),
|
||||
PlatformMenuItem(
|
||||
label: subSubMenu10[1],
|
||||
onSelected: onActivate != null ? () => onActivate(subSubMenu10[1]) : null,
|
||||
shortcut: shortcuts[subSubMenu10[1]],
|
||||
),
|
||||
PlatformMenuItem(
|
||||
label: subSubMenu10[2],
|
||||
onSelected: onActivate != null ? () => onActivate(subSubMenu10[2]) : null,
|
||||
shortcut: shortcuts[subSubMenu10[2]],
|
||||
),
|
||||
PlatformMenuItem(
|
||||
label: subSubMenu10[3],
|
||||
onSelected: onActivate != null ? () => onActivate(subSubMenu10[3]) : null,
|
||||
shortcut: shortcuts[subSubMenu10[3]],
|
||||
),
|
||||
],
|
||||
),
|
||||
PlatformMenuItem(
|
||||
label: subMenu1[2],
|
||||
onSelected: onActivate != null ? () => onActivate(subMenu1[2]) : null,
|
||||
shortcut: shortcuts[subMenu1[2]],
|
||||
),
|
||||
],
|
||||
),
|
||||
PlatformMenu(
|
||||
label: mainMenu[2],
|
||||
onOpen: onOpen != null ? () => onOpen(mainMenu[2]) : null,
|
||||
onClose: onClose != null ? () => onClose(mainMenu[2]) : null,
|
||||
menus: <MenuItem>[
|
||||
PlatformMenuItem(
|
||||
// Always disabled.
|
||||
label: subMenu2[0],
|
||||
shortcut: shortcuts[subMenu2[0]],
|
||||
),
|
||||
],
|
||||
),
|
||||
// Disabled menu
|
||||
PlatformMenu(
|
||||
label: mainMenu[3],
|
||||
onOpen: onOpen != null ? () => onOpen(mainMenu[2]) : null,
|
||||
onClose: onClose != null ? () => onClose(mainMenu[2]) : null,
|
||||
menus: <MenuItem>[],
|
||||
),
|
||||
];
|
||||
return result;
|
||||
}
|
||||
|
||||
class FakeMenuChannel implements MethodChannel {
|
||||
FakeMenuChannel(this.outgoing) : assert(outgoing != null);
|
||||
|
||||
Future<dynamic> Function(MethodCall) outgoing;
|
||||
Future<void> Function(MethodCall)? incoming;
|
||||
|
||||
List<MethodCall> outgoingCalls = <MethodCall>[];
|
||||
|
||||
@override
|
||||
BinaryMessenger get binaryMessenger => throw UnimplementedError();
|
||||
|
||||
@override
|
||||
MethodCodec get codec => const StandardMethodCodec();
|
||||
|
||||
@override
|
||||
Future<List<T>> invokeListMethod<T>(String method, [dynamic arguments]) => throw UnimplementedError();
|
||||
|
||||
@override
|
||||
Future<Map<K, V>> invokeMapMethod<K, V>(String method, [dynamic arguments]) => throw UnimplementedError();
|
||||
|
||||
@override
|
||||
Future<T> invokeMethod<T>(String method, [dynamic arguments]) async {
|
||||
final MethodCall call = MethodCall(method, arguments);
|
||||
outgoingCalls.add(call);
|
||||
return await outgoing(call) as T;
|
||||
}
|
||||
|
||||
@override
|
||||
String get name => 'flutter/menu';
|
||||
|
||||
@override
|
||||
void setMethodCallHandler(Future<void> Function(MethodCall call)? handler) => incoming = handler;
|
||||
}
|
Loading…
Reference in a new issue