// DragonX Wallet - ImGui Edition // Copyright 2024-2026 The Hush Developers // Released under the GPLv3 // // ConsoleTextLayout — pure (ImGui-free) word-wrap layout + hit-testing + selection // extraction for the console output. Text measurement is injected via ConsoleTextMeasure // so the production path uses ImFont exactly as before while tests drive it with a // fixed-width stub. Extracted from ConsoleTab::renderOutput / screenToTextPos / // getSelectedText so the trickiest console logic (byte-offset selection across wrapped // rows) becomes unit-testable. #pragma once #include #include #include namespace dragonx { namespace ui { // One visual sub-row of a (possibly wrapped) logical line. struct ConsoleWrapSegment { int byteStart = 0; // byte offset into the line's text int byteEnd = 0; // byte offset past the last char on this sub-row float yOffset = 0; // Y offset of this sub-row relative to the line's top float height = 0; // visual height of this sub-row }; // The wrap layout for a set of visible lines. struct ConsoleLayout { std::vector> segments; // per visible line std::vector heights; // per visible line (includes the inter-line gap) std::vector cumulativeY; // cumulative Y offset of each visible line float totalHeight = 0.0f; }; struct ConsoleTextPos { int line = -1; int col = 0; }; // line = raw index; col = byte offset struct ConsoleHit { int visibleRow = 0; int col = 0; }; // visibleRow = index into the visible set // Text measurement backend. Production wraps ImFont; tests inject a fixed-width stub. struct ConsoleTextMeasure { virtual ~ConsoleTextMeasure() = default; // Pixel width of the byte range [begin, end). virtual float width(const char* begin, const char* end) const = 0; // The byte position at which [begin, end) should wrap for the given width — mirrors // ImFont::CalcWordWrapPositionA. Returns `end` when the whole range fits. virtual const char* wrapPosition(const char* begin, const char* end, float wrapWidth) const = 0; }; // Accessor returning a visible/raw line's text by index (avoids copying the buffer). using ConsoleLineAccessor = std::function; // Build the wrap layout for `visibleCount` lines (text via getVisibleText(0..N-1)). ConsoleLayout BuildConsoleLayout(int visibleCount, const ConsoleLineAccessor& getVisibleText, float wrapWidth, float lineHeight, float interLineGap, const ConsoleTextMeasure& measure); // Map a point relative to the layout origin to a (visibleRow, byte col). The caller maps // visibleRow -> a raw line index. Returns {0,0} when there are no visible lines. ConsoleHit HitTestConsoleLayout(const ConsoleLayout& layout, int visibleCount, const ConsoleLineAccessor& getVisibleText, float relX, float relY, const ConsoleTextMeasure& measure); // Extract the selected text between raw positions [start, end], restricted to visible // lines when a filter is active, joining lines with '\n'. Pure. std::string ExtractConsoleSelection(int lineCount, const ConsoleLineAccessor& getLine, const std::vector& visibleIndices, ConsoleTextPos start, ConsoleTextPos end); } // namespace ui } // namespace dragonx