feat(sidebar): restore section headers and compact separators

This commit is contained in:
2026-06-10 16:22:49 +02:00
parent 540cb7ac52
commit dc51e295ea
3 changed files with 192 additions and 11 deletions
+73 -11
View File
@@ -3,6 +3,7 @@ using Dalamud.Bindings.ImGui;
using Dalamud.Interface;
using Dalamud.Interface.Utility.Raii;
using HellionChat.Code;
using HellionChat.Resources;
using HellionChat.Themes;
using HellionChat.Ui.StyleEngine;
using HellionChat.Util;
@@ -35,6 +36,10 @@ internal sealed class Sidebar
// The SelfTest reads it after driving the real Draw — no dead service roundtrip.
internal int LastRenderedGreetedGlyphCount;
// B3-4 render observability: section headers actually drawn this frame.
// Incremented only in the real header branch; reset at Draw start.
internal int LastDrawnSectionHeaderCount;
// Inline mirror of the old TabIconMapping table so the Ui layer carries
// its own glyph lookup once the standalone file is removed.
private static readonly Dictionary<string, FontAwesomeIcon> IconByName = new(
@@ -100,6 +105,7 @@ internal sealed class Sidebar
public void Draw(float windowWidth, IList<Tab> tabs, ref Tab? activeTab)
{
LastRenderedGreetedGlyphCount = 0;
LastDrawnSectionHeaderCount = 0;
if (!_fonts.FontsReady)
{
@@ -120,18 +126,74 @@ internal sealed class Sidebar
var dimAbgr = ColourUtil.RgbaToAbgr(theme.Colors.TextDim);
var dl = ImGui.GetWindowDrawList();
// B3-4 sectioned render order (1.5.6 parity): persistent → pinned
// TempTabs → unpinned TempTabs. Only the display sequence regroups;
// the tab list itself stays untouched and every row keeps its
// ORIGINAL list index for PushID, so an open context-menu popup
// stays bound to its tab when sectioning moves it visually.
var renderOrder = BuildRenderOrder(tabs);
var pinnedHeaderRendered = false;
var unpinnedHeaderRendered = false;
foreach (var i in renderOrder)
{
var tab = tabs[i];
if (TabLifecycleHelpers.IsInPinnedPool(tab) && !pinnedHeaderRendered)
{
DrawSectionHeader(
HellionStrings.PinTab_SectionHeader,
Plugin.Instance.AutoTellTabsService.PinnedTempTabCount
);
pinnedHeaderRendered = true;
}
else if (TabLifecycleHelpers.IsInUnpinnedPool(tab) && !unpinnedHeaderRendered)
{
DrawSectionHeader(
HellionStrings.AutoTellTabs_SectionHeader,
Plugin.Instance.AutoTellTabsService.ActiveTempTabCount
);
unpinnedHeaderRendered = true;
}
DrawRow(tab, i, expanded, accentRgba, textAbgr, mutedAbgr, dimAbgr, dl, ref activeTab);
}
}
// Section transition marker (1.5.6 parity): the separator always renders,
// compact mode suppresses only the header text. Real cursor-advancing
// widgets on purpose — rows advance the cursor via InvisibleButton, so a
// drawlist-only header would overlap the next row.
private void DrawSectionHeader(string header, int count)
{
ImGui.Separator();
if (Plugin.Config.AutoTellTabsCompactDisplay)
return;
ImGui.TextDisabled($"{header} ({count})");
LastDrawnSectionHeaderCount++;
}
// Mirror of 1.5.6's BuildSidebarRenderOrder: returns indices into the
// live tab list grouped by section, so the list order itself is never
// mutated and headers gate on the first tab actually reached per pool
// (an empty pool draws neither separator nor header).
private static List<int> BuildRenderOrder(IList<Tab> tabs)
{
var persistent = new List<int>(tabs.Count);
var pinned = new List<int>();
var unpinned = new List<int>();
for (var i = 0; i < tabs.Count; i++)
DrawRow(
tabs[i],
i,
expanded,
accentRgba,
textAbgr,
mutedAbgr,
dimAbgr,
dl,
ref activeTab
);
{
if (TabLifecycleHelpers.IsInPinnedPool(tabs[i]))
pinned.Add(i);
else if (TabLifecycleHelpers.IsInUnpinnedPool(tabs[i]))
unpinned.Add(i);
else
persistent.Add(i);
}
persistent.AddRange(pinned);
persistent.AddRange(unpinned);
return persistent;
}
private void DrawRow(