From a60a2f8e39e9ef7c57831172f9a6dc05bcfbd97f Mon Sep 17 00:00:00 2001 From: DanS Date: Tue, 1 Sep 2026 22:54:36 -0500 Subject: [PATCH] fix(ui): stop sidebar badges from displacing button text MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The Chat/History nav buttons centered their icon+label in a region that shrank when a badge (unread count / mining dot) was present, and the "has badge" test read the LIVE count — so the text jumped sideways the moment a count toggled (e.g. a new chat message arrived). Reserve badge clearance by whether the page CAN show a badge (constant per item), center in the full button width regardless, and cap the label with symmetric clearance so a long label still can't run under the corner badge. Co-Authored-By: Claude Opus 4.8 (1M context) --- src/ui/sidebar.h | 26 ++++++++++++++++---------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/src/ui/sidebar.h b/src/ui/sidebar.h index e5b81ca..758c6fc 100644 --- a/src/ui/sidebar.h +++ b/src/ui/sidebar.h @@ -679,15 +679,18 @@ inline bool RenderSidebar(NavPage& current, float sidebarWidth, float contentHei ImU32 textCol = selected ? Primary() : (pageNeedsUnlock ? OnSurfaceDisabled() : OnSurfaceMedium()); if (showLabels) { - // Reserve room for a badge (if this item will draw one) so the - // label centers in the space to the left of it instead of - // running underneath the badge circle. - bool itemHasBadge = - (item.page == NavPage::History && status.unconfirmedTxCount > 0) || - (item.page == NavPage::Mining && status.miningActive) || - (item.page == NavPage::Chat && status.chatUnreadCount > 0); + // The badge is a fixed top-right corner overlay, so it must NOT move + // the icon+label — otherwise the text jumps sideways the moment a live + // count toggles the badge on/off. Reserve clearance from whether the + // page CAN show a badge (constant per item), never from the current + // count, and keep the icon+label centered in the FULL button width so + // the text position and size stay identical with or without a badge. + bool itemBadgeCapable = + item.page == NavPage::History || + item.page == NavPage::Mining || + item.page == NavPage::Chat; float badgeReserve = 0.0f; - if (itemHasBadge) { + if (itemBadgeCapable) { bool dotOnlyReserve = (item.page == NavPage::Mining); float badgeRReserve = dotOnlyReserve ? badgeRadiusDot : badgeRadiusNumber; float badgeInsetXReserve = sde("badge-inset-x", 6.0f); @@ -697,14 +700,17 @@ inline bool RenderSidebar(NavPage& current, float sidebarWidth, float contentHei ImFont* font = selected ? Type().subtitle2() : Type().body2(); float lblFsz = ScaledFontSize(font); float btnW = indMax.x - indMin.x; - float maxLabelW = btnW - iconS * 2.0f - iconLabelGap - Layout::spacingXs() * 2 - badgeReserve; + // Clearance is symmetric (2x) because the group stays centered in the + // full width: reserving on both sides keeps the label's right edge clear + // of the right-side corner badge without shifting the center off-axis. + float maxLabelW = btnW - iconS * 2.0f - iconLabelGap - Layout::spacingXs() * 2 - badgeReserve * 2.0f; ImVec2 labelSz = font->CalcTextSizeA(lblFsz, 1000.0f, 0.0f, NavLabel(item)); if (labelSz.x > maxLabelW && maxLabelW > 0) { lblFsz *= maxLabelW / labelSz.x; labelSz = font->CalcTextSizeA(lblFsz, 1000.0f, 0.0f, NavLabel(item)); } float totalW = iconS * 2.0f + iconLabelGap + labelSz.x; - float btnCX = (indMin.x + indMax.x - badgeReserve) * 0.5f; + float btnCX = (indMin.x + indMax.x) * 0.5f; float startX = btnCX - totalW * 0.5f; DrawNavIcon(dl, item.page, startX + iconS, iconCY, iconS, textCol);