diff --git a/core/src/core.cpp b/core/src/core.cpp index f43445c..e0483d0 100644 --- a/core/src/core.cpp +++ b/core/src/core.cpp @@ -119,16 +119,31 @@ int sdrpp_main(int argc, char *argv[]) { defConfig["fullWaterfallUpdate"] = false; defConfig["max"] = 0.0; defConfig["maximized"] = false; - defConfig["menuOrder"] = { - "Source", - "Radio", - "Recorder", - "Sinks", - "Audio", - "Scripting", - "Band Plan", - "Display" - }; + + // Menu + defConfig["menuElements"] = json::array(); + + defConfig["menuElements"][0]["name"] = "Source"; + defConfig["menuElements"][0]["open"] = true; + + defConfig["menuElements"][1]["name"] = "Radio"; + defConfig["menuElements"][1]["open"] = true; + + defConfig["menuElements"][2]["name"] = "Recorder"; + defConfig["menuElements"][2]["open"] = true; + + defConfig["menuElements"][3]["name"] = "Sinks"; + defConfig["menuElements"][3]["open"] = true; + + defConfig["menuElements"][4]["name"] = "Scripting"; + defConfig["menuElements"][4]["open"] = false; + + defConfig["menuElements"][5]["name"] = "Band Plan"; + defConfig["menuElements"][5]["open"] = true; + + defConfig["menuElements"][6]["name"] = "Display"; + defConfig["menuElements"][6]["open"] = true; + defConfig["menuWidth"] = 300; defConfig["min"] = -120.0; @@ -169,14 +184,24 @@ int sdrpp_main(int argc, char *argv[]) { core::configManager.load(defConfig); core::configManager.enableAutoSave(); - // Fix config + core::configManager.aquire(); + // Fix missing elements in config for (auto const& item : defConfig.items()) { if (!core::configManager.conf.contains(item.key())) { spdlog::warn("Missing key in config {0}, repairing", item.key()); core::configManager.conf[item.key()] = defConfig[item.key()]; } } + + // Remove unused elements + auto items = core::configManager.conf.items(); + for (auto const& item : items) { + if (!defConfig.contains(item.key())) { + spdlog::warn("Unused key in config {0}, repairing", item.key()); + core::configManager.conf.erase(item.key()); + } + } core::configManager.release(true); // Setup window diff --git a/core/src/gui/main_window.cpp b/core/src/gui/main_window.cpp index 115f320..9f5f474 100644 --- a/core/src/gui/main_window.cpp +++ b/core/src/gui/main_window.cpp @@ -112,11 +112,24 @@ void windowInit() { credits::init(); core::configManager.aquire(); - gui::menu.order = core::configManager.conf["menuOrder"].get>(); + json menuElements = core::configManager.conf["menuElements"]; std::string modulesDir = core::configManager.conf["modulesDirectory"]; std::string resourcesDir = core::configManager.conf["resourcesDirectory"]; core::configManager.release(); + // Load menu elements + gui::menu.order.clear(); + for (auto& elem : menuElements) { + if (!elem.contains("name")) { spdlog::error("Menu element is missing name key"); continue; } + if (!elem["name"].is_string()) { spdlog::error("Menu element name isn't a string"); continue; } + if (!elem.contains("open")) { spdlog::error("Menu element is missing open key"); continue; } + if (!elem["open"].is_boolean()) { spdlog::error("Menu element name isn't a string"); continue; } + Menu::MenuOption_t opt; + opt.name = elem["name"]; + opt.open = elem["open"]; + gui::menu.order.push_back(opt); + } + gui::menu.registerEntry("Source", sourecmenu::draw, NULL); gui::menu.registerEntry("Sinks", sinkmenu::draw, NULL); gui::menu.registerEntry("Scripting", scriptingmenu::draw, NULL); @@ -541,7 +554,16 @@ void drawWindow() { ImGui::BeginChild("Left Column"); float menuColumnWidth = ImGui::GetContentRegionAvailWidth(); - gui::menu.draw(); + if (gui::menu.draw()) { + core::configManager.aquire(); + json arr = json::array(); + for (int i = 0; i < gui::menu.order.size(); i++) { + arr[i]["name"] = gui::menu.order[i].name; + arr[i]["open"] = gui::menu.order[i].open; + } + core::configManager.conf["menuElements"] = arr; + core::configManager.release(true); + } if(ImGui::CollapsingHeader("Debug")) { ImGui::Text("Frame time: %.3f ms/frame", 1000.0 / ImGui::GetIO().Framerate); diff --git a/core/src/gui/theme_manager.cpp b/core/src/gui/theme_manager.cpp index 5f872c9..df35ed2 100644 --- a/core/src/gui/theme_manager.cpp +++ b/core/src/gui/theme_manager.cpp @@ -5,8 +5,6 @@ #include #include -using nlohmann::json; - bool ThemeManager::loadThemesFromDir(std::string path) { if (!std::filesystem::is_directory(path)) { spdlog::error("Theme directory doesn't exist: {0}", path); @@ -31,6 +29,7 @@ bool ThemeManager::loadTheme(std::string path) { // Load defaults in theme Theme thm; + thm.author = "--"; // Load JSON std::ifstream file(path.c_str()); @@ -38,61 +37,50 @@ bool ThemeManager::loadTheme(std::string path) { file >> data; file.close(); - // Load theme - uint8_t val[4]; - if (data.contains("Text")) { if (decodeRGBA(data["Text"], val)) { thm.Text = ImVec4((float)val[0]/255.0f, (float)val[1]/255.0f, (float)val[2]/255.0f, (float)val[3]/255.0f); } } - if (data.contains("TextDisabled")) { if (decodeRGBA(data["TextDisabled"], val)) { thm.TextDisabled = ImVec4((float)val[0]/255.0f, (float)val[1]/255.0f, (float)val[2]/255.0f, (float)val[3]/255.0f); } } - if (data.contains("WindowBg")) { if (decodeRGBA(data["WindowBg"], val)) { thm.WindowBg = ImVec4((float)val[0]/255.0f, (float)val[1]/255.0f, (float)val[2]/255.0f, (float)val[3]/255.0f); } } - if (data.contains("ChildBg")) { if (decodeRGBA(data["ChildBg"], val)) { thm.ChildBg = ImVec4((float)val[0]/255.0f, (float)val[1]/255.0f, (float)val[2]/255.0f, (float)val[3]/255.0f); } } - if (data.contains("PopupBg")) { if (decodeRGBA(data["PopupBg"], val)) { thm.PopupBg = ImVec4((float)val[0]/255.0f, (float)val[1]/255.0f, (float)val[2]/255.0f, (float)val[3]/255.0f); } } - if (data.contains("Border")) { if (decodeRGBA(data["Border"], val)) { thm.Border = ImVec4((float)val[0]/255.0f, (float)val[1]/255.0f, (float)val[2]/255.0f, (float)val[3]/255.0f); } } - if (data.contains("BorderShadow")) { if (decodeRGBA(data["BorderShadow"], val)) { thm.BorderShadow = ImVec4((float)val[0]/255.0f, (float)val[1]/255.0f, (float)val[2]/255.0f, (float)val[3]/255.0f); } } - if (data.contains("FrameBg")) { if (decodeRGBA(data["FrameBg"], val)) { thm.FrameBg = ImVec4((float)val[0]/255.0f, (float)val[1]/255.0f, (float)val[2]/255.0f, (float)val[3]/255.0f); } } - if (data.contains("FrameBgHovered")) { if (decodeRGBA(data["FrameBgHovered"], val)) { thm.FrameBgHovered = ImVec4((float)val[0]/255.0f, (float)val[1]/255.0f, (float)val[2]/255.0f, (float)val[3]/255.0f); } } - if (data.contains("FrameBgActive")) { if (decodeRGBA(data["FrameBgActive"], val)) { thm.FrameBgActive = ImVec4((float)val[0]/255.0f, (float)val[1]/255.0f, (float)val[2]/255.0f, (float)val[3]/255.0f); } } - if (data.contains("TitleBg")) { if (decodeRGBA(data["TitleBg"], val)) { thm.TitleBg = ImVec4((float)val[0]/255.0f, (float)val[1]/255.0f, (float)val[2]/255.0f, (float)val[3]/255.0f); } } - if (data.contains("TitleBgActive")) { if (decodeRGBA(data["TitleBgActive"], val)) { thm.TitleBgActive = ImVec4((float)val[0]/255.0f, (float)val[1]/255.0f, (float)val[2]/255.0f, (float)val[3]/255.0f); } } - if (data.contains("TitleBgCollapsed")) { if (decodeRGBA(data["TitleBgCollapsed"], val)) { thm.TitleBgCollapsed = ImVec4((float)val[0]/255.0f, (float)val[1]/255.0f, (float)val[2]/255.0f, (float)val[3]/255.0f); } } - if (data.contains("MenuBarBg")) { if (decodeRGBA(data["MenuBarBg"], val)) { thm.MenuBarBg = ImVec4((float)val[0]/255.0f, (float)val[1]/255.0f, (float)val[2]/255.0f, (float)val[3]/255.0f); } } - if (data.contains("ScrollbarBg")) { if (decodeRGBA(data["ScrollbarBg"], val)) { thm.ScrollbarBg = ImVec4((float)val[0]/255.0f, (float)val[1]/255.0f, (float)val[2]/255.0f, (float)val[3]/255.0f); } } - if (data.contains("ScrollbarGrab")) { if (decodeRGBA(data["ScrollbarGrab"], val)) { thm.ScrollbarGrab = ImVec4((float)val[0]/255.0f, (float)val[1]/255.0f, (float)val[2]/255.0f, (float)val[3]/255.0f); } } - if (data.contains("ScrollbarGrabHovered")) { if (decodeRGBA(data["ScrollbarGrabHovered"], val)) { thm.ScrollbarGrabHovered = ImVec4((float)val[0]/255.0f, (float)val[1]/255.0f, (float)val[2]/255.0f, (float)val[3]/255.0f); } } - if (data.contains("ScrollbarGrabActive")) { if (decodeRGBA(data["ScrollbarGrabActive"], val)) { thm.ScrollbarGrabActive = ImVec4((float)val[0]/255.0f, (float)val[1]/255.0f, (float)val[2]/255.0f, (float)val[3]/255.0f); } } - if (data.contains("CheckMark")) { if (decodeRGBA(data["CheckMark"], val)) { thm.CheckMark = ImVec4((float)val[0]/255.0f, (float)val[1]/255.0f, (float)val[2]/255.0f, (float)val[3]/255.0f); } } - if (data.contains("SliderGrab")) { if (decodeRGBA(data["SliderGrab"], val)) { thm.SliderGrab = ImVec4((float)val[0]/255.0f, (float)val[1]/255.0f, (float)val[2]/255.0f, (float)val[3]/255.0f); } } - if (data.contains("TeSliderGrabActivext")) { if (decodeRGBA(data["SliderGrabActive"], val)) { thm.SliderGrabActive = ImVec4((float)val[0]/255.0f, (float)val[1]/255.0f, (float)val[2]/255.0f, (float)val[3]/255.0f); } } - if (data.contains("Button")) { if (decodeRGBA(data["Button"], val)) { thm.Button = ImVec4((float)val[0]/255.0f, (float)val[1]/255.0f, (float)val[2]/255.0f, (float)val[3]/255.0f); } } - if (data.contains("ButtonHovered")) { if (decodeRGBA(data["ButtonHovered"], val)) { thm.ButtonHovered = ImVec4((float)val[0]/255.0f, (float)val[1]/255.0f, (float)val[2]/255.0f, (float)val[3]/255.0f); } } - if (data.contains("ButtonActive")) { if (decodeRGBA(data["ButtonActive"], val)) { thm.ButtonActive = ImVec4((float)val[0]/255.0f, (float)val[1]/255.0f, (float)val[2]/255.0f, (float)val[3]/255.0f); } } - if (data.contains("Header")) { if (decodeRGBA(data["Header"], val)) { thm.Header = ImVec4((float)val[0]/255.0f, (float)val[1]/255.0f, (float)val[2]/255.0f, (float)val[3]/255.0f); } } - if (data.contains("HeaderHovered")) { if (decodeRGBA(data["HeaderHovered"], val)) { thm.HeaderHovered = ImVec4((float)val[0]/255.0f, (float)val[1]/255.0f, (float)val[2]/255.0f, (float)val[3]/255.0f); } } - if (data.contains("HeaderActive")) { if (decodeRGBA(data["HeaderActive"], val)) { thm.HeaderActive = ImVec4((float)val[0]/255.0f, (float)val[1]/255.0f, (float)val[2]/255.0f, (float)val[3]/255.0f); } } - if (data.contains("Separator")) { if (decodeRGBA(data["Separator"], val)) { thm.Separator = ImVec4((float)val[0]/255.0f, (float)val[1]/255.0f, (float)val[2]/255.0f, (float)val[3]/255.0f); } } - if (data.contains("SeparatorHovered")) { if (decodeRGBA(data["SeparatorHovered"], val)) { thm.SeparatorHovered = ImVec4((float)val[0]/255.0f, (float)val[1]/255.0f, (float)val[2]/255.0f, (float)val[3]/255.0f); } } - if (data.contains("SeparatorActive")) { if (decodeRGBA(data["SeparatorActive"], val)) { thm.SeparatorActive = ImVec4((float)val[0]/255.0f, (float)val[1]/255.0f, (float)val[2]/255.0f, (float)val[3]/255.0f); } } - if (data.contains("ResizeGrip")) { if (decodeRGBA(data["ResizeGrip"], val)) { thm.ResizeGrip = ImVec4((float)val[0]/255.0f, (float)val[1]/255.0f, (float)val[2]/255.0f, (float)val[3]/255.0f); } } - if (data.contains("ResizeGripHovered")) { if (decodeRGBA(data["ResizeGripHovered"], val)) { thm.ResizeGripHovered = ImVec4((float)val[0]/255.0f, (float)val[1]/255.0f, (float)val[2]/255.0f, (float)val[3]/255.0f); } } - if (data.contains("ResizeGripActive")) { if (decodeRGBA(data["ResizeGripActive"], val)) { thm.ResizeGripActive = ImVec4((float)val[0]/255.0f, (float)val[1]/255.0f, (float)val[2]/255.0f, (float)val[3]/255.0f); } } - if (data.contains("Tab")) { if (decodeRGBA(data["Tab"], val)) { thm.Tab = ImVec4((float)val[0]/255.0f, (float)val[1]/255.0f, (float)val[2]/255.0f, (float)val[3]/255.0f); } } - if (data.contains("TabHovered")) { if (decodeRGBA(data["TabHovered"], val)) { thm.TabHovered = ImVec4((float)val[0]/255.0f, (float)val[1]/255.0f, (float)val[2]/255.0f, (float)val[3]/255.0f); } } - if (data.contains("TabActive")) { if (decodeRGBA(data["TabActive"], val)) { thm.TabActive = ImVec4((float)val[0]/255.0f, (float)val[1]/255.0f, (float)val[2]/255.0f, (float)val[3]/255.0f); } } - if (data.contains("TabUnfocused")) { if (decodeRGBA(data["TabUnfocused"], val)) { thm.TabUnfocused = ImVec4((float)val[0]/255.0f, (float)val[1]/255.0f, (float)val[2]/255.0f, (float)val[3]/255.0f); } } - if (data.contains("TabUnfocusedActive")) { if (decodeRGBA(data["TabUnfocusedActive"], val)) { thm.TabUnfocusedActive = ImVec4((float)val[0]/255.0f, (float)val[1]/255.0f, (float)val[2]/255.0f, (float)val[3]/255.0f); } } - if (data.contains("PlotLines")) { if (decodeRGBA(data["PlotLines"], val)) { thm.PlotLines = ImVec4((float)val[0]/255.0f, (float)val[1]/255.0f, (float)val[2]/255.0f, (float)val[3]/255.0f); } } - if (data.contains("PlotLinesHovered")) { if (decodeRGBA(data["PlotLinesHovered"], val)) { thm.PlotLinesHovered = ImVec4((float)val[0]/255.0f, (float)val[1]/255.0f, (float)val[2]/255.0f, (float)val[3]/255.0f); } } - if (data.contains("PlotHistogram")) { if (decodeRGBA(data["PlotHistogram"], val)) { thm.PlotHistogram = ImVec4((float)val[0]/255.0f, (float)val[1]/255.0f, (float)val[2]/255.0f, (float)val[3]/255.0f); } } - if (data.contains("PlotHistogramHovered")) { if (decodeRGBA(data["PlotHistogramHovered"], val)) { thm.PlotHistogramHovered = ImVec4((float)val[0]/255.0f, (float)val[1]/255.0f, (float)val[2]/255.0f, (float)val[3]/255.0f); } } - if (data.contains("TableHeaderBg")) { if (decodeRGBA(data["TableHeaderBg"], val)) { thm.TableHeaderBg = ImVec4((float)val[0]/255.0f, (float)val[1]/255.0f, (float)val[2]/255.0f, (float)val[3]/255.0f); } } - if (data.contains("TableBorderStrong")) { if (decodeRGBA(data["TableBorderStrong"], val)) { thm.TableBorderStrong = ImVec4((float)val[0]/255.0f, (float)val[1]/255.0f, (float)val[2]/255.0f, (float)val[3]/255.0f); } } - if (data.contains("TableBorderLight")) { if (decodeRGBA(data["TableBorderLight"], val)) { thm.TableBorderLight = ImVec4((float)val[0]/255.0f, (float)val[1]/255.0f, (float)val[2]/255.0f, (float)val[3]/255.0f); } } - if (data.contains("TableRowBg")) { if (decodeRGBA(data["TableRowBg"], val)) { thm.TableRowBg = ImVec4((float)val[0]/255.0f, (float)val[1]/255.0f, (float)val[2]/255.0f, (float)val[3]/255.0f); } } - if (data.contains("TableRowBgAlt")) { if (decodeRGBA(data["TableRowBgAlt"], val)) { thm.TableRowBgAlt = ImVec4((float)val[0]/255.0f, (float)val[1]/255.0f, (float)val[2]/255.0f, (float)val[3]/255.0f); } } - if (data.contains("TextSelectedBg")) { if (decodeRGBA(data["TextSelectedBg"], val)) { thm.TextSelectedBg = ImVec4((float)val[0]/255.0f, (float)val[1]/255.0f, (float)val[2]/255.0f, (float)val[3]/255.0f); } } - if (data.contains("DragDropTarget")) { if (decodeRGBA(data["DragDropTarget"], val)) { thm.DragDropTarget = ImVec4((float)val[0]/255.0f, (float)val[1]/255.0f, (float)val[2]/255.0f, (float)val[3]/255.0f); } } - if (data.contains("NavHighlight")) { if (decodeRGBA(data["NavHighlight"], val)) { thm.NavHighlight = ImVec4((float)val[0]/255.0f, (float)val[1]/255.0f, (float)val[2]/255.0f, (float)val[3]/255.0f); } } - if (data.contains("NavWindowingHighlight")) { if (decodeRGBA(data["NavWindowingHighlight"], val)) { thm.NavWindowingHighlight = ImVec4((float)val[0]/255.0f, (float)val[1]/255.0f, (float)val[2]/255.0f, (float)val[3]/255.0f); } } - if (data.contains("NavWindowingDimBg")) { if (decodeRGBA(data["NavWindowingDimBg"], val)) { thm.NavWindowingDimBg = ImVec4((float)val[0]/255.0f, (float)val[1]/255.0f, (float)val[2]/255.0f, (float)val[3]/255.0f); } } - if (data.contains("ModalWindowDimBg")) { if (decodeRGBA(data["ModalWindowDimBg"], val)) { thm.ModalWindowDimBg = ImVec4((float)val[0]/255.0f, (float)val[1]/255.0f, (float)val[2]/255.0f, (float)val[3]/255.0f); } } + // 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; } @@ -103,62 +91,32 @@ bool ThemeManager::applyTheme(std::string 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]; - colors[ImGuiCol_Text] = thm.Text; - colors[ImGuiCol_TextDisabled] = thm.TextDisabled; - colors[ImGuiCol_WindowBg] = thm.WindowBg; - colors[ImGuiCol_ChildBg] = thm.ChildBg; - colors[ImGuiCol_PopupBg] = thm.PopupBg; - colors[ImGuiCol_Border] = thm.Border; - colors[ImGuiCol_BorderShadow] = thm.BorderShadow; - colors[ImGuiCol_FrameBg] = thm.FrameBg; - colors[ImGuiCol_FrameBgHovered] = thm.FrameBgHovered; - colors[ImGuiCol_FrameBgActive] = thm.FrameBgActive; - colors[ImGuiCol_TitleBg] = thm.TitleBg; - colors[ImGuiCol_TitleBgActive] = thm.TitleBgActive; - colors[ImGuiCol_TitleBgCollapsed] = thm.TitleBgCollapsed; - colors[ImGuiCol_MenuBarBg] = thm.MenuBarBg; - colors[ImGuiCol_ScrollbarBg] = thm.ScrollbarBg; - colors[ImGuiCol_ScrollbarGrab] = thm.ScrollbarGrab; - colors[ImGuiCol_ScrollbarGrabHovered] = thm.ScrollbarGrabHovered; - colors[ImGuiCol_ScrollbarGrabActive] = thm.ScrollbarGrabActive; - colors[ImGuiCol_CheckMark] = thm.CheckMark; - colors[ImGuiCol_SliderGrab] = thm.SliderGrab; - colors[ImGuiCol_SliderGrabActive] = thm.SliderGrabActive; - colors[ImGuiCol_Button] = thm.Button; - colors[ImGuiCol_ButtonHovered] = thm.ButtonHovered; - colors[ImGuiCol_ButtonActive] = thm.ButtonActive; - colors[ImGuiCol_Header] = thm.Header; - colors[ImGuiCol_HeaderHovered] = thm.HeaderHovered; - colors[ImGuiCol_HeaderActive] = thm.HeaderActive; - colors[ImGuiCol_Separator] = thm.Separator; - colors[ImGuiCol_SeparatorHovered] = thm.SeparatorHovered; - colors[ImGuiCol_SeparatorActive] = thm.SeparatorActive; - colors[ImGuiCol_ResizeGrip] = thm.ResizeGrip; - colors[ImGuiCol_ResizeGripHovered] = thm.ResizeGripHovered; - colors[ImGuiCol_ResizeGripActive] = thm.ResizeGripActive; - colors[ImGuiCol_Tab] = thm.Tab; - colors[ImGuiCol_TabHovered] = thm.TabHovered; - colors[ImGuiCol_TabActive] = thm.TabActive; - colors[ImGuiCol_TabUnfocused] = thm.TabUnfocused; - colors[ImGuiCol_TabUnfocusedActive] = thm.TabUnfocusedActive; - colors[ImGuiCol_PlotLines] = thm.PlotLines; - colors[ImGuiCol_PlotLinesHovered] = thm.PlotLinesHovered; - colors[ImGuiCol_PlotHistogram] = thm.PlotHistogram; - colors[ImGuiCol_PlotHistogramHovered] = thm.PlotHistogramHovered; - colors[ImGuiCol_TableHeaderBg] = thm.TableHeaderBg; - colors[ImGuiCol_TableBorderStrong] = thm.TableBorderStrong; - colors[ImGuiCol_TableBorderLight] = thm.TableBorderLight; - colors[ImGuiCol_TableRowBg] = thm.TableRowBg; - colors[ImGuiCol_TableRowBgAlt] = thm.TableRowBgAlt; - colors[ImGuiCol_TextSelectedBg] = thm.TextSelectedBg; - colors[ImGuiCol_DragDropTarget] = thm.DragDropTarget; - colors[ImGuiCol_NavHighlight] = thm.NavHighlight; - colors[ImGuiCol_NavWindowingHighlight] = thm.NavWindowingHighlight; - colors[ImGuiCol_NavWindowingDimBg] = thm.NavWindowingDimBg; - colors[ImGuiCol_ModalWindowDimBg] = thm.ModalWindowDimBg; + + 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; } @@ -172,4 +130,60 @@ bool ThemeManager::decodeRGBA(std::string str, uint8_t out[4]) { out[2] = std::stoi(str.substr(5, 2), NULL, 16); out[3] = std::stoi(str.substr(7, 2), NULL, 16); return true; -} \ No newline at end of file +} + +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} +}; \ No newline at end of file diff --git a/core/src/gui/theme_manager.h b/core/src/gui/theme_manager.h index 41a6159..d3d46c0 100644 --- a/core/src/gui/theme_manager.h +++ b/core/src/gui/theme_manager.h @@ -3,62 +3,13 @@ #include #include #include +#include + +using nlohmann::json; struct Theme { std::string author; - ImVec4 Text; - ImVec4 TextDisabled; - ImVec4 WindowBg; - ImVec4 ChildBg; - ImVec4 PopupBg; - ImVec4 Border; - ImVec4 BorderShadow; - ImVec4 FrameBg; - ImVec4 FrameBgHovered; - ImVec4 FrameBgActive; - ImVec4 TitleBg; - ImVec4 TitleBgActive; - ImVec4 TitleBgCollapsed; - ImVec4 MenuBarBg; - ImVec4 ScrollbarBg; - ImVec4 ScrollbarGrab; - ImVec4 ScrollbarGrabHovered; - ImVec4 ScrollbarGrabActive; - ImVec4 CheckMark; - ImVec4 SliderGrab; - ImVec4 SliderGrabActive; - ImVec4 Button; - ImVec4 ButtonHovered; - ImVec4 ButtonActive; - ImVec4 Header; - ImVec4 HeaderHovered; - ImVec4 HeaderActive; - ImVec4 Separator; - ImVec4 SeparatorHovered; - ImVec4 SeparatorActive; - ImVec4 ResizeGrip; - ImVec4 ResizeGripHovered; - ImVec4 ResizeGripActive; - ImVec4 Tab; - ImVec4 TabHovered; - ImVec4 TabActive; - ImVec4 TabUnfocused; - ImVec4 TabUnfocusedActive; - ImVec4 PlotLines; - ImVec4 PlotLinesHovered; - ImVec4 PlotHistogram; - ImVec4 PlotHistogramHovered; - ImVec4 TableHeaderBg; - ImVec4 TableBorderStrong; - ImVec4 TableBorderLight; - ImVec4 TableRowBg; - ImVec4 TableRowBgAlt; - ImVec4 TextSelectedBg; - ImVec4 DragDropTarget; - ImVec4 NavHighlight; - ImVec4 NavWindowingHighlight; - ImVec4 NavWindowingDimBg; - ImVec4 ModalWindowDimBg; + json data; }; class ThemeManager { @@ -72,6 +23,8 @@ public: private: static bool decodeRGBA(std::string str, uint8_t out[4]); + static std::map IMGUI_COL_IDS; + std::map themes; }; \ No newline at end of file diff --git a/core/src/gui/widgets/menu.cpp b/core/src/gui/widgets/menu.cpp index bd54ac4..e73653d 100644 --- a/core/src/gui/widgets/menu.cpp +++ b/core/src/gui/widgets/menu.cpp @@ -13,7 +13,10 @@ void Menu::registerEntry(std::string name, void (*drawHandler)(void* ctx), void* item.inst = inst; items[name] = item; if (!isInOrderList(name)) { - order.push_back(name); + MenuOption_t opt; + opt.name = name; + opt.open = true; + order.push_back(opt); } } @@ -21,35 +24,40 @@ void Menu::removeEntry(std::string name) { items.erase(name); } -void Menu::draw() { - MenuItem_t item; +bool Menu::draw() { + bool changed = false; float menuWidth = ImGui::GetContentRegionAvailWidth(); ImGuiWindow* window = ImGui::GetCurrentWindow(); - for (std::string name : order) { - if (items.find(name) == items.end()) { + for (MenuOption_t& opt : order) { + if (items.find(opt.name) == items.end()) { continue; } - item = items[name]; + MenuItem_t& item = items[opt.name]; ImRect orginalRect = window->WorkRect; if (item.inst != NULL) { - window->WorkRect = ImRect(orginalRect.Min, ImVec2(orginalRect.Max.x - ImGui::GetTextLineHeight() - 6, orginalRect.Max.y)); } - if (ImGui::CollapsingHeader(name.c_str(), ImGuiTreeNodeFlags_DefaultOpen)) { + if (ImGui::CollapsingHeader(opt.name.c_str(), opt.open ? ImGuiTreeNodeFlags_DefaultOpen : 0)) { if (item.inst != NULL) { window->WorkRect = orginalRect; ImVec2 pos = ImGui::GetCursorPos(); ImGui::SetCursorPosX(pos.x + menuWidth - ImGui::GetTextLineHeight() - 6); ImGui::SetCursorPosY(pos.y - 10 - ImGui::GetTextLineHeight()); bool enabled = item.inst->isEnabled(); - if (ImGui::Checkbox(("##_menu_checkbox_" + name).c_str(), &enabled)) { + if (ImGui::Checkbox(("##_menu_checkbox_" + opt.name).c_str(), &enabled)) { enabled ? item.inst->enable() : item.inst->disable(); } ImGui::SetCursorPos(pos); } + // Check if the state changed + if (!opt.open) { + opt.open = true; + changed = true; + } + item.drawHandler(item.ctx); ImGui::Spacing(); } @@ -59,17 +67,27 @@ void Menu::draw() { ImGui::SetCursorPosX(pos.x + menuWidth - ImGui::GetTextLineHeight() - 6); ImGui::SetCursorPosY(pos.y - 10 - ImGui::GetTextLineHeight()); bool enabled = item.inst->isEnabled(); - if (ImGui::Checkbox(("##_menu_checkbox_" + name).c_str(), &enabled)) { + if (ImGui::Checkbox(("##_menu_checkbox_" + opt.name).c_str(), &enabled)) { enabled ? item.inst->enable() : item.inst->disable(); } ImGui::SetCursorPos(pos); + + if (opt.open) { + opt.open = false; + changed = true; + } + } + else if (opt.open) { + opt.open = false; + changed = true; } } + return changed; } bool Menu::isInOrderList(std::string name) { - for (std::string _name : order) { - if (_name == name) { + for (MenuOption_t opt : order) { + if (opt.name == name) { return true; } } diff --git a/core/src/gui/widgets/menu.h b/core/src/gui/widgets/menu.h index 24622a7..7f338e8 100644 --- a/core/src/gui/widgets/menu.h +++ b/core/src/gui/widgets/menu.h @@ -8,6 +8,11 @@ class Menu { public: Menu(); + struct MenuOption_t { + std::string name; + bool open; + }; + struct MenuItem_t { void (*drawHandler)(void* ctx); void* ctx; @@ -16,9 +21,9 @@ public: void registerEntry(std::string name, void (*drawHandler)(void* ctx), void* ctx = NULL, ModuleManager::Instance* inst = NULL); void removeEntry(std::string name); - void draw(); + bool draw(); - std::vector order; + std::vector order; private: bool isInOrderList(std::string name);