Files
benilla-octo/crates/benilla-app/assets/ui/SkillFrame.xml
T
SamandClaude Fable 5.1 0535fe9097 a body lands with a thud, and twelve lists scroll again
- a dying body lands with a sound: the `$DTH` marker makes three calls in the reference (`0x624a94`) and benilla had built only the middle one, so the screen shook and nothing landed; the thud is a two-axis join — size class against terrain sound, through `DeathThudLookups.dbc` to a land or water kit — behind exactly three gates: more than 2.0 yd of liquid over the feet is complete silence, a size class outside 0..=4, and no terrain, no row or a kit of 0; it plays on bus 0, uncapped, so a battlefield's worth of bodies all land — the shake is the rare thing (49 of 430 models name a preset), the thud the universal one (329 key a `$DTH`, players included); the size axis had sat in our `CreatureDisplayInfo` schema labelled a string column 5875 does not have, and `StringLookups` stays unparsed on purpose — 9 rows benilla already resolves (1866)
- twelve faux-scroll lists showed their bar, moved their thumb and never moved their list: the reference's kit writes `frame.offset` from one handler the OWNER declares, and ours were wired to a `frame.updateFunc` field the reference does not have — Skill, Craft, TradeSkill, ScriptLog, the auction and the social panes are rewired, and a gate fails any faux frame without a handler of its own, because a list only scrolls when something drives it (1868); and the macro window's "Change Name/Icon" raised on a nil offset: `MacroPopupScrollFrame` inherits a template the manifest never loaded, and a missing template is a warning, so the frame was built BARE — no scroll bar, no scripts, no `offset = 0` — and the reference's offset read has no `or 0` cushion; the line is in the manifest, and a static gate fails any `inherits=` the manifest declares nowhere (1862)
- the character geoset selector's missing eighth branch is the tabard designer: B6 of `0x477520` is gated on "the guild registrar's tabard designer is open", not an ItemDisplayInfo column, and wears the tabard flap with an EMPTY tabard slot; dormant until `TabardFrame` migrates, but reading the whole function turned up three branches wrong since their first pass: B4-short never disabled [501,599], so the bare foot — a real submesh on 11 of the 18 character models — rendered inside every boot; B3 asked "is a chest equipped?" where the reference asks whether anything but the shirt paints the ArmLower tile, wrong more often than right (533 of 637 chest displays leave it empty, 566 of 570 wrist displays fill it); and B7 had no tabard guard, an ungated doublet arm, and a leg arm read off chest-or-legs where the reference reads the chest's bit alone (1864)
- a GameObject's display sounds have one trigger and two lanes: `GameObjectDisplayInfo.Sound[0..9]` has one reader (`0x5f4010`), reached only from the anim-event dispatcher, so benilla's state-transition arm — 19 Open and 4 Close rows the reference leaves silent, 80 Open and 38 Close doubled against the clip's own `$GO1`/`$GO3` — is deleted; and the kit's `0x200` flag is a LANE SELECT, not a loop flag: set means the ambient pool, which every campfire, brazier, torch and fountain names (37 live slots), and which had been starting untracked forever-channels (1867); and `WOW_PROBE_SERVICE=1` reads the NPC-service ladder back off a live server — four real NPCs, reporting the arm taken, the opcode sent and the window opened — with every hop inside the reference's own service reach, after a first run parked ~29 yd from Deputy Willem and blamed the client for a dropped packet (1865)

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-04 20:48:52 +07:00

875 lines
63 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 Skills tab (decision 0437 phase 4): the 1.12 `SkillFrame`, re-expressed from the
reference FrameXML `SkillFrame.{xml,lua}` (session scratchpad `ref-ui/`, decision 0246
discipline — never committed) onto our own engine/feeds. Geometry/art-paths below are QUOTED
from the ref (line citations inline, into that same `SkillFrame.xml`/`.lua`); the Lua is
the ref's logic turned onto benilla's Era API (`GetNumSkillLines`/`GetSkillLineInfo`/
`Expand`/`CollapseSkillHeader`/`SetSelectedSkill`/`GetSelectedSkill`, decision 0437's skills
seam, `crates/benilla-ui/src/script/skills.rs`, built alongside this file).
A CHILD PAGE of `CharacterFrame` (ref: `parent="CharacterFrame"`, `id="4"` — ours too, since
the Reputation page landed and took slot 3 back), shown/
hidden by `CharacterFrame_ShowSubFrame` exactly like `PaperDollFrame` — but NOT a
physically nested `<Frame>` the way PaperDollFrame is: PaperDollFrame is declared INSIDE
CharacterFrame.xml's own `<Frames>` tree (a real engine parent/child), while this page is a
SEPARATE file's top-level `<Frame>` (CharacterFrame.xml is shared-tree with another session
this pass, so only its tab machinery is touched here — not its frame tree). `parent=` really attaches
(decision 1734; the loader resolves the name globally at load and it wins over the enclosing
element — `shipped_xml_tests::every_declared_parent_really_attaches` asserts it on the loaded
tree). The line above used to say the attribute was read nowhere, which was true once and had
outlived itself. Position still rides `<Anchors relativeTo="CharacterFrame">` — a NAMED
lookup that works regardless of tree position, the same mechanism every sibling-anchor in
this codebase uses, kept because it is the geometry this page ships with — loaded right after
`CharacterFrame.xml` in the load list so it
paints ON TOP of it (later file = higher z, the established house convention every top-level
window already relies on implicitly). Because a true engine Hide-cascade doesn't reach a
non-child, `CharacterFrame.xml`'s own `CharacterFrame_OnHide` was extended (its own
header comment) to loop-hide every `CHARACTERFRAME_SUBFRAMES` entry — the fix this
page's non-nested shape requires, done once so later non-nested tabs (Pet/Reputation/Honor)
inherit it for free.
NO OWN PORTRAIT (matches the ref: `ref-ui/SkillFrame.xml` l.130-213 draws no portrait Texture
at all): the shared quadrant background art below (`UI-Character-General-TopLeft/TopRight`,
the SAME "General" variant the ref uses, l.136-159) carries the SAME baked-in portrait-ring
cutout every PaperDollInfoFrame quadrant variant shares, so `CharacterFrame`'s own
portrait/name/level (drawn at a lower z, underneath) shows through unchanged — exactly the
ref's own real-client compositing (SkillFrame is a nested child there too, relying on the
same cutout rather than redrawing the portrait itself).
THE DETAIL PANE NESTS, as the ref's does. This file used to carry the FLAT-layout claim —
"only a plain child `<Frame>` loses [its Layers]" — quoted from TrainerFrame.xml. That claim
is FALSE and was retracted in decision 1517, which corrected TrainerFrame/TradeSkillFrame/
CraftFrame and missed this file: a fourth restatement, still asserting the constraint five
weeks after it was disproved. A runtime-shown child `<Frame>` renders its own `<Layers>`
FontStrings in this engine, and the ref's `SkillDetailScrollFrame` +
`SkillDetailScrollChildFrame` are therefore built here verbatim (below).
1517 §2 says a retracted reason is not by itself cause to move approved geometry — so the
mover here is not the retraction, it is DEMAND: pfUI's `skins/blizzard/character.lua` l.299-320
indexes `SkillDetailScrollFrameScrollBar`, `SkillDetailScrollChildFrame`, `SkillDetailCostText`
and `SkillDetailStatusBarUnlearnButton` by name, and its own skin then hides the scroll child
and reparents the three detail regions onto the scroll frame — i.e. it flattens the pane
ITSELF, which it can only do if the pane is nested to begin with. The pane also could not
keep its old size inside the ref's 296-wide viewport: the old detail bar reused the 271-wide
LIST-ROW template, whose 281-wide Border put the abandon button at x 312..344 against a
viewport ending at 318 — 26 of its 32 px clipped. So the detail bar is now the ref's OWN
declaration (211×15, Border 220×32, centred under the list) rather than a borrowed list row,
which is both what fits and what the real client draws. LOOK CHANGE, called out for the
director: the detail bar narrows and centres, and the ref's two `UI-ClassTrainer-ScrollBar`
trough textures now flank the pane's right edge (the ref draws them unconditionally, bar or
no bar). Reverting is one anchor + one Size.
v1 DEVIATIONS (each re-cited at its own site below):
1. THE DETAIL PANE OMITS THE TRAINING-POINT SPEND ROW, and nothing else. Its structure
(`SkillDetailScrollFrame` → `SkillDetailScrollChildFrame` → `SkillDetailCostText` +
`SkillDetailDescriptionText` + `SkillDetailStatusBar` → `$parentUnlearnButton`) is the
ref's, verbatim. What stays OUT is the ref's `$parentLeftArrow`/`$parentRightArrow`/
`$parentLearnSkillButton` trio and the `AddSkillUp`/`RemoveSkillUp`/`BuySkillTier`
calls behind them — driven by `stepCost`/`rankCost`/`numTempPoints`, which
`GetSkillLineInfo` always reports nil/0 (0437's own named out-of-scope: 1.12 training
points are dead data).
`SkillDetailCostText` IS built, because the ref's own no-cost path is a real path, not
a stub: ref `SkillFrame.lua` l.361 `SkillDetailCostText:Hide()` is the branch a live
1.12 server always lands on, and `BenillaSkillFrame_UpdateDetail` takes exactly that
branch every time. An empty hidden FontString here is the reference's own behaviour,
not a name published to satisfy a lookup (1189/1203).
The ABANDON BUTTON is real: `SkillDetailStatusBarUnlearnButton` (the ref's
`$parentUnlearnButton`) → the `UNLEARN_SKILL` confirm → `AbandonSkill` →
`CMSG_UNLEARN_SKILL`; the isAbandonable predicate is `SkillRaceClassInfo.flags & 0x20`
— the server's own gate, real 5875 data: primary professions only. It used to be
published as `SkillDetailUnlearnButton`, a name the reference does not have, while the
comment directly above it cited `$parentUnlearnButton` correctly — the fifth instance
this session of right-behaviour-under-a-private-name (1189).
2. NO SORT CHECKBOX (ref l.215-241's own XML-commented-out "Replace once code is ready..."
`SkillSortButton`): the ref itself ships this dead/commented, so there is nothing to
transcribe (TradeSkillFrame.xml's own "ref's own dead code is dropped" precedent).
3. NO ACCEPT BUTTON (ref l.318-337's `SkillFrameAcceptButton`) — it is XML-commented dead
in the ref, and it backs the training-point economy this pane doesn't ship (deviation 1),
so there is nothing to transcribe. Its neighbour `SkillFrameCancelButton` (l.339-357) is
NOT dead and IS built: the ref's comment closes at l.337, one line above it, and the real
client draws a CLOSE button in the page's bottom-right seat. This list used to claim both
were commented out, and that misread is exactly why that seat rendered empty — decision
1496. Only its `CancelSkillUps()` call drops (deviation 1's dead economy again); the two
hides are transcribed.
4. THE RANK TEXT FORMAT IS THE REF'S BYTE-EXACT ONE, not the task brief's own paraphrase:
`ref-ui/SkillFrame.lua` l.178-186 builds `rank .. " (" .. color .. modifier .. close ..
")/" .. max` (e.g. "57 (|cff20ff20+3|r)/75") — transcribed verbatim (the
reference file is the authority on the tuple/format, the brief's prose is a summary).
5. NO FILLBAR (ref's `$parentFillBar`, driven by `numTempPoints`): [`GetSkillLineInfo`]
always reports `numTempPoints = 0` (skills.rs's own module doc — 1.12 training points are
dead data), so the fill bar could never show — dropped outright, `TradeSkillFrame.xml`'s
OWN `BenillaTradeSkillRankFrame` (the identical StatusBar shape) already sets this local
precedent for the same reason.
6. NO HOVER TOOLTIP on a row: the ref's own `SkillFrame.lua` calls `GameTooltip` NOWHERE
(grepped the full reference file) — a skill line is pure descriptor state, nothing to
preview. Unlike Trainer/TradeSkill, this window needs no `GameTooltip:Set*Item` channel.
7. `LockHighlight`/`UnlockHighlight` ENGINE GAP (TalentFrame.xml/TradeSkillFrame.xml's own
"no engine equivalent" posture, grepped `benilla-ui/src/script/button.rs` — ships
texture-pairs/SetText/Enable/Disable/RegisterForClicks/SetButtonState/Click only): the
persistent selection glow is `SkillHighlight`, the separate overlay Frame
Trainer/TradeSkillFrame already use, moved onto the selected row's StatusBar.
8. GlobalStrings this window itself needs: NONE beyond the tab label `SKILLS` (already
declared in CharacterFrame.xml, quoted there byte-exact with its GlobalStrings.lua line
cite) — the ref's own `SkillFrame.lua` reads no other GlobalString.
Rows repaint on `SKILL_LINES_CHANGED` (registered here; `crates/benilla/src/ui_char.rs`'s
`watch_skill_ups`/`feed_skills` already fire it) and on `OnShow` (ref `SkillFrame_OnShow`,
l.4-6). The list is the house faux-scroll kit (ScrollTemplates.xml, the TrainerFrame/
TradeSkillFrame precedent) over 12 fixed row slots (ref `SKILLS_TO_DISPLAY = 12`,
`SkillFrame.lua:1` — the ref's own row count, and this page shares the ref's own 384×512
coordinate space since it fills the identical CharacterFrame page area).
-->
<Script><![CDATA[
-- ── Constants (ref SkillFrame.lua:1-2) ──────────────────────────────────────────────────────
-- SKILLS_TO_DISPLAY is the reference's own GLOBAL and is published as one. It was a chunk
-- local named SKILLS_TO_DISPLAY with the real name in a trailing comment — the
-- value transcribed, the NAME kept private, which is the same shape that cost us
-- BlackoutWorld and the five SkillFrame* children. Addons read it: pfUI's character skin
-- loops `for i = 1, SKILLS_TO_DISPLAY` over the row labels
-- (skins/blizzard/character.lua:277) and died on "'for' limit must be a number".
-- 12 is honest here rather than copied — this file declares exactly 12 SkillTypeLabel rows.
SKILLS_TO_DISPLAY = 12
local SKILLFRAME_SKILL_HEIGHT = 15 -- SKILLFRAME_SKILL_HEIGHT
-- GlobalStrings (ref GlobalStrings.lua:3478), chunk-local per the codebase idiom — safe
-- because only THIS chunk's functions read it; the nil-"All" trap (TradeSkillFrame.xml's
-- deviation notes) is the inline-XML-handler case, and no inline handler references it.
-- Its earlier ABSENCE here made format() throw in UpdateDetail on every row selection —
-- half of the 2026-07-17 "tabs are flaky" report (the silent-error half).
local SKILL_DESCRIPTION = "|cffffffff%s|r %s"
local UNLEARN_SKILL = "Do you want to unlearn %s?" -- GlobalStrings.lua:4446
local UNLEARN = "Unlearn" -- GlobalStrings.lua:4445
local UNLEARN_SKILL_TOOLTIP = "Unlearn this profession" -- GlobalStrings.lua:4447
-- Paint the rank StatusBar `bar` + its `SkillName`/`SkillRank` FontStrings for one skill-line
-- ENTRY row (never a header — callers guard that): the ref's own bar-fill + rank-text half of
-- `SkillFrame_SetStatusBar` (l.76-187) and the detail's twin `SkillDetailFrame_SetStatusBar`
-- (l.161-187, l.370-399) — the SAME formatting either pane uses, factored once here since our
-- minimal detail pane (deviation 1) needs nothing else from that function.
function BenillaSkillFrame_PaintBar(bar, skillName, skillRank, skillModifier, skillMaxRank)
local nameText = getglobal(bar:GetName() .. "SkillName")
local rankText = getglobal(bar:GetName() .. "SkillRank")
-- The trough behind the fill (ref `$parentBackground`), recoloured per branch alongside
-- the fill itself — ref SkillFrame.lua:158 (normal) and :167 (proficiency), and the
-- detail pane's identical pair at :356/:376. The ref sets the normal-skill pair BEFORE
-- its `skillMaxRank` gate and lets the proficiency arm overwrite it; setting each arm's
-- own pair here is the same result with no dead write. The ref's THIRD writer of this
-- texture — the `skillCostType == 1/2/3` green/yellow/red block at :96-111, and the
-- `stepCost` "Learn <skill>" gray at :126 — is unreachable for us and would be
-- overwritten here even if it weren't: `stepCost`/`rankCost` are always nil and
-- `numTempPoints` always 0 (skills.rs's module doc — dead 1.12 data, deviation 1/5), so
-- every entry row takes the ref's own `else -- Normal skill` arm.
local bgTexture = getglobal(bar:GetName() .. "Background")
nameText:SetText(skillName)
nameText:SetTextColor(NORMAL_FONT_COLOR.r, NORMAL_FONT_COLOR.g, NORMAL_FONT_COLOR.b)
rankText:SetTextColor(HIGHLIGHT_FONT_COLOR.r, HIGHLIGHT_FONT_COLOR.g, HIGHLIGHT_FONT_COLOR.b)
if skillMaxRank == 1 then
-- ref SkillFrame.lua:161-167 verbatim: "If max rank in a skill is 1 assume that
-- its a proficiency" — a FULL gray bar and NO rank text. Two kinds of line land
-- here, and the ref can't tell them apart because it doesn't need to: the armor
-- proficiencies, which the server really does report as 1/1, and the single-rank
-- lines (class skills, Dual Wield, racials, the per-mount riding lines) whose
-- `skillMaxRank` the engine already overrode to 1 off
-- `SkillRaceClassInfo.flags & 0x400` (skills.rs's `SkillEntry::mono`) — which is
-- why a hunter's `Beast Mastery 300/300` draws gray and numberless here, exactly
-- as it does in the real client.
bar:SetMinMaxValues(0, 1)
bar:SetValue(1)
bar:SetStatusBarColor(0.5, 0.5, 0.5)
rankText:SetText("")
bgTexture:SetVertexColor(1.0, 1.0, 1.0, 0.5)
elseif skillMaxRank == 0 then
-- Not reachable off a live server (a line the player HAS always carries a max ≥ 1)
-- and the ref leaves this case to fall through its own if/elseif untouched —
-- which, on a recycled row, would leave the PREVIOUS skill's bar and rank text
-- sitting there. We blank instead: a stale-state hole is a quirk, not behaviour.
bar:SetMinMaxValues(0, 1)
bar:SetValue(0)
bar:SetStatusBarColor(0, 0, 0, 0)
rankText:SetText("")
-- back to the template's own declared base, so a recycled row shows a neutral empty
-- trough rather than the previous skill's blue or white. The base lives in the
-- TEXEL (the template's `<Color 1,1,1,0.2>`), so restoring it means an IDENTITY
-- vertex colour — writing 0.2 here would multiply it to 0.04 (decision 1101).
bgTexture:SetVertexColor(1.0, 1.0, 1.0, 1.0)
else
bar:SetStatusBarColor(0.0, 0.0, 1.0, 0.5)
bgTexture:SetVertexColor(0.0, 0.0, 0.75, 0.5)
bar:SetMinMaxValues(0, skillMaxRank)
bar:SetValue(skillRank)
-- the ref's rank-text law (SkillFrame.lua:178-186), re-expressed here (this file's
-- header comment, deviation 4).
if skillModifier == 0 then
rankText:SetText(skillRank .. "/" .. skillMaxRank)
else
local color = RED_FONT_COLOR_CODE
if skillModifier > 0 then
color = GREEN_FONT_COLOR_CODE .. "+"
end
rankText:SetText(skillRank .. " (" .. color .. skillModifier .. FONT_COLOR_CODE_CLOSE .. ")/" .. skillMaxRank)
end
end
end
-- Fill the detail pane (deviation 1) for the current `GetSelectedSkill()`, or blank it
-- when nothing is selected / the selection isn't a visible entry. The description body is
-- the ref's own (ref SkillFrame.lua l.211-217 hide/show alongside the bar; l.262 the
-- SKILL_DESCRIPTION format): `skillType` stays "" in EVERY reachable 1.12 branch — the
-- ref's single assignment ("Tertiary Skill:") is commented out in the reference itself —
-- so the format's first slot renders empty, byte-faithful. The unlearn button rides the
-- same fill (ref l.220-226): shown only for an abandonable line, carrying the name the
-- confirm popup formats and the index its accept abandons.
function BenillaSkillFrame_UpdateDetail()
local bar = getglobal("SkillDetailStatusBar")
local desc = getglobal("SkillDetailDescriptionText")
local unlearn = getglobal("SkillDetailStatusBarUnlearnButton")
-- The ref's own no-cost branch (ref SkillFrame.lua l.361), taken unconditionally:
-- stepCost/rankCost are always nil on a 1.12 server (deviation 1). Hidden here rather
-- than left `hidden="true"` from XML so the branch is visible as a branch — the ref
-- shows this string on the paths we don't have, and this is where it would be re-shown.
getglobal("SkillDetailCostText"):Hide()
local index = GetSelectedSkill()
if index == 0 then
bar:Hide()
desc:Hide()
unlearn:Hide()
return
end
local skillName, isHeader, _, skillRank, _, skillModifier, skillMaxRank, isAbandonable = GetSkillLineInfo(index)
if not skillName or isHeader then
bar:Hide()
desc:Hide()
unlearn:Hide()
return
end
bar:Show()
BenillaSkillFrame_PaintBar(bar, skillName, skillRank, skillModifier, skillMaxRank)
if isAbandonable then
unlearn.skillName = skillName
unlearn.index = index
unlearn:Show()
else
unlearn:Hide()
end
local skillType = ""
local skillDescription = select(13, GetSkillLineInfo(index)) or ""
desc:SetText(format(SKILL_DESCRIPTION, skillType, skillDescription))
desc:Show()
end
-- ── The abandon confirm + the unlearn button's handlers ──────────────────────────────────
-- The dialog is ref StaticPopup.lua:1121-1133 verbatim (minus the vestigial TEXT()
-- wrapper); its OnAccept receives dialog.data — the visible index the button stashed.
-- CANCEL stays the bare GlobalStrings global (DeathFrame.xml's own convention — the app
-- loads the real GlobalStrings.lua before any window file). The handlers are chunk
-- functions, NOT inline bodies, so the chunk-local strings above are visible to them
-- (the nil-"All" chunk-boundary rule, TradeSkillFrame.xml's deviation notes).
StaticPopupDialogs["UNLEARN_SKILL"] = {
text = UNLEARN_SKILL,
button1 = UNLEARN,
button2 = CANCEL,
OnAccept = function(index)
AbandonSkill(index)
end,
timeout = 60,
exclusive = 1,
whileDead = 1,
showAlert = 1,
hideOnEscape = 1
}
function BenillaSkillUnlearnButton_OnClick(btn)
local dialog = StaticPopup_Show("UNLEARN_SKILL", btn.skillName)
if dialog then
dialog.data = btn.index
end
end
function BenillaSkillUnlearnButton_OnEnter(btn)
GameTooltip:SetOwner(btn, "ANCHOR_RIGHT")
GameTooltip:SetText(UNLEARN_SKILL_TOOLTIP)
end
-- Paint list slot `i` (1..12) from the visible row at `index` (ref SkillFrame_SetStatusBar,
-- l.24-188 — the header branch, l.55-69, and the entry branch, l.76-187, collapsed: the
-- skillCostType coloring + stepCost/rankCost training-up arrows never apply here, since
-- GetSkillLineInfo always reports them nil/0, this file's header comment deviation 1).
function BenillaSkillFrame_SetRow(i, index, numRows)
local bar = getglobal("SkillRankFrame" .. i)
local label = getglobal("SkillTypeLabel" .. i)
if index > numRows then
bar:Hide()
bar.skillIndex = nil
label:Hide()
label.skillIndex = nil
return
end
local skillName, isHeader, isExpanded, skillRank, _, skillModifier, skillMaxRank = GetSkillLineInfo(index)
if isHeader then
bar:Hide()
bar.skillIndex = nil
label:Show()
label.skillIndex = index
label:SetText(skillName)
if isExpanded then
label:SetNormalTexture("Interface\\Buttons\\UI-MinusButton-Up")
else
label:SetNormalTexture("Interface\\Buttons\\UI-PlusButton-Up")
end
return
end
label:Hide()
label.skillIndex = nil
bar:Show()
bar.skillIndex = index
BenillaSkillFrame_PaintBar(bar, skillName, skillRank, skillModifier, skillMaxRank)
end
-- The list page (ref SkillFrame_UpdateSkills, l.402-448 — the dead
-- `UnitCharacterPoints("player")`/`SkillFrame.UpdatedSkillPoints` read, l.437-440, whose
-- result nothing downstream ever consumes even in the ref itself, is dropped — the
-- TradeSkillFrame.xml "ref's own dead code is dropped" precedent).
-- The scroll frame's `<OnVerticalScroll>` is its own chunk and cannot see this file's
-- `local SKILLFRAME_SKILL_HEIGHT`, so the pitch is handed over here rather than written as
-- a literal in the XML (1868; the trainer paid for this lesson first, in 1860).
function BenillaSkillList_OnVerticalScroll()
FauxScrollFrame_OnVerticalScroll(SKILLFRAME_SKILL_HEIGHT, BenillaSkillFrame_Update)
end
function BenillaSkillFrame_Update()
local numRows = GetNumSkillLines()
local scrollFrame = getglobal("SkillListScrollFrame")
FauxScrollFrame_Update(scrollFrame, numRows, SKILLS_TO_DISPLAY, SKILLFRAME_SKILL_HEIGHT)
-- Our trough follows the bar the reference's `_Update` just decided about; nothing in
-- its kit shows one, because the reference has no trough at all (1860).
BenillaScrollTrough_Follow("SkillListScrollFrame")
local offset = FauxScrollFrame_GetOffset(scrollFrame)
local highlight = getglobal("SkillHighlight")
highlight:Hide()
local selected = GetSelectedSkill()
for i = 1, SKILLS_TO_DISPLAY do
local index = i + offset
BenillaSkillFrame_SetRow(i, index, numRows)
if selected == index and index <= numRows then
highlight:SetPoint("TOPLEFT", getglobal("SkillRankFrame" .. i), "TOPLEFT", -5, 0)
highlight:Show()
end
end
-- The collapse-all tab's +/- icon (ref l.417-429's own numHeaders/notExpanded scan).
local allExpanded = true
for i = 1, numRows do
local _, isHeader, isExpanded = GetSkillLineInfo(i)
if isHeader and not isExpanded then
allExpanded = false
end
end
local collapseAll = getglobal("SkillFrameCollapseAllButton")
if numRows == 0 then
collapseAll:Disable()
else
collapseAll:Enable()
end
if allExpanded then
collapseAll.collapsed = nil
collapseAll:SetNormalTexture("Interface\\Buttons\\UI-MinusButton-Up")
else
collapseAll.collapsed = 1
collapseAll:SetNormalTexture("Interface\\Buttons\\UI-PlusButton-Up")
end
BenillaSkillFrame_UpdateDetail()
BenillaSkillFrameExpandButtonFrame_Fit()
end
-- A rank-bar click selects it (ref SkillBar_OnClick, l.450-455).
function BenillaSkillRankFrame_OnClick(bar)
local index = bar.skillIndex
if not index then
return
end
SetSelectedSkill(index)
BenillaSkillFrame_Update()
end
-- A header click folds/unfolds its group (ref SkillLabelTemplate's own OnClick, l.72-80).
function BenillaSkillTypeLabel_OnClick(label)
local index = label.skillIndex
if not index then
return
end
local _, _, isExpanded = GetSkillLineInfo(index)
if isExpanded then
CollapseSkillHeader(index)
-- A fold reshuffles visible indices — an UNLEARN_SKILL confirm armed for a row
-- that no longer holds the selection must not fire against the wrong line (ref
-- SkillLabelTemplate's own OnClick, ref SkillFrame.xml l.72-78).
local dialog = StaticPopup_FindVisible("UNLEARN_SKILL")
if dialog and dialog.data ~= GetSelectedSkill() then
StaticPopup_Hide("UNLEARN_SKILL")
end
else
ExpandSkillHeader(index)
end
BenillaSkillFrame_Update()
end
-- The collapse-all tab (ref SkillFrameCollapseAllButton's own isExpanded flag, l.303-309,
-- driven the TrainerFrame.xml BenillaTrainerCollapseAllButton way since this engine's
-- CollapseSkillHeader(0)/ExpandSkillHeader(0) already fold/unfold every group at once).
function BenillaSkillFrameCollapseAllButton_OnClick()
local b = getglobal("SkillFrameCollapseAllButton")
if b.collapsed then
ExpandSkillHeader(0)
else
CollapseSkillHeader(0)
FauxScrollFrame_SetOffset(getglobal("SkillListScrollFrame"), 0)
end
BenillaSkillFrame_Update()
end
-- The tab is as wide as its own label (ref SkillFrameExpandButtonFrame's OnLoad, l.312-316:
-- SetWidth(SkillFrameCollapseAllButton:GetTextWidth()+45)) — the 45 is the two 8px caps plus
-- the label's 25px seat plus the margin the ref leaves before the right cap, and the middle
-- slab stretches into whatever that leaves off its own two anchors.
--
-- Run from BOTH the ref's own OnLoad and every Update, because a measure here is not the
-- ref's synchronous one: benilla seats the VM's font engine at the frame boundary
-- (`ui_script::extract`, ahead of the tick), and XML load happens BEFORE any frame — so the
-- OnLoad call reads 0 and the tab would keep a 45px stump forever. The zero guard is what
-- makes the pair safe: an unmeasured call leaves the width alone rather than squashing the
-- tab, and the first Update after the atlas exists fits it. (The house's own preference is
-- two opposing anchors and no Lua width — UiPanels.xml's tab-highlight note — but a span
-- can only be resolved from two anchors when both edges are frames; a text width has to be
-- asked for.)
function BenillaSkillFrameExpandButtonFrame_Fit()
local label = getglobal("SkillFrameCollapseAllButton")
local w = label:GetTextWidth()
if w and w > 0 then
getglobal("SkillFrameExpandButtonFrame"):SetWidth(w + 45)
end
end
-- The page's own CLOSE (ref SkillFrameCancelButton's OnClick, l.350-356). The ref's
-- CancelSkillUps() drops out with the training-point economy this pane doesn't ship (this
-- file's header comment deviation 1); the two hides are the ref's own, in its own order —
-- the page first, then the window (CharacterFrame_OnHide's subframe loop would reach this
-- page anyway, but the ref hides it explicitly and so do we).
function BenillaSkillFrameCancelButton_OnClick()
getglobal("SkillFrame"):Hide()
HideUIPanel(getglobal("CharacterFrame"))
end
-- The window's wheel handler (bubbled up the parent chain from a row) → one row via the faux
-- kit (TrainerFrame.xml's own note: the engine bubbles a spin up the parent chain).
function BenillaSkillFrame_OnMouseWheel(delta)
BenillaFauxScrollFrame_OnMouseWheel(getglobal("SkillListScrollFrame"), delta)
end
-- ── Lifecycle (ref SkillFrame_OnLoad/_OnShow/_OnEvent, l.4-22, collapsed to the events this
-- page's feed actually fires: CHARACTER_POINTS_CHANGED backs the training-point economy this
-- pane doesn't ship, this file's header comment deviation 1). ─────────────────────────────────
function SkillFrame_OnLoad()
local frame = this
frame:RegisterEvent("SKILL_LINES_CHANGED")
end
function SkillFrame_OnShow()
BenillaSkillFrame_Update()
end
function SkillFrame_OnEvent(event)
local frame = this
if event == "SKILL_LINES_CHANGED" and frame:IsVisible() then
BenillaSkillFrame_Update()
end
end
]]></Script>
<!--
The rank StatusBar row (ref SkillStatusBarTemplate, l.4-65 — the FillBar dropped, this file's
header comment deviation 5): 271×15, name+rank overlaid, the Border child Button for OnClick
+ hover highlight (TradeSkillFrame.xml's own BenillaTradeSkillRankFrame shape, verbatim).
-->
<StatusBar name="BenillaSkillRankTemplate" virtual="true" hidden="true" drawLayer="BACKGROUND" minValue="0" maxValue="1" defaultValue="0">
<Size><AbsDimension x="271" y="15"/></Size>
<Layers>
<Layer level="ARTWORK">
<FontString name="$parentSkillName" inherits="GameFontNormalSmall">
<Anchors><Anchor point="LEFT" relativePoint="LEFT"><Offset><AbsDimension x="6" y="1"/></Offset></Anchor></Anchors>
</FontString>
<FontString name="$parentSkillRank" inherits="GameFontHighlightSmall" justifyH="LEFT">
<Size><AbsDimension x="128" y="0"/></Size>
<Anchors><Anchor point="LEFT" relativeTo="$parentSkillName" relativePoint="RIGHT"><Offset><AbsDimension x="13" y="0"/></Offset></Anchor></Anchors>
</FontString>
</Layer>
<Layer level="BACKGROUND">
<Texture name="$parentBackground"><Color r="1.0" g="1.0" b="1.0" a="0.2"/></Texture>
</Layer>
</Layers>
<Frames>
<Button name="$parentBorder">
<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"/>
<HighlightTexture file="Interface\PaperDollInfoFrame\UI-Character-Skills-BarBorderHighlight"/>
<Scripts><OnClick>BenillaSkillRankFrame_OnClick(self:GetParent())</OnClick></Scripts>
</Button>
</Frames>
<BarTexture name="$parentBar" file="Interface\PaperDollInfoFrame\UI-Character-Skills-Bar"/>
<BarColor r="0.25" g="0.25" b="0.75"/>
</StatusBar>
<!--
The category header row (ref SkillLabelTemplate, l.66-129, minus the DisabledTexture — this
window never Disable()s a header): 285×14, the +/- fold icon + name text.
-->
<Button name="BenillaSkillHeaderTemplate" virtual="true" hidden="true">
<Size><AbsDimension x="285" y="14"/></Size>
<Scripts><OnClick>BenillaSkillTypeLabel_OnClick(self)</OnClick></Scripts>
<NormalTexture file="Interface\Buttons\UI-MinusButton-UP">
<Size><AbsDimension x="16" y="16"/></Size>
<Anchors><Anchor point="LEFT" relativePoint="LEFT"><Offset><AbsDimension x="3" y="0"/></Offset></Anchor></Anchors>
</NormalTexture>
<HighlightTexture file="Interface\Buttons\UI-PlusButton-Hilight" alphaMode="ADD">
<Size><AbsDimension x="16" y="16"/></Size>
<Anchors><Anchor point="LEFT" relativePoint="LEFT"><Offset><AbsDimension x="3" y="0"/></Offset></Anchor></Anchors>
</HighlightTexture>
<ButtonText>
<Anchors><Anchor point="LEFT" relativePoint="LEFT"><Offset><AbsDimension x="25" y="0"/></Offset></Anchor></Anchors>
</ButtonText>
<NormalFont inherits="GameFontHighlight" justifyH="LEFT"/>
</Button>
<!-- id=4, the ref's own: a page's id is the slot it occupies in the WINDOW'S OWN tab row, and
ToggleCharacter selects the tab from it (ref CharacterFrame.lua:11). This was 3 while
Reputation (the ref's 3) was not built; ReputationFrame.xml landed and took that slot, so
Skills is back on the reference's number. `character_tests.rs` pins every page's id against
its slot in CHARACTERFRAME_SUBFRAMES, which is what made the renumber loud rather
than silent — exactly as this comment predicted. -->
<Frame name="SkillFrame" enableMouse="true" parent="CharacterFrame" hidden="true" id="4">
<!-- The real 384×512 page. No <Anchors relativeTo> substitute for true parenting exists
(this file's header comment) — pinned to CharacterFrame's own TOPLEFT by name. -->
<Size><AbsDimension x="384" y="512"/></Size>
<Anchors><Anchor point="TOPLEFT" relativeTo="CharacterFrame" relativePoint="TOPLEFT"/></Anchors>
<!-- Transcribed for fidelity (ref l.131-133); no loader wiring exists yet for the hit-rect
shrink (TradeSkillFrame.xml's own "transcribe the hook even if nothing drives it yet"
posture, TalentFrame.xml's precedent). -->
<HitRectInsets><AbsInset left="0" right="30" top="0" bottom="75"/></HitRectInsets>
<Layers>
<!-- The shared quadrant background (ref l.136-183): NO portrait redraw here (this file's
header comment) — "General" TopLeft/TopRight (the same variant every PaperDollInfoFrame
page shares, carrying the portrait-ring cutout) + SkillFrame's own BotLeft/BotRight. -->
<Layer level="BACKGROUND">
<Texture file="Interface\PaperDollInfoFrame\UI-Character-General-TopLeft">
<Size><AbsDimension x="256" y="256"/></Size>
<Anchors><Anchor point="TOPLEFT" relativePoint="TOPLEFT"><Offset><AbsDimension x="2" y="-1"/></Offset></Anchor></Anchors>
</Texture>
<Texture file="Interface\PaperDollInfoFrame\UI-Character-General-TopRight">
<Size><AbsDimension x="128" y="256"/></Size>
<Anchors><Anchor point="TOPRIGHT" relativePoint="TOPRIGHT"><Offset><AbsDimension x="2" y="-1"/></Offset></Anchor></Anchors>
</Texture>
<Texture file="Interface\PaperDollInfoFrame\SkillFrame-BotLeft">
<Size><AbsDimension x="256" y="256"/></Size>
<Anchors><Anchor point="BOTTOMLEFT" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="2" y="-1"/></Offset></Anchor></Anchors>
</Texture>
<Texture file="Interface\PaperDollInfoFrame\SkillFrame-BotRight">
<Size><AbsDimension x="128" y="256"/></Size>
<Anchors><Anchor point="BOTTOMRIGHT" relativePoint="BOTTOMRIGHT"><Offset><AbsDimension x="2" y="-1"/></Offset></Anchor></Anchors>
</Texture>
</Layer>
<!-- The divider bar below the list, above the detail pane (ref l.186-211's own
TOPLEFT(15,-290); nudged to -305 here to clear this file's own 12-row chain, whose
last row sits slightly lower than the ref's fixed-slot geometry — a pixel-fit
adjustment, not a structural deviation). -->
<Layer level="ARTWORK">
<Texture name="SkillHorizontalBarLeft" file="Interface\ClassTrainerFrame\UI-ClassTrainer-HorizontalBar">
<Size><AbsDimension x="256" y="16"/></Size>
<Anchors><Anchor point="TOPLEFT" relativePoint="TOPLEFT"><Offset><AbsDimension x="15" y="-305"/></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="SkillHorizontalBarLeft" relativePoint="RIGHT"/></Anchors>
<TexCoords left="0" right="0.29296875" top="0.25" bottom="0.5"/>
</Texture>
</Layer>
</Layers>
<Frames>
<!-- The expand/collapse-all tab (ref SkillFrameExpandButtonFrame, l.242-317 — the SAME
QuestLogSortTab art TrainerFrame.xml's own BenillaTrainerExpandButtonFrame uses).
The tab is SIZED BY ITS LABEL, the ref's own OnLoad law (l.312-316:
SetWidth(GetTextWidth()+45)) — the middle slab stretches between the two 8px caps
off its OWN two anchors (ref l.279-287), so the 45 is exactly the two caps plus the
label's seat. Was a fixed 54 with a fixed 38 middle, copied off
TrainerFrame.xml rather than read off THIS ref; the label's own font fix below
(12px, not 10) would have run under the right cap at that width. -->
<Frame name="SkillFrameExpandButtonFrame">
<Size><AbsDimension x="54" y="32"/></Size>
<Anchors><Anchor point="TOPLEFT" relativePoint="TOPLEFT"><Offset><AbsDimension x="70" y="-49"/></Offset></Anchor></Anchors>
<Layers><Layer level="BACKGROUND">
<Texture name="SkillFrameExpandTabLeft" file="Interface\QuestFrame\UI-QuestLogSortTab-Left"><Size><AbsDimension x="8" y="32"/></Size><Anchors><Anchor point="TOPLEFT" relativePoint="TOPLEFT"><Offset><AbsDimension x="0" y="6"/></Offset></Anchor></Anchors></Texture>
<Texture name="SkillFrameExpandTabRight" file="Interface\QuestFrame\UI-QuestLogSortTab-Right"><Size><AbsDimension x="8" y="32"/></Size><Anchors><Anchor point="TOPRIGHT" relativePoint="TOPRIGHT"><Offset><AbsDimension x="0" y="6"/></Offset></Anchor></Anchors></Texture>
<Texture name="SkillFrameExpandTabMiddle" file="Interface\QuestFrame\UI-QuestLogSortTab-Middle"><Size><AbsDimension x="0" y="32"/></Size><Anchors><Anchor point="LEFT" relativeTo="SkillFrameExpandTabLeft" relativePoint="RIGHT"/><Anchor point="RIGHT" relativeTo="SkillFrameExpandTabRight" relativePoint="LEFT"/></Anchors></Texture>
</Layer></Layers>
<Frames>
<!-- The ALL fold (ref SkillFrameCollapseAllButton, l.292-310). Its face is the
ref's SkillLabelTemplate one — the SAME face every SkillTypeLabel row wears
(BenillaSkillHeaderTemplate above, l.83-128 of the ref): the +/- glyph at
LEFT+3, the label at LEFT+25, and GameFontHighlight — WHITE at 12px, not
GameFontNormalSmall's yellow 10. Spelled out here rather than inherited
because this button keeps its own OnClick (this engine's
CollapseSkillHeader(0)/ExpandSkillHeader(0) fold everything at once, so it
needs no skillIndex/isExpanded bookkeeping) and this loader CONCATENATES an
instance's children onto its template's rather than replacing them, so an
inherited <Scripts> would stand alongside ours. Its seat is the ref's own
(-3,-3) off the left cap: the tab art's visible lip sits BELOW the
texture's geometric middle, so the label rides 3px low, not 3px high — the
+3 copied from TrainerFrame.xml put the whole "- All" 6px above where the
reference seats it. -->
<Button name="SkillFrameCollapseAllButton" text="ALL">
<Size><AbsDimension x="40" y="22"/></Size>
<Anchors><Anchor point="LEFT" relativeTo="SkillFrameExpandTabLeft" relativePoint="RIGHT"><Offset><AbsDimension x="-3" 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"><Offset><AbsDimension x="3" y="0"/></Offset></Anchor></Anchors></NormalTexture>
<HighlightTexture file="Interface\Buttons\UI-PlusButton-Hilight" alphaMode="ADD"><Size><AbsDimension x="16" y="16"/></Size><Anchors><Anchor point="LEFT" relativePoint="LEFT"><Offset><AbsDimension x="3" y="0"/></Offset></Anchor></Anchors></HighlightTexture>
<ButtonText><Anchors><Anchor point="LEFT" relativePoint="LEFT"><Offset><AbsDimension x="25" y="0"/></Offset></Anchor></Anchors></ButtonText>
<NormalFont inherits="GameFontHighlight" justifyH="LEFT"/>
<Scripts><OnClick>BenillaSkillFrameCollapseAllButton_OnClick()</OnClick></Scripts>
</Button>
</Frames>
<Scripts><OnLoad>BenillaSkillFrameExpandButtonFrame_Fit()</OnLoad></Scripts>
</Frame>
<!-- The page's own CLOSE button (ref SkillFrameCancelButton, l.339-357: 80x22,
UIPanelButtonTemplate, CENTER of TOPLEFT (305,-422), text CLOSE). LIVE in the
reference, not dead: the ref's XML comment that swallows SkillFrameAcceptButton
CLOSES at l.337, one line ABOVE this button — this file's own header comment
deviation 3 read that boundary wrong and dropped a button the real client draws,
which is why the page's bottom-right seat sat empty (decision 1496). The
UIPanelButtonTemplate face is reconstructed locally, the house convention every
window here follows (CraftFrame.xml's BenillaCraftPanelButtonTemplate note) — one
site, so inline rather than a virtual template (GroupLootFrame.xml's own precedent
for a single ref-template button). -->
<Button name="SkillFrameCancelButton" text="CLOSE">
<Size><AbsDimension x="80" y="22"/></Size>
<Anchors><Anchor point="CENTER" relativePoint="TOPLEFT"><Offset><AbsDimension x="305" y="-422"/></Offset></Anchor></Anchors>
<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>
<Scripts><OnClick>BenillaSkillFrameCancelButton_OnClick()</OnClick></Scripts>
</Button>
<!-- The persistent selection highlight (Trainer/TradeSkillFrame's own precedent, this
file's header comment deviation 7): 281×15, ARTWORK-layer UI-Listbox-Highlight2.
Declared BEFORE the rows so the row text paints OVER the glow (same z-tiebreak). -->
<Frame name="SkillHighlightFrame" hidden="true">
<Size><AbsDimension x="281" y="15"/></Size>
<Anchors><Anchor point="TOPLEFT" relativePoint="TOPLEFT"/></Anchors>
<Layers><Layer level="ARTWORK">
<Texture name="SkillHighlight" file="Interface\Buttons\UI-Listbox-Highlight2">
<Anchors><Anchor point="TOPLEFT"/><Anchor point="BOTTOMRIGHT"/></Anchors>
</Texture>
</Layer></Layers>
</Frame>
<!-- 12 header-row slots (ref SkillTypeLabel1..12, l.358-465): 285×14, row1 LEFT of
TOPLEFT(22,-86); rows 2-12 chain by (0,-18). -->
<Button name="SkillTypeLabel1" inherits="BenillaSkillHeaderTemplate">
<Anchors><Anchor point="LEFT" relativePoint="TOPLEFT"><Offset><AbsDimension x="22" y="-86"/></Offset></Anchor></Anchors>
</Button>
<Button name="SkillTypeLabel2" inherits="BenillaSkillHeaderTemplate"><Anchors><Anchor point="LEFT" relativeTo="SkillTypeLabel1"><Offset><AbsDimension x="0" y="-18"/></Offset></Anchor></Anchors></Button>
<Button name="SkillTypeLabel3" inherits="BenillaSkillHeaderTemplate"><Anchors><Anchor point="LEFT" relativeTo="SkillTypeLabel2"><Offset><AbsDimension x="0" y="-18"/></Offset></Anchor></Anchors></Button>
<Button name="SkillTypeLabel4" inherits="BenillaSkillHeaderTemplate"><Anchors><Anchor point="LEFT" relativeTo="SkillTypeLabel3"><Offset><AbsDimension x="0" y="-18"/></Offset></Anchor></Anchors></Button>
<Button name="SkillTypeLabel5" inherits="BenillaSkillHeaderTemplate"><Anchors><Anchor point="LEFT" relativeTo="SkillTypeLabel4"><Offset><AbsDimension x="0" y="-18"/></Offset></Anchor></Anchors></Button>
<Button name="SkillTypeLabel6" inherits="BenillaSkillHeaderTemplate"><Anchors><Anchor point="LEFT" relativeTo="SkillTypeLabel5"><Offset><AbsDimension x="0" y="-18"/></Offset></Anchor></Anchors></Button>
<Button name="SkillTypeLabel7" inherits="BenillaSkillHeaderTemplate"><Anchors><Anchor point="LEFT" relativeTo="SkillTypeLabel6"><Offset><AbsDimension x="0" y="-18"/></Offset></Anchor></Anchors></Button>
<Button name="SkillTypeLabel8" inherits="BenillaSkillHeaderTemplate"><Anchors><Anchor point="LEFT" relativeTo="SkillTypeLabel7"><Offset><AbsDimension x="0" y="-18"/></Offset></Anchor></Anchors></Button>
<Button name="SkillTypeLabel9" inherits="BenillaSkillHeaderTemplate"><Anchors><Anchor point="LEFT" relativeTo="SkillTypeLabel8"><Offset><AbsDimension x="0" y="-18"/></Offset></Anchor></Anchors></Button>
<Button name="SkillTypeLabel10" inherits="BenillaSkillHeaderTemplate"><Anchors><Anchor point="LEFT" relativeTo="SkillTypeLabel9"><Offset><AbsDimension x="0" y="-18"/></Offset></Anchor></Anchors></Button>
<Button name="SkillTypeLabel11" inherits="BenillaSkillHeaderTemplate"><Anchors><Anchor point="LEFT" relativeTo="SkillTypeLabel10"><Offset><AbsDimension x="0" y="-18"/></Offset></Anchor></Anchors></Button>
<Button name="SkillTypeLabel12" inherits="BenillaSkillHeaderTemplate"><Anchors><Anchor point="LEFT" relativeTo="SkillTypeLabel11"><Offset><AbsDimension x="0" y="-18"/></Offset></Anchor></Anchors></Button>
<!-- 12 rank-bar slots (ref SkillRankFrame1..12, l.746-853): 271×15, row1 TOPLEFT(38,-79);
rows 2-12 chain BOTTOMLEFT→TOPLEFT by (0,-3) — the SAME 18px pitch as the label chain
(15 bar height + 3 gap). -->
<StatusBar name="SkillRankFrame1" inherits="BenillaSkillRankTemplate" id="1">
<Anchors><Anchor point="TOPLEFT" relativePoint="TOPLEFT"><Offset><AbsDimension x="38" y="-79"/></Offset></Anchor></Anchors>
</StatusBar>
<StatusBar name="SkillRankFrame2" inherits="BenillaSkillRankTemplate" id="2"><Anchors><Anchor point="TOPLEFT" relativeTo="SkillRankFrame1" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="0" y="-3"/></Offset></Anchor></Anchors></StatusBar>
<StatusBar name="SkillRankFrame3" inherits="BenillaSkillRankTemplate" id="3"><Anchors><Anchor point="TOPLEFT" relativeTo="SkillRankFrame2" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="0" y="-3"/></Offset></Anchor></Anchors></StatusBar>
<StatusBar name="SkillRankFrame4" inherits="BenillaSkillRankTemplate" id="4"><Anchors><Anchor point="TOPLEFT" relativeTo="SkillRankFrame3" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="0" y="-3"/></Offset></Anchor></Anchors></StatusBar>
<StatusBar name="SkillRankFrame5" inherits="BenillaSkillRankTemplate" id="5"><Anchors><Anchor point="TOPLEFT" relativeTo="SkillRankFrame4" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="0" y="-3"/></Offset></Anchor></Anchors></StatusBar>
<StatusBar name="SkillRankFrame6" inherits="BenillaSkillRankTemplate" id="6"><Anchors><Anchor point="TOPLEFT" relativeTo="SkillRankFrame5" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="0" y="-3"/></Offset></Anchor></Anchors></StatusBar>
<StatusBar name="SkillRankFrame7" inherits="BenillaSkillRankTemplate" id="7"><Anchors><Anchor point="TOPLEFT" relativeTo="SkillRankFrame6" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="0" y="-3"/></Offset></Anchor></Anchors></StatusBar>
<StatusBar name="SkillRankFrame8" inherits="BenillaSkillRankTemplate" id="8"><Anchors><Anchor point="TOPLEFT" relativeTo="SkillRankFrame7" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="0" y="-3"/></Offset></Anchor></Anchors></StatusBar>
<StatusBar name="SkillRankFrame9" inherits="BenillaSkillRankTemplate" id="9"><Anchors><Anchor point="TOPLEFT" relativeTo="SkillRankFrame8" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="0" y="-3"/></Offset></Anchor></Anchors></StatusBar>
<StatusBar name="SkillRankFrame10" inherits="BenillaSkillRankTemplate" id="10"><Anchors><Anchor point="TOPLEFT" relativeTo="SkillRankFrame9" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="0" y="-3"/></Offset></Anchor></Anchors></StatusBar>
<StatusBar name="SkillRankFrame11" inherits="BenillaSkillRankTemplate" id="11"><Anchors><Anchor point="TOPLEFT" relativeTo="SkillRankFrame10" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="0" y="-3"/></Offset></Anchor></Anchors></StatusBar>
<StatusBar name="SkillRankFrame12" inherits="BenillaSkillRankTemplate" id="12"><Anchors><Anchor point="TOPLEFT" relativeTo="SkillRankFrame11" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="0" y="-3"/></Offset></Anchor></Anchors></StatusBar>
<!-- The faux-scroll frame (FauxScrollFrameTemplate, ScrollTemplates.xml): invisible
container over the 12 rows; only its right-hand Slider scroll bar renders. Sized to the
12-row span (SKILLS_TO_DISPLAY × 18px pitch), TrainerFrame.xml's own
same-TOPLEFT-as-row1 convention. Its `<OnVerticalScroll>` re-binds the rows (1868). -->
<ScrollFrame name="SkillListScrollFrame" inherits="FauxScrollFrameTemplate">
<Size><AbsDimension x="296" y="216"/></Size>
<Anchors><Anchor point="TOPLEFT" relativePoint="TOPLEFT"><Offset><AbsDimension x="22" y="-79"/></Offset></Anchor></Anchors>
<Scripts>
<!-- The reference's kit writes `frame.offset` ONLY from here: the bar's
OnValueChanged calls `SetVerticalScroll`, which lands in this handler.
Our transcription used a `frame.updateFunc` field instead, which the
reference does not have — without this the bar moves and the list does
not follow (1860). -->
<OnVerticalScroll>BenillaSkillList_OnVerticalScroll()</OnVerticalScroll>
</Scripts>
</ScrollFrame>
<!-- ═══ THE DETAIL PANE ═══ ref SkillFrame.xml l.513-747, transcribed as the ref
declares it (header: the FLAT-layout claim this file used to carry is retracted,
decision 1517). A real ScrollFrame over a real scroll child; `scrollBarHideable`
keeps the bar off, which for a one-paragraph description is every time. The two
flanking textures are the ref's own trough art on the SCROLL FRAME's own Layers
(ItemTextFrame.xml's ItemTextScrollFrame is this file's precedent for both) — the
ref draws them unconditionally, so they stand whether or not the bar does.
The scroll range is the child's whole SUBTREE box (decision 1338), which is why
the ref's 320x50 child is not the undersized thing it looks like. -->
<ScrollFrame name="SkillDetailScrollFrame" inherits="UIPanelScrollFrameTemplate">
<Size><AbsDimension x="296" y="107"/></Size>
<Anchors>
<Anchor point="TOPLEFT" relativeTo="SkillListScrollFrame" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="0" y="-8"/></Offset></Anchor>
</Anchors>
<Layers>
<Layer level="ARTWORK">
<Texture file="Interface\ClassTrainerFrame\UI-ClassTrainer-ScrollBar">
<Size><AbsDimension x="30" y="100"/></Size>
<Anchors><Anchor point="TOPLEFT" relativePoint="TOPRIGHT"><Offset><AbsDimension x="-3" y="5"/></Offset></Anchor></Anchors>
<TexCoords left="0" right="0.46875" top="0" bottom="0.78125"/>
</Texture>
<Texture file="Interface\ClassTrainerFrame\UI-ClassTrainer-ScrollBar">
<Size><AbsDimension x="30" y="100"/></Size>
<Anchors><Anchor point="BOTTOMLEFT" relativePoint="BOTTOMRIGHT"><Offset><AbsDimension x="-3" y="-2"/></Offset></Anchor></Anchors>
<TexCoords left="0.53125" right="1.0" top="0.21875" bottom="1.0"/>
</Texture>
</Layer>
</Layers>
<Scripts>
<!-- A handler declared on an instance REPLACES the template's, it does not
chain (ItemTextFrame.xml's own note), so the template's OnLoad is re-called
by hand before this instance's own line. -->
<OnLoad>
ScrollFrame_OnLoad()
self.scrollBarHideable = 1
</OnLoad>
</Scripts>
<ScrollChild>
<Frame name="SkillDetailScrollChildFrame">
<Size><AbsDimension x="320" y="50"/></Size>
<Layers>
<Layer level="ARTWORK">
<!-- The training-point price tag (ref l.567-577). Built, never
filled: `GetSkillLineInfo` reports no stepCost/rankCost on a
1.12 server, so BenillaSkillFrame_UpdateDetail takes the ref's
OWN no-cost branch (ref SkillFrame.lua l.361) and hides it —
deviation 1. It still earns its place as the DESCRIPTION's
anchor target, which is the ref's own arrangement. -->
<FontString name="SkillDetailCostText" inherits="GameFontNormalSmall" justifyH="CENTER" hidden="true">
<Size><AbsDimension x="200" y="0"/></Size>
<Anchors><Anchor point="CENTER" relativeTo="$parent" relativePoint="TOP"><Offset><AbsDimension x="-10" y="-40"/></Offset></Anchor></Anchors>
</FontString>
<!-- The description body (ref l.579-591). Its anchor is XML now:
the flat layout had to set it in SkillFrame_OnLoad because the
bar was a forward reference on the page, and inside the child
the ref's own static TOP anchor resolves. -->
<FontString name="SkillDetailDescriptionText" inherits="GameFontHighlightSmall" justifyH="LEFT" justifyV="TOP" hidden="true">
<Size><AbsDimension x="275" y="0"/></Size>
<Anchors><Anchor point="TOP" relativeTo="$parent" relativePoint="TOP"><Offset><AbsDimension x="-5" y="-35"/></Offset></Anchor></Anchors>
</FontString>
</Layer>
</Layers>
<Frames>
<!-- The selected skill's rank bar (ref l.594-747), the ref's OWN
declaration rather than the borrowed 271-wide list row it used to
be: 211x15 under a 220x32 Border, centred on the child's TOP. The
Border is a TEXTURE here, as the ref has it — the list row's
Border is a Button because a row is clickable, and the detail bar
is not, so the old "its Border click is harmless here" hedge is
gone with the borrowing. Names match the list template's
($parentSkillName/$parentSkillRank/$parentBackground), so
BenillaSkillFrame_PaintBar drives both unchanged. -->
<StatusBar name="SkillDetailStatusBar" drawLayer="BACKGROUND" minValue="0" maxValue="1" defaultValue="0" hidden="true">
<Size><AbsDimension x="211" y="15"/></Size>
<Anchors>
<Anchor point="CENTER" relativeTo="SkillDetailScrollChildFrame" relativePoint="TOP"><Offset><AbsDimension x="-10" y="-20"/></Offset></Anchor>
</Anchors>
<Layers>
<Layer level="ARTWORK">
<Texture name="$parentBorder" file="Interface\PaperDollInfoFrame\UI-Character-Skills-BarBorder">
<Size><AbsDimension x="220" y="32"/></Size>
<Anchors><Anchor point="LEFT"><Offset><AbsDimension x="-5" y="0"/></Offset></Anchor></Anchors>
</Texture>
<FontString name="$parentSkillName" inherits="GameFontNormalSmall">
<Anchors><Anchor point="LEFT"><Offset><AbsDimension x="6" y="1"/></Offset></Anchor></Anchors>
</FontString>
<FontString name="$parentSkillRank" inherits="GameFontHighlightSmall">
<Size><AbsDimension x="128" y="0"/></Size>
</FontString>
</Layer>
<Layer level="BACKGROUND">
<Texture name="$parentBackground">
<Color r="1.0" g="1.0" b="1.0" a="0.2"/>
</Texture>
</Layer>
</Layers>
<Frames>
<!-- The abandon button (ref $parentUnlearnButton, l.705-737,
verbatim art/geometry): the red circle-slash right of the
Border, shown by UpdateDetail only for an abandonable line.
HitRectInsets transcribed-inert (no loader wiring —
CharacterFrame.xml's engine-gap note). Handlers are chunk
functions (the chunk-boundary rule — OnEnter reads the
chunk-local UNLEARN_SKILL_TOOLTIP). -->
<Button name="$parentUnlearnButton" hidden="true">
<Size><AbsDimension x="32" y="32"/></Size>
<Anchors>
<Anchor point="LEFT" relativeTo="$parentBorder" relativePoint="RIGHT">
<Offset><AbsDimension x="-2" y="-1"/></Offset>
</Anchor>
</Anchors>
<HitRectInsets><AbsInset left="9" right="7" top="-7" bottom="10"/></HitRectInsets>
<Scripts>
<OnClick>BenillaSkillUnlearnButton_OnClick(self)</OnClick>
<OnEnter>BenillaSkillUnlearnButton_OnEnter(self)</OnEnter>
<OnLeave>GameTooltip:Hide()</OnLeave>
</Scripts>
<NormalTexture file="Interface\Buttons\CancelButton-Up"/>
<PushedTexture file="Interface\Buttons\CancelButton-Down"/>
<HighlightTexture file="Interface\Buttons\CancelButton-Highlight" alphaMode="ADD"/>
</Button>
</Frames>
<BarTexture name="$parentBar" file="Interface\PaperDollInfoFrame\UI-Character-Skills-Bar"/>
<BarColor r="0.25" g="0.25" b="0.75"/>
</StatusBar>
</Frames>
</Frame>
</ScrollChild>
</ScrollFrame>
</Frames>
<Scripts>
<OnLoad>SkillFrame_OnLoad()</OnLoad>
<OnShow>SkillFrame_OnShow()</OnShow>
<OnEvent>SkillFrame_OnEvent(event)</OnEvent>
<OnMouseWheel>BenillaSkillFrame_OnMouseWheel(arg1)</OnMouseWheel>
</Scripts>
</Frame>
</Ui>