feat(ui): Slice 5.4 — the authored vendor browse panel (LayoutDesc 0x21000012)
Some checks are pending
Headless portability / portable-headless (ubuntu-latest) (push) Waiting to run
Headless portability / portable-headless (windows-latest) (push) Waiting to run
Headless portability / linux-graphical (push) Waiting to run
Headless portability / linux-vulkan (push) Waiting to run
Some checks are pending
Headless portability / portable-headless (ubuntu-latest) (push) Waiting to run
Headless portability / portable-headless (windows-latest) (push) Waiting to run
Headless portability / linux-graphical (push) Waiting to run
Headless portability / linux-vulkan (push) Waiting to run
The vendor window is retail's own: LayoutDesc 0x21000012, root 0x100000B7, found by enumerating all 101 layouts for the one containing both known tab controls and clinched by the root's Type 0x10000017 — the literal UIElement::RegisterElementClass id for gmVendorUI (pc:202075). Discovery evidence and the D0 read live in the research doc's new §B.4. D0 corrected two assumptions: retail's category "tabs" are a UiMenu DROPDOWN fed by a hardcoded 18-row ordered category table (ported bit-for-bit against our ItemType enum; list always scoped to exactly one category, first-present wins, selection preserved across refresh per retail's clamp), and the layout authors THREE tabs — Items (browse, this slice), Buying and Selling (staged-transaction review, Slice 6) — decision 4's "browse/Buy tab" names the Items tab retail's mode-2 OpenTab opens. The non-default tabs render and switch pages but stay inert, fenced in comments. VendorUiController mounts Items: category dropdown, icon-cell item row with the retained scrollbar, per-unit retail pricing via VendorPricing.SellPrice (the vendor-stock path VendorProfile:: VendorSellPrice feeds), name/cost on selection. The panel is a pure projection of VendorState — opens on populate, closes on clear; the close button's VendorState.Close() is its only permitted mutation. Nothing on the wire. AP-110 narrowed (vendor leaves the absent-panels list); AP-161 files the precise Slice-6 remainder (Buying/Selling unwired, Buy/Add buttons, InqAcceptability). Twelve controller tests on a real-dat fixture. Clean-room complete solution: 11,323 passed / 4 skipped / 0 failed. Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
609a2dfda0
commit
c721830e71
10 changed files with 17024 additions and 2 deletions
|
|
@ -168,6 +168,10 @@ public sealed record AppraisalRuntimeBindings(
|
|||
Action<uint, string> SendSetInscription,
|
||||
Action<string> DisplaySystemMessage);
|
||||
|
||||
public sealed record VendorRuntimeBindings(
|
||||
VendorState State,
|
||||
Func<ItemType, uint, uint, uint, uint, uint> ResolveIcon);
|
||||
|
||||
public sealed record RetailUiRuntimeBindings(
|
||||
UiHost Host,
|
||||
RetailUiAssets Assets,
|
||||
|
|
@ -184,6 +188,7 @@ public sealed record RetailUiRuntimeBindings(
|
|||
CharacterRuntimeBindings Character,
|
||||
InventoryRuntimeBindings Inventory,
|
||||
ExternalContainerRuntimeBindings ExternalContainer,
|
||||
VendorRuntimeBindings Vendor,
|
||||
RetailUiCursorBindings Cursor,
|
||||
ConfirmationRuntimeBindings Confirmations,
|
||||
AppraisalRuntimeBindings Appraisal,
|
||||
|
|
@ -255,6 +260,7 @@ public sealed class RetailUiRuntime : IDisposable
|
|||
MountPlugins();
|
||||
MountInventory();
|
||||
MountExternalContainer();
|
||||
MountVendor();
|
||||
MountItemCooldowns();
|
||||
Host.WindowManager.WindowVisibilityChanged += OnWindowVisibilityChanged;
|
||||
BindToolbarPanelButtons();
|
||||
|
|
@ -272,6 +278,7 @@ public sealed class RetailUiRuntime : IDisposable
|
|||
WindowNames.Combat,
|
||||
WindowNames.JumpPowerbar,
|
||||
WindowNames.ExternalContainer,
|
||||
WindowNames.Vendor,
|
||||
]);
|
||||
}
|
||||
|
||||
|
|
@ -322,6 +329,7 @@ public sealed class RetailUiRuntime : IDisposable
|
|||
public InventoryController? InventoryPanelController { get; private set; }
|
||||
public RetailDialogFactory? DialogFactory { get; private set; }
|
||||
public ExternalContainerController? ExternalContainerController { get; private set; }
|
||||
public VendorUiController? VendorController { get; private set; }
|
||||
|
||||
public static RetailUiRuntime Mount(RetailUiRuntimeBindings bindings)
|
||||
{
|
||||
|
|
@ -1897,6 +1905,75 @@ public sealed class RetailUiRuntime : IDisposable
|
|||
"[D.2b] retail external-container strip mounted from LayoutDesc 0x21000008.");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Slice 5.4: the retail vendor "Items" browse tab (LayoutDesc
|
||||
/// 0x21000012, root 0x100000B7 — see
|
||||
/// docs/research/2026-08-08-slice5-vendor-browse-research.md §B.3/§B.4
|
||||
/// for the discovery + D0 category-filter read). Two-step mount
|
||||
/// (Mount then Bind then AttachController) matches
|
||||
/// <see cref="MountExternalContainer"/>: the controller needs the
|
||||
/// <see cref="RetailWindowHandle"/> to Show/Hide itself as
|
||||
/// <see cref="VendorState"/> opens/closes, so the handle must exist
|
||||
/// before the controller is constructed.
|
||||
/// </summary>
|
||||
private void MountVendor()
|
||||
{
|
||||
ImportedLayout? layout;
|
||||
lock (_bindings.Assets.DatLock)
|
||||
{
|
||||
layout = LayoutImporter.Import(
|
||||
_bindings.Assets.Dats,
|
||||
VendorUiController.LayoutId,
|
||||
VendorUiController.RootId,
|
||||
_bindings.Assets.ResolveSprite,
|
||||
_bindings.Assets.DefaultFont,
|
||||
_bindings.Assets.ResolveFont);
|
||||
}
|
||||
if (layout is null)
|
||||
{
|
||||
Console.WriteLine("[M4] vendor: LayoutDesc 0x21000012 root 0x100000B7 not found.");
|
||||
return;
|
||||
}
|
||||
|
||||
UiElement root = layout.Root;
|
||||
RetailWindowHandle handle = RetailWindowFrame.Mount(
|
||||
Host.Root,
|
||||
root,
|
||||
_bindings.Assets.ResolveSprite,
|
||||
new RetailWindowFrame.Options
|
||||
{
|
||||
WindowName = WindowNames.Vendor,
|
||||
// Root 0x100000B7 authors only the tiled center surface
|
||||
// (backdrop element 0x1000008D provides its own fill); the
|
||||
// common floating-window bevel surrounds it — same shape as
|
||||
// ExternalContainerController's 0x21000008/0x10000063.
|
||||
Chrome = RetailWindowChrome.NineSlice,
|
||||
Left = root.Left,
|
||||
Top = root.Top,
|
||||
ContentWidth = root.Width,
|
||||
ContentHeight = root.Height,
|
||||
MinWidth = root.Width,
|
||||
MinHeight = root.Height,
|
||||
Visible = false,
|
||||
ResizeX = false,
|
||||
ResizeY = false,
|
||||
ConstrainDragToParent = true,
|
||||
ConstrainResizeToParent = true,
|
||||
DrawChromeCenter = false,
|
||||
});
|
||||
|
||||
VendorRuntimeBindings b = _bindings.Vendor;
|
||||
VendorController = VendorUiController.Bind(layout, b.State, handle, b.ResolveIcon);
|
||||
if (VendorController is null)
|
||||
{
|
||||
Console.WriteLine("[M4] vendor: required authored controls are missing.");
|
||||
return;
|
||||
}
|
||||
|
||||
Host.WindowManager.AttachController(WindowNames.Vendor, VendorController);
|
||||
Console.WriteLine("[M4] retail vendor browse panel mounted from LayoutDesc 0x21000012.");
|
||||
}
|
||||
|
||||
private void MountItemCooldowns()
|
||||
{
|
||||
ItemCooldownAssets? assets;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue