Files
benilla-octo/crates/benilla-app/assets/ui/BagFrame.xml
T
SamandClaude Fable 5 348f55e37e four extra action bars as options, and the bottom band gets one owner
- the four extra action bars become per-character options, off by default, on a new Action Bars options page — the toggles ride the server's own byte (`CMSG_SET_ACTIONBAR_TOGGLES` into `PLAYER_FIELD_BYTES`), so a fresh character's zero is the default for free; turning the bottom-left bar off gives the stance bar its previously unbuilt unraised look, end-caps and 64×64 rings
- bag windows stop drawing straight through a raised bottom bar: the bottom band has ONE owner — the manage pass — the bag stack joins it through a listener seam, and a guard test walks all eight raisable-bar combinations across 24 bar/tenant pairs and refuses overlap
- the item-push card takes the reference's real geometry, closing the gap 0887 measured
- resizable addon windows stop at their bounds instead of pinning and walking across the screen: four per-frame f32s with 0.0 the disable sentinel, the clamp on the interactive drag only with the residual rebated out of the drag delta, `<ResizeBounds>` in XML honoured — and `SetFontString`'s three raises land exactly as the bytes have them

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-21 18:12:19 +07:00

2219 lines
141 KiB
XML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<Ui>
<!--
benilla's bag windows (decision 0068 T2), RE-SKINNED to the real 1.12 ContainerFrame
(dressing arc, decision 0084; whole-space generalization, decision 0216 slice 2): the
geometry and art below are QUOTED from the real `ContainerFrame.xml`/`ContainerFrame.lua`
(scratchpad `ref-framexml/ContainerFrame.xml`, `ContainerFrame.lua`; line citations inline),
turned onto our own container feed/intents. FIVE windows now — the backpack (bag id 0, 16
slots, its own hand-authored block below, unchanged in shape) plus the four equipped bags
(ids 1..4, `BenillaBagWindowTemplate`, up to 20 slots each) — the ContainerFrameTemplate
generalization this file's header always said was coming.
Every window shares the same handler functions, deriving its bag id from
`button:GetParent():GetID()`/`frame:GetID()` (the ref's own `this:GetParent():GetID()`
pattern, ContainerFrame.lua:88 &c.) rather than a hardcoded global — the XML `id=` attribute
is applied (SetID) before any child's OnLoad fires (the loader's bottom-up materialize order,
`loader/mod.rs` step 5 before step 7), so a slot button's OnLoad can already read its
parent's id. All state arrives through the container snapshot
(GetContainerNumSlots/GetContainerItemInfo) and refreshes on BAG_UPDATE/ITEM_LOCK_CHANGED.
LEFT-click drives the cursor drag (PickupContainerItem → pick up / place / swap, the app's
move drain → CMSG_SWAP_INV_ITEM/CMSG_SWAP_ITEM); RIGHT-click cancels a held cursor, else uses
the slot (UseContainerItem → CMSG_USE_ITEM / auto-equip, or a merchant sell); SHIFT+left-click
opens the stack-split spinner (the reference fork, ContainerFrame.lua:567-577).
REAL-CLIENT DIVERGENCES (noted, not bugs):
- The real client scales the whole frame to CONTAINER_SCALE=0.90 (ContainerFrame.lua l.13);
we don't do frame scale, so every window renders ~11% larger. Scaling is uniform over the
frame + its children + the background art, so the slot buttons still seat in the painted
wells — only the on-screen size differs.
- The bag portrait is the C-side SetBagPortaitTexture (ContainerFrame.lua l.422): the backpack
shows the static `Interface\Buttons\Button-Backpack-Up` art, an equipped bag its own item
icon (BenillaBagWindow_Update reads inv slot 19+bagId).
- Bags 1..4 SNUG-FIT to their real row count (BenillaBagWindow_FitBackground): the background
is stitched from the real client's `UI-Bag-Components` top/middle/bottom pieces sized to
ceil(size/4) rows (ContainerFrame.lua ContainerFrame_GenerateFrame, id~=0 branch), so a
6-slot pouch is a 2-row window, not the backpack's 5-row slab. The backpack (id 0) keeps its
own single `UI-BackpackBackground` slab (the id==0 branch), unchanged.
MONEY: reuses the shared BenillaMoney_Set/_Clear purse helpers (they live in MerchantFrame.xml
— decision 0084's reusable money display; the same reuse the loot window makes). At runtime
every FrameXML file is loaded before any event fires, so the helper is defined by the time
BenillaBagWindow_Update first runs; a unit test that loads this file in isolation must also
load MerchantFrame.xml (as the loot tests do). Money is BACKPACK ONLY (ContainerFrame.lua
l.314 shows the MoneyFrame only for id==0) — every other window hides it.
The title inherits GameFontHighlight and the counts NumberFontNormal — both live in the
shipped Fonts.xml (the font-object slice already landed), so they render in the real fonts.
-->
<Script><![CDATA[
-- The five windows' shared slot count cap: the backpack is always exactly 16
-- (GetContainerNumSlots(0) never varies — PACK_SLOTS is a wire constant); the
-- equipped-bag windows show up to this many (5 rows × 4 columns — the reference template's
-- true max is 36, but 20 is vanilla's largest actual equippable bag, and building out to 36
-- slot buttons per window for content that can never exist would be pure waste. Hides the
-- unused tail via BenillaBagSlot_Update's physIndex-past-size branch, same as the backpack.
BENILLA_BAG_WINDOW_MAX_SLOTS = 20
-- The keyring's container id (ref MainMenuBarBagButtons.lua:1) — decision 0765. It is an
-- ordinary container in every respect the seam cares about: the app feeds it like any other
-- bag (its slots are the player descriptor's own 81.., no container object), and every
-- container verb, the cursor, the pending-op lock and the wire drains take it unchanged.
-- What makes it special is only what the reference makes special: the window's art, its
-- open/close sound, and the fact that its capacity is not a wire field.
KEYRING_CONTAINER = -2
-- The keyring's usable slot count (ref GetKeyRingSize, ContainerFrame.lua:773). The
-- reference recomputes a level ladder in Lua (4 / 8 at 40 / 12 at 50 / 16 above 60) that the
-- server enforces identically (Player::GetMaxKeyringSize); benilla computes it once app-side
-- and feeds it as the container's own size, so this reads it back rather than keeping a
-- second copy of the ladder that could drift from the one the feed uses.
function GetKeyRingSize()
return GetContainerNumSlots(KEYRING_CONTAINER)
end
-- BenillaBagSlot_OnLoad(button, physIndex): physIndex is the button's CHAIN position (1 =
-- bottom-right, growing up-and-left — the anchor order below), not the game slot. The ref's
-- own formula (index = size - j + 1, ContainerFrame.lua:426/446) maps chain position → game
-- slot dynamically in BenillaBagSlot_Update, so slot 1 always reads top-left regardless of
-- how many of the window's slots are actually occupied — recomputed every Update (the ref
-- only computes it once, at window generation; recomputing is a strict robustness win for
-- the rare live bag-swap case, not a behavior change for a size that never moves).
function BenillaBagSlot_OnLoad(button, physIndex)
button.physIndex = physIndex
button.icon = getglobal(button:GetName() .. "Icon")
button.count = getglobal(button:GetName() .. "Count")
-- Both buttons must reach OnClick (the default set is LeftButtonUp only): LEFT-click is the
-- pickup gesture (PickupContainerItem — pick up / place / swap on the cursor); RIGHT-click
-- cancels a held cursor, else uses the slot (the C side sells it when a merchant is open —
-- our container drain forks).
button:RegisterForClicks("LeftButtonUp", "RightButtonUp")
-- The drag trio (decision 0216 §3): click-carry and drag are ONE code path in the
-- reference (ContainerFrame.xml:40-55 routes OnDragStart/OnReceiveDrag to the same
-- OnClick("LeftButton", ignoreModifiers=1) the plain click uses) — see
-- BenillaBagSlot_OnClick below for the identical routing (its own `ignoreModifiers`
-- suppresses the shift-split fork, the reference's own contract for drag/receive).
button:RegisterForDrag("LeftButton")
BenillaBagSlot_Update(button)
end
function BenillaBagSlot_OnClick(button, mouseButton, ignoreModifiers)
if button.slot == nil then return end -- a hidden button (physIndex past the bag's size)
local bagId = button:GetParent():GetID()
if mouseButton == "LeftButton" then
-- The reference's own five returns (ContainerFrame.lua:241) — decision 1199
-- put this binding back on its 1.12 shape.
local texture, itemCount, locked, quality, readable =
GetContainerItemInfo(bagId, button.slot)
-- The modifier fork, the reference's own order (ContainerFrame.lua:563-579;
-- BankFrame.lua:141-152 for a bank slot, which is this same handler at container
-- -1). `ignoreModifiers` (the drag-trio calls, both true) suppresses it whole —
-- the reference's drag/receive contract.
--
-- CTRL: preview the item on your own character in the dressing room (decision 1060).
if IsControlKeyDown() and not ignoreModifiers then
DressUpItemLink(GetContainerItemLink(bagId, button.slot))
return
end
-- SHIFT: post the item's link into the chat edit box if it's open, ELSE split the
-- stack. The ref's own `else` (l.567-577) — and note it gates on `IsShown()` here
-- where its siblings use `IsVisible()`; ours are the same thing (the edit box's only
-- parent is UIParent), so the ref's own word is quoted at each site.
if IsShiftKeyDown() and not ignoreModifiers then
-- The visibility test is the REF'S fork, not the helper's: with chat open this
-- arm never splits, even in the one benilla state where nothing can be posted
-- (an unresolved template — `BenillaChatEdit_InsertLink`'s own note).
if ChatFrameEditBox and ChatFrameEditBox:IsShown() then
BenillaChatEdit_InsertLink(GetContainerItemLink(bagId, button.slot))
return
end
if texture ~= nil and not locked and (itemCount or 1) >= 2 then
button.SplitStack = function(btn, n)
SplitContainerItem(bagId, btn.slot, n)
BenillaBagWindow_Repaint(btn:GetParent())
end
-- Ref ContainerFrameItemButton_OnLoad/_OnClick (ContainerFrame.lua:558-560,
-- 573-576): the owner's own SplitStack closure carries the bag/slot so
-- StackSplitFrameOkay_Click's generic `owner.SplitStack(owner, split)` call
-- needs no window-specific knowledge. SplitContainerItem lives in cursor.rs
-- rather than container.rs — it is the one container-named verb whose
-- payload is the cursor's, not a bag's — but like every container verb it is
-- a plain top-level global (decision 1198).
OpenStackSplitFrame(itemCount, button, "BOTTOMRIGHT", "TOPRIGHT")
end
return
end
-- A plain click hides any open split frame (ref ContainerFrame.lua:581) before the
-- ordinary pickup/place proceeds.
local split = getglobal("StackSplitFrame")
if split then split:Hide() end
-- PickupContainerItem is the single drag entry point (real client): an empty cursor
-- picks this slot up; a held cursor places/swaps into it (same slot = cancel). Hide the
-- hover tooltip (the item just left the slot for the cursor) and repaint so the picked
-- slot dims / un-dims immediately — the swap itself settles when the server answers.
PickupContainerItem(bagId, button.slot)
GameTooltip:Hide()
BenillaBagWindow_Repaint(button:GetParent())
elseif mouseButton == "RightButton" then
-- A held cursor cancels (client-side, nothing sent); otherwise use/equip/sell the slot.
if CursorHasItem() then
ClearCursor()
BenillaBagWindow_Repaint(button:GetParent())
else
UseContainerItem(bagId, button.slot)
end
end
end
-- Hover: the era-true GameTooltip:SetBagItem(bag, slot) — the ENGINE renders the full
-- item tooltip from the template store and owns the sell-price money law (real instance
-- + merchant open + repair off ⇒ OnTooltipAddMoney(SellPrice × stack), or the
-- ITEM_UNSELLABLE line — wow-re ui/scratch/tooltip-money.md's byte-verified gate,
-- decision 0274 P1). The durability-adjusted damaged price is deferred until durability
-- damage reaches the feed (a pristine item reduces to SellPrice × stack, the case we
-- ship). Nothing shows while the template is in flight — the OnUpdate re-enter fills it.
function BenillaBagSlot_OnEnter(button)
if button.slot == nil then return end
local bagId = button:GetParent():GetID()
local texture, itemCount, locked, quality, readable =
GetContainerItemInfo(bagId, button.slot)
-- An emptied slot drops its tooltip (ref ContainerFrame_Update's texture-nil branch,
-- ContainerFrame.lua:281-283) — without this the OnUpdate re-enter below would leave
-- a stale tooltip up after the item is sold or moved out from under the hover.
if not texture then
if GameTooltip:IsOwned(button) then GameTooltip:Hide() end
return
end
-- Ref ContainerFrameItemButton_OnEnter (ContainerFrame.lua:602-612) verbatim: a slot
-- in the RIGHT half of the screen hangs its tooltip LEFT, so it never runs off the
-- edge (the bag lives at the bottom-right — this is the ref's own screen-edge answer,
-- a side-pick, not a clamp).
if (button:GetRight() or 0) >= (GetScreenWidth() / 2) then
GameTooltip:SetOwner(button, "ANCHOR_LEFT")
else
GameTooltip:SetOwner(button, "ANCHOR_RIGHT")
end
GameTooltip:SetBagItem(bagId, button.slot)
-- The pouch/magnifier cursor (ref ContainerFrameItemButton_OnEnter tail,
-- ContainerFrame.lua l.636-642): merchant open on the MERCHANT tab and not repair
-- mode → the sell cursor over this slot; else a readable item (a mail letter copy —
-- right-click opens the reader) → the Inspect magnifier; anything else restores the
-- base cursor. The OnUpdate re-enter keeps it current while hovering (the ref's own
-- loop). The ref's `IsControlKeyDown() and hasItem` magnifier arm (l.638) is the
-- dress-up preview's own cursor tell — it landed with the dressing room (decision
-- 1060); `info` is non-nil above, which is the ref's `hasItem`.
if MerchantFrame and MerchantFrame:IsShown()
and MerchantFrame.selectedTab == 1 and not InRepairMode() then
ShowContainerSellCursor(bagId, button.slot)
elseif readable or IsControlKeyDown() then
ShowInspectCursor()
else
ResetCursor()
end
GameTooltip:Show()
end
function BenillaBagSlot_OnLeave(button)
GameTooltip:Hide()
-- Ref ItemButton OnLeave (ContainerFrame.xml): the hover's sell cursor drops with it.
ResetCursor()
end
-- Ref ContainerFrameItemButton_OnUpdate (ContainerFrame.lua:645-660): while the tooltip
-- is ours, re-run OnEnter every frame — the ref ships this loop UNTHROTTLED (its
-- updateTooltip throttle is commented out in 1.12). It is the real client's own refresh
-- mechanism: a tooltip set while the item template was still in flight fills itself in
-- the moment the answer lands, no re-hover.
function BenillaBagSlot_OnUpdate(button)
if GameTooltip:IsOwned(button) then
BenillaBagSlot_OnEnter(button)
end
end
-- The item's use-cooldown sweep (ref ContainerFrame_UpdateCooldown,
-- ContainerFrame.lua:295-299, verbatim shape over our container dialect).
function BenillaBagSlot_UpdateCooldown(button, bagId)
local cooldown = getglobal(button:GetName() .. "Cooldown")
local start, duration, enable = GetContainerItemCooldown(bagId, button.slot)
CooldownFrame_SetTimer(cooldown, start, duration, enable)
end
-- The physIndex → game-slot resolution (the module doc above) + the paint (ref
-- ContainerFrame_Update, ContainerFrame.lua:234-293). Shared by every window's slot
-- buttons. The occupied/empty fork drives the cooldown exactly as the ref does
-- (l.248-253: occupied → UpdateCooldown, empty → Cooldown:Hide()).
function BenillaBagSlot_Update(button)
local bagId = button:GetParent():GetID()
local size = GetContainerNumSlots(bagId)
if button.physIndex > size then
button.slot = nil
button:Hide()
return
end
button.slot = size - button.physIndex + 1
button:Show()
local texture, itemCount, locked = GetContainerItemInfo(bagId, button.slot)
if texture ~= nil or itemCount ~= nil then
BenillaBagSlot_UpdateCooldown(button, bagId)
button.icon:SetTexture(texture or BENILLA_FALLBACK_ICON)
-- A locked slot (its item is up on the cursor, or a send is still pending
-- server-side, decision 0216 §4/0218 §3) dims, the real client's drag feedback.
if locked then
button.icon:SetVertexColor(0.4, 0.4, 0.4)
else
button.icon:SetVertexColor(1, 1, 1)
end
if (itemCount or 1) > 1 then
button.count:SetText(itemCount)
else
button.count:SetText("")
end
else
button.icon:SetTexture(nil)
button.count:SetText("")
getglobal(button:GetName() .. "Cooldown"):Hide()
end
end
-- The generic per-window repaint (decision 0216 slice 2): every slot up to this window's
-- cap, the title (GetBagName — dynamic since a live bag swap changes it), and the money
-- purse (backpack only, ContainerFrame.lua l.314). BenillaBagFrame_Update (below) is the
-- backpack's zero-arg convenience alias, kept for existing callers (UiPanels.xml's
-- OpenBackpack/CloseBackpack, and the tests that patch it directly).
function BenillaBagWindow_Update(frame)
local bagId = frame:GetID()
local maxSlots = (bagId == 0) and 16 or BENILLA_BAG_WINDOW_MAX_SLOTS
-- The backpack predates the $parent-prefixed slot naming (BenillaBagSlot1..16, not
-- BenillaBagFrameSlot1..16 — its buttons are hand-authored, not $parent-templated) and
-- several existing tests reference that exact name directly; every other window's slots
-- DO follow frame:GetName().."Slot"..i (the virtual template's $parentSlotN).
local slotPrefix = (bagId == 0) and "BenillaBagSlot" or (frame:GetName() .. "Slot")
for i = 1, maxSlots do
local button = getglobal(slotPrefix .. i)
if button then BenillaBagSlot_Update(button) end
end
local title = getglobal(frame:GetName() .. "Name")
if title then
-- The keyring is titled from the localized KEYRING string, not GetBagName — it has
-- no bag ITEM to be named after (ref ContainerFrame.lua l.418).
if bagId == KEYRING_CONTAINER then
title:SetText(KEYRING)
else
title:SetText(GetBagName(bagId) or "")
end
end
if bagId == 0 then
BenillaMoney_Set("BenillaBagMoney", GetMoney())
elseif bagId == KEYRING_CONTAINER then
-- Fixed art, again because there is no bag item: the keyring's own portrait
-- (ref ContainerFrame.lua l.419) and its own background variant, snug-fitted to the
-- level-gated slot count exactly like an equipped bag.
-- The GLOBAL form, which is the reference's own (ContainerFrame.lua l.419
-- writes exactly this call) — 1.12 has no such Texture method.
SetPortraitToTexture(frame:GetName() .. "Portrait",
"Interface\\ContainerFrame\\KeyRing-Bag-Icon")
local size = GetContainerNumSlots(bagId)
if size > 0 then BenillaBagWindow_FitBackground(frame, size) end
else
-- The window's portrait is the bag's OWN item icon (the real client's
-- C-side SetBagPortaitTexture), not the default backpack art. An equipped bag
-- (1..4) reads its doll slot (19+bagId, Bag0Slot..Bag3Slot); a bank bag (5..10,
-- decision 0604) reads the app-resolved bank-bag feed instead — the doll array
-- stops at the equipped bags, so GetInventoryItemTexture can't reach the vault's.
-- Falls back to the backpack icon while the item template's icon is in flight.
local portrait = getglobal(frame:GetName() .. "Portrait")
if portrait then
local icon
if bagId >= 5 then
icon = BenillaGetBankBagTexture(bagId - 4)
else
icon = GetInventoryItemTexture("player", 19 + bagId)
end
if icon then
-- The circular crop (the real SetBagPortaitTexture is C-side and masks the
-- icon to the ring's inscribed circle): a raw square icon pokes its corners
-- past the round rim — the director's bank-bag popout artifact.
SetPortraitToTexture(frame:GetName() .. "Portrait", icon)
else
-- The backpack fallback art is authored round — no crop to apply.
portrait:SetTexture("Interface\\Buttons\\Button-Backpack-Up")
end
end
-- Equipped bags snug-fit their stitched background to the live row count.
local size = GetContainerNumSlots(bagId)
if size > 0 then BenillaBagWindow_FitBackground(frame, size) end
end
end
-- Fit an equipped-bag window's stitched background (BgTop/BgMiddle/BgBottom, authored in the
-- template above) and its OWN height to the bag's actual row count. This is a transcription of
-- the real client's ContainerFrame_GenerateFrame (the id~=0 branch) as shipped in the 1.12.1
-- MPQ (Interface\FrameXML\ContainerFrame.lua) — every constant below is the reference's, not a
-- measured approximation. rows = ceil(size/4). The TOP piece carries the header + the first
-- well row; the MIDDLE tiles the remaining rows; the BOTTOM is the frame's bottom rim. The
-- slot buttons anchor UP from the frame's BOTTOMRIGHT (first at -12,9) and hide past `size`
-- (BenillaBagSlot_Update), so nothing here touches them — only the art + the frame height.
-- Which UI-Bag-Components variant a window is stitched from — the ref's own `bagTextureSuffix`
-- (ContainerFrame_GenerateFrame, ContainerFrame.lua l.326-334): the bank bags get the
-- tinted "-Bank" plate, the keyring the "-Keyring" one, an equipped bag the plain sheet.
-- All three atlases are 256x512 with the same band layout, so FitBackground's tex-coords
-- are shared verbatim and only the file differs. (Ours ALSO applies the "-Bank" arm, which
-- benilla had been quietly skipping — the bank popouts have been drawing ordinary bag art
-- since decision 0604.)
function BenillaBagWindow_BgTexture(bagId)
local suffix = ""
if bagId >= 5 then
suffix = "-Bank"
elseif bagId == KEYRING_CONTAINER then
suffix = "-Keyring"
end
return "Interface\\ContainerFrame\\UI-Bag-Components" .. suffix
end
function BenillaBagWindow_FitBackground(frame, size)
local name = frame:GetName()
local top = getglobal(name .. "BgTop")
local middle = getglobal(name .. "BgMiddle")
local bottom = getglobal(name .. "BgBottom")
if not (top and middle and bottom) then return end
-- Re-set every fit, as the ref does at every generate: the template authors the plain
-- sheet, and a window's kind is only known from its id.
local bg = BenillaBagWindow_BgTexture(frame:GetID())
top:SetTexture(bg)
middle:SetTexture(bg)
bottom:SetTexture(bg)
local rowH = 41 -- rowHeight
local rows = math.ceil(size / 4)
if rows < 1 then rows = 1 end
-- isPlusTwoBag: a size leaving a remainder of 2 (6/10/14/18) has a 2-well top row, and the
-- texture carries a whole separate top band for it.
local plusTwo = (size - math.floor(size / 4) * 4) == 2
local topH
if plusTwo then
topH = 72
top:SetTexCoord(0, 1, 0.189453125, 0.330078125)
elseif rows == 1 then
topH = 86 -- one row only: chop the bottom of the texture
top:SetTexCoord(0, 1, 0.00390625, 0.16796875)
else
topH = 94
top:SetTexCoord(0, 1, 0.00390625, 0.18359375)
end
top:SetHeight(topH)
-- The top piece already carries one row, so the middle tiles `rows - 1` more. Its height is
-- one row short of a clean multiple: the reference's firstRowPixelOffset = 9 accounts for
-- the 9px the BOTTOM rim itself contributes to the final row's frame. Getting this wrong
-- pushes the rim a row-fraction too low and bleeds the NEXT row's wells in above it.
local middleH = 0
bottom:ClearAllPoints()
if rows > 1 then
-- A single middle piece spans at most ROWS_IN_BG_TEXTURE (6) rows; the reference
-- cycles up to MAX_BG_TEXTURES for windows taller than that. Our largest window is a
-- 20-slot bag (4 middle rows) and the keyring caps at 16 (3), so one always suffices.
middleH = (rows - 1) * rowH - 9
middle:SetHeight(middleH)
middle:SetTexCoord(0, 1, 0.353515625, middleH / 512 + 0.353515625)
middle:Show()
bottom:SetPoint("TOPRIGHT", middle, "BOTTOMRIGHT", 0, 0)
else
-- One row: the rim seats straight under the top piece. (The reference anchors it to the
-- hidden middle's TOP, which is the same point — the middle hangs off the top's bottom.)
middle:Hide()
bottom:SetPoint("TOPRIGHT", top, "BOTTOMRIGHT", 0, 0)
end
-- The bottom rim is a FIXED 10px band at texture v 0.330078125..0.349609375 — the strip
-- immediately below the plus-two top band, and the only true outer bottom border in the
-- texture. The reference never rescales or re-coords it; it is authored once in XML.
local bottomH = 10
bottom:SetHeight(bottomH)
bottom:SetTexCoord(0, 1, 0.330078125, 0.349609375)
frame:SetHeight(topH + middleH + bottomH)
-- A live size change (a bag swap while the stack is open) shifts every window stacked above
-- this one; re-run the stack layout so they re-seat against the new height.
if frame:IsShown() then BenillaBagStack_Relayout() end
end
function BenillaBagFrame_Update()
BenillaBagWindow_Update(BenillaBagFrame)
end
-- Routes a repaint through the backpack's own zero-arg BenillaBagFrame_Update alias when
-- `frame` IS the backpack — preserving that global's observability (existing tests patch it
-- directly to count backpack repaints, e.g. delete_item_tests.rs's stuck-darkening
-- regression check) — and BenillaBagWindow_Update generically for every other window. Every
-- runtime repaint trigger (a slot click, an event, OpenAllBags' pre-show paint) goes
-- through this (BenillaBagBarSlot_OnClick reaches it through the ToggleBag global too).
function BenillaBagWindow_Repaint(frame)
if frame:GetID() == 0 then
BenillaBagFrame_Update()
else
BenillaBagWindow_Update(frame)
end
end
-- OnLoad/OnEvent (generic, shared by all five windows — each already receives `frame`, so
-- deriving bagId from `frame:GetID()` needs no per-window body).
function BenillaBagFrame_OnLoad(frame)
frame:RegisterEvent("BAG_UPDATE")
-- The engine fires ITEM_LOCK_CHANGED(bag, slot) on every cursor pickup/clear AND every
-- pending-op send/resolve (decisions 0216 §4/0218 §3) — including clears the bag never
-- clicked through (the delete popup's No, ESC; a server failure), which the click-time
-- repaint can't see. The reference ContainerFrame repaints on exactly this event.
frame:RegisterEvent("ITEM_LOCK_CHANGED")
-- Item cooldown starts/ends (ref ContainerFrame_OnLoad, ContainerFrame.lua:21 —
-- BAG_UPDATE_COOLDOWN carries no bag argument; every open window refreshes).
frame:RegisterEvent("BAG_UPDATE_COOLDOWN")
-- The real backpack MoneyFrame refreshes on PLAYER_MONEY (SmallMoneyFrame's own OnEvent);
-- keep the purse live while the window is open. Backpack only — the other windows never
-- show a purse, so there's nothing for them to refresh.
if frame:GetID() == 0 then
frame:RegisterEvent("PLAYER_MONEY")
end
end
function BenillaBagFrame_OnEvent(frame)
if event == "PLAYER_MONEY" then
BenillaMoney_Set("BenillaBagMoney", GetMoney())
return
end
-- The cooldown-only refresh (ref ContainerFrame_UpdateCooldowns via OnEvent,
-- ContainerFrame.lua:243-253's loop shape): no bag argument — sweep every shown
-- slot of THIS window, no full repaint.
if event == "BAG_UPDATE_COOLDOWN" then
local bagId = frame:GetID()
local maxSlots = (bagId == 0) and 16 or BENILLA_BAG_WINDOW_MAX_SLOTS
local slotPrefix = (bagId == 0) and "BenillaBagSlot" or (frame:GetName() .. "Slot")
for i = 1, maxSlots do
local button = getglobal(slotPrefix .. i)
if button and button.slot then
BenillaBagSlot_UpdateCooldown(button, bagId)
end
end
return
end
-- BAG_UPDATE / ITEM_LOCK_CHANGED name their bag in arg1; only THIS window's own bag
-- repaints (ref ContainerFrame_OnEvent, ContainerFrame.lua:26-44: `this:GetID() == arg1`).
if (event == "BAG_UPDATE" or event == "ITEM_LOCK_CHANGED") and arg1 ~= frame:GetID() then
return
end
BenillaBagWindow_Repaint(frame)
end
-- ToggleBag / ToggleBackpack / OpenAllBags (ref ContainerFrame.lua l.46-82, l.662-700) —
-- TWO DISTINCT KNOBS, and benilla ran them as one until 1494. The reference's split, which
-- is now ours:
--
-- B / F12 TOGGLEBACKPACK -> ToggleBackpack() — the BACKPACK's toggle. Closed: opens
-- bag 0 ALONE. Already open: hides every container window there is. The
-- asymmetry is the reference's own — one bag out, all bags in.
-- SHIFT-B OPENALLBAGS -> OpenAllBags() — the ALL-bags toggle: everything open
-- already closes the lot, anything else opens the lot.
-- F8..F11 TOGGLEBAG1..4 -> ToggleBag(4..1) — one equipped bag each (note the
-- reference's REVERSED numbering: TOGGLEBAG1 is bag 4).
--
-- "All bags" = the backpack (bag 0, which always exists) plus each equipped-bag slot 1..4
-- that actually holds a bag (GetContainerNumSlots > 0); an empty slot has no window to
-- show. The five windows, indexed by bag id (BENILLA_BAG_WINDOWS[bagId+1]) — one code path
-- over all of them. The keyring is a seventh window with its own ToggleKeyRing: swept by
-- the two close arms below (the ref's sweeps are over ALL ContainerFrames, which is what
-- its keyring lives in), never counted as an open bag and never opened by them.
BENILLA_BAG_WINDOWS = { "BenillaBagFrame", "BenillaBagFrame1", "BenillaBagFrame2", "BenillaBagFrame3", "BenillaBagFrame4" }
-- THE REFERENCE'S NAMES ARE THE REAL ENTRY POINTS, and our own UI calls THEM.
--
-- This is how a 1.12 addon customises anything: it replaces a global and expects the
-- client to call its replacement. `Bagnon_Core/core/Overrides.lua` is the canonical shape —
-- local bToggleBackpack = ToggleBackpack
-- ToggleBackpack = function() ... end
-- and until now that override was INERT here, because the bag bindings and the bag button
-- called `BenillaBagToggle_OnClick` directly and nothing ever looked up `ToggleBackpack`.
-- Bagnon loaded, appeared in the AddOns list, replaced the globals, and the player still
-- got the stock bags. The director saw that; no instrument here could, because the corpus
-- survey loads addons and fires events but never CLICKS anything.
--
-- The class is far wider than one addon: every "hook a global to change behaviour" addon is
-- silently inert wherever our UI calls a benilla-named equivalent instead of the
-- reference's name. `CloseAllBags`/`OpenBackpack`/`CloseBackpack` already lived under the
-- ref's names in UiPanels.xml for exactly this reason; these are the three that did not.
--
-- `OpenAllBags(forceOpen)` is the ref's SHIFT-B body (ContainerFrame.lua l.662-700) and it
-- is a TOGGLE, not a plain open: one pass hides every open window while counting them
-- against how many CAN be open, and if everything already was, that pass IS the close and
-- it returns. `forceOpen` (the ref's own argument, used by callers that mean "open") skips
-- the close arm. The open arm calls the GLOBALS `ToggleBackpack()`/`ToggleBag(1..4)` on a
-- now-closed set — the ref's exact tail, so a bag addon holding either name gets both.
function OpenAllBags(forceOpen)
if UIParent and not UIParent:IsVisible() then return end
local totalBags = 1 -- the backpack always counts
for bagId = 1, 4 do
if GetContainerNumSlots(bagId) > 0 then totalBags = totalBags + 1 end
end
local bagsOpen = 0
for _, name in ipairs(BENILLA_BAG_WINDOWS) do
local frame = getglobal(name)
if frame and frame:IsShown() then
frame:Hide()
bagsOpen = bagsOpen + 1
end
end
-- Swept with the rest, but never counted (ref: `if GetID() ~= KEYRING_CONTAINER`).
local keyring = getglobal("BenillaKeyRingFrame")
if keyring and keyring:IsShown() then keyring:Hide() end
if bagsOpen >= totalBags and not forceOpen then return end
ToggleBackpack()
ToggleBag(1)
ToggleBag(2)
ToggleBag(3)
ToggleBag(4)
end
-- `ToggleBag(id)` — the ref's per-bag toggle (ContainerFrame.lua l.46-65), and the other
-- global Bagnon replaces. Ours drives the same window the all-bags path does.
--
-- The ref's `if size > 0` gate is load-bearing here and was missing until 1494: it is what
-- keeps `OpenAllBags`' blind `ToggleBag(1..4)` tail from showing an empty slot's window.
-- Bag 0 is exempt for the same reason `OpenBag` exempts it (it always exists even when the
-- feed has not told us its size yet) — the ref gets that for free from its own always-16
-- backpack.
function ToggleBag(id)
id = id or 0
if id ~= 0 and GetContainerNumSlots(id) <= 0 then return end
local frame = getglobal(BENILLA_BAG_WINDOWS[id + 1])
if not frame then return end
if frame:IsShown() then
frame:Hide()
else
BenillaBagWindow_Repaint(frame)
frame:Show()
end
end
-- `OpenBag(id)` / `CloseBag(id)` / `IsBagOpen(id)` — ToggleBag's three siblings from the
-- same reference file (ContainerFrame.lua l.144-185), and the last of that family that was
-- still missing here. `Bagnon_Core/core/Overrides.lua` l.42/51 captures BOTH of the first
-- two and calls the capture back for any bag Bagnon does not control — a nil call, on the
-- ordinary path, for an addon that only *partly* takes the bags over.
--
-- The reference's `OpenBag` opens a bag only when `GetContainerNumSlots(id) > 0`; ours keeps
-- that gate for 1..4 and exempts bag 0, which always exists (the same rule `OpenAllBags`
-- above already applies, for the same reason).
function OpenBag(id)
id = id or 0
if id ~= 0 and GetContainerNumSlots(id) <= 0 then return end
local frame = getglobal(BENILLA_BAG_WINDOWS[id + 1])
if frame and not frame:IsShown() then
BenillaBagWindow_Repaint(frame)
frame:Show()
end
end
function CloseBag(id)
local frame = getglobal(BENILLA_BAG_WINDOWS[(id or 0) + 1])
if frame and frame:IsShown() then frame:Hide() end
end
-- ONE STATED DIVERGENCE. The reference returns the INDEX of the ContainerFrame showing that
-- bag (1..NUM_CONTAINER_FRAMES) so its own code can `getglobal("ContainerFrame"..i)`; we
-- ship no such pool (each bag has one fixed window), so there is no index to name and this
-- answers 1 / nil. Every corpus caller — and the reference's own `ToggleBackpack` — uses it
-- as a boolean; a caller that indexed the result would already be reaching for frames that
-- do not exist here.
function IsBagOpen(id)
local frame = getglobal(BENILLA_BAG_WINDOWS[(id or 0) + 1])
if frame and frame:IsShown() then return 1 end
return nil
end
-- `ToggleBackpack()` — ref ContainerFrame.lua l.67-82, and NOT the all-bags toggle it was
-- here until 1494:
--
-- if ( IsBagOpen(0) ) then <hide every ContainerFrame> else ToggleBag(0) end
--
-- Open arm: `ToggleBag(0)`, the backpack ALONE (through the global, so a bag addon holding
-- `ToggleBag` gets it). Close arm: the ref's own frame sweep — every window, keyring
-- included — and NOT a `CloseAllBags()` call, because the reference does not make one here
-- and an addon holding that name would fire on a path the real client never runs it on.
-- The condition reads bag 0 SPECIFICALLY: with only bag 2 open, B opens the backpack
-- beside it rather than closing anything.
function ToggleBackpack()
if IsBagOpen(0) then
for _, name in ipairs(BENILLA_BAG_WINDOWS) do
local frame = getglobal(name)
if frame and frame:IsShown() then frame:Hide() end
end
local keyring = getglobal("BenillaKeyRingFrame")
if keyring and keyring:IsShown() then keyring:Hide() end
else
ToggleBag(0)
end
end
-- The backpack button (MainMenuBarBackpackButton): if any bag window is open, close them
-- all; otherwise open every existing bag (ref ToggleBackpack — "if numOpen > 0 then
-- CloseAllBags() else OpenAllBags()").
--
-- ONE STATED DIVERGENCE from ref BackpackButton_OnClick (MainMenuBarBagButtons.lua
-- l.55-69). The ref's tail re-derives the checked state from a scan of its NATIVE
-- ContainerFrames — and OnShow/OnHide dispatch is SYNCHRONOUS in the reference (wow-re
-- `onshow-onhide-dispatch-order.md`: Show binder 0x775750 → transition 0x76ae10 → notify
-- 0x76b260 → lua_pcall, every hop a direct call), so when an addon owns the bags its
-- OnShow's `SetChecked(1)` (Bagnon.lua l.33) lands FIRST and the ref tail stomps it: on
-- the real client the backpack button ends UNLIT after clicking it with Bagnon, and
-- lights only via the key binding (whose body is the bare `ToggleBackpack()`, no tail).
-- That click-path outcome contradicts the addon's evident intent and the director's call,
-- so ours swaps the scan for the auto-toggle UNDO below: checked belongs to the WINDOWS'
-- OnShow/OnHide writes — ours (BenillaBagFrame_OnShow/_OnHide) and any addon's alike —
-- and this handler only repairs what the CheckButton widget flipped before it ran. Every
-- stock outcome is identical to the ref's (the transition writes land inside
-- ToggleBackpack and are the last word); the addon case ends lit instead of stomped.
-- The widget-flip UNDO itself lives in the button's XML <OnClick> wrapper — the one seat
-- where the CheckButton auto-toggle has just happened — so every DIRECT caller of this
-- function (the tests, a macro) gets pure toggle semantics with no checked bookkeeping
-- to corrupt.
function BenillaBagToggle_OnClick()
-- Through the GLOBAL, so an addon that replaced `ToggleBackpack` gets the click.
ToggleBackpack()
end
-- The window's bar button (ref ContainerFrame_OnShow/OnHide: bag 0 →
-- MainMenuBarBackpackButton, bags 1..4 → CharacterBag(N-1)Slot).
function BenillaBagWindow_BarButton(frame)
local name = frame:GetName()
if name == "BenillaKeyRingFrame" then return getglobal("KeyRingButton") end
for bagId = 0, 4 do
if name == BENILLA_BAG_WINDOWS[bagId + 1] then
if bagId == 0 then return getglobal("MainMenuBarBackpackButton") end
return getglobal("CharacterBag" .. (bagId - 1) .. "Slot")
end
end
end
-- Open/close sounds — the window-sound convention (decision 0090), wrapper form: a named
-- Benilla<Window>Frame_OnShow/_OnHide fn (used when the handler is more than the bare
-- PlaySound, or — as here — to carry the cite). Kits quoted from the real ContainerFrame.lua
-- ContainerFrame_OnShow/OnHide (l.124-142 / l.100-122), reused VERBATIM by every window
-- (backpack included), INCLUDING the real fn's keyring fork: the keyring has its own
-- jingling KeyRingOpen/KeyRingClose pair (both real SoundEntries kits), every other window
-- the plain igBackPackOpen/igBackPackClose. Fired on every visibility transition (a Show/Hide
-- from a toggle button, a bag binding, a close button, or ESC), never at load (every window
-- is authored hidden="true", so none ever transitions on startup).
--
-- The `this` legacy global (RF-0025, script/event.rs) names the window that just
-- shown/hidden — used here for the open-window STACK the layout law below reads, rather
-- than a per-window OnShow/OnHide body (which would defeat the whole point of sharing one
-- handler across five windows).
function BenillaBagFrame_OnShow()
if this:GetID() == KEYRING_CONTAINER then
PlaySound("KeyRingOpen")
else
PlaySound("igBackPackOpen")
end
-- The bar lights the open bag's button (ref ContainerFrame_OnShow l.124-131,
-- SetChecked(1)) — the window side is the source of truth, so a bag opened by any
-- path (the B key, a bar click, open-all at a vendor) lights its button.
local barButton = BenillaBagWindow_BarButton(this)
if barButton then barButton:SetChecked(1) end
BenillaBagStack_Open(this)
end
function BenillaBagFrame_OnHide()
if this:GetID() == KEYRING_CONTAINER then
PlaySound("KeyRingClose")
else
PlaySound("igBackPackClose")
end
-- ...and clears it on any close (ref ContainerFrame_OnHide l.84-95, SetChecked(0)).
local barButton = BenillaBagWindow_BarButton(this)
if barButton then barButton:SetChecked(0) end
BenillaBagStack_Close(this)
end
-- ── The open-bag stack + layout law (ref ContainerFrame1.bags +
-- updateContainerFrameAnchors, ContainerFrame.lua:472-552, transcribed shape) ─────────────
-- Open windows stack bottom-right UPWARD in show order: the first at the bottom-right
-- CLEARANCE CORNER, each next one anchored above the previous (TOPRIGHT → BOTTOMRIGHT,
-- CONTAINER_SPACING=0 gap); when a stack would run off the top of the screen, the NEXT
-- window starts a fresh column one CONTAINER_WIDTH further left.
-- Out of scope (nothing here ever needs it): the ref's UI-scale/BankFrame-collision shrink
-- branch (l.505-539) — benilla has no uiScale CVar and no bank window yet.
--
-- The corner is `CONTAINER_OFFSET_X`/`CONTAINER_OFFSET_Y` — the two GLOBALS
-- `UIParent_ManageFramePositions()` recomputes every time the bottom of the screen fills up
-- or empties (UIParent.xml's var rows). Reading them live is the whole point and it is
-- load-bearing, not tidiness: this file used to keep its own `local
-- BENILLA_CONTAINER_OFFSET_X/Y = 0/70` shadow pair, which meant the stack sat at the
-- no-bars-raised corner FOREVER — with a bottom multibar up, the lowest bag window drew
-- straight through the bar's buttons (director-caught, decision 1499). A shadow copy of a
-- managed value cannot be kept in sync by anyone; the fix is to have no copy.
-- `BenillaBagStack_Relayout` is registered as a managed-position LISTENER at the end of this
-- file, so a bar raised while bags are open re-seats them on the same pass that moves the
-- cast bar and the chat frames.
BENILLA_BAG_STACK = {}
local BENILLA_CONTAINER_WIDTH = 192
local BENILLA_CONTAINER_SPACING = 0
local BENILLA_VISIBLE_CONTAINER_SPACING = 3
function BenillaBagStack_Open(frame)
table.insert(BENILLA_BAG_STACK, frame)
BenillaBagStack_Relayout()
end
function BenillaBagStack_Close(frame)
for i = 1, table.getn(BENILLA_BAG_STACK) do
if BENILLA_BAG_STACK[i] == frame then
table.remove(BENILLA_BAG_STACK, i)
break
end
end
BenillaBagStack_Relayout()
end
function BenillaBagStack_Relayout()
-- Read the clearance corner FRESH on every pass — never cached in a local that outlives
-- the call (see the law above). The `or` fallbacks cover the per-window test harnesses
-- that load this file without UIParent.xml: with no manage pass in the VM the globals
-- are nil, and the no-bars-raised corner is the right answer there by construction.
local offsetX = CONTAINER_OFFSET_X or 0
local offsetY = CONTAINER_OFFSET_Y or 70
local screenHeight = GetScreenHeight()
local freeHeight = screenHeight - offsetY
local column = 0
for i = 1, table.getn(BENILLA_BAG_STACK) do
local frame = BENILLA_BAG_STACK[i]
if i == 1 then
frame:SetPoint("BOTTOMRIGHT", -offsetX, offsetY)
elseif freeHeight < frame:GetHeight() then
column = column + 1
freeHeight = screenHeight - offsetY
frame:SetPoint("BOTTOMRIGHT",
-(column * BENILLA_CONTAINER_WIDTH) - offsetX,
offsetY)
else
frame:SetPoint("BOTTOMRIGHT", BENILLA_BAG_STACK[i - 1], "TOPRIGHT", 0, BENILLA_CONTAINER_SPACING)
end
freeHeight = freeHeight - frame:GetHeight() - BENILLA_VISIBLE_CONTAINER_SPACING
end
end
-- The stack is a CONSUMER of two managed vars, so it joins the pass's listener list: the
-- pass writes CONTAINER_OFFSET_X/Y and then calls everything registered here, which is how
-- an open bag stack gets out of the way of a bar raised underneath it. (The reference does
-- the same thing by hard-calling `updateContainerFrameAnchors()` from the end of its own
-- pass — UIParent.lua:1774; ours is that call generalized so the next var consumer does not
-- have to edit UIParent.xml to be noticed. Decision 1499.) Existence-guarded for the
-- harnesses that load this file alone.
if UIParent_RegisterManagedPositionListener then
UIParent_RegisterManagedPositionListener(BenillaBagStack_Relayout)
end
-- ── The bag bar (decision 0216 slice 2) — four 30×30 buttons left of MainMenuBarBackpackButton,
-- mirroring the reference main-bar bag buttons (CharacterBag0..3Slot). No exact reference
-- XML was available to transcribe (not in the session's extracted set) — size (30×30) and
-- placement (left of the backpack button) are per the brief; inter-button spacing is an
-- authored assumption (2px), FLAGGED for the director's look. Each button shows the
-- equipped bag's own icon (GetInventoryItemTexture, inv slots 20..23 — Bag0Slot..Bag3Slot,
-- PaperDollItemFrame.dbc-verified, decision 0216 slice 2's char-feed extension) or the
-- empty-slot art when no bag is equipped there. Click toggles that bag's window — NAMED
-- DEFERRAL: no drag-to-equip a bag onto the bar yet (that's the paper-doll drag arc, slice
-- 3's scope, not this one).
-- The bag bar (and the backpack toggle) are RELOCATED onto the action bar — anchored into
-- MainMenuBarArtFrame's bottom-right — which means they need seating one frame level
-- above the bar art or it paints over their icons. That lever lives with the frame it's
-- about: BenillaActionBarArt_SeatAbove (ActionBar.xml, loaded before this file), which
-- carries the full explanation; the micro-button row is its second caller. Called
-- nil-GUARDED here: the shipped load order always has the bar first, but ~25 bag/bank/
-- escape/merchant test harnesses load THIS file alone — and with no bar loaded there is by
-- definition no bar art to seat above, so the guard's no-op is the right answer, not a
-- swallowed failure.
-- Bag0Slot..Bag3Slot = live inv ids 20..23 (char_stats.rs SLOT_INFO, DBC-verified); OUR bag
-- ids 1..4 map the same way (bagId N ⇒ inv slot 19+N), the id PickupInventoryItem/
-- GetInventoryItemTexture speak.
function BenillaBagBarSlot_InvSlot(button) return 19 + button.bagId end
function BenillaBagBarSlot_OnLoad(button, bagId)
button.bagId = bagId
-- The button's ID is the REFERENCE's: its 1-based inventory slot, 20..23 (the real
-- client gets there through PaperDollItemSlotButton_OnLoad, which resolves
-- "CharacterBag0Slot" → "Bag0Slot" → GetInventorySlotInfo → 20). Nothing of ours reads
-- it — every body here goes through `button.bagId` — but every ADDON does:
-- `Bagnon.lua:104` computes its highlight target as `this:GetID() - 19`, and against
-- an unset id that is -19, so Bagnon highlighted the wrong bag (silently, with no
-- raise) on every bag-slot hover. `isBag` is the same story from the same template
-- (ref BagSlotButtonTemplate OnLoad l.33), read by KeyRingItemButton_OnClick.
button:SetID(19 + bagId)
button.isBag = 1
button.icon = getglobal(button:GetName() .. "IconTexture")
if BenillaActionBarArt_SeatAbove then BenillaActionBarArt_SeatAbove(button) end
-- Both buttons (ref BagSlotButtonTemplate inherits PaperDollItemSlotButtonTemplate, and
-- its OnLoad calls PaperDollItemSlotButton_OnLoad — PaperDollFrame.lua:86 — so a bag-bar
-- slot registers the pair like every doll slot; MainMenuBarBagButtons.xml:78 does the
-- same for the backpack). BagSlotButton_OnClick reads no button: right-click opens the
-- bag, exactly as left does.
button:RegisterForClicks("LeftButtonUp", "RightButtonUp")
-- Drag-to-equip (decision 0208 phase 1b's deferred bag-icon arm, now landed): the bar
-- slot is a paper-doll equipment slot for bags. LEFT drag picks the equipped bag up;
-- a drag/receive drops a carried bag in to equip it (PickupInventoryItem — the exact
-- doll-slot transition, the app maps id 20..23 onto wire bag slots 19..22).
button:RegisterForDrag("LeftButton")
button:RegisterEvent("UNIT_INVENTORY_CHANGED")
button:RegisterEvent("BAG_UPDATE")
button:RegisterEvent("ITEM_LOCK_CHANGED")
BenillaBagBarSlot_Update(button)
end
-- Placing/equipping a carried bag (or picking the equipped one up) rides PickupInventoryItem,
-- exactly like a paper-doll slot; the empty-cursor plain click just toggles the bag's window.
function BenillaBagBarSlot_Place(button)
PickupInventoryItem(BenillaBagBarSlot_InvSlot(button))
BenillaBagBarSlot_Update(button)
end
-- ref BagSlotButton_OnClick (MainMenuBarBagButtons.lua l.3-21): the cursor arm first (the
-- ref's PutItemInBag; ours is the equip/swap PickupInventoryItem arm, decision 0208 1b),
-- otherwise the toggle goes THROUGH THE GLOBAL `ToggleBag` — the exact name
-- Bagnon_Core/Overrides.lua replaces — with the ref's own button sound on that arm only.
-- Toggling the window directly here (what this did before) made every bag addon's override
-- silently inert for the four slot buttons while the backpack button honoured it: the
-- 517-line rule above ("the reference's names are the real entry points") applied to only
-- one of the five buttons, and the director met the other four.
function BenillaBagBarSlot_OnClick(button)
if CursorHasItem() then
BenillaBagBarSlot_Place(button) -- drop/equip the carried bag onto this slot
else
ToggleBag(button.bagId)
PlaySound("BAGMENUBUTTONPRESS")
end
-- The click's own CheckButton auto-toggle is provisional — re-derive from the NATIVE
-- window on every branch, the ref's exact tail (BagSlotButton_OnClick l.12-20 scans
-- ContainerFrame1..N: SetChecked(isVisible) whether the click toggled, placed a bag,
-- or did nothing). Reading the WINDOW rather than what ToggleBag did is load-bearing
-- both ways: stock, the flag is already set when the tail runs; with an addon holding
-- the bags, the native stays hidden and the slot button stays unlit — the reference's
-- own behaviour (its scan is equally blind to addon frames).
local frame = getglobal("BenillaBagFrame" .. button.bagId)
button:SetChecked(frame and frame:IsShown())
end
function BenillaBagBarSlot_OnEvent(button)
if event == "UNIT_INVENTORY_CHANGED" and arg1 ~= "player" then return end
-- ITEM_LOCK_CHANGED names its slot in arg1/arg2; the bar's slots are the EQUIPMENT_BAG
-- space, so a bag-window lock event (a real bag id in arg1) never concerns the icon —
-- repainting anyway is idempotent (the lock read below is slot-specific). BAG_UPDATE
-- fires per-bag-CONTENTS change, not per-icon change (the icon is the bag ITEM's own
-- texture) — repainting on it too covers the freshly-equipped-bag-template-in-flight frame.
BenillaBagBarSlot_Update(button)
end
-- ── The bar buttons' hovers ───────────────────────────────────────────────────────────
-- ref GlobalStrings.lua l.132 / l.1458 (enUS), fallback form: the real localized
-- GlobalStrings.lua runs before this file at runtime (ui_script/mod.rs), so these only
-- fill in for the harnesses that load no patch chain.
BACKPACK_TOOLTIP = BACKPACK_TOOLTIP or "Backpack"
EQUIP_CONTAINER = EQUIP_CONTAINER or "Equip Container"
-- ref GlobalStrings.lua l.2472 / l.2810 — the keyring's window title + button tooltip, and
-- the full-keyring refusal.
KEYRING = KEYRING or "Keyring"
NO_EMPTY_KEYRING_SLOTS_ERROR = NO_EMPTY_KEYRING_SLOTS_ERROR or "Your keyring is full."
-- Both are PLAIN tooltips — the reference calls GameTooltip:SetText here, not
-- GameTooltip_AddNewbieTip, so unlike the micro buttons next door (0661) there is no second
-- explanation line to be missing. They also anchor beside the button (ANCHOR_LEFT, so the
-- plate opens toward screen center off a bottom-right button), NOT at the default corner.
--
-- ref MainMenuBarBackpackButton OnEnter (ref-MainMenuBarBagButtons.xml l.91-99). The ref
-- appends the live binding key with GameTooltip:AppendText; ours concatenates the same
-- suffix from a LIVE GetBindingKey read since 0997, rendered through the Key Bindings
-- window's display law when it is loaded. Unbound: the bare label, the ref's own read.
-- The command is TOGGLEBACKPACK — the ref's own read here, and since 1494 ours too: it is
-- the key that runs what a plain click on this button runs.
function BenillaBagToggle_OnEnter(button)
if not GameTooltip then return end -- engine tests load file subsets
GameTooltip:SetOwner(button, "ANCHOR_LEFT")
local text = BACKPACK_TOOLTIP
local key = GetBindingKey("TOGGLEBACKPACK")
if key then
if KeyBindings_KeyText then key = KeyBindings_KeyText(key) end
text = text .. " " .. NORMAL_FONT_COLOR_CODE .. "(" .. key .. ")" .. FONT_COLOR_CODE_CLOSE
end
GameTooltip:SetText(text, 1.0, 1.0, 1.0)
end
-- ref BagSlotButton_OnEnter (ref-MainMenuBarBagButtons.lua l.86-96): the equipped bag's own
-- item tooltip, or "Equip Container" for an empty slot. The ref's "(F8)".."(F11)" suffix
-- needed GetBindingKey AND the TOGGLEBAG1..4 bindings; those bind as of 1494, so the ONE
-- thing still missing is `GameTooltip:AppendText` — our tooltip has SetText/AddLine and no
-- append verb, and the ref's suffix lands on a plate SetInventoryItem already wrote. That
-- verb's byte semantics (which line does it extend?) are unverified, so the suffix waits
-- rather than guessing; this is where it goes, over `GetBindingKey("TOGGLEBAG"..(5 -
-- button.bagId))` — the ref's index is REVERSED against the bar, so the slot nearest the
-- backpack (bagId 1) is TOGGLEBAG4/F11 and the farthest (bagId 4) is TOGGLEBAG1/F8.
function BenillaBagBarSlot_OnEnter(button)
if not GameTooltip then return end
GameTooltip:SetOwner(button, "ANCHOR_LEFT")
if not GameTooltip:SetInventoryItem("player", BenillaBagBarSlot_InvSlot(button)) then
GameTooltip:SetText(EQUIP_CONTAINER, 1.0, 1.0, 1.0)
end
end
-- `BagSlotButton_OnEnter()` — THE REFERENCE'S NAME for the hover above, and the entry point
-- the four `CharacterBag<N>Slot` buttons actually call (their XML below). Published for the
-- same standing reason `ToggleBackpack` is: an addon replaces the client's global and
-- expects the client to call the replacement.
--
-- `Bagnon.lua:67` is the exact shape, and it is a hover-frequency call, not a rare one:
-- bBagSlotButton_OnEnter = BagSlotButton_OnEnter -- capture ours
-- BagSlotButton_OnEnter = BagnonBlizBag_OnEnter -- replace the global
-- Bagnon's replacement highlights the matching slots in its own window and then calls the
-- capture. With the global absent that capture was nil, and — because nothing here ever
-- looked the global up — the replacement was never reached to raise on it: Bagnon's
-- highlight was simply, silently inert. Corpus demand is exactly one addon (this one) at
-- two sites; its siblings `BagSlotButton_OnClick`/`_OnDrag`/`_OnShiftClick`/
-- `BackpackButton_OnClick` score ZERO and are deliberately not published (1210).
--
-- The reference's body takes no argument and reads `this`; so does this one.
function BagSlotButton_OnEnter()
BenillaBagBarSlot_OnEnter(this)
end
-- ref OnLeave for both (l.100-102 / the template's own): a plain Hide, no fade — the item
-- half of the split BenillaUnitFrame_OnLeave's comment describes.
function BenillaBagBarButton_OnLeave()
if GameTooltip then GameTooltip:Hide() end
end
-- ── The keyring (decision 0765) ──────────────────────────────────────────────────────────
-- ref ToggleKeyRing (ContainerFrame.lua:757-771). The reference generates the window on
-- demand from a pool of generic ContainerFrames sized to GetKeyRingSize(); ours is a fixed
-- window like the other six, so the toggle is the bag bar's own show/hide shape and the
-- size arrives through the feed. The ref's IsOptionFrameOpen() guard has no analogue here
-- (our bag-bar buttons carry none either — the game menu greys the row instead, 0674), and
-- its SetButtonPulse stop is moot with no pulse to stop (0304).
function ToggleKeyRing()
local frame = getglobal("BenillaKeyRingFrame")
if not frame then return end
if frame:IsShown() then
frame:Hide()
else
BenillaBagWindow_Update(frame)
frame:Show()
end
end
-- ref PutKeyInKeyRing (ContainerFrame.lua:740-755) — dropping a held key onto the keyring
-- BUTTON files it in the first free slot. "Pick up" from an empty slot IS the place gesture
-- (PickupContainerItem is the one entry point: empty cursor picks up, a held cursor places),
-- which is why the reference's own line reads as a pickup.
--
-- One deliberate un-quirking: the ref passes `NO_EMPTY_KEYRING_SLOTS`, but GlobalStrings
-- only ever defined `NO_EMPTY_KEYRING_SLOTS_ERROR` — so the real client silently prints
-- nothing when your keyring is full. We print the string that exists.
function PutKeyInKeyRing()
for i = 1, GetKeyRingSize() do
-- Occupancy is `texture or itemCount`, not `texture` alone. The reference tests
-- the texture (ContainerFrame_Update) because ITS client always has the icon by
-- the time a slot is populated; ours can hold an item whose icon has not resolved
-- yet, and reading that as an empty slot would file the key on top of it
-- (decision 1199 — the transient our five-value shape exposed).
local texture, itemCount = GetContainerItemInfo(KEYRING_CONTAINER, i)
if texture == nil and itemCount == nil then
PickupContainerItem(KEYRING_CONTAINER, i)
return
end
end
if UIErrorsFrame then
UIErrorsFrame:AddMessage(NO_EMPTY_KEYRING_SLOTS_ERROR, 1.0, 0.1, 0.1)
end
end
-- ref KeyRingButton's OnLoad/OnClick/OnReceiveDrag (MainMenuBarBagButtons.xml l.142-199).
-- The button is authored HIDDEN and only ever revealed by MainMenuBar_UpdateKeyRing
-- (ActionBar.xml), which is where the whole show/hide law and its divergence note live.
function BenillaKeyRingButton_OnLoad(button)
button:SetID(KEYRING_CONTAINER)
button:RegisterForClicks("LeftButtonUp", "RightButtonUp")
button:RegisterForDrag("LeftButton")
-- The ref watches PLAYER_ENTERING_WORLD / VARIABLES_LOADED / BAG_UPDATE on the bar's art
-- frame; with no saved variable there is nothing for VARIABLES_LOADED to restore, and
-- BAG_UPDATE is what actually carries "a key just arrived".
button:RegisterEvent("PLAYER_ENTERING_WORLD")
button:RegisterEvent("BAG_UPDATE")
if BenillaActionBarArt_SeatAbove then BenillaActionBarArt_SeatAbove(button) end
BenillaKeyRingButton_OnEvent()
end
function BenillaKeyRingButton_OnEvent()
if MainMenuBar_UpdateKeyRing then MainMenuBar_UpdateKeyRing() end
end
function BenillaKeyRingButton_OnClick(button)
-- Holding something ⇒ file it; empty-handed ⇒ open/close the keyring (ref l.166-173).
if CursorHasItem() then
PutKeyInKeyRing()
else
ToggleKeyRing()
end
-- Re-derive the lit ring from the window, the same provisional-toggle tail every other
-- bag-bar button uses.
local frame = getglobal("BenillaKeyRingFrame")
button:SetChecked(frame and frame:IsShown())
end
-- ref l.180-184: the plain KEYRING title plus the ref's own empty AddLine (which reserves
-- the second row the binding key would print into — we ship no TOGGLEKEYRING binding, the
-- same gap the bag slots' "(F1)".."(F4)" suffixes have).
function BenillaKeyRingButton_OnEnter(button)
if not GameTooltip then return end
GameTooltip:SetOwner(button, "ANCHOR_LEFT")
GameTooltip:SetText(KEYRING, 1.0, 1.0, 1.0)
end
function BenillaBagBarSlot_Update(button)
local invSlot = BenillaBagBarSlot_InvSlot(button)
local texture = GetInventoryItemTexture("player", invSlot)
button.icon:SetTexture(texture or "Interface\\PaperDoll\\UI-PaperDoll-Slot-Bag")
-- Dim while the bag is up on the cursor or a pending equip is in flight (the doll's own
-- drag feedback, IsInventoryItemLocked — the bag-slot twin of BenillaBagSlot_Update).
if IsInventoryItemLocked(invSlot) then
button.icon:SetVertexColor(0.4, 0.4, 0.4)
elseif button:IsEnabled() then
button.icon:SetVertexColor(1, 1, 1)
else
-- Disabled by Disable_BagButtons below: keep the grey. The reference needs no such
-- arm — its desaturation is a shader flag on the texture, untouched by an Update
-- that only re-sets the image — but ours IS a vertex colour (UiPanels.xml
-- SetDesaturation), so a bag update behind the open game menu would otherwise
-- repaint the icon bright under a button that stayed dead.
button.icon:SetVertexColor(0.5, 0.5, 0.5)
end
end
-- ── The item-push drop animation (decision 0887) ─────────────────────────────────────────
-- An item lands in a bag: its icon pops in above the bag bar, hangs there, then falls into
-- the button that took it and shrinks away. ref ItemAnim (MainMenuBarBagButtons.xml l.4-16 +
-- .lua l.68-80, .lua l.92-94): each bag button owns a hidden child that, on ITEM_PUSH
-- naming its own container, swaps in the pushed item's icon, rewinds to sequence time 0 and
-- shows itself; OnAnimFinished hides it again. A second push on the same button RESTARTS it
-- (SetSequenceTime(0,0)); pushes into different bags run independently. That is exactly the
-- machine below, with the reference's ITEM_PUSH `arg1` translated from its inventory-slot
-- vocabulary (20..23) into ours (bagId 1..4) app-side — ui_loot.rs `push_container` carries
-- the byte-verified selector.
--
-- The reference's is a <Model> playing Interface\ItemAnimations\ForcedBackpackItem.mdx — a
-- single 4-vertex quad on one animated bone, i.e. a 2D icon card with keyframed scale, alpha
-- and offset. benilla has no Model widget (decision 0137's Cooldown took the same posture:
-- model the mechanism, let our own renderer draw it), so it is a Texture driven by the
-- model's OWN tracks, read out of the shipped .m2 with `benilla-extract m2anim`/`m2bones`:
--
-- sequence 0, CLAMP (plays once), duration 1.000 s
-- texture-weight (alpha) 0.000→0.0 0.133→1.0 0.267→1.0 1.000→0.0 linear
-- bone 0 scale 0.000→1.0 0.133→1.2 0.267→1.0 1.000→0.014 linear
-- bone 0 translation 0.000→(0,0) 0.500→(0,0) 1.000→(+0.010,-0.040)
-- the quad itself: 0.03 x 0.03 model units, blend Alpha, unlit, no depth write
--
-- So: fade in over the first 133 ms while swelling to 1.2x; settle back to full size by
-- 267 ms; from there dwindle and fade continuously for the remaining 733 ms — while the
-- POSITION holds until the half-second and only then falls. The three tracks deliberately
-- do not share a schedule: the card is already down to ~0.69 size and ~0.68 alpha when it
-- starts moving, so the drop reads as the icon being drawn into the bag rather than as a
-- solid tile sliding. Those numbers are the file's, not ours.
--
-- THE MODEL-UNIT → PIXEL SCALE is no longer ours to calibrate: the <Model> widget's
-- projection is RE-recorded now (wow-re `system/ui/scratch/modelframe-animation-clock.md`,
-- dispatched over the pet bar's B228 and folded back with it). The ortho leg gives
--
-- px per model unit = (5/3) * modelScale * layoutScale * screenHeightPx
--
-- which in FrameXML units is **1280 * modelScale * layoutScale**, aspect- and
-- resolution-independent, with the model ORIGIN at the frame rect's bottom-left. The ref's
-- `ItemAnimTemplate` (MainMenuBarBagButtons.xml:4) carries **no `scale` attribute**, so
-- modelScale is 1 and the constant is a flat 1280. Decision 0887's named gap is closed.
--
-- This REPLACES a back-fit, and the back-fit is worth naming because it looked like
-- evidence. The old 1200 came from assuming CooldownFrameTemplate's 0.04-unit sweep
-- *exactly* covers a 36 px action button at its scale="0.75" (⇒ K = 1200). The assumption
-- is the error: at the real projection that sweep is 0.04 · 1280 · 0.75 = **38.4 px**, so
-- it overhangs its button by 6.7% rather than fitting it — exactly as the autocast shine's
-- 30.72 px square overhangs its 30 px button. Solving against a wrong premise produced a
-- number that then "cross-checked" against a second model derived the same wrong way, and
-- both files cited the agreement as the reason to trust it.
--
-- Everything below is the model's own ratios times this constant, so the correction lands
-- in one place and the animation stays in proportion.
BENILLA_ITEM_PUSH_PX_PER_UNIT = 1280
BENILLA_ITEM_PUSH_DURATION = 1.0
-- ── The widget rect, and why the card is not placed against the button ─────────────────
-- A `<Model>` with no `<Size>` and no `setAllPoints` takes its rect from the **M2 header
-- bounding box** (`MD20+0xB4`, max−min per axis), consumed RAW as layout units — unlike
-- CSimpleTexture's texel fallback, there is no AbsDimension conversion. So the ref's
-- ItemAnim widget is **42.41 x 124.72**, nothing like the 36-square everyone assumes from
-- looking at the XML, and **model-space (0,0,0) lands on that rect's BOTTOM-LEFT corner**.
-- (wow-re `system/ui/scratch/modelframe-implicit-size-law.md`, §5-cross-checked: vtable
-- 0x81c5d8 +0x1c/+0x20 -> 0x76d080/0x76d0d0, bbox source 0x713640 at `713661 add esi,0xb4`.
-- The one worker who read `MD20+0xD0` — the VertexBox — got an all-zero box and concluded
-- the widget was degenerate; that wrong premise is exactly what the pair round exists to
-- catch, and it is why this looked unresolvable in 0887.)
--
-- That rect is the whole reason this animation reads the way it does: it stands 124.72 tall
-- off the button's own bottom edge, so the card's travel band reaches **54 px above the
-- button's top**. Decision 0887 could not know that and placed the card against the
-- button's CENTER instead, which is what put it in the wrong place in x and gave it no
-- horizontal drift at all.
BENILLA_ITEM_PUSH_FRAME_W = 42.41
BENILLA_ITEM_PUSH_FRAME_H = 124.72
-- Where that rect hangs off the button: the ref anchors the widget's BOTTOMRIGHT to the
-- button's, 10 in from the right for the five bag buttons and flush (0) for the keyring —
-- MainMenuBarBagButtons.xml l.19-27 (BagSlotButtonTemplate) and l.152-160 (KeyRingButton).
-- Per-button, so it is an OnLoad argument, not a constant.
BENILLA_ITEM_PUSH_ANCHOR_X = -10
-- ── The model's own numbers, unrounded ────────────────────────────────────────────────
-- Every one of these was rounded in 0887 and every rounding moved the card. The quad span
-- read as 0.03 (it is 0.0288 — a 4% size error), and the translation as (0.010, -0.040)
-- (which alone accounts for half a pixel of the landing point).
--
-- The bone transform is `v' = pivot + T(t) + S(t) * (v - pivot)` — three wow-re workers
-- derived that independently in three matrix conventions and agreed. The pivot
-- (M2CompBone+0x60) is NOT the quad's centre: it sits 0.23 px right and 0.07 px above it,
-- so the card shrinks *almost* in place and drifts a fifth of a pixel doing it. Keeping
-- the pivot as its own term rather than folding it into the centre is what makes this
-- reproduce the reference exactly at all three sampled times instead of nearly.
-- Bone 0's ROTATION track is empty (nKeys = 0), so there is no roll to model.
BENILLA_ITEM_PUSH_PIVOT_X = 0.017580900341272354
BENILLA_ITEM_PUSH_PIVOT_Y = 0.053958501666784286
BENILLA_ITEM_PUSH_QUAD_X = 0.017400000942870975
BENILLA_ITEM_PUSH_QUAD_Y = 0.053900001570582390
BENILLA_ITEM_PUSH_QUAD_SPAN = 0.028800001833587885
-- {time, value} keyframes, linear between (M2 track interpolation 1) — the file's own keys,
-- at full precision. The three tracks keep DIFFERENT schedules and that is the whole feel:
-- the card fades up while swelling to 1.2x over 133 ms, settles back to full size at
-- 267 ms, then dwindles and fades *continuously* for the remaining 733 ms — while its
-- position holds until the half second and only then falls. So it is already at ~0.69 size
-- and ~0.68 alpha when it starts to move, and it is invisible by the time it lands.
BENILLA_ITEM_PUSH_ALPHA = { {0.000, 0.0}, {0.133, 1.0}, {0.267, 1.0}, {1.000, 0.0} }
BENILLA_ITEM_PUSH_SCALE = { {0.000, 1.0}, {0.133, 1.2000000476837158}, {0.267, 1.0}, {1.000, 0.01441549975425005} }
-- Translation is per-AXIS: the card falls 51.4 px and drifts 13.3 px RIGHT, so one shared
-- 0..1 "drop" curve (0887's shape) cannot express it — the old code had no horizontal term
-- at all and moved the card the wrong way in x when it inferred one.
BENILLA_ITEM_PUSH_TRANS_X = { {0.000, 0.0}, {0.500, 0.0}, {1.000, 0.010394600220024586} }
BENILLA_ITEM_PUSH_TRANS_Y = { {0.000, 0.0}, {0.500, 0.0}, {1.000, -0.040167398750782013} }
-- Sample a {time, value} keyframe list at `t`, linear between keys, clamped at both ends
-- (the M2 sampler's own shape for a CLAMP sequence).
function BenillaItemPushAnim_Sample(track, t)
local last = table.getn(track)
if t <= track[1][1] then return track[1][2] end
for i = 2, last do
local key = track[i]
if t <= key[1] then
local prev = track[i - 1]
local span = key[1] - prev[1]
if span <= 0 then return key[2] end
return prev[2] + (key[2] - prev[2]) * (t - prev[1]) / span
end
end
return track[last][2]
end
-- Place/size/fade the card for the current clock, from the model's own transform.
--
-- The card's centre in MODEL space is `pivot + T(t) + S(t)*(quadCentre - pivot)`; times the
-- projection constant that is its offset in layout units from the model origin, which is
-- the widget rect's bottom-left corner. The rect's own BOTTOMRIGHT hangs off the button's,
-- so the card is placed against the BUTTON'S BOTTOMRIGHT — never its centre — and the
-- frame width is what converts between the two.
--
-- On screen: a button-sized card appears with its bottom edge ~13.6 px ABOVE the button's
-- top, pops to 1.2x and back while fading in, hangs there through the first half second,
-- then falls down-and-right INTO the button, shrinking to a half-pixel point at its centre
-- as it fades out. Reproduces the reference at t = 0, 0.133 and 1.0 to the hundredth of a
-- pixel (`modelframe-implicit-size-law.md` §3's table).
--
-- NOT modelled, deliberately, and both are named in the record: the reference's widget
-- CLIPS the card to its own rect, which shaves 1.94 px off the right edge at the 1.2x peak
-- **at 4:3 only** (16:10 and 16:9 clear it); and the widget clears the depth buffer inside
-- that 124.72-tall rect every paint. benilla draws this as a Texture, not a Model, so it
-- inherits neither — reproducing an aspect-conditional sliver would be hard-coding a
-- rendering artefact rather than modelling the mechanism that causes it.
function BenillaItemPushAnim_Paint(anim)
local t = anim.elapsed
local k = BENILLA_ITEM_PUSH_PX_PER_UNIT
local scale = BenillaItemPushAnim_Sample(BENILLA_ITEM_PUSH_SCALE, t)
local size = BENILLA_ITEM_PUSH_QUAD_SPAN * scale * k
local cx = (BENILLA_ITEM_PUSH_PIVOT_X
+ BenillaItemPushAnim_Sample(BENILLA_ITEM_PUSH_TRANS_X, t)
+ scale * (BENILLA_ITEM_PUSH_QUAD_X - BENILLA_ITEM_PUSH_PIVOT_X)) * k
local cy = (BENILLA_ITEM_PUSH_PIVOT_Y
+ BenillaItemPushAnim_Sample(BENILLA_ITEM_PUSH_TRANS_Y, t)
+ scale * (BENILLA_ITEM_PUSH_QUAD_Y - BENILLA_ITEM_PUSH_PIVOT_Y)) * k
anim:SetWidth(size)
anim:SetHeight(size)
anim:SetPoint("CENTER", anim:GetParent(), "BOTTOMRIGHT",
anim.anchorX - BENILLA_ITEM_PUSH_FRAME_W + cx, cy)
anim:SetAlpha(BenillaItemPushAnim_Sample(BENILLA_ITEM_PUSH_ALPHA, t))
end
-- ref ItemAnim_OnLoad (l.68-70): the card listens for ITEM_PUSH and nothing else. It is
-- authored hidden, so it costs no OnUpdate until a push shows it (events reach hidden
-- frames; OnUpdate does not — the reference's own split).
function BenillaItemPushAnim_OnLoad(anim, containerId, anchorX)
anim.containerId = containerId
-- The widget's own offset from the button's BOTTOMRIGHT: -10 for the five bag buttons,
-- flush 0 for the keyring (the ref's two different anchors — see the constant block).
anim.anchorX = anchorX or BENILLA_ITEM_PUSH_ANCHOR_X
anim.icon = getglobal(anim:GetName() .. "Icon")
anim.elapsed = 0
anim:RegisterEvent("ITEM_PUSH")
end
-- ref ItemAnim_OnEvent (l.72-80): `if this:GetParent():GetID() == arg1`, then
-- ReplaceIconTexture(arg2) / SetSequence(0) / SetSequenceTime(0,0) / Show. A push into a bag
-- with no button (a bank bag) matches nothing and animates nowhere, there and here.
function BenillaItemPushAnim_OnEvent(anim)
if arg1 ~= anim.containerId then return end
anim.icon:SetTexture(arg2)
anim.elapsed = 0
-- Seat the card above its button rather than at load: the bag bar's own level is set in
-- the BUTTON's OnLoad (BenillaActionBarArt_SeatAbove), which the loader may run after
-- this child exists. Re-deriving it per push is one call and can never read it stale.
anim:SetFrameLevel(anim:GetParent():GetFrameLevel() + 1)
BenillaItemPushAnim_Paint(anim)
anim:Show()
end
-- The CLAMP sequence's end is the ref's OnAnimFinished → this:Hide() (l.92-94).
function BenillaItemPushAnim_OnUpdate(anim, elapsed)
anim.elapsed = anim.elapsed + (elapsed or 0)
if anim.elapsed >= BENILLA_ITEM_PUSH_DURATION then
anim:Hide()
return
end
BenillaItemPushAnim_Paint(anim)
end
-- The bag BAR's five buttons (the backpack toggle + the four equipped-bag slots) — the row
-- Disable_/Enable_BagButtons greys as a unit. Distinct from UiPanels.xml's BAG_WINDOWS,
-- which is the five bag *windows* those buttons open.
BENILLA_BAG_BUTTONS = { "MainMenuBarBackpackButton", "CharacterBag0Slot", "CharacterBag1Slot",
"CharacterBag2Slot", "CharacterBag3Slot" }
-- Disable_BagButtons / Enable_BagButtons — ref MainMenuBarBagButtons.lua l.102-128, called
-- by GameMenuFrame's OnShow/OnHide (decision 0674) and by nothing else: while the game menu
-- holds the screen, the whole bag row greys out. That is the *visible* half of a rule the
-- panel manager already enforces (ShowUIPanel's native-center gate refuses every window
-- while the menu is up — UiPanels.xml `CanOpenPanels`); without it the bags would look
-- clickable and simply do nothing.
--
-- Mapping onto benilla's row: the ref's MainMenuBarBackpackButton is MainMenuBarBackpackButton and
-- CharacterBag0..3Slot are CharacterBag0Slot..4 — and in BOTH cases the greying goes onto
-- the button's `$parentIcon` LAYER texture, never onto a state texture. That is the
-- reference's own structure and it is load-bearing here: a disabled button draws its
-- DisabledTexture, and with none set it draws no state texture at all
-- (`ButtonState::region_visible`), so greying a NormalTexture would grey something already
-- gone. The four slots' UI-Quickslot2 rings ARE state textures and do vanish while
-- disabled unless the slot states one explicitly — so what you should see is the
-- ring stay put and the bag icon grey — which is why each slot repeats its ring as
-- DisabledTexture (see the slot instances below).
function Disable_BagButtons()
for _, button in ipairs(BENILLA_BAG_BUTTONS) do
local b = getglobal(button)
b:Disable()
SetDesaturation(b.icon, 1)
end
end
function Enable_BagButtons()
for _, button in ipairs(BENILLA_BAG_BUTTONS) do
local b = getglobal(button)
b:Enable()
SetDesaturation(b.icon, nil)
end
-- The four slots' own paint decides their restored colour: an item locked on the cursor
-- wants its 0.4 grey back, which the un-desaturate above does not restore (it clears
-- the greyscale flag, not the tint).
for i = 1, 4 do
BenillaBagBarSlot_Update(getglobal("CharacterBag" .. (i - 1) .. "Slot"))
end
end
]]></Script>
<!--
One bag slot (ref ItemButtonTemplate, scratchpad `ref-ItemButtonTemplate.xml` l.4-45): a 37×37
button (l.5-7); the icon fills the button (l.10, no size/anchors → owner-sized); the "xN" count
is NumberFontNormal, right-justified, at BOTTOMRIGHT (-5,2) (l.11-19); the NormalTexture is the
64×64 UI-Quickslot2 ring centered at (0,-1) (l.31-42); PushedTexture UI-Quickslot-Depress
(l.43); HighlightTexture ButtonHilight-Square ADD (l.44). Shared by every window (backpack +
the four equipped bags) — physIndex-driven, see BenillaBagSlot_OnLoad's own comment.
-->
<Button name="BenillaBagSlotTemplate" virtual="true">
<Size><AbsDimension x="37" y="37"/></Size>
<Layers>
<Layer level="BACKGROUND">
<Texture name="$parentIcon"/>
</Layer>
<Layer level="OVERLAY">
<FontString name="$parentCount" inherits="NumberFontNormal" justifyH="RIGHT">
<Anchors>
<Anchor point="BOTTOMRIGHT" relativePoint="BOTTOMRIGHT"><Offset><AbsDimension x="-5" y="2"/></Offset></Anchor>
</Anchors>
</FontString>
</Layer>
</Layers>
<Frames>
<!-- The item's use-cooldown sweep (ref ContainerFrame.xml:13 — $parentCooldown
inherits CooldownFrameTemplate; ours is the native Cooldown widget, same as the
action buttons'). Driven by BenillaBagSlot_Update / BAG_UPDATE_COOLDOWN via
GetContainerItemCooldown + CooldownFrame_SetTimer (ActionBar.xml's transcription,
loaded before this file). -->
<Cooldown name="$parentCooldown" hidden="true">
<Size><AbsDimension x="36" y="36"/></Size>
<Anchors>
<Anchor point="CENTER" relativePoint="CENTER"><Offset><AbsDimension x="0" y="-1"/></Offset></Anchor>
</Anchors>
</Cooldown>
</Frames>
<NormalTexture file="Interface\Buttons\UI-Quickslot2">
<Size><AbsDimension x="64" y="64"/></Size>
<Anchors>
<Anchor point="CENTER" relativePoint="CENTER"><Offset><AbsDimension x="0" y="-1"/></Offset></Anchor>
</Anchors>
</NormalTexture>
<PushedTexture file="Interface\Buttons\UI-Quickslot-Depress"/>
<HighlightTexture file="Interface\Buttons\ButtonHilight-Square" alphaMode="ADD"/>
<Scripts>
<OnClick>BenillaBagSlot_OnClick(self, arg1)</OnClick>
<OnEnter>BenillaBagSlot_OnEnter(self)</OnEnter>
<OnLeave>BenillaBagSlot_OnLeave(self)</OnLeave>
<OnUpdate>BenillaBagSlot_OnUpdate(self)</OnUpdate>
<!-- The drag trio (decision 0216 §3): both route to the SAME left-click path the
reference uses for both gestures (ContainerFrame.xml:40-55) — "LeftButton" hardcoded,
exactly as the reference calls it, since RegisterForDrag above only ever arms Left —
`true` (ignoreModifiers) suppresses the shift-split fork, matching the ref's own
ignoreModifiers contract for a drag/receive. -->
<OnDragStart>BenillaBagSlot_OnClick(self, "LeftButton", true)</OnDragStart>
<OnReceiveDrag>BenillaBagSlot_OnClick(self, "LeftButton", true)</OnReceiveDrag>
</Scripts>
</Button>
<Frame name="BenillaBagFrame" id="0" hidden="true">
<!-- The real backpack frame: 192 wide (CONTAINER_WIDTH, ContainerFrame.lua l.8,412) × 240 tall
(l.325), anchored to UIParent BOTTOMRIGHT with CONTAINER_OFFSET_X=0 / CONTAINER_OFFSET_Y=70
(l.11-12) — the STACK's own anchor once shown (BenillaBagStack_Relayout); this static
anchor is only the pre-first-show placeholder. -->
<Size><AbsDimension x="192" y="240"/></Size>
<Anchors>
<Anchor point="BOTTOMRIGHT" relativePoint="BOTTOMRIGHT">
<Offset><AbsDimension x="0" y="70"/></Offset>
</Anchor>
</Anchors>
<Layers>
<!-- The bag portrait: 40×40 at the frame TOPLEFT (7,-5) (ref-ContainerFrame.xml l.67-78).
BACKGROUND (below the ARTWORK background art) so it shows through the background's
portrait-ring cut-out — the same layering the loot-frame skull uses. Static
Button-Backpack-Up (the backpack portrait art; the real client's C-side
SetBagPortaitTexture). -->
<Layer level="BACKGROUND">
<Texture name="BenillaBagPortrait" file="Interface\Buttons\Button-Backpack-Up">
<Size><AbsDimension x="40" y="40"/></Size>
<Anchors>
<Anchor point="TOPLEFT" relativePoint="TOPLEFT"><Offset><AbsDimension x="7" y="-5"/></Offset></Anchor>
</Anchors>
</Texture>
</Layer>
<Layer level="ARTWORK">
<!-- The single backpack background slab (ContainerFrame.lua l.316-318, the id==0
branch): UI-BackpackBackground, 256×256, full TexCoords (0,1,0,1), anchored TOPRIGHT
of the frame (ref-ContainerFrame.xml l.86). Wider/taller than the 192×240 frame — it
overhangs left and down, exactly as the real art does; the painted slot wells assume
this TOPRIGHT anchoring. The middle/bottom strips are hidden for the backpack
(l.321-324), so this one texture is the whole window art. -->
<Texture name="BenillaBagBackground" file="Interface\ContainerFrame\UI-BackpackBackground">
<Size><AbsDimension x="256" y="256"/></Size>
<Anchors>
<Anchor point="TOPRIGHT" relativePoint="TOPRIGHT"><Offset><AbsDimension x="0" y="0"/></Offset></Anchor>
</Anchors>
<TexCoords left="0" right="1" top="0" bottom="1"/>
</Texture>
<!-- Title: the bag name, GameFontHighlight, 112×12 at the frame TOPLEFT (47,-10)
(ref-ContainerFrame.xml l.120-131). BenillaBagWindow_Update keeps it current via
GetBagName(0), which always answers "Backpack" — the authored text is just the
pre-first-Update placeholder. -->
<FontString name="BenillaBagFrameName" inherits="GameFontHighlight" text="Backpack">
<Size><AbsDimension x="112" y="12"/></Size>
<Anchors>
<Anchor point="TOPLEFT" relativePoint="TOPLEFT"><Offset><AbsDimension x="47" y="-10"/></Offset></Anchor>
</Anchors>
</FontString>
</Layer>
</Layers>
<Frames>
<!--
The item grid (ContainerFrame.lua l.425-444). Authored verbatim from the real backpack
anchor chain: Item1 at the frame BOTTOMRIGHT (-12,30) (the id==0 first-button offset,
l.433); each new row (j-1 divisible by NUM_CONTAINER_COLUMNS=4) anchors BOTTOMRIGHT to
Item(j-4)'s TOPRIGHT (0,4) (l.440), and every other button BOTTOMRIGHT to Item(j-1)'s
BOTTOMLEFT (-5,0) (l.442). So button 1 is bottom-right and the chain fills up-and-left.
Slot1 (self, 1) is CHAIN POSITION 1 (physIndex), not the game slot — BenillaBagSlot_Update
resolves the game slot dynamically (size - physIndex + 1 = 16 - physIndex + 1 for the
backpack's fixed 16), so slot 1 renders TOP-LEFT and slot 16 BOTTOM-RIGHT, matching the
ref's own visual order.
-->
<Button name="BenillaBagSlot1" inherits="BenillaBagSlotTemplate">
<Anchors><Anchor point="BOTTOMRIGHT" relativePoint="BOTTOMRIGHT"><Offset><AbsDimension x="-12" y="30"/></Offset></Anchor></Anchors>
<Scripts><OnLoad>BenillaBagSlot_OnLoad(self, 1)</OnLoad></Scripts>
</Button>
<Button name="BenillaBagSlot2" inherits="BenillaBagSlotTemplate">
<Anchors><Anchor point="BOTTOMRIGHT" relativeTo="BenillaBagSlot1" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="-5" y="0"/></Offset></Anchor></Anchors>
<Scripts><OnLoad>BenillaBagSlot_OnLoad(self, 2)</OnLoad></Scripts>
</Button>
<Button name="BenillaBagSlot3" inherits="BenillaBagSlotTemplate">
<Anchors><Anchor point="BOTTOMRIGHT" relativeTo="BenillaBagSlot2" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="-5" y="0"/></Offset></Anchor></Anchors>
<Scripts><OnLoad>BenillaBagSlot_OnLoad(self, 3)</OnLoad></Scripts>
</Button>
<Button name="BenillaBagSlot4" inherits="BenillaBagSlotTemplate">
<Anchors><Anchor point="BOTTOMRIGHT" relativeTo="BenillaBagSlot3" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="-5" y="0"/></Offset></Anchor></Anchors>
<Scripts><OnLoad>BenillaBagSlot_OnLoad(self, 4)</OnLoad></Scripts>
</Button>
<Button name="BenillaBagSlot5" inherits="BenillaBagSlotTemplate">
<Anchors><Anchor point="BOTTOMRIGHT" relativeTo="BenillaBagSlot1" relativePoint="TOPRIGHT"><Offset><AbsDimension x="0" y="4"/></Offset></Anchor></Anchors>
<Scripts><OnLoad>BenillaBagSlot_OnLoad(self, 5)</OnLoad></Scripts>
</Button>
<Button name="BenillaBagSlot6" inherits="BenillaBagSlotTemplate">
<Anchors><Anchor point="BOTTOMRIGHT" relativeTo="BenillaBagSlot5" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="-5" y="0"/></Offset></Anchor></Anchors>
<Scripts><OnLoad>BenillaBagSlot_OnLoad(self, 6)</OnLoad></Scripts>
</Button>
<Button name="BenillaBagSlot7" inherits="BenillaBagSlotTemplate">
<Anchors><Anchor point="BOTTOMRIGHT" relativeTo="BenillaBagSlot6" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="-5" y="0"/></Offset></Anchor></Anchors>
<Scripts><OnLoad>BenillaBagSlot_OnLoad(self, 7)</OnLoad></Scripts>
</Button>
<Button name="BenillaBagSlot8" inherits="BenillaBagSlotTemplate">
<Anchors><Anchor point="BOTTOMRIGHT" relativeTo="BenillaBagSlot7" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="-5" y="0"/></Offset></Anchor></Anchors>
<Scripts><OnLoad>BenillaBagSlot_OnLoad(self, 8)</OnLoad></Scripts>
</Button>
<Button name="BenillaBagSlot9" inherits="BenillaBagSlotTemplate">
<Anchors><Anchor point="BOTTOMRIGHT" relativeTo="BenillaBagSlot5" relativePoint="TOPRIGHT"><Offset><AbsDimension x="0" y="4"/></Offset></Anchor></Anchors>
<Scripts><OnLoad>BenillaBagSlot_OnLoad(self, 9)</OnLoad></Scripts>
</Button>
<Button name="BenillaBagSlot10" inherits="BenillaBagSlotTemplate">
<Anchors><Anchor point="BOTTOMRIGHT" relativeTo="BenillaBagSlot9" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="-5" y="0"/></Offset></Anchor></Anchors>
<Scripts><OnLoad>BenillaBagSlot_OnLoad(self, 10)</OnLoad></Scripts>
</Button>
<Button name="BenillaBagSlot11" inherits="BenillaBagSlotTemplate">
<Anchors><Anchor point="BOTTOMRIGHT" relativeTo="BenillaBagSlot10" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="-5" y="0"/></Offset></Anchor></Anchors>
<Scripts><OnLoad>BenillaBagSlot_OnLoad(self, 11)</OnLoad></Scripts>
</Button>
<Button name="BenillaBagSlot12" inherits="BenillaBagSlotTemplate">
<Anchors><Anchor point="BOTTOMRIGHT" relativeTo="BenillaBagSlot11" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="-5" y="0"/></Offset></Anchor></Anchors>
<Scripts><OnLoad>BenillaBagSlot_OnLoad(self, 12)</OnLoad></Scripts>
</Button>
<Button name="BenillaBagSlot13" inherits="BenillaBagSlotTemplate">
<Anchors><Anchor point="BOTTOMRIGHT" relativeTo="BenillaBagSlot9" relativePoint="TOPRIGHT"><Offset><AbsDimension x="0" y="4"/></Offset></Anchor></Anchors>
<Scripts><OnLoad>BenillaBagSlot_OnLoad(self, 13)</OnLoad></Scripts>
</Button>
<Button name="BenillaBagSlot14" inherits="BenillaBagSlotTemplate">
<Anchors><Anchor point="BOTTOMRIGHT" relativeTo="BenillaBagSlot13" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="-5" y="0"/></Offset></Anchor></Anchors>
<Scripts><OnLoad>BenillaBagSlot_OnLoad(self, 14)</OnLoad></Scripts>
</Button>
<Button name="BenillaBagSlot15" inherits="BenillaBagSlotTemplate">
<Anchors><Anchor point="BOTTOMRIGHT" relativeTo="BenillaBagSlot14" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="-5" y="0"/></Offset></Anchor></Anchors>
<Scripts><OnLoad>BenillaBagSlot_OnLoad(self, 15)</OnLoad></Scripts>
</Button>
<Button name="BenillaBagSlot16" inherits="BenillaBagSlotTemplate">
<Anchors><Anchor point="BOTTOMRIGHT" relativeTo="BenillaBagSlot15" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="-5" y="0"/></Offset></Anchor></Anchors>
<Scripts><OnLoad>BenillaBagSlot_OnLoad(self, 16)</OnLoad></Scripts>
</Button>
<!-- The money purse (ContainerFrame.lua l.314 shows the MoneyFrame for the backpack; ref
SmallMoneyFrame anchored TOPRIGHT (-2,-216), ref-ContainerFrame.xml l.135-143). The
template pads 13px right of the copper coin (its content chains from x=-13 inside
the frame — same pad as the merchant purse), so the COIN edge the ref frame anchor
implies is -2-13 = -15. Three position-slots chained right-to-left with the real 4px
spacing (Coin1 rightmost, copper), filled copper-first and shrunk to content by
BenillaMoney_Set("BenillaBagMoney", GetMoney()); Coin2/3 chain to its left. -->
<Frame name="BenillaBagMoneyCoin1">
<Size><AbsDimension x="13" y="13"/></Size>
<Anchors>
<Anchor point="TOPRIGHT" relativePoint="TOPRIGHT"><Offset><AbsDimension x="-15" y="-216"/></Offset></Anchor>
</Anchors>
<Layers>
<Layer level="OVERLAY">
<Texture name="$parentIcon" file="Interface\MoneyFrame\UI-MoneyIcons">
<Size><AbsDimension x="13" y="13"/></Size>
<Anchors><Anchor point="RIGHT" relativePoint="RIGHT"/></Anchors>
<TexCoords left="0.5" right="0.75" top="0" bottom="1"/>
</Texture>
<FontString name="$parentNum" inherits="NumberFontNormal" justifyH="RIGHT">
<Size><AbsDimension x="20" y="13"/></Size>
<Anchors><Anchor point="RIGHT" relativePoint="RIGHT"><Offset><AbsDimension x="-13" y="0"/></Offset></Anchor></Anchors>
</FontString>
</Layer>
</Layers>
</Frame>
<Frame name="BenillaBagMoneyCoin2">
<Size><AbsDimension x="13" y="13"/></Size>
<Anchors>
<Anchor point="RIGHT" relativeTo="BenillaBagMoneyCoin1" relativePoint="LEFT"><Offset><AbsDimension x="-4" y="0"/></Offset></Anchor>
</Anchors>
<Layers>
<Layer level="OVERLAY">
<Texture name="$parentIcon" file="Interface\MoneyFrame\UI-MoneyIcons">
<Size><AbsDimension x="13" y="13"/></Size>
<Anchors><Anchor point="RIGHT" relativePoint="RIGHT"/></Anchors>
<TexCoords left="0.25" right="0.5" top="0" bottom="1"/>
</Texture>
<FontString name="$parentNum" inherits="NumberFontNormal" justifyH="RIGHT">
<Size><AbsDimension x="20" y="13"/></Size>
<Anchors><Anchor point="RIGHT" relativePoint="RIGHT"><Offset><AbsDimension x="-13" y="0"/></Offset></Anchor></Anchors>
</FontString>
</Layer>
</Layers>
</Frame>
<Frame name="BenillaBagMoneyCoin3">
<Size><AbsDimension x="13" y="13"/></Size>
<Anchors>
<Anchor point="RIGHT" relativeTo="BenillaBagMoneyCoin2" relativePoint="LEFT"><Offset><AbsDimension x="-4" y="0"/></Offset></Anchor>
</Anchors>
<Layers>
<Layer level="OVERLAY">
<Texture name="$parentIcon" file="Interface\MoneyFrame\UI-MoneyIcons">
<Size><AbsDimension x="13" y="13"/></Size>
<Anchors><Anchor point="RIGHT" relativePoint="RIGHT"/></Anchors>
<TexCoords left="0" right="0.25" top="0" bottom="1"/>
</Texture>
<FontString name="$parentNum" inherits="NumberFontNormal" justifyH="RIGHT">
<Size><AbsDimension x="20" y="13"/></Size>
<Anchors><Anchor point="RIGHT" relativePoint="RIGHT"><Offset><AbsDimension x="-13" y="0"/></Offset></Anchor></Anchors>
</FontString>
</Layer>
</Layers>
</Frame>
<!-- Close: the real ContainerFrame close button position, TOPRIGHT (0,-1)
(ref-ContainerFrame.xml l.217-224), on the minimize-button art UIPanelCloseButton
itself uses (ref-UIPanelTemplates.xml l.100-102 — the same art merchant/loot draw; our
engine ships no UIPanelCloseButton template, so we draw it, matching the house style).
A close hides the window (the standalone-addon equivalent of ToggleBag). -->
<Button name="BenillaBagCloseButton">
<Size><AbsDimension x="32" y="32"/></Size>
<Anchors>
<Anchor point="TOPRIGHT" relativePoint="TOPRIGHT"><Offset><AbsDimension x="0" y="-1"/></Offset></Anchor>
</Anchors>
<NormalTexture file="Interface\Buttons\UI-Panel-MinimizeButton-Up"/>
<PushedTexture file="Interface\Buttons\UI-Panel-MinimizeButton-Down"/>
<HighlightTexture file="Interface\Buttons\UI-Panel-MinimizeButton-Highlight" alphaMode="ADD"/>
<Scripts>
<OnClick>getglobal("BenillaBagFrame"):Hide()</OnClick>
</Scripts>
</Button>
</Frames>
<Scripts>
<OnLoad>BenillaBagFrame_OnLoad(self)</OnLoad>
<OnEvent>BenillaBagFrame_OnEvent(self)</OnEvent>
<OnShow>BenillaBagFrame_OnShow()</OnShow>
<OnHide>BenillaBagFrame_OnHide()</OnHide>
</Scripts>
</Frame>
<!--
The generalized equipped-bag window (decision 0216 slice 2): the same portrait + slot template
as the backpack, but a stitched UI-Bag-Components background that SNUG-FITS the bag's row count
(BenillaBagWindow_FitBackground) rather than the backpack's fixed slab. The authored 260 height
is only a placeholder — FitBackground sets the live height on every repaint (before Show). No
money frame; first slot at the ref's non-backpack offset (-12,9 vs the backpack's -12,30 —
ContainerFrame.lua l.435 vs l.433, the id==0 fork). Carries NO <Scripts> block — each of the
four concrete windows below supplies its own (so the template/instance merge never has to
reconcile two OnLoad bodies; see `framexml.rs::merge`'s doc — a template with no Scripts child
can't collide with the instance's).
-->
<Frame name="BenillaBagWindowTemplate" virtual="true" hidden="true">
<Size><AbsDimension x="192" y="260"/></Size>
<Anchors>
<Anchor point="BOTTOMRIGHT" relativePoint="BOTTOMRIGHT">
<Offset><AbsDimension x="0" y="70"/></Offset>
</Anchor>
</Anchors>
<Layers>
<Layer level="BACKGROUND">
<Texture name="$parentPortrait" file="Interface\Buttons\Button-Backpack-Up">
<Size><AbsDimension x="40" y="40"/></Size>
<Anchors>
<Anchor point="TOPLEFT" relativePoint="TOPLEFT"><Offset><AbsDimension x="7" y="-5"/></Offset></Anchor>
</Anchors>
</Texture>
</Layer>
<Layer level="ARTWORK">
<!-- The snug-fit background, STITCHED from the real client's UI-Bag-Components (256×512;
ContainerFrame.lua ContainerFrame_GenerateFrame, the id~=0 branch), so an equipped
bag's window is exactly its row count tall instead of the backpack's fixed 5-row
slab (decision 0216's flagged divergence, now closed). Three right-anchored 256-wide
pieces, all sized/tex-coorded per-bag by BenillaBagWindow_FitBackground (below): the
TOP carries the title bar + portrait ring + the top well row (a full 4-well row, or
a partial 2-well row for a size%4==2 bag — the reference's isPlusTwoBag fork); the
MIDDLE tiles the remaining full well rows (41px each, the button row stride, from
texture v 0.353515625 = the grid's top edge); the BOTTOM is the frame's bottom rim.
Authored values are a 2-row full-bag placeholder — every Show repaints through
FitBackground first, so the live geometry always wins. -->
<Texture name="$parentBgTop" file="Interface\ContainerFrame\UI-Bag-Components">
<Size><AbsDimension x="256" y="94"/></Size>
<Anchors>
<Anchor point="TOPRIGHT" relativePoint="TOPRIGHT"><Offset><AbsDimension x="0" y="0"/></Offset></Anchor>
</Anchors>
<TexCoords left="0" right="1" top="0.00390625" bottom="0.18359375"/>
</Texture>
<Texture name="$parentBgMiddle" file="Interface\ContainerFrame\UI-Bag-Components">
<Size><AbsDimension x="256" y="32"/></Size>
<Anchors>
<Anchor point="TOPRIGHT" relativeTo="$parentBgTop" relativePoint="BOTTOMRIGHT"><Offset><AbsDimension x="0" y="0"/></Offset></Anchor>
</Anchors>
<TexCoords left="0" right="1" top="0.353515625" bottom="0.416015625"/>
</Texture>
<Texture name="$parentBgBottom" file="Interface\ContainerFrame\UI-Bag-Components">
<Size><AbsDimension x="256" y="10"/></Size>
<Anchors>
<Anchor point="TOPRIGHT" relativeTo="$parentBgMiddle" relativePoint="BOTTOMRIGHT"><Offset><AbsDimension x="0" y="0"/></Offset></Anchor>
</Anchors>
<TexCoords left="0" right="1" top="0.330078125" bottom="0.349609375"/>
</Texture>
<FontString name="$parentName" inherits="GameFontHighlight">
<Size><AbsDimension x="112" y="12"/></Size>
<Anchors>
<Anchor point="TOPLEFT" relativePoint="TOPLEFT"><Offset><AbsDimension x="47" y="-10"/></Offset></Anchor>
</Anchors>
</FontString>
</Layer>
</Layers>
<Frames>
<!-- 20 slots, 5 rows × 4 columns, chain-anchored exactly like the backpack's own 16 (see
that block's comment) but from the non-backpack first-button offset (-12,9). -->
<!-- First slot at (-12, 9) — the reference's id~=0 first-button offset (the backpack's is
-12,30). The grid then seats itself on the baked wells for free, because the background
is built bottom-up from the same 41px stride: the rim is 10px, the button's bottom edge
sits 9px up, and every row above is +41. -->
<Button name="$parentSlot1" inherits="BenillaBagSlotTemplate">
<Anchors><Anchor point="BOTTOMRIGHT" relativePoint="BOTTOMRIGHT"><Offset><AbsDimension x="-12" y="9"/></Offset></Anchor></Anchors>
<Scripts><OnLoad>BenillaBagSlot_OnLoad(self, 1)</OnLoad></Scripts>
</Button>
<Button name="$parentSlot2" inherits="BenillaBagSlotTemplate">
<Anchors><Anchor point="BOTTOMRIGHT" relativeTo="$parentSlot1" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="-5" y="0"/></Offset></Anchor></Anchors>
<Scripts><OnLoad>BenillaBagSlot_OnLoad(self, 2)</OnLoad></Scripts>
</Button>
<Button name="$parentSlot3" inherits="BenillaBagSlotTemplate">
<Anchors><Anchor point="BOTTOMRIGHT" relativeTo="$parentSlot2" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="-5" y="0"/></Offset></Anchor></Anchors>
<Scripts><OnLoad>BenillaBagSlot_OnLoad(self, 3)</OnLoad></Scripts>
</Button>
<Button name="$parentSlot4" inherits="BenillaBagSlotTemplate">
<Anchors><Anchor point="BOTTOMRIGHT" relativeTo="$parentSlot3" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="-5" y="0"/></Offset></Anchor></Anchors>
<Scripts><OnLoad>BenillaBagSlot_OnLoad(self, 4)</OnLoad></Scripts>
</Button>
<Button name="$parentSlot5" inherits="BenillaBagSlotTemplate">
<Anchors><Anchor point="BOTTOMRIGHT" relativeTo="$parentSlot1" relativePoint="TOPRIGHT"><Offset><AbsDimension x="0" y="4"/></Offset></Anchor></Anchors>
<Scripts><OnLoad>BenillaBagSlot_OnLoad(self, 5)</OnLoad></Scripts>
</Button>
<Button name="$parentSlot6" inherits="BenillaBagSlotTemplate">
<Anchors><Anchor point="BOTTOMRIGHT" relativeTo="$parentSlot5" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="-5" y="0"/></Offset></Anchor></Anchors>
<Scripts><OnLoad>BenillaBagSlot_OnLoad(self, 6)</OnLoad></Scripts>
</Button>
<Button name="$parentSlot7" inherits="BenillaBagSlotTemplate">
<Anchors><Anchor point="BOTTOMRIGHT" relativeTo="$parentSlot6" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="-5" y="0"/></Offset></Anchor></Anchors>
<Scripts><OnLoad>BenillaBagSlot_OnLoad(self, 7)</OnLoad></Scripts>
</Button>
<Button name="$parentSlot8" inherits="BenillaBagSlotTemplate">
<Anchors><Anchor point="BOTTOMRIGHT" relativeTo="$parentSlot7" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="-5" y="0"/></Offset></Anchor></Anchors>
<Scripts><OnLoad>BenillaBagSlot_OnLoad(self, 8)</OnLoad></Scripts>
</Button>
<Button name="$parentSlot9" inherits="BenillaBagSlotTemplate">
<Anchors><Anchor point="BOTTOMRIGHT" relativeTo="$parentSlot5" relativePoint="TOPRIGHT"><Offset><AbsDimension x="0" y="4"/></Offset></Anchor></Anchors>
<Scripts><OnLoad>BenillaBagSlot_OnLoad(self, 9)</OnLoad></Scripts>
</Button>
<Button name="$parentSlot10" inherits="BenillaBagSlotTemplate">
<Anchors><Anchor point="BOTTOMRIGHT" relativeTo="$parentSlot9" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="-5" y="0"/></Offset></Anchor></Anchors>
<Scripts><OnLoad>BenillaBagSlot_OnLoad(self, 10)</OnLoad></Scripts>
</Button>
<Button name="$parentSlot11" inherits="BenillaBagSlotTemplate">
<Anchors><Anchor point="BOTTOMRIGHT" relativeTo="$parentSlot10" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="-5" y="0"/></Offset></Anchor></Anchors>
<Scripts><OnLoad>BenillaBagSlot_OnLoad(self, 11)</OnLoad></Scripts>
</Button>
<Button name="$parentSlot12" inherits="BenillaBagSlotTemplate">
<Anchors><Anchor point="BOTTOMRIGHT" relativeTo="$parentSlot11" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="-5" y="0"/></Offset></Anchor></Anchors>
<Scripts><OnLoad>BenillaBagSlot_OnLoad(self, 12)</OnLoad></Scripts>
</Button>
<Button name="$parentSlot13" inherits="BenillaBagSlotTemplate">
<Anchors><Anchor point="BOTTOMRIGHT" relativeTo="$parentSlot9" relativePoint="TOPRIGHT"><Offset><AbsDimension x="0" y="4"/></Offset></Anchor></Anchors>
<Scripts><OnLoad>BenillaBagSlot_OnLoad(self, 13)</OnLoad></Scripts>
</Button>
<Button name="$parentSlot14" inherits="BenillaBagSlotTemplate">
<Anchors><Anchor point="BOTTOMRIGHT" relativeTo="$parentSlot13" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="-5" y="0"/></Offset></Anchor></Anchors>
<Scripts><OnLoad>BenillaBagSlot_OnLoad(self, 14)</OnLoad></Scripts>
</Button>
<Button name="$parentSlot15" inherits="BenillaBagSlotTemplate">
<Anchors><Anchor point="BOTTOMRIGHT" relativeTo="$parentSlot14" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="-5" y="0"/></Offset></Anchor></Anchors>
<Scripts><OnLoad>BenillaBagSlot_OnLoad(self, 15)</OnLoad></Scripts>
</Button>
<Button name="$parentSlot16" inherits="BenillaBagSlotTemplate">
<Anchors><Anchor point="BOTTOMRIGHT" relativeTo="$parentSlot15" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="-5" y="0"/></Offset></Anchor></Anchors>
<Scripts><OnLoad>BenillaBagSlot_OnLoad(self, 16)</OnLoad></Scripts>
</Button>
<Button name="$parentSlot17" inherits="BenillaBagSlotTemplate">
<Anchors><Anchor point="BOTTOMRIGHT" relativeTo="$parentSlot13" relativePoint="TOPRIGHT"><Offset><AbsDimension x="0" y="4"/></Offset></Anchor></Anchors>
<Scripts><OnLoad>BenillaBagSlot_OnLoad(self, 17)</OnLoad></Scripts>
</Button>
<Button name="$parentSlot18" inherits="BenillaBagSlotTemplate">
<Anchors><Anchor point="BOTTOMRIGHT" relativeTo="$parentSlot17" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="-5" y="0"/></Offset></Anchor></Anchors>
<Scripts><OnLoad>BenillaBagSlot_OnLoad(self, 18)</OnLoad></Scripts>
</Button>
<Button name="$parentSlot19" inherits="BenillaBagSlotTemplate">
<Anchors><Anchor point="BOTTOMRIGHT" relativeTo="$parentSlot18" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="-5" y="0"/></Offset></Anchor></Anchors>
<Scripts><OnLoad>BenillaBagSlot_OnLoad(self, 19)</OnLoad></Scripts>
</Button>
<Button name="$parentSlot20" inherits="BenillaBagSlotTemplate">
<Anchors><Anchor point="BOTTOMRIGHT" relativeTo="$parentSlot19" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="-5" y="0"/></Offset></Anchor></Anchors>
<Scripts><OnLoad>BenillaBagSlot_OnLoad(self, 20)</OnLoad></Scripts>
</Button>
<!-- Close: same art/position as the backpack's, but resolves ITS OWN parent via `this`
(the button, legacy global) rather than a hardcoded name — one body, five windows. -->
<Button name="$parentCloseButton">
<Size><AbsDimension x="32" y="32"/></Size>
<Anchors>
<Anchor point="TOPRIGHT" relativePoint="TOPRIGHT"><Offset><AbsDimension x="0" y="-1"/></Offset></Anchor>
</Anchors>
<NormalTexture file="Interface\Buttons\UI-Panel-MinimizeButton-Up"/>
<PushedTexture file="Interface\Buttons\UI-Panel-MinimizeButton-Down"/>
<HighlightTexture file="Interface\Buttons\UI-Panel-MinimizeButton-Highlight" alphaMode="ADD"/>
<Scripts>
<OnClick>this:GetParent():Hide()</OnClick>
</Scripts>
</Button>
</Frames>
</Frame>
<!-- The four equipped-bag windows: id=1..4 (SetID applied before any child's OnLoad — the
loader's bottom-up materialize order), each supplying its OWN Scripts block (the template
above carries none, so there is nothing to merge/collide with). -->
<Frame name="BenillaBagFrame1" id="1" inherits="BenillaBagWindowTemplate">
<Scripts>
<OnLoad>BenillaBagFrame_OnLoad(self)</OnLoad>
<OnEvent>BenillaBagFrame_OnEvent(self)</OnEvent>
<OnShow>BenillaBagFrame_OnShow()</OnShow>
<OnHide>BenillaBagFrame_OnHide()</OnHide>
</Scripts>
</Frame>
<Frame name="BenillaBagFrame2" id="2" inherits="BenillaBagWindowTemplate">
<Scripts>
<OnLoad>BenillaBagFrame_OnLoad(self)</OnLoad>
<OnEvent>BenillaBagFrame_OnEvent(self)</OnEvent>
<OnShow>BenillaBagFrame_OnShow()</OnShow>
<OnHide>BenillaBagFrame_OnHide()</OnHide>
</Scripts>
</Frame>
<Frame name="BenillaBagFrame3" id="3" inherits="BenillaBagWindowTemplate">
<Scripts>
<OnLoad>BenillaBagFrame_OnLoad(self)</OnLoad>
<OnEvent>BenillaBagFrame_OnEvent(self)</OnEvent>
<OnShow>BenillaBagFrame_OnShow()</OnShow>
<OnHide>BenillaBagFrame_OnHide()</OnHide>
</Scripts>
</Frame>
<Frame name="BenillaBagFrame4" id="4" inherits="BenillaBagWindowTemplate">
<Scripts>
<OnLoad>BenillaBagFrame_OnLoad(self)</OnLoad>
<OnEvent>BenillaBagFrame_OnEvent(self)</OnEvent>
<OnShow>BenillaBagFrame_OnShow()</OnShow>
<OnHide>BenillaBagFrame_OnHide()</OnHide>
</Scripts>
</Frame>
<!--
The keyring window (decision 0765) — id = KEYRING_CONTAINER. Structurally just a seventh
container window on the SAME template as the four equipped bags and the six bank popouts;
everything that makes it a keyring (the "-Keyring" plate, the KeyRing-Bag-Icon portrait, the
KEYRING title, the jingling open/close pair) is the id-keyed fork inside the shared handlers,
exactly as ContainerFrame.lua does it. Its 20 template buttons cover the keyring's 16-slot
ceiling; the level-gated remainder hides through the ordinary physIndex-past-size branch.
It is deliberately NOT in BENILLA_BAG_WINDOWS: the keyring is not one of "your bags", so no
bag toggle ever OPENS it and a vendor's auto-open never touches it — the reference's own
arrangement (OpenAllBags' open arm is ToggleBackpack + ToggleBag(1..4), and its count
explicitly excludes KEYRING_CONTAINER). It is still SWEPT by the two close arms, which the
ref runs over every ContainerFrame — the keyring lives in one there, so 1494 sweeps ours by
name alongside the list. ESC closes it too — UiPanels.xml's CloseAllWindows hides every open
container, the ref's own loop.
-->
<Frame name="BenillaKeyRingFrame" id="-2" inherits="BenillaBagWindowTemplate">
<Scripts>
<OnLoad>BenillaBagFrame_OnLoad(self)</OnLoad>
<OnEvent>BenillaBagFrame_OnEvent(self)</OnEvent>
<OnShow>BenillaBagFrame_OnShow()</OnShow>
<OnHide>BenillaBagFrame_OnHide()</OnHide>
</Scripts>
</Frame>
<!--
The backpack toggle, RELOCATED onto the main action bar's right side so the bar reads whole
(the real client's MainMenuBarBackpackButton, ref-MainMenuBarBagButtons.xml l.54-64): a 37x37
button parented/anchored to the bar art frame at BOTTOMRIGHT (-6,2). Its behavior (toggle the
backpack window) is unchanged; only its seat moved from the free-floating screen corner onto
the bar. (MainMenuBarArtFrame is defined in ActionBar.xml, which the loader loads before
this file; the anchor resolves by name.)
-->
<CheckButton name="MainMenuBarBackpackButton">
<Size><AbsDimension x="37" y="37"/></Size>
<Anchors>
<Anchor point="BOTTOMRIGHT" relativeTo="MainMenuBarArtFrame" relativePoint="BOTTOMRIGHT">
<Offset><AbsDimension x="-6" y="2"/></Offset>
</Anchor>
</Anchors>
<!-- The backpack art is a LAYER texture, not the NormalTexture — the reference's own
structure (MainMenuBarBackpackButton inherits ItemButtonTemplate and sets
`MainMenuBarBackpackButtonIconTexture` in its OnLoad; the state textures are the
template's ring). It matters because `Disable_BagButtons` (decision 0674) disables this
button while the game menu is up, and a *state* texture with no DisabledTexture beside
it is not drawn in the disabled state (`ButtonState::region_visible` — the byte rule at
`SetState 0x779790`), so the backpack icon vanished outright instead of greying
(director-reported). As a layer it is untouched by button state, exactly like the four
bag-bar slots' own `$parentIcon`, and the greying is the vertex tint on it. -->
<Layers>
<Layer level="BACKGROUND">
<Texture name="$parentIconTexture" file="Interface\Buttons\Button-Backpack-Up"/>
</Layer>
</Layers>
<!-- The item-push drop card (decision 0887, BenillaItemPushAnim_* above) — the ref's
$parentItemAnim, one per bag button, container id 0 = the backpack. -->
<Frames>
<Frame name="$parentItemAnim" hidden="true">
<Size><AbsDimension x="36" y="36"/></Size>
<Anchors><Anchor point="CENTER"/></Anchors>
<Layers>
<Layer level="OVERLAY">
<Texture name="$parentIcon" setAllPoints="true"/>
</Layer>
</Layers>
<Scripts>
<OnLoad>BenillaItemPushAnim_OnLoad(self, 0)</OnLoad>
<OnEvent>BenillaItemPushAnim_OnEvent(self)</OnEvent>
<OnUpdate>BenillaItemPushAnim_OnUpdate(self, arg1)</OnUpdate>
</Scripts>
</Frame>
</Frames>
<HighlightTexture file="Interface\Buttons\ButtonHilight-Square" alphaMode="ADD"/>
<!-- The lit ring while the backpack window is open (ref MainMenuBarBackpackButton
l.104) — a CheckButton like the ref's; OnShow/OnHide of the window drive it. -->
<CheckedTexture file="Interface\Buttons\CheckButtonHilight" alphaMode="ADD"/>
<Scripts>
<!-- Seat the backpack icon above the bar art too (same reason as the bag-bar slots —
BenillaActionBarArt_SeatAbove's comment). -->
<OnLoad>
self.icon = getglobal("MainMenuBarBackpackButtonIconTexture")
if BenillaActionBarArt_SeatAbove then BenillaActionBarArt_SeatAbove(self) end
</OnLoad>
<!-- The undo repairs the CheckButton widget's own pre-handler flip; from there the
checked state belongs to the windows' OnShow/OnHide writes (ours and any
addon's) — the stated divergence at BenillaBagToggle_OnClick's comment.
The shift fork is the ref's own (MainMenuBarBagButtons.xml l.81-87): the button
carries the SAME pair of knobs the B / SHIFT-B keys do (1494), so a shift-click
opens all bags and a plain one toggles the backpack. -->
<OnClick>
self:SetChecked(not self:GetChecked())
if IsShiftKeyDown() then
OpenAllBags()
else
BenillaBagToggle_OnClick()
end
</OnClick>
<OnEnter>BenillaBagToggle_OnEnter(self)</OnEnter>
<OnLeave>BenillaBagBarButton_OnLeave()</OnLeave>
</Scripts>
</CheckButton>
<!--
The bag bar (decision 0216 slice 2, BenillaBagBarSlot_* above): four 36×36 buttons to the
LEFT of MainMenuBarBackpackButton, mirroring the reference CharacterBag0..3Slot row. Bar slot N (1..4)
sits at chain position N counting away from the backpack button — CharacterBag0Slot (bagId
1, Bag0Slot) is CLOSEST to the backpack, CharacterBag3Slot (bagId 4, Bag3Slot) FARTHEST —
the real bar's own left-to-right order. Sized to MATCH the action buttons (36×36 icon in a
66×66 UI-Quickslot2 ring, 6px chain gap = the 42px stride), so the bag icons fill their slots
exactly like the spell icons instead of floating undersized (the earlier 30×30 guess).
-->
<CheckButton name="CharacterBag0Slot">
<Size><AbsDimension x="36" y="36"/></Size>
<Anchors>
<Anchor point="RIGHT" relativeTo="MainMenuBarBackpackButton" relativePoint="LEFT"><Offset><AbsDimension x="-6" y="0"/></Offset></Anchor>
</Anchors>
<Layers><Layer level="BACKGROUND"><Texture name="$parentIconTexture"/></Layer></Layers>
<!-- The item-push drop card (decision 0887, BenillaItemPushAnim_* above) — the ref's
$parentItemAnim, one per bag button; container id 1. -->
<Frames>
<Frame name="$parentItemAnim" hidden="true">
<Size><AbsDimension x="36" y="36"/></Size>
<Anchors><Anchor point="CENTER"/></Anchors>
<Layers>
<Layer level="OVERLAY">
<Texture name="$parentIcon" setAllPoints="true"/>
</Layer>
</Layers>
<Scripts>
<OnLoad>BenillaItemPushAnim_OnLoad(self, 1)</OnLoad>
<OnEvent>BenillaItemPushAnim_OnEvent(self)</OnEvent>
<OnUpdate>BenillaItemPushAnim_OnUpdate(self, arg1)</OnUpdate>
</Scripts>
</Frame>
</Frames>
<NormalTexture file="Interface\Buttons\UI-Quickslot2">
<Size><AbsDimension x="66" y="66"/></Size>
<Anchors><Anchor point="CENTER" relativePoint="CENTER"/></Anchors>
</NormalTexture>
<!-- The same ring again as the DISABLED art, so the slot keeps its frame while
Disable_BagButtons has the row greyed under the game menu (decision 0674). A button
with no DisabledTexture draws NO state texture when disabled
(`ButtonState::region_visible`) — the ring would blink out of the bar entirely. The
reference needs no such line because its widget keeps the previously shown texture up
when the new state's slot is empty (`SetState 0x779790`'s gated hide); ours states it
instead of relying on that. -->
<DisabledTexture file="Interface\Buttons\UI-Quickslot2">
<Size><AbsDimension x="66" y="66"/></Size>
<Anchors><Anchor point="CENTER" relativePoint="CENTER"/></Anchors>
</DisabledTexture>
<HighlightTexture file="Interface\Buttons\ButtonHilight-Square" alphaMode="ADD"/>
<!-- The lit ring while this bag's window is open (ref BagSlotButtonTemplate l.52). -->
<CheckedTexture file="Interface\Buttons\CheckButtonHilight" alphaMode="ADD"/>
<Scripts>
<OnLoad>BenillaBagBarSlot_OnLoad(self, 1)</OnLoad>
<OnClick>BenillaBagBarSlot_OnClick(self)</OnClick>
<OnEvent>BenillaBagBarSlot_OnEvent(self)</OnEvent>
<OnEnter>BagSlotButton_OnEnter()</OnEnter>
<OnLeave>BenillaBagBarButton_OnLeave()</OnLeave>
<!-- The drag trio: OnDragStart picks the equipped bag up, OnReceiveDrag drops a carried
bag in — both the same PickupInventoryItem transition (empty cursor picks up, a held
bag places), mirroring the paper-doll/bag-slot drag routing. -->
<OnDragStart>BenillaBagBarSlot_Place(self)</OnDragStart>
<OnReceiveDrag>BenillaBagBarSlot_Place(self)</OnReceiveDrag>
</Scripts>
</CheckButton>
<CheckButton name="CharacterBag1Slot">
<Size><AbsDimension x="36" y="36"/></Size>
<Anchors>
<Anchor point="RIGHT" relativeTo="CharacterBag0Slot" relativePoint="LEFT"><Offset><AbsDimension x="-6" y="0"/></Offset></Anchor>
</Anchors>
<Layers><Layer level="BACKGROUND"><Texture name="$parentIconTexture"/></Layer></Layers>
<!-- The item-push drop card (decision 0887, BenillaItemPushAnim_* above) — the ref's
$parentItemAnim, one per bag button; container id 2. -->
<Frames>
<Frame name="$parentItemAnim" hidden="true">
<Size><AbsDimension x="36" y="36"/></Size>
<Anchors><Anchor point="CENTER"/></Anchors>
<Layers>
<Layer level="OVERLAY">
<Texture name="$parentIcon" setAllPoints="true"/>
</Layer>
</Layers>
<Scripts>
<OnLoad>BenillaItemPushAnim_OnLoad(self, 2)</OnLoad>
<OnEvent>BenillaItemPushAnim_OnEvent(self)</OnEvent>
<OnUpdate>BenillaItemPushAnim_OnUpdate(self, arg1)</OnUpdate>
</Scripts>
</Frame>
</Frames>
<NormalTexture file="Interface\Buttons\UI-Quickslot2">
<Size><AbsDimension x="66" y="66"/></Size>
<Anchors><Anchor point="CENTER" relativePoint="CENTER"/></Anchors>
</NormalTexture>
<!-- The same ring again as the DISABLED art, so the slot keeps its frame while
Disable_BagButtons has the row greyed under the game menu (decision 0674). A button
with no DisabledTexture draws NO state texture when disabled
(`ButtonState::region_visible`) — the ring would blink out of the bar entirely. The
reference needs no such line because its widget keeps the previously shown texture up
when the new state's slot is empty (`SetState 0x779790`'s gated hide); ours states it
instead of relying on that. -->
<DisabledTexture file="Interface\Buttons\UI-Quickslot2">
<Size><AbsDimension x="66" y="66"/></Size>
<Anchors><Anchor point="CENTER" relativePoint="CENTER"/></Anchors>
</DisabledTexture>
<HighlightTexture file="Interface\Buttons\ButtonHilight-Square" alphaMode="ADD"/>
<CheckedTexture file="Interface\Buttons\CheckButtonHilight" alphaMode="ADD"/>
<Scripts>
<OnLoad>BenillaBagBarSlot_OnLoad(self, 2)</OnLoad>
<OnClick>BenillaBagBarSlot_OnClick(self)</OnClick>
<OnEvent>BenillaBagBarSlot_OnEvent(self)</OnEvent>
<OnEnter>BagSlotButton_OnEnter()</OnEnter>
<OnLeave>BenillaBagBarButton_OnLeave()</OnLeave>
<!-- The drag trio: OnDragStart picks the equipped bag up, OnReceiveDrag drops a carried
bag in — both the same PickupInventoryItem transition (empty cursor picks up, a held
bag places), mirroring the paper-doll/bag-slot drag routing. -->
<OnDragStart>BenillaBagBarSlot_Place(self)</OnDragStart>
<OnReceiveDrag>BenillaBagBarSlot_Place(self)</OnReceiveDrag>
</Scripts>
</CheckButton>
<CheckButton name="CharacterBag2Slot">
<Size><AbsDimension x="36" y="36"/></Size>
<Anchors>
<Anchor point="RIGHT" relativeTo="CharacterBag1Slot" relativePoint="LEFT"><Offset><AbsDimension x="-6" y="0"/></Offset></Anchor>
</Anchors>
<Layers><Layer level="BACKGROUND"><Texture name="$parentIconTexture"/></Layer></Layers>
<!-- The item-push drop card (decision 0887, BenillaItemPushAnim_* above) — the ref's
$parentItemAnim, one per bag button; container id 3. -->
<Frames>
<Frame name="$parentItemAnim" hidden="true">
<Size><AbsDimension x="36" y="36"/></Size>
<Anchors><Anchor point="CENTER"/></Anchors>
<Layers>
<Layer level="OVERLAY">
<Texture name="$parentIcon" setAllPoints="true"/>
</Layer>
</Layers>
<Scripts>
<OnLoad>BenillaItemPushAnim_OnLoad(self, 3)</OnLoad>
<OnEvent>BenillaItemPushAnim_OnEvent(self)</OnEvent>
<OnUpdate>BenillaItemPushAnim_OnUpdate(self, arg1)</OnUpdate>
</Scripts>
</Frame>
</Frames>
<NormalTexture file="Interface\Buttons\UI-Quickslot2">
<Size><AbsDimension x="66" y="66"/></Size>
<Anchors><Anchor point="CENTER" relativePoint="CENTER"/></Anchors>
</NormalTexture>
<!-- The same ring again as the DISABLED art, so the slot keeps its frame while
Disable_BagButtons has the row greyed under the game menu (decision 0674). A button
with no DisabledTexture draws NO state texture when disabled
(`ButtonState::region_visible`) — the ring would blink out of the bar entirely. The
reference needs no such line because its widget keeps the previously shown texture up
when the new state's slot is empty (`SetState 0x779790`'s gated hide); ours states it
instead of relying on that. -->
<DisabledTexture file="Interface\Buttons\UI-Quickslot2">
<Size><AbsDimension x="66" y="66"/></Size>
<Anchors><Anchor point="CENTER" relativePoint="CENTER"/></Anchors>
</DisabledTexture>
<HighlightTexture file="Interface\Buttons\ButtonHilight-Square" alphaMode="ADD"/>
<CheckedTexture file="Interface\Buttons\CheckButtonHilight" alphaMode="ADD"/>
<Scripts>
<OnLoad>BenillaBagBarSlot_OnLoad(self, 3)</OnLoad>
<OnClick>BenillaBagBarSlot_OnClick(self)</OnClick>
<OnEvent>BenillaBagBarSlot_OnEvent(self)</OnEvent>
<OnEnter>BagSlotButton_OnEnter()</OnEnter>
<OnLeave>BenillaBagBarButton_OnLeave()</OnLeave>
<!-- The drag trio: OnDragStart picks the equipped bag up, OnReceiveDrag drops a carried
bag in — both the same PickupInventoryItem transition (empty cursor picks up, a held
bag places), mirroring the paper-doll/bag-slot drag routing. -->
<OnDragStart>BenillaBagBarSlot_Place(self)</OnDragStart>
<OnReceiveDrag>BenillaBagBarSlot_Place(self)</OnReceiveDrag>
</Scripts>
</CheckButton>
<CheckButton name="CharacterBag3Slot">
<Size><AbsDimension x="36" y="36"/></Size>
<Anchors>
<Anchor point="RIGHT" relativeTo="CharacterBag2Slot" relativePoint="LEFT"><Offset><AbsDimension x="-6" y="0"/></Offset></Anchor>
</Anchors>
<Layers><Layer level="BACKGROUND"><Texture name="$parentIconTexture"/></Layer></Layers>
<!-- The item-push drop card (decision 0887, BenillaItemPushAnim_* above) — the ref's
$parentItemAnim, one per bag button; container id 4. -->
<Frames>
<Frame name="$parentItemAnim" hidden="true">
<Size><AbsDimension x="36" y="36"/></Size>
<Anchors><Anchor point="CENTER"/></Anchors>
<Layers>
<Layer level="OVERLAY">
<Texture name="$parentIcon" setAllPoints="true"/>
</Layer>
</Layers>
<Scripts>
<OnLoad>BenillaItemPushAnim_OnLoad(self, 4)</OnLoad>
<OnEvent>BenillaItemPushAnim_OnEvent(self)</OnEvent>
<OnUpdate>BenillaItemPushAnim_OnUpdate(self, arg1)</OnUpdate>
</Scripts>
</Frame>
</Frames>
<NormalTexture file="Interface\Buttons\UI-Quickslot2">
<Size><AbsDimension x="66" y="66"/></Size>
<Anchors><Anchor point="CENTER" relativePoint="CENTER"/></Anchors>
</NormalTexture>
<!-- The same ring again as the DISABLED art, so the slot keeps its frame while
Disable_BagButtons has the row greyed under the game menu (decision 0674). A button
with no DisabledTexture draws NO state texture when disabled
(`ButtonState::region_visible`) — the ring would blink out of the bar entirely. The
reference needs no such line because its widget keeps the previously shown texture up
when the new state's slot is empty (`SetState 0x779790`'s gated hide); ours states it
instead of relying on that. -->
<DisabledTexture file="Interface\Buttons\UI-Quickslot2">
<Size><AbsDimension x="66" y="66"/></Size>
<Anchors><Anchor point="CENTER" relativePoint="CENTER"/></Anchors>
</DisabledTexture>
<HighlightTexture file="Interface\Buttons\ButtonHilight-Square" alphaMode="ADD"/>
<CheckedTexture file="Interface\Buttons\CheckButtonHilight" alphaMode="ADD"/>
<Scripts>
<OnLoad>BenillaBagBarSlot_OnLoad(self, 4)</OnLoad>
<OnClick>BenillaBagBarSlot_OnClick(self)</OnClick>
<OnEvent>BenillaBagBarSlot_OnEvent(self)</OnEvent>
<OnEnter>BagSlotButton_OnEnter()</OnEnter>
<OnLeave>BenillaBagBarButton_OnLeave()</OnLeave>
<!-- The drag trio: OnDragStart picks the equipped bag up, OnReceiveDrag drops a carried
bag in — both the same PickupInventoryItem transition (empty cursor picks up, a held
bag places), mirroring the paper-doll/bag-slot drag routing. -->
<OnDragStart>BenillaBagBarSlot_Place(self)</OnDragStart>
<OnReceiveDrag>BenillaBagBarSlot_Place(self)</OnReceiveDrag>
</Scripts>
</CheckButton>
<!--
The keyring button (decision 0765) — ref KeyRingButton, MainMenuBarBagButtons.xml l.142-199.
Sits at the FAR left of the bag row, past CharacterBag3Slot (the ref's CharacterBag3Slot),
and is authored HIDDEN: nothing reveals it but MainMenuBar_UpdateKeyRing, and only
while HasKey() — the point being that a character with no key has no keyring at all, and the
bar's plate has no socket for one.
18x39 and the TexCoords are the ref's exactly, and they are not arbitrary: the three state
atlases are 32x64 holding one 18x39 button, so 18/32 = 0.5625 and 39/64 = 0.609375 crop it
1:1. The chain gap is OUR row's -6 rather than the ref's -5, so the button seats evenly
against the 36px slots this bar scaled its own row to.
Unlike the five bag buttons it is NOT in BENILLA_BAG_BUTTONS: its art is a state texture, not
an $parentIcon layer, so Disable_BagButtons' SetDesaturation(b.icon) has nothing to grey —
and the ref leaves it out of its own Disable_BagButtons for the same reason.
-->
<CheckButton name="KeyRingButton" hidden="true">
<Size><AbsDimension x="18" y="39"/></Size>
<Anchors>
<Anchor point="RIGHT" relativeTo="CharacterBag3Slot" relativePoint="LEFT"><Offset><AbsDimension x="-6" y="0"/></Offset></Anchor>
</Anchors>
<!-- The item-push drop card (decision 0887, BenillaItemPushAnim_* above) — the ref's
$parentItemAnim, one per bag button; container id -2 (KEYRING_CONTAINER). The ref gives
the keyring one too (MainMenuBarBagButtons.xml l.152-160), and the byte selector really
does reach it: a push landing in player-array slots 81..112 resolves to -2, not 0. -->
<Frames>
<Frame name="$parentItemAnim" hidden="true">
<Size><AbsDimension x="36" y="36"/></Size>
<Anchors><Anchor point="CENTER"/></Anchors>
<Layers>
<Layer level="OVERLAY">
<Texture name="$parentIcon" setAllPoints="true"/>
</Layer>
</Layers>
<Scripts>
<OnLoad>BenillaItemPushAnim_OnLoad(self, -2, 0)</OnLoad>
<OnEvent>BenillaItemPushAnim_OnEvent(self)</OnEvent>
<OnUpdate>BenillaItemPushAnim_OnUpdate(self, arg1)</OnUpdate>
</Scripts>
</Frame>
</Frames>
<NormalTexture file="Interface\Buttons\UI-Button-KeyRing">
<TexCoords left="0" right="0.5625" top="0" bottom="0.609375"/>
</NormalTexture>
<PushedTexture file="Interface\Buttons\UI-Button-KeyRing-Down">
<TexCoords left="0" right="0.5625" top="0" bottom="0.609375"/>
</PushedTexture>
<HighlightTexture file="Interface\Buttons\UI-Button-KeyRing-Highlight" alphaMode="ADD">
<TexCoords left="0" right="0.5625" top="0" bottom="0.609375"/>
</HighlightTexture>
<!-- The lit ring while the keyring window is open — the bag row's own convention
(the ref drives the same state through UpdateMicroButtons' SetButtonState). -->
<CheckedTexture file="Interface\Buttons\CheckButtonHilight" alphaMode="ADD"/>
<Scripts>
<OnLoad>BenillaKeyRingButton_OnLoad(self)</OnLoad>
<OnEvent>BenillaKeyRingButton_OnEvent()</OnEvent>
<OnClick>BenillaKeyRingButton_OnClick(self)</OnClick>
<OnEnter>BenillaKeyRingButton_OnEnter(self)</OnEnter>
<OnLeave>BenillaBagBarButton_OnLeave()</OnLeave>
<!-- A key dragged onto the button files itself, the ref's own OnReceiveDrag (l.174-178). -->
<OnReceiveDrag>PutKeyInKeyRing()</OnReceiveDrag>
</Scripts>
</CheckButton>
</Ui>