Files
benilla-octo/crates/benilla/assets/ui/CraftFrame.xml
T
SamandClaude Fable 5 2ad0043523 benilla: initial public release
A from-scratch World of Warcraft 1.12.1 client in Rust + Bevy.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-24 14:27:01 +07:00

932 lines
66 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 Enchanting window (decision 0437 phase 3): the 1.12 `CraftFrame`, transcribed from
the reference `Blizzard_CraftUI.xml`/`.lua` (session scratchpad `ref-ui/`, decision 0246
discipline — never committed) onto our own engine/feeds. Geometry/art-paths/structure below are
QUOTED from the ref (line citations inline); the Lua is transcribed logic turned onto benilla's
Era API (`GetCraftName`/`GetCraftDisplaySkillLine`/`GetNumCrafts`/`GetCraftInfo`/reagents/
`GetCraftSpellFocus`/`DoCraft`/`CloseCraft`, decision 0437's engine surface,
`crates/benilla-ui/src/script/craft.rs`). The exact twin of `TradeSkillFrame.xml` — same
engine-gap substitutions, same FLAT-list INTERIM, same doc conventions — this header only calls
out where CRAFT'S OWN ref actually differs (verified against `Blizzard_CraftUI.lua`, not
assumed from TradeSkill's shape).
NOT an NPC session (unlike Gossip/Merchant/Trainer): the window opens client-side off your own
cast of an effect-47 opener spell — no NPC, no server round trip to open it (0437 §"What
scoping verified", TradeSkillFrame.xml's own identical header note). It registers
`CRAFT_SHOW`/`CRAFT_UPDATE`/`CRAFT_CLOSE` on ITSELF (TradeSkillFrame.xml's own precedent: this
engine has no separate FrameXML driver that dispatches SHOW for you).
Modeled on the two known-good dressed windows for the framing (MerchantFrame.xml's own header
comment, TradeSkillFrame.xml's own precedent for this exact art family — both windows are
`ClassTrainerFrame`-styled 384×512 four-quadrant windows and share BYTE-IDENTICAL geometry for
every region this file also carries: the portrait, the four border quadrants' Size/Anchors, the
skill-border/horizontal-bar decorative art, the rank StatusBar block, the persistent selection
highlight, the 8-row list + its ScrollFrame, the detail header art, the reagent grid's anchor
chain (down to the SAME "Reagent7 anchors off Reagent6, not Reagent5" ref quirk), and the
Create/Exit button positions):
- MerchantFrame.xml/TradeSkillFrame.xml — the 384×512 four-quadrant window art (no
<Backdrop>: the art IS the frame), the "player" portrait binding (`SetPortraitTexture(region,
"player")` — ref l.89-91 confirms Craft ALSO binds "player", not "npc": this is not an NPC
session), the UIPanel slot system (carries NO hardcoded <Anchors> — ShowUIPanel/SetLeftFrame
SetPoints it), OnShow/OnHide PlaySound pair.
- TradeSkillFrame.xml — the FLAT layout constraint (below), the faux-scroll list kit
(BenillaFauxScrollFrameTemplate, ScrollTemplates.xml), the locally-reconstructed
UIPanelButtonTemplate/close-button/item-button idioms (no such templates ship in this house
dialect — TalentFrame.xml's own ENGINE API note, restated per-window, StackSplit.xml's own
"own local virtual template, not a cross-file inherits" convention), the persistent
selection-highlight Frame, and the `button.craftIndex`/`Button:SetTextColor` engine-gap
substitutions (deviation 7 below).
FLAT layout (TradeSkillFrame.xml's own house constraint, restated here): a child <Frame> shown
at runtime does not render its own <Layers> FontStrings in this engine, so the detail-pane
FontStrings + the title live on BenillaCraftFrame's OWN <Layers> (OVERLAY, above the window's
own BACKGROUND/BORDER/ARTWORK art groups), never on a nested sub-frame. The ref's
`CraftDetailScrollFrame`/`CraftDetailScrollChildFrame` therefore don't exist here — the detail
is a FIXED (non-scrolling) pane, computed once from the flattened ScrollFrame+ScrollChild
offsets (TOPLEFT 20,-234 + each child's own ref-quoted offset — BYTE-IDENTICAL to TradeSkill's
own detail-scroll anchor, since both refs place it there). Per-region regions that DO need
Show()/Hide() live on Button/StatusBar/Texture children (those render their own Layers) — the
rank bar, the detail icon, and the 8 reagent slots.
v1 DEVIATIONS (pinned to decision 0437's in-flight wow-re §5 dispatch where noted):
1. FLAT LIST, NO HEADERS. `GetCraftInfo`'s `craftType` is never `"header"` in v1 (the engine
API contract) — the ref's header BRANCH (the row's fold icon/texture swap, the row-text
21px-right reposition loop) is still dropped outright, not even as a no-op:
`Expand`/`CollapseCraftSkillLine` exist but are wired as true engine-side NO-OPS
(`craft.rs`'s own module doc + `Expand/CollapseCraftSkillLine` comment) — the 1.12 craft
list is single-group, so the real client's own header path is unexercised here; a click
changes nothing observable beyond the button's own local state.
`CraftExpandButtonFrame`/`CraftCollapseAllButton` (ref l.265-325) now SHIP, for fidelity —
this deviation's own earlier revision omitted them entirely; closed this session
(TradeSkillFrame.xml's own identical CollapseAll tab, restored there the same session).
`BenillaCraftFrame_Update`'s own restored tail (ref l.248-282, minus the row-text-reposition
loop, still dropped above) counts headers to size the tab's texture — since `GetCraftInfo`
never returns `"header"`, that count is always 0, so the tail's own `numHeaders > 0` branch
(ref l.270) always takes the ELSE arm: `BenillaCraftExpandButtonFrame` ends up HIDDEN in
practice, a faithful-but-currently-inert outcome (this file's own deviation 2 "ships for
fidelity, never actually visible yet" shape). `CraftTypeColor["header"]`/`["used"]`/
`["none"]` are kept in the transcribed color table for fidelity but are unreachable this way
(TradeSkillFrame.xml's own identical posture). v1 still ships no filter dropdowns.
2. THE SPELL-FOCUS TOOL NEVER REDDENS. `BuildColoredListString` is REUSED, not redefined — it
is already a true Lua GLOBAL after `TradeSkillFrame.xml` loads (`if not
BuildColoredListString then function BuildColoredListString(...) ... end end`, no `local`),
and `TradeSkillFrame.xml`'s own header comment names this file as the second consumer.
`CraftFrame.xml` is loaded AFTER `TradeSkillFrame.xml` in `ui_script/mod.rs`'s load list, so
this file calls it directly with no guard/redefinition. It reds an unmet tool byte-faithfully,
but `GetCraftSpellFocus`'s `hasTool` has no client-side proximity check yet (0437 INTERIM,
pinned to the §5 dispatch, TradeSkillFrame.xml's own identical note) — v1 never actually
reds a tool in practice.
3. NO DRESSING-ROOM / CHAT-LINK INSERT. The ref's Ctrl+click `DressUpItemLink` / Shift+click
`ChatFrameEditBox:Insert` on `CraftIcon`/`CraftReagent*` (ref l.44-51, 555-563) are dropped
— the cursor-item arc hasn't landed (MerchantFrame.xml's/TradeSkillFrame.xml's own identical
"a no-op here until the cursor-item arc" precedent). `CraftIcon`'s `this.hasItem = 1`
OnLoad flag (ref l.553) is dropped too — it is read only by `UIParent.lua`'s own generic
Ctrl-click dressing-room handler (grepped), which this dropped branch never reaches; a real,
now-inert vestige, not a benilla omission.
4. NO `SKILL_LINES_CHANGED` self-listen on the rank StatusBar (ref l.253-260, TradeSkillFrame's
own identical deviation): the skill-up feedback/diff-watcher is 0437 phase 4, not yet built
— the bar's value/color/text refresh only rides this window's own SHOW/UPDATE/selection
paint.
5. NO `PLAYER_ENTERING_WORLD` hide (TradeSkillFrame.xml's own identical choice for this session
shape) — opening this window is your own client-side cast, not an NPC session.
6. NO BEAST TRAINING / NO TRAINING POINTS (0437's own scope line — the pet arc owns Beast
Training's CraftFrame use). Every ref region/branch that exists ONLY to serve a pet ability
row is omitted, not stubbed: `CraftFramePointsLabel`/`CraftFramePointsText`
(`Craft_UpdateTrainingPoints`, ref l.428-438), the per-row `Craft<n>Cost` FontString (always
blank in v1 since `GetCraftInfo`'s `trainingPointCost` is a hardcoded `0` — `craft.rs`'s own
module doc), and the Requirements line's pet-level fallback branch (`requiredLevel`, also a
hardcoded `0` — ref l.364-369, unreachable). `UNIT_PET_TRAINING_POINTS` is not registered
(nothing left to drive it). `GetCraftButtonToken()` always returns `"CREATE"` — Beast
Training's `"TRAIN"` token is the only other value the ref ever returns, and that branch is
out of scope (`craft.rs`'s own note).
7. ENGINE API gaps (TradeSkillFrame.xml's own "no engine equivalent" posture, restated per
substitution actually used here):
- `Button:SetTextColor`/`:LockHighlight`/`:UnlockHighlight` have no native method (grepped;
`benilla-ui/src/script/button.rs` ships texture-pairs/SetText/Enable/Disable/
RegisterForClicks/SetButtonState/Click only) — row coloring routes through a named child
FontString (`$parentName`/`$parentSubText`) instead of the button's own inherited text
region (the ref's `Craft<n>Text`/`Craft<n>SubText`, `ClassTrainerSkillButtonTemplate`'s
auto-created text object); the persistent selection glow is the separate
`BenillaCraftHighlightFrame` (TradeSkillFrame.xml's own selection-highlight precedent)
instead of Lock/UnlockHighlight — no independent hover glow beyond it, and (verified
against Craft's OWN ref, unlike TradeSkill's row template) `CraftButtonTemplate` never
overrides `OnEnter` for a tooltip either — this window's list rows carry NO hover tooltip
at all, faithfully (TradeSkill's own row hover IS a real ref behavior; Craft's is not).
- `Button:GetID()`/`:SetID()` have no native method (same grep) — the ref's
`craftButton:SetID(craftIndex)` + `this:GetID()` pair becomes a plain Lua field,
`button.craftIndex`, set in the Update loop and read by the click handler
(`ClassTrainerSkillButtonTemplate`'s own `this.r/g/b`-restoring `OnLeave` — a color dance
made moot by the FontString substitution above — is dropped for the same reason
TradeSkillFrame.xml's own row `OnLeave` dropped it: nothing ever recolors the row on
hover in this engine, so there is nothing to restore).
- `EditBox:SetNumber`/`SecondsToTime` are NOT needed here at all: Craft has no repeat-count
input (deviation 8) and no per-recipe cooldown concept (no `CraftSkillCooldown` region
exists in the ref) — TradeSkillFrame.xml's own engine-gap shims for those simply don't
apply to this window.
8. NO CREATE ALL (a REAL ref difference, not an engine gap — verified: `CraftCreateButton`'s
OnClick is bare `DoCraft(GetCraftSelectionIndex())`, ref l.667, no count argument
anywhere). This window ships ONE action button; no repeat-count EditBox, no -/+ buttons, no
`CraftCreateAllButton`, no `GetCraftskillRepeatCount`/`UPDATE_CRAFTRECAST` — `craft.rs`'s
own module doc.
9. Dead ref code dropped, not transcribed (TradeSkillFrame.xml's own `reagentToAnchorTo`
precedent for exactly this move): `CraftSubTypeColor` + its `subColor` local (ref l.15-22,
189) are computed but never read anywhere in the reference — grepped, confirmed zero other
uses — a real vestige. The row-width scrollbar toggle (ref l.174-187, `293` vs `323`) is
dropped the same way TradeSkill's was: the ScrollFrame itself is always visible in both
engines (only its *scrollbar* toggles on overflow), so rows are fixed at 293
(TradeSkillFrame.xml's own row width).
10. SELECTION SURVIVES `CRAFT_SHOW` (a deliberate deviation from Craft's OWN ref, flagged):
the real `CraftFrame_Show` (ref l.26-49) unconditionally re-selects index 1 on every open,
discarding whatever was selected before. Our engine's `set_craft` already PRESERVES the
selection across a re-push by spell id (`craft.rs`'s own doc, TradeSkill's identical
`set_trade_skill` law) specifically so re-opening the same skill line doesn't visibly jump
the detail pane — forcing index 1 on every Show would fight that mechanism for no benefit.
`BenillaCraftFrame_Show` therefore mirrors `BenillaTradeSkillFrame_Show`'s own pattern
(keep a nonzero selection, else pick 1) instead of Craft's own ref line-for-line.
11. THE ITEM-TARGET PICK UX NOTE (0437 §"What scoping verified", `craft.rs`'s own
`needs_item_target` doc): `DoCraft` on an `ENCHANT_ITEM`-family recipe arms an APP-SIDE
item-target pick (the `TARGET_FLAG_ITEM` cast-target flow) — this XML plays NO part in
that flow; `CraftCreateButton_OnClick` below is the same bare `DoCraft(...)` call
regardless of which recipe is selected, exactly like the ref's own button.
12. GlobalStrings the ref reads (`REQUIRES_LABEL`, `SPELL_REAGENTS`, `PARENS_TEMPLATE`,
`CREATE`, `EXIT`) are declared LOCALLY below as the byte-exact 1.12 enUS values (session
scratchpad extraction, decision 0246's discipline, line-cited per constant),
TradeSkillFrame.xml's own idiom — with ONE exception: `CREATE` is declared as a REAL Lua
GLOBAL (`CREATE = CREATE or "Create"`, guarded so a second file loading it stays
idempotent), not `local`, because `getglobal(GetCraftButtonToken())` (ref l.106, faithfully
transcribed in `BenillaCraftFrame_Update` below) needs an actual `_G["CREATE"]` entry to
resolve — a genuine structural requirement of that ref call, not a style choice.
`GRAY_FONT_COLOR`/`HIGHLIGHT_FONT_COLOR` are Fonts.xml globals (CharacterFrame.xml's own
precedent for reading them unqualified).
Registers at the left UIPanel slot (`UiPanels.xml`). The ref's OWN registration
(`Blizzard_CraftUI.lua:24`, `UIPanelWindows["CraftFrame"] = { area = "left", pushable = 4 }`) is
`pushable = 4` — VERIFIED at that line (NOT `3`; `3` is `TradeSkillFrame`'s own line 13 value, a
different window/depth — grepped both files directly to confirm before using either number).
ESC-closable through the standard UIPanel chain. `CloseCraft()` fires on hide for ANY reason
(ESC, close X, Exit, `CRAFT_CLOSE`, or another left-slot window displacing it) —
TradeSkillFrame.xml's own `CloseTradeSkill`-on-hide precedent, adapted (no server round trip per
0437, purely client-side session teardown).
-->
<Script><![CDATA[
-- ── Constants (ref Blizzard_CraftUI.lua:2-4) ────────────────────────────────────────────────
local CRAFTS_DISPLAYED = 8 -- CRAFTS_DISPLAYED (ref l.2)
local MAX_CRAFT_REAGENTS = 8 -- MAX_CRAFT_REAGENTS (ref l.3)
local CRAFT_SKILL_HEIGHT = 16 -- CRAFT_SKILL_HEIGHT (ref l.4)
-- The recipe-row color table (ref l.6-13), transcribed verbatim including "header"/"used"/
-- "none" — all UNREACHABLE in v1 (this file's header comment, deviation 1). CraftSubTypeColor
-- (ref l.15-22) is DROPPED outright — dead ref code, deviation 9.
CraftTypeColor = {}
CraftTypeColor["optimal"] = { r = 1.00, g = 0.50, b = 0.25 }
CraftTypeColor["medium"] = { r = 1.00, g = 1.00, b = 0.00 }
CraftTypeColor["easy"] = { r = 0.25, g = 0.75, b = 0.25 }
CraftTypeColor["trivial"] = { r = 0.50, g = 0.50, b = 0.50 }
CraftTypeColor["used"] = { r = 0.50, g = 0.50, b = 0.50 }
CraftTypeColor["header"] = { r = 1.00, g = 0.82, b = 0 }
CraftTypeColor["none"] = { r = 0.25, g = 0.75, b = 0.25 }
-- ── GlobalStrings the ref reads (byte-exact 1.12 enUS, this file's header comment cites the
-- extraction + the CREATE-must-be-global exception) — chunk-local except CREATE. ─────────────
local REQUIRES_LABEL = "Requires:" -- REQUIRES_LABEL (GlobalStrings.lua:3339)
local PARENS_TEMPLATE = "(%s)" -- PARENS_TEMPLATE (2984)
local EXIT_LABEL = "Exit" -- EXIT (1933)
CREATE = CREATE or "Create" -- CREATE (871) -- REAL global, see header comment
-- BuildColoredListString is REUSED from TradeSkillFrame.xml (already a true global by the time
-- this file loads — this file's header comment, deviation 2). No redefinition here.
-- ── Detail pane ─────────────────────────────────────────────────────────────────────────────
-- No crafts known at all (ref l.131-150): blank the whole detail pane. Regions on this window's
-- own Layers "hide" via SetText("") — TradeSkillFrame.xml's own convention, which predates
-- region Show/Hide (region.rs has the real pair now); equivalent either way.
function BenillaCraftFrame_HideDetails()
getglobal("BenillaCraftFrameTitleText"):SetText(GetCraftName())
getglobal("BenillaCraftName"):SetText("")
getglobal("BenillaCraftRequirements"):SetText("")
getglobal("BenillaCraftDescription"):SetText("")
getglobal("BenillaCraftIcon"):Hide()
for i = 1, MAX_CRAFT_REAGENTS do
getglobal("BenillaCraftReagent" .. i):Hide()
end
getglobal("BenillaCraftCreateButton"):Disable()
end
-- Fill the detail pane for the selected recipe `id` (ref CraftFrame_SetSelection, l.285-408;
-- the header branch is dropped — deviation 1, never reached). Also the SOURCE of the selection
-- highlight's color (set once here, ref l.305-306).
function BenillaCraftFrame_SetSelection(id)
if not id or id == 0 then
BenillaCraftFrame_HideDetails()
return
end
SelectCraft(id)
if GetCraftSelectionIndex() > GetNumCrafts() then
return
end
local craftName, craftSubSpellName, craftType = GetCraftInfo(id)
local color = CraftTypeColor[craftType]
if color then
getglobal("BenillaCraftHighlight"):SetVertexColor(color.r, color.g, color.b)
end
-- General info (ref l.308-320).
getglobal("BenillaCraftFrameTitleText"):SetText(GetCraftName())
getglobal("BenillaCraftName"):SetText(craftName)
local icon = getglobal("BenillaCraftIcon")
icon:Show()
icon:SetNormalTexture(GetCraftIcon(id) or BENILLA_FALLBACK_ICON)
-- The description + the Reagents label's DYNAMIC reposition (ref l.312-318, transcribed
-- verbatim): when there's a description, the label sits 10px below its bottom edge; when
-- there isn't, it sits directly where the (blank) description would have been.
if GetCraftDescription(id) then
getglobal("BenillaCraftDescription"):SetText(GetCraftDescription(id))
getglobal("BenillaCraftReagentLabel"):SetPoint("TOPLEFT", "BenillaCraftDescription", "BOTTOMLEFT", 0, -10)
else
getglobal("BenillaCraftDescription"):SetText(" ")
getglobal("BenillaCraftReagentLabel"):SetPoint("TOPLEFT", "BenillaCraftDescription", "TOPLEFT", 0, 0)
end
-- Reagents (ref l.321-359): up to MAX_CRAFT_REAGENTS, gray-out + creatable gate.
local creatable = true
local numReagents = GetCraftNumReagents(id)
for i = 1, MAX_CRAFT_REAGENTS do
local reagent = getglobal("BenillaCraftReagent" .. i)
if i <= numReagents then
local reagentName, reagentTexture, need, have = GetCraftReagentInfo(id, i)
if not reagentName or not reagentTexture then
-- Item template not landed yet for this reagent — hide it, like the ref does
-- for any nil reagent (engine API note, decision 0437).
reagent:Hide()
else
reagent:Show()
reagent.icon:SetTexture(reagentTexture)
reagent.name:SetText(reagentName)
have = have or 0
if have < need then
reagent.icon:SetVertexColor(0.5, 0.5, 0.5)
reagent.name:SetTextColor(GRAY_FONT_COLOR.r, GRAY_FONT_COLOR.g, GRAY_FONT_COLOR.b)
creatable = false
else
reagent.icon:SetVertexColor(1.0, 1.0, 1.0)
reagent.name:SetTextColor(HIGHLIGHT_FONT_COLOR.r, HIGHLIGHT_FONT_COLOR.g, HIGHLIGHT_FONT_COLOR.b)
end
local haveText = have
if have >= 100 then haveText = "*" end
reagent.count:SetText(haveText .. " /" .. need)
end
else
reagent:Hide()
end
end
-- Requires: line (ref l.361-372) — ONE concatenated FontString, unlike TradeSkill's split
-- Label+Text pair (Craft's own ref genuinely differs here — this file's header comment
-- points at TradeSkillFrame.xml's split for comparison). The pet-level fallback branch
-- (ref l.364-369) is unreachable in v1 (deviation 6) and dropped.
local requiredTotems = BuildColoredListString(GetCraftSpellFocus(id))
if requiredTotems then
getglobal("BenillaCraftRequirements"):SetText(REQUIRES_LABEL .. " " .. requiredTotems)
else
getglobal("BenillaCraftRequirements"):SetText("")
end
if creatable then
getglobal("BenillaCraftCreateButton"):Enable()
else
getglobal("BenillaCraftCreateButton"):Disable()
end
end
-- ── The list page (ref CraftFrame_Update, l.99-283 — the header/expand-icon/training-points
-- tail is dropped: deviations 1/6, never reached in v1) ────────────────────────────────────────
function BenillaCraftFrame_Update()
getglobal("BenillaCraftFrameTitleText"):SetText(GetCraftName())
getglobal("BenillaCraftCreateButton"):SetText(getglobal(GetCraftButtonToken()))
local numCrafts = GetNumCrafts()
if numCrafts == 0 then
BenillaCraftFrame_HideDetails()
else
getglobal("BenillaCraftCollapseAllButton"):Enable() -- ref l.158 (no matching Disable()
-- anywhere in Craft's own ref — a real asymmetry vs. TradeSkill's own l.86/94 pair,
-- transcribed as-is, not "fixed").
end
local scrollFrame = getglobal("BenillaCraftListScrollFrame")
BenillaFauxScrollFrame_Update(scrollFrame, numCrafts, CRAFTS_DISPLAYED, CRAFT_SKILL_HEIGHT)
local craftOffset = BenillaFauxScrollFrame_GetOffset(scrollFrame)
-- The skill-rank StatusBar (ref l.107-123): Show/Hide are real here (real widgets, not
-- flattened Layer regions).
local name, rank, maxRank = GetCraftDisplaySkillLine()
local rankFrame = getglobal("BenillaCraftRankFrame")
if name then
getglobal("BenillaCraftRankFrameSkillName"):SetText(name)
rankFrame:SetStatusBarColor(0.0, 0.0, 1.0, 0.5)
getglobal("BenillaCraftRankFrameBackground"):SetVertexColor(0.0, 0.0, 0.75, 0.5)
rankFrame:SetMinMaxValues(0, maxRank)
rankFrame:SetValue(rank)
getglobal("BenillaCraftRankFrameSkillRank"):SetText(rank .. "/" .. maxRank)
rankFrame:Show()
getglobal("BenillaCraftSkillBorderLeft"):Show()
getglobal("BenillaCraftSkillBorderRight"):Show()
else
rankFrame:Hide()
getglobal("BenillaCraftSkillBorderLeft"):Hide()
getglobal("BenillaCraftSkillBorderRight"):Hide()
end
local highlight = getglobal("BenillaCraftHighlightFrame")
highlight:Hide()
local selection = GetCraftSelectionIndex()
for i = 1, CRAFTS_DISPLAYED do
local craftIndex = i + craftOffset
local button = getglobal("BenillaCraft" .. i)
if craftIndex <= numCrafts then
local craftName, craftSubSpellName, craftType, numAvailable = GetCraftInfo(craftIndex)
button.craftIndex = craftIndex
local color = CraftTypeColor[craftType]
if color then
button.name:SetTextColor(color.r, color.g, color.b)
end
if (numAvailable or 0) == 0 then
button.name:SetText(" " .. craftName)
else
button.name:SetText(" " .. craftName .. " [" .. numAvailable .. "]")
end
if craftSubSpellName and craftSubSpellName ~= "" then
button.subtext:SetText(format(PARENS_TEMPLATE, craftSubSpellName))
else
button.subtext:SetText("")
end
button:Show()
if selection == craftIndex then
highlight:SetPoint("TOPLEFT", button, "TOPLEFT", 0, 0)
highlight:Show()
end
else
button.craftIndex = nil
button:Hide()
end
end
-- Count headers + how many stay folded (ref l.249-259), to drive the CollapseAll tab's own
-- texture below — restored, deviation 1 (still functionally inert: GetCraftInfo never
-- returns "header" in v1, so numHeaders is always 0 here).
local numHeaders, notExpanded = 0, 0
for i = 1, numCrafts do
local craftName, craftSubSpellName, craftType, numAvailable, isExpanded = GetCraftInfo(i)
if craftName and craftType == "header" then
numHeaders = numHeaders + 1
if not isExpanded then
notExpanded = notExpanded + 1
end
end
end
-- If all headers are not expanded then show the collapse texture, otherwise the expand one
-- (ref l.261-267).
local collapseAll = getglobal("BenillaCraftCollapseAllButton")
if notExpanded ~= numHeaders then
collapseAll.collapsed = nil
collapseAll:SetNormalTexture("Interface\\Buttons\\UI-MinusButton-Up")
else
collapseAll.collapsed = true
collapseAll:SetNormalTexture("Interface\\Buttons\\UI-PlusButton-Up")
end
-- Show the tab only when there are headers to fold (ref l.269-282; the row-text-reposition
-- loops there stay dropped, deviation 1) — always Hidden in v1 since numHeaders is always 0.
if numHeaders > 0 then
getglobal("BenillaCraftExpandButtonFrame"):Show()
else
getglobal("BenillaCraftExpandButtonFrame"):Hide()
end
end
-- A row click selects it (ref CraftButton_OnClick + SetSelection's non-header path; the
-- header-fold branch is dropped, deviation 1). Left-button only. NO OnEnter/OnLeave here —
-- Craft's OWN ref never adds a row-hover tooltip (verified, header comment deviation 7).
function BenillaCraftSkillButton_OnClick(button, mouseButton)
if mouseButton ~= "LeftButton" then return end
local index = button.craftIndex
if not index then return end
BenillaCraftFrame_SetSelection(index)
BenillaCraftFrame_Update()
end
-- The window's wheel handler (bubbled up the parent chain from a row) → one row via the faux
-- kit (TradeSkillFrame.xml's own note: the engine bubbles a spin up the parent chain, so the
-- handler lives on the window, not a child).
function BenillaCraftFrame_OnMouseWheel(delta)
BenillaFauxScrollFrame_OnMouseWheel(getglobal("BenillaCraftListScrollFrame"), delta)
end
-- ── The CollapseAll tab (ref CraftCollapseAllButton_OnClick, l.417-425) — TradeSkillFrame.xml's
-- own identical shape: toggles Expand/CollapseCraftSkillLine(0) off the button's own `.collapsed`
-- flag (re-derived every Update() above), no direct BenillaCraftFrame_Update() call (the ref
-- doesn't call one either). UNLIKE TradeSkill's own pair, `Expand`/`CollapseCraftSkillLine` are
-- true engine no-ops here (header comment, deviation 1) — no touched flag, no CRAFT_UPDATE, so a
-- click changes nothing observably beyond this button's own local state until the next Update()
-- runs some other way. ──────────────────────────────────────────────────────────────────────
function BenillaCraftCollapseAllButton_OnClick(self)
if self.collapsed then
self.collapsed = nil
ExpandCraftSkillLine(0)
else
self.collapsed = true
BenillaFauxScrollFrame_SetOffset(getglobal("BenillaCraftListScrollFrame"), 0)
CollapseCraftSkillLine(0)
end
end
-- ── The detail icon (ref CraftIcon OnEnter, l.564-568 — the Ctrl/Shift dressing-room branches
-- dropped, deviation 3): SetCraftSpell, NOT SetCraftItem — Craft has no product-item concept
-- (craft.rs's own module doc). ───────────────────────────────────────────────────────────────
function BenillaCraftIcon_OnEnter(button)
if GetCraftSelectionIndex() ~= 0 then
GameTooltip:SetOwner(button, "ANCHOR_RIGHT")
GameTooltip:SetCraftSpell(GetCraftSelectionIndex())
GameTooltip:Show()
end
end
-- ── The reagent slots (ref CraftItemTemplate OnEnter, l.31-35 — the dressing-room/chat-link
-- OnClick branches dropped, deviation 3) ────────────────────────────────────────────────────
function BenillaCraftReagent_OnLoad(button, index)
button.reagentIndex = index
local n = button:GetName()
button.icon = getglobal(n .. "Icon")
button.name = getglobal(n .. "Name")
button.count = getglobal(n .. "Count")
end
function BenillaCraftReagent_OnEnter(button)
GameTooltip:SetOwner(button, "ANCHOR_TOPLEFT")
GameTooltip:SetCraftItem(GetCraftSelectionIndex(), button.reagentIndex)
GameTooltip:Show()
end
-- ── Create / Exit (ref l.654-670 — no Create All, no repeat-count input: deviation 8) ───────
function BenillaCraftCreateButton_OnClick()
-- Bare index → spell id, exactly the ref's own OnClick (ref l.667). An ENCHANT_ITEM recipe
-- arms an app-side item-target pick from here — this XML plays no part in that (header
-- comment, deviation 11).
DoCraft(GetCraftSelectionIndex())
end
function BenillaCraftCancelButton_OnClick()
HideUIPanel(getglobal("BenillaCraftFrame"))
end
-- ── The window lifecycle (MerchantFrame.xml/TradeSkillFrame.xml's own shape) ────────────────
function BenillaCraftFrame_OnLoad(frame)
frame:RegisterEvent("CRAFT_SHOW")
frame:RegisterEvent("CRAFT_UPDATE")
frame:RegisterEvent("CRAFT_CLOSE")
-- SPELLS_CHANGED (ref l.58/92-93): a real, non-Beast-Training event (a newly learned enchant
-- rank should refresh the list) — registered for fidelity; forward-compatible if this
-- engine doesn't fire it yet (TradeSkillFrame.xml's own UNIT_PORTRAIT_UPDATE posture).
-- UNIT_PET_TRAINING_POINTS (ref l.59) is NOT registered — nothing left to drive it
-- (deviation 6, Craft_UpdateTrainingPoints doesn't exist in v1).
frame:RegisterEvent("SPELLS_CHANGED")
frame:RegisterEvent("UNIT_PORTRAIT_UPDATE")
local scrollFrame = getglobal("BenillaCraftListScrollFrame")
scrollFrame.updateFunc = BenillaCraftFrame_Update
end
-- Fresh session paint (ref CraftFrame_Show, l.26-49 — the header index-select branch is
-- dropped, deviation 1; the selection-reset itself is ALSO overridden, deviation 10: this
-- keeps a persisted selection instead of always forcing index 1).
function BenillaCraftFrame_Show(frame)
getglobal("BenillaCraftCreateButton"):Disable()
if GetCraftSelectionIndex() == 0 then
if GetNumCrafts() > 0 then
BenillaCraftFrame_SetSelection(1)
end
else
BenillaCraftFrame_SetSelection(GetCraftSelectionIndex())
end
BenillaFauxScrollFrame_SetOffset(getglobal("BenillaCraftListScrollFrame"), 0)
SetPortraitTexture(getglobal("BenillaCraftFramePortrait"), "player")
BenillaCraftFrame_Update()
ShowUIPanel(frame)
end
function BenillaCraftFrame_OnEvent(frame)
if event == "CRAFT_SHOW" then
BenillaCraftFrame_Show(frame)
elseif event == "CRAFT_UPDATE" then
-- ref l.68-87: re-anchor the selection if it's fallen off a (possibly shorter) list.
local selection = GetCraftSelectionIndex()
if selection > 1 and selection <= GetNumCrafts() then
BenillaCraftFrame_SetSelection(selection)
elseif GetNumCrafts() > 0 then
BenillaCraftFrame_SetSelection(1)
BenillaFauxScrollFrame_SetOffset(getglobal("BenillaCraftListScrollFrame"), 0)
end
BenillaCraftFrame_Update()
elseif event == "CRAFT_CLOSE" then
HideUIPanel(frame)
elseif event == "SPELLS_CHANGED" then
BenillaCraftFrame_Update()
elseif event == "UNIT_PORTRAIT_UPDATE" then
if arg1 == "player" then
SetPortraitTexture(getglobal("BenillaCraftFramePortrait"), "player")
end
end
end
function BenillaCraftFrameCloseButton_OnClick()
HideUIPanel(getglobal("BenillaCraftFrame"))
end
]]></Script>
<!--
One list row (ref CraftButtonTemplate inherits ClassTrainerSkillButtonTemplate, l.4-28 for the
template, l.344-415 for the instances): a 293×16 text row, name + a parenthesized rank subtext
column (unlike TradeSkill's own row template, which carries no subname — this file's header
comment) — TradeSkillFrame.xml's own BenillaTradeSkillSkillButtonTemplate shape, plus the fixed
subtext column (TrainerFrame.xml's own Name/SubText fixed-column precedent, since this engine
has no FontString-width-based dynamic anchor). NO OnEnter/OnLeave — Craft's own ref adds no row
hover tooltip (header comment, deviation 7).
-->
<Button name="BenillaCraftSkillButtonTemplate" virtual="true" hidden="true">
<Size><AbsDimension x="293" y="16"/></Size>
<Layers>
<Layer level="ARTWORK">
<FontString name="$parentName" inherits="GameFontNormal" justifyH="LEFT">
<Size><AbsDimension x="180" y="16"/></Size>
<Anchors><Anchor point="LEFT" relativePoint="LEFT"><Offset><AbsDimension x="4" y="0"/></Offset></Anchor></Anchors>
</FontString>
<FontString name="$parentSubText" inherits="GameFontNormalSmall" justifyH="LEFT">
<Size><AbsDimension x="99" y="16"/></Size>
<Anchors><Anchor point="LEFT" relativePoint="LEFT"><Offset><AbsDimension x="190" y="0"/></Offset></Anchor></Anchors>
</FontString>
</Layer>
</Layers>
<Scripts>
<OnLoad>
self.name = getglobal(self:GetName() .. "Name")
self.subtext = getglobal(self:GetName() .. "SubText")
</OnLoad>
<OnClick>BenillaCraftSkillButton_OnClick(self, arg1)</OnClick>
</Scripts>
</Button>
<!--
One reagent slot (ref CraftItemTemplate inherits QuestItemTemplate, l.29-53 — TradeSkillFrame's
own BenillaTradeSkillReagentTemplate shape, byte-identical layout): a 28×28 icon, the reagent
name to its right, "have /need" below the name.
-->
<Button name="BenillaCraftReagentTemplate" virtual="true" hidden="true">
<Size><AbsDimension x="140" y="32"/></Size>
<Layers>
<Layer level="BACKGROUND">
<Texture name="$parentIcon">
<Size><AbsDimension x="28" y="28"/></Size>
<Anchors><Anchor point="TOPLEFT" relativePoint="TOPLEFT"><Offset><AbsDimension x="0" y="-2"/></Offset></Anchor></Anchors>
</Texture>
</Layer>
<Layer level="ARTWORK">
<FontString name="$parentName" inherits="GameFontHighlightSmall" justifyH="LEFT" justifyV="TOP">
<Size><AbsDimension x="104" y="14"/></Size>
<Anchors><Anchor point="TOPLEFT" relativeTo="$parentIcon" relativePoint="TOPRIGHT"><Offset><AbsDimension x="4" y="0"/></Offset></Anchor></Anchors>
</FontString>
<FontString name="$parentCount" inherits="GameFontNormalSmall" justifyH="LEFT">
<Anchors><Anchor point="TOPLEFT" relativeTo="$parentName" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="0" y="-1"/></Offset></Anchor></Anchors>
</FontString>
</Layer>
</Layers>
<Scripts>
<OnEnter>BenillaCraftReagent_OnEnter(self)</OnEnter>
<OnLeave>GameTooltip:Hide()</OnLeave>
</Scripts>
</Button>
<!-- A panel action button (Create/Exit): the UIPanelButtonTemplate face locally reconstructed
(this house dialect ships no such template — TalentFrame.xml's own ENGINE API note),
TradeSkillFrame.xml's identical-shaped BenillaTradeSkillPanelButtonTemplate — its OWN local
copy per this house's per-window convention (StackSplit.xml's own "not a cross-file inherits"
note), not a cross-file reuse of that other window's virtual template. -->
<Button name="BenillaCraftPanelButtonTemplate" virtual="true">
<Size><AbsDimension x="80" y="22"/></Size>
<NormalFont inherits="GameFontNormal"/>
<HighlightFont inherits="GameFontHighlight"/>
<DisabledFont inherits="GameFontDisable"/>
<NormalTexture file="Interface\Buttons\UI-Panel-Button-Up"><TexCoords left="0" right="0.625" top="0" bottom="0.6875"/></NormalTexture>
<PushedTexture file="Interface\Buttons\UI-Panel-Button-Down"><TexCoords left="0" right="0.625" top="0" bottom="0.6875"/></PushedTexture>
<DisabledTexture file="Interface\Buttons\UI-Panel-Button-Disabled"><TexCoords left="0" right="0.625" top="0" bottom="0.6875"/></DisabledTexture>
<HighlightTexture file="Interface\Buttons\UI-Panel-Button-Highlight" alphaMode="ADD"><TexCoords left="0" right="0.625" top="0" bottom="0.6875"/></HighlightTexture>
</Button>
<Frame name="BenillaCraftFrame" hidden="true">
<!-- The real 384×512 window. No <Anchors> — ShowUIPanel/SetLeftFrame SetPoints it to the left
slot (MerchantFrame.xml/TradeSkillFrame.xml's own convention). -->
<Size><AbsDimension x="384" y="512"/></Size>
<!-- Transcribed for fidelity (ref l.65-67); no loader wiring exists yet for the hit-rect
shrink — TalentFrame.xml's own "transcribe the hook even if nothing drives it yet"
posture. -->
<HitRectInsets><AbsInset left="0" right="34" top="0" bottom="75"/></HitRectInsets>
<Layers>
<!-- "player" portrait 60×60 TOPLEFT (7,-6) (ref l.70-81), BACKGROUND so the window art's
ring frames it. Bound via SetPortraitTexture(region, "player") in Show/OnEvent — not an
NPC session (header comment). -->
<Layer level="BACKGROUND">
<Texture name="BenillaCraftFramePortrait">
<Size><AbsDimension x="60" y="60"/></Size>
<Anchors><Anchor point="TOPLEFT" relativePoint="TOPLEFT"><Offset><AbsDimension x="7" y="-6"/></Offset></Anchor></Anchors>
</Texture>
</Layer>
<!-- The four quadrant slabs that ARE the window art (ref l.83-125): ALL FOUR from
ClassTrainerFrame's shared art — unlike TradeSkill's own mixed set (its BotLeft comes
from TradeSkillFrame's own art instead), Craft's ref uses one uniform art family
throughout, transcribed exactly as given. -->
<Layer level="BORDER">
<Texture file="Interface\ClassTrainerFrame\UI-ClassTrainer-TopLeft"><Size><AbsDimension x="256" y="256"/></Size><Anchors><Anchor point="TOPLEFT" relativePoint="TOPLEFT"/></Anchors></Texture>
<Texture file="Interface\ClassTrainerFrame\UI-ClassTrainer-TopRight"><Size><AbsDimension x="128" y="256"/></Size><Anchors><Anchor point="TOPRIGHT" relativePoint="TOPRIGHT"/></Anchors></Texture>
<Texture file="Interface\ClassTrainerFrame\UI-ClassTrainer-BotLeft"><Size><AbsDimension x="256" y="256"/></Size><Anchors><Anchor point="BOTTOMLEFT" relativePoint="BOTTOMLEFT"/></Anchors></Texture>
<Texture file="Interface\ClassTrainerFrame\UI-ClassTrainer-BotRight"><Size><AbsDimension x="128" y="256"/></Size><Anchors><Anchor point="BOTTOMRIGHT" relativePoint="BOTTOMRIGHT"/></Anchors></Texture>
<!-- Title: GetCraftName() directly — unlike TradeSkill's "%s"-formatted TRADE_SKILL_TITLE,
Craft's ref sets this raw (ref l.101: CraftFrameTitleText:SetText(GetCraftName())).
"Enchanting" is a harmless static placeholder until the first paint. -->
<FontString name="BenillaCraftFrameTitleText" inherits="GameFontNormal" justifyH="CENTER" text="Enchanting">
<Anchors><Anchor point="TOP" relativePoint="TOP"><Offset><AbsDimension x="0" y="-17"/></Offset></Anchor></Anchors>
</FontString>
</Layer>
<!-- The skill-border + horizontal-divider decorative bars (ref l.126-174), sampled from the
same two shared ClassTrainer/TradeSkill textures by TexCoord — byte-identical to
TradeSkillFrame.xml's own equivalents. -->
<Layer level="ARTWORK">
<Texture name="BenillaCraftSkillBorderLeft" file="Interface\TradeSkillFrame\UI-TradeSkill-SkillBorder">
<Size><AbsDimension x="256" y="8"/></Size>
<Anchors><Anchor point="TOPLEFT" relativePoint="TOPLEFT"><Offset><AbsDimension x="63" y="-50"/></Offset></Anchor></Anchors>
<TexCoords left="0" right="1.0" top="0" bottom="0.25"/>
</Texture>
<Texture name="BenillaCraftSkillBorderRight" file="Interface\TradeSkillFrame\UI-TradeSkill-SkillBorder">
<Size><AbsDimension x="28" y="8"/></Size>
<Anchors><Anchor point="LEFT" relativeTo="BenillaCraftSkillBorderLeft" relativePoint="RIGHT"/></Anchors>
<TexCoords left="0" right="0.109375" top="0.25" bottom="0.5"/>
</Texture>
<Texture name="BenillaCraftHorizontalBarLeft" file="Interface\ClassTrainerFrame\UI-ClassTrainer-HorizontalBar">
<Size><AbsDimension x="256" y="16"/></Size>
<Anchors><Anchor point="TOPLEFT" relativePoint="TOPLEFT"><Offset><AbsDimension x="15" y="-221"/></Offset></Anchor></Anchors>
<TexCoords left="0" right="1.0" top="0" bottom="0.25"/>
</Texture>
<Texture file="Interface\ClassTrainerFrame\UI-ClassTrainer-HorizontalBar">
<Size><AbsDimension x="75" y="16"/></Size>
<Anchors><Anchor point="LEFT" relativeTo="BenillaCraftHorizontalBarLeft" relativePoint="RIGHT"/></Anchors>
<TexCoords left="0" right="0.29296875" top="0.25" bottom="0.5"/>
</Texture>
</Layer>
<!-- The FIXED detail-pane FontStrings + header art (ref CraftDetailScrollChildFrame,
l.456-537), flattened onto this window's OWN Layers at OVERLAY (the flat-layout gap,
this file's header comment) — computed from the ref's ScrollFrame anchor (TOPLEFT
20,-234, byte-identical to TradeSkillFrame's own) + each region's own ScrollChild-
relative offset. Declaration order matters (same parent+level ⇒ z-tiebreak,
TradeSkillFrame.xml's own note): the header art is declared BEFORE ReagentLabel so the
"Reagents: " banner text paints over its lower edge, exactly as the reference does.
CraftCost (ref l.483-491, the per-recipe training-point cost line) is DROPPED — deviation 6,
always blank in v1. -->
<Layer level="OVERLAY">
<FontString name="BenillaCraftName" inherits="GameFontNormal" justifyH="LEFT">
<Size><AbsDimension x="244" y="0"/></Size>
<Anchors><Anchor point="TOPLEFT" relativePoint="TOPLEFT"><Offset><AbsDimension x="70" y="-239"/></Offset></Anchor></Anchors>
</FontString>
<!-- ONE concatenated FontString (Craft's own ref, unlike TradeSkill's split
Label+Text pair — header comment). -->
<FontString name="BenillaCraftRequirements" inherits="GameFontHighlightSmall" justifyH="LEFT">
<Anchors><Anchor point="TOPLEFT" relativeTo="BenillaCraftName" relativePoint="BOTTOMLEFT"/></Anchors>
</FontString>
<Texture name="BenillaCraftDetailHeaderLeft" file="Interface\ClassTrainerFrame\UI-ClassTrainer-DetailHeaderLeft">
<Size><AbsDimension x="256" y="64"/></Size>
<Anchors><Anchor point="TOPLEFT" relativePoint="TOPLEFT"><Offset><AbsDimension x="20" y="-231"/></Offset></Anchor></Anchors>
</Texture>
<Texture file="Interface\ClassTrainerFrame\UI-ClassTrainer-DetailHeaderRight">
<Size><AbsDimension x="64" y="64"/></Size>
<Anchors><Anchor point="TOPLEFT" relativeTo="BenillaCraftDetailHeaderLeft" relativePoint="TOPRIGHT"/></Anchors>
</Texture>
<!-- The recipe's own description (ref l.516-527) — TradeSkill's detail pane has no
analogue for this region (header comment). Its BOTTOMLEFT is the ReagentLabel's
DYNAMIC anchor target, re-pointed every SetSelection (SetSelection's own Lua). -->
<FontString name="BenillaCraftDescription" inherits="GameFontHighlightSmall" justifyH="LEFT">
<Size><AbsDimension x="290" y="0"/></Size>
<Anchors><Anchor point="TOPLEFT" relativePoint="TOPLEFT"><Offset><AbsDimension x="25" y="-284"/></Offset></Anchor></Anchors>
</FontString>
<!-- STATIC always-shown label (TradeSkillFrame.xml's own precedent: the ref's
Show/Hide-on-reagent-count dance is dropped, every recipe here has ≥1 reagent in
practice) — its POSITION is dynamic (SetSelection), its text/visibility is not. -->
<FontString name="BenillaCraftReagentLabel" inherits="GameFontNormalSmall" text="Reagents: ">
<Anchors><Anchor point="TOPLEFT" relativeTo="BenillaCraftDescription" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="0" y="-10"/></Offset></Anchor></Anchors>
</FontString>
</Layer>
</Layers>
<Frames>
<!-- The scroll-wheel catcher (TradeSkillFrame.xml's own note) — MUST be first so every real
row below out-ranks it in hit-testing. -->
<Button name="BenillaCraftWheelCatcher">
<Anchors>
<Anchor point="TOPLEFT" relativePoint="TOPLEFT"/>
<Anchor point="BOTTOMRIGHT" relativePoint="BOTTOMRIGHT"/>
</Anchors>
<Scripts><OnMouseWheel>BenillaCraftFrame_OnMouseWheel(arg1)</OnMouseWheel></Scripts>
</Button>
<!-- The top-right close X (ref a UIPanelCloseButton at TOPRIGHT -29,-8, l.688-696; the
minimize-button art stands in — this house dialect ships no such template,
TradeSkillFrame.xml's own precedent). -->
<Button name="BenillaCraftFrameCloseButton">
<Size><AbsDimension x="32" y="32"/></Size>
<Anchors><Anchor point="TOPRIGHT" relativePoint="TOPRIGHT"><Offset><AbsDimension x="-29" y="-8"/></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>BenillaCraftFrameCloseButton_OnClick()</OnClick></Scripts>
</Button>
<!-- The skill-rank StatusBar (ref CraftRankFrame, l.196-264): blue fill, "name rank/max"
text, the UI-Character-Skills-Bar[Border] art — byte-identical to TradeSkillFrame's own.
SKILL_LINES_CHANGED self-listen dropped (deviation 4). -->
<StatusBar name="BenillaCraftRankFrame" drawLayer="BACKGROUND" minValue="0" maxValue="1" defaultValue="0">
<Size><AbsDimension x="268" y="15"/></Size>
<Anchors><Anchor point="TOPLEFT" relativePoint="TOPLEFT"><Offset><AbsDimension x="73" y="-37"/></Offset></Anchor></Anchors>
<Layers>
<Layer level="ARTWORK">
<FontString name="BenillaCraftRankFrameSkillName" inherits="GameFontNormalSmall">
<Anchors><Anchor point="LEFT" relativePoint="LEFT"><Offset><AbsDimension x="6" y="1"/></Offset></Anchor></Anchors>
</FontString>
<FontString name="BenillaCraftRankFrameSkillRank" inherits="GameFontHighlightSmall" justifyH="LEFT">
<Size><AbsDimension x="128" y="0"/></Size>
<Anchors><Anchor point="LEFT" relativeTo="BenillaCraftRankFrameSkillName" relativePoint="RIGHT"><Offset><AbsDimension x="13" y="0"/></Offset></Anchor></Anchors>
</FontString>
</Layer>
<Layer level="BACKGROUND">
<Texture name="BenillaCraftRankFrameBackground"><Color r="1.0" g="1.0" b="1.0" a="0.2"/></Texture>
</Layer>
</Layers>
<Frames>
<Button name="BenillaCraftRankFrameBorder">
<Size><AbsDimension x="281" y="32"/></Size>
<Anchors><Anchor point="LEFT" relativePoint="LEFT"><Offset><AbsDimension x="-5" y="0"/></Offset></Anchor></Anchors>
<NormalTexture file="Interface\PaperDollInfoFrame\UI-Character-Skills-BarBorder"/>
</Button>
</Frames>
<BarTexture name="BenillaCraftRankFrameBar" file="Interface\PaperDollInfoFrame\UI-Character-Skills-Bar"/>
<BarColor r="0.25" g="0.25" b="0.75"/>
</StatusBar>
<!-- The expand/collapse-all tab (ref CraftExpandButtonFrame, l.265-325): verbatim
geometry/art — TradeSkillFrame.xml's own identical tab frame, EXCEPT the button itself
is 22×22 here (Craft's own ref value, not TradeSkill's 40×22 — each window's own ref,
not copy-pasted). TrainerFrame.xml's own BenillaTrainerCollapseAllButton is the
reconstruction precedent this mirrors (standalone Button, not this file's row template —
header comment, deviation 1). Always ends up Hidden in v1 (Update()'s own tail, header
comment) — ships for fidelity, not yet reachable. -->
<Frame name="BenillaCraftExpandButtonFrame">
<Size><AbsDimension x="54" y="32"/></Size>
<Anchors><Anchor point="TOPLEFT" relativePoint="TOPLEFT"><Offset><AbsDimension x="15" y="-71"/></Offset></Anchor></Anchors>
<Layers><Layer level="BACKGROUND">
<Texture name="BenillaCraftExpandTabLeft" file="Interface\ClassTrainerFrame\UI-ClassTrainer-ExpandTab-Left"><Size><AbsDimension x="8" y="32"/></Size><Anchors><Anchor point="TOPLEFT" relativePoint="TOPLEFT"/></Anchors></Texture>
<Texture name="BenillaCraftExpandTabMiddle" file="Interface\QuestFrame\UI-QuestLogSortTab-Middle"><Size><AbsDimension x="38" y="32"/></Size><Anchors><Anchor point="LEFT" relativeTo="BenillaCraftExpandTabLeft" relativePoint="RIGHT"><Offset><AbsDimension x="0" y="6"/></Offset></Anchor></Anchors></Texture>
<Texture file="Interface\QuestFrame\UI-QuestLogSortTab-Right"><Size><AbsDimension x="8" y="32"/></Size><Anchors><Anchor point="LEFT" relativeTo="BenillaCraftExpandTabMiddle" relativePoint="RIGHT"/></Anchors></Texture>
</Layer></Layers>
<Frames>
<Button name="BenillaCraftCollapseAllButton" text="All">
<Size><AbsDimension x="22" y="22"/></Size>
<Anchors><Anchor point="LEFT" relativeTo="BenillaCraftExpandTabLeft" relativePoint="RIGHT"><Offset><AbsDimension x="0" y="3"/></Offset></Anchor></Anchors>
<NormalTexture file="Interface\Buttons\UI-MinusButton-Up"><Size><AbsDimension x="16" y="16"/></Size><Anchors><Anchor point="LEFT" relativePoint="LEFT"/></Anchors></NormalTexture>
<HighlightTexture file="Interface\Buttons\UI-PlusButton-Hilight" alphaMode="ADD"><Size><AbsDimension x="16" y="16"/></Size><Anchors><Anchor point="LEFT" relativePoint="LEFT"/></Anchors></HighlightTexture>
<ButtonText><Anchors><Anchor point="LEFT" relativePoint="LEFT"><Offset><AbsDimension x="18" y="0"/></Offset></Anchor></Anchors></ButtonText>
<NormalFont inherits="GameFontNormalSmall" justifyH="LEFT"/>
<Scripts>
<OnClick>BenillaCraftCollapseAllButton_OnClick(self)</OnClick>
</Scripts>
</Button>
</Frames>
</Frame>
<!-- The persistent selection highlight (ref CraftHighlightFrame, l.331-343): 293×16,
ARTWORK-layer UI-Listbox-Highlight2. Declared BEFORE the rows so the row text paints
OVER the glow (TradeSkillFrame.xml's own z-tiebreak note). Vertex-colored by the
selected recipe's CraftTypeColor (SetSelection), positioned onto the selected visible
row (Update). -->
<Frame name="BenillaCraftHighlightFrame" hidden="true">
<Size><AbsDimension x="293" y="16"/></Size>
<Anchors><Anchor point="TOPLEFT" relativePoint="TOPLEFT"/></Anchors>
<Layers><Layer level="ARTWORK">
<Texture name="BenillaCraftHighlight" file="Interface\Buttons\UI-Listbox-Highlight2">
<Anchors><Anchor point="TOPLEFT"/><Anchor point="BOTTOMRIGHT"/></Anchors>
</Texture>
</Layer></Layers>
</Frame>
<!-- 8 list rows (ref Craft1..8, l.344-415): 293×16, row1 TOPLEFT (22,-96); rows 2-8 chain
TOPLEFT→prior BOTTOMLEFT with no gap — byte-identical to TradeSkillFrame's own. -->
<Button name="BenillaCraft1" inherits="BenillaCraftSkillButtonTemplate">
<Anchors><Anchor point="TOPLEFT" relativePoint="TOPLEFT"><Offset><AbsDimension x="22" y="-96"/></Offset></Anchor></Anchors>
</Button>
<Button name="BenillaCraft2" inherits="BenillaCraftSkillButtonTemplate"><Anchors><Anchor point="TOPLEFT" relativeTo="BenillaCraft1" relativePoint="BOTTOMLEFT"/></Anchors></Button>
<Button name="BenillaCraft3" inherits="BenillaCraftSkillButtonTemplate"><Anchors><Anchor point="TOPLEFT" relativeTo="BenillaCraft2" relativePoint="BOTTOMLEFT"/></Anchors></Button>
<Button name="BenillaCraft4" inherits="BenillaCraftSkillButtonTemplate"><Anchors><Anchor point="TOPLEFT" relativeTo="BenillaCraft3" relativePoint="BOTTOMLEFT"/></Anchors></Button>
<Button name="BenillaCraft5" inherits="BenillaCraftSkillButtonTemplate"><Anchors><Anchor point="TOPLEFT" relativeTo="BenillaCraft4" relativePoint="BOTTOMLEFT"/></Anchors></Button>
<Button name="BenillaCraft6" inherits="BenillaCraftSkillButtonTemplate"><Anchors><Anchor point="TOPLEFT" relativeTo="BenillaCraft5" relativePoint="BOTTOMLEFT"/></Anchors></Button>
<Button name="BenillaCraft7" inherits="BenillaCraftSkillButtonTemplate"><Anchors><Anchor point="TOPLEFT" relativeTo="BenillaCraft6" relativePoint="BOTTOMLEFT"/></Anchors></Button>
<Button name="BenillaCraft8" inherits="BenillaCraftSkillButtonTemplate"><Anchors><Anchor point="TOPLEFT" relativeTo="BenillaCraft7" relativePoint="BOTTOMLEFT"/></Anchors></Button>
<!-- The faux-scroll frame (BenillaFauxScrollFrameTemplate, ScrollTemplates.xml): invisible
container over the 8 rows; only its right-hand Slider scroll bar renders. Sized to the
8-row span (CRAFTS_DISPLAYED × CRAFT_SKILL_HEIGHT), TradeSkillFrame.xml's own
same-TOPLEFT-as-row1 convention. updateFunc set in BenillaCraftFrame_OnLoad. -->
<Frame name="BenillaCraftListScrollFrame" inherits="BenillaFauxScrollFrameTemplate">
<Size><AbsDimension x="293" y="128"/></Size>
<Anchors><Anchor point="TOPLEFT" relativePoint="TOPLEFT"><Offset><AbsDimension x="22" y="-96"/></Offset></Anchor></Anchors>
</Frame>
<!-- The detail icon (ref CraftIcon, l.540-577): a 37×37 button over a 64×64 UI-EmptySlot
ring (TrainerFrame.xml's own BenillaTrainerSkillIcon shape, TradeSkillFrame.xml's own
identical reuse) at the flattened header art's top-left. No made-count overlay — Craft
has no min/max-made concept (craft.rs's own module doc), unlike TradeSkill's own icon. -->
<Button name="BenillaCraftIcon" hidden="true">
<Size><AbsDimension x="37" y="37"/></Size>
<Anchors><Anchor point="TOPLEFT" relativePoint="TOPLEFT"><Offset><AbsDimension x="28" y="-237"/></Offset></Anchor></Anchors>
<Layers>
<Layer level="BACKGROUND">
<Texture file="Interface\Buttons\UI-EmptySlot"><Size><AbsDimension x="64" y="64"/></Size><Anchors><Anchor point="TOPLEFT" relativePoint="TOPLEFT"><Offset><AbsDimension x="-13" y="13"/></Offset></Anchor></Anchors></Texture>
</Layer>
</Layers>
<Scripts>
<OnEnter>BenillaCraftIcon_OnEnter(self)</OnEnter>
<OnLeave>GameTooltip:Hide()</OnLeave>
</Scripts>
</Button>
<!-- Up to 8 reagents, the ref's own 2-column grid + anchor chain (l.578-649): odd slots
chain down the left column (TOPLEFT→prior BOTTOMLEFT, -2 gap), even slots sit LEFT of
the odd slot in the same row. Reagent7 chains off Reagent6 (not Reagent5) — a literal
ref quirk shared with TradeSkillFrame's own reagent grid (this file's header comment,
deviation 9's sibling note): harmless, since 6's row-mate LEFT anchor to 5 already puts
it at 5's own row height. Reagent1's own offset is (0,-3) here — Craft's ref literal
value, NOT TradeSkill's own (-5,-3) (transcribed from each window's own ref, not
copy-pasted). -->
<Button name="BenillaCraftReagent1" inherits="BenillaCraftReagentTemplate">
<Anchors><Anchor point="TOPLEFT" relativeTo="BenillaCraftReagentLabel" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="0" y="-3"/></Offset></Anchor></Anchors>
<Scripts><OnLoad>BenillaCraftReagent_OnLoad(self, 1)</OnLoad></Scripts>
</Button>
<Button name="BenillaCraftReagent2" inherits="BenillaCraftReagentTemplate">
<Anchors><Anchor point="LEFT" relativeTo="BenillaCraftReagent1" relativePoint="RIGHT"/></Anchors>
<Scripts><OnLoad>BenillaCraftReagent_OnLoad(self, 2)</OnLoad></Scripts>
</Button>
<Button name="BenillaCraftReagent3" inherits="BenillaCraftReagentTemplate">
<Anchors><Anchor point="TOPLEFT" relativeTo="BenillaCraftReagent1" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="0" y="-2"/></Offset></Anchor></Anchors>
<Scripts><OnLoad>BenillaCraftReagent_OnLoad(self, 3)</OnLoad></Scripts>
</Button>
<Button name="BenillaCraftReagent4" inherits="BenillaCraftReagentTemplate">
<Anchors><Anchor point="LEFT" relativeTo="BenillaCraftReagent3" relativePoint="RIGHT"/></Anchors>
<Scripts><OnLoad>BenillaCraftReagent_OnLoad(self, 4)</OnLoad></Scripts>
</Button>
<Button name="BenillaCraftReagent5" inherits="BenillaCraftReagentTemplate">
<Anchors><Anchor point="TOPLEFT" relativeTo="BenillaCraftReagent3" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="0" y="-2"/></Offset></Anchor></Anchors>
<Scripts><OnLoad>BenillaCraftReagent_OnLoad(self, 5)</OnLoad></Scripts>
</Button>
<Button name="BenillaCraftReagent6" inherits="BenillaCraftReagentTemplate">
<Anchors><Anchor point="LEFT" relativeTo="BenillaCraftReagent5" relativePoint="RIGHT"/></Anchors>
<Scripts><OnLoad>BenillaCraftReagent_OnLoad(self, 6)</OnLoad></Scripts>
</Button>
<Button name="BenillaCraftReagent7" inherits="BenillaCraftReagentTemplate">
<Anchors><Anchor point="TOPLEFT" relativeTo="BenillaCraftReagent6" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="0" y="-2"/></Offset></Anchor></Anchors>
<Scripts><OnLoad>BenillaCraftReagent_OnLoad(self, 7)</OnLoad></Scripts>
</Button>
<Button name="BenillaCraftReagent8" inherits="BenillaCraftReagentTemplate">
<Anchors><Anchor point="LEFT" relativeTo="BenillaCraftReagent7" relativePoint="RIGHT"/></Anchors>
<Scripts><OnLoad>BenillaCraftReagent_OnLoad(self, 8)</OnLoad></Scripts>
</Button>
<!-- The action row (ref l.654-696): Create / Exit only — NO Create All, NO -/+, NO
repeat-count EditBox (deviation 8). Positions byte-identical to TradeSkillFrame's own
Create/Cancel buttons (ref l.658-663/676-681 vs Craft's own l.658-663/675-680 — the
SAME two constants in both refs). -->
<Button name="BenillaCraftCreateButton" inherits="BenillaCraftPanelButtonTemplate" text="Create">
<Anchors><Anchor point="CENTER" relativeTo="BenillaCraftFrame" relativePoint="TOPLEFT"><Offset><AbsDimension x="224" y="-422"/></Offset></Anchor></Anchors>
<Scripts><OnClick>BenillaCraftCreateButton_OnClick()</OnClick></Scripts>
</Button>
<Button name="BenillaCraftCancelButton" inherits="BenillaCraftPanelButtonTemplate" text="Exit">
<Anchors><Anchor point="CENTER" relativeTo="BenillaCraftFrame" relativePoint="TOPLEFT"><Offset><AbsDimension x="305" y="-422"/></Offset></Anchor></Anchors>
<Scripts><OnClick>BenillaCraftCancelButton_OnClick()</OnClick></Scripts>
</Button>
</Frames>
<!-- The window lifecycle (MerchantFrame.xml/TradeSkillFrame.xml's own shape): OnHide calls
CloseCraft() for ANY hide reason. The list wheel lives on the WINDOW (the engine bubbles a
spin up the parent chain). -->
<Scripts>
<OnLoad>BenillaCraftFrame_OnLoad(self)</OnLoad>
<OnEvent>BenillaCraftFrame_OnEvent(self)</OnEvent>
<OnMouseWheel>BenillaCraftFrame_OnMouseWheel(arg1)</OnMouseWheel>
<OnShow>PlaySound("igCharacterInfoOpen")</OnShow>
<OnHide>CloseCraft(); PlaySound("igCharacterInfoClose")</OnHide>
</Scripts>
</Frame>
</Ui>