Create the Native Menu Bar of Mac Apps with Flutter

Since the Flutter 3.0, the Flutter supports macOS Desktop Apps Development officially.

I was interested in that is the Flutter able to create the features typical of macOS apps. The most of them I think is the menu bar.

I checked the issues of the Flutter repository in the GitHub and the release notes, I found the pull-request as below.

The Flutter 3.0 release notes says following.

Implements a PlatformMenuBar widget and associated data structures by @gspencergoog in https://github.com/flutter/flutter/pull/100274

From the Flutter 3.0 release notes

I saw that the Flutter to be able to create the mac native menu bar, I created the sample code to see the details. The sample code is in the GitHub.

TOC

Classes for the Menu Bar

The classes for the Menu Bar are following:

  • PlatformMenuBar
  • PlatformMenu
  • PlatformMenuItemGroup
  • PlatformMenuItem

You can create the typical macOS menu bar with the flutter as the next screen capture.

Native macOS menu bar.
Native macOS menu bar

Create the menu bar

Use the PlatformMenuBar class to create the menu bar. The PlatformMenuBar class is a widget and inherited from the StatefulWidget class. I created the sample code to create the menu bar with the PlatformMenuBar class. At this time, the menu bar is an empty.

class _MyHomePageState extends State<MyHomePage> {
    @override
    Widget build(BuildContext context) {
        return PlatformMenuBar(
            menus: <MenuItem> [
            ],
            body: Center(
// 省略
            ),
        );
    }
}

Create the menus

The each of menus such as “File” menu, “Edit” menu and etc are instances of the PlatformMenu class. You put them into the array and pass it to the menus parameter of the PlatformMenuBar class constructor.

return PlatformMenuBar(
  menus: <MenuItem>[
    PlatformMenu(/*省略*/), // Application Menu
    PlatformMenu(/*省略*/), // File Menu
    PlatformMenu(/*省略*/), // Edit Menu
    PlatformMenu(/*省略*/), // View Menu
    PlatformMenu(/*省略*/), // Window Menu
  ],
  body: Center(
  ),
);

The application menu

There are two common menus which the every macOS apps have:

  • the Apple menu
  • the Application menu

The apple menu is managed by the macOS, the application cant’t modify its items.

The application menu is at next to the apple menu, its title is a foreground application name.

The order of menus are same as the array which was passed to the PlatformMenuBar class constructor. The first item of the array is an application menu.

The menu title and items

You specify the menu title to the label parameter and items to the menus parameter.

For example, the following code is creating the File menu has only an item New.

PlatformMenu(
  label: 'File',
  menus: <MenuItem>[
    PlatformMenuItemGroup(
      members: <MenuItem>[
        PlatformMenuItem(
          label: 'New',
        ),
      ],
    ),
  ],
),

The general macOS App menu structure

The menu structure of a typical Mac application looks like this.

  • Apple Menu
  • Application Menu
  • File Menu
  • Edit Menu
  • View Menu
  • App’s own menus
  • Window Menu
  • Help Menu

However, it does not necessarily mean that the menu structure has to be exactly like this, so a menu that was not created even in Flutter will not be created automatically.

When you don’t create the menus, the apple menu and the application menu are created automatically. The items of the application menu are not created automatically.

The Human Interface Guidelines explains the general menu structure.

Create the separators

In the AppKit, both menu items and separators are instances of the NSMenuItem class. The seprator is treated as a menu item.

It is different in the Flutter. The separator splits menu items in the menu into the groups. For example, the menu has two separators, it is considered as three groups.

  • Item 1
  • Item 2
  • Separator
  • Item 3
  • Item 4
  • Separator
  • Item 5

You can use PlatformMenuItemGroup class to create the group. A code for the above example is following.

PlatformMenu(
  label: 'MyMenu',
  menus: <MenuItem>[
    PlatformMenuItemGroup(
      members: <MenuItem>[
        PlatformMenuItem(/*省略*/), // Item 1
        PlatformMenuItem(/*省略*/), // Item 2
      ],
    ),
    PlatformMenuItemGroup(
      members: <MenuItem>[
        PlatformMenuItem(/*省略*/), // Item 3
        PlatformMenuItem(/*省略*/), // Item 4
      ],
    ),
    PlatformMenuItemGroup(
      members: <MenuItem>[
        PlatformMenuItem(/*省略*/), // Item 5
      ],
    ),
  ],
),

The separator is appeared as group delimitor.

Create the menu item

You can use PlatformMenuItem class to create the menu items.

PlatformMenuItem(
  label: 'Item Title',
),

The item title is specified to label parameter.

Implement the action when the item selected

You can pass the action is executed when the item is selected to the onSelected parameter.

PlatformMenuItem(
  label: 'Increment',
  onSelected: () {
    // メニューが選択されたときの処理
    _incrementCounter();
  },
),

This example, the _incrementCounter() method is executed when the Increment menu item is selected.

Assign shortcut keys

You can specify the shortcut to the shortcut parameter. The shortcut is a SingleActivator class instance.

PlatformMenuItem(
  label: 'Increment',
  shortcut: const SingleActivator(LogicalKeyboardKey.keyI, meta: true),
  onSelected: () {
    _incrementCounter();
  },
),

This code assign the Command key and I key to the shortcut.

The key to be pressed together with the modifier key is specified in the LogicalKeyboardKey class. The modifier key is specified by an argument to the SingleActivator constructor. The following arguments are available, including those omitted.

ParamterModifier Key
controlControl Key
shiftShift Key
altOption Key
metaCommand Key
SingleActivator constructor parameters

Platform specific menu items

You can use PlatformProvidedMenuItem class to create the platform specific menu items such as the Services and Show All in the application menu, they are couldn’t be created by the application.

For example, the code to create the Services menu item is following.

PlatformMenuItemGroup(
  members: <MenuItem>[
    if (PlatformProvidedMenuItem.hasMenu(PlatformProvidedMenuItemType.servicesSubmenu))
      const PlatformProvidedMenuItem(type: PlatformProvidedMenuItemType.servicesSubmenu),
  ],
),

The hasMenu() method returns whether the menu item specified to the argument is supported or not.

You can specify the menu item to be created to the type argument of the PlatformProvidedMenuItem constructor. The macOS specific menus are defined at the writing time is following.

  • about
  • quit
  • servicesSubmenu
  • hide
  • hideOtherApplications
  • showAllApplications
  • startSpeaking
  • stopSpeaking
  • toggleFullScreen
  • minimizeWindow
  • zoomWindow
  • arrangeWindowsInFront

Only macOS specific menu items are defined in the Flutter 3.0.

Sample Code

I uploaded the sample code into the GitHub. Please see it for the actual code.

Let's share this post !

Author of this article

Akira Hayashiのアバター Akira Hayashi Representative, Software Engineer

I am an application developer loves programming. This blog is a tech blog, its articles are learning notes. In my work, I mainly focus on desktop and mobile application development, but I also write technical books and teach seminars. The websites of my work and books are here -> RK Kaihatsu.

TOC