Viewport Context Menu & Overlays
Overview
This example demonstrates two viewport extension features: - Viewport Context Menu: Adding items to the right-click menu in the 3D viewport - Viewport Overlays: Drawing custom overlays (text, shapes, guides) on top of the viewport each frame
Files
package.json
{
"name": "Viewport Extensions Addon",
"author": "Polyphase Examples",
"description": "Adds viewport context menu items and overlays.",
"version": "1.0.0",
"tags": ["editor", "example"],
"native": {
"target": "editor",
"sourceDir": "Source",
"binaryName": "viewportextensionsaddon",
"apiVersion": 2
}
}
Source/ViewportExtensionsAddon.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
static bool sShowGrid = true;
static bool sShowStats = true;
/**
* @brief Context menu callback: spawn a node at the clicked position.
*/
static void OnSpawnAtCursor(void* userData)
{
sEngineAPI->LogDebug("Spawn at cursor position");
}
/**
* @brief Context menu callback: toggle grid overlay.
*/
static void OnToggleGrid(void* userData)
{
sShowGrid = !sShowGrid;
sEngineAPI->LogDebug("Grid overlay %s", sShowGrid ? "enabled" : "disabled");
}
/**
* @brief Viewport overlay: draws stats and guides on the viewport.
* @param viewportX Viewport top-left X coordinate (screen space).
* @param viewportY Viewport top-left Y coordinate (screen space).
* @param viewportW Viewport width in pixels.
* @param viewportH Viewport height in pixels.
* @param userData User data (unused).
*/
static void DrawStatsOverlay(float viewportX, float viewportY,
float viewportW, float viewportH, void* userData)
{
if (!sShowStats) return;
ImDrawList* drawList = ImGui::GetWindowDrawList();
// Draw an info box in the top-right corner
float boxW = 160.0f;
float boxH = 50.0f;
float margin = 10.0f;
float boxX = viewportX + viewportW - boxW - margin;
float boxY = viewportY + margin;
drawList->AddRectFilled(
ImVec2(boxX, boxY),
ImVec2(boxX + boxW, boxY + boxH),
IM_COL32(0, 0, 0, 160),
4.0f
);
drawList->AddText(
ImVec2(boxX + 8.0f, boxY + 6.0f),
IM_COL32(200, 200, 200, 255),
"Custom Overlay"
);
drawList->AddText(
ImVec2(boxX + 8.0f, boxY + 24.0f),
IM_COL32(150, 255, 150, 255),
"Status: Active"
);
}
/**
* @brief Viewport overlay: draws a center crosshair.
*/
static void DrawCrosshairOverlay(float viewportX, float viewportY,
float viewportW, float viewportH, void* userData)
{
if (!sShowGrid) return;
ImDrawList* drawList = ImGui::GetWindowDrawList();
float cx = viewportX + viewportW * 0.5f;
float cy = viewportY + viewportH * 0.5f;
float size = 20.0f;
ImU32 color = IM_COL32(255, 255, 0, 100);
drawList->AddLine(ImVec2(cx - size, cy), ImVec2(cx + size, cy), color);
drawList->AddLine(ImVec2(cx, cy - size), ImVec2(cx, cy + size), color);
}
#endif
static int OnLoad(PolyphaseEngineAPI* api)
{
sEngineAPI = api;
return 0;
}
static void OnUnload()
{
sEngineAPI = nullptr;
}
#if EDITOR
static void RegisterEditorUI(EditorUIHooks* hooks, uint64_t hookId)
{
// Viewport context menu items
hooks->AddViewportContextItem(hookId, "Spawn Node at Cursor", OnSpawnAtCursor, nullptr);
hooks->AddViewportContextItem(hookId, "Toggle Grid Overlay", OnToggleGrid, nullptr);
// Viewport overlays (drawn every frame)
hooks->RegisterViewportOverlay(hookId, "StatsOverlay", DrawStatsOverlay, nullptr);
hooks->RegisterViewportOverlay(hookId, "CrosshairOverlay", DrawCrosshairOverlay, nullptr);
}
#endif
extern "C" OCTAVE_PLUGIN_API int PolyphasePlugin_GetDesc(PolyphasePluginDesc* desc)
{
desc->apiVersion = OCTAVE_PLUGIN_API_VERSION;
desc->pluginName = "Viewport Extensions 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
AddViewportContextItem
void (*AddViewportContextItem)(HookId hookId, const char* itemPath,
MenuCallback callback, void* userData);
Adds an item to the viewport right-click context menu.
RegisterViewportOverlay
void (*RegisterViewportOverlay)(HookId hookId, const char* overlayName,
ViewportOverlayCallback drawFunc, void* userData);
Registers a per-frame overlay callback. The callback receives viewport bounds in screen coordinates.
ViewportOverlayCallback
typedef void (*ViewportOverlayCallback)(float viewportX, float viewportY,
float viewportW, float viewportH, void* userData);
Best Practices
- Use ImDrawList - Access via
ImGui::GetWindowDrawList()for drawing shapes, lines, and text - Screen Coordinates - All viewport parameters are in screen space; use them directly with ImDrawList
- Performance - Overlays are called every frame; keep drawing lightweight
- Toggle Support - Provide context menu items or shortcuts to toggle overlays on/off
- Semi-Transparent Backgrounds - Use alpha values (e.g.,
IM_COL32(0,0,0,160)) for overlay boxes