diff --git a/src/AcDream.App/UI/Layout/PaperdollController.cs b/src/AcDream.App/UI/Layout/PaperdollController.cs index 41763f01..82c47758 100644 --- a/src/AcDream.App/UI/Layout/PaperdollController.cs +++ b/src/AcDream.App/UI/Layout/PaperdollController.cs @@ -14,6 +14,32 @@ namespace AcDream.App.UI.Layout; /// public sealed class PaperdollController : IItemListDragHandler { + // ── Slots-toggle public surface ─────────────────────────────────────────────────────────────── + /// + /// The 9 armor-slot element-ids whose Visible state the Slots button (0x100005BE) toggles. + /// Doll-view: hidden. Slot-view: shown. Source: gmPaperDollUI::ListenToElementMessage decomp + /// 175674-175706 — these are the only 9 ids that element flips; the other ~12 equip slots + /// (jewelry, weapon, wrists, feet…) are NON-armor and remain visible in both views. + /// + public static readonly uint[] ArmorSlotElementIds = + { + 0x100005abu, 0x100005acu, 0x100005adu, 0x100005aeu, 0x100005afu, + 0x100005b0u, 0x100005b1u, 0x100005b2u, 0x100005b3u, + }; + + /// + /// View-state model for the Slots toggle (pure logic, no UI coupling). + /// Default is doll-view (SlotView == false): the 3-D character is visible, + /// the 9 armor slots are hidden. Calling Toggle() alternates between views. + /// + public sealed class PaperdollViewState + { + public bool SlotView { get; private set; } // false = doll-view (default) + public bool DollVisible => !SlotView; + public bool ArmorSlotsVisible => SlotView; + public void Toggle() => SlotView = !SlotView; + } + // WEAPON_READY_SLOT_LOC (acclient.h:3235): the weapon-hand doll slot accepts any wieldable weapon. private const EquipMask WeaponSlotMask = EquipMask.MeleeWeapon | EquipMask.MissileWeapon | EquipMask.Held | EquipMask.TwoHanded; @@ -51,6 +77,11 @@ public sealed class PaperdollController : IItemListDragHandler private readonly Action? _sendWield; // (itemGuid, equipMask) → GetAndWieldItem 0x001A private readonly List<(EquipMask Mask, UiItemList List)> _slots = new(); + // ── Slots-toggle state ──────────────────────────────────────────────────────────────────────── + private readonly PaperdollViewState _viewState = new(); + private readonly List _armorSlots = new(); + private UiElement? _dollViewport; // UiViewport wired in Slice 3; UiElement? keeps this task independent + private PaperdollController( ImportedLayout layout, ClientObjectTable objects, Func playerGuid, Func iconIds, Action? sendWield, @@ -80,6 +111,17 @@ public sealed class PaperdollController : IItemListDragHandler _objects.ObjectRemoved += OnObjectChanged; _objects.ObjectUpdated += OnObjectChanged; + // ── Slots-toggle wiring ─────────────────────────────────────────────────────────────────── + foreach (var id in ArmorSlotElementIds) + if (layout.FindElement(id) is UiItemList armor) _armorSlots.Add(armor); + + _dollViewport = layout.FindElement(0x100001D5u); // doll viewport (may be null until Slice 3) + + if (layout.FindElement(0x100005BEu) is UiButton slotsBtn) + slotsBtn.OnClick = () => { _viewState.Toggle(); ApplyView(); }; + + ApplyView(); // initial state = doll-view (armor slots hidden) + Populate(); } @@ -161,6 +203,17 @@ public sealed class PaperdollController : IItemListDragHandler _sendWield?.Invoke(payload.ObjId, (uint)wieldMask); } + /// + /// Pushes the current onto the live widgets: + /// hides/shows the doll viewport and each armor slot according to the toggle. + /// Called once at construction (doll-view default) and on every button click. + /// + private void ApplyView() + { + if (_dollViewport is not null) _dollViewport.Visible = _viewState.DollVisible; + foreach (var a in _armorSlots) a.Visible = _viewState.ArmorSlotsVisible; + } + /// Detach event handlers (idempotent). public void Dispose() { diff --git a/tests/AcDream.App.Tests/UI/Layout/PaperdollToggleTests.cs b/tests/AcDream.App.Tests/UI/Layout/PaperdollToggleTests.cs new file mode 100644 index 00000000..7241dd01 --- /dev/null +++ b/tests/AcDream.App.Tests/UI/Layout/PaperdollToggleTests.cs @@ -0,0 +1,44 @@ +using AcDream.App.UI.Layout; +using Xunit; + +namespace AcDream.App.Tests.UI.Layout; + +public class PaperdollToggleTests +{ + // The 9 armor element-ids that gmPaperDollUI::ListenToElementMessage flips + // (decomp 175674-175706). Doll-view hides these; slot-view shows them. + private static readonly uint[] ArmorIds = + { + 0x100005abu, 0x100005acu, 0x100005adu, 0x100005aeu, 0x100005afu, + 0x100005b0u, 0x100005b1u, 0x100005b2u, 0x100005b3u, + }; + + [Fact] + public void ArmorSlotIds_match_the_decomp_nine() + { + Assert.Equal(ArmorIds, PaperdollController.ArmorSlotElementIds); + } + + [Fact] + public void DollView_default_shows_doll_hides_armor() + { + var v = new PaperdollController.PaperdollViewState(); + Assert.False(v.SlotView); + Assert.True(v.DollVisible); + Assert.False(v.ArmorSlotsVisible); + } + + [Fact] + public void Toggle_to_slotview_hides_doll_shows_armor_and_back() + { + var v = new PaperdollController.PaperdollViewState(); + v.Toggle(); + Assert.True(v.SlotView); + Assert.False(v.DollVisible); + Assert.True(v.ArmorSlotsVisible); + v.Toggle(); + Assert.False(v.SlotView); + Assert.True(v.DollVisible); + Assert.False(v.ArmorSlotsVisible); + } +}