Files
benilla-octo/crates/benilla-app/assets/ui/ItemButtonTemplate.xml
T
SamandClaude Fable 5 14bd5ea1a4 the bank is the reference's own window, and far sight freezes your body
- the bank window is the reference's own `BankFrame.xml` run off the player's chain — our 890-line copy is deleted whole; putting bags into the bank's bag slots works (the two verbs its buttons are wired to now exist), the purse is a real `SmallMoneyFrameTemplate` splitting 987654321 copper into three coin buttons instead of one string, `BANKFRAME_OPENED` loses the argument the real client never fires, and the bank stops auto-opening your bags while the vendor and mailbox keep doing it; the empty-slot click still plays the bag sound, because the reference's own line plays it on every click the cursor did not consume — named as faithful rather than quietly fixed
- underneath it, the bank's inventory band is a VIEW over the container feed, not a second copy — inventory ids 40..69 had answered nil because the doll array stops at 24, so the stock file swapped in would have drawn an empty bank; the six bank bag slots become real inventory slots (64..=69) fed off the player descriptor, the three bag verbs are carved from the binary (`PutItemInBackpack` IS `PutItemInBag(0xFF)` through a ten-byte thunk, the occupied-by-an-ordinary-item leg auto-stores with no destination slot at all, and the return is the Lua number 1 or nil — the nil being the whole reason a plain left-click opens a bag), and the swap caught `BankButtonIDToInvSlotID`'s bag arm six slots wrong: it took a bag number 1..6 where the reference passes the button's own id, which is the container id
- far sight freezes your own body while the view is out — the fifth conjunct of the movement predicates' shared precondition, joining the health term so the walk, the turn and the follow teardown go down together; both halves are load-bearing: the `IsActivePlayer` half is what keeps a Mind Control victim walking (possession rides the same PLAYER_FARSIGHT field), and the latch half is the RESOLVED engage rather than the raw field — and the camera is untouched, you still look around freely, which is the whole point of the spell
- the reset offer no longer outlives the character that earned it — log out after walking out of the Deadmines and your next character was offered a reset the first one had earned — and the one lockout leg no probe can ever reach (vmangos writes a last-instance map only from raid binds, which the client's handler discards as not-a-party-dungeon) is now covered end to end through the real system, its module doc saying plainly: tested and never observed

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

257 lines
14 KiB
XML

<!--
Our implementation of the eight `SetItemButton*` globals every 1.12 window uses to paint an item
slot: its icon, its stack count, its merchant stock line, and the four vertex-colour verbs. The
specification is the reference's `ItemButtonTemplate.lua` — the names, the argument order and the
behaviour of every branch are its contract; the code below is ours, and it is factored the way
eight near-identical verbs want to be factored (one region lookup, one tint verb) rather than the
way eight separately-written ones came out.
**Why this file exists, and what it cost to be without it.** The director installed Bagnon, hit
B, and got a frame with a title and a gold line and NO BAG SLOTS. `Bagnon_Core/core/Item.lua`
ends every slot update with
l.204 SetItemButtonDesaturated(item, locked, 0.5, 0.5, 0.5)
l.209 SetItemButtonTexture(item, texture)
l.210 SetItemButtonCount(item, itemCount)
and all three were nil. The raise lands inside `BagnonFrame_AddBag`'s per-slot loop, so the
FIRST slot aborts the loop, `frame.size` never advances, `BagnonFrame_Layout` never runs and
the window is left exactly as wide as its own border. `ui_script::bagnon_render_tests` is the
reproduction.
These are FrameXML, not engine (decision 1190's split): the real client ships them as Lua in
`Interface\FrameXML\ItemButtonTemplate.lua`, so ours belong here in our own interface rather
than in a Rust binding. The specification was read off the install's own copy with the
`mpqcat` example over `WoW/Data/interface.MPQ` — not from memory.
(An XML comment may not contain a double hyphen, which is why no command line is spelled out
here; `benilla-mpq/examples/mpqcat.rs`'s own header carries the invocation.)
**Corpus demand, counted rather than assumed** (the 218-addon vanilla corpus; `grep -rl`, one
row per verb, addons/sites):
SetItemButtonTexture 4 addons / 19 sites
SetItemButtonDesaturated 3 addons / 8 sites
SetItemButtonCount 3 addons / 7 sites
SetItemButtonTextureVertexColor 3 addons / 7 sites
SetItemButtonNormalTextureVertexColor 1 addon / 2 sites
SetItemButtonNameFrameVertexColor 1 addon / 2 sites
SetItemButtonSlotVertexColor 0 / 0
SetItemButtonStock 0 / 0
Six addons in all — Bagnon_Core, CT_MailMod, CT_MasterMod, AdvancedTradeSkillWindow,
QuestHistory, SpecialTalentUI. The last two rows have **zero** corpus callers and are here
anyway because the family is one contract and, once the tint verb below is shared, they are one
line each; that is stated rather than dressed up as demand (1210).
**The virtual `ItemButtonTemplate` Button IS here now — 1719: a prior measured decision is
revisited when the measurement changes, and it changed.** The paragraph below is kept because
it records how the number was wrong before it was also incomplete. It used to say
"the harness's template demand ranks it at zero on both axes", full stop. The zero was real;
the demand was not. pfUI asks for `ContainerFrameItemButtonTemplate` — which the reference
declares as `inherits="ItemButtonTemplate"` (ContainerFrame.xml l.4), so it wants this one
transitively — through a shape the census could not see:
local tpl = "ContainerFrameItemButtonTemplate"
if bag == -1 then tpl = "BankItemButtonGenericTemplate" end
CreateFrame("Button", "pfBag" .. bag .. "item" .. slot, pfUI.bags[bag], tpl)
The instrument said so at itself the whole time (`missing_templates`' doc named the literal-only
bound), which is the difference between an honest under-report and a wrong number — but a bound
stated at the function and forgotten at the call site is only half a guard. The census now
resolves a local bound to a literal, and a test asserts both what it sees and what it still
cannot (concatenation, table fields, parameters).
That paragraph then weighed the chain as "a real unit of work" and left it. **The weighing was
wrong, and wrong for a reason worth recording: it priced the SCRIPTS, which we already have.**
`ContainerFrameItemButtonTemplate`'s handlers are `ContainerFrameItemButton_OnLoad/_OnClick/
_OnEnter/_OnUpdate` and `KeyRingItemButton_OnClick` — and this client does not transcribe those,
it EXECUTES the reference's own `ContainerFrame.lua` off the player's patch chain
(`ui_script::reference_ui`, SOURCED). They have been live the whole time. What was missing was
never the behaviour; it was two XML declarations, because the reference ships its templates in
`ContainerFrame.xml` and that file is not in the sourced set.
So the chain cost what it actually cost: this Button, and — at the time — a transcribed
`ContainerFrameItemButtonTemplate` beside the adapter that already wrapped one of those very
handlers. **Decision 1751 then took the last step and made both moot for the windows
themselves**: `Interface\FrameXML\ContainerFrame.xml` is a manifest entry now, so the
reference's OWN declaration of that template is the live one and the transcription is deleted.
This Button stays, because it is the reference's PUBLISHED surface for addons that build their
own slots and it has to exist whether or not any window of ours uses it.
**The bank arm resolves too, since 1751's second window.** `BankItemButtonGenericTemplate`
inherits `BankItemButtonTemplate`, whose handlers live in `BankFrame.lua` — and that file
arrives through `Interface\FrameXML\BankFrame.xml`'s own `<Script file=>`, off the chain, the
same way. This paragraph used to say the arm was a named omission (1134 §4) whose real cost was
the unsourced Lua plus `this:GetInventorySlot()`, a widget method we do not bind. Neither is
true any more: the Lua is sourced, and `GetInventorySlot` is not a widget method at all — it is
a plain field `BankFrameBaseButton_OnLoad` assigns (`this.GetInventorySlot =
ButtonInventorySlot`). pfUI reaches the bank arm for `bag == -1`, and it now finds a real
template.
-->
<Ui xmlns="http://www.blizzard.com/wow/ui/">
<Script><![CDATA[
-- The naming contract every verb below rests on: an "item button" is a Button whose own
-- name prefixes its regions — `<name>IconTexture`, `<name>Count`, `<name>Stock`,
-- `<name>NormalTexture`, `<name>NameFrame`, `<name>SlotTexture`. That is the reference's
-- `$parent…` template shape, and it is why these are free functions taking a button rather
-- than widget methods: any button that follows the naming works, whoever built it.
--
-- TWO DIFFERENT NILS, and the difference is the whole error policy of this file.
-- A nil BUTTON is a no-op everywhere: callers hand us whatever their loop is holding, and
-- the reference tolerates that in all eight verbs, so we do too. A button that is missing
-- the REGION is not tolerated — the lookup yields nil and the very next `:` raises. That
-- loudness is deliberate: "this is not an item button" is a real mistake by the caller, and
-- 1203's lesson is that the silent version of a failure is the expensive one.
-- One named region off a button, which is the only address these verbs ever compute. Nil in,
-- nil out, so the button guard reads as one expression instead of eight copied early
-- returns.
local function region(button, suffix)
return button and getglobal(button:GetName() .. suffix)
end
-- Four of the eight differ in nothing but which region they tint, so they share the verb
-- and name the region. The guard is on the BUTTON and deliberately not on the region it
-- resolved: a nil button is the tolerated case, a button carrying no such region is the
-- loud one. `texture` is a local rather than an inline call only so that the raise names
-- which lookup came back empty.
local function tint(button, suffix, r, g, b)
local texture = region(button, suffix)
if button then
texture:SetVertexColor(r, g, b)
end
end
function SetItemButtonCount(button, count)
if not button then
return
end
count = count or 0
button.count = count
local label = region(button, "Count")
-- `isBag` is the reference's own flag for a bag button, where even a single item
-- counts as a number worth showing. Above 999 the slot shows a bare `*` — the stack
-- is "more than the label can say" — while `button.count` keeps the real number.
if count > 1 or (button.isBag and count > 0) then
label:SetText(count > 999 and "*" or count)
label:Show()
else
label:Hide()
end
end
function SetItemButtonStock(button, numInStock)
if not button then
return
end
-- The reference defaults this to `""` and then compares it with `> 0`, which is a
-- string/number comparison Lua refuses — a raise on the only path the default can
-- reach. Ours defaults to 0, which is what the branch below actually means. Not aping
-- a quirk (method step 3); the behaviour on every reachable path is identical.
numInStock = numInStock or 0
button.numInStock = numInStock
local label = region(button, "Stock")
if numInStock > 0 then
label:SetText(format(MERCHANT_STOCK, numInStock))
label:Show()
else
label:Hide()
end
end
function SetItemButtonTexture(button, texture)
if not button then
return
end
local icon = region(button, "IconTexture")
-- Visibility tracks the ARGUMENT, not the region's contents, and it is settled before
-- the texture is assigned: clearing a slot with a nil texture must leave the region
-- hidden, never an empty quad standing where the icon was.
if texture then
icon:Show()
else
icon:Hide()
end
icon:SetTexture(texture)
end
function SetItemButtonTextureVertexColor(button, r, g, b)
tint(button, "IconTexture", r, g, b)
end
function SetItemButtonNormalTextureVertexColor(button, r, g, b)
tint(button, "NormalTexture", r, g, b)
end
function SetItemButtonNameFrameVertexColor(button, r, g, b)
tint(button, "NameFrame", r, g, b)
end
function SetItemButtonSlotVertexColor(button, r, g, b)
tint(button, "SlotTexture", r, g, b)
end
function SetItemButtonDesaturated(button, desaturated, r, g, b)
-- The one verb whose own nil guard is on the ICON rather than the button: the
-- reference checks both, and `region()` collapses them into this single test without
-- changing which calls no-op and which raise.
local icon = region(button, "IconTexture")
if not icon then
return
end
-- The reference asks the texture itself whether the desaturating shader is available
-- (`local shaderSupported = icon:SetDesaturated(desaturated)`) and falls through to a
-- flat grey tint when it is not — 1.12 ran on fixed-function hardware where that was
-- the ordinary answer. Since decision 1327 benilla's answer is YES: the icon greys to
-- luminance in the shader and wears the caller's own tint on top, which is what the
-- ref's own `(1, 0.65, 0.65, 0.65)` and `(locked, 0.5, 0.5, 0.5)` calls are asking
-- for. The `icon.SetDesaturated and` guard stays for a region leaf without the verb;
-- the flat-grey arm below is then the ref's own fallback, unchanged.
local shaderSupported = icon.SetDesaturated and icon:SetDesaturated(desaturated)
-- Undesaturated is full colour whatever the caller passed; desaturated is the caller's
-- own tint when they named one AND the shader took it, and the flat grey otherwise.
if not desaturated then
r, g, b = 1.0, 1.0, 1.0
elseif not r or not shaderSupported then
r, g, b = 0.5, 0.5, 0.5
end
icon:SetVertexColor(r, g, b)
end
]]></Script>
<!-- Ref `Interface\FrameXML\ItemButtonTemplate.xml` l.4-46, structure verbatim: the region
NAMES are the whole point of the file, because they are the contract the eight verbs above
and every addon both address. `$parentIconTexture`, `$parentCount`, `$parentStock`,
`$parentNormalTexture` — pfUI reads three of those four off a button it built itself
(modules/bags.lua l.418-431) and never asks who built it.
The reference declares NO scripts on this Button, and neither do we; it is a shape, and
whoever inherits it brings the behaviour. -->
<Button name="ItemButtonTemplate" virtual="true">
<Size><AbsDimension x="37" y="37"/></Size>
<Layers>
<Layer level="BORDER">
<Texture name="$parentIconTexture"/>
<FontString name="$parentCount" inherits="NumberFontNormal" justifyH="RIGHT" hidden="true">
<Anchors>
<Anchor point="BOTTOMRIGHT"><Offset><AbsDimension x="-5" y="2"/></Offset></Anchor>
</Anchors>
</FontString>
<FontString name="$parentStock" inherits="NumberFontNormalYellow" justifyH="LEFT" hidden="true">
<Anchors>
<Anchor point="TOPLEFT"><Offset><AbsDimension x="0" y="-2"/></Offset></Anchor>
</Anchors>
</FontString>
</Layer>
</Layers>
<NormalTexture name="$parentNormalTexture" file="Interface\Buttons\UI-Quickslot2">
<Size><AbsDimension x="64" y="64"/></Size>
<Anchors>
<Anchor point="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"/>
</Button>
</Ui>