// DragonX Wallet - ImGui Edition // Copyright 2024-2026 The Hush Developers // Released under the GPLv3 #include "lite_console_tab.h" #include "../../app.h" #include "../../data/wallet_state.h" #include "../../util/i18n.h" #include "../../wallet/lite_diagnostics.h" #include "../../wallet/lite_wallet_controller.h" #include "../layout.h" #include "../material/type.h" #include "../material/colors.h" #include "imgui.h" #include #include #include #include namespace dragonx { namespace ui { using namespace material; namespace { // Re-snapshot the (mutex-guarded) log only when it actually changes, not every frame. std::vector s_lines; std::uint64_t s_cachedGeneration = static_cast(-1); bool s_autoScroll = true; // Colour error/success lines for at-a-glance scanning (substring match on the messages the // controller emits). Anything else renders in the muted default colour. ImU32 lineColor(const std::string& line) { const auto has = [&line](const char* s) { return line.find(s) != std::string::npos; }; if (has("failed") || has(" unreachable") || has("blocked") || has("could not") || has("Error") || has("error")) return Error(); if (has(": connected") || has("opened") || has("wallet ready") || has("Ready")) return Success(); return OnSurfaceMedium(); } } // namespace void RenderLiteConsoleTab(App* app) { if (!app) return; // ── Header ────────────────────────────────────────────────────────────────── Type().text(TypeStyle::H6, TR("lite_console_title")); Type().textColored(TypeStyle::Caption, OnSurfaceMedium(), TR("lite_console_intro")); ImGui::Spacing(); // ── Live status (read straight from the controller — always meaningful, even if the // event log below is empty) ───────────────────────────────────────────────── { const wallet::LiteWalletController* lw = app->liteWallet(); const char* connText; ImU32 connCol; if (!lw) { connText = "Lite backend unavailable"; connCol = Error(); } else if (lw->walletOpen()) { connText = "Connected"; connCol = Success(); } else if (lw->openInProgress()) { connText = "Connecting\xE2\x80\xA6"; // ellipsis connCol = Warning(); } else { connText = "Disconnected"; connCol = Error(); } Type().textColored(TypeStyle::Overline, OnSurfaceMedium(), TR("lite_console_status")); ImGui::SameLine(); Type().textColored(TypeStyle::Body2, connCol, connText); if (lw && lw->walletOpen()) { const SyncInfo& sync = app->state().sync; char buf[96]; if (sync.syncing && !sync.isSynced()) { double vp = sync.verification_progress; if (vp < 0.0) vp = 0.0; else if (vp > 1.0) vp = 1.0; std::snprintf(buf, sizeof(buf), "Syncing %.1f%% (block %d / %d)", vp * 100.0, sync.blocks, sync.headers); } else { std::snprintf(buf, sizeof(buf), "Synced (block %d)", sync.blocks); } Type().textColored(TypeStyle::Caption, OnSurfaceMedium(), buf); } // Surface the last open failure (e.g. server unreachable) prominently. const std::string& openErr = app->liteOpenError(); if (!openErr.empty() && (!lw || !lw->walletOpen())) { Type().textColored(TypeStyle::Caption, Error(), (std::string("Last error: ") + openErr).c_str()); } } ImGui::Spacing(); // Snapshot the event log (only when it changed). auto& diag = wallet::LiteDiagnostics::instance(); const std::uint64_t gen = diag.generation(); if (gen != s_cachedGeneration) { s_lines = diag.snapshot(); s_cachedGeneration = gen; } // ── Toolbar ───────────────────────────────────────────────────────────────── if (ImGui::Button(TR("lite_console_clear"))) diag.clear(); ImGui::SameLine(); if (ImGui::Button(TR("lite_console_copy"))) { std::string all; for (const auto& l : s_lines) { all += l; all.push_back('\n'); } ImGui::SetClipboardText(all.c_str()); } ImGui::SameLine(); ImGui::Checkbox(TR("lite_console_autoscroll"), &s_autoScroll); ImGui::Spacing(); // ── Event log (terminal-styled scroll region) ─────────────────────────────── ImGui::PushStyleColor(ImGuiCol_ChildBg, IM_COL32(0, 0, 0, 90)); ImGui::BeginChild("##LiteConsoleLog", ImVec2(0, 0), true, ImGuiWindowFlags_HorizontalScrollbar); ImGui::PushFont(Type().caption()); if (s_lines.empty()) { ImGui::PushStyleColor(ImGuiCol_Text, OnSurfaceDisabled()); ImGui::TextUnformatted(TR("lite_console_empty")); ImGui::PopStyleColor(); } else { for (const auto& line : s_lines) { ImGui::PushStyleColor(ImGuiCol_Text, lineColor(line)); ImGui::TextUnformatted(line.c_str()); // not format-interpreted — safe for any content ImGui::PopStyleColor(); } } // Keep pinned to the newest line only while the user is already at the bottom. if (s_autoScroll && ImGui::GetScrollY() >= ImGui::GetScrollMaxY()) ImGui::SetScrollHereY(1.0f); ImGui::PopFont(); ImGui::EndChild(); ImGui::PopStyleColor(); } } // namespace ui } // namespace dragonx