CARVIEW |
Navigation Menu
-
-
Notifications
You must be signed in to change notification settings - Fork 7
Building better titlebar menus
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.
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();
TitlebarBuilder& addEditMenuDefaultItems();
void finish() noexcept;
void render() noexcept;
void clear() noexcept;
};
The member functions are as follows:
-
setBuildNativeOnMacOS
- This functions allows you to toggle global menu integration on macOS. Does nothing on other platforms -
setContext
- Sets avoid*
context that can be consumed by on-click callback events for menu items -
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), 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 separator -
addSubmenu
- 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.
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
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 menu -
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)
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.
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 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 edit menu is a common menu that most applications have. It should deal with tasks related to editing something in your application such as copy/paste, undo/redo, etc.
Applications on macOS should provide an Edit
menu with the following default items:

You can add the default items using the addEditMenuDefaultItems()
function.
Caution
The function needs to be called for a submenu that is already named Edit
Note
The macOS design guidelines mention that the Edit
menu should always be after the File
menu or in case a File
menu does not exist, directly after the application 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 items 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.
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.
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();
}
The event safety for most functions in the builder is begin
and post-begin
. The render
function's event safety is tick
.
A C API is also provided. It follows the basic C API development conventions, as defined here.
This project is supported by all the people who joined our discord server and became beta testers. If you want to join the discord you can click 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