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

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

1249 lines
84 KiB
XML

<Ui>
<!--
benilla's talent window (decision 0304 §4, "The talent window" — same-session sibling of the
engine seam in `benilla-ui/src/script/talent.rs`): the 384x512 book chassis + the class-art
4-corner backdrop + the 3 tree tabs + the scrolled 8-tier x 4-column talent grid with its
prereq branch/arrow lines, dressed from the real 1.12 **load-on-demand addon**
`Blizzard_TalentUI.xml`/`.lua`/`Blizzard_TalentUITemplates.xml` — extracted from the MPQ
chain to `WoW/_extracted_addons/Blizzard_TalentUI/` in this worktree (line cites below are
into those three files directly).
Docks the left UIPanel slot exactly like Character/SpellBook/Gossip/Merchant/Quest/QuestLog
(`UiPanels.xml`'s `UIPanelWindows["BenillaTalentFrame"] = { area = "left", pushable = 6 }`,
the ref's own `pushable = 6` — one tier above the plain NPC-session windows' `0`, matching the
ref's row exactly) — so it competes for/replaces those windows, never stacks with them.
`ToggleTalentFrame()` (ref `TalentFrame_Toggle`, Blizzard_TalentUI.lua:73-79, RENAMED — see
below) is the show/hide entry point; the app's 'N' binding already calls it by this exact name
(`ui_script/input.rs:198-201`, landed ahead of this file per decision 0304's own pin).
ONE deliberate naming deviation from the ref, applied consistently: every frame this file
defines is Benilla-prefixed (`BenillaTalentFrame`, `BenillaTalentFrameTalent1`, …) — the same
posture every other window in this codebase already takes. Layered on top of that, `Toggle-
TalentFrame` (not the ref's own `TalentFrame_Toggle`) matches this codebase's established
`Toggle<Thing>` shape for a window's addon-facing entry point (`ToggleSpellBook`/
`ToggleCharacter`/`ToggleQuestLog` — SpellBookFrame.xml's header comment makes the identical
choice for the identical reason) — kept BARE, not Benilla-prefixed, matching that precedent.
SCOPE CALL — 3 tabs, not 5. The ref's own `MAX_TALENT_TABS = 5` is defensive headroom; 1.12
vanilla ships EXACTLY 3 talent trees for every class with no exception (Warrior Arms/Fury/
Protection, Mage Arcane/Fire/Frost, …, an unchanging 1.12 game-design fact, not a guess). This
file declares exactly 3 static tab buttons and renumbers the constant `BENILLA_MAX_TALENT_TABS
= 3` (Benilla-prefixed, not bare `MAX_TALENT_TABS`, since its VALUE differs from the ref's own
— a reader must not assume it still means 5). `BenillaTalentFrame_Update` additionally clamps
`PanelTemplates_SetNumTabs`'s own argument to this cap (`min(numTabs, BENILLA_MAX_TALENT_TABS)`
— a small defensive addition beyond the ref, which trusts its own 5 static tabs always
suffice): without it, a hypothetical `GetNumTalentTabs() > 3` would ask `PanelTemplates_
UpdateTabs` to index a `BenillaTalentFrameTab4` that doesn't exist and error. 1.12 data never
exceeds 3 (comment above) — the clamp just makes that assumption SAFE rather than merely
"safe by data luck", the honest version of the same trust the ref's own 5-tab headroom
expresses for ITS OWN assumption.
TAB STRIP — reuses the house's EXISTING tab kit, not a hand-rolled `BenillaTalentFrame.
selectedTab` variable. The task brief's own ENGINE API note flagged `PanelTemplates_*` as
unavailable ("implement your own small select/deselect Lua") — verified false this session:
`UiPanels.xml:502-578` already provides the FULL kit (`PanelTemplates_SetNumTabs/_SetTab/
_GetSelectedTab/_UpdateTabs/_SelectTab/_DeselectTab/_TabResize`), and `CharacterFrameTab-
ButtonTemplate` (`UiPanels.xml:603-654`) is ALREADY battle-tested by CharacterFrame.xml's own
tab. Better still: the REFERENCE's own `TalentTabTemplate` (Blizzard_TalentUITemplates.xml:3)
itself `inherits="CharacterFrameTabButtonTemplate"` — the exact same base template. Reusing
both here is therefore not a shortcut but the MORE faithful, MORE DRY choice: identical art,
identical selected/deselected mechanics, one less parallel implementation to maintain. Only
`TalentTabTemplate`'s OWN additions get a local sub-template (`BenillaTalentTabTemplate`
below): the OnClick + OnLeave (its `PLAYER_LEVEL_UP` level-9 pulse-flash OnLoad/OnEvent is
OMITTED — see Sounds/OMITs below). `TalentFrame.selectedTab` (read directly by the reference's
own `TalentButtonTemplate` inline OnEnter, Blizzard_TalentUITemplates.xml:92) and
`PanelTemplates_GetSelectedTab(TalentFrame)` (read by TalentFrame.lua's own functions) are
THE SAME FIELD — `PanelTemplates_SetTab` stores straight onto `frame.selectedTab` — so this
file spells each call site exactly as the reference itself does, not a benilla-
introduced inconsistency.
SCROLL DESIGN — a real ScrollFrame with a FIXED-HEIGHT child, not the reference's own dynamic
`UIPanelScrollFrameTemplate` (which auto-grows its child via a C-side `UpdateScrollChildRect`
this engine has no equivalent for — QuestLogFrame.xml's own documented gap: "the resolver
never grows a frame to fit content that overflows it"). The grid this window scrolls is
UNLIKE QuestLogFrame's variable quest-text pane: it is always EXACTLY 8 tiers of absolutely-
positioned buttons/branches/arrows, never resized by content — so instead of QuestLogFrame's
per-frame convergence loop (`_ResizeChild`/`_OnUpdate`, needed there because its text height is
data-dependent), the scroll child here is simply sized to a CONSTANT
`BENILLA_TALENT_SCROLL_CHILD_H = 504` (8 tiers x the ref's own 63px pitch — the task's own
sizing note): `INITIAL_TALENT_OFFSET_Y(20) + 7*63 + TALENT_BUTTON_SIZE(32) = 493 < 504`
covers even the lowest tier's own button with 11px to spare, and no branch/arrow ever paints
BELOW its own tier's row (verified against `BenillaTalentFrame_DrawLines` below: a talent's
`.down`/arrows only ever touch tiers at or above its own). No `UpdateScrollChildRect` call is
therefore needed anywhere (dropped from the ref's own per-tier-loop tail) — the range is a
load-time constant, not something to re-derive every Update.
The scrollbar reuses `BenillaScrollBarTemplate` (the real, DRAGGABLE `Slider`, decision 0250,
`ScrollTemplates.xml`) directly — NOT QuestLogFrame.xml's own hand-rolled "visual only, no
drag" bar. That file's header comment explicitly says "this engine has no Slider widget"; that
claim is now STALE (the Slider primitive landed after, decision 0250) — reusing the
since-real thing here is a rebase onto a landed primitive, not a fidelity regression. The
Slider's OWN `OnValueChanged` (inherited from `BenillaScrollBarTemplate`, wired to the FAUX
offset bookkeeping `BenillaFauxScrollBar_OnValueChanged`) is REPLACED (not chained past) by
this file's own override below — verified, not assumed: `apply_scripts`
(`benilla-ui/src/loader/scripts.rs`) walks EVERY `<Scripts>` block found after `inherits=`
merge and calls `SetScript` for each handler in order; `merge()` (`framexml.rs`) appends the
base template's children before the instance's own, so the base's `<Scripts>` registers first
and this file's own registers LAST — `SetScript` is a single-slot assignment, so the last
registration wins outright. `BenillaScrollBar_UpdateArrows`/`_Step` (ScrollTemplates.xml)
are reused as-is: both read only a Slider's own value/range/step, nothing faux-specific.
Named deferrals/OMITs (each re-cited at its own site below):
- `SetButtonPulse` (ref `TalentFrame_OnShow`/`_OnHide`/`TalentFrameTab_OnClick`'s
pulse-clear loop, and `TalentTabTemplate`'s own `PLAYER_LEVEL_UP` level-9 flash-hint
OnLoad/OnEvent): decision 0304's own Scope-calls section, "No microbutton pulse — the
reference pulses TalentMicroButton on point gain". The microbar itself has since landed
(`MicroMenu.xml`) and `UpdateMicroButtons()` is live in OnShow/OnHide below — but the
PULSE stays out: it rides `PLAYER_LEVEL_UP`, which this client never fires. All four
pulse sites drop cleanly (none gate anything else).
- `message(...)` (5 sites: the `numTalents > MAX_NUM_TALENTS` reminder, `_GetArrowTexture`/
`_GetBranchTexture`'s pool-exhaustion notices, and 3 sites in `_DrawLines`'s own layout-
blocked guards): no engine `message()` global exists (grepped `crates/benilla-ui/src`;
GossipFrame.xml's own "too many quests" message() is the same-shaped, already-accepted
OMIT in this codebase). Every site is a defensive diagnostic for a data bug 1.12's own
authored `Talent.dbc` never produces (no tree exceeds 20 talents/tab, no tree is laid out
blocked) — dropped outright, control flow (the `return`s / nil-propagation) kept intact.
The two texture-pool getters additionally gain a nil-guard at their TWO call sites
(`_SetArrowTexture`/`_SetBranchTexture`) beyond the reference's own behavior (which lets
its dropped `message()` call's nil flow straight into a `:SetTexCoord` crash) — a small,
documented safety margin now that the diagnostic is gone, not a behavior change for any
reachable (≤30 branches/arrows) input.
- `UIPanelCloseButton`/`UIPanelButtonTemplate`/`ItemButtonTemplate` (ref base templates):
none ship in this house dialect. The close button uses the house minimize-button art
(SpellBookFrame/CharacterFrame's own precedent); the bottom "Close" button locally
reconstructs `UIPanelButtonTemplate`'s art (`UI-Panel-Button-*`, StackSplit.xml's own
precedent for the identical gap); the talent button locally reconstructs exactly what
`ItemButtonTemplate` (`WoW/_extracted_framexml/ItemButtonTemplate.xml`, read this session)
contributes (37x37, the Quickslot2 ring, the icon Texture child) plus `TalentButtonTemplate`
(Blizzard_TalentUITemplates.xml)'s own additions (the EmptySlot backdrop, RankBorder, Rank
text) — CharacterFrame.xml's own `BenillaPaperDollItemSlotTemplate` is the identical-shape
precedent for this whole reconstruction move.
- `SetItemButtonTexture`: no engine equivalent (ENGINE API note). The icon Texture child
(`$parentIconTexture`) is `SetTexture`'d directly.
- `SetItemButtonDesaturated` IS the ref's own verb here (ItemButtonTemplate.xml, loaded far
above this file in the .toc), called exactly as the ref calls it. It used to be open-coded
as `SetVertexColor(0.65, 0.65, 0.65)` / restored to `(1, 1, 1)`, because the engine had no
desaturation and a dim tint was the whole of what a grey-out could be. Decision 1327 gave
the engine `Texture:SetDesaturated`, so the ref's verb now lands as greyscale AND the 0.65
dim — B162: at 0 talent points every unlearned icon in the real client goes greyscale, and
a 0.65 multiply on colourful art does not read as greyed out at all.
Engine-gap notes (transcribed anyway per this file's own instructions, or worked around):
- `text=` XML attributes are a LITERAL, verbatim `SetText` in this engine — verified,
`benilla-ui/src/loader/regions.rs:222-223` and `loader/widgets.rs:153-154` both pass the
attribute string straight through, no GlobalStrings lookup. The ref's own `text="TALENTS"`/
`text="CHARACTER_POINTS1_COLON"` (both static — neither FontString is EVER touched by
TalentFrame.lua, confirmed by reading the whole file this session) therefore become the
literal, properly-cased resolved strings ("Talents" / "Talent Points:") directly in this
file's XML, CharacterFrame.xml's own `text="Armor:"` precedent for exactly this class of
static label (its `ARMOR_COLON` Lua declaration exists for tooltip composition elsewhere,
never for driving that XML attribute). `TALENTS`/`CHARACTER_POINTS1_COLON` are still
declared below via the `X = X or "…"` idiom for citation/provenance only (unread by any
Lua in this file) — their exact wording is this session's best-known value for the real
1.12 GlobalStrings entry, NOT re-verified against a live extraction (none is present in
this worktree this session; `MASTERY_POINTS_SPENT`'s wording+line came from the task brief
itself and IS load-bearing, since it's genuinely `format()`-composed at runtime below).
- `HitRectInsets`: transcribed for fidelity; no loader wiring exists yet (CharacterFrame.
xml's own precedent note) — a harmless no-op today.
- `UNIT_PORTRAIT_UPDATE`: registered per the ref (`TalentFrame_OnLoad`); this engine never
fires it (grepped `crates/benilla-ui/src`, `crates/benilla/src`) — forward-compatible,
harmless, the same "transcribe the hook even if nothing drives it yet" posture as
HitRectInsets above.
- Bare `min`/`max`/`select` (used unqualified in `_DrawLines`/`_SetPrereqs` below): CONFIRMED
available — `min`/`max` are house-stdlib aliases for `math.min`/`math.max`
(`benilla-ui/src/script/stdlib.rs`), `select` is stock Lua 5.1. The ref's own `arg`/`arg.n`
Lua-5.0 vararg table (`_SetPrereqs`) is written the 5.1-native way
(WorldMapFrame.xml's own identical note) — replaced with `select("#", ...)`/
`select(i, ...)` over a trailing `...`, WorldMapFrame.xml's own precedent for the identical
substitution.
- `GetTalentTabInfo` called TWICE in a row by the ref's own `TalentFrame_Update` (l.152-154:
once truncated to just its first return for the `talentTabName` truthiness check, then
again for the full tuple — a real redundancy in the reference's own code, not a
byte-meaningful pattern) collapses to the ONE call already shared by both uses,
SpellBookFrame.xml's own `ToggleSpellBook` precedent for this exact class of cleanup
("a redundant-code cleanup, not a behavior change").
Sounds (decision 0090, cited verified this session per the task brief): `TalentScreenOpen`/
`TalentScreenClose` on Show/Hide (ids 6144/6145, ref `TalentFrame_OnShow`/`_OnHide`),
`igCharacterInfoTab` on tab click (id 841, ref `TalentFrameTab_OnClick`).
-->
<Script><![CDATA[
-- ── GlobalStrings values this window cites (ref-GlobalStrings.lua) — TALENTS/CHARACTER_
-- POINTS1_COLON are provenance-only (this file's header comment: their FontStrings are
-- static XML text, never Lua-read); MASTERY_POINTS_SPENT IS genuinely format()-composed
-- below and its wording+line are the task brief's own (GlobalStrings:2674). ────────────────
TALENTS = TALENTS or "Talents"
CHARACTER_POINTS1_COLON = CHARACTER_POINTS1_COLON or "Talent Points:"
MASTERY_POINTS_SPENT = MASTERY_POINTS_SPENT or "Points spent in %s Talents:" -- :2674
-- ── This window's own constants ─────────────────────────────────────────────────────────
--
-- Spec: ref Blizzard_TalentUI.lua:2-11. The NAMES and VALUES are the contract — a tree's
-- data is laid out against them and addons read them — so those are the reference's; the
-- grouping is ours, by what each one is for rather than by the order they happen to appear.
-- The tree's shape: eight rows of four cells, never more than twenty talents in one tree.
MAX_NUM_TALENT_TIERS = 8
NUM_TALENT_COLUMNS = 4
MAX_NUM_TALENTS = 20
-- Where that shape lands inside the scroll child: the first cell sits INITIAL_TALENT_
-- OFFSET_X/Y in from its top-left corner and every next one is a PITCH further along.
-- TALENT_BUTTON_SIZE is how far the connector art reaches out of a cell, NOT the talent
-- button's own 37px size — the two are independent (this file's header comment, "The
-- talent button"). Naming the pitch is what let the two places that used to spell out
-- `((n - 1) * 63) + INITIAL_TALENT_OFFSET_…` — the button placement and the connector draw
-- — collapse into the one `talentCellOrigin` below, so they cannot drift apart.
INITIAL_TALENT_OFFSET_X = 35
INITIAL_TALENT_OFFSET_Y = 20
TALENT_BUTTON_SIZE = 32
BENILLA_TALENT_GRID_PITCH = 63
-- The connector art comes out of two fixed pools, claimed in order by each repaint and
-- hidden again past the last one claimed (BenillaTalentFrame_Get*Texture below).
MAX_NUM_BRANCH_TEXTURES = 30
MAX_NUM_ARROW_TEXTURES = 30
-- One drawing-state cell per grid position — built once in OnLoad, cleared (never rebuilt)
-- by every repaint. What a cell records is written down at `clearBranchCell` below.
TALENT_BRANCH_ARRAY = {}
-- Benilla-prefixed + renumbered (5 -> 3): this file's header comment, "SCOPE CALL".
BENILLA_MAX_TALENT_TABS = 3
-- The scroll child's fixed height: this file's header comment, "SCROLL DESIGN".
BENILLA_TALENT_SCROLL_CHILD_H = 504
BENILLA_TALENT_SCROLL_FRAME_H = 332 -- the ref's own TalentFrameScrollFrame height (ref l.278)
BENILLA_TALENT_SCROLL_MAX = BENILLA_TALENT_SCROLL_CHILD_H - BENILLA_TALENT_SCROLL_FRAME_H
BENILLA_TALENT_SCROLL_STEP = 20 -- one wheel notch / arrow-click step (QuestLogFrame.xml's
-- own BENILLA_QUESTLOG_SCROLL_STEP precedent for a real
-- ScrollFrame's per-notch pixel step)
-- ── The branch/arrow atlases: CUT from the sheet, not typed out ─────────────────────────
--
-- Spec: the same two tables the reference builds (ref Blizzard_TalentUI.lua:13-69) — same
-- global names, same [direction][+1/-1] shape, same quads to the bit. Written our own way,
-- because what these are is not 104 fractions: both sheets are ONE STRIP of 32px cells
-- with the lit variant on the top row and the greyed variant directly beneath it, so a
-- direction is fully described by three facts — which cell carries its art, whether it is
-- drawn u-reversed, and how tall the drawn part of a row is. Those facts are the spec
-- below and the loop under them cuts the quads, which is both shorter and legible: the
-- strip's layout is visible in the source instead of implied by the numbers.
--
-- The grid is MEASURED, not inferred from the fractions — both BLPs pulled out of the
-- live patch chain this session (`mpqx <Data> 'Interface\TalentFrame\UI-Talent*.blp'`,
-- dimensions off the BLP2 header):
-- UI-TalentBranches 256 x 64 — seven 32px cells on a 33px pitch (x = 0, 33, 66, …)
-- UI-TalentArrows 64 x 64 — two 32px cells, butted, no gutter
-- Every edge is therefore n/256 or n/64 — exact binary fractions, so the divides below are
-- exact in doubles and these quads are bit-identical to the ones the reference lists.
--
-- Three things in the data that look like mistakes and are the sheet's own:
-- · `left` and `right` are the SAME quad. There is one piece of horizontal branch art;
-- which side of the button it lands on is the draw loop's doing (xOffset -/+
-- TALENT_BUTTON_SIZE in _Update below), not the sheet's.
-- · the corner pieces are drawn in ONE handedness — `topleft`/`bottomleft` are
-- `topright`/`bottomright` with u reversed (SetTexCoord reads left > right as a
-- mirror), which is how one drawing serves both.
-- · the two VERTICAL pieces take a 31px row, leaving a 2px gutter across the sheet's
-- middle, where every other cell takes the flat 32. The greyed row stays the lit
-- row's reflection through the midline either way — one `rowH` drives both.
local function talentSheetQuads(sheetW, sheetH, cellW, pitch, spec)
local quads = {}
for name, cell in pairs(spec) do
local u0, u1 = cell[1] * pitch / sheetW, (cell[1] * pitch + cellW) / sheetW
if cell.flip then u0, u1 = u1, u0 end
local v = (cell.rowH or cellW) / sheetH
quads[name] = { [1] = { u0, u1, 0, v }, [-1] = { u0, u1, 1 - v, 1.0 } }
end
return quads
end
-- Which cell serves which direction (the reference's own key names, which is where the
-- reading of each cell's art comes from): 0 `down`, 1 `up`, 2 the horizontal run, 3 the
-- bottom corners, 4 the top corners, 5 the T pointing down, 6 the T pointing up.
TALENT_BRANCH_TEXTURECOORDS = talentSheetQuads(256, 64, 32, 33, {
down = { 0, rowH = 31 },
up = { 1, rowH = 31 },
left = { 2 },
right = { 2 },
bottomright = { 3 },
bottomleft = { 3, flip = true },
topright = { 4 },
topleft = { 4, flip = true },
tdown = { 5 },
tup = { 6 },
})
-- cell 0 is the arrow that points up the column; cell 1 is the sideways one, mirrored for
-- the other hand.
TALENT_ARROW_TEXTURECOORDS = talentSheetQuads(64, 64, 32, 32, {
top = { 0 },
left = { 1 },
right = { 1, flip = true },
})
-- ── The branch grid ─────────────────────────────────────────────────────────────────────
--
-- A cell of TALENT_BRANCH_ARRAY holds the talent that occupies its grid position (`id`),
-- and the drawing state of the connector art passing THROUGH that position: four half-
-- branches (`up`/`down`/`left`/`right`, each the piece of a segment on this cell's side of
-- the join) and three arrowheads. Every one of those is either 0 — nothing drawn here — or
-- the ±1 that the draw pass indexes TALENT_BRANCH_TEXTURECOORDS/TALENT_ARROW_TEXTURECOORDS
-- by: +1 the lit art, -1 the greyed. That is the whole reason a met and an unmet prereq are
-- one code path in two colours rather than two code paths.
--
-- Clearing rather than rebuilding keeps the 32 cells allocated exactly once (OnLoad), and
-- puts their field set in ONE place instead of two that can drift apart.
local function clearBranchCell(cell)
cell.id = nil
cell.up, cell.down, cell.left, cell.right = 0, 0, 0, 0
cell.topArrow, cell.leftArrow, cell.rightArrow = 0, 0, 0
return cell
end
-- A grid cell's top-left corner inside the scroll child. The talent button seats exactly on
-- it; the connector art seats at the reference's own 2px inset from it (+2 x, -2 y).
local function talentCellOrigin(tier, column)
return (column - 1) * BENILLA_TALENT_GRID_PITCH + INITIAL_TALENT_OFFSET_X,
-((tier - 1) * BENILLA_TALENT_GRID_PITCH) - INITIAL_TALENT_OFFSET_Y
end
-- ── The window: toggle (ref TalentFrame_Toggle, l.73-79) ────────────────────────────────
-- Renamed ToggleTalentFrame — this file's header comment, "ONE deliberate naming deviation".
-- Kept BARE (not Benilla-prefixed) — the addon-facing entry point; the 'N' keybinding
-- already calls this by name (ui_script/input.rs).
function ToggleTalentFrame()
if BenillaTalentFrame:IsVisible() then
HideUIPanel(BenillaTalentFrame)
else
ShowUIPanel(BenillaTalentFrame)
end
end
-- ref TalentFrame_OnLoad (l.81-93).
function BenillaTalentFrame_OnLoad(frame)
PanelTemplates_SetNumTabs(frame, BENILLA_MAX_TALENT_TABS)
PanelTemplates_SetTab(frame, 1)
frame:RegisterEvent("CHARACTER_POINTS_CHANGED")
frame:RegisterEvent("SPELLS_CHANGED")
-- UNIT_PORTRAIT_UPDATE: this file's header comment (engine-gap notes) — never fired
-- today, registered anyway for ref fidelity/forward-compat.
frame:RegisterEvent("UNIT_PORTRAIT_UPDATE")
-- The grid's one allocation: every repaint clears these same cells in place.
for i = 1, MAX_NUM_TALENT_TIERS do
TALENT_BRANCH_ARRAY[i] = {}
for j = 1, NUM_TALENT_COLUMNS do
TALENT_BRANCH_ARRAY[i][j] = clearBranchCell({})
end
end
end
-- ref TalentFrame_OnShow (l.95-103): SetButtonPulse still OMITTED (decision 0304's own "No
-- microbutton pulse" scope call — the pulse rides PLAYER_LEVEL_UP, which this client never
-- fires). UpdateMicroButtons is LIVE as of the micro-menu slice (MicroMenu.xml).
function BenillaTalentFrame_OnShow()
if UpdateMicroButtons then UpdateMicroButtons() end
PlaySound("TalentScreenOpen")
BenillaTalentFrame_Update()
end
-- ref TalentFrame_OnHide (l.105-108).
function BenillaTalentFrame_OnHide()
if UpdateMicroButtons then UpdateMicroButtons() end
PlaySound("TalentScreenClose")
end
-- ref TalentFrame_OnEvent (l.110-118) — no IsVisible() guard: followed exactly as THIS
-- reference does it (it refreshes even while hidden; a different window guarding its own
-- OnEvent, e.g. SpellBookFrame, is that window's own choice, not borrowed here).
function BenillaTalentFrame_OnEvent(frame)
if event == "CHARACTER_POINTS_CHANGED" or event == "SPELLS_CHANGED" then
BenillaTalentFrame_Update()
elseif event == "UNIT_PORTRAIT_UPDATE" then
if arg1 == "player" then
SetPortraitTexture(getglobal("BenillaTalentFramePortrait"), "player")
end
end
end
-- ref TalentFrameTalent_OnEvent (Blizzard_TalentUITemplates.xml:120-124) — the hovered
-- button's own CHARACTER_POINTS_CHANGED re-poll. GameTooltip:IsOwned VERIFIED present
-- (`benilla-ui/src/script/tooltip/verbs.rs:107`), used as-is (not an emulation).
function BenillaTalentButton_OnEvent(button)
if GameTooltip:IsOwned(button) then
GameTooltip:SetTalent(PanelTemplates_GetSelectedTab(BenillaTalentFrame), button:GetID())
end
end
-- ref TalentFrame_Update (l.126-321) — the big one. See this file's header comment for the
-- SetItemButtonTexture/SetItemButtonDesaturated substitution, the message() drops, and the
-- GetTalentTabInfo double-call collapse.
function BenillaTalentFrame_Update()
-- Setup Tabs
local tab, name, iconTexture, pointsSpent, button
local numTabs = GetNumTalentTabs()
for i = 1, BENILLA_MAX_TALENT_TABS do
tab = getglobal("BenillaTalentFrameTab" .. i)
if i <= numTabs then
name, iconTexture, pointsSpent = GetTalentTabInfo(i)
if i == PanelTemplates_GetSelectedTab(BenillaTalentFrame) then
-- If tab is the selected tab set the points spent info
getglobal("BenillaTalentFrameSpentPoints"):SetText(
format(MASTERY_POINTS_SPENT, name) .. " " ..
HIGHLIGHT_FONT_COLOR_CODE .. pointsSpent .. FONT_COLOR_CODE_CLOSE)
BenillaTalentFrame.pointsSpent = pointsSpent
end
tab:SetText(name)
PanelTemplates_TabResize(10, tab)
tab:Show()
else
tab:Hide()
end
end
-- Defensive clamp beyond the ref: this file's header comment ("SCOPE CALL").
PanelTemplates_SetNumTabs(BenillaTalentFrame, min(numTabs, BENILLA_MAX_TALENT_TABS))
PanelTemplates_UpdateTabs(BenillaTalentFrame)
-- Setup Frame
SetPortraitTexture(getglobal("BenillaTalentFramePortrait"), "player")
BenillaTalentFrame_UpdateTalentPoints()
local selectedTab = PanelTemplates_GetSelectedTab(BenillaTalentFrame)
-- ref l.152-154's own double GetTalentTabInfo call collapses to one — this file's
-- header comment ("GetTalentTabInfo called TWICE").
local talentTabName, texture, points, fileName = GetTalentTabInfo(selectedTab)
local base
if talentTabName then
base = "Interface\\TalentFrame\\" .. fileName .. "-"
else
-- temporary default for classes without talents poor guys (ref's own comment)
base = "Interface\\TalentFrame\\MageFire-"
end
getglobal("BenillaTalentFrameBackgroundTopLeft"):SetTexture(base .. "TopLeft")
getglobal("BenillaTalentFrameBackgroundTopRight"):SetTexture(base .. "TopRight")
getglobal("BenillaTalentFrameBackgroundBottomLeft"):SetTexture(base .. "BottomLeft")
getglobal("BenillaTalentFrameBackgroundBottomRight"):SetTexture(base .. "BottomRight")
local numTalents = GetNumTalents(selectedTab)
-- ref's own "Too many talents in talent frame!" message() DROPPED — this file's header
-- comment (no engine message() global; a defensive diagnostic 1.12 data never trips).
BenillaTalentFrame_ResetBranches()
local tier, column, rank, maxRank, isExceptional, meetsPrereq
local forceDesaturated, tierUnlocked
for i = 1, MAX_NUM_TALENTS do
button = getglobal("BenillaTalentFrameTalent" .. i)
if i <= numTalents then
-- Set the button info
name, iconTexture, tier, column, rank, maxRank, isExceptional, meetsPrereq =
GetTalentInfo(selectedTab, i)
local buttonName = "BenillaTalentFrameTalent" .. i
getglobal(buttonName .. "Rank"):SetText(rank)
BenillaSetTalentButtonLocation(button, tier, column)
TALENT_BRANCH_ARRAY[tier][column].id = button:GetID()
-- If player has no talent points then show only talents with points in them
if BenillaTalentFrame.talentPoints <= 0 and rank == 0 then
forceDesaturated = 1
else
forceDesaturated = nil
end
-- If the player has spent at least 5 talent points in the previous tier
if (tier - 1) * 5 <= BenillaTalentFrame.pointsSpent then
tierUnlocked = 1
else
tierUnlocked = nil
end
-- SetItemButtonTexture(button, iconTexture): this file's header comment (ENGINE
-- API note) — set the icon Texture child directly. A nil texture (shouldn't
-- happen once fed, ui_talent.rs's own Talent.dbc x Spell.dbc join) leaves
-- whatever was last shown rather than erroring on a nil SetTexture.
if iconTexture then
getglobal(buttonName .. "IconTexture"):SetTexture(iconTexture)
end
-- Talent must meet prereqs or the player must have no points to spend
if BenillaTalentFrame_SetPrereqs(tier, column, forceDesaturated, tierUnlocked,
GetTalentPrereqs(selectedTab, i)) and meetsPrereq then
SetItemButtonDesaturated(button, nil)
if rank < maxRank then
-- Rank is green if not maxed out
getglobal(buttonName .. "Slot"):SetVertexColor(0.1, 1.0, 0.1)
getglobal(buttonName .. "Rank"):SetTextColor(
GREEN_FONT_COLOR.r, GREEN_FONT_COLOR.g, GREEN_FONT_COLOR.b)
else
getglobal(buttonName .. "Slot"):SetVertexColor(1.0, 0.82, 0)
getglobal(buttonName .. "Rank"):SetTextColor(
NORMAL_FONT_COLOR.r, NORMAL_FONT_COLOR.g, NORMAL_FONT_COLOR.b)
end
getglobal(buttonName .. "RankBorder"):Show()
getglobal(buttonName .. "Rank"):Show()
else
SetItemButtonDesaturated(button, 1, 0.65, 0.65, 0.65)
getglobal(buttonName .. "Slot"):SetVertexColor(0.5, 0.5, 0.5)
if rank == 0 then
getglobal(buttonName .. "RankBorder"):Hide()
getglobal(buttonName .. "Rank"):Hide()
else
getglobal(buttonName .. "RankBorder"):SetVertexColor(0.5, 0.5, 0.5)
getglobal(buttonName .. "Rank"):SetTextColor(
GRAY_FONT_COLOR.r, GRAY_FONT_COLOR.g, GRAY_FONT_COLOR.b)
end
end
button:Show()
else
button:Hide()
end
end
-- Now paint the routes the pass above marked onto the grid (spec: ref l.232-320): walk
-- every cell in order, claim a pooled texture for each piece of art it asks for, and
-- hide whatever is left of the pools at the end. UpdateScrollChildRect() is DROPPED
-- from the tier loop's tail — this file's header comment ("SCROLL DESIGN": a fixed-
-- height child never needs its range re-derived).
--
-- `ignoreUp` deliberately survives from one cell to the NEXT: an empty cell carrying a
-- straight vertical through it draws its own `down` piece one row lower, so the cell
-- below must not draw the `up` piece again on top of it.
local node
local xOffset, yOffset
local ignoreUp
local tempNode
BenillaTalentFrame_ResetBranchTextureCount()
BenillaTalentFrame_ResetArrowTextureCount()
for i = 1, MAX_NUM_TALENT_TIERS do
for j = 1, NUM_TALENT_COLUMNS do
node = TALENT_BRANCH_ARRAY[i][j]
-- The cell's own corner, then the connector art's inset inside it.
xOffset, yOffset = talentCellOrigin(i, j)
xOffset, yOffset = xOffset + 2, yOffset - 2
if node.id then
-- Has talent
if node.up ~= 0 then
if not ignoreUp then
BenillaTalentFrame_SetBranchTexture(i, j,
TALENT_BRANCH_TEXTURECOORDS["up"][node.up], xOffset, yOffset + TALENT_BUTTON_SIZE)
else
ignoreUp = nil
end
end
if node.down ~= 0 then
BenillaTalentFrame_SetBranchTexture(i, j,
TALENT_BRANCH_TEXTURECOORDS["down"][node.down], xOffset, yOffset - TALENT_BUTTON_SIZE + 1)
end
if node.left ~= 0 then
BenillaTalentFrame_SetBranchTexture(i, j,
TALENT_BRANCH_TEXTURECOORDS["left"][node.left], xOffset - TALENT_BUTTON_SIZE, yOffset)
end
if node.right ~= 0 then
-- See if any connecting branches are gray and if so color them gray
tempNode = TALENT_BRANCH_ARRAY[i][j + 1]
if tempNode.left ~= 0 and tempNode.down < 0 then
BenillaTalentFrame_SetBranchTexture(i, j - 1,
TALENT_BRANCH_TEXTURECOORDS["right"][tempNode.down], xOffset + TALENT_BUTTON_SIZE, yOffset)
else
BenillaTalentFrame_SetBranchTexture(i, j,
TALENT_BRANCH_TEXTURECOORDS["right"][node.right], xOffset + TALENT_BUTTON_SIZE + 1, yOffset)
end
end
-- Draw arrows
if node.rightArrow ~= 0 then
BenillaTalentFrame_SetArrowTexture(i, j,
TALENT_ARROW_TEXTURECOORDS["right"][node.rightArrow], xOffset + TALENT_BUTTON_SIZE / 2 + 5, yOffset)
end
if node.leftArrow ~= 0 then
BenillaTalentFrame_SetArrowTexture(i, j,
TALENT_ARROW_TEXTURECOORDS["left"][node.leftArrow], xOffset - TALENT_BUTTON_SIZE / 2 - 5, yOffset)
end
if node.topArrow ~= 0 then
BenillaTalentFrame_SetArrowTexture(i, j,
TALENT_ARROW_TEXTURECOORDS["top"][node.topArrow], xOffset, yOffset + TALENT_BUTTON_SIZE / 2 + 5)
end
else
-- Doesn't have a talent
if node.up ~= 0 and node.left ~= 0 and node.right ~= 0 then
BenillaTalentFrame_SetBranchTexture(i, j, TALENT_BRANCH_TEXTURECOORDS["tup"][node.up], xOffset, yOffset)
elseif node.down ~= 0 and node.left ~= 0 and node.right ~= 0 then
BenillaTalentFrame_SetBranchTexture(i, j, TALENT_BRANCH_TEXTURECOORDS["tdown"][node.down], xOffset, yOffset)
elseif node.left ~= 0 and node.down ~= 0 then
BenillaTalentFrame_SetBranchTexture(i, j, TALENT_BRANCH_TEXTURECOORDS["topright"][node.left], xOffset, yOffset)
BenillaTalentFrame_SetBranchTexture(i, j, TALENT_BRANCH_TEXTURECOORDS["down"][node.down], xOffset, yOffset - 32)
elseif node.left ~= 0 and node.up ~= 0 then
BenillaTalentFrame_SetBranchTexture(i, j, TALENT_BRANCH_TEXTURECOORDS["bottomright"][node.left], xOffset, yOffset)
elseif node.left ~= 0 and node.right ~= 0 then
BenillaTalentFrame_SetBranchTexture(i, j, TALENT_BRANCH_TEXTURECOORDS["right"][node.right], xOffset + TALENT_BUTTON_SIZE, yOffset)
BenillaTalentFrame_SetBranchTexture(i, j, TALENT_BRANCH_TEXTURECOORDS["left"][node.left], xOffset + 1, yOffset)
elseif node.right ~= 0 and node.down ~= 0 then
BenillaTalentFrame_SetBranchTexture(i, j, TALENT_BRANCH_TEXTURECOORDS["topleft"][node.right], xOffset, yOffset)
BenillaTalentFrame_SetBranchTexture(i, j, TALENT_BRANCH_TEXTURECOORDS["down"][node.down], xOffset, yOffset - 32)
elseif node.right ~= 0 and node.up ~= 0 then
BenillaTalentFrame_SetBranchTexture(i, j, TALENT_BRANCH_TEXTURECOORDS["bottomleft"][node.right], xOffset, yOffset)
elseif node.up ~= 0 and node.down ~= 0 then
BenillaTalentFrame_SetBranchTexture(i, j, TALENT_BRANCH_TEXTURECOORDS["up"][node.up], xOffset, yOffset)
BenillaTalentFrame_SetBranchTexture(i, j, TALENT_BRANCH_TEXTURECOORDS["down"][node.down], xOffset, yOffset - 32)
ignoreUp = 1
end
end
end
end
-- Hide any unused branch textures
for i = BenillaTalentFrame_GetBranchTextureCount(), MAX_NUM_BRANCH_TEXTURES do
getglobal("BenillaTalentFrameBranch" .. i):Hide()
end
-- Hide any unused arrow textures
for i = BenillaTalentFrame_GetArrowTextureCount(), MAX_NUM_ARROW_TEXTURES do
getglobal("BenillaTalentFrameArrow" .. i):Hide()
end
end
function BenillaTalentFrame_SetArrowTexture(tier, column, texCoords, xOffset, yOffset)
local arrowTexture = BenillaTalentFrame_GetArrowTexture()
-- nil-guard beyond the ref: this file's header comment (message() drop).
if not arrowTexture then return end
arrowTexture:SetTexCoord(texCoords[1], texCoords[2], texCoords[3], texCoords[4])
arrowTexture:SetPoint("TOPLEFT", "BenillaTalentFrameScrollChild", "TOPLEFT", xOffset, yOffset)
end
function BenillaTalentFrame_SetBranchTexture(tier, column, texCoords, xOffset, yOffset)
local branchTexture = BenillaTalentFrame_GetBranchTexture()
if not branchTexture then return end
branchTexture:SetTexCoord(texCoords[1], texCoords[2], texCoords[3], texCoords[4])
branchTexture:SetPoint("TOPLEFT", "BenillaTalentFrameScrollChild", "TOPLEFT", xOffset, yOffset)
end
-- ref TalentFrame_GetArrowTexture (l.335-344): message() DROPPED (this file's header
-- comment) — the exhaustion path otherwise matches the ref's own (nil past the 30th; both
-- callers above now guard it, unlike the ref's own dropped-message-then-crash shape).
function BenillaTalentFrame_GetArrowTexture()
local arrowTexture = getglobal("BenillaTalentFrameArrow" .. BenillaTalentFrame.arrowIndex)
BenillaTalentFrame.arrowIndex = BenillaTalentFrame.arrowIndex + 1
if arrowTexture then
arrowTexture:Show()
end
return arrowTexture
end
function BenillaTalentFrame_GetBranchTexture()
local branchTexture = getglobal("BenillaTalentFrameBranch" .. BenillaTalentFrame.textureIndex)
BenillaTalentFrame.textureIndex = BenillaTalentFrame.textureIndex + 1
if branchTexture then
branchTexture:Show()
end
return branchTexture
end
function BenillaTalentFrame_ResetArrowTextureCount()
BenillaTalentFrame.arrowIndex = 1
end
function BenillaTalentFrame_ResetBranchTextureCount()
BenillaTalentFrame.textureIndex = 1
end
function BenillaTalentFrame_GetArrowTextureCount()
return BenillaTalentFrame.arrowIndex
end
function BenillaTalentFrame_GetBranchTextureCount()
return BenillaTalentFrame.textureIndex
end
-- ref TalentFrame_SetPrereqs (l.373-395) — the Lua 5.0 `arg`/`arg.n` vararg table replaced
-- with `select` (this file's header comment, WorldMapFrame.xml's own precedent). The four
-- named leading params already consume the ref's arg[1..4]; the trailing `...` is exactly
-- the ref's arg[5..n] prereq triplets.
function BenillaTalentFrame_SetPrereqs(buttonTier, buttonColumn, forceDesaturated, tierUnlocked, ...)
local requirementsMet
if tierUnlocked and not forceDesaturated then
requirementsMet = 1
else
requirementsMet = nil
end
local n = select("#", ...)
for i = 1, n, 3 do
local tier, column, isLearnable = select(i, ...)
if not isLearnable or forceDesaturated then
requirementsMet = nil
end
BenillaTalentFrame_DrawLines(buttonTier, buttonColumn, tier, column, requirementsMet)
end
return requirementsMet
end
-- ── Routing one prereq onto the grid ────────────────────────────────────────────────────
--
-- Spec: ref TalentFrame_DrawLines (l.398-527). A route is never stored as a path; each cell
-- it crosses simply records which of its own half-branches and arrowheads the route lit,
-- and the value recorded is the route's STATE — +1 met, -1 unmet — which is the very key
-- the draw pass looks the art up by (`clearBranchCell` above).
--
-- The one rule every leg obeys: a route may not run through a cell that holds a talent of
-- its own, because the connector art would land under that button. Each leg scans its span
-- first and abandons the whole route rather than draw over one. (The reference raises a
-- `message()` at three of those points; the diagnostic is dropped and the abandonment kept
-- — this file's header comment, "message()". No 1.12 tree trips any of them.)
--
-- These five are the grid's vocabulary; the routing below is written entirely in them.
-- One link of a vertical run: the lower half of the upper cell plus the upper half of the
-- cell beneath it. Two half-branches are what make one drawn segment.
local function linkDown(tier, column, state)
TALENT_BRANCH_ARRAY[tier][column].down = state
TALENT_BRANCH_ARRAY[tier + 1][column].up = state
end
-- The same across, left cell to right cell.
local function linkRight(tier, column, state)
TALENT_BRANCH_ARRAY[tier][column].right = state
TALENT_BRANCH_ARRAY[tier][column + 1].left = state
end
-- Does a talent sit anywhere in this span of one tier / of one column? (An empty span —
-- adjacent cells with nothing between them — is trivially clear.)
local function occupiedAcross(tier, fromColumn, toColumn)
for i = fromColumn, toColumn do
if TALENT_BRANCH_ARRAY[tier][i].id then return true end
end
return false
end
local function occupiedDown(column, fromTier, toTier)
for i = fromTier, toTier do
if TALENT_BRANCH_ARRAY[i][column].id then return true end
end
return false
end
-- The arrowhead on the dependent talent's own cell, pointing back along the way the route
-- came in — right if the prereq lies to the right of us, left if it lies to the left.
local function pointArrowSideways(cell, buttonColumn, column, state)
if buttonColumn < column then
cell.rightArrow = state
else
cell.leftArrow = state
end
end
-- Mark the route from a prerequisite at (tier, column) to the talent that depends on it at
-- (buttonTier, buttonColumn). Called once per prereq by BenillaTalentFrame_SetPrereqs.
function BenillaTalentFrame_DrawLines(buttonTier, buttonColumn, tier, column, requirementsMet)
local state = -1
if requirementsMet then state = 1 end
-- The dependent talent's own cell: SetPrereqs' caller has already stamped its `id`, so
-- it always exists. Every branch below ends by marking an arrowhead on it.
local cell = TALENT_BRANCH_ARRAY[buttonTier][buttonColumn]
-- STRAIGHT UP THE COLUMN. One vertical run, and the last link stops half short: the
-- talent's own cell wears the arrowhead where an `up` stub would otherwise go.
if buttonColumn == column then
if occupiedDown(column, tier + 1, buttonTier - 1) then return end
for i = tier, buttonTier - 1 do
if i + 1 < buttonTier then
linkDown(i, column, state)
else
TALENT_BRANCH_ARRAY[i][column].down = state
end
end
cell.topArrow = state
return
end
-- ALONG THE TIER. One horizontal run between the two columns, arrowhead on the side
-- the prereq lies. Both halves of every join are marked, including the talent's own.
if buttonTier == tier then
local left, right = min(buttonColumn, column), max(buttonColumn, column)
if occupiedAcross(tier, left + 1, right - 1) then return end
for i = left, right - 1 do
linkRight(tier, i, state)
end
pointArrowSideways(cell, buttonColumn, column, state)
return
end
-- DIAGONAL, so the route must turn one corner, and there are two corners to choose
-- from. Preferred: run ACROSS the prereq's own tier to our column, then DOWN it. What
-- has to be clear for that is the prereq's tier from the cell NEXT to the prereq
-- through to the corner at our own column — the prereq's own cell is where the route
-- starts, so it is never in its own way.
local left, right = min(buttonColumn, column), max(buttonColumn, column)
local from, to = left, right
if left == column then from = left + 1 else to = right - 1 end
if not occupiedAcross(tier, from, to) then
-- The corner and our own cell's top half, marked outright: the loop below covers
-- both, EXCEPT when the prereq sits below us and the run is empty.
TALENT_BRANCH_ARRAY[tier][buttonColumn].down = state
cell.up = state
for i = tier, buttonTier - 1 do
linkDown(i, buttonColumn, state)
end
for i = left, right - 1 do
linkRight(tier, i, state)
end
cell.topArrow = state
return
end
-- That corner is blocked, so take the other one: DOWN the prereq's column first, then
-- across our own tier — which now has to be the clear span, from our cell to the corner.
from, to = left, right
if left == buttonColumn then from = left + 1 else to = right - 1 end
if occupiedAcross(buttonTier, from, to) then return end
-- Two reference quirks live in these three lines and BOTH are deliberately kept, because
-- they are what the real client draws: the across leg is never marked at all (the
-- reference's own two lines for it are commented out at Blizzard_TalentUI.lua:508-509),
-- and each vertical segment is attributed to the OUTER halves of its two cells rather
-- than the facing ones — the mirror image of `linkDown`, which is why that helper is not
-- used here.
for i = tier, buttonTier - 1 do
TALENT_BRANCH_ARRAY[i][column].up = state
TALENT_BRANCH_ARRAY[i + 1][column].down = state
end
pointArrowSideways(cell, buttonColumn, column, state)
end
-- ref TalentFrameTalent_OnClick (Blizzard_TalentUITemplates.xml:97-99).
function BenillaTalentButton_OnClick(button)
LearnTalent(PanelTemplates_GetSelectedTab(BenillaTalentFrame), button:GetID())
end
-- ref TalentFrameTab_OnClick (l.533-540): the SetButtonPulse-clearing loop OMITTED — this
-- file's header comment (decision 0304's own "No microbutton pulse" scope call).
function BenillaTalentFrameTab_OnClick(tab)
PanelTemplates_SetTab(BenillaTalentFrame, tab:GetID())
BenillaTalentFrame_Update()
PlaySound("igCharacterInfoTab")
end
-- ref TalentFrame_UpdateTalentPoints (l.543-547).
function BenillaTalentFrame_UpdateTalentPoints()
local cp1, cp2 = UnitCharacterPoints("player")
getglobal("BenillaTalentFrameTalentPointsText"):SetText(cp1)
BenillaTalentFrame.talentPoints = cp1
end
-- Seat a talent button on its grid cell (spec: ref SetTalentButtonLocation, l.549-553) —
-- the same corner the connector art insets from, so the two can never drift apart.
function BenillaSetTalentButtonLocation(button, tier, column)
local x, y = talentCellOrigin(tier, column)
button:SetPoint("TOPLEFT", button:GetParent(), "TOPLEFT", x, y)
end
-- Back to a blank grid before a repaint re-derives it (spec: ref TalentFrame_ResetBranches,
-- l.555-568). The field set lives at `clearBranchCell`, shared with OnLoad's build.
function BenillaTalentFrame_ResetBranches()
for i = 1, MAX_NUM_TALENT_TIERS do
for j = 1, NUM_TALENT_COLUMNS do
clearBranchCell(TALENT_BRANCH_ARRAY[i][j])
end
end
end
-- ref TalentButtonTemplate's own inline OnEnter (Blizzard_TalentUITemplates.xml:90-93) —
-- `BenillaTalentFrame.selectedTab` read directly, matching the ref's own choice at THIS
-- site (this file's header comment, "TAB STRIP").
function BenillaTalentButton_OnEnter(button)
GameTooltip:SetOwner(button, "ANCHOR_RIGHT")
GameTooltip:SetTalent(BenillaTalentFrame.selectedTab, button:GetID())
end
-- ── The scroll frame (this file's header comment, "SCROLL DESIGN") ──────────────────────
function BenillaTalentFrameScroll_OnLoad(scrollFrame)
scrollFrame:SetScrollChild(getglobal("BenillaTalentFrameScrollChild"))
local bar = getglobal("BenillaTalentFrameScrollBar")
bar:SetMinMaxValues(0, BENILLA_TALENT_SCROLL_MAX)
bar:SetValueStep(BENILLA_TALENT_SCROLL_STEP)
bar:SetValue(0)
end
-- The real-ScrollFrame half of the documented Slider<->ScrollFrame wiring (`slider.rs`'s
-- own header comment: "OnValueChanged -> this:GetParent():SetVerticalScroll(arg1)") —
-- REPLACES BenillaScrollBarTemplate's inherited faux-offset handler (this file's header
-- comment, verified via loader/scripts.rs). BenillaScrollBar_UpdateArrows is faux-
-- agnostic (ScrollTemplates.xml's own body) and reused as-is for the arrow enable state.
function BenillaTalentFrameScrollBar_OnValueChanged(scrollBar, value)
scrollBar:GetParent():SetVerticalScroll(value)
BenillaScrollBar_UpdateArrows(scrollBar)
end
]]></Script>
<!--
The tree tab (ref TalentTabTemplate, Blizzard_TalentUITemplates.xml:3-23): inherits the
house's own CharacterFrameTabButtonTemplate (UiPanels.xml) — this file's header comment,
"TAB STRIP". The ref's own PLAYER_LEVEL_UP OnLoad/OnEvent (the level-9 pulse-flash hint) is
OMITTED with SetButtonPulse (this file's header comment); the ref's own blank OnEnter has
nothing to transcribe either.
-->
<Button name="BenillaTalentTabTemplate" inherits="CharacterFrameTabButtonTemplate" virtual="true">
<Scripts>
<OnClick>BenillaTalentFrameTab_OnClick(self)</OnClick>
<OnLeave>GameTooltip:Hide()</OnLeave>
</Scripts>
</Button>
<!-- The branch/arrow art pool templates (ref TalentBranchTemplate/TalentArrowTemplate,
Blizzard_TalentUITemplates.xml:24-39, verbatim: 32x32, default TOPLEFT anchor — every
instance below is repositioned every Update via SetPoint, this default is just where an
instance sits before its first placement). -->
<Texture name="BenillaTalentBranchTemplate" file="Interface\TalentFrame\UI-TalentBranches" virtual="true">
<Size><AbsDimension x="32" y="32"/></Size>
<Anchors><Anchor point="TOPLEFT"/></Anchors>
</Texture>
<Texture name="BenillaTalentArrowTemplate" file="Interface\TalentFrame\UI-TalentArrows" virtual="true">
<Size><AbsDimension x="32" y="32"/></Size>
<Anchors><Anchor point="TOPLEFT"/></Anchors>
</Texture>
<!--
The talent button (ref TalentButtonTemplate inherits ItemButtonTemplate,
Blizzard_TalentUITemplates.xml:40-101 + WoW/_extracted_framexml/ItemButtonTemplate.xml, both
read in full this session — self-contained rather than inheriting a base ItemButtonTemplate,
this file's header comment ("OMITs") and CharacterFrame.xml's own BenillaPaperDollItemSlot-
Template precedent for the identical gap. 37x37 (ItemButtonTemplate's own real size —
TALENT_BUTTON_SIZE=32 is a GRID PITCH constant, not this button's rendered size; the two are
independent, verified against ItemButtonTemplate.xml's own unsized $parentIconTexture +
64x64 NormalTexture ring, which visually overlaps neighboring buttons by design at the 63px
pitch — matches the ref exactly). Cooldown/AutoCast (ItemButtonTemplate has neither anyway)
don't apply here. CHARACTER_POINTS_CHANGED re-poll while hovered (ref l.85-89).
-->
<Button name="BenillaTalentButtonTemplate" virtual="true">
<Size><AbsDimension x="37" y="37"/></Size>
<Anchors><Anchor point="TOPLEFT"/></Anchors>
<Layers>
<Layer level="BACKGROUND">
<Texture name="$parentSlot" file="Interface\Buttons\UI-EmptySlot-White">
<Size><AbsDimension x="64" y="64"/></Size>
<Anchors><Anchor point="CENTER"/></Anchors>
</Texture>
</Layer>
<Layer level="BORDER">
<!-- ItemButtonTemplate's own icon Texture child: unsized/unanchored, verbatim (its
own XML, l.10) — CharacterFrame.xml's $parentIconTexture precedent for the
identical shape. -->
<Texture name="$parentIconTexture"/>
</Layer>
<Layer level="OVERLAY">
<Texture name="$parentRankBorder" file="Interface\TalentFrame\TalentFrame-RankBorder">
<Size><AbsDimension x="32" y="32"/></Size>
<Anchors><Anchor point="CENTER" relativePoint="BOTTOMRIGHT"><Offset><AbsDimension x="0" y="0"/></Offset></Anchor></Anchors>
</Texture>
<FontString name="$parentRank" inherits="GameFontNormalSmall">
<Anchors><Anchor point="CENTER" relativeTo="$parentRankBorder"><Offset><AbsDimension x="0" y="0"/></Offset></Anchor></Anchors>
</FontString>
</Layer>
</Layers>
<NormalTexture name="$parentNormalTexture" file="Interface\Buttons\UI-Quickslot2">
<Size><AbsDimension x="64" y="64"/></Size>
<Anchors><Anchor point="CENTER"><Offset><AbsDimension x="0" y="-1"/></Offset></Anchor></Anchors>
</NormalTexture>
<PushedTexture file="Interface\Buttons\UI-Quickslot-Depress"/>
<HighlightTexture file="Interface\Buttons\ButtonHilight-Square" alphaMode="ADD"/>
<Scripts>
<OnLoad>self:RegisterEvent("CHARACTER_POINTS_CHANGED")</OnLoad>
<OnEvent>BenillaTalentButton_OnEvent(self)</OnEvent>
<OnEnter>BenillaTalentButton_OnEnter(self)</OnEnter>
<OnLeave>GameTooltip:Hide()</OnLeave>
<OnClick>BenillaTalentButton_OnClick(self)</OnClick>
</Scripts>
</Button>
<Frame name="BenillaTalentFrame" hidden="true">
<!-- The real 384x512 window (ref Blizzard_TalentUI.xml:5-8). No <Anchors> — ShowUIPanel/
SetLeftFrame SetPoints this to the left slot on open (UiPanels.xml's own convention). -->
<Size><AbsDimension x="384" y="512"/></Size>
<!-- HitRectInsets: transcribed for fidelity; no loader wiring exists yet (this file's
header comment) — a harmless no-op today. -->
<HitRectInsets><AbsInset left="0" right="30" top="0" bottom="45"/></HitRectInsets>
<Layers>
<!-- Portrait 60x60 TOPLEFT (7,-6) (ref l.19-31). Set in OnShow/on refresh via
SetPortraitTexture (CharacterFrame.xml's own precedent) — "player" is a fixed
token here, this window only ever shows the local player. -->
<Layer level="BACKGROUND">
<Texture name="BenillaTalentFramePortrait">
<Size><AbsDimension x="60" y="60"/></Size>
<Anchors><Anchor point="TOPLEFT"><Offset><AbsDimension x="7" y="-6"/></Offset></Anchor></Anchors>
</Texture>
</Layer>
<!-- The static chassis (ref l.34-82): the PaperDollInfoFrame top (shared with
CharacterFrame) + the TalentFrame-specific bottom, both class-agnostic. -->
<Layer level="BORDER">
<Texture file="Interface\PaperDollInfoFrame\UI-Character-General-TopLeft">
<Size><AbsDimension x="256" y="256"/></Size>
<Anchors><Anchor point="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"><Offset><AbsDimension x="2" y="-1"/></Offset></Anchor></Anchors>
</Texture>
<Texture file="Interface\TalentFrame\UI-TalentFrame-BotLeft">
<Size><AbsDimension x="256" y="256"/></Size>
<Anchors><Anchor point="BOTTOMLEFT"><Offset><AbsDimension x="2" y="-1"/></Offset></Anchor></Anchors>
</Texture>
<Texture file="Interface\TalentFrame\UI-TalentFrame-BotRight">
<Size><AbsDimension x="128" y="256"/></Size>
<Anchors><Anchor point="BOTTOMRIGHT"><Offset><AbsDimension x="2" y="-1"/></Offset></Anchor></Anchors>
</Texture>
</Layer>
<!-- The class-specific tree art (ref l.84-202): the 4 dynamic quadrants (retextured
every Update from `base`), the title, and the points-remaining/points-spent bar. -->
<Layer level="ARTWORK">
<Texture name="BenillaTalentFrameBackgroundTopLeft">
<Size><AbsDimension x="256" y="256"/></Size>
<Anchors><Anchor point="TOPLEFT"><Offset><AbsDimension x="23" y="-77"/></Offset></Anchor></Anchors>
</Texture>
<Texture name="BenillaTalentFrameBackgroundTopRight">
<Size><AbsDimension x="64" y="256"/></Size>
<Anchors><Anchor point="TOPLEFT" relativeTo="BenillaTalentFrameBackgroundTopLeft" relativePoint="TOPRIGHT"/></Anchors>
</Texture>
<Texture name="BenillaTalentFrameBackgroundBottomLeft">
<Size><AbsDimension x="256" y="128"/></Size>
<Anchors><Anchor point="TOPLEFT" relativeTo="BenillaTalentFrameBackgroundTopLeft" relativePoint="BOTTOMLEFT"/></Anchors>
</Texture>
<Texture name="BenillaTalentFrameBackgroundBottomRight">
<Size><AbsDimension x="64" y="128"/></Size>
<Anchors><Anchor point="TOPLEFT" relativeTo="BenillaTalentFrameBackgroundTopLeft" relativePoint="BOTTOMRIGHT"/></Anchors>
</Texture>
<!-- text="Talents": this file's header comment (engine-gap notes, text= is
literal — never Lua-read, the ref's own TalentFrameTitleText isn't either). -->
<FontString name="BenillaTalentFrameTitleText" inherits="GameFontNormal" text="Talents">
<Anchors><Anchor point="TOP"><Offset><AbsDimension x="0" y="-18"/></Offset></Anchor></Anchors>
</FontString>
<Texture name="BenillaTalentFramePointsLeft" file="Interface\Common\Common-Input-Border">
<Size><AbsDimension x="8" y="20"/></Size>
<Anchors><Anchor point="TOPLEFT" relativeTo="BenillaTalentFrame"><Offset><AbsDimension x="75" y="-48"/></Offset></Anchor></Anchors>
<TexCoords left="0" right="0.0625" top="0" bottom="0.625"/>
</Texture>
<Texture name="BenillaTalentFramePointsMiddle" file="Interface\Common\Common-Input-Border">
<Size><AbsDimension x="248" y="20"/></Size>
<Anchors><Anchor point="LEFT" relativeTo="BenillaTalentFramePointsLeft" relativePoint="RIGHT"/></Anchors>
<TexCoords left="0.0625" right="0.9375" top="0" bottom="0.625"/>
</Texture>
<Texture name="BenillaTalentFramePointsRight" file="Interface\Common\Common-Input-Border">
<Size><AbsDimension x="8" y="20"/></Size>
<Anchors><Anchor point="LEFT" relativeTo="BenillaTalentFramePointsMiddle" relativePoint="RIGHT"/></Anchors>
<TexCoords left="0.9375" right="1.0" top="0" bottom="0.625"/>
</Texture>
<!-- Fed every Update by format(MASTERY_POINTS_SPENT, name) — the live global, not
static text (this file's header comment). -->
<FontString name="BenillaTalentFrameSpentPoints" inherits="GameFontNormalSmall">
<Anchors><Anchor point="TOP" relativeTo="BenillaTalentFramePointsMiddle"><Offset><AbsDimension x="0" y="-5"/></Offset></Anchor></Anchors>
</FontString>
<!-- Fed by BenillaTalentFrame_UpdateTalentPoints (the unspent-points NUMBER). -->
<FontString name="BenillaTalentFrameTalentPointsText" inherits="GameFontHighlightSmall">
<Anchors><Anchor point="BOTTOMRIGHT" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="252" y="87"/></Offset></Anchor></Anchors>
</FontString>
<!-- text="Talent Points:": this file's header comment (engine-gap notes, same as
the title — TalentFrame.lua never touches this FontString either). -->
<FontString name="BenillaTalentFrameTalentPoints" inherits="GameFontNormalSmall" text="Talent Points:">
<Anchors><Anchor point="RIGHT" relativeTo="BenillaTalentFrameTalentPointsText" relativePoint="LEFT"><Offset><AbsDimension x="-3" y="0"/></Offset></Anchor></Anchors>
</FontString>
</Layer>
</Layers>
<Frames>
<!-- Close: the house minimize-button art (no UIPanelCloseButton template ships —
SpellBookFrame/CharacterFrame's own precedent), the same TOPRIGHT(-44,-25)
position as every other window's close button (ref l.205-213). -->
<Button name="BenillaTalentFrameCloseButton">
<Size><AbsDimension x="32" y="32"/></Size>
<Anchors><Anchor point="CENTER" relativePoint="TOPRIGHT"><Offset><AbsDimension x="-44" y="-25"/></Offset></Anchor></Anchors>
<NormalTexture file="Interface\Buttons\UI-Panel-MinimizeButton-Up"/>
<PushedTexture file="Interface\Buttons\UI-Panel-MinimizeButton-Down"/>
<HighlightTexture file="Interface\Buttons\UI-Panel-MinimizeButton-Highlight" alphaMode="ADD"/>
<Scripts>
<OnClick>HideUIPanel(BenillaTalentFrame)</OnClick>
</Scripts>
</Button>
<!-- The bottom "Close" text button (ref TalentFrameCancelButton, l.214-230): a second,
redundant close affordance the ref genuinely ships (identical HideUIPanel action to
the X button above) — included for completeness, this file's header comment
("OMITs") notes no reason to drop it. UIPanelButtonTemplate reconstructed locally
(StackSplit.xml's own precedent for the identical gap); text="Close" is this file's
header comment's literal-text convention, not a GlobalStrings token read. -->
<Button name="BenillaTalentFrameCancelButton" text="Close">
<Size><AbsDimension x="80" y="22"/></Size>
<Anchors><Anchor point="CENTER" relativePoint="TOPLEFT"><Offset><AbsDimension x="305" y="-420"/></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>HideUIPanel(BenillaTalentFrame)</OnClick>
</Scripts>
</Button>
<!-- The 3 tree tabs (ref l.231-275, MAX_TALENT_TABS renumbered 5 -> 3, this file's
header comment "SCOPE CALL"). -->
<Button name="BenillaTalentFrameTab1" inherits="BenillaTalentTabTemplate" id="1">
<Anchors><Anchor point="BOTTOMLEFT"><Offset><AbsDimension x="15" y="46"/></Offset></Anchor></Anchors>
</Button>
<Button name="BenillaTalentFrameTab2" inherits="BenillaTalentTabTemplate" id="2">
<Anchors><Anchor point="LEFT" relativeTo="BenillaTalentFrameTab1" relativePoint="RIGHT"><Offset><AbsDimension x="-15" y="0"/></Offset></Anchor></Anchors>
</Button>
<Button name="BenillaTalentFrameTab3" inherits="BenillaTalentTabTemplate" id="3">
<Anchors><Anchor point="LEFT" relativeTo="BenillaTalentFrameTab2" relativePoint="RIGHT"><Offset><AbsDimension x="-15" y="0"/></Offset></Anchor></Anchors>
</Button>
<!-- The scrolled talent grid (ref l.276-419: TalentFrameScrollFrame, UIPanelScrollFrame-
Template) — this file's header comment, "SCROLL DESIGN": a real ScrollFrame (decision
0112) with a FIXED-height child (BENILLA_TALENT_SCROLL_CHILD_H) and a reused real
Slider scrollbar (BenillaScrollBarTemplate, ScrollTemplates.xml), not the ref's own
dynamic UIPanelScrollFrameTemplate. -->
<ScrollFrame name="BenillaTalentFrameScroll">
<Size><AbsDimension x="296" y="332"/></Size>
<Anchors><Anchor point="TOPRIGHT"><Offset><AbsDimension x="-65" y="-77"/></Offset></Anchor></Anchors>
<Frames>
<!-- The scrollbar: BenillaScrollBarTemplate's own geometry (ScrollTemplates.xml's
FauxScrollFrameTemplate, whose Slider anchor shape is reused directly
here without the Frame wrapper around it — this file's header comment). Its
inherited OnValueChanged is REPLACED below (not chained — verified). -->
<Slider name="BenillaTalentFrameScrollBar" inherits="BenillaScrollBarTemplate">
<Anchors>
<Anchor point="TOPLEFT" relativePoint="TOPRIGHT"><Offset><AbsDimension x="6" y="-16"/></Offset></Anchor>
<Anchor point="BOTTOMLEFT" relativePoint="BOTTOMRIGHT"><Offset><AbsDimension x="6" y="16"/></Offset></Anchor>
</Anchors>
<Scripts>
<OnValueChanged>BenillaTalentFrameScrollBar_OnValueChanged(self, arg1)</OnValueChanged>
</Scripts>
</Slider>
<!-- The scroll child (SetScrollChild'd in BenillaTalentFrameScroll_OnLoad below,
QuestLogFrame.xml's own real-ScrollFrame precedent for the wiring shape):
320 wide (the ref's own TalentFrameScrollChildFrame width, ref l.318-321),
504 tall (this file's header comment, "SCROLL DESIGN" — a load-time constant,
never resized). -->
<Frame name="BenillaTalentFrameScrollChild">
<Size><AbsDimension x="320" y="504"/></Size>
<Anchors><Anchor point="TOPLEFT"/></Anchors>
<Layers>
<Layer level="BACKGROUND">
<Texture name="BenillaTalentFrameBranch1" inherits="BenillaTalentBranchTemplate"/>
<Texture name="BenillaTalentFrameBranch2" inherits="BenillaTalentBranchTemplate"/>
<Texture name="BenillaTalentFrameBranch3" inherits="BenillaTalentBranchTemplate"/>
<Texture name="BenillaTalentFrameBranch4" inherits="BenillaTalentBranchTemplate"/>
<Texture name="BenillaTalentFrameBranch5" inherits="BenillaTalentBranchTemplate"/>
<Texture name="BenillaTalentFrameBranch6" inherits="BenillaTalentBranchTemplate"/>
<Texture name="BenillaTalentFrameBranch7" inherits="BenillaTalentBranchTemplate"/>
<Texture name="BenillaTalentFrameBranch8" inherits="BenillaTalentBranchTemplate"/>
<Texture name="BenillaTalentFrameBranch9" inherits="BenillaTalentBranchTemplate"/>
<Texture name="BenillaTalentFrameBranch10" inherits="BenillaTalentBranchTemplate"/>
<Texture name="BenillaTalentFrameBranch11" inherits="BenillaTalentBranchTemplate"/>
<Texture name="BenillaTalentFrameBranch12" inherits="BenillaTalentBranchTemplate"/>
<Texture name="BenillaTalentFrameBranch13" inherits="BenillaTalentBranchTemplate"/>
<Texture name="BenillaTalentFrameBranch14" inherits="BenillaTalentBranchTemplate"/>
<Texture name="BenillaTalentFrameBranch15" inherits="BenillaTalentBranchTemplate"/>
<Texture name="BenillaTalentFrameBranch16" inherits="BenillaTalentBranchTemplate"/>
<Texture name="BenillaTalentFrameBranch17" inherits="BenillaTalentBranchTemplate"/>
<Texture name="BenillaTalentFrameBranch18" inherits="BenillaTalentBranchTemplate"/>
<Texture name="BenillaTalentFrameBranch19" inherits="BenillaTalentBranchTemplate"/>
<Texture name="BenillaTalentFrameBranch20" inherits="BenillaTalentBranchTemplate"/>
<Texture name="BenillaTalentFrameBranch21" inherits="BenillaTalentBranchTemplate"/>
<Texture name="BenillaTalentFrameBranch22" inherits="BenillaTalentBranchTemplate"/>
<Texture name="BenillaTalentFrameBranch23" inherits="BenillaTalentBranchTemplate"/>
<Texture name="BenillaTalentFrameBranch24" inherits="BenillaTalentBranchTemplate"/>
<Texture name="BenillaTalentFrameBranch25" inherits="BenillaTalentBranchTemplate"/>
<Texture name="BenillaTalentFrameBranch26" inherits="BenillaTalentBranchTemplate"/>
<Texture name="BenillaTalentFrameBranch27" inherits="BenillaTalentBranchTemplate"/>
<Texture name="BenillaTalentFrameBranch28" inherits="BenillaTalentBranchTemplate"/>
<Texture name="BenillaTalentFrameBranch29" inherits="BenillaTalentBranchTemplate"/>
<Texture name="BenillaTalentFrameBranch30" inherits="BenillaTalentBranchTemplate"/>
</Layer>
</Layers>
<Frames>
<!-- The 20 talent buttons (ref l.360-379, MAX_NUM_TALENTS) — id=1..20 is
the button's own within-tab index (GetTalentInfo's 2nd arg / GetID()).
No static Anchors: BenillaSetTalentButtonLocation SetPoints each one
every Update, off its OWN template's default TOPLEFT. -->
<Button name="BenillaTalentFrameTalent1" inherits="BenillaTalentButtonTemplate" id="1"/>
<Button name="BenillaTalentFrameTalent2" inherits="BenillaTalentButtonTemplate" id="2"/>
<Button name="BenillaTalentFrameTalent3" inherits="BenillaTalentButtonTemplate" id="3"/>
<Button name="BenillaTalentFrameTalent4" inherits="BenillaTalentButtonTemplate" id="4"/>
<Button name="BenillaTalentFrameTalent5" inherits="BenillaTalentButtonTemplate" id="5"/>
<Button name="BenillaTalentFrameTalent6" inherits="BenillaTalentButtonTemplate" id="6"/>
<Button name="BenillaTalentFrameTalent7" inherits="BenillaTalentButtonTemplate" id="7"/>
<Button name="BenillaTalentFrameTalent8" inherits="BenillaTalentButtonTemplate" id="8"/>
<Button name="BenillaTalentFrameTalent9" inherits="BenillaTalentButtonTemplate" id="9"/>
<Button name="BenillaTalentFrameTalent10" inherits="BenillaTalentButtonTemplate" id="10"/>
<Button name="BenillaTalentFrameTalent11" inherits="BenillaTalentButtonTemplate" id="11"/>
<Button name="BenillaTalentFrameTalent12" inherits="BenillaTalentButtonTemplate" id="12"/>
<Button name="BenillaTalentFrameTalent13" inherits="BenillaTalentButtonTemplate" id="13"/>
<Button name="BenillaTalentFrameTalent14" inherits="BenillaTalentButtonTemplate" id="14"/>
<Button name="BenillaTalentFrameTalent15" inherits="BenillaTalentButtonTemplate" id="15"/>
<Button name="BenillaTalentFrameTalent16" inherits="BenillaTalentButtonTemplate" id="16"/>
<Button name="BenillaTalentFrameTalent17" inherits="BenillaTalentButtonTemplate" id="17"/>
<Button name="BenillaTalentFrameTalent18" inherits="BenillaTalentButtonTemplate" id="18"/>
<Button name="BenillaTalentFrameTalent19" inherits="BenillaTalentButtonTemplate" id="19"/>
<Button name="BenillaTalentFrameTalent20" inherits="BenillaTalentButtonTemplate" id="20"/>
<!-- The arrow-art pool (ref l.380-415: TalentFrameArrowFrame,
setAllPoints="true") — an explicit Size matching the scroll child
instead, the equivalent shape CharacterFrame.xml's paper doll takes. -->
<Frame name="BenillaTalentFrameArrowFrame">
<Size><AbsDimension x="320" y="504"/></Size>
<Anchors><Anchor point="TOPLEFT"/></Anchors>
<Layers>
<Layer level="OVERLAY">
<Texture name="BenillaTalentFrameArrow1" inherits="BenillaTalentArrowTemplate"/>
<Texture name="BenillaTalentFrameArrow2" inherits="BenillaTalentArrowTemplate"/>
<Texture name="BenillaTalentFrameArrow3" inherits="BenillaTalentArrowTemplate"/>
<Texture name="BenillaTalentFrameArrow4" inherits="BenillaTalentArrowTemplate"/>
<Texture name="BenillaTalentFrameArrow5" inherits="BenillaTalentArrowTemplate"/>
<Texture name="BenillaTalentFrameArrow6" inherits="BenillaTalentArrowTemplate"/>
<Texture name="BenillaTalentFrameArrow7" inherits="BenillaTalentArrowTemplate"/>
<Texture name="BenillaTalentFrameArrow8" inherits="BenillaTalentArrowTemplate"/>
<Texture name="BenillaTalentFrameArrow9" inherits="BenillaTalentArrowTemplate"/>
<Texture name="BenillaTalentFrameArrow10" inherits="BenillaTalentArrowTemplate"/>
<Texture name="BenillaTalentFrameArrow11" inherits="BenillaTalentArrowTemplate"/>
<Texture name="BenillaTalentFrameArrow12" inherits="BenillaTalentArrowTemplate"/>
<Texture name="BenillaTalentFrameArrow13" inherits="BenillaTalentArrowTemplate"/>
<Texture name="BenillaTalentFrameArrow14" inherits="BenillaTalentArrowTemplate"/>
<Texture name="BenillaTalentFrameArrow15" inherits="BenillaTalentArrowTemplate"/>
<Texture name="BenillaTalentFrameArrow16" inherits="BenillaTalentArrowTemplate"/>
<Texture name="BenillaTalentFrameArrow17" inherits="BenillaTalentArrowTemplate"/>
<Texture name="BenillaTalentFrameArrow18" inherits="BenillaTalentArrowTemplate"/>
<Texture name="BenillaTalentFrameArrow19" inherits="BenillaTalentArrowTemplate"/>
<Texture name="BenillaTalentFrameArrow20" inherits="BenillaTalentArrowTemplate"/>
<Texture name="BenillaTalentFrameArrow21" inherits="BenillaTalentArrowTemplate"/>
<Texture name="BenillaTalentFrameArrow22" inherits="BenillaTalentArrowTemplate"/>
<Texture name="BenillaTalentFrameArrow23" inherits="BenillaTalentArrowTemplate"/>
<Texture name="BenillaTalentFrameArrow24" inherits="BenillaTalentArrowTemplate"/>
<Texture name="BenillaTalentFrameArrow25" inherits="BenillaTalentArrowTemplate"/>
<Texture name="BenillaTalentFrameArrow26" inherits="BenillaTalentArrowTemplate"/>
<Texture name="BenillaTalentFrameArrow27" inherits="BenillaTalentArrowTemplate"/>
<Texture name="BenillaTalentFrameArrow28" inherits="BenillaTalentArrowTemplate"/>
<Texture name="BenillaTalentFrameArrow29" inherits="BenillaTalentArrowTemplate"/>
<Texture name="BenillaTalentFrameArrow30" inherits="BenillaTalentArrowTemplate"/>
</Layer>
</Layers>
</Frame>
</Frames>
</Frame>
</Frames>
<Scripts>
<OnLoad>BenillaTalentFrameScroll_OnLoad(self)</OnLoad>
<!-- WoW wheel convention: +1 per notch up (toward the top, decreasing scroll),
-1 down (ChatFrame.xml's own handler quotes the same convention). Reuses
BenillaScrollBar_Step (this file's header comment: generic, Slider-only). -->
<OnMouseWheel>BenillaScrollBar_Step(getglobal("BenillaTalentFrameScrollBar"), arg1 > 0 and -1 or 1)</OnMouseWheel>
<!-- The real ScrollFrame's own half of the documented pair (this file's header
comment / BenillaTalentFrameScrollBar_OnValueChanged's own comment). -->
<OnVerticalScroll>getglobal("BenillaTalentFrameScrollBar"):SetValue(arg1)</OnVerticalScroll>
</Scripts>
</ScrollFrame>
</Frames>
<Scripts>
<OnLoad>BenillaTalentFrame_OnLoad(self)</OnLoad>
<OnShow>BenillaTalentFrame_OnShow()</OnShow>
<OnHide>BenillaTalentFrame_OnHide()</OnHide>
<OnEvent>BenillaTalentFrame_OnEvent(self)</OnEvent>
</Scripts>
</Frame>
</Ui>