Skip to content

Commit 406d86b

Browse files
authored
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.
1 parent 6efdf0a commit 406d86b

File tree

3 files changed

+150
-29
lines changed

3 files changed

+150
-29
lines changed

examples/api/lib/material/platform_menu_bar/platform_menu_bar.0.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,7 @@ class _MyMenuBarAppState extends State<MyMenuBarApp> {
126126
],
127127
),
128128
],
129-
body: Center(
129+
child: Center(
130130
child: Text(_showMessage
131131
? _message
132132
: 'This space intentionally left blank.\n'

packages/flutter/lib/src/widgets/platform_menu_bar.dart

Lines changed: 143 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,10 @@ import 'dart:async';
77
import 'package:flutter/foundation.dart';
88
import 'package:flutter/services.dart';
99

10+
import 'actions.dart';
11+
import 'basic.dart';
1012
import 'binding.dart';
13+
import 'focus_manager.dart';
1114
import 'framework.dart';
1215
import 'shortcuts.dart';
1316

@@ -44,6 +47,7 @@ class ShortcutSerialization {
4447
/// This is used by a [CharacterActivator] to serialize itself.
4548
ShortcutSerialization.character(String character)
4649
: _internal = <String, Object?>{_kShortcutCharacter: character},
50+
_character = character,
4751
assert(character.length == 1);
4852

4953
/// Creates a [ShortcutSerialization] representing a specific
@@ -70,6 +74,11 @@ class ShortcutSerialization {
7074
trigger != LogicalKeyboardKey.metaRight,
7175
'Specifying a modifier key as a trigger is not allowed. '
7276
'Use provided boolean parameters instead.'),
77+
_trigger = trigger,
78+
_control = control,
79+
_shift = shift,
80+
_alt = alt,
81+
_meta = meta,
7382
_internal = <String, Object?>{
7483
_kShortcutTrigger: trigger.keyId,
7584
_kShortcutModifiers: (control ? _shortcutModifierControl : 0) |
@@ -80,6 +89,35 @@ class ShortcutSerialization {
8089

8190
final Map<String, Object?> _internal;
8291

92+
/// The keyboard key that triggers this shortcut, if any.
93+
LogicalKeyboardKey? get trigger => _trigger;
94+
LogicalKeyboardKey? _trigger;
95+
96+
/// The character that triggers this shortcut, if any.
97+
String? get character => _character;
98+
String? _character;
99+
100+
/// If this shortcut has a [trigger], this indicates whether or not the
101+
/// control modifier needs to be down or not.
102+
bool? get control => _control;
103+
bool? _control;
104+
105+
/// If this shortcut has a [trigger], this indicates whether or not the
106+
/// shift modifier needs to be down or not.
107+
bool? get shift => _shift;
108+
bool? _shift;
109+
110+
/// If this shortcut has a [trigger], this indicates whether or not the
111+
/// alt modifier needs to be down or not.
112+
bool? get alt => _alt;
113+
bool? _alt;
114+
115+
/// If this shortcut has a [trigger], this indicates whether or not the meta
116+
/// (also known as the Windows or Command key) modifier needs to be down or
117+
/// not.
118+
bool? get meta => _meta;
119+
bool? _meta;
120+
83121
/// The bit mask for the [LogicalKeyboardKey.meta] key (or it's left/right
84122
/// equivalents) being down.
85123
static const int _shortcutModifierMeta = 1 << 0;
@@ -124,10 +162,13 @@ mixin MenuSerializableShortcut {
124162
/// An abstract class for describing cascading menu hierarchies that are part of
125163
/// a [PlatformMenuBar].
126164
///
127-
/// This type is used by [PlatformMenuDelegate.setMenus] to accept the menu
165+
/// This type is also used by [PlatformMenuDelegate.setMenus] to accept the menu
128166
/// hierarchy to be sent to the platform, and by [PlatformMenuBar] to define the
129167
/// menu hierarchy.
130168
///
169+
/// This class is abstract, and so can't be used directly. Typically subclasses
170+
/// like [PlatformMenuItem] are used.
171+
///
131172
/// See also:
132173
///
133174
/// * [PlatformMenuBar], a widget that renders menu items using platform APIs
@@ -141,8 +182,8 @@ abstract class MenuItem with Diagnosticable {
141182
///
142183
/// The `delegate` is the [PlatformMenuDelegate] that is requesting the
143184
/// serialization. The `index` is the position of this menu item in the list
144-
/// of children of the [PlatformMenu] it belongs to, and `count` is the number
145-
/// of children in the [PlatformMenu] it belongs to.
185+
/// of [menus] of the [PlatformMenu] it belongs to, and `count` is the number
186+
/// of [menus] in the [PlatformMenu] it belongs to.
146187
///
147188
/// The `getId` parameter is a [MenuItemSerializableIdGenerator] function that
148189
/// generates a unique ID for each menu item, which is to be returned in the
@@ -152,6 +193,17 @@ abstract class MenuItem with Diagnosticable {
152193
required MenuItemSerializableIdGenerator getId,
153194
});
154195

196+
/// The optional shortcut that selects this [MenuItem].
197+
///
198+
/// This shortcut is only enabled when [onSelected] is set.
199+
MenuSerializableShortcut? get shortcut => null;
200+
201+
/// Returns any child [MenuItem]s of this item.
202+
///
203+
/// Returns an empty list if this type of menu item doesn't have
204+
/// children.
205+
List<MenuItem> get menus => const <MenuItem>[];
206+
155207
/// Returns all descendant [MenuItem]s of this item.
156208
///
157209
/// Returns an empty list if this type of menu item doesn't have
@@ -163,9 +215,27 @@ abstract class MenuItem with Diagnosticable {
163215
///
164216
/// Only items that do not have submenus will have this callback invoked.
165217
///
218+
/// Only one of [onSelected] or [onSelectedIntent] may be specified.
219+
///
220+
/// If neither [onSelected] nor [onSelectedIntent] are specified, then this
221+
/// menu item is considered to be disabled.
222+
///
166223
/// The default implementation returns null.
167224
VoidCallback? get onSelected => null;
168225

226+
/// Returns an intent, if any, to be invoked if the platform receives a
227+
/// "Menu.selectedCallback" method call from the platform for this item.
228+
///
229+
/// Only items that do not have submenus will have this intent invoked.
230+
///
231+
/// Only one of [onSelected] or [onSelectedIntent] may be specified.
232+
///
233+
/// If neither [onSelected] nor [onSelectedIntent] are specified, then this
234+
/// menu item is considered to be disabled.
235+
///
236+
/// The default implementation returns null.
237+
Intent? get onSelectedIntent => null;
238+
169239
/// Returns a callback, if any, to be invoked if the platform menu receives a
170240
/// "Menu.opened" method call from the platform for this item.
171241
///
@@ -181,6 +251,12 @@ abstract class MenuItem with Diagnosticable {
181251
///
182252
/// The default implementation returns null.
183253
VoidCallback? get onClose => null;
254+
255+
/// Returns the list of group members if this menu item is a "grouping" menu
256+
/// item, such as [PlatformMenuItemGroup].
257+
///
258+
/// Defaults to an empty list.
259+
List<MenuItem> get members => const <MenuItem>[];
184260
}
185261

186262
/// An abstract delegate class that can be used to set
@@ -383,7 +459,12 @@ class DefaultPlatformMenuDelegate extends PlatformMenuDelegate {
383459
}
384460
final MenuItem item = _idMap[id]!;
385461
if (call.method == _kMenuSelectedCallbackMethod) {
462+
assert(item.onSelected == null || item.onSelectedIntent == null,
463+
'Only one of MenuItem.onSelected or MenuItem.onSelectedIntent may be specified');
386464
item.onSelected?.call();
465+
if (item.onSelectedIntent != null) {
466+
Actions.maybeInvoke(FocusManager.instance.primaryFocus!.context!, item.onSelectedIntent!);
467+
}
387468
} else if (call.method == _kMenuItemOpenedMethod) {
388469
item.onOpen?.call();
389470
} else if (call.method == _kMenuItemClosedMethod) {
@@ -407,7 +488,7 @@ class DefaultPlatformMenuDelegate extends PlatformMenuDelegate {
407488
/// the platform menu bar.
408489
///
409490
/// As far as Flutter is concerned, this widget has no visual representation,
410-
/// and intercepts no events: it just returns the [body] from its build
491+
/// and intercepts no events: it just returns the [child] from its build
411492
/// function. This is because all of the rendering, shortcuts, and event
412493
/// handling for the menu is handled by the plugin on the host platform.
413494
///
@@ -429,18 +510,32 @@ class DefaultPlatformMenuDelegate extends PlatformMenuDelegate {
429510
class PlatformMenuBar extends StatefulWidget with DiagnosticableTreeMixin {
430511
/// Creates a const [PlatformMenuBar].
431512
///
432-
/// The [body] and [menus] attributes are required.
513+
/// The [child] and [menus] attributes are required.
433514
const PlatformMenuBar({
434515
super.key,
435-
required this.body,
436516
required this.menus,
437-
});
517+
this.child,
518+
@Deprecated(
519+
'Use the child attribute instead. '
520+
'This feature was deprecated after v3.1.0-0.0.pre.'
521+
)
522+
this.body,
523+
}) : assert(body == null || child == null,
524+
'The body argument is deprecated, and only one of body or child may be used.');
525+
526+
/// The widget below this widget in the tree.
527+
///
528+
/// {@macro flutter.widgets.ProxyWidget.child}
529+
final Widget? child;
438530

439-
/// The widget to be rendered in the Flutter window that these platform menus
440-
/// are associated with.
531+
/// The widget below this widget in the tree.
441532
///
442-
/// This is typically the body of the application's UI.
443-
final Widget body;
533+
/// This attribute is deprecated, use [child] instead.
534+
@Deprecated(
535+
'Use the child attribute instead. '
536+
'This feature was deprecated after v3.1.0-0.0.pre.'
537+
)
538+
final Widget? body;
444539

445540
/// The list of menu items that are the top level children of the
446541
/// [PlatformMenuBar].
@@ -512,7 +607,7 @@ class _PlatformMenuBarState extends State<PlatformMenuBar> {
512607
Widget build(BuildContext context) {
513608
// PlatformMenuBar is really about managing the platform menu bar, and
514609
// doesn't do any rendering or event handling in Flutter.
515-
return widget.body;
610+
return widget.child ?? widget.body ?? const SizedBox();
516611
}
517612
}
518613

@@ -547,6 +642,7 @@ class PlatformMenu extends MenuItem with DiagnosticableTreeMixin {
547642
/// The menu items in the submenu opened by this menu item.
548643
///
549644
/// If this is an empty list, this [PlatformMenu] will be disabled.
645+
@override
550646
final List<MenuItem> menus;
551647

552648
/// Returns all descendant [MenuItem]s of this item.
@@ -646,6 +742,7 @@ class PlatformMenuItemGroup extends MenuItem {
646742
/// The [MenuItem]s that are members of this menu item group.
647743
///
648744
/// An assertion will be thrown if there isn't at least one member of the group.
745+
@override
649746
final List<MenuItem> members;
650747

651748
@override
@@ -654,19 +751,32 @@ class PlatformMenuItemGroup extends MenuItem {
654751
required MenuItemSerializableIdGenerator getId,
655752
}) {
656753
assert(members.isNotEmpty, 'There must be at least one member in a PlatformMenuItemGroup');
754+
return serialize(this, delegate, getId: getId);
755+
}
756+
757+
/// Converts the supplied object to the correct channel representation for the
758+
/// 'flutter/menu' channel.
759+
///
760+
/// This API is supplied so that implementers of [PlatformMenuItemGroup] can share
761+
/// this implementation.
762+
static Iterable<Map<String, Object?>> serialize(
763+
MenuItem group,
764+
PlatformMenuDelegate delegate, {
765+
required MenuItemSerializableIdGenerator getId,
766+
}) {
657767
final List<Map<String, Object?>> result = <Map<String, Object?>>[];
658768
result.add(<String, Object?>{
659-
_kIdKey: getId(this),
769+
_kIdKey: getId(group),
660770
_kIsDividerKey: true,
661771
});
662-
for (final MenuItem item in members) {
772+
for (final MenuItem item in group.members) {
663773
result.addAll(item.toChannelRepresentation(
664774
delegate,
665775
getId: getId,
666776
));
667777
}
668778
result.add(<String, Object?>{
669-
_kIdKey: getId(this),
779+
_kIdKey: getId(group),
670780
_kIsDividerKey: true,
671781
});
672782
return result;
@@ -697,14 +807,16 @@ class PlatformMenuItem extends MenuItem {
697807
required this.label,
698808
this.shortcut,
699809
this.onSelected,
700-
});
810+
this.onSelectedIntent,
811+
}) : assert(onSelected == null || onSelectedIntent == null, 'Only one of onSelected or onSelectedIntent may be specified');
701812

702813
/// The required label used for rendering the menu item.
703814
final String label;
704815

705816
/// The optional shortcut that selects this [PlatformMenuItem].
706817
///
707818
/// This shortcut is only enabled when [onSelected] is set.
819+
@override
708820
final MenuSerializableShortcut? shortcut;
709821

710822
/// An optional callback that is called when this [PlatformMenuItem] is
@@ -714,6 +826,13 @@ class PlatformMenuItem extends MenuItem {
714826
@override
715827
final VoidCallback? onSelected;
716828

829+
/// An optional intent that is invoked when this [PlatformMenuItem] is
830+
/// selected.
831+
///
832+
/// If unset, this menu item will be disabled.
833+
@override
834+
final Intent? onSelectedIntent;
835+
717836
@override
718837
Iterable<Map<String, Object?>> toChannelRepresentation(
719838
PlatformMenuDelegate delegate, {
@@ -741,6 +860,9 @@ class PlatformMenuItem extends MenuItem {
741860
};
742861
}
743862

863+
@override
864+
String toStringShort() => '${describeIdentity(this)}($label)';
865+
744866
@override
745867
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
746868
super.debugFillProperties(properties);
@@ -779,9 +901,7 @@ class PlatformProvidedMenuItem extends PlatformMenuItem {
779901
const PlatformProvidedMenuItem({
780902
required this.type,
781903
this.enabled = true,
782-
}) : super(
783-
label: '', // The label is ignored for standard menus.
784-
);
904+
}) : super(label: ''); // The label is ignored for platform provided menus.
785905

786906
/// The type of default menu this is.
787907
///
@@ -832,7 +952,7 @@ class PlatformProvidedMenuItem extends PlatformMenuItem {
832952
assert(() {
833953
if (!hasMenu(type)) {
834954
throw ArgumentError(
835-
'Platform ${defaultTargetPlatform.name} has no standard menu for '
955+
'Platform ${defaultTargetPlatform.name} has no platform provided menu for '
836956
'$type. Call PlatformProvidedMenuItem.hasMenu to determine this before '
837957
'instantiating one.',
838958
);
@@ -856,7 +976,8 @@ class PlatformProvidedMenuItem extends PlatformMenuItem {
856976
}
857977
}
858978

859-
/// The list of possible standard, prebuilt menus for use in a [PlatformMenuBar].
979+
/// The list of possible platform provided, prebuilt menus for use in a
980+
/// [PlatformMenuBar].
860981
///
861982
/// These are menus that the platform typically provides that cannot be
862983
/// reproduced in Flutter without calling platform functions, but are standard
@@ -870,7 +991,7 @@ class PlatformProvidedMenuItem extends PlatformMenuItem {
870991
/// Add these to your [PlatformMenuBar] using the [PlatformProvidedMenuItem]
871992
/// class.
872993
///
873-
/// You can tell if the platform supports the given standard menu using the
994+
/// You can tell if the platform provides the given menu using the
874995
/// [PlatformProvidedMenuItem.hasMenu] method.
875996
// Must be kept in sync with the plugin code's enum of the same name.
876997
enum PlatformProvidedMenuItemType {

0 commit comments

Comments
 (0)