Files
benilla-octo/crates/benilla-app/assets/ui/SpellBookFrame.xml
T
SamandClaude Fable 5 5ce93810b0 the autocast shine squares up, and a pill watches the tail
- the shine's stars size by the screen diagonal instead of a frozen 4:3 constant, so a widescreen window stops blobbing them
- each button's shine wears its own authored geometry: the spell book's 36x36 model rect at 1.22 scale, not the pet bar's — and its autocast brackets reach the button's corners with the glow sharing their centre
- one deliberate deviation, recorded as such: the spell book's shine borrows the pet button's rim ratio, because the reference's own washes out there
- the perf HUD gains a pill that measures frame cost and its tail, and latches the spike that would otherwise scroll away

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

966 lines
59 KiB
XML

<Ui>
<!--
benilla's spellbook window (decision 0216 §8, slice 5 — the P-key window, the spell
SOURCE for the cursor-payload arc): the 384x512 book + the 12-spell page grid + the
right-edge skill-line tabs + the page-turn pair, dressed from the real 1.12
`SpellBookFrame.xml`/`.lua` — extracted from the MPQ chain to session scratch this
session (director-local reference artifacts, never committed — the 0068/0138 pattern;
line cites below are into that extraction's own `ref-SpellBookFrame.xml`/`.lua`).
Docks the left UIPanel slot exactly like Character/Merchant/Gossip/Quest/QuestLog
(`UiPanels.xml`'s `UIPanelWindows["SpellBookFrame"] = { area = "left", pushable =
0 }`, mirroring the ref's own `SpellBookFrame` row) — so it competes for/replaces those
windows, never stacks with them. `ToggleSpellBook(bookType)` (ref `SpellBookFrame.lua:
9-50`) is the show/hide entry point, and it is addon-facing: it wears the reference's bare
name for the same reason `ToggleCharacter`/`ToggleQuestLog` do. The app's 'P' binding calls
it with `BOOKTYPE_SPELL` (`ui_script/input.rs`, ref `Bindings.xml:582-583`).
Every frame this file defines wears the REFERENCE's name (`SpellBookFrame`, `SpellButton1`,
…), because addons address them by it — the `Benilla` prefix these carried until the
reference-naming pass is gone. A child with no counterpart in the 1.12 namespace keeps the
bare stripped name so `$parent`/`GetName()..` composition inside the family stays coherent.
The book MODEL (tabs = skill lines, the flat slot list, offsets) is built entirely by the
app from `PlayerActions.spells` through the `Spell.dbc`/`SkillLine.dbc` join
(`crate::ui_spellbook`) and pushed through `benilla_ui::script::spellbook`'s snapshot seam
— this file only ever reads it through `GetNumSpellTabs`/`GetSpellTabInfo`/`GetSpellName`/
`GetSpellTexture`/`IsSpellPassive`, exactly the ref's own Era API shape. `BOOKTYPE_SPELL`/
`BOOKTYPE_PET` are installed by the ENGINE (`spellbook.rs`'s `install()`), not this file's
own `<Script>` — the one deliberate exception to the ref's own home for them
(`SpellBookFrame.lua:5-6`), so the engine's OWN tests can exercise the pet-deferral path
without this XML (see that module's doc for the full reasoning). `SPELLS_PER_PAGE`/
`MAX_SKILLLINE_TABS`/`SPELLBOOK_PAGENUMBERS` stay here — genuine per-window UI state, the
ref's own home for them.
THE PET BOOK IS LIVE (decision 1032) — the second tab, its 12-slot page, the autocast
overlay + sparkle, the right-click autocast toggle, and the cast/pickup pair, all over
`crate::ui_pet_book`'s feed of `SMSG_PET_SPELLS`' own spell list. Two reference QUIRKS are
reproduced on purpose here and each carries a do-not-fix note at its site (the standing
rule from decision 1030): the pet page **ignores its page number**, and its page arrows
write the *spell* book's page counter. See `SpellBook_GetSpellID` and the two
page-turn handlers.
Named deferrals/OMITs (each re-cited at its own site below):
- `LEARNED_SPELL_IN_TAB`'s tab flash (`SpellBookTabFlashFrame`, `CheckButtonGlow`): no
such event exists yet (no "just learned a spell" signal split out of the general
`SPELLS_CHANGED` diff). The ref returns early on it for the pet book anyway.
- The CRAFT_SHOW/_CLOSE/TRADE_SKILL_SHOW/_CLOSE legs of the checked ring: the ring
itself is LIVE (`BenillaSpellButton_UpdateSelection`/`IsCurrentCast` over the
app-resolved slot verdict — the shapeshift arm makes Ghost Wolf's/a druid form's
slot glow while its form is up), but the trade-skill-window arm of the engine
predicate is unmodeled (the same named gap `ui_action`'s IsCurrentAction carries),
so those four events would re-read an arm that never fires — omitted with it.
- The `this.updateTooltip`/`OnUpdate` re-poll loop around the hover: `GameTooltip:SetSpell`
itself is LIVE (the engine channel, decision 0276's line law; it reads whichever book
`bookType` names — 1050), but its per-frame re-poll is dropped, since nothing in the
rendered lines changes per-frame the way a live cooldown would. The ref's own
`if ( GameTooltip:SetSpell(…) )` return value exists only to arm that loop.
- `maxLines="3"` on `$parentSpellName` (word-wrap line cap): no such FontString
attribute is modeled (grepped `benilla-ui/src/loader/widgets.rs`) — the name just
wraps unbounded inside its box width, a harmless cosmetic gap on the rare long name.
- `MultiActionBar_ShowAllGrids/_HideAllGrids()` (ref `SpellBookFrame_OnShow`/`_OnHide`):
no multi-bar grid-show mechanism exists in this engine yet — dropped, the same class of
gap CharacterFrame's own OnShow/OnHide already carry. Its neighbour in that pair,
`UpdateMicroButtons()`, is LIVE since the micro-menu slice (`MicroMenu.xml`) — nil-
guarded at the call sites only because several test harnesses load this window without
the action bar behind it.
Sounds (decision 0090): `igSpellBookOpen`/`igSpellBookClose` on show/hide (ref
`SpellBookFrame_Update`/`_OnHide`, VERIFIED present in the real `SoundEntries.dbc` this
session), `igAbiliityPageTurn` (the ref's own literal spelling — VERIFIED as the real
registered kit name, id 836, `Sound\interface\iAbilitiesTurnPage{A,B,C}.wav`; not a typo
to "fix") on both page-turn clicks.
-->
<Script><![CDATA[
-- ── GlobalStrings values this window uses (ref-GlobalStrings.lua, cited per line — the
-- `X = X or "..."` idiom matches CastingBar.xml/CharacterFrame.xml's own convention) ────
SPELLBOOK = SPELLBOOK or "Spellbook" -- :3820
PAGE_NUMBER = PAGE_NUMBER or "Page %d" -- :2981
-- The pet tab's label AND the window title while the pet book is up. Keyed, not chosen:
-- HasPetSpells()'s second return is ChrClasses.dbc field 4 ("PET"/"DEMON") and the ref
-- concatenates it (`getglobal("PET_TYPE_"..token)`, ref SpellBookFrame.lua:173).
PET_TYPE_PET = PET_TYPE_PET or "Pet" -- :3058
PET_TYPE_DEMON = PET_TYPE_DEMON or "Demon" -- :3057
-- ── This window's own constants (ref SpellBookFrame.lua:1-7, Benilla-prefixed where the
-- ref's own name would collide with the engine's BOOKTYPE_SPELL/BOOKTYPE_PET globals —
-- this file's header comment on that one deviation) ─────────────────────────────────────
SPELLS_PER_PAGE = 12
MAX_SKILLLINE_TABS = 8
SPELLBOOK_PAGENUMBERS = {}
-- ── The window: toggle (ref ToggleSpellBook, l.9-50) ─────────────────────────────────────
-- Kept BARE (not Benilla-prefixed) — the addon-facing entry point, matching this
-- codebase's own ToggleCharacter/ToggleQuestLog precedent. The ref's own DUPLICATED
-- prev/next enable/disable (once inside the `currentPage > maxPages` clamp branch, once
-- again unconditionally right after — both produce the identical end state) collapses to
-- the ONE SpellBook_UpdatePageArrows() call already shared by every other
-- page-turn site below — a redundant-code cleanup, not a behavior change.
function ToggleSpellBook(bookType)
-- ref l.11-14: asking for a pet book you haven't got does NOTHING — not even close a
-- spellbook that is already open. The TOGGLEPETBOOK key binding is the live caller.
if bookType == BOOKTYPE_PET and not HasPetSpells() then
return
end
local isVisible = SpellBookFrame:IsVisible()
HideUIPanel(SpellBookFrame)
if (not isVisible or (SpellBookFrame.bookType ~= bookType)) then
SpellBookFrame.bookType = bookType
ShowUIPanel(SpellBookFrame)
end
BenillaSpellBook_ClampPage()
SpellBook_UpdatePageArrows()
SpellBookPageText:SetText(format(PAGE_NUMBER, SpellBook_GetCurrentPage()))
end
-- Clamp the selected tab's page back onto range (ref's own inline logic inside
-- ToggleSpellBook, l.22-26) — split out so both ToggleSpellBook and a future re-entry
-- share it, rather than inlining it twice.
function BenillaSpellBook_ClampPage()
local currentPage, maxPages = SpellBook_GetCurrentPage()
if currentPage > maxPages then
SPELLBOOK_PAGENUMBERS[SpellBookFrame.selectedSkillLine] = maxPages
BenillaUpdateSpells()
end
end
function SpellBookFrame_OnLoad()
local frame = this
frame:RegisterEvent("SPELLS_CHANGED")
-- LEARNED_SPELL_IN_TAB OMITTED — this file's header comment.
frame.bookType = BOOKTYPE_SPELL
for i = 1, MAX_SKILLLINE_TABS do
SPELLBOOK_PAGENUMBERS[i] = 1
end
SPELLBOOK_PAGENUMBERS["pet"] = 1 -- ref l.66; read by GetCurrentPage, written by nothing
SpellBookSkillLineTab_OnClick(1)
end
-- ref SpellBookFrame_OnEvent (l.75-92), the SPELLS_CHANGED branch only (LEARNED_SPELL_IN_TAB
-- OMITTED, header comment).
function SpellBookFrame_OnEvent()
local frame = this
if event == "SPELLS_CHANGED" and frame:IsVisible() then
SpellBookFrame_Update()
SpellBook_UpdatePageArrows()
end
end
-- ref SpellBookFrame_OnShow (l.94-105): MultiActionBar_ShowAllGrids OMITTED (header
-- comment); the tab-flash UIFrameFlash call goes with LEARNED_SPELL_IN_TAB.
-- UpdateMicroButtons is LIVE as of the micro-menu slice (MicroMenu.xml).
function SpellBookFrame_OnShow()
if UpdateMicroButtons then UpdateMicroButtons() end
SpellBookFrame_Update(1)
end
-- ref SpellBookFrame_Update (l.107-164), whole. Three things happen here and they are
-- easy to conflate: the skill-line strip is gated on the book TYPE (the pet book has no
-- tabs at all — GetSpellTabInfo never sees it), the Spell/Pet toggle row is gated on
-- HasPetSpells(), and the title + open sound fork on which book is up.
function SpellBookFrame_Update(showing)
if showing then
SpellBookSkillLineTab_OnClick(SpellBookFrame.selectedSkillLine)
BenillaUpdateSpells()
end
local isPet = SpellBookFrame.bookType == BOOKTYPE_PET
local numSkillLineTabs = GetNumSpellTabs()
for i = 1, MAX_SKILLLINE_TABS do
local skillLineTab = getglobal("SpellBookSkillLineTab" .. i)
if i <= numSkillLineTabs and not isPet then
local name, texture = GetSpellTabInfo(i)
skillLineTab:SetNormalTexture(texture)
skillLineTab.tooltip = name
skillLineTab:Show()
skillLineTab:SetChecked(SpellBookFrame.selectedSkillLine == i)
else
skillLineTab:Hide()
end
end
-- ref l.108-111 + l.141-151: hide the row first, then raise exactly the tabs that
-- exist. Tab 3 never appears in 1.12 (no third book) and is hidden by the template's
-- own `hidden="true"`; it is kept so the anchor chain reads like the ref's.
SpellBookFrameTabButton1:Hide()
SpellBookFrameTabButton2:Hide()
local hasPetSpells, petToken = HasPetSpells()
SpellBookFrame.petTitle = nil
if hasPetSpells then
SpellBookFrame_SetTabType(SpellBookFrameTabButton1, BOOKTYPE_SPELL)
SpellBookFrame_SetTabType(SpellBookFrameTabButton2, BOOKTYPE_PET, petToken)
elseif isPet then
-- The pet died/was dismissed while its book was open: the ref closes the window
-- and reverts, rather than showing an empty pet page (l.147-151).
HideUIPanel(SpellBookFrame)
SpellBookFrame.bookType = BOOKTYPE_SPELL
isPet = false
end
if not isPet then
SpellBookTitleText:SetText(SPELLBOOK)
if showing then PlaySound("igSpellBookOpen") end
else
SpellBookTitleText:SetText(SpellBookFrame.petTitle)
-- ref l.161: the pet book opens on the ABILITY kit, not the spellbook one. Both
-- ship in the real SoundEntries.dbc.
if showing then PlaySound("igAbilityOpen") end
end
end
-- ref SpellBookFrame_SetTabType (l.166-183). `binding` is what the tab's tooltip appends
-- the bound key to (MicroButtonTooltipText); `petTitle` is set here as a side effect,
-- exactly as the ref does, because this is the only place the token is in hand.
function SpellBookFrame_SetTabType(tabButton, bookType, token)
if bookType == BOOKTYPE_SPELL then
tabButton.bookType = BOOKTYPE_SPELL
tabButton:SetText(SPELLBOOK)
tabButton.binding = "TOGGLESPELLBOOK"
else
tabButton.bookType = BOOKTYPE_PET
local label = getglobal("PET_TYPE_" .. (token or "PET")) or PET_TYPE_PET
tabButton:SetText(label)
tabButton.binding = "TOGGLEPETBOOK"
SpellBookFrame.petTitle = label
end
-- The tab for the book you are ON is disabled, which is also what draws it as the
-- selected (Tab1-Selected) plate — the ref uses <DisabledTexture> for the "current"
-- look rather than a checked state.
if SpellBookFrame.bookType == bookType then
tabButton:Disable()
else
tabButton:Enable()
end
tabButton:Show()
end
-- ref SpellBookFrame_OnHide (l.186-204): MultiActionBar_HideAllGrids and the tab-flash
-- cleanup OMITTED (header comment); always the spell-book close kit (no pet book to branch
-- on). UpdateMicroButtons is LIVE as of the micro-menu slice (MicroMenu.xml).
function SpellBookFrame_OnHide()
if UpdateMicroButtons then UpdateMicroButtons() end
if SpellBookFrame.bookType == BOOKTYPE_PET then
PlaySound("igAbilityClose") -- ref l.191
else
PlaySound("igSpellBookClose")
end
end
-- UpdateSpells(): the ref's OWN global (`SpellBookFrame.lua:26/116/411/426/450` — never
-- DEFINED in this session's extraction, a base-UI/C-level helper outside this file)
-- transcribed here as BenillaUpdateSpells(): refresh every spell button on the current
-- page (this file's own convention: explicit button params, not ambient `this`).
function BenillaUpdateSpells()
for i = 1, SPELLS_PER_PAGE do
BenillaSpellButton_UpdateButton(getglobal("SpellButton" .. i))
end
end
-- ── The spell button (ref SpellButton_*, l.206-404) ──────────────────────────────────────
-- CRAFT_SHOW/_CLOSE/TRADE_SKILL_SHOW/_CLOSE are OMITTED (header comment: no craft or
-- trade-skill window arm); SPELLS_CHANGED and SPELL_UPDATE_COOLDOWN drive the full refresh
-- (the ref's own shared branch, l.221) and CURRENT_SPELL_CAST_CHANGED the checked ring.
-- PET_BAR_UPDATE is LIVE (ref l.214 + l.227-231) and it is the pet page's whole repaint
-- wire: the pet book's cooldowns, autocast bits and active ring all move without the BOOK
-- changing, so SPELLS_CHANGED alone would leave a stale page.
function SpellButton_OnLoad()
local button = this
button:RegisterEvent("SPELLS_CHANGED")
button:RegisterEvent("CURRENT_SPELL_CAST_CHANGED")
button:RegisterEvent("SPELL_UPDATE_COOLDOWN")
button:RegisterEvent("PET_BAR_UPDATE")
button:RegisterForDrag("LeftButton")
button:RegisterForClicks("LeftButtonUp", "RightButtonUp")
button.autocastable = getglobal(button:GetName() .. "AutoCastable")
-- The ref's `$parentAutoCast` is a <Model> playing UI-AutoCastButton.mdx; ours is
-- the native shine lane (decision 1383 — PetActionBar.xml's sparkle header has the
-- story, `autocast_shine` the numbers): $parentShine is the token marker, and
-- showing it is the whole enable.
button.shine = getglobal(button:GetName() .. "Shine")
BenillaSpellButton_UpdateButton(button)
end
function SpellButton_OnEvent(event)
local button = this
if event == "SPELLS_CHANGED" or event == "SPELL_UPDATE_COOLDOWN" then
BenillaSpellButton_UpdateButton(button)
elseif event == "CURRENT_SPELL_CAST_CHANGED" then
BenillaSpellButton_UpdateSelection(button)
elseif event == "PET_BAR_UPDATE" then
if SpellBookFrame.bookType == BOOKTYPE_PET then
BenillaSpellButton_UpdateButton(button)
end
end
end
-- ref SpellButton_UpdateSelection (l.292-306): the checked ring — `IsCurrentCast` over
-- the app-resolved verdict (the ACTIVE shapeshift form's own spell — Ghost Wolf glows
-- while the shaman is the wolf, a druid form likewise; the engine predicate `0x4b3600`
-- deliberately has NO casting-now arm, so an ordinary cast never lights a book slot).
-- The ref's out-of-tab bounds fork collapses into the one SetChecked: IsCurrentCast
-- answers nil past the book's end already.
function BenillaSpellButton_UpdateSelection(button)
local id = SpellBook_GetSpellID(button:GetID())
button:SetChecked(IsCurrentCast(id, SpellBookFrame.bookType))
end
-- ref SpellButton_OnEnter (l.235-244): the engine's SetSpell — the byte-verified spell
-- line law (decision 0276: cost|range one line, casttime|cooldown one line, passives
-- omit it, the $-substituted gold description; no rank column from the book — the
-- verified showRank=0).
function BenillaSpellButton_OnEnter(button)
if not GameTooltip then return end -- engine tests load file subsets
local id = SpellBook_GetSpellID(button:GetID())
if not GetSpellName(id, SpellBookFrame.bookType) then
return
end
GameTooltip:SetOwner(button, "ANCHOR_RIGHT")
GameTooltip:SetSpell(id, SpellBookFrame.bookType)
end
-- ref SpellButton_OnClick (l.263-290): the MacroFrame branch and the right-click pet
-- autocast fork are both OMITTED (header comment) — shift-click always picks up (the
-- ref's own "no MacroFrame visible" fallthrough), every other click casts. The
-- `id > MAX_SPELLS` guard is dropped too: GetSpellName/CastSpell/PickupSpell already
-- answer nil/no-op for any id past the book's real length, so the extra guard checks
-- nothing a MAX_SPELLS constant would add here.
function SpellButton_OnClick(drag)
local button = this
-- The mouse button is the ambient `arg1`, exactly as the reference reads it (l.284) —
-- its XML passes nothing on OnClick and a bare `1` on the two drag slots. Only the
-- `drag` falsy arm below reaches it, so the drag slots never read a stale arg1.
local mouseButton = arg1
local id = SpellBook_GetSpellID(button:GetID())
button:SetChecked("false")
if drag then
PickupSpell(id, SpellBookFrame.bookType)
elseif not IsShiftKeyDown() and mouseButton and mouseButton ~= "LeftButton"
and SpellBookFrame.bookType == BOOKTYPE_PET then
-- ref l.284-285: on the PET page a non-left click flips autocast instead of
-- casting. Shift still outranks it — the ref tests shift first, so a shift
-- right-click on a pet spell picks it up.
ToggleSpellAutocast(id, SpellBookFrame.bookType)
elseif IsShiftKeyDown() then
-- ref SpellButton_OnClick l.271-283 (LIVE since decision 0983): with the macro
-- editor open a shift-click APPENDS `/cast <name>[(<rank>)]` to the body instead of
-- picking the spell up — the behaviour MACRO_HELP_TEXT_LINE5 advertises ("Shift
-- click a spell in your spellbook with the macro UI open to add it to your macro").
-- The `/cast` alias comes from the shipped strings (SLASH_CAST1), never typed here.
--
-- DO NOT "FIX" THIS TO INSERT THE BARE NAME (B248, decision 1328). The whole
-- `/cast` line is what 1.12.1 writes; the bare-name-at-the-cursor insert people
-- remember is a LATER client's (`ChatEdit_InsertLink`). And the gate is SHIFT,
-- not the mouse button: a plain right click on this page casts, same as a left one
-- — pinned both ways in `ui_script::spellbook_tests`.
local macroFrame = getglobal("BenillaMacroFrame")
if macroFrame and macroFrame:IsVisible() then
local spellName, subSpellName = GetSpellName(id, SpellBookFrame.bookType)
if spellName and not IsSpellPassive(id, SpellBookFrame.bookType) then
local line = (SLASH_CAST1 or "/cast") .. " " .. spellName
if subSpellName and string.len(subSpellName) > 0 then
line = line .. "(" .. subSpellName .. ")"
end
BenillaMacroFrame_AddMacroLine(line)
end
else
PickupSpell(id, SpellBookFrame.bookType)
end
else
CastSpell(id, SpellBookFrame.bookType)
BenillaSpellButton_UpdateSelection(button) -- ref l.288: the ring re-reads at once
end
end
-- ref SpellButton_UpdateButton (l.307-404): the autocast overlay/model is OMITTED
-- (header comment); the cooldown sweep (l.359-365) and the checked-ring tail call are
-- LIVE. Passive gates CastSpell engine-side (spellbook.rs), not here — this only grays
-- the presentation.
function BenillaSpellButton_UpdateButton(button)
if not button:IsVisible() then
return
end
if GameTooltip:IsOwned(button) then
BenillaSpellButton_OnEnter(button)
end
if not SpellBookFrame.selectedSkillLine then
SpellBookFrame.selectedSkillLine = 1
end
local isPet = SpellBookFrame.bookType == BOOKTYPE_PET
local temp, tabTexture, offset, numSpells = GetSpellTabInfo(SpellBookFrame.selectedSkillLine)
offset = offset or 0
numSpells = numSpells or 0
SpellBookFrame.selectedSkillLineOffset = offset
local id = SpellBook_GetSpellID(button:GetID())
local name = button:GetName()
local iconTexture = getglobal(name .. "IconTexture")
local spellString = getglobal(name .. "SpellName")
local subSpellString = getglobal(name .. "SubSpellName")
local cooldown = getglobal(name .. "Cooldown")
local autocastable = getglobal(name .. "AutoCastable")
local highlightTexture = getglobal(name .. "Highlight")
local normalTexture = getglobal(name .. "NormalTexture")
-- TWO WAYS A SLOT COMES UP EMPTY, one dress for both. Past the end of the tab the
-- button also goes DEAD; a slot the book counts but has no icon for stays live and
-- only puts its highlight kit back to the square. Everything else the two do — the
-- five regions down, the autocast bit cleared, the ring unchecked, the button's own
-- ring back to plain white — is the same blanking, and it is written once here rather
-- than once per arm.
--
-- ref l.328: the out-of-tab test is `(bookType ~= BOOKTYPE_PET) and ...` — the pet
-- page has no tab to be out of, so a pet button past the end of the book falls to the
-- no-icon arm instead and stays ENABLED. Faithful, and harmless: an enabled button
-- with no spell behind it clicks into GetSpellName's nil and does nothing.
local outOfTab = (not isPet) and id > (offset + numSpells)
if outOfTab then
button:Disable()
else
button:Enable()
end
local texture
if not outOfTab then
texture = GetSpellTexture(id, SpellBookFrame.bookType)
end
if outOfTab or (not texture) or (texture == "") then
iconTexture:Hide()
spellString:Hide()
subSpellString:Hide()
cooldown:Hide()
autocastable:Hide()
button.shine:Hide()
if not outOfTab then
highlightTexture:SetTexture("Interface\\Buttons\\ButtonHilight-Square")
end
button:SetChecked(0)
normalTexture:SetVertexColor(1.0, 1.0, 1.0)
return
end
-- The cooldown sweep + the enable-keyed icon dim (ref l.359-365): enable == 0 is an
-- on-hold cooldown (Stealth/Feign Death parked until SMSG_COOLDOWN_EVENT) — the
-- widget stays hidden (CooldownFrame_SetTimer's own gate) and the icon reads 40%.
local start, duration, enable = GetSpellCooldown(id, SpellBookFrame.bookType)
CooldownFrame_SetTimer(cooldown, start, duration, enable)
local brightness = (enable == 1) and 1.0 or 0.4
iconTexture:SetVertexColor(brightness, brightness, brightness)
-- ref l.367-377: the autocast pair. `GetSpellAutocast` answers (nil, nil) for the
-- player book by the engine's own short-circuit, so this needs no isPet guard — the
-- overlay and the sparkle simply never come on outside the pet page. Showing the
-- shine marker IS the sparkle (decision 1383 — the native lane draws at it).
local autoCastAllowed, autoCastEnabled = GetSpellAutocast(id, SpellBookFrame.bookType)
if autoCastAllowed then autocastable:Show() else autocastable:Hide() end
if autoCastEnabled then button.shine:Show() else button.shine:Hide() end
local spellName, subSpellName = GetSpellName(id, SpellBookFrame.bookType)
local isPassive = IsSpellPassive(id, SpellBookFrame.bookType)
-- A passive slot wears a whole different dress — the button ring blacked right out,
-- the round passive highlight in place of the square one, and the name in the passive
-- grey. Three values that always travel together, so they are chosen once and applied
-- once instead of each verb appearing in both arms.
local ringLevel, highlightFile, nameColor
if isPassive then
ringLevel, highlightFile, nameColor =
0, "Interface\\Buttons\\UI-PassiveHighlight", PASSIVE_SPELL_FONT_COLOR
else
ringLevel, highlightFile, nameColor =
1.0, "Interface\\Buttons\\ButtonHilight-Square", NORMAL_FONT_COLOR
end
normalTexture:SetVertexColor(ringLevel, ringLevel, ringLevel)
highlightTexture:SetTexture(highlightFile)
spellString:SetTextColor(nameColor.r, nameColor.g, nameColor.b)
iconTexture:SetTexture(texture)
spellString:SetText(spellName)
subSpellString:SetText(subSpellName)
if subSpellName and subSpellName ~= "" then
spellString:SetPoint("LEFT", button, "RIGHT", 4, 4)
else
spellString:SetPoint("LEFT", button, "RIGHT", 4, 2)
end
iconTexture:Show()
spellString:Show()
subSpellString:Show()
BenillaSpellButton_UpdateSelection(button) -- ref l.403: every full refresh re-reads the ring
end
-- ── Page turn (ref PrevPageButton_OnClick/NextPageButton_OnClick, l.406-434) ─────────────
--
-- DO NOT "FIX" THE KEY (decision 1032, the standing rule from 1030). The reference writes
-- `SPELLBOOK_PAGENUMBERS[selectedSkillLine]` on BOTH books, while `SpellBook_GetCurrentPage`
-- reads `SPELLBOOK_PAGENUMBERS["pet"]` for the pet one — a key nothing writes. So on the
-- pet page the arrows turn nothing, the page text says a number the page does not honour,
-- and the *spell* book's page counter moves behind your back. All three are the reference's
-- and all three are reproduced; `SpellBook_GetSpellID`'s note is the other half.
function BenillaSpellBookPrevPageButton_OnClick()
local pageNum = SpellBook_GetCurrentPage() - 1
SPELLBOOK_PAGENUMBERS[SpellBookFrame.selectedSkillLine] = pageNum
SpellBook_UpdatePageArrows()
SpellBookPageText:SetText(format(PAGE_NUMBER, pageNum))
BenillaUpdateSpells()
-- ref l.414-417: the pet page re-asserts its own title, because the spell book's page
-- text handler is shared and the title is not.
if SpellBookFrame.bookType == BOOKTYPE_PET then
SpellBookTitleText:SetText(SpellBookFrame.petTitle)
end
PlaySound("igAbiliityPageTurn")
end
function BenillaSpellBookNextPageButton_OnClick()
local pageNum = SpellBook_GetCurrentPage() + 1
SPELLBOOK_PAGENUMBERS[SpellBookFrame.selectedSkillLine] = pageNum
SpellBook_UpdatePageArrows()
SpellBookPageText:SetText(format(PAGE_NUMBER, pageNum))
BenillaUpdateSpells()
if SpellBookFrame.bookType == BOOKTYPE_PET then
SpellBookTitleText:SetText(SpellBookFrame.petTitle)
end
PlaySound("igAbiliityPageTurn")
end
-- ── The skill-line tabs (ref SpellBookSkillLineTab_OnClick, l.436-457) ───────────────────
function SpellBookSkillLineTab_OnClick(id)
local update
if not id then
update = 1
id = this:GetID()
end
SpellBookFrame.selectedSkillLine = id
local name, texture, offset, numSpells = GetSpellTabInfo(id)
SpellBookFrame.selectedSkillLineOffset = offset or 0
SpellBookFrame.selectedSkillLineNumSpells = numSpells or 0
SpellBook_UpdatePageArrows()
SpellBookFrame_Update()
SpellBookPageText:SetText(format(PAGE_NUMBER, SpellBook_GetCurrentPage()))
if update then
BenillaUpdateSpells()
end
end
-- ref SpellBook_GetSpellID (l.459-465). This is the book id the engine's own byte-verified
-- seam expects verbatim (`benilla-ui/src/script/spellbook.rs`'s module doc).
--
-- DO NOT "FIX" THE PET ARM (decision 1032, the standing rule from 1030). The reference's
-- pet branch is a bare `return id` — no tab offset, and **no page term** — so the pet page
-- always shows book entries 1..12 no matter what page number the window is displaying. A
-- well-trained hunter pet can know more than twelve spells, so this is visible, and it is
-- still the reference's behaviour. Its sibling quirk is in the two page-turn handlers.
function SpellBook_GetSpellID(id)
if SpellBookFrame.bookType == BOOKTYPE_PET then
return id
end
return id + (SpellBookFrame.selectedSkillLineOffset or 0)
+ (SPELLS_PER_PAGE * ((SPELLBOOK_PAGENUMBERS[SpellBookFrame.selectedSkillLine] or 1) - 1))
end
-- ref SpellBook_UpdatePageArrows (l.467-479). An arrow is live exactly while the page it
-- points at is a DIFFERENT page. Stated as an inequality rather than a range test on
-- purpose: the pet book's arrows write a counter its own page ignores (the do-not-fix
-- notes above), so a page number can sit outside 1..maxPages, and when it does BOTH arrows
-- stay live — which is the reference's answer, not an accident of this spelling.
function SpellBook_UpdatePageArrows()
local currentPage, maxPages = SpellBook_GetCurrentPage()
if currentPage ~= 1 then SpellBookPrevPageButton:Enable() else SpellBookPrevPageButton:Disable() end
if currentPage ~= maxPages then SpellBookNextPageButton:Enable() else SpellBookNextPageButton:Disable() end
end
-- ref SpellBook_GetCurrentPage (l.481-493), both branches. `maxPages` is clamped to at
-- least 1 (a defensive addition this file's own — an empty/not-yet-fed book must still
-- show ONE (empty) page rather than disabling both page buttons into a currentPage=1 >
-- maxPages=0 dead state).
--
-- The pet branch reads `SPELLBOOK_PAGENUMBERS["pet"]`, which is the counter NOTHING ever
-- writes — see the page-turn handlers. So it is always 1, and with it `maxPages > 1` the
-- Next arrow stays enabled forever on a big pet book. Reference behaviour; see the
-- do-not-fix note on `SpellBook_GetSpellID`.
function SpellBook_GetCurrentPage()
local numPetSpells = HasPetSpells()
if numPetSpells and SpellBookFrame.bookType == BOOKTYPE_PET then
return SPELLBOOK_PAGENUMBERS["pet"] or 1,
max(ceil(numPetSpells / SPELLS_PER_PAGE), 1)
end
local currentPage = SPELLBOOK_PAGENUMBERS[SpellBookFrame.selectedSkillLine] or 1
local temp, texture, offset, numSpells = GetSpellTabInfo(SpellBookFrame.selectedSkillLine)
local maxPages = max(ceil((numSpells or 0) / SPELLS_PER_PAGE), 1)
return currentPage, maxPages
end
]]></Script>
<!--
The skill-line tab (ref SpellBookSkillLineTabTemplate, l.4-39): 32x32, the
SpellBook-SkillLineTab 64x64 backdrop offset (-3,11), an empty NormalTexture placeholder
(filled at runtime by SetNormalTexture — SetNormalTexture creates the region lazily on
first call, `benilla-ui/src/script/button.rs`'s `ensure_slot`, verified this session), the
ADD highlight + CheckedTexture ring for the SELECTED tab.
-->
<CheckButton name="BenillaSpellBookSkillLineTabTemplate" virtual="true">
<Size><AbsDimension x="32" y="32"/></Size>
<Layers>
<Layer level="BACKGROUND">
<Texture file="Interface\SpellBook\SpellBook-SkillLineTab">
<Size><AbsDimension x="64" y="64"/></Size>
<Anchors>
<Anchor point="TOPLEFT"><Offset><AbsDimension x="-3" y="11"/></Offset></Anchor>
</Anchors>
</Texture>
</Layer>
</Layers>
<NormalTexture/>
<HighlightTexture file="Interface\Buttons\ButtonHilight-Square" alphaMode="ADD"/>
<CheckedTexture file="Interface\Buttons\CheckButtonHilight" alphaMode="ADD"/>
<Scripts>
<OnClick>SpellBookSkillLineTab_OnClick()</OnClick>
<OnEnter>
GameTooltip:SetOwner(self, "ANCHOR_RIGHT")
if self.tooltip then
GameTooltip:SetText(self.tooltip)
GameTooltip:Show()
end
</OnEnter>
<OnLeave>GameTooltip:Hide()</OnLeave>
</Scripts>
</CheckButton>
<!--
The spell button (ref SpellButtonTemplate, l.74-201): 37x37, the UI-Spellbook-SpellBackground
64x64 backdrop offset (-3,3), the icon (setAllPoints, hidden until fed), the name/rank
FontStrings anchored off the button's own RIGHT edge, the UI-Quickslot2 ring + Depress push +
ADD highlight + the CheckedTexture ring (`IsCurrentCast`'s glow), and the Cooldown child
(ref l.136: $parentCooldown inherits CooldownFrameTemplate — ours is the native Cooldown
widget at the template's own 36x36 CENTER (0,-1), the ActionBar/BagFrame transcription).
The AutoCast overlay/model is OMITTED (this file's header comment — pet-only autocast).
-->
<CheckButton name="BenillaSpellButtonTemplate" virtual="true">
<Size><AbsDimension x="37" y="37"/></Size>
<Layers>
<Layer level="BACKGROUND">
<Texture name="$parentBackground" file="Interface\Spellbook\UI-Spellbook-SpellBackground">
<Size><AbsDimension x="64" y="64"/></Size>
<Anchors>
<Anchor point="TOPLEFT"><Offset><AbsDimension x="-3" y="3"/></Offset></Anchor>
</Anchors>
</Texture>
</Layer>
<Layer level="OVERLAY">
<!-- The corner brackets that say a pet spell CAN autocast. The art fills only the
middle 33/64 = 0.5156 of UI-AutoCastableOverlay.blp, so where the brackets land
is set entirely by how big the texture is drawn:
pet button ref 58 -> 58 x 0.5156 = 29.9 on a 30 button. Dead in the corners.
spell book ref 60 -> 60 x 0.5156 = 30.9 on a 37 button. THREE UNITS INSIDE
each edge, floating — the ref barely grew the overlay (58->60)
while the button grew 30->37.
DEVIATION 2 of 3 in this widget (decision 1393, extending 1392). 71.53 is the
pet button's own ratio carried over — 37 x 58/30 — so the bracket square lands
on this button's corners exactly as it does on that one. Write 60 to go back to
the reference. -->
<Texture name="$parentAutoCastable" file="Interface\Buttons\UI-AutoCastableOverlay" hidden="true">
<Size><AbsDimension x="71.53" y="71.53"/></Size>
<Anchors><Anchor point="CENTER"><Offset><AbsDimension x="0" y="0"/></Offset></Anchor></Anchors>
</Texture>
<!-- The animated sibling: the ref's $parentAutoCast <Model>, which on this
template carries its own 36x36 CENTER (1,1) rect at scale="1.22".
DEVIATION 3 of 3 (decision 1393, extending 1392), and both halves are the same
idea — borrow the pet button's proportions so this button reads like that one:
* rect 36 CENTER (1,1) -> 37 CENTER (0,0). The ref's +1,+1 puts the glow a
unit up-and-right of the brackets, which sit dead centre; measured on the
director's capture, the glow's centre really was ~1 unit right of the
button's. Concentric now, so top/right stop reading loose.
* scale 1.22 -> 1.48. 0.02 x 1280 x 1.48 = 37.888 = 1.024 x 37 — the same
1.024 rim/viewport ratio the ref writes on the pet button, which is what
puts the rim ON the viewport edge so the widget's own scissor halves every
star (1387). At the ref's 0.87x the rim floats clear and nothing clips it.
To go back to the reference exactly: 36x36, CENTER (1,1), and 1.22. -->
<Texture name="$parentShine" file="benilla:autocast-shine:1.48" hidden="true">
<Size><AbsDimension x="37" y="37"/></Size>
<Anchors><Anchor point="CENTER"><Offset><AbsDimension x="0" y="0"/></Offset></Anchor></Anchors>
</Texture>
</Layer>
<Layer level="BORDER">
<Texture name="$parentIconTexture" setAllPoints="true" hidden="true"/>
<!-- maxLines="3" unmodeled (this file's header comment) — wraps unbounded instead. -->
<FontString name="$parentSpellName" inherits="GameFontNormal" hidden="true" justifyH="LEFT">
<Size><AbsDimension x="103" y="0"/></Size>
<Anchors>
<Anchor point="LEFT" relativePoint="RIGHT"><Offset><AbsDimension x="4" y="0"/></Offset></Anchor>
</Anchors>
</FontString>
<FontString name="$parentSubSpellName" inherits="SubSpellFont" hidden="true" justifyH="LEFT">
<Size><AbsDimension x="79" y="18"/></Size>
<Anchors>
<Anchor point="TOPLEFT" relativeTo="$parentSpellName" relativePoint="BOTTOMLEFT">
<Offset><AbsDimension x="0" y="4"/></Offset>
</Anchor>
</Anchors>
</FontString>
</Layer>
</Layers>
<Frames>
<Cooldown name="$parentCooldown" hidden="true">
<Size><AbsDimension x="36" y="36"/></Size>
<Anchors>
<Anchor point="CENTER" relativePoint="CENTER"><Offset><AbsDimension x="0" y="-1"/></Offset></Anchor>
</Anchors>
</Cooldown>
</Frames>
<Scripts>
<OnLoad>SpellButton_OnLoad()</OnLoad>
<OnEvent>SpellButton_OnEvent(event)</OnEvent>
<OnClick>SpellButton_OnClick()</OnClick>
<OnShow>BenillaSpellButton_UpdateButton(self)</OnShow>
<!-- The reference routes both drag entries to the plain click path with drag=1
(SpellButtonTemplate l.169-174) — the bag/doll/action slots' own precedent. -->
<OnDragStart>SpellButton_OnClick(1)</OnDragStart>
<OnReceiveDrag>SpellButton_OnClick(1)</OnReceiveDrag>
<OnEnter>BenillaSpellButton_OnEnter(self)</OnEnter>
<OnLeave>GameTooltip:Hide()</OnLeave>
</Scripts>
<NormalTexture name="$parentNormalTexture" file="Interface\Buttons\UI-Quickslot2">
<Size><AbsDimension x="64" y="64"/></Size>
<Anchors><Anchor point="CENTER"/></Anchors>
</NormalTexture>
<PushedTexture file="Interface\Buttons\UI-Quickslot-Depress"/>
<HighlightTexture name="$parentHighlight" file="Interface\Buttons\ButtonHilight-Square" alphaMode="ADD"/>
<CheckedTexture file="Interface\Buttons\CheckButtonHilight" alphaMode="ADD"/>
</CheckButton>
<!--
The Spell/Pet toggle tab (ref SpellBookFrameTabButtonTemplate, l.40-71): a 128x64 plate
that hangs off the window's bottom-left, drawn UNSELECTED normally and swapping to the
selected plate through its <DisabledTexture> — the ref uses Disable() for "this is the
book you are on", not a checked state, which is why this is a plain Button and not a
CheckButton. Hidden by default; SpellBookFrame_Update raises exactly the ones that exist.
-->
<Button name="BenillaSpellBookFrameTabButtonTemplate" virtual="true" hidden="true">
<Size><AbsDimension x="128" y="64"/></Size>
<HitRectInsets><AbsInset left="15" right="14" top="13" bottom="15"/></HitRectInsets>
<Scripts>
<OnClick>ToggleSpellBook(self.bookType)</OnClick>
<OnEnter>
GameTooltip:SetOwner(self, "ANCHOR_RIGHT")
-- ref l.53: the label plus its bound key. MicroButtonTooltipText is MicroMenu.xml's
-- (loaded before this file); nil-guarded like every other cross-file call here.
if MicroButtonTooltipText then
GameTooltip:SetText(MicroButtonTooltipText(self:GetText(), self.binding), 1.0, 1.0, 1.0)
else
GameTooltip:SetText(self:GetText(), 1.0, 1.0, 1.0)
end
GameTooltip:Show()
</OnEnter>
<OnLeave>GameTooltip:Hide()</OnLeave>
</Scripts>
<ButtonText>
<Anchors><Anchor point="CENTER"><Offset><AbsDimension x="0" y="3"/></Offset></Anchor></Anchors>
</ButtonText>
<NormalFont inherits="GameFontNormalSmall"/>
<HighlightFont inherits="GameFontHighlightSmall"/>
<DisabledFont inherits="GameFontHighlightSmall"/>
<NormalTexture file="Interface\SpellBook\UI-SpellBook-Tab-Unselected"/>
<HighlightTexture file="Interface\SpellBook\UI-SpellbookPanel-Tab-Highlight" alphaMode="ADD"/>
</Button>
<Frame name="SpellBookFrame" hidden="true">
<!-- The real 384x512 window (ref l.202-205). 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 (CharacterFrame's
own precedent note) — a harmless no-op today. -->
<HitRectInsets><AbsInset left="0" right="30" top="0" bottom="70"/></HitRectInsets>
<Layers>
<Layer level="BACKGROUND">
<Texture file="Interface\Spellbook\Spellbook-Icon">
<Size><AbsDimension x="58" y="58"/></Size>
<Anchors>
<Anchor point="TOPLEFT"><Offset><AbsDimension x="10" y="-8"/></Offset></Anchor>
</Anchors>
</Texture>
</Layer>
<Layer level="ARTWORK">
<!-- The four quadrant plates, anchored off the frame's own corners exactly like
the ref (l.231-263) — simpler than CharacterFrame's TOPLEFT-offset scheme
since the ref itself uses the corner anchors directly here. -->
<Texture file="Interface\Spellbook\UI-SpellbookPanel-TopLeft">
<Size><AbsDimension x="256" y="256"/></Size>
<Anchors><Anchor point="TOPLEFT"/></Anchors>
</Texture>
<Texture file="Interface\Spellbook\UI-SpellbookPanel-TopRight">
<Size><AbsDimension x="128" y="256"/></Size>
<Anchors><Anchor point="TOPRIGHT"/></Anchors>
</Texture>
<Texture file="Interface\Spellbook\UI-SpellbookPanel-BotLeft">
<Size><AbsDimension x="256" y="256"/></Size>
<Anchors><Anchor point="BOTTOMLEFT"/></Anchors>
</Texture>
<Texture file="Interface\Spellbook\UI-SpellbookPanel-BotRight">
<Size><AbsDimension x="128" y="256"/></Size>
<Anchors><Anchor point="BOTTOMRIGHT"/></Anchors>
</Texture>
<FontString name="SpellBookTitleText" inherits="GameFontNormal" text="SPELLBOOK">
<Anchors>
<Anchor point="CENTER"><Offset><AbsDimension x="6" y="230"/></Offset></Anchor>
</Anchors>
</FontString>
<FontString name="SpellBookPageText" inherits="GameFontNormal">
<Size><AbsDimension x="102" y="0"/></Size>
<Anchors>
<Anchor point="BOTTOM"><Offset><AbsDimension x="-14" y="96"/></Offset></Anchor>
</Anchors>
</FontString>
</Layer>
</Layers>
<Frames>
<!-- Close: the house minimize-button art (no UIPanelCloseButton template ships —
Gossip/Bag/Merchant/CharacterFrame's own precedent), the same TOPRIGHT(-44,-25)
position as every other window's close button. -->
<Button name="SpellBookFrameCloseButton">
<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(SpellBookFrame)</OnClick>
</Scripts>
</Button>
<!-- ref l.288-317. Tab 3 exists in the ref's own XML and is never raised in 1.12
(there is no third book); kept so the anchor chain reads like the ref's. -->
<Button name="SpellBookFrameTabButton1" inherits="BenillaSpellBookFrameTabButtonTemplate">
<Anchors>
<Anchor point="CENTER" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="79" y="61"/></Offset></Anchor>
</Anchors>
<DisabledTexture file="Interface\SpellBook\UI-SpellBook-Tab1-Selected"/>
</Button>
<Button name="SpellBookFrameTabButton2" inherits="BenillaSpellBookFrameTabButtonTemplate">
<Anchors>
<Anchor point="LEFT" relativeTo="SpellBookFrameTabButton1" relativePoint="RIGHT">
<Offset><AbsDimension x="-20" y="0"/></Offset>
</Anchor>
</Anchors>
<DisabledTexture file="Interface\SpellBook\UI-SpellBook-Tab1-Selected"/>
</Button>
<Button name="SpellBookFrameTabButton3" inherits="BenillaSpellBookFrameTabButtonTemplate">
<Anchors>
<Anchor point="LEFT" relativeTo="SpellBookFrameTabButton2" relativePoint="RIGHT">
<Offset><AbsDimension x="-20" y="0"/></Offset>
</Anchor>
</Anchors>
<DisabledTexture file="Interface\SpellBook\UI-SpellBook-Tab3-Selected"/>
</Button>
<Button name="SpellBookPrevPageButton">
<Size><AbsDimension x="32" y="32"/></Size>
<Anchors>
<Anchor point="CENTER" relativeTo="SpellBookFrame" relativePoint="BOTTOMLEFT">
<Offset><AbsDimension x="50" y="105"/></Offset>
</Anchor>
</Anchors>
<Layers>
<Layer level="BACKGROUND">
<FontString inherits="GameFontNormal" justifyH="LEFT" text="PREV">
<Anchors><Anchor point="LEFT" relativeTo="SpellBookPrevPageButton" relativePoint="RIGHT"/></Anchors>
</FontString>
</Layer>
</Layers>
<Scripts><OnClick>BenillaSpellBookPrevPageButton_OnClick()</OnClick></Scripts>
<NormalTexture file="Interface\Buttons\UI-SpellbookIcon-PrevPage-Up"/>
<PushedTexture file="Interface\Buttons\UI-SpellbookIcon-PrevPage-Down"/>
<DisabledTexture file="Interface\Buttons\UI-SpellbookIcon-PrevPage-Disabled"/>
<HighlightTexture file="Interface\Buttons\UI-Common-MouseHilight" alphaMode="ADD"/>
</Button>
<Button name="SpellBookNextPageButton">
<Size><AbsDimension x="32" y="32"/></Size>
<Anchors>
<Anchor point="CENTER" relativeTo="SpellBookFrame" relativePoint="BOTTOMLEFT">
<Offset><AbsDimension x="314" y="105"/></Offset>
</Anchor>
</Anchors>
<Layers>
<Layer level="BACKGROUND">
<FontString inherits="GameFontNormal" justifyH="RIGHT" text="NEXT">
<Anchors><Anchor point="RIGHT" relativeTo="SpellBookNextPageButton" relativePoint="LEFT"/></Anchors>
</FontString>
</Layer>
</Layers>
<Scripts><OnClick>BenillaSpellBookNextPageButton_OnClick()</OnClick></Scripts>
<NormalTexture file="Interface\Buttons\UI-SpellbookIcon-NextPage-Up"/>
<PushedTexture file="Interface\Buttons\UI-SpellbookIcon-NextPage-Down"/>
<DisabledTexture file="Interface\Buttons\UI-SpellbookIcon-NextPage-Disabled"/>
<HighlightTexture file="Interface\Buttons\UI-Common-MouseHilight" alphaMode="ADD"/>
</Button>
<!-- The 12-spell page grid (ref l.387-494): 2 columns x 6 rows, ids assigned the
ref's own column-major pattern (col1 ids 1-6, col2 ids 7-12) — SpellBook_GetSpellID
reads a button's `id=` as its within-page/within-tab 1-based position, not its
screen order. -->
<CheckButton name="SpellButton1" inherits="BenillaSpellButtonTemplate" id="1">
<Anchors><Anchor point="TOPLEFT"><Offset><AbsDimension x="34" y="-85"/></Offset></Anchor></Anchors>
</CheckButton>
<CheckButton name="SpellButton2" inherits="BenillaSpellButtonTemplate" id="7">
<Anchors><Anchor point="TOPLEFT" relativeTo="SpellButton1"><Offset><AbsDimension x="157" y="0"/></Offset></Anchor></Anchors>
</CheckButton>
<CheckButton name="SpellButton3" inherits="BenillaSpellButtonTemplate" id="2">
<Anchors><Anchor point="TOPLEFT" relativeTo="SpellButton1" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="0" y="-14"/></Offset></Anchor></Anchors>
</CheckButton>
<CheckButton name="SpellButton4" inherits="BenillaSpellButtonTemplate" id="8">
<Anchors><Anchor point="TOPLEFT" relativeTo="SpellButton3"><Offset><AbsDimension x="157" y="0"/></Offset></Anchor></Anchors>
</CheckButton>
<CheckButton name="SpellButton5" inherits="BenillaSpellButtonTemplate" id="3">
<Anchors><Anchor point="TOPLEFT" relativeTo="SpellButton3" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="0" y="-14"/></Offset></Anchor></Anchors>
</CheckButton>
<CheckButton name="SpellButton6" inherits="BenillaSpellButtonTemplate" id="9">
<Anchors><Anchor point="TOPLEFT" relativeTo="SpellButton5"><Offset><AbsDimension x="157" y="0"/></Offset></Anchor></Anchors>
</CheckButton>
<CheckButton name="SpellButton7" inherits="BenillaSpellButtonTemplate" id="4">
<Anchors><Anchor point="TOPLEFT" relativeTo="SpellButton5" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="0" y="-14"/></Offset></Anchor></Anchors>
</CheckButton>
<CheckButton name="SpellButton8" inherits="BenillaSpellButtonTemplate" id="10">
<Anchors><Anchor point="TOPLEFT" relativeTo="SpellButton7"><Offset><AbsDimension x="157" y="0"/></Offset></Anchor></Anchors>
</CheckButton>
<CheckButton name="SpellButton9" inherits="BenillaSpellButtonTemplate" id="5">
<Anchors><Anchor point="TOPLEFT" relativeTo="SpellButton7" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="0" y="-14"/></Offset></Anchor></Anchors>
</CheckButton>
<CheckButton name="SpellButton10" inherits="BenillaSpellButtonTemplate" id="11">
<Anchors><Anchor point="TOPLEFT" relativeTo="SpellButton9"><Offset><AbsDimension x="157" y="0"/></Offset></Anchor></Anchors>
</CheckButton>
<CheckButton name="SpellButton11" inherits="BenillaSpellButtonTemplate" id="6">
<Anchors><Anchor point="TOPLEFT" relativeTo="SpellButton9" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="0" y="-14"/></Offset></Anchor></Anchors>
</CheckButton>
<CheckButton name="SpellButton12" inherits="BenillaSpellButtonTemplate" id="12">
<Anchors><Anchor point="TOPLEFT" relativeTo="SpellButton11"><Offset><AbsDimension x="157" y="0"/></Offset></Anchor></Anchors>
</CheckButton>
<!-- The right-edge skill-line tabs, up to MAX_SKILLLINE_TABS (8) — ref l.495-566. -->
<CheckButton name="SpellBookSkillLineTab1" inherits="BenillaSpellBookSkillLineTabTemplate" id="1">
<Anchors><Anchor point="TOPLEFT" relativePoint="TOPRIGHT"><Offset><AbsDimension x="-32" y="-65"/></Offset></Anchor></Anchors>
</CheckButton>
<CheckButton name="SpellBookSkillLineTab2" inherits="BenillaSpellBookSkillLineTabTemplate" id="2">
<Anchors><Anchor point="TOPLEFT" relativeTo="SpellBookSkillLineTab1" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="0" y="-17"/></Offset></Anchor></Anchors>
</CheckButton>
<CheckButton name="SpellBookSkillLineTab3" inherits="BenillaSpellBookSkillLineTabTemplate" id="3">
<Anchors><Anchor point="TOPLEFT" relativeTo="SpellBookSkillLineTab2" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="0" y="-17"/></Offset></Anchor></Anchors>
</CheckButton>
<CheckButton name="SpellBookSkillLineTab4" inherits="BenillaSpellBookSkillLineTabTemplate" id="4">
<Anchors><Anchor point="TOPLEFT" relativeTo="SpellBookSkillLineTab3" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="0" y="-17"/></Offset></Anchor></Anchors>
</CheckButton>
<CheckButton name="SpellBookSkillLineTab5" inherits="BenillaSpellBookSkillLineTabTemplate" id="5">
<Anchors><Anchor point="TOPLEFT" relativeTo="SpellBookSkillLineTab4" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="0" y="-17"/></Offset></Anchor></Anchors>
</CheckButton>
<CheckButton name="SpellBookSkillLineTab6" inherits="BenillaSpellBookSkillLineTabTemplate" id="6">
<Anchors><Anchor point="TOPLEFT" relativeTo="SpellBookSkillLineTab5" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="0" y="-17"/></Offset></Anchor></Anchors>
</CheckButton>
<CheckButton name="SpellBookSkillLineTab7" inherits="BenillaSpellBookSkillLineTabTemplate" id="7">
<Anchors><Anchor point="TOPLEFT" relativeTo="SpellBookSkillLineTab6" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="0" y="-17"/></Offset></Anchor></Anchors>
</CheckButton>
<CheckButton name="SpellBookSkillLineTab8" inherits="BenillaSpellBookSkillLineTabTemplate" id="8">
<Anchors><Anchor point="TOPLEFT" relativeTo="SpellBookSkillLineTab7" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="0" y="-17"/></Offset></Anchor></Anchors>
</CheckButton>
</Frames>
<Scripts>
<OnLoad>SpellBookFrame_OnLoad()</OnLoad>
<OnEvent>SpellBookFrame_OnEvent()</OnEvent>
<OnShow>SpellBookFrame_OnShow()</OnShow>
<OnHide>SpellBookFrame_OnHide()</OnHide>
</Scripts>
</Frame>
</Ui>