Custom Gizmo Tools
Overview
This example demonstrates how to register a custom gizmo tool that appears alongside the built-in Translate, Rotate, and Scale tools in the viewport toolbar. When your tool is active and a node is selected, your draw callback is invoked each frame.
Files
package.json
{
"name": "Gizmo Tool Addon",
"author": "Polyphase Examples",
"description": "Adds a custom gizmo tool to the viewport toolbar.",
"version": "1.0.0",
"tags": ["editor", "example"],
"native": {
"target": "editor",
"sourceDir": "Source",
"binaryName": "gizmotooladdon",
"apiVersion": 2
}
}
Source/GizmoToolAddon.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 gizmo tool.
* Called each frame when this tool is active and a node is selected.
*
* @param selectedNode Pointer to the currently selected Node.
* @param userData User data (unused).
*/
static void DrawAlignTool(void* selectedNode, void* userData)
{
// Draw a small tool options panel
ImGui::Text("Align Tool Options:");
ImGui::Separator();
if (ImGui::Button("Align to Grid"))
{
sEngineAPI->LogDebug("Aligning selected node to grid");
}
if (ImGui::Button("Align to Ground"))
{
sEngineAPI->LogDebug("Aligning selected node to ground");
}
if (ImGui::Button("Center on Parent"))
{
sEngineAPI->LogDebug("Centering selected node on parent");
}
}
/**
* @brief Draw callback for a snap tool.
*/
static void DrawSnapTool(void* selectedNode, void* userData)
{
static float sSnapDistance = 1.0f;
ImGui::Text("Snap Tool:");
ImGui::Separator();
ImGui::DragFloat("Snap Distance", &sSnapDistance, 0.1f, 0.1f, 100.0f);
if (ImGui::Button("Snap to Nearest"))
{
sEngineAPI->LogDebug("Snapping to nearest object (dist=%.1f)", sSnapDistance);
}
}
#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)
{
hooks->RegisterGizmoTool(
hookId,
"AlignTool", // Unique tool name
"A", // Icon character for toolbar button
"Align Tool", // Tooltip text
DrawAlignTool, // Draw callback
nullptr // User data
);
hooks->RegisterGizmoTool(
hookId,
"SnapTool",
"S",
"Snap Tool",
DrawSnapTool,
nullptr
);
}
#endif
extern "C" OCTAVE_PLUGIN_API int PolyphasePlugin_GetDesc(PolyphasePluginDesc* desc)
{
desc->apiVersion = OCTAVE_PLUGIN_API_VERSION;
desc->pluginName = "Gizmo Tool 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;
}
Result
The viewport toolbar will show:
[T] [R] [S] [A] [S]
^ ^ ^ ^ ^
| | | | └── Snap Tool (addon)
| | | └────── Align Tool (addon)
| | └────────── Scale (built-in)
| └────────────── Rotate (built-in)
└────────────────── Translate (built-in)
API Reference
RegisterGizmoTool
void (*RegisterGizmoTool)(
HookId hookId,
const char* toolName,
const char* iconText,
const char* tooltip,
GizmoToolDrawCallback drawFunc,
void* userData
);
Parameters:
- hookId - The hook identifier
- toolName - Unique name for the tool
- iconText - Character(s) shown on the toolbar button
- tooltip - Tooltip displayed on hover
- drawFunc - Called each frame when tool is active: void drawFunc(void* selectedNode, void* userData)
- userData - Optional user data
UnregisterGizmoTool
void (*UnregisterGizmoTool)(HookId hookId, const char* toolName);
Best Practices
- Icon Text - Use a single character for consistency with built-in tools
- Node Required - Your draw callback only fires when a node is selected
- Tool State - Use static variables or userData to persist tool settings
- Compact UI - Keep the tool options UI small since it shares space with the viewport