From 1c248d727acc23e894f42175a1ed77bc4326850d Mon Sep 17 00:00:00 2001 From: DanS Date: Wed, 1 Jul 2026 19:25:12 -0500 Subject: [PATCH] =?UTF-8?q?refactor(console):=20phase=205b=20=E2=80=94=20d?= =?UTF-8?q?ecompose=20toolbar/input/commands-popup=20methods?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Finish the thin-view pass on the remaining large render methods (pure code motion, no behavior change): - renderToolbar (225 -> 99 lines): extracted drawToolbarStatus(), the drawLogFilterToggles() checkbox group, drawFilterInput(), and drawZoomControls(). - renderInput: factored the command echo + built-in interception + submit into submitConsoleCommand(exec, cmd) -> bool, leaving renderInput to own just the glass panel + InputText + completion/history callback. - renderCommandsPopup (194 -> 135 lines): removed the duplicated lowercase-and- find filter logic (match-count loop and per-row filter now share consoleCommandMatchesFilter()) and extracted the [optional]-param dimming loop into drawConsoleCommandParams(). Full-node + lite build clean; ctest green; source hygiene clean. Co-Authored-By: Claude Opus 4.8 (1M context) --- src/ui/windows/console_tab.cpp | 381 +++++++++++++++++---------------- src/ui/windows/console_tab.h | 14 +- 2 files changed, 213 insertions(+), 182 deletions(-) diff --git a/src/ui/windows/console_tab.cpp b/src/ui/windows/console_tab.cpp index e5444d6..957cfa1 100644 --- a/src/ui/windows/console_tab.cpp +++ b/src/ui/windows/console_tab.cpp @@ -366,30 +366,8 @@ void ConsoleTab::renderToolbar(ConsoleCommandExecutor& exec) ImGui::SetCursorPosX(ImGui::GetCursorPosX() + Layout::spacingMd()); // Backend status with colored dot (daemon state / lite connection). - { - ConsoleStatusLine st = exec.toolbarStatus(); - if (!st.text.empty()) { - ImVec2 cp = ImGui::GetCursorScreenPos(); - float dotR = schema::UI().drawElement("tabs.console", "status-dot-radius-base").size + schema::UI().drawElement("tabs.console", "status-dot-radius-scale").size * Layout::hScale(); - float dotY = cp.y + ImGui::GetTextLineHeight() * 0.5f; - float dotX = cp.x + dotR + 2; + drawToolbarStatus(exec); - if (st.pulse) { - float a = schema::UI().drawElement("animations", "pulse-base-glow").size + schema::UI().drawElement("animations", "pulse-amp-glow").size * (float)std::sin((double)ImGui::GetTime() * schema::UI().drawElement("animations", "pulse-speed-fast").size); - ImU32 pCol = (st.color & 0x00FFFFFF) | ((ImU32)(255 * a) << 24); - dl->AddCircleFilled(ImVec2(dotX, dotY), dotR, pCol); - } else { - dl->AddCircleFilled(ImVec2(dotX, dotY), dotR, st.color); - } - - ImGui::Dummy(ImVec2(dotR * 2 + 6, 0)); - ImGui::SameLine(); - Type().textColored(TypeStyle::Caption, st.color, st.text.c_str()); - } else { - Type().textColored(TypeStyle::Caption, OnSurfaceDisabled(), TR("console_no_daemon")); - } - } - ImGui::SameLine(); ImGui::Spacing(); ImGui::SameLine(); @@ -406,49 +384,8 @@ void ConsoleTab::renderToolbar(ConsoleCommandExecutor& exec) // Log filters (daemon/errors/rpc-trace/app) apply only to a daemon log source. if (exec.hasLogFilters()) { - // Daemon messages toggle - ImGui::Checkbox(TR("console_daemon"), &s_daemon_messages_enabled); - if (ImGui::IsItemHovered()) { - material::Tooltip("%s", TR("console_show_daemon_output")); + drawLogFilterToggles(); } - - ImGui::SameLine(); - ImGui::Spacing(); - ImGui::SameLine(); - - // Errors-only toggle - ImGui::Checkbox(TR("console_errors"), &s_errors_only_enabled); - if (ImGui::IsItemHovered()) { - material::Tooltip("%s", TR("console_show_errors_only")); - } - - ImGui::SameLine(); - ImGui::Spacing(); - ImGui::SameLine(); - - // App RPC trace toggle — captures method/source only, never results or params - if (ImGui::Checkbox(TR("console_rpc_trace"), &s_rpc_trace_enabled)) { - rpc::RPCClient::setTraceEnabled(s_rpc_trace_enabled); - } - if (ImGui::IsItemHovered()) { - material::Tooltip("%s", TR("console_show_rpc_trace")); - } - - ImGui::SameLine(); - ImGui::Spacing(); - ImGui::SameLine(); - - // App messages toggle — "[app] ..." wallet log lines - ImGui::Checkbox(TR("console_app"), &s_app_messages_enabled); - if (ImGui::IsItemHovered()) { - material::Tooltip("%s", TR("console_show_app_output")); - } - - ImGui::SameLine(); - ImGui::Spacing(); - ImGui::SameLine(); - - } // exec.hasLogFilters() // Clear button if (TactileButton(TR("console_clear"), ImVec2(0, 0), schema::UI().resolveFont("button"))) { @@ -501,6 +438,90 @@ void ConsoleTab::renderToolbar(ConsoleCommandExecutor& exec) ImGui::SameLine(); // Output filter input + drawFilterInput(); + + // Zoom +/- buttons (right side of toolbar) + ImGui::SameLine(); + ImGui::Spacing(); + ImGui::SameLine(); + drawZoomControls(); +} + +void ConsoleTab::drawToolbarStatus(ConsoleCommandExecutor& exec) +{ + using namespace material; + ImDrawList* dl = ImGui::GetWindowDrawList(); + ConsoleStatusLine st = exec.toolbarStatus(); + if (!st.text.empty()) { + ImVec2 cp = ImGui::GetCursorScreenPos(); + float dotR = schema::UI().drawElement("tabs.console", "status-dot-radius-base").size + schema::UI().drawElement("tabs.console", "status-dot-radius-scale").size * Layout::hScale(); + float dotY = cp.y + ImGui::GetTextLineHeight() * 0.5f; + float dotX = cp.x + dotR + 2; + + if (st.pulse) { + float a = schema::UI().drawElement("animations", "pulse-base-glow").size + schema::UI().drawElement("animations", "pulse-amp-glow").size * (float)std::sin((double)ImGui::GetTime() * schema::UI().drawElement("animations", "pulse-speed-fast").size); + ImU32 pCol = (st.color & 0x00FFFFFF) | ((ImU32)(255 * a) << 24); + dl->AddCircleFilled(ImVec2(dotX, dotY), dotR, pCol); + } else { + dl->AddCircleFilled(ImVec2(dotX, dotY), dotR, st.color); + } + + ImGui::Dummy(ImVec2(dotR * 2 + 6, 0)); + ImGui::SameLine(); + Type().textColored(TypeStyle::Caption, st.color, st.text.c_str()); + } else { + Type().textColored(TypeStyle::Caption, OnSurfaceDisabled(), TR("console_no_daemon")); + } +} + +void ConsoleTab::drawLogFilterToggles() +{ + // Daemon messages toggle + ImGui::Checkbox(TR("console_daemon"), &s_daemon_messages_enabled); + if (ImGui::IsItemHovered()) { + material::Tooltip("%s", TR("console_show_daemon_output")); + } + + ImGui::SameLine(); + ImGui::Spacing(); + ImGui::SameLine(); + + // Errors-only toggle + ImGui::Checkbox(TR("console_errors"), &s_errors_only_enabled); + if (ImGui::IsItemHovered()) { + material::Tooltip("%s", TR("console_show_errors_only")); + } + + ImGui::SameLine(); + ImGui::Spacing(); + ImGui::SameLine(); + + // App RPC trace toggle — captures method/source only, never results or params + if (ImGui::Checkbox(TR("console_rpc_trace"), &s_rpc_trace_enabled)) { + rpc::RPCClient::setTraceEnabled(s_rpc_trace_enabled); + } + if (ImGui::IsItemHovered()) { + material::Tooltip("%s", TR("console_show_rpc_trace")); + } + + ImGui::SameLine(); + ImGui::Spacing(); + ImGui::SameLine(); + + // App messages toggle — "[app] ..." wallet log lines + ImGui::Checkbox(TR("console_app"), &s_app_messages_enabled); + if (ImGui::IsItemHovered()) { + material::Tooltip("%s", TR("console_show_app_output")); + } + + ImGui::SameLine(); + ImGui::Spacing(); + ImGui::SameLine(); +} + +void ConsoleTab::drawFilterInput() +{ + using namespace material; float zoomBtnSpace = ImGui::GetFrameHeight() * 2.0f + Layout::spacingSm() * 3.0f; float filterAvail = ImGui::GetContentRegionAvail().x - zoomBtnSpace; float filterW = std::min(schema::UI().drawElement("tabs.console", "filter-max-width").size, filterAvail * schema::UI().drawElement("tabs.console", "filter-width-ratio").size); @@ -512,31 +533,29 @@ void ConsoleTab::renderToolbar(ConsoleCommandExecutor& exec) Type().textColored(TypeStyle::Caption, filter_match_count_ > 0 ? OnSurfaceMedium() : Error(), mc.c_str()); } +} - // Zoom +/- buttons (right side of toolbar) - ImGui::SameLine(); - ImGui::Spacing(); - ImGui::SameLine(); - { - auto& S = schema::UI(); - float zoomStep = S.drawElement("tabs.console", "zoom-step").sizeOr(0.1f); - float zoomMin = S.drawElement("tabs.console", "zoom-min").sizeOr(0.5f); - float zoomMax = S.drawElement("tabs.console", "zoom-max").sizeOr(3.0f); - float btnSz = ImGui::GetFrameHeight(); +void ConsoleTab::drawZoomControls() +{ + using namespace material; + auto& S = schema::UI(); + float zoomStep = S.drawElement("tabs.console", "zoom-step").sizeOr(0.1f); + float zoomMin = S.drawElement("tabs.console", "zoom-min").sizeOr(0.5f); + float zoomMax = S.drawElement("tabs.console", "zoom-max").sizeOr(3.0f); + float btnSz = ImGui::GetFrameHeight(); - if (TactileButton(ICON_MD_REMOVE, ImVec2(btnSz, btnSz), Type().iconMed())) { - s_console_zoom = std::max(zoomMin, s_console_zoom - zoomStep); - } - if (ImGui::IsItemHovered()) { - material::Tooltip(TR("console_zoom_out"), s_console_zoom * 100.0f); - } - ImGui::SameLine(); - if (TactileButton(ICON_MD_ADD, ImVec2(btnSz, btnSz), Type().iconMed())) { - s_console_zoom = std::min(zoomMax, s_console_zoom + zoomStep); - } - if (ImGui::IsItemHovered()) { - material::Tooltip(TR("console_zoom_in"), s_console_zoom * 100.0f); - } + if (TactileButton(ICON_MD_REMOVE, ImVec2(btnSz, btnSz), Type().iconMed())) { + s_console_zoom = std::max(zoomMin, s_console_zoom - zoomStep); + } + if (ImGui::IsItemHovered()) { + material::Tooltip(TR("console_zoom_out"), s_console_zoom * 100.0f); + } + ImGui::SameLine(); + if (TactileButton(ICON_MD_ADD, ImVec2(btnSz, btnSz), Type().iconMed())) { + s_console_zoom = std::min(zoomMax, s_console_zoom + zoomStep); + } + if (ImGui::IsItemHovered()) { + material::Tooltip(TR("console_zoom_in"), s_console_zoom * 100.0f); } } @@ -1108,34 +1127,7 @@ void ConsoleTab::renderInput(ConsoleCommandExecutor& exec) if (tb == std::string::npos) cmd.clear(); else cmd = cmd.substr(tb); while (!cmd.empty() && (cmd.back() == ' ' || cmd.back() == '\t')) cmd.pop_back(); - if (!cmd.empty()) { - addLine("> " + cmd, ConsoleChannel::Command); - AppendConsoleHistory(command_history_, cmd, 100); - history_index_ = -1; - - // First token, lowercased, for built-in interception. - std::string first; - { - size_t fb = cmd.find_first_not_of(" \t"); - size_t fe = cmd.find_first_of(" \t", fb); - first = cmd.substr(fb, fe == std::string::npos ? std::string::npos : fe - fb); - std::transform(first.begin(), first.end(), first.begin(), - [](unsigned char c) { return static_cast(std::tolower(c)); }); - } - auto add = [this](const std::string& l, ConsoleChannel c) { addLine(l, c); }; - if (first == "clear" || first == "cls") { - // View-only clear — NEVER forwarded (the lite backend's `clear` wipes tx history). - clear(); - selection_.clear(); - } else if (first == "help") { - exec.printHelp(add); - } else if (first == "quit" || first == "exit") { - addLine(TR("console_quit_note"), ConsoleChannel::Info); - } else if (!exec.isReady()) { - addLine(TR("console_not_connected"), ConsoleChannel::Error); - } else { - exec.submit(cmd); - } + if (submitConsoleCommand(exec, cmd)) { input_buffer_[0] = '\0'; reclaim_focus = true; } @@ -1150,6 +1142,94 @@ void ConsoleTab::renderInput(ConsoleCommandExecutor& exec) } } +bool ConsoleTab::submitConsoleCommand(ConsoleCommandExecutor& exec, const std::string& cmd) +{ + if (cmd.empty()) return false; + + addLine("> " + cmd, ConsoleChannel::Command); + AppendConsoleHistory(command_history_, cmd, 100); + history_index_ = -1; + + // First token, lowercased, for built-in interception. + std::string first; + { + size_t fb = cmd.find_first_not_of(" \t"); + size_t fe = cmd.find_first_of(" \t", fb); + first = cmd.substr(fb, fe == std::string::npos ? std::string::npos : fe - fb); + std::transform(first.begin(), first.end(), first.begin(), + [](unsigned char c) { return static_cast(std::tolower(c)); }); + } + auto add = [this](const std::string& l, ConsoleChannel c) { addLine(l, c); }; + if (first == "clear" || first == "cls") { + // View-only clear — NEVER forwarded (the lite backend's `clear` wipes tx history). + clear(); + selection_.clear(); + } else if (first == "help") { + exec.printHelp(add); + } else if (first == "quit" || first == "exit") { + addLine(TR("console_quit_note"), ConsoleChannel::Info); + } else if (!exec.isReady()) { + addLine(TR("console_not_connected"), ConsoleChannel::Error); + } else { + exec.submit(cmd); + } + return true; +} + +namespace { +// True if any of a command's name/desc/params contains `filterLower` (already lowercased). +// An empty filter matches everything. +bool consoleCommandMatchesFilter(const char* name, const char* desc, const char* params, + const std::string& filterLower) +{ + if (filterLower.empty()) return true; + auto contains = [&filterLower](const char* s) { + std::string v(s); + std::transform(v.begin(), v.end(), v.begin(), ::tolower); + return v.find(filterLower) != std::string::npos; + }; + return contains(name) || contains(desc) || contains(params); +} + +// Draw a command's parameter string into the current table cell, dimming optional [params]. +void drawConsoleCommandParams(const char* params) +{ + using namespace material; + const char* p = params; + bool first = true; + while (*p) { + const char* bracketStart = strchr(p, '['); + if (bracketStart) { + // Draw the required part before the bracket. + if (bracketStart > p) { + if (!first) ImGui::SameLine(0, 0); + std::string req(p, bracketStart); + ImGui::TextColored(ImGui::ColorConvertU32ToFloat4(OnSurfaceDisabled()), "%s", req.c_str()); + first = false; + } + const char* bracketEnd = strchr(bracketStart, ']'); + if (bracketEnd) { + if (!first) ImGui::SameLine(0, 0); + std::string opt(bracketStart, bracketEnd + 1); + ImGui::TextColored(ImGui::ColorConvertU32ToFloat4(OnSurfaceMedium()), "%s", opt.c_str()); + first = false; + p = bracketEnd + 1; + } else { + if (!first) ImGui::SameLine(0, 0); + ImGui::TextColored(ImGui::ColorConvertU32ToFloat4(OnSurfaceDisabled()), "%s", bracketStart); + first = false; + break; + } + } else { + if (!first) ImGui::SameLine(0, 0); + ImGui::TextColored(ImGui::ColorConvertU32ToFloat4(OnSurfaceDisabled()), "%s", p); + first = false; + break; + } + } +} +} // namespace + void ConsoleTab::renderCommandsPopup() { using namespace material; @@ -1190,15 +1270,8 @@ void ConsoleTab::renderCommandsPopup() matchCount = cat.count; } else { for (int i = 0; i < cat.count; i++) { - std::string name(cat.commands[i].name); - std::string desc(cat.commands[i].desc); - std::string params(cat.commands[i].params); - std::transform(name.begin(), name.end(), name.begin(), ::tolower); - std::transform(desc.begin(), desc.end(), desc.begin(), ::tolower); - std::transform(params.begin(), params.end(), params.begin(), ::tolower); - if (name.find(filter) != std::string::npos || - desc.find(filter) != std::string::npos || - params.find(filter) != std::string::npos) { + if (consoleCommandMatchesFilter(cat.commands[i].name, cat.commands[i].desc, + cat.commands[i].params, filter)) { matchCount++; } } @@ -1234,20 +1307,9 @@ void ConsoleTab::renderCommandsPopup() for (int i = 0; i < cat.count; i++) { const auto& cmd = cat.commands[i]; - std::string nameLower(cmd.name); - std::string descLower(cmd.desc); - std::string paramsLower(cmd.params); - std::transform(nameLower.begin(), nameLower.end(), nameLower.begin(), ::tolower); - std::transform(descLower.begin(), descLower.end(), descLower.begin(), ::tolower); - std::transform(paramsLower.begin(), paramsLower.end(), paramsLower.begin(), ::tolower); - - if (!filter.empty() && - nameLower.find(filter) == std::string::npos && - descLower.find(filter) == std::string::npos && - paramsLower.find(filter) == std::string::npos) { + if (!consoleCommandMatchesFilter(cmd.name, cmd.desc, cmd.params, filter)) continue; - } - + ImGui::TableNextRow(); ImGui::TableNextColumn(); ImGui::PushStyleColor(ImGuiCol_Text, @@ -1275,48 +1337,7 @@ void ConsoleTab::renderCommandsPopup() ImGui::PopStyleColor(3); if (showParams) { ImGui::TableNextColumn(); - // Style optional params [param] in dimmed text - const char* p = cmd.params; - bool first = true; - while (*p) { - const char* bracketStart = strchr(p, '['); - if (bracketStart) { - // Draw required part before bracket - if (bracketStart > p) { - if (!first) ImGui::SameLine(0, 0); - std::string req(p, bracketStart); - ImGui::TextColored( - ImGui::ColorConvertU32ToFloat4(OnSurfaceDisabled()), - "%s", req.c_str()); - first = false; - } - // Find matching ] - const char* bracketEnd = strchr(bracketStart, ']'); - if (bracketEnd) { - if (!first) ImGui::SameLine(0, 0); - std::string opt(bracketStart, bracketEnd + 1); - ImGui::TextColored( - ImGui::ColorConvertU32ToFloat4(OnSurfaceMedium()), - "%s", opt.c_str()); - first = false; - p = bracketEnd + 1; - } else { - if (!first) ImGui::SameLine(0, 0); - ImGui::TextColored( - ImGui::ColorConvertU32ToFloat4(OnSurfaceDisabled()), - "%s", bracketStart); - first = false; - break; - } - } else { - if (!first) ImGui::SameLine(0, 0); - ImGui::TextColored( - ImGui::ColorConvertU32ToFloat4(OnSurfaceDisabled()), - "%s", p); - first = false; - break; - } - } + drawConsoleCommandParams(cmd.params); } ImGui::TableNextColumn(); ImGui::TextColored( diff --git a/src/ui/windows/console_tab.h b/src/ui/windows/console_tab.h index 654fc33..6d449b3 100644 --- a/src/ui/windows/console_tab.h +++ b/src/ui/windows/console_tab.h @@ -100,10 +100,20 @@ private: // 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 renderToolbar(ConsoleCommandExecutor& exec); - void renderInput(ConsoleCommandExecutor& exec); void renderCommandsPopup(); + // renderToolbar() draws the top bar; these are its sub-steps: + void renderToolbar(ConsoleCommandExecutor& exec); + void drawToolbarStatus(ConsoleCommandExecutor& exec); // backend status dot + label + void drawLogFilterToggles(); // daemon/errors/rpc/app 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_