diff --git a/main.cpp b/main.cpp index 6e36d81..a15a266 100644 --- a/main.cpp +++ b/main.cpp @@ -1,5 +1,6 @@ #include "config.h" #include "imgui.h" +#include "imgui_stdlib.h" #include "imgui_impl_sdl2.h" #include "imgui_impl_opengl3.h" #include "file_browser.h" @@ -276,12 +277,21 @@ int main(int, char**) if (is_empty(Theme::themeDir)) { path lightPath = Theme::themeDir / "light.json"; path darkPath = Theme::themeDir / "dark.json"; + string builtinDescription = _TRS_CTX("Built-in themes > Theme default strings > name", "(built-in)"); if (!exists(lightPath)) { Theme light(false); + ThemeStrings &strings = light.strings["fallback"]; + strings.name = _TRS_CTX("Built-in light theme > Theme default strings > name", "Default light"); + strings.description = builtinDescription; + light.strings[CURRENT_LANGUAGE] = strings; light.Save(lightPath); } if (!exists(darkPath)) { Theme dark(true); + ThemeStrings &strings = dark.strings["fallback"]; + strings.name = _TRS_CTX("Built-in dark theme > Theme default strings > name", "Default dark"); + strings.description = builtinDescription; + dark.strings[CURRENT_LANGUAGE] = strings; dark.Save(darkPath); } delete theme; @@ -431,10 +441,10 @@ int main(int, char**) if (ImGui::Button(_TRI_CTX(ICON_FK_MAGIC, "Preference > Related non-preference button", "Theme Editor"), ImVec2(ImGui::GetWindowWidth() - (ImGui::GetStyle().WindowPadding.x * 2.0f), 0))) { theme_editor = true; } - static char filter[1024] = {0}; + static string filter = ""; ImGui::Text(_TR_CTX("Preference > Theme selector > Filter label", "Filter:")); ImGui::SameLine(); ImGui::SetNextItemWidth(ImGui::GetWindowWidth() - ImGui::GetCursorPosX() - ImGui::GetStyle().WindowPadding.x); - ImGui::InputText("##FilterInput", filter, 1024); + ImGui::InputText("##FilterInput", &filter); ImGui::Text(_TR_CTX("Preferences > Theme selector > Selector label", "Select a theme...")); ImVec2 ChildSize = ImVec2(ImGui::GetWindowWidth() - (ImGui::GetStyle().WindowPadding.x * 2.0f), -ImGui::GetFrameHeightWithSpacing()); if (ImGui::BeginChildFrame(ImGui::GetID("##ThemesContainer"), ChildSize)) { @@ -444,11 +454,12 @@ int main(int, char**) ImGui::TableSetupColumn("Name", ImGuiTableColumnFlags_WidthStretch); ImGui::TableSetupColumn("Remove", 0); for (auto themePath : Theme::availableThemes) { - if (themePath.stem().string().starts_with(filter)) { + string themeStem = themePath.stem().string(); + if (themeStem.starts_with(filter)) { ImGui::TableNextRow(); ImGui::TableSetColumnIndex(0); const bool is_selected = themePath == theme->file_path; - if (ImGui::Selectable(themePath.stem().generic_string().c_str(), is_selected, 0)) { + if (ImGui::Selectable((theme->themeStrings[themePath].name + string(" (") + string(themeStem) + string(")")).c_str(), is_selected, 0)) { delete theme; theme = new Theme(themePath); theme->Apply(accent_color); @@ -458,7 +469,7 @@ int main(int, char**) ImGui::SetItemDefaultFocus(); } else { ImGui::TableSetColumnIndex(1); - if (ImGui::SmallButton((string(ICON_FK_WINDOW_CLOSE "##") + themePath.stem().generic_string()).c_str())) { + if (ImGui::SmallButton((string(ICON_FK_WINDOW_CLOSE "##") + themeStem).c_str())) { std::filesystem::remove(themePath); Theme::updateAvailableThemes(); break; diff --git a/meson.build b/meson.build index 7c0a4e6..55b4858 100644 --- a/meson.build +++ b/meson.build @@ -63,12 +63,14 @@ srcs = [ 'imgui/imgui_tables.cpp', 'imgui/imgui_draw.cpp', 'imgui/imgui_demo.cpp', + 'imgui/misc/cpp/imgui_stdlib.cpp', 'imgui/backends/imgui_impl_sdl2.cpp', 'imgui/backends/imgui_impl_opengl3.cpp', ] include_dirs = [ 'imgui', + 'imgui/misc/cpp', 'imgui/backends', 'imgui-filebrowser', 'IconFontCppHeaders', diff --git a/theme.cpp b/theme.cpp index 038c3a9..4f37d63 100644 --- a/theme.cpp +++ b/theme.cpp @@ -8,13 +8,16 @@ #include #include #include +#include #include "IconsForkAwesome.h" +#include "imgui_stdlib.h" using namespace std::filesystem; using namespace std::numbers; 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) { @@ -126,6 +129,13 @@ bool Theme::ShowEditor(bool* open, Theme* &theme, ImGuiID dockid, int window_wid 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")); @@ -222,16 +232,17 @@ bool Theme::ShowEditor(bool* open, Theme* &theme, ImGuiID dockid, int window_wid 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 char filter[1024] = {0}; + static string filter = ""; ImGui::Text(_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, 1024); + ImGui::InputText("##FilterInput", &filter); ImGui::Text(_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) { - if (themePath.stem().string().starts_with(filter)) { + string themeStem = themePath.stem().string(); + if (themeStem.starts_with(filter)) { const bool is_selected = themePath == selectedThemePath; - if (ImGui::Selectable(themePath.stem().generic_string().c_str(), is_selected)) { + if (ImGui::Selectable((theme->themeStrings[themePath].name + string(" (") + string(themeStem) + string(")")).c_str(), is_selected)) { selectedThemePath = themePath; } if (is_selected) { @@ -243,7 +254,7 @@ bool Theme::ShowEditor(bool* open, Theme* &theme, ImGuiID dockid, int window_wid } if (ImGui::Button(_TR_CTX("Theme Editor > Load dialog > Load button", "Load"))) { if (!selectedThemePath.empty()) { - filter[0] = '\0'; + filter = ""; loadOpen = false; delete theme; theme = new Theme(selectedThemePath); @@ -255,7 +266,7 @@ bool Theme::ShowEditor(bool* open, Theme* &theme, ImGuiID dockid, int window_wid ImGui::SameLine(); if (ImGui::Button(_TR_CTX("Theme Editor > Load dialog > Cancel button", "Cancel"))) { selectedThemePath = path(); - filter[0] = '\0'; + filter = ""; loadOpen = false; } ImGui::EndPopup(); @@ -266,18 +277,19 @@ bool Theme::ShowEditor(bool* open, Theme* &theme, ImGuiID dockid, int window_wid 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 char selectedThemeName[1024] = {0}; - static char filter[1024] = {0}; + static string selectedThemeName = ""; + static string filter = ""; ImGui::Text(_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::InputText("##FilterInput", &filter, 1024); ImGui::Text(_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) { - if (themePath.stem().string().starts_with(filter)) { - const bool is_selected = strcmp(themePath.stem().generic_string().c_str(), selectedThemeName) == 0; - if (ImGui::Selectable(themePath.stem().generic_string().c_str(), is_selected)) { - strncpy(selectedThemeName, themePath.stem().generic_string().c_str(), 1023); + 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(); @@ -287,12 +299,12 @@ bool Theme::ShowEditor(bool* open, Theme* &theme, ImGuiID dockid, int window_wid 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, 1024); + 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[0] = '\0'; // This empties the string by taking advantage of C strings. - filter[0] = '\0'; + selectedThemeName = ""; + filter = ""; saveAsOpen = false; theme->Save(Theme::themeDir / selectedThemePath.replace_extension(".json")); theme->file_path = selectedThemePath.generic_string(); @@ -300,8 +312,8 @@ bool Theme::ShowEditor(bool* open, Theme* &theme, ImGuiID dockid, int window_wid } ImGui::SameLine(); if (ImGui::Button(_TR_CTX("Theme Editor > Save as dialog > Cancel button", "Cancel"))) { - selectedThemeName[0] = '\0'; // Same as above - filter[0] = '\0'; + selectedThemeName = ""; + filter = ""; saveAsOpen = false; } ImGui::EndPopup(); @@ -337,12 +349,12 @@ void Theme::Save(string path) { { Json::Value stringsList; for (auto kv : strings) { - Json::Value stringsEntry; + Json::Value stringsEntryJson; string language = kv.first; - ThemeStrings strings = kv.second; - stringsEntry["Name"] = strings.name; - stringsEntry["Description"] = strings.description; - stringsList[language] = stringsEntry; + ThemeStrings stringsEntry = kv.second; + stringsEntryJson["Name"] = stringsEntry.name; + stringsEntryJson["Description"] = stringsEntry.description; + stringsList[language] = stringsEntryJson; } metadata["Strings"] = stringsList; } @@ -414,10 +426,19 @@ void Theme::updateAvailableThemes() { themeDir = path(prefPath) / path("themes"); create_directories(themeDir); availableThemes.clear(); + themeStrings.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()); + path path = dir_entry.path(); + availableThemes.insert(path); + Json::Value config; + std::ifstream stream; + stream.open(path); + if (stream.is_open()) { + stream >> config; + themeStrings[path] = ThemeStrings(config); + } } } } @@ -427,6 +448,7 @@ Theme::Theme() { throw std::exception(); } updateAvailableThemes(); + strings["fallback"] = ThemeStrings(); } Theme::Theme(bool dark) : Theme() { @@ -451,6 +473,46 @@ 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(Json::Value config) : ThemeStrings() { + char *language_c = CURRENT_LANGUAGE; + string language = language_c; + if (config.isMember("meta")) { + Json::Value metadata = config["meta"]; + //metadata["SchemaVersion"] = 1; + if (metadata.isMember("Strings")) { + Json::Value stringsList = metadata["Strings"]; + if (stringsList.isMember(language)) { + Json::Value stringsEntryJson = stringsList[language]; + if (stringsEntryJson.isMember("Name")) { + name = stringsEntryJson["Name"].asString(); + } + if (stringsEntryJson.isMember("Description")) { + description = stringsEntryJson["Description"].asString(); + } + } else if (metadata.isMember("fallback")) { + Json::Value stringsEntryJson = stringsList["fallback"]; + if (stringsEntryJson.isMember("Name")) { + name = stringsEntryJson["Name"].asString(); + } + if (stringsEntryJson.isMember("Description")) { + description = stringsEntryJson["Description"].asString(); + } + } + } + } +} Theme::Theme(string path) : Theme() { Json::Value config; std::ifstream stream; @@ -463,14 +525,15 @@ Theme::Theme(string path) : Theme() { if (metadata.isMember("Strings")) { Json::Value stringsList = metadata["Strings"]; for (string language : stringsList.getMemberNames()) { - Json::Value stringsEntry = stringsList[language]; - ThemeStrings strings; - if (stringsEntry.isMember("Name")) { - strings.name = stringsEntry["Name"].asString(); + Json::Value stringsEntryJson = stringsList[language]; + ThemeStrings stringsEntry; + if (stringsEntryJson.isMember("Name")) { + stringsEntry.name = stringsEntryJson["Name"].asString(); } - if (stringsEntry.isMember("Description")) { - strings.description = stringsEntry["Description"].asString(); + if (stringsEntryJson.isMember("Description")) { + stringsEntry.description = stringsEntryJson["Description"].asString(); } + strings[language] = stringsEntry; } } } diff --git a/theme.h b/theme.h index 2b4f0c2..d94f4b4 100644 --- a/theme.h +++ b/theme.h @@ -14,6 +14,7 @@ struct ThemeStrings { string name; string description; ThemeStrings(); + ThemeStrings(Json::Value config); }; class Theme { @@ -21,6 +22,7 @@ class Theme { public: static std::set availableThemes; + static std::map themeStrings; static void updateAvailableThemes(); static path themeDir; static const char* prefPath; @@ -29,6 +31,7 @@ class Theme { string file_path; std::map strings; std::set HueEnabledColors; + ThemeStrings GetStrings(); static bool ShowEditor(bool *open, Theme* &theme, ImGuiID dockid, int window_width, int window_height); void Apply(float hue); void Save(string path); diff --git a/translation.h b/translation.h index 4ced31e..531a37b 100644 --- a/translation.h +++ b/translation.h @@ -19,4 +19,5 @@ inline static const char *gettext_ctx(const char *ctx, const char *msgid) { #define _TRIS(icon, str) (std::string(icon) + _TRS(str)) #define _TRI(icon, str) _TRIS(icon, str).c_str() #define _TRIS_CTX(icon, ctx, str) (std::string(icon) + _TRS_CTX(ctx, str)) -#define _TRI_CTX(icon, ctx, str) _TRIS_CTX(icon, ctx, str).c_str() \ No newline at end of file +#define _TRI_CTX(icon, ctx, str) _TRIS_CTX(icon, ctx, str).c_str() +#define CURRENT_LANGUAGE textdomain(NULL) \ No newline at end of file