feat(settings): daemon maintenance actions right-aligned on Test row; fix Open-folder overflow

- Remove the "Advanced" toggle. The four maintenance actions (Install bundled /
  Rescan / Delete blockchain / Repair wallet) now render right-aligned on the same
  row as Check-for-updates / Refresh / Test connection (wrapping to a right-aligned
  next row only when the window is too narrow). Predicates preserved.
- Fix the Open-data-folder button overflowing the card's right edge (worse at high
  display scaling): its width was estimated with the CURRENT font, not the button
  font, so the estimate was wrong. Measure with the button font and give the button
  an explicit width so its right edge lands exactly on the card edge regardless of
  DPI/font-scale timing.
- Drop the now-unused node_advanced_expanded flag.

Full-node build clean; hygiene clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-07-02 02:26:58 -05:00
parent 36386b2b84
commit 96e05b4ba6

View File

@@ -144,7 +144,6 @@ struct SettingsPageState {
std::set<std::string> debug_categories; std::set<std::string> debug_categories;
bool debug_cats_dirty = false; bool debug_cats_dirty = false;
bool debug_expanded = false; bool debug_expanded = false;
bool node_advanced_expanded = true; // Node & Security: rare/destructive daemon actions (shown by default)
bool effects_expanded = false; bool effects_expanded = false;
bool tools_expanded = false; bool tools_expanded = false;
bool confirm_clear_ztx = false; bool confirm_clear_ztx = false;
@@ -2024,8 +2023,12 @@ void RenderSettingsPage(App* app) {
// gets whatever width is left after the data-dir label, wallet-size // gets whatever width is left after the data-dir label, wallet-size
// label+value, and the trailing button. // label+value, and the trailing button.
ImFont* nodeBtnFont = S.resolveFont("button"); ImFont* nodeBtnFont = S.resolveFont("button");
float openBtnW = ImGui::CalcTextSize(TR("settings_open_data_dir")).x + if (!nodeBtnFont) nodeBtnFont = Type().button();
ImGui::GetStyle().FramePadding.x * 2.0f + btnPad; // Measure with the BUTTON font (not the current font) so the width matches
// the real TactileButton size — the old estimate mis-measured and the button
// overflowed the card's right edge, worse at high DPI.
float openBtnW = nodeBtnFont->CalcTextSizeA(nodeBtnFont->LegacySize, FLT_MAX, 0,
TR("settings_open_data_dir")).x + ImGui::GetStyle().FramePadding.x * 2.0f;
float dataDirLabelW = ImGui::CalcTextSize(TR("settings_data_dir")).x; float dataDirLabelW = ImGui::CalcTextSize(TR("settings_data_dir")).x;
float sizeLabelW = ImGui::CalcTextSize(TR("settings_wallet_size_label")).x; float sizeLabelW = ImGui::CalcTextSize(TR("settings_wallet_size_label")).x;
float sizeValueW = ImGui::CalcTextSize(size_str.c_str()).x; float sizeValueW = ImGui::CalcTextSize(size_str.c_str()).x;
@@ -2084,18 +2087,17 @@ void RenderSettingsPage(App* app) {
else else
ImGui::TextDisabled("%s", TR("settings_not_found")); ImGui::TextDisabled("%s", TR("settings_not_found"));
// Open-data-folder button: right-aligned when there is room, else trailing — // Open-data-folder button: right-aligned on the data-dir row (rowTopY), with
// kept on the data-dir row (rowTopY), not the next line. // an EXPLICIT width (openBtnW) so its right edge lands exactly on the card
// edge and never overflows, regardless of DPI / font-scale timing.
float rowY = rowTopY; float rowY = rowTopY;
float rightEdge = sectionOrigin.x + contentW; float rightEdge = sectionOrigin.x + contentW;
float actualOpenW = ImGui::CalcTextSize(TR("settings_open_data_dir")).x + float openX = rightEdge - openBtnW;
ImGui::GetStyle().FramePadding.x * 2.0f;
float openX = rightEdge - actualOpenW;
float afterSizeX = ImGui::GetItemRectMax().x + spMd; float afterSizeX = ImGui::GetItemRectMax().x + spMd;
ImGui::SameLine(0, spMd); ImGui::SameLine(0, spMd);
if (openX > afterSizeX) if (openX > afterSizeX)
ImGui::SetCursorScreenPos(ImVec2(openX, rowY)); ImGui::SetCursorScreenPos(ImVec2(openX, rowY));
if (TactileButton(TR("settings_open_data_dir"), ImVec2(0, 0), nodeBtnFont)) { if (TactileButton(TR("settings_open_data_dir"), ImVec2(openBtnW, 0), nodeBtnFont)) {
util::Platform::openFolder(dirPath); util::Platform::openFolder(dirPath);
} }
if (ImGui::IsItemHovered()) material::Tooltip("%s", TR("tt_open_data_dir")); if (ImGui::IsItemHovered()) material::Tooltip("%s", TR("tt_open_data_dir"));
@@ -2250,8 +2252,10 @@ void RenderSettingsPage(App* app) {
if (ui::DaemonUpdateDialog::consumeInstalled()) if (ui::DaemonUpdateDialog::consumeInstalled())
s_settingsState.daemon_info_loaded = false; s_settingsState.daemon_info_loaded = false;
// Common action row: Check for updates | Refresh | Test connection. // Action row: Check for updates | Refresh | Test connection on the left,
// with the four maintenance actions right-aligned on the same row.
ImGui::Dummy(ImVec2(0, Layout::spacingXs())); ImGui::Dummy(ImVec2(0, Layout::spacingXs()));
float daemonBtnRowY = ImGui::GetCursorScreenPos().y;
if (TactileButton(TR("daemon_update_check"), ImVec2(0, 0), dbBtnFont)) { if (TactileButton(TR("daemon_update_check"), ImVec2(0, 0), dbBtnFont)) {
ui::DaemonUpdateDialog::show(app, inst.exists ? inst.version : std::string()); ui::DaemonUpdateDialog::show(app, inst.exists ? inst.version : std::string());
} }
@@ -2281,10 +2285,11 @@ void RenderSettingsPage(App* app) {
if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled)) material::Tooltip("%s", TR("tt_test_conn")); if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled)) material::Tooltip("%s", TR("tt_test_conn"));
ImGui::EndDisabled(); ImGui::EndDisabled();
// The FOUR destructive maintenance actions, in one horizontal row prefixed // The four maintenance actions (Install bundled / Rescan / Delete blockchain
// by a visible "Advanced" toggle. Every BeginDisabled/EndDisabled predicate // / Repair wallet), right-aligned on the SAME row as Test connection — no
// is preserved exactly. // separate "Advanced" toggle. Every BeginDisabled/EndDisabled predicate is
auto renderDaemonAdvancedButtons = [&]() { // preserved exactly. Wraps to a right-aligned next row when too narrow.
auto renderDaemonMaintenanceButtons = [&]() {
ImGui::BeginDisabled(!app->isUsingEmbeddedDaemon() || !bun.available); ImGui::BeginDisabled(!app->isUsingEmbeddedDaemon() || !bun.available);
if (TactileButton(TR("daemon_install_bundled"), ImVec2(0, 0), dbBtnFont)) { if (TactileButton(TR("daemon_install_bundled"), ImVec2(0, 0), dbBtnFont)) {
s_settingsState.confirm_reinstall_daemon = true; s_settingsState.confirm_reinstall_daemon = true;
@@ -2314,40 +2319,28 @@ void RenderSettingsPage(App* app) {
if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled)) material::Tooltip("%s", TR("tt_repair_wallet")); if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled)) material::Tooltip("%s", TR("tt_repair_wallet"));
ImGui::EndDisabled(); ImGui::EndDisabled();
}; };
// Advanced — a clearly-clickable toggle (label + arrow) on its own line,
// controlling the rare + destructive daemon maintenance actions. When
// expanded the four buttons render on one horizontal row: inline with the
// toggle when there is room, otherwise wrapped to the next full-width row.
ImGui::Dummy(ImVec2(0, Layout::spacingSm()));
{ {
char advBuf[64]; // Group width (button font) to right-align the four maintenance actions.
const char* advArrow = s_settingsState.node_advanced_expanded const char* mLabels[] = { TR("daemon_install_bundled"), TR("rescan"),
? ICON_MD_EXPAND_LESS : ICON_MD_EXPAND_MORE; TR("delete_blockchain"), TR("repair_wallet") };
snprintf(advBuf, sizeof(advBuf), "%s %s##NodeAdvancedToggle", float groupW = spMd * 3.0f;
TR("advanced"), advArrow); for (const char* l : mLabels)
// Width of the four Advanced buttons + gaps, to decide inline vs. wrap. groupW += dbBtnFont->CalcTextSizeA(dbBtnFont->LegacySize, FLT_MAX, 0, l).x
float advBtnsW = spMd * 3; + ImGui::GetStyle().FramePadding.x * 2.0f;
const char* advLabels[] = { TR("daemon_install_bundled"), TR("rescan"), float afterTestX = ImGui::GetItemRectMax().x + spMd; // right edge of Test connection
TR("delete_blockchain"), TR("repair_wallet") }; float rightEdge = sectionOrigin.x + contentW;
for (const char* l : advLabels) float groupX = rightEdge - groupW;
advBtnsW += ImGui::CalcTextSize(l).x + ImGui::GetStyle().FramePadding.x * 2.0f; if (groupX >= afterTestX) {
float advToggleW = ImGui::CalcTextSize(advBuf).x + // Fits on the Test-connection row, right-aligned.
ImGui::GetStyle().FramePadding.x * 2.0f; ImGui::SameLine(0, 0);
ImGui::SetCursorScreenPos(ImVec2(groupX, daemonBtnRowY));
if (TactileButton(advBuf, ImVec2(0, 0), dbBtnFont)) { } else {
s_settingsState.node_advanced_expanded = !s_settingsState.node_advanced_expanded; // Too narrow: wrap to the next row, right-aligned (clamped to left edge).
} ImGui::Dummy(ImVec2(0, Layout::spacingXs()));
if (s_settingsState.node_advanced_expanded) { float wrapX = std::max(sectionOrigin.x, rightEdge - groupW);
const bool inline_ = (advToggleW + spMd + advBtnsW) <= contentW; ImGui::SetCursorScreenPos(ImVec2(wrapX, ImGui::GetCursorScreenPos().y));
if (inline_) {
ImGui::SameLine(0, spMd);
} else {
ImGui::Dummy(ImVec2(0, Layout::spacingXs()));
ImGui::SetCursorScreenPos(ImVec2(sectionOrigin.x, ImGui::GetCursorScreenPos().y));
}
renderDaemonAdvancedButtons();
} }
renderDaemonMaintenanceButtons();
} }
} }