impr: Refactor various view drawing code (#1698)
Refactored: - ViewDatainspector - ViewAbout --------- Co-authored-by: Nik <werwolv98@gmail.com>
This commit is contained in:
parent
2ef256ee74
commit
41b2523005
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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 ©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<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 ©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;
|
||||
}
|
||||
|
||||
|
||||
}
|
Loading…
Reference in New Issue
Block a user