diff --git a/lib/libimhex/include/hex/api/tutorial_manager.hpp b/lib/libimhex/include/hex/api/tutorial_manager.hpp index 0c8beaf11..af55d517b 100644 --- a/lib/libimhex/include/hex/api/tutorial_manager.hpp +++ b/lib/libimhex/include/hex/api/tutorial_manager.hpp @@ -22,6 +22,8 @@ EXPORT_MODULE namespace hex { Right = 8 }; + using DrawFunction = std::function; + struct Tutorial { Tutorial() = delete; Tutorial(const UnlocalizedString &unlocalizedName, const UnlocalizedString &unlocalizedDescription) : @@ -101,6 +103,7 @@ EXPORT_MODULE namespace hex { std::vector m_highlights; std::optional m_message; std::function m_onAppear, m_onComplete; + DrawFunction m_drawFunction; }; Step& addStep(); @@ -166,6 +169,8 @@ EXPORT_MODULE namespace hex { */ static void reset(); + static void setRenderer(std::function renderer); + private: TutorialManager() = delete; diff --git a/lib/libimhex/include/hex/ui/imgui_imhex_extensions.h b/lib/libimhex/include/hex/ui/imgui_imhex_extensions.h index 9793bfb0d..9ebd13abe 100644 --- a/lib/libimhex/include/hex/ui/imgui_imhex_extensions.h +++ b/lib/libimhex/include/hex/ui/imgui_imhex_extensions.h @@ -319,6 +319,7 @@ namespace ImGuiExt { bool DimmedButtonToggle(const char *icon, bool *v, ImVec2 size = ImVec2(0, 0), ImVec2 iconOffset = ImVec2(0, 0)); bool DimmedIconToggle(const char *icon, bool *v); bool DimmedIconToggle(const char *iconOn, const char *iconOff, bool *v); + bool DimmedArrowButton(const char *id, ImGuiDir dir, ImVec2 size = ImVec2(ImGui::GetFrameHeight(), ImGui::GetFrameHeight())); void TextOverlay(const char *text, ImVec2 pos, float maxWidth = -1); diff --git a/lib/libimhex/source/api/tutorial_manager.cpp b/lib/libimhex/source/api/tutorial_manager.cpp index 4b5ef1a3f..de6e7154d 100644 --- a/lib/libimhex/source/api/tutorial_manager.cpp +++ b/lib/libimhex/source/api/tutorial_manager.cpp @@ -32,6 +32,8 @@ namespace hex { ImGuiID s_activeHelpId; bool s_helpHoverActive = false; + AutoReset(const std::string &)>> s_renderer; + class IDStack { public: @@ -126,6 +128,17 @@ namespace hex { } } }); + + if (*s_renderer == nullptr) { + *s_renderer = [](const std::string &message) { + return [message] { + ImGui::PushTextWrapPos(300_scaled); + ImGui::TextUnformatted(message.c_str()); + ImGui::PopTextWrapPos(); + ImGui::NewLine(); + }; + }; + } } const std::map& TutorialManager::getTutorials() { @@ -333,30 +346,29 @@ namespace hex { ImGui::SetNextWindowPos(position, ImGuiCond_Always, pivot); ImGui::SetNextWindowViewport(ImGui::GetMainViewport()->ID); - if (ImGui::Begin("##TutorialMessage", nullptr, ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse | ImGuiWindowFlags_NoDocking | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoFocusOnAppearing)) { + ImGui::SetNextWindowSize(ImVec2(300_scaled, 0)); + if (ImGui::Begin(message->unlocalizedTitle.empty() ? "##TutorialMessage" : Lang(message->unlocalizedTitle), nullptr, ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse | ImGuiWindowFlags_NoDocking | ImGuiWindowFlags_NoFocusOnAppearing)) { ImGui::BringWindowToDisplayFront(ImGui::GetCurrentWindowRead()); - if (!message->unlocalizedTitle.empty()) - ImGuiExt::Header(Lang(message->unlocalizedTitle), true); - + auto &step = s_currentTutorial->second.m_currentStep; if (!message->unlocalizedMessage.empty()) { - ImGui::PushTextWrapPos(300_scaled); - ImGui::TextUnformatted(Lang(message->unlocalizedMessage)); - ImGui::PopTextWrapPos(); + step->m_drawFunction(); + ImGui::NewLine(); ImGui::NewLine(); } - ImGui::BeginDisabled(s_currentTutorial->second.m_currentStep == s_currentTutorial->second.m_steps.begin()); - if (ImGui::ArrowButton("Backwards", ImGuiDir_Left)) { + ImGui::BeginDisabled(step == s_currentTutorial->second.m_steps.begin()); + if (ImGuiExt::DimmedArrowButton("Backwards", ImGuiDir_Left)) { s_currentTutorial->second.m_currentStep->advance(-1); } ImGui::EndDisabled(); ImGui::SameLine(); - ImGui::BeginDisabled(!message->allowSkip && s_currentTutorial->second.m_currentStep == s_currentTutorial->second.m_latestStep); - if (ImGui::ArrowButton("Forwards", ImGuiDir_Right)) { - s_currentTutorial->second.m_currentStep->advance(1); + ImGui::SetCursorPosX(ImGui::GetWindowWidth() - ImGui::GetFrameHeight() - ImGui::GetStyle().WindowPadding.x); + ImGui::BeginDisabled(!message->allowSkip && step == s_currentTutorial->second.m_latestStep); + if (ImGuiExt::DimmedArrowButton("Forwards", ImGuiDir_Right)) { + step->advance(1); } ImGui::EndDisabled(); } @@ -387,6 +399,10 @@ namespace hex { s_highlightDisplays->clear(); } + void TutorialManager::setRenderer(std::function renderer) { + s_renderer = std::move(renderer); + } + TutorialManager::Tutorial::Step& TutorialManager::Tutorial::addStep() { auto &newStep = m_steps.emplace_back(this); m_currentStep = m_steps.end(); @@ -402,6 +418,9 @@ namespace hex { return; m_currentStep->addHighlights(); + + if (m_currentStep->m_message.has_value()) + m_currentStep->m_drawFunction = (*s_renderer)(Lang(m_currentStep->m_message->unlocalizedMessage)); } void TutorialManager::Tutorial::Step::addHighlights() const { @@ -426,8 +445,12 @@ namespace hex { std::advance(m_parent->m_latestStep, steps); std::advance(m_parent->m_currentStep, steps); - if (m_parent->m_currentStep != m_parent->m_steps.end()) + if (m_parent->m_currentStep != m_parent->m_steps.end()) { m_parent->m_currentStep->addHighlights(); + + if (m_message.has_value()) + m_parent->m_currentStep->m_drawFunction = (*s_renderer)(Lang(m_parent->m_currentStep->m_message->unlocalizedMessage)); + } else s_currentTutorial = s_tutorials->end(); } diff --git a/lib/libimhex/source/ui/imgui_imhex_extensions.cpp b/lib/libimhex/source/ui/imgui_imhex_extensions.cpp index de8cf302c..eb2535a38 100644 --- a/lib/libimhex/source/ui/imgui_imhex_extensions.cpp +++ b/lib/libimhex/source/ui/imgui_imhex_extensions.cpp @@ -1218,6 +1218,21 @@ namespace ImGuiExt { return res; } + bool DimmedArrowButton(const char *id, ImGuiDir dir, ImVec2 size) { + PushStyleColor(ImGuiCol_ButtonHovered, GetCustomColorU32(ImGuiCustomCol_DescButtonHovered)); + PushStyleColor(ImGuiCol_Button, GetCustomColorU32(ImGuiCustomCol_DescButton)); + PushStyleColor(ImGuiCol_Text, GetColorU32(ImGuiCol_ButtonActive)); + PushStyleColor(ImGuiCol_ButtonActive, GetCustomColorU32(ImGuiCustomCol_DescButtonActive)); + PushStyleVar(ImGuiStyleVar_FrameBorderSize, 1.5 * hex::ImHexApi::System::getGlobalScale()); + + bool res = ArrowButtonEx(id, dir, size); + + PopStyleColor(4); + PopStyleVar(1); + + return res; + } + bool DimmedButtonToggle(const char *icon, bool *v, ImVec2 size, ImVec2 iconOffset) { bool pushed = false; bool toggled = false; diff --git a/plugins/builtin/source/content/tutorials/tutorials.cpp b/plugins/builtin/source/content/tutorials/tutorials.cpp index 00b198f32..2f49ab52d 100644 --- a/plugins/builtin/source/content/tutorials/tutorials.cpp +++ b/plugins/builtin/source/content/tutorials/tutorials.cpp @@ -1,9 +1,18 @@ +#include +#include + namespace hex::plugin::builtin { void registerIntroductionTutorial(); void registerTutorials() { + TutorialManager::setRenderer([](const std::string &message) { + return [markdown = std::make_shared(message)] { + markdown->draw(); + }; + }); + registerIntroductionTutorial(); } -} \ No newline at end of file +} diff --git a/plugins/ui/include/ui/markdown.hpp b/plugins/ui/include/ui/markdown.hpp index 10fc655e9..4685ad822 100644 --- a/plugins/ui/include/ui/markdown.hpp +++ b/plugins/ui/include/ui/markdown.hpp @@ -20,6 +20,11 @@ namespace hex::ui { public: Markdown() = default; Markdown(const std::string &text); + Markdown(const Markdown &) = delete; + Markdown(Markdown &&other) = default; + + Markdown &operator=(const Markdown &) = delete; + Markdown &operator=(Markdown &&other) = default; void draw(); void reset();