With any console filter on, each batch of new output rescanned all N lines to rebuild the visible set. A text filter lowercase-copied the needle and every line to test it: O(total characters) and 2N allocations per batch. When the filter is unchanged and the model only appended or evicted lines since the last pass, the visible indices drop the evicted lines and shift down, and only the new lines are tested. The result equals a full rescan, since line text and channel never change and the filtered view has no folds. A filter change, Clear, or a model window that doesn't follow on from the last one still rescans. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
230 lines
11 KiB
C++
230 lines
11 KiB
C++
// DragonX Wallet - ImGui Edition
|
|
// Copyright 2024-2026 The Hush Developers
|
|
// Released under the GPLv3
|
|
|
|
#pragma once
|
|
|
|
#include "console_channel.h"
|
|
#include "console_model.h"
|
|
#include "console_scroll_controller.h"
|
|
#include "console_selection_controller.h"
|
|
#include "console_text_layout.h"
|
|
#include "../layout.h"
|
|
#include "../../daemon/embedded_daemon.h"
|
|
#include "../../daemon/xmrig_manager.h"
|
|
#include "../../rpc/rpc_client.h"
|
|
#include "../../rpc/rpc_worker.h"
|
|
|
|
#include <cstdint>
|
|
#include <string>
|
|
#include <vector>
|
|
#include <deque>
|
|
#include <mutex>
|
|
|
|
namespace dragonx {
|
|
namespace ui {
|
|
|
|
class ConsoleCommandExecutor;
|
|
struct ConsoleLogFilterCaps;
|
|
struct ConsoleCommandEntry;
|
|
|
|
/**
|
|
* @brief Console tab — a rich terminal shared by the full-node and lite variants.
|
|
*
|
|
* The command execution + log sources are provided by a ConsoleCommandExecutor, so this
|
|
* one widget serves both dragonxd (RPC) and the lite backend.
|
|
*/
|
|
class ConsoleTab {
|
|
public:
|
|
ConsoleTab();
|
|
~ConsoleTab();
|
|
|
|
/**
|
|
* @brief Render the console tab against a backend executor.
|
|
*/
|
|
void render(ConsoleCommandExecutor& exec);
|
|
|
|
/**
|
|
* @brief Render the command-reference popup at top-level scope.
|
|
* Must be called outside any child window so the modal blocks all input. `exec` supplies the
|
|
* reference table (full-node RPC vs lite backend verbs); may be null (no console yet) -> no-op.
|
|
*/
|
|
void renderCommandsPopupModal(ConsoleCommandExecutor* exec);
|
|
|
|
// Debug/UI-sweep hook: force the RPC command-reference popup open/closed so the full UI sweep
|
|
// can capture it (the popup is otherwise opened only by a toolbar button).
|
|
void sweepSetCommandsPopup(bool open) { show_commands_popup_ = open; }
|
|
|
|
/**
|
|
* @brief Add a line to the console output
|
|
*/
|
|
void addLine(const std::string& line, ConsoleChannel channel = ConsoleChannel::None);
|
|
void addRpcTraceLine(const std::string& source, const std::string& method);
|
|
|
|
/**
|
|
* @brief Clear console output
|
|
*/
|
|
void clear();
|
|
|
|
// Ask the console to place the keyboard focus in the command input on the next render (consumed once).
|
|
// Called when the user switches to the Console tab, gated by the console_auto_focus setting.
|
|
void requestInputFocus() { focus_input_pending_ = true; }
|
|
|
|
// Scanline effect toggle (set from settings)
|
|
static bool s_scanline_enabled;
|
|
|
|
// Console output zoom factor (1.0 = default caption font size)
|
|
static float s_console_zoom;
|
|
|
|
// Draw the per-line left color accent bars (channel-colored). Toggled from the toolbar.
|
|
static bool s_line_accents_enabled;
|
|
|
|
// Color output text per channel. When false the console is monochrome text. Toolbar-toggled.
|
|
static bool s_line_text_color_enabled;
|
|
|
|
// Show/hide daemon output messages
|
|
static bool s_daemon_messages_enabled;
|
|
|
|
// Show only error messages (filter toggle)
|
|
static bool s_errors_only_enabled;
|
|
|
|
// Show app RPC calls made through RPCClient (method/source only)
|
|
static bool s_rpc_trace_enabled;
|
|
|
|
// Show "[app] ..." wallet log lines
|
|
static bool s_app_messages_enabled;
|
|
|
|
/// Refresh console text colors for current theme (call after theme switch)
|
|
static void refreshColors();
|
|
|
|
// Colors — follow active theme (public for use by notification forwarding)
|
|
static ImU32 COLOR_COMMAND;
|
|
static ImU32 COLOR_RESULT;
|
|
static ImU32 COLOR_ERROR;
|
|
static ImU32 COLOR_DAEMON;
|
|
static ImU32 COLOR_INFO;
|
|
static ImU32 COLOR_RPC;
|
|
|
|
private:
|
|
// Text color for a channel, resolved from the current theme (COLOR_* + material). Each
|
|
// line stores only its semantic ConsoleChannel (in ConsoleModel); the text color and
|
|
// the left accent-bar color are both derived from it at draw time.
|
|
ImU32 channelTextColor(ConsoleChannel channel) const;
|
|
// Saturated accent color for a channel — the left accent-bar color, also used to tint the
|
|
// matching toolbar filter toggle. 0 for channels that get no accent (result/JSON/None).
|
|
static ImU32 channelAccentColor(ConsoleChannel channel);
|
|
|
|
// Drain the thread-safe ingest queue into the visible model (once per frame, main
|
|
// thread), applying the line cap + selection/scroll bookkeeping for the new lines.
|
|
void drainModel();
|
|
|
|
// Format a completed command result (JSON role -> channel) into console lines.
|
|
void addFormattedResult(const std::string& result, bool is_error);
|
|
void renderStatusHeader(ConsoleCommandExecutor& exec);
|
|
void renderCommandsPopup(ConsoleCommandExecutor& exec);
|
|
// right pane; jsonArgs=true (full-node RPC) auto-quotes string args, false (lite) inserts bare
|
|
void renderCommandDetail(const ConsoleCommandEntry& cmd, const char* catLabel, bool jsonArgs);
|
|
void insertCommandToInput(const ConsoleCommandEntry& cmd); // fill input + close the modal
|
|
|
|
// renderToolbar() draws the top bar; these are its sub-steps:
|
|
void renderToolbar(ConsoleCommandExecutor& exec);
|
|
void drawToolbarStatus(ConsoleCommandExecutor& exec); // backend status dot + label
|
|
void drawLogFilterToggles(const ConsoleLogFilterCaps& caps); // color-coded filter checkboxes
|
|
void drawFilterInput(); // text filter + match count
|
|
void drawZoomControls(); // zoom -/+ buttons
|
|
|
|
// renderInput() draws the command line; dispatch is factored out:
|
|
void renderInput(ConsoleCommandExecutor& exec);
|
|
// Echo + handle a submitted command line (built-ins + submit). True if non-empty/handled.
|
|
bool submitConsoleCommand(ConsoleCommandExecutor& exec, const std::string& cmd);
|
|
|
|
// renderOutput() orchestrates the scrollable output area; these are its sub-steps:
|
|
void renderOutput();
|
|
void computeVisibleLines(bool& hasTextFilter, std::string& filterLower); // -> visible_indices_
|
|
void handleOutputInteraction(ImVec2 mousePos, bool mouseInOutput); // wheel/selection/keys
|
|
void drawVisibleLines(float padX, float lineHeight, bool hasTextFilter,
|
|
const std::string& filterLower); // bars/guides/highlight/text
|
|
void drawNewOutputIndicator(); // jump-to-bottom pill
|
|
void drawOutputContextMenu(); // right-click menu
|
|
|
|
// Map a screen point to a raw {line, col} position via this frame's wrapped layout.
|
|
// (View-side because it needs layout_ + output_origin_; feeds the selection controller.)
|
|
ConsoleTextPos screenToTextPos(ImVec2 screen_pos) const;
|
|
|
|
// The current selection's text (via selection_ over the visible model). "" if inactive.
|
|
std::string selectedText() const;
|
|
|
|
// Thread-safe line store: producers on any thread call model_.ingest(); the main
|
|
// thread drains it once per frame in render(). The visible deque is main-thread-only.
|
|
ConsoleModel model_;
|
|
std::vector<std::string> command_history_;
|
|
int history_index_ = -1;
|
|
char input_buffer_[4096] = {0};
|
|
bool stop_confirm_pending_ = false; // 'stop' typed once, awaiting a confirming second 'stop'
|
|
bool focus_input_pending_ = false; // one-shot: focus the command input next render (tab-open auto-focus)
|
|
// (log-ingestion cursors + result queue moved to the ConsoleCommandExecutor)
|
|
|
|
// Auto-scroll state machine (pin-to-bottom, wheel-up cooldown, new-line backlog count).
|
|
ConsoleScrollController scroll_;
|
|
|
|
// Text-selection state + logic (anchor/caret, ordering, extraction, eviction shift).
|
|
ConsoleSelectionController selection_;
|
|
float output_scroll_y_ = 0.0f; // Track scroll position for selection
|
|
ImVec2 output_origin_ = {0, 0}; // Top-left of output area
|
|
float output_line_height_ = 0.0f; // Text line height (for scanline alignment)
|
|
|
|
struct ScanlineRow {
|
|
float yTop = 0.0f;
|
|
float yBot = 0.0f;
|
|
int rowIndex = 0;
|
|
};
|
|
std::vector<ScanlineRow> scanline_rows_;
|
|
|
|
// Output filter
|
|
char filter_text_[128] = {0};
|
|
mutable int filter_match_count_ = 0; // lines matching the text filter (for the toolbar)
|
|
std::string context_token_; // hash/address under the cursor at right-click
|
|
mutable std::vector<int> visible_indices_; // Cached for selection mapping
|
|
bool folding_active_ = true; // fold UI shown only in the unfiltered (foldable) view
|
|
bool has_text_filter_ = false; // computed once per frame (before the toolbar draws it)
|
|
std::string filter_lower_; // lowercased filter needle for match highlighting
|
|
// Flat (filtered) view bookkeeping for the incremental computeVisibleLines path: the exact filter and the
|
|
// absolute model window [vis_flat_first_, vis_flat_end_) the current visible_indices_ were computed for.
|
|
bool vis_flat_valid_ = false;
|
|
int vis_flat_flags_ = 0;
|
|
std::string vis_flat_filter_;
|
|
std::uint64_t vis_flat_first_ = 0;
|
|
std::uint64_t vis_flat_end_ = 0;
|
|
|
|
// Memoization keys so the two expensive per-frame passes rebuild only on change (not every frame):
|
|
// computeVisibleLines (filter scan over the whole model) is keyed on the model revision + filter
|
|
// state; BuildConsoleLayout (glyph-by-glyph text shaping of every visible line) is keyed on the
|
|
// resulting visible-set generation + wrap width + line height/zoom.
|
|
std::uint64_t vis_key_ = ~0ull; // key of the last computeVisibleLines
|
|
std::uint64_t vis_generation_ = 0; // bumped whenever visible_indices_ is rebuilt
|
|
std::uint64_t layout_key_ = ~0ull; // key of the last layout assembly
|
|
|
|
// Per-raw-line wrap cache (index == model_ index after sync). layout_.segments point INTO it, so it is only
|
|
// mutated by sync() immediately followed by AssembleConsoleLayout (renderOutput), or by reset() together
|
|
// with clearing layout_ (clear()). Touch it nowhere else.
|
|
ConsoleWrapCache wrap_cache_;
|
|
|
|
// Wrap layout for the visible lines (segments + per-line heights + cumulative Y), re-assembled from
|
|
// wrap_cache_ when the visible set / geometry changes, and consumed by the renderer + hit-testing.
|
|
mutable ConsoleLayout layout_;
|
|
|
|
// Commands popup (command explorer) — indexes into the active executor's commandReference()
|
|
// table (full-node RPC commands or the lite backend verbs), not always the full-node one.
|
|
bool show_commands_popup_ = false;
|
|
char command_search_[128] = {0}; // reference search filter (cleared when the modal opens)
|
|
int cmd_sel_cat_ = -1; // detail-pane selection: category index into that table
|
|
int cmd_sel_idx_ = -1; // detail-pane selection: command index within that category
|
|
std::string pending_submit_; // command to run next frame (deferred so the modal needs no executor)
|
|
const ConsoleCommandEntry* run_confirm_cmd_ = nullptr; // destructive "Insert & run" awaiting confirmation
|
|
char cmd_param_bufs_[6][256] = {{0}}; // parameter-builder input fields
|
|
const ConsoleCommandEntry* cmd_param_owner_ = nullptr; // command the param buffers currently hold
|
|
};
|
|
|
|
} // namespace ui
|
|
} // namespace dragonx
|