Unverified Commit 406d86b4 authored by Greg Spencer's avatar Greg Spencer Committed by GitHub

PlatformMenuBar changes to bring it into line with upcoming MenuBar implementation (#104565)

When I was doing the MenuBar implementation, I made some changes to the PlatformMenuBar to allow it to understand shortcuts a little more, and to deprecate the body parameter rename it to child to match most other widgets.

These are those changes, separated out because they are separable, and I'm trying to make the MenuBar PR smaller.
parent 6efdf0ae
......@@ -126,7 +126,7 @@ class _MyMenuBarAppState extends State<MyMenuBarApp> {
],
),
],
body: Center(
child: Center(
child: Text(_showMessage
? _message
: 'This space intentionally left blank.\n'
......
......@@ -7,7 +7,10 @@ import 'dart:async';
import 'package:flutter/foundation.dart';
import 'package:flutter/services.dart';
import 'actions.dart';
import 'basic.dart';
import 'binding.dart';
import 'focus_manager.dart';
import 'framework.dart';
import 'shortcuts.dart';
......@@ -44,6 +47,7 @@ class ShortcutSerialization {
/// This is used by a [CharacterActivator] to serialize itself.
ShortcutSerialization.character(String character)
: _internal = <String, Object?>{_kShortcutCharacter: character},
_character = character,
assert(character.length == 1);
/// Creates a [ShortcutSerialization] representing a specific
......@@ -70,6 +74,11 @@ class ShortcutSerialization {
trigger != LogicalKeyboardKey.metaRight,
'Specifying a modifier key as a trigger is not allowed. '
'Use provided boolean parameters instead.'),
_trigger = trigger,
_control = control,
_shift = shift,
_alt = alt,
_meta = meta,
_internal = <String, Object?>{
_kShortcutTrigger: trigger.keyId,
_kShortcutModifiers: (control ? _shortcutModifierControl : 0) |
......@@ -80,6 +89,35 @@ class ShortcutSerialization {
final Map<String, Object?> _internal;
/// The keyboard key that triggers this shortcut, if any.
LogicalKeyboardKey? get trigger => _trigger;
LogicalKeyboardKey? _trigger;
/// The character that triggers this shortcut, if any.
String? get character => _character;
String? _character;
/// If this shortcut has a [trigger], this indicates whether or not the
/// control modifier needs to be down or not.
bool? get control => _control;
bool? _control;
/// If this shortcut has a [trigger], this indicates whether or not the
/// shift modifier needs to be down or not.
bool? get shift => _shift;
bool? _shift;
/// If this shortcut has a [trigger], this indicates whether or not the
/// alt modifier needs to be down or not.
bool? get alt => _alt;
bool? _alt;
/// If this shortcut has a [trigger], this indicates whether or not the meta
/// (also known as the Windows or Command key) modifier needs to be down or
/// not.
bool? get meta => _meta;
bool? _meta;
/// The bit mask for the [LogicalKeyboardKey.meta] key (or it's left/right
/// equivalents) being down.
static const int _shortcutModifierMeta = 1 << 0;
......@@ -124,10 +162,13 @@ mixin MenuSerializableShortcut {
/// An abstract class for describing cascading menu hierarchies that are part of
/// a [PlatformMenuBar].
///
/// This type is used by [PlatformMenuDelegate.setMenus] to accept the menu
/// This type is also used by [PlatformMenuDelegate.setMenus] to accept the menu
/// hierarchy to be sent to the platform, and by [PlatformMenuBar] to define the
/// menu hierarchy.
///
/// This class is abstract, and so can't be used directly. Typically subclasses
/// like [PlatformMenuItem] are used.
///
/// See also:
///
/// * [PlatformMenuBar], a widget that renders menu items using platform APIs
......@@ -141,8 +182,8 @@ abstract class MenuItem with Diagnosticable {
///
/// The `delegate` is the [PlatformMenuDelegate] that is requesting the
/// serialization. The `index` is the position of this menu item in the list
/// of children of the [PlatformMenu] it belongs to, and `count` is the number
/// of children in the [PlatformMenu] it belongs to.
/// of [menus] of the [PlatformMenu] it belongs to, and `count` is the number
/// of [menus] in the [PlatformMenu] it belongs to.
///
/// The `getId` parameter is a [MenuItemSerializableIdGenerator] function that
/// generates a unique ID for each menu item, which is to be returned in the
......@@ -152,6 +193,17 @@ abstract class MenuItem with Diagnosticable {
required MenuItemSerializableIdGenerator getId,
});
/// The optional shortcut that selects this [MenuItem].
///
/// This shortcut is only enabled when [onSelected] is set.
MenuSerializableShortcut? get shortcut => null;
/// Returns any child [MenuItem]s of this item.
///
/// Returns an empty list if this type of menu item doesn't have
/// children.
List<MenuItem> get menus => const <MenuItem>[];
/// Returns all descendant [MenuItem]s of this item.
///
/// Returns an empty list if this type of menu item doesn't have
......@@ -163,9 +215,27 @@ abstract class MenuItem with Diagnosticable {
///
/// Only items that do not have submenus will have this callback invoked.
///
/// Only one of [onSelected] or [onSelectedIntent] may be specified.
///
/// If neither [onSelected] nor [onSelectedIntent] are specified, then this
/// menu item is considered to be disabled.
///
/// The default implementation returns null.
VoidCallback? get onSelected => null;
/// Returns an intent, if any, to be invoked if the platform receives a
/// "Menu.selectedCallback" method call from the platform for this item.
///
/// Only items that do not have submenus will have this intent invoked.
///
/// Only one of [onSelected] or [onSelectedIntent] may be specified.
///
/// If neither [onSelected] nor [onSelectedIntent] are specified, then this
/// menu item is considered to be disabled.
///
/// The default implementation returns null.
Intent? get onSelectedIntent => null;
/// Returns a callback, if any, to be invoked if the platform menu receives a
/// "Menu.opened" method call from the platform for this item.
///
......@@ -181,6 +251,12 @@ abstract class MenuItem with Diagnosticable {
///
/// The default implementation returns null.
VoidCallback? get onClose => null;
/// Returns the list of group members if this menu item is a "grouping" menu
/// item, such as [PlatformMenuItemGroup].
///
/// Defaults to an empty list.
List<MenuItem> get members => const <MenuItem>[];
}
/// An abstract delegate class that can be used to set
......@@ -383,7 +459,12 @@ class DefaultPlatformMenuDelegate extends PlatformMenuDelegate {
}
final MenuItem item = _idMap[id]!;
if (call.method == _kMenuSelectedCallbackMethod) {
assert(item.onSelected == null || item.onSelectedIntent == null,
'Only one of MenuItem.onSelected or MenuItem.onSelectedIntent may be specified');
item.onSelected?.call();
if (item.onSelectedIntent != null) {
Actions.maybeInvoke(FocusManager.instance.primaryFocus!.context!, item.onSelectedIntent!);
}
} else if (call.method == _kMenuItemOpenedMethod) {
item.onOpen?.call();
} else if (call.method == _kMenuItemClosedMethod) {
......@@ -407,7 +488,7 @@ class DefaultPlatformMenuDelegate extends PlatformMenuDelegate {
/// the platform menu bar.
///
/// As far as Flutter is concerned, this widget has no visual representation,
/// and intercepts no events: it just returns the [body] from its build
/// and intercepts no events: it just returns the [child] from its build
/// function. This is because all of the rendering, shortcuts, and event
/// handling for the menu is handled by the plugin on the host platform.
///
......@@ -429,18 +510,32 @@ class DefaultPlatformMenuDelegate extends PlatformMenuDelegate {
class PlatformMenuBar extends StatefulWidget with DiagnosticableTreeMixin {
/// Creates a const [PlatformMenuBar].
///
/// The [body] and [menus] attributes are required.
/// The [child] and [menus] attributes are required.
const PlatformMenuBar({
super.key,
required this.body,
required this.menus,
});
this.child,
@Deprecated(
'Use the child attribute instead. '
'This feature was deprecated after v3.1.0-0.0.pre.'
)
this.body,
}) : assert(body == null || child == null,
'The body argument is deprecated, and only one of body or child may be used.');
/// The widget to be rendered in the Flutter window that these platform menus
/// are associated with.
/// The widget below this widget in the tree.
///
/// This is typically the body of the application's UI.
final Widget body;
/// {@macro flutter.widgets.ProxyWidget.child}
final Widget? child;
/// The widget below this widget in the tree.
///
/// This attribute is deprecated, use [child] instead.
@Deprecated(
'Use the child attribute instead. '
'This feature was deprecated after v3.1.0-0.0.pre.'
)
final Widget? body;
/// The list of menu items that are the top level children of the
/// [PlatformMenuBar].
......@@ -512,7 +607,7 @@ class _PlatformMenuBarState extends State<PlatformMenuBar> {
Widget build(BuildContext context) {
// PlatformMenuBar is really about managing the platform menu bar, and
// doesn't do any rendering or event handling in Flutter.
return widget.body;
return widget.child ?? widget.body ?? const SizedBox();
}
}
......@@ -547,6 +642,7 @@ class PlatformMenu extends MenuItem with DiagnosticableTreeMixin {
/// The menu items in the submenu opened by this menu item.
///
/// If this is an empty list, this [PlatformMenu] will be disabled.
@override
final List<MenuItem> menus;
/// Returns all descendant [MenuItem]s of this item.
......@@ -646,6 +742,7 @@ class PlatformMenuItemGroup extends MenuItem {
/// The [MenuItem]s that are members of this menu item group.
///
/// An assertion will be thrown if there isn't at least one member of the group.
@override
final List<MenuItem> members;
@override
......@@ -654,19 +751,32 @@ class PlatformMenuItemGroup extends MenuItem {
required MenuItemSerializableIdGenerator getId,
}) {
assert(members.isNotEmpty, 'There must be at least one member in a PlatformMenuItemGroup');
return serialize(this, delegate, getId: getId);
}
/// Converts the supplied object to the correct channel representation for the
/// 'flutter/menu' channel.
///
/// This API is supplied so that implementers of [PlatformMenuItemGroup] can share
/// this implementation.
static Iterable<Map<String, Object?>> serialize(
MenuItem group,
PlatformMenuDelegate delegate, {
required MenuItemSerializableIdGenerator getId,
}) {
final List<Map<String, Object?>> result = <Map<String, Object?>>[];
result.add(<String, Object?>{
_kIdKey: getId(this),
_kIdKey: getId(group),
_kIsDividerKey: true,
});
for (final MenuItem item in members) {
for (final MenuItem item in group.members) {
result.addAll(item.toChannelRepresentation(
delegate,
getId: getId,
));
}
result.add(<String, Object?>{
_kIdKey: getId(this),
_kIdKey: getId(group),
_kIsDividerKey: true,
});
return result;
......@@ -697,7 +807,8 @@ class PlatformMenuItem extends MenuItem {
required this.label,
this.shortcut,
this.onSelected,
});
this.onSelectedIntent,
}) : assert(onSelected == null || onSelectedIntent == null, 'Only one of onSelected or onSelectedIntent may be specified');
/// The required label used for rendering the menu item.
final String label;
......@@ -705,6 +816,7 @@ class PlatformMenuItem extends MenuItem {
/// The optional shortcut that selects this [PlatformMenuItem].
///
/// This shortcut is only enabled when [onSelected] is set.
@override
final MenuSerializableShortcut? shortcut;
/// An optional callback that is called when this [PlatformMenuItem] is
......@@ -714,6 +826,13 @@ class PlatformMenuItem extends MenuItem {
@override
final VoidCallback? onSelected;
/// An optional intent that is invoked when this [PlatformMenuItem] is
/// selected.
///
/// If unset, this menu item will be disabled.
@override
final Intent? onSelectedIntent;
@override
Iterable<Map<String, Object?>> toChannelRepresentation(
PlatformMenuDelegate delegate, {
......@@ -741,6 +860,9 @@ class PlatformMenuItem extends MenuItem {
};
}
@override
String toStringShort() => '${describeIdentity(this)}($label)';
@override
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
super.debugFillProperties(properties);
......@@ -779,9 +901,7 @@ class PlatformProvidedMenuItem extends PlatformMenuItem {
const PlatformProvidedMenuItem({
required this.type,
this.enabled = true,
}) : super(
label: '', // The label is ignored for standard menus.
);
}) : super(label: ''); // The label is ignored for platform provided menus.
/// The type of default menu this is.
///
......@@ -832,7 +952,7 @@ class PlatformProvidedMenuItem extends PlatformMenuItem {
assert(() {
if (!hasMenu(type)) {
throw ArgumentError(
'Platform ${defaultTargetPlatform.name} has no standard menu for '
'Platform ${defaultTargetPlatform.name} has no platform provided menu for '
'$type. Call PlatformProvidedMenuItem.hasMenu to determine this before '
'instantiating one.',
);
......@@ -856,7 +976,8 @@ class PlatformProvidedMenuItem extends PlatformMenuItem {
}
}
/// The list of possible standard, prebuilt menus for use in a [PlatformMenuBar].
/// The list of possible platform provided, prebuilt menus for use in a
/// [PlatformMenuBar].
///
/// These are menus that the platform typically provides that cannot be
/// reproduced in Flutter without calling platform functions, but are standard
......@@ -870,7 +991,7 @@ class PlatformProvidedMenuItem extends PlatformMenuItem {
/// Add these to your [PlatformMenuBar] using the [PlatformProvidedMenuItem]
/// class.
///
/// You can tell if the platform supports the given standard menu using the
/// You can tell if the platform provides the given menu using the
/// [PlatformProvidedMenuItem.hasMenu] method.
// Must be kept in sync with the plugin code's enum of the same name.
enum PlatformProvidedMenuItemType {
......
......@@ -51,7 +51,6 @@ void main() {
MaterialApp(
home: Material(
child: PlatformMenuBar(
body: const Center(child: Text('Body')),
menus: createTestMenus(
onActivate: onActivate,
onOpen: onOpen,
......@@ -63,6 +62,7 @@ void main() {
subSubMenu10[3]: const SingleActivator(LogicalKeyboardKey.keyD, meta: true),
},
),
child: const Center(child: Text('Body')),
),
),
),
......@@ -163,11 +163,11 @@ void main() {
const MaterialApp(
home: Material(
child: PlatformMenuBar(
body: PlatformMenuBar(
body: SizedBox(),
menus: <MenuItem>[],
),
child: PlatformMenuBar(
menus: <MenuItem>[],
child: SizedBox(),
),
),
),
),
......@@ -180,8 +180,8 @@ void main() {
shortcut: SingleActivator(LogicalKeyboardKey.keyA),
);
const PlatformMenuBar menuBar = PlatformMenuBar(
body: SizedBox(),
menus: <MenuItem>[item],
child: SizedBox(),
);
await tester.pumpWidget(
......@@ -197,7 +197,7 @@ void main() {
menuBar.toStringDeep(),
equalsIgnoringHashCodes(
'PlatformMenuBar#00000\n'
' └─PlatformMenuItem#00000\n'
' └─PlatformMenuItem#00000(label2)\n'
' label: "label2"\n'
' shortcut: SingleActivator#00000(keys: Key A)\n'
' DISABLED\n',
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment