fix: plain <menu> popup scrollbar draws retail chrome, not a flat bar
Owner live-client report 2026-09-07: "For scrollable dropdown or the meta window we use the same assets as we do in for example chat or inventory window." The plain-style <menu> popup's scrollable-overflow scrollbar (DrawScrollablePopupPlain / DrawPopupScrollbarPlain in UiMenu.cs) drew a home-made flat 1px track + flat thumb instead of the gold track + up/down arrow buttons + thumb the chat SpewBox and inventory UiItemList already use through RetailScrollbarChrome. The owner only ever objected to the retail ROW art (checkmark glyph, gradient panel) — the bar itself was never in scope for the plain-row fix, so this change touches only the scrollbar draw call and leaves the plain row rendering untouched. DrawScrollablePopupPlain now calls the existing DrawPopupScrollbar helper (the same procedural sprite-chrome draw VendorUiController/ ConfigOptionsPageController already use) whenever a SpriteResolve is wired, falling back to the old flat DrawPopupScrollbarPlain only for a hand-built UiMenu with no resolver at all. New RetailScrollbarChrome.ApplyToMenuPopup(UiMenu) wires the same vertical skin ids (Track/Up/Down/ThumbTop/Mid/Bot Normal) the chat/inventory scrollbar uses onto a menu's own ScrollTrackSprite/etc properties. Mutation shown to fail first: UiMenuPlainStyleTests's Plain_OpenPopup_ScrollableOverflow_DrawsPlainTrackAndFlatThumb_NoDatArt and Plain_ScrollablePopup_ContentFits_DrawsTrackWithNoThumb asserted resolveCalls==0 and an all-fill scrollbar — both failed (6 resolve calls, 6 sprite quads instead of 0) against the new DrawPopupScrollbar call before being rewritten to Plain_OpenPopup_ScrollableOverflow_DrawsRetailScrollbarChrome_RowsStayPlain and Plain_ScrollablePopup_ContentFits_DrawsNoScrollbarAtAll, which pin the new sprite-chrome behavior (6 resolved ids on overflow: track, up, down, thumb top/mid/bottom; 3 on content-fits: track+up+down, no thumb; 0 on a menu built with no resolver) while re-asserting the rows are still plain fills with zero retail row-sprite quads. Retail's own RetailButtonArt=true popup path (DrawGridPopup/DrawScrollablePopup) is untouched — its regression golden (Retail_OpenPopup_DrawIsByteForByteUnchanged_RegressionGolden) still passes byte-for-byte. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
parent
b36b036475
commit
67aba8c386
3 changed files with 96 additions and 26 deletions
|
|
@ -84,6 +84,27 @@ internal static class RetailScrollbarChrome
|
|||
bar.ThumbBotPressedSprite = ThumbBotPressed;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Wires the retail vertical skin onto a <see cref="UiMenu"/> popup's own
|
||||
/// procedural scrollbar properties (<see cref="UiMenu.ScrollTrackSprite"/>
|
||||
/// etc). Owner live-client report 2026-09-07 ("For scrollable dropdown or
|
||||
/// the meta window we use the same assets as we do in for example chat or
|
||||
/// inventory window"): the popup's own <c>DrawPopupScrollbar</c> draws a
|
||||
/// simpler Normal-only chrome (no hover/pressed states — matching how
|
||||
/// <c>VendorUiController</c>/<c>ConfigOptionsPageController</c> already
|
||||
/// wire these exact ids), so only the Normal-state constants are needed
|
||||
/// here.
|
||||
/// </summary>
|
||||
internal static void ApplyToMenuPopup(UiMenu menu)
|
||||
{
|
||||
menu.ScrollTrackSprite = Track;
|
||||
menu.ScrollThumbTopSprite = ThumbTopNormal;
|
||||
menu.ScrollThumbSprite = ThumbMidNormal;
|
||||
menu.ScrollThumbBottomSprite = ThumbBotNormal;
|
||||
menu.ScrollUpSprite = UpNormal;
|
||||
menu.ScrollDownSprite = DownNormal;
|
||||
}
|
||||
|
||||
/// <summary>Wires the full retail horizontal skin onto <paramref name="bar"/>.
|
||||
/// The leading (<see cref="UiScrollbar.UpSprite"/>) slot is the LEFT edge.</summary>
|
||||
internal static void ApplyHorizontal(UiScrollbar bar)
|
||||
|
|
|
|||
|
|
@ -884,9 +884,19 @@ public sealed class UiMenu : UiElement
|
|||
}
|
||||
|
||||
/// <summary>Plain counterpart of <see cref="DrawScrollablePopup"/> — same
|
||||
/// <see cref="VisibleTopRow"/>-sliced single column, plain
|
||||
/// selected/hover row fills, and a plain scrollbar
|
||||
/// (<see cref="DrawPopupScrollbarPlain"/>) instead of the sprite chrome.</summary>
|
||||
/// <see cref="VisibleTopRow"/>-sliced single column and plain
|
||||
/// selected/hover row fills, but the SCROLLBAR itself draws retail's own
|
||||
/// chrome (owner live-client report 2026-09-07: "For scrollable dropdown
|
||||
/// or the meta window we use the same assets as we do in for example
|
||||
/// chat or inventory window") via the shared <see cref="DrawPopupScrollbar"/>
|
||||
/// helper — the exact ids <see cref="RetailScrollbarChrome.ApplyToMenuPopup"/>
|
||||
/// wires onto <see cref="ScrollTrackSprite"/> etc, the SAME sprite ids the
|
||||
/// chat SpewBox/inventory <see cref="UiItemList"/> scrollbar uses. Rows
|
||||
/// stay plain by design (the owner accepted the flat dark list; only the
|
||||
/// bar was objectionable). A menu built with no <see cref="SpriteResolve"/>
|
||||
/// at all (a hand-built test/legacy fixture) falls back to the fully
|
||||
/// flat <see cref="DrawPopupScrollbarPlain"/> rather than silently
|
||||
/// drawing nothing.</summary>
|
||||
private void DrawScrollablePopupPlain(UiRenderContext ctx)
|
||||
{
|
||||
ConfigurePopupScroll();
|
||||
|
|
@ -918,17 +928,26 @@ public sealed class UiMenu : UiElement
|
|||
avail ? PlainTextColor : TextColorGhosted);
|
||||
}
|
||||
|
||||
DrawPopupScrollbarPlain(ctx, inX + ColumnWidth, inY);
|
||||
if (SpriteResolve is { } resolve)
|
||||
DrawPopupScrollbar(ctx, resolve, inX + ColumnWidth, inY);
|
||||
else
|
||||
DrawPopupScrollbarPlain(ctx, inX + ColumnWidth, inY);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Plain counterpart of <see cref="DrawPopupScrollbar"/>: a 1px-bordered
|
||||
/// track and a flat thumb, both in <see cref="PlainBorderColor"/> — no DAT
|
||||
/// thumb/track/arrow-button art at all. Shares the exact same
|
||||
/// <see cref="UiScrollbar.ThumbRect"/> geometry (so the thumb's drawn
|
||||
/// position matches <see cref="HandleScrollablePopupMouseDown"/>'s hit-test
|
||||
/// math), but draws no separate up/down button glyphs — plain mode has no
|
||||
/// art for them and the click regions already work through geometry alone
|
||||
/// NO-RESOLVER FALLBACK ONLY (see <see cref="DrawScrollablePopupPlain"/>'s
|
||||
/// own doc comment — the 2026-09-07 owner directive moved the normal
|
||||
/// plain-popup scrollbar to retail's own chrome via
|
||||
/// <see cref="DrawPopupScrollbar"/>). This draws a 1px-bordered track and
|
||||
/// a flat thumb, both in <see cref="PlainBorderColor"/> — no DAT
|
||||
/// thumb/track/arrow-button art at all — for the rare case a
|
||||
/// <see cref="UiMenu"/> is built with <see cref="Scrollable"/> true but no
|
||||
/// <see cref="SpriteResolve"/> at all (a hand-built test/legacy fixture).
|
||||
/// Shares the exact same <see cref="UiScrollbar.ThumbRect"/> geometry (so
|
||||
/// the thumb's drawn position matches
|
||||
/// <see cref="HandleScrollablePopupMouseDown"/>'s hit-test math), but
|
||||
/// draws no separate up/down button glyphs — plain mode has no art for
|
||||
/// them and the click regions already work through geometry alone
|
||||
/// (<see cref="HandleScrollablePopupMouseDown"/> is unchanged).
|
||||
/// </summary>
|
||||
private void DrawPopupScrollbarPlain(UiRenderContext ctx, float x, float y)
|
||||
|
|
|
|||
|
|
@ -359,8 +359,17 @@ public sealed class UiMenuPlainStyleTests
|
|||
Assert.Equal(RetailChromeSprites.Border + UiMenu.PlainPadding, glyphSeg.Verts[0], 3);
|
||||
}
|
||||
|
||||
// ── Owner live-client report 2026-09-07 ("For scrollable dropdown or the
|
||||
// meta window we use the same assets as we do in for example chat or
|
||||
// inventory window"): the plain popup's SCROLLBAR now draws retail's own
|
||||
// chrome (the exact sprite ids RetailScrollbarChrome wires onto
|
||||
// chat/inventory's own bar) — only the ROWS stayed plain. These two tests
|
||||
// used to pin a fully flat/untextured scrollbar; they now pin the
|
||||
// opposite: real sprite draws for the bar, untouched plain fills for the
|
||||
// rows, and no visible bar at all once the content fits.
|
||||
|
||||
[Fact]
|
||||
public void Plain_OpenPopup_ScrollableOverflow_DrawsPlainTrackAndFlatThumb_NoDatArt()
|
||||
public void Plain_OpenPopup_ScrollableOverflow_DrawsRetailScrollbarChrome_RowsStayPlain()
|
||||
{
|
||||
int resolveCalls = 0;
|
||||
var menu = MakePopupMenu(retailButtonArt: false, itemCount: 12, rowsPerColumn: 5, scrollable: true,
|
||||
|
|
@ -373,27 +382,39 @@ public sealed class UiMenuPlainStyleTests
|
|||
var segs = renderer.DebugSpriteSegmentVerts;
|
||||
|
||||
Assert.True(menu.PopupScroll.HasOverflow);
|
||||
Assert.Equal(0, resolveCalls);
|
||||
Assert.Equal(0, QuadCount(segs, menu.ScrollTrackSprite));
|
||||
Assert.Equal(0, QuadCount(segs, menu.ScrollThumbSprite));
|
||||
// Track + up + down + thumb top/mid/bottom = 6 resolved sprite ids —
|
||||
// the SAME chrome ids the chat/inventory scrollbar resolves through
|
||||
// the same SpriteResolve seam, no longer the flat DrawFill-only path.
|
||||
Assert.Equal(6, resolveCalls);
|
||||
Assert.Equal(1, QuadCount(segs, menu.ScrollTrackSprite));
|
||||
Assert.Equal(1, QuadCount(segs, menu.ScrollUpSprite));
|
||||
Assert.Equal(1, QuadCount(segs, menu.ScrollDownSprite));
|
||||
Assert.Equal(1, QuadCount(segs, menu.ScrollThumbTopSprite));
|
||||
Assert.Equal(1, QuadCount(segs, menu.ScrollThumbSprite));
|
||||
Assert.Equal(1, QuadCount(segs, menu.ScrollThumbBottomSprite));
|
||||
|
||||
float outerTop = menu.Height;
|
||||
float inX = RetailChromeSprites.Border, inY = outerTop + RetailChromeSprites.Border;
|
||||
float scrollbarX = inX + PlainColumnWidth;
|
||||
|
||||
// The rows are untouched by the chrome swap: still a plain fill, no
|
||||
// DAT row/checkbox art at all (RetailButtonArt=false's own contract).
|
||||
Assert.True(HasFillQuad(segs, inX, inY, PlainColumnWidth, PlainRowHeight, menu.PlainSelectedColor),
|
||||
"expected visible row 0 (selected/current) filled with PlainSelectedColor");
|
||||
Assert.True(HasFillQuad(segs, scrollbarX, inY, menu.ScrollbarWidth, 5 * PlainRowHeight, menu.PlainBackgroundColor),
|
||||
"expected the scrollbar track background fill");
|
||||
"expected visible row 0 (selected/current) still filled with PlainSelectedColor");
|
||||
Assert.Equal(0, QuadCount(segs, menu.ItemHighlightSprite));
|
||||
Assert.Equal(0, QuadCount(segs, menu.ItemNormalSprite));
|
||||
|
||||
// popup bg(1)+outline(4) + selected row(1) + scrollbar bg(1)+outline(4) + thumb(1) = 12.
|
||||
Assert.Equal(12, QuadCount(segs, 0u));
|
||||
// popup bg(1)+outline(4) + selected row(1) = 6 untextured quads;
|
||||
// the scrollbar itself no longer contributes any (it is all sprite
|
||||
// draws now).
|
||||
Assert.Equal(6, QuadCount(segs, 0u));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Plain_ScrollablePopup_ContentFits_DrawsTrackWithNoThumb()
|
||||
public void Plain_ScrollablePopup_ContentFits_DrawsNoScrollbarAtAll()
|
||||
{
|
||||
var menu = MakePopupMenu(retailButtonArt: false, itemCount: 3, rowsPerColumn: 5, scrollable: true);
|
||||
int resolveCalls = 0;
|
||||
var menu = MakePopupMenu(retailButtonArt: false, itemCount: 3, rowsPerColumn: 5, scrollable: true,
|
||||
countResolveCall: n => resolveCalls += n);
|
||||
OpenAndHover(menu);
|
||||
|
||||
var (renderer, ctx) = MakeContext(200f, 200f);
|
||||
|
|
@ -402,9 +423,18 @@ public sealed class UiMenuPlainStyleTests
|
|||
|
||||
Assert.False(menu.PopupScroll.HasOverflow);
|
||||
|
||||
// popup bg(1)+outline(4) + scrollbar bg(1)+outline(4) = 10, no thumb quad
|
||||
// (nothing selected/hovered here either).
|
||||
Assert.Equal(10, QuadCount(segs, 0u));
|
||||
// Content-fits still draws the track + up/down buttons (retail's own
|
||||
// proportion-0x88-defaults-to-1.0 rule — a content-fits bar shows a
|
||||
// full-track thumb elsewhere in this class), but no thumb: 3 resolves.
|
||||
Assert.Equal(3, resolveCalls);
|
||||
Assert.Equal(1, QuadCount(segs, menu.ScrollTrackSprite));
|
||||
Assert.Equal(1, QuadCount(segs, menu.ScrollUpSprite));
|
||||
Assert.Equal(1, QuadCount(segs, menu.ScrollDownSprite));
|
||||
Assert.Equal(0, QuadCount(segs, menu.ScrollThumbSprite));
|
||||
|
||||
// popup bg(1)+outline(4) = 5 untextured quads (nothing
|
||||
// selected/hovered here either, and the scrollbar draws no fills).
|
||||
Assert.Equal(5, QuadCount(segs, 0u));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue