feat(settings): 2x2 grid for Node & Security (fills the card width)

Single-column left every row hugging the left with a blank right half (and a
split RPC row); two-column variants left a bottom-corner gap. Rework the card as
a 2x2 grid on wide full-node windows so both halves carry real content:
  TOP-LEFT NODE/Data | TOP-RIGHT RPC
  BOTTOM-LEFT SECURITY | BOTTOM-RIGHT DAEMON BINARY
with the two top cells sharing a row bottom, a thin vertical + horizontal
divider, and the Daemon "Advanced" destructive buttons dropping to a full-width
4-button strip below the grid when expanded.

Gated behind one `useGrid` flag (supportsFullNodeLifecycleActions() &&
availWidth >= node-grid-breakpoint, default 900). When false — narrow windows and
lite — it falls through to the existing single full-width column, unchanged
(one-flag-revertible). Wallet Size moved to its own row (removes a mid-row gap in
both layouts). RPC / Security / Daemon rebased to per-cell X/width via aliases;
Advanced buttons factored into one lambda so the disabled predicates live in a
single place. Design produced + adversarially critiqued via a multi-agent pass.

New: components.settings-page.node-grid-breakpoint (ui.toml), "rpc_connection"
i18n string. Full-node + lite build clean; ctest green; hygiene clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-07-02 00:55:52 -05:00
parent 7826be4b13
commit fc5ce019e2
3 changed files with 170 additions and 87 deletions

View File

@@ -1325,6 +1325,7 @@ wallet-btn-padding = { size = 24.0 }
rpc-label-min-width = { size = 70.0 }
rpc-label-width = { size = 85.0 }
security-combo-width = { size = 120.0 }
node-grid-breakpoint = { size = 900.0 }
port-input-min-width = { size = 60.0 }
port-input-width-ratio = { size = 0.4 }
idle-combo-width = { size = 64.0 }

View File

@@ -1508,18 +1508,28 @@ void RenderSettingsPage(App* app) {
return std::max(minBtnW, maxTextW + btnPad * 2);
};
// --- NODE + SECURITY: side by side when wide, stacked when narrow ---
// --- NODE + SECURITY: 2x2 grid on wide full-node windows, stacked otherwise ---
{
// Single full-width column: Node -> RPC -> Security -> Daemon Binary stack
// vertically, each at full card width. (Two-column variants left an unbalanced
// empty gap for this content, and the Daemon Binary button rows want the full
// width anyway.) `stacked` drives the existing vertical-flow path below.
bool stacked = true;
float leftColW = contentW;
float rightColW = contentW;
// Wide full-node: a 2x2 grid fills the card width with no empty gap —
// TOP-LEFT = NODE/Data, TOP-RIGHT = RPC,
// BOTTOM-LEFT = SECURITY, BOTTOM-RIGHT = DAEMON BINARY head,
// with the Advanced destructive buttons dropping to a full-width strip below.
// Narrow windows (or lite, which has no daemon) fall through to the original
// single full-width column: Node -> RPC -> Security -> Daemon Binary stack.
// `stacked` still drives the existing vertical-flow path below.
float gridBP = S.drawElement("components.settings-page", "node-grid-breakpoint").sizeOr(900.0f);
bool useGrid = app->supportsFullNodeLifecycleActions() && (availWidth >= gridBP);
bool stacked = !useGrid;
float gutter = Layout::spacingLg();
float leftColW = useGrid ? std::floor((contentW - gutter) * 0.5f) : contentW;
float rightColW = useGrid ? (contentW - gutter - leftColW) : contentW;
ImVec2 sectionOrigin = ImGui::GetCursorScreenPos();
float leftX = sectionOrigin.x;
float rightX = sectionOrigin.x;
float leftX = sectionOrigin.x;
float rightX = useGrid ? (sectionOrigin.x + leftColW + gutter) : sectionOrigin.x;
// Grid path: NODE (left) and RPC (right) live in separate clips, so their
// bottoms are captured inside the content block below and read out afterward.
float leftBottomGrid = sectionOrigin.y;
float rpcBottomGrid = sectionOrigin.y;
// ---- LEFT COLUMN: NODE ----
ImGui::SetCursorScreenPos(ImVec2(leftX, sectionOrigin.y));
@@ -1896,14 +1906,10 @@ void RenderSettingsPage(App* app) {
std::string size_str = (wallet_size > 0) ? util::Platform::formatFileSize(wallet_size) : TR("settings_not_found");
std::string dirPath = util::Platform::getDragonXDataDir();
// Calculate space taken by "Wallet Size: <size>" on the right
float walletSizeLabelW = ImGui::CalcTextSize(TR("settings_wallet_size_label")).x + Layout::spacingXs();
float walletSizeValW = ImGui::CalcTextSize(size_str.c_str()).x;
float walletSizeTotalW = walletSizeLabelW + walletSizeValW + Layout::spacingLg();
// Available space for "Data Dir: <path>"
// Wallet Size now lives on its own row below (so the grid's left cell
// has no mid-row gap), so the path gets the full column width.
float dataDirLabelW = ImGui::CalcTextSize(TR("settings_data_dir")).x + Layout::spacingXs();
float availForPath = leftColW - walletSizeTotalW - dataDirLabelW;
float availForPath = leftColW - dataDirLabelW;
ImGui::TextUnformatted(TR("settings_data_dir"));
ImGui::SameLine(0, Layout::spacingXs());
@@ -1941,7 +1947,9 @@ void RenderSettingsPage(App* app) {
if (ImGui::IsItemClicked())
util::Platform::openFolder(dirPath);
ImGui::SameLine(0, Layout::spacingLg());
// Wallet Size on its own row (no SameLine) so the left grid cell has
// no mid-row gap; reads fine full-width too.
ImGui::SetCursorScreenPos(ImVec2(leftX, ImGui::GetCursorScreenPos().y));
ImGui::TextUnformatted(TR("settings_wallet_size_label"));
ImGui::SameLine(0, Layout::spacingXs());
if (wallet_size > 0) {
@@ -1958,20 +1966,40 @@ void RenderSettingsPage(App* app) {
}
if (ImGui::IsItemHovered()) material::Tooltip("%s", TR("tt_open_data_dir"));
// When gridded, RPC moves into the TOP-RIGHT cell: close the LEFT clip here
// and open the RIGHT cell rebased to rightX/rightColW. When stacked, RPC
// stays inline in the left column exactly as before.
if (useGrid) {
ImGui::PopFont(); // body2 re-pushed below for the right cell
leftBottomGrid = ImGui::GetCursorScreenPos().y;
ImGui::PopClipRect();
float rpcTopY = sectionOrigin.y;
ImGui::SetCursorScreenPos(ImVec2(rightX, rpcTopY));
ImGui::PushClipRect(ImVec2(rightX, rpcTopY),
ImVec2(rightX + rightColW, rpcTopY + 9999), true);
Type().textColored(TypeStyle::Overline, OnSurfaceMedium(), TR("rpc_connection"));
ImGui::Dummy(ImVec2(0, Layout::spacingXs()));
ImGui::PushFont(body2Info);
}
// RPC cell geometry: rebased to the right cell when gridded, else the left.
const float rpcColX = useGrid ? rightX : leftX;
const float rpcColW = useGrid ? rightColW : leftColW;
ImGui::Dummy(ImVec2(0, Layout::spacingXs()));
// RPC connection — two columns: (Host | Username) and (Port | Password)
float rpcHalfW = (leftColW - Layout::spacingMd()) * 0.5f;
float rpcHalfW = (rpcColW - Layout::spacingMd()) * 0.5f;
float rpcHalfLblW = std::min(rpcLblW, rpcHalfW * 0.4f);
float rpcHalfInputW = std::max(40.0f, rpcHalfW - rpcHalfLblW);
float rpcRightColX = leftX + rpcHalfW + Layout::spacingMd();
float rpcRightColX = rpcColX + rpcHalfW + Layout::spacingMd();
// Row 1: RPC Host + Username
float row1Y = ImGui::GetCursorScreenPos().y;
ImGui::SetCursorScreenPos(ImVec2(leftX, row1Y));
ImGui::SetCursorScreenPos(ImVec2(rpcColX, row1Y));
ImGui::AlignTextToFramePadding();
ImGui::TextUnformatted(TR("rpc_host"));
ImGui::SameLine(leftX - sectionOrigin.x + rpcHalfLblW);
ImGui::SameLine(rpcColX - sectionOrigin.x + rpcHalfLblW);
ImGui::SetNextItemWidth(rpcHalfInputW);
ImGui::InputText("##RPCHost", s_settingsState.rpc_host, sizeof(s_settingsState.rpc_host));
if (ImGui::IsItemHovered()) material::Tooltip("%s", TR("tt_rpc_host"));
@@ -1985,14 +2013,14 @@ void RenderSettingsPage(App* app) {
ImGui::InputText("##RPCUser", s_settingsState.rpc_user, sizeof(s_settingsState.rpc_user));
if (ImGui::IsItemHovered()) material::Tooltip("%s", TR("tt_rpc_user"));
ImGui::SetCursorScreenPos(ImVec2(leftX, std::max(afterRow1Y, ImGui::GetCursorScreenPos().y)));
ImGui::SetCursorScreenPos(ImVec2(rpcColX, std::max(afterRow1Y, ImGui::GetCursorScreenPos().y)));
// Row 2: Port + Password
float row2Y = ImGui::GetCursorScreenPos().y;
ImGui::SetCursorScreenPos(ImVec2(leftX, row2Y));
ImGui::SetCursorScreenPos(ImVec2(rpcColX, row2Y));
ImGui::AlignTextToFramePadding();
ImGui::TextUnformatted(TR("rpc_port"));
ImGui::SameLine(leftX - sectionOrigin.x + rpcHalfLblW);
ImGui::SameLine(rpcColX - sectionOrigin.x + rpcHalfLblW);
ImGui::SetNextItemWidth(rpcHalfInputW);
ImGui::InputText("##RPCPort", s_settingsState.rpc_port, sizeof(s_settingsState.rpc_port));
if (ImGui::IsItemHovered()) material::Tooltip("%s", TR("tt_rpc_port"));
@@ -2007,33 +2035,45 @@ void RenderSettingsPage(App* app) {
ImGuiInputTextFlags_Password);
if (ImGui::IsItemHovered()) material::Tooltip("%s", TR("tt_rpc_pass"));
ImGui::SetCursorScreenPos(ImVec2(leftX, std::max(afterRow2Y, ImGui::GetCursorScreenPos().y)));
ImGui::SetCursorScreenPos(ImVec2(rpcColX, std::max(afterRow2Y, ImGui::GetCursorScreenPos().y)));
ImGui::SetCursorScreenPos(ImVec2(leftX, ImGui::GetCursorScreenPos().y));
ImGui::SetCursorScreenPos(ImVec2(rpcColX, ImGui::GetCursorScreenPos().y));
Type().textColored(TypeStyle::Caption, OnSurfaceDisabled(),
TR("settings_auto_detected"));
if (s_settingsState.rpc_plaintext_remote) {
ImGui::PushStyleColor(ImGuiCol_Text, ImGui::ColorConvertU32ToFloat4(Warning()));
ImGui::PushTextWrapPos(leftX + leftColW);
ImGui::PushTextWrapPos(rpcColX + rpcColW);
ImGui::TextWrapped("Remote RPC is using plaintext HTTP. Add rpctls=1 to DRAGONX.conf if your daemon supports TLS.");
ImGui::PopTextWrapPos();
ImGui::PopStyleColor();
}
if (useGrid) {
rpcBottomGrid = ImGui::GetCursorScreenPos().y;
}
}
ImGui::PopFont();
}
float leftBottom = ImGui::GetCursorScreenPos().y;
float leftBottom = useGrid ? leftBottomGrid : ImGui::GetCursorScreenPos().y;
float rpcBottom = useGrid ? rpcBottomGrid : leftBottom;
ImGui::PopClipRect();
// ---- RIGHT COLUMN: SECURITY ----
// Stacked: flows below the left column; side-by-side: aligned to the section top.
float rightTopY = stacked ? (leftBottom + Layout::spacingMd()) : sectionOrigin.y;
ImGui::SetCursorScreenPos(ImVec2(rightX, rightTopY));
ImGui::PushClipRect(ImVec2(rightX, rightTopY),
ImVec2(rightX + rightColW, rightTopY + 9999), true);
// Grid: the two top cells share a row bottom; the bottom cells hang below it.
float row1Bottom = std::max(leftBottom, rpcBottom);
// ---- SECURITY ----
// Grid: BOTTOM-LEFT cell, under NODE. Stacked: flows below the left column.
// Rebased to the LEFT column (X=leftX, width=leftColW) when gridded.
const float secX = useGrid ? leftX : rightX;
const float secColW = useGrid ? leftColW : rightColW;
float rightTopY = useGrid ? (row1Bottom + Layout::spacingMd())
: (stacked ? (leftBottom + Layout::spacingMd()) : sectionOrigin.y);
ImGui::SetCursorScreenPos(ImVec2(secX, rightTopY));
ImGui::PushClipRect(ImVec2(secX, rightTopY),
ImVec2(secX + secColW, rightTopY + 9999), true);
Type().textColored(TypeStyle::Overline, OnSurfaceMedium(), TR("security"));
ImGui::Dummy(ImVec2(0, Layout::spacingXs()));
@@ -2046,9 +2086,9 @@ void RenderSettingsPage(App* app) {
bool isEncrypted = app->state().isEncrypted();
bool isLocked = app->state().isLocked();
float secBtnW = std::min(rowBtnW({TR("settings_encrypt_wallet"), TR("settings_change_passphrase"), TR("settings_lock_now")}), (rightColW - Layout::spacingMd()) * 0.5f);
float secBtnW = std::min(rowBtnW({TR("settings_encrypt_wallet"), TR("settings_change_passphrase"), TR("settings_lock_now")}), (secColW - Layout::spacingMd()) * 0.5f);
ImGui::SetCursorScreenPos(ImVec2(rightX, ImGui::GetCursorScreenPos().y));
ImGui::SetCursorScreenPos(ImVec2(secX, ImGui::GetCursorScreenPos().y));
if (!isEncrypted) {
if (TactileButton(TR("settings_encrypt_wallet"), ImVec2(secBtnW, 0), S.resolveFont("button")))
app->showEncryptDialog();
@@ -2078,7 +2118,7 @@ void RenderSettingsPage(App* app) {
ImGui::TextColored(ImVec4(0.3f,1.0f,0.5f,1.0f), "%s", TR("settings_unlocked"));
}
// Remove Encryption button
ImGui::SetCursorScreenPos(ImVec2(rightX, ImGui::GetCursorScreenPos().y + Layout::spacingXs()));
ImGui::SetCursorScreenPos(ImVec2(secX, ImGui::GetCursorScreenPos().y + Layout::spacingXs()));
if (TactileButton(TR("settings_remove_encryption"), ImVec2(secBtnW, 0), S.resolveFont("button")))
app->showDecryptDialog();
if (ImGui::IsItemHovered()) material::Tooltip("%s", TR("tt_remove_encrypt"));
@@ -2098,9 +2138,9 @@ void RenderSettingsPage(App* app) {
for (int i = 0; i < 6; i++) {
if (timeoutValues[i] == timeout) { selTimeout = i; break; }
}
ImGui::SetCursorScreenPos(ImVec2(rightX, ImGui::GetCursorScreenPos().y));
ImGui::SetCursorScreenPos(ImVec2(secX, ImGui::GetCursorScreenPos().y));
Type().textColored(TypeStyle::Caption, OnSurfaceMedium(), TR("settings_auto_lock"));
ImGui::SetCursorScreenPos(ImVec2(rightX, ImGui::GetCursorScreenPos().y));
ImGui::SetCursorScreenPos(ImVec2(secX, ImGui::GetCursorScreenPos().y));
ImGui::PushItemWidth(comboW);
if (ImGui::Combo("##autolock", &selTimeout, timeoutLabels, 6)) {
app->settings()->setAutoLockTimeout(timeoutValues[selTimeout]);
@@ -2116,9 +2156,9 @@ void RenderSettingsPage(App* app) {
bool isEncryptedPIN = app->state().isEncrypted();
if (isEncryptedPIN) {
bool hasPIN = app->hasPinVault();
float pinBtnW = std::min(rowBtnW({TR("settings_set_pin"), TR("settings_change_pin"), TR("settings_remove_pin")}), (rightColW - Layout::spacingSm()) * 0.5f);
float pinBtnW = std::min(rowBtnW({TR("settings_set_pin"), TR("settings_change_pin"), TR("settings_remove_pin")}), (secColW - Layout::spacingSm()) * 0.5f);
ImGui::SetCursorScreenPos(ImVec2(rightX, ImGui::GetCursorScreenPos().y));
ImGui::SetCursorScreenPos(ImVec2(secX, ImGui::GetCursorScreenPos().y));
if (!hasPIN) {
if (TactileButton(TR("settings_set_pin"), ImVec2(pinBtnW, 0), S.resolveFont("button")))
app->showPinSetupDialog();
@@ -2141,25 +2181,31 @@ void RenderSettingsPage(App* app) {
ImGui::TextColored(ImVec4(0.3f,1.0f,0.5f,1.0f), "%s", TR("settings_pin_active"));
}
} else {
ImGui::SetCursorScreenPos(ImVec2(rightX, ImGui::GetCursorScreenPos().y));
ImGui::SetCursorScreenPos(ImVec2(secX, ImGui::GetCursorScreenPos().y));
ImGui::TextColored(ImVec4(1,1,1,0.3f), "%s", TR("settings_encrypt_first_pin"));
}
}
float rightBottom = ImGui::GetCursorScreenPos().y;
float secBottom = rightBottom;
ImGui::PopClipRect();
// ---- DAEMON BINARY — masonry-placed into the shorter column ----
// Node/RPC (left) is taller than Security (right), so Daemon Binary drops under
// Security, filling the empty bottom-right and balancing the two columns. When
// the columns are stacked (narrow), it spans full width below both.
// ---- DAEMON BINARY ----
// Grid: BOTTOM-RIGHT cell (under RPC), sharing the bottom-row top with SECURITY.
// Stacked (narrow): spans full width below everything. (The old masonry pick —
// dropping it into the shorter column — only applied to the abandoned 2-col path.)
float dbBottom = 0.0f;
float gridMaxBottom = row1Bottom; // grid path: set in the daemon block below
if (app->supportsFullNodeLifecycleActions()) {
const bool dbFull = stacked;
const bool dbFull = useGrid ? false : stacked;
const bool dbLeftCol = leftBottom <= rightBottom;
const float dbColW = dbFull ? contentW : (dbLeftCol ? leftColW : rightColW);
const float dbTopY = dbFull ? std::max(leftBottom, rightBottom)
: (dbLeftCol ? leftBottom : rightBottom);
const float dbLeftX = dbFull ? sectionOrigin.x : (dbLeftCol ? leftX : rightX);
const float dbColW = useGrid ? rightColW
: (dbFull ? contentW : (dbLeftCol ? leftColW : rightColW));
const float dbTopY = useGrid ? (row1Bottom + Layout::spacingMd())
: (dbFull ? std::max(leftBottom, rightBottom)
: (dbLeftCol ? leftBottom : rightBottom));
const float dbLeftX = useGrid ? rightX
: (dbFull ? sectionOrigin.x : (dbLeftCol ? leftX : rightX));
ImGui::SetCursorScreenPos(ImVec2(dbLeftX, dbTopY));
ImGui::PushClipRect(ImVec2(dbLeftX, dbTopY),
ImVec2(dbLeftX + dbColW, dbTopY + 9999.0f), true);
@@ -2196,7 +2242,7 @@ void RenderSettingsPage(App* app) {
// 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(dbColW * 0.4f, 340.0f);
const float dbCol2X = dbLeftX + std::min(dbColW * (useGrid ? 0.5f : 0.4f), useGrid ? 200.0f : 340.0f);
ImGui::SetCursorScreenPos(ImVec2(dbLeftX, dbStartY));
Type().textColored(TypeStyle::Overline, OnSurfaceMedium(), TR("daemon_installed"));
@@ -2271,6 +2317,40 @@ void RenderSettingsPage(App* app) {
if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled)) material::Tooltip("%s", TR("tt_test_conn"));
ImGui::EndDisabled();
// The FOUR destructive Advanced buttons, factored so they can render inline
// (stacked) or as a full-width strip (grid). btnW==0 → auto width. Every
// BeginDisabled/EndDisabled predicate is preserved exactly.
auto renderDaemonAdvancedButtons = [&](float btnW) {
ImGui::BeginDisabled(!app->isUsingEmbeddedDaemon() || !bun.available);
if (TactileButton(TR("daemon_install_bundled"), ImVec2(btnW, 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(btnW, 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(btnW, 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(btnW, 0), dbBtnFont)) {
s_settingsState.confirm_repair_wallet = true;
}
if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled)) material::Tooltip("%s", TR("tt_repair_wallet"));
ImGui::EndDisabled();
};
// Advanced — rare + destructive daemon actions behind a collapsible header
// (reuses the DEBUG LOGGING expander idiom).
ImGui::Dummy(ImVec2(0, Layout::spacingSm()));
@@ -2292,47 +2372,48 @@ void RenderSettingsPage(App* app) {
float arrowW = iconFont->CalcTextSizeA(iconFont->LegacySize, FLT_MAX, 0, advArrow).x;
dl->AddText(iconFont, iconFont->LegacySize, ImVec2(advPos.x + dbColW - arrowW, textY), OnSurfaceMedium(), advArrow);
}
if (s_settingsState.node_advanced_expanded) {
// Stacked: render the Advanced buttons inline under the header. Grid: they
// drop to a full-width strip below the grid (rendered after the grid closes).
if (!useGrid && 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();
renderDaemonAdvancedButtons(0.0f);
}
float dbBottom = ImGui::GetCursorScreenPos().y;
dbBottom = ImGui::GetCursorScreenPos().y;
ImGui::PopClipRect();
if (dbFull || dbLeftCol) leftBottom = std::max(leftBottom, dbBottom);
if (dbFull || !dbLeftCol) rightBottom = std::max(rightBottom, dbBottom);
if (!useGrid) {
if (dbFull || dbLeftCol) leftBottom = std::max(leftBottom, dbBottom);
if (dbFull || !dbLeftCol) rightBottom = std::max(rightBottom, dbBottom);
}
// Grid finalization: the Advanced buttons drop to a full-width strip below
// the 2x2 grid, then thin dividers separate the four cells. Done inside the
// daemon block so renderDaemonAdvancedButtons (which captures bun/dbBtnFont)
// is still in scope. useGrid implies this block ran, so this covers it.
if (useGrid) {
float gridBottom = std::max(secBottom, dbBottom);
float advBottom = gridBottom;
if (s_settingsState.node_advanced_expanded) {
float advTopY = gridBottom + gutter;
ImGui::SetCursorScreenPos(ImVec2(sectionOrigin.x, advTopY));
float advBtnW = std::floor((contentW - 3 * Layout::spacingMd()) / 4.0f);
renderDaemonAdvancedButtons(advBtnW);
advBottom = ImGui::GetCursorScreenPos().y;
}
gridMaxBottom = std::max(gridBottom, advBottom);
// Thin, low-alpha dividers separating the four grid cells.
const ImU32 divCol = OnSurfaceDisabled();
float vDivX = leftX + leftColW + gutter * 0.5f;
dl->AddLine(ImVec2(vDivX, sectionOrigin.y), ImVec2(vDivX, gridBottom), divCol);
dl->AddLine(ImVec2(sectionOrigin.x, row1Bottom),
ImVec2(sectionOrigin.x + contentW, row1Bottom), divCol);
}
}
// Advance the card cursor past the taller column.
float maxBottom = std::max(leftBottom, rightBottom);
// Advance the card cursor past the taller cell/column.
float maxBottom = useGrid ? gridMaxBottom : std::max(leftBottom, rightBottom);
ImGui::SetCursorScreenPos(ImVec2(sectionOrigin.x, maxBottom));
}

View File

@@ -257,6 +257,7 @@ void I18n::loadBuiltinEnglish()
strings_["setup_wizard"] = "Run Setup Wizard...";
// RPC / Explorer settings
strings_["rpc_connection"] = "RPC CONNECTION";
strings_["rpc_host"] = "Host";
strings_["rpc_port"] = "Port";
strings_["rpc_user"] = "Username";