From 4f9f62ee8e21896523d24637ad934766bc7001b2 Mon Sep 17 00:00:00 2001 From: Zachary Hall Date: Sun, 9 Jul 2023 18:56:12 -0700 Subject: [PATCH] Add basic custom themes --- .gitignore | 2 + main.cpp | 99 +++--------- meson.build | 1 + theme.cpp | 421 ++++++++++++++++++++++++++++++++++++++++++++++++++++ theme.h | 30 ++++ 5 files changed, 476 insertions(+), 77 deletions(-) create mode 100644 theme.cpp create mode 100644 theme.h diff --git a/.gitignore b/.gitignore index a251e74..f5b810d 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,5 @@ assets/*.h build* .vscode subprojects/*/ +.cache +compile_commands.json \ No newline at end of file diff --git a/main.cpp b/main.cpp index 853f426..15c51e5 100644 --- a/main.cpp +++ b/main.cpp @@ -3,6 +3,7 @@ #include "imgui_impl_opengl3.h" #include "imfilebrowser.h" #include "playback.h" +#include "theme.h" #include "icon.h" #include "IconsForkAwesome.h" #include @@ -29,62 +30,7 @@ using namespace std::filesystem; using namespace std::numbers; using std::string; static float accent_color = 280.0; -float GetHue(ImVec4 rgba){ - float r = rgba.x, g = rgba.y, b = rgba.z; - if (r == g && g == b) { - return -1.0; - } - float hue; - if ((r >= g) && (g >= b)) { - hue = 60.0 * (g-b)/(r-b); - } else if ((g > r) && (r >= b)) { - hue = 60.0 * (2.0 - ((r-b)/(g-b))); - } else if ((g >= b) && (b > r)) { - hue = 60.0 * (2.0 + ((b-r)/(g-r))); - } else if ((b > g) && (g > r)) { - hue = 60.0 * (4.0 - ((g-r)/(b-r))); - } else if ((b > r) && (r >= g)) { - hue = 60.0 * (4.0 - ((r-g)/(b-g))); - } else if ((r >= b) && (b > g)) { - hue = 60.0 * (6.0 - ((b-g)/(r-g))); - } else { - hue = -1.0; - } - return hue; -} -void change_accent_color(ImVec4 &color, float hue) { - ImVec4 in = color; - float Target = hue; - float Current = GetHue(in); - if (Current < 0.0f) { - return; - } - float H = 360-Target+Current; - float U = cos(H*pi/180.0); - float W = sin(H*pi/180.0); - ImVec4 out = in; - out.x = (.299+.701*U+.168*W)*in.x - + (.587-.587*U+.330*W)*in.y - + (.114-.114*U-.497*W)*in.z; - out.y = (.299-.299*U-.328*W)*in.x - + (.587+.413*U+.035*W)*in.y - + (.114-.114*U+.292*W)*in.z; - out.z = (.299-.3*U+1.25*W)*in.x - + (.587-.588*U-1.05*W)*in.y - + (.114+.886*U-.203*W)*in.z; - color = out; -} -void UpdateStyle(bool dark) { - if (dark) { - ImGui::StyleColorsDark(); - } else { - ImGui::StyleColorsLight(); - } - for (auto& color : ImGui::GetStyle().Colors) { - change_accent_color(color, accent_color); - } -} string PadZeros(string input, size_t required_length) { return std::string(required_length - std::min(required_length, input.length()), '0') + input; } @@ -135,6 +81,7 @@ int main(int, char**) } IMG_Init(IMG_INIT_PNG|IMG_INIT_WEBP); const char* prefPath = SDL_GetPrefPath("Catmeow72", NAME); + Theme::prefPath = prefPath; // Decide GL+GLSL versions #if defined(IMGUI_IMPL_OPENGL_ES2) @@ -196,20 +143,8 @@ int main(int, char**) //io.ConfigViewportsNoAutoMerge = true; //io.ConfigViewportsNoTaskBarIcon = true; - // Setup Dear ImGui style - UpdateStyle(true); //ImGui::StyleColorsLight(); - // When viewports are enabled we tweak WindowRounding/WindowBg so platform windows can look identical to regular ones. - ImGuiStyle& style = ImGui::GetStyle(); - if (io.ConfigFlags & ImGuiConfigFlags_ViewportsEnable) - { - style.WindowRounding = 0.0f; - style.Colors[ImGuiCol_WindowBg].w = 1.0f; - } - style.FrameRounding = 12; - style.WindowRounding = 12; - // Setup Platform/Renderer backends ImGui_ImplSDL2_InitForOpenGL(window, gl_context); ImGui_ImplOpenGL3_Init(glsl_version); @@ -255,8 +190,9 @@ int main(int, char**) float position = 0.0; // Main loop bool done = false; - bool dark_mode = true; + Theme *theme = new Theme(false); bool prefs_window = false; + bool theme_editor = false; bool stopped = true; { Json::Value config; @@ -264,8 +200,12 @@ int main(int, char**) stream.open(path(prefPath) / "config.json"); if (stream.is_open()) { stream >> config; - if (config.isMember("dark_mode")) { - dark_mode = config["dark_mode"].asBool();\ + if (config.isMember("theme_name")) { + path themePath = theme->themeDir / config["theme_name"].asString(); + if (exists(themePath)) { + delete theme; + theme = new Theme(themePath); + } } if (config.isMember("accent_color")) { accent_color = config["accent_color"].asFloat(); @@ -274,7 +214,6 @@ int main(int, char**) show_demo_window = config["demo_window"].asBool(); } stream.close(); - UpdateStyle(dark_mode); } } #ifdef __EMSCRIPTEN__ @@ -381,16 +320,18 @@ int main(int, char**) ImGui::SetNextWindowSizeConstraints(min_size, max_size); ImGui::Begin("Preferences...", &prefs_window); { - if (ImGui::Checkbox(ICON_FK_MOON "Dark Mode", &dark_mode)) { - UpdateStyle(dark_mode); + if (ImGui::Button(ICON_FK_MAGIC "Theme Editor")) { + theme_editor = true; } ImGui::SetNextItemWidth(ImGui::GetWindowWidth() - (ImGui::GetStyle().FramePadding.x * 4)); - if (ImGui::SliderFloat("##AccentColor", &accent_color, 0.0, 360.0, "UI hue: %.0f°", ImGuiSliderFlags_NoRoundToFormat)) { - UpdateStyle(dark_mode); - } + ImGui::SliderFloat("##AccentColor", &accent_color, 0.0, 360.0, "UI hue: %.0f°", ImGuiSliderFlags_NoRoundToFormat); } ImGui::End(); } + if (theme_editor) { + theme->ShowEditor(&theme_editor, theme); + } + theme->Apply(accent_color); fileDialog.Display(); if (fileDialog.HasSelected()) { @@ -441,7 +382,11 @@ int main(int, char**) Json::Value config; std::ofstream stream; stream.open(path(prefPath) / "config.json"); - config["dark_mode"] = dark_mode; + path themePath(theme->file_path); + themePath = themePath.filename(); + if (!themePath.empty()) { + config["theme_name"] = themePath.filename().string(); + } config["accent_color"] = accent_color; config["demo_window"] = show_demo_window; stream << config; diff --git a/meson.build b/meson.build index 0eedbc4..0112a03 100644 --- a/meson.build +++ b/meson.build @@ -19,6 +19,7 @@ deps = [ srcs = [ 'main.cpp', 'playback.cpp', + 'theme.cpp', 'imgui/imgui.cpp', 'imgui/imgui_widgets.cpp', 'imgui/imgui_tables.cpp', diff --git a/theme.cpp b/theme.cpp new file mode 100644 index 0000000..0884422 --- /dev/null +++ b/theme.cpp @@ -0,0 +1,421 @@ +#include "theme.h" +#include "imgui.h" +#include "json/value.h" +#include +#include +#include +#include +#include +#include +#include + +using namespace std::filesystem; +using namespace std::numbers; +const char* Theme::prefPath = NULL; +bool Theme::ShowEditor(bool* open, Theme* &theme) { + ImGui::Begin("Theme Editor", open); + ImGuiStyle *ref = &style; + ImGui::PushItemWidth(ImGui::GetWindowWidth() * 0.50f); + if (ImGui::Button("Create light")) { + delete theme; + theme = new Theme(false); + + ImGui::PopItemWidth(); + ImGui::End(); + return true; + } + ImGui::SameLine(); + if (ImGui::Button("Create dark")) { + delete theme; + theme = new Theme(true); + + ImGui::PopItemWidth(); + ImGui::End(); + return true; + } + if (ImGui::Button("Import...")) { + importDialog.SetTitle("Import theme..."); + importDialog.SetTypeFilters({ ".json"}); + std::string userdir = std::getenv( + #ifdef _WIN32 + "UserProfile" + #else + "HOME" + #endif + ); + importDialog.SetPwd(userdir); + importDialog.Open(); + } + ImGui::SameLine(); + if (ImGui::Button("Export...")) { + exportDialog = ImGui::FileBrowser(ImGuiFileBrowserFlags_EnterNewFilename|ImGuiFileBrowserFlags_CreateNewDir); + exportDialog.SetTitle("Export theme..."); + exportDialog.SetTypeFilters({ ".json"}); + std::string userdir = std::getenv( + #ifdef _WIN32 + "UserProfile" + #else + "HOME" + #endif + ); + exportDialog.SetPwd(userdir); + exportDialog.Open(); + } + importDialog.Display(); + exportDialog.Display(); + if (!file_path.empty()) { + if (ImGui::Button("Revert")) { + string file_path_backup = file_path; + delete theme; + theme = new Theme(file_path_backup); + ImGui::PopItemWidth(); + ImGui::End(); + return true; + } + ImGui::SameLine(); + if (ImGui::Button("Save")) { + Save(file_path); + } + } + if (ImGui::Button("Load...")) { + loadOpen = true; + } + ImGui::SameLine(); + if (ImGui::Button("Save as...")) { + saveAsOpen = true; + } + + // Simplified Settings (expose floating-pointer border sizes as boolean representing 0.0f or 1.0f) + if (ImGui::SliderFloat("FrameRounding", &style.FrameRounding, 0.0f, 12.0f, "%.0f")) + style.GrabRounding = style.FrameRounding; // Make GrabRounding always the same value as FrameRounding + { bool border = (style.WindowBorderSize > 0.0f); if (ImGui::Checkbox("WindowBorder", &border)) { style.WindowBorderSize = border ? 1.0f : 0.0f; } } + ImGui::SameLine(); + { bool border = (style.FrameBorderSize > 0.0f); if (ImGui::Checkbox("FrameBorder", &border)) { style.FrameBorderSize = border ? 1.0f : 0.0f; } } + ImGui::SameLine(); + { bool border = (style.PopupBorderSize > 0.0f); if (ImGui::Checkbox("PopupBorder", &border)) { style.PopupBorderSize = border ? 1.0f : 0.0f; } } + + ImGui::Separator(); + + if (ImGui::BeginTabBar("##tabs", ImGuiTabBarFlags_None)) + { + if (ImGui::BeginTabItem("Sizes")) + { + + ImGui::SeparatorText("Borders"); + ImGui::SliderFloat("WindowBorderSize", &style.WindowBorderSize, 0.0f, 1.0f, "%.0f"); + ImGui::SliderFloat("ChildBorderSize", &style.ChildBorderSize, 0.0f, 1.0f, "%.0f"); + ImGui::SliderFloat("PopupBorderSize", &style.PopupBorderSize, 0.0f, 1.0f, "%.0f"); + ImGui::SliderFloat("FrameBorderSize", &style.FrameBorderSize, 0.0f, 1.0f, "%.0f"); + ImGui::SliderFloat("TabBorderSize", &style.TabBorderSize, 0.0f, 1.0f, "%.0f"); + + ImGui::SeparatorText("Rounding"); + ImGui::SliderFloat("WindowRounding", &style.WindowRounding, 0.0f, 12.0f, "%.0f"); + ImGui::SliderFloat("ChildRounding", &style.ChildRounding, 0.0f, 12.0f, "%.0f"); + ImGui::SliderFloat("FrameRounding", &style.FrameRounding, 0.0f, 12.0f, "%.0f"); + ImGui::SliderFloat("PopupRounding", &style.PopupRounding, 0.0f, 12.0f, "%.0f"); + ImGui::SliderFloat("ScrollbarRounding", &style.ScrollbarRounding, 0.0f, 12.0f, "%.0f"); + ImGui::SliderFloat("GrabRounding", &style.GrabRounding, 0.0f, 12.0f, "%.0f"); + ImGui::SliderFloat("TabRounding", &style.TabRounding, 0.0f, 12.0f, "%.0f"); + + ImGui::EndTabItem(); + } + + if (ImGui::BeginTabItem("Colors")) + { + + static ImGuiTextFilter filter; + filter.Draw("Filter colors", ImGui::GetFontSize() * 16); + + static ImGuiColorEditFlags alpha_flags = 0; + if (ImGui::RadioButton("Opaque", alpha_flags == ImGuiColorEditFlags_None)) { alpha_flags = ImGuiColorEditFlags_None; } ImGui::SameLine(); + if (ImGui::RadioButton("Alpha", alpha_flags == ImGuiColorEditFlags_AlphaPreview)) { alpha_flags = ImGuiColorEditFlags_AlphaPreview; } ImGui::SameLine(); + if (ImGui::RadioButton("Both", alpha_flags == ImGuiColorEditFlags_AlphaPreviewHalf)) { alpha_flags = ImGuiColorEditFlags_AlphaPreviewHalf; } + + ImGui::BeginChild("##colors", ImVec2(0, 0), true, ImGuiWindowFlags_AlwaysVerticalScrollbar | ImGuiWindowFlags_AlwaysHorizontalScrollbar | ImGuiWindowFlags_NavFlattened); + ImGui::PushItemWidth(-160); + for (int i = 0; i < ImGuiCol_COUNT; i++) + { + const char* name = ImGui::GetStyleColorName(i); + if (!filter.PassFilter(name)) + continue; + ImGui::PushID(i); + ImGui::ColorEdit4("##color", (float*)&style.Colors[i], ImGuiColorEditFlags_AlphaBar | alpha_flags | ImGuiColorEditFlags_DisplayHSV); + if (memcmp(&style.Colors[i], &ref->Colors[i], sizeof(ImVec4)) != 0) + { + // Tips: in a real user application, you may want to merge and use an icon font into the main font, + // so instead of "Save"/"Revert" you'd use icons! + // Read the FAQ and docs/FONTS.md about using icon fonts. It's really easy and super convenient! + ImGui::SameLine(0.0f, style.ItemInnerSpacing.x); if (ImGui::Button("Save")) { ref->Colors[i] = style.Colors[i]; } + ImGui::SameLine(0.0f, style.ItemInnerSpacing.x); if (ImGui::Button("Revert")) { style.Colors[i] = ref->Colors[i]; } + } + ImGui::SameLine(0.0f, style.ItemInnerSpacing.x); + ImGui::TextUnformatted(name); + bool hueEnabled = HueEnabledColors.contains(i); + if (ImGui::Checkbox("Match accent color hue", &hueEnabled)) { + if (hueEnabled) { + HueEnabledColors.insert(i); + } else { + HueEnabledColors.erase(i); + } + } + ImGui::PopID(); + } + ImGui::PopItemWidth(); + ImGui::EndChild(); + + ImGui::EndTabItem(); + } + + ImGui::EndTabBar(); + } + + ImGui::PopItemWidth(); + ImGui::End(); + if (importDialog.HasSelected()) { + path selected_path = importDialog.GetSelected(); + path filename = selected_path.filename(); + copy_file(selected_path, themeDir / filename); + availableThemes.insert(filename); + } + if (exportDialog.HasSelected()) { + path selected_path = importDialog.GetSelected(); + Save(selected_path); + } + if (loadOpen) { + ImGui::OpenPopup("Load..."); + } + if (ImGui::BeginPopupModal("Load...", &loadOpen)) { + static path selectedThemePath; + static char filter[1024] = {0}; + ImGui::SetNextItemWidth(ImGui::GetWindowWidth() - (ImGui::GetStyle().WindowPadding.x * 2.0f)); + ImGui::InputText("Filter: ", filter, 1024); + ImGui::Text("Available themes..."); + if (ImGui::BeginListBox("##Themes", ImVec2(ImGui::GetWindowWidth() - (ImGui::GetStyle().WindowPadding.x * 2.0f), -ImGui::GetTextLineHeightWithSpacing() - ImGui::GetStyle().WindowPadding.y))) { + for (auto themePath : availableThemes) { + if (themePath.stem().string().starts_with(filter)) { + const bool is_selected = themePath == selectedThemePath; + if (ImGui::Selectable(themePath.stem().c_str(), is_selected)) { + selectedThemePath = themePath; + } + if (is_selected) { + ImGui::SetItemDefaultFocus(); + } + } + } + ImGui::EndListBox(); + } + if (ImGui::Button("Load")) { + if (!selectedThemePath.empty()) { + filter[0] = '\0'; + loadOpen = false; + delete theme; + theme = new Theme(selectedThemePath); + selectedThemePath = path(); + ImGui::EndPopup(); + return true; + } + } + ImGui::SameLine(); + if (ImGui::Button("Cancel")) { + selectedThemePath = path(); + filter[0] = '\0'; + loadOpen = false; + } + ImGui::EndPopup(); + } + if (saveAsOpen) { + ImGui::OpenPopup("Save as..."); + } + if (ImGui::BeginPopupModal("Save as...", &saveAsOpen)) { + static char selectedThemeName[1024] = {0}; + static char filter[1024] = {0}; + ImGui::SetNextItemWidth(ImGui::GetWindowWidth() - (ImGui::GetStyle().WindowPadding.x * 2.0f)); + ImGui::InputText("Filter: ", filter, 1024); + ImGui::Text("Available themes..."); + if (ImGui::BeginListBox("##Themes", ImVec2(ImGui::GetWindowWidth() - (ImGui::GetStyle().WindowPadding.x * 2.0f), -ImGui::GetFrameHeightWithSpacing() - ImGui::GetTextLineHeightWithSpacing() - ImGui::GetStyle().WindowPadding.y))) { + for (auto themePath : availableThemes) { + if (themePath.stem().string().starts_with(filter)) { + const bool is_selected = strcmp(themePath.stem().c_str(), selectedThemeName) == 0; + if (ImGui::Selectable(themePath.stem().c_str(), is_selected)) { + strncpy(selectedThemeName, themePath.stem().c_str(), 1024); + } + if (is_selected) { + ImGui::SetItemDefaultFocus(); + } + } + } + ImGui::EndListBox(); + } + ImGui::SetNextItemWidth(ImGui::GetWindowWidth() - (ImGui::GetStyle().WindowPadding.x * 2.0f)); + ImGui::InputText("Theme name: ", selectedThemeName, 1024); + if (ImGui::Button("Save")) { + path selectedThemePath(selectedThemeName); + if (!selectedThemePath.empty() && !selectedThemePath.is_absolute()) { + selectedThemeName[0] = '\0'; // This empties the string by taking advantage of C strings. + filter[0] = '\0'; + saveAsOpen = false; + Save(themeDir / selectedThemePath.replace_extension(".json")); + file_path = selectedThemePath; + } + } + ImGui::SameLine(); + if (ImGui::Button("Cancel")) { + selectedThemeName[0] = '\0'; // Same as above + filter[0] = '\0'; + saveAsOpen = false; + } + ImGui::EndPopup(); + } + return false; +} +ImVec4 change_accent_color(ImVec4 in, float hue) { + if (in.x == in.y && in.y == in.z) { + return in; + } + ImVec4 hsv = in; + ImVec4 out = in; + ImGui::ColorConvertRGBtoHSV(in.x, in.y, in.z, hsv.x, hsv.y, hsv.z); + hsv.x = hue / 360.0f; + ImGui::ColorConvertHSVtoRGB(hsv.x, hsv.y, hsv.z, out.x, out.y, out.z); + return out; +} +void Theme::Apply(float hue) { + ImGuiStyle& actual_style = ImGui::GetStyle(); + actual_style = style; + for (int i = 0; i < ImGuiCol_COUNT; i++) + { + if (HueEnabledColors.contains(i)) { + actual_style.Colors[i] = change_accent_color(style.Colors[i], hue); + } + } + ImGuiIO& io = ImGui::GetIO(); + if (io.ConfigFlags & ImGuiConfigFlags_ViewportsEnable) + { + actual_style.WindowRounding = 0.0f; + actual_style.Colors[ImGuiCol_WindowBg].w = 1.0f; + } +} +void Theme::Save(string path) { + { + Json::Value config; + std::ofstream stream; + stream.open(path); + { + Json::Value rounding; + rounding["Frame"] = style.FrameRounding; + rounding["Window"] = style.WindowRounding; + rounding["Child"] = style.ChildRounding; + rounding["Popup"] = style.PopupRounding; + rounding["Scrollbar"] = style.ScrollbarRounding; + rounding["Grab"] = style.GrabRounding; + rounding["Tab"] = style.TabRounding; + config["rounding"] = rounding; + } + { + Json::Value borders; + borders["Frame"] = style.FrameBorderSize; + borders["Window"] = style.WindowBorderSize; + borders["Child"] = style.ChildBorderSize; + borders["Popup"] = style.PopupBorderSize; + borders["Tab"] = style.TabBorderSize; + config["borders"] = borders; + } + { + Json::Value colors; + for (int i = 0; i < ImGuiCol_COUNT; i++) + { + const char* name = ImGui::GetStyleColorName(i); + ImVec4 color = style.Colors[i]; + Json::Value colorValue; + colorValue["r"] = color.x; + colorValue["g"] = color.y; + colorValue["b"] = color.z; + colorValue["a"] = color.w; + colorValue["ConvertToAccent"] = HueEnabledColors.contains(i); + colors[name] = colorValue; + } + config["colors"] = colors; + } + stream << config; + stream.close(); + } + updateAvailableThemes(); +} +void Theme::updateAvailableThemes() { + availableThemes.clear(); + for (auto const& dir_entry : directory_iterator(themeDir)) { + if (dir_entry.is_regular_file()) { + if (dir_entry.path().extension().string() == ".json") { + availableThemes.insert(dir_entry.path()); + } + } + } +} +Theme::Theme() { + if (prefPath == NULL) { + throw std::exception(); + } + themeDir = path(prefPath) / path("themes"); + create_directories(themeDir); + updateAvailableThemes(); +} + +Theme::Theme(bool dark) : Theme() { + if (dark) { + ImGui::StyleColorsDark(&style); + } else { + ImGui::StyleColorsLight(&style); + style.FrameBorderSize = 1; + } + for (int i = 0; i < ImGuiCol_COUNT; i++) + { + HueEnabledColors.insert(i); + } + style.FrameRounding = 12; + style.GrabRounding = 12; + style.WindowRounding = 12; +} +Theme::Theme(string path) : Theme() { + Json::Value config; + std::ifstream stream; + stream.open(path); + if (stream.is_open()) { + stream >> config; + if (config.isMember("rounding")) { + Json::Value rounding = config["rounding"]; + style.FrameRounding = rounding["Frame"].asFloat(); + style.WindowRounding = rounding["Window"].asFloat(); + style.ChildRounding = rounding["Child"].asFloat(); + style.PopupRounding = rounding["Popup"].asFloat(); + style.ScrollbarRounding = rounding["Scrollbar"].asFloat(); + style.GrabRounding = rounding["Grab"].asFloat(); + style.TabRounding = rounding["Tab"].asFloat(); + } + if (config.isMember("borders")) { + Json::Value borders = config["borders"]; + style.FrameBorderSize = borders["Frame"].asFloat(); + style.WindowBorderSize = borders["Window"].asFloat(); + style.ChildBorderSize = borders["Child"].asFloat(); + style.PopupBorderSize = borders["Popup"].asFloat(); + style.TabBorderSize = borders["Tab"].asFloat(); + } + if (config.isMember("colors")) { + Json::Value colors = config["colors"]; + for (int i = 0; i < ImGuiCol_COUNT; i++) + { + const char* name = ImGui::GetStyleColorName(i); + if (colors.isMember(name)) { + Json::Value colorValue = colors[name]; + ImVec4 color = ImVec4(colorValue["r"].asFloat(), colorValue["g"].asFloat(), colorValue["b"].asFloat(), colorValue["a"].asFloat()); + bool hueShifted = colorValue["ConvertToAccent"].asBool(); + style.Colors[i] = color; + if (hueShifted) { + HueEnabledColors.insert(i); + } + } + } + } + stream.close(); + } + file_path = path; +} \ No newline at end of file diff --git a/theme.h b/theme.h new file mode 100644 index 0000000..26ca388 --- /dev/null +++ b/theme.h @@ -0,0 +1,30 @@ +#pragma once +#include "imgui.h" +#include +#include +#include "imfilebrowser.h" +#include +using std::string; +using namespace std::filesystem; + +class Theme { + ImGuiStyle style; + ImGui::FileBrowser importDialog; + ImGui::FileBrowser exportDialog; + bool loadOpen = false; + bool saveAsOpen = false; + std::set availableThemes; + void updateAvailableThemes(); + + public: + path themeDir; + static const char* prefPath; + string file_path; + std::set HueEnabledColors; + bool ShowEditor(bool *open, Theme* &theme); + void Apply(float hue); + void Save(string path); + Theme(); + Theme(bool dark); + Theme(string path); +}; \ No newline at end of file