feat(sidebar): restore per-tab greeted toggle glyph

This commit is contained in:
2026-06-10 14:59:51 +02:00
parent 1f2c354471
commit 540cb7ac52
3 changed files with 186 additions and 3 deletions
+82 -3
View File
@@ -28,6 +28,12 @@ internal sealed class Sidebar
private const float RowHeight = 32f;
private const float PopOutHitWidth = 22f;
private const float GreetedHitWidth = 22f;
// B3-2 render observability: counts greeted glyphs actually drawn this frame.
// Incremented ONLY in the real glyph branch in DrawRow; reset at Draw start.
// The SelfTest reads it after driving the real Draw — no dead service roundtrip.
internal int LastRenderedGreetedGlyphCount;
// Inline mirror of the old TabIconMapping table so the Ui layer carries
// its own glyph lookup once the standalone file is removed.
@@ -81,8 +87,20 @@ internal sealed class Sidebar
? Math.Clamp((float)Plugin.Config.SidebarWidth, MinSidebarWidth, MaxSidebarWidth)
: IconOnlyWidth;
// Factored click logic so the SelfTest exercises the real toggle, not a direct
// MarkGreeted call (which would be a dead path the render never takes).
internal void ToggleGreetedForSelfTest(Tab tab)
{
if (Plugin.Instance.AutoTellTabsService.IsGreeted(tab))
Plugin.Instance.AutoTellTabsService.UnmarkGreeted(tab);
else
Plugin.Instance.AutoTellTabsService.MarkGreeted(tab);
}
public void Draw(float windowWidth, IList<Tab> tabs, ref Tab? activeTab)
{
LastRenderedGreetedGlyphCount = 0;
if (!_fonts.FontsReady)
{
ImGui.Dummy(new Vector2(IconOnlyWidth, 0));
@@ -99,10 +117,21 @@ internal sealed class Sidebar
var accentRgba = _resolver.Resolve(Token.AccentPrimary, theme.Colors);
var textAbgr = ColourUtil.RgbaToAbgr(theme.Colors.TextPrimary);
var mutedAbgr = ColourUtil.RgbaToAbgr(theme.Colors.TextMuted);
var dimAbgr = ColourUtil.RgbaToAbgr(theme.Colors.TextDim);
var dl = ImGui.GetWindowDrawList();
for (var i = 0; i < tabs.Count; i++)
DrawRow(tabs[i], i, expanded, accentRgba, textAbgr, mutedAbgr, dl, ref activeTab);
DrawRow(
tabs[i],
i,
expanded,
accentRgba,
textAbgr,
mutedAbgr,
dimAbgr,
dl,
ref activeTab
);
}
private void DrawRow(
@@ -112,6 +141,7 @@ internal sealed class Sidebar
uint accentRgba,
uint textAbgr,
uint mutedAbgr,
uint dimAbgr,
ImDrawListPtr dl,
ref Tab? activeTab
)
@@ -130,11 +160,26 @@ internal sealed class Sidebar
return;
}
// 1.5.6 parity: greeted state dims the tab icon whenever the toggle is
// configured on. The clickable affordance additionally needs an expanded
// sidebar with room for a third hit area beside the pop-out slot — in
// icon-only or min-drag mode it is skipped entirely.
var greetedConfigured = tab.IsTempTab && Plugin.Config.AutoTellTabsShowGreetedToggle;
var showGreeted =
greetedConfigured && expanded && avail > GreetedHitWidth + PopOutHitWidth + 4f;
// Only split off a separate pop-out hit area when there's room for
// both buttons. Below that, the whole row stays as a single
// selectable strip without the pop-out affordance.
var hasPopOut = avail > PopOutHitWidth + 4f;
var tabHitWidth = hasPopOut ? avail - PopOutHitWidth : avail;
if (showGreeted)
{
// Greeted slot sits at the left edge (1.5.6 placement); the row
// button starts after it so the three hit areas never overlap.
tabHitWidth -= GreetedHitWidth;
ImGui.SetCursorScreenPos(origin + new Vector2(GreetedHitWidth, 0f));
}
ImGui.InvisibleButton("row", new Vector2(tabHitWidth, RowHeight));
var rowHovered = ImGui.IsItemHovered();
@@ -153,11 +198,25 @@ internal sealed class Sidebar
);
var icon = ResolveTabIcon(tab);
// Dim precedence (1.5.6): the active tab always keeps its regular
// color; only greeted, non-active tabs drop to TextDim.
var isCurrentTab = tab == activeTab;
var iconColor = textAbgr;
if (
!isCurrentTab
&& greetedConfigured
&& Plugin.Instance.AutoTellTabsService.IsGreeted(tab)
)
iconColor = dimAbgr;
// Icon and label shift right by the greeted slot when it is shown.
var contentX = showGreeted ? GreetedHitWidth : 0f;
using (_fonts.FontAwesome.Push())
dl.AddText(origin + new Vector2(10f, 8f), textAbgr, icon.ToIconString());
dl.AddText(origin + new Vector2(10f + contentX, 8f), iconColor, icon.ToIconString());
if (expanded)
dl.AddText(origin + new Vector2(32f, 8f), textAbgr, tab.Name);
dl.AddText(origin + new Vector2(32f + contentX, 8f), textAbgr, tab.Name);
TabContextMenu.Draw(tab, "ctx", _pool);
@@ -180,6 +239,26 @@ internal sealed class Sidebar
}
}
if (showGreeted)
{
// The hit area sits at the LEFT edge of the row, but the item must
// be submitted AFTER TabContextMenu.Draw — any interactive item
// between the row button and the popup call would steal the
// right-click trigger (B3-1 ordering constraint).
ImGui.SetCursorScreenPos(origin);
ImGui.InvisibleButton("greeted", new Vector2(GreetedHitWidth, RowHeight));
if (ImGui.IsItemClicked())
ToggleGreetedForSelfTest(tab);
// CheckCircle = greeted, plain Check = still pending (1.5.6 mapping).
var greetedGlyph = Plugin.Instance.AutoTellTabsService.IsGreeted(tab)
? FontAwesomeIcon.CheckCircle
: FontAwesomeIcon.Check;
using (_fonts.FontAwesome.Push())
dl.AddText(origin + new Vector2(4f, 8f), mutedAbgr, greetedGlyph.ToIconString());
LastRenderedGreetedGlyphCount++;
}
ImGui.PopID();
}