refactor(console): phase 1 — extract pure ConsoleTextLayout (wrap/hit-test/selection)
Lift the console's word-wrap layout, screen->text hit-testing, and selection extraction
out of ConsoleTab into a pure, ImGui-free module (console_text_layout.{h,cpp}). Text
measurement is injected via a ConsoleTextMeasure interface — production uses an
ImFont-backed impl (identical CalcWordWrapPositionA/CalcTextSizeA calls as before), while
tests drive a fixed-width stub.
- BuildConsoleLayout: per-line wrap segments + heights + cumulative Y (was inline in
renderOutput).
- HitTestConsoleLayout: point -> (visibleRow, byte col) (was screenToTextPos).
- ExtractConsoleSelection: selected text across visible lines (was getSelectedText).
- ConsoleTab now holds a single `ConsoleLayout layout_` (replacing 4 mutable members;
drops the dead cached_wrap_width_) and calls the pure functions.
- New unit test testConsoleTextLayout covers wrapping, multi-row hit-testing, bottom
clamp, and filtered/unfiltered selection — the biggest previously-untested surface.
No behavior change (algorithm preserved verbatim). Full-node + lite build clean, ctest
green.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
72
src/ui/windows/console_text_layout.h
Normal file
72
src/ui/windows/console_text_layout.h
Normal file
@@ -0,0 +1,72 @@
|
||||
// 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 <functional>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
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<std::vector<ConsoleWrapSegment>> segments; // per visible line
|
||||
std::vector<float> heights; // per visible line (includes the inter-line gap)
|
||||
std::vector<float> 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<const std::string&(int)>;
|
||||
|
||||
// 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<int>& visibleIndices,
|
||||
ConsoleTextPos start, ConsoleTextPos end);
|
||||
|
||||
} // namespace ui
|
||||
} // namespace dragonx
|
||||
Reference in New Issue
Block a user