diff --git a/lib/libimhex/include/hex/api/content_registry.hpp b/lib/libimhex/include/hex/api/content_registry.hpp index 174a77cab..cb4af9bae 100644 --- a/lib/libimhex/include/hex/api/content_registry.hpp +++ b/lib/libimhex/include/hex/api/content_registry.hpp @@ -279,6 +279,8 @@ namespace hex { Widgets::Widget* add(const UnlocalizedString &unlocalizedCategory, const UnlocalizedString &unlocalizedSubCategory, const UnlocalizedString &unlocalizedName, std::unique_ptr &&widget); void printSettingReadError(const UnlocalizedString &unlocalizedCategory, const UnlocalizedString &unlocalizedName, const nlohmann::json::exception &e); + + void runOnChangeHandlers(const UnlocalizedString &unlocalizedCategory, const UnlocalizedString &unlocalizedName, const nlohmann::json &value); } template T> @@ -293,6 +295,28 @@ namespace hex { void setCategoryDescription(const UnlocalizedString &unlocalizedCategory, const UnlocalizedString &unlocalizedDescription); + class SettingsValue { + public: + SettingsValue(nlohmann::json value) : m_value(std::move(value)) {} + + template + T get(std::common_type_t defaultValue) const { + try { + auto result = m_value; + if (result.is_number() && std::same_as) + result = m_value.get() != 0; + if (m_value.is_null()) + result = defaultValue; + + return result.get(); + } catch (const nlohmann::json::exception &e) { + return defaultValue; + } + } + private: + nlohmann::json m_value; + }; + template [[nodiscard]] T read(const UnlocalizedString &unlocalizedCategory, const UnlocalizedString &unlocalizedName, const std::common_type_t &defaultValue) { auto setting = impl::getSetting(unlocalizedCategory, unlocalizedName, defaultValue); @@ -314,8 +338,12 @@ namespace hex { template void write(const UnlocalizedString &unlocalizedCategory, const UnlocalizedString &unlocalizedName, const std::common_type_t &value) { impl::getSetting(unlocalizedCategory, unlocalizedName, value) = value; + impl::runOnChangeHandlers(unlocalizedCategory, unlocalizedName, value); } + using OnChangeCallback = std::function; + u64 onChange(const UnlocalizedString &unlocalizedCategory, const UnlocalizedString &unlocalizedName, const OnChangeCallback &callback); + } /* Command Palette Command Registry. Allows adding of new commands to the command palette */ diff --git a/lib/libimhex/include/hex/api/event_manager.hpp b/lib/libimhex/include/hex/api/event_manager.hpp index d38e67bf2..85690ff5f 100644 --- a/lib/libimhex/include/hex/api/event_manager.hpp +++ b/lib/libimhex/include/hex/api/event_manager.hpp @@ -215,7 +215,6 @@ namespace hex { EVENT_DEF(EventHighlightingChanged); EVENT_DEF(EventWindowClosing, GLFWwindow *); EVENT_DEF(EventRegionSelected, ImHexApi::HexEditor::ProviderRegion); - EVENT_DEF(EventSettingsChanged); EVENT_DEF(EventAbnormalTermination, int); EVENT_DEF(EventThemeChanged); EVENT_DEF(EventOSThemeChanged); diff --git a/lib/libimhex/source/api/content_registry.cpp b/lib/libimhex/source/api/content_registry.cpp index a57c33799..fdba2a759 100644 --- a/lib/libimhex/source/api/content_registry.cpp +++ b/lib/libimhex/source/api/content_registry.cpp @@ -8,6 +8,7 @@ #include #include +#include #include #include @@ -29,6 +30,13 @@ namespace hex { namespace impl { + struct OnChange { + u32 id; + OnChangeCallback callback; + }; + + static AutoReset>>> s_onChangeCallbacks; + static AutoReset s_settings; const nlohmann::json& getSettingsData() { return s_settings; @@ -89,6 +97,14 @@ namespace hex { if (!loaded) store(); + + for (const auto &[category, rest] : *impl::s_onChangeCallbacks) { + for (const auto &[name, callbacks] : rest) { + for (const auto &[id, callback] : callbacks) { + callback(getSetting(category, name, {})); + } + } + } } void store() { @@ -160,6 +176,19 @@ namespace hex { hex::log::error("Failed to read setting {}/{}: {}", unlocalizedCategory.get(), unlocalizedName.get(), e.what()); } + void runOnChangeHandlers(const UnlocalizedString &unlocalizedCategory, const UnlocalizedString &unlocalizedName, const nlohmann::json &value) { + if (auto categoryIt = s_onChangeCallbacks->find(unlocalizedCategory); categoryIt != s_onChangeCallbacks->end()) { + if (auto nameIt = categoryIt->second.find(unlocalizedName); nameIt != categoryIt->second.end()) { + for (const auto &[id, callback] : nameIt->second) { + try { + callback(value); + } catch (const nlohmann::json::exception &e) { + log::error("Failed to run onChange handler for setting {}/{}: {}", unlocalizedCategory.get(), unlocalizedName.get(), e.what()); + } + } + } + } + } } @@ -169,6 +198,43 @@ namespace hex { category->unlocalizedDescription = unlocalizedDescription; } + u64 onChange(const UnlocalizedString &unlocalizedCategory, const UnlocalizedString &unlocalizedName, const OnChangeCallback &callback) { + static u64 id = 1; + (*impl::s_onChangeCallbacks)[unlocalizedCategory][unlocalizedName].emplace_back(id, callback); + + auto result = id; + id += 1; + + return result; + } + + void removeOnChangeHandler(u64 id) { + bool done = false; + auto categoryIt = impl::s_onChangeCallbacks->begin(); + for (; categoryIt != impl::s_onChangeCallbacks->end(); ++categoryIt) { + auto nameIt = categoryIt->second.begin(); + for (; nameIt != categoryIt->second.end(); ++nameIt) { + done = std::erase_if(nameIt->second, [id](const impl::OnChange &entry) { + return entry.id == id; + }) > 0; + + if (done) break; + } + + if (done) { + if (nameIt->second.empty()) + categoryIt->second.erase(nameIt); + + break; + } + } + + if (done) { + if (categoryIt->second.empty()) + impl::s_onChangeCallbacks->erase(categoryIt); + } + } + namespace Widgets { bool Checkbox::draw(const std::string &name) { diff --git a/main/gui/source/window/window.cpp b/main/gui/source/window/window.cpp index bc1da14d9..6da4529a5 100644 --- a/main/gui/source/window/window.cpp +++ b/main/gui/source/window/window.cpp @@ -71,7 +71,8 @@ namespace hex { this->registerEventHandlers(); ContentRegistry::Settings::impl::store(); - EventSettingsChanged::post(); + ContentRegistry::Settings::impl::load(); + EventWindowInitialized::post(); EventImHexStartupFinished::post(); } diff --git a/plugins/builtin/source/content/background_services.cpp b/plugins/builtin/source/content/background_services.cpp index 15bd4cdbd..5c07b3395 100644 --- a/plugins/builtin/source/content/background_services.cpp +++ b/plugins/builtin/source/content/background_services.cpp @@ -86,9 +86,12 @@ namespace hex::plugin::builtin { } void registerBackgroundServices() { - EventSettingsChanged::subscribe([]{ - networkInterfaceServiceEnabled = ContentRegistry::Settings::read("hex.builtin.setting.general", "hex.builtin.setting.general.network_interface", false); - autoBackupTime = ContentRegistry::Settings::read("hex.builtin.setting.general", "hex.builtin.setting.general.auto_backup_time", 0) * 30; + ContentRegistry::Settings::onChange("hex.builtin.setting.general", "hex.builtin.setting.general.network_interface", [](const ContentRegistry::Settings::SettingsValue &value) { + networkInterfaceServiceEnabled = value.get(false); + }); + + ContentRegistry::Settings::onChange("hex.builtin.setting.general", "hex.builtin.setting.general.auto_backup_time", [](const ContentRegistry::Settings::SettingsValue &value) { + autoBackupTime = value.get(0) * 30; }); ContentRegistry::BackgroundServices::registerService("hex.builtin.background_service.network_interface", handleNetworkInterfaceService); diff --git a/plugins/builtin/source/content/ui_items.cpp b/plugins/builtin/source/content/ui_items.cpp index b806732b0..c614ac495 100644 --- a/plugins/builtin/source/content/ui_items.cpp +++ b/plugins/builtin/source/content/ui_items.cpp @@ -286,8 +286,8 @@ namespace hex::plugin::builtin { }); static bool alwaysShowProviderTabs = false; - EventSettingsChanged::subscribe([] { - alwaysShowProviderTabs = ContentRegistry::Settings::read("hex.builtin.setting.interface", "hex.builtin.setting.interface.always_show_provider_tabs", false); + ContentRegistry::Settings::onChange("hex.builtin.setting.interface", "hex.builtin.setting.interface.always_show_provider_tabs", [](const ContentRegistry::Settings::SettingsValue &value) { + alwaysShowProviderTabs = value.get(false); }); ContentRegistry::Interface::addToolbarItem([] { diff --git a/plugins/builtin/source/content/views/view_data_inspector.cpp b/plugins/builtin/source/content/views/view_data_inspector.cpp index d599020b4..187bf3ca6 100644 --- a/plugins/builtin/source/content/views/view_data_inspector.cpp +++ b/plugins/builtin/source/content/views/view_data_inspector.cpp @@ -47,9 +47,8 @@ namespace hex::plugin::builtin { m_selectedProvider = nullptr; }); - EventSettingsChanged::subscribe(this, [this] { - auto filterValues = ContentRegistry::Settings::read>("hex.builtin.setting.data_inspector", "hex.builtin.setting.data_inspector.hidden_rows", { }); - + ContentRegistry::Settings::onChange("hex.builtin.setting.data_inspector", "hex.builtin.setting.data_inspector.hidden_rows", [this](const ContentRegistry::Settings::SettingsValue &value) { + auto filterValues = value.get>({}); m_hiddenValues = std::set(filterValues.begin(), filterValues.end()); }); } @@ -57,7 +56,6 @@ namespace hex::plugin::builtin { ViewDataInspector::~ViewDataInspector() { EventRegionSelected::unsubscribe(this); EventProviderClosed::unsubscribe(this); - EventSettingsChanged::unsubscribe(this); } diff --git a/plugins/builtin/source/content/views/view_hex_editor.cpp b/plugins/builtin/source/content/views/view_hex_editor.cpp index 9df06d44e..6de662ef8 100644 --- a/plugins/builtin/source/content/views/view_hex_editor.cpp +++ b/plugins/builtin/source/content/views/view_hex_editor.cpp @@ -462,7 +462,6 @@ namespace hex::plugin::builtin { EventProviderChanged::unsubscribe(this); EventProviderOpened::unsubscribe(this); EventHighlightingChanged::unsubscribe(this); - EventSettingsChanged::unsubscribe(this); ContentRegistry::Settings::write("hex.builtin.setting.hex_editor", "hex.builtin.setting.hex_editor.bytes_per_row", m_hexEditor.getBytesPerRow()); } @@ -878,11 +877,17 @@ namespace hex::plugin::builtin { }); m_hexEditor.setBytesPerRow(ContentRegistry::Settings::read("hex.builtin.setting.hex_editor", "hex.builtin.setting.hex_editor.bytes_per_row", m_hexEditor.getBytesPerRow())); - EventSettingsChanged::subscribe(this, [this] { - m_hexEditor.setSelectionColor(ContentRegistry::Settings::read("hex.builtin.setting.hex_editor", "hex.builtin.setting.hex_editor.highlight_color", 0x60C08080)); - m_hexEditor.enableSyncScrolling(ContentRegistry::Settings::read("hex.builtin.setting.hex_editor", "hex.builtin.setting.hex_editor.sync_scrolling", false)); - m_hexEditor.setByteCellPadding(ContentRegistry::Settings::read("hex.builtin.setting.hex_editor", "hex.builtin.setting.hex_editor.byte_padding", 0)); - m_hexEditor.setCharacterCellPadding(ContentRegistry::Settings::read("hex.builtin.setting.hex_editor", "hex.builtin.setting.hex_editor.char_padding", 0)); + ContentRegistry::Settings::onChange("hex.builtin.setting.hex_editor", "hex.builtin.setting.highlight_color", [this](const ContentRegistry::Settings::SettingsValue &value) { + m_hexEditor.setSelectionColor(value.get(0x60C08080)); + }); + ContentRegistry::Settings::onChange("hex.builtin.setting.hex_editor", "hex.builtin.setting.hex_editor.sync_scrolling", [this](const ContentRegistry::Settings::SettingsValue &value) { + m_hexEditor.enableSyncScrolling(value.get(false)); + }); + ContentRegistry::Settings::onChange("hex.builtin.setting.hex_editor", "hex.builtin.setting.hex_editor.byte_padding", [this](const ContentRegistry::Settings::SettingsValue &value) { + m_hexEditor.setByteCellPadding(value.get(0)); + }); + ContentRegistry::Settings::onChange("hex.builtin.setting.hex_editor", "hex.builtin.setting.hex_editor.char_padding", [this](const ContentRegistry::Settings::SettingsValue &value) { + m_hexEditor.setCharacterCellPadding(value.get(0)); }); } diff --git a/plugins/builtin/source/content/views/view_pattern_data.cpp b/plugins/builtin/source/content/views/view_pattern_data.cpp index ebb796f55..763fa7e9c 100644 --- a/plugins/builtin/source/content/views/view_pattern_data.cpp +++ b/plugins/builtin/source/content/views/view_pattern_data.cpp @@ -12,12 +12,15 @@ namespace hex::plugin::builtin { ViewPatternData::ViewPatternData() : View::Window("hex.builtin.view.pattern_data.name", ICON_VS_DATABASE) { // Handle tree style setting changes - EventSettingsChanged::subscribe(this, [this] { - m_treeStyle = ui::PatternDrawer::TreeStyle(ContentRegistry::Settings::read("hex.builtin.setting.interface", "hex.builtin.setting.interface.pattern_tree_style", 0)); + + ContentRegistry::Settings::onChange("hex.builtin.setting.interface", "hex.builtin.setting.interface.pattern_tree_style", [this](const ContentRegistry::Settings::SettingsValue &value) { + m_treeStyle = ui::PatternDrawer::TreeStyle(value.get(0)); for (auto &drawer : m_patternDrawer.all()) drawer->setTreeStyle(m_treeStyle); + }); - m_rowColoring = ContentRegistry::Settings::read("hex.builtin.setting.interface", "hex.builtin.setting.interface.pattern_data_row_bg", false); + ContentRegistry::Settings::onChange("hex.builtin.setting.interface", "hex.builtin.setting.interface.pattern_data_row_bg", [this](const ContentRegistry::Settings::SettingsValue &value) { + m_rowColoring = bool(value.get(false)); for (auto &drawer : m_patternDrawer.all()) drawer->enableRowColoring(m_rowColoring); }); @@ -48,7 +51,6 @@ namespace hex::plugin::builtin { } ViewPatternData::~ViewPatternData() { - EventSettingsChanged::unsubscribe(this); EventPatternEvaluating::unsubscribe(this); EventPatternExecuted::unsubscribe(this); } diff --git a/plugins/builtin/source/content/views/view_pattern_editor.cpp b/plugins/builtin/source/content/views/view_pattern_editor.cpp index d8446d470..6c30f64d1 100644 --- a/plugins/builtin/source/content/views/view_pattern_editor.cpp +++ b/plugins/builtin/source/content/views/view_pattern_editor.cpp @@ -1657,9 +1657,11 @@ namespace hex::plugin::builtin { m_hasUnevaluatedChanges = true; }); - EventSettingsChanged::subscribe(this, [this] { - m_sourceCode.enableSync(ContentRegistry::Settings::read("hex.builtin.setting.general", "hex.builtin.setting.general.sync_pattern_source", false)); - m_autoLoadPatterns = ContentRegistry::Settings::read("hex.builtin.setting.general", "hex.builtin.setting.general.auto_load_patterns", true); + ContentRegistry::Settings::onChange("hex.builtin.setting.general", "hex.builtin.setting.general.sync_pattern_source", [this](const ContentRegistry::Settings::SettingsValue &value) { + m_sourceCode.enableSync(value.get(false)); + }); + ContentRegistry::Settings::onChange("hex.builtin.setting.general", "hex.builtin.setting.general.auto_load_patterns", [this](const ContentRegistry::Settings::SettingsValue &value) { + m_autoLoadPatterns = value.get(true); }); EventProviderOpened::subscribe(this, [this](prv::Provider *provider) { diff --git a/plugins/builtin/source/content/views/view_settings.cpp b/plugins/builtin/source/content/views/view_settings.cpp index 01d7df4b5..cadf415ce 100644 --- a/plugins/builtin/source/content/views/view_settings.cpp +++ b/plugins/builtin/source/content/views/view_settings.cpp @@ -101,7 +101,6 @@ namespace hex::plugin::builtin { log::debug("Setting [{} / {}]: Value was changed to {}", category.unlocalizedName.get(), setting.unlocalizedName.get(), nlohmann::to_string(newValue)); // Signal that the setting was changed - EventSettingsChanged::post(); widget->onChanged(); // Request a restart if the setting requires it diff --git a/plugins/builtin/source/content/welcome_screen.cpp b/plugins/builtin/source/content/welcome_screen.cpp index f985e713b..7de52842c 100644 --- a/plugins/builtin/source/content/welcome_screen.cpp +++ b/plugins/builtin/source/content/welcome_screen.cpp @@ -443,45 +443,38 @@ namespace hex::plugin::builtin { recent::registerEventHandlers(); recent::updateRecentEntries(); - (void)EventFrameBegin::subscribe(drawWelcomeScreen); + EventFrameBegin::subscribe(drawWelcomeScreen); // Sets a background when they are no views - (void)EventFrameBegin::subscribe([]{ + EventFrameBegin::subscribe([]{ if (ImHexApi::Provider::isValid() && !isAnyViewOpen()) drawNoViewsBackground(); }); - (void)EventSettingsChanged::subscribe([] { - { - auto theme = ContentRegistry::Settings::read("hex.builtin.setting.interface", "hex.builtin.setting.interface.color", ThemeManager::NativeTheme); + ContentRegistry::Settings::onChange("hex.builtin.setting.interface", "hex.builtin.setting.interface.color", [](const ContentRegistry::Settings::SettingsValue &value) { + auto theme = value.get("Dark"); + if (theme != ThemeManager::NativeTheme) { + static std::string lastTheme; - if (theme != ThemeManager::NativeTheme) { - static std::string lastTheme; - - if (theme != lastTheme) { - RequestChangeTheme::post(theme); - lastTheme = theme; - } + if (theme != lastTheme) { + RequestChangeTheme::post(theme); + lastTheme = theme; } - - s_simplifiedWelcomeScreen = ContentRegistry::Settings::read("hex.builtin.setting.interface", "hex.builtin.setting.interface.simplified_welcome_screen", false); - } - - { - auto language = ContentRegistry::Settings::read("hex.builtin.setting.interface", "hex.builtin.setting.interface.language", "en-US"); - - if (language != LocalizationManager::getSelectedLanguage()) - LocalizationManager::loadLanguage(language); - } - - { - auto targetFps = ContentRegistry::Settings::read("hex.builtin.setting.interface", "hex.builtin.setting.interface.fps", 14); - - ImHexApi::System::setTargetFPS(static_cast(targetFps)); } }); + ContentRegistry::Settings::onChange("hex.builtin.setting.interface", "hex.builtin.setting.interface.simplified_welcome_screen", [](const ContentRegistry::Settings::SettingsValue &value) { + s_simplifiedWelcomeScreen = value.get(false); + }); + ContentRegistry::Settings::onChange("hex.builtin.setting.interface", "hex.builtin.setting.interface.language", [](const ContentRegistry::Settings::SettingsValue &value) { + auto language = value.get("en-US"); + if (language != LocalizationManager::getSelectedLanguage()) + LocalizationManager::loadLanguage(language); + }); + ContentRegistry::Settings::onChange("hex.builtin.setting.interface", "hex.builtin.setting.interface.fps", [](const ContentRegistry::Settings::SettingsValue &value) { + ImHexApi::System::setTargetFPS(static_cast(value.get(14))); + }); - (void)RequestChangeTheme::subscribe([](const std::string &theme) { + RequestChangeTheme::subscribe([](const std::string &theme) { auto changeTexture = [&](const std::string &path) { return ImGuiExt::Texture(romfs::get(path).span(), ImGuiExt::Texture::Filter::Linear); }; diff --git a/plugins/ui/source/ui/hex_editor.cpp b/plugins/ui/source/ui/hex_editor.cpp index 05ad75ad6..05a19c4b9 100644 --- a/plugins/ui/source/ui/hex_editor.cpp +++ b/plugins/ui/source/ui/hex_editor.cpp @@ -78,7 +78,7 @@ namespace hex::ui { } HexEditor::~HexEditor() { - EventSettingsChanged::unsubscribe(this); + } constexpr static u16 getByteColumnSeparatorCount(u16 columnCount) { diff --git a/plugins/windows/source/content/ui_items.cpp b/plugins/windows/source/content/ui_items.cpp index be63c6108..a860cee63 100644 --- a/plugins/windows/source/content/ui_items.cpp +++ b/plugins/windows/source/content/ui_items.cpp @@ -16,8 +16,8 @@ namespace hex::plugin::windows { void addFooterItems() { static bool showResourceUsage = true; - EventSettingsChanged::subscribe([]{ - showResourceUsage = ContentRegistry::Settings::read("hex.builtin.setting.interface", "hex.builtin.setting.interface.show_resource_usage", true); + ContentRegistry::Settings::onChange("hex.builtin.setting.interface", "hex.builtin.setting.interface.show_resource_usage", [](const ContentRegistry::Settings::SettingsValue &value) { + showResourceUsage = value.get(false); }); ContentRegistry::Interface::addFooterItem([] {