Step 160: add theme engine and JSON themes
This commit is contained in:
@@ -8,6 +8,7 @@
|
||||
|
||||
#include "imgui.h"
|
||||
#include "SyntaxHighlighter.h"
|
||||
#include "ThemeEngine.h"
|
||||
#include "EditorMode.h"
|
||||
#include <string>
|
||||
#include <vector>
|
||||
@@ -306,7 +307,10 @@ public:
|
||||
// Gutter background
|
||||
ImVec2 gutterA(origin.x, y);
|
||||
ImVec2 gutterB(origin.x + gutterWidth, y + lineHeight);
|
||||
drawList->AddRectFilled(gutterA, gutterB, IM_COL32(20, 20, 20, 255));
|
||||
drawList->AddRectFilled(
|
||||
gutterA, gutterB,
|
||||
ThemeEngine::instance().editorColor("gutter_bg",
|
||||
IM_COL32(20, 20, 20, 255)));
|
||||
|
||||
// Line number (right-aligned)
|
||||
if (options.showLineNumbers) {
|
||||
@@ -314,14 +318,19 @@ public:
|
||||
snprintf(numBuf, sizeof(numBuf), "%d", ln + 1);
|
||||
ImVec2 numSize = font->CalcTextSizeA(font->FontSize, FLT_MAX, -1.0f, numBuf);
|
||||
ImVec2 numPos(origin.x + gutterWidth - 4.0f - numSize.x, y);
|
||||
drawList->AddText(font, font->FontSize, numPos, IM_COL32(120, 120, 120, 255), numBuf);
|
||||
drawList->AddText(font, font->FontSize, numPos,
|
||||
ThemeEngine::instance().editorColor("gutter_text",
|
||||
IM_COL32(120, 120, 120, 255)),
|
||||
numBuf);
|
||||
}
|
||||
|
||||
// Diagnostics marker
|
||||
bool hasError = lineIn(options.errorLines, ln);
|
||||
bool hasWarn = lineIn(options.warningLines, ln);
|
||||
if (hasError || hasWarn) {
|
||||
ImU32 color = hasError ? IM_COL32(220, 80, 80, 255) : IM_COL32(220, 160, 60, 255);
|
||||
ImU32 color = hasError
|
||||
? ThemeEngine::instance().editorColor("diag_error", IM_COL32(220, 80, 80, 255))
|
||||
: ThemeEngine::instance().editorColor("diag_warning", IM_COL32(220, 160, 60, 255));
|
||||
ImVec2 center(origin.x + 3.0f, y + lineHeight * 0.5f);
|
||||
drawList->AddCircleFilled(center, 3.0f, color);
|
||||
}
|
||||
@@ -361,7 +370,8 @@ public:
|
||||
SuggestionMarker suggestion;
|
||||
if (suggestionAtLine(*options.suggestions, ln, suggestion)) {
|
||||
ImVec2 center(origin.x + 20.0f, y + lineHeight * 0.5f);
|
||||
ImU32 color = IM_COL32(240, 200, 40, 255);
|
||||
ImU32 color = ThemeEngine::instance().editorColor("suggestion",
|
||||
IM_COL32(240, 200, 40, 255));
|
||||
drawList->AddCircleFilled(center, 3.0f, color);
|
||||
ImVec2 a(center.x - 4.0f, center.y - 4.0f);
|
||||
ImVec2 b(center.x + 4.0f, center.y + 4.0f);
|
||||
@@ -384,14 +394,19 @@ public:
|
||||
AnnotationConflictMarker conflict;
|
||||
if (conflictAtLine(*options.conflicts, ln, conflict)) {
|
||||
ImVec2 center(origin.x + 12.0f, y + lineHeight * 0.5f);
|
||||
drawList->AddCircle(center, 5.0f, IM_COL32(220, 80, 80, 255), 12, 1.5f);
|
||||
drawList->AddCircle(center, 5.0f,
|
||||
ThemeEngine::instance().editorColor("conflict",
|
||||
IM_COL32(220, 80, 80, 255)),
|
||||
12, 1.5f);
|
||||
if (ln == std::min(conflict.childLine, conflict.parentLine) &&
|
||||
conflict.childLine >= 0 && conflict.parentLine >= 0) {
|
||||
float y1 = textBase.y + conflict.childLine * lineHeight + lineHeight * 0.5f;
|
||||
float y2 = textBase.y + conflict.parentLine * lineHeight + lineHeight * 0.5f;
|
||||
drawList->AddLine(ImVec2(origin.x + 12.0f, y1),
|
||||
ImVec2(origin.x + 12.0f, y2),
|
||||
IM_COL32(220, 80, 80, 180), 1.0f);
|
||||
ThemeEngine::instance().editorColor("conflict_line",
|
||||
IM_COL32(220, 80, 80, 180)),
|
||||
1.0f);
|
||||
}
|
||||
ImVec2 a(center.x - 5.0f, center.y - 5.0f);
|
||||
ImVec2 b(center.x + 5.0f, center.y + 5.0f);
|
||||
@@ -408,9 +423,15 @@ public:
|
||||
if (fold) {
|
||||
ImVec2 triCenter(origin.x + 6.0f, y + lineHeight * 0.5f);
|
||||
if (fold->folded) {
|
||||
drawTriangle(drawList, triCenter, IM_COL32(160, 160, 160, 255), true);
|
||||
drawTriangle(drawList, triCenter,
|
||||
ThemeEngine::instance().editorColor("fold_indicator",
|
||||
IM_COL32(160, 160, 160, 255)),
|
||||
true);
|
||||
} else {
|
||||
drawTriangle(drawList, triCenter, IM_COL32(160, 160, 160, 255), false);
|
||||
drawTriangle(drawList, triCenter,
|
||||
ThemeEngine::instance().editorColor("fold_indicator",
|
||||
IM_COL32(160, 160, 160, 255)),
|
||||
false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -418,7 +439,9 @@ public:
|
||||
if (options.showCurrentLine && ln == currentLine) {
|
||||
ImVec2 hlA(textBase.x, y);
|
||||
ImVec2 hlB(textBase.x + textAreaWidth, y + lineHeight);
|
||||
drawList->AddRectFilled(hlA, hlB, IM_COL32(40, 40, 40, 120));
|
||||
drawList->AddRectFilled(hlA, hlB,
|
||||
ThemeEngine::instance().editorColor("line_highlight",
|
||||
IM_COL32(40, 40, 40, 120)));
|
||||
}
|
||||
if (options.highlightLine >= 0 && ln == options.highlightLine) {
|
||||
ImVec2 hlA(textBase.x, y);
|
||||
@@ -445,7 +468,9 @@ public:
|
||||
int colB = lineSelEnd - start;
|
||||
ImVec2 a(textBase.x + colA * charAdvance, y);
|
||||
ImVec2 b(textBase.x + colB * charAdvance, y + lineHeight);
|
||||
drawList->AddRectFilled(a, b, IM_COL32(60, 100, 160, 120));
|
||||
drawList->AddRectFilled(a, b,
|
||||
ThemeEngine::instance().editorColor("selection",
|
||||
IM_COL32(60, 100, 160, 120)));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -494,7 +519,10 @@ public:
|
||||
ImVec2 textSize = font->CalcTextSizeA(font->FontSize, FLT_MAX, -1.0f, marker.message.c_str());
|
||||
ImVec2 bgA(tagPos.x - 2.0f, tagPos.y);
|
||||
ImVec2 bgB(tagPos.x + textSize.x + 6.0f, tagPos.y + lineHeight * 0.8f);
|
||||
drawList->AddRectFilled(bgA, bgB, IM_COL32(30, 30, 30, 220), 2.0f);
|
||||
drawList->AddRectFilled(bgA, bgB,
|
||||
ThemeEngine::instance().editorColor("annotation_tag_bg",
|
||||
IM_COL32(30, 30, 30, 220)),
|
||||
2.0f);
|
||||
drawList->AddText(font, font->FontSize, ImVec2(tagPos.x + 2.0f, tagPos.y),
|
||||
marker.color, marker.message.c_str());
|
||||
}
|
||||
@@ -503,7 +531,10 @@ public:
|
||||
// Folded placeholder
|
||||
if (fold && fold->folded) {
|
||||
ImVec2 p(textBase.x + len * charAdvance + 6.0f, y);
|
||||
drawList->AddText(font, font->FontSize, p, IM_COL32(140, 140, 140, 255), "{...}");
|
||||
drawList->AddText(font, font->FontSize, p,
|
||||
ThemeEngine::instance().editorColor("fold_placeholder",
|
||||
IM_COL32(140, 140, 140, 255)),
|
||||
"{...}");
|
||||
}
|
||||
|
||||
if (options.diagnostics) {
|
||||
@@ -539,7 +570,10 @@ public:
|
||||
int col = cursor_ - lineStart;
|
||||
float x = textBase.x + col * charAdvance;
|
||||
float y = textBase.y + curLine * lineHeight;
|
||||
drawList->AddLine(ImVec2(x, y), ImVec2(x, y + lineHeight), IM_COL32(240, 240, 240, 255), 1.0f);
|
||||
drawList->AddLine(ImVec2(x, y), ImVec2(x, y + lineHeight),
|
||||
ThemeEngine::instance().editorColor("caret",
|
||||
IM_COL32(240, 240, 240, 255)),
|
||||
1.0f);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -548,11 +582,17 @@ public:
|
||||
float miniHeight = lineCount * lineHeight;
|
||||
ImVec2 miniA(minimapBaseX, minimapBaseY);
|
||||
ImVec2 miniB(minimapBaseX + minimapWidth, minimapBaseY + miniHeight);
|
||||
drawList->AddRectFilled(miniA, miniB, IM_COL32(18, 18, 18, 255));
|
||||
drawList->AddRectFilled(miniA, miniB,
|
||||
ThemeEngine::instance().editorColor("minimap_bg",
|
||||
IM_COL32(18, 18, 18, 255)));
|
||||
for (int ln = 0; ln < lineCount; ++ln) {
|
||||
float y = minimapBaseY + ln * lineHeight;
|
||||
ImU32 color = IM_COL32(80, 80, 80, 255);
|
||||
if (ln == currentLine) color = IM_COL32(120, 120, 160, 255);
|
||||
ImU32 color = ThemeEngine::instance().editorColor("minimap_line",
|
||||
IM_COL32(80, 80, 80, 255));
|
||||
if (ln == currentLine) {
|
||||
color = ThemeEngine::instance().editorColor("minimap_line_active",
|
||||
IM_COL32(120, 120, 160, 255));
|
||||
}
|
||||
drawList->AddRectFilled(ImVec2(minimapBaseX + 2.0f, y),
|
||||
ImVec2(minimapBaseX + minimapWidth - 2.0f, y + 2.0f),
|
||||
color);
|
||||
@@ -563,7 +603,9 @@ public:
|
||||
viewEnd = std::max(0.0f, std::min(1.0f, viewEnd));
|
||||
ImVec2 vA(minimapBaseX, minimapBaseY + viewStart * miniHeight);
|
||||
ImVec2 vB(minimapBaseX + minimapWidth, minimapBaseY + viewEnd * miniHeight);
|
||||
drawList->AddRect(vA, vB, IM_COL32(120, 160, 220, 180));
|
||||
drawList->AddRect(vA, vB,
|
||||
ThemeEngine::instance().editorColor("minimap_view",
|
||||
IM_COL32(120, 160, 220, 180)));
|
||||
}
|
||||
|
||||
ImGui::EndChild();
|
||||
@@ -628,20 +670,22 @@ private:
|
||||
}
|
||||
|
||||
static ImU32 colorFor(TokenCategory cat) {
|
||||
ImU32 fallback = IM_COL32(220, 220, 220, 255);
|
||||
switch (cat) {
|
||||
case TokenCategory::Keyword: return IM_COL32(196, 126, 220, 255);
|
||||
case TokenCategory::String: return IM_COL32(207, 138, 94, 255);
|
||||
case TokenCategory::Comment: return IM_COL32(107, 133, 89, 255);
|
||||
case TokenCategory::Number: return IM_COL32(181, 204, 140, 255);
|
||||
case TokenCategory::Function: return IM_COL32(220, 220, 140, 255);
|
||||
case TokenCategory::Parameter: return IM_COL32(153, 199, 229, 255);
|
||||
case TokenCategory::Type: return IM_COL32(77, 179, 174, 255);
|
||||
case TokenCategory::Operator: return IM_COL32(220, 220, 220, 255);
|
||||
case TokenCategory::Punctuation: return IM_COL32(160, 160, 160, 255);
|
||||
case TokenCategory::Builtin: return IM_COL32(77, 179, 229, 255);
|
||||
case TokenCategory::Identifier: return IM_COL32(160, 200, 230, 255);
|
||||
default: return IM_COL32(220, 220, 220, 255);
|
||||
case TokenCategory::Keyword: fallback = IM_COL32(196, 126, 220, 255); break;
|
||||
case TokenCategory::String: fallback = IM_COL32(207, 138, 94, 255); break;
|
||||
case TokenCategory::Comment: fallback = IM_COL32(107, 133, 89, 255); break;
|
||||
case TokenCategory::Number: fallback = IM_COL32(181, 204, 140, 255); break;
|
||||
case TokenCategory::Function: fallback = IM_COL32(220, 220, 140, 255); break;
|
||||
case TokenCategory::Parameter: fallback = IM_COL32(153, 199, 229, 255); break;
|
||||
case TokenCategory::Type: fallback = IM_COL32(77, 179, 174, 255); break;
|
||||
case TokenCategory::Operator: fallback = IM_COL32(220, 220, 220, 255); break;
|
||||
case TokenCategory::Punctuation: fallback = IM_COL32(160, 160, 160, 255); break;
|
||||
case TokenCategory::Builtin: fallback = IM_COL32(77, 179, 229, 255); break;
|
||||
case TokenCategory::Identifier: fallback = IM_COL32(160, 200, 230, 255); break;
|
||||
default: break;
|
||||
}
|
||||
return ThemeEngine::instance().syntaxColor(cat, fallback);
|
||||
}
|
||||
|
||||
static int lineFromPos(int pos, const std::vector<int>& lineStarts) {
|
||||
@@ -778,7 +822,9 @@ private:
|
||||
endCol = std::max(startCol, endCol);
|
||||
float xStart = textBaseX + startCol * charAdvance;
|
||||
float xEnd = textBaseX + endCol * charAdvance;
|
||||
ImU32 color = (d.severity == 1) ? IM_COL32(220, 80, 80, 255) : IM_COL32(220, 160, 60, 255);
|
||||
ImU32 color = (d.severity == 1)
|
||||
? ThemeEngine::instance().editorColor("diag_error", IM_COL32(220, 80, 80, 255))
|
||||
: ThemeEngine::instance().editorColor("diag_warning", IM_COL32(220, 160, 60, 255));
|
||||
float x = xStart;
|
||||
float amp = 2.0f;
|
||||
bool up = true;
|
||||
|
||||
@@ -150,7 +150,7 @@ private:
|
||||
std::string emacsConfigPath_;
|
||||
int fontSize_ = 15;
|
||||
int tabSize_ = 4;
|
||||
std::string theme_ = "Dark";
|
||||
std::string theme_ = "VSCode Dark";
|
||||
int autoSaveSeconds_ = 0;
|
||||
bool showMinimap_ = false;
|
||||
bool showLineNumbers_ = true;
|
||||
|
||||
233
editor/src/ThemeEngine.h
Normal file
233
editor/src/ThemeEngine.h
Normal file
@@ -0,0 +1,233 @@
|
||||
#pragma once
|
||||
// Step 160: Theme engine
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <unordered_map>
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
#include <sstream>
|
||||
#include <algorithm>
|
||||
#include <nlohmann/json.hpp>
|
||||
#include "imgui.h"
|
||||
#include "SyntaxHighlighter.h"
|
||||
|
||||
using json = nlohmann::json;
|
||||
|
||||
struct ThemeDefinition {
|
||||
std::string name;
|
||||
std::unordered_map<std::string, ImU32> syntaxColors;
|
||||
std::unordered_map<std::string, ImU32> editorColors;
|
||||
std::unordered_map<int, ImVec4> imguiColors;
|
||||
};
|
||||
|
||||
class ThemeEngine {
|
||||
public:
|
||||
static ThemeEngine& instance() {
|
||||
static ThemeEngine inst;
|
||||
return inst;
|
||||
}
|
||||
|
||||
void clear() {
|
||||
themes_.clear();
|
||||
current_ = -1;
|
||||
}
|
||||
|
||||
bool loadThemesFromDirectory(const std::string& dir) {
|
||||
namespace fs = std::filesystem;
|
||||
std::error_code ec;
|
||||
if (!fs::exists(dir, ec)) return false;
|
||||
bool loaded = false;
|
||||
for (const auto& entry : fs::directory_iterator(dir)) {
|
||||
if (!entry.is_regular_file()) continue;
|
||||
auto path = entry.path();
|
||||
if (path.extension() != ".json") continue;
|
||||
if (loadThemeFile(path.string())) loaded = true;
|
||||
}
|
||||
return loaded;
|
||||
}
|
||||
|
||||
bool loadThemeFile(const std::string& path) {
|
||||
std::ifstream in(path);
|
||||
if (!in.is_open()) return false;
|
||||
std::ostringstream ss;
|
||||
ss << in.rdbuf();
|
||||
return loadThemeFromJson(ss.str());
|
||||
}
|
||||
|
||||
bool loadThemeFromJson(const std::string& text) {
|
||||
try {
|
||||
json j = json::parse(text);
|
||||
ThemeDefinition theme;
|
||||
if (!parseTheme(j, theme)) return false;
|
||||
registerTheme(theme);
|
||||
return true;
|
||||
} catch (...) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
void registerTheme(const ThemeDefinition& theme) {
|
||||
for (auto& t : themes_) {
|
||||
if (t.name == theme.name) {
|
||||
t = theme;
|
||||
return;
|
||||
}
|
||||
}
|
||||
themes_.push_back(theme);
|
||||
}
|
||||
|
||||
std::vector<std::string> listThemes() const {
|
||||
std::vector<std::string> names;
|
||||
for (const auto& t : themes_) names.push_back(t.name);
|
||||
std::sort(names.begin(), names.end());
|
||||
return names;
|
||||
}
|
||||
|
||||
bool applyTheme(const std::string& name) {
|
||||
for (size_t i = 0; i < themes_.size(); ++i) {
|
||||
if (themes_[i].name == name) {
|
||||
current_ = (int)i;
|
||||
applyToImGui(themes_[i]);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string currentThemeName() const {
|
||||
if (current_ < 0 || current_ >= (int)themes_.size()) return "";
|
||||
return themes_[current_].name;
|
||||
}
|
||||
|
||||
ImU32 syntaxColor(TokenCategory cat, ImU32 fallback) const {
|
||||
const ThemeDefinition* theme = currentTheme();
|
||||
if (!theme) return fallback;
|
||||
std::string key = SyntaxHighlighter::categoryName(cat);
|
||||
auto it = theme->syntaxColors.find(key);
|
||||
return it != theme->syntaxColors.end() ? it->second : fallback;
|
||||
}
|
||||
|
||||
ImU32 editorColor(const std::string& key, ImU32 fallback) const {
|
||||
const ThemeDefinition* theme = currentTheme();
|
||||
if (!theme) return fallback;
|
||||
auto it = theme->editorColors.find(key);
|
||||
return it != theme->editorColors.end() ? it->second : fallback;
|
||||
}
|
||||
|
||||
private:
|
||||
ThemeEngine() = default;
|
||||
|
||||
const ThemeDefinition* currentTheme() const {
|
||||
if (current_ < 0 || current_ >= (int)themes_.size()) return nullptr;
|
||||
return &themes_[current_];
|
||||
}
|
||||
|
||||
static bool parseHexColor(const std::string& text,
|
||||
ImU32& outU32,
|
||||
ImVec4& outVec) {
|
||||
std::string hex = text;
|
||||
if (!hex.empty() && hex[0] == '#') hex = hex.substr(1);
|
||||
if (hex.size() != 6 && hex.size() != 8) return false;
|
||||
auto toByte = [](const std::string& s) {
|
||||
return (int)std::strtol(s.c_str(), nullptr, 16);
|
||||
};
|
||||
int r = toByte(hex.substr(0, 2));
|
||||
int g = toByte(hex.substr(2, 2));
|
||||
int b = toByte(hex.substr(4, 2));
|
||||
int a = 255;
|
||||
if (hex.size() == 8) a = toByte(hex.substr(6, 2));
|
||||
outU32 = IM_COL32(r, g, b, a);
|
||||
outVec = ImVec4(r / 255.0f, g / 255.0f, b / 255.0f, a / 255.0f);
|
||||
return true;
|
||||
}
|
||||
|
||||
static int imguiColorId(const std::string& name) {
|
||||
if (name == "Text") return ImGuiCol_Text;
|
||||
if (name == "WindowBg") return ImGuiCol_WindowBg;
|
||||
if (name == "ChildBg") return ImGuiCol_ChildBg;
|
||||
if (name == "PopupBg") return ImGuiCol_PopupBg;
|
||||
if (name == "Border") return ImGuiCol_Border;
|
||||
if (name == "FrameBg") return ImGuiCol_FrameBg;
|
||||
if (name == "FrameBgHovered") return ImGuiCol_FrameBgHovered;
|
||||
if (name == "FrameBgActive") return ImGuiCol_FrameBgActive;
|
||||
if (name == "TitleBg") return ImGuiCol_TitleBg;
|
||||
if (name == "TitleBgActive") return ImGuiCol_TitleBgActive;
|
||||
if (name == "Header") return ImGuiCol_Header;
|
||||
if (name == "HeaderHovered") return ImGuiCol_HeaderHovered;
|
||||
if (name == "HeaderActive") return ImGuiCol_HeaderActive;
|
||||
if (name == "Tab") return ImGuiCol_Tab;
|
||||
if (name == "TabHovered") return ImGuiCol_TabHovered;
|
||||
if (name == "TabActive") return ImGuiCol_TabActive;
|
||||
if (name == "TabUnfocused") return ImGuiCol_TabUnfocused;
|
||||
if (name == "TabUnfocusedActive") return ImGuiCol_TabUnfocusedActive;
|
||||
if (name == "Button") return ImGuiCol_Button;
|
||||
if (name == "ButtonHovered") return ImGuiCol_ButtonHovered;
|
||||
if (name == "ButtonActive") return ImGuiCol_ButtonActive;
|
||||
if (name == "ScrollbarBg") return ImGuiCol_ScrollbarBg;
|
||||
if (name == "ScrollbarGrab") return ImGuiCol_ScrollbarGrab;
|
||||
if (name == "ScrollbarGrabHovered") return ImGuiCol_ScrollbarGrabHovered;
|
||||
if (name == "ScrollbarGrabActive") return ImGuiCol_ScrollbarGrabActive;
|
||||
if (name == "CheckMark") return ImGuiCol_CheckMark;
|
||||
if (name == "SliderGrab") return ImGuiCol_SliderGrab;
|
||||
if (name == "SliderGrabActive") return ImGuiCol_SliderGrabActive;
|
||||
if (name == "Separator") return ImGuiCol_Separator;
|
||||
if (name == "SeparatorHovered") return ImGuiCol_SeparatorHovered;
|
||||
if (name == "SeparatorActive") return ImGuiCol_SeparatorActive;
|
||||
return -1;
|
||||
}
|
||||
|
||||
static bool parseTheme(const json& j, ThemeDefinition& out) {
|
||||
if (!j.is_object()) return false;
|
||||
out.name = j.value("name", "");
|
||||
if (out.name.empty()) return false;
|
||||
|
||||
if (j.contains("syntax") && j["syntax"].is_object()) {
|
||||
for (const auto& [key, value] : j["syntax"].items()) {
|
||||
if (!value.is_string()) continue;
|
||||
ImU32 u32;
|
||||
ImVec4 v4;
|
||||
if (parseHexColor(value.get<std::string>(), u32, v4)) {
|
||||
out.syntaxColors[key] = u32;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (j.contains("editor") && j["editor"].is_object()) {
|
||||
for (const auto& [key, value] : j["editor"].items()) {
|
||||
if (!value.is_string()) continue;
|
||||
ImU32 u32;
|
||||
ImVec4 v4;
|
||||
if (parseHexColor(value.get<std::string>(), u32, v4)) {
|
||||
out.editorColors[key] = u32;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (j.contains("imgui") && j["imgui"].is_object()) {
|
||||
for (const auto& [key, value] : j["imgui"].items()) {
|
||||
if (!value.is_string()) continue;
|
||||
ImU32 u32;
|
||||
ImVec4 v4;
|
||||
if (!parseHexColor(value.get<std::string>(), u32, v4)) continue;
|
||||
int colId = imguiColorId(key);
|
||||
if (colId >= 0) {
|
||||
out.imguiColors[colId] = v4;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static void applyToImGui(const ThemeDefinition& theme) {
|
||||
ImGuiStyle& style = ImGui::GetStyle();
|
||||
for (const auto& [colId, color] : theme.imguiColors) {
|
||||
if (colId >= 0 && colId < ImGuiCol_COUNT) {
|
||||
style.Colors[colId] = color;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<ThemeDefinition> themes_;
|
||||
int current_ = -1;
|
||||
};
|
||||
@@ -16,6 +16,7 @@
|
||||
#include "EditorState.h"
|
||||
#include "EditorUtils.h"
|
||||
#include "CompletionUtils.h"
|
||||
#include "ThemeEngine.h"
|
||||
|
||||
static std::string emacsChordForEvent(SDL_Keycode sym, SDL_Keymod mods) {
|
||||
std::string base;
|
||||
@@ -105,17 +106,19 @@ int main(int, char**) {
|
||||
uiFont = io.Fonts->AddFontDefault();
|
||||
}
|
||||
|
||||
SetupVSCodeDarkTheme();
|
||||
|
||||
ImGui_ImplSDL2_InitForOpenGL(window, gl_context);
|
||||
ImGui_ImplOpenGL3_Init(glsl_version);
|
||||
|
||||
// Editor state
|
||||
EditorState state;
|
||||
state.init();
|
||||
if (state.settings.getTheme() == "Light") {
|
||||
SetupVSCodeLightTheme();
|
||||
} else {
|
||||
ThemeEngine& themes = ThemeEngine::instance();
|
||||
themes.loadThemesFromDirectory("themes");
|
||||
themes.loadThemesFromDirectory("editor/themes");
|
||||
std::string themeName = state.settings.getTheme();
|
||||
if (themeName == "Dark") themeName = "VSCode Dark";
|
||||
if (themeName == "Light") themeName = "VSCode Light";
|
||||
if (!themes.applyTheme(themeName)) {
|
||||
SetupVSCodeDarkTheme();
|
||||
}
|
||||
io.FontGlobalScale = state.settings.getFontSize() / baseFontSize;
|
||||
@@ -835,12 +838,31 @@ int main(int, char**) {
|
||||
settingsChanged = true;
|
||||
}
|
||||
|
||||
const char* themeLabels[] = {"Dark", "Light"};
|
||||
int themeIndex = state.settings.getTheme() == "Light" ? 1 : 0;
|
||||
if (ImGui::Combo("Theme", &themeIndex, themeLabels, 2)) {
|
||||
state.settings.setTheme(themeLabels[themeIndex]);
|
||||
if (themeIndex == 1) SetupVSCodeLightTheme();
|
||||
else SetupVSCodeDarkTheme();
|
||||
std::vector<std::string> themeNames = ThemeEngine::instance().listThemes();
|
||||
if (themeNames.empty()) {
|
||||
themeNames = {"VSCode Dark", "VSCode Light"};
|
||||
}
|
||||
std::string currentTheme = state.settings.getTheme();
|
||||
if (currentTheme == "Dark") currentTheme = "VSCode Dark";
|
||||
if (currentTheme == "Light") currentTheme = "VSCode Light";
|
||||
int themeIndex = 0;
|
||||
for (size_t i = 0; i < themeNames.size(); ++i) {
|
||||
if (themeNames[i] == currentTheme) {
|
||||
themeIndex = (int)i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
std::vector<const char*> themeLabels;
|
||||
themeLabels.reserve(themeNames.size());
|
||||
for (const auto& name : themeNames) themeLabels.push_back(name.c_str());
|
||||
if (ImGui::Combo("Theme", &themeIndex, themeLabels.data(), (int)themeLabels.size())) {
|
||||
state.settings.setTheme(themeNames[themeIndex]);
|
||||
if (!ThemeEngine::instance().applyTheme(themeNames[themeIndex])) {
|
||||
if (themeNames[themeIndex].find("Light") != std::string::npos)
|
||||
SetupVSCodeLightTheme();
|
||||
else
|
||||
SetupVSCodeDarkTheme();
|
||||
}
|
||||
settingsChanged = true;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user