impr: Allow tutorials to use markdown formatted text

This commit is contained in:
WerWolv 2025-12-15 20:07:43 +01:00
parent 021c7e5fdb
commit 6b16f39be4
6 changed files with 72 additions and 14 deletions

View File

@ -22,6 +22,8 @@ EXPORT_MODULE namespace hex {
Right = 8
};
using DrawFunction = std::function<void()>;
struct Tutorial {
Tutorial() = delete;
Tutorial(const UnlocalizedString &unlocalizedName, const UnlocalizedString &unlocalizedDescription) :
@ -101,6 +103,7 @@ EXPORT_MODULE namespace hex {
std::vector<Highlight> m_highlights;
std::optional<Message> m_message;
std::function<void()> 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<DrawFunction(const std::string &)> renderer);
private:
TutorialManager() = delete;

View File

@ -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);

View File

@ -32,6 +32,8 @@ namespace hex {
ImGuiID s_activeHelpId;
bool s_helpHoverActive = false;
AutoReset<std::function<std::function<void()>(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<std::string, TutorialManager::Tutorial>& 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<DrawFunction(const std::string &)> 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();
}

View File

@ -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;

View File

@ -1,8 +1,17 @@
#include <hex/api/tutorial_manager.hpp>
#include <ui/markdown.hpp>
namespace hex::plugin::builtin {
void registerIntroductionTutorial();
void registerTutorials() {
TutorialManager::setRenderer([](const std::string &message) {
return [markdown = std::make_shared<ui::Markdown>(message)] {
markdown->draw();
};
});
registerIntroductionTutorial();
}

View File

@ -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();