1
0
mirror of synced 2024-11-28 09:30:51 +01:00

impr: Refactor various view drawing code (#1698)

Refactored:
- ViewDatainspector
- ViewAbout

---------

Co-authored-by: Nik <werwolv98@gmail.com>
This commit is contained in:
Justus Garbe 2024-06-08 13:56:48 +02:00 committed by GitHub
parent 2ef256ee74
commit 41b2523005
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 752 additions and 611 deletions

View File

@ -4,6 +4,7 @@
#include <imgui.h>
#include <hex/ui/view.hpp>
#include <hex/plugin.hpp>
#include <hex/helpers/http_requests.hpp>
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;

View File

@ -5,6 +5,8 @@
#include <hex/api/content_registry.hpp>
#include <hex/api/task_manager.hpp>
#include <wolv/io/file.hpp>
#include <bit>
#include <cstdio>
#include <string>
@ -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<std::string, pl::core::Token::Literal>& 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;

View File

@ -1,4 +1,5 @@
#include "content/views/view_about.hpp"
#include "hex/ui/popup.hpp"
#include <hex/api_urls.hpp>
#include <hex/api/content_registry.hpp>
@ -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<std::pair<const char *, fs::ImHexPath>, 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<std::string> notes;
};
static ReleaseNotes parseReleaseNotes(const HttpRequest::Result<std::string>& 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<std::string>();
// Get the release notes and split it into lines
auto body = json["body"].get<std::string>();
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<std::string> 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<std::string>();
// Get the release notes and split it into lines
auto body = json["body"].get<std::string>();
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<Commit> parseCommits(const HttpRequest::Result<std::string>& response) {
nlohmann::json json;
std::vector<Commit> 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<std::string>();
// 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<std::string>();
auto sha = commit["sha"].get<std::string>();
auto date = commit["commit"]["author"]["date"].get<std::string>();
auto author = hex::format("{} <{}>",
commit["commit"]["author"]["name"].get<std::string>(),
commit["commit"]["author"]["email"].get<std::string>()
);
// 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<Commit> 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<std::string>();
// 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<std::string>();
auto sha = commit["sha"].get<std::string>();
auto date = commit["commit"]["author"]["date"].get<std::string>();
auto author = hex::format("{} <{}>",
commit["commit"]["author"]["name"].get<std::string>(),
commit["commit"]["author"]["email"].get<std::string>()
);
// 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;

View File

@ -12,7 +12,6 @@
#include <pl/pattern_language.hpp>
#include <pl/patterns/pattern.hpp>
#include <wolv/io/file.hpp>
#include <wolv/utils/string.hpp>
#include <ranges>
@ -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<u8> 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<std::string, pl::core::Token::Literal> 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<ContentRegistry::DataInspector::impl::EditingFunction> editingFunction;
if (!formatWriteFunction.empty()) {
editingFunction = [formatWriteFunction, &pattern](const std::string &value, std::endian) -> std::vector<u8> {
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<u8> 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<std::string, pl::core::Token::Literal> 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<std::string, pl::core::Token::Literal>& 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<ContentRegistry::DataInspector::impl::EditingFunction> editingFunction;
if (!formatWriteFunction.empty()) {
editingFunction = [formatWriteFunction, &pattern](const std::string &value,
std::endian) -> std::vector<u8> {
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 &copyValue = 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<std::vector<std::string>>("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<std::vector<std::string>>(
"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 &copyValue = 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;
}
}