diff --git a/lib/libimhex/include/hex/api/content_registry/data_inspector.hpp b/lib/libimhex/include/hex/api/content_registry/data_inspector.hpp index 2e3cabce5..42c135742 100644 --- a/lib/libimhex/include/hex/api/content_registry/data_inspector.hpp +++ b/lib/libimhex/include/hex/api/content_registry/data_inspector.hpp @@ -8,6 +8,7 @@ #include #include #include +#include EXPORT_MODULE namespace hex { @@ -22,8 +23,10 @@ EXPORT_MODULE namespace hex { namespace impl { + struct DoNotUseThisByItselfTag {}; + using DisplayFunction = std::function; - using EditingFunction = std::function(std::string, std::endian)>; + using EditingFunction = std::function>(std::string&, std::endian, DoNotUseThisByItselfTag)>; using GeneratorFunction = std::function &, std::endian, NumberDisplayStyle)>; struct Entry { @@ -38,6 +41,35 @@ EXPORT_MODULE namespace hex { } + namespace EditWidget { + + class Widget { + public: + using Function = std::function(const std::string&, std::endian)>; + + explicit Widget(const Function &function) : m_function(function) {} + + virtual ~Widget() = default; + virtual std::optional> draw(std::string &value, std::endian endian) = 0; + std::optional> operator()(std::string &value, std::endian endian, impl::DoNotUseThisByItselfTag) { + return draw(value, endian); + } + + std::vector 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> 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 diff --git a/lib/libimhex/source/api/content_registry.cpp b/lib/libimhex/source/api/content_registry.cpp index 2902bd1b9..f0f3de479 100644 --- a/lib/libimhex/source/api/content_registry.cpp +++ b/lib/libimhex/source/api/content_registry.cpp @@ -870,6 +870,18 @@ namespace hex { } + namespace EditWidget { + std::optional> 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 editingFunction) { log::debug("Registered new data inspector format: {}", unlocalizedName.get()); diff --git a/plugins/builtin/source/content/data_inspector.cpp b/plugins/builtin/source/content/data_inspector.cpp index 2a285e220..b94f8e4e8 100644 --- a/plugins/builtin/source/content/data_inspector.cpp +++ b/plugins/builtin/source/content/data_inspector.cpp @@ -33,54 +33,60 @@ namespace hex::plugin::builtin { }; template - static std::vector stringToUnsigned(const std::string &value, std::endian endian) requires(sizeof(T) <= sizeof(u64)) { - const auto result = wolv::util::from_chars(value).value_or(0); - if (result > std::numeric_limits::max()) return {}; + 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 { + const auto result = wolv::util::from_chars(value).value_or(0); + if (result > std::numeric_limits::max()) return {}; - std::vector bytes(Size, 0x00); - std::memcpy(bytes.data(), &result, bytes.size()); + std::vector bytes(Size, 0x00); + std::memcpy(bytes.data(), &result, bytes.size()); - if (endian != std::endian::native) - std::reverse(bytes.begin(), bytes.end()); + if (endian != std::endian::native) + std::reverse(bytes.begin(), bytes.end()); - return bytes; + return bytes; + }); } template - static std::vector stringToSigned(const std::string &value, std::endian endian) requires(sizeof(T) <= sizeof(u64)) { - const auto result = wolv::util::from_chars(value).value_or(0); - if (result > std::numeric_limits::max() || result < std::numeric_limits::min()) return {}; + 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 { + const auto result = wolv::util::from_chars(value).value_or(0); + if (result > std::numeric_limits::max() || result < std::numeric_limits::min()) return {}; - std::vector bytes(Size, 0x00); - std::memcpy(bytes.data(), &result, bytes.size()); + std::vector bytes(Size, 0x00); + std::memcpy(bytes.data(), &result, bytes.size()); - if (endian != std::endian::native) - std::reverse(bytes.begin(), bytes.end()); + if (endian != std::endian::native) + std::reverse(bytes.begin(), bytes.end()); - return bytes; + return bytes; + }); } template - static std::vector stringToFloat(const std::string &value, std::endian endian) requires(sizeof(T) <= sizeof(long double)) { - const T result = wolv::util::from_chars(value).value_or(0); + 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 { + const T result = wolv::util::from_chars(value).value_or(0); - std::vector bytes(sizeof(T), 0x00); - std::memcpy(bytes.data(), &result, bytes.size()); + std::vector bytes(sizeof(T), 0x00); + std::memcpy(bytes.data(), &result, bytes.size()); - if (endian != std::endian::native) - std::reverse(bytes.begin(), bytes.end()); + if (endian != std::endian::native) + std::reverse(bytes.begin(), bytes.end()); - return bytes; + return bytes; + }); } template - static std::vector 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) - return stringToUnsigned(value, endian); + return stringToUnsigned(); else if constexpr (std::signed_integral) - return stringToSigned(value, endian); + return stringToSigned(); else - return {}; + static_assert("Unsupported type for stringToInteger"); } template @@ -154,7 +160,8 @@ namespace hex::plugin::builtin { ImGui::TextUnformatted(binary.c_str()); return binary; }; - }, [](const std::string &value, std::endian endian) -> std::vector { + }, + ContentRegistry::DataInspector::EditWidget::TextInput([](const std::string &value, std::endian endian) -> std::vector { 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(integerToString), - stringToInteger + stringToInteger() ); ContentRegistry::DataInspector::add("hex.builtin.inspector.i8", sizeof(i8), drawString(integerToString), - stringToInteger + stringToInteger() ); ContentRegistry::DataInspector::add("hex.builtin.inspector.u16", sizeof(u16), drawString(integerToString), - stringToInteger + stringToInteger() ); ContentRegistry::DataInspector::add("hex.builtin.inspector.i16", sizeof(i16), drawString(integerToString), - stringToInteger + stringToInteger() ); ContentRegistry::DataInspector::add("hex.builtin.inspector.u24", 3, drawString(integerToString), - stringToInteger + stringToInteger() ); ContentRegistry::DataInspector::add("hex.builtin.inspector.i24", 3, drawString(integerToString), - stringToInteger + stringToInteger() ); ContentRegistry::DataInspector::add("hex.builtin.inspector.u32", sizeof(u32), drawString(integerToString), - stringToInteger + stringToInteger() ); ContentRegistry::DataInspector::add("hex.builtin.inspector.i32", sizeof(i32), drawString(integerToString), - stringToInteger + stringToInteger() ); ContentRegistry::DataInspector::add("hex.builtin.inspector.u48", 6, drawString(integerToString), - stringToInteger + stringToInteger() ); ContentRegistry::DataInspector::add("hex.builtin.inspector.i48", 6, drawString(integerToString), - stringToInteger + stringToInteger() ); ContentRegistry::DataInspector::add("hex.builtin.inspector.u64", sizeof(u64), drawString(integerToString), - stringToInteger + stringToInteger() ); ContentRegistry::DataInspector::add("hex.builtin.inspector.i64", sizeof(i64), drawString(integerToString), - stringToInteger + stringToInteger() ); 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 + stringToFloat() ); 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 + stringToFloat() ); 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 + stringToFloat() ); 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 { + ContentRegistry::DataInspector::EditWidget::TextInput([](const std::string &value, std::endian endian) -> std::vector { std::ignore = endian; return hex::crypt::encodeSleb128(wolv::util::from_chars(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 { + ContentRegistry::DataInspector::EditWidget::TextInput([](const std::string &value, std::endian endian) -> std::vector { std::ignore = endian; return hex::crypt::encodeUleb128(wolv::util::from_chars(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(buffer.data())); return [value] { ImGuiExt::TextFormatted("'{0}'", value.c_str()); return value; }; }, - [](const std::string &value, std::endian endian) -> std::vector { + ContentRegistry::DataInspector::EditWidget::TextInput([](const std::string &value, std::endian endian) -> std::vector { 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 { + ContentRegistry::DataInspector::EditWidget::TextInput([](const std::string &value, std::endian endian) -> std::vector { std::vector 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 { + ContentRegistry::DataInspector::EditWidget::TextInput([](const std::string &value, std::endian endian) -> std::vector { std::vector 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 { + ContentRegistry::DataInspector::EditWidget::TextInput([](const std::string &value, std::endian endian) -> std::vector { std::vector 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 { + ContentRegistry::DataInspector::EditWidget::TextInput([](const std::string &value, std::endian endian) -> std::vector { 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 { + ContentRegistry::DataInspector::EditWidget::TextInput([](const std::string &value, std::endian endian) -> std::vector { 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 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 { + ContentRegistry::DataInspector::EditWidget::TextInput([](const std::string &value, std::endian endian) -> std::vector { 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 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 { + ContentRegistry::DataInspector::EditWidget::TextInput([](const std::string &value, std::endian endian) -> std::vector { 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 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 &, std::endian, Style) mutable { diff --git a/plugins/builtin/source/content/views/view_data_inspector.cpp b/plugins/builtin/source/content/views/view_data_inspector.cpp index 35e87cb50..10380b637 100644 --- a/plugins/builtin/source/content/views/view_data_inspector.cpp +++ b/plugins/builtin/source/content/views/view_data_inspector.cpp @@ -198,8 +198,7 @@ namespace hex::plugin::builtin { // Set up the editing function if a write formatter is available std::optional editingFunction; if (!pattern->getWriteFormatterFunction().empty()) { - editingFunction = [&pattern](const std::string &value, - std::endian) -> std::vector { + editingFunction = ContentRegistry::DataInspector::EditWidget::TextInput([&pattern](const std::string &value, std::endian) -> std::vector { 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,47 +475,42 @@ namespace hex::plugin::builtin { } ImGui::EndPopup(); } + } else { + if (ImGui::IsKeyPressed(ImGuiKey_Escape)) { + entry.editing = false; + } - return; + // Handle editing mode + ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(0, 0)); + ImGui::SetNextItemWidth(-1); + ImGui::SetKeyboardFocusHere(); + + // 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; }); + + // 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; + } } - if (ImGui::IsKeyPressed(ImGuiKey_Escape)) { - entry.editing = false; - } - - // 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() {