Files
benilla-octo/crates/benilla-app/assets/ui/ItemButtonTemplate.xml
T
SamandClaude Fable 5 a683d5524f the interface code is ours: the transcription debt is paid
- forty files that carried the reference's literal Lua are rewritten as our own expression of the behaviour — worst identical run 61 lines down to 7 — and the README now says exactly that
- the overlap gate measures the code itself across all four reference corpora, and ratchets: the number may only go down
- a lost session ends at the login screen instead of silently re-authenticating and stealing the account back
- a building's skybox is a real model: Caverns of Time's white ceiling becomes its painted nebula and additive stars

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

183 lines
9.6 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 deliberately NOT here.** The harness's template
demand ranks it at zero on both axes — no corpus addon names it in a `CreateFrame` fourth
argument or an XML `inherits=` (they name their own `*ItemButtonTemplate`s, or
`ContainerFrameItemButtonTemplate`) — and our own bag windows already have
`BenillaBagSlotTemplate`. Building an unwanted template is exactly the unmeasured work 1210
was written about.
-->
<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. benilla has no `Texture:SetDesaturated` at all (it is the top
-- row of the harness's missing-METHODS ranking, 61 corpus addons), so the feature test
-- is on the METHOD's existence and the no-shader branch is the one we take. When the
-- real verb lands this line starts using it with nothing else to change.
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>
</Ui>