diff --git a/lib/libimhex/include/hex/api/tutorial_manager.hpp b/lib/libimhex/include/hex/api/tutorial_manager.hpp index 27439f4da..a4786dc6b 100644 --- a/lib/libimhex/include/hex/api/tutorial_manager.hpp +++ b/lib/libimhex/include/hex/api/tutorial_manager.hpp @@ -151,6 +151,9 @@ namespace hex { static void addInteractiveHelpText(std::initializer_list> &&ids, UnlocalizedString unlocalizedString); static void addInteractiveHelpLink(std::initializer_list> &&ids, std::string link); + static void setLastItemInteractiveHelpPopup(std::function callback); + static void setLastItemInteractiveHelpLink(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 1a8a24bed..0ce3d17af 100644 --- a/lib/libimhex/source/api/tutorial_manager.cpp +++ b/lib/libimhex/source/api/tutorial_manager.cpp @@ -11,6 +11,8 @@ #include +#include + namespace hex { namespace { @@ -20,10 +22,12 @@ namespace hex { AutoReset> s_highlights; AutoReset>> s_highlightDisplays; + AutoReset> s_interactiveHelpDisplays; AutoReset>> s_interactiveHelpItems; ImRect s_hoveredRect; ImGuiID s_hoveredId; + ImGuiID s_activeHelpId; bool s_helpHoverActive = false; @@ -91,13 +95,23 @@ namespace hex { 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); + { + const auto element = hex::s_highlights->find(id); + if (element != hex::s_highlights->end()) { + hex::s_highlightDisplays->emplace_back(boundingBox, element->second); + + const auto window = ImGui::GetCurrentWindow(); + if (window != nullptr && window->DockNode != nullptr && window->DockNode->TabBar != nullptr) + window->DockNode->TabBar->NextSelectedTabId = window->TabId; + } + } + + { + const auto element = s_interactiveHelpItems->find(id); + if (element != s_interactiveHelpItems->end()) { + (*s_interactiveHelpDisplays)[id] = boundingBox; + } - const auto window = ImGui::GetCurrentWindow(); - if (window != nullptr && window->DockNode != nullptr && window->DockNode->TabBar != nullptr) - window->DockNode->TabBar->NextSelectedTabId = window->TabId; } if (id != 0 && boundingBox.Contains(ImGui::GetMousePos())) { @@ -128,10 +142,10 @@ namespace hex { }); } - void TutorialManager::addInteractiveHelpText(std::initializer_list> &&ids, UnlocalizedString text) { + void TutorialManager::addInteractiveHelpText(std::initializer_list> &&ids, UnlocalizedString unlocalizedString) { auto id = calculateId(ids); - s_interactiveHelpItems->emplace(id, [text = std::move(text)]{ + s_interactiveHelpItems->emplace(id, [text = std::move(unlocalizedString)]{ log::info("{}", Lang(text).get()); }); } @@ -144,6 +158,39 @@ namespace hex { }); } + void TutorialManager::setLastItemInteractiveHelpPopup(std::function callback) { + auto id = ImGui::GetItemID(); + + if (!s_interactiveHelpItems->contains(id)) { + s_interactiveHelpItems->emplace(id, [id]{ + s_activeHelpId = id; + }); + } + + if (id == s_activeHelpId) { + ImGui::SetNextWindowSize(scaled({ 400, 0 })); + if (ImGui::BeginTooltip()) { + callback(); + ImGui::EndTooltip(); + } + + if (ImGui::IsMouseClicked(ImGuiMouseButton_Left) || ImGui::IsKeyPressed(ImGuiKey_Escape)) + s_activeHelpId = 0; + } + } + + void TutorialManager::setLastItemInteractiveHelpLink(std::string link) { + auto id = ImGui::GetItemID(); + + if (s_interactiveHelpItems->contains(id)) + return; + + 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()) @@ -157,6 +204,19 @@ namespace hex { const auto &drawList = ImGui::GetForegroundDrawList(); drawList->AddText(ImGui::GetMousePos() + scaled({ 10, -5, }), ImGui::GetColorU32(ImGuiCol_Text), "?"); + for (const auto &[id, boundingBox] : *s_interactiveHelpDisplays) { + drawList->AddRect( + boundingBox.Min - ImVec2(5, 5), + boundingBox.Max + ImVec2(5, 5), + ImGui::GetColorU32(ImGuiCol_PlotHistogram), + 5.0F, + ImDrawFlags_None, + 2.0F + ); + } + + s_interactiveHelpDisplays->clear(); + const bool mouseClicked = ImGui::IsMouseClicked(ImGuiMouseButton_Left); if (s_hoveredId != 0) { drawList->AddRectFilled(s_hoveredRect.Min, s_hoveredRect.Max, 0x30FFFFFF); @@ -175,6 +235,11 @@ namespace hex { if (mouseClicked || ImGui::IsKeyPressed(ImGuiKey_Escape)) { s_helpHoverActive = false; } + + // Discard mouse click so it doesn't activate clicked item + ImGui::GetIO().MouseDown[ImGuiMouseButton_Left] = false; + ImGui::GetIO().MouseReleased[ImGuiMouseButton_Left] = false; + ImGui::GetIO().MouseClicked[ImGuiMouseButton_Left] = false; } for (const auto &[rect, unlocalizedText] : *s_highlightDisplays) { diff --git a/main/gui/source/window/window.cpp b/main/gui/source/window/window.cpp index 9cea79681..f97fffa0a 100644 --- a/main/gui/source/window/window.cpp +++ b/main/gui/source/window/window.cpp @@ -299,6 +299,8 @@ namespace hex { ImGui_ImplGlfw_NewFrame(); ImGui::NewFrame(); + TutorialManager::drawTutorial(); + EventFrameBegin::post(); // Handle all undocked floating windows @@ -633,8 +635,6 @@ namespace hex { void Window::frameEnd() { EventFrameEnd::post(); - TutorialManager::drawTutorial(); - // Clean up all tasks that are done TaskManager::collectGarbage(); diff --git a/plugins/builtin/source/content/window_decoration.cpp b/plugins/builtin/source/content/window_decoration.cpp index 9af9ff6c3..f2efe828f 100644 --- a/plugins/builtin/source/content/window_decoration.cpp +++ b/plugins/builtin/source/content/window_decoration.cpp @@ -13,6 +13,7 @@ #include #include +#include #include #include @@ -206,7 +207,7 @@ namespace hex::plugin::builtin { // Draw custom title bar buttons if (!titleBarButtons.empty()) { - ImGui::SetCursorPosX(ImGui::GetWindowWidth() - 7_scaled - buttonSize.x * float((titleBarButtonsVisible ? 4 : 0) + titleBarButtons.size())); + ImGui::SetCursorPosX(ImGui::GetWindowWidth() - 7_scaled - (buttonSize.x + ImGui::GetStyle().ItemSpacing.x) * float((titleBarButtonsVisible ? 4 : 0) + titleBarButtons.size())); if (ImGui::GetCursorPosX() > (searchBoxPos.x + searchBoxSize.x)) { for (const auto &[icon, tooltip, callback] : titleBarButtons) {