Files
ObsidianDragon/src/ui/windows/console_selection_controller.h
DanS 0cdbbd024e refactor(console): phase 4 — extract ConsoleSelectionController from the god-class
Pull the text-selection concern out of ConsoleTab into a focused, ImGui-free
ConsoleSelectionController (console_selection_controller.{h,cpp}). It owns the
anchor/caret state and all the fiddly logic around it: the mouse-drag lifecycle
(beginDrag/updateDrag/endDrag), select-all, range ordering, text extraction over
the visible model, and the index shift applied when the buffer cap evicts lines
from the top.

ConsoleTab now delegates: the four scattered selection fields (is_selecting_,
has_selection_, sel_anchor_, sel_end_) and five helper methods (selectionStart/
End, isPosBeforeOrEqual, getSelectedText, clearSelection) collapse to a single
selection_ member plus a thin selectedText() wrapper. screenToTextPos (still
view-side — it needs the frame's layout) now returns the shared ConsoleTextPos,
so ConsoleTab::TextPos is retired.

The byte-offset / eviction math was previously only reachable through the live
ImGui renderer; it now has direct unit coverage (testConsoleSelectionController:
ordering, drag lifecycle, upward drag, select-all, partial/full/no-op eviction
shift, and filtered extraction). Full-node + lite build clean; ctest green;
source hygiene clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-01 18:46:06 -05:00

66 lines
2.6 KiB
C++

// DragonX Wallet - ImGui Edition
// Copyright 2024-2026 The Hush Developers
// Released under the GPLv3
//
// ConsoleSelectionController — owns the console's text-selection state (anchor + caret) and
// all the pure logic around it: the mouse-drag lifecycle, select-all, ordering, extraction,
// and the index shift applied when lines are evicted from the top by the buffer cap.
//
// It is deliberately ImGui-free. Screen->text hit-testing stays in the view (it needs the
// current frame's wrapped layout + output origin); the view feeds the resulting
// ConsoleTextPos values in here. That keeps the fiddly byte-offset / eviction math unit
// testable in isolation from the renderer.
#pragma once
#include "console_text_layout.h" // ConsoleTextPos, ConsoleLineAccessor, ExtractConsoleSelection
#include <string>
#include <vector>
namespace dragonx {
namespace ui {
class ConsoleSelectionController {
public:
// Mouse-drag lifecycle. Positions are raw {line, col}: line = index into the full model,
// col = byte offset within that line.
void beginDrag(ConsoleTextPos pos); // button press
void updateDrag(ConsoleTextPos pos); // movement while held
void endDrag(ConsoleTextPos pos); // release
// Select the whole buffer, [0,0] .. [lineCount-1, lastLineByteLen]. No-op if empty.
void selectAll(int lineCount, int lastLineByteLen);
// Drop the selection entirely (also ends any in-progress drag).
void clear();
bool active() const { return active_; } // a selection currently exists
bool dragging() const { return dragging_; } // a drag is in progress
// Ordered bounds, start <= end. Only meaningful while active().
ConsoleTextPos rangeStart() const;
ConsoleTextPos rangeEnd() const;
// After the cap evicts `popped` lines from the front, shift the selection up to track
// the same text. Drops the selection if it fell entirely off the top.
void shiftForEviction(int popped);
// Selected text, or "" when inactive. `visibleIndices` are the raw line indices currently
// shown (the selection spans raw lines but copies only the visible ones).
std::string extract(int lineCount, const ConsoleLineAccessor& getLine,
const std::vector<int>& visibleIndices) const;
// Position ordering: is a at or before b in reading order?
static bool isBeforeOrEqual(ConsoleTextPos a, ConsoleTextPos b);
private:
ConsoleTextPos anchor_{-1, 0}; // where the drag began / the selection origin
ConsoleTextPos caret_{-1, 0}; // current drag position / the selection focus
bool active_ = false;
bool dragging_ = false;
};
} // namespace ui
} // namespace dragonx