From 21057d51e1b0cd2785d59ebeea7797ae2dd783b3 Mon Sep 17 00:00:00 2001 From: WerWolv Date: Thu, 16 Nov 2023 13:23:28 +0100 Subject: [PATCH] impr: More Welcome Screen UI improvements --- .../include/hex/api/achievement_manager.hpp | 6 ++ .../include/hex/ui/imgui_imhex_extensions.h | 1 + .../source/api/achievement_manager.cpp | 16 +++++ .../source/ui/imgui_imhex_extensions.cpp | 55 ++++++++++++++++ plugins/builtin/romfs/lang/en_US.json | 3 + plugins/builtin/source/content/recent.cpp | 10 ++- .../builtin/source/content/welcome_screen.cpp | 63 ++++++++++++------- 7 files changed, 127 insertions(+), 27 deletions(-) diff --git a/lib/libimhex/include/hex/api/achievement_manager.hpp b/lib/libimhex/include/hex/api/achievement_manager.hpp index f5b89627c..0f65cc756 100644 --- a/lib/libimhex/include/hex/api/achievement_manager.hpp +++ b/lib/libimhex/include/hex/api/achievement_manager.hpp @@ -404,6 +404,12 @@ namespace hex { */ static void clearTemporary(); + /** + * \brief Returns the current progress of all achievements + * \return A pair containing the number of unlocked achievements and the total number of achievements + */ + static std::pair getProgress(); + private: static void achievementAdded(); }; diff --git a/lib/libimhex/include/hex/ui/imgui_imhex_extensions.h b/lib/libimhex/include/hex/ui/imgui_imhex_extensions.h index 264b6c1cf..afc5d61a8 100644 --- a/lib/libimhex/include/hex/ui/imgui_imhex_extensions.h +++ b/lib/libimhex/include/hex/ui/imgui_imhex_extensions.h @@ -113,6 +113,7 @@ namespace ImGui { bool Hyperlink(const char *label, const ImVec2 &size_arg = ImVec2(0, 0), ImGuiButtonFlags flags = 0); bool BulletHyperlink(const char *label, const ImVec2 &size_arg = ImVec2(0, 0), ImGuiButtonFlags flags = 0); bool DescriptionButton(const char *label, const char *description, const ImVec2 &size_arg = ImVec2(0, 0), ImGuiButtonFlags flags = 0); + bool DescriptionButtonProgress(const char *label, const char *description, float fraction, const ImVec2 &size_arg = ImVec2(0, 0), ImGuiButtonFlags flags = 0); void HelpHover(const char *text); diff --git a/lib/libimhex/source/api/achievement_manager.cpp b/lib/libimhex/source/api/achievement_manager.cpp index 953bbf150..2dcbf70c2 100644 --- a/lib/libimhex/source/api/achievement_manager.cpp +++ b/lib/libimhex/source/api/achievement_manager.cpp @@ -157,6 +157,22 @@ namespace hex { getAchievementNodes(false).clear(); } + std::pair AchievementManager::getProgress() { + u32 unlocked = 0; + u32 total = 0; + + for (auto &[categoryName, achievements] : getAchievements()) { + for (auto &[achievementName, achievement] : achievements) { + total += 1; + if (achievement->isUnlocked()) { + unlocked += 1; + } + } + } + + return { unlocked, total }; + } + void AchievementManager::achievementAdded() { getAchievementStartNodes(false).clear(); getAchievementNodes(false).clear(); diff --git a/lib/libimhex/source/ui/imgui_imhex_extensions.cpp b/lib/libimhex/source/ui/imgui_imhex_extensions.cpp index 3369ca4eb..be1d7ff52 100644 --- a/lib/libimhex/source/ui/imgui_imhex_extensions.cpp +++ b/lib/libimhex/source/ui/imgui_imhex_extensions.cpp @@ -265,6 +265,61 @@ namespace ImGui { return pressed; } + bool DescriptionButtonProgress(const char *label, const char *description, float fraction, const ImVec2 &size_arg, ImGuiButtonFlags flags) { + ImGuiWindow *window = GetCurrentWindow(); + if (window->SkipItems) + return false; + + ImGuiContext &g = *GImGui; + const ImGuiStyle &style = g.Style; + const ImGuiID id = window->GetID(label); + const ImVec2 text_size = CalcTextSize((std::string(label) + "\n " + std::string(description)).c_str(), nullptr, true); + const ImVec2 label_size = CalcTextSize(label, nullptr, true); + + ImVec2 pos = window->DC.CursorPos; + if ((flags & ImGuiButtonFlags_AlignTextBaseLine) && style.FramePadding.y < window->DC.CurrLineTextBaseOffset) // Try to vertically align buttons that are smaller/have no padding so that text baseline matches (bit hacky, since it shouldn't be a flag) + pos.y += window->DC.CurrLineTextBaseOffset - style.FramePadding.y; + ImVec2 size = CalcItemSize(size_arg, text_size.x + style.FramePadding.x * 4.0f, text_size.y + style.FramePadding.y * 6.0f); + + const ImRect bb(pos, pos + size); + ItemSize(size, style.FramePadding.y); + if (!ItemAdd(bb, id)) + return false; + + if (g.LastItemData.InFlags & ImGuiItemFlags_ButtonRepeat) + flags |= ImGuiButtonFlags_Repeat; + bool hovered, held; + bool pressed = ButtonBehavior(bb, id, &hovered, &held, flags); + + PushStyleVar(ImGuiStyleVar_ButtonTextAlign, ImVec2(0.0, 0.5)); + PushStyleVar(ImGuiStyleVar_FrameBorderSize, 1); + + // Render + const ImU32 col = GetCustomColorU32((held && hovered) ? ImGuiCustomCol_DescButtonActive : hovered ? ImGuiCustomCol_DescButtonHovered + : ImGuiCustomCol_DescButton); + RenderNavHighlight(bb, id); + RenderFrame(bb.Min, bb.Max, col, false, style.FrameRounding); + PushStyleColor(ImGuiCol_Text, GetColorU32(ImGuiCol_ButtonActive)); + RenderTextWrapped(bb.Min + style.FramePadding * 2, label, nullptr, CalcWrapWidthForPos(window->DC.CursorPos, window->DC.TextWrapPos)); + PopStyleColor(); + PushStyleColor(ImGuiCol_Text, GetColorU32(ImGuiCol_Text)); + RenderTextClipped(bb.Min + style.FramePadding * 2 + ImVec2(style.FramePadding.x * 2, label_size.y), bb.Max - style.FramePadding, description, nullptr, &text_size, style.ButtonTextAlign, &bb); + PopStyleColor(); + + RenderFrame(ImVec2(bb.Min.x, bb.Max.y - 5 * hex::ImHexApi::System::getGlobalScale()), bb.Max, GetColorU32(ImGuiCol_ScrollbarBg), false, style.FrameRounding); + RenderFrame(ImVec2(bb.Min.x, bb.Max.y - 5 * hex::ImHexApi::System::getGlobalScale()), ImVec2(bb.Min.x + fraction * bb.GetSize().x, bb.Max.y), GetColorU32(ImGuiCol_Button), false, style.FrameRounding); + RenderFrame(bb.Min, bb.Max, 0x00, true, style.FrameRounding); + + PopStyleVar(2); + + // Automatically close popups + // if (pressed && !(flags & ImGuiButtonFlags_DontClosePopups) && (window->Flags & ImGuiWindowFlags_Popup)) + // CloseCurrentPopup(); + + IMGUI_TEST_ENGINE_ITEM_INFO(id, label, window->DC.LastItemStatusFlags); + return pressed; + } + void HelpHover(const char *text) { const auto iconColor = ImGui::GetStyleColorVec4(ImGuiCol_ButtonActive); diff --git a/plugins/builtin/romfs/lang/en_US.json b/plugins/builtin/romfs/lang/en_US.json index 365e460c6..63014f276 100644 --- a/plugins/builtin/romfs/lang/en_US.json +++ b/plugins/builtin/romfs/lang/en_US.json @@ -115,6 +115,7 @@ "hex.builtin.common.range.entire_data": "Entire Data", "hex.builtin.common.range.selection": "Selection", "hex.builtin.common.region": "Region", + "hex.builtin.common.remove": "Remove", "hex.builtin.common.reset": "Reset", "hex.builtin.common.set": "Set", "hex.builtin.common.settings": "Settings", @@ -1065,6 +1066,8 @@ "hex.builtin.welcome.help.gethelp.link": "https://github.com/WerWolv/ImHex/discussions/categories/get-help", "hex.builtin.welcome.help.repo": "GitHub Repository", "hex.builtin.welcome.help.repo.link": "https://imhex.werwolv.net/git", + "hex.builtin.welcome.learn.achievements.title": "Achievement Progress", + "hex.builtin.welcome.learn.achievements.desc": "Learn how to use ImHex by completing all achievements", "hex.builtin.welcome.learn.latest.desc": "Read ImHex's current changelog", "hex.builtin.welcome.learn.latest.link": "https://github.com/WerWolv/ImHex/releases/latest", "hex.builtin.welcome.learn.latest.title": "Latest Release", diff --git a/plugins/builtin/source/content/recent.cpp b/plugins/builtin/source/content/recent.cpp index a37c09e7e..d67c51402 100644 --- a/plugins/builtin/source/content/recent.cpp +++ b/plugins/builtin/source/content/recent.cpp @@ -178,19 +178,23 @@ namespace hex::plugin::builtin::recent { const auto &recentEntry = *it; bool shouldRemove = false; + const bool isProject = recentEntry.type == "project"; + ImGui::PushID(&recentEntry); ON_SCOPE_EXIT { ImGui::PopID(); }; const char* icon; - if (recentEntry.type == "project") { + if (isProject) { icon = ICON_VS_PROJECT; } else { icon = ICON_VS_FILE_BINARY; } - if (ImGui::BulletHyperlink(hex::format("{} {}", icon, hex::limitStringLength(recentEntry.displayName, 32)).c_str())) { + if (ImGui::Hyperlink(hex::format("{} {}", icon, hex::limitStringLength(recentEntry.displayName, 32)).c_str())) { loadRecentEntry(recentEntry); break; } + if (!isProject) + ImGui::SetItemTooltip("%s", LangEntry(recentEntry.type).get().c_str()); // Detect right click on recent provider std::string popupID = hex::format("RecentEntryMenu.{}", recentEntry.getHash()); @@ -199,7 +203,7 @@ namespace hex::plugin::builtin::recent { } if (ImGui::BeginPopup(popupID.c_str())) { - if (ImGui::MenuItem("Remove")) { + if (ImGui::MenuItem("hex.builtin.common.remove"_lang)) { shouldRemove = true; } ImGui::EndPopup(); diff --git a/plugins/builtin/source/content/welcome_screen.cpp b/plugins/builtin/source/content/welcome_screen.cpp index 928ed2baa..67513152a 100644 --- a/plugins/builtin/source/content/welcome_screen.cpp +++ b/plugins/builtin/source/content/welcome_screen.cpp @@ -164,38 +164,46 @@ namespace hex::plugin::builtin { ImGui::TableNextRow(ImGuiTableRowFlags_None, ImGui::GetTextLineHeightWithSpacing() * 6); ImGui::TableNextColumn(); + static bool otherProvidersVisible = false; ImGui::SetCursorPosY(ImGui::GetCursorPosY() + 5_scaled); - ImGui::BeginSubWindow("hex.builtin.welcome.header.start"_lang, ImVec2(), ImGuiChildFlags_AutoResizeX); + { - if (ImGui::IconHyperlink(ICON_VS_NEW_FILE, "hex.builtin.welcome.start.create_file"_lang)) { - auto newProvider = hex::ImHexApi::Provider::createProvider("hex.builtin.provider.mem_file", true); - if (newProvider != nullptr && !newProvider->open()) - hex::ImHexApi::Provider::remove(newProvider); - else - EventManager::post(newProvider); + auto startPos = ImGui::GetCursorPos(); + ImGui::BeginSubWindow("hex.builtin.welcome.header.start"_lang, ImVec2(), ImGuiChildFlags_AutoResizeX); + { + if (ImGui::IconHyperlink(ICON_VS_NEW_FILE, "hex.builtin.welcome.start.create_file"_lang)) { + auto newProvider = hex::ImHexApi::Provider::createProvider("hex.builtin.provider.mem_file", true); + if (newProvider != nullptr && !newProvider->open()) + hex::ImHexApi::Provider::remove(newProvider); + else + EventManager::post(newProvider); + } + if (ImGui::IconHyperlink(ICON_VS_GO_TO_FILE, "hex.builtin.welcome.start.open_file"_lang)) + EventManager::post("Open File"); + if (ImGui::IconHyperlink(ICON_VS_NOTEBOOK, "hex.builtin.welcome.start.open_project"_lang)) + EventManager::post("Open Project"); + if (ImGui::IconHyperlink(ICON_VS_TELESCOPE, "hex.builtin.welcome.start.open_other"_lang)) + otherProvidersVisible = !otherProvidersVisible; } - if (ImGui::IconHyperlink(ICON_VS_GO_TO_FILE, "hex.builtin.welcome.start.open_file"_lang)) - EventManager::post("Open File"); - if (ImGui::IconHyperlink(ICON_VS_NOTEBOOK, "hex.builtin.welcome.start.open_project"_lang)) - EventManager::post("Open Project"); - if (ImGui::IconHyperlink(ICON_VS_TELESCOPE, "hex.builtin.welcome.start.open_other"_lang)) - ImGui::OpenPopup("hex.builtin.welcome.start.popup.open_other"_lang); + ImGui::EndSubWindow(); + auto endPos = ImGui::GetCursorPos(); - ImGui::SetNextWindowPos(ImGui::GetWindowPos() + ImGui::GetCursorPos()); - if (ImGui::BeginPopup("hex.builtin.welcome.start.popup.open_other"_lang)) { + if (otherProvidersVisible) { + ImGui::SameLine(0, 2_scaled); + ImGui::SetCursorPos(ImGui::GetCursorPos() + ImVec2(0, (endPos - startPos).y / 2)); + ImGui::TextUnformatted(ICON_VS_ARROW_RIGHT); + ImGui::SameLine(0, 2_scaled); + ImGui::BeginSubWindow("hex.builtin.welcome.start.open_other"_lang, ImVec2(0, ImGui::GetTextLineHeightWithSpacing() * 6), ImGuiChildFlags_AutoResizeX); for (const auto &unlocalizedProviderName : ContentRegistry::Provider::impl::getEntries()) { if (ImGui::Hyperlink(LangEntry(unlocalizedProviderName))) { ImHexApi::Provider::createProvider(unlocalizedProviderName); - ImGui::CloseCurrentPopup(); + otherProvidersVisible = false; } } - - ImGui::EndPopup(); + ImGui::EndSubWindow(); } } - ImGui::EndSubWindow(); - // Draw recent entries ImGui::Dummy({}); @@ -286,16 +294,23 @@ namespace hex::plugin::builtin { ImGui::TableNextColumn(); ImGui::BeginSubWindow("hex.builtin.welcome.header.learn"_lang, ImVec2(ImGui::GetContentRegionAvail().x - windowPadding, 0), ImGuiChildFlags_AutoResizeX); { - if (ImGui::DescriptionButton("hex.builtin.welcome.learn.latest.title"_lang, "hex.builtin.welcome.learn.latest.desc"_lang, ImVec2(ImGui::GetContentRegionAvail().x, 0))) + const auto size = ImVec2(ImGui::GetContentRegionAvail().x, 0); + if (ImGui::DescriptionButton("hex.builtin.welcome.learn.latest.title"_lang, "hex.builtin.welcome.learn.latest.desc"_lang, size)) hex::openWebpage("hex.builtin.welcome.learn.latest.link"_lang); - if (ImGui::DescriptionButton("hex.builtin.welcome.learn.imhex.title"_lang, "hex.builtin.welcome.learn.imhex.desc"_lang, ImVec2(ImGui::GetContentRegionAvail().x, 0))) { + if (ImGui::DescriptionButton("hex.builtin.welcome.learn.imhex.title"_lang, "hex.builtin.welcome.learn.imhex.desc"_lang, size)) { AchievementManager::unlockAchievement("hex.builtin.achievement.starting_out", "hex.builtin.achievement.starting_out.docs.name"); hex::openWebpage("hex.builtin.welcome.learn.imhex.link"_lang); } - if (ImGui::DescriptionButton("hex.builtin.welcome.learn.pattern.title"_lang, "hex.builtin.welcome.learn.pattern.desc"_lang, ImVec2(ImGui::GetContentRegionAvail().x, 0))) + if (ImGui::DescriptionButton("hex.builtin.welcome.learn.pattern.title"_lang, "hex.builtin.welcome.learn.pattern.desc"_lang, size)) hex::openWebpage("hex.builtin.welcome.learn.pattern.link"_lang); - if (ImGui::DescriptionButton("hex.builtin.welcome.learn.plugins.title"_lang, "hex.builtin.welcome.learn.plugins.desc"_lang, ImVec2(ImGui::GetContentRegionAvail().x, 0))) + if (ImGui::DescriptionButton("hex.builtin.welcome.learn.plugins.title"_lang, "hex.builtin.welcome.learn.plugins.desc"_lang, size)) hex::openWebpage("hex.builtin.welcome.learn.plugins.link"_lang); + + if (auto [unlocked, total] = AchievementManager::getProgress(); unlocked != total) { + if (ImGui::DescriptionButtonProgress("hex.builtin.welcome.learn.achievements.title"_lang, "hex.builtin.welcome.learn.achievements.desc"_lang, float(unlocked) / float(total), size)) { + EventManager::post("Achievements"); + } + } } ImGui::EndSubWindow();