feat(settings): About logo scales to card height; Node&Security Advanced expander; lite gating
- About: draw the logo deferred, scaled to the card's full height (aspect-preserved, capped to the reserved width) so there's no empty space below it. - Node & Security: split the daemon toolbar — Refresh + Test connection stay visible; the rare/destructive actions (Install bundled, Rescan, Delete blockchain, Repair wallet) move behind a collapsible "Advanced" header (reusing the DEBUG LOGGING idiom). - Lite gating: hide the DEBUG LOGGING card (dragonxd debug= categories, no daemon in lite) and the RPC-backed encrypt/change/lock/remove block (lite has its own encryption in the NODE column) behind full-node guards; label the lite left column "WALLET" not "NODE". (Auto-lock + PIN remain in both.) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1327,7 +1327,9 @@ security-combo-width = { size = 120.0 }
|
||||
port-input-min-width = { size = 60.0 }
|
||||
port-input-width-ratio = { size = 0.4 }
|
||||
idle-combo-width = { size = 64.0 }
|
||||
about-logo-size = { size = 64.0 }
|
||||
# Reserved height basis for the About-card logo; the logo is drawn scaled to the
|
||||
# card's actual height (aspect-preserved) and capped to this * aspect in width.
|
||||
about-logo-size = { size = 150.0 }
|
||||
|
||||
[components.main-layout]
|
||||
app-bar-height = { size = 64.0 }
|
||||
|
||||
@@ -144,6 +144,7 @@ struct SettingsPageState {
|
||||
std::set<std::string> debug_categories;
|
||||
bool debug_cats_dirty = false;
|
||||
bool debug_expanded = false;
|
||||
bool node_advanced_expanded = false; // Node & Security: rare/destructive daemon actions
|
||||
bool effects_expanded = false;
|
||||
bool tools_expanded = false;
|
||||
bool confirm_clear_ztx = false;
|
||||
@@ -1521,7 +1522,10 @@ void RenderSettingsPage(App* app) {
|
||||
ImGui::PushClipRect(ImVec2(leftX, sectionOrigin.y),
|
||||
ImVec2(leftX + leftColW, sectionOrigin.y + 9999), true);
|
||||
|
||||
Type().textColored(TypeStyle::Overline, OnSurfaceMedium(), TR("node"));
|
||||
// Lite has no daemon — this column holds wallet lifecycle/encryption, so label
|
||||
// it "WALLET" rather than "NODE".
|
||||
Type().textColored(TypeStyle::Overline, OnSurfaceMedium(),
|
||||
app->isLiteBuild() ? TR("wallet") : TR("node"));
|
||||
ImGui::Dummy(ImVec2(0, Layout::spacingXs()));
|
||||
|
||||
{
|
||||
@@ -2029,7 +2033,10 @@ void RenderSettingsPage(App* app) {
|
||||
ImGui::Dummy(ImVec2(0, Layout::spacingXs()));
|
||||
|
||||
{
|
||||
// Encrypt / Change passphrase / Lock buttons
|
||||
// Encrypt / Change passphrase / Lock / Remove — RPC-backed, so full-node
|
||||
// only. In lite the wallet's encryption controls live in the NODE column
|
||||
// above (via the lite backend), so this duplicate is hidden.
|
||||
if (!app->isLiteBuild()) {
|
||||
bool isEncrypted = app->state().isEncrypted();
|
||||
bool isLocked = app->state().isLocked();
|
||||
|
||||
@@ -2072,6 +2079,7 @@ void RenderSettingsPage(App* app) {
|
||||
}
|
||||
|
||||
ImGui::Dummy(ImVec2(0, Layout::spacingXs()));
|
||||
} // !isLiteBuild — RPC-backed encrypt/change/lock/remove block
|
||||
|
||||
// Auto-Lock timeout
|
||||
{
|
||||
@@ -2223,17 +2231,8 @@ void RenderSettingsPage(App* app) {
|
||||
if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled)) material::Tooltip("%s", TR("tt_daemon_update_check"));
|
||||
|
||||
ImGui::Dummy(ImVec2(0, Layout::spacingXs()));
|
||||
// All node actions on one full-width toolbar row: daemon-binary actions first
|
||||
// (Install bundled | Refresh), then maintenance (Test connection | Rescan |
|
||||
// Delete blockchain | Repair wallet).
|
||||
// Common actions, always visible: Refresh | Test connection.
|
||||
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());
|
||||
if (TactileButton(TR("refresh"), ImVec2(0, 0), dbBtnFont)) {
|
||||
s_settingsState.daemon_info_loaded = false;
|
||||
}
|
||||
@@ -2256,27 +2255,61 @@ void RenderSettingsPage(App* app) {
|
||||
}
|
||||
}
|
||||
if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled)) material::Tooltip("%s", TR("tt_test_conn"));
|
||||
ImGui::SameLine(0, Layout::spacingMd());
|
||||
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;
|
||||
|
||||
// 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 (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 (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();
|
||||
}
|
||||
if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled)) material::Tooltip("%s", TR("tt_repair_wallet"));
|
||||
ImGui::EndDisabled();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2373,19 +2406,17 @@ void RenderSettingsPage(App* app) {
|
||||
ImGui::SetCursorScreenPos(ImVec2(cardMin.x, cardMin.y + pad));
|
||||
ImGui::Indent(pad);
|
||||
|
||||
// Logo on the left side of the about card
|
||||
// Logo on the left side of the about card. Deferred: reserve horizontal space
|
||||
// now, but draw the image after the card's final height is known so it scales to
|
||||
// the full card height (no empty space below it).
|
||||
ImTextureID logoTex = app->getLogoTexture();
|
||||
float logoAreaW = 0;
|
||||
ImVec2 logoPos = ImGui::GetCursorScreenPos();
|
||||
float logoAspect = (app->getLogoHeight() > 0)
|
||||
? (float)app->getLogoWidth() / (float)app->getLogoHeight() : 1.0f;
|
||||
float logoReserveH = schema::UI().drawElement("components.settings-page", "about-logo-size").sizeOr(150.0f);
|
||||
if (logoTex != 0) {
|
||||
float logoMaxH = schema::UI().drawElement("components.settings-page", "about-logo-size").sizeOr(64.0f);
|
||||
float logoH = logoMaxH;
|
||||
float aspect = (app->getLogoHeight() > 0) ? (float)app->getLogoWidth() / (float)app->getLogoHeight() : 1.0f;
|
||||
float logoW = logoH * aspect;
|
||||
ImVec2 logoPos = ImGui::GetCursorScreenPos();
|
||||
dl->AddImage(logoTex,
|
||||
ImVec2(logoPos.x, logoPos.y),
|
||||
ImVec2(logoPos.x + logoW, logoPos.y + logoH));
|
||||
logoAreaW = logoW + Layout::spacingLg();
|
||||
logoAreaW = logoReserveH * logoAspect + Layout::spacingLg();
|
||||
ImGui::Indent(logoAreaW);
|
||||
}
|
||||
|
||||
@@ -2471,6 +2502,18 @@ void RenderSettingsPage(App* app) {
|
||||
ImGui::Unindent(pad);
|
||||
|
||||
ImVec2 cardMax(cardMin.x + availWidth, ImGui::GetCursorScreenPos().y);
|
||||
|
||||
// Draw the logo now that the card height is known — scaled to the card height
|
||||
// (inset by padding), aspect-preserved, and capped to the reserved width so it
|
||||
// never overlaps the text. Still on the content channel (1), above the glass.
|
||||
if (logoTex != 0) {
|
||||
float reserveW = logoReserveH * logoAspect;
|
||||
float logoH = std::max(16.0f, (cardMax.y - logoPos.y) - bottomPad);
|
||||
float logoW = logoH * logoAspect;
|
||||
if (logoW > reserveW) { logoW = reserveW; logoH = (logoAspect > 0.0f) ? logoW / logoAspect : logoH; }
|
||||
dl->AddImage(logoTex, logoPos, ImVec2(logoPos.x + logoW, logoPos.y + logoH));
|
||||
}
|
||||
|
||||
dl->ChannelsSetCurrent(0);
|
||||
DrawGlassPanel(dl, cardMin, cardMax, glassSpec);
|
||||
dl->ChannelsMerge();
|
||||
@@ -2482,9 +2525,10 @@ void RenderSettingsPage(App* app) {
|
||||
ImGui::Dummy(ImVec2(0, gap));
|
||||
|
||||
// ====================================================================
|
||||
// DEBUG LOGGING — collapsible card with glass styling
|
||||
// DEBUG LOGGING — collapsible card (full-node only: these are dragonxd
|
||||
// daemon debug= categories written to DRAGONX.conf; lite has no daemon)
|
||||
// ====================================================================
|
||||
{
|
||||
if (app->supportsFullNodeLifecycleActions()) {
|
||||
// Clickable header row
|
||||
ImVec2 headerPos = ImGui::GetCursorScreenPos();
|
||||
const char* arrow = s_settingsState.debug_expanded ? ICON_MD_EXPAND_LESS : ICON_MD_EXPAND_MORE;
|
||||
|
||||
Reference in New Issue
Block a user