feat: Add support for custom inspector edit widgets

This commit is contained in:
WerWolv 2025-12-12 13:15:16 +01:00
parent 21e61bfce6
commit de25ce7fbb
4 changed files with 152 additions and 107 deletions

View File

@ -8,6 +8,7 @@
#include <optional>
#include <string>
#include <vector>
#include <bit>
EXPORT_MODULE namespace hex {
@ -22,8 +23,10 @@ EXPORT_MODULE namespace hex {
namespace impl {
struct DoNotUseThisByItselfTag {};
using DisplayFunction = std::function<std::string()>;
using EditingFunction = std::function<std::vector<u8>(std::string, std::endian)>;
using EditingFunction = std::function<std::optional<std::vector<u8>>(std::string&, std::endian, DoNotUseThisByItselfTag)>;
using GeneratorFunction = std::function<DisplayFunction(const std::vector<u8> &, std::endian, NumberDisplayStyle)>;
struct Entry {
@ -38,6 +41,35 @@ EXPORT_MODULE namespace hex {
}
namespace EditWidget {
class Widget {
public:
using Function = std::function<std::vector<u8>(const std::string&, std::endian)>;
explicit Widget(const Function &function) : m_function(function) {}
virtual ~Widget() = default;
virtual std::optional<std::vector<u8>> draw(std::string &value, std::endian endian) = 0;
std::optional<std::vector<u8>> operator()(std::string &value, std::endian endian, impl::DoNotUseThisByItselfTag) {
return draw(value, endian);
}
std::vector<u8> getBytes(const std::string &value, std::endian endian) const {
return m_function(value, endian);
}
private:
Function m_function;
};
struct TextInput : Widget {
explicit TextInput(const Function &function) : Widget(function) {}
std::optional<std::vector<u8>> draw(std::string &value, std::endian endian) override;
};
}
/**
* @brief Adds a new entry to the data inspector
* @param unlocalizedName The unlocalized name of the entry

View File

@ -870,6 +870,18 @@ namespace hex {
}
namespace EditWidget {
std::optional<std::vector<u8>> TextInput::draw(std::string &value, std::endian endian) {
if (ImGui::InputText("##InspectorLineEditing", value,
ImGuiInputTextFlags_EnterReturnsTrue |
ImGuiInputTextFlags_AutoSelectAll)) {
return getBytes(value, endian);
}
return std::nullopt;
}
}
void add(const UnlocalizedString &unlocalizedName, size_t requiredSize, impl::GeneratorFunction displayGeneratorFunction, std::optional<impl::EditingFunction> editingFunction) {
log::debug("Registered new data inspector format: {}", unlocalizedName.get());

View File

@ -33,7 +33,8 @@ namespace hex::plugin::builtin {
};
template<std::unsigned_integral T, size_t Size = sizeof(T)>
static std::vector<u8> stringToUnsigned(const std::string &value, std::endian endian) requires(sizeof(T) <= sizeof(u64)) {
static ContentRegistry::DataInspector::impl::EditingFunction stringToUnsigned() requires(sizeof(T) <= sizeof(u64)) {
return ContentRegistry::DataInspector::EditWidget::TextInput([](const std::string &value, std::endian endian) -> std::vector<u8> {
const auto result = wolv::util::from_chars<u64>(value).value_or(0);
if (result > std::numeric_limits<T>::max()) return {};
@ -44,10 +45,12 @@ namespace hex::plugin::builtin {
std::reverse(bytes.begin(), bytes.end());
return bytes;
});
}
template<std::signed_integral T, size_t Size = sizeof(T)>
static std::vector<u8> stringToSigned(const std::string &value, std::endian endian) requires(sizeof(T) <= sizeof(u64)) {
static ContentRegistry::DataInspector::impl::EditingFunction stringToSigned() requires(sizeof(T) <= sizeof(u64)) {
return ContentRegistry::DataInspector::EditWidget::TextInput([](const std::string &value, std::endian endian) -> std::vector<u8> {
const auto result = wolv::util::from_chars<i64>(value).value_or(0);
if (result > std::numeric_limits<T>::max() || result < std::numeric_limits<T>::min()) return {};
@ -58,10 +61,12 @@ namespace hex::plugin::builtin {
std::reverse(bytes.begin(), bytes.end());
return bytes;
});
}
template<std::floating_point T>
static std::vector<u8> stringToFloat(const std::string &value, std::endian endian) requires(sizeof(T) <= sizeof(long double)) {
static ContentRegistry::DataInspector::impl::EditingFunction stringToFloat() requires(sizeof(T) <= sizeof(long double)) {
return ContentRegistry::DataInspector::EditWidget::TextInput([](const std::string &value, std::endian endian) -> std::vector<u8> {
const T result = wolv::util::from_chars<double>(value).value_or(0);
std::vector<u8> bytes(sizeof(T), 0x00);
@ -71,16 +76,17 @@ namespace hex::plugin::builtin {
std::reverse(bytes.begin(), bytes.end());
return bytes;
});
}
template<std::integral T, size_t Size = sizeof(T)>
static std::vector<u8> stringToInteger(const std::string &value, std::endian endian) requires(sizeof(T) <= sizeof(u64)) {
static ContentRegistry::DataInspector::impl::EditingFunction stringToInteger() requires(sizeof(T) <= sizeof(u64)) {
if constexpr (std::unsigned_integral<T>)
return stringToUnsigned<T, Size>(value, endian);
return stringToUnsigned<T, Size>();
else if constexpr (std::signed_integral<T>)
return stringToSigned<T, Size>(value, endian);
return stringToSigned<T, Size>();
else
return {};
static_assert("Unsupported type for stringToInteger");
}
template<std::unsigned_integral T, size_t Size = sizeof(T)>
@ -154,7 +160,8 @@ namespace hex::plugin::builtin {
ImGui::TextUnformatted(binary.c_str());
return binary;
};
}, [](const std::string &value, std::endian endian) -> std::vector<u8> {
},
ContentRegistry::DataInspector::EditWidget::TextInput([](const std::string &value, std::endian endian) -> std::vector<u8> {
std::ignore = endian;
std::string binary = value;
@ -167,69 +174,69 @@ namespace hex::plugin::builtin {
return { result.value() };
else
return { };
}
})
);
ContentRegistry::DataInspector::add("hex.builtin.inspector.u8", sizeof(u8),
drawString<u8>(integerToString<u8>),
stringToInteger<u8>
stringToInteger<u8>()
);
ContentRegistry::DataInspector::add("hex.builtin.inspector.i8", sizeof(i8),
drawString<i8>(integerToString<i8>),
stringToInteger<i8>
stringToInteger<i8>()
);
ContentRegistry::DataInspector::add("hex.builtin.inspector.u16", sizeof(u16),
drawString<u16>(integerToString<u16>),
stringToInteger<u16>
stringToInteger<u16>()
);
ContentRegistry::DataInspector::add("hex.builtin.inspector.i16", sizeof(i16),
drawString<i16>(integerToString<i16>),
stringToInteger<i16>
stringToInteger<i16>()
);
ContentRegistry::DataInspector::add("hex.builtin.inspector.u24", 3,
drawString<u32, 3>(integerToString<u32, 3>),
stringToInteger<u32, 3>
stringToInteger<u32, 3>()
);
ContentRegistry::DataInspector::add("hex.builtin.inspector.i24", 3,
drawString<i32, 3>(integerToString<i32, 3>),
stringToInteger<i32, 3>
stringToInteger<i32, 3>()
);
ContentRegistry::DataInspector::add("hex.builtin.inspector.u32", sizeof(u32),
drawString<u32>(integerToString<u32>),
stringToInteger<u32>
stringToInteger<u32>()
);
ContentRegistry::DataInspector::add("hex.builtin.inspector.i32", sizeof(i32),
drawString<i32>(integerToString<i32>),
stringToInteger<i32>
stringToInteger<i32>()
);
ContentRegistry::DataInspector::add("hex.builtin.inspector.u48", 6,
drawString<u64, 6>(integerToString<u64, 6>),
stringToInteger<u64, 6>
stringToInteger<u64, 6>()
);
ContentRegistry::DataInspector::add("hex.builtin.inspector.i48", 6,
drawString<i64, 6>(integerToString<i64, 6>),
stringToInteger<i64, 6>
stringToInteger<i64, 6>()
);
ContentRegistry::DataInspector::add("hex.builtin.inspector.u64", sizeof(u64),
drawString<u64>(integerToString<u64>),
stringToInteger<u64>
stringToInteger<u64>()
);
ContentRegistry::DataInspector::add("hex.builtin.inspector.i64", sizeof(i64),
drawString<i64>(integerToString<i64>),
stringToInteger<i64>
stringToInteger<i64>()
);
ContentRegistry::DataInspector::add("hex.builtin.inspector.float16", sizeof(u16),
@ -255,7 +262,7 @@ namespace hex::plugin::builtin {
auto value = fmt::format(fmt::runtime(formatString), hex::changeEndianness(result, endian));
return [value] { ImGui::TextUnformatted(value.c_str()); return value; };
},
stringToFloat<float>
stringToFloat<float>()
);
ContentRegistry::DataInspector::add("hex.builtin.inspector.double", sizeof(double),
@ -268,7 +275,7 @@ namespace hex::plugin::builtin {
auto value = fmt::format(fmt::runtime(formatString), hex::changeEndianness(result, endian));
return [value] { ImGui::TextUnformatted(value.c_str()); return value; };
},
stringToFloat<double>
stringToFloat<double>()
);
ContentRegistry::DataInspector::add("hex.builtin.inspector.long_double", sizeof(long double),
@ -281,7 +288,7 @@ namespace hex::plugin::builtin {
auto value = fmt::format(fmt::runtime(formatString), hex::changeEndianness(result, endian));
return [value] { ImGui::TextUnformatted(value.c_str()); return value; };
},
stringToFloat<long double>
stringToFloat<long double>()
);
ContentRegistry::DataInspector::add("hex.builtin.inspector.bfloat16", sizeof(u16),
@ -359,11 +366,11 @@ namespace hex::plugin::builtin {
return [value] { ImGui::TextUnformatted(value.c_str()); return value; };
},
[](const std::string &value, std::endian endian) -> std::vector<u8> {
ContentRegistry::DataInspector::EditWidget::TextInput([](const std::string &value, std::endian endian) -> std::vector<u8> {
std::ignore = endian;
return hex::crypt::encodeSleb128(wolv::util::from_chars<i64>(value).value_or(0));
}
})
);
ContentRegistry::DataInspector::add("hex.builtin.inspector.uleb128", 1, (sizeof(u128) * 8 / 7) + 1,
@ -376,11 +383,11 @@ namespace hex::plugin::builtin {
return [value] { ImGui::TextUnformatted(value.c_str()); return value; };
},
[](const std::string &value, std::endian endian) -> std::vector<u8> {
ContentRegistry::DataInspector::EditWidget::TextInput([](const std::string &value, std::endian endian) -> std::vector<u8> {
std::ignore = endian;
return hex::crypt::encodeUleb128(wolv::util::from_chars<u64>(value).value_or(0));
}
})
);
ContentRegistry::DataInspector::add("hex.builtin.inspector.bool", sizeof(bool),
@ -411,13 +418,13 @@ namespace hex::plugin::builtin {
auto value = makePrintable(*reinterpret_cast<char8_t *>(buffer.data()));
return [value] { ImGuiExt::TextFormatted("'{0}'", value.c_str()); return value; };
},
[](const std::string &value, std::endian endian) -> std::vector<u8> {
ContentRegistry::DataInspector::EditWidget::TextInput([](const std::string &value, std::endian endian) -> std::vector<u8> {
std::ignore = endian;
if (value.length() > 1) return { };
return { u8(value[0]) };
}
})
);
ContentRegistry::DataInspector::add("hex.builtin.inspector.wide", sizeof(wchar_t),
@ -432,7 +439,7 @@ namespace hex::plugin::builtin {
auto value = fmt::format("{0}", c <= 255 ? makePrintable(c) : wolv::util::wstringToUtf8(std::wstring(&c, 1)).value_or("???"));
return [value] { ImGuiExt::TextFormatted("L'{0}'", value.c_str()); return value; };
},
[](const std::string &value, std::endian endian) -> std::vector<u8> {
ContentRegistry::DataInspector::EditWidget::TextInput([](const std::string &value, std::endian endian) -> std::vector<u8> {
std::vector<u8> bytes;
auto wideString = wolv::util::utf8ToWstring(value);
if (!wideString.has_value())
@ -445,7 +452,7 @@ namespace hex::plugin::builtin {
std::reverse(bytes.begin(), bytes.end());
return bytes;
}
})
);
ContentRegistry::DataInspector::add("hex.builtin.inspector.char16", sizeof(char16_t),
@ -460,7 +467,7 @@ namespace hex::plugin::builtin {
auto value = fmt::format("{0}", c <= 255 ? makePrintable(c) : wolv::util::utf16ToUtf8(std::u16string(&c, 1)).value_or("???"));
return [value] { ImGuiExt::TextFormatted("u'{0}'", value.c_str()); return value; };
},
[](const std::string &value, std::endian endian) -> std::vector<u8> {
ContentRegistry::DataInspector::EditWidget::TextInput([](const std::string &value, std::endian endian) -> std::vector<u8> {
std::vector<u8> bytes;
auto wideString = wolv::util::utf8ToUtf16(value);
if (!wideString.has_value())
@ -473,7 +480,7 @@ namespace hex::plugin::builtin {
std::reverse(bytes.begin(), bytes.end());
return bytes;
}
})
);
ContentRegistry::DataInspector::add("hex.builtin.inspector.char32", sizeof(char32_t),
@ -488,7 +495,7 @@ namespace hex::plugin::builtin {
auto value = fmt::format("{0}", c <= 255 ? makePrintable(c) : wolv::util::utf32ToUtf8(std::u32string(&c, 1)).value_or("???"));
return [value] { ImGuiExt::TextFormatted("U'{0}'", value.c_str()); return value; };
},
[](const std::string &value, std::endian endian) -> std::vector<u8> {
ContentRegistry::DataInspector::EditWidget::TextInput([](const std::string &value, std::endian endian) -> std::vector<u8> {
std::vector<u8> bytes;
auto wideString = wolv::util::utf8ToUtf32(value);
if (!wideString.has_value())
@ -501,7 +508,7 @@ namespace hex::plugin::builtin {
std::reverse(bytes.begin(), bytes.end());
return bytes;
}
})
);
ContentRegistry::DataInspector::add("hex.builtin.inspector.utf8", sizeof(char8_t) * 4,
@ -551,11 +558,11 @@ namespace hex::plugin::builtin {
return [value, copyValue] { ImGuiExt::TextFormatted("\"{0}\"", value.c_str()); return copyValue; };
},
[](const std::string &value, std::endian endian) -> std::vector<u8> {
ContentRegistry::DataInspector::EditWidget::TextInput([](const std::string &value, std::endian endian) -> std::vector<u8> {
std::ignore = endian;
return hex::decodeByteString(value);
}
})
);
ContentRegistry::DataInspector::add("hex.builtin.inspector.wstring", sizeof(wchar_t),
@ -588,7 +595,7 @@ namespace hex::plugin::builtin {
return [value, copyValue] { ImGuiExt::TextFormatted("L\"{0}\"", value.c_str()); return copyValue; };
},
[](const std::string &value, std::endian endian) -> std::vector<u8> {
ContentRegistry::DataInspector::EditWidget::TextInput([](const std::string &value, std::endian endian) -> std::vector<u8> {
auto utf8 = hex::decodeByteString(value);
auto wstring = wolv::util::utf8ToWstring({ utf8.begin(), utf8.end() });
if (!wstring.has_value())
@ -601,7 +608,7 @@ namespace hex::plugin::builtin {
std::vector<u8> bytes(wstring->size() * sizeof(wchar_t), 0x00);
std::memcpy(bytes.data(), wstring->data(), bytes.size());
return bytes;
}
})
);
ContentRegistry::DataInspector::add("hex.builtin.inspector.string16", sizeof(char16_t),
@ -634,7 +641,7 @@ namespace hex::plugin::builtin {
return [value, copyValue] { ImGuiExt::TextFormatted("u\"{0}\"", value.c_str()); return copyValue; };
},
[](const std::string &value, std::endian endian) -> std::vector<u8> {
ContentRegistry::DataInspector::EditWidget::TextInput([](const std::string &value, std::endian endian) -> std::vector<u8> {
auto utf8 = hex::decodeByteString(value);
auto utf16 = wolv::util::utf8ToUtf16({ utf8.begin(), utf8.end() });
if (!utf16.has_value())
@ -647,7 +654,7 @@ namespace hex::plugin::builtin {
std::vector<u8> bytes(utf16->size() * sizeof(char16_t), 0x00);
std::memcpy(bytes.data(), utf16->data(), bytes.size());
return bytes;
}
})
);
ContentRegistry::DataInspector::add("hex.builtin.inspector.string32", sizeof(char32_t),
@ -680,7 +687,7 @@ namespace hex::plugin::builtin {
return [value, copyValue] { ImGuiExt::TextFormatted("U\"{0}\"", value.c_str()); return copyValue; };
},
[](const std::string &value, std::endian endian) -> std::vector<u8> {
ContentRegistry::DataInspector::EditWidget::TextInput([](const std::string &value, std::endian endian) -> std::vector<u8> {
auto utf8 = hex::decodeByteString(value);
auto utf32 = wolv::util::utf8ToUtf32({ utf8.begin(), utf8.end() });
if (!utf32.has_value())
@ -693,7 +700,7 @@ namespace hex::plugin::builtin {
std::vector<u8> bytes(utf32->size() * sizeof(char32_t), 0x00);
std::memcpy(bytes.data(), utf32->data(), bytes.size());
return bytes;
}
})
);
ContentRegistry::DataInspector::add("hex.builtin.inspector.custom_encoding", 1, [encodingFile = EncodingFile()](const std::vector<u8> &, std::endian, Style) mutable {

View File

@ -198,8 +198,7 @@ namespace hex::plugin::builtin {
// Set up the editing function if a write formatter is available
std::optional<ContentRegistry::DataInspector::impl::EditingFunction> editingFunction;
if (!pattern->getWriteFormatterFunction().empty()) {
editingFunction = [&pattern](const std::string &value,
std::endian) -> std::vector<u8> {
editingFunction = ContentRegistry::DataInspector::EditWidget::TextInput([&pattern](const std::string &value, std::endian) -> std::vector<u8> {
try {
pattern->setValue(value);
} catch (const pl::core::err::EvaluatorError::Exception &error) {
@ -208,7 +207,7 @@ namespace hex::plugin::builtin {
}
return {};
};
});
}
try {
@ -441,7 +440,7 @@ namespace hex::plugin::builtin {
ImGui::SameLine();
// Handle copying the value to the clipboard when clicking the row
if (ImGui::Selectable("##InspectorLine", m_selectedEntryName == entry.unlocalizedName, ImGuiSelectableFlags_SpanAllColumns | ImGuiSelectableFlags_AllowOverlap)) {
if (ImGui::Selectable("##InspectorLine", m_selectedEntryName == entry.unlocalizedName, ImGuiSelectableFlags_SpanAllColumns | ImGuiSelectableFlags_AllowOverlap | ImGuiSelectableFlags_AllowDoubleClick)) {
m_selectedEntryName = entry.unlocalizedName;
if (auto selection = ImHexApi::HexEditor::getSelection(); selection.has_value()) {
ImHexApi::HexEditor::setSelection(Region { selection->getStartAddress(), entry.requiredSize });
@ -476,10 +475,7 @@ namespace hex::plugin::builtin {
}
ImGui::EndPopup();
}
return;
}
} else {
if (ImGui::IsKeyPressed(ImGuiKey_Escape)) {
entry.editing = false;
}
@ -489,18 +485,14 @@ namespace hex::plugin::builtin {
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);
// Draw editing widget and capture edited value
auto bytes = (*entry.editingFunction)(m_editingValue, m_endian, {});
if (bytes.has_value()) {
if (m_invert)
std::ranges::transform(bytes, bytes.begin(), [](auto byte) { return byte ^ 0xFF; });
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());
m_selectedProvider->write(m_startAddress, bytes->data(), bytes->size());
// Disable editing mode
m_editingValue.clear();
@ -519,6 +511,8 @@ namespace hex::plugin::builtin {
}
}
}
void ViewDataInspector::drawEndianSetting() {
if (ui::endiannessSlider(m_endian)) {
m_shouldInvalidate = true;