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:
@@ -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 }
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
|
||||
@@ -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";
|
||||
|
||||
Reference in New Issue
Block a user