diff --git a/lib/libimhex/include/hex/api/tutorial_manager.hpp b/lib/libimhex/include/hex/api/tutorial_manager.hpp index 212750777..4ec21a7d9 100644 --- a/lib/libimhex/include/hex/api/tutorial_manager.hpp +++ b/lib/libimhex/include/hex/api/tutorial_manager.hpp @@ -101,6 +101,9 @@ namespace hex { Step& addStep(); + const std::string& getUnlocalizedName() const { return this->m_unlocalizedName; } + const std::string& getUnlocalizedDescription() const { return this->m_unlocalizedDescription; } + private: friend class TutorialManager; @@ -112,6 +115,17 @@ namespace hex { decltype(m_steps)::iterator m_currentStep, m_latestStep; }; + /** + * @brief Gets a list of all tutorials + * @return List of all tutorials + */ + static const std::map& getTutorials(); + + /** + * @brief Gets the currently running tutorial + * @return Iterator pointing to the current tutorial + */ + static std::map::iterator getCurrentTutorial(); /** * @brief Creates a new tutorial that can be started later diff --git a/lib/libimhex/source/api/tutorial_manager.cpp b/lib/libimhex/source/api/tutorial_manager.cpp index 83e224b2a..92ea2b775 100644 --- a/lib/libimhex/source/api/tutorial_manager.cpp +++ b/lib/libimhex/source/api/tutorial_manager.cpp @@ -55,6 +55,16 @@ namespace hex { } + + const std::map& TutorialManager::getTutorials() { + return s_tutorials; + } + + std::map::iterator TutorialManager::getCurrentTutorial() { + return s_currentTutorial; + } + + TutorialManager::Tutorial& TutorialManager::createTutorial(const std::string& unlocalizedName, const std::string& unlocalizedDescription) { return s_tutorials.try_emplace(unlocalizedName, Tutorial(unlocalizedName, unlocalizedDescription)).first->second; } @@ -228,6 +238,8 @@ namespace hex { if (m_parent->m_currentStep != m_parent->m_steps.end()) m_parent->m_currentStep->addHighlights(); + else + s_currentTutorial = s_tutorials.end(); } diff --git a/plugins/builtin/CMakeLists.txt b/plugins/builtin/CMakeLists.txt index 94f19ed89..261cad505 100644 --- a/plugins/builtin/CMakeLists.txt +++ b/plugins/builtin/CMakeLists.txt @@ -116,6 +116,7 @@ add_imhex_plugin( source/content/views/view_logs.cpp source/content/views/view_achievements.cpp source/content/views/view_highlight_rules.cpp + source/content/views/view_tutorials.cpp source/content/views/view_yara.cpp source/content/helpers/notification.cpp diff --git a/plugins/builtin/include/content/views/view_tutorials.hpp b/plugins/builtin/include/content/views/view_tutorials.hpp new file mode 100644 index 000000000..736c0132a --- /dev/null +++ b/plugins/builtin/include/content/views/view_tutorials.hpp @@ -0,0 +1,35 @@ +#pragma once + +#include + +#include + +namespace hex::plugin::builtin { + + class ViewTutorials : public View::Floating { + public: + ViewTutorials(); + ~ViewTutorials() override = default; + + void drawContent() override; + + [[nodiscard]] bool shouldDraw() const override { return true; } + [[nodiscard]] bool hasViewMenuItemEntry() const override { return false; } + + ImVec2 getMinSize() const override { + return scaled({ 400, 300 }); + } + + ImVec2 getMaxSize() const override { + return this->getMinSize(); + } + + ImGuiWindowFlags getWindowFlags() const override { + return Floating::getWindowFlags() | ImGuiWindowFlags_NoResize; + } + + private: + const TutorialManager::Tutorial *m_selectedTutorial = nullptr; + }; + +} \ No newline at end of file diff --git a/plugins/builtin/romfs/lang/en_US.json b/plugins/builtin/romfs/lang/en_US.json index 3a360ee74..1849fbb71 100644 --- a/plugins/builtin/romfs/lang/en_US.json +++ b/plugins/builtin/romfs/lang/en_US.json @@ -1096,6 +1096,9 @@ "hex.builtin.view.theme_manager.save_theme": "Save Theme", "hex.builtin.view.theme_manager.styles": "Styles", "hex.builtin.view.tools.name": "Tools", + "hex.builtin.view.tutorials.name": "Interactive Tutorials", + "hex.builtin.view.tutorials.description": "Description", + "hex.builtin.view.tutorials.start": "Start Tutorial", "hex.builtin.view.yara.error": "Yara Compiler error: {0}", "hex.builtin.view.yara.header.matches": "Matches", "hex.builtin.view.yara.header.rules": "Rules", diff --git a/plugins/builtin/source/content/views.cpp b/plugins/builtin/source/content/views.cpp index 111626270..0814bebcf 100644 --- a/plugins/builtin/source/content/views.cpp +++ b/plugins/builtin/source/content/views.cpp @@ -22,6 +22,7 @@ #include "content/views/view_logs.hpp" #include "content/views/view_achievements.hpp" #include "content/views/view_highlight_rules.hpp" +#include "content/views/view_tutorials.hpp" namespace hex::plugin::builtin { @@ -50,6 +51,7 @@ namespace hex::plugin::builtin { ContentRegistry::Views::add(); ContentRegistry::Views::add(); ContentRegistry::Views::add(); + ContentRegistry::Views::add(); } } \ No newline at end of file diff --git a/plugins/builtin/source/content/views/view_tutorials.cpp b/plugins/builtin/source/content/views/view_tutorials.cpp new file mode 100644 index 000000000..25ba2d8de --- /dev/null +++ b/plugins/builtin/source/content/views/view_tutorials.cpp @@ -0,0 +1,61 @@ +#include "content/views/view_tutorials.hpp" + +#include +#include + +#include + +namespace hex::plugin::builtin { + + ViewTutorials::ViewTutorials() : View::Floating("hex.builtin.view.tutorials.name") { + ContentRegistry::Interface::addMenuItem({ "hex.builtin.menu.help", "hex.builtin.view.tutorials.name" }, 4000, Shortcut::None, [&, this] { + this->getWindowOpenState() = true; + }); + } + + + void ViewTutorials::drawContent() { + const auto& tutorials = TutorialManager::getTutorials(); + const auto& currTutorial = TutorialManager::getCurrentTutorial(); + + if (ImGui::BeginTable("TutorialLayout", 2, ImGuiTableFlags_SizingFixedFit)) { + ImGui::TableSetupColumn("Name", ImGuiTableColumnFlags_WidthStretch, 0.3F); + ImGui::TableSetupColumn("Description", ImGuiTableColumnFlags_WidthStretch, 0.7F); + + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + + if (ImGui::BeginTable("Tutorials", 1, ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg, ImGui::GetContentRegionAvail())) { + for (const auto &tutorial : tutorials | std::views::values) { + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + + if (ImGui::Selectable(Lang(tutorial.getUnlocalizedName()), this->m_selectedTutorial == &tutorial, ImGuiSelectableFlags_SpanAllColumns)) { + this->m_selectedTutorial = &tutorial; + } + } + + ImGui::EndTable(); + } + + ImGui::TableNextColumn(); + + if (this->m_selectedTutorial != nullptr) { + ImGuiExt::BeginSubWindow("hex.builtin.view.tutorials.description"_lang, ImGui::GetContentRegionAvail() - ImVec2(0, ImGui::GetTextLineHeightWithSpacing() + ImGui::GetStyle().ItemSpacing.y * 2)); + { + ImGuiExt::TextFormattedWrapped(Lang(this->m_selectedTutorial->getUnlocalizedDescription())); + } + ImGuiExt::EndSubWindow(); + + ImGui::BeginDisabled(currTutorial != tutorials.end()); + if (ImGuiExt::DimmedButton("hex.builtin.view.tutorials.start"_lang, ImVec2(ImGui::GetContentRegionAvail().x, 0))) { + TutorialManager::startTutorial(this->m_selectedTutorial->getUnlocalizedName()); + } + ImGui::EndDisabled(); + } + + ImGui::EndTable(); + } + } + +} \ No newline at end of file