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-min-width = { size = 70.0 }
|
||||||
rpc-label-width = { size = 85.0 }
|
rpc-label-width = { size = 85.0 }
|
||||||
security-combo-width = { size = 120.0 }
|
security-combo-width = { size = 120.0 }
|
||||||
|
node-grid-breakpoint = { size = 900.0 }
|
||||||
port-input-min-width = { size = 60.0 }
|
port-input-min-width = { size = 60.0 }
|
||||||
port-input-width-ratio = { size = 0.4 }
|
port-input-width-ratio = { size = 0.4 }
|
||||||
idle-combo-width = { size = 64.0 }
|
idle-combo-width = { size = 64.0 }
|
||||||
|
|||||||
@@ -1508,18 +1508,28 @@ void RenderSettingsPage(App* app) {
|
|||||||
return std::max(minBtnW, maxTextW + btnPad * 2);
|
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
|
// Wide full-node: a 2x2 grid fills the card width with no empty gap —
|
||||||
// vertically, each at full card width. (Two-column variants left an unbalanced
|
// TOP-LEFT = NODE/Data, TOP-RIGHT = RPC,
|
||||||
// empty gap for this content, and the Daemon Binary button rows want the full
|
// BOTTOM-LEFT = SECURITY, BOTTOM-RIGHT = DAEMON BINARY head,
|
||||||
// width anyway.) `stacked` drives the existing vertical-flow path below.
|
// with the Advanced destructive buttons dropping to a full-width strip below.
|
||||||
bool stacked = true;
|
// Narrow windows (or lite, which has no daemon) fall through to the original
|
||||||
float leftColW = contentW;
|
// single full-width column: Node -> RPC -> Security -> Daemon Binary stack.
|
||||||
float rightColW = contentW;
|
// `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();
|
ImVec2 sectionOrigin = ImGui::GetCursorScreenPos();
|
||||||
float leftX = sectionOrigin.x;
|
float leftX = sectionOrigin.x;
|
||||||
float rightX = 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 ----
|
// ---- LEFT COLUMN: NODE ----
|
||||||
ImGui::SetCursorScreenPos(ImVec2(leftX, sectionOrigin.y));
|
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 size_str = (wallet_size > 0) ? util::Platform::formatFileSize(wallet_size) : TR("settings_not_found");
|
||||||
std::string dirPath = util::Platform::getDragonXDataDir();
|
std::string dirPath = util::Platform::getDragonXDataDir();
|
||||||
|
|
||||||
// Calculate space taken by "Wallet Size: <size>" on the right
|
// Wallet Size now lives on its own row below (so the grid's left cell
|
||||||
float walletSizeLabelW = ImGui::CalcTextSize(TR("settings_wallet_size_label")).x + Layout::spacingXs();
|
// has no mid-row gap), so the path gets the full column width.
|
||||||
float walletSizeValW = ImGui::CalcTextSize(size_str.c_str()).x;
|
|
||||||
float walletSizeTotalW = walletSizeLabelW + walletSizeValW + Layout::spacingLg();
|
|
||||||
|
|
||||||
// Available space for "Data Dir: <path>"
|
|
||||||
float dataDirLabelW = ImGui::CalcTextSize(TR("settings_data_dir")).x + Layout::spacingXs();
|
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::TextUnformatted(TR("settings_data_dir"));
|
||||||
ImGui::SameLine(0, Layout::spacingXs());
|
ImGui::SameLine(0, Layout::spacingXs());
|
||||||
@@ -1941,7 +1947,9 @@ void RenderSettingsPage(App* app) {
|
|||||||
if (ImGui::IsItemClicked())
|
if (ImGui::IsItemClicked())
|
||||||
util::Platform::openFolder(dirPath);
|
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::TextUnformatted(TR("settings_wallet_size_label"));
|
||||||
ImGui::SameLine(0, Layout::spacingXs());
|
ImGui::SameLine(0, Layout::spacingXs());
|
||||||
if (wallet_size > 0) {
|
if (wallet_size > 0) {
|
||||||
@@ -1958,20 +1966,40 @@ void RenderSettingsPage(App* app) {
|
|||||||
}
|
}
|
||||||
if (ImGui::IsItemHovered()) material::Tooltip("%s", TR("tt_open_data_dir"));
|
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()));
|
ImGui::Dummy(ImVec2(0, Layout::spacingXs()));
|
||||||
|
|
||||||
// RPC connection — two columns: (Host | Username) and (Port | Password)
|
// 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 rpcHalfLblW = std::min(rpcLblW, rpcHalfW * 0.4f);
|
||||||
float rpcHalfInputW = std::max(40.0f, rpcHalfW - rpcHalfLblW);
|
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
|
// Row 1: RPC Host + Username
|
||||||
float row1Y = ImGui::GetCursorScreenPos().y;
|
float row1Y = ImGui::GetCursorScreenPos().y;
|
||||||
ImGui::SetCursorScreenPos(ImVec2(leftX, row1Y));
|
ImGui::SetCursorScreenPos(ImVec2(rpcColX, row1Y));
|
||||||
ImGui::AlignTextToFramePadding();
|
ImGui::AlignTextToFramePadding();
|
||||||
ImGui::TextUnformatted(TR("rpc_host"));
|
ImGui::TextUnformatted(TR("rpc_host"));
|
||||||
ImGui::SameLine(leftX - sectionOrigin.x + rpcHalfLblW);
|
ImGui::SameLine(rpcColX - sectionOrigin.x + rpcHalfLblW);
|
||||||
ImGui::SetNextItemWidth(rpcHalfInputW);
|
ImGui::SetNextItemWidth(rpcHalfInputW);
|
||||||
ImGui::InputText("##RPCHost", s_settingsState.rpc_host, sizeof(s_settingsState.rpc_host));
|
ImGui::InputText("##RPCHost", s_settingsState.rpc_host, sizeof(s_settingsState.rpc_host));
|
||||||
if (ImGui::IsItemHovered()) material::Tooltip("%s", TR("tt_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));
|
ImGui::InputText("##RPCUser", s_settingsState.rpc_user, sizeof(s_settingsState.rpc_user));
|
||||||
if (ImGui::IsItemHovered()) material::Tooltip("%s", TR("tt_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
|
// Row 2: Port + Password
|
||||||
float row2Y = ImGui::GetCursorScreenPos().y;
|
float row2Y = ImGui::GetCursorScreenPos().y;
|
||||||
ImGui::SetCursorScreenPos(ImVec2(leftX, row2Y));
|
ImGui::SetCursorScreenPos(ImVec2(rpcColX, row2Y));
|
||||||
ImGui::AlignTextToFramePadding();
|
ImGui::AlignTextToFramePadding();
|
||||||
ImGui::TextUnformatted(TR("rpc_port"));
|
ImGui::TextUnformatted(TR("rpc_port"));
|
||||||
ImGui::SameLine(leftX - sectionOrigin.x + rpcHalfLblW);
|
ImGui::SameLine(rpcColX - sectionOrigin.x + rpcHalfLblW);
|
||||||
ImGui::SetNextItemWidth(rpcHalfInputW);
|
ImGui::SetNextItemWidth(rpcHalfInputW);
|
||||||
ImGui::InputText("##RPCPort", s_settingsState.rpc_port, sizeof(s_settingsState.rpc_port));
|
ImGui::InputText("##RPCPort", s_settingsState.rpc_port, sizeof(s_settingsState.rpc_port));
|
||||||
if (ImGui::IsItemHovered()) material::Tooltip("%s", TR("tt_rpc_port"));
|
if (ImGui::IsItemHovered()) material::Tooltip("%s", TR("tt_rpc_port"));
|
||||||
@@ -2007,33 +2035,45 @@ void RenderSettingsPage(App* app) {
|
|||||||
ImGuiInputTextFlags_Password);
|
ImGuiInputTextFlags_Password);
|
||||||
if (ImGui::IsItemHovered()) material::Tooltip("%s", TR("tt_rpc_pass"));
|
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(),
|
Type().textColored(TypeStyle::Caption, OnSurfaceDisabled(),
|
||||||
TR("settings_auto_detected"));
|
TR("settings_auto_detected"));
|
||||||
if (s_settingsState.rpc_plaintext_remote) {
|
if (s_settingsState.rpc_plaintext_remote) {
|
||||||
ImGui::PushStyleColor(ImGuiCol_Text, ImGui::ColorConvertU32ToFloat4(Warning()));
|
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::TextWrapped("Remote RPC is using plaintext HTTP. Add rpctls=1 to DRAGONX.conf if your daemon supports TLS.");
|
||||||
ImGui::PopTextWrapPos();
|
ImGui::PopTextWrapPos();
|
||||||
ImGui::PopStyleColor();
|
ImGui::PopStyleColor();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (useGrid) {
|
||||||
|
rpcBottomGrid = ImGui::GetCursorScreenPos().y;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ImGui::PopFont();
|
ImGui::PopFont();
|
||||||
}
|
}
|
||||||
|
|
||||||
float leftBottom = ImGui::GetCursorScreenPos().y;
|
float leftBottom = useGrid ? leftBottomGrid : ImGui::GetCursorScreenPos().y;
|
||||||
|
float rpcBottom = useGrid ? rpcBottomGrid : leftBottom;
|
||||||
ImGui::PopClipRect();
|
ImGui::PopClipRect();
|
||||||
|
|
||||||
// ---- RIGHT COLUMN: SECURITY ----
|
// Grid: the two top cells share a row bottom; the bottom cells hang below it.
|
||||||
// Stacked: flows below the left column; side-by-side: aligned to the section top.
|
float row1Bottom = std::max(leftBottom, rpcBottom);
|
||||||
float rightTopY = stacked ? (leftBottom + Layout::spacingMd()) : sectionOrigin.y;
|
|
||||||
ImGui::SetCursorScreenPos(ImVec2(rightX, rightTopY));
|
// ---- SECURITY ----
|
||||||
ImGui::PushClipRect(ImVec2(rightX, rightTopY),
|
// Grid: BOTTOM-LEFT cell, under NODE. Stacked: flows below the left column.
|
||||||
ImVec2(rightX + rightColW, rightTopY + 9999), true);
|
// 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"));
|
Type().textColored(TypeStyle::Overline, OnSurfaceMedium(), TR("security"));
|
||||||
ImGui::Dummy(ImVec2(0, Layout::spacingXs()));
|
ImGui::Dummy(ImVec2(0, Layout::spacingXs()));
|
||||||
@@ -2046,9 +2086,9 @@ void RenderSettingsPage(App* app) {
|
|||||||
bool isEncrypted = app->state().isEncrypted();
|
bool isEncrypted = app->state().isEncrypted();
|
||||||
bool isLocked = app->state().isLocked();
|
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 (!isEncrypted) {
|
||||||
if (TactileButton(TR("settings_encrypt_wallet"), ImVec2(secBtnW, 0), S.resolveFont("button")))
|
if (TactileButton(TR("settings_encrypt_wallet"), ImVec2(secBtnW, 0), S.resolveFont("button")))
|
||||||
app->showEncryptDialog();
|
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"));
|
ImGui::TextColored(ImVec4(0.3f,1.0f,0.5f,1.0f), "%s", TR("settings_unlocked"));
|
||||||
}
|
}
|
||||||
// Remove Encryption button
|
// 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")))
|
if (TactileButton(TR("settings_remove_encryption"), ImVec2(secBtnW, 0), S.resolveFont("button")))
|
||||||
app->showDecryptDialog();
|
app->showDecryptDialog();
|
||||||
if (ImGui::IsItemHovered()) material::Tooltip("%s", TR("tt_remove_encrypt"));
|
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++) {
|
for (int i = 0; i < 6; i++) {
|
||||||
if (timeoutValues[i] == timeout) { selTimeout = i; break; }
|
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"));
|
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);
|
ImGui::PushItemWidth(comboW);
|
||||||
if (ImGui::Combo("##autolock", &selTimeout, timeoutLabels, 6)) {
|
if (ImGui::Combo("##autolock", &selTimeout, timeoutLabels, 6)) {
|
||||||
app->settings()->setAutoLockTimeout(timeoutValues[selTimeout]);
|
app->settings()->setAutoLockTimeout(timeoutValues[selTimeout]);
|
||||||
@@ -2116,9 +2156,9 @@ void RenderSettingsPage(App* app) {
|
|||||||
bool isEncryptedPIN = app->state().isEncrypted();
|
bool isEncryptedPIN = app->state().isEncrypted();
|
||||||
if (isEncryptedPIN) {
|
if (isEncryptedPIN) {
|
||||||
bool hasPIN = app->hasPinVault();
|
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 (!hasPIN) {
|
||||||
if (TactileButton(TR("settings_set_pin"), ImVec2(pinBtnW, 0), S.resolveFont("button")))
|
if (TactileButton(TR("settings_set_pin"), ImVec2(pinBtnW, 0), S.resolveFont("button")))
|
||||||
app->showPinSetupDialog();
|
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"));
|
ImGui::TextColored(ImVec4(0.3f,1.0f,0.5f,1.0f), "%s", TR("settings_pin_active"));
|
||||||
}
|
}
|
||||||
} else {
|
} 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"));
|
ImGui::TextColored(ImVec4(1,1,1,0.3f), "%s", TR("settings_encrypt_first_pin"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
float rightBottom = ImGui::GetCursorScreenPos().y;
|
float rightBottom = ImGui::GetCursorScreenPos().y;
|
||||||
|
float secBottom = rightBottom;
|
||||||
ImGui::PopClipRect();
|
ImGui::PopClipRect();
|
||||||
|
|
||||||
// ---- DAEMON BINARY — masonry-placed into the shorter column ----
|
// ---- DAEMON BINARY ----
|
||||||
// Node/RPC (left) is taller than Security (right), so Daemon Binary drops under
|
// Grid: BOTTOM-RIGHT cell (under RPC), sharing the bottom-row top with SECURITY.
|
||||||
// Security, filling the empty bottom-right and balancing the two columns. When
|
// Stacked (narrow): spans full width below everything. (The old masonry pick —
|
||||||
// the columns are stacked (narrow), it spans full width below both.
|
// 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()) {
|
if (app->supportsFullNodeLifecycleActions()) {
|
||||||
const bool dbFull = stacked;
|
const bool dbFull = useGrid ? false : stacked;
|
||||||
const bool dbLeftCol = leftBottom <= rightBottom;
|
const bool dbLeftCol = leftBottom <= rightBottom;
|
||||||
const float dbColW = dbFull ? contentW : (dbLeftCol ? leftColW : rightColW);
|
const float dbColW = useGrid ? rightColW
|
||||||
const float dbTopY = dbFull ? std::max(leftBottom, rightBottom)
|
: (dbFull ? contentW : (dbLeftCol ? leftColW : rightColW));
|
||||||
: (dbLeftCol ? leftBottom : rightBottom);
|
const float dbTopY = useGrid ? (row1Bottom + Layout::spacingMd())
|
||||||
const float dbLeftX = dbFull ? sectionOrigin.x : (dbLeftCol ? leftX : rightX);
|
: (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::SetCursorScreenPos(ImVec2(dbLeftX, dbTopY));
|
||||||
ImGui::PushClipRect(ImVec2(dbLeftX, dbTopY),
|
ImGui::PushClipRect(ImVec2(dbLeftX, dbTopY),
|
||||||
ImVec2(dbLeftX + dbColW, dbTopY + 9999.0f), true);
|
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.
|
// Installed | Bundled side by side across the full container width.
|
||||||
const float dbStartY = ImGui::GetCursorScreenPos().y;
|
const float dbStartY = ImGui::GetCursorScreenPos().y;
|
||||||
const float dbLineH = ImGui::GetTextLineHeightWithSpacing();
|
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));
|
ImGui::SetCursorScreenPos(ImVec2(dbLeftX, dbStartY));
|
||||||
Type().textColored(TypeStyle::Overline, OnSurfaceMedium(), TR("daemon_installed"));
|
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"));
|
if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled)) material::Tooltip("%s", TR("tt_test_conn"));
|
||||||
ImGui::EndDisabled();
|
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
|
// Advanced — rare + destructive daemon actions behind a collapsible header
|
||||||
// (reuses the DEBUG LOGGING expander idiom).
|
// (reuses the DEBUG LOGGING expander idiom).
|
||||||
ImGui::Dummy(ImVec2(0, Layout::spacingSm()));
|
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;
|
float arrowW = iconFont->CalcTextSizeA(iconFont->LegacySize, FLT_MAX, 0, advArrow).x;
|
||||||
dl->AddText(iconFont, iconFont->LegacySize, ImVec2(advPos.x + dbColW - arrowW, textY), OnSurfaceMedium(), advArrow);
|
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::Dummy(ImVec2(0, Layout::spacingXs()));
|
||||||
ImGui::SetCursorScreenPos(ImVec2(dbLeftX, ImGui::GetCursorScreenPos().y));
|
ImGui::SetCursorScreenPos(ImVec2(dbLeftX, ImGui::GetCursorScreenPos().y));
|
||||||
ImGui::BeginDisabled(!app->isUsingEmbeddedDaemon() || !bun.available);
|
renderDaemonAdvancedButtons(0.0f);
|
||||||
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();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
float dbBottom = ImGui::GetCursorScreenPos().y;
|
dbBottom = ImGui::GetCursorScreenPos().y;
|
||||||
ImGui::PopClipRect();
|
ImGui::PopClipRect();
|
||||||
if (dbFull || dbLeftCol) leftBottom = std::max(leftBottom, dbBottom);
|
if (!useGrid) {
|
||||||
if (dbFull || !dbLeftCol) rightBottom = std::max(rightBottom, dbBottom);
|
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.
|
// Advance the card cursor past the taller cell/column.
|
||||||
float maxBottom = std::max(leftBottom, rightBottom);
|
float maxBottom = useGrid ? gridMaxBottom : std::max(leftBottom, rightBottom);
|
||||||
ImGui::SetCursorScreenPos(ImVec2(sectionOrigin.x, maxBottom));
|
ImGui::SetCursorScreenPos(ImVec2(sectionOrigin.x, maxBottom));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -257,6 +257,7 @@ void I18n::loadBuiltinEnglish()
|
|||||||
strings_["setup_wizard"] = "Run Setup Wizard...";
|
strings_["setup_wizard"] = "Run Setup Wizard...";
|
||||||
|
|
||||||
// RPC / Explorer settings
|
// RPC / Explorer settings
|
||||||
|
strings_["rpc_connection"] = "RPC CONNECTION";
|
||||||
strings_["rpc_host"] = "Host";
|
strings_["rpc_host"] = "Host";
|
||||||
strings_["rpc_port"] = "Port";
|
strings_["rpc_port"] = "Port";
|
||||||
strings_["rpc_user"] = "Username";
|
strings_["rpc_user"] = "Username";
|
||||||
|
|||||||
Reference in New Issue
Block a user