diff --git a/res/lang/de.json b/res/lang/de.json index 8f9f4fa..8d643cc 100644 --- a/res/lang/de.json +++ b/res/lang/de.json @@ -1672,6 +1672,7 @@ "validate_not_mine": "Nicht im Besitz dieser Wallet", "validate_ownership": "Eigentum:", "validate_results": "Ergebnisse:", + "validate_results_placeholder": "Ergebnisse erscheinen hier", "validate_shielded_type": "Abgeschirmt (z-Adresse)", "validate_status": "Status:", "validate_title": "Adresse validieren", @@ -1830,4 +1831,4 @@ "your_addresses": "Ihre Adressen", "z_address": "Z-Adresse", "z_addresses": "Z-Adressen" -} +} \ No newline at end of file diff --git a/res/lang/es.json b/res/lang/es.json index f9ef36f..848663e 100644 --- a/res/lang/es.json +++ b/res/lang/es.json @@ -1672,6 +1672,7 @@ "validate_not_mine": "No es propiedad de esta cartera", "validate_ownership": "Propiedad:", "validate_results": "Resultados:", + "validate_results_placeholder": "Los resultados aparecerán aquí", "validate_shielded_type": "Protegida (dirección z)", "validate_status": "Estado:", "validate_title": "Validar Dirección", @@ -1830,4 +1831,4 @@ "your_addresses": "Sus Direcciones", "z_address": "Dirección Z", "z_addresses": "Direcciones Z" -} +} \ No newline at end of file diff --git a/res/lang/fr.json b/res/lang/fr.json index 2dbf5b3..e25216a 100644 --- a/res/lang/fr.json +++ b/res/lang/fr.json @@ -1672,6 +1672,7 @@ "validate_not_mine": "N'appartient pas à ce portefeuille", "validate_ownership": "Propriété :", "validate_results": "Résultats :", + "validate_results_placeholder": "Les résultats apparaîtront ici", "validate_shielded_type": "Blindée (z-adresse)", "validate_status": "Statut :", "validate_title": "Valider l'adresse", @@ -1830,4 +1831,4 @@ "your_addresses": "Vos adresses", "z_address": "Adresse Z", "z_addresses": "Adresses Z" -} +} \ No newline at end of file diff --git a/res/lang/ja.json b/res/lang/ja.json index d5c42c3..7588451 100644 --- a/res/lang/ja.json +++ b/res/lang/ja.json @@ -1669,6 +1669,7 @@ "validate_not_mine": "このウォレットに属していません", "validate_ownership": "所有者:", "validate_results": "結果:", + "validate_results_placeholder": "ここに結果が表示されます", "validate_shielded_type": "シールド(zアドレス)", "validate_status": "ステータス:", "validate_title": "アドレスを検証", @@ -1827,4 +1828,4 @@ "your_addresses": "あなたのアドレス", "z_address": "Zアドレス", "z_addresses": "Zアドレス" -} +} \ No newline at end of file diff --git a/res/lang/ko.json b/res/lang/ko.json index a015d8d..d95957e 100644 --- a/res/lang/ko.json +++ b/res/lang/ko.json @@ -1671,6 +1671,7 @@ "validate_not_mine": "이 지갑에 속하지 않음", "validate_ownership": "소유자:", "validate_results": "결과:", + "validate_results_placeholder": "결과가 여기에 표시됩니다", "validate_shielded_type": "차폐 (z 주소)", "validate_status": "상태:", "validate_title": "주소 검증", @@ -1829,4 +1830,4 @@ "your_addresses": "내 주소", "z_address": "Z 주소", "z_addresses": "Z 주소" -} +} \ No newline at end of file diff --git a/res/lang/pt.json b/res/lang/pt.json index 97a95e5..8b97cdf 100644 --- a/res/lang/pt.json +++ b/res/lang/pt.json @@ -1672,6 +1672,7 @@ "validate_not_mine": "Não pertence a esta carteira", "validate_ownership": "Propriedade:", "validate_results": "Resultados:", + "validate_results_placeholder": "Os resultados aparecerão aqui", "validate_shielded_type": "Blindado (z-endereço)", "validate_status": "Status:", "validate_title": "Validar Endereço", @@ -1830,4 +1831,4 @@ "your_addresses": "Seus Endereços", "z_address": "Endereço Z", "z_addresses": "Endereços Z" -} +} \ No newline at end of file diff --git a/res/lang/ru.json b/res/lang/ru.json index deef9a3..b4defbe 100644 --- a/res/lang/ru.json +++ b/res/lang/ru.json @@ -1672,6 +1672,7 @@ "validate_not_mine": "Не принадлежит этому кошельку", "validate_ownership": "Принадлежность:", "validate_results": "Результаты:", + "validate_results_placeholder": "Результаты появятся здесь", "validate_shielded_type": "Экранированный (z-адрес)", "validate_status": "Статус:", "validate_title": "Проверить адрес", @@ -1830,4 +1831,4 @@ "your_addresses": "Ваши адреса", "z_address": "Z-адрес", "z_addresses": "Z-адреса" -} +} \ No newline at end of file diff --git a/res/lang/zh.json b/res/lang/zh.json index 461352f..615a6c5 100644 --- a/res/lang/zh.json +++ b/res/lang/zh.json @@ -1670,6 +1670,7 @@ "validate_not_mine": "不属于此钱包", "validate_ownership": "所有权:", "validate_results": "结果:", + "validate_results_placeholder": "结果将显示在此处", "validate_shielded_type": "屏蔽(z 地址)", "validate_status": "状态:", "validate_title": "验证地址", @@ -1828,4 +1829,4 @@ "your_addresses": "您的地址", "z_address": "Z 地址", "z_addresses": "Z 地址" -} +} \ No newline at end of file diff --git a/src/app.cpp b/src/app.cpp index 7a73ec2..9de781f 100644 --- a/src/app.cpp +++ b/src/app.cpp @@ -4174,7 +4174,12 @@ void App::renderSeedMigrationDialog() case SeedMigrationStep::ShowSeed: { ui::material::DialogWarningHeader(TR("mig_seed_warning")); ImGui::Spacing(); - ui::RenderSeedWordGrid(ui::SplitSeedWords(seed_migration_seed_)); + // Box the 24-word grid in a glass panel so the wallet's most critical secret reads as a + // distinct artifact, not body copy. Purely visual — the words/derivation/logic are untouched. + { + ui::material::GlassSectionScope seedPanel; + ui::RenderSeedWordGrid(ui::SplitSeedWords(seed_migration_seed_)); + } ImGui::Spacing(); ImGui::TextColored(kMedium, "%s", TR("mig_receive_addr")); ImGui::TextWrapped("%s", seed_migration_dest_.c_str()); diff --git a/src/app_security.cpp b/src/app_security.cpp index 36be4c7..07731ea 100644 --- a/src/app_security.cpp +++ b/src/app_security.cpp @@ -1450,6 +1450,11 @@ void App::renderEncryptWalletDialog() { ov.cardWidth = 460.0f; ov.idSuffix = "changepass"; if (BeginOverlayDialog(ov)) { + // Same fund-loss consequence as Encrypt/Remove Encryption if the new + // passphrase is lost — reuse their warning string/header for consistency. + DialogWarningHeader(TR("wiz_encrypt_warning")); + ImGui::Spacing(); + ImGui::TextUnformatted(TR("change_pass_current")); ImGui::PushItemWidth(-1); ImGui::InputText("##chg_old", change_old_pass_buf_, sizeof(change_old_pass_buf_), diff --git a/src/app_wizard.cpp b/src/app_wizard.cpp index 398964b..2b30807 100644 --- a/src/app_wizard.cpp +++ b/src/app_wizard.cpp @@ -294,29 +294,43 @@ void App::renderFirstRunWizard() { { int state = cardState(0); bool isFocused = (state == 1); + bool isCollapsed = (state == 2); // Completed: minimize to a compact pill (mirrors Card 1) float cx = leftX + cardPad; float cy = card0Top + cardPad; float contentW = colW - 2 * cardPad; - // Step indicator - { + // Step indicator + title (inline when collapsed) + if (isCollapsed) { + // Compact single-line: check icon + "Step 1" + "Appearance" float iconW = iconFont->CalcTextSizeA(iconFont->LegacySize, FLT_MAX, 0, stepIcon(state)).x; dl->AddText(iconFont, iconFont->LegacySize, ImVec2(cx, cy), dimCol, stepIcon(state)); - dl->AddText(captionFont, captionFont->LegacySize, ImVec2(cx + iconW + 4.0f * dp, cy), dimCol, TR("wiz_step1")); - cy += captionFont->LegacySize + 6.0f * dp; - } + float labelX = cx + iconW + 4.0f * dp; + dl->AddText(captionFont, captionFont->LegacySize, ImVec2(labelX, cy), dimCol, TR("wiz_step1")); + float step1W = captionFont->CalcTextSizeA(captionFont->LegacySize, FLT_MAX, 0, TR("wiz_step1")).x; + float titleX = labelX + step1W + 12.0f * dp; + dl->AddText(bodyFont, bodyFont->LegacySize, ImVec2(titleX, cy), dimCol, TR("wiz_appearance")); + cy += captionFont->LegacySize + 4.0f * dp; + } else { + // Step indicator + { + float iconW = iconFont->CalcTextSizeA(iconFont->LegacySize, FLT_MAX, 0, stepIcon(state)).x; + dl->AddText(iconFont, iconFont->LegacySize, ImVec2(cx, cy), dimCol, stepIcon(state)); + dl->AddText(captionFont, captionFont->LegacySize, ImVec2(cx + iconW + 4.0f * dp, cy), dimCol, TR("wiz_step1")); + cy += captionFont->LegacySize + 6.0f * dp; + } - // Title - { - const char* t = TR("wiz_appearance"); - dl->AddText(titleFont, titleFont->LegacySize, ImVec2(cx, cy), textCol, t); - cy += titleFont->LegacySize + 10.0f * dp; - } + // Title + { + const char* t = TR("wiz_appearance"); + dl->AddText(titleFont, titleFont->LegacySize, ImVec2(cx, cy), textCol, t); + cy += titleFont->LegacySize + 10.0f * dp; + } - // Separator - dl->AddLine(ImVec2(cx, cy), ImVec2(cx + contentW, cy), - (textCol & 0x00FFFFFF) | IM_COL32(0,0,0,40), 1.0f * dp); - cy += 14.0f * dp; + // Separator + dl->AddLine(ImVec2(cx, cy), ImVec2(cx + contentW, cy), + (textCol & 0x00FFFFFF) | IM_COL32(0,0,0,40), 1.0f * dp); + cy += 14.0f * dp; + } float& wiz_blur_amount = wizardUi.blur_amount; bool& wiz_theme_effects = wizardUi.theme_effects; @@ -352,6 +366,9 @@ void App::renderFirstRunWizard() { wiz_appearance_init = true; } + // Controls: rendered for the focused and upcoming states so content is visible under + // the dim overlay; skipped entirely once completed so the card shrinks to a compact pill. + if (!isCollapsed) { // Render controls always so content is visible under the dim // overlay when not focused; disable interaction when not active. ImGui::BeginDisabled(!isFocused); @@ -668,13 +685,21 @@ void App::renderFirstRunWizard() { cy += btnH; } - cy += cardPad; - // Lock card height to the tallest content ever seen - float& card0MaxH = wizardUi.card0_max_h; - card0MaxH = std::max(card0MaxH, cy - card0Top); - card0Bot = card0Top + card0MaxH; + } // if (!isCollapsed) - // Card 0 finalization deferred until after cards 1+2 are sized + cy += cardPad; + // Lock card height to the tallest content ever seen (but not when collapsed) + float& card0MaxH = wizardUi.card0_max_h; + if (isCollapsed) { + // Completed: finalize immediately as a compact pill (do not stretch to the + // right column height, and skip the deferred stretch below). + card0Bot = card0Top + (cy - card0Top); + finalizeCard(leftX, colW, card0Top, card0Bot, state); + } else { + card0MaxH = std::max(card0MaxH, cy - card0Top); + card0Bot = card0Top + card0MaxH; + // Card 0 finalization deferred until after cards 1+2 are sized + } } @@ -1447,7 +1472,9 @@ void App::renderFirstRunWizard() { } // --- Deferred Card 0 finalization: match right column total height --- - { + // Only for the focused/upcoming Appearance card; a completed one was already finalized + // above as a compact pill and must not be re-stretched. + if (cardState(0) != 2) { float rightColBot = card2Bot; if (rightColBot > card0Bot) card0Bot = rightColBot; finalizeCard(leftX, colW, card0Top, card0Bot, cardState(0)); diff --git a/src/ui/material/draw_helpers.h b/src/ui/material/draw_helpers.h index 697729f..3d8dbf1 100644 --- a/src/ui/material/draw_helpers.h +++ b/src/ui/material/draw_helpers.h @@ -1640,7 +1640,10 @@ inline bool BeginOverlayDialog(const OverlayDialogSpec& spec) // the measuring frame (its geometry is a placeholder; the whole card is hidden until centered). if (!floating && !hideForMeasure) { GlassPanelSpec cardGlass; - cardGlass.rounding = 16.0f; cardGlass.fillAlpha = 35; cardGlass.borderAlpha = 50; cardGlass.borderWidth = 1.0f; + // Fill/border alpha govern every overlay dialog's card boundary — kept well above the default + // glass panel so the card reads as a distinct surface over busy backdrops (tx lists, mining + // tiles, chat) while staying translucent rather than opaque. + cardGlass.rounding = 16.0f; cardGlass.fillAlpha = 60; cardGlass.borderAlpha = 90; cardGlass.borderWidth = 1.0f; DrawGlassPanel(dl, cardMin, cardMax, cardGlass); } diff --git a/src/ui/windows/address_transfer_dialog.h b/src/ui/windows/address_transfer_dialog.h index c6baad1..ede349d 100644 --- a/src/ui/windows/address_transfer_dialog.h +++ b/src/ui/windows/address_transfer_dialog.h @@ -169,9 +169,9 @@ public: } if (amountValid && newFromBal < 1e-9) { ImGui::Spacing(); - ImGui::PushStyleColor(ImGuiCol_Text, ImGui::ColorConvertU32ToFloat4(Warning())); - ImGui::TextWrapped("%s", TR("sends_full_balance_warning")); - ImGui::PopStyleColor(); + // Full-balance send: same warning-icon treatment as the de-shielding header above, + // so this stakes-bearing line reads as distinct from the neutral preview text. + DialogWarningHeader(TR("sends_full_balance_warning")); } // Buttons diff --git a/src/ui/windows/contacts_tab.cpp b/src/ui/windows/contacts_tab.cpp index 79f2206..43d9770 100644 --- a/src/ui/windows/contacts_tab.cpp +++ b/src/ui/windows/contacts_tab.cpp @@ -207,6 +207,28 @@ static bool isShieldedAddr(const std::string& a) { return !a.empty() && a[0] == 'z'; } +// Width-aware middle-ellipsis truncation (mirrors the add/edit dialog's local fitMiddle lambda, but +// file-scope so the Cards/List rows can share it too): keeps head + tail, shrinking symmetrically +// until the rendered width fits maxW. minFront/minBack are the schema-configured floor — below that +// the fixed-length util::truncateMiddle result is used instead, so very cramped rows still read as +// "front...back" rather than collapsing to a near-empty stub. Addresses are ASCII, so byte-wise +// trimming is safe. +static std::string truncateAddressToWidth(const std::string& s, ImFont* f, float size, float maxW, + int minFront, int minBack) { + const std::string floor = util::truncateMiddle(s, minFront, minBack); + auto w = [&](const std::string& t){ return f->CalcTextSizeA(size, FLT_MAX, 0, t.c_str()).x; }; + if (w(s) <= maxW) return s; // fits in full — no truncation needed at all + if (maxW <= 0.0f) return floor; // no room to measure against — fall back to the floor + const std::string ell = "\xE2\x80\xA6"; + size_t head = s.size() / 2, tail = s.size() - head; + while (head + tail > static_cast(minFront + minBack)) { + std::string cand = s.substr(0, head) + ell + s.substr(s.size() - tail); + if (w(cand) <= maxW) return cand; + if (head >= tail) --head; else --tail; + } + return floor; // couldn't fit even at the configured floor — use the fixed-length result +} + // Accent colour for a contact's address type (Z = shielded/green, T = transparent/amber), tuned per // theme. File-scope so both the list rows and the edit-dialog preview share one source of truth. static ImU32 contactTypeColor(bool shielded, bool light) { @@ -1219,10 +1241,13 @@ void RenderContactsTab(App* app) dl->AddText(lblF, lblSz, ImVec2(tx, ty), material::OnSurface(), entry.label.c_str()); dl->PopClipRect(); // Un-collapse to the full address on hover (clipped to the text column so it never - // runs under the trailing actions); middle-truncated otherwise. + // runs under the trailing actions); otherwise middle-truncated to FIT the actual text + // column width (tx..textMaxX) rather than a fixed char count — wide rows show more of + // the address instead of leaving a dead gap before the action-icon cluster. std::string addr = rowHovered ? entry.address - : util::truncateMiddle(entry.address, addrFrontLbl.truncate, addrBackLbl.truncate); + : truncateAddressToWidth(entry.address, adrF, adrSz, textMaxX - tx, + addrFrontLbl.truncate, addrBackLbl.truncate); dl->PushClipRect(ImVec2(tx, mn.y), ImVec2(textMaxX, mx.y), true); dl->AddText(adrF, adrSz, ImVec2(tx, ty + lblSz + 3.0f * dp), material::OnSurfaceMedium(), addr.c_str()); diff --git a/src/ui/windows/explorer_tab.cpp b/src/ui/windows/explorer_tab.cpp index efe4c29..266fb1c 100644 --- a/src/ui/windows/explorer_tab.cpp +++ b/src/ui/windows/explorer_tab.cpp @@ -620,7 +620,16 @@ static void renderChainStats(App* app, float availWidth) { ImVec2(cardMin.x + pad, cardMin.y + pad * 0.5f), Primary(), TR("explorer_chain_stats")); drawStatusPill(cardMin, cardW); - float labelY = cardMin.y + pad * 0.5f + headerH + Layout::spacingLg(); + // Distribute the two stat blocks (Height, Best Block) evenly through the + // content region so the card matches the density of the sibling 2x2 metric + // grid instead of pinning Height to the top and Best Block to the bottom + // edge with a large empty gap between them. + float contentTop = cardMin.y + pad * 0.5f + headerH; + float contentBottom = cardMax.y - pad; + float blockGap = std::max(Layout::spacingMd(), + (contentBottom - contentTop - heroLineH - hashLineH) / 3.0f); + + float labelY = contentTop + blockGap; dl->AddText(capFont, capFont->LegacySize, ImVec2(cardMin.x + pad, labelY), OnSurfaceMedium(), TR("explorer_block_height")); @@ -640,7 +649,7 @@ static void renderChainStats(App* app, float availWidth) { ImVec2(cardMin.x + pad + barW * progress, barY + barH), WithAlpha(Warning(), 180), barH * 0.5f); } - float hashLabelY = cardMax.y - pad - hashLineH; + float hashLabelY = labelY + heroLineH + blockGap; dl->AddText(capFont, capFont->LegacySize, ImVec2(cardMin.x + pad, hashLabelY), OnSurfaceMedium(), TR("peers_best_block")); diff --git a/src/ui/windows/market_tab.cpp b/src/ui/windows/market_tab.cpp index d064ae0..2a5008b 100644 --- a/src/ui/windows/market_tab.cpp +++ b/src/ui/windows/market_tab.cpp @@ -2136,11 +2136,29 @@ static void mktDrawPortfolio(const MktCtx& cx) float rowGap = pfRowGapFor(style, mktDp); float rowsH = std::max(rowH, portfolioH - pfSummaryH); + // Anchor the bottom of the portfolio: the group list otherwise ends at its content height, + // leaving un-anchored dead space between the last row and the content-area bottom. Grow the + // rows region to fill the remaining vertical space (down to the scroll area's bottom, less a + // small margin) and wrap it in a contained glass panel — so it reads as one framed table with + // a real bottom edge (like Explorer's block-list card) instead of trailing off into nothing. + // The empty state then centres in this full-height panel rather than in a one-row band. ImGui::SetCursorScreenPos(ImVec2(cardMin.x, cardMin.y + pfSummaryH)); + float floorH = ImGui::GetContentRegionAvail().y - gap; // to the scroll bottom, keep a margin + float panelH = std::max(rowsH, floorH); + { + ImVec2 pMin(cardMin.x, cardMin.y + pfSummaryH); + ImVec2 pMax(rightEdge, pMin.y + panelH); + GlassPanelSpec pg; + pg.rounding = Layout::glassRounding(); + pg.fillAlpha = 12; pg.borderAlpha = 24; // faint container: frames without competing with the per-row cards + DrawGlassPanel(dl, pMin, pMax, pg); + } // Flush-left child (no window padding) so rows align with the summary; a scrollbar appears - // only when the visible groups overflow the bounded height. + // only when the visible groups overflow the bounded height. NoBackground so the contained + // glass panel drawn above is the sole surface — otherwise the opaque ChildBg (non-acrylic + // mode) would paint over the panel fill + border. ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0, 0)); - ImGui::BeginChild("##pfRows", ImVec2(availWidth, rowsH), false); + ImGui::BeginChild("##pfRows", ImVec2(availWidth, panelH), false, ImGuiWindowFlags_NoBackground); ImGui::PopStyleVar(); // Zero item-spacing INSIDE the child: each row emits an InvisibleButton + a gap Dummy, and // ImGui's default ItemSpacing.y between those items would inflate the content past rowsH and diff --git a/src/ui/windows/receive_tab.cpp b/src/ui/windows/receive_tab.cpp index 21dc2a7..3f209a1 100644 --- a/src/ui/windows/receive_tab.cpp +++ b/src/ui/windows/receive_tab.cpp @@ -362,9 +362,14 @@ static void RenderRecentReceived(const AddressInfo& /* addr */, if (tx.type != "receive" && tx.type != "mined") continue; recvs.push_back(&tx); } - if (recvs.size() > 4) recvs.resize(4); // show only the 4 most recent (newest-first) + // Grow the list to fill the dead space beneath the (fixed-height) receive card: + // fit as many newest-first rows as the remaining region can show, instead of a + // fixed 4. The child scrolls if the real history exceeds what fits, so nothing is + // lost. A floor of 4 keeps the section substantial when the region is short. float listH = std::max(rowH, ImGui::GetContentRegionAvail().y); + size_t maxRows = std::max(4, (size_t)std::floor(listH / rowH)); + if (recvs.size() > maxRows) recvs.resize(maxRows); // newest-first ImGui::BeginChild("##RecentReceivedRows", ImVec2(width, listH), false, ImGuiWindowFlags_NoBackground); @@ -372,8 +377,9 @@ static void RenderRecentReceived(const AddressInfo& /* addr */, char buf[64]; if (recvs.empty()) { - ImGui::SetCursorPosY(Layout::spacingMd()); - Type().textColored(TypeStyle::Caption, OnSurfaceDisabled(), TR("no_recent_receives")); + // Fill the empty canvas with a centered material empty-state (icon + title) + // rather than a lone left-aligned caption stranded at the top of dead space. + material::DrawEmptyState(ICON_MD_CALL_RECEIVED, TR("no_recent_receives")); ImGui::EndChild(); return; } diff --git a/src/ui/windows/send_tab.cpp b/src/ui/windows/send_tab.cpp index 834d48a..3d30188 100644 --- a/src/ui/windows/send_tab.cpp +++ b/src/ui/windows/send_tab.cpp @@ -1123,9 +1123,14 @@ static void RenderRecentSends(const WalletState& state, float width, ImFont* cap if (tx.type != "send" && tx.type != "shield") continue; sends.push_back(&tx); } - if (sends.size() > 4) sends.resize(4); // show only the 4 most recent (newest-first) + // Grow the list to fill the dead space beneath the (fixed-height) compose card: + // fit as many newest-first rows as the remaining region can show, instead of a + // fixed 4. The child scrolls if the real history exceeds what fits, so nothing is + // lost. A floor of 4 keeps the section substantial when the region is short. float listH = std::max(rowH, ImGui::GetContentRegionAvail().y); + size_t maxRows = std::max(4, (size_t)std::floor(listH / rowH)); + if (sends.size() > maxRows) sends.resize(maxRows); // newest-first ImGui::BeginChild("##RecentSendRows", ImVec2(width, listH), false, ImGuiWindowFlags_NoBackground); @@ -1133,8 +1138,9 @@ static void RenderRecentSends(const WalletState& state, float width, ImFont* cap char buf[64]; if (sends.empty()) { - ImGui::SetCursorPosY(Layout::spacingMd()); - Type().textColored(TypeStyle::Caption, OnSurfaceDisabled(), TR("send_no_recent")); + // Fill the empty canvas with a centered material empty-state (icon + title) + // rather than a lone left-aligned caption stranded at the top of dead space. + material::DrawEmptyState(ICON_MD_CALL_MADE, TR("send_no_recent")); ImGui::EndChild(); return; } diff --git a/src/ui/windows/shield_dialog.cpp b/src/ui/windows/shield_dialog.cpp index e3477f8..ae9b979 100644 --- a/src/ui/windows/shield_dialog.cpp +++ b/src/ui/windows/shield_dialog.cpp @@ -146,7 +146,10 @@ void ShieldDialog::render(App* app) ImGui::Spacing(); - // Fee + // Fee + UTXO limit share one row (two columns) to tighten vertical rhythm. + float pairColX = ImGui::GetContentRegionAvail().x * 0.5f; + + // Fee (left column) ImGui::Text("%s", TR("fee_label")); ImGui::SetNextItemWidth(feeInput.width * Layout::dpiScale()); ImGui::InputDouble("##Fee", &s_fee, 0.0001, 0.001, "%.8f"); @@ -154,17 +157,19 @@ void ShieldDialog::render(App* app) if (s_fee > 1.0) s_fee = 1.0; // guard a fat-fingered huge fee (mirrors utxo clamp) ImGui::SameLine(); ImGui::TextDisabled("DRGX"); - - ImGui::Spacing(); - // UTXO limit + // UTXO limit (right column) — hint drops under the input (rather than beside it) since + // "Max UTXOs per operation" is too long to share the narrower half-width column with "DRGX". + ImGui::SameLine(pairColX); + ImGui::BeginGroup(); ImGui::Text("%s", TR("shield_utxo_limit")); ImGui::SetNextItemWidth(utxoInput.width * Layout::dpiScale()); ImGui::InputInt("##Limit", &s_utxo_limit); - ImGui::SameLine(); - ImGui::TextDisabled("%s", TR("shield_max_utxos")); if (s_utxo_limit < 1) s_utxo_limit = 1; if (s_utxo_limit > 100) s_utxo_limit = 100; + material::Type().textColored(material::TypeStyle::Caption, material::OnSurfaceMedium(), + TR("shield_max_utxos")); + ImGui::EndGroup(); ImGui::Spacing(); diff --git a/src/ui/windows/validate_address_dialog.cpp b/src/ui/windows/validate_address_dialog.cpp index 7f7ad4a..c0228a2 100644 --- a/src/ui/windows/validate_address_dialog.cpp +++ b/src/ui/windows/validate_address_dialog.cpp @@ -208,6 +208,14 @@ void ValidateAddressDialog::render(App* app) } } else if (!app->isConnected()) { ImGui::TextColored(ImVec4(1.0f, 0.6f, 0.0f, 1.0f), "%s", TR("not_connected")); + } else { + // Pre-interaction placeholder: keeps the results area from reading as a large + // dead gap between the Validate/Paste row and the Close button until a result exists. + ImGui::Spacing(); + float cw = ImGui::GetContentRegionAvail().x; + ImVec2 ts = ImGui::CalcTextSize(TR("validate_results_placeholder")); + ImGui::SetCursorPosX(ImGui::GetCursorPosX() + (cw - ts.x) * 0.5f); + ImGui::TextDisabled("%s", TR("validate_results_placeholder")); } // Close button at bottom — centered via the shared footer helper (no separator, matching diff --git a/src/ui/windows/wallets_dialog.h b/src/ui/windows/wallets_dialog.h index 7d0ffd4..a5a66b7 100644 --- a/src/ui/windows/wallets_dialog.h +++ b/src/ui/windows/wallets_dialog.h @@ -71,9 +71,12 @@ public: const int kMaxVisibleRows = 7; ImFont* nameFont = Type().subtitle1(); ImFont* metaFont = Type().caption(); - // The list is always sized to its MAX height (kMaxVisibleRows) for a consistent modal size — it - // does not shrink to fit a few wallets; fewer rows leave empty space, more than 7 scroll. - const int visRows = kMaxVisibleRows; + // Size the list to the ACTUAL wallet count so a few wallets sit tight against the controls below + // instead of stranding ~200px of empty rows before the create/scan prompts; cap at kMaxVisibleRows + // (more than that scrolls within the cap). An empty list keeps two rows of height so the centered + // empty-state hint (drawn inside the list child) still has room to show. + const int numWallets = (int)s_rows.size(); + const int visRows = numWallets <= 0 ? 2 : std::min(numWallets, kMaxVisibleRows); const float cardPadY = Layout::spacingMd(); // roomier cards (was spacingSm) const float walRowH = cardPadY * 2.0f + nameFont->LegacySize + Layout::spacingSm() + metaFont->LegacySize; const float cardGap = Layout::spacingMd(); // more breathing room between wallet cards @@ -421,9 +424,10 @@ public: ImGui::Dummy(ImVec2(rowW, walRowH)); if (i + 1 < s_rows.size()) ImGui::Dummy(ImVec2(rowW, cardGap)); } - // Empty-state nudge: the list sits at a fixed max height, so a handful of wallets leave blank - // space below. When there's real room to spare, fill it with a subtle centered hint (a folder - // glyph + one line) instead of dead space; it's purely decorative — the actions live below. + // Empty-state nudge: the list is now sized to the actual wallet count, so a populated list has + // no room to spare and this draws nothing (the cards butt up against the controls below). It + // fires only when the list is EMPTY (the reserved rows leave room): a subtle centered hint (a + // folder glyph + one line) instead of a blank box; purely decorative — the actions live below. { const float remainY = ImGui::GetContentRegionAvail().y; if (remainY > walRowH * 1.6f) { diff --git a/src/util/i18n.cpp b/src/util/i18n.cpp index 641cab8..779269e 100644 --- a/src/util/i18n.cpp +++ b/src/util/i18n.cpp @@ -2242,6 +2242,7 @@ void I18n::loadBuiltinEnglish() strings_["validate_not_mine"] = "Not owned by this wallet"; strings_["validate_ownership"] = "Ownership:"; strings_["validate_results"] = "Results:"; + strings_["validate_results_placeholder"] = "Results will appear here"; strings_["validate_shielded_type"] = "Shielded (z-address)"; strings_["validate_status"] = "Status:"; strings_["validate_title"] = "Validate Address";