From 41b2523005f464c1a33173708a9b6f633b793f8f Mon Sep 17 00:00:00 2001 From: Justus Garbe <55301990+jumanji144@users.noreply.github.com> Date: Sat, 8 Jun 2024 13:56:48 +0200 Subject: [PATCH] impr: Refactor various view drawing code (#1698) Refactored: - ViewDatainspector - ViewAbout --------- Co-authored-by: Nik --- .../include/content/views/view_about.hpp | 5 + .../content/views/view_data_inspector.hpp | 16 + .../source/content/views/view_about.cpp | 559 +++++++------ .../content/views/view_data_inspector.cpp | 783 ++++++++++-------- 4 files changed, 752 insertions(+), 611 deletions(-) diff --git a/plugins/builtin/include/content/views/view_about.hpp b/plugins/builtin/include/content/views/view_about.hpp index 7f84a486f..58900bcb1 100644 --- a/plugins/builtin/include/content/views/view_about.hpp +++ b/plugins/builtin/include/content/views/view_about.hpp @@ -4,6 +4,7 @@ #include #include +#include #include namespace hex::plugin::builtin { @@ -33,12 +34,16 @@ namespace hex::plugin::builtin { void drawAboutPopup(); void drawAboutMainPage(); + void drawBuildInformation(); void drawContributorPage(); void drawLibraryCreditsPage(); void drawLoadedPlugins(); + void drawPluginRow(const hex::Plugin& plugin); void drawPathsPage(); void drawReleaseNotesPage(); void drawCommitHistoryPage(); + void drawCommitsTable(const auto& commits); + void drawCommitRow(const auto& commit); void drawLicensePage(); ImGuiExt::Texture m_logoTexture; diff --git a/plugins/builtin/include/content/views/view_data_inspector.hpp b/plugins/builtin/include/content/views/view_data_inspector.hpp index 3e93bd9d4..29c00b3f4 100644 --- a/plugins/builtin/include/content/views/view_data_inspector.hpp +++ b/plugins/builtin/include/content/views/view_data_inspector.hpp @@ -5,6 +5,8 @@ #include #include +#include + #include #include #include @@ -31,7 +33,21 @@ namespace hex::plugin::builtin { private: void invalidateData(); void updateInspectorRows(); + void updateInspectorRowsTask(); + void executeInspectors(); + void executeInspector(const std::string& code, const std::fs::path& path, const std::map& inVariables); + + void inspectorReadFunction(u64 offset, u8 *buffer, size_t size); + + // draw functions + void drawEndianSetting(); + void drawRadixSetting(); + void drawInvertSetting(); + void drawInspectorRows(); + void drawInspectorRow(InspectorCacheEntry& entry); + + ContentRegistry::DataInspector::impl::DisplayFunction createPatternErrorDisplayFunction(); private: bool m_shouldInvalidate = true; diff --git a/plugins/builtin/source/content/views/view_about.cpp b/plugins/builtin/source/content/views/view_about.cpp index 731df8210..7c02a9795 100644 --- a/plugins/builtin/source/content/views/view_about.cpp +++ b/plugins/builtin/source/content/views/view_about.cpp @@ -1,4 +1,5 @@ #include "content/views/view_about.hpp" +#include "hex/ui/popup.hpp" #include #include @@ -128,58 +129,7 @@ namespace hex::plugin::builtin { ImGuiExt::BeginSubWindow("Build Information", ImVec2(450_scaled, 0), ImGuiChildFlags_AutoResizeX | ImGuiChildFlags_AutoResizeY); { - if (ImGui::BeginTable("Information", 1, ImGuiTableFlags_RowBg | ImGuiTableFlags_BordersInner)) { - ImGui::Indent(); - - ImGui::TableNextRow(); - ImGui::TableNextColumn(); - { - // Draw basic information about ImHex and its version - ImGuiExt::TextFormatted("ImHex Hex Editor v{} by WerWolv", ImHexApi::System::getImHexVersion()); - ImGui::Indent(25_scaled); - ImGuiExt::TextFormatted("Powered by Dear ImGui v{}", ImGui::GetVersion()); - ImGui::Unindent(25_scaled); - } - - ImGui::TableNextColumn(); - { - ImGuiExt::TextFormatted(" {} ", ICON_VS_SOURCE_CONTROL); - - ImGui::SameLine(0, 0); - - // Draw a clickable link to the current commit - if (ImGuiExt::Hyperlink(hex::format("{0}@{1}", ImHexApi::System::getCommitBranch(), ImHexApi::System::getCommitHash()).c_str())) - hex::openWebpage("https://github.com/WerWolv/ImHex/commit/" + ImHexApi::System::getCommitHash(true)); - } - - ImGui::TableNextColumn(); - { - // Draw the build date and time - ImGuiExt::TextFormatted("Compiled on {} at {}", __DATE__, __TIME__); - } - - ImGui::TableNextColumn(); - { - // Draw the author of the current translation - ImGui::TextUnformatted("hex.builtin.view.help.about.translator"_lang); - } - - ImGui::TableNextColumn(); - { - // Draw information about the open-source nature of ImHex - ImGui::TextUnformatted("hex.builtin.view.help.about.source"_lang); - - ImGui::SameLine(); - - // Draw a clickable link to the GitHub repository - if (ImGuiExt::Hyperlink(ICON_VS_LOGO_GITHUB " " "WerWolv/ImHex")) - hex::openWebpage("https://github.com/WerWolv/ImHex"); - } - - ImGui::Unindent(); - - ImGui::EndTable(); - } + this->drawBuildInformation(); } ImGuiExt::EndSubWindow(); @@ -235,12 +185,60 @@ namespace hex::plugin::builtin { ImGui::NewLine(); } - struct Contributor { - const char *name; - const char *description; - const char *link; - bool mainContributor; - }; + void ViewAbout::drawBuildInformation() { + if (ImGui::BeginTable("Information", 1, ImGuiTableFlags_RowBg | ImGuiTableFlags_BordersInner)) { + ImGui::Indent(); + + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + { + // Draw basic information about ImHex and its version + ImGuiExt::TextFormatted("ImHex Hex Editor v{} by WerWolv", ImHexApi::System::getImHexVersion()); + ImGui::Indent(25_scaled); + ImGuiExt::TextFormatted("Powered by Dear ImGui v{}", ImGui::GetVersion()); + ImGui::Unindent(25_scaled); + } + + ImGui::TableNextColumn(); + { + ImGuiExt::TextFormatted(" {} ", ICON_VS_SOURCE_CONTROL); + + ImGui::SameLine(0, 0); + + // Draw a clickable link to the current commit + if (ImGuiExt::Hyperlink(hex::format("{0}@{1}", ImHexApi::System::getCommitBranch(), ImHexApi::System::getCommitHash()).c_str())) + hex::openWebpage("https://github.com/WerWolv/ImHex/commit/" + ImHexApi::System::getCommitHash(true)); + } + + ImGui::TableNextColumn(); + { + // Draw the build date and time + ImGuiExt::TextFormatted("Compiled on {} at {}", __DATE__, __TIME__); + } + + ImGui::TableNextColumn(); + { + // Draw the author of the current translation + ImGui::TextUnformatted("hex.builtin.view.help.about.translator"_lang); + } + + ImGui::TableNextColumn(); + { + // Draw information about the open-source nature of ImHex + ImGui::TextUnformatted("hex.builtin.view.help.about.source"_lang); + + ImGui::SameLine(); + + // Draw a clickable link to the GitHub repository + if (ImGuiExt::Hyperlink(ICON_VS_LOGO_GITHUB " " "WerWolv/ImHex")) + hex::openWebpage("https://github.com/WerWolv/ImHex"); + } + + ImGui::Unindent(); + + ImGui::EndTable(); + } + } static void drawContributorTable(const char *title, const auto &contributors) { ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2()); @@ -272,6 +270,13 @@ namespace hex::plugin::builtin { } void ViewAbout::drawContributorPage() { + struct Contributor { + const char *name; + const char *description; + const char *link; + bool mainContributor; + }; + constexpr static std::array Contributors = { Contributor { "iTrooz", "A huge amount of help maintaining ImHex and the CI", "https://github.com/iTrooz", true }, Contributor { "jumanji144", "A ton of help with the Pattern Language, API and usage stats", "https://github.com/jumanji144", true }, @@ -395,43 +400,7 @@ namespace hex::plugin::builtin { ImGui::TableHeadersRow(); for (const auto &plugin : plugins) { - if (plugin.isLibraryPlugin()) - continue; - - auto features = plugin.getFeatures(); - - ImGui::TableNextRow(); - ImGui::TableNextColumn(); - bool open = false; - - ImGui::PushStyleColor(ImGuiCol_Text, ImGui::GetColorU32(ImGuiCol_Text)); - if (features.empty()) - ImGui::BulletText("%s", plugin.getPluginName().c_str()); - else - open = ImGui::TreeNode(plugin.getPluginName().c_str()); - ImGui::PopStyleColor(); - - ImGui::TableNextColumn(); - ImGui::TextUnformatted(plugin.getPluginAuthor().c_str()); - ImGui::TableNextColumn(); - ImGui::TextUnformatted(plugin.getPluginDescription().c_str()); - ImGui::TableNextColumn(); - ImGui::TextUnformatted(plugin.isLoaded() ? ICON_VS_CHECK : ICON_VS_CLOSE); - - if (open) { - for (const auto &feature : plugin.getFeatures()) { - ImGui::TableNextRow(); - ImGui::TableNextColumn(); - ImGuiExt::TextFormatted(" {}", feature.name.c_str()); - ImGui::TableNextColumn(); - ImGui::TableNextColumn(); - ImGui::TableNextColumn(); - ImGui::TextUnformatted(feature.enabled ? ICON_VS_CHECK : ICON_VS_CLOSE); - - } - - ImGui::TreePop(); - } + this->drawPluginRow(plugin); } ImGui::EndTable(); @@ -440,6 +409,46 @@ namespace hex::plugin::builtin { ImGuiExt::EndSubWindow(); } + void ViewAbout::drawPluginRow(const hex::Plugin& plugin) { + if (plugin.isLibraryPlugin()) + return; + + auto features = plugin.getFeatures(); + + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + bool open = false; + + ImGui::PushStyleColor(ImGuiCol_Text, ImGui::GetColorU32(ImGuiCol_Text)); + if (features.empty()) + ImGui::BulletText("%s", plugin.getPluginName().c_str()); + else + open = ImGui::TreeNode(plugin.getPluginName().c_str()); + ImGui::PopStyleColor(); + + ImGui::TableNextColumn(); + ImGui::TextUnformatted(plugin.getPluginAuthor().c_str()); + ImGui::TableNextColumn(); + ImGui::TextUnformatted(plugin.getPluginDescription().c_str()); + ImGui::TableNextColumn(); + ImGui::TextUnformatted(plugin.isLoaded() ? ICON_VS_CHECK : ICON_VS_CLOSE); + + if (open) { + for (const auto &feature : plugin.getFeatures()) { + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGuiExt::TextFormatted(" {}", feature.name.c_str()); + ImGui::TableNextColumn(); + ImGui::TableNextColumn(); + ImGui::TableNextColumn(); + ImGui::TextUnformatted(feature.enabled ? ICON_VS_CHECK : ICON_VS_CLOSE); + + } + + ImGui::TreePop(); + } + } + void ViewAbout::drawPathsPage() { constexpr static std::array, size_t(fs::ImHexPath::END)> PathTypes = { @@ -504,9 +513,66 @@ namespace hex::plugin::builtin { } + static void drawRegularLine(const std::string& line) { + ImGui::Bullet(); + ImGui::SameLine(); + + // Check if the line contains bold text + auto boldStart = line.find("**"); + if (boldStart == std::string::npos) { + // Draw the line normally + ImGui::TextUnformatted(line.c_str()); + + return; + } + + // Find the end of the bold text + auto boldEnd = line.find("**", boldStart + 2); + + // Draw the line with the bold text highlighted + ImGui::TextUnformatted(line.substr(0, boldStart).c_str()); + ImGui::SameLine(0, 0); + ImGuiExt::TextFormattedColored(ImGuiExt::GetCustomColorVec4(ImGuiCustomCol_Highlight), "{}", + line.substr(boldStart + 2, boldEnd - boldStart - 2).c_str()); + ImGui::SameLine(0, 0); + ImGui::TextUnformatted(line.substr(boldEnd + 2).c_str()); + } + + struct ReleaseNotes { + std::string title; + std::vector notes; + }; + + static ReleaseNotes parseReleaseNotes(const HttpRequest::Result& response) { + ReleaseNotes notes; + nlohmann::json json; + + if (!response.isSuccess()) { + // An error occurred, display it + notes.notes.push_back("## HTTP Error: " + std::to_string(response.getStatusCode())); + + return notes; + } + + // A valid response was received, parse it + try { + json = nlohmann::json::parse(response.getData()); + + // Get the release title + notes.title = json["name"].get(); + + // Get the release notes and split it into lines + auto body = json["body"].get(); + notes.notes = wolv::util::splitString(body, "\r\n"); + } catch (std::exception &e) { + notes.notes.push_back("## Error: " + std::string(e.what())); + } + + return notes; + } + void ViewAbout::drawReleaseNotesPage() { - static std::string releaseTitle; - static std::vector releaseNotes; + static ReleaseNotes notes; // Set up the request to get the release notes the first time the page is opened AT_FIRST_TIME { @@ -518,67 +584,23 @@ namespace hex::plugin::builtin { // Wait for the request to finish and parse the response if (m_releaseNoteRequest.valid()) { if (m_releaseNoteRequest.wait_for(std::chrono::seconds(0)) == std::future_status::ready) { - auto response = m_releaseNoteRequest.get(); - nlohmann::json json; - - if (response.isSuccess()) { - // A valid response was received, parse it - try { - json = nlohmann::json::parse(response.getData()); - - // Get the release title - releaseTitle = json["name"].get(); - - // Get the release notes and split it into lines - auto body = json["body"].get(); - releaseNotes = wolv::util::splitString(body, "\r\n"); - } catch (std::exception &e) { - releaseNotes.push_back("## Error: " + std::string(e.what())); - } - } else { - // An error occurred, display it - releaseNotes.push_back("## HTTP Error: " + std::to_string(response.getStatusCode())); - } + notes = parseReleaseNotes(m_releaseNoteRequest.get()); } else { // Draw a spinner while the release notes are loading ImGuiExt::TextSpinner("hex.ui.common.loading"_lang); } } - - // Function to handle drawing of a regular text line - static const auto drawRegularLine = [](const std::string &line) { - ImGui::Bullet(); - ImGui::SameLine(); - - // Check if the line contains bold text - auto boldStart = line.find("**"); - if (boldStart != std::string::npos) { - // Find the end of the bold text - auto boldEnd = line.find("**", boldStart + 2); - - // Draw the line with the bold text highlighted - ImGui::TextUnformatted(line.substr(0, boldStart).c_str()); - ImGui::SameLine(0, 0); - ImGuiExt::TextFormattedColored(ImGuiExt::GetCustomColorVec4(ImGuiCustomCol_Highlight), "{}", line.substr(boldStart + 2, boldEnd - boldStart - 2).c_str()); - ImGui::SameLine(0, 0); - ImGui::TextUnformatted(line.substr(boldEnd + 2).c_str()); - } else { - // Draw the line normally - ImGui::TextUnformatted(line.c_str()); - } - }; - // Draw the release title - if (!releaseTitle.empty()) { - auto title = hex::format("v{}: {}", ImHexApi::System::getImHexVersion(false), releaseTitle); + if (!notes.title.empty()) { + auto title = hex::format("v{}: {}", ImHexApi::System::getImHexVersion(false), notes.title); ImGuiExt::Header(title.c_str(), true); ImGui::Separator(); } // Draw the release notes and format them using parts of the GitHub Markdown syntax // This is not a full implementation of the syntax, but it's enough to make the release notes look good. - for (const auto &line : releaseNotes) { + for (const auto &line : notes.notes) { if (line.starts_with("## ")) { // Draw H2 Header ImGuiExt::Header(line.substr(3).c_str()); @@ -599,16 +621,79 @@ namespace hex::plugin::builtin { } } - void ViewAbout::drawCommitHistoryPage() { - struct Commit { - std::string hash; - std::string message; - std::string description; - std::string author; - std::string date; - std::string url; - }; + struct Commit { + std::string hash; + std::string message; + std::string description; + std::string author; + std::string date; + std::string url; + }; + static std::vector parseCommits(const HttpRequest::Result& response) { + nlohmann::json json; + std::vector commits; + + if (!response.isSuccess()) { + // An error occurred, display it + commits.emplace_back( + "hex.ui.common.error"_lang, + "HTTP " + std::to_string(response.getStatusCode()), + "", + "", + "" + ); + + return { }; + } + + // A valid response was received, parse it + try { + json = nlohmann::json::parse(response.getData()); + + for (auto &commit: json) { + const auto message = commit["commit"]["message"].get(); + + // Split commit title and description. They're separated by two newlines. + const auto messageEnd = message.find("\n\n"); + + auto commitTitle = messageEnd == std::string::npos ? message : message.substr(0, messageEnd); + auto commitDescription = + messageEnd == std::string::npos ? "" : message.substr(commitTitle.size() + 2); + + auto url = commit["html_url"].get(); + auto sha = commit["sha"].get(); + auto date = commit["commit"]["author"]["date"].get(); + auto author = hex::format("{} <{}>", + commit["commit"]["author"]["name"].get(), + commit["commit"]["author"]["email"].get() + ); + + // Move the commit data into the list of commits + commits.emplace_back( + std::move(sha), + std::move(commitTitle), + std::move(commitDescription), + std::move(author), + std::move(date), + std::move(url) + ); + } + + } catch (std::exception &e) { + commits.emplace_back( + "hex.ui.common.error"_lang, + e.what(), + "", + "", + "" + ); + } + + return commits; + } + + void ViewAbout::drawCommitHistoryPage() { static std::vector commits; // Set up the request to get the commit history the first time the page is opened @@ -620,61 +705,7 @@ namespace hex::plugin::builtin { // Wait for the request to finish and parse the response if (m_commitHistoryRequest.valid()) { if (m_commitHistoryRequest.wait_for(std::chrono::seconds(0)) == std::future_status::ready) { - auto response = m_commitHistoryRequest.get(); - nlohmann::json json; - - if (response.isSuccess()) { - // A valid response was received, parse it - try { - json = nlohmann::json::parse(response.getData()); - - for (auto &commit : json) { - const auto message = commit["commit"]["message"].get(); - - // Split commit title and description. They're separated by two newlines. - const auto messageEnd = message.find("\n\n"); - - auto commitTitle = messageEnd == std::string::npos ? message : message.substr(0, messageEnd); - auto commitDescription = messageEnd == std::string::npos ? "" : message.substr(commitTitle.size() + 2); - - auto url = commit["html_url"].get(); - auto sha = commit["sha"].get(); - auto date = commit["commit"]["author"]["date"].get(); - auto author = hex::format("{} <{}>", - commit["commit"]["author"]["name"].get(), - commit["commit"]["author"]["email"].get() - ); - - // Move the commit data into the list of commits - commits.emplace_back( - std::move(sha), - std::move(commitTitle), - std::move(commitDescription), - std::move(author), - std::move(date), - std::move(url) - ); - } - - } catch (std::exception &e) { - commits.emplace_back( - "hex.ui.common.error"_lang, - e.what(), - "", - "", - "" - ); - } - } else { - // An error occurred, display it - commits.emplace_back( - "hex.ui.common.error"_lang, - "HTTP " + std::to_string(response.getStatusCode()), - "", - "", - "" - ); - } + commits = parseCommits(m_commitHistoryRequest.get()); } else { // Draw a spinner while the commits are loading ImGuiExt::TextSpinner("hex.ui.common.loading"_lang); @@ -682,66 +713,76 @@ namespace hex::plugin::builtin { } // Draw commits table - if (!commits.empty()) { - ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2()); - ImGuiExt::BeginSubWindow("Commits", ImGui::GetContentRegionAvail()); - ImGui::PopStyleVar(); - { - if (ImGui::BeginTable("##commits", 2, ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg | ImGuiTableFlags_SizingFixedFit | ImGuiTableFlags_ScrollY)) { - // Draw commits - for (const auto &commit : commits) { - ImGui::PushID(commit.hash.c_str()); - ImGui::TableNextRow(); + if (commits.empty()) return; - // Draw hover tooltip - ImGui::TableNextColumn(); - if (ImGui::Selectable("##commit", false, ImGuiSelectableFlags_SpanAllColumns)) { - hex::openWebpage(commit.url); - } + ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2()); + ImGuiExt::BeginSubWindow("Commits", ImGui::GetContentRegionAvail()); + ImGui::PopStyleVar(); - if (ImGui::IsItemHovered()) { - if (ImGui::BeginTooltip()) { - // Draw author and commit date - ImGuiExt::TextFormattedColored(ImGuiExt::GetCustomColorVec4(ImGuiCustomCol_Highlight), "{}", commit.author); - ImGui::SameLine(); - ImGuiExt::TextFormatted("@ {}", commit.date.c_str()); + this->drawCommitsTable(commits); - // Draw description if there is one - if (!commit.description.empty()) { - ImGui::Separator(); - ImGuiExt::TextFormatted("{}", commit.description); - } + ImGuiExt::EndSubWindow(); + } - ImGui::EndTooltip(); - } + void ViewAbout::drawCommitsTable(const auto& commits) { + if (ImGui::BeginTable("##commits", 2, ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg | ImGuiTableFlags_SizingFixedFit | ImGuiTableFlags_ScrollY)) { + // Draw commits + for (const auto &commit: commits) { + ImGui::PushID(commit.hash.c_str()); + ImGui::TableNextRow(); - } + this->drawCommitRow(commit); - // Draw commit hash - ImGui::SameLine(0, 0); - ImGuiExt::TextFormattedColored(ImGuiExt::GetCustomColorVec4(ImGuiCustomCol_Highlight), "{}", commit.hash.substr(0, 7)); - - // Draw the commit message - ImGui::TableNextColumn(); - - const ImColor color = [&]{ - if (commit.hash == ImHexApi::System::getCommitHash(true)) - return ImGui::GetStyleColorVec4(ImGuiCol_HeaderActive); - else - return ImGui::GetStyleColorVec4(ImGuiCol_Text); - }(); - ImGuiExt::TextFormattedColored(color, commit.message); - - ImGui::PopID(); - } - - ImGui::EndTable(); - } + ImGui::PopID(); } - ImGuiExt::EndSubWindow(); + + ImGui::EndTable(); } } + void ViewAbout::drawCommitRow(const auto &commit) { + // Draw hover tooltip + ImGui::TableNextColumn(); + if (ImGui::Selectable("##commit", false, ImGuiSelectableFlags_SpanAllColumns)) { + hex::openWebpage(commit.url); + } + + if (ImGui::IsItemHovered()) { + if (ImGui::BeginTooltip()) { + // Draw author and commit date + ImGuiExt::TextFormattedColored(ImGuiExt::GetCustomColorVec4(ImGuiCustomCol_Highlight), "{}", + commit.author); + ImGui::SameLine(); + ImGuiExt::TextFormatted("@ {}", commit.date.c_str()); + + // Draw description if there is one + if (!commit.description.empty()) { + ImGui::Separator(); + ImGuiExt::TextFormatted("{}", commit.description); + } + + ImGui::EndTooltip(); + } + + } + + // Draw commit hash + ImGui::SameLine(0, 0); + ImGuiExt::TextFormattedColored(ImGuiExt::GetCustomColorVec4(ImGuiCustomCol_Highlight), "{}", + commit.hash.substr(0, 7)); + + // Draw the commit message + ImGui::TableNextColumn(); + + const ImColor color = [&] { + if (commit.hash == ImHexApi::System::getCommitHash(true)) + return ImGui::GetStyleColorVec4(ImGuiCol_HeaderActive); + else + return ImGui::GetStyleColorVec4(ImGuiCol_Text); + }(); + ImGuiExt::TextFormattedColored(color, commit.message); + } + void ViewAbout::drawLicensePage() { const auto indentation = 50_scaled; diff --git a/plugins/builtin/source/content/views/view_data_inspector.cpp b/plugins/builtin/source/content/views/view_data_inspector.cpp index c3693e2db..f8b4e14ed 100644 --- a/plugins/builtin/source/content/views/view_data_inspector.cpp +++ b/plugins/builtin/source/content/views/view_data_inspector.cpp @@ -12,7 +12,6 @@ #include #include -#include #include #include @@ -58,169 +57,177 @@ namespace hex::plugin::builtin { EventProviderClosed::unsubscribe(this); } - void ViewDataInspector::updateInspectorRows() { - m_updateTask = TaskManager::createBackgroundTask("Update Inspector", [this, validBytes = m_validBytes, startAddress = m_startAddress, endian = m_endian, invert = m_invert, numberDisplayStyle = m_numberDisplayStyle](auto &) { - m_workData.clear(); - - if (m_selectedProvider == nullptr) - return; - - // Decode bytes using registered inspectors - for (auto &entry : ContentRegistry::DataInspector::impl::getEntries()) { - if (validBytes < entry.requiredSize) - continue; - - // Try to read as many bytes as requested and possible - std::vector buffer(validBytes > entry.maxSize ? entry.maxSize : validBytes); - m_selectedProvider->read(startAddress, buffer.data(), buffer.size()); - - // Handle invert setting - if (invert) { - for (auto &byte : buffer) - byte ^= 0xFF; - } - - // Insert processed data into the inspector list - m_workData.push_back({ - entry.unlocalizedName, - entry.generatorFunction(buffer, endian, numberDisplayStyle), - entry.editingFunction, - false, - entry.unlocalizedName - }); - } - - - // Decode bytes using custom inspectors defined using the pattern language - const std::map inVariables = { - { "numberDisplayStyle", u128(numberDisplayStyle) } - }; - - // Setup a new pattern language runtime - ContentRegistry::PatternLanguage::configureRuntime(m_runtime, m_selectedProvider); - - // Setup the runtime to read from the selected provider - m_runtime.setDataSource(m_selectedProvider->getBaseAddress(), m_selectedProvider->getActualSize(), - [this, invert](u64 offset, u8 *buffer, size_t size) { - // Read bytes from the selected provider - m_selectedProvider->read(offset, buffer, size); - - // Handle invert setting - if (invert) { - for (auto &byte : std::span(buffer, size)) - byte ^= 0xFF; - } - }); - - // Prevent dangerous function calls - m_runtime.setDangerousFunctionCallHandler([] { return false; }); - - // Set the default endianness based on the endian setting - m_runtime.setDefaultEndian(endian); - - // Set start address to the selected address - m_runtime.setStartAddress(startAddress); - - // Loop over all files in the inspectors folder and execute them - for (const auto &folderPath : fs::getDefaultPaths(fs::ImHexPath::Inspectors)) { - for (const auto &entry : std::fs::recursive_directory_iterator(folderPath)) { - const auto &filePath = entry.path(); - // Skip non-files and files that don't end with .hexpat - if (!entry.exists() || !entry.is_regular_file() || filePath.extension() != ".hexpat") - continue; - - // Read the inspector file - wolv::io::File file(filePath, wolv::io::File::Mode::Read); - if (file.isValid()) { - auto inspectorCode = file.readString(); - - // Execute the inspector file - if (!inspectorCode.empty()) { - if (m_runtime.executeString(inspectorCode, pl::api::Source::DefaultSource, {}, inVariables, true)) { - - // Loop over patterns produced by the runtime - const auto &patterns = m_runtime.getPatterns(); - for (const auto &pattern : patterns) { - // Skip hidden patterns - if (pattern->getVisibility() == pl::ptrn::Visibility::Hidden) - continue; - - // Set up the editing function if a write formatter is available - auto formatWriteFunction = pattern->getWriteFormatterFunction(); - std::optional editingFunction; - if (!formatWriteFunction.empty()) { - editingFunction = [formatWriteFunction, &pattern](const std::string &value, std::endian) -> std::vector { - try { - pattern->setValue(value); - } catch (const pl::core::err::EvaluatorError::Exception &error) { - log::error("Failed to set value of pattern '{}' to '{}': {}", pattern->getDisplayName(), value, error.what()); - return { }; - } - - return { }; - }; - } - - try { - // Set up the display function using the pattern's formatter - auto displayFunction = [value = pattern->getFormattedValue()] { - ImGui::TextUnformatted(value.c_str()); - return value; - }; - - // Insert the inspector into the list - m_workData.emplace_back( - pattern->getDisplayName(), - displayFunction, - editingFunction, - false, - wolv::util::toUTF8String(filePath) + ":" + pattern->getVariableName() - ); - - AchievementManager::unlockAchievement("hex.builtin.achievement.patterns", "hex.builtin.achievement.patterns.data_inspector.name"); - } catch (const pl::core::err::EvaluatorError::Exception &error) { - log::error("Failed to get value of pattern '{}': {}", pattern->getDisplayName(), error.what()); - } - } - } else { - std::string errorMessage; - if (const auto &compileErrors = m_runtime.getCompileErrors(); !compileErrors.empty()) { - for (const auto &error : compileErrors) { - errorMessage += hex::format("{}\n", error.format()); - } - } else if (const auto &evalError = m_runtime.getEvalError(); evalError.has_value()) { - errorMessage += hex::format("{}:{} {}\n", evalError->line, evalError->column, evalError->message); - } - - auto displayFunction = [errorMessage = std::move(errorMessage)] { - ImGuiExt::HelpHover( - errorMessage.c_str(), - "hex.builtin.view.data_inspector.execution_error"_lang, - ImGuiExt::GetCustomColorU32(ImGuiCustomCol_LoggerError) - ); - - return errorMessage; - }; - - m_workData.emplace_back( - wolv::util::toUTF8String(filePath.filename()), - std::move(displayFunction), - std::nullopt, - false, - wolv::util::toUTF8String(filePath) - ); - } - } - } - } - } - - m_dataValid = true; - + m_updateTask = TaskManager::createBackgroundTask("Update Inspector", [this](auto &) { + this->updateInspectorRowsTask(); }); } + void ViewDataInspector::updateInspectorRowsTask() { + m_workData.clear(); + + if (m_selectedProvider == nullptr) + return; + + // Decode bytes using registered inspectors + for (auto &entry : ContentRegistry::DataInspector::impl::getEntries()) { + if (m_validBytes < entry.requiredSize) + continue; + + // Try to read as many bytes as requested and possible + std::vector buffer(m_validBytes > entry.maxSize ? entry.maxSize : m_validBytes); + m_selectedProvider->read(m_startAddress, buffer.data(), buffer.size()); + + // Handle invert setting + if (m_invert) { + for (auto &byte : buffer) + byte ^= 0xFF; + } + + // Insert processed data into the inspector list + m_workData.emplace_back( + entry.unlocalizedName, + entry.generatorFunction(buffer, m_endian, m_numberDisplayStyle), + entry.editingFunction, + false, + entry.unlocalizedName + ); + } + + // Execute custom inspectors + this->executeInspectors(); + + m_dataValid = true; + } + + void ViewDataInspector::inspectorReadFunction(u64 offset, u8 *buffer, size_t size) { + m_selectedProvider->read(offset, buffer, size); + + // Handle invert setting + if (m_invert) { + for (auto &byte : std::span(buffer, size)) + byte ^= 0xFF; + } + } + + void ViewDataInspector::executeInspectors() { + // Decode bytes using custom inspectors defined using the pattern language + const std::map inVariables = { + { "numberDisplayStyle", u128(m_numberDisplayStyle) } + }; + + // Setup a new pattern language runtime + ContentRegistry::PatternLanguage::configureRuntime(m_runtime, m_selectedProvider); + + // Setup the runtime to read from the selected provider + m_runtime.setDataSource(m_selectedProvider->getBaseAddress(), m_selectedProvider->getActualSize(), [this](u64 offset, u8 *buffer, size_t size) { + this->inspectorReadFunction(offset, buffer, size); + }); + + // Prevent dangerous function calls + m_runtime.setDangerousFunctionCallHandler([] { return false; }); + + // Set the default endianness based on the endian setting + m_runtime.setDefaultEndian(m_endian); + + // Set start address to the selected address + m_runtime.setStartAddress(m_startAddress); + + // Loop over all files in the inspectors folder and execute them + for (const auto &folderPath : fs::getDefaultPaths(fs::ImHexPath::Inspectors)) { + for (const auto &entry: std::fs::recursive_directory_iterator(folderPath)) { + const auto &filePath = entry.path(); + // Skip non-files and files that don't end with .hexpat + if (!entry.exists() || !entry.is_regular_file() || filePath.extension() != ".hexpat") + continue; + + // Read the inspector file + wolv::io::File file(filePath, wolv::io::File::Mode::Read); + + if (!file.isValid()) continue; + auto inspectorCode = file.readString(); + + // Execute the inspector file + if (inspectorCode.empty()) continue; + this->executeInspector(inspectorCode, filePath, inVariables); + } + } + } + + void ViewDataInspector::executeInspector(const std::string& code, const std::fs::path& path, const std::map& inVariables) { + if (!m_runtime.executeString(code, pl::api::Source::DefaultSource, {}, inVariables, true)) { + + auto displayFunction = createPatternErrorDisplayFunction(); + + // Insert the inspector into the list + m_workData.emplace_back( + wolv::util::toUTF8String(path.filename()), + std::move(displayFunction), + std::nullopt, + false, + wolv::util::toUTF8String(path) + ); + + return; + } + + // Loop over patterns produced by the runtime + const auto &patterns = m_runtime.getPatterns(); + for (const auto &pattern: patterns) { + // Skip hidden patterns + if (pattern->getVisibility() == pl::ptrn::Visibility::Hidden) + continue; + + // Set up the editing function if a write formatter is available + auto formatWriteFunction = pattern->getWriteFormatterFunction(); + std::optional editingFunction; + if (!formatWriteFunction.empty()) { + editingFunction = [formatWriteFunction, &pattern](const std::string &value, + std::endian) -> std::vector { + try { + pattern->setValue(value); + } catch (const pl::core::err::EvaluatorError::Exception &error) { + log::error("Failed to set value of pattern '{}' to '{}': {}", + pattern->getDisplayName(), value, error.what()); + } + + return {}; + }; + } + + try { + // Set up the display function using the pattern's formatter + auto displayFunction = [value = pattern->getFormattedValue()] { + ImGui::TextUnformatted(value.c_str()); + return value; + }; + + // Insert the inspector into the list + m_workData.emplace_back( + pattern->getDisplayName(), + displayFunction, + editingFunction, + false, + wolv::util::toUTF8String(path) + ":" + pattern->getVariableName() + ); + + AchievementManager::unlockAchievement("hex.builtin.achievement.patterns", + "hex.builtin.achievement.patterns.data_inspector.name"); + } catch (const pl::core::err::EvaluatorError::Exception &) { + auto displayFunction = createPatternErrorDisplayFunction(); + + // Insert the inspector into the list + m_workData.emplace_back( + wolv::util::toUTF8String(path.filename()), + std::move(displayFunction), + std::nullopt, + false, + wolv::util::toUTF8String(path) + ); + } + } + } + void ViewDataInspector::drawContent() { if (m_dataValid && !m_updateTask.isRunning()) { m_dataValid = false; @@ -233,204 +240,276 @@ namespace hex::plugin::builtin { this->updateInspectorRows(); } - if (m_selectedProvider != nullptr && m_selectedProvider->isReadable() && m_validBytes > 0) { - u32 validLineCount = m_cachedData.size(); - if (!m_tableEditingModeEnabled) { - validLineCount = std::count_if(m_cachedData.begin(), m_cachedData.end(), [this](const auto &entry) { - return !m_hiddenValues.contains(entry.filterValue); - }); - } - - if (ImGui::BeginTable("##datainspector", m_tableEditingModeEnabled ? 3 : 2, ImGuiTableFlags_Borders | ImGuiTableFlags_SizingFixedFit | ImGuiTableFlags_RowBg, ImVec2(0, ImGui::GetTextLineHeightWithSpacing() * (validLineCount + 1)))) { - ImGui::TableSetupScrollFreeze(0, 1); - ImGui::TableSetupColumn("hex.builtin.view.data_inspector.table.name"_lang, ImGuiTableColumnFlags_WidthFixed); - ImGui::TableSetupColumn("hex.builtin.view.data_inspector.table.value"_lang, ImGuiTableColumnFlags_WidthStretch); - - if (m_tableEditingModeEnabled) - ImGui::TableSetupColumn("##favorite", ImGuiTableColumnFlags_WidthFixed, ImGui::GetTextLineHeight()); - - ImGui::TableHeadersRow(); - - int inspectorRowId = 1; - for (auto &[unlocalizedName, displayFunction, editingFunction, editing, filterValue] : m_cachedData) { - bool grayedOut = false; - if (m_hiddenValues.contains(filterValue)) { - if (!m_tableEditingModeEnabled) - continue; - else - grayedOut = true; - } - - ImGui::PushID(inspectorRowId); - ImGui::TableNextRow(); - ImGui::TableNextColumn(); - - ImGui::BeginDisabled(grayedOut); - - // Render inspector row name - ImGui::TextUnformatted(Lang(unlocalizedName)); - ImGui::TableNextColumn(); - - if (!editing) { - // Handle regular display case - - // Render inspector row value - const auto ©Value = displayFunction(); - - ImGui::SameLine(); - - // Handle copying the value to the clipboard when clicking the row - if (ImGui::Selectable("##InspectorLine", false, ImGuiSelectableFlags_SpanAllColumns | ImGuiSelectableFlags_AllowOverlap)) { - ImGui::SetClipboardText(copyValue.c_str()); - } - - // Enter editing mode when double-clicking the row - if (ImGui::IsItemHovered() && ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left) && editingFunction.has_value() && m_selectedProvider->isWritable()) { - editing = true; - m_editingValue = copyValue; - } - } else { - // Handle editing mode - - ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(0, 0)); - ImGui::SetNextItemWidth(-1); - ImGui::SetKeyboardFocusHere(); - - // Draw input text box - if (ImGui::InputText("##InspectorLineEditing", m_editingValue, ImGuiInputTextFlags_EnterReturnsTrue | ImGuiInputTextFlags_AutoSelectAll)) { - // Turn the entered value into bytes - auto bytes = editingFunction.value()(m_editingValue, m_endian); - - if (m_invert) - std::ranges::transform(bytes, bytes.begin(), [](auto byte) { return byte ^ 0xFF; }); - - // Write those bytes to the selected provider at the current address - m_selectedProvider->write(m_startAddress, bytes.data(), bytes.size()); - - // Disable editing mode - m_editingValue.clear(); - editing = false; - - // Reload all inspector rows - m_shouldInvalidate = true; - } - ImGui::PopStyleVar(); - - // Disable editing mode when clicking outside the input text box - if (!ImGui::IsItemHovered() && ImGui::IsMouseClicked(ImGuiMouseButton_Left)) { - m_editingValue.clear(); - editing = false; - } - } - - ImGui::EndDisabled(); - - if (m_tableEditingModeEnabled) { - ImGui::TableNextColumn(); - - ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(0, 0)); - ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImGui::GetStyleColorVec4(ImGuiCol_Text)); - - bool hidden = m_hiddenValues.contains(filterValue); - if (ImGuiExt::DimmedButton(hidden ? ICON_VS_EYE : ICON_VS_EYE_CLOSED)) { - if (hidden) - m_hiddenValues.erase(filterValue); - else - m_hiddenValues.insert(filterValue); - - { - std::vector filterValues(m_hiddenValues.begin(), m_hiddenValues.end()); - - ContentRegistry::Settings::write>("hex.builtin.setting.data_inspector", "hex.builtin.setting.data_inspector.hidden_rows", filterValues); - } - } - - ImGui::PopStyleColor(); - ImGui::PopStyleVar(); - } - - ImGui::PopID(); - inspectorRowId++; - } - - ImGui::EndTable(); - } - - ImGuiExt::DimmedButtonToggle("hex.ui.common.edit"_lang, &m_tableEditingModeEnabled, ImVec2(ImGui::GetContentRegionAvail().x, 0)); - - ImGui::NewLine(); - ImGui::Separator(); - ImGui::NewLine(); - - // Draw inspector settings - - // Draw endian setting - { - int selection = [this] { - switch (m_endian) { - default: - case std::endian::little: return 0; - case std::endian::big: return 1; - } - }(); - - std::array options = { "hex.ui.common.little"_lang, "hex.ui.common.big"_lang }; - - if (ImGui::SliderInt("hex.ui.common.endian"_lang, &selection, 0, options.size() - 1, options[selection], ImGuiSliderFlags_NoInput)) { - m_shouldInvalidate = true; - - switch (selection) { - default: - case 0: m_endian = std::endian::little; break; - case 1: m_endian = std::endian::big; break; - } - } - } - - // Draw radix setting - { - int selection = [this] { - switch (m_numberDisplayStyle) { - default: - case NumberDisplayStyle::Decimal: return 0; - case NumberDisplayStyle::Hexadecimal: return 1; - case NumberDisplayStyle::Octal: return 2; - } - }(); - std::array options = { "hex.ui.common.decimal"_lang, "hex.ui.common.hexadecimal"_lang, "hex.ui.common.octal"_lang }; - - if (ImGui::SliderInt("hex.ui.common.number_format"_lang, &selection, 0, options.size() - 1, options[selection], ImGuiSliderFlags_NoInput)) { - m_shouldInvalidate = true; - - switch (selection) { - default: - case 0: m_numberDisplayStyle = NumberDisplayStyle::Decimal; break; - case 1: m_numberDisplayStyle = NumberDisplayStyle::Hexadecimal; break; - case 2: m_numberDisplayStyle = NumberDisplayStyle::Octal; break; - } - } - } - - // Draw invert setting - { - int selection = m_invert ? 1 : 0; - std::array options = { "hex.ui.common.no"_lang, "hex.ui.common.yes"_lang }; - - if (ImGui::SliderInt("hex.builtin.view.data_inspector.invert"_lang, &selection, 0, options.size() - 1, options[selection], ImGuiSliderFlags_NoInput)) { - m_shouldInvalidate = true; - - m_invert = selection == 1; - } - } - } else { + if (m_selectedProvider == nullptr || !m_selectedProvider->isReadable() || m_validBytes <= 0) { // Draw a message when no bytes are selected - std::string text = "hex.builtin.view.data_inspector.no_data"_lang; - auto textSize = ImGui::CalcTextSize(text.c_str()); + std::string text = "hex.builtin.view.data_inspector.no_data"_lang; + auto textSize = ImGui::CalcTextSize(text.c_str()); auto availableSpace = ImGui::GetContentRegionAvail(); ImGui::SetCursorPos((availableSpace - textSize) / 2.0F); ImGui::TextUnformatted(text.c_str()); + + return; + } + + u32 validLineCount = m_cachedData.size(); + if (!m_tableEditingModeEnabled) { + validLineCount = std::count_if(m_cachedData.begin(), m_cachedData.end(), [this](const auto &entry) { + return !m_hiddenValues.contains(entry.filterValue); + }); + } + + if (ImGui::BeginTable("##datainspector", m_tableEditingModeEnabled ? 3 : 2, + ImGuiTableFlags_Borders | ImGuiTableFlags_SizingFixedFit | ImGuiTableFlags_RowBg, + ImVec2(0, ImGui::GetTextLineHeightWithSpacing() * (validLineCount + 1)))) { + ImGui::TableSetupScrollFreeze(0, 1); + ImGui::TableSetupColumn("hex.builtin.view.data_inspector.table.name"_lang, + ImGuiTableColumnFlags_WidthFixed); + ImGui::TableSetupColumn("hex.builtin.view.data_inspector.table.value"_lang, + ImGuiTableColumnFlags_WidthStretch); + + if (m_tableEditingModeEnabled) + ImGui::TableSetupColumn("##favorite", ImGuiTableColumnFlags_WidthFixed, ImGui::GetTextLineHeight()); + + ImGui::TableHeadersRow(); + + this->drawInspectorRows(); + + ImGui::EndTable(); + } + + ImGuiExt::DimmedButtonToggle("hex.ui.common.edit"_lang, &m_tableEditingModeEnabled, + ImVec2(ImGui::GetContentRegionAvail().x, 0)); + + ImGui::NewLine(); + ImGui::Separator(); + ImGui::NewLine(); + + // Draw inspector settings + + // Draw endian setting + this->drawEndianSetting(); + + // Draw radix setting + this->drawRadixSetting(); + + // Draw invert setting + this->drawInvertSetting(); + } + + void ViewDataInspector::drawInspectorRows() { + int inspectorRowId = 1; + for (auto &entry : m_cachedData) { + ON_SCOPE_EXIT { + ImGui::PopID(); + inspectorRowId++; + }; + + bool grayedOut = m_hiddenValues.contains(entry.filterValue); + if (!m_tableEditingModeEnabled && grayedOut) + continue; + + ImGui::PushID(inspectorRowId); + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + + ImGui::BeginDisabled(grayedOut); + + this->drawInspectorRow(entry); + + ImGui::EndDisabled(); + + if (!m_tableEditingModeEnabled) { + continue; + } + + ImGui::TableNextColumn(); + + ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(0, 0)); + ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImGui::GetStyleColorVec4(ImGuiCol_Text)); + + bool hidden = m_hiddenValues.contains(entry.filterValue); + if (ImGuiExt::DimmedButton(hidden ? ICON_VS_EYE : ICON_VS_EYE_CLOSED)) { + if (hidden) + m_hiddenValues.erase(entry.filterValue); + else + m_hiddenValues.insert(entry.filterValue); + + std::vector filterValues(m_hiddenValues.begin(), m_hiddenValues.end()); + + ContentRegistry::Settings::write>( + "hex.builtin.setting.data_inspector", + "hex.builtin.setting.data_inspector.hidden_rows", filterValues); + } + + ImGui::PopStyleColor(); + ImGui::PopStyleVar(); } } + void ViewDataInspector::drawInspectorRow(InspectorCacheEntry& entry) { + // Render inspector row name + ImGui::TextUnformatted(Lang(entry.unlocalizedName)); + ImGui::TableNextColumn(); + + if (!entry.editing) { + // Handle regular display case + + // Render inspector row value + const auto ©Value = entry.displayFunction(); + + ImGui::SameLine(); + + // Handle copying the value to the clipboard when clicking the row + if (ImGui::Selectable("##InspectorLine", false, ImGuiSelectableFlags_SpanAllColumns | + ImGuiSelectableFlags_AllowOverlap)) { + ImGui::SetClipboardText(copyValue.c_str()); + } + + // Enter editing mode when double-clicking the row + if (ImGui::IsItemHovered() && ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left) && + entry.editingFunction.has_value() && m_selectedProvider->isWritable()) { + entry.editing = true; + m_editingValue = copyValue; + } + + return; + } + + // Handle editing mode + ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(0, 0)); + ImGui::SetNextItemWidth(-1); + ImGui::SetKeyboardFocusHere(); + + // Draw input text box + if (ImGui::InputText("##InspectorLineEditing", m_editingValue, + ImGuiInputTextFlags_EnterReturnsTrue | + ImGuiInputTextFlags_AutoSelectAll)) { + // Turn the entered value into bytes + auto bytes = entry.editingFunction.value()(m_editingValue, m_endian); + + if (m_invert) + std::ranges::transform(bytes, bytes.begin(), [](auto byte) { return byte ^ 0xFF; }); + + // Write those bytes to the selected provider at the current address + m_selectedProvider->write(m_startAddress, bytes.data(), bytes.size()); + + // Disable editing mode + m_editingValue.clear(); + entry.editing = false; + + // Reload all inspector rows + m_shouldInvalidate = true; + } + + ImGui::PopStyleVar(); + + // Disable editing mode when clicking outside the input text box + if (!ImGui::IsItemHovered() && ImGui::IsMouseClicked(ImGuiMouseButton_Left)) { + m_editingValue.clear(); + entry.editing = false; + } + } + + void ViewDataInspector::drawEndianSetting() { + int selection = [this] { + switch (m_endian) { + default: + case std::endian::little: + return 0; + case std::endian::big: + return 1; + } + }(); + + std::array options = {"hex.ui.common.little"_lang, "hex.ui.common.big"_lang}; + + if (ImGui::SliderInt("hex.ui.common.endian"_lang, &selection, 0, options.size() - 1, options[selection], + ImGuiSliderFlags_NoInput)) { + m_shouldInvalidate = true; + + switch (selection) { + default: + case 0: + m_endian = std::endian::little; + break; + case 1: + m_endian = std::endian::big; + break; + } + } + } + + void ViewDataInspector::drawRadixSetting() { + int selection = [this] { + switch (m_numberDisplayStyle) { + default: + case NumberDisplayStyle::Decimal: + return 0; + case NumberDisplayStyle::Hexadecimal: + return 1; + case NumberDisplayStyle::Octal: + return 2; + } + }(); + std::array options = {"hex.ui.common.decimal"_lang, "hex.ui.common.hexadecimal"_lang, + "hex.ui.common.octal"_lang}; + + if (ImGui::SliderInt("hex.ui.common.number_format"_lang, &selection, 0, options.size() - 1, + options[selection], ImGuiSliderFlags_NoInput)) { + m_shouldInvalidate = true; + + switch (selection) { + default: + case 0: + m_numberDisplayStyle = NumberDisplayStyle::Decimal; + break; + case 1: + m_numberDisplayStyle = NumberDisplayStyle::Hexadecimal; + break; + case 2: + m_numberDisplayStyle = NumberDisplayStyle::Octal; + break; + } + } + } + + void ViewDataInspector::drawInvertSetting() { + int selection = m_invert ? 1 : 0; + std::array options = {"hex.ui.common.no"_lang, "hex.ui.common.yes"_lang}; + + if (ImGui::SliderInt("hex.builtin.view.data_inspector.invert"_lang, &selection, 0, options.size() - 1, + options[selection], ImGuiSliderFlags_NoInput)) { + m_shouldInvalidate = true; + + m_invert = selection == 1; + } + } + + ContentRegistry::DataInspector::impl::DisplayFunction ViewDataInspector::createPatternErrorDisplayFunction() { + // Generate error message + std::string errorMessage; + if (const auto &compileErrors = m_runtime.getCompileErrors(); !compileErrors.empty()) { + for (const auto &error : compileErrors) { + errorMessage += hex::format("{}\n", error.format()); + } + } else if (const auto &evalError = m_runtime.getEvalError(); evalError.has_value()) { + errorMessage += hex::format("{}:{} {}\n", evalError->line, evalError->column, evalError->message); + } + + // Create a dummy display function that displays the error message + auto displayFunction = [errorMessage = std::move(errorMessage)] { + ImGuiExt::HelpHover( + errorMessage.c_str(), + "hex.builtin.view.data_inspector.execution_error"_lang, + ImGuiExt::GetCustomColorU32(ImGuiCustomCol_LoggerError) + ); + + return errorMessage; + }; + + return displayFunction; + } + + } \ No newline at end of file