diff --git a/src/AcDream.App/UI/UiItemSlot.cs b/src/AcDream.App/UI/UiItemSlot.cs index 35973c09..a3c95895 100644 --- a/src/AcDream.App/UI/UiItemSlot.cs +++ b/src/AcDream.App/UI/UiItemSlot.cs @@ -38,6 +38,23 @@ public sealed class UiItemSlot : UiElement /// state id 0x10000040). public uint DragRejectSprite { get; set; } = 0x060011F8u; + /// True when this cell is the OPEN container (its contents fill the grid). Draws the + /// open-container triangle. Port of UIElement_ItemList::UpdateOpenContainerIndicator + /// (0x004e3070) → SetOpenContainerState (0x004e1200): shown when item.itemID == openContainerId. + public bool IsOpenContainer { get; set; } + /// Open-container triangle sprite (element 0x10000450 on the container prototype + /// 0x1000033F). Configurable; guard id != 0 before resolving. + public uint OpenContainerSprite { get; set; } = 0x06005D9Cu; + + /// True when this cell is the SELECTED item. Draws the green/yellow selection square. + /// Port of UIElement_ItemList::ItemList_SetSelectedItem (0x004e2fe0) → SetSelectedState + /// (0x004e1240): shown when item.itemID == selectedItemId. Uniform across item + container cells. + public bool Selected { get; set; } + /// Selected-item square sprite (element 0x10000342 on the 32×32 item prototype + /// 0x10000341; pixel-confirmed green/yellow frame). Drawn as a procedural overlay so it renders + /// on the 36×36 container cell too (whose prototype lacks the square child). + public uint SelectedSprite { get; set; } = 0x06004D21u; + /// Accept/reject overlay state while a drag hovers this cell. public enum DragAcceptState { None, Accept, Reject } private DragAcceptState _dragAccept = DragAcceptState.None; @@ -220,6 +237,23 @@ public sealed class UiItemSlot : UiElement } } + // Open-container triangle (retail SetOpenContainerState 0x004e1200) + selected-item square + // (retail SetSelectedState 0x004e1240). Drawn procedurally like the digit/drag overlays; + // guard id != 0 BEFORE resolving (feedback_ui_resolve_zero_magenta). Triangle under the + // square; both under the transient drag overlay below. + if (IsOpenContainer && SpriteResolve is not null && OpenContainerSprite != 0) + { + var (tex, _, _) = SpriteResolve(OpenContainerSprite); + if (tex != 0) + ctx.DrawSprite(tex, 0f, 0f, Width, Height, 0f, 0f, 1f, 1f, Vector4.One); + } + if (Selected && SpriteResolve is not null && SelectedSprite != 0) + { + var (tex, _, _) = SpriteResolve(SelectedSprite); + if (tex != 0) + ctx.DrawSprite(tex, 0f, 0f, Width, Height, 0f, 0f, 1f, 1f, Vector4.One); + } + // Drag-rollover accept/reject frame (retail SetDragAcceptState 0x10000041/40). // Guard id != 0 BEFORE resolving — resolve(0) returns the 1×1 magenta placeholder // with a non-zero GL handle (feedback_ui_resolve_zero_magenta). diff --git a/tests/AcDream.App.Tests/UI/UiItemSlotTests.cs b/tests/AcDream.App.Tests/UI/UiItemSlotTests.cs index f99e9dc5..c0e91753 100644 --- a/tests/AcDream.App.Tests/UI/UiItemSlotTests.cs +++ b/tests/AcDream.App.Tests/UI/UiItemSlotTests.cs @@ -141,4 +141,20 @@ public class UiItemSlotTests s.SetShortcutNum(0, peace: true); Assert.Null(s.ActiveDigitArray()); } + + // ── Container-switching indicator overlays (decomp SetOpenContainerState 0x004e1200 / + // SetSelectedState 0x004e1240) ─────────────────────────────────────────────────────── + [Fact] + public void Selected_defaultsFalse() => Assert.False(new UiItemSlot().Selected); + + [Fact] + public void IsOpenContainer_defaultsFalse() => Assert.False(new UiItemSlot().IsOpenContainer); + + [Fact] + public void SelectedSprite_default_isGreenYellowSquare() + => Assert.Equal(0x06004D21u, new UiItemSlot().SelectedSprite); + + [Fact] + public void OpenContainerSprite_default_isTriangle() + => Assert.Equal(0x06005D9Cu, new UiItemSlot().OpenContainerSprite); }