diff --git a/lib/libimhex/CMakeLists.txt b/lib/libimhex/CMakeLists.txt index 84d6a62a5..fdfc90a48 100644 --- a/lib/libimhex/CMakeLists.txt +++ b/lib/libimhex/CMakeLists.txt @@ -103,8 +103,8 @@ if (NOT IMHEX_EXTERNAL_PLUGIN_BUILD) target_link_libraries(libimhex PUBLIC ${FOUNDATION}) endif () - target_link_libraries(libimhex PRIVATE microtar libpl plcli libpl-gen libwolv ${NFD_LIBRARIES} magic dl ${NLOHMANN_JSON_LIBRARIES} ${MBEDTLS_LIBRARIES} ${LIBBACKTRACE_LIBRARIES} ${JTHREAD_LIBRARIES}) - target_link_libraries(libimhex PUBLIC ${IMGUI_LIBRARIES}) + target_link_libraries(libimhex PRIVATE microtar libwolv ${NFD_LIBRARIES} magic dl ${NLOHMANN_JSON_LIBRARIES} ${MBEDTLS_LIBRARIES} ${JTHREAD_LIBRARIES}) + target_link_libraries(libimhex PUBLIC libpl ${IMGUI_LIBRARIES}) endif() target_link_libraries(libimhex ${LIBIMHEX_LIBRARY_TYPE} ${NLOHMANN_JSON_LIBRARIES} imgui_all_includes ${FMT_LIBRARIES}) diff --git a/lib/libimhex/include/hex/api/content_registry.hpp b/lib/libimhex/include/hex/api/content_registry.hpp index ed9694c18..894394929 100644 --- a/lib/libimhex/include/hex/api/content_registry.hpp +++ b/lib/libimhex/include/hex/api/content_registry.hpp @@ -18,6 +18,7 @@ using ImGuiDataType = int; using ImGuiInputTextFlags = int; struct ImColor; +enum ImGuiCustomCol : int; namespace hex { @@ -656,6 +657,13 @@ namespace hex { /* Interface Registry. Allows adding new items to various interfaces */ namespace Interface { + struct Icon { + Icon(const char *glyph, ImGuiCustomCol color = ImGuiCustomCol(0x00)) : glyph(glyph), color(color) {} + + std::string glyph; + ImGuiCustomCol color; + }; + namespace impl { using DrawCallback = std::function; @@ -670,12 +678,13 @@ namespace hex { struct MenuItem { std::vector unlocalizedNames; - const char *icon; + Icon icon; std::unique_ptr shortcut; View *view; MenuCallback callback; EnabledCallback enabledCallback; SelectedCallback selectedCallback; + i32 toolbarIndex; }; struct SidebarItem { @@ -723,7 +732,7 @@ namespace hex { */ void addMenuItem( const std::vector &unlocalizedMainMenuNames, - const char *icon, + const Icon &icon, u32 priority, const Shortcut &shortcut, const impl::MenuCallback &function, @@ -743,7 +752,7 @@ namespace hex { */ void addMenuItem( const std::vector &unlocalizedMainMenuNames, - const char *icon, + const Icon &icon, u32 priority, const Shortcut &shortcut, const impl::MenuCallback &function, @@ -829,6 +838,13 @@ namespace hex { */ void addToolbarItem(const impl::DrawCallback &function); + /** + * @brief Adds a menu item to the toolbar + * @param unlocalizedName Unlocalized name of the menu item + * @param color Color of the toolbar icon + */ + void addMenuItemToToolbar(const UnlocalizedString &unlocalizedName, ImGuiCustomCol color); + /** * @brief Adds a new sidebar item * @param icon The icon to use for the item diff --git a/lib/libimhex/include/hex/api/localization_manager.hpp b/lib/libimhex/include/hex/api/localization_manager.hpp index 1bf84dd30..cfdd311b5 100644 --- a/lib/libimhex/include/hex/api/localization_manager.hpp +++ b/lib/libimhex/include/hex/api/localization_manager.hpp @@ -3,6 +3,7 @@ #include #include #include +#include #include diff --git a/lib/libimhex/include/hex/ui/imgui_imhex_extensions.h b/lib/libimhex/include/hex/ui/imgui_imhex_extensions.h index 295680184..ab30cbaa2 100644 --- a/lib/libimhex/include/hex/ui/imgui_imhex_extensions.h +++ b/lib/libimhex/include/hex/ui/imgui_imhex_extensions.h @@ -14,7 +14,7 @@ #include -enum ImGuiCustomCol { +enum ImGuiCustomCol : int { ImGuiCustomCol_DescButton, ImGuiCustomCol_DescButtonHovered, ImGuiCustomCol_DescButtonActive, diff --git a/lib/libimhex/source/api/content_registry.cpp b/lib/libimhex/source/api/content_registry.cpp index db5e68175..dee26e8ef 100644 --- a/lib/libimhex/source/api/content_registry.cpp +++ b/lib/libimhex/source/api/content_registry.cpp @@ -148,8 +148,6 @@ namespace hex { const auto entry = insertOrGetEntry(subCategory->entries, unlocalizedName); entry->widget = std::move(widget); - entry->widget->load(getSetting(unlocalizedCategory, unlocalizedName, entry->widget->store())); - entry->widget->onChanged(); return entry->widget.get(); } @@ -723,22 +721,26 @@ namespace hex { } void addMenuItem(const std::vector &unlocalizedMainMenuNames, u32 priority, const Shortcut &shortcut, const impl::MenuCallback &function, const impl::EnabledCallback& enabledCallback, const impl::SelectedCallback &selectedCallback, View *view) { - addMenuItem(unlocalizedMainMenuNames, nullptr, priority, shortcut, function, enabledCallback, selectedCallback, view); + addMenuItem(unlocalizedMainMenuNames, "", priority, shortcut, function, enabledCallback, selectedCallback, view); } - void addMenuItem(const std::vector &unlocalizedMainMenuNames, const char *icon, u32 priority, const Shortcut &shortcut, const impl::MenuCallback &function, const impl::EnabledCallback& enabledCallback, View *view) { + void addMenuItem(const std::vector &unlocalizedMainMenuNames, const Icon &icon, u32 priority, const Shortcut &shortcut, const impl::MenuCallback &function, const impl::EnabledCallback& enabledCallback, View *view) { addMenuItem(unlocalizedMainMenuNames, icon, priority, shortcut, function, enabledCallback, []{ return false; }, view); } void addMenuItem(const std::vector &unlocalizedMainMenuNames, u32 priority, const Shortcut &shortcut, const impl::MenuCallback &function, const impl::EnabledCallback& enabledCallback, View *view) { - addMenuItem(unlocalizedMainMenuNames, nullptr, priority, shortcut, function, enabledCallback, []{ return false; }, view); + addMenuItem(unlocalizedMainMenuNames, "", priority, shortcut, function, enabledCallback, []{ return false; }, view); } - void addMenuItem(const std::vector &unlocalizedMainMenuNames, const char *icon, u32 priority, const Shortcut &shortcut, const impl::MenuCallback &function, const impl::EnabledCallback& enabledCallback, const impl::SelectedCallback &selectedCallback, View *view) { + void addMenuItem(const std::vector &unlocalizedMainMenuNames, const Icon &icon, u32 priority, const Shortcut &shortcut, const impl::MenuCallback &function, const impl::EnabledCallback& enabledCallback, const impl::SelectedCallback &selectedCallback, View *view) { log::debug("Added new menu item to menu {} with priority {}", unlocalizedMainMenuNames[0].get(), priority); + Icon coloredIcon = icon; + if (coloredIcon.color == 0x00) + coloredIcon.color = ImGuiCustomCol_ToolbarGray; + impl::getMenuItems().insert({ - priority, impl::MenuItem { unlocalizedMainMenuNames, icon, std::make_unique(shortcut), view, function, enabledCallback, selectedCallback } + priority, impl::MenuItem { unlocalizedMainMenuNames, coloredIcon, std::make_unique(shortcut), view, function, enabledCallback, selectedCallback, -1 } }); if (shortcut != Shortcut::None) { @@ -750,7 +752,7 @@ namespace hex { } void addMenuItemSubMenu(std::vector unlocalizedMainMenuNames, u32 priority, const impl::MenuCallback &function, const impl::EnabledCallback& enabledCallback) { - addMenuItemSubMenu(std::move(unlocalizedMainMenuNames), nullptr, priority, function, enabledCallback); + addMenuItemSubMenu(std::move(unlocalizedMainMenuNames), "", priority, function, enabledCallback); } void addMenuItemSubMenu(std::vector unlocalizedMainMenuNames, const char *icon, u32 priority, const impl::MenuCallback &function, const impl::EnabledCallback& enabledCallback) { @@ -758,14 +760,14 @@ namespace hex { unlocalizedMainMenuNames.emplace_back(impl::SubMenuValue); impl::getMenuItems().insert({ - priority, impl::MenuItem { unlocalizedMainMenuNames, icon, std::make_unique(), nullptr, function, enabledCallback, []{ return false; } } + priority, impl::MenuItem { unlocalizedMainMenuNames, icon, std::make_unique(), nullptr, function, enabledCallback, []{ return false; }, -1 } }); } void addMenuItemSeparator(std::vector unlocalizedMainMenuNames, u32 priority) { unlocalizedMainMenuNames.emplace_back(impl::SeparatorValue); impl::getMenuItems().insert({ - priority, impl::MenuItem { unlocalizedMainMenuNames, "", std::make_unique(), nullptr, []{}, []{ return true; }, []{ return false; } } + priority, impl::MenuItem { unlocalizedMainMenuNames, "", std::make_unique(), nullptr, []{}, []{ return true; }, []{ return false; }, -1 } }); } @@ -781,6 +783,21 @@ namespace hex { impl::getToolbarItems().push_back(function); } + void addMenuItemToToolbar(const UnlocalizedString& unlocalizedName, ImGuiCustomCol color) { + auto maxIndex = std::ranges::max_element(impl::getMenuItems(), [](const auto &a, const auto &b) { + return a.second.toolbarIndex < b.second.toolbarIndex; + })->second.toolbarIndex; + + for (auto &[priority, menuItem] : impl::getMenuItems()) { + if (menuItem.unlocalizedNames.back() == unlocalizedName) { + menuItem.toolbarIndex = maxIndex + 1; + menuItem.icon.color = color; + break; + } + } + } + + void addSidebarItem(const std::string &icon, const impl::DrawCallback &function, const impl::EnabledCallback &enabledCallback) { impl::getSidebarItems().push_back({ icon, function, enabledCallback }); } diff --git a/lib/libimhex/source/api/localization_manager.cpp b/lib/libimhex/source/api/localization_manager.cpp index 251023871..49670ef2b 100644 --- a/lib/libimhex/source/api/localization_manager.cpp +++ b/lib/libimhex/source/api/localization_manager.cpp @@ -79,6 +79,7 @@ namespace hex { Lang::Lang(const UnlocalizedString &unlocalizedString) : m_unlocalizedString(unlocalizedString.get()) { } Lang::Lang(std::string_view unlocalizedString) : m_unlocalizedString(unlocalizedString) { } + Lang::operator std::string() const { return get(); } diff --git a/main/gui/CMakeLists.txt b/main/gui/CMakeLists.txt index a3ed23daf..752c217f5 100644 --- a/main/gui/CMakeLists.txt +++ b/main/gui/CMakeLists.txt @@ -54,7 +54,7 @@ set_target_properties(main PROPERTIES target_compile_definitions(main PRIVATE IMHEX_PROJECT_NAME="${PROJECT_NAME}") -target_link_libraries(main PRIVATE libromfs-imhex libimhex libwolv libpl plcli libpl-gen ${FMT_LIBRARIES} LLVMDemangle ${LIBBACKTRACE_LIBRARIES}) +target_link_libraries(main PRIVATE libromfs-imhex libimhex libwolv ${LIBBACKTRACE_LIBRARIES} LLVMDemangle) if (WIN32) target_link_libraries(main PRIVATE usp10 wsock32 ws2_32 Dwmapi.lib) else () diff --git a/main/gui/source/window/window.cpp b/main/gui/source/window/window.cpp index bf0854674..e29a76fe6 100644 --- a/main/gui/source/window/window.cpp +++ b/main/gui/source/window/window.cpp @@ -519,9 +519,9 @@ namespace hex { } for (auto &[priority, menuItem] : ContentRegistry::Interface::impl::getMenuItems()) { - const auto &[unlocalizedNames, icon, shortcut, view, callback, enabledCallback, selectedCallack] = menuItem; + const auto &[unlocalizedNames, icon, shortcut, view, callback, enabledCallback, selectedCallack, toolbarIndex] = menuItem; - createNestedMenu(unlocalizedNames, icon, *shortcut, callback, enabledCallback, selectedCallack); + createNestedMenu(unlocalizedNames, icon.glyph.c_str(), *shortcut, callback, enabledCallback, selectedCallack); } }; @@ -792,10 +792,10 @@ namespace hex { // Draw main menu popups for (auto &[priority, menuItem] : ContentRegistry::Interface::impl::getMenuItems()) { - const auto &[unlocalizedNames, icon, shortcut, view, callback, enabledCallback, selectedCallback] = menuItem; + const auto &[unlocalizedNames, icon, shortcut, view, callback, enabledCallback, selectedCallback, toolbarIndex] = menuItem; if (ImGui::BeginPopup(unlocalizedNames.front().get().c_str())) { - createNestedMenu({ unlocalizedNames.begin() + 1, unlocalizedNames.end() }, icon, *shortcut, callback, enabledCallback, selectedCallback); + createNestedMenu({ unlocalizedNames.begin() + 1, unlocalizedNames.end() }, icon.glyph.c_str(), *shortcut, callback, enabledCallback, selectedCallback); ImGui::EndPopup(); } } diff --git a/main/updater/CMakeLists.txt b/main/updater/CMakeLists.txt index a4caf2f5a..e8f1fd824 100644 --- a/main/updater/CMakeLists.txt +++ b/main/updater/CMakeLists.txt @@ -1,11 +1,11 @@ -project(updater) +project(updater) add_executable(updater source/main.cpp ) target_compile_definitions(updater PRIVATE IMHEX_PROJECT_NAME="${PROJECT_NAME}") -target_link_libraries(updater PRIVATE libimhex wolv::io ${FMT_LIBRARIES}) +target_link_libraries(updater PRIVATE libimhex ${FMT_LIBRARIES}) add_dependencies(main updater) if (APPLE) diff --git a/plugins/builtin/romfs/lang/en_US.json b/plugins/builtin/romfs/lang/en_US.json index a45732cdf..c366d33ff 100644 --- a/plugins/builtin/romfs/lang/en_US.json +++ b/plugins/builtin/romfs/lang/en_US.json @@ -483,6 +483,9 @@ "hex.builtin.setting.proxy.url.tooltip": "http(s):// or socks5:// (e.g., http://127.0.0.1:1080)", "hex.builtin.setting.shortcuts": "Shortcuts", "hex.builtin.setting.shortcuts.global": "Global Shortcuts", + "hex.builtin.setting.toolbar": "Toolbar", + "hex.builtin.setting.toolbar.description": "Add or remove menu options to the toolbar.", + "hex.builtin.setting.toolbar.icons": "Toolbar Icons", "hex.builtin.shortcut.next_provider": "Select next provider", "hex.builtin.shortcut.prev_provider": "Select previous provider", "hex.builtin.title_bar_button.debug_build": "Debug build", diff --git a/plugins/builtin/source/content/main_menu_items.cpp b/plugins/builtin/source/content/main_menu_items.cpp index 2cb435ca7..ced55429a 100644 --- a/plugins/builtin/source/content/main_menu_items.cpp +++ b/plugins/builtin/source/content/main_menu_items.cpp @@ -351,7 +351,7 @@ namespace hex::plugin::builtin { }, noRunningTasks); /* Open File */ - ContentRegistry::Interface::addMenuItem({ "hex.builtin.menu.file", "hex.builtin.menu.file.open_file" }, ICON_VS_OPEN_PREVIEW, 1100, CTRLCMD + Keys::O, [] { + ContentRegistry::Interface::addMenuItem({ "hex.builtin.menu.file", "hex.builtin.menu.file.open_file" }, ICON_VS_FOLDER_OPENED, 1100, CTRLCMD + Keys::O, [] { RequestOpenWindow::post("Open File"); }, noRunningTasks); diff --git a/plugins/builtin/source/content/settings_entries.cpp b/plugins/builtin/source/content/settings_entries.cpp index d862b1567..d711c4f58 100644 --- a/plugins/builtin/source/content/settings_entries.cpp +++ b/plugins/builtin/source/content/settings_entries.cpp @@ -360,6 +360,247 @@ namespace hex::plugin::builtin { bool m_hasDuplicate = false; }; + class ToolbarIconsWidget : public ContentRegistry::Settings::Widgets::Widget { + private: + struct MenuItemSorter { + bool operator()(const auto *a, const auto *b) const { + return a->toolbarIndex < b->toolbarIndex; + } + }; + + public: + bool draw(const std::string &) override { + bool changed = false; + + // Top level layout table + if (ImGui::BeginTable("##top_level", 2, ImGuiTableFlags_None, ImGui::GetContentRegionAvail())) { + ImGui::TableSetupColumn("##left", ImGuiTableColumnFlags_WidthStretch, 0.3F); + ImGui::TableSetupColumn("##right", ImGuiTableColumnFlags_WidthStretch, 0.7F); + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + + // Draw list of menu items that can be added to the toolbar + if (ImGui::BeginTable("##all_icons", 1, ImGuiTableFlags_BordersOuter | ImGuiTableFlags_RowBg | ImGuiTableFlags_ScrollY, ImVec2(0, 280_scaled))) { + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + + // Loop over all available menu items + for (auto &[priority, menuItem] : ContentRegistry::Interface::impl::getMenuItems()) { + // Filter out items without icon, separators, submenus and items that are already in the toolbar + + if (menuItem.icon.glyph.empty()) + continue; + + const auto &unlocalizedName = menuItem.unlocalizedNames.back(); + if (menuItem.unlocalizedNames.size() > 2) + continue; + if (unlocalizedName.get() == ContentRegistry::Interface::impl::SeparatorValue) + continue; + if (unlocalizedName.get() == ContentRegistry::Interface::impl::SubMenuValue) + continue; + if (menuItem.toolbarIndex != -1) + continue; + + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + + // Draw the menu item + ImGui::Selectable(hex::format("{} {}", menuItem.icon.glyph, Lang(unlocalizedName)).c_str(), false, ImGuiSelectableFlags_SpanAllColumns); + + // Handle draggin the menu item to the toolbar box + if (ImGui::BeginDragDropSource()) { + auto ptr = &menuItem; + ImGui::SetDragDropPayload("MENU_ITEM_PAYLOAD", &ptr, sizeof(void*)); + + ImGuiExt::TextFormatted("{} {}", menuItem.icon.glyph, Lang(unlocalizedName)); + + ImGui::EndDragDropSource(); + } + } + + ImGui::EndTable(); + } + + // Handle dropping menu items from the toolbar box + if (ImGui::BeginDragDropTarget()) { + if (auto payload = ImGui::AcceptDragDropPayload("TOOLBAR_ITEM_PAYLOAD"); payload != nullptr) { + auto &menuItem = *static_cast(payload->Data); + + menuItem->toolbarIndex = -1; + changed = true; + } + + ImGui::EndDragDropTarget(); + } + + ImGui::TableNextColumn(); + + // Draw toolbar icon box + ImGuiExt::BeginSubWindow("hex.builtin.setting.toolbar.icons"_lang, ImGui::GetContentRegionAvail()); + { + if (ImGui::BeginTable("##icons", 6, ImGuiTableFlags_SizingStretchSame, ImGui::GetContentRegionAvail())) { + ImGui::TableNextRow(); + + // Find all menu items that are in the toolbar and sort them by their toolbar index + std::set toolbarItems; + for (auto &[priority, menuItem] : ContentRegistry::Interface::impl::getMenuItems()) { + if (menuItem.toolbarIndex == -1) + continue; + + toolbarItems.emplace(&menuItem); + } + + // Loop over all toolbar items + for (auto &menuItem : toolbarItems) { + // Filter out items without icon, separators, submenus and items that are not in the toolbar + if (menuItem->icon.glyph.empty()) + continue; + + const auto &unlocalizedName = menuItem->unlocalizedNames.back(); + if (menuItem->unlocalizedNames.size() > 2) + continue; + if (unlocalizedName.get() == ContentRegistry::Interface::impl::SubMenuValue) + continue; + if (menuItem->toolbarIndex == -1) + continue; + + ImGui::TableNextColumn(); + + // Draw the toolbar item + ImGui::InvisibleButton(unlocalizedName.get().c_str(), ImVec2(ImGui::GetContentRegionAvail().x, ImGui::GetContentRegionAvail().x)); + + // Handle dragging the toolbar item around + if (ImGui::BeginDragDropSource()) { + ImGui::SetDragDropPayload("TOOLBAR_ITEM_PAYLOAD", &menuItem, sizeof(void*)); + + ImGuiExt::TextFormatted("{} {}", menuItem->icon.glyph, Lang(unlocalizedName)); + + ImGui::EndDragDropSource(); + } + + // Handle dropping toolbar items onto each other to reorder them + if (ImGui::BeginDragDropTarget()) { + if (auto payload = ImGui::AcceptDragDropPayload("TOOLBAR_ITEM_PAYLOAD"); payload != nullptr) { + auto &otherMenuItem = *static_cast(payload->Data); + + std::swap(menuItem->toolbarIndex, otherMenuItem->toolbarIndex); + changed = true; + } + + ImGui::EndDragDropTarget(); + } + + // Handle right clicking toolbar items to open the color selection popup + if (ImGui::IsItemClicked(ImGuiMouseButton_Right)) + ImGui::OpenPopup(unlocalizedName.get().c_str()); + + // Draw the color selection popup + if (ImGui::BeginPopup(unlocalizedName.get().c_str())) { + constexpr static std::array Colors = { + ImGuiCustomCol_ToolbarGray, + ImGuiCustomCol_ToolbarRed, + ImGuiCustomCol_ToolbarYellow, + ImGuiCustomCol_ToolbarGreen, + ImGuiCustomCol_ToolbarBlue, + ImGuiCustomCol_ToolbarPurple, + ImGuiCustomCol_ToolbarBrown + }; + + // Draw all the color buttons + for (auto color : Colors) { + ImGui::PushID(&color); + if (ImGui::ColorButton(hex::format("##color{}", u32(color)).c_str(), ImGuiExt::GetCustomColorVec4(color), ImGuiColorEditFlags_NoTooltip | ImGuiColorEditFlags_NoAlpha | ImGuiColorEditFlags_NoPicker, ImVec2(20, 20))) { + menuItem->icon.color = color; + ImGui::CloseCurrentPopup(); + changed = true; + } + ImGui::PopID(); + ImGui::SameLine(); + } + + ImGui::EndPopup(); + } + + auto min = ImGui::GetItemRectMin(); + auto max = ImGui::GetItemRectMax(); + auto iconSize = ImGui::CalcTextSize(menuItem->icon.glyph.c_str()); + + auto text = Lang(unlocalizedName).get(); + if (text.ends_with("...")) + text = text.substr(0, text.size() - 3); + + auto textSize = ImGui::CalcTextSize(text.c_str()); + + // Draw icon and text of the toolbar item + auto drawList = ImGui::GetWindowDrawList(); + drawList->AddText((min + ((max - min) - iconSize) / 2) - ImVec2(0, 10_scaled), ImGuiExt::GetCustomColorU32(ImGuiCustomCol(menuItem->icon.color)), menuItem->icon.glyph.c_str()); + drawList->AddText((min + ((max - min)) / 2) + ImVec2(-textSize.x / 2, 5_scaled), ImGui::GetColorU32(ImGuiCol_Text), text.c_str()); + } + + ImGui::EndTable(); + } + } + ImGuiExt::EndSubWindow(); + + // Handle dropping menu items onto the toolbar box + if (ImGui::BeginDragDropTarget()) { + if (auto payload = ImGui::AcceptDragDropPayload("MENU_ITEM_PAYLOAD"); payload != nullptr) { + auto &menuItem = *static_cast(payload->Data); + + menuItem->toolbarIndex = m_currIndex; + m_currIndex += 1; + changed = true; + } + + ImGui::EndDragDropTarget(); + } + + ImGui::EndTable(); + } + + return changed; + } + + nlohmann::json store() override { + std::map> items; + + for (const auto &[priority, menuItem] : ContentRegistry::Interface::impl::getMenuItems()) { + if (menuItem.toolbarIndex != -1) + items.emplace(menuItem.toolbarIndex, std::make_pair(menuItem.unlocalizedNames.back().get(), menuItem.icon.color)); + } + + return items; + } + + void load(const nlohmann::json &data) override { + if (data.is_null()) + return; + + auto toolbarItems = data.get>>(); + if (toolbarItems.empty()) + return; + + for (auto &[priority, menuItem] : ContentRegistry::Interface::impl::getMenuItems()) + menuItem.toolbarIndex = -1; + + for (auto &[priority, menuItem] : ContentRegistry::Interface::impl::getMenuItems()) { + for (const auto &[index, value] : toolbarItems) { + const auto &[name, color] = value; + if (menuItem.unlocalizedNames.back().get() == name) { + menuItem.toolbarIndex = index; + menuItem.icon.color = ImGuiCustomCol(color); + break; + } + } + } + + m_currIndex = toolbarItems.size(); + } + + private: + i32 m_currIndex = 0; + }; + } void registerSettings() { @@ -536,6 +777,11 @@ namespace hex::plugin::builtin { } }); + /* Toolbar icons */ + ContentRegistry::Settings::setCategoryDescription("hex.builtin.setting.toolbar", "hex.builtin.setting.toolbar.description"); + + ContentRegistry::Settings::add("hex.builtin.setting.toolbar", "", "hex.builtin.setting.toolbar.icons"); + } static void loadLayoutSettings() { diff --git a/plugins/builtin/source/content/ui_items.cpp b/plugins/builtin/source/content/ui_items.cpp index 515cb1f41..20d8536f5 100644 --- a/plugins/builtin/source/content/ui_items.cpp +++ b/plugins/builtin/source/content/ui_items.cpp @@ -233,6 +233,12 @@ namespace hex::plugin::builtin { } } + struct MenuItemSorter { + bool operator()(const auto *a, const auto *b) const { + return a->toolbarIndex < b->toolbarIndex; + } + }; + void addToolbarItems() { ShortcutManager::addGlobalShortcut(AllowWhileTyping + ALT + CTRLCMD + Keys::Left, "hex.builtin.shortcut.prev_provider", []{ auto currIndex = ImHexApi::Provider::getCurrentProviderIndex(); @@ -274,87 +280,39 @@ namespace hex::plugin::builtin { }); ContentRegistry::Interface::addToolbarItem([] { - auto provider = ImHexApi::Provider::get(); - bool providerValid = provider != nullptr; - bool tasksRunning = TaskManager::getRunningTaskCount() > 0; + std::set menuItems; - // Undo - ImGui::BeginDisabled(!providerValid || !provider->canUndo()); - { - if (ImGuiExt::ToolBarButton(ICON_VS_DISCARD, ImGuiExt::GetCustomColorVec4(ImGuiCustomCol_ToolbarBlue))) - provider->undo(); - } - ImGui::EndDisabled(); - - // Redo - ImGui::BeginDisabled(!providerValid || !provider->canRedo()); - { - if (ImGuiExt::ToolBarButton(ICON_VS_REDO, ImGuiExt::GetCustomColorVec4(ImGuiCustomCol_ToolbarBlue))) - provider->redo(); - } - ImGui::EndDisabled(); - - ImGui::SeparatorEx(ImGuiSeparatorFlags_Vertical); - - ImGui::BeginDisabled(tasksRunning); - { - // Create new file - if (ImGuiExt::ToolBarButton(ICON_VS_FILE, ImGuiExt::GetCustomColorVec4(ImGuiCustomCol_ToolbarGray))) { - auto newProvider = hex::ImHexApi::Provider::createProvider("hex.builtin.provider.mem_file", true); - if (newProvider != nullptr && !newProvider->open()) - hex::ImHexApi::Provider::remove(newProvider); - else - EventProviderOpened::post(newProvider); - } - - // Open file - if (ImGuiExt::ToolBarButton(ICON_VS_FOLDER_OPENED, ImGuiExt::GetCustomColorVec4(ImGuiCustomCol_ToolbarBrown))) - RequestOpenWindow::post("Open File"); - } - ImGui::EndDisabled(); - - ImGui::SeparatorEx(ImGuiSeparatorFlags_Vertical); - - // Save file - ImGui::BeginDisabled(!providerValid || !provider->isWritable() || !provider->isSavable()); - { - if (ImGuiExt::ToolBarButton(ICON_VS_SAVE, ImGuiExt::GetCustomColorVec4(ImGuiCustomCol_ToolbarBlue))) - provider->save(); - } - ImGui::EndDisabled(); - - // Save file as - ImGui::BeginDisabled(!providerValid || !provider->isSavable()); - { - if (ImGuiExt::ToolBarButton(ICON_VS_SAVE_AS, ImGuiExt::GetCustomColorVec4(ImGuiCustomCol_ToolbarBlue))) - fs::openFileBrowser(fs::DialogMode::Save, {}, [&provider](auto path) { - provider->saveAs(path); - }); - } - ImGui::EndDisabled(); - - - ImGui::SeparatorEx(ImGuiSeparatorFlags_Vertical); - - - // Create bookmark - ImGui::BeginDisabled(!providerValid || !provider->isReadable() || !ImHexApi::HexEditor::isSelectionValid()); - { - if (ImGuiExt::ToolBarButton(ICON_VS_BOOKMARK, ImGuiExt::GetCustomColorVec4(ImGuiCustomCol_ToolbarGreen))) { - auto region = ImHexApi::HexEditor::getSelection(); - - if (region.has_value()) - ImHexApi::Bookmarks::add(region->getStartAddress(), region->getSize(), {}, {}); + for (const auto &[priority, menuItem] : ContentRegistry::Interface::impl::getMenuItems()) { + if (menuItem.toolbarIndex != -1) { + menuItems.insert(&menuItem); } } - ImGui::EndDisabled(); + + for (const auto &menuItem : menuItems) { + if (menuItem->unlocalizedNames.back().get() == ContentRegistry::Interface::impl::SeparatorValue) { + ImGui::SeparatorEx(ImGuiSeparatorFlags_Vertical); + continue; + } + + ImGui::BeginDisabled(!menuItem->enabledCallback()); + if (ImGuiExt::ToolBarButton(menuItem->icon.glyph.c_str(), ImGuiExt::GetCustomColorVec4(ImGuiCustomCol(menuItem->icon.color)))) { + menuItem->callback(); + } + ImGui::EndDisabled(); + } + }); + + // Provider switcher + ContentRegistry::Interface::addToolbarItem([] { + const auto provider = ImHexApi::Provider::get(); + const bool providerValid = provider != nullptr; + const bool tasksRunning = TaskManager::getRunningTaskCount() > 0; ImGui::SeparatorEx(ImGuiSeparatorFlags_Vertical); ImGui::Spacing(); ImGui::Spacing(); ImGui::Spacing(); - // Provider switcher ImGui::BeginDisabled(!providerValid || tasksRunning); { auto &providers = ImHexApi::Provider::getProviders(); @@ -446,6 +404,14 @@ namespace hex::plugin::builtin { } ImGui::EndDisabled(); }); + + ContentRegistry::Interface::addMenuItemToToolbar("hex.builtin.menu.edit.undo", ImGuiCustomCol_ToolbarBlue); + ContentRegistry::Interface::addMenuItemToToolbar("hex.builtin.menu.edit.redo", ImGuiCustomCol_ToolbarBlue); + ContentRegistry::Interface::addMenuItemToToolbar("hex.builtin.menu.file.create_file", ImGuiCustomCol_ToolbarGray); + ContentRegistry::Interface::addMenuItemToToolbar("hex.builtin.menu.file.open_file", ImGuiCustomCol_ToolbarBrown); + ContentRegistry::Interface::addMenuItemToToolbar("hex.builtin.view.hex_editor.menu.file.save", ImGuiCustomCol_ToolbarBlue); + ContentRegistry::Interface::addMenuItemToToolbar("hex.builtin.view.hex_editor.menu.file.save_as", ImGuiCustomCol_ToolbarBlue); + ContentRegistry::Interface::addMenuItemToToolbar("hex.builtin.menu.edit.bookmark.create", ImGuiCustomCol_ToolbarGreen); } void handleBorderlessWindowMode() { diff --git a/plugins/builtin/source/content/views/view_settings.cpp b/plugins/builtin/source/content/views/view_settings.cpp index ee1d3bc50..c5a7dded1 100644 --- a/plugins/builtin/source/content/views/view_settings.cpp +++ b/plugins/builtin/source/content/views/view_settings.cpp @@ -24,10 +24,26 @@ namespace hex::plugin::builtin { ContentRegistry::Interface::addMenuItem({ "hex.builtin.menu.extras", "hex.builtin.view.settings.name" }, ICON_VS_SETTINGS_GEAR, 4000, Shortcut::None, [&, this] { this->getWindowOpenState() = true; }); + + EventImHexStartupFinished::subscribe(this, [] { + for (const auto &[unlocalizedCategory, unlocalizedDescription, subCategories] : ContentRegistry::Settings::impl::getSettings()) { + for (const auto &[unlocalizedSubCategory, entries] : subCategories) { + for (const auto &[unlocalizedName, widget] : entries) { + try { + widget->load(ContentRegistry::Settings::impl::getSetting(unlocalizedCategory, unlocalizedName, widget->store())); + widget->onChanged(); + } catch (const std::exception &e) { + log::error("Failed to load setting [{} / {}]: {}", unlocalizedCategory.get(), unlocalizedName.get(), e.what()); + } + } + } + } + }); } ViewSettings::~ViewSettings() { RequestOpenWindow::unsubscribe(this); + EventImHexStartupFinished::unsubscribe(this); } void ViewSettings::drawContent() { diff --git a/plugins/builtin/source/plugin_builtin.cpp b/plugins/builtin/source/plugin_builtin.cpp index 7b1fcd575..ed75e92a4 100644 --- a/plugins/builtin/source/plugin_builtin.cpp +++ b/plugins/builtin/source/plugin_builtin.cpp @@ -81,6 +81,8 @@ IMHEX_PLUGIN_SETUP("Built-in", "WerWolv", "Default ImHex functionality") { loadFonts(); extractBundledFiles(); + registerMainMenuEntries(); + registerEventHandlers(); registerDataVisualizers(); registerDataInspectorEntries(); @@ -114,7 +116,5 @@ IMHEX_PLUGIN_SETUP("Built-in", "WerWolv", "Default ImHex functionality") { addToolbarItems(); addGlobalUIItems(); - registerMainMenuEntries(); - handleBorderlessWindowMode(); }