1
0
mirror of synced 2024-11-24 15:50:16 +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,8 +400,18 @@ namespace hex::plugin::builtin {
ImGui::TableHeadersRow();
for (const auto &plugin : plugins) {
this->drawPluginRow(plugin);
}
ImGui::EndTable();
}
}
ImGuiExt::EndSubWindow();
}
void ViewAbout::drawPluginRow(const hex::Plugin& plugin) {
if (plugin.isLibraryPlugin())
continue;
return;
auto features = plugin.getFeatures();
@ -434,12 +449,6 @@ namespace hex::plugin::builtin {
}
}
ImGui::EndTable();
}
}
ImGuiExt::EndSubWindow();
}
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,7 +621,6 @@ namespace hex::plugin::builtin {
}
}
void ViewAbout::drawCommitHistoryPage() {
struct Commit {
std::string hash;
std::string message;
@ -609,21 +630,23 @@ namespace hex::plugin::builtin {
std::string url;
};
static std::vector<Commit> commits;
// Set up the request to get the commit history the first time the page is opened
AT_FIRST_TIME {
static HttpRequest request("GET", GitHubApiURL + std::string("/commits?per_page=100"));
m_commitHistoryRequest = request.execute();
};
// 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();
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 { };
}
if (response.isSuccess()) {
// A valid response was received, parse it
try {
json = nlohmann::json::parse(response.getData());
@ -635,7 +658,8 @@ namespace hex::plugin::builtin {
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 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>();
@ -665,16 +689,23 @@ namespace hex::plugin::builtin {
""
);
}
} else {
// An error occurred, display it
commits.emplace_back(
"hex.ui.common.error"_lang,
"HTTP " + std::to_string(response.getStatusCode()),
"",
"",
""
);
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
AT_FIRST_TIME {
static HttpRequest request("GET", GitHubApiURL + std::string("/commits?per_page=100"));
m_commitHistoryRequest = request.execute();
};
// 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) {
commits = parseCommits(m_commitHistoryRequest.get());
} else {
// Draw a spinner while the commits are loading
ImGuiExt::TextSpinner("hex.ui.common.loading"_lang);
@ -682,17 +713,34 @@ namespace hex::plugin::builtin {
}
// Draw commits table
if (!commits.empty()) {
if (commits.empty()) return;
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2());
ImGuiExt::BeginSubWindow("Commits", ImGui::GetContentRegionAvail());
ImGui::PopStyleVar();
{
this->drawCommitsTable(commits);
ImGuiExt::EndSubWindow();
}
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);
ImGui::PopID();
}
ImGui::EndTable();
}
}
void ViewAbout::drawCommitRow(const auto &commit) {
// Draw hover tooltip
ImGui::TableNextColumn();
if (ImGui::Selectable("##commit", false, ImGuiSelectableFlags_SpanAllColumns)) {
@ -702,7 +750,8 @@ namespace hex::plugin::builtin {
if (ImGui::IsItemHovered()) {
if (ImGui::BeginTooltip()) {
// Draw author and commit date
ImGuiExt::TextFormattedColored(ImGuiExt::GetCustomColorVec4(ImGuiCustomCol_Highlight), "{}", commit.author);
ImGuiExt::TextFormattedColored(ImGuiExt::GetCustomColorVec4(ImGuiCustomCol_Highlight), "{}",
commit.author);
ImGui::SameLine();
ImGuiExt::TextFormatted("@ {}", commit.date.c_str());
@ -719,7 +768,8 @@ namespace hex::plugin::builtin {
// Draw commit hash
ImGui::SameLine(0, 0);
ImGuiExt::TextFormattedColored(ImGuiExt::GetCustomColorVec4(ImGuiCustomCol_Highlight), "{}", commit.hash.substr(0, 7));
ImGuiExt::TextFormattedColored(ImGuiExt::GetCustomColorVec4(ImGuiCustomCol_Highlight), "{}",
commit.hash.substr(0, 7));
// Draw the commit message
ImGui::TableNextColumn();
@ -731,15 +781,6 @@ namespace hex::plugin::builtin {
return ImGui::GetStyleColorVec4(ImGuiCol_Text);
}();
ImGuiExt::TextFormattedColored(color, commit.message);
ImGui::PopID();
}
ImGui::EndTable();
}
}
ImGuiExt::EndSubWindow();
}
}
void ViewAbout::drawLicensePage() {

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,9 +57,13 @@ 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_updateTask = TaskManager::createBackgroundTask("Update Inspector", [this](auto &) {
this->updateInspectorRowsTask();
});
}
void ViewDataInspector::updateInspectorRowsTask() {
m_workData.clear();
if (m_selectedProvider == nullptr)
@ -68,59 +71,67 @@ namespace hex::plugin::builtin {
// Decode bytes using registered inspectors
for (auto &entry : ContentRegistry::DataInspector::impl::getEntries()) {
if (validBytes < entry.requiredSize)
if (m_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());
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 (invert) {
if (m_invert) {
for (auto &byte : buffer)
byte ^= 0xFF;
}
// Insert processed data into the inspector list
m_workData.push_back({
m_workData.emplace_back(
entry.unlocalizedName,
entry.generatorFunction(buffer, endian, numberDisplayStyle),
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(numberDisplayStyle) }
{ "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, 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;
}
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(endian);
m_runtime.setDefaultEndian(m_endian);
// Set start address to the selected address
m_runtime.setStartAddress(startAddress);
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)) {
@ -132,12 +143,33 @@ namespace hex::plugin::builtin {
// Read the inspector file
wolv::io::File file(filePath, wolv::io::File::Mode::Read);
if (file.isValid()) {
if (!file.isValid()) continue;
auto inspectorCode = file.readString();
// Execute the inspector file
if (!inspectorCode.empty()) {
if (m_runtime.executeString(inspectorCode, pl::api::Source::DefaultSource, {}, inVariables, true)) {
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();
@ -150,12 +182,13 @@ namespace hex::plugin::builtin {
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> {
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 { };
log::error("Failed to set value of pattern '{}' to '{}': {}",
pattern->getDisplayName(), value, error.what());
}
return {};
@ -175,51 +208,25 @@ namespace hex::plugin::builtin {
displayFunction,
editingFunction,
false,
wolv::util::toUTF8String(filePath) + ":" + pattern->getVariableName()
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 &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;
};
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(filePath.filename()),
wolv::util::toUTF8String(path.filename()),
std::move(displayFunction),
std::nullopt,
false,
wolv::util::toUTF8String(filePath)
wolv::util::toUTF8String(path)
);
}
}
}
}
}
m_dataValid = true;
});
}
void ViewDataInspector::drawContent() {
if (m_dataValid && !m_updateTask.isRunning()) {
@ -233,7 +240,18 @@ namespace hex::plugin::builtin {
this->updateInspectorRows();
}
if (m_selectedProvider != nullptr && m_selectedProvider->isReadable() && m_validBytes > 0) {
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());
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) {
@ -241,65 +259,134 @@ namespace hex::plugin::builtin {
});
}
if (ImGui::BeginTable("##datainspector", m_tableEditingModeEnabled ? 3 : 2, ImGuiTableFlags_Borders | ImGuiTableFlags_SizingFixedFit | ImGuiTableFlags_RowBg, ImVec2(0, ImGui::GetTextLineHeightWithSpacing() * (validLineCount + 1)))) {
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);
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;
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);
// Render inspector row name
ImGui::TextUnformatted(Lang(unlocalizedName));
this->drawInspectorRow(entry);
ImGui::EndDisabled();
if (!m_tableEditingModeEnabled) {
continue;
}
ImGui::TableNextColumn();
if (!editing) {
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 = displayFunction();
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)) {
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;
if (ImGui::IsItemHovered() && ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left) &&
entry.editingFunction.has_value() && m_selectedProvider->isWritable()) {
entry.editing = true;
m_editingValue = copyValue;
}
} else {
// Handle editing mode
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)) {
if (ImGui::InputText("##InspectorLineEditing", m_editingValue,
ImGuiInputTextFlags_EnterReturnsTrue |
ImGuiInputTextFlags_AutoSelectAll)) {
// Turn the entered value into bytes
auto bytes = editingFunction.value()(m_editingValue, m_endian);
auto bytes = entry.editingFunction.value()(m_editingValue, m_endian);
if (m_invert)
std::ranges::transform(bytes, bytes.begin(), [](auto byte) { return byte ^ 0xFF; });
@ -309,128 +396,120 @@ namespace hex::plugin::builtin {
// Disable editing mode
m_editingValue.clear();
editing = false;
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();
editing = false;
entry.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
{
void ViewDataInspector::drawEndianSetting() {
int selection = [this] {
switch (m_endian) {
default:
case std::endian::little: return 0;
case std::endian::big: return 1;
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)) {
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;
case 0:
m_endian = std::endian::little;
break;
case 1:
m_endian = std::endian::big;
break;
}
}
}
// Draw radix setting
{
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;
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 };
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)) {
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;
case 0:
m_numberDisplayStyle = NumberDisplayStyle::Decimal;
break;
case 1:
m_numberDisplayStyle = NumberDisplayStyle::Hexadecimal;
break;
case 2:
m_numberDisplayStyle = NumberDisplayStyle::Octal;
break;
}
}
}
// Draw invert setting
{
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)) {
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 {
// 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());
auto availableSpace = ImGui::GetContentRegionAvail();
ImGui::SetCursorPos((availableSpace - textSize) / 2.0F);
ImGui::TextUnformatted(text.c_str());
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;
}
}