#include "theme.h" #include "imgui.h" #include "json/value.h" #include "thirdparty/toml.hpp" #include #include #include #include #include #include #include #include "IconsForkAwesome.h" #include "imgui_stdlib.h" #include #include #include using namespace std::filesystem; const char* Theme::prefPath = NULL; path Theme::themeDir = path(); std::set Theme::availableThemes = std::set(); std::map Theme::themeStrings = std::map(); 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; } bool Theme::ShowEditor(bool* open, Theme* &theme, ImGuiID dockid, int window_width, int window_height) { static FileBrowser importDialog(false, ImGuiFileBrowserFlags_NoTitleBar|ImGuiFileBrowserFlags_NoMove|ImGuiFileBrowserFlags_NoResize); static FileBrowser exportDialog(true, ImGuiFileBrowserFlags_NoTitleBar|ImGuiFileBrowserFlags_NoMove|ImGuiFileBrowserFlags_NoResize); static bool loadOpen = false; static bool saveAsOpen = false; ImGui::SetNextWindowDockID(dockid); ImGui::Begin(_TRI_CTX(ICON_FK_MAGIC, "Window title", "Theme Editor"), open); ImGuiStyle& style = theme->style; ImGui::PushItemWidth(ImGui::GetWindowWidth() * 0.50f); ImVec2 buttonSize = ImVec2((ImGui::GetWindowWidth() * 0.50f) - (ImGui::GetStyle().WindowPadding.x) - (ImGui::GetStyle().ItemSpacing.x * 0.5f), 0); if (ImGui::Button(_TR_CTX("Theme Editor | preset button", "Create light"), buttonSize)) { delete theme; theme = new Theme(false); ImGui::PopItemWidth(); ImGui::End(); return true; } ImGui::SameLine(); if (ImGui::Button(_TR_CTX("Theme Editor | preset button", "Create dark"), buttonSize)) { delete theme; theme = new Theme(true); ImGui::PopItemWidth(); ImGui::End(); return true; } if (ImGui::Button(_TR_CTX("Theme Editor | import button. Opens the theme import file dialog", "Import..."), buttonSize)) { importDialog.SetTitle(_TR_CTX("Theme Editor file dialog title", "Import theme...")); importDialog.SetTypeFilters(_TR_CTX("Theme Editor file dialog filter name", "Theme JSON files"), { ".toml"}); std::string userdir = std::getenv( #ifdef _WIN32 "UserProfile" #else "HOME" #endif ); importDialog.SetPwd(userdir); importDialog.Open(); } ImGui::SameLine(); if (ImGui::Button(_TR_CTX("Theme Editor | export button. Opens the theme export file dialog", "Export..."), buttonSize)) { exportDialog.SetTitle(_TR_CTX("Theme Editor file dialog title", "Export theme...")); exportDialog.SetTypeFilters(_TR_CTX("Theme Editor file dialog filter name", "Theme JSON files"), { ".toml"}); std::string userdir = std::getenv( #ifdef _WIN32 "UserProfile" #else "HOME" #endif ); exportDialog.SetPwd(userdir); exportDialog.Open(); } if (importDialog.IsOpened()) { importDialog.SetWindowSize(window_width, window_height); importDialog.SetWindowPos(0, 0); } if (exportDialog.IsOpened()) { exportDialog.SetWindowSize(window_width, window_height); exportDialog.SetWindowPos(0, 0); } importDialog.Display(); exportDialog.Display(); if (!theme->file_path.empty()) { if (ImGui::Button(_TR_CTX("Theme Editor | button. Reverts to saved file.", "Revert"), buttonSize)) { string file_path_backup = theme->file_path; delete theme; theme = new Theme(file_path_backup); ImGui::PopItemWidth(); ImGui::End(); return true; } ImGui::SameLine(); if (ImGui::Button(_TR_CTX("Theme Editor | button. Saves the theme to it's current location. Not shown when it doesn't already have one.", "Save"), buttonSize)) { theme->Save(theme->file_path); } } if (ImGui::Button(_TR_CTX("Theme Editor | button. Opens the theme loading dialog for themes created or imported by the user.", "Load..."), buttonSize)) { loadOpen = true; } ImGui::SameLine(); if (ImGui::Button(_TR_CTX("Theme Editor | button. Opens the theme saving dialog for themes created or imported by the user.", "Save as..."), buttonSize)) { saveAsOpen = true; } // Simplified Settings (expose floating-pointer border sizes as boolean representing 0.0f or 1.0f) if (ImGui::SliderFloat(_TR_CTX("Theme Editor | slider. Simplified frame rounding.", "Frame Rounding"), &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(_TR_CTX("Theme Editor | checkbox", "Window Border"), &border)) { style.WindowBorderSize = border ? 1.0f : 0.0f; } } ImGui::SameLine(); { bool border = (style.FrameBorderSize > 0.0f); if (ImGui::Checkbox(_TR_CTX("Theme Editor | checkbox", "Frame Border"), &border)) { style.FrameBorderSize = border ? 1.0f : 0.0f; } } ImGui::SameLine(); { bool border = (style.PopupBorderSize > 0.0f); if (ImGui::Checkbox(_TR_CTX("Theme Editor | checkbox", "Popup Border"), &border)) { style.PopupBorderSize = border ? 1.0f : 0.0f; } } ImGui::Separator(); if (ImGui::BeginTabBar("##tabs", ImGuiTabBarFlags_None)) { if (ImGui::BeginTabItem(_TR_CTX("Theme Editor | Tab label", "Strings"))) { ImGui::InputText(_TR_CTX("Theme Editor | Strings | Name input label", "Name"), &theme->strings["fallback"].name); ImGui::InputText(_TR_CTX("Theme Editor | Strings | Description input label", "Description"), &theme->strings["fallback"].description); ImGui::EndTabItem(); } if (ImGui::BeginTabItem(_TR_CTX("Theme Editor | Tab label", "Sizes"))) { ImGui::SeparatorText(_TR_CTX("Theme Editor | Sizes | Section label", "Sizing")); ImGui::SliderFloat2(_TR_CTX("Theme Editor | Sizes | Sizing | label of XY sliders", "Window Padding"), (float*)&style.WindowPadding, 0.0f, 20.0f, "%.0f"); ImGui::SliderFloat2(_TR_CTX("Theme Editor | Sizes | Sizing | label of XY sliders", "Frame Padding"), (float*)&style.FramePadding, 0.0f, 20.0f, "%.0f"); ImGui::SliderFloat2(_TR_CTX("Theme Editor | Sizes | Sizing | label of XY sliders", "Cell Padding"), (float*)&style.CellPadding, 0.0f, 20.0f, "%.0f"); ImGui::SliderFloat2(_TR_CTX("Theme Editor | Sizes | Sizing | label of XY sliders", "Item Spacing"), (float*)&style.ItemSpacing, 0.0f, 20.0f, "%.0f"); ImGui::SliderFloat2(_TR_CTX("Theme Editor | Sizes | Sizing | label of XY sliders", "Inner Item Spacing"), (float*)&style.ItemInnerSpacing, 0.0f, 20.0f, "%.0f"); //ImGui::SliderFloat2(_TR_CTX("Theme Editor | Sizes | Sizing | label of XY sliders", "TouchExtraPadding", (float*)&style.TouchExtraPadding, 0.0f, 10.0f, "%.0f"); //ImGui::SliderFloat(_TR_CTX("Theme Editor | Sizes | Sizing | slider label", "IndentSpacing", &style.IndentSpacing, 0.0f, 30.0f, "%.0f"); ImGui::SliderFloat(_TR_CTX("Theme Editor | Sizes | Sizing | slider label", "Scrollbar Size"), &style.ScrollbarSize, 1.0f, 20.0f, "%.0f"); ImGui::SliderFloat(_TR_CTX("Theme Editor | Sizes | Sizing | slider label", "Minimum Grabber Size"), &style.GrabMinSize, 1.0f, 20.0f, "%.0f"); ImGui::SliderFloat2(_TR_CTX("Theme Editor | Sizes | Sizing | label of XY sliders", "Separator Text Padding"), (float*)&style.SeparatorTextPadding, 0.0f, 40.0f, "%.0f"); ImGui::SeparatorText(_TR_CTX("Theme Editor | Sizes | Section label", "Borders")); ImGui::SliderFloat(_TR_CTX("Theme Editor | Sizes | Borders | slider label", "Window Border Size"), &style.WindowBorderSize, 0.0f, 1.0f, "%.0f"); ImGui::SliderFloat(_TR_CTX("Theme Editor | Sizes | Borders | slider label", "Child Border Size"), &style.ChildBorderSize, 0.0f, 1.0f, "%.0f"); ImGui::SliderFloat(_TR_CTX("Theme Editor | Sizes | Borders | slider label", "Popup Border Size"), &style.PopupBorderSize, 0.0f, 1.0f, "%.0f"); ImGui::SliderFloat(_TR_CTX("Theme Editor | Sizes | Borders | slider label", "Frame Border Size"), &style.FrameBorderSize, 0.0f, 1.0f, "%.0f"); ImGui::SliderFloat(_TR_CTX("Theme Editor | Sizes | Borders | slider label", "Tab Border Size"), &style.TabBorderSize, 0.0f, 1.0f, "%.0f"); ImGui::SliderFloat(_TR_CTX("Theme Editor | Sizes | Borders | slider label", "Separator Text Border Size"), &style.SeparatorTextBorderSize, 0.0f, 10.0f, "%.0f"); ImGui::SeparatorText(_TR_CTX("Theme Editor | Sizes | Section label", "Rounding")); ImGui::SliderFloat(_TR_CTX("Theme Editor | Sizes | Rounding | slider label", "Window Rounding"), &style.WindowRounding, 0.0f, 12.0f, "%.0f"); ImGui::SliderFloat(_TR_CTX("Theme Editor | Sizes | Rounding | slider label", "Child Rounding"), &style.ChildRounding, 0.0f, 12.0f, "%.0f"); ImGui::SliderFloat(_TR_CTX("Theme Editor | Sizes | Rounding | slider label", "Frame Rounding"), &style.FrameRounding, 0.0f, 12.0f, "%.0f"); ImGui::SliderFloat(_TR_CTX("Theme Editor | Sizes | Rounding | slider label", "Popup Rounding"), &style.PopupRounding, 0.0f, 12.0f, "%.0f"); ImGui::SliderFloat(_TR_CTX("Theme Editor | Sizes | Rounding | slider label", "Scrollbar Rounding"), &style.ScrollbarRounding, 0.0f, 12.0f, "%.0f"); ImGui::SliderFloat(_TR_CTX("Theme Editor | Sizes | Rounding | slider label", "Grabber Rounding"), &style.GrabRounding, 0.0f, 12.0f, "%.0f"); ImGui::SliderFloat(_TR_CTX("Theme Editor | Sizes | Rounding | slider label", "Tab Rounding"), &style.TabRounding, 0.0f, 12.0f, "%.0f"); ImGui::EndTabItem(); } if (ImGui::BeginTabItem(_TR_CTX("Theme Editor | Tab label", "Colors"))) { static ImGuiTextFilter filter; filter.Draw(_TR_CTX("Theme Editor | Colors | text filter", "Filter colors"), ImGui::GetFontSize() * 16); static ImGuiColorEditFlags alpha_flags = 0; if (ImGui::RadioButton(_TR_CTX("Theme Editor | Colors | preview settings radio button", "Opaque"), alpha_flags == ImGuiColorEditFlags_None)) { alpha_flags = ImGuiColorEditFlags_None; } ImGui::SameLine(); if (ImGui::RadioButton(_TR_CTX("Theme Editor | Colors | preview settings radio button", "Alpha"), alpha_flags == ImGuiColorEditFlags_AlphaPreview)) { alpha_flags = ImGuiColorEditFlags_AlphaPreview; } ImGui::SameLine(); if (ImGui::RadioButton(_TR_CTX("Theme Editor | Colors | preview settings radio button", "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); AccentColorizer &colorizer = theme->AccentColorizers[i]; ImGui::TextUnformatted(_TR_CTX("Theme Editor | Colors | (Any color) | recoloring label", "Match accent color preference: ")); ImGui::Checkbox(_TR_CTX("Theme Editor | Colors | (Any color) | recoloring checkbox | Hue", "H: "), &colorizer.Hue); ImGui::SameLine(); ImGui::Checkbox(_TR_CTX("Theme Editor | Colors | (Any color) | recoloring checkbox | Saturation", "S: "), &colorizer.Saturation); ImGui::SameLine(); ImGui::Checkbox(_TR_CTX("Theme Editor | Colors | (Any color) | recoloring checkbox | Value", "V: "), &colorizer.Value); ImGui::SameLine(); ImGui::Checkbox(_TR_CTX("Theme Editor | Colors | (Any color) | recoloring checkbox | Alpha (opacity)", "A: "), &colorizer.Alpha); ImGui::SameLine(); ImGui::ColorEdit4("##color", (float*)&style.Colors[i], ImGuiColorEditFlags_AlphaBar | alpha_flags | ImGuiColorEditFlags_DisplayHSV); ImGui::SameLine(0.0f, style.ItemInnerSpacing.x); ImGui::TextUnformatted(name); 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, theme->themeDir / filename); Theme::updateAvailableThemes(); importDialog.ClearSelected(); } if (exportDialog.HasSelected()) { path selected_path = exportDialog.GetSelected(); theme->Save(selected_path); exportDialog.ClearSelected(); } if (loadOpen) { ImGui::OpenPopup(_TR_CTX("Theme Editor | Custom modal dialog title", "Load...")); ImGui::SetNextWindowPos(ImVec2(0, 0)); ImGui::SetNextWindowSize(ImVec2(window_width, window_height)); if (ImGui::BeginPopupModal(_TR_CTX("Theme Editor | Custom modal dialog title", "Load..."), nullptr, ImGuiWindowFlags_Modal|ImGuiWindowFlags_NoMove|ImGuiWindowFlags_NoResize)) { static path selectedThemePath; static string filter = ""; ImGui::TextUnformatted(_TR_CTX("Theme Editor | Load dialog | Theme selector | filter label", "Filter:")); ImGui::SameLine(); ImGui::SetNextItemWidth(ImGui::GetWindowWidth() - ImGui::GetCursorPosX() - ImGui::GetStyle().WindowPadding.x); ImGui::InputText("##FilterInput", &filter); ImGui::TextUnformatted(_TR_CTX("Theme Editor | Load dialog | Theme selector | label", "Available themes...")); if (ImGui::BeginListBox("##Themes", ImVec2(ImGui::GetWindowWidth() - (ImGui::GetStyle().WindowPadding.x * 2.0f), -ImGui::GetTextLineHeightWithSpacing() - ImGui::GetStyle().WindowPadding.y))) { for (auto themePath : Theme::availableThemes) { string themeStem = themePath.stem().string(); if (themeStem.starts_with(filter)) { const bool is_selected = themePath == selectedThemePath; if (ImGui::Selectable((theme->themeStrings[themePath].name + string(" (") + string(themeStem) + string(")")).c_str(), is_selected)) { selectedThemePath = themePath; } if (is_selected) { ImGui::SetItemDefaultFocus(); } } } ImGui::EndListBox(); } if (ImGui::Button(_TR_CTX("Theme Editor | Load dialog | Load button", "Load"))) { if (!selectedThemePath.empty()) { filter = ""; loadOpen = false; delete theme; theme = new Theme(selectedThemePath); selectedThemePath = path(); ImGui::EndPopup(); return true; } } ImGui::SameLine(); if (ImGui::Button(_TR_CTX("Theme Editor | Load dialog | Cancel button", "Cancel"))) { selectedThemePath = path(); filter = ""; loadOpen = false; } ImGui::EndPopup(); } } if (saveAsOpen) { ImGui::OpenPopup(_TR_CTX("Theme Editor | Custom modal dialog title", "Save as...")); ImGui::SetNextWindowPos(ImVec2(0, 0)); ImGui::SetNextWindowSize(ImVec2(window_width, window_height)); if (ImGui::BeginPopupModal(_TR_CTX("Theme Editor | Custom modal dialog title", "Save as..."), nullptr, ImGuiWindowFlags_Modal|ImGuiWindowFlags_NoMove|ImGuiWindowFlags_NoResize)) { static string selectedThemeName = ""; static string filter = ""; ImGui::TextUnformatted(_TR_CTX("Theme Editor | Save as dialog | Theme selector | filter label", "Filter:")); ImGui::SameLine(); ImGui::SetNextItemWidth(ImGui::GetWindowWidth() - ImGui::GetCursorPosX() - ImGui::GetStyle().WindowPadding.x); ImGui::InputText("##FilterInput", &filter, 1024); ImGui::TextUnformatted(_TR_CTX("Theme Editor | Save as dialog | Theme selector | label", "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 : Theme::availableThemes) { string themeStem = themePath.stem().string(); if (themeStem.starts_with(filter)) { const bool is_selected = themeStem == selectedThemeName; if (ImGui::Selectable((theme->themeStrings[themePath].name + string(" (") + string(themeStem) + string(")")).c_str(), is_selected)) { selectedThemeName = themeStem; } if (is_selected) { ImGui::SetItemDefaultFocus(); } } } ImGui::EndListBox(); } ImGui::SetNextItemWidth(ImGui::GetWindowWidth() - (ImGui::GetStyle().WindowPadding.x * 2.0f)); ImGui::InputText(_TR_CTX("Theme Editor | Save as dialog | Theme name input label", "Theme name: "), &selectedThemeName); if (ImGui::Button(_TR_CTX("Theme Editor | Save as dialog | Save button", "Save"))) { path selectedThemePath(selectedThemeName); if (!selectedThemePath.empty() && !selectedThemePath.is_absolute()) { selectedThemeName = ""; filter = ""; saveAsOpen = false; theme->Save(Theme::themeDir / selectedThemePath.replace_extension(".toml")); theme->file_path = selectedThemePath.generic_string(); } } ImGui::SameLine(); if (ImGui::Button(_TR_CTX("Theme Editor | Save as dialog | Cancel button", "Cancel"))) { selectedThemeName = ""; filter = ""; saveAsOpen = false; } ImGui::EndPopup(); } } return false; } AccentColorizer::AccentColorizer() { Hue = false; Saturation = false; Value = false; Alpha = false; } AccentColorizer::AccentColorizer(toml::table table) : AccentColorizer() { Hue = **table["hue"].as_boolean(); Saturation = **table["saturation"].as_boolean(); Value = **table["value"].as_boolean(); Alpha = **table["alpha"].as_boolean(); } toml::table AccentColorizer::Serialize() { toml::table output; output.insert("hue", Hue); output.insert("saturation", Saturation); output.insert("value", Value); output.insert("alpha", Alpha); return output; } void AccentColorizer::Colorize(ImVec4 accent, ImVec4 &color) { ImVec4 hsv = color; ImGui::ColorConvertRGBtoHSV(color.x, color.y, color.z, hsv.x, hsv.y, hsv.z); if (Saturation) hsv.y = accent.y; if (Value) hsv.z = accent.z; if (Hue) hsv.x = accent.x; ImGui::ColorConvertHSVtoRGB(hsv.x, hsv.y, hsv.z, color.x, color.y, color.z); if (Alpha) color.w *= accent.w; } void Theme::Apply(ImVec4 accent, float scale) { ImGuiStyle& actual_style = ImGui::GetStyle(); actual_style = style; for (int i = 0; i < ImGuiCol_COUNT; i++) { if (AccentColorizers.contains(i)) { AccentColorizers[i].Colorize(accent, actual_style.Colors[i]); } } ImGuiIO& io = ImGui::GetIO(); if (io.ConfigFlags & ImGuiConfigFlags_ViewportsEnable) { actual_style.WindowRounding = 0.0f; actual_style.Colors[ImGuiCol_WindowBg].w = 1.0f; } actual_style.ScaleAllSizes(scale); actual_style.WindowMinSize.x = MAX(actual_style.WindowMinSize.x, 1.0); actual_style.WindowMinSize.y = MAX(actual_style.WindowMinSize.y, 1.0); } void Theme::Save(string path) { INFO.writefln("Saving theme to %s...", path.c_str()); { toml::table config; #ifdef __EMSCRIPTEN__ std::ostringstream stream; #else std::ofstream stream; stream.open(path); #endif { toml::table metadata; metadata.insert("SchemaVersion", 3); { toml::table stringsList; for (auto kv : strings) { toml::table stringsEntryJson; string language = kv.first; ThemeStrings stringsEntry = kv.second; stringsEntryJson.insert("name", stringsEntry.name); stringsEntryJson.insert("desc", stringsEntry.description); stringsList.insert(language, stringsEntryJson); } metadata.insert("Strings", stringsList); } config.insert("meta", metadata); } { toml::table rounding; rounding.insert("Frame", style.FrameRounding); rounding.insert("Window", style.WindowRounding); rounding.insert("Child", style.ChildRounding); rounding.insert("Popup", style.PopupRounding); rounding.insert("Scrollbar", style.ScrollbarRounding); rounding.insert("Grab", style.GrabRounding); rounding.insert("Tab", style.TabRounding); config.insert("rounding", rounding); } { toml::table sizing; sizing.insert("FrameX", style.FramePadding.x); sizing.insert("FrameY", style.FramePadding.y); sizing.insert("WindowX", style.WindowPadding.x); sizing.insert("WindowY", style.WindowPadding.y); sizing.insert("CellX", style.CellPadding.x); sizing.insert("CellY", style.CellPadding.y); sizing.insert("SeparatorTextX", style.SeparatorTextPadding.x); sizing.insert("SeparatorTextY", style.SeparatorTextPadding.y); sizing.insert("ItemSpacingX", style.ItemSpacing.x); sizing.insert("ItemSpacingY", style.ItemSpacing.y); sizing.insert("Scrollbar", style.ScrollbarSize); sizing.insert("Grab", style.GrabMinSize); config.insert("sizing", sizing); } { toml::table borders; borders.insert("Frame", style.FrameBorderSize); borders.insert("Window", style.WindowBorderSize); borders.insert("Child", style.ChildBorderSize); borders.insert("Popup", style.PopupBorderSize); borders.insert("Tab", style.TabBorderSize); borders.insert("SeparatorText", style.SeparatorTextBorderSize); config.insert("borders", borders); } { toml::table colors; for (int i = 0; i < ImGuiCol_COUNT; i++) { const char* name = ImGui::GetStyleColorName(i); ImVec4 color = style.Colors[i]; toml::table colorValue; colorValue.insert("r", color.x); colorValue.insert("g", color.y); colorValue.insert("b", color.z); colorValue.insert("a", color.w); colorValue.insert("accent", AccentColorizers[i].Serialize()); colors.insert(name, colorValue); } config.insert("colors", colors); } stream << config; #ifdef __EMSCRIPTEN__ path = path; std::string str = stream.str(); write_storage(path.c_str(), str.c_str(), str.length() + 1); #else stream.close(); #endif } updateAvailableThemes(); } void Theme::Save(path path) { Save((string)path.string()); } void Theme::updateAvailableThemes() { #ifdef __EMSCRIPTEN__ themeDir = "theme"; #else themeDir = path(prefPath) / path("themes"); create_directories(themeDir); #endif availableThemes.clear(); themeStrings.clear(); #ifdef __EMSCRIPTEN__ const char **theme_files = nullptr; find_keys("theme/", &theme_files); if (theme_files[0] != nullptr) { for (size_t i = 0; theme_files[i] != nullptr; i++) { const char *dir_entry = theme_files[i]; if (dir_entry == nullptr) { break; } string curpath = dir_entry; const char *theme_contents = nullptr; read_storage(dir_entry, &theme_contents, nullptr); if (theme_contents == nullptr) { continue; } availableThemes.insert(curpath); toml::table config = toml::parse(theme_contents); free((void*)theme_contents); themeStrings[curpath] = ThemeStrings(config); free((void*)dir_entry); } } free((void*)theme_files); #else for (auto const& dir_entry : directory_iterator(themeDir)) { if (dir_entry.is_regular_file()) { if (dir_entry.path().extension().string() == ".json") { string curpath = Migrate(dir_entry.path().string()); std::filesystem::remove(dir_entry.path()); availableThemes.insert(curpath); toml::table config = toml::parse_file(curpath); themeStrings[curpath] = ThemeStrings(config); } else if (dir_entry.path().extension().string() == ".toml") { string curpath = dir_entry.path().string(); availableThemes.insert(curpath); toml::table config = toml::parse_file(curpath); themeStrings[curpath] = ThemeStrings(config); } } } #endif } Theme::Theme() { if (prefPath == NULL) { throw std::exception(); } updateAvailableThemes(); strings["fallback"] = ThemeStrings(); } 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++) { ImVec4 color = style.Colors[i]; auto colorizer = AccentColorizer(); if (color.x != color.y || color.y != color.z) { colorizer.Hue = true; colorizer.Saturation = true; colorizer.Alpha = true; } AccentColorizers[i] = colorizer; } style.FrameRounding = 12; style.GrabRounding = 12; style.WindowRounding = 12; } ThemeStrings::ThemeStrings() { name = _TRS_CTX("Theme default strings | name", "A theme"); description = _TRS_CTX("Theme default strings | description", "(No description)"); } ThemeStrings Theme::GetStrings() { char *language_c = CURRENT_LANGUAGE; string language = language_c; if (strings.contains(language)) { return strings[language]; } else { if (!strings.contains("fallback")) { strings["fallback"] = ThemeStrings(); } return strings["fallback"]; } } ThemeStrings::ThemeStrings(toml::table config) : ThemeStrings() { char *language_c = CURRENT_LANGUAGE; string language = language_c; if (config.contains("meta")) { toml::table metadata = *config["meta"].as_table(); //metadata["SchemaVersion"] = 1; if (metadata.contains("Strings")) { toml::table stringsList = *metadata["Strings"].as_table(); if (stringsList.contains(language)) { toml::table stringsEntryJson = *stringsList[language].as_table(); if (stringsEntryJson.contains("name")) { name = **stringsEntryJson["name"].as_string(); } if (stringsEntryJson.contains("desc")) { description = **stringsEntryJson["desc"].as_string(); } } else if (metadata.contains("fallback")) { toml::table stringsEntryJson = *stringsList["fallback"].as_table(); if (stringsEntryJson.contains("name")) { name = **stringsEntryJson["name"].as_string(); } if (stringsEntryJson.contains("desc")) { description = **stringsEntryJson["desc"].as_string(); } } } } } std::string Theme::Migrate(std::string path) { if (path.ends_with(".json")) { INFO.writefln("Migrating theme file '%s'...", path.c_str()); std::ifstream stream(path); Json::Value config; stream >> config; toml::table newConfig; if (config.isMember("meta")) { Json::Value metadata = config["meta"]; toml::table newMeta; if (metadata.isMember("Strings")) { Json::Value stringsList = metadata["Strings"]; toml::table newStringsList; for (string language : stringsList.getMemberNames()) { Json::Value stringsEntryJson = stringsList[language]; toml::table newStringsEntry; if (stringsEntryJson.isMember("Name")) { string value = stringsEntryJson["Name"].asString(); newStringsEntry.insert("name", value); } if (stringsEntryJson.isMember("Description")) { string value = stringsEntryJson["Description"].asString(); newStringsEntry.insert("desc", value); } newStringsList.insert(language, newStringsEntry); } newMeta.insert("Strings", newStringsList); } newConfig.insert("meta", newMeta); } if (config.isMember("rounding")) { Json::Value rounding = config["rounding"]; toml::table newRounding; for (string key : rounding.getMemberNames()) { newRounding.insert(key, rounding[key].asFloat()); } newConfig.insert("rounding", newRounding); } if (config.isMember("sizing")) { Json::Value rounding = config["sizing"]; toml::table newRounding; for (string key : rounding.getMemberNames()) { newRounding.insert(key, rounding[key].asFloat()); } newConfig.insert("sizing", newRounding); } if (config.isMember("borders")) { Json::Value rounding = config["borders"]; toml::table newRounding; for (string key : rounding.getMemberNames()) { newRounding.insert(key, rounding[key].asFloat()); } newConfig.insert("borders", newRounding); } if (config.isMember("colors")) { Json::Value colors = config["colors"]; toml::table newColors; for (int i = 0; i < ImGuiCol_COUNT; i++) { toml::table newColor; 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()); newColor.insert("r", colorValue["r"].asFloat()); newColor.insert("g", colorValue["g"].asFloat()); newColor.insert("b", colorValue["b"].asFloat()); newColor.insert("a", colorValue["a"].asFloat()); toml::table newAccentOptions; if (colorValue["ConvertToAccent"].isBool()) { newAccentOptions.insert("hue", colorValue["ConvertToAccent"].asBool()); newAccentOptions.insert("saturation", false); newAccentOptions.insert("value", false); newAccentOptions.insert("alpha", false); } else { Json::Value accentOptions = colorValue["ConvertToAccent"]; newAccentOptions.insert("hue", accentOptions["hue"].asBool()); newAccentOptions.insert("saturation", accentOptions["saturation"].asBool()); newAccentOptions.insert("value", accentOptions["value"].asBool()); newAccentOptions.insert("alpha", accentOptions["alpha"].asBool()); } newColor.insert("accent", newAccentOptions); newColors.insert(name, newColor); } } } stream.close(); std::string newPath = path.replace(path.size() - 4, 4, "toml"); std::ofstream outStream(newPath); outStream << newConfig; outStream.close(); return newPath; } return path; } Theme::Theme(string path) : Theme() { path = Migrate(path); INFO.writefln("Loading theme '%s'...", path.c_str()); toml::table config; #ifdef __EMSCRIPTEN__ const char *contents = nullptr; read_storage(path.c_str(), &contents, nullptr); if (contents == nullptr) { throw std::exception(); } config = toml::parse(contents); free((void*)contents); #else config = toml::parse_file(path); #endif if (config.contains("meta")) { toml::table metadata = *config["meta"].as_table(); //metadata["SchemaVersion"] = 1; if (metadata.contains("Strings")) { toml::table stringsList = *metadata["Strings"].as_table(); for (auto kv : stringsList) { string language = string(kv.first.str()); toml::table stringEntryToml = *kv.second.as_table(); ThemeStrings stringsEntry; if (stringEntryToml.contains("name")) { stringsEntry.name = stringEntryToml["name"].as_string()->value_or("Unknown"); } if (stringEntryToml.contains("desc")) { stringsEntry.description = stringEntryToml["desc"].as_string()->value_or("No description."); } strings[language] = stringsEntry; } } } if (config.contains("rounding")) { toml::table rounding = *config["rounding"].as_table(); style.FrameRounding = (float)(**(rounding["Frame"].as_floating_point())); style.WindowRounding = (float)(**(rounding["Window"].as_floating_point())); style.ChildRounding = (float)(**(rounding["Child"].as_floating_point())); style.PopupRounding = (float)(**(rounding["Popup"].as_floating_point())); style.ScrollbarRounding = (float)(**(rounding["Scrollbar"].as_floating_point())); style.GrabRounding = (float)(**(rounding["Grab"].as_floating_point())); style.TabRounding = (float)(**(rounding["Tab"].as_floating_point())); } if (config.contains("sizing")) { toml::table sizing = *config["sizing"].as_table(); style.FramePadding.x = (float)(**sizing["FrameX"].as_floating_point()); style.FramePadding.y = (float)(**sizing["FrameY"].as_floating_point()); style.WindowPadding.x = (float)(**sizing["WindowX"].as_floating_point()); style.WindowPadding.y = (float)(**sizing["WindowY"].as_floating_point()); style.CellPadding.x = (float)(**sizing["CellX"].as_floating_point()); style.CellPadding.y = (float)(**sizing["CellY"].as_floating_point()); style.SeparatorTextPadding.x = (float)(**sizing["SeparatorTextX"].as_floating_point()); style.SeparatorTextPadding.y = (float)(**sizing["SeparatorTextY"].as_floating_point()); style.ItemSpacing.x = (float)(**sizing["ItemSpacingX"].as_floating_point()); style.ItemSpacing.y = (float)(**sizing["ItemSpacingY"].as_floating_point()); style.ScrollbarSize = (float)(**sizing["Scrollbar"].as_floating_point()); style.GrabMinSize = (float)(**sizing["Grab"].as_floating_point()); } if (config.contains("borders")) { toml::table borders = *config["borders"].as_table(); style.FrameBorderSize = (float)(**borders["Frame"].as_floating_point()); style.WindowBorderSize = (float)(**borders["Window"].as_floating_point()); style.ChildBorderSize = (float)(**borders["Child"].as_floating_point()); style.PopupBorderSize = (float)(**borders["Popup"].as_floating_point()); style.TabBorderSize = (float)(**borders["Tab"].as_floating_point()); if (borders.contains("SeparatorText")) { style.SeparatorTextBorderSize = (float)(**borders["SeparatorText"].as_floating_point()); } } if (config.contains("colors")) { toml::table colors = *config["colors"].as_table(); for (int i = 0; i < ImGuiCol_COUNT; i++) { const char* name = ImGui::GetStyleColorName(i); if (colors.contains(name)) { toml::table colorValue = *colors[name].as_table(); ImVec4 color = ImVec4((float)**colorValue["r"].as_floating_point(), (float)**colorValue["g"].as_floating_point(), (float)**colorValue["b"].as_floating_point(), (float)**colorValue["a"].as_floating_point()); AccentColorizers[i] = AccentColorizer(*colorValue["accent"].as_table()); style.Colors[i] = color; } } } file_path = path; } Theme::Theme(path path) : Theme(path.string()) { }