feat(sidebar): draw row surfaces, accent bar and separators
The sidebar drew a hover sweep, an icon and a label per row and nothing else. No hover fill, no active fill, no accent bar, no separator -- the active tab was visually indistinguishable from the rest. The theme preview in settings has been showing all of it for months without the real sidebar delivering any. Rows now go through the Row widget: base surface, active surface, hover interpolated between them, a 2px accent bar on the active row and a bottom separator. Hover is detected with IsMouseHoveringRect, not IsItemHovered. The row button is up to two hit widths narrower than the row itself (popout slot, greeted slot), so a full-width surface driven by the item would flicker at the edges. AllowWhenBlockedByActiveItem is required on the window check, otherwise the fill disappears the moment the button is pressed, because InvisibleButton owns the active id by then. ItemSpacing is pushed to zero around the row loop so surfaces sit flush instead of leaving a stripe of window background between them. Style vars are a global stack and the context menu inherits them, so TabContextMenu now restores a normal spacing inside its popup -- without that its entries would touch. The row surface is drawn strictly with draw-list calls between the row button and TabContextMenu.Draw, which is the ordering the popup trigger depends on.
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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();
|
||||
|
||||
Reference in New Issue
Block a user