feat: Added Minimap to Hex Editor
This commit is contained in:
parent
17f769c40d
commit
069544eb93
@ -985,11 +985,19 @@ namespace hex {
|
||||
u16 m_maxCharsPerCell;
|
||||
};
|
||||
|
||||
struct MiniMapVisualizer {
|
||||
using Callback = std::function<ImColor(const std::vector<u8>&)>;
|
||||
|
||||
UnlocalizedString unlocalizedName;
|
||||
Callback callback;
|
||||
};
|
||||
|
||||
namespace impl {
|
||||
|
||||
void addDataVisualizer(std::shared_ptr<DataVisualizer> &&visualizer);
|
||||
|
||||
std::vector<std::shared_ptr<DataVisualizer>> &getVisualizers();
|
||||
std::vector<std::shared_ptr<MiniMapVisualizer>> &getMiniMapVisualizers();
|
||||
|
||||
}
|
||||
|
||||
@ -1010,6 +1018,13 @@ namespace hex {
|
||||
*/
|
||||
std::shared_ptr<DataVisualizer> getVisualizerByName(const UnlocalizedString &unlocalizedName);
|
||||
|
||||
/**
|
||||
* @brief Adds a new minimap visualizer
|
||||
* @param unlocalizedName Unlocalized name of the minimap visualizer
|
||||
* @param callback The callback that will be called to get the color of a line
|
||||
*/
|
||||
void addMiniMapVisualizer(UnlocalizedString unlocalizedName, MiniMapVisualizer::Callback callback);
|
||||
|
||||
}
|
||||
|
||||
/* Diffing Registry. Allows adding new diffing algorithms */
|
||||
|
@ -1001,6 +1001,12 @@ namespace hex {
|
||||
return visualizers;
|
||||
}
|
||||
|
||||
std::vector<std::shared_ptr<MiniMapVisualizer>> &getMiniMapVisualizers() {
|
||||
static std::vector<std::shared_ptr<MiniMapVisualizer>> visualizers;
|
||||
|
||||
return visualizers;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
std::shared_ptr<DataVisualizer> getVisualizerByName(const UnlocalizedString &unlocalizedName) {
|
||||
@ -1012,6 +1018,10 @@ namespace hex {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void addMiniMapVisualizer(UnlocalizedString unlocalizedName, MiniMapVisualizer::Callback callback) {
|
||||
impl::getMiniMapVisualizers().emplace_back(std::make_shared<MiniMapVisualizer>(std::move(unlocalizedName), std::move(callback)));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
namespace ContentRegistry::Diffing {
|
||||
|
@ -45,6 +45,7 @@ add_imhex_plugin(
|
||||
source/content/workspaces.cpp
|
||||
source/content/pl_visualizers.cpp
|
||||
source/content/out_of_box_experience.cpp
|
||||
source/content/minimap_visualizers.cpp
|
||||
|
||||
source/content/data_processor_nodes/basic_nodes.cpp
|
||||
source/content/data_processor_nodes/control_nodes.cpp
|
||||
|
@ -171,6 +171,8 @@
|
||||
"hex.builtin.menu.view.debug": "Show Debugging View",
|
||||
"hex.builtin.menu.view.demo": "Show ImGui Demo",
|
||||
"hex.builtin.menu.view.fps": "Display FPS",
|
||||
"hex.builtin.minimap_visualizer.entropy": "Local Entropy",
|
||||
"hex.builtin.minimap_visualizer.zeros": "Zeros Count",
|
||||
"hex.builtin.nodes.arithmetic": "Arithmetic",
|
||||
"hex.builtin.nodes.arithmetic.add": "Addition",
|
||||
"hex.builtin.nodes.arithmetic.add.header": "Add",
|
||||
|
57
plugins/builtin/source/content/minimap_visualizers.cpp
Normal file
57
plugins/builtin/source/content/minimap_visualizers.cpp
Normal file
@ -0,0 +1,57 @@
|
||||
#include <hex/api/content_registry.hpp>
|
||||
|
||||
#include <imgui.h>
|
||||
#include <hex/ui/imgui_imhex_extensions.h>
|
||||
|
||||
#include <hex/helpers/utils.hpp>
|
||||
|
||||
#include <wolv/utils/string.hpp>
|
||||
|
||||
|
||||
namespace hex::plugin::builtin {
|
||||
|
||||
namespace {
|
||||
|
||||
ImColor entropyMiniMapVisualizer(const std::vector<u8> &data) {
|
||||
std::array<u32, 256> frequencies = { 0 };
|
||||
for (u8 byte : data)
|
||||
frequencies[byte]++;
|
||||
|
||||
double entropy = 0.0;
|
||||
for (u32 frequency : frequencies) {
|
||||
if (frequency == 0)
|
||||
continue;
|
||||
|
||||
double probability = static_cast<double>(frequency) / data.size();
|
||||
entropy -= probability * std::log2(probability);
|
||||
}
|
||||
|
||||
// Calculate color
|
||||
ImColor color = ImColor::HSV(0.0F, 0.0F, 1.0F);
|
||||
|
||||
if (entropy > 0.0) {
|
||||
double hue = std::clamp(entropy / 8.0, 0.0, 1.0);
|
||||
color = ImColor::HSV(static_cast<float>(hue) / 0.75F, 0.8F, 1.0F);
|
||||
}
|
||||
|
||||
return color;
|
||||
}
|
||||
|
||||
ImColor zerosMiniMapVisualizer(const std::vector<u8> &data) {
|
||||
u32 zerosCount = 0;
|
||||
for (u8 byte : data) {
|
||||
if (byte == 0x00)
|
||||
zerosCount++;
|
||||
}
|
||||
|
||||
return ImColor::HSV(0.0F, 0.0F, 1.0F - (double(zerosCount) / data.size()));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void registerMiniMapVisualizers() {
|
||||
ContentRegistry::HexEditor::addMiniMapVisualizer("hex.builtin.minimap_visualizer.entropy", entropyMiniMapVisualizer);
|
||||
ContentRegistry::HexEditor::addMiniMapVisualizer("hex.builtin.minimap_visualizer.zeros", zerosMiniMapVisualizer);
|
||||
}
|
||||
|
||||
}
|
@ -14,6 +14,7 @@ namespace hex::plugin::builtin {
|
||||
|
||||
void registerEventHandlers();
|
||||
void registerDataVisualizers();
|
||||
void registerMiniMapVisualizers();
|
||||
void registerDataInspectorEntries();
|
||||
void registerToolEntries();
|
||||
void registerPatternLanguageFunctions();
|
||||
@ -86,6 +87,7 @@ IMHEX_PLUGIN_SETUP("Built-in", "WerWolv", "Default ImHex functionality") {
|
||||
|
||||
registerEventHandlers();
|
||||
registerDataVisualizers();
|
||||
registerMiniMapVisualizers();
|
||||
registerDataInspectorEntries();
|
||||
registerToolEntries();
|
||||
registerPatternLanguageFunctions();
|
||||
|
@ -100,6 +100,8 @@ namespace hex::ui {
|
||||
void drawEditor(const ImVec2 &size);
|
||||
void drawFooter(const ImVec2 &size);
|
||||
void drawTooltip(u64 address, const u8 *data, size_t size) const;
|
||||
void drawScrollbar(ImVec2 characterSize);
|
||||
void drawMinimap(ImVec2 characterSize);
|
||||
|
||||
void handleSelection(u64 address, u32 bytesPerCell, const u8 *data, bool cellHovered);
|
||||
std::optional<color_t> applySelectionColor(u64 byteAddress, std::optional<color_t> color);
|
||||
@ -324,11 +326,15 @@ namespace hex::ui {
|
||||
bool m_shouldUpdateEditingValue = false;
|
||||
std::vector<u8> m_editingBytes;
|
||||
|
||||
std::shared_ptr<ContentRegistry::HexEditor::MiniMapVisualizer> m_miniMapVisualizer;
|
||||
|
||||
color_t m_selectionColor = 0x60C08080;
|
||||
bool m_upperCaseHex = true;
|
||||
bool m_grayOutZero = true;
|
||||
bool m_showAscii = true;
|
||||
bool m_showCustomEncoding = true;
|
||||
bool m_showMiniMap = false;
|
||||
int m_miniMapWidth = 5;
|
||||
bool m_showHumanReadableUnits = true;
|
||||
u32 m_byteCellPadding = 0, m_characterCellPadding = 0;
|
||||
bool m_footerCollapsed = true;
|
||||
|
@ -88,6 +88,8 @@
|
||||
"hex.ui.hex_editor.human_readable_units_footer": "Convert sizes to human-readable units",
|
||||
"hex.ui.hex_editor.data_size": "Data Size",
|
||||
"hex.ui.hex_editor.gray_out_zero": "Grey out zeros",
|
||||
"hex.ui.hex_editor.minimap": "Mini Map",
|
||||
"hex.ui.hex_editor.minimap.width": "Width",
|
||||
"hex.ui.hex_editor.no_bytes": "No bytes available",
|
||||
"hex.ui.hex_editor.page": "Page",
|
||||
"hex.ui.hex_editor.region": "Region",
|
||||
|
@ -72,6 +72,7 @@ namespace hex::ui {
|
||||
|
||||
HexEditor::HexEditor(prv::Provider *provider) : m_provider(provider) {
|
||||
m_currDataVisualizer = ContentRegistry::HexEditor::getVisualizerByName("hex.builtin.visualizer.hexadecimal.8bit");
|
||||
m_miniMapVisualizer = ContentRegistry::HexEditor::impl::getMiniMapVisualizers().front();
|
||||
}
|
||||
|
||||
HexEditor::~HexEditor() {
|
||||
@ -150,6 +151,105 @@ namespace hex::ui {
|
||||
ImGui::PopStyleVar();
|
||||
}
|
||||
|
||||
void HexEditor::drawScrollbar(ImVec2 characterSize) {
|
||||
ImS64 numRows = m_provider == nullptr ? 0 : (m_provider->getSize() / m_bytesPerRow) + ((m_provider->getSize() % m_bytesPerRow) == 0 ? 0 : 1);
|
||||
|
||||
auto window = ImGui::GetCurrentWindowRead();
|
||||
const auto outerRect = window->Rect();
|
||||
const auto innerRect = window->InnerRect;
|
||||
const auto borderSize = window->WindowBorderSize;
|
||||
const auto scrollbarWidth = ImGui::GetStyle().ScrollbarSize;
|
||||
const auto bb = ImRect(ImMax(outerRect.Min.x, outerRect.Max.x - borderSize - scrollbarWidth), innerRect.Min.y, outerRect.Max.x, innerRect.Max.y);
|
||||
|
||||
constexpr auto roundingCorners = ImDrawFlags_RoundCornersTopRight | ImDrawFlags_RoundCornersBottomRight;
|
||||
constexpr auto axis = ImGuiAxis_Y;
|
||||
|
||||
if (numRows > 0) {
|
||||
ImGui::PushID("MainScrollBar");
|
||||
ImGui::ScrollbarEx(
|
||||
bb,
|
||||
ImGui::GetWindowScrollbarID(window, axis),
|
||||
axis,
|
||||
&m_scrollPosition.get(),
|
||||
(std::ceil(innerRect.Max.y - innerRect.Min.y) / characterSize.y),
|
||||
std::nextafterf(numRows + ImGui::GetWindowSize().y / characterSize.y, std::numeric_limits<float>::max()),
|
||||
roundingCorners);
|
||||
ImGui::PopID();
|
||||
}
|
||||
|
||||
if (m_showMiniMap && m_miniMapVisualizer != nullptr)
|
||||
this->drawMinimap(characterSize);
|
||||
|
||||
if (ImGui::IsWindowHovered()) {
|
||||
m_scrollPosition += ImS64(ImGui::GetIO().MouseWheel * -5);
|
||||
}
|
||||
|
||||
if (m_scrollPosition < 0)
|
||||
m_scrollPosition = 0;
|
||||
if (m_scrollPosition > (numRows - 1))
|
||||
m_scrollPosition = numRows - 1;
|
||||
}
|
||||
|
||||
void HexEditor::drawMinimap(ImVec2 characterSize) {
|
||||
ImS64 numRows = m_provider == nullptr ? 0 : (m_provider->getSize() / m_bytesPerRow) + ((m_provider->getSize() % m_bytesPerRow) == 0 ? 0 : 1);
|
||||
|
||||
auto window = ImGui::GetCurrentWindowRead();
|
||||
const auto outerRect = window->Rect();
|
||||
const auto innerRect = window->InnerRect;
|
||||
const auto borderSize = window->WindowBorderSize;
|
||||
const auto scrollbarWidth = ImGui::GetStyle().ScrollbarSize;
|
||||
const auto bb = ImRect(ImMax(outerRect.Min.x, outerRect.Max.x - borderSize - scrollbarWidth) - scrollbarWidth * (1 + m_miniMapWidth), innerRect.Min.y, outerRect.Max.x - scrollbarWidth, innerRect.Max.y);
|
||||
|
||||
constexpr auto roundingCorners = ImDrawFlags_RoundCornersTopRight | ImDrawFlags_RoundCornersBottomRight;
|
||||
constexpr auto axis = ImGuiAxis_Y;
|
||||
|
||||
constexpr static u64 RowCount = 256;
|
||||
const auto rowHeight = innerRect.GetSize().y / RowCount;
|
||||
const auto scrollPos = m_scrollPosition.get();
|
||||
const auto grabSize = rowHeight * m_visibleRowCount;
|
||||
const auto grabPos = (RowCount - m_visibleRowCount) * (double(scrollPos) / numRows);
|
||||
|
||||
auto drawList = ImGui::GetWindowDrawList();
|
||||
|
||||
drawList->ChannelsSplit(2);
|
||||
drawList->ChannelsSetCurrent(1);
|
||||
if (numRows > 0) {
|
||||
ImGui::PushID("MiniMapScrollBar");
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_GrabMinSize, grabSize);
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_ScrollbarRounding, 0);
|
||||
ImGui::PushStyleColor(ImGuiCol_ScrollbarGrab, ImGui::GetColorU32(ImGuiCol_ScrollbarGrab, 0.4F));
|
||||
ImGui::PushStyleColor(ImGuiCol_ScrollbarGrabActive, ImGui::GetColorU32(ImGuiCol_ScrollbarGrabActive, 0.5F));
|
||||
ImGui::PushStyleColor(ImGuiCol_ScrollbarGrabHovered, ImGui::GetColorU32(ImGuiCol_ScrollbarGrabHovered, 0.5F));
|
||||
ImGui::ScrollbarEx(
|
||||
bb,
|
||||
ImGui::GetWindowScrollbarID(window, axis),
|
||||
axis,
|
||||
&m_scrollPosition.get(),
|
||||
(std::ceil(innerRect.Max.y - innerRect.Min.y) / characterSize.y),
|
||||
std::nextafterf((numRows - m_visibleRowCount) + ImGui::GetWindowSize().y / characterSize.y, std::numeric_limits<float>::max()),
|
||||
roundingCorners);
|
||||
ImGui::PopStyleVar(2);
|
||||
ImGui::PopStyleColor(3);
|
||||
ImGui::PopID();
|
||||
}
|
||||
drawList->ChannelsSetCurrent(0);
|
||||
|
||||
std::vector<u8> rowData(m_bytesPerRow);
|
||||
const auto drawStart = scrollPos - grabPos;
|
||||
for (u64 y = drawStart; y < std::min<u64>(drawStart + RowCount, m_provider->getSize() / m_bytesPerRow); y += 1) {
|
||||
const auto rowStart = bb.Min + ImVec2(0, (y - drawStart) * rowHeight);
|
||||
const auto rowEnd = rowStart + ImVec2(bb.GetSize().x, rowHeight);
|
||||
|
||||
m_provider->read(y * m_bytesPerRow + m_provider->getBaseAddress() + m_provider->getCurrentPageAddress(), rowData.data(), rowData.size());
|
||||
|
||||
drawList->AddRectFilled(rowStart, rowEnd, m_miniMapVisualizer->callback(rowData));
|
||||
}
|
||||
|
||||
drawList->ChannelsMerge();
|
||||
}
|
||||
|
||||
|
||||
|
||||
void HexEditor::drawCell(u64 address, const u8 *data, size_t size, bool hovered, CellType cellType) {
|
||||
static DataVisualizerAscii asciiVisualizer;
|
||||
|
||||
@ -313,39 +413,7 @@ namespace hex::ui {
|
||||
}
|
||||
|
||||
if (ImGui::BeginChild("Hex View", size, ImGuiChildFlags_None, ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse)) {
|
||||
// Draw scrollbar
|
||||
{
|
||||
const auto window = ImGui::GetCurrentWindowRead();
|
||||
const auto axis = ImGuiAxis_Y;
|
||||
ImS64 numRows = m_provider == nullptr ? 0 : (m_provider->getSize() / m_bytesPerRow) + ((m_provider->getSize() % m_bytesPerRow) == 0 ? 0 : 1);
|
||||
|
||||
const auto outerRect = window->Rect();
|
||||
const auto innerRect = window->InnerRect;
|
||||
const auto borderSize = window->WindowBorderSize;
|
||||
const auto scrollbarWidth = ImGui::GetStyle().ScrollbarSize;
|
||||
const auto bb = ImRect(ImMax(outerRect.Min.x, outerRect.Max.x - borderSize - scrollbarWidth), innerRect.Min.y, outerRect.Max.x, innerRect.Max.y);
|
||||
const auto roundingCorners = ImDrawFlags_RoundCornersTopRight | ImDrawFlags_RoundCornersBottomRight;
|
||||
|
||||
if (numRows > 0) {
|
||||
ImGui::ScrollbarEx(
|
||||
bb,
|
||||
ImGui::GetWindowScrollbarID(window, axis),
|
||||
axis,
|
||||
&m_scrollPosition.get(),
|
||||
(std::ceil(innerRect.Max.y - innerRect.Min.y) / CharacterSize.y),
|
||||
std::nextafterf(numRows + ImGui::GetWindowSize().y / CharacterSize.y, std::numeric_limits<float>::max()),
|
||||
roundingCorners);
|
||||
}
|
||||
|
||||
if (ImGui::IsWindowHovered()) {
|
||||
m_scrollPosition += ImS64(ImGui::GetIO().MouseWheel * -5);
|
||||
}
|
||||
|
||||
if (m_scrollPosition < 0)
|
||||
m_scrollPosition = 0;
|
||||
if (m_scrollPosition > (numRows - 1))
|
||||
m_scrollPosition = numRows - 1;
|
||||
}
|
||||
this->drawScrollbar(CharacterSize);
|
||||
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_CellPadding, ImVec2(0.5, 0));
|
||||
if (ImGui::BeginTable("##hex", byteColumnCount, ImGuiTableFlags_SizingFixedFit | ImGuiTableFlags_NoKeepColumnsVisible, size)) {
|
||||
@ -850,9 +918,33 @@ namespace hex::ui {
|
||||
// Custom encoding view
|
||||
ImGui::BeginDisabled(!m_currCustomEncoding.has_value());
|
||||
ImGuiExt::DimmedIconToggle(ICON_VS_WHITESPACE, &m_showCustomEncoding);
|
||||
ImGuiExt::InfoTooltip("hex.ui.hex_editor.custom_encoding_view"_lang);
|
||||
ImGui::EndDisabled();
|
||||
|
||||
ImGuiExt::InfoTooltip("hex.ui.hex_editor.custom_encoding_view"_lang);
|
||||
ImGui::SameLine(0, 1_scaled);
|
||||
|
||||
// Minimap
|
||||
ImGuiExt::DimmedIconToggle(ICON_VS_MAP, &m_showMiniMap);
|
||||
ImGuiExt::InfoTooltip("hex.ui.hex_editor.minimap"_lang);
|
||||
if (ImGui::IsItemClicked(ImGuiMouseButton_Right) && m_miniMapVisualizer != nullptr)
|
||||
ImGui::OpenPopup("MiniMapOptions");
|
||||
|
||||
if (ImGui::BeginPopup("MiniMapOptions")) {
|
||||
ImGui::SliderInt("hex.ui.hex_editor.minimap.width"_lang, &m_miniMapWidth, 1, 25, "%d", ImGuiSliderFlags_AlwaysClamp);
|
||||
|
||||
if (ImGui::BeginCombo("##minimap_visualizer", Lang(m_miniMapVisualizer->unlocalizedName))) {
|
||||
|
||||
for (const auto &visualizer : ContentRegistry::HexEditor::impl::getMiniMapVisualizers()) {
|
||||
if (ImGui::Selectable(Lang(visualizer->unlocalizedName))) {
|
||||
m_miniMapVisualizer = visualizer;
|
||||
}
|
||||
}
|
||||
|
||||
ImGui::EndCombo();
|
||||
}
|
||||
|
||||
ImGui::EndPopup();
|
||||
}
|
||||
|
||||
ImGui::SameLine();
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user