Files
ObsidianDragon/src/ui/windows/console_selection_controller.cpp
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

90 lines
2.4 KiB
C++

// DragonX Wallet - ImGui Edition
// Copyright 2024-2026 The Hush Developers
// Released under the GPLv3
#include "console_selection_controller.h"
namespace dragonx {
namespace ui {
bool ConsoleSelectionController::isBeforeOrEqual(ConsoleTextPos a, ConsoleTextPos b)
{
if (a.line < b.line) return true;
if (a.line > b.line) return false;
return a.col <= b.col;
}
void ConsoleSelectionController::beginDrag(ConsoleTextPos pos)
{
anchor_ = pos;
caret_ = pos;
dragging_ = true;
active_ = false; // not a real selection until the caret moves off the anchor
}
void ConsoleSelectionController::updateDrag(ConsoleTextPos pos)
{
caret_ = pos;
// Becomes a real selection once the caret leaves the anchor; never downgrades back.
if (caret_.line != anchor_.line || caret_.col != anchor_.col)
active_ = true;
}
void ConsoleSelectionController::endDrag(ConsoleTextPos pos)
{
caret_ = pos;
dragging_ = false;
}
void ConsoleSelectionController::selectAll(int lineCount, int lastLineByteLen)
{
if (lineCount <= 0) return;
anchor_ = {0, 0};
caret_ = {lineCount - 1, lastLineByteLen};
active_ = true;
}
void ConsoleSelectionController::clear()
{
active_ = false;
dragging_ = false;
anchor_ = {-1, 0};
caret_ = {-1, 0};
}
ConsoleTextPos ConsoleSelectionController::rangeStart() const
{
return isBeforeOrEqual(anchor_, caret_) ? anchor_ : caret_;
}
ConsoleTextPos ConsoleSelectionController::rangeEnd() const
{
return isBeforeOrEqual(anchor_, caret_) ? caret_ : anchor_;
}
void ConsoleSelectionController::shiftForEviction(int popped)
{
if (popped <= 0 || !active_) return;
anchor_.line -= popped;
caret_.line -= popped;
if (anchor_.line < 0 && caret_.line < 0) {
// Entire selection was in the removed range.
active_ = false;
dragging_ = false;
} else {
if (anchor_.line < 0) { anchor_.line = 0; anchor_.col = 0; }
if (caret_.line < 0) { caret_.line = 0; caret_.col = 0; }
}
}
std::string ConsoleSelectionController::extract(int lineCount, const ConsoleLineAccessor& getLine,
const std::vector<int>& visibleIndices) const
{
if (!active_) return "";
return ExtractConsoleSelection(lineCount, getLine, visibleIndices,
rangeStart(), rangeEnd());
}
} // namespace ui
} // namespace dragonx