diff --git a/HellionChat/Ui/Components/Sidebar.cs b/HellionChat/Ui/Components/Sidebar.cs index 1efed53..cf0e6c9 100644 --- a/HellionChat/Ui/Components/Sidebar.cs +++ b/HellionChat/Ui/Components/Sidebar.cs @@ -110,10 +110,22 @@ internal sealed class Sidebar var origin = ImGui.GetCursorScreenPos(); var avail = ImGui.GetContentRegionAvail().X; - var tabHitWidth = MathF.Max(0f, avail - PopOutHitWidth); - // Tab hit area sits left of the pop-out button so the two never - // steal each other's clicks. + // Drop the row entirely when the sidebar is dragged below the width + // of a single hit target. ImGui's InvisibleButton asserts on a + // zero-width size, which crashes the whole window at min-drag. + if (avail < 2f) + { + ImGui.PopID(); + return; + } + + // 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; + ImGui.InvisibleButton("row", new Vector2(tabHitWidth, RowHeight)); var rowHovered = ImGui.IsItemHovered(); if (ImGui.IsItemClicked()) @@ -144,13 +156,17 @@ internal sealed class Sidebar ImGui.EndPopup(); } - ImGui.SameLine(0f, 0f); - ImGui.InvisibleButton("popout", new Vector2(PopOutHitWidth, RowHeight)); - var popHovered = ImGui.IsItemHovered(); - if (ImGui.IsItemClicked()) - LogPopOutStub(tab); + var popHovered = false; + if (hasPopOut) + { + ImGui.SameLine(0f, 0f); + ImGui.InvisibleButton("popout", new Vector2(PopOutHitWidth, RowHeight)); + popHovered = ImGui.IsItemHovered(); + if (ImGui.IsItemClicked()) + LogPopOutStub(tab); + } - if (rowHovered || popHovered) + if (hasPopOut && (rowHovered || popHovered)) { using (_fonts.FontAwesome.Push()) { @@ -174,12 +190,23 @@ internal sealed class Sidebar if (tab.IsTempTab) return FontAwesomeIcon.Envelope; - // Channel-type fallback. The v1.5.6 TabIconGlyphResolver did the - // same thing — picks the first selected channel and maps its - // ChatType to a category icon so tabs without a user-set icon - // still look distinct. - if (tab.SelectedChannels.Count > 0) - return ResolveByChannelType(tab.SelectedChannels.Keys.First()); + // Channel-type fallback. Walk every selected key, not just the first, + // so a System tab that filters multiple system-flavoured ChatTypes + // still picks up fa-cog when one of the later keys carries the match. + // The Comment default only wins when every key falls into the + // generic-text bucket (Say / Yell / Shout etc.). + foreach (var chatType in tab.SelectedChannels.Keys) + { + var glyph = ResolveByChannelType(chatType); + if (glyph != FontAwesomeIcon.Comment) + return glyph; + } + + // Last-resort name match for tabs that filter exotic ChatTypes the + // mapping above doesn't cover — keeps the System tab visually + // distinct even with a custom channel set. + if (tab.Name.Contains("system", StringComparison.OrdinalIgnoreCase)) + return FontAwesomeIcon.Cog; return FontAwesomeIcon.Comment; }