#include #include #include #include #include #include bool ThemeManager::loadThemesFromDir(std::string path) { if (!std::filesystem::is_directory(path)) { spdlog::error("Theme directory doesn't exist: {0}", path); return false; } themes.clear(); for (const auto & file : std::filesystem::directory_iterator(path)) { std::string _path = file.path().generic_string(); if (file.path().extension().generic_string() != ".json") { continue; } loadTheme(_path); } return true; } bool ThemeManager::loadTheme(std::string path) { if (!std::filesystem::is_regular_file(path)) { spdlog::error("Theme file doesn't exist: {0}", path); return false; } // Load defaults in theme Theme thm; thm.author = "--"; // Load JSON std::ifstream file(path.c_str()); json data; file >> data; file.close(); // Load theme name if (!data.contains("name")) { spdlog::error("Theme {0} is missing the name parameter", path); return false; } if (!data["name"].is_string()) { spdlog::error("Theme {0} contains invalid name field. Expected string", path); return false; } std::string name = data["name"]; // Load theme author if available if (data.contains("author")) { if (!data["author"].is_string()) { spdlog::error("Theme {0} contains invalid author field. Expected string", path); return false; } thm.author = data["author"]; } // Iterate through all parameters and check their contents std::map params = data; for (auto const& [param, val] : params) { if (param == "name" || param == "author") { continue; } bool isValid = false; // If param is a color, check that it's a valid RGBA hex value if (IMGUI_COL_IDS.find(param) != IMGUI_COL_IDS.end()) { if (val[0] != '#' || !std::all_of(val.begin() + 1, val.end(), ::isxdigit) || val.length() != 9) { spdlog::error("Theme {0} contains invalid {1} field. Expected hex RGBA color", path, param); return false; } isValid = true; } if (!isValid) { spdlog::error("Theme {0} contains unknown {1} field.", path, param); return false; } } thm.data = data; themes[name] = thm; return true; } bool ThemeManager::applyTheme(std::string name) { if (themes.find(name) == themes.end()) { spdlog::error("Unknown theme: {0}", name); return false; } ImGui::StyleColorsDark(); auto& style = ImGui::GetStyle(); style.WindowRounding = 0.0f; style.ChildRounding = 0.0f; style.FrameRounding = 0.0f; style.GrabRounding = 0.0f; style.PopupRounding = 0.0f; style.ScrollbarRounding = 0.0f; ImVec4* colors = style.Colors; Theme thm = themes[name]; uint8_t ret[4]; std::map params = thm.data; for (auto const& [param, val] : params) { if (param == "name" || param == "author") { continue; } // If param is a color, check that it's a valid RGBA hex value if (IMGUI_COL_IDS.find(param) != IMGUI_COL_IDS.end()) { decodeRGBA(val, ret); colors[IMGUI_COL_IDS[param]] = ImVec4((float)ret[0]/255.0f, (float)ret[1]/255.0f, (float)ret[2]/255.0f, (float)ret[3]/255.0f); continue; } } return true; } bool ThemeManager::decodeRGBA(std::string str, uint8_t out[4]) { if (str[0] != '#' || !std::all_of(str.begin() + 1, str.end(), ::isxdigit) || str.length() != 9) { return false; } out[0] = std::stoi(str.substr(1, 2), NULL, 16); out[1] = std::stoi(str.substr(3, 2), NULL, 16); out[2] = std::stoi(str.substr(5, 2), NULL, 16); out[3] = std::stoi(str.substr(7, 2), NULL, 16); return true; } std::map ThemeManager::IMGUI_COL_IDS = { {"Text", ImGuiCol_Text}, {"TextDisabled", ImGuiCol_TextDisabled}, {"WindowBg", ImGuiCol_WindowBg}, {"ChildBg", ImGuiCol_ChildBg}, {"PopupBg", ImGuiCol_PopupBg}, {"Border", ImGuiCol_Border}, {"BorderShadow", ImGuiCol_BorderShadow}, {"FrameBg", ImGuiCol_FrameBg}, {"FrameBgHovered", ImGuiCol_FrameBgHovered}, {"FrameBgActive", ImGuiCol_FrameBgActive}, {"TitleBg", ImGuiCol_TitleBg}, {"TitleBgActive", ImGuiCol_TitleBgActive}, {"TitleBgCollapsed", ImGuiCol_TitleBgCollapsed}, {"MenuBarBg", ImGuiCol_MenuBarBg}, {"ScrollbarBg", ImGuiCol_ScrollbarBg}, {"ScrollbarGrab", ImGuiCol_ScrollbarGrab}, {"ScrollbarGrabHovered", ImGuiCol_ScrollbarGrabHovered}, {"ScrollbarGrabActive", ImGuiCol_ScrollbarGrabActive}, {"CheckMark", ImGuiCol_CheckMark}, {"SliderGrab", ImGuiCol_SliderGrab}, {"SliderGrabActive", ImGuiCol_SliderGrabActive}, {"Button", ImGuiCol_Button}, {"ButtonHovered", ImGuiCol_ButtonHovered}, {"ButtonActive", ImGuiCol_ButtonActive}, {"Header", ImGuiCol_Header}, {"HeaderHovered", ImGuiCol_HeaderHovered}, {"HeaderActive", ImGuiCol_HeaderActive}, {"Separator", ImGuiCol_Separator}, {"SeparatorHovered", ImGuiCol_SeparatorHovered}, {"SeparatorActive", ImGuiCol_SeparatorActive}, {"ResizeGrip", ImGuiCol_ResizeGrip}, {"ResizeGripHovered", ImGuiCol_ResizeGripHovered}, {"ResizeGripActive", ImGuiCol_ResizeGripActive}, {"Tab", ImGuiCol_Tab}, {"TabHovered", ImGuiCol_TabHovered}, {"TabActive", ImGuiCol_TabActive}, {"TabUnfocused", ImGuiCol_TabUnfocused}, {"TabUnfocusedActive", ImGuiCol_TabUnfocusedActive}, {"PlotLines", ImGuiCol_PlotLines}, {"PlotLinesHovered", ImGuiCol_PlotLinesHovered}, {"PlotHistogram", ImGuiCol_PlotHistogram}, {"PlotHistogramHovered", ImGuiCol_PlotHistogramHovered}, {"TableHeaderBg", ImGuiCol_TableHeaderBg}, {"TableBorderStrong", ImGuiCol_TableBorderStrong}, {"TableBorderLight", ImGuiCol_TableBorderLight}, {"TableRowBg", ImGuiCol_TableRowBg}, {"TableRowBgAlt", ImGuiCol_TableRowBgAlt}, {"TextSelectedBg", ImGuiCol_TextSelectedBg}, {"DragDropTarget", ImGuiCol_DragDropTarget}, {"NavHighlight", ImGuiCol_NavHighlight}, {"NavWindowingHighlight", ImGuiCol_NavWindowingHighlight}, {"NavWindowingDimBg", ImGuiCol_NavWindowingDimBg}, {"ModalWindowDimBg", ImGuiCol_ModalWindowDimBg} };