Files
benilla-octo/crates/benilla-app/assets/ui/BagFrame.xml
T
SamandClaude Fable 5 67315143f9 the bag windows are the real 1.12 files, and /walk actually walks
- the bag windows are the reference's own `ContainerFrame.xml` running off the player's patch chain — decision 1751 makes the stock 1.12 UI off the install the end state and `assets/ui` scaffolding that retires file-by-file (86 files against the 99-file stock corpus, 72% of the 1128-function global surface already built) — via one ordered manifest, `benilla.toc`, where a bare name is a file we ship and an `Interface\FrameXML\` path is the player's own file; the windows on screen are `ContainerFrame1..12`, the bank's six copy-pasted popouts fold into them at ids 5..10, net −750 lines, and the swap forced real engine verbs into being rather than stubs (`SetBagPortaitTexture` with its id-0 no-texture arm, `GameTooltip:AppendText`, `SetButtonPulse`, and a `<Model>` whose file is the cooldown indicator materializing as the native sweep, which three corpus addons inherit)
- the 1.12 walk toggle lands: `TOGGLERUN` latches one bit with `ToggleRun`'s own silent refusal chain (dead, rooted, on a live server spline), and the walk animation was never missing at all — the flag reaches it through exactly one channel, the speed (`0x5fd100` picks Walk over Run on `speed > 2 × walkSpeed`); pinning it caught our remote extrapolator with the two min rungs backwards — `min(walk, run)` runs BEFORE the backward min, so a walking backpedaller moves at 2.5 yd/s where we would have played them the Run clip at 4.5 — and one shared `current_speed` now keeps the local controller and the extrapolator from ever disagreeing about it
- the instance and raid lockout messages exist — six packets that were dropped as unknown opcodes now land, four of them as the CHAT_MSG_SYSTEM lines the client composes itself, which is why no Lua handler ever surfaced them — plus `IsInInstance`, `ResetInstances`, and the unit popup's "Reset all instances" row behind the reference's own predicate; where the binary contradicts vmangos: the ownership flag means "any instance bind" but vmangos only ever names raid binds, so the row was dead for every character without a raid lockout — term 1 is now also satisfied by our own eyes, walking out of a party dungeon the server itself bound us to
- opening the world map showed 3D world at the sheet's margins: `BlackoutWorld` was parented to the very `UIParent` a fullscreen panel hides, and three more frames had crossed the same boundary — the shared dropdown lists (so the map's continent and zone pickers answered a click with nothing at all), the master-looter menu, and the screenshot confirmation, which could never appear during a cinematic; `frame_flag_gate` now checks the parent seat against the reference both ways a frame acquires one (the attribute, template-inherited included, and `<Frames>` nesting), and its first harvest took 30 divergences down to three accepted ones

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-31 20:17:25 +07:00

995 lines
64 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 BAR** — the five buttons on the right of the main action bar, and nothing
else. The bag WINDOWS this file used to hold are gone: they are the reference's own
`ContainerFrame1..12`, executed off the player's installed patch chain (decision 1751,
`Interface\FrameXML\ContainerFrame.xml` in benilla.toc, three lines above this one).
So what remains is this client's stand-in for the reference's `MainMenuBarBagButtons.xml` /
`.lua` — the backpack button, `CharacterBag0Slot`..`CharacterBag3Slot`, `KeyRingButton`, and
the item-push card that flies into whichever of them took an item. It keeps the reference's
names because those are what addons reach for, and it will be replaced by the reference's
own file the day `PutItemInBag` / `PickupBagFromSlot` are bound (see the note at
`BagSlotButton_OnClick`, which is the migration ticket).
THE VERBS ARE THE REFERENCE'S NOW, and every button below calls them through the global:
`ToggleBackpack`, `ToggleBag`, `OpenAllBags`, `ToggleKeyRing`, `PutKeyInKeyRing`,
`IsBagOpen` — all from the sourced `ContainerFrame.lua`. That is not incidental: those
names are precisely what a bag addon replaces, so a button that called a private body
instead would make the addon silently inert (the lesson this file's own history carries —
four of the five buttons did exactly that once).
WHAT LIGHTS A BUTTON. The reference's `ContainerFrame_OnShow`/`_OnHide` write
`MainMenuBarBackpackButton:SetChecked` / `CharacterBag<N>Slot:SetChecked` directly, so with
stock windows the flag is already correct by the time a click handler's tail runs; each
tail below re-derives it with `IsBagOpen`, which is the reference's own scan under the name
it publishes for it. `KeyRingButton` is the same at `KEYRING_CONTAINER`.
KEYRING. `KEYRING_CONTAINER = -2` lives here because it lives in
`MainMenuBarBagButtons.lua:1` in the reference — and the sourced `ContainerFrame.lua` reads
it, so this file is load-bearing for a file above it in the manifest. Decision 0765's
keyring-as-container stands: the app feeds it like any other bag, so the reference's own
window, verbs and sounds take it unchanged. The one place the reference detours out of the
container API for a keyring slot is its tooltip, and that detour is adapted — with its
reasons — in `ContainerFrameAdapters.xml`.
ITEM PUSH (decision 0887): `BENILLA_ITEM_PUSH_*` + `BenillaItemPushAnim_*` are ours, and
deliberately not the reference's `ItemAnimTemplate` — that is a `<Model>` playing an .mdx
sequence, and this engine renders no FrameXML models (see `Cooldown.xml`'s own note). The
card below is the same animation sampled from the model's tracks and drawn as a quad.
The counts are NumberFontNormal and the labels GameFontHighlight — both from the shipped
Fonts.xml, so they render in the real fonts.
-->
<Script><![CDATA[
-- The keyring's container id (ref MainMenuBarBagButtons.lua:1) — decision 0765. It is an
-- ordinary container in every respect the seam cares about: the app feeds it like any other
-- bag (its slots are the player descriptor's own 81.., no container object), and every
-- container verb, the cursor, the pending-op lock and the wire drains take it unchanged.
-- What makes it special is only what the reference makes special: the window's art, its
-- open/close sound, and the fact that its capacity is not a wire field.
KEYRING_CONTAINER = -2
-- The backpack button (MainMenuBarBackpackButton): if any bag window is open, close them
-- all; otherwise open every existing bag (ref ToggleBackpack — "if numOpen > 0 then
-- CloseAllBags() else OpenAllBags()").
--
-- ONE STATED DIVERGENCE from ref BackpackButton_OnClick (MainMenuBarBagButtons.lua
-- l.55-69). The ref's tail re-derives the checked state from a scan of its NATIVE
-- ContainerFrames — and OnShow/OnHide dispatch is SYNCHRONOUS in the reference (wow-re
-- `onshow-onhide-dispatch-order.md`: Show binder 0x775750 → transition 0x76ae10 → notify
-- 0x76b260 → lua_pcall, every hop a direct call), so when an addon owns the bags its
-- OnShow's `SetChecked(1)` (Bagnon.lua l.33) lands FIRST and the ref tail stomps it: on
-- the real client the backpack button ends UNLIT after clicking it with Bagnon, and
-- lights only via the key binding (whose body is the bare `ToggleBackpack()`, no tail).
-- That click-path outcome contradicts the addon's evident intent and the director's call,
-- so ours swaps the scan for the auto-toggle UNDO below: checked belongs to the WINDOWS'
-- OnShow/OnHide writes — ours (BenillaBagFrame_OnShow/_OnHide) and any addon's alike —
-- and this handler only repairs what the CheckButton widget flipped before it ran. Every
-- stock outcome is identical to the ref's (the transition writes land inside
-- ToggleBackpack and are the last word); the addon case ends lit instead of stomped.
-- The widget-flip UNDO itself lives in the button's XML <OnClick> wrapper — the one seat
-- where the CheckButton auto-toggle has just happened — so every DIRECT caller of this
-- function (the tests, a macro) gets pure toggle semantics with no checked bookkeeping
-- to corrupt.
function BenillaBagToggle_OnClick()
-- Through the GLOBAL, so an addon that replaced `ToggleBackpack` gets the click.
ToggleBackpack()
end
-- ── The bag bar (decision 0216 slice 2) — four 30×30 buttons left of MainMenuBarBackpackButton,
-- mirroring the reference main-bar bag buttons (CharacterBag0..3Slot). No exact reference
-- XML was available to transcribe (not in the session's extracted set) — size (30×30) and
-- placement (left of the backpack button) are per the brief; inter-button spacing is an
-- authored assumption (2px), FLAGGED for the director's look. Each button shows the
-- equipped bag's own icon (GetInventoryItemTexture, inv slots 20..23 — Bag0Slot..Bag3Slot,
-- PaperDollItemFrame.dbc-verified, decision 0216 slice 2's char-feed extension) or the
-- empty-slot art when no bag is equipped there. Click toggles that bag's window — NAMED
-- DEFERRAL: no drag-to-equip a bag onto the bar yet (that's the paper-doll drag arc, slice
-- 3's scope, not this one).
-- The bag bar (and the backpack toggle) are RELOCATED onto the action bar — anchored into
-- MainMenuBarArtFrame's bottom-right — which means they need seating one frame level
-- above the bar art or it paints over their icons. That lever lives with the frame it's
-- about: BenillaActionBarArt_SeatAbove (ActionBar.xml, loaded before this file), which
-- carries the full explanation; the micro-button row is its second caller. Called
-- nil-GUARDED here: the shipped load order always has the bar first, but ~25 bag/bank/
-- escape/merchant test harnesses load THIS file alone — and with no bar loaded there is by
-- definition no bar art to seat above, so the guard's no-op is the right answer, not a
-- swallowed failure.
-- Bag0Slot..Bag3Slot = live inv ids 20..23 (char_stats.rs SLOT_INFO, DBC-verified); OUR bag
-- ids 1..4 map the same way (bagId N ⇒ inv slot 19+N), the id PickupInventoryItem/
-- GetInventoryItemTexture speak.
function BenillaBagBarSlot_InvSlot(button) return 19 + button.bagId end
function BenillaBagBarSlot_OnLoad(button, bagId)
button.bagId = bagId
-- The button's ID is the REFERENCE's: its 1-based inventory slot, 20..23 (the real
-- client gets there through PaperDollItemSlotButton_OnLoad, which resolves
-- "CharacterBag0Slot" → "Bag0Slot" → GetInventorySlotInfo → 20). Nothing of ours reads
-- it — every body here goes through `button.bagId` — but every ADDON does:
-- `Bagnon.lua:104` computes its highlight target as `this:GetID() - 19`, and against
-- an unset id that is -19, so Bagnon highlighted the wrong bag (silently, with no
-- raise) on every bag-slot hover. `isBag` is the same story from the same template
-- (ref BagSlotButtonTemplate OnLoad l.33), read by KeyRingItemButton_OnClick.
button:SetID(19 + bagId)
button.isBag = 1
button.icon = getglobal(button:GetName() .. "IconTexture")
if BenillaActionBarArt_SeatAbove then BenillaActionBarArt_SeatAbove(button) end
-- Both buttons (ref BagSlotButtonTemplate inherits PaperDollItemSlotButtonTemplate, and
-- its OnLoad calls PaperDollItemSlotButton_OnLoad — PaperDollFrame.lua:86 — so a bag-bar
-- slot registers the pair like every doll slot; MainMenuBarBagButtons.xml:78 does the
-- same for the backpack). BagSlotButton_OnClick reads no button: right-click opens the
-- bag, exactly as left does.
button:RegisterForClicks("LeftButtonUp", "RightButtonUp")
-- Drag-to-equip (decision 0208 phase 1b's deferred bag-icon arm, now landed): the bar
-- slot is a paper-doll equipment slot for bags. LEFT drag picks the equipped bag up;
-- a drag/receive drops a carried bag in to equip it (PickupInventoryItem — the exact
-- doll-slot transition, the app maps id 20..23 onto wire bag slots 19..22).
button:RegisterForDrag("LeftButton")
button:RegisterEvent("UNIT_INVENTORY_CHANGED")
button:RegisterEvent("BAG_UPDATE")
button:RegisterEvent("ITEM_LOCK_CHANGED")
BenillaBagBarSlot_Update(button)
end
-- Placing/equipping a carried bag (or picking the equipped one up) rides PickupInventoryItem,
-- exactly like a paper-doll slot; the empty-cursor plain click just toggles the bag's window.
function BenillaBagBarSlot_Place(button)
PickupInventoryItem(BenillaBagBarSlot_InvSlot(button))
BenillaBagBarSlot_Update(button)
end
-- ref BagSlotButton_OnClick (MainMenuBarBagButtons.lua l.3-21): the cursor arm first (the
-- ref's PutItemInBag; ours is the equip/swap PickupInventoryItem arm, decision 0208 1b),
-- otherwise the toggle goes THROUGH THE GLOBAL `ToggleBag` — the exact name
-- Bagnon_Core/Overrides.lua replaces — with the ref's own button sound on that arm only.
-- Toggling the window directly here (what this did before) made every bag addon's override
-- silently inert for the four slot buttons while the backpack button honoured it: the
-- 517-line rule above ("the reference's names are the real entry points") applied to only
-- one of the five buttons, and the director met the other four.
function BenillaBagBarSlot_OnClick(button)
if CursorHasItem() then
BenillaBagBarSlot_Place(button) -- drop/equip the carried bag onto this slot
else
ToggleBag(button.bagId)
PlaySound("BAGMENUBUTTONPRESS")
end
-- The click's own CheckButton auto-toggle is provisional — re-derive from the NATIVE
-- window on every branch, the ref's exact tail (BagSlotButton_OnClick l.12-20 scans
-- ContainerFrame1..N: SetChecked(isVisible) whether the click toggled, placed a bag,
-- or did nothing). `IsBagOpen` IS that scan, under the name the reference publishes
-- for it. Reading the WINDOW rather than what ToggleBag did is load-bearing both ways:
-- stock, the flag is already set when the tail runs (ContainerFrame_OnShow wrote it);
-- with an addon holding the bags, no ContainerFrame is shown and the slot button stays
-- unlit — the reference's own behaviour, its scan being equally blind to addon frames.
button:SetChecked(IsBagOpen(button.bagId) and 1 or nil)
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."
-- **A REFERENCE TYPO, repaired here rather than lived with** (decision 1751: a divergence
-- becomes faithful or becomes a record, never silent — this is the record).
--
-- `PutKeyInKeyRing`'s full-ring arm reads `NO_EMPTY_KEYRING_SLOTS`
-- (ContainerFrame.lua l.753), and 1.12 ships **only** `NO_EMPTY_KEYRING_SLOTS_ERROR`
-- (GlobalStrings.lua l.2810 — checked, the shorter name appears nowhere in the file). So
-- in the real client the text is nil, `UIErrorsFrame:AddMessage` refuses it, and dropping
-- a key onto a full keyring reports nothing at all.
--
-- benilla ran its own `PutKeyInKeyRing` before 1751 and simply used the right name; now
-- the reference's own file runs, so the choice is explicit. **Supplying the name the
-- reference meant is the call**: the mechanism is not in dispute (its own line says which
-- string it wants), nothing of Blizzard's is edited, and the alternative is a player
-- gesture that silently does nothing while an error scrolls past. If fidelity to the bug
-- is ever wanted instead, deleting this line is the whole change.
NO_EMPTY_KEYRING_SLOTS = NO_EMPTY_KEYRING_SLOTS or NO_EMPTY_KEYRING_SLOTS_ERROR
-- Both are PLAIN tooltips — the reference calls GameTooltip:SetText here, not
-- GameTooltip_AddNewbieTip, so unlike the micro buttons next door (0661) there is no second
-- explanation line to be missing. They also anchor beside the button (ANCHOR_LEFT, so the
-- plate opens toward screen center off a bottom-right button), NOT at the default corner.
--
-- ref MainMenuBarBackpackButton OnEnter (ref-MainMenuBarBagButtons.xml l.91-99). The ref
-- appends the live binding key with GameTooltip:AppendText; ours concatenates the same
-- suffix from a LIVE GetBindingKey read since 0997, rendered through the Key Bindings
-- window's display law when it is loaded. Unbound: the bare label, the ref's own read.
-- The command is TOGGLEBACKPACK — the ref's own read here, and since 1494 ours too: it is
-- the key that runs what a plain click on this button runs.
function BenillaBagToggle_OnEnter(button)
if not GameTooltip then return end -- engine tests load file subsets
GameTooltip:SetOwner(button, "ANCHOR_LEFT")
local text = BACKPACK_TOOLTIP
local key = GetBindingKey("TOGGLEBACKPACK")
if key then
if KeyBindings_KeyText then key = KeyBindings_KeyText(key) end
text = text .. " " .. NORMAL_FONT_COLOR_CODE .. "(" .. key .. ")" .. FONT_COLOR_CODE_CLOSE
end
GameTooltip:SetText(text, 1.0, 1.0, 1.0)
end
-- ref BagSlotButton_OnEnter (ref-MainMenuBarBagButtons.lua l.86-96): the equipped bag's own
-- item tooltip, or "Equip Container" for an empty slot. The ref's "(F8)".."(F11)" suffix
-- needed GetBindingKey AND the TOGGLEBAG1..4 bindings; those bind as of 1494, so the ONE
-- thing still missing is `GameTooltip:AppendText` — our tooltip has SetText/AddLine and no
-- append verb, and the ref's suffix lands on a plate SetInventoryItem already wrote. That
-- verb's byte semantics (which line does it extend?) are unverified, so the suffix waits
-- rather than guessing; this is where it goes, over `GetBindingKey("TOGGLEBAG"..(5 -
-- button.bagId))` — the ref's index is REVERSED against the bar, so the slot nearest the
-- backpack (bagId 1) is TOGGLEBAG4/F11 and the farthest (bagId 4) is TOGGLEBAG1/F8.
function BenillaBagBarSlot_OnEnter(button)
if not GameTooltip then return end
GameTooltip:SetOwner(button, "ANCHOR_LEFT")
if not GameTooltip:SetInventoryItem("player", BenillaBagBarSlot_InvSlot(button)) then
GameTooltip:SetText(EQUIP_CONTAINER, 1.0, 1.0, 1.0)
end
end
-- `BagSlotButton_OnEnter()` — THE REFERENCE'S NAME for the hover above, and the entry point
-- the four `CharacterBag<N>Slot` buttons actually call (their XML below). Published for the
-- same standing reason `ToggleBackpack` is: an addon replaces the client's global and
-- expects the client to call the replacement.
--
-- `Bagnon.lua:67` is the exact shape, and it is a hover-frequency call, not a rare one:
-- bBagSlotButton_OnEnter = BagSlotButton_OnEnter -- capture ours
-- BagSlotButton_OnEnter = BagnonBlizBag_OnEnter -- replace the global
-- Bagnon's replacement highlights the matching slots in its own window and then calls the
-- capture. With the global absent that capture was nil, and — because nothing here ever
-- looked the global up — the replacement was never reached to raise on it: Bagnon's
-- highlight was simply, silently inert. Corpus demand is exactly one addon (this one) at
-- two sites; its siblings `BagSlotButton_OnClick`/`_OnDrag`/`_OnShiftClick`/
-- `BackpackButton_OnClick` score ZERO and are deliberately not published (1210).
--
-- The reference's body takes no argument and reads `this`; so does this one.
function BagSlotButton_OnEnter()
BenillaBagBarSlot_OnEnter(this)
end
-- ref BagSlotButton_OnClick / _OnDrag / _OnShiftClick (MainMenuBarBagButtons.lua l.3-53),
-- published beside _OnEnter above for the same reason it was: they are the names
-- BagSlotButtonTemplate binds, and an addon that builds its bag bar from that template
-- reaches them and nothing else. pfUI is the measured caller (modules/bags.lua l.537), and
-- it CHAINS them — `GetScript("OnEnter")` then calling what it got — so a missing one is
-- not a dead button, it is a nil call inside the addon's own wrapper.
--
-- **Our four bar slots do not come through here** and are untouched: they carry an explicit
-- bagId from BenillaBagBarSlot_OnLoad and wire the Benilla bodies directly. These three
-- exist so a TEMPLATE-built button reaches the same bodies — one implementation, two entry
-- points, which is the whole point of publishing the reference's name over ours.
--
-- NOT the reference's bodies, and ONE of the two reasons for that expired with decision
-- 1751's bag swap. The reference's versions do two things: scan
-- ContainerFrame1..NUM_CONTAINER_FRAMES to re-derive the checked state — which now works
-- here, and the tails below do exactly it through `IsBagOpen` — and reach for
-- `PutItemInBag` / `PickupBagFromSlot`, which this client still does not bind (our
-- equip/swap arm is the paper doll's `PickupInventoryItem`, 0208 1b). That second half is
-- what still keeps `Interface\FrameXML\MainMenuBarBagButtons.xml` out of the manifest:
-- sourcing it today hands every caller a body that raises on a verb we have not built.
-- Those two verbs are the bag BAR's migration ticket, and this file is what it replaces.
function BagSlotButton_OnClick()
BenillaBagBarSlot_OnClick(this)
end
-- The drag arm: ref picks the equipped bag up with PickupBagFromSlot; ours is the same
-- PickupInventoryItem transition the bar slots already use, so an empty cursor picks the
-- bag up and a held bag places it.
function BagSlotButton_OnDrag()
BenillaBagBarSlot_Place(this)
end
-- Shift-click opens every bag if this slot HOLDS one (ref l.38-41 verbatim), then re-reads
-- the checked state off our own window the way BenillaBagBarSlot_OnClick's tail does.
function BagSlotButton_OnShiftClick()
if GetInventoryItemTexture("player", this:GetID()) then
OpenAllBags()
end
this:SetChecked(this.bagId and IsBagOpen(this.bagId) and 1 or nil)
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
-- ref KeyRingButton's OnLoad/OnClick/OnReceiveDrag (MainMenuBarBagButtons.xml l.142-199).
-- The button is authored HIDDEN and only ever revealed by MainMenuBar_UpdateKeyRing
-- (ActionBar.xml), which is where the whole show/hide law and its divergence note live.
function BenillaKeyRingButton_OnLoad(button)
button:SetID(KEYRING_CONTAINER)
button:RegisterForClicks("LeftButtonUp", "RightButtonUp")
button:RegisterForDrag("LeftButton")
-- The ref watches PLAYER_ENTERING_WORLD / VARIABLES_LOADED / BAG_UPDATE on the bar's art
-- frame; with no saved variable there is nothing for VARIABLES_LOADED to restore, and
-- BAG_UPDATE is what actually carries "a key just arrived".
button:RegisterEvent("PLAYER_ENTERING_WORLD")
button:RegisterEvent("BAG_UPDATE")
if BenillaActionBarArt_SeatAbove then BenillaActionBarArt_SeatAbove(button) end
BenillaKeyRingButton_OnEvent()
end
function BenillaKeyRingButton_OnEvent()
if MainMenuBar_UpdateKeyRing then MainMenuBar_UpdateKeyRing() end
end
function BenillaKeyRingButton_OnClick(button)
-- Holding something ⇒ file it; empty-handed ⇒ open/close the keyring (ref l.166-173).
if CursorHasItem() then
PutKeyInKeyRing()
else
ToggleKeyRing()
end
-- Re-derive the lit ring from the window, the same provisional-toggle tail every other
-- bag-bar button uses. The keyring's window is an ordinary ContainerFrame now, so this
-- is the same `IsBagOpen` scan as the four bag slots', at KEYRING_CONTAINER.
button:SetChecked(IsBagOpen(KEYRING_CONTAINER) and 1 or nil)
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() ~= 0 then
button.icon:SetVertexColor(1, 1, 1)
else
-- Disabled by Disable_BagButtons below: keep the grey. The reference needs no such
-- arm — its desaturation is a shader flag on the texture, untouched by an Update
-- that only re-sets the image — but ours IS a vertex colour (UiPanels.xml
-- SetDesaturation), so a bag update behind the open game menu would otherwise
-- repaint the icon bright under a button that stayed dead.
button.icon:SetVertexColor(0.5, 0.5, 0.5)
end
end
-- ── The item-push drop animation (decision 0887) ─────────────────────────────────────────
-- An item lands in a bag: its icon pops in above the bag bar, hangs there, then falls into
-- the button that took it and shrinks away. ref ItemAnim (MainMenuBarBagButtons.xml l.4-16 +
-- .lua l.68-80, .lua l.92-94): each bag button owns a hidden child that, on ITEM_PUSH
-- naming its own container, swaps in the pushed item's icon, rewinds to sequence time 0 and
-- shows itself; OnAnimFinished hides it again. A second push on the same button RESTARTS it
-- (SetSequenceTime(0,0)); pushes into different bags run independently. That is exactly the
-- machine below, with the reference's ITEM_PUSH `arg1` translated from its inventory-slot
-- vocabulary (20..23) into ours (bagId 1..4) app-side — ui_loot.rs `push_container` carries
-- the byte-verified selector.
--
-- The reference's is a <Model> playing Interface\ItemAnimations\ForcedBackpackItem.mdx — a
-- single 4-vertex quad on one animated bone, i.e. a 2D icon card with keyframed scale, alpha
-- and offset. benilla has no Model widget (decision 0137's Cooldown took the same posture:
-- model the mechanism, let our own renderer draw it), so it is a Texture driven by the
-- model's OWN tracks, read out of the shipped .m2 with `benilla-extract m2anim`/`m2bones`:
--
-- sequence 0, CLAMP (plays once), duration 1.000 s
-- texture-weight (alpha) 0.000→0.0 0.133→1.0 0.267→1.0 1.000→0.0 linear
-- bone 0 scale 0.000→1.0 0.133→1.2 0.267→1.0 1.000→0.014 linear
-- bone 0 translation 0.000→(0,0) 0.500→(0,0) 1.000→(+0.010,-0.040)
-- the quad itself: 0.03 x 0.03 model units, blend Alpha, unlit, no depth write
--
-- So: fade in over the first 133 ms while swelling to 1.2x; settle back to full size by
-- 267 ms; from there dwindle and fade continuously for the remaining 733 ms — while the
-- POSITION holds until the half-second and only then falls. The three tracks deliberately
-- do not share a schedule: the card is already down to ~0.69 size and ~0.68 alpha when it
-- starts moving, so the drop reads as the icon being drawn into the bag rather than as a
-- solid tile sliding. Those numbers are the file's, not ours.
--
-- THE MODEL-UNIT → PIXEL SCALE is no longer ours to calibrate: the <Model> widget's
-- projection is RE-recorded now (wow-re `system/ui/scratch/modelframe-animation-clock.md`,
-- dispatched over the pet bar's B228 and folded back with it). The ortho leg gives
--
-- px per model unit = (5/3) * modelScale * layoutScale * screenHeightPx
--
-- which in FrameXML units is **1280 * modelScale * layoutScale**, aspect- and
-- resolution-independent, with the model ORIGIN at the frame rect's bottom-left. The ref's
-- `ItemAnimTemplate` (MainMenuBarBagButtons.xml:4) carries **no `scale` attribute**, so
-- modelScale is 1 and the constant is a flat 1280. Decision 0887's named gap is closed.
--
-- This REPLACES a back-fit, and the back-fit is worth naming because it looked like
-- evidence. The old 1200 came from assuming CooldownFrameTemplate's 0.04-unit sweep
-- *exactly* covers a 36 px action button at its scale="0.75" (⇒ K = 1200). The assumption
-- is the error: at the real projection that sweep is 0.04 · 1280 · 0.75 = **38.4 px**, so
-- it overhangs its button by 6.7% rather than fitting it — exactly as the autocast shine's
-- 30.72 px square overhangs its 30 px button. Solving against a wrong premise produced a
-- number that then "cross-checked" against a second model derived the same wrong way, and
-- both files cited the agreement as the reason to trust it.
--
-- Everything below is the model's own ratios times this constant, so the correction lands
-- in one place and the animation stays in proportion.
BENILLA_ITEM_PUSH_PX_PER_UNIT = 1280
BENILLA_ITEM_PUSH_DURATION = 1.0
-- ── The widget rect, and why the card is not placed against the button ─────────────────
-- A `<Model>` with no `<Size>` and no `setAllPoints` takes its rect from the **M2 header
-- bounding box** (`MD20+0xB4`, max−min per axis), consumed RAW as layout units — unlike
-- CSimpleTexture's texel fallback, there is no AbsDimension conversion. So the ref's
-- ItemAnim widget is **42.41 x 124.72**, nothing like the 36-square everyone assumes from
-- looking at the XML, and **model-space (0,0,0) lands on that rect's BOTTOM-LEFT corner**.
-- (wow-re `system/ui/scratch/modelframe-implicit-size-law.md`, §5-cross-checked: vtable
-- 0x81c5d8 +0x1c/+0x20 -> 0x76d080/0x76d0d0, bbox source 0x713640 at `713661 add esi,0xb4`.
-- The one worker who read `MD20+0xD0` — the VertexBox — got an all-zero box and concluded
-- the widget was degenerate; that wrong premise is exactly what the pair round exists to
-- catch, and it is why this looked unresolvable in 0887.)
--
-- That rect is the whole reason this animation reads the way it does: it stands 124.72 tall
-- off the button's own bottom edge, so the card's travel band reaches **54 px above the
-- button's top**. Decision 0887 could not know that and placed the card against the
-- button's CENTER instead, which is what put it in the wrong place in x and gave it no
-- horizontal drift at all.
BENILLA_ITEM_PUSH_FRAME_W = 42.41
BENILLA_ITEM_PUSH_FRAME_H = 124.72
-- Where that rect hangs off the button: the ref anchors the widget's BOTTOMRIGHT to the
-- button's, 10 in from the right for the five bag buttons and flush (0) for the keyring —
-- MainMenuBarBagButtons.xml l.19-27 (BagSlotButtonTemplate) and l.152-160 (KeyRingButton).
-- Per-button, so it is an OnLoad argument, not a constant.
BENILLA_ITEM_PUSH_ANCHOR_X = -10
-- ── The model's own numbers, unrounded ────────────────────────────────────────────────
-- Every one of these was rounded in 0887 and every rounding moved the card. The quad span
-- read as 0.03 (it is 0.0288 — a 4% size error), and the translation as (0.010, -0.040)
-- (which alone accounts for half a pixel of the landing point).
--
-- The bone transform is `v' = pivot + T(t) + S(t) * (v - pivot)` — three wow-re workers
-- derived that independently in three matrix conventions and agreed. The pivot
-- (M2CompBone+0x60) is NOT the quad's centre: it sits 0.23 px right and 0.07 px above it,
-- so the card shrinks *almost* in place and drifts a fifth of a pixel doing it. Keeping
-- the pivot as its own term rather than folding it into the centre is what makes this
-- reproduce the reference exactly at all three sampled times instead of nearly.
-- Bone 0's ROTATION track is empty (nKeys = 0), so there is no roll to model.
BENILLA_ITEM_PUSH_PIVOT_X = 0.017580900341272354
BENILLA_ITEM_PUSH_PIVOT_Y = 0.053958501666784286
BENILLA_ITEM_PUSH_QUAD_X = 0.017400000942870975
BENILLA_ITEM_PUSH_QUAD_Y = 0.053900001570582390
BENILLA_ITEM_PUSH_QUAD_SPAN = 0.028800001833587885
-- {time, value} keyframes, linear between (M2 track interpolation 1) — the file's own keys,
-- at full precision. The three tracks keep DIFFERENT schedules and that is the whole feel:
-- the card fades up while swelling to 1.2x over 133 ms, settles back to full size at
-- 267 ms, then dwindles and fades *continuously* for the remaining 733 ms — while its
-- position holds until the half second and only then falls. So it is already at ~0.69 size
-- and ~0.68 alpha when it starts to move, and it is invisible by the time it lands.
BENILLA_ITEM_PUSH_ALPHA = { {0.000, 0.0}, {0.133, 1.0}, {0.267, 1.0}, {1.000, 0.0} }
BENILLA_ITEM_PUSH_SCALE = { {0.000, 1.0}, {0.133, 1.2000000476837158}, {0.267, 1.0}, {1.000, 0.01441549975425005} }
-- Translation is per-AXIS: the card falls 51.4 px and drifts 13.3 px RIGHT, so one shared
-- 0..1 "drop" curve (0887's shape) cannot express it — the old code had no horizontal term
-- at all and moved the card the wrong way in x when it inferred one.
BENILLA_ITEM_PUSH_TRANS_X = { {0.000, 0.0}, {0.500, 0.0}, {1.000, 0.010394600220024586} }
BENILLA_ITEM_PUSH_TRANS_Y = { {0.000, 0.0}, {0.500, 0.0}, {1.000, -0.040167398750782013} }
-- Sample a {time, value} keyframe list at `t`, linear between keys, clamped at both ends
-- (the M2 sampler's own shape for a CLAMP sequence).
function BenillaItemPushAnim_Sample(track, t)
local last = table.getn(track)
if t <= track[1][1] then return track[1][2] end
for i = 2, last do
local key = track[i]
if t <= key[1] then
local prev = track[i - 1]
local span = key[1] - prev[1]
if span <= 0 then return key[2] end
return prev[2] + (key[2] - prev[2]) * (t - prev[1]) / span
end
end
return track[last][2]
end
-- Place/size/fade the card for the current clock, from the model's own transform.
--
-- The card's centre in MODEL space is `pivot + T(t) + S(t)*(quadCentre - pivot)`; times the
-- projection constant that is its offset in layout units from the model origin, which is
-- the widget rect's bottom-left corner. The rect's own BOTTOMRIGHT hangs off the button's,
-- so the card is placed against the BUTTON'S BOTTOMRIGHT — never its centre — and the
-- frame width is what converts between the two.
--
-- On screen: a button-sized card appears with its bottom edge ~13.6 px ABOVE the button's
-- top, pops to 1.2x and back while fading in, hangs there through the first half second,
-- then falls down-and-right INTO the button, shrinking to a half-pixel point at its centre
-- as it fades out. Reproduces the reference at t = 0, 0.133 and 1.0 to the hundredth of a
-- pixel (`modelframe-implicit-size-law.md` §3's table).
--
-- NOT modelled, deliberately, and both are named in the record: the reference's widget
-- CLIPS the card to its own rect, which shaves 1.94 px off the right edge at the 1.2x peak
-- **at 4:3 only** (16:10 and 16:9 clear it); and the widget clears the depth buffer inside
-- that 124.72-tall rect every paint. benilla draws this as a Texture, not a Model, so it
-- inherits neither — reproducing an aspect-conditional sliver would be hard-coding a
-- rendering artefact rather than modelling the mechanism that causes it.
function BenillaItemPushAnim_Paint(anim)
local t = anim.elapsed
local k = BENILLA_ITEM_PUSH_PX_PER_UNIT
local scale = BenillaItemPushAnim_Sample(BENILLA_ITEM_PUSH_SCALE, t)
local size = BENILLA_ITEM_PUSH_QUAD_SPAN * scale * k
local cx = (BENILLA_ITEM_PUSH_PIVOT_X
+ BenillaItemPushAnim_Sample(BENILLA_ITEM_PUSH_TRANS_X, t)
+ scale * (BENILLA_ITEM_PUSH_QUAD_X - BENILLA_ITEM_PUSH_PIVOT_X)) * k
local cy = (BENILLA_ITEM_PUSH_PIVOT_Y
+ BenillaItemPushAnim_Sample(BENILLA_ITEM_PUSH_TRANS_Y, t)
+ scale * (BENILLA_ITEM_PUSH_QUAD_Y - BENILLA_ITEM_PUSH_PIVOT_Y)) * k
anim:SetWidth(size)
anim:SetHeight(size)
anim:SetPoint("CENTER", anim:GetParent(), "BOTTOMRIGHT",
anim.anchorX - BENILLA_ITEM_PUSH_FRAME_W + cx, cy)
anim:SetAlpha(BenillaItemPushAnim_Sample(BENILLA_ITEM_PUSH_ALPHA, t))
end
-- ref ItemAnim_OnLoad (l.68-70): the card listens for ITEM_PUSH and nothing else. It is
-- authored hidden, so it costs no OnUpdate until a push shows it (events reach hidden
-- frames; OnUpdate does not — the reference's own split).
function BenillaItemPushAnim_OnLoad(anim, containerId, anchorX)
anim.containerId = containerId
-- The widget's own offset from the button's BOTTOMRIGHT: -10 for the five bag buttons,
-- flush 0 for the keyring (the ref's two different anchors — see the constant block).
anim.anchorX = anchorX or BENILLA_ITEM_PUSH_ANCHOR_X
anim.icon = getglobal(anim:GetName() .. "Icon")
anim.elapsed = 0
anim:RegisterEvent("ITEM_PUSH")
end
-- ref ItemAnim_OnEvent (l.72-80): `if this:GetParent():GetID() == arg1`, then
-- ReplaceIconTexture(arg2) / SetSequence(0) / SetSequenceTime(0,0) / Show. A push into a bag
-- with no button (a bank bag) matches nothing and animates nowhere, there and here.
function BenillaItemPushAnim_OnEvent(anim)
if arg1 ~= anim.containerId then return end
anim.icon:SetTexture(arg2)
anim.elapsed = 0
-- Seat the card above its button rather than at load: the bag bar's own level is set in
-- the BUTTON's OnLoad (BenillaActionBarArt_SeatAbove), which the loader may run after
-- this child exists. Re-deriving it per push is one call and can never read it stale.
anim:SetFrameLevel(anim:GetParent():GetFrameLevel() + 1)
BenillaItemPushAnim_Paint(anim)
anim:Show()
end
-- The CLAMP sequence's end is the ref's OnAnimFinished → this:Hide() (l.92-94).
function BenillaItemPushAnim_OnUpdate(anim, elapsed)
anim.elapsed = anim.elapsed + (elapsed or 0)
if anim.elapsed >= BENILLA_ITEM_PUSH_DURATION then
anim:Hide()
return
end
BenillaItemPushAnim_Paint(anim)
end
-- The bag BAR's five buttons (the backpack toggle + the four equipped-bag slots) — the row
-- Disable_/Enable_BagButtons greys as a unit. Distinct from UiPanels.xml's BAG_WINDOWS,
-- which is the five bag *windows* those buttons open.
BENILLA_BAG_BUTTONS = { "MainMenuBarBackpackButton", "CharacterBag0Slot", "CharacterBag1Slot",
"CharacterBag2Slot", "CharacterBag3Slot" }
-- Disable_BagButtons / Enable_BagButtons — ref MainMenuBarBagButtons.lua l.102-128, called
-- by GameMenuFrame's OnShow/OnHide (decision 0674) and by nothing else: while the game menu
-- holds the screen, the whole bag row greys out. That is the *visible* half of a rule the
-- panel manager already enforces (ShowUIPanel's native-center gate refuses every window
-- while the menu is up — UiPanels.xml `CanOpenPanels`); without it the bags would look
-- clickable and simply do nothing.
--
-- Mapping onto benilla's row: the ref's MainMenuBarBackpackButton is MainMenuBarBackpackButton and
-- CharacterBag0..3Slot are CharacterBag0Slot..4 — and in BOTH cases the greying goes onto
-- the button's `$parentIcon` LAYER texture, never onto a state texture. That is the
-- reference's own structure and it is load-bearing here: a disabled button draws its
-- DisabledTexture, and with none set it draws no state texture at all
-- (`ButtonState::region_visible`), so greying a NormalTexture would grey something already
-- gone. The four slots' UI-Quickslot2 rings ARE state textures and do vanish while
-- disabled unless the slot states one explicitly — so what you should see is the
-- ring stay put and the bag icon grey — which is why each slot repeats its ring as
-- DisabledTexture (see the slot instances below).
function Disable_BagButtons()
for _, button in ipairs(BENILLA_BAG_BUTTONS) do
local b = getglobal(button)
b:Disable()
SetDesaturation(b.icon, 1)
end
end
function Enable_BagButtons()
for _, button in ipairs(BENILLA_BAG_BUTTONS) do
local b = getglobal(button)
b:Enable()
SetDesaturation(b.icon, nil)
end
-- The four slots' own paint decides their restored colour: an item locked on the cursor
-- wants its 0.4 grey back, which the un-desaturate above does not restore (it clears
-- the greyscale flag, not the tint).
for i = 1, 4 do
BenillaBagBarSlot_Update(getglobal("CharacterBag" .. (i - 1) .. "Slot"))
end
end
]]></Script>
<!--
The backpack toggle, RELOCATED onto the main action bar's right side so the bar reads whole
(the real client's MainMenuBarBackpackButton, ref-MainMenuBarBagButtons.xml l.54-64): a 37x37
button parented/anchored to the bar art frame at BOTTOMRIGHT (-6,2). Its behavior (toggle the
backpack window) is unchanged; only its seat moved from the free-floating screen corner onto
the bar. (MainMenuBarArtFrame is defined in ActionBar.xml, which the loader loads before
this file; the anchor resolves by name.)
-->
<CheckButton name="MainMenuBarBackpackButton" id="0" parent="MainMenuBarArtFrame">
<Size><AbsDimension x="37" y="37"/></Size>
<Anchors>
<Anchor point="BOTTOMRIGHT" relativeTo="MainMenuBarArtFrame" relativePoint="BOTTOMRIGHT">
<Offset><AbsDimension x="-6" y="2"/></Offset>
</Anchor>
</Anchors>
<!-- The backpack art is a LAYER texture, not the NormalTexture — the reference's own
structure (MainMenuBarBackpackButton inherits ItemButtonTemplate and sets
`MainMenuBarBackpackButtonIconTexture` in its OnLoad; the state textures are the
template's ring). It matters because `Disable_BagButtons` (decision 0674) disables this
button while the game menu is up, and a *state* texture with no DisabledTexture beside
it is not drawn in the disabled state (`ButtonState::region_visible` — the byte rule at
`SetState 0x779790`), so the backpack icon vanished outright instead of greying
(director-reported). As a layer it is untouched by button state, exactly like the four
bag-bar slots' own `$parentIcon`, and the greying is the vertex tint on it. -->
<Layers>
<Layer level="BACKGROUND">
<Texture name="$parentIconTexture" file="Interface\Buttons\Button-Backpack-Up"/>
</Layer>
</Layers>
<!-- The item-push drop card (decision 0887, BenillaItemPushAnim_* above) — the ref's
$parentItemAnim, one per bag button, container id 0 = the backpack. -->
<Frames>
<Frame name="$parentItemAnim" hidden="true">
<Size><AbsDimension x="36" y="36"/></Size>
<Anchors><Anchor point="CENTER"/></Anchors>
<Layers>
<Layer level="OVERLAY">
<Texture name="$parentIcon" setAllPoints="true"/>
</Layer>
</Layers>
<Scripts>
<OnLoad>BenillaItemPushAnim_OnLoad(self, 0)</OnLoad>
<OnEvent>BenillaItemPushAnim_OnEvent(self)</OnEvent>
<OnUpdate>BenillaItemPushAnim_OnUpdate(self, arg1)</OnUpdate>
</Scripts>
</Frame>
</Frames>
<HighlightTexture file="Interface\Buttons\ButtonHilight-Square" alphaMode="ADD"/>
<!-- The lit ring while the backpack window is open (ref MainMenuBarBackpackButton
l.104) — a CheckButton like the ref's; OnShow/OnHide of the window drive it. -->
<CheckedTexture file="Interface\Buttons\CheckButtonHilight" alphaMode="ADD"/>
<Scripts>
<!-- Seat the backpack icon above the bar art too (same reason as the bag-bar slots —
BenillaActionBarArt_SeatAbove's comment). -->
<OnLoad>
self.icon = getglobal("MainMenuBarBackpackButtonIconTexture")
if BenillaActionBarArt_SeatAbove then BenillaActionBarArt_SeatAbove(self) end
</OnLoad>
<!-- The undo repairs the CheckButton widget's own pre-handler flip; from there the
checked state belongs to the windows' OnShow/OnHide writes (ours and any
addon's) — the stated divergence at BenillaBagToggle_OnClick's comment.
The shift fork is the ref's own (MainMenuBarBagButtons.xml l.81-87): the button
carries the SAME pair of knobs the B / SHIFT-B keys do (1494), so a shift-click
opens all bags and a plain one toggles the backpack. -->
<OnClick>
self:SetChecked(not self:GetChecked())
if IsShiftKeyDown() then
OpenAllBags()
else
BenillaBagToggle_OnClick()
end
</OnClick>
<OnEnter>BenillaBagToggle_OnEnter(self)</OnEnter>
<OnLeave>BenillaBagBarButton_OnLeave()</OnLeave>
</Scripts>
</CheckButton>
<!--
The bag bar (decision 0216 slice 2, BenillaBagBarSlot_* above): four 36×36 buttons to the
LEFT of MainMenuBarBackpackButton, mirroring the reference CharacterBag0..3Slot row. Bar slot N (1..4)
sits at chain position N counting away from the backpack button — CharacterBag0Slot (bagId
1, Bag0Slot) is CLOSEST to the backpack, CharacterBag3Slot (bagId 4, Bag3Slot) FARTHEST —
the real bar's own left-to-right order. Sized to MATCH the action buttons (36×36 icon in a
66×66 UI-Quickslot2 ring, 6px chain gap = the 42px stride), so the bag icons fill their slots
exactly like the spell icons instead of floating undersized (the earlier 30×30 guess).
-->
<CheckButton name="CharacterBag0Slot" parent="MainMenuBarArtFrame">
<Size><AbsDimension x="36" y="36"/></Size>
<Anchors>
<Anchor point="RIGHT" relativeTo="MainMenuBarBackpackButton" relativePoint="LEFT"><Offset><AbsDimension x="-6" y="0"/></Offset></Anchor>
</Anchors>
<Layers><Layer level="BACKGROUND"><Texture name="$parentIconTexture"/></Layer></Layers>
<!-- The item-push drop card (decision 0887, BenillaItemPushAnim_* above) — the ref's
$parentItemAnim, one per bag button; container id 1. -->
<Frames>
<Frame name="$parentItemAnim" hidden="true">
<Size><AbsDimension x="36" y="36"/></Size>
<Anchors><Anchor point="CENTER"/></Anchors>
<Layers>
<Layer level="OVERLAY">
<Texture name="$parentIcon" setAllPoints="true"/>
</Layer>
</Layers>
<Scripts>
<OnLoad>BenillaItemPushAnim_OnLoad(self, 1)</OnLoad>
<OnEvent>BenillaItemPushAnim_OnEvent(self)</OnEvent>
<OnUpdate>BenillaItemPushAnim_OnUpdate(self, arg1)</OnUpdate>
</Scripts>
</Frame>
</Frames>
<NormalTexture file="Interface\Buttons\UI-Quickslot2">
<Size><AbsDimension x="66" y="66"/></Size>
<Anchors><Anchor point="CENTER" relativePoint="CENTER"/></Anchors>
</NormalTexture>
<!-- The same ring again as the DISABLED art, so the slot keeps its frame while
Disable_BagButtons has the row greyed under the game menu (decision 0674). A button
with no DisabledTexture draws NO state texture when disabled
(`ButtonState::region_visible`) — the ring would blink out of the bar entirely. The
reference needs no such line because its widget keeps the previously shown texture up
when the new state's slot is empty (`SetState 0x779790`'s gated hide); ours states it
instead of relying on that. -->
<DisabledTexture file="Interface\Buttons\UI-Quickslot2">
<Size><AbsDimension x="66" y="66"/></Size>
<Anchors><Anchor point="CENTER" relativePoint="CENTER"/></Anchors>
</DisabledTexture>
<HighlightTexture file="Interface\Buttons\ButtonHilight-Square" alphaMode="ADD"/>
<!-- The lit ring while this bag's window is open (ref BagSlotButtonTemplate l.52). -->
<CheckedTexture file="Interface\Buttons\CheckButtonHilight" alphaMode="ADD"/>
<Scripts>
<OnLoad>BenillaBagBarSlot_OnLoad(self, 1)</OnLoad>
<OnClick>BenillaBagBarSlot_OnClick(self)</OnClick>
<OnEvent>BenillaBagBarSlot_OnEvent(self)</OnEvent>
<OnEnter>BagSlotButton_OnEnter()</OnEnter>
<OnLeave>BenillaBagBarButton_OnLeave()</OnLeave>
<!-- The drag trio: OnDragStart picks the equipped bag up, OnReceiveDrag drops a carried
bag in — both the same PickupInventoryItem transition (empty cursor picks up, a held
bag places), mirroring the paper-doll/bag-slot drag routing. -->
<OnDragStart>BenillaBagBarSlot_Place(self)</OnDragStart>
<OnReceiveDrag>BenillaBagBarSlot_Place(self)</OnReceiveDrag>
</Scripts>
</CheckButton>
<CheckButton name="CharacterBag1Slot" parent="MainMenuBarArtFrame">
<Size><AbsDimension x="36" y="36"/></Size>
<Anchors>
<Anchor point="RIGHT" relativeTo="CharacterBag0Slot" relativePoint="LEFT"><Offset><AbsDimension x="-6" y="0"/></Offset></Anchor>
</Anchors>
<Layers><Layer level="BACKGROUND"><Texture name="$parentIconTexture"/></Layer></Layers>
<!-- The item-push drop card (decision 0887, BenillaItemPushAnim_* above) — the ref's
$parentItemAnim, one per bag button; container id 2. -->
<Frames>
<Frame name="$parentItemAnim" hidden="true">
<Size><AbsDimension x="36" y="36"/></Size>
<Anchors><Anchor point="CENTER"/></Anchors>
<Layers>
<Layer level="OVERLAY">
<Texture name="$parentIcon" setAllPoints="true"/>
</Layer>
</Layers>
<Scripts>
<OnLoad>BenillaItemPushAnim_OnLoad(self, 2)</OnLoad>
<OnEvent>BenillaItemPushAnim_OnEvent(self)</OnEvent>
<OnUpdate>BenillaItemPushAnim_OnUpdate(self, arg1)</OnUpdate>
</Scripts>
</Frame>
</Frames>
<NormalTexture file="Interface\Buttons\UI-Quickslot2">
<Size><AbsDimension x="66" y="66"/></Size>
<Anchors><Anchor point="CENTER" relativePoint="CENTER"/></Anchors>
</NormalTexture>
<!-- The same ring again as the DISABLED art, so the slot keeps its frame while
Disable_BagButtons has the row greyed under the game menu (decision 0674). A button
with no DisabledTexture draws NO state texture when disabled
(`ButtonState::region_visible`) — the ring would blink out of the bar entirely. The
reference needs no such line because its widget keeps the previously shown texture up
when the new state's slot is empty (`SetState 0x779790`'s gated hide); ours states it
instead of relying on that. -->
<DisabledTexture file="Interface\Buttons\UI-Quickslot2">
<Size><AbsDimension x="66" y="66"/></Size>
<Anchors><Anchor point="CENTER" relativePoint="CENTER"/></Anchors>
</DisabledTexture>
<HighlightTexture file="Interface\Buttons\ButtonHilight-Square" alphaMode="ADD"/>
<CheckedTexture file="Interface\Buttons\CheckButtonHilight" alphaMode="ADD"/>
<Scripts>
<OnLoad>BenillaBagBarSlot_OnLoad(self, 2)</OnLoad>
<OnClick>BenillaBagBarSlot_OnClick(self)</OnClick>
<OnEvent>BenillaBagBarSlot_OnEvent(self)</OnEvent>
<OnEnter>BagSlotButton_OnEnter()</OnEnter>
<OnLeave>BenillaBagBarButton_OnLeave()</OnLeave>
<!-- The drag trio: OnDragStart picks the equipped bag up, OnReceiveDrag drops a carried
bag in — both the same PickupInventoryItem transition (empty cursor picks up, a held
bag places), mirroring the paper-doll/bag-slot drag routing. -->
<OnDragStart>BenillaBagBarSlot_Place(self)</OnDragStart>
<OnReceiveDrag>BenillaBagBarSlot_Place(self)</OnReceiveDrag>
</Scripts>
</CheckButton>
<CheckButton name="CharacterBag2Slot" parent="MainMenuBarArtFrame">
<Size><AbsDimension x="36" y="36"/></Size>
<Anchors>
<Anchor point="RIGHT" relativeTo="CharacterBag1Slot" relativePoint="LEFT"><Offset><AbsDimension x="-6" y="0"/></Offset></Anchor>
</Anchors>
<Layers><Layer level="BACKGROUND"><Texture name="$parentIconTexture"/></Layer></Layers>
<!-- The item-push drop card (decision 0887, BenillaItemPushAnim_* above) — the ref's
$parentItemAnim, one per bag button; container id 3. -->
<Frames>
<Frame name="$parentItemAnim" hidden="true">
<Size><AbsDimension x="36" y="36"/></Size>
<Anchors><Anchor point="CENTER"/></Anchors>
<Layers>
<Layer level="OVERLAY">
<Texture name="$parentIcon" setAllPoints="true"/>
</Layer>
</Layers>
<Scripts>
<OnLoad>BenillaItemPushAnim_OnLoad(self, 3)</OnLoad>
<OnEvent>BenillaItemPushAnim_OnEvent(self)</OnEvent>
<OnUpdate>BenillaItemPushAnim_OnUpdate(self, arg1)</OnUpdate>
</Scripts>
</Frame>
</Frames>
<NormalTexture file="Interface\Buttons\UI-Quickslot2">
<Size><AbsDimension x="66" y="66"/></Size>
<Anchors><Anchor point="CENTER" relativePoint="CENTER"/></Anchors>
</NormalTexture>
<!-- The same ring again as the DISABLED art, so the slot keeps its frame while
Disable_BagButtons has the row greyed under the game menu (decision 0674). A button
with no DisabledTexture draws NO state texture when disabled
(`ButtonState::region_visible`) — the ring would blink out of the bar entirely. The
reference needs no such line because its widget keeps the previously shown texture up
when the new state's slot is empty (`SetState 0x779790`'s gated hide); ours states it
instead of relying on that. -->
<DisabledTexture file="Interface\Buttons\UI-Quickslot2">
<Size><AbsDimension x="66" y="66"/></Size>
<Anchors><Anchor point="CENTER" relativePoint="CENTER"/></Anchors>
</DisabledTexture>
<HighlightTexture file="Interface\Buttons\ButtonHilight-Square" alphaMode="ADD"/>
<CheckedTexture file="Interface\Buttons\CheckButtonHilight" alphaMode="ADD"/>
<Scripts>
<OnLoad>BenillaBagBarSlot_OnLoad(self, 3)</OnLoad>
<OnClick>BenillaBagBarSlot_OnClick(self)</OnClick>
<OnEvent>BenillaBagBarSlot_OnEvent(self)</OnEvent>
<OnEnter>BagSlotButton_OnEnter()</OnEnter>
<OnLeave>BenillaBagBarButton_OnLeave()</OnLeave>
<!-- The drag trio: OnDragStart picks the equipped bag up, OnReceiveDrag drops a carried
bag in — both the same PickupInventoryItem transition (empty cursor picks up, a held
bag places), mirroring the paper-doll/bag-slot drag routing. -->
<OnDragStart>BenillaBagBarSlot_Place(self)</OnDragStart>
<OnReceiveDrag>BenillaBagBarSlot_Place(self)</OnReceiveDrag>
</Scripts>
</CheckButton>
<CheckButton name="CharacterBag3Slot" parent="MainMenuBarArtFrame">
<Size><AbsDimension x="36" y="36"/></Size>
<Anchors>
<Anchor point="RIGHT" relativeTo="CharacterBag2Slot" relativePoint="LEFT"><Offset><AbsDimension x="-6" y="0"/></Offset></Anchor>
</Anchors>
<Layers><Layer level="BACKGROUND"><Texture name="$parentIconTexture"/></Layer></Layers>
<!-- The item-push drop card (decision 0887, BenillaItemPushAnim_* above) — the ref's
$parentItemAnim, one per bag button; container id 4. -->
<Frames>
<Frame name="$parentItemAnim" hidden="true">
<Size><AbsDimension x="36" y="36"/></Size>
<Anchors><Anchor point="CENTER"/></Anchors>
<Layers>
<Layer level="OVERLAY">
<Texture name="$parentIcon" setAllPoints="true"/>
</Layer>
</Layers>
<Scripts>
<OnLoad>BenillaItemPushAnim_OnLoad(self, 4)</OnLoad>
<OnEvent>BenillaItemPushAnim_OnEvent(self)</OnEvent>
<OnUpdate>BenillaItemPushAnim_OnUpdate(self, arg1)</OnUpdate>
</Scripts>
</Frame>
</Frames>
<NormalTexture file="Interface\Buttons\UI-Quickslot2">
<Size><AbsDimension x="66" y="66"/></Size>
<Anchors><Anchor point="CENTER" relativePoint="CENTER"/></Anchors>
</NormalTexture>
<!-- The same ring again as the DISABLED art, so the slot keeps its frame while
Disable_BagButtons has the row greyed under the game menu (decision 0674). A button
with no DisabledTexture draws NO state texture when disabled
(`ButtonState::region_visible`) — the ring would blink out of the bar entirely. The
reference needs no such line because its widget keeps the previously shown texture up
when the new state's slot is empty (`SetState 0x779790`'s gated hide); ours states it
instead of relying on that. -->
<DisabledTexture file="Interface\Buttons\UI-Quickslot2">
<Size><AbsDimension x="66" y="66"/></Size>
<Anchors><Anchor point="CENTER" relativePoint="CENTER"/></Anchors>
</DisabledTexture>
<HighlightTexture file="Interface\Buttons\ButtonHilight-Square" alphaMode="ADD"/>
<CheckedTexture file="Interface\Buttons\CheckButtonHilight" alphaMode="ADD"/>
<Scripts>
<OnLoad>BenillaBagBarSlot_OnLoad(self, 4)</OnLoad>
<OnClick>BenillaBagBarSlot_OnClick(self)</OnClick>
<OnEvent>BenillaBagBarSlot_OnEvent(self)</OnEvent>
<OnEnter>BagSlotButton_OnEnter()</OnEnter>
<OnLeave>BenillaBagBarButton_OnLeave()</OnLeave>
<!-- The drag trio: OnDragStart picks the equipped bag up, OnReceiveDrag drops a carried
bag in — both the same PickupInventoryItem transition (empty cursor picks up, a held
bag places), mirroring the paper-doll/bag-slot drag routing. -->
<OnDragStart>BenillaBagBarSlot_Place(self)</OnDragStart>
<OnReceiveDrag>BenillaBagBarSlot_Place(self)</OnReceiveDrag>
</Scripts>
</CheckButton>
<!--
The keyring button (decision 0765) — ref KeyRingButton, MainMenuBarBagButtons.xml l.142-199.
Sits at the FAR left of the bag row, past CharacterBag3Slot (the ref's CharacterBag3Slot),
and is authored HIDDEN: nothing reveals it but MainMenuBar_UpdateKeyRing, and only
while HasKey() — the point being that a character with no key has no keyring at all, and the
bar's plate has no socket for one.
18x39 and the TexCoords are the ref's exactly, and they are not arbitrary: the three state
atlases are 32x64 holding one 18x39 button, so 18/32 = 0.5625 and 39/64 = 0.609375 crop it
1:1. The chain gap is OUR row's -6 rather than the ref's -5, so the button seats evenly
against the 36px slots this bar scaled its own row to.
Unlike the five bag buttons it is NOT in BENILLA_BAG_BUTTONS: its art is a state texture, not
an $parentIcon layer, so Disable_BagButtons' SetDesaturation(b.icon) has nothing to grey —
and the ref leaves it out of its own Disable_BagButtons for the same reason.
-->
<CheckButton name="KeyRingButton" parent="MainMenuBarArtFrame" hidden="true">
<Size><AbsDimension x="18" y="39"/></Size>
<Anchors>
<Anchor point="RIGHT" relativeTo="CharacterBag3Slot" relativePoint="LEFT"><Offset><AbsDimension x="-6" y="0"/></Offset></Anchor>
</Anchors>
<!-- The item-push drop card (decision 0887, BenillaItemPushAnim_* above) — the ref's
$parentItemAnim, one per bag button; container id -2 (KEYRING_CONTAINER). The ref gives
the keyring one too (MainMenuBarBagButtons.xml l.152-160), and the byte selector really
does reach it: a push landing in player-array slots 81..112 resolves to -2, not 0. -->
<Frames>
<Frame name="$parentItemAnim" hidden="true">
<Size><AbsDimension x="36" y="36"/></Size>
<Anchors><Anchor point="CENTER"/></Anchors>
<Layers>
<Layer level="OVERLAY">
<Texture name="$parentIcon" setAllPoints="true"/>
</Layer>
</Layers>
<Scripts>
<OnLoad>BenillaItemPushAnim_OnLoad(self, -2, 0)</OnLoad>
<OnEvent>BenillaItemPushAnim_OnEvent(self)</OnEvent>
<OnUpdate>BenillaItemPushAnim_OnUpdate(self, arg1)</OnUpdate>
</Scripts>
</Frame>
</Frames>
<NormalTexture file="Interface\Buttons\UI-Button-KeyRing">
<TexCoords left="0" right="0.5625" top="0" bottom="0.609375"/>
</NormalTexture>
<PushedTexture file="Interface\Buttons\UI-Button-KeyRing-Down">
<TexCoords left="0" right="0.5625" top="0" bottom="0.609375"/>
</PushedTexture>
<HighlightTexture file="Interface\Buttons\UI-Button-KeyRing-Highlight" alphaMode="ADD">
<TexCoords left="0" right="0.5625" top="0" bottom="0.609375"/>
</HighlightTexture>
<!-- The lit ring while the keyring window is open — the bag row's own convention
(the ref drives the same state through UpdateMicroButtons' SetButtonState). -->
<CheckedTexture file="Interface\Buttons\CheckButtonHilight" alphaMode="ADD"/>
<Scripts>
<OnLoad>BenillaKeyRingButton_OnLoad(self)</OnLoad>
<OnEvent>BenillaKeyRingButton_OnEvent()</OnEvent>
<OnClick>BenillaKeyRingButton_OnClick(self)</OnClick>
<OnEnter>BenillaKeyRingButton_OnEnter(self)</OnEnter>
<OnLeave>BenillaBagBarButton_OnLeave()</OnLeave>
<!-- A key dragged onto the button files itself, the ref's own OnReceiveDrag (l.174-178). -->
<OnReceiveDrag>PutKeyInKeyRing()</OnReceiveDrag>
</Scripts>
</CheckButton>
</Ui>