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:
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;
& init(int& selectedIndex) noexcept;
RadioBuilder& add(const FString& label, bool* bEnabled = nullptr);
RadioBuilder};
class TitlebarBuilder
{
public:
& 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();
TitlebarBuilder
void finish() noexcept;
void render() noexcept;
void clear() noexcept;
};
Building basic menus
The member functions are as follows:
setBuildNativeOnMacOS
- This functions allows you to toggle global menu integration on macOS. Does nothing on other platformssetContext
- Sets avoid*
context that can be consumed by on-click callback events for menu itemsaddMenuItem
- Adds a menu item to the current titlebar builder instance. A menu item can have a string label, a string hint(for key bindings), avoid(void*)
callback function that will be called when the button is clicked and abool*
that controls whether the item can be clicked(if set tonullptr
the item is not disabled)addSeparator
- Adds a separatoraddSubmenu
- Adds a submenu. Submenus are built by proving a label for the submenu and a constant reference to anotherTitlebarBuilder
. It also has the samebool* bEnabled
argument.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 samebool* 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:
finish
- finishes constructing the menu. Call this as the last function when building the menurender
- 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:
You can generate these defaults using the
addAppMenuDefaultItems()
function inside your application
menu:
In development builds, or when the correct fields in the
Info.plist
file are not set, your
About <application>
popup might look like this:
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:
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:
You can add the default settings using the
addWindowMenuDefaultItems()
function:
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:
You can add the default help menu for your application using
addHelpMenuDefaultItems()
function:
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):
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.
- Home
- Beginner content
- Install guide
- Creating and using the UI components
- The Instance
- The Init Info struct
- Building better titlebar menus
- Textures
- Logging
- Unicode support
- Additional features
- Client-side bar
- Custom type definitions
- Memory management
- C API development
- Config files and Folders
- Interfaces
- Internal Event safety
- Customising the build system
- Modules system
- Collaborating with others
- Advanced content
- Developer and contributor resources
- Misc