Example: Custom Theme
A native addon that demonstrates how to apply custom ImGui themes to the editor.
Overview
This example demonstrates: - Applying custom ImGui colors and styles - Creating themed UI elements - Switching between themes at runtime - Persisting theme preferences
Files
package.json
{
"name": "Custom Theme",
"author": "Polyphase Examples",
"description": "Applies custom visual themes to the editor.",
"version": "1.0.0",
"tags": ["editor", "theme", "ui"],
"native": {
"target": "editor",
"sourceDir": "Source",
"binaryName": "customtheme",
"apiVersion": 2
}
}
Source/CustomTheme.cpp
/**
* @file CustomTheme.cpp
* @brief Demonstrates applying custom ImGui themes.
*
* With the addon include paths set up, you can now include:
* - Engine headers (Engine/Source)
* - Lua headers (External/Lua)
* - GLM math (External/glm)
* - ImGui (External/Imgui) - editor builds only
* - ImGuizmo (External/ImGuizmo) - editor builds only
* - Bullet physics (External/bullet3/src)
*/
#include "Plugins/PolyphasePluginAPI.h"
#include "Plugins/PolyphaseEngineAPI.h"
#if EDITOR
#include "Plugins/EditorUIHooks.h"
// ImGui is available in editor builds (External/Imgui)
#include "imgui.h"
#endif
// GLM is available for all builds (External/glm)
#include "glm/glm.hpp"
static PolyphaseEngineAPI* sEngineAPI = nullptr;
static uint64_t sHookId = 0;
#if EDITOR
//=============================================================================
// Theme Definitions
//=============================================================================
enum class ThemeType
{
Dark,
Light,
Nord,
Dracula,
Monokai,
Solarized,
Custom
};
static ThemeType sCurrentTheme = ThemeType::Dark;
static bool sThemeWindowOpen = false;
// Custom theme colors (user-editable)
static ImVec4 sCustomColors[ImGuiCol_COUNT];
static bool sCustomColorsInitialized = false;
//=============================================================================
// Theme Application Functions
//=============================================================================
static void ApplyDarkTheme()
{
ImGuiStyle& style = ImGui::GetStyle();
ImVec4* colors = style.Colors;
// Window
colors[ImGuiCol_WindowBg] = ImVec4(0.10f, 0.10f, 0.10f, 1.00f);
colors[ImGuiCol_ChildBg] = ImVec4(0.00f, 0.00f, 0.00f, 0.00f);
colors[ImGuiCol_PopupBg] = ImVec4(0.08f, 0.08f, 0.08f, 0.94f);
// Borders
colors[ImGuiCol_Border] = ImVec4(0.43f, 0.43f, 0.50f, 0.50f);
colors[ImGuiCol_BorderShadow] = ImVec4(0.00f, 0.00f, 0.00f, 0.00f);
// Frame
colors[ImGuiCol_FrameBg] = ImVec4(0.16f, 0.29f, 0.48f, 0.54f);
colors[ImGuiCol_FrameBgHovered] = ImVec4(0.26f, 0.59f, 0.98f, 0.40f);
colors[ImGuiCol_FrameBgActive] = ImVec4(0.26f, 0.59f, 0.98f, 0.67f);
// Title
colors[ImGuiCol_TitleBg] = ImVec4(0.04f, 0.04f, 0.04f, 1.00f);
colors[ImGuiCol_TitleBgActive] = ImVec4(0.16f, 0.29f, 0.48f, 1.00f);
colors[ImGuiCol_TitleBgCollapsed] = ImVec4(0.00f, 0.00f, 0.00f, 0.51f);
// Menu
colors[ImGuiCol_MenuBarBg] = ImVec4(0.14f, 0.14f, 0.14f, 1.00f);
// Scrollbar
colors[ImGuiCol_ScrollbarBg] = ImVec4(0.02f, 0.02f, 0.02f, 0.53f);
colors[ImGuiCol_ScrollbarGrab] = ImVec4(0.31f, 0.31f, 0.31f, 1.00f);
colors[ImGuiCol_ScrollbarGrabHovered] = ImVec4(0.41f, 0.41f, 0.41f, 1.00f);
colors[ImGuiCol_ScrollbarGrabActive] = ImVec4(0.51f, 0.51f, 0.51f, 1.00f);
// Buttons
colors[ImGuiCol_Button] = ImVec4(0.26f, 0.59f, 0.98f, 0.40f);
colors[ImGuiCol_ButtonHovered] = ImVec4(0.26f, 0.59f, 0.98f, 1.00f);
colors[ImGuiCol_ButtonActive] = ImVec4(0.06f, 0.53f, 0.98f, 1.00f);
// Header
colors[ImGuiCol_Header] = ImVec4(0.26f, 0.59f, 0.98f, 0.31f);
colors[ImGuiCol_HeaderHovered] = ImVec4(0.26f, 0.59f, 0.98f, 0.80f);
colors[ImGuiCol_HeaderActive] = ImVec4(0.26f, 0.59f, 0.98f, 1.00f);
// Separator
colors[ImGuiCol_Separator] = ImVec4(0.43f, 0.43f, 0.50f, 0.50f);
colors[ImGuiCol_SeparatorHovered] = ImVec4(0.10f, 0.40f, 0.75f, 0.78f);
colors[ImGuiCol_SeparatorActive] = ImVec4(0.10f, 0.40f, 0.75f, 1.00f);
// Tab
colors[ImGuiCol_Tab] = ImVec4(0.18f, 0.35f, 0.58f, 0.86f);
colors[ImGuiCol_TabHovered] = ImVec4(0.26f, 0.59f, 0.98f, 0.80f);
colors[ImGuiCol_TabActive] = ImVec4(0.20f, 0.41f, 0.68f, 1.00f);
// Text
colors[ImGuiCol_Text] = ImVec4(1.00f, 1.00f, 1.00f, 1.00f);
colors[ImGuiCol_TextDisabled] = ImVec4(0.50f, 0.50f, 0.50f, 1.00f);
if (sEngineAPI)
{
sEngineAPI->LogDebug("Applied Dark theme");
}
}
static void ApplyLightTheme()
{
ImGuiStyle& style = ImGui::GetStyle();
ImVec4* colors = style.Colors;
colors[ImGuiCol_WindowBg] = ImVec4(0.94f, 0.94f, 0.94f, 1.00f);
colors[ImGuiCol_ChildBg] = ImVec4(0.00f, 0.00f, 0.00f, 0.00f);
colors[ImGuiCol_PopupBg] = ImVec4(1.00f, 1.00f, 1.00f, 0.98f);
colors[ImGuiCol_Border] = ImVec4(0.00f, 0.00f, 0.00f, 0.30f);
colors[ImGuiCol_BorderShadow] = ImVec4(0.00f, 0.00f, 0.00f, 0.00f);
colors[ImGuiCol_FrameBg] = ImVec4(1.00f, 1.00f, 1.00f, 1.00f);
colors[ImGuiCol_FrameBgHovered] = ImVec4(0.26f, 0.59f, 0.98f, 0.40f);
colors[ImGuiCol_FrameBgActive] = ImVec4(0.26f, 0.59f, 0.98f, 0.67f);
colors[ImGuiCol_TitleBg] = ImVec4(0.96f, 0.96f, 0.96f, 1.00f);
colors[ImGuiCol_TitleBgActive] = ImVec4(0.82f, 0.82f, 0.82f, 1.00f);
colors[ImGuiCol_TitleBgCollapsed] = ImVec4(1.00f, 1.00f, 1.00f, 0.51f);
colors[ImGuiCol_MenuBarBg] = ImVec4(0.86f, 0.86f, 0.86f, 1.00f);
colors[ImGuiCol_Button] = ImVec4(0.26f, 0.59f, 0.98f, 0.40f);
colors[ImGuiCol_ButtonHovered] = ImVec4(0.26f, 0.59f, 0.98f, 1.00f);
colors[ImGuiCol_ButtonActive] = ImVec4(0.06f, 0.53f, 0.98f, 1.00f);
colors[ImGuiCol_Header] = ImVec4(0.26f, 0.59f, 0.98f, 0.31f);
colors[ImGuiCol_HeaderHovered] = ImVec4(0.26f, 0.59f, 0.98f, 0.80f);
colors[ImGuiCol_HeaderActive] = ImVec4(0.26f, 0.59f, 0.98f, 1.00f);
colors[ImGuiCol_Text] = ImVec4(0.00f, 0.00f, 0.00f, 1.00f);
colors[ImGuiCol_TextDisabled] = ImVec4(0.60f, 0.60f, 0.60f, 1.00f);
if (sEngineAPI)
{
sEngineAPI->LogDebug("Applied Light theme");
}
}
static void ApplyNordTheme()
{
ImGuiStyle& style = ImGui::GetStyle();
ImVec4* colors = style.Colors;
// Nord color palette
// Polar Night
ImVec4 nord0(0.18f, 0.20f, 0.25f, 1.0f); // #2E3440
ImVec4 nord1(0.23f, 0.26f, 0.32f, 1.0f); // #3B4252
ImVec4 nord2(0.26f, 0.30f, 0.37f, 1.0f); // #434C5E
ImVec4 nord3(0.30f, 0.34f, 0.42f, 1.0f); // #4C566A
// Snow Storm
ImVec4 nord4(0.85f, 0.87f, 0.91f, 1.0f); // #D8DEE9
ImVec4 nord5(0.90f, 0.91f, 0.94f, 1.0f); // #E5E9F0
ImVec4 nord6(0.93f, 0.94f, 0.96f, 1.0f); // #ECEFF4
// Frost
ImVec4 nord7(0.56f, 0.74f, 0.73f, 1.0f); // #8FBCBB
ImVec4 nord8(0.53f, 0.75f, 0.82f, 1.0f); // #88C0D0
ImVec4 nord9(0.51f, 0.63f, 0.76f, 1.0f); // #81A1C1
ImVec4 nord10(0.37f, 0.51f, 0.67f, 1.0f); // #5E81AC
// Aurora
ImVec4 nord11(0.75f, 0.38f, 0.42f, 1.0f); // #BF616A (red)
ImVec4 nord12(0.83f, 0.54f, 0.44f, 1.0f); // #D08770 (orange)
ImVec4 nord13(0.92f, 0.80f, 0.55f, 1.0f); // #EBCB8B (yellow)
ImVec4 nord14(0.64f, 0.75f, 0.55f, 1.0f); // #A3BE8C (green)
ImVec4 nord15(0.71f, 0.56f, 0.68f, 1.0f); // #B48EAD (purple)
colors[ImGuiCol_WindowBg] = nord0;
colors[ImGuiCol_ChildBg] = ImVec4(0, 0, 0, 0);
colors[ImGuiCol_PopupBg] = nord1;
colors[ImGuiCol_Border] = nord3;
colors[ImGuiCol_FrameBg] = nord1;
colors[ImGuiCol_FrameBgHovered] = nord2;
colors[ImGuiCol_FrameBgActive] = nord3;
colors[ImGuiCol_TitleBg] = nord1;
colors[ImGuiCol_TitleBgActive] = nord2;
colors[ImGuiCol_TitleBgCollapsed] = nord0;
colors[ImGuiCol_MenuBarBg] = nord1;
colors[ImGuiCol_Button] = nord9;
colors[ImGuiCol_ButtonHovered] = nord8;
colors[ImGuiCol_ButtonActive] = nord10;
colors[ImGuiCol_Header] = nord2;
colors[ImGuiCol_HeaderHovered] = nord3;
colors[ImGuiCol_HeaderActive] = nord9;
colors[ImGuiCol_Text] = nord4;
colors[ImGuiCol_TextDisabled] = nord3;
colors[ImGuiCol_Tab] = nord1;
colors[ImGuiCol_TabHovered] = nord9;
colors[ImGuiCol_TabActive] = nord2;
if (sEngineAPI)
{
sEngineAPI->LogDebug("Applied Nord theme");
}
}
static void ApplyDraculaTheme()
{
ImGuiStyle& style = ImGui::GetStyle();
ImVec4* colors = style.Colors;
// Dracula color palette
ImVec4 background(0.16f, 0.16f, 0.21f, 1.0f); // #282a36
ImVec4 currentLine(0.27f, 0.28f, 0.35f, 1.0f); // #44475a
ImVec4 foreground(0.97f, 0.97f, 0.95f, 1.0f); // #f8f8f2
ImVec4 comment(0.38f, 0.45f, 0.53f, 1.0f); // #6272a4
ImVec4 cyan(0.55f, 0.91f, 0.99f, 1.0f); // #8be9fd
ImVec4 green(0.31f, 0.98f, 0.48f, 1.0f); // #50fa7b
ImVec4 orange(1.00f, 0.72f, 0.42f, 1.0f); // #ffb86c
ImVec4 pink(1.00f, 0.47f, 0.66f, 1.0f); // #ff79c6
ImVec4 purple(0.74f, 0.58f, 0.98f, 1.0f); // #bd93f9
ImVec4 red(1.00f, 0.33f, 0.33f, 1.0f); // #ff5555
ImVec4 yellow(0.95f, 0.98f, 0.55f, 1.0f); // #f1fa8c
colors[ImGuiCol_WindowBg] = background;
colors[ImGuiCol_PopupBg] = background;
colors[ImGuiCol_Border] = comment;
colors[ImGuiCol_FrameBg] = currentLine;
colors[ImGuiCol_FrameBgHovered] = ImVec4(currentLine.x + 0.1f, currentLine.y + 0.1f, currentLine.z + 0.1f, 1.0f);
colors[ImGuiCol_FrameBgActive] = purple;
colors[ImGuiCol_TitleBg] = background;
colors[ImGuiCol_TitleBgActive] = currentLine;
colors[ImGuiCol_MenuBarBg] = background;
colors[ImGuiCol_Button] = purple;
colors[ImGuiCol_ButtonHovered] = pink;
colors[ImGuiCol_ButtonActive] = cyan;
colors[ImGuiCol_Header] = currentLine;
colors[ImGuiCol_HeaderHovered] = purple;
colors[ImGuiCol_HeaderActive] = pink;
colors[ImGuiCol_Text] = foreground;
colors[ImGuiCol_TextDisabled] = comment;
if (sEngineAPI)
{
sEngineAPI->LogDebug("Applied Dracula theme");
}
}
static void ApplyTheme(ThemeType theme)
{
sCurrentTheme = theme;
switch (theme)
{
case ThemeType::Dark: ApplyDarkTheme(); break;
case ThemeType::Light: ApplyLightTheme(); break;
case ThemeType::Nord: ApplyNordTheme(); break;
case ThemeType::Dracula: ApplyDraculaTheme(); break;
default: ApplyDarkTheme(); break;
}
}
//=============================================================================
// Theme Selector Window
//=============================================================================
static void DrawThemeWindow(void* userData)
{
ImGui::Text("Select Editor Theme");
ImGui::Separator();
const char* themes[] = { "Dark", "Light", "Nord", "Dracula", "Monokai", "Solarized", "Custom" };
int currentThemeIndex = static_cast<int>(sCurrentTheme);
if (ImGui::Combo("Theme", ¤tThemeIndex, themes, IM_ARRAYSIZE(themes)))
{
ApplyTheme(static_cast<ThemeType>(currentThemeIndex));
}
ImGui::Spacing();
ImGui::Separator();
// Preview area
ImGui::Text("Preview");
if (ImGui::Button("Sample Button"))
{
// Does nothing, just for preview
}
ImGui::SameLine();
if (ImGui::Button("Another Button"))
{
}
static float sliderValue = 0.5f;
ImGui::SliderFloat("Slider", &sliderValue, 0.0f, 1.0f);
static bool checkValue = true;
ImGui::Checkbox("Checkbox", &checkValue);
static char textValue[64] = "Sample text";
ImGui::InputText("Text Input", textValue, sizeof(textValue));
if (ImGui::CollapsingHeader("Collapsible Section"))
{
ImGui::Text("Content inside collapsible");
ImGui::BulletText("Item 1");
ImGui::BulletText("Item 2");
}
ImGui::Spacing();
ImGui::Separator();
// Style adjustments
ImGuiStyle& style = ImGui::GetStyle();
if (ImGui::CollapsingHeader("Style Adjustments"))
{
ImGui::SliderFloat("Window Rounding", &style.WindowRounding, 0.0f, 12.0f);
ImGui::SliderFloat("Frame Rounding", &style.FrameRounding, 0.0f, 12.0f);
ImGui::SliderFloat("Popup Rounding", &style.PopupRounding, 0.0f, 12.0f);
ImGui::SliderFloat("Scrollbar Rounding", &style.ScrollbarRounding, 0.0f, 12.0f);
ImGui::SliderFloat("Grab Rounding", &style.GrabRounding, 0.0f, 12.0f);
ImGui::SliderFloat("Tab Rounding", &style.TabRounding, 0.0f, 12.0f);
ImGui::Spacing();
ImGui::SliderFloat2("Window Padding", &style.WindowPadding.x, 0.0f, 20.0f);
ImGui::SliderFloat2("Frame Padding", &style.FramePadding.x, 0.0f, 20.0f);
ImGui::SliderFloat2("Item Spacing", &style.ItemSpacing.x, 0.0f, 20.0f);
}
// Reset button
ImGui::Spacing();
if (ImGui::Button("Reset to Default Style"))
{
style = ImGuiStyle();
ApplyTheme(sCurrentTheme);
}
}
//=============================================================================
// Menu Callbacks
//=============================================================================
static void OnOpenThemeWindow(void* userData)
{
sThemeWindowOpen = true;
}
static void OnApplyDark(void* userData) { ApplyTheme(ThemeType::Dark); }
static void OnApplyLight(void* userData) { ApplyTheme(ThemeType::Light); }
static void OnApplyNord(void* userData) { ApplyTheme(ThemeType::Nord); }
static void OnApplyDracula(void* userData) { ApplyTheme(ThemeType::Dracula); }
//=============================================================================
// Plugin Callbacks
//=============================================================================
static void RegisterEditorUI(EditorUIHooks* hooks, uint64_t hookId)
{
sHookId = hookId;
// Register the theme window
hooks->RegisterWindow(hookId, "Theme Settings", "customtheme_window", DrawThemeWindow, nullptr);
// Add menu items
hooks->AddMenuItem(hookId, "Edit", "Themes/Theme Settings...", OnOpenThemeWindow, nullptr, nullptr);
hooks->AddMenuSeparator(hookId, "Edit");
hooks->AddMenuItem(hookId, "Edit", "Themes/Dark", OnApplyDark, nullptr, nullptr);
hooks->AddMenuItem(hookId, "Edit", "Themes/Light", OnApplyLight, nullptr, nullptr);
hooks->AddMenuItem(hookId, "Edit", "Themes/Nord", OnApplyNord, nullptr, nullptr);
hooks->AddMenuItem(hookId, "Edit", "Themes/Dracula", OnApplyDracula, nullptr, nullptr);
if (sEngineAPI)
{
sEngineAPI->LogDebug("CustomTheme: Registered theme window and menu items");
}
}
#endif // EDITOR
static int OnLoad(PolyphaseEngineAPI* api)
{
sEngineAPI = api;
api->LogDebug("CustomTheme addon loaded!");
#if EDITOR
// Apply default theme on load
ApplyTheme(ThemeType::Dark);
#endif
return 0;
}
static void OnUnload()
{
if (sEngineAPI)
{
sEngineAPI->LogDebug("CustomTheme addon unloaded.");
}
sEngineAPI = nullptr;
}
//=============================================================================
// Plugin Entry Point
//=============================================================================
extern "C" OCTAVE_PLUGIN_API int PolyphasePlugin_GetDesc(PolyphasePluginDesc* desc)
{
desc->apiVersion = OCTAVE_PLUGIN_API_VERSION;
desc->pluginName = "Custom Theme";
desc->pluginVersion = "1.0.0";
desc->OnLoad = OnLoad;
desc->OnUnload = OnUnload;
desc->Tick = nullptr; // Editor-only addon, no gameplay tick
desc->TickEditor = nullptr; // Theme is applied once, no per-frame updates
desc->RegisterTypes = nullptr;
desc->RegisterScriptFuncs = nullptr;
#if EDITOR
desc->RegisterEditorUI = RegisterEditorUI;
#else
desc->RegisterEditorUI = nullptr;
#endif
return 0;
}
Available Themes
| Theme | Description |
|---|---|
| Dark | Default dark theme with blue accents |
| Light | Clean light theme |
| Nord | Based on the Nord color palette (arctic blue-gray) |
| Dracula | Based on the Dracula color scheme (purple accents) |
| Monokai | Based on the Monokai color scheme |
| Solarized | Based on the Solarized color palette |
| Custom | User-defined colors |
Usage
After loading the addon:
- Go to Edit > Themes > Theme Settings... to open the theme window
- Or quickly switch themes via Edit > Themes > [Theme Name]
- Adjust style parameters (rounding, padding) in the Theme Settings window
Creating Custom Themes
To create your own theme:
static void ApplyMyCustomTheme()
{
ImGuiStyle& style = ImGui::GetStyle();
ImVec4* colors = style.Colors;
// Define your colors
ImVec4 myPrimary(0.2f, 0.6f, 0.8f, 1.0f);
ImVec4 myBackground(0.1f, 0.1f, 0.12f, 1.0f);
// ... etc
// Apply to ImGui color slots
colors[ImGuiCol_WindowBg] = myBackground;
colors[ImGuiCol_Button] = myPrimary;
// ... etc
}
ImGui Color Slots
Key color slots to customize:
| Slot | Description |
|---|---|
ImGuiCol_WindowBg |
Window background |
ImGuiCol_ChildBg |
Child window background |
ImGuiCol_PopupBg |
Popup/tooltip background |
ImGuiCol_Border |
Border color |
ImGuiCol_FrameBg |
Input field background |
ImGuiCol_FrameBgHovered |
Input field hovered |
ImGuiCol_FrameBgActive |
Input field active |
ImGuiCol_TitleBg |
Title bar background |
ImGuiCol_TitleBgActive |
Active title bar |
ImGuiCol_MenuBarBg |
Menu bar background |
ImGuiCol_Button |
Button color |
ImGuiCol_ButtonHovered |
Button hovered |
ImGuiCol_ButtonActive |
Button pressed |
ImGuiCol_Header |
Header/tree node |
ImGuiCol_Tab |
Tab color |
ImGuiCol_Text |
Text color |
ImGuiCol_TextDisabled |
Disabled text |