From 7fe9a768d46b4d851b5861a5b6b72384ff0eed24 Mon Sep 17 00:00:00 2001 From: Nik Date: Sat, 21 Oct 2023 23:07:33 +0200 Subject: [PATCH] impr: Rewrote entire settings API and UI (#1378) --- .../include/hex/api/content_registry.hpp | 322 ++++++--- lib/libimhex/source/api/content_registry.cpp | 339 ++++++--- main/gui/source/init/tasks.cpp | 44 +- main/gui/source/window/window.cpp | 2 +- .../popups/popup_telemetry_request.hpp | 2 +- plugins/builtin/romfs/lang/de_DE.json | 2 +- plugins/builtin/romfs/lang/en_US.json | 8 +- plugins/builtin/romfs/lang/es_ES.json | 2 +- plugins/builtin/romfs/lang/it_IT.json | 2 +- plugins/builtin/romfs/lang/ja_JP.json | 2 +- plugins/builtin/romfs/lang/ko_KR.json | 2 +- plugins/builtin/romfs/lang/pt_BR.json | 2 +- plugins/builtin/romfs/lang/zh_CN.json | 2 +- plugins/builtin/romfs/lang/zh_TW.json | 2 +- .../source/content/background_services.cpp | 2 +- plugins/builtin/source/content/recent.cpp | 4 +- .../source/content/settings_entries.cpp | 683 +++++++----------- .../builtin/source/content/tools_entries.cpp | 2 +- .../content/views/view_achievements.cpp | 4 +- .../content/views/view_pattern_editor.cpp | 10 +- .../source/content/views/view_settings.cpp | 106 +-- .../builtin/source/content/welcome_screen.cpp | 12 +- plugins/builtin/source/plugin_builtin.cpp | 2 +- plugins/builtin/source/ui/hex_editor.cpp | 6 +- .../source/content/settings_entries.cpp | 29 +- plugins/windows/source/plugin_windows.cpp | 6 +- 26 files changed, 818 insertions(+), 781 deletions(-) diff --git a/lib/libimhex/include/hex/api/content_registry.hpp b/lib/libimhex/include/hex/api/content_registry.hpp index b676f82bc..e7530f799 100644 --- a/lib/libimhex/include/hex/api/content_registry.hpp +++ b/lib/libimhex/include/hex/api/content_registry.hpp @@ -14,16 +14,18 @@ #include #include #include +#include #include #if defined(OS_WEB) #include #endif -#include +#include using ImGuiDataType = int; using ImGuiInputTextFlags = int; +struct ImColor; namespace hex { @@ -47,127 +49,237 @@ namespace hex { /* Settings Registry. Allows adding of new entries into the ImHex preferences window. */ namespace Settings { + namespace Widgets { + + class Widget { + public: + virtual ~Widget() = default; + + virtual bool draw(const std::string &name) = 0; + + virtual void load(const nlohmann::json &data) = 0; + virtual nlohmann::json store() = 0; + + class Interface { + public: + friend class Widget; + + Interface& requiresRestart() { + this->m_requiresRestart = true; + + return *this; + } + + Interface& setEnabledCallback(std::function callback) { + this->m_enabledCallback = std::move(callback); + + return *this; + } + + Interface& setChangedCallback(std::function callback) { + this->m_changedCallback = std::move(callback); + + return *this; + } + + Interface& setTooltip(const std::string &tooltip) { + this->m_tooltip = tooltip; + + return *this; + } + + [[nodiscard]] + Widget& getWidget() const { + return *this->m_widget; + } + + private: + explicit Interface(Widget *widget) : m_widget(widget) {} + Widget *m_widget; + + bool m_requiresRestart = false; + std::function m_enabledCallback; + std::function m_changedCallback; + std::optional m_tooltip; + }; + + [[nodiscard]] + bool doesRequireRestart() const { + return this->m_interface.m_requiresRestart; + } + + [[nodiscard]] + bool isEnabled() const { + return !this->m_interface.m_enabledCallback || this->m_interface.m_enabledCallback(); + } + + [[nodiscard]] + const std::optional& getTooltip() const { + return this->m_interface.m_tooltip; + } + + void onChanged() { + if (this->m_interface.m_changedCallback) + this->m_interface.m_changedCallback(*this); + } + + [[nodiscard]] + Interface& getInterface() { + return this->m_interface; + } + + private: + Interface m_interface = Interface(this); + }; + + class Checkbox : public Widget { + public: + explicit Checkbox(bool defaultValue) : m_value(defaultValue) { } + + bool draw(const std::string &name) override; + + void load(const nlohmann::json &data) override; + nlohmann::json store() override; + + [[nodiscard]] bool isChecked() const { return this->m_value; } + + private: + bool m_value; + }; + class SliderInteger : public Widget { + public: + SliderInteger(i32 defaultValue, i32 min, i32 max) : m_value(defaultValue), m_min(min), m_max(max) { } + bool draw(const std::string &name) override; + + void load(const nlohmann::json &data) override; + nlohmann::json store() override; + + [[nodiscard]] i32 getValue() const { return this->m_value; } + + private: + int m_value; + i32 m_min, m_max; + }; + class SliderFloat : public Widget { + public: + SliderFloat(float defaultValue, float min, float max) : m_value(defaultValue), m_min(min), m_max(max) { } + bool draw(const std::string &name) override; + + void load(const nlohmann::json &data) override; + nlohmann::json store() override; + + [[nodiscard]] float getValue() const { return this->m_value; } + + private: + float m_value; + float m_min, m_max; + }; + class ColorPicker : public Widget { + public: + explicit ColorPicker(ImColor defaultColor); + + bool draw(const std::string &name) override; + + void load(const nlohmann::json &data) override; + nlohmann::json store() override; + + [[nodiscard]] ImColor getColor() const; + + private: + std::array m_value; + }; + class DropDown : public Widget { + public: + explicit DropDown(const std::vector &items, const std::vector &settingsValues) : m_items(items), m_settingsValues(settingsValues) { } + + bool draw(const std::string &name) override; + + void load(const nlohmann::json &data) override; + nlohmann::json store() override; + + [[nodiscard]] + const nlohmann::json& getValue() const; + + private: + std::vector m_items; + std::vector m_settingsValues; + int m_value = 0; + }; + class TextBox : public Widget { + public: + explicit TextBox(std::string defaultValue) : m_value(std::move(defaultValue)) { } + + bool draw(const std::string &name) override; + + void load(const nlohmann::json &data) override; + nlohmann::json store() override; + + [[nodiscard]] + const std::string& getValue() const { return this->m_value; } + + private: + std::string m_value; + }; + class FilePicker : public Widget { + public: + bool draw(const std::string &name) override; + + void load(const nlohmann::json &data) override; + nlohmann::json store() override; + + [[nodiscard]] + std::fs::path getPath() const { return this->m_value; } + + private: + std::string m_value; + }; + + + } + namespace impl { - using Callback = std::function; struct Entry { - std::string name; - bool requiresRestart; - Callback callback; + std::string unlocalizedName; + std::unique_ptr widget; + }; + + struct SubCategory { + std::string unlocalizedName; + std::vector entries; }; struct Category { - std::string name; - size_t slot = 0; - - bool operator<(const Category &other) const { - return name < other.name; - } - - explicit operator const std::string &() const { - return name; - } + std::string unlocalizedName; + std::string unlocalizedDescription; + std::vector subCategories; }; void load(); void store(); void clear(); - std::map> &getEntries(); - std::map &getCategoryDescriptions(); - nlohmann::json getSetting(const std::string &unlocalizedCategory, const std::string &unlocalizedName); + std::vector &getSettings(); + nlohmann::json& getSetting(const std::string &unlocalizedCategory, const std::string &unlocalizedName, const nlohmann::json &defaultValue); nlohmann::json &getSettingsData(); + + Widgets::Widget* add(const std::string &unlocalizedCategory, const std::string &unlocalizedSubCategory, const std::string &unlocalizedName, std::unique_ptr &&widget); } + template T> + Widgets::Widget::Interface& add(const std::string &unlocalizedCategory, const std::string &unlocalizedSubCategory, const std::string &unlocalizedName, auto && ... args) { + return impl::add( + unlocalizedCategory, + unlocalizedSubCategory, + unlocalizedName, + std::make_unique(std::forward(args)...) + )->getInterface(); + } - /** - * @brief Adds a new integer setting entry - * @param unlocalizedCategory The category of the setting - * @param unlocalizedName The name of the setting - * @param defaultValue The default value of the setting - * @param callback The callback that will be called when the settings item in the preferences window is rendered - * @param requiresRestart Whether the setting requires a restart to take effect - */ - void add(const std::string &unlocalizedCategory, const std::string &unlocalizedName, i64 defaultValue, const impl::Callback &callback, bool requiresRestart = false); - - /** - * @brief Adds a new string setting entry - * @param unlocalizedCategory The category of the setting - * @param unlocalizedName The name of the setting - * @param defaultValue The default value of the setting - * @param callback The callback that will be called when the settings item in the preferences window is rendered - * @param requiresRestart Whether the setting requires a restart to take effect - */ - void add(const std::string &unlocalizedCategory, const std::string &unlocalizedName, const std::string &defaultValue, const impl::Callback &callback, bool requiresRestart = false); - - /** - * @brief Adds a new string list setting entry - * @param unlocalizedCategory The category of the setting - * @param unlocalizedName The name of the setting - * @param defaultValue The default value of the setting - * @param callback The callback that will be called when the settings item in the preferences window is rendered - * @param requiresRestart Whether the setting requires a restart to take effect - */ - void add(const std::string &unlocalizedCategory, const std::string &unlocalizedName, const std::vector &defaultValue, const impl::Callback &callback, bool requiresRestart = false); - - /** - * @brief Adds a description to a given category - * @param unlocalizedCategory The name of the category - * @param unlocalizedCategoryDescription The description of the category - */ - void addCategoryDescription(const std::string &unlocalizedCategory, const std::string &unlocalizedCategoryDescription); - - /** - * @brief Writes a integer value to the settings file - * @param unlocalizedCategory The category of the setting - * @param unlocalizedName The name of the setting - * @param value The value to write - */ - void write(const std::string &unlocalizedCategory, const std::string &unlocalizedName, i64 value); - - /** - * @brief Writes a string value to the settings file - * @param unlocalizedCategory The category of the setting - * @param unlocalizedName The name of the setting - * @param value The value to write - */ - void write(const std::string &unlocalizedCategory, const std::string &unlocalizedName, const std::string &value); - - /** - * @brief Writes a string list value to the settings file - * @param unlocalizedCategory The category of the setting - * @param unlocalizedName The name of the setting - * @param value The value to write - */ - void write(const std::string &unlocalizedCategory, const std::string &unlocalizedName, const std::vector &value); - - /** - * @brief Reads an integer value from the settings file - * @param unlocalizedCategory The category of the setting - * @param unlocalizedName The name of the setting - * @param defaultValue The default value of the setting - * @return The value of the setting - */ - i64 read(const std::string &unlocalizedCategory, const std::string &unlocalizedName, i64 defaultValue); - - /** - * @brief Reads a string value from the settings file - * @param unlocalizedCategory The category of the setting - * @param unlocalizedName The name of the setting - * @param defaultValue The default value of the setting - * @return The value of the setting - */ - std::string read(const std::string &unlocalizedCategory, const std::string &unlocalizedName, const std::string &defaultValue); - - /** - * @brief Reads a string list value from the settings file - * @param unlocalizedCategory The category of the setting - * @param unlocalizedName The name of the setting - * @param defaultValue The default value of the setting - * @return The value of the setting - */ - std::vector read(const std::string &unlocalizedCategory, const std::string &unlocalizedName, const std::vector &defaultValue = {}); + void setCategoryDescription(const std::string &unlocalizedCategory, const std::string &unlocalizedDescription); + nlohmann::json read(const std::string &unlocalizedCategory, const std::string &unlocalizedName, const nlohmann::json &defaultValue); + void write(const std::string &unlocalizedCategory, const std::string &unlocalizedName, const nlohmann::json &value); } /* Command Palette Command Registry. Allows adding of new commands to the command palette */ diff --git a/lib/libimhex/source/api/content_registry.cpp b/lib/libimhex/source/api/content_registry.cpp index 62270ca2b..4982b4d76 100644 --- a/lib/libimhex/source/api/content_registry.cpp +++ b/lib/libimhex/source/api/content_registry.cpp @@ -8,6 +8,7 @@ #include #include + #if defined(OS_WEB) #include #include @@ -26,23 +27,14 @@ namespace hex { namespace impl { - std::map> &getEntries() { - static std::map> entries; - - return entries; - } - - std::map &getCategoryDescriptions() { - static std::map descriptions; - - return descriptions; - } - - nlohmann::json getSetting(const std::string &unlocalizedCategory, const std::string &unlocalizedName) { + nlohmann::json& getSetting(const std::string &unlocalizedCategory, const std::string &unlocalizedName, const nlohmann::json &defaultValue) { auto &settings = getSettingsData(); - if (!settings.contains(unlocalizedCategory)) return {}; - if (!settings[unlocalizedCategory].contains(unlocalizedName)) return {}; + if (!settings.contains(unlocalizedCategory)) + settings[unlocalizedCategory] = {}; + + if (!settings[unlocalizedCategory].contains(unlocalizedName)) + settings[unlocalizedCategory][unlocalizedName] = defaultValue; return settings[unlocalizedCategory][unlocalizedName]; } @@ -119,135 +111,258 @@ namespace hex { } #endif - static auto getCategoryEntry(const std::string &unlocalizedCategory) { - auto &entries = getEntries(); - const size_t curSlot = entries.size(); - auto found = entries.find(Category { unlocalizedCategory }); - - if (found == entries.end()) { - auto [iter, _] = entries.emplace(Category { unlocalizedCategory, curSlot }, std::vector {}); - return iter; + template + static T* insertOrGetEntry(std::vector &vector, const std::string &unlocalizedName) { + T *foundEntry = nullptr; + for (auto &entry : vector) { + if (entry.unlocalizedName == unlocalizedName) { + foundEntry = &entry; + break; + } } - return found; + if (foundEntry == nullptr) { + if (unlocalizedName.empty()) + foundEntry = &*vector.emplace(vector.begin(), unlocalizedName); + else + foundEntry = &vector.emplace_back(unlocalizedName); + } + + return foundEntry; + } + + std::vector &getSettings() { + static std::vector categories; + + return categories; + } + + Widgets::Widget* add(const std::string &unlocalizedCategory, const std::string &unlocalizedSubCategory, const std::string &unlocalizedName, std::unique_ptr &&widget) { + auto category = impl::insertOrGetEntry(impl::getSettings(), unlocalizedCategory); + auto subCategory = impl::insertOrGetEntry(category->subCategories, unlocalizedSubCategory); + auto entry = impl::insertOrGetEntry(subCategory->entries, unlocalizedName); + + entry->widget = std::move(widget); + entry->widget->load(getSetting(unlocalizedCategory, unlocalizedName, entry->widget->store())); + + return entry->widget.get(); } } - void add(const std::string &unlocalizedCategory, const std::string &unlocalizedName, i64 defaultValue, const impl::Callback &callback, bool requiresRestart) { - log::debug("Registered new integer setting: [{}]: {}", unlocalizedCategory, unlocalizedName); + void setCategoryDescription(const std::string &unlocalizedCategory, const std::string &unlocalizedDescription) { + auto category = impl::insertOrGetEntry(impl::getSettings(), unlocalizedCategory); - impl::getCategoryEntry(unlocalizedCategory)->second.emplace_back(impl::Entry { unlocalizedName, requiresRestart, callback }); - - auto &json = impl::getSettingsData(); - - if (!json.contains(unlocalizedCategory)) - json[unlocalizedCategory] = nlohmann::json::object(); - if (!json[unlocalizedCategory].contains(unlocalizedName) || !json[unlocalizedCategory][unlocalizedName].is_number()) - json[unlocalizedCategory][unlocalizedName] = int(defaultValue); + category->unlocalizedDescription = unlocalizedDescription; } - void add(const std::string &unlocalizedCategory, const std::string &unlocalizedName, const std::string &defaultValue, const impl::Callback &callback, bool requiresRestart) { - log::debug("Registered new string setting: [{}]: {}", unlocalizedCategory, unlocalizedName); + nlohmann::json read(const std::string &unlocalizedCategory, const std::string &unlocalizedName, const nlohmann::json &defaultValue) { + auto setting = impl::getSetting(unlocalizedCategory, unlocalizedName, defaultValue); - impl::getCategoryEntry(unlocalizedCategory)->second.emplace_back(impl::Entry { unlocalizedName, requiresRestart, callback }); + if (setting.is_number() && defaultValue.is_boolean()) + setting = setting.get() != 0; - auto &json = impl::getSettingsData(); - - if (!json.contains(unlocalizedCategory)) - json[unlocalizedCategory] = nlohmann::json::object(); - if (!json[unlocalizedCategory].contains(unlocalizedName) || !json[unlocalizedCategory][unlocalizedName].is_string()) - json[unlocalizedCategory][unlocalizedName] = std::string(defaultValue); + return setting; } - void add(const std::string &unlocalizedCategory, const std::string &unlocalizedName, const std::vector &defaultValue, const impl::Callback &callback, bool requiresRestart) { - log::debug("Registered new string array setting: [{}]: {}", unlocalizedCategory, unlocalizedName); - - impl::getCategoryEntry(unlocalizedCategory)->second.emplace_back(impl::Entry { unlocalizedName, requiresRestart, callback }); - - auto &json = impl::getSettingsData(); - - if (!json.contains(unlocalizedCategory)) - json[unlocalizedCategory] = nlohmann::json::object(); - if (!json[unlocalizedCategory].contains(unlocalizedName) || !json[unlocalizedCategory][unlocalizedName].is_array()) - json[unlocalizedCategory][unlocalizedName] = defaultValue; + void write(const std::string &unlocalizedCategory, const std::string &unlocalizedName, const nlohmann::json &value) { + impl::getSetting(unlocalizedCategory, unlocalizedName, value) = value; } - void addCategoryDescription(const std::string &unlocalizedCategory, const std::string &unlocalizedCategoryDescription) { - impl::getCategoryDescriptions()[unlocalizedCategory] = unlocalizedCategoryDescription; - } + namespace Widgets { - void write(const std::string &unlocalizedCategory, const std::string &unlocalizedName, i64 value) { - auto &json = impl::getSettingsData(); + bool Checkbox::draw(const std::string &name) { + return ImGui::Checkbox(name.c_str(), &this->m_value); + } - if (!json.contains(unlocalizedCategory)) - json[unlocalizedCategory] = nlohmann::json::object(); + void Checkbox::load(const nlohmann::json &data) { + if (data.is_number()) { + this->m_value = data.get() != 0; + } else if (data.is_boolean()) { + this->m_value = data.get(); + } else { + log::warn("Invalid data type loaded from settings for checkbox!"); + } + } - json[unlocalizedCategory][unlocalizedName] = value; - } - - void write(const std::string &unlocalizedCategory, const std::string &unlocalizedName, const std::string &value) { - auto &json = impl::getSettingsData(); - - if (!json.contains(unlocalizedCategory)) - json[unlocalizedCategory] = nlohmann::json::object(); - - json[unlocalizedCategory][unlocalizedName] = value; - } - - void write(const std::string &unlocalizedCategory, const std::string &unlocalizedName, const std::vector &value) { - auto &json = impl::getSettingsData(); - - if (!json.contains(unlocalizedCategory)) - json[unlocalizedCategory] = nlohmann::json::object(); - - json[unlocalizedCategory][unlocalizedName] = value; - } + nlohmann::json Checkbox::store() { + return this->m_value; + } - i64 read(const std::string &unlocalizedCategory, const std::string &unlocalizedName, i64 defaultValue) { - auto &json = impl::getSettingsData(); + bool SliderInteger::draw(const std::string &name) { + return ImGui::SliderInt(name.c_str(), &this->m_value, this->m_min, this->m_max); + } - if (!json.contains(unlocalizedCategory)) - return defaultValue; - if (!json[unlocalizedCategory].contains(unlocalizedName)) - return defaultValue; + void SliderInteger::load(const nlohmann::json &data) { + if (data.is_number_integer()) { + this->m_value = data.get(); + } else { + log::warn("Invalid data type loaded from settings for slider!"); + } + } - if (!json[unlocalizedCategory][unlocalizedName].is_number()) - json[unlocalizedCategory][unlocalizedName] = defaultValue; + nlohmann::json SliderInteger::store() { + return this->m_value; + } - return json[unlocalizedCategory][unlocalizedName].get(); - } - std::string read(const std::string &unlocalizedCategory, const std::string &unlocalizedName, const std::string &defaultValue) { - auto &json = impl::getSettingsData(); + bool SliderFloat::draw(const std::string &name) { + return ImGui::SliderFloat(name.c_str(), &this->m_value, this->m_min, this->m_max); + } - if (!json.contains(unlocalizedCategory)) - return defaultValue; - if (!json[unlocalizedCategory].contains(unlocalizedName)) - return defaultValue; + void SliderFloat::load(const nlohmann::json &data) { + if (data.is_number()) { + this->m_value = data.get(); + } else { + log::warn("Invalid data type loaded from settings for slider!"); + } + } - if (!json[unlocalizedCategory][unlocalizedName].is_string()) - json[unlocalizedCategory][unlocalizedName] = defaultValue; + nlohmann::json SliderFloat::store() { + return this->m_value; + } - return json[unlocalizedCategory][unlocalizedName].get(); - } - std::vector read(const std::string &unlocalizedCategory, const std::string &unlocalizedName, const std::vector &defaultValue) { - auto &json = impl::getSettingsData(); + ColorPicker::ColorPicker(ImColor defaultColor) { + this->m_value = { + defaultColor.Value.x, + defaultColor.Value.y, + defaultColor.Value.z, + defaultColor.Value.w + }; + } - if (!json.contains(unlocalizedCategory)) - return defaultValue; - if (!json[unlocalizedCategory].contains(unlocalizedName)) - return defaultValue; + bool ColorPicker::draw(const std::string &name) { + return ImGui::ColorEdit4(name.c_str(), this->m_value.data(), ImGuiColorEditFlags_NoInputs); + } - if (!json[unlocalizedCategory][unlocalizedName].is_array()) - json[unlocalizedCategory][unlocalizedName] = defaultValue; + void ColorPicker::load(const nlohmann::json &data) { + if (data.is_number()) { + ImColor color(data.get()); + this->m_value = { color.Value.x, color.Value.y, color.Value.z, color.Value.w }; + } else { + log::warn("Invalid data type loaded from settings for color picker!"); + } + } - if (!json[unlocalizedCategory][unlocalizedName].array().empty() && !json[unlocalizedCategory][unlocalizedName][0].is_string()) - json[unlocalizedCategory][unlocalizedName] = defaultValue; + nlohmann::json ColorPicker::store() { + ImColor color(this->m_value[0], this->m_value[1], this->m_value[2], this->m_value[3]); + + return ImU32(color); + } + + ImColor ColorPicker::getColor() const { + return { this->m_value[0], this->m_value[1], this->m_value[2], this->m_value[3] }; + } + + + bool DropDown::draw(const std::string &name) { + const char *preview = ""; + if (size_t(this->m_value) < this->m_items.size()) + preview = this->m_items[this->m_value].c_str(); + + bool changed = false; + if (ImGui::BeginCombo(name.c_str(), LangEntry(preview))) { + + int index = 0; + for (const auto &item : this->m_items) { + bool selected = (index == this->m_value); + + if (ImGui::Selectable(LangEntry(item), selected)) { + this->m_value = index; + changed = true; + } + + if (selected) + ImGui::SetItemDefaultFocus(); + + index += 1; + } + + ImGui::EndCombo(); + } + + return changed; + } + + void DropDown::load(const nlohmann::json &data) { + this->m_value = 0; + + int index = 0; + for (const auto &item : this->m_settingsValues) { + if (item == data) { + this->m_value = index; + break; + } + + index += 1; + } + } + + nlohmann::json DropDown::store() { + if (size_t(this->m_value) >= this->m_items.size()) + return nullptr; + + return this->m_settingsValues[this->m_value]; + } + + const nlohmann::json& DropDown::getValue() const { + return this->m_settingsValues[this->m_value]; + } + + + bool TextBox::draw(const std::string &name) { + return ImGui::InputText(name.c_str(), this->m_value); + } + + void TextBox::load(const nlohmann::json &data) { + if (data.is_string()) { + this->m_value = data.get(); + } else { + log::warn("Invalid data type loaded from settings for text box!"); + } + } + + nlohmann::json TextBox::store() { + return this->m_value; + } + + + bool FilePicker::draw(const std::string &name) { + bool changed = false; + if (ImGui::InputText("##font_path", this->m_value)) { + changed = true; + } + + ImGui::SameLine(); + + if (ImGui::IconButton(ICON_VS_FOLDER_OPENED, ImGui::GetStyleColorVec4(ImGuiCol_Text))) { + return fs::openFileBrowser(fs::DialogMode::Open, { { "TTF Font", "ttf" }, { "OTF Font", "otf" } }, + [&](const std::fs::path &path) { + this->m_value = wolv::util::toUTF8String(path); + }); + } + + ImGui::SameLine(); + + ImGui::TextFormatted("{}", name); + + return changed; + } + + void FilePicker::load(const nlohmann::json &data) { + if (data.is_string()) { + this->m_value = data.get(); + } else { + log::warn("Invalid data type loaded from settings for file picker!"); + } + } + + nlohmann::json FilePicker::store() { + return this->m_value; + } - return json[unlocalizedCategory][unlocalizedName].get>(); } } diff --git a/main/gui/source/init/tasks.cpp b/main/gui/source/init/tasks.cpp index 15195e8e9..b39492641 100644 --- a/main/gui/source/init/tasks.cpp +++ b/main/gui/source/init/tasks.cpp @@ -71,7 +71,7 @@ namespace hex::init { ImHexApi::System::impl::addInitArgument("update-available", latestVersion.data()); // Check if there is a telemetry uuid - std::string uuid = ContentRegistry::Settings::read("hex.builtin.setting.general", "hex.builtin.setting.general.uuid", ""); + std::string uuid = ContentRegistry::Settings::read("hex.builtin.setting.general", "hex.builtin.setting.general.uuid", "").get(); if(uuid.empty()) { // Generate a new uuid uuid = wolv::hash::generateUUID(); @@ -182,7 +182,7 @@ namespace hex::init { // Load font related settings { - std::fs::path fontFile = ContentRegistry::Settings::read("hex.builtin.setting.font", "hex.builtin.setting.font.font_path", ""); + std::fs::path fontFile = ContentRegistry::Settings::read("hex.builtin.setting.font", "hex.builtin.setting.font.font_path", "").get(); if (!fontFile.empty()) { if (!wolv::io::fs::exists(fontFile) || !wolv::io::fs::isRegularFile(fontFile)) { log::warn("Custom font file {} not found! Falling back to default font.", wolv::util::toUTF8String(fontFile)); @@ -210,7 +210,7 @@ namespace hex::init { // If a custom font has been loaded now, also load the font size float fontSize = defaultFontSize; if (!fontFile.empty()) { - fontSize = ContentRegistry::Settings::read("hex.builtin.setting.font", "hex.builtin.setting.font.font_size", 13) * ImHexApi::System::getGlobalScale(); + fontSize = ContentRegistry::Settings::read("hex.builtin.setting.font", "hex.builtin.setting.font.font_size", 13).get() * ImHexApi::System::getGlobalScale(); } ImHexApi::System::impl::setFontSize(fontSize); @@ -310,7 +310,7 @@ namespace hex::init { IM_DELETE(fonts); // Disable unicode support in settings - ContentRegistry::Settings::write("hex.builtin.setting.general", "hex.builtin.setting.general.load_all_unicode_chars", false); + ContentRegistry::Settings::write("hex.builtin.setting.general", "hex.builtin.setting.font.load_all_unicode_chars", false); // Try to load the font atlas again return loadFontsImpl(false); @@ -331,7 +331,7 @@ namespace hex::init { // Check if unicode support is enabled in the settings and that the user doesn't use the No GPU version on Windows // The Mesa3D software renderer on Windows identifies itself as "VMware, Inc." bool shouldLoadUnicode = - ContentRegistry::Settings::read("hex.builtin.setting.general", "hex.builtin.setting.general.load_all_unicode_chars", false) && + ContentRegistry::Settings::read("hex.builtin.setting.general", "hex.builtin.setting.font.load_all_unicode_chars", false) && ImHexApi::System::getGPUVendor() != "VMware, Inc."; return loadFontsImpl(shouldLoadUnicode); @@ -361,7 +361,7 @@ namespace hex::init { ImHexApi::System::getCustomFontPath().clear(); ImHexApi::Messaging::impl::getHandlers().clear(); - ContentRegistry::Settings::impl::getEntries().clear(); + ContentRegistry::Settings::impl::getSettings().clear(); ContentRegistry::Settings::impl::getSettingsData().clear(); ContentRegistry::CommandPaletteCommands::impl::getEntries().clear(); @@ -567,33 +567,11 @@ namespace hex::init { } bool configureUIScale() { - float interfaceScaling; - switch (ContentRegistry::Settings::read("hex.builtin.setting.interface", "hex.builtin.setting.interface.scaling", 0)) { - default: - case 0: - interfaceScaling = ImHexApi::System::getNativeScale(); - break; - case 1: - interfaceScaling = 0.5F; - break; - case 2: - interfaceScaling = 1.0F; - break; - case 3: - interfaceScaling = 1.5F; - break; - case 4: - interfaceScaling = 2.0F; - break; - case 5: - interfaceScaling = 3.0F; - break; - case 6: - interfaceScaling = 4.0F; - break; - } + int interfaceScaling = ContentRegistry::Settings::read("hex.builtin.setting.interface", "hex.builtin.setting.interface.scaling", 0).get() * 10; + if (interfaceScaling == 0) + interfaceScaling = ImHexApi::System::getNativeScale(); - ImHexApi::System::impl::setGlobalScale(interfaceScaling); + ImHexApi::System::impl::setGlobalScale(interfaceScaling / 10.0F); return true; } @@ -608,7 +586,7 @@ namespace hex::init { return true; } - // run all exit taks, and print to console + // Run all exit tasks, and print to console void runExitTasks() { for (const auto &[name, task, async] : init::getExitTasks()) { task(); diff --git a/main/gui/source/window/window.cpp b/main/gui/source/window/window.cpp index 955c84098..16d2ad9a2 100644 --- a/main/gui/source/window/window.cpp +++ b/main/gui/source/window/window.cpp @@ -1037,7 +1037,7 @@ namespace hex { io.FontGlobalScale = 1.0F; if (glfwGetPrimaryMonitor() != nullptr) { - bool multiWindowEnabled = ContentRegistry::Settings::read("hex.builtin.setting.interface", "hex.builtin.setting.interface.multi_windows", 0) != 0; + bool multiWindowEnabled = ContentRegistry::Settings::read("hex.builtin.setting.interface", "hex.builtin.setting.interface.multi_windows", false); if (multiWindowEnabled) io.ConfigFlags |= ImGuiConfigFlags_ViewportsEnable; diff --git a/plugins/builtin/include/content/popups/popup_telemetry_request.hpp b/plugins/builtin/include/content/popups/popup_telemetry_request.hpp index 218f092a9..1a2e0f743 100644 --- a/plugins/builtin/include/content/popups/popup_telemetry_request.hpp +++ b/plugins/builtin/include/content/popups/popup_telemetry_request.hpp @@ -14,7 +14,7 @@ namespace hex::plugin::builtin { PopupTelemetryRequest() : hex::Popup("hex.builtin.common.question", false) { // Check if there is a telemetry uuid - this->m_uuid = ContentRegistry::Settings::read("hex.builtin.setting.general", "hex.builtin.setting.general.uuid", ""); + this->m_uuid = ContentRegistry::Settings::read("hex.builtin.setting.general", "hex.builtin.setting.general.uuid", "").get(); if(this->m_uuid.empty()) { // Generate a new uuid this->m_uuid = wolv::hash::generateUUID(); diff --git a/plugins/builtin/romfs/lang/de_DE.json b/plugins/builtin/romfs/lang/de_DE.json index fc306c62e..0f4d9b715 100644 --- a/plugins/builtin/romfs/lang/de_DE.json +++ b/plugins/builtin/romfs/lang/de_DE.json @@ -516,7 +516,7 @@ "hex.builtin.setting.general": "Allgemein", "hex.builtin.setting.general.auto_load_patterns": "Automatisches Laden unterstützter Pattern", "hex.builtin.setting.general.server_contact": "Update checks und Statistiken zulassen", - "hex.builtin.setting.general.load_all_unicode_chars": "Alle Unicode Zeichen laden", + "hex.builtin.setting.font.load_all_unicode_chars": "Alle Unicode Zeichen laden", "hex.builtin.setting.general.network_interface": "", "hex.builtin.setting.general.save_recent_providers": "", "hex.builtin.setting.general.show_tips": "Tipps beim Start anzeigen", diff --git a/plugins/builtin/romfs/lang/en_US.json b/plugins/builtin/romfs/lang/en_US.json index 6eb84ee14..88f358a47 100644 --- a/plugins/builtin/romfs/lang/en_US.json +++ b/plugins/builtin/romfs/lang/en_US.json @@ -516,13 +516,17 @@ "hex.builtin.setting.folders.description": "Specify additional search paths for patterns, scripts, Yara rules and more", "hex.builtin.setting.folders.remove_folder": "Remove currently selected folder from list", "hex.builtin.setting.font": "Font", + "hex.builtin.setting.font.glyphs": "Glyphs", + "hex.builtin.setting.font.custom_font": "Custom Font", "hex.builtin.setting.font.font_path": "Custom Font Path", "hex.builtin.setting.font.font_size": "Font Size", "hex.builtin.setting.font.font_size.tooltip": "The font size can only be adjusted when a custom font has been selected above.\n\nThis is because ImHex uses a pixel-perfect bitmap font by default. Scaling it by any non-integer factor will only cause it to become blurry.", "hex.builtin.setting.general": "General", + "hex.builtin.setting.general.patterns": "Patterns", + "hex.builtin.setting.general.network": "Network", "hex.builtin.setting.general.auto_load_patterns": "Auto-load supported pattern", "hex.builtin.setting.general.server_contact": "Enable update checks and usage statistics", - "hex.builtin.setting.general.load_all_unicode_chars": "Load all unicode characters", + "hex.builtin.setting.font.load_all_unicode_chars": "Load all unicode characters", "hex.builtin.setting.general.network_interface": "Enable network interface", "hex.builtin.setting.general.save_recent_providers": "Save recently used providers", "hex.builtin.setting.general.show_tips": "Show tips on startup", @@ -533,7 +537,7 @@ "hex.builtin.setting.hex_editor.bytes_per_row": "Bytes per row", "hex.builtin.setting.hex_editor.char_padding": "Extra character cell padding", "hex.builtin.setting.hex_editor.highlight_color": "Selection highlight color", - "hex.builtin.setting.hex_editor.sync_scrolling": "Synchronize editor position", + "hex.builtin.setting.hex_editor.sync_scrolling": "Synchronize editor scroll position", "hex.builtin.setting.imhex": "ImHex", "hex.builtin.setting.imhex.recent_files": "Recent Files", "hex.builtin.setting.interface": "Interface", diff --git a/plugins/builtin/romfs/lang/es_ES.json b/plugins/builtin/romfs/lang/es_ES.json index cfa43a5e3..8c0981ee0 100644 --- a/plugins/builtin/romfs/lang/es_ES.json +++ b/plugins/builtin/romfs/lang/es_ES.json @@ -516,7 +516,7 @@ "hex.builtin.setting.general": "General", "hex.builtin.setting.general.auto_load_patterns": "Cargar automáticamente patterns soportados", "hex.builtin.setting.general.server_contact": "", - "hex.builtin.setting.general.load_all_unicode_chars": "Cargar todos los caracteres unicode", + "hex.builtin.setting.font.load_all_unicode_chars": "Cargar todos los caracteres unicode", "hex.builtin.setting.general.network_interface": "", "hex.builtin.setting.general.save_recent_providers": "Guardar proveedores recientemente utilizados", "hex.builtin.setting.general.show_tips": "Mostrar consejos al inicio", diff --git a/plugins/builtin/romfs/lang/it_IT.json b/plugins/builtin/romfs/lang/it_IT.json index e7f56bd7c..56025134f 100644 --- a/plugins/builtin/romfs/lang/it_IT.json +++ b/plugins/builtin/romfs/lang/it_IT.json @@ -516,7 +516,7 @@ "hex.builtin.setting.general": "Generali", "hex.builtin.setting.general.auto_load_patterns": "Auto-caricamento del pattern supportato", "hex.builtin.setting.general.server_contact": "", - "hex.builtin.setting.general.load_all_unicode_chars": "", + "hex.builtin.setting.font.load_all_unicode_chars": "", "hex.builtin.setting.general.network_interface": "", "hex.builtin.setting.general.save_recent_providers": "", "hex.builtin.setting.general.show_tips": "Mostra consigli all'avvio", diff --git a/plugins/builtin/romfs/lang/ja_JP.json b/plugins/builtin/romfs/lang/ja_JP.json index 2a9994c69..f12ce7b50 100644 --- a/plugins/builtin/romfs/lang/ja_JP.json +++ b/plugins/builtin/romfs/lang/ja_JP.json @@ -516,7 +516,7 @@ "hex.builtin.setting.general": "基本", "hex.builtin.setting.general.auto_load_patterns": "対応するパターンを自動で読み込む", "hex.builtin.setting.general.server_contact": "", - "hex.builtin.setting.general.load_all_unicode_chars": "", + "hex.builtin.setting.font.load_all_unicode_chars": "", "hex.builtin.setting.general.network_interface": "", "hex.builtin.setting.general.save_recent_providers": "", "hex.builtin.setting.general.show_tips": "起動時に豆知識を表示", diff --git a/plugins/builtin/romfs/lang/ko_KR.json b/plugins/builtin/romfs/lang/ko_KR.json index 152d3ed01..929ec89b4 100644 --- a/plugins/builtin/romfs/lang/ko_KR.json +++ b/plugins/builtin/romfs/lang/ko_KR.json @@ -516,7 +516,7 @@ "hex.builtin.setting.general": "일반", "hex.builtin.setting.general.auto_load_patterns": "지원하는 패턴 자동으로 로드", "hex.builtin.setting.general.server_contact": "", - "hex.builtin.setting.general.load_all_unicode_chars": "", + "hex.builtin.setting.font.load_all_unicode_chars": "", "hex.builtin.setting.general.network_interface": "", "hex.builtin.setting.general.save_recent_providers": "", "hex.builtin.setting.general.show_tips": "시작 시 팁 표시", diff --git a/plugins/builtin/romfs/lang/pt_BR.json b/plugins/builtin/romfs/lang/pt_BR.json index daae860b6..1abafa16a 100644 --- a/plugins/builtin/romfs/lang/pt_BR.json +++ b/plugins/builtin/romfs/lang/pt_BR.json @@ -516,7 +516,7 @@ "hex.builtin.setting.general": "General", "hex.builtin.setting.general.auto_load_patterns": "Padrão compatível com carregamento automático", "hex.builtin.setting.general.server_contact": "", - "hex.builtin.setting.general.load_all_unicode_chars": "", + "hex.builtin.setting.font.load_all_unicode_chars": "", "hex.builtin.setting.general.network_interface": "", "hex.builtin.setting.general.save_recent_providers": "", "hex.builtin.setting.general.show_tips": "Mostrar dicas na inicialização", diff --git a/plugins/builtin/romfs/lang/zh_CN.json b/plugins/builtin/romfs/lang/zh_CN.json index 3f520f679..49e8efb2b 100644 --- a/plugins/builtin/romfs/lang/zh_CN.json +++ b/plugins/builtin/romfs/lang/zh_CN.json @@ -516,7 +516,7 @@ "hex.builtin.setting.general": "通用", "hex.builtin.setting.general.auto_load_patterns": "自动加载支持的模式", "hex.builtin.setting.general.server_contact": "启用更新检查和使用统计", - "hex.builtin.setting.general.load_all_unicode_chars": "加载所有 Unicode 字符", + "hex.builtin.setting.font.load_all_unicode_chars": "加载所有 Unicode 字符", "hex.builtin.setting.general.network_interface": "启动网络", "hex.builtin.setting.general.save_recent_providers": "保存最近使用的提供者", "hex.builtin.setting.general.show_tips": "在启动时显示每日提示", diff --git a/plugins/builtin/romfs/lang/zh_TW.json b/plugins/builtin/romfs/lang/zh_TW.json index 8e978b59e..b077440b9 100644 --- a/plugins/builtin/romfs/lang/zh_TW.json +++ b/plugins/builtin/romfs/lang/zh_TW.json @@ -516,7 +516,7 @@ "hex.builtin.setting.general": "一般", "hex.builtin.setting.general.auto_load_patterns": "自動載入支援的模式", "hex.builtin.setting.general.server_contact": "啟用檢查更新和使用統計", - "hex.builtin.setting.general.load_all_unicode_chars": "載入所有 unicode 字元", + "hex.builtin.setting.font.load_all_unicode_chars": "載入所有 unicode 字元", "hex.builtin.setting.general.network_interface": "啟用網路介面", "hex.builtin.setting.general.save_recent_providers": "儲存近期使用過的提供者", "hex.builtin.setting.general.show_tips": "啟動時顯示提示", diff --git a/plugins/builtin/source/content/background_services.cpp b/plugins/builtin/source/content/background_services.cpp index 3837b871d..b649d7a07 100644 --- a/plugins/builtin/source/content/background_services.cpp +++ b/plugins/builtin/source/content/background_services.cpp @@ -54,7 +54,7 @@ namespace hex::plugin::builtin { void registerBackgroundServices() { EventManager::subscribe([]{ - networkInterfaceServiceEnabled = bool(ContentRegistry::Settings::read("hex.builtin.setting.general", "hex.builtin.setting.general.network_interface", 0)); + networkInterfaceServiceEnabled = bool(ContentRegistry::Settings::read("hex.builtin.setting.general", "hex.builtin.setting.general.network_interface", false)); }); ContentRegistry::BackgroundServices::registerService("hex.builtin.background_service.network_interface"_lang, handleNetworkInterfaceService); diff --git a/plugins/builtin/source/content/recent.cpp b/plugins/builtin/source/content/recent.cpp index 597ac94e0..1846bb90e 100644 --- a/plugins/builtin/source/content/recent.cpp +++ b/plugins/builtin/source/content/recent.cpp @@ -27,7 +27,7 @@ namespace hex::plugin::builtin::recent { void registerEventHandlers() { // Save every opened provider as a "recent" shortcut (void)EventManager::subscribe([](prv::Provider *provider) { - if (ContentRegistry::Settings::read("hex.builtin.setting.general", "hex.builtin.setting.general.save_recent_providers", 1) == 1) { + if (ContentRegistry::Settings::read("hex.builtin.setting.general", "hex.builtin.setting.general.save_recent_providers", true)) { auto fileName = hex::format("{:%y%m%d_%H%M%S}.json", fmt::gmtime(std::chrono::system_clock::now())); // Do not save to recents if the provider is part of a project @@ -59,7 +59,7 @@ namespace hex::plugin::builtin::recent { // Save opened projects as a "recent" shortcut (void)EventManager::subscribe([] { - if (ContentRegistry::Settings::read("hex.builtin.setting.general", "hex.builtin.setting.general.save_recent_providers", 1) == 1) { + if (ContentRegistry::Settings::read("hex.builtin.setting.general", "hex.builtin.setting.general.save_recent_providers", true)) { auto fileName = hex::format("{:%y%m%d_%H%M%S}.json", fmt::gmtime(std::chrono::system_clock::now())); auto projectFileName = ProjectFile::getPath().filename(); diff --git a/plugins/builtin/source/content/settings_entries.cpp b/plugins/builtin/source/content/settings_entries.cpp index 0a914cbc8..d41781d55 100644 --- a/plugins/builtin/source/content/settings_entries.cpp +++ b/plugins/builtin/source/content/settings_entries.cpp @@ -1,12 +1,10 @@ #include #include -#include #include #include #include #include -#include #include #include @@ -14,230 +12,234 @@ #include -#include - -#include - -namespace { - - std::vector userFolders; - - void loadUserFoldersFromSetting(const std::vector &paths) { - userFolders.clear(); - for (const auto &path : paths) { - userFolders.emplace_back( - reinterpret_cast(path.data()), - reinterpret_cast(path.data() + path.size()) - ); - } - } - -} +#include namespace hex::plugin::builtin { + namespace { + + /* + Values of this setting: + 0 - do not check for updates on startup + 1 - check for updates on startup + 2 - default value - ask the user if he wants to check for updates. This value should only be encountered on the first startup. + */ + class ServerContactWidget : public ContentRegistry::Settings::Widgets::Widget { + public: + bool draw(const std::string &name) override { + bool enabled = this->m_value == 1; + + if (ImGui::Checkbox(name.data(), &enabled)) { + this->m_value = enabled ? 1 : 0; + return true; + } + + return false; + } + + void load(const nlohmann::json &data) override { + if (data.is_number()) + this->m_value = data.get(); + } + + nlohmann::json store() override { + return this->m_value; + } + + private: + u32 m_value = 2; + }; + + class FPSWidget : public ContentRegistry::Settings::Widgets::Widget { + public: + bool draw(const std::string &name) override { + auto format = [this] -> std::string { + if (this->m_value > 200) + return "hex.builtin.setting.interface.fps.unlocked"_lang; + else if (this->m_value < 15) + return "hex.builtin.setting.interface.fps.native"_lang; + else + return "%d FPS"; + }(); + + if (ImGui::SliderInt(name.data(), &this->m_value, 14, 201, format.c_str(), ImGuiSliderFlags_AlwaysClamp)) { + return true; + } + + return false; + } + + void load(const nlohmann::json &data) override { + if (data.is_number()) + this->m_value = data.get(); + } + + nlohmann::json store() override { + return this->m_value; + } + + private: + int m_value = 60; + }; + + class UserFolderWidget : public ContentRegistry::Settings::Widgets::Widget { + public: + bool draw(const std::string &) override { + bool result = false; + + if (!ImGui::BeginListBox("", ImVec2(-38, -10))) { + return false; + } else { + for (size_t n = 0; n < this->m_paths.size(); n++) { + const bool isSelected = (this->m_itemIndex == n); + if (ImGui::Selectable(wolv::util::toUTF8String(this->m_paths[n]).c_str(), isSelected)) { + this->m_itemIndex = n; + } + + if (isSelected) { + ImGui::SetItemDefaultFocus(); + } + } + ImGui::EndListBox(); + } + ImGui::SameLine(); + ImGui::BeginGroup(); + + if (ImGui::IconButton(ICON_VS_NEW_FOLDER, ImGui::GetStyleColorVec4(ImGuiCol_Text), ImVec2(30, 30))) { + fs::openFileBrowser(fs::DialogMode::Folder, {}, [&](const std::fs::path &path) { + if (std::find(this->m_paths.begin(), this->m_paths.end(), path) == this->m_paths.end()) { + this->m_paths.emplace_back(path); + ImHexApi::System::setAdditionalFolderPaths(this->m_paths); + + result = true; + } + }); + } + ImGui::InfoTooltip("hex.builtin.setting.folders.add_folder"_lang); + + if (ImGui::IconButton(ICON_VS_REMOVE_CLOSE, ImGui::GetStyleColorVec4(ImGuiCol_Text), ImVec2(30, 30))) { + if (!this->m_paths.empty()) { + this->m_paths.erase(std::next(this->m_paths.begin(), this->m_itemIndex)); + ImHexApi::System::setAdditionalFolderPaths(this->m_paths); + + result = true; + } + } + ImGui::InfoTooltip("hex.builtin.setting.folders.remove_folder"_lang); + + ImGui::EndGroup(); + + return result; + } + + void load(const nlohmann::json &data) override { + if (data.is_array()) { + std::vector pathStrings = data; + + for (const auto &pathString : pathStrings) { + this->m_paths.emplace_back(pathString); + } + } + } + + nlohmann::json store() override { + std::vector pathStrings; + + for (const auto &path : this->m_paths) { + pathStrings.push_back(wolv::util::toUTF8String(path)); + } + + return pathStrings; + } + + private: + u32 m_itemIndex = 0; + std::vector m_paths; + }; + + class ScalingWidget : public ContentRegistry::Settings::Widgets::Widget { + public: + bool draw(const std::string &name) override { + auto format = [this] -> std::string { + if (this->m_value == 0) + return "hex.builtin.setting.interface.scaling.native"_lang; + else + return "x%.1f"; + }(); + + if (ImGui::SliderFloat(name.data(), &this->m_value, 0, 10, format.c_str(), ImGuiSliderFlags_AlwaysClamp)) { + return true; + } + + return false; + } + + void load(const nlohmann::json &data) override { + if (data.is_number()) + this->m_value = data.get(); + } + + nlohmann::json store() override { + return this->m_value; + } + + private: + float m_value = 0; + }; + + } + void registerSettings() { /* General */ - ContentRegistry::Settings::add("hex.builtin.setting.general", "hex.builtin.setting.general.show_tips", 1, [](auto name, nlohmann::json &setting) { - static bool enabled = static_cast(setting); + namespace Widgets = ContentRegistry::Settings::Widgets; - if (ImGui::Checkbox(name.data(), &enabled)) { - setting = static_cast(enabled); - return true; - } - - return false; - }); - - ContentRegistry::Settings::add("hex.builtin.setting.general", "hex.builtin.setting.general.auto_load_patterns", 1, [](auto name, nlohmann::json &setting) { - static bool enabled = static_cast(setting); - - if (ImGui::Checkbox(name.data(), &enabled)) { - setting = static_cast(enabled); - return true; - } - - return false; - }); - - ContentRegistry::Settings::add("hex.builtin.setting.general", "hex.builtin.setting.general.sync_pattern_source", 0, [](auto name, nlohmann::json &setting) { - static bool enabled = static_cast(setting); - - if (ImGui::Checkbox(name.data(), &enabled)) { - setting = static_cast(enabled); - return true; - } - - return false; - }); - - ContentRegistry::Settings::add("hex.builtin.setting.general", "hex.builtin.setting.general.load_all_unicode_chars", 0, [](auto name, nlohmann::json &setting) { - static bool enabled = static_cast(setting); - - if (ImGui::Checkbox(name.data(), &enabled)) { - setting = static_cast(enabled); - return true; - } - - return false; - }); - - ContentRegistry::Settings::add("hex.builtin.setting.general", "hex.builtin.setting.general.save_recent_providers", 1, [](auto name, nlohmann::json &setting) { - static bool enabled = static_cast(setting); - - if (ImGui::Checkbox(name.data(), &enabled)) { - setting = static_cast(enabled); - return true; - } - - return false; - }); - - ContentRegistry::Settings::add("hex.builtin.setting.general", "hex.builtin.setting.general.network_interface", 0, [](auto name, nlohmann::json &setting) { - static bool enabled = static_cast(setting); - - if (ImGui::Checkbox(name.data(), &enabled)) { - setting = static_cast(enabled); - return true; - } - - return false; - }); - - /* - Values of this setting : - 0 - do not check for updates on startup - 1 - check for updates on startup - 2 - default value - ask the user if he wants to check for updates. This value should only be encountered on the first startup. - */ - ContentRegistry::Settings::add("hex.builtin.setting.general", "hex.builtin.setting.general.server_contact", 2, [](auto name, nlohmann::json &setting) { - static bool enabled = static_cast(setting) == 1; - - if (ImGui::Checkbox(name.data(), &enabled)) { - setting = static_cast(enabled); - return true; - } - - return false; - }); - - ContentRegistry::Settings::add("hex.builtin.setting.general", "hex.builtin.setting.general.upload_crash_logs", 1, [](auto name, nlohmann::json &setting) { - static bool enabled = static_cast(setting) == 1; - - if (ImGui::Checkbox(name.data(), &enabled)) { - setting = static_cast(enabled); - return true; - } - - return false; - }); + ContentRegistry::Settings::add("hex.builtin.setting.general", "", "hex.builtin.setting.general.show_tips", true); + ContentRegistry::Settings::add("hex.builtin.setting.general", "", "hex.builtin.setting.general.save_recent_providers", true); + ContentRegistry::Settings::add("hex.builtin.setting.general", "hex.builtin.setting.general.patterns", "hex.builtin.setting.general.auto_load_patterns", true); + ContentRegistry::Settings::add("hex.builtin.setting.general", "hex.builtin.setting.general.patterns", "hex.builtin.setting.general.sync_pattern_source", false); + ContentRegistry::Settings::add("hex.builtin.setting.general", "hex.builtin.setting.general.network", "hex.builtin.setting.general.network_interface", false); + ContentRegistry::Settings::add("hex.builtin.setting.general", "hex.builtin.setting.general.network", "hex.builtin.setting.general.server_contact"); + ContentRegistry::Settings::add("hex.builtin.setting.general", "hex.builtin.setting.general.network", "hex.builtin.setting.general.upload_crash_logs", true); /* Interface */ - ContentRegistry::Settings::add("hex.builtin.setting.interface", "hex.builtin.setting.interface.color", "Dark", [](auto name, nlohmann::json &setting) { - static auto selection = static_cast(setting); + auto themeNames = ThemeManager::getThemeNames(); + std::vector themeJsons = { }; + for (const auto &themeName : themeNames) + themeJsons.emplace_back(themeName); - const auto themeNames = ThemeManager::getThemeNames(); - bool changed = false; + themeNames.emplace(themeNames.begin(), ThemeManager::NativeTheme); + themeJsons.emplace(themeJsons.begin(), ThemeManager::NativeTheme); - if (ImGui::BeginCombo(name.data(), selection.c_str())) { - if (ImGui::Selectable(ThemeManager::NativeTheme, selection == ThemeManager::NativeTheme)) { - selection = ThemeManager::NativeTheme; - setting = selection; - ImHexApi::System::enableSystemThemeDetection(true); - changed = true; - } + ContentRegistry::Settings::add("hex.builtin.setting.interface", "", "hex.builtin.setting.interface.color", + themeNames, + themeJsons).setChangedCallback([](auto &widget) { + auto dropDown = static_cast(&widget); - for (const auto &themeName : themeNames) { - if (ImGui::Selectable(themeName.c_str(), selection == themeName)) { - selection = themeName; - setting = selection; - ImHexApi::System::enableSystemThemeDetection(false); - ThemeManager::changeTheme(selection); - changed = true; - } - } + if (dropDown->getValue() == ThemeManager::NativeTheme) + ImHexApi::System::enableSystemThemeDetection(true); + else { + ImHexApi::System::enableSystemThemeDetection(false); + ThemeManager::changeTheme(dropDown->getValue()); + } + }); - ImGui::EndCombo(); - } + ContentRegistry::Settings::add("hex.builtin.setting.interface", "", "hex.builtin.setting.interface.scaling").requiresRestart(); - return changed; - }); + std::vector languageNames; + std::vector languageCodes; - ContentRegistry::Settings::add( - "hex.builtin.setting.interface", "hex.builtin.setting.interface.scaling", 0, [](auto name, nlohmann::json &setting) { - static int selection = static_cast(setting); + for (auto &[languageCode, languageName] : LangEntry::getSupportedLanguages()) { + languageNames.emplace_back(languageName); + languageCodes.emplace_back(languageCode); + } - const char *scaling[] = { - "hex.builtin.setting.interface.scaling.native"_lang, - "hex.builtin.setting.interface.scaling.x0_5"_lang, - "hex.builtin.setting.interface.scaling.x1_0"_lang, - "hex.builtin.setting.interface.scaling.x1_5"_lang, - "hex.builtin.setting.interface.scaling.x2_0"_lang, - }; + ContentRegistry::Settings::add("hex.builtin.setting.interface", "", "hex.builtin.setting.interface.language", languageNames, languageCodes); - if (ImGui::Combo(name.data(), &selection, scaling, IM_ARRAYSIZE(scaling))) { - setting = selection; - return true; - } - - return false; - }, - true); - - ContentRegistry::Settings::add("hex.builtin.setting.interface", "hex.builtin.setting.interface.language", "en-US", [](auto name, nlohmann::json &setting) { - auto &languages = LangEntry::getSupportedLanguages(); - if (!languages.contains(setting.get())) - setting = "en-US"; - - bool changed = false; - if (ImGui::BeginCombo(name.data(), languages.at(setting.get()).c_str())) { - for (auto &[languageCode, languageName] : languages) { - if (ImGui::Selectable(languageName.c_str(), setting == languageCode)) { - setting = languageCode; - changed = true; - } - } - - ImGui::EndCombo(); - } - - return changed; - }); - - ContentRegistry::Settings::add("hex.builtin.setting.interface", "hex.builtin.setting.interface.wiki_explain_language", "en", [](auto name, nlohmann::json &setting) { - static auto lang = std::string(setting); - - if (ImGui::InputText(name.data(), lang, ImGuiInputTextFlags_CharsNoBlank)) { - // Remove trailing null bytes - setting = std::string(lang.c_str()); - return true; - } - - return false; - }); - - ContentRegistry::Settings::add("hex.builtin.setting.interface", "hex.builtin.setting.interface.fps", 60, [](auto name, nlohmann::json &setting) { - static int fps = static_cast(setting); - - auto format = [] -> std::string { - if (fps > 200) - return "hex.builtin.setting.interface.fps.unlocked"_lang; - else if (fps < 15) - return "hex.builtin.setting.interface.fps.native"_lang; - else - return "%d FPS"; - }(); - - if (ImGui::SliderInt(name.data(), &fps, 14, 201, format.c_str(), ImGuiSliderFlags_AlwaysClamp)) { - setting = fps; - return true; - } - - return false; - }); + ContentRegistry::Settings::add("hex.builtin.setting.interface", "", "hex.builtin.setting.interface.wiki_explain_language", "en"); + ContentRegistry::Settings::add("hex.builtin.setting.interface", "", "hex.builtin.setting.interface.fps"); #if defined (OS_LINUX) constexpr static auto MultiWindowSupportEnabledDefault = 0; @@ -245,245 +247,64 @@ namespace hex::plugin::builtin { constexpr static auto MultiWindowSupportEnabledDefault = 1; #endif - ContentRegistry::Settings::add("hex.builtin.setting.interface", "hex.builtin.setting.interface.multi_windows", MultiWindowSupportEnabledDefault, [](auto name, nlohmann::json &setting) { - static bool enabled = static_cast(setting); + ContentRegistry::Settings::add("hex.builtin.setting.interface", "", "hex.builtin.setting.interface.multi_windows", MultiWindowSupportEnabledDefault).requiresRestart(); + ContentRegistry::Settings::add("hex.builtin.setting.interface", "", "hex.builtin.setting.interface.restore_window_pos", false); - if (ImGui::Checkbox(name.data(), &enabled)) { - setting = static_cast(enabled); - return true; - } - - return false; - }, true); - - ContentRegistry::Settings::add("hex.builtin.setting.hex_editor", "hex.builtin.setting.hex_editor.highlight_color", 0x60C08080, [](auto name, nlohmann::json &setting) { - static auto color = static_cast(setting); - - std::array colorArray = { - ((color >> 0) & 0x000000FF) / float(0xFF), - ((color >> 8) & 0x000000FF) / float(0xFF), - ((color >> 16) & 0x000000FF) / float(0xFF), - ((color >> 24) & 0x000000FF) / float(0xFF) - }; - - if (ImGui::ColorEdit4(name.data(), colorArray.data(), ImGuiColorEditFlags_AlphaBar | ImGuiColorEditFlags_AlphaPreviewHalf | ImGuiColorEditFlags_NoDragDrop | ImGuiColorEditFlags_NoInputs)) { - color = - (color_t(colorArray[0] * 0xFF) << 0) | - (color_t(colorArray[1] * 0xFF) << 8) | - (color_t(colorArray[2] * 0xFF) << 16) | - (color_t(colorArray[3] * 0xFF) << 24); - - setting = color; - - return true; - } - - return false; - }); - - ContentRegistry::Settings::add("hex.builtin.setting.hex_editor", "hex.builtin.setting.hex_editor.sync_scrolling", 0, [](auto name, nlohmann::json &setting) { - static bool syncScrolling = static_cast(setting); - - if (ImGui::Checkbox(name.data(), &syncScrolling)) { - setting = static_cast(syncScrolling); - return true; - } - - return false; - }); - - ContentRegistry::Settings::add("hex.builtin.setting.hex_editor", "hex.builtin.setting.hex_editor.byte_padding", 0, [](auto name, nlohmann::json &setting) { - static int padding = static_cast(setting); - - if (ImGui::SliderInt(name.data(), &padding, 0, 50)) { - setting = padding; - return true; - } - - return false; - }); - - ContentRegistry::Settings::add("hex.builtin.setting.hex_editor", "hex.builtin.setting.hex_editor.char_padding", 0, [](auto name, nlohmann::json &setting) { - static int padding = static_cast(setting); - - if (ImGui::SliderInt(name.data(), &padding, 0, 50)) { - setting = padding; - return true; - } - - return false; - }); - - ContentRegistry::Settings::add("hex.builtin.setting.interface", "hex.builtin.setting.interface.restore_window_pos", 0, [](auto name, nlohmann::json &setting) { - static bool restoreWindowPos = static_cast(setting); - - if (ImGui::Checkbox(name.data(), &restoreWindowPos)) { - setting = static_cast(restoreWindowPos); - return true; - } - - return false; - }); + ContentRegistry::Settings::add("hex.builtin.setting.hex_editor", "", "hex.builtin.setting.hex_editor.highlight_color", ImColor(0x80, 0x80, 0xC0, 0x60)); + ContentRegistry::Settings::add("hex.builtin.setting.hex_editor", "", "hex.builtin.setting.hex_editor.sync_scrolling", false); + ContentRegistry::Settings::add("hex.builtin.setting.hex_editor", "", "hex.builtin.setting.hex_editor.byte_padding", 0, 0, 50); + ContentRegistry::Settings::add("hex.builtin.setting.hex_editor", "", "hex.builtin.setting.hex_editor.char_padding", 0, 0, 50); /* Fonts */ - static std::string fontPath; - ContentRegistry::Settings::add( - "hex.builtin.setting.font", "hex.builtin.setting.font.font_path", "", [](auto name, nlohmann::json &setting) { - fontPath = static_cast(setting); + ContentRegistry::Settings::add("hex.builtin.setting.font", "hex.builtin.setting.font.glyphs", "hex.builtin.setting.font.load_all_unicode_chars", false); + auto fontPathSetting = ContentRegistry::Settings::add("hex.builtin.setting.font", "hex.builtin.setting.font.custom_font", "hex.builtin.setting.font.font_path").requiresRestart(); + ContentRegistry::Settings::add("hex.builtin.setting.font", "hex.builtin.setting.font.custom_font", "hex.builtin.setting.font.font_size", 13, 0, 100) + .requiresRestart() + .setEnabledCallback([fontPathSetting]{ + auto &filePicker = static_cast(fontPathSetting.getWidget()); - if (ImGui::InputText("##font_path", fontPath)) { - setting = fontPath; - return true; - } - - ImGui::SameLine(); - - if (ImGui::IconButton(ICON_VS_FOLDER_OPENED, ImGui::GetStyleColorVec4(ImGuiCol_Text))) { - return fs::openFileBrowser(fs::DialogMode::Open, { { "TTF Font", "ttf" }, { "OTF Font", "otf" } }, - [&](const std::fs::path &path) { - fontPath = wolv::util::toUTF8String(path); - setting = fontPath; - }); - } - - ImGui::SameLine(); - - ImGui::TextFormatted("{}", name); - - return false; - }, - true); - - ContentRegistry::Settings::add( - "hex.builtin.setting.font", "hex.builtin.setting.font.font_size", 13, [](auto name, nlohmann::json &setting) { - static int fontSize = static_cast(setting); - - ImGui::BeginDisabled(fontPath.empty()); - ON_SCOPE_EXIT { ImGui::EndDisabled(); }; - - if (ImGui::SliderInt(name.data(), &fontSize, 0, 100, "%d", ImGuiSliderFlags_NoInput)) { - setting = fontSize; - return true; - } - - if (fontPath.empty() && ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled)) { - ImGui::SetNextWindowSize(scaled(ImVec2(300, 0))); - ImGui::BeginTooltip(); - ImGui::TextFormattedWrapped("{}", "hex.builtin.setting.font.font_size.tooltip"_lang); - ImGui::EndTooltip(); - } - - return false; - }, - true); + return !filePicker.getPath().empty(); + }); /* Folders */ - static const std::string dirsSetting { "hex.builtin.setting.folders" }; - - ContentRegistry::Settings::addCategoryDescription(dirsSetting, "hex.builtin.setting.folders.description"); - - ContentRegistry::Settings::add(dirsSetting, dirsSetting, std::vector {}, [](auto name, nlohmann::json &setting) { - hex::unused(name); - - static size_t currentItemIndex = [&setting] {loadUserFoldersFromSetting(setting); return 0; }(); - - auto saveToSetting = [&setting] { - std::vector folderStrings; - for (const auto &folder : userFolders) { - auto utfString = folder.u8string(); - // JSON stores char8_t as array, char8_t is not supported as of now - folderStrings.emplace_back(reinterpret_cast(&utfString.front()), reinterpret_cast(std::next(&utfString.back()))); - } - setting = folderStrings; - ImHexApi::System::setAdditionalFolderPaths(userFolders); - }; - - bool result = false; - - if (!ImGui::BeginListBox("", ImVec2(-38, -FLT_MIN))) { - return false; - } else { - for (size_t n = 0; n < userFolders.size(); n++) { - const bool isSelected = (currentItemIndex == n); - if (ImGui::Selectable(wolv::util::toUTF8String(userFolders.at(n)).c_str(), isSelected)) { currentItemIndex = n; } - if (isSelected) { ImGui::SetItemDefaultFocus(); } - } - ImGui::EndListBox(); - } - ImGui::SameLine(); - ImGui::BeginGroup(); - - if (ImGui::IconButton(ICON_VS_NEW_FOLDER, ImGui::GetCustomColorVec4(ImGuiCustomCol_DescButton), ImVec2(30, 30))) { - fs::openFileBrowser(fs::DialogMode::Folder, {}, [&](const std::fs::path &path) { - if (std::find(userFolders.begin(), userFolders.end(), path) == userFolders.end()) { - userFolders.emplace_back(path); - saveToSetting(); - result = true; - } - }); - } - ImGui::InfoTooltip("hex.builtin.setting.folders.add_folder"_lang); - - if (ImGui::IconButton(ICON_VS_REMOVE_CLOSE, ImGui::GetCustomColorVec4(ImGuiCustomCol_DescButton), ImVec2(30, 30))) { - if (!userFolders.empty()) { - userFolders.erase(std::next(userFolders.begin(), currentItemIndex)); - saveToSetting(); - - result = true; - } - } - ImGui::InfoTooltip("hex.builtin.setting.folders.remove_folder"_lang); - - ImGui::EndGroup(); - - return result; - }); + ContentRegistry::Settings::setCategoryDescription("hex.builtin.setting.folders", "hex.builtin.setting.folders.description"); + ContentRegistry::Settings::add("hex.builtin.setting.folders", "", "hex.builtin.setting.folders.description"); /* Proxy */ - static const std::string proxySetting { "hex.builtin.setting.proxy" }; + HttpRequest::setProxy(ContentRegistry::Settings::read("hex.builtin.setting.proxy", "hex.builtin.setting.proxy.url", "").get()); - HttpRequest::setProxy(ContentRegistry::Settings::read(proxySetting, "hex.builtin.setting.proxy.url", "")); + ContentRegistry::Settings::setCategoryDescription("hex.builtin.setting.proxy", "hex.builtin.setting.proxy.description"); - ContentRegistry::Settings::addCategoryDescription(proxySetting, "hex.builtin.setting.proxy.description"); + auto proxyEnabledSetting = ContentRegistry::Settings::add("hex.builtin.setting.proxy", "", "hex.builtin.setting.proxy.enable", false).setChangedCallback([](Widgets::Widget &widget) { + auto checkBox = static_cast(&widget); - ContentRegistry::Settings::add( - proxySetting, "hex.builtin.setting.proxy.url", "", [](auto name, nlohmann::json &setting) { - static std::string proxyUrl = static_cast(setting); - static bool enableProxy = !proxyUrl.empty(); + if (checkBox->isChecked()) { + HttpRequest::setProxy(ContentRegistry::Settings::read("hex.builtin.setting.proxy", "hex.builtin.setting.proxy.url", "").get()); + } else { + HttpRequest::setProxy(""); + } + }); - bool result = false; + ContentRegistry::Settings::add("hex.builtin.setting.proxy", "", "hex.builtin.setting.proxy.url", "") + .setEnabledCallback([proxyEnabledSetting] { + auto &checkBox = static_cast(proxyEnabledSetting.getWidget()); - if (ImGui::Checkbox("hex.builtin.setting.proxy.enable"_lang, &enableProxy)) { - setting = enableProxy ? proxyUrl : ""; - HttpRequest::setProxy(enableProxy ? proxyUrl : ""); - result = true; - } + return checkBox.isChecked(); + }) + .setChangedCallback([](Widgets::Widget &widget) { + auto textBox = static_cast(&widget); - ImGui::BeginDisabled(!enableProxy); - if (ImGui::InputText("##proxy_url", proxyUrl)) { - setting = proxyUrl; - HttpRequest::setProxy(proxyUrl); - result = true; - } - ImGui::EndDisabled(); - - ImGui::InfoTooltip("hex.builtin.setting.proxy.url.tooltip"_lang); - - ImGui::SameLine(); - - ImGui::TextFormatted("{}", name); - return result; - }, - false); + HttpRequest::setProxy(textBox->getValue()); + }); } static void loadThemeSettings() { - auto theme = ContentRegistry::Settings::read("hex.builtin.setting.interface", "hex.builtin.setting.interface.color", ThemeManager::NativeTheme); + auto theme = ContentRegistry::Settings::read("hex.builtin.setting.interface", "hex.builtin.setting.interface.color", ThemeManager::NativeTheme).get(); if (theme == ThemeManager::NativeTheme) ImHexApi::System::enableSystemThemeDetection(true); @@ -493,16 +314,20 @@ namespace hex::plugin::builtin { } } - static void loadFoldersSettings() { - auto directories = ContentRegistry::Settings::read("hex.builtin.setting.folders", "hex.builtin.setting.folders", std::vector { }); + static void loadFolderSettings() { + auto folderPathStrings = ContentRegistry::Settings::read("hex.builtin.setting.folders", "hex.builtin.setting.folders", std::vector { }); - loadUserFoldersFromSetting(directories); - ImHexApi::System::setAdditionalFolderPaths(userFolders); + std::vector paths; + for (const auto &pathString : folderPathStrings) { + paths.emplace_back(pathString); + } + + ImHexApi::System::setAdditionalFolderPaths(paths); } void loadSettings() { loadThemeSettings(); - loadFoldersSettings(); + loadFolderSettings(); } } diff --git a/plugins/builtin/source/content/tools_entries.cpp b/plugins/builtin/source/content/tools_entries.cpp index 1a3db0459..f2030fe3f 100644 --- a/plugins/builtin/source/content/tools_entries.cpp +++ b/plugins/builtin/source/content/tools_entries.cpp @@ -656,7 +656,7 @@ namespace hex::plugin::builtin { }*/ std::string getWikipediaApiUrl() { - auto setting = ContentRegistry::Settings::read("hex.builtin.setting.interface", "hex.builtin.setting.interface.wiki_explain_language", "en"); + std::string setting = ContentRegistry::Settings::read("hex.builtin.setting.interface", "hex.builtin.setting.interface.wiki_explain_language", "en"); return "https://" + setting + ".wikipedia.org/w/api.php?format=json&action=query&prop=extracts&explaintext&redirects=10&formatversion=2"; } diff --git a/plugins/builtin/source/content/views/view_achievements.cpp b/plugins/builtin/source/content/views/view_achievements.cpp index e4a59fcb8..58b617f94 100644 --- a/plugins/builtin/source/content/views/view_achievements.cpp +++ b/plugins/builtin/source/content/views/view_achievements.cpp @@ -22,7 +22,7 @@ namespace hex::plugin::builtin { }); // Load settings - this->m_showPopup = bool(ContentRegistry::Settings::read("hex.builtin.setting.interface", "hex.builtin.setting.interface.achievement_popup", 1)); + this->m_showPopup = ContentRegistry::Settings::read("hex.builtin.setting.interface", "hex.builtin.setting.interface.achievement_popup", true); } ViewAchievements::~ViewAchievements() { @@ -381,7 +381,7 @@ namespace hex::plugin::builtin { ImGui::BeginGroup(); { if (ImGui::Checkbox("Show popup", &this->m_showPopup)) - ContentRegistry::Settings::write("hex.builtin.setting.interface", "hex.builtin.setting.interface.achievement_popup", i64(this->m_showPopup)); + ContentRegistry::Settings::write("hex.builtin.setting.interface", "hex.builtin.setting.interface.achievement_popup", this->m_showPopup); } ImGui::EndGroup(); diff --git a/plugins/builtin/source/content/views/view_pattern_editor.cpp b/plugins/builtin/source/content/views/view_pattern_editor.cpp index 4357d98a1..a712ee409 100644 --- a/plugins/builtin/source/content/views/view_pattern_editor.cpp +++ b/plugins/builtin/source/content/views/view_pattern_editor.cpp @@ -1044,13 +1044,15 @@ namespace hex::plugin::builtin { }); EventManager::subscribe(this, [this] { - this->m_syncPatternSourceCode = ContentRegistry::Settings::read("hex.builtin.setting.general", "hex.builtin.setting.general.sync_pattern_source", 0) == 1; - this->m_autoLoadPatterns = ContentRegistry::Settings::read("hex.builtin.setting.general", "hex.builtin.setting.general.auto_load_patterns", 1) == 1; + this->m_syncPatternSourceCode = ContentRegistry::Settings::read("hex.builtin.setting.general", "hex.builtin.setting.general.sync_pattern_source", false); + this->m_autoLoadPatterns = ContentRegistry::Settings::read("hex.builtin.setting.general", "hex.builtin.setting.general.auto_load_patterns", true); }); EventManager::subscribe(this, [this](prv::Provider *provider) { this->m_shouldAnalyze.get(provider) = true; this->m_envVarEntries->push_back({ 0, "", 0, EnvVarType::Integer }); + + this->m_debuggerDrawer.get(provider) = std::make_unique(); }); EventManager::subscribe(this, [this](prv::Provider *oldProvider, prv::Provider *newProvider) { @@ -1069,10 +1071,6 @@ namespace hex::plugin::builtin { } }); - EventManager::subscribe(this, [this](prv::Provider *provider) { - this->m_debuggerDrawer.get(provider) = std::make_unique(); - }); - EventManager::subscribe(this, [this](prv::Provider *) { if (this->m_syncPatternSourceCode && ImHexApi::Provider::getProviders().empty()) { this->m_textEditor.SetText(""); diff --git a/plugins/builtin/source/content/views/view_settings.cpp b/plugins/builtin/source/content/views/view_settings.cpp index 192c95578..54134c1ba 100644 --- a/plugins/builtin/source/content/views/view_settings.cpp +++ b/plugins/builtin/source/content/views/view_settings.cpp @@ -36,66 +36,76 @@ namespace hex::plugin::builtin { if (ImGui::BeginPopupModal(View::toWindowName("hex.builtin.view.settings.name").c_str(), &this->getWindowOpenState(), ImGuiWindowFlags_NoResize)) { if (ImGui::BeginTabBar("settings")) { - auto &entries = ContentRegistry::Settings::impl::getEntries(); - - // Sort the categories by slot - auto sortedCategories = [&entries] { - std::vector::const_iterator> sortedCategories; - - for (auto it = entries.cbegin(); it != entries.cend(); it++) { - sortedCategories.emplace_back(it); - } - - std::sort(sortedCategories.begin(), sortedCategories.end(), [](auto &item0, auto &item1) { - return item0->first.slot < item1->first.slot; - }); - - return sortedCategories; - }(); - - // Get the description of the current category - const auto &descriptions = ContentRegistry::Settings::impl::getCategoryDescriptions(); + auto &categories = ContentRegistry::Settings::impl::getSettings(); // Draw all categories - for (auto &iter : sortedCategories) { - auto &[category, settings] = *iter; + for (auto &category : categories) { + + // Skip empty categories + if (category.subCategories.empty()) + continue; // For each category, create a new tab - if (ImGui::BeginTabItem(LangEntry(category.name))) { - const std::string &categoryDesc = descriptions.contains(category.name) ? descriptions.at(category.name) : category.name; - + if (ImGui::BeginTabItem(LangEntry(category.unlocalizedName))) { // Draw the category description - LangEntry descriptionEntry(categoryDesc); - ImGui::TextFormattedWrapped("{}", descriptionEntry); - ImGui::InfoTooltip(descriptionEntry); - ImGui::Separator(); + if (!category.unlocalizedDescription.empty()) { + ImGui::TextFormattedWrapped("{}", LangEntry(category.unlocalizedDescription)); + ImGui::NewLine(); + } + + bool firstSubCategory = true; // Draw all settings of that category - for (auto &[name, requiresRestart, callback] : settings) { - // Get the current value of the setting - auto &setting = ContentRegistry::Settings::impl::getSettingsData()[category.name][name]; + for (auto &subCategory : category.subCategories) { - // Execute the settings drawing callback - if (callback(LangEntry(name), setting)) { - // Handle a setting being changed + // Skip empty subcategories + if (subCategory.entries.empty()) + continue; - // Print a debug message - log::debug("Setting [{}]: {} was changed to {}", category.name, name, [&] -> std::string { - if (setting.is_number()) - return std::to_string(setting.get()); - else if (setting.is_string()) - return setting.get(); - else - return ""; - }()); + if (!subCategory.unlocalizedName.empty()) + ImGui::Header(LangEntry(subCategory.unlocalizedName), firstSubCategory); - // Post an event - EventManager::post(); + firstSubCategory = false; - // Request a restart if the setting requires it - if (requiresRestart) - this->m_restartRequested = true; + ImGui::PushStyleVar(ImGuiStyleVar_CellPadding, scaled({5, 5})); + if (ImGui::BeginTable("##subCategory", 1, ImGuiTableFlags_BordersOuter | ImGuiTableFlags_SizingStretchSame)) { + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + + for (auto &setting : subCategory.entries) { + ImGui::BeginDisabled(!setting.widget->isEnabled()); + bool settingChanged = setting.widget->draw(LangEntry(setting.unlocalizedName)); + ImGui::EndDisabled(); + + if (auto tooltip = setting.widget->getTooltip(); tooltip.has_value() && ImGui::IsItemHovered()) + ImGui::InfoTooltip(LangEntry(tooltip.value())); + + auto &widget = setting.widget; + + // Handle a setting being changed + if (settingChanged) { + auto newValue = widget->store(); + + // Write new value to settings + ContentRegistry::Settings::write(category.unlocalizedName, setting.unlocalizedName, newValue); + + // Print a debug message + log::debug("Setting [{} / {}]: Value was changed to {}", category.unlocalizedName, setting.unlocalizedName, nlohmann::to_string(newValue)); + + // Signal that the setting was changed + EventManager::post(); + widget->onChanged(); + + // Request a restart if the setting requires it + if (widget->doesRequireRestart()) + this->m_restartRequested = true; + } + } + + ImGui::EndTable(); } + ImGui::PopStyleVar(); + } ImGui::EndTabItem(); diff --git a/plugins/builtin/source/content/welcome_screen.cpp b/plugins/builtin/source/content/welcome_screen.cpp index e391e0ff3..79cee51db 100644 --- a/plugins/builtin/source/content/welcome_screen.cpp +++ b/plugins/builtin/source/content/welcome_screen.cpp @@ -58,7 +58,7 @@ namespace hex::plugin::builtin { m_restoreCallback(restoreCallback), m_deleteCallback(deleteCallback) { - this->m_reportError = ContentRegistry::Settings::read("hex.builtin.setting.general", "hex.builtin.setting.general.upload_crash_logs", 1); + this->m_reportError = ContentRegistry::Settings::read("hex.builtin.setting.general", "hex.builtin.setting.general.upload_crash_logs", true); } void drawContent() override { @@ -104,7 +104,7 @@ namespace hex::plugin::builtin { } } - ContentRegistry::Settings::write("hex.builtin.setting.general", "hex.builtin.setting.general.upload_crash_logs", i64(this->m_reportError)); + ContentRegistry::Settings::write("hex.builtin.setting.general", "hex.builtin.setting.general.upload_crash_logs", this->m_reportError); this->close(); } @@ -394,7 +394,7 @@ namespace hex::plugin::builtin { (void)EventManager::subscribe([]() { { - auto theme = ContentRegistry::Settings::read("hex.builtin.setting.interface", "hex.builtin.setting.interface.color", ThemeManager::NativeTheme); + auto theme = ContentRegistry::Settings::read("hex.builtin.setting.interface", "hex.builtin.setting.interface.color", ThemeManager::NativeTheme).get(); if (theme != ThemeManager::NativeTheme) { static std::string lastTheme; @@ -407,14 +407,14 @@ namespace hex::plugin::builtin { } { - auto language = ContentRegistry::Settings::read("hex.builtin.setting.interface", "hex.builtin.setting.interface.language", "en-US"); + auto language = ContentRegistry::Settings::read("hex.builtin.setting.interface", "hex.builtin.setting.interface.language", "en-US").get(); if (language != LangEntry::getSelectedLanguage()) LangEntry::loadLanguage(language); } { - auto targetFps = ContentRegistry::Settings::read("hex.builtin.setting.interface", "hex.builtin.setting.interface.fps", 14); + auto targetFps = ContentRegistry::Settings::read("hex.builtin.setting.interface", "hex.builtin.setting.interface.fps", 14).get(); ImHexApi::System::setTargetFPS(targetFps); } @@ -532,7 +532,7 @@ namespace hex::plugin::builtin { auto chosenTip = chosenCategory[random()%chosenCategory.size()]; s_tipOfTheDay = chosenTip.get(); - bool showTipOfTheDay = ContentRegistry::Settings::read("hex.builtin.setting.general", "hex.builtin.setting.general.show_tips", 1); + bool showTipOfTheDay = ContentRegistry::Settings::read("hex.builtin.setting.general", "hex.builtin.setting.general.show_tips", true); if (showTipOfTheDay) PopupTipOfTheDay::open(); } diff --git a/plugins/builtin/source/plugin_builtin.cpp b/plugins/builtin/source/plugin_builtin.cpp index 723507e95..e604cfdd7 100644 --- a/plugins/builtin/source/plugin_builtin.cpp +++ b/plugins/builtin/source/plugin_builtin.cpp @@ -81,6 +81,7 @@ IMHEX_PLUGIN_SETUP("Built-in", "WerWolv", "Default ImHex functionality") { registerPatternLanguageVisualizers(); registerPatternLanguageInlineVisualizers(); registerCommandPaletteCommands(); + registerThemes(); registerSettings(); loadSettings(); registerDataProcessorNodes(); @@ -91,7 +92,6 @@ IMHEX_PLUGIN_SETUP("Built-in", "WerWolv", "Default ImHex functionality") { registerViews(); registerThemeHandlers(); registerStyleHandlers(); - registerThemes(); registerBackgroundServices(); registerNetworkEndpoints(); registerFileHandlers(); diff --git a/plugins/builtin/source/ui/hex_editor.cpp b/plugins/builtin/source/ui/hex_editor.cpp index 7930b5396..dc48cc58d 100644 --- a/plugins/builtin/source/ui/hex_editor.cpp +++ b/plugins/builtin/source/ui/hex_editor.cpp @@ -75,9 +75,9 @@ namespace hex::plugin::builtin::ui { EventManager::subscribe(this, [this] { this->m_selectionColor = ContentRegistry::Settings::read("hex.builtin.setting.hex_editor", "hex.builtin.setting.hex_editor.highlight_color", 0x60C08080); - this->m_syncScrolling = ContentRegistry::Settings::read("hex.builtin.setting.hex_editor", "hex.builtin.setting.hex_editor.sync_scrolling", 0); - this->m_byteCellPadding = ContentRegistry::Settings::read("hex.builtin.setting.hex_editor", "hex.builtin.setting.hex_editor.byte_padding", 0); - this->m_characterCellPadding = ContentRegistry::Settings::read("hex.builtin.setting.hex_editor", "hex.builtin.setting.hex_editor.char_padding", 0); + this->m_syncScrolling = ContentRegistry::Settings::read("hex.builtin.setting.hex_editor", "hex.builtin.setting.hex_editor.sync_scrolling", false); + this->m_byteCellPadding = ContentRegistry::Settings::read("hex.builtin.setting.hex_editor", "hex.builtin.setting.hex_editor.byte_padding", false); + this->m_characterCellPadding = ContentRegistry::Settings::read("hex.builtin.setting.hex_editor", "hex.builtin.setting.hex_editor.char_padding", false); }); } diff --git a/plugins/windows/source/content/settings_entries.cpp b/plugins/windows/source/content/settings_entries.cpp index 27f0e9640..c6bf8b3c8 100644 --- a/plugins/windows/source/content/settings_entries.cpp +++ b/plugins/windows/source/content/settings_entries.cpp @@ -21,13 +21,13 @@ namespace hex::plugin::windows { RegCreateKeyExA(HKEY_CURRENT_USER, ImHexContextMenuKey, 0x00, nullptr, REG_OPTION_NON_VOLATILE, KEY_SET_VALUE, nullptr, &imHexRootKey, nullptr); RegSetValueA(imHexRootKey, nullptr, REG_SZ, "Open with ImHex", 0x00); - // Add Icon key to use first icon embedded in exe + // Add 'Icon' key to use first icon embedded in exe std::array imHexPath = { 0 }; GetModuleFileNameA(nullptr, imHexPath.data(), imHexPath.size()); auto iconValue = hex::format(R"("{}",0)", imHexPath.data()); RegSetKeyValueA(imHexRootKey, nullptr, "Icon", REG_SZ, iconValue.c_str(), iconValue.size() + 1); - // Add command key to pass file path as first argument to ImHex + // Add 'command' key to pass the right-clicked file path as first argument to ImHex auto commandValue = hex::format(R"("{}" "%1")", imHexPath.data()); RegSetValueA(imHexRootKey, "command", REG_SZ, commandValue.c_str(), commandValue.size() + 1); RegCloseKey(imHexRootKey); @@ -51,24 +51,19 @@ namespace hex::plugin::windows { /* General */ - ContentRegistry::Settings::add("hex.builtin.setting.general", "hex.builtin.setting.general.context_menu_entry", 0, [](auto name, nlohmann::json &setting) { - static bool enabled = hasImHexContextMenuEntry(); + namespace Widgets = ContentRegistry::Settings::Widgets; - if (ImGui::Checkbox(name.data(), &enabled)) { + ContentRegistry::Settings::add("hex.builtin.setting.general", "", "hex.builtin.setting.general.context_menu_entry", false) + .setChangedCallback([](auto &widget) { + auto checked = static_cast(widget).isChecked(); - if (enabled) - addImHexContextMenuEntry(); - else - removeImHexContextMenuEntry(); + if (checked) + addImHexContextMenuEntry(); + else + removeImHexContextMenuEntry(); - enabled = hasImHexContextMenuEntry(); - setting = enabled; - - return true; - } - - return false; - }); + widget.load(hasImHexContextMenuEntry()); + }); } } \ No newline at end of file diff --git a/plugins/windows/source/plugin_windows.cpp b/plugins/windows/source/plugin_windows.cpp index 44c108859..c23e8ff66 100644 --- a/plugins/windows/source/plugin_windows.cpp +++ b/plugins/windows/source/plugin_windows.cpp @@ -22,7 +22,7 @@ namespace hex::plugin::windows { static void detectSystemTheme() { // Setup system theme change detector EventManager::subscribe([] { - bool themeFollowSystem = ContentRegistry::Settings::read("hex.builtin.setting.interface", "hex.builtin.setting.interface.color", ThemeManager::NativeTheme) == ThemeManager::NativeTheme; + bool themeFollowSystem = ContentRegistry::Settings::read("hex.builtin.setting.interface", "hex.builtin.setting.interface.color", ThemeManager::NativeTheme).get() == ThemeManager::NativeTheme; if (!themeFollowSystem) return; @@ -43,7 +43,7 @@ static void detectSystemTheme() { }); EventManager::subscribe([=] { - bool themeFollowSystem = ContentRegistry::Settings::read("hex.builtin.setting.interface", "hex.builtin.setting.interface.color", ThemeManager::NativeTheme) == ThemeManager::NativeTheme; + bool themeFollowSystem = ContentRegistry::Settings::read("hex.builtin.setting.interface", "hex.builtin.setting.interface.color", ThemeManager::NativeTheme).get() == ThemeManager::NativeTheme; if (themeFollowSystem) EventManager::post(); @@ -51,7 +51,7 @@ static void detectSystemTheme() { } static void checkBorderlessWindowOverride() { - bool borderlessWindowForced = ContentRegistry::Settings::read("hex.builtin.setting.interface", "hex.builtin.setting.interface.force_borderless_window_mode", 0) != 0; + bool borderlessWindowForced = ContentRegistry::Settings::read("hex.builtin.setting.interface", "hex.builtin.setting.interface.force_borderless_window_mode", false); if (borderlessWindowForced) ImHexApi::System::impl::setBorderlessWindowMode(true);