Step 345: Theme Data + Tabbed Panel Logic (12/12 tests)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Bill
2026-02-16 08:29:29 -07:00
parent 3f419daec5
commit 97b9e522c3
2 changed files with 348 additions and 0 deletions

185
editor/src/ThemeData.h Normal file
View File

@@ -0,0 +1,185 @@
#pragma once
// Step 345-347: Theme data model — headless semantic color definitions
// Does NOT depend on ImGui; provides color values that can be applied to ImGui or other renderers
#include <string>
#include <map>
#include <nlohmann/json.hpp>
struct Color4 {
float r = 0, g = 0, b = 0, a = 1.0f;
Color4() = default;
Color4(float r_, float g_, float b_, float a_ = 1.0f) : r(r_), g(g_), b(b_), a(a_) {}
// From hex "#RRGGBB" or "#RRGGBBAA"
static Color4 fromHex(const std::string& hex) {
Color4 c;
if (hex.size() >= 7 && hex[0] == '#') {
c.r = std::stoi(hex.substr(1, 2), nullptr, 16) / 255.0f;
c.g = std::stoi(hex.substr(3, 2), nullptr, 16) / 255.0f;
c.b = std::stoi(hex.substr(5, 2), nullptr, 16) / 255.0f;
if (hex.size() >= 9) c.a = std::stoi(hex.substr(7, 2), nullptr, 16) / 255.0f;
}
return c;
}
std::string toHex() const {
char buf[10];
snprintf(buf, sizeof(buf), "#%02x%02x%02x",
(int)(r * 255), (int)(g * 255), (int)(b * 255));
return buf;
}
bool isPureBlack() const { return r == 0 && g == 0 && b == 0; }
bool isPureWhite() const { return r == 1 && g == 1 && b == 1; }
};
struct WhetstoneTheme {
std::string name;
// Backgrounds
Color4 bg, bgAlt, bgPanel, bgPopup;
// Text
Color4 text, textDim, textAccent, textError, textWarning, textSuccess;
// Borders
Color4 border, borderFocused;
// Accent (primary brand color)
Color4 accent, accentHover, accentActive;
// Selection
Color4 selectionBg, selectionText;
// Scrollbar
Color4 scrollbar, scrollbarHover;
// Diagnostics
Color4 diagError, diagWarning, diagInfo, diagHint;
// Syntax highlighting
Color4 syntaxKeyword, syntaxString, syntaxNumber, syntaxComment;
Color4 syntaxFunction, syntaxType, syntaxOperator, syntaxAnnotation;
// Font sizes
float fontSizePrimary = 14.0f; // monospace code font
float fontSizeUI = 13.0f; // sans-serif UI font
float fontSizeSmall = 11.0f; // labels, badges
nlohmann::json toJson() const {
nlohmann::json j;
j["name"] = name;
j["bg"] = bg.toHex(); j["bgAlt"] = bgAlt.toHex();
j["bgPanel"] = bgPanel.toHex(); j["bgPopup"] = bgPopup.toHex();
j["text"] = text.toHex(); j["textDim"] = textDim.toHex();
j["accent"] = accent.toHex();
j["diagError"] = diagError.toHex(); j["diagWarning"] = diagWarning.toHex();
j["syntaxKeyword"] = syntaxKeyword.toHex(); j["syntaxString"] = syntaxString.toHex();
j["syntaxNumber"] = syntaxNumber.toHex(); j["syntaxComment"] = syntaxComment.toHex();
j["fontSizePrimary"] = fontSizePrimary;
j["fontSizeUI"] = fontSizeUI;
return j;
}
};
inline WhetstoneTheme getDefaultDarkTheme() {
WhetstoneTheme t;
t.name = "Whetstone Dark";
// Background: deep blue-grey
t.bg = Color4::fromHex("#1a1d23");
t.bgAlt = Color4::fromHex("#22252b");
t.bgPanel = Color4::fromHex("#2a2d35");
t.bgPopup = Color4::fromHex("#30333b");
// Text
t.text = Color4::fromHex("#e0e0e0");
t.textDim = Color4::fromHex("#808890");
t.textAccent = Color4::fromHex("#7aa2f7");
t.textError = Color4::fromHex("#f7768e");
t.textWarning = Color4::fromHex("#e0af68");
t.textSuccess = Color4::fromHex("#9ece6a");
// Borders
t.border = Color4::fromHex("#3a3d45");
t.borderFocused = Color4::fromHex("#7aa2f7");
// Accent
t.accent = Color4::fromHex("#7aa2f7");
t.accentHover = Color4::fromHex("#89b4fa");
t.accentActive = Color4::fromHex("#5d8fea");
// Selection
t.selectionBg = Color4::fromHex("#3d4f6f");
t.selectionText = Color4::fromHex("#e0e0e0");
// Scrollbar
t.scrollbar = Color4::fromHex("#3a3d45");
t.scrollbarHover = Color4::fromHex("#4a4d55");
// Diagnostics
t.diagError = Color4::fromHex("#f7768e");
t.diagWarning = Color4::fromHex("#e0af68");
t.diagInfo = Color4::fromHex("#7dcfff");
t.diagHint = Color4::fromHex("#bb9af7");
// Syntax
t.syntaxKeyword = Color4::fromHex("#7aa2f7");
t.syntaxString = Color4::fromHex("#9ece6a");
t.syntaxNumber = Color4::fromHex("#ff9e64");
t.syntaxComment = Color4::fromHex("#565f89");
t.syntaxFunction = Color4::fromHex("#7aa2f7");
t.syntaxType = Color4::fromHex("#2ac3de");
t.syntaxOperator = Color4::fromHex("#89ddff");
t.syntaxAnnotation = Color4::fromHex("#bb9af7");
return t;
}
inline WhetstoneTheme getDefaultLightTheme() {
WhetstoneTheme t;
t.name = "Whetstone Light";
t.bg = Color4::fromHex("#f0f0f0");
t.bgAlt = Color4::fromHex("#e8e8e8");
t.bgPanel = Color4::fromHex("#ffffff");
t.bgPopup = Color4::fromHex("#ffffff");
t.text = Color4::fromHex("#1a1a1a");
t.textDim = Color4::fromHex("#6a6a6a");
t.textAccent = Color4::fromHex("#2563eb");
t.textError = Color4::fromHex("#dc2626");
t.textWarning = Color4::fromHex("#d97706");
t.textSuccess = Color4::fromHex("#16a34a");
t.border = Color4::fromHex("#d0d0d0");
t.borderFocused = Color4::fromHex("#2563eb");
t.accent = Color4::fromHex("#2563eb");
t.accentHover = Color4::fromHex("#3b82f6");
t.accentActive = Color4::fromHex("#1d4ed8");
t.selectionBg = Color4::fromHex("#bfdbfe");
t.selectionText = Color4::fromHex("#1a1a1a");
t.scrollbar = Color4::fromHex("#c0c0c0");
t.scrollbarHover = Color4::fromHex("#a0a0a0");
t.diagError = Color4::fromHex("#dc2626");
t.diagWarning = Color4::fromHex("#d97706");
t.diagInfo = Color4::fromHex("#2563eb");
t.diagHint = Color4::fromHex("#7c3aed");
t.syntaxKeyword = Color4::fromHex("#2563eb");
t.syntaxString = Color4::fromHex("#16a34a");
t.syntaxNumber = Color4::fromHex("#d97706");
t.syntaxComment = Color4::fromHex("#6b7280");
t.syntaxFunction = Color4::fromHex("#7c3aed");
t.syntaxType = Color4::fromHex("#0891b2");
t.syntaxOperator = Color4::fromHex("#374151");
t.syntaxAnnotation = Color4::fromHex("#7c3aed");
return t;
}