feat(settings): fix Node & Security empty space — 70/30 split + Advanced in right column

The fixed 60/40 split left a large empty gap in the bottom-right: the SECURITY
column is short while NODE (data dir + RPC grid) is tall, and the DAEMON BINARY
row spans full width below both. Two changes:

- Widen the side-by-side split to 70/30 so NODE (the content-heavy column, esp.
  the RPC grid) gets the room and the right-side gap shrinks.
- Move the "Advanced" destructive-actions expander out of the full-width DAEMON
  BINARY row and into the right column below Security, filling the gap. Its four
  actions now stack vertically to fit the narrow column.

Full-node + lite build clean; source hygiene clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-07-01 23:48:45 -05:00
parent 419f8bb9cb
commit a1e4acfe86

View File

@@ -1515,8 +1515,10 @@ void RenderSettingsPage(App* app) {
// grid especially), so stack them into one full-width column instead.
float stackWidth = S.drawElement("components.settings-page", "node-sec-stack-width").sizeOr(600.0f);
bool stacked = contentW < stackWidth;
float leftColW = stacked ? contentW : (contentW - colGap) * 0.6f;
float rightColW = stacked ? contentW : (contentW - colGap) * 0.4f;
// 70/30 when side by side: NODE holds far more (data dir + RPC grid) than the
// short SECURITY column, so give it the width and shrink the right-side gap.
float leftColW = stacked ? contentW : (contentW - colGap) * 0.7f;
float rightColW = stacked ? contentW : (contentW - colGap) * 0.3f;
ImVec2 sectionOrigin = ImGui::GetCursorScreenPos();
float leftX = sectionOrigin.x;
float rightX = stacked ? sectionOrigin.x : (sectionOrigin.x + leftColW + colGap);
@@ -2146,6 +2148,59 @@ void RenderSettingsPage(App* app) {
}
}
// Advanced — rare/destructive daemon actions (full-node only), placed in the
// right column below Security so that space isn't left empty. Buttons stack
// vertically to fit the narrow column.
if (app->supportsFullNodeLifecycleActions()) {
ImGui::Dummy(ImVec2(0, Layout::spacingSm()));
ImGui::SetCursorScreenPos(ImVec2(rightX, ImGui::GetCursorScreenPos().y));
ImVec2 advPos = ImGui::GetCursorScreenPos();
const char* advArrow = s_settingsState.node_advanced_expanded ? ICON_MD_EXPAND_LESS : ICON_MD_EXPAND_MORE;
ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0,0,0,0));
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(1,1,1,0.05f));
ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4(1,1,1,0.08f));
if (ImGui::Button("##NodeAdvancedToggle", ImVec2(rightColW, ImGui::GetFrameHeight())))
s_settingsState.node_advanced_expanded = !s_settingsState.node_advanced_expanded;
ImGui::PopStyleColor(3);
ImFont* ovA = Type().overline();
float advTextY = advPos.y + (ImGui::GetFrameHeight() - ovA->LegacySize) * 0.5f;
dl->AddText(ovA, ovA->LegacySize, ImVec2(advPos.x, advTextY), OnSurfaceMedium(), TR("advanced"));
ImFont* icoA = Type().iconSmall(); if (!icoA) icoA = ovA;
float arrowW = icoA->CalcTextSizeA(icoA->LegacySize, FLT_MAX, 0, advArrow).x;
dl->AddText(icoA, icoA->LegacySize, ImVec2(advPos.x + rightColW - arrowW, advTextY), OnSurfaceMedium(), advArrow);
if (s_settingsState.node_advanced_expanded) {
ImFont* advBtnFont = S.resolveFont("button");
auto advRow = [&]() { ImGui::SetCursorScreenPos(ImVec2(rightX, ImGui::GetCursorScreenPos().y)); };
ImGui::Dummy(ImVec2(0, Layout::spacingXs()));
advRow();
ImGui::BeginDisabled(!app->isUsingEmbeddedDaemon() || !s_settingsState.bundled_daemon.available);
if (TactileButton(TR("daemon_install_bundled"), ImVec2(rightColW, 0), advBtnFont))
s_settingsState.confirm_reinstall_daemon = true;
if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled)) material::Tooltip("%s", TR("tt_daemon_install_bundled"));
ImGui::EndDisabled();
advRow();
ImGui::BeginDisabled(!app->isConnected());
if (TactileButton(TR("rescan"), ImVec2(rightColW, 0), advBtnFont)) {
s_settingsState.confirm_rescan = true;
s_settingsState.rescan_height_detecting = false;
s_settingsState.rescan_height_detected = false;
}
if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled)) material::Tooltip("%s", TR("tt_rescan"));
ImGui::EndDisabled();
advRow();
ImGui::BeginDisabled(!app->isUsingEmbeddedDaemon());
if (TactileButton(TR("delete_blockchain"), ImVec2(rightColW, 0), advBtnFont))
s_settingsState.confirm_delete_blockchain = true;
if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled)) material::Tooltip("%s", TR("tt_delete_blockchain"));
ImGui::EndDisabled();
advRow();
if (TactileButton(TR("repair_wallet"), ImVec2(rightColW, 0), advBtnFont))
s_settingsState.confirm_repair_wallet = true;
if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled)) material::Tooltip("%s", TR("tt_repair_wallet"));
}
}
float rightBottom = ImGui::GetCursorScreenPos().y;
ImGui::PopClipRect();
@@ -2262,60 +2317,8 @@ void RenderSettingsPage(App* app) {
}
if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled)) material::Tooltip("%s", TR("tt_test_conn"));
ImGui::EndDisabled();
// Advanced — rare + destructive daemon actions behind a collapsible header
// (reuses the DEBUG LOGGING expander idiom).
ImGui::Dummy(ImVec2(0, Layout::spacingSm()));
{
ImVec2 advPos = ImGui::GetCursorScreenPos();
const char* advArrow = s_settingsState.node_advanced_expanded ? ICON_MD_EXPAND_LESS : ICON_MD_EXPAND_MORE;
ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0,0,0,0));
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(1,1,1,0.05f));
ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4(1,1,1,0.08f));
if (ImGui::Button("##NodeAdvancedToggle", ImVec2(contentW, ImGui::GetFrameHeight()))) {
s_settingsState.node_advanced_expanded = !s_settingsState.node_advanced_expanded;
}
ImGui::PopStyleColor(3);
ImFont* ovFont = Type().overline();
float textY = advPos.y + (ImGui::GetFrameHeight() - ovFont->LegacySize) * 0.5f;
dl->AddText(ovFont, ovFont->LegacySize, ImVec2(advPos.x, textY), OnSurfaceMedium(), TR("advanced"));
ImFont* iconFont = Type().iconSmall();
if (!iconFont) iconFont = ovFont;
float arrowW = iconFont->CalcTextSizeA(iconFont->LegacySize, FLT_MAX, 0, advArrow).x;
dl->AddText(iconFont, iconFont->LegacySize, ImVec2(advPos.x + contentW - arrowW, textY), OnSurfaceMedium(), advArrow);
}
if (s_settingsState.node_advanced_expanded) {
ImGui::Dummy(ImVec2(0, Layout::spacingXs()));
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)) material::Tooltip("%s", TR("tt_daemon_install_bundled"));
ImGui::EndDisabled();
ImGui::SameLine(0, Layout::spacingMd());
ImGui::BeginDisabled(!app->isConnected());
if (TactileButton(TR("rescan"), ImVec2(0, 0), dbBtnFont)) {
s_settingsState.confirm_rescan = true;
// Re-probe the available block range each time the dialog is opened.
s_settingsState.rescan_height_detecting = false;
s_settingsState.rescan_height_detected = false;
}
if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled)) material::Tooltip("%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)) material::Tooltip("%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)) material::Tooltip("%s", TR("tt_repair_wallet"));
ImGui::EndDisabled();
}
// (The "Advanced" destructive-actions expander now lives in the Security
// column above, filling what used to be empty space.)
}
}