feat(sidebar): show unread counts as accent badges

The unread marker was a 4px dot in StatusDanger. Red reads as an error, and an
unread message is not one. It is now a count badge in AccentEmber, which is
what the theme preview in settings has always shown.

It has to be drawn outside the FontAwesome scope: that atlas carries no ASCII
digits, so the number would have come out blank inside it. The icon width is
still measured inside the scope and handed out.

Placement follows the mode. Expanded puts the badge right-aligned ahead of the
popout slot, where a three-digit count still fits; icon-only keeps it over the
icon like the old dot.

The frozen vertical offsets go at the same time. The 8f was (32 - 16) / 2 for a
16px font and stayed wrong at every other Config.FontSizeV2, which display
scaling does not feed into. Both the icon and the label now centre against the
measured line height, and the label starts after the measured icon width
instead of a hard 32f.
This commit is contained in:
2026-08-17 23:53:30 +02:00
parent 293d56bdbe
commit 9936638bb3
+33 -25
View File
@@ -141,7 +141,6 @@ internal sealed class Sidebar
var textAbgr = ColourUtil.RgbaToAbgr(theme.Colors.TextPrimary);
var mutedAbgr = ColourUtil.RgbaToAbgr(theme.Colors.TextMuted);
var dimAbgr = ColourUtil.RgbaToAbgr(theme.Colors.TextDim);
var dangerAbgr = ColourUtil.RgbaToAbgr(theme.Colors.StatusDanger);
var dl = ImGui.GetWindowDrawList();
// B3-4 sectioned render order (1.5.6 parity): persistent → pinned
@@ -181,17 +180,7 @@ internal sealed class Sidebar
unpinnedHeaderRendered = true;
}
DrawRow(
tab,
expanded,
accentRgba,
textAbgr,
mutedAbgr,
dimAbgr,
dangerAbgr,
dl,
ref activeTab
);
DrawRow(tab, expanded, accentRgba, textAbgr, mutedAbgr, dimAbgr, dl, ref activeTab);
}
}
@@ -221,7 +210,6 @@ internal sealed class Sidebar
uint textAbgr,
uint mutedAbgr,
uint dimAbgr,
uint dangerAbgr,
ImDrawListPtr dl,
ref Tab? activeTab
)
@@ -328,24 +316,44 @@ internal sealed class Sidebar
// Icon and label shift right by the greeted slot when it is shown.
var contentX = showGreeted ? GreetedHitWidth : 0f;
var scale = Metrics.Scale;
var iconInset = 10f * scale;
// Centred, not a frozen offset: the old 8f was (32 - 16) / 2 for a 16px
// font and stays wrong at any other Config.FontSizeV2.
var contentY = Metrics.CenterY(RowHeight);
float iconRight;
using (_fonts.FontAwesome.Push())
{
var iconStr = icon.ToIconString();
dl.AddText(origin + new Vector2(10f + contentX, 8f), iconColor, iconStr);
// 1.5.6-parity unread dot, top-right of the icon. The active tab is
// zeroed every frame (MainWindow.Draw), so the dot never shows on the
// tab you're viewing; UnreadMode.None opts a tab out entirely.
if (!isCurrentTab && tab.UnreadMode != UnreadMode.None && tab.Unread > 0)
{
var iconRight = 10f + contentX + ImGui.CalcTextSize(iconStr).X;
dl.AddCircleFilled(origin + new Vector2(iconRight - 2f, 6f), 4f, dangerAbgr, 12);
LastRenderedUnreadDotCount++;
}
dl.AddText(origin + new Vector2(iconInset + contentX, contentY), iconColor, iconStr);
iconRight = iconInset + contentX + ImGui.CalcTextSize(iconStr).X;
}
if (expanded)
dl.AddText(origin + new Vector2(32f + contentX, 8f), textAbgr, tab.Name);
dl.AddText(origin + new Vector2(iconRight + 6f * scale, contentY), textAbgr, tab.Name);
// Unread count. Drawn outside the icon-font scope on purpose: the
// FontAwesome atlas carries no ASCII digits, so the number would come out
// blank. The active tab is zeroed every frame (MainWindow.Draw), so it
// never shows on the tab you are viewing; UnreadMode.None opts out.
if (!isCurrentTab && tab.UnreadMode != UnreadMode.None && tab.Unread > 0)
{
var unread = (int)Math.Min(tab.Unread, int.MaxValue);
var badgeSize = Badge.CalcSize(unread);
var badgeX = expanded
? avail - PopOutHitWidth - badgeSize.X - 4f * scale
: iconRight - badgeSize.X * 0.5f;
Badge.Draw(
origin + new Vector2(badgeX, MetricsMath.CenterY(RowHeight, badgeSize.Y)),
unread,
_palette.Abgr(Token.AccentEmber, colors),
textAbgr
);
LastRenderedUnreadDotCount++;
}
TabContextMenu.Draw(tab, "ctx", _pool);