Files
benilla-octo/crates/benilla-app/assets/ui/ItemButtonTemplate.xml
T
SamandClaude Opus 5 9a184d3476 the guild window opens: roster, ranks, notes and the message of the day
- the social window's third tab is live — the roster in both its views, the member card with its promote/demote/remove verbs, the rank editor's thirteen permissions, the guild information board, and the invite, leave, kick and note dialogs behind them
- setting your hearthstone at an innkeeper now asks before it takes and says so afterwards, and the innkeeper's gossip lines get their own icons back
- unavailable talents grey out, and every greyed icon in the client now goes through the same luminance fold the original used
- the buff tooltip's remaining-time line reads gold with the client's own hour/minute ladder, the macro editor takes a shift-click to paste a spell, trainer rows repaint after a purchase, and a long book page stops flickering its ellipsis

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-16 17:38:54 +07:00

184 lines
9.7 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. 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>
</Ui>