// GUITheme.h — ImGui theming helpers and background mode #pragma once #include #include #include #include #include #include #include #include "themes/ThemeHelpers.h" namespace kte { // Background mode selection for light/dark palettes enum class BackgroundMode { Light, Dark }; // Global background mode; default to Dark to match prior defaults static inline auto gBackgroundMode = BackgroundMode::Dark; // Basic theme identifier (kept minimal; some ids are aliases) enum class ThemeId { EInk = 0, GruvboxDarkMedium = 1, GruvboxLightMedium = 1, // alias to unified gruvbox index Nord = 2, Plan9 = 3, Solarized = 4, }; // Current theme tracking static inline auto gCurrentTheme = ThemeId::Nord; static inline std::size_t gCurrentThemeIndex = 0; // Forward declarations for helpers used below static size_t ThemeIndexFromId(ThemeId id); static ThemeId ThemeIdFromIndex(size_t idx); // Helpers to set/query background mode static void SetBackgroundMode(const BackgroundMode m) { gBackgroundMode = m; } static BackgroundMode GetBackgroundMode() { return gBackgroundMode; } static inline const char * BackgroundModeName() { return gBackgroundMode == BackgroundMode::Light ? "light" : "dark"; } // Include individual theme implementations split under ./themes #include "themes/Nord.h" #include "themes/Plan9.h" #include "themes/Solarized.h" #include "themes/Gruvbox.h" #include "themes/EInk.h" // Theme abstraction and registry (generalized theme system) class Theme { public: virtual ~Theme() = default; [[nodiscard]] virtual const char *Name() const = 0; // canonical name (e.g., "nord", "gruvbox-dark") virtual void Apply() const = 0; // apply to current ImGui style virtual ThemeId Id() = 0; // theme identifier }; namespace detail { struct NordTheme final : Theme { [[nodiscard]] const char *Name() const override { return "nord"; } void Apply() const override { ApplyNordImGuiTheme(); } ThemeId Id() override { return ThemeId::Nord; } }; struct GruvboxTheme final : Theme { [[nodiscard]] const char *Name() const override { return "gruvbox"; } void Apply() const override { if (gBackgroundMode == BackgroundMode::Light) ApplyGruvboxLightMediumTheme(); else ApplyGruvboxDarkMediumTheme(); } ThemeId Id() override { // Legacy maps to dark; unified under base id GruvboxDarkMedium return ThemeId::GruvboxDarkMedium; } }; struct EInkTheme final : Theme { [[nodiscard]] const char *Name() const override { return "eink"; } void Apply() const override { if (gBackgroundMode == BackgroundMode::Dark) ApplyEInkDarkImGuiTheme(); else ApplyEInkImGuiTheme(); } ThemeId Id() override { return ThemeId::EInk; } }; struct SolarizedTheme final : Theme { [[nodiscard]] const char *Name() const override { return "solarized"; } void Apply() const override { if (gBackgroundMode == BackgroundMode::Light) ApplySolarizedLightTheme(); else ApplySolarizedDarkTheme(); } ThemeId Id() override { return ThemeId::Solarized; } }; struct Plan9Theme final : Theme { [[nodiscard]] const char *Name() const override { return "plan9"; } void Apply() const override { ApplyPlan9Theme(); } ThemeId Id() override { return ThemeId::Plan9; } }; } // namespace detail static const std::vector > & ThemeRegistry() { static std::vector > reg; if (reg.empty()) { // Alphabetical by canonical name: eink, gruvbox, nord, plan9, solarized reg.emplace_back(std::make_unique()); reg.emplace_back(std::make_unique()); reg.emplace_back(std::make_unique()); reg.emplace_back(std::make_unique()); reg.emplace_back(std::make_unique()); } return reg; } // Canonical theme name for a given ThemeId (via registry order) [[maybe_unused]] static const char * ThemeName(const ThemeId id) { const auto ® = ThemeRegistry(); const size_t idx = ThemeIndexFromId(id); if (idx < reg.size()) return reg[idx]->Name(); return "unknown"; } // Helper to apply a theme by id and update current theme static void ApplyTheme(const ThemeId id) { const auto ® = ThemeRegistry(); const size_t idx = ThemeIndexFromId(id); if (idx < reg.size()) { reg[idx]->Apply(); gCurrentTheme = id; gCurrentThemeIndex = idx; } } [[maybe_unused]] static ThemeId CurrentTheme() { return gCurrentTheme; } // Cycle helpers [[maybe_unused]] static ThemeId NextTheme() { const auto ® = ThemeRegistry(); if (reg.empty()) { return gCurrentTheme; } const size_t nxt = (gCurrentThemeIndex + 1) % reg.size(); ApplyTheme(ThemeIdFromIndex(nxt)); return gCurrentTheme; } [[maybe_unused]] static ThemeId PrevTheme() { const auto ® = ThemeRegistry(); if (reg.empty()) { return gCurrentTheme; } const size_t prv = (gCurrentThemeIndex + reg.size() - 1) % reg.size(); ApplyTheme(ThemeIdFromIndex(prv)); return gCurrentTheme; } // Name-based API [[maybe_unused]] static const Theme * GetThemeByName(const std::string &name) { const auto ® = ThemeRegistry(); for (const auto &t: reg) { if (name == t->Name()) return t.get(); } return nullptr; } [[maybe_unused]] static bool ApplyThemeByName(const std::string &name) { // Handle aliases and background-specific names std::string n = name; // lowercase copy std::transform(n.begin(), n.end(), n.begin(), [](unsigned char c) { return static_cast(std::tolower(c)); }); if (n == "gruvbox-dark") { SetBackgroundMode(BackgroundMode::Dark); n = "gruvbox"; } else if (n == "gruvbox-light") { SetBackgroundMode(BackgroundMode::Light); n = "gruvbox"; } else if (n == "solarized-dark") { SetBackgroundMode(BackgroundMode::Dark); n = "solarized"; } else if (n == "solarized-light") { SetBackgroundMode(BackgroundMode::Light); n = "solarized"; } else if (n == "eink-dark") { SetBackgroundMode(BackgroundMode::Dark); n = "eink"; } else if (n == "eink-light") { SetBackgroundMode(BackgroundMode::Light); n = "eink"; } const auto ® = ThemeRegistry(); for (size_t i = 0; i < reg.size(); ++i) { if (n == reg[i]->Name()) { reg[i]->Apply(); gCurrentThemeIndex = i; gCurrentTheme = ThemeIdFromIndex(i); return true; } } return false; } [[maybe_unused]] static const char * CurrentThemeName() { const auto ® = ThemeRegistry(); if (gCurrentThemeIndex < reg.size()) { return reg[gCurrentThemeIndex]->Name(); } return "unknown"; } // Helpers to map between legacy ThemeId and registry index static size_t ThemeIndexFromId(const ThemeId id) { switch (id) { case ThemeId::EInk: return 0; case ThemeId::GruvboxDarkMedium: return 1; case ThemeId::Nord: return 2; case ThemeId::Plan9: return 3; case ThemeId::Solarized: return 4; } return 0; } static ThemeId ThemeIdFromIndex(const size_t idx) { switch (idx) { default: case 0: return ThemeId::EInk; case 1: return ThemeId::GruvboxDarkMedium; // unified gruvbox case 2: return ThemeId::Nord; case 3: return ThemeId::Plan9; case 4: return ThemeId::Solarized; } } // --- Syntax palette (v1): map TokenKind to ink color per current theme/background --- [[maybe_unused]] static ImVec4 SyntaxInk(const TokenKind k) { // Basic palettes for dark/light backgrounds; tuned for Nord-ish defaults const bool dark = (GetBackgroundMode() == BackgroundMode::Dark); // Base text const ImVec4 def = dark ? RGBA(0xD8DEE9) : RGBA(0x2E3440); switch (k) { case TokenKind::Keyword: return dark ? RGBA(0x81A1C1) : RGBA(0x5E81AC); case TokenKind::Type: return dark ? RGBA(0x8FBCBB) : RGBA(0x4C566A); case TokenKind::String: return dark ? RGBA(0xA3BE8C) : RGBA(0x6C8E5E); case TokenKind::Char: return dark ? RGBA(0xA3BE8C) : RGBA(0x6C8E5E); case TokenKind::Comment: return dark ? RGBA(0x616E88) : RGBA(0x7A869A); case TokenKind::Number: return dark ? RGBA(0xEBCB8B) : RGBA(0xB58900); case TokenKind::Preproc: return dark ? RGBA(0xD08770) : RGBA(0xAF3A03); case TokenKind::Constant: return dark ? RGBA(0xB48EAD) : RGBA(0x7B4B7F); case TokenKind::Function: return dark ? RGBA(0x88C0D0) : RGBA(0x3465A4); case TokenKind::Operator: return dark ? RGBA(0xECEFF4) : RGBA(0x2E3440); case TokenKind::Punctuation: return dark ? RGBA(0xECEFF4) : RGBA(0x2E3440); case TokenKind::Identifier: return def; case TokenKind::Whitespace: return def; case TokenKind::Error: return dark ? RGBA(0xBF616A) : RGBA(0xCC0000); case TokenKind::Default: default: return def; } } } // namespace kte