From 7df00b0909bd3b125e1da224dad7951129d512a2 Mon Sep 17 00:00:00 2001 From: DanS Date: Fri, 19 Jun 2026 02:41:33 -0500 Subject: [PATCH] fix(ui): move Daemon binary into its own full-width row below Node & Security The Daemon binary panel (info + the all-actions toolbar) lived inside the 60%-wide NODE column, so the six-button toolbar clipped. Pull it out into a dedicated full-width row rendered after the NODE + SECURITY columns reconcile, so it spans the whole card: Installed | Bundled info side by side, status line, and the Install bundled | Refresh | Test connection | Rescan | Delete blockchain | Repair wallet toolbar now have the full container width and no longer clip. The NODE column keeps only the node/RPC info. Co-Authored-By: Claude Opus 4.8 --- src/ui/pages/settings_page.cpp | 268 ++++++++++++++++----------------- 1 file changed, 126 insertions(+), 142 deletions(-) diff --git a/src/ui/pages/settings_page.cpp b/src/ui/pages/settings_page.cpp index 9ccd623..4deac9a 100644 --- a/src/ui/pages/settings_page.cpp +++ b/src/ui/pages/settings_page.cpp @@ -2005,148 +2005,6 @@ void RenderSettingsPage(App* app) { ImGui::PopStyleColor(); } - ImGui::Dummy(ImVec2(0, Layout::spacingSm())); - // Node maintenance buttons (full-node build only) - if (app->supportsFullNodeLifecycleActions()) { - ImFont* btnFont = S.resolveFont("button"); - - // ---- Daemon binary: installed vs bundled, with explicit reinstall ---- - // The wallet only auto-places dragonxd when it's missing (never overwrites a - // size-mismatched one), so this panel reports the installed binary and lets the - // user deliberately replace it with the version bundled in this wallet build. - if (!s_settingsState.daemon_info_loaded) { - s_settingsState.installed_daemon = dragonx::resources::getInstalledDaemonInfo(); - s_settingsState.bundled_daemon = dragonx::resources::getBundledDaemonInfo(); - s_settingsState.daemon_info_loaded = true; - } - const auto& inst = s_settingsState.installed_daemon; - const auto& bun = s_settingsState.bundled_daemon; - - auto fmtDate = [](std::int64_t epoch) -> std::string { - if (epoch <= 0) return "—"; - std::time_t t = static_cast(epoch); - std::tm tmv{}; -#ifdef _WIN32 - localtime_s(&tmv, &t); -#else - localtime_r(&t, &tmv); -#endif - char buf[32]; - std::strftime(buf, sizeof(buf), "%Y-%m-%d", &tmv); - return std::string(buf); - }; - - ImGui::Dummy(ImVec2(0, Layout::spacingMd())); - Type().textColored(TypeStyle::Overline, OnSurfaceMedium(), TR("daemon_binary")); - ImGui::Dummy(ImVec2(0, Layout::spacingXs())); - - // Installed | Bundled side by side, spanning the node column's width. - const float dbStartY = ImGui::GetCursorScreenPos().y; - const float dbLineH = ImGui::GetTextLineHeightWithSpacing(); - const float dbCol2X = leftX + leftColW * 0.5f; - const ImVec4 dbDim = ImGui::ColorConvertU32ToFloat4(OnSurfaceMedium()); - - // Column 1 — Installed (version, then size · date) - ImGui::SetCursorScreenPos(ImVec2(leftX, dbStartY)); - Type().textColored(TypeStyle::Overline, OnSurfaceMedium(), TR("daemon_installed")); - ImGui::SetCursorScreenPos(ImVec2(leftX, dbStartY + dbLineH)); - if (inst.exists) { - ImGui::TextUnformatted(inst.version.empty() ? TR("unknown") : inst.version.c_str()); - ImGui::SetCursorScreenPos(ImVec2(leftX, dbStartY + dbLineH * 2)); - ImGui::TextColored(dbDim, "%s · %s", - util::Platform::formatFileSize(inst.size).c_str(), - fmtDate(inst.modifiedEpoch).c_str()); - } else { - ImGui::TextColored(dbDim, "%s", TR("daemon_not_installed")); - } - - // Column 2 — Bundled (version, then size) - ImGui::SetCursorScreenPos(ImVec2(dbCol2X, dbStartY)); - Type().textColored(TypeStyle::Overline, OnSurfaceMedium(), TR("daemon_bundled")); - ImGui::SetCursorScreenPos(ImVec2(dbCol2X, dbStartY + dbLineH)); - if (bun.available) { - ImGui::TextUnformatted(bun.version.empty() ? TR("unknown") : bun.version.c_str()); - ImGui::SetCursorScreenPos(ImVec2(dbCol2X, dbStartY + dbLineH * 2)); - ImGui::TextColored(dbDim, "%s", util::Platform::formatFileSize(bun.size).c_str()); - } else { - ImGui::TextColored(dbDim, "%s", TR("daemon_none_bundled")); - } - - // Status line, below both sub-columns, spanning the node column. - ImGui::SetCursorScreenPos(ImVec2(leftX, dbStartY + dbLineH * 3 + Layout::spacingXs())); - if (bun.available) { - const bool sameSize = inst.exists && inst.size == bun.size; - if (!inst.exists) - ImGui::TextColored(ImVec4(1.0f, 0.8f, 0.2f, 1.0f), "%s", TR("daemon_status_missing")); - else if (sameSize) - ImGui::TextColored(ImVec4(0.3f, 0.8f, 0.3f, 1.0f), "%s", TR("daemon_status_match")); - else - ImGui::TextColored(ImVec4(1.0f, 0.8f, 0.2f, 1.0f), "%s", TR("daemon_status_differ")); - } - - ImGui::Dummy(ImVec2(0, Layout::spacingXs())); - // One toolbar row of all node actions: the daemon-binary actions first - // (Install bundled | Refresh), then the maintenance actions right after - // (Test connection | Rescan | Delete blockchain | Repair wallet). Auto-sized so - // they pack onto a single line. - ImGui::SetCursorScreenPos(ImVec2(leftX, ImGui::GetCursorScreenPos().y)); - // Install bundled (embedded daemon + a bundled daemon present) - ImGui::BeginDisabled(!app->isUsingEmbeddedDaemon() || !bun.available); - if (TactileButton(TR("daemon_install_bundled"), ImVec2(0, 0), btnFont)) { - s_settingsState.confirm_reinstall_daemon = true; - } - if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled)) ImGui::SetTooltip("%s", TR("tt_daemon_install_bundled")); - ImGui::EndDisabled(); - ImGui::SameLine(0, Layout::spacingMd()); - // Refresh (always available) - if (TactileButton(TR("refresh"), ImVec2(0, 0), btnFont)) { - s_settingsState.daemon_info_loaded = false; // recompute next frame - } - ImGui::SameLine(0, Layout::spacingMd()); - // Test connection | Rescan blockchain (need an active RPC connection) - ImGui::BeginDisabled(!app->isConnected()); - if (TactileButton(TR("test_connection"), ImVec2(0, 0), btnFont)) { - if (app->rpc() && app->rpc()->isConnected() && app->worker()) { - app->worker()->post([rpc = app->rpc()]() -> rpc::RPCWorker::MainCb { - try { - rpc::RPCClient::TraceScope trace("Settings / Test connection"); - rpc->call("getinfo"); - return []() { - Notifications::instance().success("RPC connection OK"); - }; - } catch (const std::exception& e) { - std::string err = e.what(); - return [err]() { - Notifications::instance().error("RPC error: " + err); - }; - } - }); - } else { - Notifications::instance().warning("Not connected to daemon"); - } - } - if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled)) ImGui::SetTooltip("%s", TR("tt_test_conn")); - ImGui::SameLine(0, Layout::spacingMd()); - if (TactileButton(TR("rescan"), ImVec2(0, 0), btnFont)) { - s_settingsState.confirm_rescan = true; - } - if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled)) ImGui::SetTooltip("%s", TR("tt_rescan")); - ImGui::EndDisabled(); - ImGui::SameLine(0, Layout::spacingMd()); - // Delete blockchain | Repair wallet (embedded daemon only) - ImGui::BeginDisabled(!app->isUsingEmbeddedDaemon()); - if (TactileButton(TR("delete_blockchain"), ImVec2(0, 0), btnFont)) { - s_settingsState.confirm_delete_blockchain = true; - } - if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled)) ImGui::SetTooltip("%s", TR("tt_delete_blockchain")); - ImGui::SameLine(0, Layout::spacingMd()); - if (TactileButton(TR("repair_wallet"), ImVec2(0, 0), btnFont)) { - s_settingsState.confirm_repair_wallet = true; - } - if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled)) ImGui::SetTooltip("%s", TR("tt_repair_wallet")); - ImGui::EndDisabled(); - } - } ImGui::PopFont(); @@ -2273,6 +2131,132 @@ void RenderSettingsPage(App* app) { // Advance cursor past both columns float maxBottom = std::max(leftBottom, rightBottom); ImGui::SetCursorScreenPos(ImVec2(sectionOrigin.x, maxBottom)); + + // ---- DAEMON BINARY — full-width row beneath the NODE + SECURITY columns ---- + if (app->supportsFullNodeLifecycleActions()) { + ImFont* dbBtnFont = S.resolveFont("button"); + if (!s_settingsState.daemon_info_loaded) { + s_settingsState.installed_daemon = dragonx::resources::getInstalledDaemonInfo(); + s_settingsState.bundled_daemon = dragonx::resources::getBundledDaemonInfo(); + s_settingsState.daemon_info_loaded = true; + } + const auto& inst = s_settingsState.installed_daemon; + const auto& bun = s_settingsState.bundled_daemon; + + auto fmtDate = [](std::int64_t epoch) -> std::string { + if (epoch <= 0) return "—"; + std::time_t t = static_cast(epoch); + std::tm tmv{}; +#ifdef _WIN32 + localtime_s(&tmv, &t); +#else + localtime_r(&t, &tmv); +#endif + char buf[32]; + std::strftime(buf, sizeof(buf), "%Y-%m-%d", &tmv); + return std::string(buf); + }; + + const float dbLeftX = sectionOrigin.x; + const ImVec4 dbDim = ImGui::ColorConvertU32ToFloat4(OnSurfaceMedium()); + + ImGui::Dummy(ImVec2(0, Layout::spacingLg())); + Type().textColored(TypeStyle::Overline, OnSurfaceMedium(), TR("daemon_binary")); + ImGui::Dummy(ImVec2(0, Layout::spacingXs())); + + // Installed | Bundled side by side across the full container width. + const float dbStartY = ImGui::GetCursorScreenPos().y; + const float dbLineH = ImGui::GetTextLineHeightWithSpacing(); + const float dbCol2X = dbLeftX + std::min(contentW * 0.4f, 340.0f); + + ImGui::SetCursorScreenPos(ImVec2(dbLeftX, dbStartY)); + Type().textColored(TypeStyle::Overline, OnSurfaceMedium(), TR("daemon_installed")); + ImGui::SetCursorScreenPos(ImVec2(dbLeftX, dbStartY + dbLineH)); + if (inst.exists) { + ImGui::TextUnformatted(inst.version.empty() ? TR("unknown") : inst.version.c_str()); + ImGui::SetCursorScreenPos(ImVec2(dbLeftX, dbStartY + dbLineH * 2)); + ImGui::TextColored(dbDim, "%s · %s", + util::Platform::formatFileSize(inst.size).c_str(), + fmtDate(inst.modifiedEpoch).c_str()); + } else { + ImGui::TextColored(dbDim, "%s", TR("daemon_not_installed")); + } + + ImGui::SetCursorScreenPos(ImVec2(dbCol2X, dbStartY)); + Type().textColored(TypeStyle::Overline, OnSurfaceMedium(), TR("daemon_bundled")); + ImGui::SetCursorScreenPos(ImVec2(dbCol2X, dbStartY + dbLineH)); + if (bun.available) { + ImGui::TextUnformatted(bun.version.empty() ? TR("unknown") : bun.version.c_str()); + ImGui::SetCursorScreenPos(ImVec2(dbCol2X, dbStartY + dbLineH * 2)); + ImGui::TextColored(dbDim, "%s", util::Platform::formatFileSize(bun.size).c_str()); + } else { + ImGui::TextColored(dbDim, "%s", TR("daemon_none_bundled")); + } + + ImGui::SetCursorScreenPos(ImVec2(dbLeftX, dbStartY + dbLineH * 3 + Layout::spacingXs())); + if (bun.available) { + const bool sameSize = inst.exists && inst.size == bun.size; + if (!inst.exists) + ImGui::TextColored(ImVec4(1.0f, 0.8f, 0.2f, 1.0f), "%s", TR("daemon_status_missing")); + else if (sameSize) + ImGui::TextColored(ImVec4(0.3f, 0.8f, 0.3f, 1.0f), "%s", TR("daemon_status_match")); + else + ImGui::TextColored(ImVec4(1.0f, 0.8f, 0.2f, 1.0f), "%s", TR("daemon_status_differ")); + } + + ImGui::Dummy(ImVec2(0, Layout::spacingXs())); + // All node actions on one full-width toolbar row: daemon-binary actions first + // (Install bundled | Refresh), then maintenance (Test connection | Rescan | + // Delete blockchain | Repair wallet). + ImGui::SetCursorScreenPos(ImVec2(dbLeftX, ImGui::GetCursorScreenPos().y)); + ImGui::BeginDisabled(!app->isUsingEmbeddedDaemon() || !bun.available); + if (TactileButton(TR("daemon_install_bundled"), ImVec2(0, 0), dbBtnFont)) { + s_settingsState.confirm_reinstall_daemon = true; + } + if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled)) ImGui::SetTooltip("%s", TR("tt_daemon_install_bundled")); + ImGui::EndDisabled(); + ImGui::SameLine(0, Layout::spacingMd()); + if (TactileButton(TR("refresh"), ImVec2(0, 0), dbBtnFont)) { + s_settingsState.daemon_info_loaded = false; + } + ImGui::SameLine(0, Layout::spacingMd()); + ImGui::BeginDisabled(!app->isConnected()); + if (TactileButton(TR("test_connection"), ImVec2(0, 0), dbBtnFont)) { + if (app->rpc() && app->rpc()->isConnected() && app->worker()) { + app->worker()->post([rpc = app->rpc()]() -> rpc::RPCWorker::MainCb { + try { + rpc::RPCClient::TraceScope trace("Settings / Test connection"); + rpc->call("getinfo"); + return []() { Notifications::instance().success("RPC connection OK"); }; + } catch (const std::exception& e) { + std::string err = e.what(); + return [err]() { Notifications::instance().error("RPC error: " + err); }; + } + }); + } else { + Notifications::instance().warning("Not connected to daemon"); + } + } + if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled)) ImGui::SetTooltip("%s", TR("tt_test_conn")); + ImGui::SameLine(0, Layout::spacingMd()); + if (TactileButton(TR("rescan"), ImVec2(0, 0), dbBtnFont)) { + s_settingsState.confirm_rescan = true; + } + if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled)) ImGui::SetTooltip("%s", TR("tt_rescan")); + ImGui::EndDisabled(); + ImGui::SameLine(0, Layout::spacingMd()); + ImGui::BeginDisabled(!app->isUsingEmbeddedDaemon()); + if (TactileButton(TR("delete_blockchain"), ImVec2(0, 0), dbBtnFont)) { + s_settingsState.confirm_delete_blockchain = true; + } + if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled)) ImGui::SetTooltip("%s", TR("tt_delete_blockchain")); + ImGui::SameLine(0, Layout::spacingMd()); + if (TactileButton(TR("repair_wallet"), ImVec2(0, 0), dbBtnFont)) { + s_settingsState.confirm_repair_wallet = true; + } + if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled)) ImGui::SetTooltip("%s", TR("tt_repair_wallet")); + ImGui::EndDisabled(); + } } ImGui::Dummy(ImVec2(0, bottomPad));