From 8e7149cadc4101c8f406a51ba74239ec3817e93d Mon Sep 17 00:00:00 2001 From: Jon Kazama Date: Sat, 23 May 2026 21:08:22 +0200 Subject: [PATCH] fix(ui): guard sidebar row at min drag and widen system-icon match MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit DrawRow asserted on a zero-width InvisibleButton when the window was dragged below the pop-out hit threshold — the row now drops out cleanly under 2px of remaining sidebar width, and the pop-out button only splits off when there's room for both hit areas. The trailing pop-out icon is hidden too when its strip is collapsed, so the row stays as a single selectable strip on extreme drags. System icon path: ResolveTabIcon used to look only at the first key in SelectedChannels, so a System tab whose first filter happened to be a generic ChatType slipped through to Comment. The resolve now walks every key and keeps the first non-Comment match, and a final case-insensitive name match flips the icon to fa-cog when the user's filter set falls completely outside the channel-type table. --- HellionChat/Ui/Components/Sidebar.cs | 57 ++++++++++++++++++++-------- 1 file changed, 42 insertions(+), 15 deletions(-) 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; }