One of the many ubiquitous UI components in modern desktop applications is the titlebar menu. You already know that titlebar components can be used to build such a menu with dear imgui.

However, drawing the menu using raw dear imgui calls is not the best solution when building portable applications.

Why is it not the best solution? Introducing macOS and its global menu:

image

It might not be apparent at first, especially for first-time macOS users, but the terminal emulator in the picture above actually renders a menu. Application menus on macOS are actually registered with a global OS menu that changes depending on the currently, focused application. This means that any application that wants to comply with macOS design guidelines has to stop rendering its own menu on macOS and register its menu with the OS instead.

To solve this issue, with release 1.2 the titlebar menu builder was introduced. This class allows you to easily build menu bars that will be rendered with dear imgui on all platforms, except on macOS, where the menu will be registered directly with the operating system automatically.

API overview

Then API consists of the TitlebarBuilder and the RadioBuilder class, which allow you to build out your menu with a fluent builder interface. They look like this:

class RadioBuilder
{
public:
    explicit RadioBuilder(int& selectedIndex) noexcept;
    RadioBuilder& init(int& selectedIndex) noexcept;
    RadioBuilder& add(const FString& label, bool* bEnabled = nullptr);
};

class TitlebarBuilder
{
public:
    TitlebarBuilder& setBuildNativeOnMacOS(bool bBuildNativeOnMacOS) noexcept;
    TitlebarBuilder& setContext(void* data) noexcept;

    TitlebarBuilder& addMenuItem(const FString& label, const FString& hint = "", const TFunction<void(void*)>& f = [](void*) -> void {}, bool* bEnabled = nullptr) noexcept;
    TitlebarBuilder& addSeparator() noexcept;
    TitlebarBuilder& addSubmenu(const FString& label, const TitlebarBuilder& submenu, bool* bEnabled = nullptr) noexcept;
    TitlebarBuilder& addCheckbox(const FString& label, bool& bSelected, bool* bEnabled = nullptr);
    TitlebarBuilder& addRadioGroup(const RadioBuilder& submenu);

    TitlebarBuilder& addAppMenuDefaultItems();
    TitlebarBuilder& addWindowMenuDefaultItems();

    void finish() noexcept;

    void render() noexcept;

    void clear() noexcept;
};

Building basic menus

The member functions are as follows:

  1. setBuildNativeOnMacOS - This functions allows you to toggle global menu integration on macOS. Does nothing on other platforms
  2. setContext - Sets a void* context that can be consumed by on-click callback events for menu items
  3. addMenuItem - Adds a menu item to the current titlebar builder instance. A menu item can have a string label, a string hint(for key bindings), a void(void*) callback function that will be called when the button is clicked and a bool* that controls whether the item can be clicked(if set to nullptr the item is not disabled)
  4. addSeparator - Adds a separator
  5. addSubmenu - Adds a submenu. Submenus are built by proving a label for the submenu and a constant reference to another TitlebarBuilder. It also has the same bool* bEnabled argument.
  6. addCheckbox - Adds a checkbox. It can have a label and a pointer to a boolean that will be changed by the framework. It also has the same bool* bEnabled argument.

Drawing radio buttons

To draw radio buttons, you need to submit a RadioBuilder instance.

The init() function or the single-argument constructor are used to initialise the RadioBuilder with its integer that is used to tell the different radio buttons apart.

The add() function adds a radio button. It can have a label and an optional bool* that controls whether the item can be clicked(if set to nullptr the item is not disabled).

Use TitlebarBuilder::addRadioGroup() to add the radio group to the menu.

Caution

Failing to call the init() function or the single-argument constructor with a valid non-null pointer to an integer will result in an error. In such case the radio group will not be rendered

Rendering the menu

Now that you know how to build a menu, we need to render it. The following functions deal with rendering the menu:

  1. finish - finishes constructing the menu. Call this as the last function when building the menu
  2. render - Renders the menu. Should be called every frame inside the tick event of a titlebar component.

Warning

When targeting an OS menu on macOS, there is a common issue where your first menu is with the name of your application. This is normal and is caused by macOS always requiring the first menu in the menu bar to be with the name of the application. Use the __APPLE__ macro to conditionally create a submenu with an empty("") label that will serve as your "Application menu".

Tip

If you have a shortcut for a menu item that's registered through the Input interface, you can display it in the menu by setting the hint string of a menu item to the output of Utility::keyToText(action, false)

Rebuilding the menu and reactivity

Due to the design of both dear imgui and the macOS OS menu API, it is currently impossible to have much reactivity in the menu, beyond dynamically changing whether a widget is enabled or disabled.

For use-cases which require dynamic changing of labels and more, the entire menu needs to be rebuilt from scratch by calling the clear function and building the menu in the same manner as before.

Complying with macOS standards

For your application to comply with the design language of macOS it needs to have some more components displayed on its menu.

The application menu

The application menu is the first menu of your application's menu bar. It is always visible and is always named after your application, no matter the name you submit to macOS.

There are some items that are required to be in the application menu, for example, an About <application> button, a Quit <application> button, or a Services submenu. Some applications can have additional menu items. For example, Safari also offers options for opening the settings or for clearing the browsing history:

image

You can generate these defaults using the addAppMenuDefaultItems() function inside your application menu:

image

In development builds, or when the correct fields in the Info.plist file are not set, your About <application> popup might look like this:

image

Make sure to set strings such as NSHumanReadableCopyright, CFBundleName, CFBundleVersion or CFBundleShortVersionString in your Info.plist file. This way, macOS can generate an About <application> popup for your application automatically. Example:

image

Caution

The function needs to be called for the first submenu of your application.

The Window menu

Applications on macOS should have a Window menu that adds more advanced window controls, compared to the traffic light buttons. Your application might also add custom items to it. For example, Safari adds buttons to control tab arrangement:

image

You can add the default settings using the addWindowMenuDefaultItems() function:

image

Caution

The function needs to be called for a submenu that is already named Window

Note

The Window menu is generally placed last, before the Help menu.

The Help menu

On macOS, applications have to display a Help menu as their last menu. This menu consists of a search bar that can be used to search both the application's help book and its menu items.

Your application can also add additional items to the Help menu, for example IDEs by JetBrains often add many additional help resources:

image

You can add the default help menu for your application using addHelpMenuDefaultItems() function:

image

For applications that have not set a help book in their Info.plist file a popup like this will open when clicking on the <Application> Help button(Once a help book is added, the button will redirect automatically):

image

Documentation on setting or adding help books can be found here.

Caution

The function needs to be called for a submenu that is already named Help

Note

The Help menu should always be the last menu of your application.

Example

Here is an example menu:

void MyApp::Title::begin()
{
    beginAutohandle();

    static bool bEnabled = false;

    builder
#ifdef __APPLE__
    .addSubmenu("", UImGui::TitlebarBuilder{}
        .addAppMenuDefaultItems()
    )
#endif
    .addSubmenu("File", UImGui::TitlebarBuilder{}
        .addMenuItem("Open", "LCMD+O")
        .addMenuItem("Save", "LCMD+S")
        .addSeparator()
        .addMenuItem("After separator")
        .addSubmenu("Test", UImGui::TitlebarBuilder{}
            .addMenuItem("A")
            .addMenuItem("B")
            .addSeparator()
            .addMenuItem("C", "", [](void*) -> void { Logger::log("C", ULOG_LOG_TYPE_WARNING); }, &bEnabled)
        )
        .addSeparator()
        .addCheckbox("Re-enable C", bEnabled)
        .addSeparator()
        .addRadioGroup(UImGui::RadioBuilder(i)
            .add("Apple")
            .add("Orange")
            .add("Pear")
            .add("Dragonfruit", &bEnabled)
        )
    ).addSubmenu("Testing", UImGui::TitlebarBuilder{}
        .addMenuItem("Open", "LCTRL+LCMD+LOpt+UpArr")
        .addMenuItem("Save", "LOpt+S")
        .addSeparator()
        .addMenuItem("After separator")
        .addSeparator()
    )
#ifdef __APPLE__
    .addSubmenu("Window", UImGui::TitlebarBuilder{}
        .addWindowMenuDefaultItems()
    )
#endif
    .addSubmenu("Help", UImGui::TitlebarBuilder{}
        .addHelpMenuDefaultItems()
    )
    .setBuildNativeOnMacOS(true).finish();
}

Event safety

The event safety for most functions in the builder is begin and post-begin. The render function's event safety is tick.

C API

A C API is also provided. It follows the basic C API development conventions, as defined here.