#pragma once #include #include #include #include #include #include #include namespace hex::ui { class ScrollPosition { public: ScrollPosition() = default; // We explicitly don't assign any data during copy and move operations so that each instance of the // Hex Editor will get its own independent scroll position ScrollPosition(const ScrollPosition&) { } ScrollPosition(ScrollPosition&&) noexcept { } ScrollPosition& operator=(const ScrollPosition&) { return *this; } ScrollPosition& operator=(ScrollPosition&&) noexcept { return *this; } void setSynced(bool synced) { m_synced = synced; } void setProvider(prv::Provider *provider) { m_provider = provider; } ImS64& get() { if (m_synced || m_provider == nullptr) return m_syncedPosition; else return m_unsyncedPosition.get(m_provider); } [[nodiscard]] const ImS64& get() const { if (m_synced || m_provider == nullptr) return m_syncedPosition; else return m_unsyncedPosition.get(m_provider); } operator ImS64&() { return this->get(); } operator const ImS64&() const { return this->get(); } ScrollPosition& operator=(ImS64 value) { if (value < 0) value = 0; this->get() = value; return *this; } auto operator<=>(const ScrollPosition &other) const { return this->get() <=> other.get(); } private: bool m_synced = false; prv::Provider *m_provider = nullptr; ImS64 m_syncedPosition = 0; PerProvider m_unsyncedPosition; }; class HexEditor { public: explicit HexEditor(prv::Provider *provider = nullptr); void draw(float height = ImGui::GetContentRegionAvail().y); HexEditor(const HexEditor&) = default; HexEditor& operator=(const HexEditor&) = default; HexEditor(HexEditor &&editor) noexcept = default; HexEditor& operator=(HexEditor &&) noexcept = default; void setProvider(prv::Provider *provider) { m_provider = provider; m_currValidRegion = { Region::Invalid(), false }; m_scrollPosition.setProvider(provider); } [[nodiscard]] prv::Provider* getProvider() const { return m_provider; } void setUnknownDataCharacter(char character) { m_unknownDataCharacter = character; } private: enum class CellType : u8 { None, Hex, ASCII }; enum class AddressFormat : u8 { Hexadecimal, Decimal, Octal }; void drawCell(u64 address, u8 *data, size_t size, bool hovered, CellType cellType); void drawSeparatorLine(u64 address, bool drawVerticalConnector); void drawFrame(u32 x, u32 y, Region region, u64 byteAddress, u16 bytesPerCell, const ImVec2 &cellPos, const ImVec2 &cellSize, const ImColor &frameColor) const; void drawInsertCursor(Region region, u64 byteAddress, const ImVec2 &cellPos, const ImVec2 &cellSize, const ImColor &frameColor) const; void drawBackgroundHighlight(const ImVec2 &cellPos, const ImVec2 &cellSize, const ImColor &backgroundColor) const; void drawSelection(u32 x, u32 y, Region region, u64 byteAddress, u16 bytesPerCell, const ImVec2 &cellPos, const ImVec2 &cellSize, const ImColor &frameColor) const; 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 drawMinimapPopup(); void handleSelection(u64 address, u32 bytesPerCell, const u8 *data, bool cellHovered); std::optional applySelectionColor(u64 byteAddress, std::optional color); std::string formatAddress(u64 address, u32 width = 2, bool prefix = false) const; public: void setSelectionUnchecked(std::optional start, std::optional end) { m_selectionStart = start; m_selectionEnd = end; m_cursorPosition = end; } void setSelection(const Region ®ion) { this->setSelection(region.getStartAddress(), region.getEndAddress()); } void setSelection(u64 start, u64 end) { if (!ImHexApi::Provider::isValid() || m_provider == nullptr) return; if (start > m_provider->getBaseAddress() + m_provider->getActualSize()) return; if (start < m_provider->getBaseAddress()) return; if (m_provider->getActualSize() == 0) return; const size_t maxAddress = m_provider->getActualSize() + m_provider->getBaseAddress() - 1; constexpr static auto alignDown = [](u64 value, u64 alignment) { return value & ~(alignment - 1); }; m_selectionChanged = m_selectionStart != start || m_selectionEnd != end; if (!m_selectionStart.has_value()) m_selectionStart = start; if (!m_selectionEnd.has_value()) m_selectionEnd = end; if (auto bytesPerCell = m_currDataVisualizer == nullptr ? 1 : m_currDataVisualizer->getBytesPerCell(); bytesPerCell > 1) { if (end > start) { start = alignDown(start, bytesPerCell); end = alignDown(end, bytesPerCell) + (bytesPerCell - 1); } else { start = alignDown(start, bytesPerCell) + (bytesPerCell - 1); end = alignDown(end, bytesPerCell); } } m_selectionStart = std::clamp(start, 0, maxAddress); m_selectionEnd = std::clamp(end, 0, maxAddress); m_cursorPosition = m_selectionStart; if (m_selectionChanged) { auto selection = this->getSelection(); EventRegionSelected::post(ImHexApi::HexEditor::ProviderRegion{ { selection.address, selection.size }, m_provider }); m_shouldModifyValue = true; } if (m_mode == Mode::Insert) { m_selectionStart = m_selectionEnd; m_cursorBlinkTimer = -0.3F; } } [[nodiscard]] Region getSelection() const { if (!isSelectionValid()) return Region::Invalid(); const auto start = std::min(m_selectionStart.value(), m_selectionEnd.value()); const auto end = std::max(m_selectionStart.value(), m_selectionEnd.value()); const size_t size = end - start + 1; return { start, size }; } [[nodiscard]] std::optional getCursorPosition() const { return m_cursorPosition; } void setCursorPosition(u64 cursorPosition) { m_cursorPosition = cursorPosition; } [[nodiscard]] bool isSelectionValid() const { return m_selectionStart.has_value() && m_selectionEnd.has_value(); } void jumpToSelection(float pivot = 0.0F) { m_shouldJumpToSelection = true; m_jumpPivot = pivot; } void scrollToSelection() { m_shouldScrollToSelection = true; } void jumpIfOffScreen() { m_shouldScrollToSelection = true; m_shouldJumpWhenOffScreen = true; } [[nodiscard]] u16 getBytesPerRow() const { return m_bytesPerRow; } [[nodiscard]] u16 getBytesPerCell() const { return m_currDataVisualizer->getBytesPerCell(); } void setBytesPerRow(u16 bytesPerRow) { m_bytesPerRow = bytesPerRow; } [[nodiscard]] u16 getVisibleRowCount() const { return m_visibleRowCount; } void setSelectionColor(color_t color) { m_selectionColor = color; } void enableUpperCaseHex(bool upperCaseHex) { m_upperCaseHex = upperCaseHex; } bool shouldUpperCaseHex() const { return m_upperCaseHex; } void enableGrayOutZeros(bool grayOutZeros) { m_grayOutZero = grayOutZeros; } bool shouldGrayOutZeros() const { return m_grayOutZero; } void enableShowAscii(bool showAscii) { m_showAscii = showAscii; } bool shouldShowAscii() const { return m_showAscii; } void enableShowExtendedAscii(bool showExtendedAscii) { m_showExtendedAscii = showExtendedAscii; } bool shouldShowExtendedAscii() const { return m_showExtendedAscii; } void enableSyncScrolling(bool syncScrolling) { m_scrollPosition.setSynced(syncScrolling); } void setByteCellPadding(u32 byteCellPadding) { m_byteCellPadding = byteCellPadding; } void setCharacterCellPadding(u32 characterCellPadding) { m_characterCellPadding = characterCellPadding; } void setMiniMapWidth(int miniMapWidth) { m_miniMapWidth = miniMapWidth; } [[nodiscard]] bool isMiniMapVisible() const { return m_showMiniMap; } int getMiniMapWidth() const { return m_miniMapWidth; } std::optional getMiniMapVisualizer() const { if (!m_showMiniMap) return std::nullopt; return m_miniMapVisualizer->unlocalizedName.get(); } void setMiniMapVisualizer(const UnlocalizedString &miniMapVisualizerName) { if (miniMapVisualizerName.empty()) { m_showMiniMap = false; m_miniMapVisualizer = nullptr; } else { for (const auto &visualizer : ContentRegistry::HexEditor::impl::getMiniMapVisualizers()) { if (visualizer->unlocalizedName == miniMapVisualizerName) { m_miniMapVisualizer = visualizer; m_showMiniMap = true; return; } } } } [[nodiscard]] const std::optional& getCustomEncoding() const { return m_currCustomEncoding; } void setCustomEncoding(const EncodingFile &encoding) { m_currCustomEncoding = encoding; m_encodingLineStartAddresses.clear(); } void setCustomEncoding(EncodingFile &&encoding) { m_currCustomEncoding = std::move(encoding); m_encodingLineStartAddresses.clear(); } void forceUpdateScrollPosition() { m_shouldUpdateScrollPosition = true; } void setForegroundHighlightCallback(const std::function(u64, const u8 *, size_t)> &callback) { m_foregroundColorCallback = callback; } void setBackgroundHighlightCallback(const std::function(u64, const u8 *, size_t)> &callback) { m_backgroundColorCallback = callback; } void setHoverChangedCallback(const std::function &callback) { m_hoverChangedCallback = callback; } void setTooltipCallback(const std::function &callback) { m_tooltipCallback = callback; } void setShowSelectionInFooter(bool showSelection) { m_showSelectionInFooter = showSelection; } [[nodiscard]] i64 getScrollPosition() { return m_scrollPosition.get(); } void setScrollPosition(i64 scrollPosition) { m_scrollPosition.get() = scrollPosition; } void setEditingAddress(u64 address) { m_editingAddress = address; m_shouldModifyValue = false; m_enteredEditingMode = true; m_editingBytes.resize(m_currDataVisualizer->getBytesPerCell()); m_provider->read(address + m_provider->getBaseAddress(), m_editingBytes.data(), m_editingBytes.size()); m_editingCellType = CellType::Hex; } void clearEditingAddress() { m_editingAddress = std::nullopt; } enum class Mode : u8 { Overwrite, Insert }; void setMode(Mode mode) { if (mode == Mode::Insert) { // Don't enter insert mode if the provider doesn't support resizing the underlying data if (!m_provider->isResizable()) return; // Get rid of any selection in insert mode m_selectionStart = m_selectionEnd; m_cursorPosition = m_selectionEnd; m_selectionChanged = true; } m_mode = mode; } [[nodiscard]] Mode getMode() const { return m_mode; } private: prv::Provider *m_provider = nullptr; std::optional m_selectionStart; std::optional m_selectionEnd; std::optional m_cursorPosition; ScrollPosition m_scrollPosition; Region m_frameStartSelectionRegion = Region::Invalid(); Region m_hoveredRegion = Region::Invalid(); u16 m_bytesPerRow = 16; std::endian m_dataVisualizerEndianness = std::endian::little; std::shared_ptr m_currDataVisualizer; char m_unknownDataCharacter = '?'; u64 m_separatorStride = 0; bool m_shouldJumpToSelection = false; float m_jumpPivot = 0.0F; bool m_shouldScrollToSelection = false; bool m_shouldJumpWhenOffScreen = false; bool m_shouldUpdateScrollPosition = false; bool m_selectionChanged = false; u16 m_visibleRowCount = 0; CellType m_editingCellType = CellType::None; AddressFormat m_addressFormat = AddressFormat::Hexadecimal; std::optional m_editingAddress; bool m_shouldModifyValue = false; bool m_enteredEditingMode = false; bool m_shouldUpdateEditingValue = false; std::vector m_editingBytes; u32 m_maxFittingColumns = 16; bool m_autoFitColumns = false; std::shared_ptr m_miniMapVisualizer; color_t m_selectionColor = 0x60C08080; bool m_upperCaseHex = true; bool m_grayOutZero = true; bool m_showAscii = true; bool m_showExtendedAscii = false; bool m_showCustomEncoding = true; bool m_showMiniMap = false; bool m_showSelectionInFooter = false; int m_miniMapWidth = 5; bool m_minimapValueBrightness = true; u32 m_byteCellPadding = 0, m_characterCellPadding = 0; bool m_footerCollapsed = true; std::optional m_currCustomEncoding; std::vector m_encodingLineStartAddresses; std::pair m_currValidRegion = { Region::Invalid(), false }; static std::optional defaultColorCallback(u64, const u8 *, size_t) { return std::nullopt; } static void defaultTooltipCallback(u64, const u8 *, size_t) { } std::function(u64, const u8 *, size_t)> m_foregroundColorCallback = defaultColorCallback, m_backgroundColorCallback = defaultColorCallback; std::function m_hoverChangedCallback = [](auto, auto){ }; std::function m_tooltipCallback = defaultTooltipCallback; Mode m_mode = Mode::Overwrite; float m_cursorBlinkTimer = -0.3F; }; }