Files
benilla-octo/crates/benilla-app/assets/ui/SkillFrame.xml
T
SamandClaude Fable 5 c9fe3d7a48 the camera follows again, /errors opens the log, cmangos lets you in
- the camera auto-follow returns, as a setting: Camera Following Style on the Controls options page — Smart (the default), Always, Never — so after an orbit drag the camera swings back behind you while you run; far sight still forces the chase off
- broken addons announce themselves: world entry prints "3 addon load failures — type /errors to see them", and the `/errors` window lists every script error this session with its traceback — 256 deduplicated rows, repeats collapsing onto a count — while the five load-failure classes that only ever hit the terminal (missing .toc, unparseable FrameXML, dependency cycles…) finally get a channel
- B opens the backpack alone, SHIFT-B opens all bags, F8–F11 toggle the individual slots — the reference's six bag commands instead of one command wearing every chord; and the Skills page gets its CLOSE button, with the ALL fold in the row font
- cmangos-classic logons work: `CMSG_AUTH_SESSION` now carries the real client's twelve secure-addon records (342 bytes, compressing to 130) — an empty addon block is a shape no client can send, and cmangos kicks for it where vmangos shrugs; Warden-enabled anticheat remains a separate wall

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-21 18:10:06 +07:00

755 lines
54 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=` is
transcribed for fidelity but is a NO-OP in this engine (grepped `benilla-ui/src/loader`: the
XML `parent` attribute is read nowhere — only physical `<Frames>` nesting creates a real
parent). Position instead rides `<Anchors relativeTo="CharacterFrame">` (a NAMED
lookup, works regardless of tree position, the same mechanism every sibling-anchor in this
codebase already uses) — 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 `BENILLA_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).
FLAT layout (TrainerFrame.xml/TradeSkillFrame.xml's own house constraint): this window has no
DETAIL FontStrings needing a nested child Frame in the first place — the minimal detail pane
(below) is a single StatusBar instance whose own name/rank FontStrings live on ITS OWN Layers
(a StatusBar, like a Button, renders its own Layers fine — only a plain child `<Frame>` loses
them), the exact `BenillaTradeSkillRankFrame` shape TradeSkillFrame.xml already proved.
v1 DEVIATIONS (each re-cited at its own site below):
1. THE DETAIL PANE IS MINIMAL-PLUS-DESCRIPTION-PLUS-ABANDON: the selected skill's rank
StatusBar + name + the DESCRIPTION BODY (`SkillDetailDescriptionText` — ref
`SkillDetailDescriptionText`/`SKILL_DESCRIPTION`, real since `GetSkillLineInfo`'s 13th
return carries `SkillLine.dbc` col 12) + the ABANDON BUTTON
(`SkillDetailUnlearnButton` → 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). STILL OMITTED:
the training-point spend row (the ref's `LeftArrow`/`RightArrow`/`LearnSkillButton`/
`SkillDetailCostText`, all driven by `stepCost`/`rankCost`, which `GetSkillLineInfo`
always reports nil/0 — 0437's own named out-of-scope: 1.12 training points are dead
data).
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) ──────────────────────────────────────────────────────
local SKILLFRAME_SKILLS_DISPLAYED = 12 -- SKILLS_TO_DISPLAY
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("SkillDetailBar")
local desc = getglobal("SkillDetailDescriptionText")
local unlearn = getglobal("SkillDetailUnlearnButton")
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).
function BenillaSkillFrame_Update()
local numRows = GetNumSkillLines()
local scrollFrame = getglobal("SkillListScrollFrame")
FauxScrollFrame_Update(scrollFrame, numRows, SKILLFRAME_SKILLS_DISPLAYED, SKILLFRAME_SKILL_HEIGHT)
local offset = FauxScrollFrame_GetOffset(scrollFrame)
local highlight = getglobal("SkillHighlight")
highlight:Hide()
local selected = GetSelectedSkill()
for i = 1, SKILLFRAME_SKILLS_DISPLAYED 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("SkillCollapseAllButton")
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()
BenillaSkillExpandButtonFrame_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 BenillaSkillCollapseAllButton_OnClick()
local b = getglobal("SkillCollapseAllButton")
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 BenillaSkillExpandButtonFrame_Fit()
local label = getglobal("SkillCollapseAllButton")
local w = label:GetTextWidth()
if w and w > 0 then
getglobal("SkillExpandButtonFrame"):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")
getglobal("SkillListScrollFrame").updateFunc = BenillaSkillFrame_Update
-- The description body hangs under the detail bar — anchored here, not in XML,
-- because the bar is a child frame declared after the page's layers and anchors
-- resolve at SetPoint time (an XML forward reference silently lands on the page;
-- the MailFrame error-text OnLoad precedent, ref MailFrame.xml l.1040). Cleared
-- first: the load-time implicit anchor (decision 1310) seated this justifyH="LEFT"
-- string at the page's LEFT, and SetPoint replaces only its own point -- without the
-- clear both anchors stand and the left/center edges resolve off the page, not the
-- bar. (The ref needs no clear on ITS file only because its XML anchors the string.)
SkillDetailDescriptionText:ClearAllPoints()
SkillDetailDescriptionText:SetPoint("TOPLEFT", "SkillDetailBar", "BOTTOMLEFT", -2, -10)
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 BENILLA_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>
<!-- The detail pane's description body (ref SkillDetailDescriptionText, l.579-591:
GameFontHighlightSmall, 275 wide, justified LEFT/TOP). The ref hangs it inside
its SkillDetailScrollChildFrame; this page's flat layout (header comment) has
no such child, so it hangs under the detail bar instead — the same pane
position, minus the (never-needed for one sentence) scroll machinery. The
anchor is set in SkillFrame_OnLoad, NOT here: the bar is a child frame
declared below and an XML forward reference silently lands on the page. -->
<FontString name="SkillDetailDescriptionText" inherits="GameFontHighlightSmall" justifyH="LEFT" justifyV="TOP" hidden="true">
<Size><AbsDimension x="275" y="0"/></Size>
</FontString>
</Layer>
</Layers>
<Frames>
<!-- The scroll-wheel catcher (TrainerFrame.xml's own note) — MUST be first so every real
row below out-ranks it in hit-testing, and sized to the LIST REGION ONLY (rows span
y -79..-295, x 22..309), NEVER the whole page: unlike the trainer's standalone
window, this page is CharacterFrame's LAST child, so a full-page catcher
out-ranks every EARLIER CharacterFrame child — it ate the tab row and the close X
whenever Skills was up (the 2026-07-17 "can't switch back to Character" report;
character_tests.rs's tab_round_trip_with_a_selected_skill_by_point is the repro). -->
<Button name="SkillWheelCatcher">
<Size><AbsDimension x="290" y="222"/></Size>
<Anchors><Anchor point="TOPLEFT" relativePoint="TOPLEFT"><Offset><AbsDimension x="20" y="-76"/></Offset></Anchor></Anchors>
<Scripts><OnMouseWheel>BenillaSkillFrame_OnMouseWheel(arg1)</OnMouseWheel></Scripts>
</Button>
<!-- 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="SkillExpandButtonFrame">
<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="SkillExpandTabLeft" 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="SkillExpandTabRight" 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="SkillExpandTabMiddle" file="Interface\QuestFrame\UI-QuestLogSortTab-Middle"><Size><AbsDimension x="0" y="32"/></Size><Anchors><Anchor point="LEFT" relativeTo="SkillExpandTabLeft" relativePoint="RIGHT"/><Anchor point="RIGHT" relativeTo="SkillExpandTabRight" 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="SkillCollapseAllButton" text="ALL">
<Size><AbsDimension x="40" y="22"/></Size>
<Anchors><Anchor point="LEFT" relativeTo="SkillExpandTabLeft" 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>BenillaSkillCollapseAllButton_OnClick()</OnClick></Scripts>
</Button>
</Frames>
<Scripts><OnLoad>BenillaSkillExpandButtonFrame_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">
<Anchors><Anchor point="TOPLEFT" relativePoint="TOPLEFT"><Offset><AbsDimension x="38" y="-79"/></Offset></Anchor></Anchors>
</StatusBar>
<StatusBar name="SkillRankFrame2" inherits="BenillaSkillRankTemplate"><Anchors><Anchor point="TOPLEFT" relativeTo="SkillRankFrame1" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="0" y="-3"/></Offset></Anchor></Anchors></StatusBar>
<StatusBar name="SkillRankFrame3" inherits="BenillaSkillRankTemplate"><Anchors><Anchor point="TOPLEFT" relativeTo="SkillRankFrame2" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="0" y="-3"/></Offset></Anchor></Anchors></StatusBar>
<StatusBar name="SkillRankFrame4" inherits="BenillaSkillRankTemplate"><Anchors><Anchor point="TOPLEFT" relativeTo="SkillRankFrame3" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="0" y="-3"/></Offset></Anchor></Anchors></StatusBar>
<StatusBar name="SkillRankFrame5" inherits="BenillaSkillRankTemplate"><Anchors><Anchor point="TOPLEFT" relativeTo="SkillRankFrame4" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="0" y="-3"/></Offset></Anchor></Anchors></StatusBar>
<StatusBar name="SkillRankFrame6" inherits="BenillaSkillRankTemplate"><Anchors><Anchor point="TOPLEFT" relativeTo="SkillRankFrame5" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="0" y="-3"/></Offset></Anchor></Anchors></StatusBar>
<StatusBar name="SkillRankFrame7" inherits="BenillaSkillRankTemplate"><Anchors><Anchor point="TOPLEFT" relativeTo="SkillRankFrame6" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="0" y="-3"/></Offset></Anchor></Anchors></StatusBar>
<StatusBar name="SkillRankFrame8" inherits="BenillaSkillRankTemplate"><Anchors><Anchor point="TOPLEFT" relativeTo="SkillRankFrame7" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="0" y="-3"/></Offset></Anchor></Anchors></StatusBar>
<StatusBar name="SkillRankFrame9" inherits="BenillaSkillRankTemplate"><Anchors><Anchor point="TOPLEFT" relativeTo="SkillRankFrame8" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="0" y="-3"/></Offset></Anchor></Anchors></StatusBar>
<StatusBar name="SkillRankFrame10" inherits="BenillaSkillRankTemplate"><Anchors><Anchor point="TOPLEFT" relativeTo="SkillRankFrame9" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="0" y="-3"/></Offset></Anchor></Anchors></StatusBar>
<StatusBar name="SkillRankFrame11" inherits="BenillaSkillRankTemplate"><Anchors><Anchor point="TOPLEFT" relativeTo="SkillRankFrame10" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="0" y="-3"/></Offset></Anchor></Anchors></StatusBar>
<StatusBar name="SkillRankFrame12" inherits="BenillaSkillRankTemplate"><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 (SKILLFRAME_SKILLS_DISPLAYED × 18px pitch), TrainerFrame.xml's own
same-TOPLEFT-as-row1 convention. updateFunc set in SkillFrame_OnLoad. -->
<Frame 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>
</Frame>
<!-- The minimal detail pane (this file's header comment, deviation 1): the selected
skill's own rank StatusBar + name — no description/abandon/spend row. Reuses the SAME
rank template the list rows use (its Border click is harmless here: no .skillIndex is
ever set on this instance, so BenillaSkillRankFrame_OnClick's guard no-ops it). -->
<StatusBar name="SkillDetailBar" inherits="BenillaSkillRankTemplate" hidden="true">
<Anchors><Anchor point="TOPLEFT" relativePoint="TOPLEFT"><Offset><AbsDimension x="38" y="-325"/></Offset></Anchor></Anchors>
<Frames>
<!-- The unlearn (abandon) button (ref $parentUnlearnButton, ref SkillFrame.xml
l.705-737, verbatim art/geometry): the red circle-slash right of the bar's
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="SkillDetailUnlearnButton" hidden="true">
<Size><AbsDimension x="32" y="32"/></Size>
<Anchors>
<Anchor point="LEFT" relativeTo="SkillDetailBarBorder" 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>
</StatusBar>
</Frames>
<Scripts>
<OnLoad>SkillFrame_OnLoad()</OnLoad>
<OnShow>SkillFrame_OnShow()</OnShow>
<OnEvent>SkillFrame_OnEvent(event)</OnEvent>
<OnMouseWheel>BenillaSkillFrame_OnMouseWheel(arg1)</OnMouseWheel>
</Scripts>
</Frame>
</Ui>