feat(ui): implement UI/UX audit — i18n, HiDPI, theme, destructive-action & overflow fixes
Implements all 26 confirmed UI/UX audit findings plus the 4 dashboard/timeline tile labels. Verified across full-node + Lite + Windows builds (ctest green) and an adversarial diff review (one market-tab delete regression caught + fixed). i18n coverage: - Balance hero/quick-actions/toasts + dashboard & timeline tiles (Total Balance, Shielded, Transparent, Quick Send, Quick Receive, Click to open, Market) - Send: Review Send / Cancel / Paste / view-only tooltip / memo byte counter - Settings: Lite lifecycle errors, plaintext-RPC security warning, 8 toasts - Mining pool-payout tooltip - Resolve daemon_update_title double-assignment collision (new daemon_update_prompt_title) - 23 new keys translated into all 8 locales; CJK subset font rebuilt HiDPI: DPI-scale the Send amount bar, mining thread-tile clamp bounds, receive loading skeleton, and sidebar notification-badge insets. Light theme: theme-aware material::SurfaceOverlay() for balance bar tracks and row-hover highlights; contacts active-pill foreground uses OnPrimary(). Destructive actions: arm/confirm for portfolio-group delete, saved pool/worker remove, and avatar-image delete. Interaction/overflow: route balance star/eye buttons and the console fold-toggle through popup-safe guards; clip peer addr/subver; truncate the balance custom label; measure Settings tool-button widths; mining stepper disabled-state feedback; +/- stepper buttons restyled to match the thread tiles. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -665,14 +665,26 @@ void RenderPeersTab(App* app)
|
||||
dl->AddCircleFilled(ImVec2(cx + S.drawElement("tabs.peers", "ping-dot-x-offset").size, cy + body2->LegacySize * 0.5f), pingDotR, dotCol);
|
||||
|
||||
float addrX = cx + S.drawElement("tabs.peers", "address-x-offset").size;
|
||||
// Reserve the line-1 trailing zone (ping + direction pill live on the far right,
|
||||
// the nearest being the ping at innerW - pingW - spacingXl*3). A long IPv6+port
|
||||
// addr must clip before it so it can't run under those. Mirror the ping formula
|
||||
// conservatively and leave a gap.
|
||||
float line1RightLimit = rowPos.x + innerW - Layout::spacingXl() * 3 -
|
||||
capFont->CalcTextSizeA(capFont->LegacySize, FLT_MAX, 0, "0000ms").x -
|
||||
Layout::spacingSm();
|
||||
dl->PushClipRect(ImVec2(addrX, rowPos.y), ImVec2(line1RightLimit, rowEnd.y), true);
|
||||
dl->AddText(body2, body2->LegacySize, ImVec2(addrX, cy), OnSurface(), peer.addr.c_str());
|
||||
dl->PopClipRect();
|
||||
|
||||
// Seed node icon — rendered right after the IP address
|
||||
// Seed node icon — rendered right after the IP address, but never past the
|
||||
// reserved trailing zone (a long addr would otherwise push it into the ping text).
|
||||
if (IsSeedNode(peer.addr)) {
|
||||
ImVec2 addrSz = body2->CalcTextSizeA(body2->LegacySize, FLT_MAX, 0, peer.addr.c_str());
|
||||
ImFont* iconFont = Type().iconSmall();
|
||||
float iconY = cy + (body2->LegacySize - iconFont->LegacySize) * 0.5f;
|
||||
dl->AddText(iconFont, iconFont->LegacySize, ImVec2(addrX + addrSz.x + Layout::spacingSm(), iconY), WithAlpha(Success(), 200), ICON_MD_GRASS);
|
||||
float seedIconX = std::min(addrX + addrSz.x + Layout::spacingSm(),
|
||||
line1RightLimit - iconFont->LegacySize);
|
||||
dl->AddText(iconFont, iconFont->LegacySize, ImVec2(seedIconX, iconY), WithAlpha(Success(), 200), ICON_MD_GRASS);
|
||||
}
|
||||
|
||||
{
|
||||
@@ -696,30 +708,55 @@ void RenderPeersTab(App* app)
|
||||
}
|
||||
|
||||
float cy2 = cy + body2->LegacySize + Layout::spacingXs();
|
||||
dl->AddText(capFont, capFont->LegacySize, ImVec2(cx + S.drawElement("tabs.peers", "address-x-offset").size, cy2),
|
||||
OnSurfaceDisabled(), peer.subver.c_str());
|
||||
float subverX = cx + S.drawElement("tabs.peers", "address-x-offset").size;
|
||||
|
||||
// Reserve the line-2 trailing widths up front so an untrusted, arbitrarily long
|
||||
// remote subver cannot push the TLS badge / ban-score off the row. Order right→left:
|
||||
// ban-score pinned far right, then the TLS/no-TLS badge, then the clipped subver text.
|
||||
float line2RightLimit = rowPos.x + innerW - Layout::spacingLg();
|
||||
float banScoreLeftX = line2RightLimit;
|
||||
char banBuf[16];
|
||||
ImU32 banCol = 0;
|
||||
bool haveBanScore = peer.banscore > 0;
|
||||
if (haveBanScore) {
|
||||
snprintf(banBuf, sizeof(banBuf), TR("peers_ban_score"), peer.banscore);
|
||||
banCol = peer.banscore > 50 ? Error() : Warning();
|
||||
float banW = capFont->CalcTextSizeA(capFont->LegacySize, FLT_MAX, 0, banBuf).x;
|
||||
banScoreLeftX = line2RightLimit - banW;
|
||||
// The TLS/no-TLS badge sits to the LEFT of the ban score with a gap.
|
||||
line2RightLimit = banScoreLeftX - Layout::spacingLg();
|
||||
}
|
||||
|
||||
float tlsBadgeW = std::max(S.drawElement("tabs.peers", "tls-badge-min-width").size, S.drawElement("tabs.peers", "tls-badge-width").size * hs);
|
||||
float noTlsW = capFont->CalcTextSizeA(capFont->LegacySize, FLT_MAX, 0, TR("peers_no_tls")).x;
|
||||
float badgeW = peer.tls_cipher.empty() ? noTlsW : tlsBadgeW;
|
||||
// Hard right boundary for the subver text column, leaving room for the badge.
|
||||
float subverMaxX = line2RightLimit - badgeW - Layout::spacingSm();
|
||||
if (subverMaxX < subverX) subverMaxX = subverX;
|
||||
|
||||
float verW = capFont->CalcTextSizeA(capFont->LegacySize, FLT_MAX, 0, peer.subver.c_str()).x;
|
||||
float tlsBadgeW = std::max(S.drawElement("tabs.peers", "tls-badge-min-width").size, S.drawElement("tabs.peers", "tls-badge-width").size * hs);
|
||||
dl->PushClipRect(ImVec2(subverX, rowPos.y), ImVec2(subverMaxX, rowEnd.y), true);
|
||||
dl->AddText(capFont, capFont->LegacySize, ImVec2(subverX, cy2),
|
||||
OnSurfaceDisabled(), peer.subver.c_str());
|
||||
dl->PopClipRect();
|
||||
|
||||
// Pin the badge just after the (clipped) subver, but never past the reserved zone.
|
||||
float badgeX = std::min(subverX + verW + Layout::spacingSm(), subverMaxX + Layout::spacingSm());
|
||||
if (!peer.tls_cipher.empty()) {
|
||||
ImU32 tlsBg = WithAlpha(Success(), 25);
|
||||
ImU32 tlsFg = WithAlpha(Success(), 200);
|
||||
ImVec2 tlsMin(cx + S.drawElement("tabs.peers", "address-x-offset").size + verW + Layout::spacingSm(), cy2);
|
||||
ImVec2 tlsMin(badgeX, cy2);
|
||||
ImVec2 tlsMax(tlsMin.x + tlsBadgeW, tlsMin.y + capFont->LegacySize + 2);
|
||||
dl->AddRectFilled(tlsMin, tlsMax, tlsBg, S.drawElement("tabs.peers", "tls-badge-rounding").size);
|
||||
dl->AddText(capFont, capFont->LegacySize, ImVec2(tlsMin.x + 4, cy2 + 1), tlsFg, "TLS");
|
||||
} else {
|
||||
dl->AddText(capFont, capFont->LegacySize, ImVec2(cx + S.drawElement("tabs.peers", "address-x-offset").size + verW + Layout::spacingSm(), cy2),
|
||||
dl->AddText(capFont, capFont->LegacySize, ImVec2(badgeX, cy2),
|
||||
WithAlpha(Error(), 140), TR("peers_no_tls"));
|
||||
}
|
||||
|
||||
if (peer.banscore > 0) {
|
||||
char banBuf[16];
|
||||
snprintf(banBuf, sizeof(banBuf), TR("peers_ban_score"), peer.banscore);
|
||||
ImU32 banCol = peer.banscore > 50 ? Error() : Warning();
|
||||
ImVec2 banSz = capFont->CalcTextSizeA(capFont->LegacySize, FLT_MAX, 0, banBuf);
|
||||
if (haveBanScore) {
|
||||
dl->AddText(capFont, capFont->LegacySize,
|
||||
ImVec2(rowPos.x + innerW - banSz.x - Layout::spacingLg(), cy2), banCol, banBuf);
|
||||
ImVec2(banScoreLeftX, cy2), banCol, banBuf);
|
||||
}
|
||||
|
||||
ImGui::InvisibleButton("##peerRow", ImVec2(innerW, rowH));
|
||||
|
||||
Reference in New Issue
Block a user