@
feat(D.2b): chat polish — typing fix, opacity, scrollbar 3-slice, retail channel menu Visual-iteration batch (decomp-grounded), each fix verified against the retail screenshots: - typing: UiElement.HitTest aborted on ClickThrough BEFORE walking children, so the ClickThrough UiDatElement panels blocked hit-testing to the input/transcript inside them. Check ClickThrough AFTER the child walk (it only gates whether THIS element claims the hit). Restores input focus + typing. - opacity: UiElement.Opacity + a UiRenderContext alpha stack applied to sprite/rect draws (text bypasses it, stays sharp); chat frame Opacity=0.75 → translucent chat. - brown sliver: grow the transcript panel up 9px to cover the dropped resize-bar strip. - scrollbar: real 3-slice thumb (caps 0x06004C60/66 + tiled mid) + tiled track. - max/min: shifted one button-width left of the scrollbar (dat right-anchors collide). - system text now green (retail ChatMessageType 5; was yellow). - word-wrap: transcript lines wrap to the panel width (greedy, ports GlyphList::Recalculate). - channel menu reworked to retail gmMainChatUI::InitTalkFocusMenu: "Chat" button + a TWO-COLUMN popup of the 14 talk-focus items (Squelch, Tell to Selected, Chat to All, Tell to Fellows, ...) on a tan panel; channel items set the active outbound channel. Build + 392 App tests green. Visual confirmation in progress. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> @
This commit is contained in:
parent
0ec36f6197
commit
1da697ec2a
7 changed files with 329 additions and 126 deletions
|
|
@ -1,3 +1,4 @@
|
|||
using System.Linq;
|
||||
using AcDream.App.UI;
|
||||
using AcDream.UI.Abstractions;
|
||||
|
||||
|
|
@ -6,42 +7,40 @@ namespace AcDream.App.Tests.UI;
|
|||
public class UiChannelMenuTests
|
||||
{
|
||||
[Fact]
|
||||
public void Channels_HasExpected12Entries()
|
||||
public void Items_HasExpected14Entries()
|
||||
{
|
||||
Assert.Equal(12, UiChannelMenu.Channels.Length);
|
||||
// Retail gmMainChatUI::InitTalkFocusMenu: squelch + tell-selected + 12 channels.
|
||||
Assert.Equal(14, UiChannelMenu.Items.Length);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Channels_FirstEntry_IsSay()
|
||||
public void Items_FirstEntry_IsSquelch_Special()
|
||||
{
|
||||
Assert.Equal(ChatChannelKind.Say, UiChannelMenu.Channels[0].Channel);
|
||||
Assert.Equal("Say", UiChannelMenu.Channels[0].Label);
|
||||
Assert.Equal("Squelch (ignore)", UiChannelMenu.Items[0].Label);
|
||||
Assert.Null(UiChannelMenu.Items[0].Channel); // special item, no channel
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Channels_LastEntry_IsOlthoi()
|
||||
public void Items_LastEntry_IsOlthoi()
|
||||
{
|
||||
var last = UiChannelMenu.Channels[^1];
|
||||
var last = UiChannelMenu.Items[^1];
|
||||
Assert.Equal("Tell to Olthoi Chat", last.Label);
|
||||
Assert.Equal(ChatChannelKind.Olthoi, last.Channel);
|
||||
Assert.Equal("Olthoi", last.Label);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Channels_ContainsAllExpectedKinds()
|
||||
public void Items_ContainAll12ChannelKinds()
|
||||
{
|
||||
var kinds = new HashSet<ChatChannelKind>(UiChannelMenu.Channels.Select(c => c.Channel));
|
||||
Assert.Contains(ChatChannelKind.Say, kinds);
|
||||
Assert.Contains(ChatChannelKind.General, kinds);
|
||||
Assert.Contains(ChatChannelKind.Trade, kinds);
|
||||
Assert.Contains(ChatChannelKind.Lfg, kinds);
|
||||
Assert.Contains(ChatChannelKind.Fellowship, kinds);
|
||||
Assert.Contains(ChatChannelKind.Allegiance, kinds);
|
||||
Assert.Contains(ChatChannelKind.Patron, kinds);
|
||||
Assert.Contains(ChatChannelKind.Vassals, kinds);
|
||||
Assert.Contains(ChatChannelKind.Monarch, kinds);
|
||||
Assert.Contains(ChatChannelKind.Roleplay, kinds);
|
||||
Assert.Contains(ChatChannelKind.Society, kinds);
|
||||
Assert.Contains(ChatChannelKind.Olthoi, kinds);
|
||||
var kinds = new HashSet<ChatChannelKind>(
|
||||
UiChannelMenu.Items.Where(i => i.Channel is not null).Select(i => i.Channel!.Value));
|
||||
foreach (var k in new[]
|
||||
{
|
||||
ChatChannelKind.Say, ChatChannelKind.General, ChatChannelKind.Trade, ChatChannelKind.Lfg,
|
||||
ChatChannelKind.Fellowship, ChatChannelKind.Allegiance, ChatChannelKind.Patron,
|
||||
ChatChannelKind.Vassals, ChatChannelKind.Monarch, ChatChannelKind.Roleplay,
|
||||
ChatChannelKind.Society, ChatChannelKind.Olthoi,
|
||||
})
|
||||
Assert.Contains(k, kinds);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -52,25 +51,50 @@ public class UiChannelMenuTests
|
|||
}
|
||||
|
||||
[Fact]
|
||||
public void OnChannelChanged_FiredWhenSelectionMadeViaEvent()
|
||||
public void Select_LeftColumnItem_FiresChannel()
|
||||
{
|
||||
var menu = new UiChannelMenu { Width = 80f, Height = 18f };
|
||||
|
||||
// Open the popup (click inside the button area — Data2 >= 0).
|
||||
var openEvt = new UiEvent(0, menu, UiEventType.MouseDown, 0, 10, 5);
|
||||
var openEvt = new UiEvent(0, menu, UiEventType.MouseDown, 0, 10, 5); // open
|
||||
Assert.True(menu.OnEvent(openEvt));
|
||||
|
||||
// Click on the second item (General) in the upward popup.
|
||||
// Popup renders UPWARD: top = -(12 * 16) = -192.
|
||||
// Item i=1 (General) occupies y in [-192 + 16, -192 + 32) = [-176, -160).
|
||||
// A click at ly = -176 + 8 = -168 hits item index = (int)((-168 + 192) / 16) = (int)(24/16) = 1.
|
||||
ChatChannelKind? fired = null;
|
||||
menu.OnChannelChanged = k => fired = k;
|
||||
|
||||
var selectEvt = new UiEvent(0, menu, UiEventType.MouseDown, 0, 10, -168);
|
||||
Assert.True(menu.OnEvent(selectEvt));
|
||||
// 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);
|
||||
}
|
||||
|
||||
Assert.Equal(ChatChannelKind.General, fired);
|
||||
Assert.Equal(ChatChannelKind.General, menu.Selected);
|
||||
[Fact]
|
||||
public void Select_RightColumnItem_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 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);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Select_SpecialItem_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++;
|
||||
|
||||
// "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
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue