1
0
mirror of synced 2024-11-24 15:50:16 +01:00

feat: Added basic introduction tutorial

This commit is contained in:
WerWolv 2023-12-13 23:03:39 +01:00
parent 346f1362c6
commit f000b6bc0a
16 changed files with 246 additions and 28 deletions

View File

@ -32,7 +32,10 @@
/* Forward declarations */ /* Forward declarations */
struct GLFWwindow; struct GLFWwindow;
namespace hex { class Achievement; } namespace hex {
class Achievement;
class View;
}
namespace hex { namespace hex {
@ -248,6 +251,7 @@ namespace hex {
EVENT_DEF(EventImHexClosing); EVENT_DEF(EventImHexClosing);
EVENT_DEF(EventAchievementUnlocked, const Achievement&); EVENT_DEF(EventAchievementUnlocked, const Achievement&);
EVENT_DEF(EventSearchBoxClicked); EVENT_DEF(EventSearchBoxClicked);
EVENT_DEF(EventViewOpened, View*);
EVENT_DEF(EventProviderDataModified, prv::Provider *, u64, u64, const u8*); EVENT_DEF(EventProviderDataModified, prv::Provider *, u64, u64, const u8*);
EVENT_DEF(EventProviderDataInserted, prv::Provider *, u64, u64); EVENT_DEF(EventProviderDataInserted, prv::Provider *, u64, u64);
@ -269,6 +273,7 @@ namespace hex {
EVENT_DEF(RequestAddBookmark, Region, std::string, std::string, color_t, u64*); EVENT_DEF(RequestAddBookmark, Region, std::string, std::string, color_t, u64*);
EVENT_DEF(RequestRemoveBookmark, u64); EVENT_DEF(RequestRemoveBookmark, u64);
EVENT_DEF(RequestSetPatternLanguageCode, std::string); EVENT_DEF(RequestSetPatternLanguageCode, std::string);
EVENT_DEF(RequestRunPatternCode);
EVENT_DEF(RequestLoadPatternLanguageFile, std::fs::path); EVENT_DEF(RequestLoadPatternLanguageFile, std::fs::path);
EVENT_DEF(RequestSavePatternLanguageFile, std::fs::path); EVENT_DEF(RequestSavePatternLanguageFile, std::fs::path);
EVENT_DEF(RequestUpdateWindowTitle); EVENT_DEF(RequestUpdateWindowTitle);

View File

@ -61,6 +61,9 @@ namespace hex {
*/ */
Step& allowSkip(); Step& allowSkip();
Step& onAppear(std::function<void()> callback);
Step& onComplete(std::function<void()> callback);
/** /**
* @brief Checks if this step is the current step * @brief Checks if this step is the current step
@ -76,7 +79,7 @@ namespace hex {
private: private:
struct Highlight { struct Highlight {
std::string unlocalizedText; std::string unlocalizedText;
ImGuiID highlightId; std::vector<std::variant<Lang, std::string, int>> highlightIds;
}; };
struct Message { struct Message {
@ -97,6 +100,7 @@ namespace hex {
Tutorial *m_parent; Tutorial *m_parent;
std::vector<Highlight> m_highlights; std::vector<Highlight> m_highlights;
std::optional<Message> m_message; std::optional<Message> m_message;
std::function<void()> m_onAppear, m_onComplete;
}; };
Step& addStep(); Step& addStep();

View File

@ -92,6 +92,8 @@ namespace hex {
[[nodiscard]] bool didWindowJustOpen(); [[nodiscard]] bool didWindowJustOpen();
void setWindowJustOpened(bool state); void setWindowJustOpened(bool state);
void trackViewOpenState();
static void discardNavigationRequests(); static void discardNavigationRequests();
[[nodiscard]] static std::string toWindowName(const std::string &unlocalizedName); [[nodiscard]] static std::string toWindowName(const std::string &unlocalizedName);
@ -104,7 +106,7 @@ namespace hex {
private: private:
std::string m_unlocalizedViewName; std::string m_unlocalizedViewName;
bool m_windowOpen = false; bool m_windowOpen = false, m_prevWindowOpen = false;
std::map<Shortcut, ShortcutManager::ShortcutEntry> m_shortcuts; std::map<Shortcut, ShortcutManager::ShortcutEntry> m_shortcuts;
bool m_windowJustOpened = false; bool m_windowJustOpened = false;

View File

@ -1,6 +1,7 @@
#include <hex/api/tutorial_manager.hpp> #include <hex/api/tutorial_manager.hpp>
#include <hex/api/imhex_api.hpp> #include <hex/api/imhex_api.hpp>
#include <hex/api/localization_manager.hpp> #include <hex/api/localization_manager.hpp>
#include <hex/api/task_manager.hpp>
#include <imgui_internal.h> #include <imgui_internal.h>
#include <hex/helpers/utils.hpp> #include <hex/helpers/utils.hpp>
@ -91,15 +92,23 @@ namespace hex {
{ {
if (!unlocalizedText.empty()) { if (!unlocalizedText.empty()) {
const auto margin = ImGui::GetStyle().WindowPadding; const auto mainWindowPos = ImHexApi::System::getMainWindowPosition();
const auto mainWindowSize = ImHexApi::System::getMainWindowSize();
const ImVec2 windowPos = { rect.Min.x + 20_scaled, rect.Max.y + 10_scaled }; const auto margin = ImGui::GetStyle().WindowPadding;
ImVec2 windowPos = { rect.Min.x + 20_scaled, rect.Max.y + 10_scaled };
ImVec2 windowSize = { std::max<float>(rect.Max.x - rect.Min.x - 40_scaled, 300_scaled), 0 }; ImVec2 windowSize = { std::max<float>(rect.Max.x - rect.Min.x - 40_scaled, 300_scaled), 0 };
const char* text = Lang(unlocalizedText); const char* text = Lang(unlocalizedText);
const auto textSize = ImGui::CalcTextSize(text, nullptr, false, windowSize.x - margin.x * 2); const auto textSize = ImGui::CalcTextSize(text, nullptr, false, windowSize.x - margin.x * 2);
windowSize.y = textSize.y + margin.y * 2; windowSize.y = textSize.y + margin.y * 2;
if (windowPos.y + windowSize.y > mainWindowPos.y + mainWindowSize.y)
windowPos.y = rect.Min.y - windowSize.y - 10_scaled;
if (windowPos.y < mainWindowPos.y)
windowPos.y = mainWindowPos.y + 10_scaled;
drawList->AddRectFilled(windowPos, windowPos + windowSize, ImGui::GetColorU32(ImGuiCol_WindowBg) | 0xFF000000); drawList->AddRectFilled(windowPos, windowPos + windowSize, ImGui::GetColorU32(ImGuiCol_WindowBg) | 0xFF000000);
drawList->AddRect(windowPos, windowPos + windowSize, ImGui::GetColorU32(ImGuiCol_Border)); drawList->AddRect(windowPos, windowPos + windowSize, ImGui::GetColorU32(ImGuiCol_Border));
drawList->AddText(nullptr, 0.0F, windowPos + margin, ImGui::GetColorU32(ImGuiCol_Text), text, nullptr, windowSize.x - margin.x * 2); drawList->AddText(nullptr, 0.0F, windowPos + margin, ImGui::GetColorU32(ImGuiCol_Text), text, nullptr, windowSize.x - margin.x * 2);
@ -170,7 +179,7 @@ namespace hex {
ImGui::SameLine(); ImGui::SameLine();
ImGui::BeginDisabled(s_currentTutorial->second.m_currentStep == s_currentTutorial->second.m_steps.end() || (!message->allowSkip && s_currentTutorial->second.m_currentStep == s_currentTutorial->second.m_latestStep)); ImGui::BeginDisabled(!message->allowSkip && s_currentTutorial->second.m_currentStep == s_currentTutorial->second.m_latestStep);
if (ImGui::ArrowButton("Forwards", ImGuiDir_Right)) { if (ImGui::ArrowButton("Forwards", ImGuiDir_Right)) {
s_currentTutorial->second.m_currentStep->advance(1); s_currentTutorial->second.m_currentStep->advance(1);
} }
@ -221,19 +230,51 @@ namespace hex {
} }
void TutorialManager::Tutorial::Step::addHighlights() const { void TutorialManager::Tutorial::Step::addHighlights() const {
for (const auto &[text, id] : this->m_highlights) { if (this->m_onAppear)
s_highlights.emplace(id, text.c_str()); this->m_onAppear();
for (const auto &[text, ids] : this->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.c_str());
} }
} }
void TutorialManager::Tutorial::Step::removeHighlights() const { void TutorialManager::Tutorial::Step::removeHighlights() const {
for (const auto &[text, id] : this->m_highlights) { for (const auto &[text, ids] : this->m_highlights) {
s_highlights.erase(id); 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());
} }
} }
void TutorialManager::Tutorial::Step::advance(i32 steps) const { void TutorialManager::Tutorial::Step::advance(i32 steps) const {
m_parent->m_currentStep->removeHighlights(); m_parent->m_currentStep->removeHighlights();
if (m_parent->m_currentStep == m_parent->m_latestStep && steps > 0)
std::advance(m_parent->m_latestStep, steps);
std::advance(m_parent->m_currentStep, 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())
@ -244,22 +285,9 @@ namespace hex {
TutorialManager::Tutorial::Step& TutorialManager::Tutorial::Step::addHighlight(const std::string& unlocalizedText, std::initializer_list<std::variant<Lang, std::string, int>>&& ids) { TutorialManager::Tutorial::Step& TutorialManager::Tutorial::Step::addHighlight(const std::string& unlocalizedText, std::initializer_list<std::variant<Lang, std::string, int>>&& 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);
}
this->m_highlights.emplace_back( this->m_highlights.emplace_back(
unlocalizedText, unlocalizedText,
idStack.get() ids
); );
return *this; return *this;
@ -297,6 +325,19 @@ namespace hex {
return *this; return *this;
} }
TutorialManager::Tutorial::Step& TutorialManager::Tutorial::Step::onAppear(std::function<void()> callback) {
this->m_onAppear = std::move(callback);
return *this;
}
TutorialManager::Tutorial::Step& TutorialManager::Tutorial::Step::onComplete(std::function<void()> callback) {
this->m_onComplete = std::move(callback);
return *this;
}
bool TutorialManager::Tutorial::Step::isCurrent() const { bool TutorialManager::Tutorial::Step::isCurrent() const {
@ -311,7 +352,12 @@ namespace hex {
void TutorialManager::Tutorial::Step::complete() const { void TutorialManager::Tutorial::Step::complete() const {
if (this->isCurrent()) { if (this->isCurrent()) {
this->advance(); this->advance();
this->m_parent->m_latestStep = this->m_parent->m_currentStep;
if (this->m_onComplete) {
TaskManager::doLater([this] {
this->m_onComplete();
});
}
} }
} }

View File

@ -59,6 +59,13 @@ namespace hex {
this->m_windowJustOpened = state; this->m_windowJustOpened = state;
} }
void View::trackViewOpenState() {
if (this->m_windowOpen && !this->m_prevWindowOpen)
this->setWindowJustOpened(true);
this->m_prevWindowOpen = this->m_windowOpen;
}
void View::discardNavigationRequests() { void View::discardNavigationRequests() {
if (ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows)) if (ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows))
ImGui::GetIO().ConfigFlags &= ~ImGuiConfigFlags_NavEnableKeyboard; ImGui::GetIO().ConfigFlags &= ~ImGuiConfigFlags_NavEnableKeyboard;

View File

@ -789,6 +789,7 @@ namespace hex {
// Draw view // Draw view
view->draw(); view->draw();
view->trackViewOpenState();
if (view->getWindowOpenState()) { if (view->getWindowOpenState()) {
auto window = ImGui::FindWindowByName(view->getName().c_str()); auto window = ImGui::FindWindowByName(view->getName().c_str());
@ -806,6 +807,7 @@ namespace hex {
// Dock the window if it's not already docked // Dock the window if it's not already docked
if (view->didWindowJustOpen() && !ImGui::IsWindowDocked()) { if (view->didWindowJustOpen() && !ImGui::IsWindowDocked()) {
ImGui::DockBuilderDockWindow(windowName.c_str(), ImHexApi::System::getMainDockSpaceId()); ImGui::DockBuilderDockWindow(windowName.c_str(), ImHexApi::System::getMainDockSpaceId());
EventViewOpened::post(view.get());
} }
ImGui::End(); ImGui::End();

View File

@ -82,6 +82,9 @@ add_imhex_plugin(
source/content/tools/tcp_client_server.cpp source/content/tools/tcp_client_server.cpp
source/content/tools/wiki_explainer.cpp source/content/tools/wiki_explainer.cpp
source/content/tutorials/tutorials.cpp
source/content/tutorials/introduction.cpp
source/content/pl_visualizers/line_plot.cpp source/content/pl_visualizers/line_plot.cpp
source/content/pl_visualizers/scatter_plot.cpp source/content/pl_visualizers/scatter_plot.cpp
source/content/pl_visualizers/image.cpp source/content/pl_visualizers/image.cpp

View File

@ -17,7 +17,7 @@ namespace hex::plugin::builtin {
[[nodiscard]] bool hasViewMenuItemEntry() const override { return false; } [[nodiscard]] bool hasViewMenuItemEntry() const override { return false; }
ImVec2 getMinSize() const override { ImVec2 getMinSize() const override {
return scaled({ 400, 300 }); return scaled({ 600, 400 });
} }
ImVec2 getMaxSize() const override { ImVec2 getMaxSize() const override {

View File

@ -740,6 +740,18 @@
"hex.builtin.tools.wiki_explain.invalid_response": "Invalid response from Wikipedia!", "hex.builtin.tools.wiki_explain.invalid_response": "Invalid response from Wikipedia!",
"hex.builtin.tools.wiki_explain.results": "Results", "hex.builtin.tools.wiki_explain.results": "Results",
"hex.builtin.tools.wiki_explain.search": "Search", "hex.builtin.tools.wiki_explain.search": "Search",
"hex.builtin.tutorial.introduction": "Introduction to ImHex",
"hex.builtin.tutorial.introduction.description": "This tutorial will guide you through the basic usage of ImHex to get you started.",
"hex.builtin.tutorial.introduction.step1.title": "Welcome to ImHex!",
"hex.builtin.tutorial.introduction.step1.description": "ImHex is a Reverse Engineering Suite and Hex Editor with its main focus on visualizing binary data for easy comprehension.\n\nYou can continue to the next step by clicking the right arrow button below.",
"hex.builtin.tutorial.introduction.step2.title": "Opening Data",
"hex.builtin.tutorial.introduction.step2.description": "ImHex supports loading data from a variety of sources. This includes Files, Raw disks, another Process's memory and more.\n\nAll these options can be found on the Welcome screen or under the File menu.",
"hex.builtin.tutorial.introduction.step2.highlight": "Let's create a new empty file by clicking on the 'New File' button.",
"hex.builtin.tutorial.introduction.step3.highlight": "This is the Hex Editor. It displays the individual bytes of the loaded data and also allows you to edit them by double clicking one.\n\nYou can navigate the data by using the arrow keys or the mouse wheel.",
"hex.builtin.tutorial.introduction.step4.highlight": "This is the Data Inspector. It displays the data of the currently selected bytes in a more readable format.\n\nYou can also edit the data here by double clicking on a row.",
"hex.builtin.tutorial.introduction.step5.highlight.pattern_editor": "This is the Pattern Editor. It allows you to write code using the Pattern Language which can highlight and decode binary data structures inside of your loaded data.\n\nYou can learn more about the Pattern Language in the documentation.",
"hex.builtin.tutorial.introduction.step5.highlight.pattern_data": "This view contains a tree view representing the data structures you defined using the Pattern Language.",
"hex.builtin.tutorial.introduction.step6.highlight": "You can find more tutorials and documentation in the Help menu.",
"hex.builtin.undo_operation.insert": "Inserted {0}", "hex.builtin.undo_operation.insert": "Inserted {0}",
"hex.builtin.undo_operation.remove": "Removed {0}", "hex.builtin.undo_operation.remove": "Removed {0}",
"hex.builtin.undo_operation.write": "Wrote {0}", "hex.builtin.undo_operation.write": "Wrote {0}",
@ -1180,6 +1192,7 @@
"hex.builtin.popup.safety_backup.report_error": "Send crash log to developers", "hex.builtin.popup.safety_backup.report_error": "Send crash log to developers",
"hex.builtin.popup.safety_backup.restore": "Yes, Restore", "hex.builtin.popup.safety_backup.restore": "Yes, Restore",
"hex.builtin.popup.safety_backup.title": "Restore lost data", "hex.builtin.popup.safety_backup.title": "Restore lost data",
"hex.builtin.popup.play_tutorial.desc": "As this is your first time launching ImHex, would you like to play through the interactive Tutorial?",
"hex.builtin.welcome.start.create_file": "Create New File", "hex.builtin.welcome.start.create_file": "Create New File",
"hex.builtin.welcome.start.open_file": "Open File", "hex.builtin.welcome.start.open_file": "Open File",
"hex.builtin.welcome.start.open_other": "Other Providers", "hex.builtin.welcome.start.open_other": "Other Providers",

View File

@ -522,8 +522,7 @@ namespace hex::plugin::builtin {
if (view->hasViewMenuItemEntry()) { if (view->hasViewMenuItemEntry()) {
auto &state = view->getWindowOpenState(); auto &state = view->getWindowOpenState();
if (ImGui::MenuItem(Lang(view->getUnlocalizedName()), "", &state, ImHexApi::Provider::isValid() && !LayoutManager::isLayoutLocked())) ImGui::MenuItem(Lang(view->getUnlocalizedName()), "", &state, ImHexApi::Provider::isValid() && !LayoutManager::isLayoutLocked());
view->setWindowJustOpened(state);
} }
} }

View File

@ -0,0 +1,106 @@
#include <content/providers/memory_file_provider.hpp>
#include <hex/api/event_manager.hpp>
#include <hex/api/shortcut_manager.hpp>
#include <hex/api/tutorial_manager.hpp>
#include <hex/ui/view.hpp>
namespace hex::plugin::builtin {
void registerIntroductionTutorial() {
using enum TutorialManager::Position;
auto &tutorial = TutorialManager::createTutorial("hex.builtin.tutorial.introduction", "hex.builtin.tutorial.introduction.description");
{
tutorial.addStep()
.setMessage(
"hex.builtin.tutorial.introduction.step1.title",
"hex.builtin.tutorial.introduction.step1.description",
Bottom | Right
)
.allowSkip();
}
{
auto &step = tutorial.addStep();
step.setMessage(
"hex.builtin.tutorial.introduction.step2.title",
"hex.builtin.tutorial.introduction.step2.description",
Bottom | Right
)
.addHighlight("hex.builtin.tutorial.introduction.step2.highlight",
{
"Welcome Screen/Start##SubWindow_685A2CE9",
Lang("hex.builtin.welcome.start.create_file")
})
.onAppear([&step] {
EventProviderOpened::subscribe(&step, [&step](prv::Provider *provider) {
if (dynamic_cast<MemoryFileProvider*>(provider))
step.complete();
});
})
.onComplete([&step] {
EventProviderOpened::unsubscribe(&step);
});
}
{
tutorial.addStep()
.addHighlight("hex.builtin.tutorial.introduction.step3.highlight", {
View::toWindowName("hex.builtin.view.hex_editor.name")
})
.allowSkip();
}
{
tutorial.addStep()
.addHighlight("hex.builtin.tutorial.introduction.step4.highlight", {
View::toWindowName("hex.builtin.view.data_inspector.name")
})
.onAppear([]{
ImHexApi::HexEditor::setSelection(Region { 0, 1 });
})
.allowSkip();
}
{
tutorial.addStep()
.addHighlight("hex.builtin.tutorial.introduction.step5.highlight.pattern_editor", {
View::toWindowName("hex.builtin.view.pattern_editor.name")
})
.addHighlight("hex.builtin.tutorial.introduction.step5.highlight.pattern_data", {
View::toWindowName("hex.builtin.view.pattern_data.name")
})
.onAppear([] {
RequestSetPatternLanguageCode::post("\n\n\n\n\n\nstruct Test {\n u8 value;\n};\n\nTest test @ 0x00;");
RequestRunPatternCode::post();
})
.allowSkip();
}
{
auto &step = tutorial.addStep();
step.addHighlight("hex.builtin.tutorial.introduction.step6.highlight", {
"##MainMenuBar",
"##menubar",
Lang("hex.builtin.menu.help")
})
.addHighlight({
"##Menu_00",
Lang("hex.builtin.view.tutorials.name")
})
.onAppear([&step] {
EventViewOpened::subscribe([&step](View *view){
if (view->getUnlocalizedName() == "hex.builtin.view.tutorials.name")
step.complete();
});
})
.onComplete([&step]{
EventViewOpened::unsubscribe(&step);
})
.allowSkip();
}
}
}

View File

@ -0,0 +1,9 @@
namespace hex::plugin::builtin {
void registerIntroductionTutorial();
void registerTutorials() {
registerIntroductionTutorial();
}
}

View File

@ -145,6 +145,7 @@ namespace hex::plugin::builtin {
ViewPatternEditor::~ViewPatternEditor() { ViewPatternEditor::~ViewPatternEditor() {
RequestSetPatternLanguageCode::unsubscribe(this); RequestSetPatternLanguageCode::unsubscribe(this);
RequestRunPatternCode::unsubscribe(this);
EventFileLoaded::unsubscribe(this); EventFileLoaded::unsubscribe(this);
EventProviderChanged::unsubscribe(this); EventProviderChanged::unsubscribe(this);
EventProviderClosed::unsubscribe(this); EventProviderClosed::unsubscribe(this);
@ -1103,6 +1104,10 @@ namespace hex::plugin::builtin {
this->loadPatternFile(path, ImHexApi::Provider::get()); this->loadPatternFile(path, ImHexApi::Provider::get());
}); });
RequestRunPatternCode::subscribe(this, [this] {
this->m_triggerAutoEvaluate = true;
});
RequestSavePatternLanguageFile::subscribe(this, [this](const std::fs::path &path) { RequestSavePatternLanguageFile::subscribe(this, [this](const std::fs::path &path) {
wolv::io::File file(path, wolv::io::File::Mode::Create); wolv::io::File file(path, wolv::io::File::Mode::Create);
file.writeString(wolv::util::trim(this->m_textEditor.GetText())); file.writeString(wolv::util::trim(this->m_textEditor.GetText()));

View File

@ -27,6 +27,9 @@ namespace hex::plugin::builtin {
if (ImGui::BeginTable("Tutorials", 1, ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg, ImGui::GetContentRegionAvail())) { if (ImGui::BeginTable("Tutorials", 1, ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg, ImGui::GetContentRegionAvail())) {
for (const auto &tutorial : tutorials | std::views::values) { for (const auto &tutorial : tutorials | std::views::values) {
if (this->m_selectedTutorial == nullptr)
this->m_selectedTutorial = &tutorial;
ImGui::TableNextRow(); ImGui::TableNextRow();
ImGui::TableNextColumn(); ImGui::TableNextColumn();

View File

@ -30,6 +30,8 @@
#include <string> #include <string>
#include <random> #include <random>
#include <content/popups/popup_question.hpp>
#include <hex/api/tutorial_manager.hpp>
#include <hex/api/workspace_manager.hpp> #include <hex/api/workspace_manager.hpp>
namespace hex::plugin::builtin { namespace hex::plugin::builtin {
@ -539,6 +541,16 @@ namespace hex::plugin::builtin {
PopupTelemetryRequest::open(); PopupTelemetryRequest::open();
#endif #endif
} }
if (ContentRegistry::Settings::read("hex.builtin.setting.general", "hex.builtin.setting.general.prev_launch_version", "") == "") {
PopupQuestion::open("hex.builtin.popup.play_tutorial.desc"_lang,
[]{
TutorialManager::startTutorial("hex.builtin.tutorial.introduction");
},
[]{ });
}
ContentRegistry::Settings::write("hex.builtin.setting.general", "hex.builtin.setting.general.prev_launch_version", ImHexApi::System::getImHexVersion());
}); });
// Clear project context if we go back to the welcome screen // Clear project context if we go back to the welcome screen

View File

@ -39,6 +39,7 @@ namespace hex::plugin::builtin {
void registerProjectHandlers(); void registerProjectHandlers();
void registerAchievements(); void registerAchievements();
void registerReportGenerators(); void registerReportGenerators();
void registerTutorials();
void loadWorkspaces(); void loadWorkspaces();
void addFooterItems(); void addFooterItems();
@ -109,6 +110,7 @@ IMHEX_PLUGIN_SETUP("Built-in", "WerWolv", "Default ImHex functionality") {
registerCommandForwarders(); registerCommandForwarders();
registerAchievements(); registerAchievements();
registerReportGenerators(); registerReportGenerators();
registerTutorials();
loadWorkspaces(); loadWorkspaces();
addFooterItems(); addFooterItems();