diff --git a/lib/libimhex/CMakeLists.txt b/lib/libimhex/CMakeLists.txt index 5cdc125cb..42844eed4 100644 --- a/lib/libimhex/CMakeLists.txt +++ b/lib/libimhex/CMakeLists.txt @@ -37,6 +37,7 @@ set(LIBIMHEX_SOURCES source/helpers/tar.cpp source/helpers/debugging.cpp source/helpers/default_paths.cpp + source/helpers/imgui_hooks.cpp source/test/tests.cpp diff --git a/lib/libimhex/include/hex/api/event_manager.hpp b/lib/libimhex/include/hex/api/event_manager.hpp index 0ad558af5..6b7882f47 100644 --- a/lib/libimhex/include/hex/api/event_manager.hpp +++ b/lib/libimhex/include/hex/api/event_manager.hpp @@ -275,6 +275,7 @@ namespace hex { EVENT_DEF_NO_LOG(EventFrameBegin); EVENT_DEF_NO_LOG(EventFrameEnd); EVENT_DEF_NO_LOG(EventSetTaskBarIconState, u32, u32, u32); + EVENT_DEF_NO_LOG(EventImGuiElementRendered, ImGuiID, const std::array&) EVENT_DEF(RequestAddInitTask, std::string, bool, std::function); EVENT_DEF(RequestAddExitTask, std::string, std::function); diff --git a/lib/libimhex/include/hex/api/tutorial_manager.hpp b/lib/libimhex/include/hex/api/tutorial_manager.hpp index d84fcde2f..27439f4da 100644 --- a/lib/libimhex/include/hex/api/tutorial_manager.hpp +++ b/lib/libimhex/include/hex/api/tutorial_manager.hpp @@ -119,6 +119,8 @@ namespace hex { decltype(m_steps)::iterator m_currentStep, m_latestStep; }; + static void init(); + /** * @brief Gets a list of all tutorials * @return List of all tutorials @@ -145,6 +147,10 @@ namespace hex { */ static void startTutorial(const UnlocalizedString &unlocalizedName); + static void startHelpHover(); + static void addInteractiveHelpText(std::initializer_list> &&ids, UnlocalizedString unlocalizedString); + static void addInteractiveHelpLink(std::initializer_list> &&ids, std::string link); + /** * @brief Draws the tutorial diff --git a/lib/libimhex/source/api/tutorial_manager.cpp b/lib/libimhex/source/api/tutorial_manager.cpp index 91fd2c699..fcb2cb474 100644 --- a/lib/libimhex/source/api/tutorial_manager.cpp +++ b/lib/libimhex/source/api/tutorial_manager.cpp @@ -21,6 +21,11 @@ namespace hex { AutoReset> s_highlights; AutoReset>> s_highlightDisplays; + AutoReset>> s_interactiveHelpItems; + ImRect s_hoveredRect; + ImGuiID s_hoveredId; + bool s_helpHoverActive = false; + class IDStack { public: @@ -56,8 +61,42 @@ namespace hex { ImVector idStack; }; + ImGuiID calculateId(const auto &ids) { + IDStack idStack; + + for (const auto &id : ids) { + std::visit(wolv::util::overloaded { + [&idStack](const Lang &id) { + idStack.add(id.get()); + }, + [&idStack](const auto &id) { + idStack.add(id); + } + }, id); + } + + return idStack.get(); + } + } + void TutorialManager::init() { + EventImGuiElementRendered::subscribe([](ImGuiID id, const std::array bb){ + const auto boundingBox = ImRect(bb[0], bb[1], bb[2], bb[3]); + + const auto element = hex::s_highlights->find(id); + if (element != hex::s_highlights->end()) { + hex::s_highlightDisplays->emplace_back(boundingBox, element->second); + } + + if (id != 0 && boundingBox.Contains(ImGui::GetMousePos())) { + if ((s_hoveredRect.GetArea() == 0 || boundingBox.GetArea() < s_hoveredRect.GetArea()) && s_interactiveHelpItems->contains(id)) { + s_hoveredRect = boundingBox; + s_hoveredId = id; + } + } + }); + } const std::map& TutorialManager::getTutorials() { return s_tutorials; @@ -72,6 +111,28 @@ namespace hex { return s_tutorials->try_emplace(unlocalizedName, Tutorial(unlocalizedName, unlocalizedDescription)).first->second; } + void TutorialManager::startHelpHover() { + TaskManager::doLater([]{ + s_helpHoverActive = true; + }); + } + + void TutorialManager::addInteractiveHelpText(std::initializer_list> &&ids, UnlocalizedString text) { + auto id = calculateId(ids); + + s_interactiveHelpItems->emplace(id, [text = std::move(text)]{ + log::info("{}", Lang(text).get()); + }); + } + + void TutorialManager::addInteractiveHelpLink(std::initializer_list> &&ids, std::string link) { + auto id = calculateId(ids); + + s_interactiveHelpItems->emplace(id, [link = std::move(link)]{ + hex::openWebpage(link); + }); + } + void TutorialManager::startTutorial(const UnlocalizedString &unlocalizedName) { s_currentTutorial = s_tutorials->find(unlocalizedName); if (s_currentTutorial == s_tutorials->end()) @@ -81,6 +142,22 @@ namespace hex { } void TutorialManager::drawHighlights() { + if (s_helpHoverActive && s_hoveredId != 0) { + ImGui::GetForegroundDrawList()->AddRectFilled(s_hoveredRect.Min, s_hoveredRect.Max, 0x30FFFFFF); + + if (ImGui::IsMouseClicked(ImGuiMouseButton_Left)) { + s_helpHoverActive = false; + + auto it = s_interactiveHelpItems->find(s_hoveredId); + if (it != s_interactiveHelpItems->end()) { + it->second(); + } + } + + s_hoveredId = 0; + s_hoveredRect = {}; + } + for (const auto &[rect, unlocalizedText] : *s_highlightDisplays) { const auto drawList = ImGui::GetForegroundDrawList(); @@ -241,39 +318,13 @@ namespace hex { m_onAppear(); for (const auto &[text, ids] : m_highlights) { - IDStack idStack; - - for (const auto &id : ids) { - std::visit(wolv::util::overloaded { - [&idStack](const Lang &id) { - idStack.add(id.get()); - }, - [&idStack](const auto &id) { - idStack.add(id); - } - }, id); - } - - s_highlights->emplace(idStack.get(), text); + s_highlights->emplace(calculateId(ids), text); } } void TutorialManager::Tutorial::Step::removeHighlights() const { for (const auto &[text, ids] : m_highlights) { - IDStack idStack; - - for (const auto &id : ids) { - std::visit(wolv::util::overloaded { - [&idStack](const Lang &id) { - idStack.add(id.get()); - }, - [&idStack](const auto &id) { - idStack.add(id); - } - }, id); - } - - s_highlights->erase(idStack.get()); + s_highlights->erase(calculateId(ids)); } } @@ -368,15 +419,4 @@ namespace hex { } } -} - -void ImGuiTestEngineHook_ItemAdd(ImGuiContext*, ImGuiID id, const ImRect& bb, const ImGuiLastItemData*) { - const auto element = hex::s_highlights->find(id); - if (element != hex::s_highlights->end()) { - hex::s_highlightDisplays->emplace_back(bb, element->second); - } -} - -void ImGuiTestEngineHook_ItemInfo(ImGuiContext*, ImGuiID, const char*, ImGuiItemStatusFlags) {} -void ImGuiTestEngineHook_Log(ImGuiContext*, const char*, ...) {} -const char* ImGuiTestEngine_FindItemDebugLabel(ImGuiContext*, ImGuiID) { return nullptr; } \ No newline at end of file +} \ No newline at end of file diff --git a/lib/libimhex/source/helpers/imgui_hooks.cpp b/lib/libimhex/source/helpers/imgui_hooks.cpp new file mode 100644 index 000000000..fa3688f09 --- /dev/null +++ b/lib/libimhex/source/helpers/imgui_hooks.cpp @@ -0,0 +1,15 @@ +#include + +#include +#include + +#include + +void ImGuiTestEngineHook_ItemAdd(ImGuiContext*, ImGuiID id, const ImRect& bb, const ImGuiLastItemData*) { + std::array boundingBox = { bb.Min.x, bb.Min.y, bb.Max.x, bb.Max.y }; + hex::EventImGuiElementRendered::post(id, boundingBox); +} + +void ImGuiTestEngineHook_ItemInfo(ImGuiContext*, ImGuiID, const char*, ImGuiItemStatusFlags) {} +void ImGuiTestEngineHook_Log(ImGuiContext*, const char*, ...) {} +const char* ImGuiTestEngine_FindItemDebugLabel(ImGuiContext*, ImGuiID) { return nullptr; } \ No newline at end of file diff --git a/main/gui/source/window/window.cpp b/main/gui/source/window/window.cpp index 4717b3599..e40998786 100644 --- a/main/gui/source/window/window.cpp +++ b/main/gui/source/window/window.cpp @@ -77,6 +77,8 @@ namespace hex { EventWindowInitialized::post(); EventImHexStartupFinished::post(); + + TutorialManager::init(); } Window::~Window() { diff --git a/plugins/builtin/source/content/main_menu_items.cpp b/plugins/builtin/source/content/main_menu_items.cpp index aa92303e7..26f8a228d 100644 --- a/plugins/builtin/source/content/main_menu_items.cpp +++ b/plugins/builtin/source/content/main_menu_items.cpp @@ -8,6 +8,7 @@ #include #include #include +#include #include #include diff --git a/plugins/builtin/source/content/views/view_pattern_editor.cpp b/plugins/builtin/source/content/views/view_pattern_editor.cpp index 1820424fa..cfda35038 100644 --- a/plugins/builtin/source/content/views/view_pattern_editor.cpp +++ b/plugins/builtin/source/content/views/view_pattern_editor.cpp @@ -1527,7 +1527,7 @@ namespace hex::plugin::builtin { ImGui::TableNextColumn(); ImGuiExt::TextFormatted("{} ", "hex.ui.common.comment"_lang); ImGui::TableNextColumn(); - ImGui::TextWrapped(" \"%s\"", comment.c_str()); + ImGuiExt::TextFormattedWrapped(" \"{}\"", comment); } ImGui::EndTable();