feat(D.2b): data-driven channel menu chrome + greying + scroll-arrow fix

Investigation found the menu popup is fully dat-driven (UIElement_Menu::MakePopup
@0x46d310 reads LayoutDesc 0x21000006 elements 0x1000001C/1D/1E — the "stray" top-level
elements). Render the popup from the real sprites instead of a flat rect:
- panel 0x0600124C, item row 0x0600124E, selected row 0x0600124D; 191x17 rows, 2 cols.
- drawing rows as SPRITES also fixes the z-order (a DrawRect bg composited OVER the
  labels; sprites share the labels submission bucket so text lands on top).
- item greying: available channels white, unavailable salmon (colorPink) — static
  approximation (Say/General/Trade/LFG) with an AvailabilityProvider hook for live
  TurbineChat state; unavailable items are inert on click. Ports ResetAllTalkFocusMenuButtons.
- scroll arrows: both dat sprites point down (export-confirmed); V-flip the top button
  so it points up.
Tabs confirmed to have NO digits in retail (blank gold frames) — acdream already matches.

Build + 392 App tests green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@
This commit is contained in:
Erik 2026-06-16 11:33:38 +02:00
parent 7094a1c847
commit bb983ae850
4 changed files with 155 additions and 64 deletions

View file

@ -6,10 +6,13 @@ namespace AcDream.App.Tests.UI;
public class UiChannelMenuTests
{
// PopupH = Rows(7) * ItemH(17) = 119; popup opens upward so top = -119.
// Item idx -> col = idx/7, row = idx%7; row band y in [top+row*17, top+(row+1)*17).
// Right column needs lx >= ColW(191).
[Fact]
public void Items_HasExpected14Entries()
{
// Retail gmMainChatUI::InitTalkFocusMenu: squelch + tell-selected + 12 channels.
Assert.Equal(14, UiChannelMenu.Items.Length);
}
@ -17,7 +20,7 @@ public class UiChannelMenuTests
public void Items_FirstEntry_IsSquelch_Special()
{
Assert.Equal("Squelch (ignore)", UiChannelMenu.Items[0].Label);
Assert.Null(UiChannelMenu.Items[0].Channel); // special item, no channel
Assert.Null(UiChannelMenu.Items[0].Channel);
}
[Fact]
@ -46,30 +49,11 @@ public class UiChannelMenuTests
[Fact]
public void DefaultSelected_IsSay()
{
var menu = new UiChannelMenu();
Assert.Equal(ChatChannelKind.Say, menu.Selected);
Assert.Equal(ChatChannelKind.Say, new UiChannelMenu().Selected);
}
[Fact]
public void Select_LeftColumnItem_FiresChannel()
{
var menu = new UiChannelMenu { Width = 80f, Height = 18f };
var openEvt = new UiEvent(0, menu, UiEventType.MouseDown, 0, 10, 5); // open
Assert.True(menu.OnEvent(openEvt));
ChatChannelKind? fired = null;
menu.OnChannelChanged = k => fired = k;
// PopupH = 7*16 = 112, top = -112. "Chat to All" (Say) is index 2 = left col, row 2:
// y in [-112+32, -112+48) = [-80,-64). Click (lx=10 < ColW, ly=-72) → idx 2 → Say.
var selEvt = new UiEvent(0, menu, UiEventType.MouseDown, 0, 10, -72);
Assert.True(menu.OnEvent(selEvt));
Assert.Equal(ChatChannelKind.Say, fired);
Assert.Equal(ChatChannelKind.Say, menu.Selected);
}
[Fact]
public void Select_RightColumnItem_FiresChannel()
public void Select_AvailableLeftColumnItem_FiresChannel()
{
var menu = new UiChannelMenu { Width = 80f, Height = 18f };
Assert.True(menu.OnEvent(new UiEvent(0, menu, UiEventType.MouseDown, 0, 10, 5))); // open
@ -77,11 +61,25 @@ public class UiChannelMenuTests
ChatChannelKind? fired = null;
menu.OnChannelChanged = k => fired = k;
// "Tell to Monarch" is index 7 = right col (lx >= ColW 150), row 0:
// y in [-112, -96). Click (lx=160, ly=-104) → col 1, row 0 → idx 7 → Monarch.
Assert.True(menu.OnEvent(new UiEvent(0, menu, UiEventType.MouseDown, 0, 160, -104)));
Assert.Equal(ChatChannelKind.Monarch, fired);
Assert.Equal(ChatChannelKind.Monarch, menu.Selected);
// "Chat to All" (Say) is index 2 = left col, row 2: y in [-85,-68). Say is available.
Assert.True(menu.OnEvent(new UiEvent(0, menu, UiEventType.MouseDown, 0, 10, -76)));
Assert.Equal(ChatChannelKind.Say, fired);
Assert.Equal(ChatChannelKind.Say, menu.Selected);
}
[Fact]
public void Select_AvailableRightColumnItem_FiresChannel()
{
var menu = new UiChannelMenu { Width = 80f, Height = 18f };
Assert.True(menu.OnEvent(new UiEvent(0, menu, UiEventType.MouseDown, 0, 10, 5))); // open
ChatChannelKind? fired = null;
menu.OnChannelChanged = k => fired = k;
// "Tell to Trade Chat" (Trade) is index 11 = right col (lx>=191), row 4: y in [-51,-34).
Assert.True(menu.OnEvent(new UiEvent(0, menu, UiEventType.MouseDown, 0, 200, -42)));
Assert.Equal(ChatChannelKind.Trade, fired);
Assert.Equal(ChatChannelKind.Trade, menu.Selected);
}
[Fact]
@ -89,12 +87,39 @@ public class UiChannelMenuTests
{
var menu = new UiChannelMenu { Width = 80f, Height = 18f };
Assert.True(menu.OnEvent(new UiEvent(0, menu, UiEventType.MouseDown, 0, 10, 5))); // open
int fired = 0;
menu.OnChannelChanged = _ => fired++;
// "Squelch (ignore)" is index 0 = left col, row 0: y in [-112, -96). No channel.
Assert.True(menu.OnEvent(new UiEvent(0, menu, UiEventType.MouseDown, 0, 10, -104)));
Assert.Equal(0, fired); // special item is a no-op
// "Squelch (ignore)" is index 0 = left col, row 0 (null channel): y in [-119,-102).
Assert.True(menu.OnEvent(new UiEvent(0, menu, UiEventType.MouseDown, 0, 10, -110)));
Assert.Equal(0, fired);
}
[Fact]
public void Select_UnavailableChannel_DoesNotFire()
{
var menu = new UiChannelMenu { Width = 80f, Height = 18f };
Assert.True(menu.OnEvent(new UiEvent(0, menu, UiEventType.MouseDown, 0, 10, 5))); // open
int fired = 0;
menu.OnChannelChanged = _ => fired++;
// "Tell to Fellows" (Fellowship) is index 3 = left col, row 3: y in [-68,-51).
// Fellowship is unavailable by the default static gate, so the click is inert.
Assert.True(menu.OnEvent(new UiEvent(0, menu, UiEventType.MouseDown, 0, 10, -60)));
Assert.Equal(0, fired);
}
[Fact]
public void AvailabilityProvider_Overrides_DefaultGate()
{
var menu = new UiChannelMenu { Width = 80f, Height = 18f, AvailabilityProvider = _ => true };
Assert.True(menu.OnEvent(new UiEvent(0, menu, UiEventType.MouseDown, 0, 10, 5))); // open
ChatChannelKind? fired = null;
menu.OnChannelChanged = k => fired = k;
// With every channel available, "Tell to Fellows" (idx 3, row 3) now fires.
Assert.True(menu.OnEvent(new UiEvent(0, menu, UiEventType.MouseDown, 0, 10, -60)));
Assert.Equal(ChatChannelKind.Fellowship, fired);
}
}