diff --git a/HellionChat/Ui/Components/Sidebar.cs b/HellionChat/Ui/Components/Sidebar.cs index e52ec21..d30e197 100644 --- a/HellionChat/Ui/Components/Sidebar.cs +++ b/HellionChat/Ui/Components/Sidebar.cs @@ -32,6 +32,10 @@ internal sealed class Sidebar private static float PopOutHitWidth => Metrics.SidebarPopOutHitWidth; private static float GreetedHitWidth => Metrics.SidebarGreetedHitWidth; + // Counts rows that got the active surface this frame. At most one, but not + // exactly one: PickMainActiveTab returns null when every tab is popped out. + internal int LastRenderedActiveSurfaceCount { get; private set; } + // 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. @@ -115,6 +119,7 @@ internal sealed class Sidebar LastRenderedGreetedGlyphCount = 0; LastRenderedUnreadDotCount = 0; LastDrawnSectionHeaderCount = 0; + LastRenderedActiveSurfaceCount = 0; if (!_fonts.FontsReady) { @@ -150,6 +155,12 @@ internal sealed class Sidebar ); var pinnedHeaderRendered = false; var unpinnedHeaderRendered = false; + + // Rows carry their own full-height surface now, so the default gap + // between them would read as a stripe of window background. The section + // headers are unaffected: LineDivider brings its own padding. + using var rowSpacing = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, Vector2.Zero); + foreach (var i in renderOrder) { var tab = tabs[i]; @@ -226,7 +237,7 @@ internal sealed class Sidebar // 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) + if (avail < Metrics.SidebarMinDrawWidth) { ImGui.PopID(); return; @@ -266,19 +277,43 @@ internal sealed class Sidebar TabLifecycleHelpers.OnTabActivated(tab, previous); } + // Not IsItemHovered: the row button is up to two hit widths narrower than + // the row, so a full-width surface driven by it would flicker at the + // edges. AllowWhenBlockedByActiveItem keeps the surface while the button + // is held down; without it the fill vanishes on press. + var rowMax = origin + new Vector2(avail, RowHeight); + var surfaceHovered = + ImGui.IsMouseHoveringRect(origin, rowMax) + && ImGui.IsWindowHovered(ImGuiHoveredFlags.AllowWhenBlockedByActiveItem); + // GetID is seeded from the window's ID stack, so the same "row" literal // stays distinct per window and per PushID'd tab. The old interpolated // key allocated two strings per row per frame. var hoverId = ImGui.GetID("row"u8); - var hoverAmount = StyleEngine.HoverState.Query(hoverId, rowHovered); - dl.DrawHoverSheen( + var hoverAmount = HoverState.Query(hoverId, surfaceHovered); + + var isActiveRow = ReferenceEquals(tab, activeTab); + if (isActiveRow) + LastRenderedActiveSurfaceCount++; + + var colors = _themes.Active.Colors; + Row.Draw( origin, - origin + new Vector2(avail, RowHeight), - accentRgba, - hoverAmount, - rowHovered + new Vector2(avail, RowHeight), + new RowVisualState + { + IsActive = isActiveRow, + HoverAmount = hoverAmount, + SurfaceAbgr = _palette.Abgr(Token.SurfaceBase, colors), + SurfaceHoverAbgr = _palette.Abgr(Token.SurfaceHover, colors), + SurfaceActiveAbgr = _palette.Abgr(Token.SurfaceActive, colors), + AccentAbgr = _palette.Abgr(Token.AccentPrimary, colors), + BorderAbgr = _palette.Abgr(Token.Border, colors), + } ); + dl.DrawHoverSheen(origin, rowMax, accentRgba, hoverAmount, surfaceHovered); + var icon = ResolveTabIcon(tab); // Dim precedence (1.5.6): the active tab always keeps its regular diff --git a/HellionChat/Ui/Components/TabContextMenu.cs b/HellionChat/Ui/Components/TabContextMenu.cs index 738a6e7..27e1556 100644 --- a/HellionChat/Ui/Components/TabContextMenu.cs +++ b/HellionChat/Ui/Components/TabContextMenu.cs @@ -1,6 +1,7 @@ using Dalamud.Bindings.ImGui; using Dalamud.Interface; using Dalamud.Interface.Utility; +using Dalamud.Interface.Utility.Raii; using FFXIVClientStructs.FFXIV.Client.UI; using HellionChat.Resources; using HellionChat.Util; @@ -39,6 +40,17 @@ internal static class TabContextMenu return; } + // The sidebar pushes ItemSpacing to zero so its rows sit flush, and style + // vars are a global stack the popup inherits. Without restoring a normal + // spacing here the menu entries would touch each other. + using var spacing = ImRaii.PushStyle( + ImGuiStyleVar.ItemSpacing, + ImGui.GetStyle().ItemSpacing with + { + Y = MathF.Max(ImGui.GetStyle().ItemSpacing.Y, 4f * ImGuiHelpers.GlobalScale), + } + ); + // Rename: focus the field the first frame the popup appears. if (ImGui.IsWindowAppearing()) ImGui.SetKeyboardFocusHere();