Top Level Menu Extension
Overview
This example demonstrates how to add a custom top-level menu to the editor's viewport bar using the AddTopLevelMenuItem hook. Top-level menus appear alongside "File", "Edit", "View", etc., and can contain submenus and custom actions. Cleanup is automatic when the plugin unloads.
Files
package.json
{
"name": "Custom Menu Addon",
"author": "Polyphase Examples",
"description": "Adds a custom top-level menu to the editor.",
"version": "1.0.0",
"tags": ["editor", "example"],
"native": {
"target": "editor",
"sourceDir": "Source",
"binaryName": "custommenuaddon",
"apiVersion": 2
}
}
Source/CustomMenuAddon.cpp
#include "Plugins/PolyphasePluginAPI.h"
#include "Plugins/PolyphaseEngineAPI.h"
#if EDITOR
#include "Plugins/EditorUIHooks.h"
#include "imgui.h"
#endif
static PolyphaseEngineAPI* sEngineAPI = nullptr;
#if EDITOR
/**
* @brief Draw callback for the custom top-level menu.
* @param userData User-provided data (unused in this example).
*
* Called each frame when the menu is open. Use ImGui menu functions
* such as ImGui::MenuItem() and ImGui::BeginMenu() to draw items.
*/
static void DrawCustomMenu(void* userData)
{
if (ImGui::MenuItem("Say Hello"))
{
sEngineAPI->LogDebug("Hello from custom menu!");
}
if (ImGui::MenuItem("Open Documentation"))
{
sEngineAPI->LogDebug("Opening documentation...");
}
ImGui::Separator();
if (ImGui::BeginMenu("Submenu"))
{
if (ImGui::MenuItem("Option 1"))
{
sEngineAPI->LogDebug("Option 1 selected");
}
if (ImGui::MenuItem("Option 2"))
{
sEngineAPI->LogDebug("Option 2 selected");
}
if (ImGui::MenuItem("Option 3", nullptr, false, false))
{
// Disabled item - will not be called
}
ImGui::EndMenu();
}
}
#endif
/**
* @brief Called when the plugin is loaded by the engine.
* @param api Pointer to the engine API.
* @return 0 on success, non-zero on failure.
*/
static int OnLoad(PolyphaseEngineAPI* api)
{
sEngineAPI = api;
api->LogDebug("Custom Menu Addon loaded!");
return 0;
}
/**
* @brief Called when the plugin is about to be unloaded.
*/
static void OnUnload()
{
if (sEngineAPI) sEngineAPI->LogDebug("Custom Menu Addon unloading.");
sEngineAPI = nullptr;
}
#if EDITOR
/**
* @brief Register editor UI hooks.
* @param hooks Pointer to the EditorUIHooks function table.
* @param hookId Unique identifier for this plugin's hooks.
*
* All hooks registered here are automatically cleaned up via
* RemoveAllHooks(hookId) when the plugin unloads.
*/
static void RegisterEditorUI(EditorUIHooks* hooks, uint64_t hookId)
{
hooks->AddTopLevelMenuItem(hookId, "Custom", DrawCustomMenu, nullptr);
sEngineAPI->LogDebug("Custom top-level menu registered");
}
#endif
extern "C" OCTAVE_PLUGIN_API int PolyphasePlugin_GetDesc(PolyphasePluginDesc* desc)
{
desc->apiVersion = OCTAVE_PLUGIN_API_VERSION;
desc->pluginName = "Custom Menu Addon";
desc->pluginVersion = "1.0.0";
desc->OnLoad = OnLoad;
desc->OnUnload = OnUnload;
desc->Tick = nullptr;
desc->TickEditor = nullptr;
desc->RegisterTypes = nullptr;
desc->RegisterScriptFuncs = nullptr;
#if EDITOR
desc->RegisterEditorUI = RegisterEditorUI;
#else
desc->RegisterEditorUI = nullptr;
#endif
desc->OnEditorPreInit = nullptr;
desc->OnEditorReady = nullptr;
return 0;
}
API Reference
AddTopLevelMenuItem
void (*AddTopLevelMenuItem)(HookId hookId, const char* menuName, TopLevelMenuDrawCallback drawFunc, void* userData);
Adds a custom top-level menu to the editor's viewport bar.
Parameters:
- hookId - The hook identifier provided by RegisterEditorUI
- menuName - Display name for the menu (e.g., "Custom", "Tools")
- drawFunc - Callback invoked each frame when the menu is open
- userData - Optional user data passed to the callback
RemoveTopLevelMenuItem
void (*RemoveTopLevelMenuItem)(HookId hookId, const char* menuName);
Removes a previously registered top-level menu. Called automatically on plugin unload.
Parameters:
- hookId - The hook identifier provided by RegisterEditorUI
- menuName - Name of the menu to remove
TopLevelMenuDrawCallback
typedef void (*TopLevelMenuDrawCallback)(void* userData);
Callback signature for menu drawing functions.
Parameters:
- userData - User-provided data from registration
Best Practices
- Menu Naming - Use concise, descriptive names that fit the editor's existing menu bar
- ImGui Context - The ImGui context is already set when your callback is invoked
- Separator Usage - Use
ImGui::Separator()to logically group menu items - Disabled Items - Use the fourth parameter of
ImGui::MenuItem()to disable items when appropriate - Automatic Cleanup - Hooks are cleaned up automatically; manual removal is not required
- Editor Guard - Wrap all editor-specific code in
#if EDITORblocks - RegisterEditorUI - Register menus in
RegisterEditorUI, not inOnLoad