Files
kte/docs/themes.md
Kyle Isom cbbde43dc2 Stub out previous undo implementation; update docs.
- Remove outdated `undo-state.md`
- Add two code quality/optimization reports that were used to guide previous work:
  - `code-report.md` (optimization)
  - `code-report-quality.md` (stability and code health)
- Add `themes.md`.
- Update undo system docs and roadmap.
2025-12-03 15:12:28 -08:00

14 KiB

Themes in kte

Overview

kte's GUI frontend (kge) uses ImGui for rendering and supports multiple color themes. Themes define the visual appearance of the editor interface including colors for text, backgrounds, buttons, borders, and other UI elements.

Theme files are located in the themes/ directory and are header-only C++ files that configure ImGui's style system.

Available themes

Current themes (alphabetically):

  • amber — Monochrome amber/black CRT-inspired theme
  • eink — E-ink inspired high-contrast theme (light/dark variants)
  • everforest — Warm, forest-inspired palette
  • gruvbox — Retro groove color scheme (light/dark variants)
  • kanagawa-paper — Inspired by traditional Japanese art
  • lcars — Star Trek LCARS interface style
  • nord — Arctic, north-bluish color palette
  • old-book — Sepia-toned vintage book aesthetic (light/dark variants)
  • orbital — Space-themed dark palette
  • plan9 — Minimalist Plan 9 from Bell Labs inspired
  • solarized — Ethan Schoonover's Solarized (light/dark variants)
  • weyland-yutani — Alien franchise corporate aesthetic
  • zenburn — Low-contrast, easy-on-the-eyes theme

Configuration

Themes are configured via $HOME/.config/kte/kge.ini:

theme = nord
background = dark
  • theme — The theme name (e.g., "nord", "gruvbox", "solarized")
  • background — Either "dark" or "light" (for themes supporting both variants)

Themes can also be switched at runtime using the :theme <name> command.

Theme structure

Each theme is a header file in themes/ that defines one or more functions to apply the theme. The basic structure:

  1. Include ThemeHelpers.h — Provides the RGBA() helper function
  2. Define palette — Create ImVec4 color constants using RGBA(0xRRGGBB)
  3. Get ImGui style — Obtain reference via ImGui::GetStyle()
  4. Set style parameters — Configure padding, rounding, border sizes, etc.
  5. Assign colors — Map palette to ImGuiCol_* enum values

Minimal example structure

// themes/MyTheme.h
#pragma once
#include "ThemeHelpers.h"

static void
ApplyMyTheme()
{
    // 1. Define color palette
    const ImVec4 bg     = RGBA(0x1e1e1e);
    const ImVec4 fg     = RGBA(0xd4d4d4);
    const ImVec4 accent = RGBA(0x007acc);
    
    // 2. Get style reference
    ImGuiStyle &style = ImGui::GetStyle();
    
    // 3. Set style parameters
    style.WindowPadding    = ImVec2(8.0f, 8.0f);
    style.FrameRounding    = 3.0f;
    style.WindowBorderSize = 1.0f;
    // ... additional style parameters
    
    // 4. Assign colors
    ImVec4 *colors = style.Colors;
    colors[ImGuiCol_Text]     = fg;
    colors[ImGuiCol_WindowBg] = bg;
    colors[ImGuiCol_Button]   = accent;
    // ... additional color assignments
}

The RGBA() helper

The RGBA() function (defined in themes/ThemeHelpers.h) converts packed RGB hex values to ImGui's ImVec4 format:

const ImVec4 color = RGBA(0xRRGGBB);        // Opaque (alpha = 1.0)
const ImVec4 color = RGBA(0xRRGGBB, 0.5f);  // With custom alpha

Examples:

const ImVec4 white = RGBA(0xFFFFFF);
const ImVec4 black = RGBA(0x000000);
const ImVec4 red   = RGBA(0xFF0000);
const ImVec4 blue  = RGBA(0x0000FF);
const ImVec4 semi  = RGBA(0x808080, 0.5f);  // 50% transparent gray

ImGui color elements

Themes must define colors for ImGui's UI elements. Key ImGuiCol_* values:

Text

  • ImGuiCol_Text — Main text color
  • ImGuiCol_TextDisabled — Disabled/grayed-out text
  • ImGuiCol_TextSelectedBg — Text selection background

Windows and backgrounds

  • ImGuiCol_WindowBg — Window background
  • ImGuiCol_ChildBg — Child window background
  • ImGuiCol_PopupBg — Popup window background

Borders

  • ImGuiCol_Border — Border color
  • ImGuiCol_BorderShadow — Border shadow (often transparent)

Frames (input fields, etc.)

  • ImGuiCol_FrameBg — Frame background (normal state)
  • ImGuiCol_FrameBgHovered — Frame background when hovered
  • ImGuiCol_FrameBgActive — Frame background when active/clicked

Title bars

  • ImGuiCol_TitleBg — Title bar (unfocused)
  • ImGuiCol_TitleBgActive — Title bar (focused)
  • ImGuiCol_TitleBgCollapsed — Collapsed title bar

Interactive elements

  • ImGuiCol_Button — Button background
  • ImGuiCol_ButtonHovered — Button when hovered
  • ImGuiCol_ButtonActive — Button when pressed
  • ImGuiCol_CheckMark — Checkmark/radio button indicator
  • ImGuiCol_SliderGrab — Slider grab handle
  • ImGuiCol_SliderGrabActive — Slider grab when dragging

Headers and separators

  • ImGuiCol_Header — Header (tree nodes, collapsing headers)
  • ImGuiCol_HeaderHovered — Header when hovered
  • ImGuiCol_HeaderActive — Header when clicked
  • ImGuiCol_Separator — Separator line
  • ImGuiCol_SeparatorHovered — Separator when hovered
  • ImGuiCol_SeparatorActive — Separator when dragged

Scrollbars

  • ImGuiCol_ScrollbarBg — Scrollbar background
  • ImGuiCol_ScrollbarGrab — Scrollbar grab
  • ImGuiCol_ScrollbarGrabHovered — Scrollbar grab when hovered
  • ImGuiCol_ScrollbarGrabActive — Scrollbar grab when dragging

Tabs

  • ImGuiCol_Tab — Tab (inactive)
  • ImGuiCol_TabHovered — Tab when hovered
  • ImGuiCol_TabActive — Tab (active)
  • ImGuiCol_TabUnfocused — Tab in unfocused window
  • ImGuiCol_TabUnfocusedActive — Active tab in unfocused window

Tables

  • ImGuiCol_TableHeaderBg — Table header background
  • ImGuiCol_TableBorderStrong — Strong table borders
  • ImGuiCol_TableBorderLight — Light table borders
  • ImGuiCol_TableRowBg — Table row background
  • ImGuiCol_TableRowBgAlt — Alternating table row background

Navigation and overlays

  • ImGuiCol_MenuBarBg — Menu bar background
  • ImGuiCol_ResizeGrip — Resize grip indicator
  • ImGuiCol_ResizeGripHovered — Resize grip when hovered
  • ImGuiCol_ResizeGripActive — Resize grip when dragging
  • ImGuiCol_DragDropTarget — Drag-and-drop target highlight
  • ImGuiCol_NavHighlight — Navigation highlight
  • ImGuiCol_NavWindowingHighlight — Window navigation highlight
  • ImGuiCol_NavWindowingDimBg — Window navigation dim background
  • ImGuiCol_ModalWindowDimBg — Modal window dim background

Plots (graphs)

  • ImGuiCol_PlotLines — Plot line color
  • ImGuiCol_PlotLinesHovered — Plot line when hovered
  • ImGuiCol_PlotHistogram — Histogram color
  • ImGuiCol_PlotHistogramHovered — Histogram when hovered

Style parameters

In addition to colors, themes can customize style parameters:

ImGuiStyle &style = ImGui::GetStyle();

// Padding and spacing
style.WindowPadding    = ImVec2(8.0f, 8.0f);   // Window content padding
style.FramePadding     = ImVec2(6.0f, 4.0f);   // Frame (input fields) padding
style.CellPadding      = ImVec2(6.0f, 4.0f);   // Table cell padding
style.ItemSpacing      = ImVec2(6.0f, 6.0f);   // Space between items
style.ItemInnerSpacing = ImVec2(6.0f, 4.0f);   // Space within composite items

// Rounding
style.WindowRounding = 4.0f;  // Window corner rounding
style.FrameRounding  = 3.0f;  // Frame corner rounding
style.PopupRounding  = 4.0f;  // Popup corner rounding
style.GrabRounding   = 3.0f;  // Grab handle rounding
style.TabRounding    = 4.0f;  // Tab corner rounding

// Borders
style.WindowBorderSize = 1.0f;  // Window border width
style.FrameBorderSize  = 1.0f;  // Frame border width

// Scrollbars
style.ScrollbarSize = 14.0f;   // Scrollbar width
style.GrabMinSize   = 10.0f;   // Minimum grab handle size

Creating a new theme

Follow these steps to add a new theme to kte:

1. Create the theme file

Create a new header file in themes/ (e.g., themes/MyTheme.h):

// themes/MyTheme.h — Brief description
#pragma once
#include "ThemeHelpers.h"

// Expects to be included from GUITheme.h after <imgui.h> and RGBA() helper

static void
ApplyMyTheme()
{
    // Define your color palette
    const ImVec4 background = RGBA(0x1e1e1e);
    const ImVec4 foreground = RGBA(0xd4d4d4);
    const ImVec4 accent     = RGBA(0x007acc);
    // ... more colors
    
    ImGuiStyle &style = ImGui::GetStyle();
    
    // Configure style parameters
    style.WindowPadding = ImVec2(8.0f, 8.0f);
    // ... more style settings
    
    ImVec4 *colors = style.Colors;
    
    // Assign all required colors
    colors[ImGuiCol_Text]     = foreground;
    colors[ImGuiCol_WindowBg] = background;
    // ... assign all other ImGuiCol_* values
}

Refer to existing themes like Nord.h for a complete example of all required color assignments.

2. Add theme to GUITheme.h

Edit GUITheme.h to integrate your theme:

a) Add to ThemeId enum:

enum class ThemeId {
    // ... existing themes
    MyTheme = 13,  // Use next available number
};

b) Include your theme header:

// After other theme includes
#include "themes/MyTheme.h"

c) Create wrapper class in detail namespace:

namespace detail {
// ... existing theme classes

struct MyThemeWrapper final : Theme {
    [[nodiscard]] const char *Name() const override
    {
        return "mytheme";  // Lowercase canonical name
    }
    
    void Apply() const override
    {
        ApplyMyTheme();
    }
    
    ThemeId Id() override
    {
        return ThemeId::MyTheme;
    }
};
} // namespace detail

d) Register in ThemeRegistry():

static const std::vector<std::unique_ptr<Theme>> &
ThemeRegistry()
{
    static std::vector<std::unique_ptr<Theme>> reg;
    if (reg.empty()) {
        // Add in alphabetical order by canonical name
        reg.emplace_back(std::make_unique<detail::AmberTheme>());
        // ... existing themes
        reg.emplace_back(std::make_unique<detail::MyThemeWrapper>());
        // ... remaining themes
    }
    return reg;
}

3. Test your theme

Rebuild kte and test:

# Set theme in config
echo "theme = mytheme" >> ~/.config/kte/kge.ini

# Or switch at runtime
kge
:theme mytheme

Light/Dark theme variants

Some themes support both light and dark background modes. To implement this:

1. Create separate functions for each variant

// themes/MyTheme.h
#pragma once
#include "ThemeHelpers.h"

static void
ApplyMyThemeDark()
{
    const ImVec4 bg = RGBA(0x1e1e1e);  // Dark background
    const ImVec4 fg = RGBA(0xd4d4d4);  // Light text
    // ... rest of dark theme
}

static void
ApplyMyThemeLight()
{
    const ImVec4 bg = RGBA(0xffffff);  // Light background
    const ImVec4 fg = RGBA(0x1e1e1e);  // Dark text
    // ... rest of light theme
}

2. Check background mode in Apply()

// In GUITheme.h wrapper class
struct MyThemeWrapper final : Theme {
    // ... Name() and Id() methods
    
    void Apply() const override
    {
        if (gBackgroundMode == BackgroundMode::Dark)
            ApplyMyThemeDark();
        else
            ApplyMyThemeLight();
    }
};

See Solarized.h, Gruvbox.h, EInk.h, or OldBook.h for complete examples.

Updating existing themes

To modify an existing theme:

1. Locate the theme file

Theme files are in themes/ directory. For example, Nord theme is in themes/Nord.h.

2. Modify colors or style

Edit the ApplyXxxTheme() function:

  • Update palette color definitions
  • Change style parameters
  • Reassign ImGuiCol_* values

3. Rebuild and test

# Rebuild kte
cmake --build build

# Test changes
./build/kge

Changes take effect immediately on next launch or theme switch.

Best practices

When creating or updating themes:

  1. Start from an existing theme — Copy a similar theme as a template (e.g., Nord.h for dark themes, Solarized.h for light/dark variants)

  2. Define a complete palette first — Create all color constants at the top before assigning them

  3. Assign all colors — Ensure every ImGuiCol_* value is set to avoid inheriting unexpected colors

  4. Use consistent naming — Follow existing conventions (e.g., nord0, base03, descriptive names)

  5. Test interactivity — Verify hover, active, and disabled states for buttons, frames, and other interactive elements

  6. Consider contrast — Ensure text is readable against backgrounds; test with different content

  7. Test transparency — Use alpha values carefully for overlays, dim backgrounds, and selection highlights

  8. Match style to theme — Adjust rounding, padding, and borders to suit the theme's aesthetic (e.g., sharp corners for retro themes, rounded for modern)

  9. Document inspiration — Note the color scheme's origin or inspiration in the file header

  10. Maintain alphabetical order — When registering in ThemeRegistry(), maintain alphabetical order by canonical name

Troubleshooting

Theme not appearing

  • Check that the theme is registered in ThemeRegistry() in alphabetical order
  • Verify the canonical name matches what you're using in config or commands
  • Ensure the theme header is included in GUITheme.h

Colors look wrong

  • Verify hex color values are in 0xRRGGBB format (not 0xBBGGRR)
  • Check alpha values for semi-transparent elements
  • Ensure all ImGuiCol_* values are assigned

Style inconsistent

  • Make sure style parameters are set before color assignments
  • Check that you're getting ImGui::GetStyle() reference correctly
  • Verify no global style changes are overriding theme settings

References