feat(D.2b): UiMenu (Type 6) — generic dropdown; channel knowledge moves to controller (widget-generalization Task 4)
UiChannelMenu → UiMenu: removed ChatChannelKind, the 14-item array, the button-text map, and the availability default. Generic surface: MenuItem (label + object? Payload), Selected (object?), OnSelect, EnabledProvider, ButtonLabelProvider, RowsPerColumn/RowHeight/ColumnWidth (all settable). All draw/event mechanics unchanged — same popup geometry, same click coordinates, same 8-piece bevel, same 3-slice button face. ChatWindowController gains ChannelItems[], ChannelButtonLabel(), and ChannelAvailable() (verbatim from old widget), and populates the factory-built Type-6 UiMenu via find-by-id rather than constructing a replacement widget. The Menu property type is now UiMenu. OnChannelChanged wrap replaced with the generic OnSelect wrap for the ReflowInputRow hook. DatWidgetFactory registers Type 6 → new UiMenu(). Tests: UiChannelMenuTests → UiMenuTests (10 tests, all green); factory Type6 test added; ChatWindowControllerTests updated to use OnSelect. Divergence register: AP-42 added (flat item model vs retail nested-submenu MakePopup @0x46d310 — latent, unreachable through the chat menu). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
805ab5f40b
commit
955f7a69a8
8 changed files with 302 additions and 252 deletions
|
|
@ -74,12 +74,52 @@ public sealed class ChatWindowController
|
|||
public UiScrollbar Scrollbar { get; private set; } = null!;
|
||||
|
||||
/// <summary>Channel-selector menu widget.</summary>
|
||||
public UiChannelMenu Menu { get; private set; } = null!;
|
||||
public UiMenu Menu { get; private set; } = null!;
|
||||
|
||||
// ── Private state ──────────────────────────────────────────────────────
|
||||
|
||||
private ChatChannelKind _activeChannel = ChatChannelKind.Say;
|
||||
|
||||
// ── Channel knowledge (ported from old UiChannelMenu — gmMainChatUI::InitTalkFocusMenu @0x4cdc50) ──
|
||||
|
||||
private static readonly (string Label, ChatChannelKind? Channel)[] ChannelItems =
|
||||
{
|
||||
("Squelch (ignore)", null),
|
||||
("Tell to Selected", null),
|
||||
("Chat to All", ChatChannelKind.Say),
|
||||
("Tell to Fellows", ChatChannelKind.Fellowship),
|
||||
("Tell to General Chat", ChatChannelKind.General),
|
||||
("Tell to LFG Chat", ChatChannelKind.Lfg),
|
||||
("Tell to Society Chat", ChatChannelKind.Society),
|
||||
("Tell to Monarch", ChatChannelKind.Monarch),
|
||||
("Tell to Patron", ChatChannelKind.Patron),
|
||||
("Tell to Vassals", ChatChannelKind.Vassals),
|
||||
("Tell to Allegiance", ChatChannelKind.Allegiance),
|
||||
("Tell to Trade Chat", ChatChannelKind.Trade),
|
||||
("Tell to Roleplay Chat", ChatChannelKind.Roleplay),
|
||||
("Tell to Olthoi Chat", ChatChannelKind.Olthoi),
|
||||
};
|
||||
|
||||
private static string ChannelButtonLabel(ChatChannelKind k) => k switch
|
||||
{
|
||||
ChatChannelKind.Say => "Chat",
|
||||
ChatChannelKind.General => "General",
|
||||
ChatChannelKind.Trade => "Trade",
|
||||
ChatChannelKind.Lfg => "LFG",
|
||||
ChatChannelKind.Fellowship => "Fellow",
|
||||
ChatChannelKind.Allegiance => "Alleg",
|
||||
ChatChannelKind.Patron => "Patron",
|
||||
ChatChannelKind.Vassals => "Vassals",
|
||||
ChatChannelKind.Monarch => "Monarch",
|
||||
ChatChannelKind.Roleplay => "Roleplay",
|
||||
ChatChannelKind.Society => "Society",
|
||||
ChatChannelKind.Olthoi => "Olthoi",
|
||||
_ => "Chat",
|
||||
};
|
||||
|
||||
private static bool ChannelAvailable(ChatChannelKind k)
|
||||
=> k is ChatChannelKind.Say or ChatChannelKind.General or ChatChannelKind.Trade or ChatChannelKind.Lfg;
|
||||
|
||||
/// <summary>Window height before maximize (stored to restore on un-maximize).</summary>
|
||||
private float _normalHeight;
|
||||
/// <summary>Window top before maximize.</summary>
|
||||
|
|
@ -110,7 +150,7 @@ public sealed class ChatWindowController
|
|||
/// <param name="debugFont">Fallback debug bitmap font (used when
|
||||
/// <paramref name="datFont"/> is null).</param>
|
||||
/// <param name="resolve">Dat RenderSurface id → (GL tex handle, px width, px height).
|
||||
/// Forwarded to <see cref="UiScrollbar"/> and <see cref="UiChannelMenu"/>.</param>
|
||||
/// Forwarded to <see cref="UiScrollbar"/> and <see cref="UiMenu"/>.</param>
|
||||
public static ChatWindowController? Bind(
|
||||
ElementInfo rootInfo,
|
||||
ImportedLayout layout,
|
||||
|
|
@ -216,29 +256,23 @@ public sealed class ChatWindowController
|
|||
c.Scrollbar = bar;
|
||||
}
|
||||
|
||||
// ── Channel menu — replace the imported menu placeholder ──────────
|
||||
var menuEl = layout.FindElement(MenuId);
|
||||
if (menuEl?.Parent is { } menuParent)
|
||||
// ── Channel menu — bind the factory-built Type-6 UiMenu ──────────
|
||||
if (layout.FindElement(MenuId) is UiMenu menu)
|
||||
{
|
||||
c.Menu = new UiChannelMenu
|
||||
menu.DatFont = datFont; menu.Font = debugFont; menu.SpriteResolve = resolve;
|
||||
menu.NormalSprite = MenuNormal; menu.PressedSprite = MenuPressed;
|
||||
menu.PopupBgSprite = MenuPopupBg;
|
||||
menu.ItemNormalSprite = MenuItemRow; menu.ItemHighlightSprite = MenuItemSelected;
|
||||
menu.Items = System.Array.ConvertAll(ChannelItems,
|
||||
t => new UiMenu.MenuItem(t.Label, (object?)t.Channel));
|
||||
menu.Selected = (object?)c._activeChannel;
|
||||
menu.EnabledProvider = p => p is not ChatChannelKind ch || ChannelAvailable(ch);
|
||||
menu.ButtonLabelProvider = () => ChannelButtonLabel(c._activeChannel);
|
||||
menu.OnSelect = p =>
|
||||
{
|
||||
Left = menuEl.Left,
|
||||
Top = menuEl.Top,
|
||||
Width = menuEl.Width,
|
||||
Height = menuEl.Height,
|
||||
Anchors = menuEl.Anchors,
|
||||
DatFont = datFont,
|
||||
Font = debugFont,
|
||||
SpriteResolve = resolve,
|
||||
NormalSprite = MenuNormal,
|
||||
PressedSprite = MenuPressed,
|
||||
PopupBgSprite = MenuPopupBg,
|
||||
ItemNormalSprite = MenuItemRow,
|
||||
ItemHighlightSprite = MenuItemSelected,
|
||||
if (p is ChatChannelKind ch) { c._activeChannel = ch; menu.Selected = p; }
|
||||
};
|
||||
c.Menu.OnChannelChanged = k => c._activeChannel = k;
|
||||
menuParent.RemoveChild(menuEl);
|
||||
menuParent.AddChild(c.Menu);
|
||||
c.Menu = menu;
|
||||
}
|
||||
|
||||
// ── Send button — Enter-alternate submit trigger ──────────────────
|
||||
|
|
@ -269,8 +303,8 @@ public sealed class ChatWindowController
|
|||
c.Input.Width = System.MathF.Max(40f, inputRight - c.Input.Left);
|
||||
c.Input.ResetAnchorCapture();
|
||||
}
|
||||
var onChanged = c.Menu.OnChannelChanged;
|
||||
c.Menu.OnChannelChanged = k => { onChanged?.Invoke(k); ReflowInputRow(); };
|
||||
var onSelect = c.Menu.OnSelect;
|
||||
c.Menu.OnSelect = p => { onSelect?.Invoke(p); ReflowInputRow(); };
|
||||
ReflowInputRow();
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue