From 1270596f305de51c133538d12a4dab639e9fd541 Mon Sep 17 00:00:00 2001 From: Erik Date: Tue, 16 Jun 2026 22:21:21 +0200 Subject: [PATCH] feat(D.5.1): UiItemSlot widget (UIElement_UIItem cell port) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Behavioral leaf widget for the toolbar item cell. Draws the empty-slot sprite (0x060074CF) when unbound; draws the pre-composited icon texture when a weenie is bound via SetItem(). ConsumesDatChildren=true prevents the LayoutImporter from double-building the dat sub-elements. SpriteResolve is configurable so paperdoll equip slots can swap in per-slot silhouettes later. No Clicked/OnEvent — that wiring comes in Task 8 (ToolbarController). Co-Authored-By: Claude Opus 4.8 (1M context) --- src/AcDream.App/UI/UiItemSlot.cs | 54 +++++++++++++++++++ tests/AcDream.App.Tests/UI/UiItemSlotTests.cs | 31 +++++++++++ 2 files changed, 85 insertions(+) create mode 100644 src/AcDream.App/UI/UiItemSlot.cs create mode 100644 tests/AcDream.App.Tests/UI/UiItemSlotTests.cs diff --git a/src/AcDream.App/UI/UiItemSlot.cs b/src/AcDream.App/UI/UiItemSlot.cs new file mode 100644 index 00000000..ebd1f33e --- /dev/null +++ b/src/AcDream.App/UI/UiItemSlot.cs @@ -0,0 +1,54 @@ +using System; +using System.Numerics; + +namespace AcDream.App.UI; + +/// +/// One item-in-a-slot cell (port of retail UIElement_UIItem, class 0x10000032). +/// A behavioral LEAF: it draws the empty-slot sprite when unbound, else a +/// pre-composited icon texture (set by the controller). Holds the bound weenie +/// guid (retail UIElement_UIItem::itemID, +0x5FC). +/// +public sealed class UiItemSlot : UiElement +{ + public UiItemSlot() { ClickThrough = false; } + + public override bool ConsumesDatChildren => true; + + /// Bound weenie guid (0 = empty). Retail UIElement_UIItem::itemID. + public uint ItemId { get; private set; } + + /// Pre-composited icon GL texture for the bound item (0 = none). + public uint IconTexture { get; private set; } + + /// Empty-slot sprite. Default = the generic toolbar empty-slot border + /// 0x060074CF (uiitem template 0x21000037, state ItemSlot_Empty). Configurable so + /// paperdoll equip slots can use their per-slot silhouettes later. + public uint EmptySprite { get; set; } = 0x060074CFu; + + /// RenderSurface id -> (GL texture, w, h). Set by the factory/controller. + public Func? SpriteResolve { get; set; } + + public void SetItem(uint itemId, uint iconTexture) + { + ItemId = itemId; + IconTexture = iconTexture; + } + + public void Clear() { ItemId = 0; IconTexture = 0; } + + protected override void OnDraw(UiRenderContext ctx) + { + if (ItemId != 0 && IconTexture != 0) + { + ctx.DrawSprite(IconTexture, 0f, 0f, Width, Height, 0f, 0f, 1f, 1f, Vector4.One); + return; + } + if (SpriteResolve is not null && EmptySprite != 0) + { + var (tex, _, _) = SpriteResolve(EmptySprite); + if (tex != 0) + ctx.DrawSprite(tex, 0f, 0f, Width, Height, 0f, 0f, 1f, 1f, Vector4.One); + } + } +} diff --git a/tests/AcDream.App.Tests/UI/UiItemSlotTests.cs b/tests/AcDream.App.Tests/UI/UiItemSlotTests.cs new file mode 100644 index 00000000..381c281c --- /dev/null +++ b/tests/AcDream.App.Tests/UI/UiItemSlotTests.cs @@ -0,0 +1,31 @@ +using AcDream.App.UI; + +namespace AcDream.App.Tests.UI; + +public class UiItemSlotTests +{ + [Fact] + public void IsLeafWidget() + => Assert.True(new UiItemSlot().ConsumesDatChildren); + + [Fact] + public void DefaultEmptySprite_isToolbarBorder() + => Assert.Equal(0x060074CFu, new UiItemSlot().EmptySprite); + + [Fact] + public void Empty_whenNoItem() + { + var s = new UiItemSlot(); + Assert.Equal(0u, s.ItemId); + Assert.Equal(0u, s.IconTexture); + } + + [Fact] + public void SetItem_setsIdAndTexture() + { + var s = new UiItemSlot(); + s.SetItem(0x5001u, 0x99u); + Assert.Equal(0x5001u, s.ItemId); + Assert.Equal(0x99u, s.IconTexture); + } +}