Files
benilla-octo/crates/benilla/assets/ui/BagFrame.xml
T
SamandClaude Fable 5 2f40b6a9a0 ice block freezes you solid, and loot lines link their item
- ice block freezes the body the frame it lands, before the arm even rises
- the loot message links the item, so the name wears its quality colour
- a looted item drops into the bag that actually took it
- effect models live their authored life, born, held, decayed, and bar fills sit under their borders

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-02 18:01:42 +07:00

1881 lines
118 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 C_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
-- (C_Container.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
-- C_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 C_Container.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
local info = C_Container.GetContainerItemInfo(bagId, button.slot)
-- The stack-split trigger — SHIFT + left-click on an unlocked stack of ≥2, the
-- reference fork verbatim (ContainerFrame.lua:567-577; ctrl's dress-up and the
-- chat-link insert are named deferrals). `ignoreModifiers` (the drag-trio calls,
-- both true) suppresses the fork, the reference's own drag/receive contract.
if IsShiftKeyDown() and not ignoreModifiers then
if info and not info.isLocked and (info.stackCount 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 is a TOP-LEVEL
-- global (cursor.rs — not under C_Container despite the container-verb
-- naming; GetContainerItemInfo/PickupContainerItem ARE namespaced).
OpenStackSplitFrame(info.stackCount, 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("BenillaStackSplitFrame")
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.
C_Container.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
C_Container.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 info = C_Container.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 info 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 (the Ctrl+click
-- dress-up preview cursor) is omitted with the dress-up feature itself.
if BenillaMerchantFrame and BenillaMerchantFrame:IsShown()
and BenillaMerchantFrame.selectedTab == 1 and not InRepairMode() then
ShowContainerSellCursor(bagId, button.slot)
elseif info.isReadable 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 C_Container dialect).
function BenillaBagSlot_UpdateCooldown(button, bagId)
local cooldown = getglobal(button:GetName() .. "Cooldown")
local start, duration, enable = C_Container.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 = C_Container.GetContainerNumSlots(bagId)
if button.physIndex > size then
button.slot = nil
button:Hide()
return
end
button.slot = size - button.physIndex + 1
button:Show()
local info = C_Container.GetContainerItemInfo(bagId, button.slot)
if info then
BenillaBagSlot_UpdateCooldown(button, bagId)
button.icon:SetTexture(info.iconFileID 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 info.isLocked then
button.icon:SetVertexColor(0.4, 0.4, 0.4)
else
button.icon:SetVertexColor(1, 1, 1)
end
if (info.stackCount or 1) > 1 then
button.count:SetText(info.stackCount)
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 (C_Container.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(C_Container.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.
local portrait = getglobal(frame:GetName() .. "Portrait")
if portrait then
portrait:SetPortraitToTexture("Interface\\ContainerFrame\\KeyRing-Bag-Icon")
end
local size = C_Container.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.
portrait:SetPortraitToTexture(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 = C_Container.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, BenillaOpenAllBags' pre-show paint) goes
-- through this; BenillaBagBarSlot_OnClick calls BenillaBagWindow_Update directly (it only ever
-- targets bags 1..4).
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
-- OpenAllBags / CloseAllBags / ToggleBackpack (ref ContainerFrame.lua l.152-217): the 'B'
-- binding (OPENALLBAGS) and the backpack button BOTH open or close EVERY bag at once, not
-- just the backpack — the real client's own behavior (MainMenuBarBackpackButton's OnClick and
-- the OPENALLBAGS binding both route to ToggleBackpack). "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.
BENILLA_BAG_WINDOWS = { "BenillaBagFrame", "BenillaBagFrame1", "BenillaBagFrame2", "BenillaBagFrame3", "BenillaBagFrame4" }
function BenillaOpenAllBags()
for bagId = 0, 4 do
-- The backpack always exists; an equipped-bag slot only when a bag is in it.
if bagId == 0 or C_Container.GetContainerNumSlots(bagId) > 0 then
local frame = getglobal(BENILLA_BAG_WINDOWS[bagId + 1])
if frame and not frame:IsShown() then
BenillaBagWindow_Repaint(frame) -- bag 0 routes through its observable alias
frame:Show()
end
end
end
end
function BenillaCloseAllBags()
for _, name in ipairs(BENILLA_BAG_WINDOWS) do
local frame = getglobal(name)
if frame and frame:IsShown() then frame:Hide() end
end
end
-- The backpack button (BenillaBagToggle) and the 'B' binding: if any bag window is open,
-- close them all; otherwise open every existing bag (ref ToggleBackpack — "if numOpen > 0
-- then CloseAllBags() else OpenAllBags()").
function BenillaBagToggle_OnClick()
local anyOpen = false
for _, name in ipairs(BENILLA_BAG_WINDOWS) do
local frame = getglobal(name)
if frame and frame:IsShown() then anyOpen = true end
end
if anyOpen then BenillaCloseAllBags() else BenillaOpenAllBags() end
-- The auto-toggle is provisional (ref BackpackButton_OnClick l.58-67): the button
-- lights for the BACKPACK window specifically, whatever the all-toggle just did.
local backpack = getglobal(BENILLA_BAG_WINDOWS[1])
getglobal("BenillaBagToggle"):SetChecked(backpack and backpack:IsShown())
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("BenillaKeyRingButton") end
for bagId = 0, 4 do
if name == BENILLA_BAG_WINDOWS[bagId + 1] then
if bagId == 0 then return getglobal("BenillaBagToggle") end
return getglobal("BenillaBagBarSlot" .. bagId)
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, the 'B' 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 backpack's own
-- anchor (BOTTOMRIGHT, x=0/y=70 — CONTAINER_OFFSET_X/Y), 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.
BENILLA_BAG_STACK = {}
local BENILLA_CONTAINER_OFFSET_X = 0
local BENILLA_CONTAINER_OFFSET_Y = 70
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()
local screenHeight = GetScreenHeight()
local freeHeight = screenHeight - BENILLA_CONTAINER_OFFSET_Y
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", -BENILLA_CONTAINER_OFFSET_X, BENILLA_CONTAINER_OFFSET_Y)
elseif freeHeight < frame:GetHeight() then
column = column + 1
freeHeight = screenHeight - BENILLA_CONTAINER_OFFSET_Y
frame:SetPoint("BOTTOMRIGHT",
-(column * BENILLA_CONTAINER_WIDTH) - BENILLA_CONTAINER_OFFSET_X,
BENILLA_CONTAINER_OFFSET_Y)
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 bag bar (decision 0216 slice 2) — four 30×30 buttons left of BenillaBagToggle,
-- 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
-- BenillaActionBarArtFrame'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
button.icon = getglobal(button:GetName() .. "Icon")
if BenillaActionBarArt_SeatAbove then BenillaActionBarArt_SeatAbove(button) end
button:RegisterForClicks("LeftButtonUp")
-- 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
function BenillaBagBarSlot_OnClick(button)
if CursorHasItem() then
BenillaBagBarSlot_Place(button) -- drop/equip the carried bag onto this slot
else
local frame = getglobal("BenillaBagFrame" .. button.bagId)
if frame then
if frame:IsShown() then
frame:Hide()
else
BenillaBagWindow_Update(frame)
frame:Show()
end
end
end
-- The click's own CheckButton auto-toggle is provisional — re-derive from the
-- window on every branch, the ref's exact tail (BagSlotButton_OnClick l.12-20:
-- SetChecked(isVisible) whether the click toggled, placed a bag, or did nothing).
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 TOGGLEBACKPACK key with GameTooltip:AppendText; this engine has neither
-- GetBindingKey nor AppendText, so the suffix is concatenated from the one bare-key binding
-- we actually ship (ui_script/input.rs `bare_key_binding`: KeyB) — MicroMenu.xml's own shape
-- for the same problem, and the rendered line is identical.
function BenillaBagToggle_OnEnter(button)
if not GameTooltip then return end -- engine tests load file subsets
GameTooltip:SetOwner(button, "ANCHOR_LEFT")
GameTooltip:SetText(
BACKPACK_TOOLTIP .. " " .. NORMAL_FONT_COLOR_CODE .. "(B)" .. FONT_COLOR_CODE_CLOSE,
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 "(F1)".."(F4)" suffix
-- needs GetBindingKey AND the TOGGLEBAG1..4 bindings — benilla ships neither, so no slot
-- carries one; the day those bind, this is where the suffix goes.
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
-- 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
if not C_Container.GetContainerItemInfo(KEYRING_CONTAINER, i) then
C_Container.PickupContainerItem(KEYRING_CONTAINER, i)
return
end
end
if BenillaErrorsFrame_AddMessage then
BenillaErrorsFrame_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 BenillaMainMenuBar_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 BenillaMainMenuBar_UpdateKeyRing then BenillaMainMenuBar_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 ONE AUTHORED NUMBER is the model-unit → pixel scale, because a <Model> frame's 3D
-- projection is not RE-recorded (the ref's ItemAnim frame has no <Size> at all, so the rect
-- cannot be it) — see decision 0887's named gap. 1200 px/unit is the sibling calibration:
-- CooldownFrameTemplate is the same kind of degenerate UI model, its four quads span 0.04
-- units, and at its scale="0.75" that sweep covers a 36px action button ⇒ 0.04·K·0.75 = 36.
-- At K = 1200 this model's own numbers land on 36 px of icon (exactly a bag-bar button),
-- a 48 px fall and a 12 px sideways drift — which is why the sizes below are stated as the
-- model's ratios times the icon, not as free pixels: correct the scale in one place and the
-- whole animation stays in proportion.
BENILLA_ITEM_PUSH_PX_PER_UNIT = 1200
BENILLA_ITEM_PUSH_DURATION = 1.0
BENILLA_ITEM_PUSH_SIZE = 0.03 * BENILLA_ITEM_PUSH_PX_PER_UNIT -- 36
BENILLA_ITEM_PUSH_FALL = 0.04 * BENILLA_ITEM_PUSH_PX_PER_UNIT -- 48
BENILLA_ITEM_PUSH_DRIFT = 0.01 * BENILLA_ITEM_PUSH_PX_PER_UNIT -- 12
-- {time, value} keyframes, linear between (M2 track interpolation 1) — the file's own keys.
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.2}, {0.267, 1.0}, {1.000, 0.014} }
-- The translation track as a 0..1 fraction of the fall (both components move together, so
-- one curve drives both axes — the file keys (0,0) at 0.000 and 0.500, the full offset at
-- 1.000, i.e. it hangs for the first half second and then drops).
BENILLA_ITEM_PUSH_DROP = { {0.000, 0.0}, {0.500, 0.0}, {1.000, 1.0} }
-- 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. The card LANDS centred on its button and
-- STARTS a fall's height above it (and a drift's width to the left), so the icon reads as
-- floating clear above the bar and then dropping into the bag — the model's own relative
-- motion, anchored to the button rather than to the ref's unresolvable model origin.
function BenillaItemPushAnim_Paint(anim)
local t = anim.elapsed
local drop = BenillaItemPushAnim_Sample(BENILLA_ITEM_PUSH_DROP, t)
local size = BENILLA_ITEM_PUSH_SIZE * BenillaItemPushAnim_Sample(BENILLA_ITEM_PUSH_SCALE, t)
anim:SetWidth(size)
anim:SetHeight(size)
anim:SetPoint("CENTER", anim:GetParent(), "CENTER",
BENILLA_ITEM_PUSH_DRIFT * (drop - 1),
BENILLA_ITEM_PUSH_FALL * (1 - drop))
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)
anim.containerId = containerId
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 = { "BenillaBagToggle", "BenillaBagBarSlot1", "BenillaBagBarSlot2",
"BenillaBagBarSlot3", "BenillaBagBarSlot4" }
-- 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 BenillaBagToggle and
-- CharacterBag0..3Slot are BenillaBagBarSlot1..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 0.4 grey, not the flat white SetDesaturation(nil) just left.
for i = 1, 4 do
BenillaBagBarSlot_Update(getglobal("BenillaBagBarSlot" .. i))
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
C_Container.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 'B'
/ the backpack toggle / a vendor's auto-open never touch it, which is the reference's own
arrangement (OpenAllBags counts it out, and CloseAllBags is id-driven over 1..4). ESC still
closes it — 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. (BenillaActionBarArtFrame is defined in ActionBar.xml, which the loader loads before
this file; the anchor resolves by name.)
-->
<CheckButton name="BenillaBagToggle">
<Size><AbsDimension x="37" y="37"/></Size>
<Anchors>
<Anchor point="BOTTOMRIGHT" relativeTo="BenillaActionBarArtFrame" 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="$parentIcon" 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("BenillaBagToggleIcon")
if BenillaActionBarArt_SeatAbove then BenillaActionBarArt_SeatAbove(self) end
</OnLoad>
<OnClick>BenillaBagToggle_OnClick()</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 BenillaBagToggle, mirroring the reference CharacterBag0..3Slot row. Bar slot N (1..4)
sits at chain position N counting away from the backpack button — BenillaBagBarSlot1 (bagId
1, Bag0Slot) is CLOSEST to the backpack, BenillaBagBarSlot4 (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="BenillaBagBarSlot1">
<Size><AbsDimension x="36" y="36"/></Size>
<Anchors>
<Anchor point="RIGHT" relativeTo="BenillaBagToggle" relativePoint="LEFT"><Offset><AbsDimension x="-6" y="0"/></Offset></Anchor>
</Anchors>
<Layers><Layer level="BACKGROUND"><Texture name="$parentIcon"/></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>BenillaBagBarSlot_OnEnter(self)</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="BenillaBagBarSlot2">
<Size><AbsDimension x="36" y="36"/></Size>
<Anchors>
<Anchor point="RIGHT" relativeTo="BenillaBagBarSlot1" relativePoint="LEFT"><Offset><AbsDimension x="-6" y="0"/></Offset></Anchor>
</Anchors>
<Layers><Layer level="BACKGROUND"><Texture name="$parentIcon"/></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>BenillaBagBarSlot_OnEnter(self)</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="BenillaBagBarSlot3">
<Size><AbsDimension x="36" y="36"/></Size>
<Anchors>
<Anchor point="RIGHT" relativeTo="BenillaBagBarSlot2" relativePoint="LEFT"><Offset><AbsDimension x="-6" y="0"/></Offset></Anchor>
</Anchors>
<Layers><Layer level="BACKGROUND"><Texture name="$parentIcon"/></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>BenillaBagBarSlot_OnEnter(self)</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="BenillaBagBarSlot4">
<Size><AbsDimension x="36" y="36"/></Size>
<Anchors>
<Anchor point="RIGHT" relativeTo="BenillaBagBarSlot3" relativePoint="LEFT"><Offset><AbsDimension x="-6" y="0"/></Offset></Anchor>
</Anchors>
<Layers><Layer level="BACKGROUND"><Texture name="$parentIcon"/></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>BenillaBagBarSlot_OnEnter(self)</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 BenillaBagBarSlot4 (the ref's CharacterBag3Slot),
and is authored HIDDEN: nothing reveals it but BenillaMainMenuBar_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="BenillaKeyRingButton" hidden="true">
<Size><AbsDimension x="18" y="39"/></Size>
<Anchors>
<Anchor point="RIGHT" relativeTo="BenillaBagBarSlot4" 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)</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>