Files
benilla-octo/crates/benilla-app/assets/ui/ActionBar.xml
T
SamandClaude Fable 5 45f001bbc5 the hover tax is pinned and cut, and a spell-less macro is a button
- a tooltip change costs ~1.3 ms — five whole-interface recomputes, each working as designed: the quad re-convert at 474 µs, the Lua tick at 287, the layout resolve at 200, bevy's `Assets<Mesh>` tax at ~270 and the mesh rebuild at 146 — pinned with new hover probes (a jitter dial, a `WOW_PROBE_HOVER_DUTY` duty cycle so run drift cancels, an `AssetChanged` tax meter), and the layout preamble's two whole-roster scans become lists maintained at the writer (52–83 µs → 1–11)
- the splice compared this frame's entries against last frame's by index, so one inserted highlight shifted the 160 entries behind it and read as "everything changed"; it now aligns on `z` — an identity, not a position — and a `Text` entry that writes only quads became spliceable, so a tooltip change re-converts 22 entries instead of 520 (`convert` +439 µs → +107), with `WOW_UI_SPLICE_VERIFY` running the full conversion behind every spliced frame: 0 mismatches over 1,078
- a spell-less macro is a usable action button, not the 0.4 grey — the reference's usable compute has a dedicated leg answering usable for the bound-spell zero, cross-checked at the bytes in the reference's disassembly — and the button's Name line is written unconditionally from `GetActionText` as the reference does, so the macro's name sits under its icon and a `/cast` of a spell the character does not know now greys, which is also 1.12's behaviour (B340)
- a capture bounds its run in wall-clock seconds, because a frame cap is no cap at the ~1 fps a starved `nextDrawable` leaves — 1800 frames is thirty minutes there — and the teardown backstop runs on every run as a liveness guarantee, carrying the exit code the run asked for; the Multisampling row reads Off / 2x / 4x, since "1x" contradicts the no-multisampling byte both of the reference's enumerators normalise to and the bit-depth prefix was identical on every row

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-27 15:13:58 +07:00

2316 lines
146 KiB
XML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<Ui>
<!--
benilla's main action bar, RE-SKINNED to the real 1.12 MainMenuBar (decision 0068 slice-1;
dressing arc, decision 0084). The geometry + art below are QUOTED from the real FrameXML
(scratchpad refs, line citations inline): ref-MainMenuBar.xml (the bar art, end caps, XP bar),
ref-ActionBarFrame.xml (the 12-button anchor chain + page buttons), ref-ActionButtonTemplate.xml
(the button template). The Lua data flow is UNCHANGED — the vanilla page formula, the
ACTIONBAR_SLOT_CHANGED repaint, HasAction/GetActionTexture in, UseAction out.
Structure mirrors the real hierarchy so the draw order falls out of the frame tree:
MainMenuBar (1024x53, BOTTOM of screen)
├─ MainMenuExpBar (StatusBar, TOP) — drawn first ⇒ lowest
│ ├─ ExhaustionLevelFillBar (BORDER) — the pale rested wash up to the tick
│ └─ MainMenuBarOverlayFrame (DIALOG) — the hover "curr / max" numerals
├─ MainMenuBarArtFrame (fills the bar) — dwarf strips + end caps over the XP bar
│ └─ 12 buttons + page up/down — child frames ⇒ over the art
└─ ExhaustionTick (DIALOG) — the rested-boundary marker on the strip
(The real client gets the same order via RaiseFrameLevel(MainMenuBarArtFrame); our loader's
later-sibling-is-higher + child-over-parent rules reproduce it without the explicit raise.)
SCOPE / real-client divergences (noted, not bugs):
- No frame scale: the real bar is UIParent-scaled; we render 1:1. Geometry is otherwise exact.
- Page up/down page the bar for real (`CURRENT_ACTIONBAR_PAGE`, below) and the numeral
follows through `ChangeActionBarPage`, not through the ref's ACTIONBAR_PAGE_CHANGED —
which we never fire, so an addon LISTENING for the page change hears nothing (it is a
Rust-side event with no Lua producer). Addons that drive paging call the verb.
- HotKey renders the LIVE binding label, abbreviated the ref's own way since 1008
(GetBindingText(key, "KEY_", 1) — UIParent.xml carries our own version; "s-2" for
SHIFT-2, "·" for a 2+-modifier chord). A slot with NO binding wears the ref's
RANGE_INDICATOR dot path instead (red dot only while out of range); a bound slot's
out-of-range read is the label's red tint (the OnUpdate range timer below).
- The state/feedback layer (decision 0137 phase 4) is the ref ActionButton.lua re-expressed:
UpdateState (checked on IsCurrentAction/IsAutoRepeatAction), UpdateUsable (the 1/1/1 ·
0.5/0.5/1 · 0.4/0.4/0.4 vertex-color trio), UpdateCooldown (CooldownFrame_SetTimer),
the Border's equipped green, and the 0.4 s Flash blink — with ONE structural deviation:
the event set registers on the BAR frame and fans to all 12 buttons, instead of the
ref's per-button Register/Unregister churn on HasAction (12 fixed buttons make the
skip-empties optimization noise; the handlers themselves are the ref's).
- Count: ActionButton_UpdateCount re-expressed, gated on IsConsumableAction exactly as the
ref does (0216 §7's "count > 0" simplification is retired — it put a stack number under
a mount, which has no stack; the engine-side law is ItemInfo::is_consumable, byte-cited
from 0x4e5250).
- Empty-slot grid (ref ActionButton.lua:241-259's ShowGrid/HideGrid, a per-button COUNTER):
the counter is BACK, and the argument that retired it is exactly why. 0216 §7 collapsed it
to one global boolean because "the cursor is a single global payload, so no two shows can
ever nest" — true while the payload was the only thing that could ask for a grid. Decision
1500 gives it a second asker (ALWAYS_SHOW_MULTIBARS, which holds the four extra bars'
empty wells open with no payload in hand), and with two independent askers the boolean is
wrong by its own reasoning: dropping a payload would close wells the option is still
asking for. So `button.showgrid` COUNTS, exactly as the reference's does, and
ActionButton_ShowGrid/_HideGrid below are its two verbs.
The count — rather than a flag — is also what the addon corpus already assumes: zBar
REPLACES both verbs and writes `button.showgrid = 1/0`, CT_BarMod writes 0/1/2 and
increments it on OUR ActionButton1-12, and Bartender2 HOOKS ActionButton_ShowGrid at
load — which died on a nil global here until now, the same session-start crash class
1218/1219 fixed for MultiBarLeft.
Unlike the ref (which HIDES an empty, non-grid slot entirely), our MAIN bar always shows
all 12 wells; the grid only swaps an EMPTY slot's ring art between the occupied and empty
Quickslot textures. The ref's extra SetVertexColor(1,1,1,0.5) dim on a grid ring is NOT
ours — that look is unchanged since 0216 and is the director's to move.
- Exhaustion tick + rest fill, XP text (hover/cvar-gated), MaxLevelBar: OUT. The bar's
right-hand cluster is otherwise complete — the backpack toggle + four bag slots live in
BagFrame.xml and the eight micro buttons in MicroMenu.xml (both anchored into
MainMenuBarArtFrame and seated over the art by BenillaActionBarArt_SeatAbove below),
and the performance meter is at the foot of this file. The KEYRING landed with decision
0765: its button and container live in BagFrame.xml (the ref's own split —
MainMenuBarBagButtons.xml / ContainerFrame.lua), and the bar's half of it — swapping the
two right-hand strips to the keyring art and sliding the performance meter over to make
room — is MainMenuBar_UpdateKeyRing below, exactly where the ref keeps it
(MainMenuBar.lua l.174-183).
-->
<Script><![CDATA[
BENILLA_FALLBACK_ICON = "Interface\\Icons\\INV_Misc_QuestionMark"
-- **The reference's action-bar constant block (ActionButton.lua:1-9).** Every one of these
-- was cited in comments across this file and MultiBars.xml and DEFINED nowhere, so an addon
-- reading them got nil. `zBar.lua:40` is the shape:
--
-- to = to or value.max or NUM_ACTIONBAR_BUTTONS
-- for i = from, to do -- 'for' limit must be a number
--
-- Found by the use-probe, which is the only column that touches anything.
--
-- `CURRENT_ACTIONBAR_PAGE` used to be withheld from this list on the grounds that it is the
-- reference's MUTABLE page cursor and we did not page the main bar — "defining it as a
-- frozen 1 would hand every addon a number that is silently wrong forever". That reasoning
-- was right and is now discharged rather than overruled: the bar pages, the global is live
-- state the paged-id formula reads, and it is declared with the paging verbs below rather
-- than here among the true constants. A frozen 1 would still be a lie; this one moves.
NUM_ACTIONBAR_PAGES = 6
NUM_ACTIONBAR_BUTTONS = 12
RIGHT_ACTIONBAR_PAGE = 3
LEFT_ACTIONBAR_PAGE = 4
BOTTOMRIGHT_ACTIONBAR_PAGE = 5
BOTTOMLEFT_ACTIONBAR_PAGE = 6
-- Ref ActionButton.lua:4 / UIParent.lua: the flash blink period and the range-check cadence.
ATTACK_BUTTON_FLASH_TIME = 0.4
TOOLTIP_UPDATE_TIME = 0.2
-- "Lock ActionBars" (ref UIOptionsFrame.lua:102-103 — uvar LOCK_ACTIONBAR, default "0").
-- The reference declares it in its options window; ours lives with the bar it locks, which
-- is this codebase's rule for every stock global (1128/1134): the assignment sits beside its
-- consumers, and the options row CAPTURES it rather than restating a default of its own.
-- Read by the drag guards below and by the pet bar's (PetActionBar.xml, the ref's own second
-- consumer at PetActionBarFrame.lua:270/278); driven by the Action Bars page and by the
-- TOGGLEACTIONBARLOCK binding the option's tooltip promises (decision 1136).
LOCK_ACTIONBAR = "0"
RegisterForSave("LOCK_ACTIONBAR")
-- CooldownFrame_SetTimer moved to Cooldown.xml (the ref's own file split) — the bag
-- slots share it, so it loads before both consumers.
-- One button's HotKey corner — the ref's ActionButton_UpdateHotkeys law in FULL since
-- 1008 (closing 0997's raw-token residue): the ABBREVIATED binding text through the
-- GetBindingText the ref specifies (ours, in UIParent.xml — one modifier abbreviates, "s-2";
-- two or more collapse to the ref's "·"), and with NO binding the ref's no-binding
-- leg — an empty label the OnUpdate range timer fills with the RANGE_INDICATOR dot
-- while out of range (until 1008 that leg was multibar-only; now any unbound slot,
-- main bar included, wears it). A harness with no registered bindings paints nothing.
function BenillaActionButton_PaintHotkey(button)
local key = GetBindingKey(button.bindingCommand)
local text
if GetBindingText then
text = GetBindingText(key, "KEY_", 1)
else
-- lone-file harness (the shipped manifest always has UIParent.xml first):
-- the raw token, the MicroMenu.xml fallback idiom.
text = key or ""
end
button.noBindingLabel = (text == "")
button.hotkey:SetText(text)
end
-- Every button's label, repainted on UPDATE_BINDINGS (a rebind, a set switch, the seed).
function BenillaActionBar_UpdateHotkeys()
for i = 1, table.getn(BENILLA_ACTION_BUTTONS) do
BenillaActionButton_PaintHotkey(BENILLA_ACTION_BUTTONS[i])
end
end
-- Every live action button — the main bar's 12 plus the multibars' (MultiBars.xml joins
-- at OnLoad). The fan-out roster for the bar-level events below: the ref registers the
-- feedback events per-button; our bar-level fan needs the full set in one place.
BENILLA_ACTION_BUTTONS = {}
-- ── Main-bar paging (ref ActionButton.lua l.1-12 and l.63-101) ───────────────────────
-- The main bar has six pages, and we already hold every slot they address: the app owns
-- the authoritative 1..120 action table, so the DATA for pages 2-6 was always there and
-- only the page SELECTOR was missing. Its absence was on screen, not just in the API —
-- the page arrows below shipped as art with no OnClick, which this file's own comment
-- admitted ("ART ONLY - we have no bar paging, so the clicks are no-ops").
--
-- `Bartender2.lua:686` is the corpus tell: three bare `ChangeActionBarPage()` calls, and
-- the addon died on the nil global at session start.
CURRENT_ACTIONBAR_PAGE = 1
-- "Table of actionbar pages and whether they're viewable or not" (the reference's own
-- comment). The page walk below honours it rather than counting, which is what makes the
-- whole thing work: a page an extra bar already displays must not ALSO be reachable on the
-- main bar, or paging up shows you the twelve actions already on screen beside it.
--
-- **This is the shipped START state, not a constant: it FOLLOWS the four bar toggles.**
-- All six pages are viewable here because all four extra bars now ship hidden (decision
-- 1500 — they are player options, off by default), and `MultiActionBar_Update`
-- (MultiBars.xml) does the arithmetic from then on: a bar that comes up takes its page out
-- of the cycle, a bar that goes down puts it back. Blanking two rows at declaration was
-- only ever right because visibility was static (0270's always-on bottom pair), and the
-- moment the player can move it a declaration cannot be the answer.
VIEWABLE_ACTION_BAR_PAGES = {1, 1, 1, 1, 1, 1}
-- `ChangeActionBarPage()` takes NO arguments — every reference caller sets
-- CURRENT_ACTIONBAR_PAGE first and then calls this to apply it (l.79, l.100), and
-- Bartender2 calls it bare.
--
-- Upstream it is a client binding; here it is FrameXML, deliberately. Our paged action id
-- is computed in Lua by `BenillaActionBar_ActionFor`, so the page IS this global and the
-- only work left for the verb is the work the reference gets by firing
-- ACTIONBAR_PAGE_CHANGED: repaint the twelve buttons, and restamp the page numeral (the
-- ref's own listener for that event is the up arrow's OnEvent, l.176-181 — the numeral is
-- the ONLY thing it does there). Addons CALL this, they never replace it, so the
-- divergence is unobservable from Lua — unlike a name we shadow, which is 1222's class.
function ChangeActionBarPage()
MainMenuBarPageNumber:SetText(CURRENT_ACTIONBAR_PAGE)
for i = 1, table.getn(BENILLA_ACTION_BUTTONS) do
BenillaActionButton_Update(BENILLA_ACTION_BUTTONS[i])
end
end
function ActionBar_PageUp()
local page = CURRENT_ACTIONBAR_PAGE + 1
local nextPage
for i = page, NUM_ACTIONBAR_PAGES do
if VIEWABLE_ACTION_BAR_PAGES[i] then nextPage = i break end
end
-- Past the last viewable page, wrap to 1 — the reference wraps to the LITERAL 1 here
-- rather than to the first viewable page, which is its own asymmetry with PageDown
-- (which rescans). Kept, because it is observable: with page 1 blanked, paging up off
-- the end lands on a page the down-walk would have skipped.
CURRENT_ACTIONBAR_PAGE = nextPage or 1
ChangeActionBarPage()
end
function ActionBar_PageDown()
local page = CURRENT_ACTIONBAR_PAGE - 1
local prevPage
for i = page, 1, -1 do
if VIEWABLE_ACTION_BAR_PAGES[i] then prevPage = i break end
end
if not prevPage then
for i = NUM_ACTIONBAR_PAGES, 1, -1 do
if VIEWABLE_ACTION_BAR_PAGES[i] then prevPage = i break end
end
end
CURRENT_ACTIONBAR_PAGE = prevPage; ChangeActionBarPage()
end
-- Seat a TOP-LEVEL frame that anchors INTO the bar art (the bag bar + backpack toggle,
-- BagFrame.xml; the micro-button row, MicroMenu.xml) one frame level above the art, so its
-- icons draw over the bar plate instead of under it.
--
-- Why it's needed: those frames are top-level, so they default to a LOWER frame level than
-- the bar's own art (a child hierarchy — the ExpBar's dwarf notches + the metal/well art sit
-- at the bar's child level). Frame level dominates the flat painter's order, so the bar art
-- would paint OVER them — the ring shows but the centered icon is fully covered (the "empty
-- rings, no bag icons" bug). The bar's OWN 12 buttons avoid this by being CHILDREN of the art
-- frame (child-over-parent). (SetParent onto the art frame would be the structural twin, but
-- the engine keys draw order at creation and does not re-key on a runtime reparent, so the
-- explicit level is the reliable lever.) Lives here, with the frame it's about, because two
-- files now need it — it began as BagFrame.xml's private BenillaBagBar_SeatAboveActionBar.
function BenillaActionBarArt_SeatAbove(frame)
local art = getglobal("MainMenuBarArtFrame")
if art then frame:SetFrameLevel(art:GetFrameLevel() + 1) end
end
-- ── The keyring's half of the bar (ref MainMenuBar_UpdateKeyRing, MainMenuBar.lua
-- l.174-183; decision 0765) ──────────────────────────────────────────────────────────────
-- The keyring is not just a button bolted onto the bar: the bar's two RIGHT-hand strips are
-- re-textured to a variant plate that has a socket for it, and the performance meter slides
-- 8px further left out of the way. Both atlases are 256-wide 43px strips at the same on-
-- screen size and anchors, so only the file + TexCoords change (UI-MainMenuBar-KeyRing is
-- 256x128 and holds exactly the two bands the ref names: 0.3359375 * 128 = 43px each).
--
-- Called by KeyRingButton's own OnEvent (BagFrame.xml). Everything is nil-guarded:
-- this file loads without BagFrame.xml in several harnesses, and BagFrame.xml loads without
-- this one in ~25 bag/bank/escape/merchant tests — either way the missing half no-ops.
--
-- DIVERGENCE (decision 0765): the ref latches this ON forever the first time HasKey() is
-- true, via the saved variable SHOW_KEYRING (and pulses the button for 60s to announce it).
-- This engine has no saved variables, and a session-scoped latch would leak across a
-- character switch — showing a keyring on a character with no key — so the state is derived
-- from HasKey() on every BAG_UPDATE instead, in BOTH directions. Observably identical
-- unless you destroy your last key, which then un-shows the keyring rather than leaving an
-- empty one behind. The 60s pulse is out, consistently with decision 0304's "no button
-- pulse" for the micro buttons.
function MainMenuBar_UpdateKeyRing()
local show = HasKey and HasKey()
-- The two right-hand strips (MainMenuBarTexture2 at x=+128, Texture3 at x=+384),
-- keyring plate or the ordinary dwarf plate. The dwarf coords are this file's own
-- authored ones (see the strips below) — restated here because this is the only code
-- that ever changes them back.
local strips = {
{ name = "MainMenuBarTexture2",
keyring = 0.6640625, keyringBottom = 1.0,
dwarf = 0.33203125, dwarfBottom = 0.5 },
{ name = "MainMenuBarTexture3",
keyring = 0.1640625, keyringBottom = 0.5,
dwarf = 0.08203125, dwarfBottom = 0.25 },
}
for _, strip in ipairs(strips) do
local tex = getglobal(strip.name)
if tex then
if show then
tex:SetTexture("Interface\\MainMenuBar\\UI-MainMenuBar-KeyRing")
tex:SetTexCoord(0, 1, strip.keyring, strip.keyringBottom)
else
tex:SetTexture("Interface\\MainMenuBar\\UI-MainMenuBar-Dwarf")
tex:SetTexCoord(0, 1, strip.dwarf, strip.dwarfBottom)
end
end
end
-- The performance meter's recess moves with the plate: -227 without the keyring,
-- -235 with it (ref l.180). Its own comment at the foot of this file has the geometry.
local perf = getglobal("MainMenuBarPerformanceBarFrame")
local bar = getglobal("MainMenuBar")
if perf and bar then
perf:SetPoint("BOTTOMRIGHT", bar, "BOTTOMRIGHT", (show and -235) or -227, -10)
end
local button = getglobal("KeyRingButton")
if button then
if show then button:Show() else button:Hide() end
end
-- Losing the last key with the keyring open would strand an orphan window; the ref
-- can't reach this state (its latch is one-way), so this is ours to decide.
local frame = getglobal("BenillaKeyRingFrame")
if frame and not show and frame:IsShown() then frame:Hide() end
end
-- ── The bonus-bar slide (decision 1524; ref BonusActionBarFrame.lua:1-98) ──────────────
--
-- The reference never re-pages its main bar: entering a form slides a 505x43 REPLICA of
-- the bar (own art, own 12 buttons on the bonus page) up out of the screen-bottom edge
-- over 0.15s, plays igBonusBarOpen when it lands, and leaves it covering the button strip
-- for as long as the form is up; leaving the form slides it back down carrying the OLD
-- form's icons (lastBonusBar) and hides it — no sound on the way down. benilla re-pages
-- the main bar (1223) — so here the overlay is the TRANSITION VEHICLE and the faithful
-- addon-visible state (`BonusActionBarFrame:IsShown()` is true exactly while a form is
-- up, as on the real client): the main bar keeps painting the OLD page under the rising
-- overlay (BenillaActionBar_DisplayedOffset) and adopts the bonus page when it lands.
--
-- Ref timing/geometry, quoted: SLIDETIME 0.15, YPOS 43 (= the frame's own height, so it
-- rises exactly out of the screen edge), XPOS 4 (the ref Lua re-anchors every tick at 4,
-- overriding its own XML's 40; the art's left TexCoord is trimmed 4px to line back up).
-- Ours slides a PROGRESS fraction rather than the ref's mode+timer pair: flipping form
-- mid-slide turns the bar around from where it is, where the ref's timer arithmetic
-- mirror-jumps the position (a quirk, not the mechanism).
BONUSACTIONBAR_SLIDETIME = 0.15
BONUSACTIONBAR_YPOS = 43
BONUSACTIONBAR_XPOS = 4
-- Ref-named verb (BonusActionBarFrame.lua:74): addons call it. Begin (or continue toward)
-- the up-slide; a bar already landed is a no-op — a direct form→form swap (warrior stance
-- dance, cat→bear powershift) repaints without re-sliding or re-sounding, ref-verbatim.
function ShowBonusActionBar()
local frame = BonusActionBarFrame
if not frame:IsShown() then
frame.slideProgress = 0
frame:Show()
end
if (frame.slideProgress or 0) < 1 then
frame.mode = "show"
end
end
-- Ref-named verb (l.87): begin the down-slide. Nothing to do when already hidden; a
-- mid-show flip turns around from the current position.
function HideBonusActionBar()
local frame = BonusActionBarFrame
if frame:IsShown() and frame.mode ~= "hide" then
frame.mode = "hide"
end
end
function BenillaBonusBar_OnLoad(frame)
frame.mode = "none"
frame.slideProgress = 0
-- Ref field (l.17/23, read by ActionButton_GetPagedID:449-451): the last NONZERO
-- offset, so the down-slide keeps painting the form's icons after the offset is 0.
frame.lastBonusBar = 1
end
function BonusActionBar_OnUpdate(elapsed)
local frame = BonusActionBarFrame
if frame.mode == "show" then
frame.slideProgress = frame.slideProgress + elapsed / BONUSACTIONBAR_SLIDETIME
if frame.slideProgress >= 1 then
frame.slideProgress = 1
frame.mode = "none"
frame:SetPoint("TOPLEFT", MainMenuBar, "BOTTOMLEFT",
BONUSACTIONBAR_XPOS, BONUSACTIONBAR_YPOS)
-- The landing edge IS the sound (ref l.61-64: played on slide-up completion,
-- never on the way down), and the moment the main bar under the overlay
-- adopts the bonus page (DisplayedOffset stops suppressing).
PlaySound("igBonusBarOpen")
for i = 1, table.getn(BENILLA_ACTION_BUTTONS) do
BenillaActionButton_Update(BENILLA_ACTION_BUTTONS[i])
end
else
frame:SetPoint("TOPLEFT", MainMenuBar, "BOTTOMLEFT",
BONUSACTIONBAR_XPOS, frame.slideProgress * BONUSACTIONBAR_YPOS)
end
elseif frame.mode == "hide" then
frame.slideProgress = frame.slideProgress - elapsed / BONUSACTIONBAR_SLIDETIME
if frame.slideProgress <= 0 then
frame.slideProgress = 0
frame.mode = "none"
frame:SetPoint("TOPLEFT", MainMenuBar, "BOTTOMLEFT", BONUSACTIONBAR_XPOS, 0)
frame:Hide()
else
frame:SetPoint("TOPLEFT", MainMenuBar, "BOTTOMLEFT",
BONUSACTIONBAR_XPOS, frame.slideProgress * BONUSACTIONBAR_YPOS)
end
end
end
-- The offset the MAIN bar paints. Live, except during the up-slide: the old page keeps
-- painting under the rising overlay and the bonus page arrives when it lands (the ref's
-- main bar shows page 1 the whole time a form is up — ours adopts the bonus page at
-- landing instead, 1223's re-paging model; 1524). The getglobal guard: buttons paint
-- during load, before BonusActionBarFrame (declared after the bar) exists.
function BenillaActionBar_DisplayedOffset()
local frame = getglobal("BonusActionBarFrame")
if frame and frame.mode == "show" then
return 0
end
return GetBonusBarOffset()
end
-- The wire/API action id the MAIN bar's button i displays (the vanilla page formula).
-- INTERNAL to BenillaActionButton_Action below — the ONLY caller. A button path that
-- reaches past it computes a MAIN-bar id for a multibar button (base ignored), which
-- reads as "the slot below": the tooltip hover did exactly that until 2026-07-21.
function BenillaActionBar_ActionFor(index)
local offset = BenillaActionBar_DisplayedOffset()
local base = 0
if offset > 0 then
base = (6 + offset - 1) * 12
else
-- The paged main bar. A bonus page (stance/form/stealth) OUTRANKS it, which is
-- why this is the else arm and not an addition: the reference's own
-- `ActionButton_GetPagedID` takes the bonus branch first.
base = (CURRENT_ACTIONBAR_PAGE - 1) * 12
end
return base + index
end
-- The action id a specific BUTTON displays: the ref forks on the parent frame's NAME
-- (ActionButton_GetPagedID:446-467 — MultiBarBottomLeft ⇒ page 6, MultiBarBottomRight ⇒
-- page 5, else the paged/bonus formula); we carry the fork as data — a fixed per-button
-- page base set at OnLoad (MultiBars.xml), nil for the bonus-paged main bar.
-- **Adopt a button that never ran our OnLoad.**
--
-- Our handlers read a region cache (`button.icon`, `.count`, `.cooldown`, …) that
-- `BenillaActionButton_OnLoad` fills. An addon inheriting `ActionBarButtonTemplate` gets
-- those handlers and supplies its OWN `<OnLoad>`, which REPLACES the template's rather
-- than chaining — so ours never runs and every one of those reads was nil. zBar.xml:7 is
-- the corpus shape; it died in OUR file at `ActionBar.xml:529` on `button.icon`.
--
-- Resolving from the derived names is not a fallback bolted on: it is exactly what our
-- OnLoad does, and what the REFERENCE does on every access (`ActionButton_Update` reads
-- `getglobal(this:GetName().."Icon")` each time rather than caching). We cache because it
-- is cheaper; this is the same lookup for the buttons that never got cached.
--
-- It deliberately does NOT join `BENILLA_ACTION_BUTTONS`. That roster is the event fan-out
-- for OUR bars, and a foreign button's owner drives its own updates — adding it would have
-- us repainting somebody else's button behind their back.
function BenillaActionButton_Adopt(button)
if not button or button.icon then return end
local n = button:GetName()
if not n then return end
button.icon = getglobal(n .. "Icon")
button.hotkey = getglobal(n .. "HotKey")
button.count = getglobal(n .. "Count")
button.flash = getglobal(n .. "Flash")
button.border = getglobal(n .. "Border")
button.cooldown = getglobal(n .. "Cooldown")
button.name = getglobal(n .. "Name")
button.flashing = button.flashing or 0
button.flashtime = button.flashtime or 0
-- The grid count, same posture: a foreign button reaching ActionButton_HideGrid with
-- no count would raise on the arithmetic, which is 1199's class of silent-wrong-shape.
button.showgrid = button.showgrid or 0
end
function BenillaActionButton_Action(button)
-- `index` falls back to the button's ID, which is where the REFERENCE reads it from:
-- `ActionButton_GetPagedID` uses `this:GetID()`, and our `.index` is only that value
-- cached at OnLoad. A button that inherits `ActionBarButtonTemplate` and wires its own
-- OnLoad — zBar.xml:7 does exactly this — never runs ours, so `.index` is nil while
-- `GetID()` is set from the XML `id=`. Before the fallback, our own handler raised
-- `ActionBar.xml:228: arithmetic on local 'index'` on somebody else's button.
--
-- Still nothing? Action 0, which is not a real slot: every reader answers nil for it
-- and the button draws empty. Deliberately not an early `return nil` — the callers are
-- arithmetic all the way down, and a nil would move the raise rather than remove it.
local index = button.index or button:GetID() or 0
if button.base then
return button.base + index
end
-- The overlay's own buttons (ref ActionButton_GetPagedID:447-452): the LIVE bonus
-- page — with lastBonusBar standing in once the offset has dropped, so the
-- down-slide keeps painting (and a click mid-descent keeps using) the old form's
-- slots, ref-verbatim. Distinct from the main bar's DisplayedOffset suppression:
-- these buttons show the NEW page from the first slide frame.
if button.isBonus then
local offset = GetBonusBarOffset()
if offset == 0 then
local frame = getglobal("BonusActionBarFrame")
offset = (frame and frame.lastBonusBar) or 1
end
return (6 + offset - 1) * 12 + index
end
return BenillaActionBar_ActionFor(index)
end
-- Hover: SetAction's pure delegation (decision 0276 — a SPELL slot renders the spell
-- tooltip with its rank column, an ITEM slot the shared item renderer; ref
-- ActionButton_SetTooltip). The re-enter keeps it live across page flips.
-- Anchor: ref ActionButton_SetTooltip (ActionButton.lua l.366-372) branches on the
-- UberTooltips CVar — "1" → the default corner, else beside the button, LEFT for the bars
-- that sit at the screen edge and RIGHT for the rest. This branch was collapsed to the "1"
-- leg until B230, on the reason that benilla shipped no CVar state for anything to move
-- it; 0954 ended that and cvars.rs now registers the key (default "1", byte-read — the
-- evidence lives on that row), so the fork is live and the stock look is unchanged.
--
-- The ref's LEFT set is {MultiBarBottomRight, MultiBarRight, MultiBarLeft} — the three bars
-- that sit at (or against) the right edge, so their plates have to open toward the centre.
-- All three exist here now: the two VERTICAL bars landed with 1219 and became player
-- options with 1500, and this comment said "only the first exists" for as long as that was
-- true. Membership is by FRAME, not by visibility — a hidden bar is never hovered anyway.
function BenillaActionButton_OnEnter(button)
BenillaActionButton_Adopt(button)
if not GameTooltip then return end -- engine tests load file subsets
if GetCVar("UberTooltips") == "0" then
-- `parent and` first: an orphan button's nil parent would otherwise match a nil
-- MultiBarRight in a harness that loaded no multibars, and anchor the wrong way.
local parent = button:GetParent()
local edgeBar = parent and (parent == MultiBarBottomRight
or parent == MultiBarRight or parent == MultiBarLeft)
if edgeBar then GameTooltip:SetOwner(button, "ANCHOR_LEFT") else GameTooltip:SetOwner(button, "ANCHOR_RIGHT") end
else
GameTooltip_SetDefaultAnchor(GameTooltip, button)
end
GameTooltip:SetAction(BenillaActionButton_Action(button))
end
function BenillaActionButton_OnLoad(button, index, base, commandPrefix)
button.index = index
button.base = base
button.icon = getglobal(button:GetName() .. "Icon")
button.hotkey = getglobal(button:GetName() .. "HotKey")
button.count = getglobal(button:GetName() .. "Count")
button.flash = getglobal(button:GetName() .. "Flash")
button.border = getglobal(button:GetName() .. "Border")
button.cooldown = getglobal(button:GetName() .. "Cooldown")
button.name = getglobal(button:GetName() .. "Name")
-- Ref ActionButton_OnLoad:104-106: the flash/range state the OnUpdate loop drives.
button.flashing = 0
button.flashtime = 0
button.rangeTimer = nil
-- ref ActionButton_OnLoad l.104: the empty-well grid count starts at zero.
button.showgrid = 0
-- Every bar button carries its binding COMMAND (0997 main bar, 1008 multibars):
-- the caller names the command family (MultiBars.xml passes MULTIACTIONBAR1/2BUTTON
-- — the ref's own bar↔page mapping quoted there); nothing passed = the main bar's
-- ACTIONBUTTON. The label paints from it here and repaints on UPDATE_BINDINGS.
button.bindingCommand = (commandPrefix or "ACTIONBUTTON") .. index
BenillaActionButton_PaintHotkey(button)
table.insert(BENILLA_ACTION_BUTTONS, button)
-- BOTH mouse buttons, ref-verbatim (ActionButton_OnLoad, ActionButton.lua:108-109):
-- an action button is a two-button button. The OnClick body reads no `arg1` — in the
-- ref either button runs the same PickupAction/UseAction fork — so right-click USES
-- the action, which is how food and spells are used off the bar. Without this the
-- widget default ({"LeftButtonUp"}, `benilla_ui::script::button`) silently swallowed
-- every right-click on the bar (director's report B200).
button:RegisterForClicks("LeftButtonUp", "RightButtonUp")
-- The drag trio (decision 0216 §3/§7): click-carry and drag share one downstream path
-- by construction (OnClick/OnDragStart both call PickupAction; OnReceiveDrag calls
-- PlaceAction — ref ActionBarFrame.xml:12-38). Both buttons drag, ref-verbatim too
-- (l.108) — a started drag suppresses the click, so right-press-and-move relocates the
-- action while right-click-in-place still uses it. Both drag ends honour LOCK_ACTIONBAR
-- since 1136 — see the guards themselves for what the lock does and does not stop.
button:RegisterForDrag("LeftButton", "RightButton")
BenillaActionButton_Update(button)
end
-- Ref ActionBarFrame.xml:12-22's fork verbatim: shift picks up, else uses (checkCursor=1 —
-- the engine routes a click-with-payload to PlaceAction instead, decision 0216 §7).
function BenillaActionButton_OnClick(button)
BenillaActionButton_Adopt(button)
local action = BenillaActionButton_Action(button)
if IsShiftKeyDown() then
PickupAction(action)
else
UseAction(action, 1)
end
BenillaActionButton_Update(button)
end
-- The lock (ref ActionBarFrame.xml:23-38, both handlers wrapped in `if LOCK_ACTIONBAR ~= "1"
-- then … end`). Note what the reference does NOT guard, and neither do we: the shift-click
-- pick-up in OnClick above (its l.12-22 fork runs PickupAction unconditionally). So the lock
-- stops the *drag*, and shift-click stays the deliberate way to move an action with the bar
-- locked — which is what makes the setting usable rather than a wall.
function BenillaActionButton_OnDragStart(button)
BenillaActionButton_Adopt(button)
if LOCK_ACTIONBAR == "1" then return end
PickupAction(BenillaActionButton_Action(button))
BenillaActionButton_Update(button)
end
function BenillaActionButton_OnReceiveDrag(button)
BenillaActionButton_Adopt(button)
if LOCK_ACTIONBAR == "1" then return end
PlaceAction(BenillaActionButton_Action(button))
BenillaActionButton_Update(button)
end
-- The default ACTIONBUTTON1..12 key bindings (ref Bindings.xml:121-127: keys 1..9,0,-,=
-- run ActionButtonDown on keystate "down" and ActionButtonUp on "up" via runOnUp), both
-- re-expressed from ActionButton.lua:15-28/30-45 and routed here from the app's key feed.
-- DOWN pushes the button only from NORMAL; UP fires the action only from PUSHED — the
-- state pair is both the pushed visual and the debounce (a key-repeat DOWN or a stray UP
-- is a no-op). Calls UseAction DIRECTLY with NO checkCursor argument — unlike the
-- mouse-click fork above, a keybind never places (decision 0216 §7: the ref itself never
-- routes ActionButtonUp through OnClick, so this no longer goes through Click() either —
-- the two entry points share the id formula, not the click path). The ref's
-- BonusActionBar fork is not modeled (no bonus bar frame).
--
-- SIGNATURES ARE THE 1.12 ONES (decision 1199): `ActionButtonDown(id)` /
-- `ActionButtonUp(id, onSelf)`, the shapes an addon calls. These two never took a frame —
-- they take the bar INDEX in both clients — so nothing here changes but the names and the
-- restored `onSelf`, which rides through to UseAction's third argument exactly as the ref's
-- l.56 does (our binding accepts and ignores it — `script/action.rs`).
-- Both route to the overlay's buttons while it is up — ref ActionButton.lua:15-61's own
-- fork, and the half of the swap that must NOT wait for the slide: the overlay is shown
-- from the first slide frame, so a key pressed the instant you enter stealth already
-- drives the stealth page (and, during the down-slide, still drives the old form's page —
-- lastBonusBar — exactly as the reference does).
function ActionButtonDown(id)
if BonusActionBarFrame:IsShown() then
local button = getglobal("BonusActionButton" .. id)
if ( button:GetButtonState() == "NORMAL" ) then button:SetButtonState("PUSHED") end
return
end
local button = getglobal("ActionButton" .. id)
if ( button:GetButtonState() == "NORMAL" ) then button:SetButtonState("PUSHED") end
end
function ActionButtonUp(id, onSelf)
local button
if BonusActionBarFrame:IsShown() then
button = getglobal("BonusActionButton" .. id)
else
button = getglobal("ActionButton" .. id)
end
if ( button:GetButtonState() == "PUSHED" ) then
button:SetButtonState("NORMAL")
UseAction(BenillaActionButton_Action(button), 0, onSelf)
-- Ref ActionButtonUp:55-59: the checked ring reflects the use immediately.
BenillaActionButton_UpdateState(button)
end
end
-- Ref ActionButton_UpdateState:261-267 — the checked ring: the current action (Attack
-- while auto-attacking, the in-flight cast) or the live auto-repeat spell.
function BenillaActionButton_UpdateState(button)
local action = BenillaActionButton_Action(button)
if ( IsCurrentAction(action) or IsAutoRepeatAction(action) ) then button:SetChecked(1) else button:SetChecked(0) end
end
-- Ref ActionButton_UpdateUsable:269-283, verbatim colors: usable white; out of power the
-- blue-grey 0.5/0.5/1.0 on icon AND ring; otherwise-unusable the 0.4 grey icon only.
function BenillaActionButton_UpdateUsable(button)
local action = BenillaActionButton_Action(button)
local normalTexture = getglobal(button:GetName() .. "NormalTexture")
local isUsable, notEnoughMana = IsUsableAction(action)
if ( isUsable ) then
button.icon:SetVertexColor(1.0, 1.0, 1.0)
normalTexture:SetVertexColor(1.0, 1.0, 1.0)
elseif ( notEnoughMana ) then
button.icon:SetVertexColor(0.5, 0.5, 1.0)
normalTexture:SetVertexColor(0.5, 0.5, 1.0)
else
button.icon:SetVertexColor(0.4, 0.4, 0.4)
normalTexture:SetVertexColor(1.0, 1.0, 1.0)
end
end
-- Ref ActionButton_UpdateCooldown:294-298.
function BenillaActionButton_UpdateCooldown(button)
local start, duration, enable = GetActionCooldown(BenillaActionButton_Action(button))
CooldownFrame_SetTimer(button.cooldown, start, duration, enable)
end
-- Ref ActionButton_UpdateFlash/StartFlash/StopFlash/IsFlashing:469-496: the red blink on
-- an engaged Attack button / a live auto-repeat, toggled by the OnUpdate loop below.
function BenillaActionButton_UpdateFlash(button)
local action = BenillaActionButton_Action(button)
if ( (IsAttackAction(action) and IsCurrentAction(action)) or IsAutoRepeatAction(action) ) then
BenillaActionButton_StartFlash(button)
else
BenillaActionButton_StopFlash(button)
end
end
function BenillaActionButton_StartFlash(button)
button.flashing = 1
button.flashtime = 0
BenillaActionButton_UpdateState(button)
end
function BenillaActionButton_StopFlash(button)
button.flashing = 0
button.flash:Hide()
BenillaActionButton_UpdateState(button)
end
-- Ref ActionButton_OnUpdate:384-428, minus the tooltip refresh (no per-button tooltip
-- yet): the 0.4 s flash toggle, and the 0.2 s range recheck — a bound slot's label
-- tints red/grey, an unbound one wears the RANGE_INDICATOR dot (both legs the ref's,
-- unified in 1008 — the SCOPE note).
function ActionButton_OnUpdate(elapsed)
BenillaActionButton_Adopt(this)
local button = this
if ( button.flashing == 1 ) then
button.flashtime = button.flashtime - elapsed
if ( button.flashtime <= 0 ) then
local overtime = -button.flashtime
if ( overtime >= ATTACK_BUTTON_FLASH_TIME ) then
overtime = 0
end
button.flashtime = ATTACK_BUTTON_FLASH_TIME - overtime
if ( button.flash:IsVisible() ) then
button.flash:Hide()
else
button.flash:Show()
end
end
end
if ( button.rangeTimer ) then
button.rangeTimer = button.rangeTimer - elapsed
if ( button.rangeTimer <= 0 ) then
if ( IsActionInRange(BenillaActionButton_Action(button)) == 0 ) then
-- An UNBOUND button has no label to tint: paint the ref's
-- RANGE_INDICATOR dot instead (net ref behavior — red dot iff out of
-- range). Since 1008 the fork is the binding text itself (any bar's
-- slot can be bound or bare now), not which bar owns the button.
if button.noBindingLabel then
button.hotkey:SetText("●")
end
button.hotkey:SetVertexColor(1.0, 0.1, 0.1)
else
if button.noBindingLabel then
button.hotkey:SetText("")
end
button.hotkey:SetVertexColor(0.6, 0.6, 0.6)
end
button.rangeTimer = TOOLTIP_UPDATE_TIME
end
end
end
-- the ref's count law (ActionButton.lua:285-292), re-expressed here. The gate is
-- IsConsumableAction, NOT "count > 0": a consumable you have run out of shows a literal
-- "0", and an item that is not consumable (a mount, a trinket, a bag) shows nothing at
-- all, however many copies you hold.
function BenillaActionButton_UpdateCount(button)
local action = BenillaActionButton_Action(button)
if IsConsumableAction(action) then
button.count:SetText(GetActionCount(action))
else
button.count:SetText("")
end
end
-- ── The reference's names, so an addon that hooks them is reached ──────────────────────
--
-- The 1.12 customisation model is "replace a global and the client calls your replacement"
-- (Bagnon does it to `ToggleBackpack`; `zBar`/`zBarEx` do
-- `SetScript("OnShow", ActionButton_Update)`; `CT_BarMod` calls `ActionButton_Update()`
-- outright). Wherever our UI calls a `Benilla`-named function and never the reference's
-- name, every one of those addons is SILENTLY INERT — the override is installed and never
-- invoked. A survey cannot see it; it loads addons and fires events but never clicks.
--
-- These are ADAPTERS, not renames, and the distinction is the whole point: the reference's
-- contract is `this`-shaped and no-argument, ours takes the button explicitly. Renaming
-- would let `TipBuddy`'s `originalUnitFrame_OnEnter()` reach us with a nil frame — reachable
-- but broken, which is worse than inert. The adapter publishes the reference's SHAPE and
-- delegates.
--
-- NOT adapted, and named so nobody assumes it was missed: `ActionButton_OnLoad`. Ours is
-- `(button, index, base, commandPrefix)` and the reference's takes nothing — the extra
-- arguments are real state our per-container registration needs, so no adapter can supply
-- them. That is the per-button event registration change already at the top of the arc
-- queue, not a naming fix.
-- **`ActionButton_OnLoad()` — the reference's own, taking nothing and reading `this`.**
--
-- This sat at the top of the arc queue for the whole wave, recorded as unadaptable: ours
-- takes `(button, index, base, commandPrefix)` and the reference's takes none, so "no
-- adapter can supply them". Reading ref ActionButton.lua:103-119 shows that was wrong —
-- the reference derives all three from `this` and it is our own signature that caches
-- them:
--
-- index -> `this:GetID()` (what ActionButton_GetPagedID reads)
-- commandPrefix -> `this.buttonType`, a field the BAR sets, "ACTIONBUTTON" by default
-- (ref l.118 `ActionButton_UpdateHotkeys(this.buttonType)`)
-- base -> not an OnLoad concept at all; the reference forks on the PARENT's
-- NAME at read time. A foreign bar ("zBar1") matches no multibar, so
-- the paged/bonus formula applies — which is what leaving `base` nil
-- already does here.
--
-- The events are registered on the BUTTON, as the reference does, and reach it through the
-- `<OnEvent>` this change adds to `ActionBarButtonTemplate`. Our own bars do NOT call this
-- and never register, so they keep being driven by the BENILLA_ACTION_BUTTONS fan-out and
-- cannot be double-driven.
function ActionButton_OnLoad()
local button = this
if not button then return end
BenillaActionButton_Adopt(button)
button.index = button.index or button:GetID()
button.bindingCommand = (button.buttonType or "ACTIONBUTTON") .. (button.index or 1)
button.showgrid = 0
button.flashing = 0
button.flashtime = 0
button:RegisterForDrag("LeftButton", "RightButton")
button:RegisterForClicks("LeftButtonUp", "RightButtonUp")
button:RegisterEvent("PLAYER_ENTERING_WORLD")
button:RegisterEvent("UPDATE_BONUS_ACTIONBAR")
button:RegisterEvent("ACTIONBAR_SHOWGRID")
button:RegisterEvent("ACTIONBAR_HIDEGRID")
button:RegisterEvent("ACTIONBAR_PAGE_CHANGED")
button:RegisterEvent("ACTIONBAR_SLOT_CHANGED")
button:RegisterEvent("UPDATE_BINDINGS")
BenillaActionButton_PaintHotkey(button)
BenillaActionButton_Update(button)
end
-- The per-button event arm the reference's own template carries. Only buttons that ran
-- `ActionButton_OnLoad` ever registered, so this is a no-op for ours.
function ActionButton_OnEvent()
local button = this
if not button then return end
BenillaActionButton_Adopt(button)
-- Through the two verbs, ref-verbatim in shape (ActionButton_OnEvent l.317-324) and
-- no longer the 1/0 assignment this arm shipped with: `showgrid` is a COUNT now, and a
-- foreign button handed a bare 0 while the option still wanted its wells open would
-- close them. Each verb repaints, so these arms return rather than fall through.
if event == "ACTIONBAR_SHOWGRID" then
ActionButton_ShowGrid(button)
return
elseif event == "ACTIONBAR_HIDEGRID" then
ActionButton_HideGrid(button)
return
elseif event == "UPDATE_BINDINGS" then
BenillaActionButton_PaintHotkey(button)
end
BenillaActionButton_Update(button)
end
-- ref ActionButton.lua:446 — the action id a button displays, forked on its parent bar.
-- `BenillaActionButton_Action` IS this function; only the name was ours. CT_BarMod calls it
-- the moment there is an action to page, which is why an EMPTY-world survey never saw it:
-- it surfaced only when the fixture started seating a populated session.
function ActionButton_GetPagedID(button)
return BenillaActionButton_Action(button or this)
end
function ActionButton_Update()
-- Adopt here too: this adapter carries the REFERENCE's name, so `this` is very often a
-- button somebody else built (zBar's, CT_BarMod's) that never ran our OnLoad and so has
-- no region cache. The handler-boundary adopts do not cover it — an addon calls this
-- global directly, without any of our scripts in the stack.
BenillaActionButton_Adopt(this)
BenillaActionButton_Update(this)
end
function ActionButton_UpdateUsable()
BenillaActionButton_UpdateUsable(this)
end
-- ── The empty-slot grid (ref ActionButton.lua l.243-260) ────────────────────────────────
-- `button.showgrid` is a COUNT of how many things currently want this button's empty well
-- visible, and it is the reference's own field under its own name — see the SCOPE note for
-- why a count and not a flag, and for the three corpus addons that already write it.
--
-- Today there are exactly two askers and they are independent: the cursor's placeable
-- payload (the ACTIONBAR_SHOWGRID/_HIDEGRID pair, fanned over the whole roster in
-- BenillaActionBar_OnEvent) and ALWAYS_SHOW_MULTIBARS (MultiBars.xml's
-- MultiActionBar_UpdateGridVisibility, over the four extra bars only). Either alone holds
-- the wells open; the count is what lets one let go without closing what the other holds.
--
-- Divergence from the ref, deliberate: ours re-derive the whole button through
-- BenillaActionButton_Update rather than doing their own Show/Hide + vertex tint. There is
-- one paint path in this file and the grid is one of its inputs, so a verb that painted
-- directly would be a second, slightly different answer to "what does an empty well look
-- like" — which is how the two got out of step in the ref (its ShowGrid dims the ring, its
-- Update never restores the dim).
function BenillaActionButton_GridShown(button)
return button.showgrid ~= nil and button.showgrid > 0
end
function ActionButton_ShowGrid(button)
button = button or this
BenillaActionButton_Adopt(button)
button.showgrid = (button.showgrid or 0) + 1
BenillaActionButton_Update(button)
end
function ActionButton_HideGrid(button)
button = button or this
BenillaActionButton_Adopt(button)
-- Clamped at zero. The ref subtracts unconditionally and can go negative — a button
-- that joined the roster mid-payload takes one HIDEGRID it never took a SHOWGRID for,
-- and from then on its wells need TWO shows to open. Ours simply cannot owe.
local count = (button.showgrid or 0) - 1
if count < 0 then count = 0 end
button.showgrid = count
BenillaActionButton_Update(button)
end
function BenillaActionButton_Update(button)
local action = BenillaActionButton_Action(button)
if HasAction(action) then
button.icon:SetTexture(GetActionTexture(action) or BENILLA_FALLBACK_ICON)
button.icon:Show()
button:SetNormalTexture("Interface\\Buttons\\UI-Quickslot2")
button:Show()
button.rangeTimer = -1
BenillaActionButton_UpdateCount(button)
BenillaActionButton_UpdateState(button)
BenillaActionButton_UpdateUsable(button)
BenillaActionButton_UpdateCooldown(button)
BenillaActionButton_UpdateFlash(button)
else
-- ref l.168: the empty arm HIDES the icon region — it does not clear the path. The
-- distinction is load-bearing here: UpdateUsable's SetVertexColor tint survives a
-- SetTexture(nil) ("a tint outlives the art"), and an occupied slot going empty on
-- a character switch left that white tint on a pathless region — the solid white
-- buttons of the 2026-08-07 report (decision 1108, with the engine-side draw gate).
button.icon:Hide()
button.count:SetText("")
button.cooldown:Hide()
button.rangeTimer = nil
button.hotkey:SetVertexColor(0.6, 0.6, 0.6)
button:SetChecked(0)
BenillaActionButton_StopFlash(button)
local grid = BenillaActionButton_GridShown(button)
if button.base then
-- An EMPTY multibar well is the ref's own default: hidden entirely, appearing
-- as a drop-target ring only while the grid is asked for (a held payload, or
-- ALWAYS_SHOW_MULTIBARS). Only the MAIN bar keeps our always-visible wells
-- divergence (the SCOPE note) — its art has sockets; floating rings over bare
-- world would not read as a bar.
button.hotkey:SetText("")
if not grid then
button:Hide()
else
button:SetNormalTexture("Interface\\Buttons\\UI-Quickslot")
button:Show()
end
else
-- While the grid is up, an empty slot shows the ref's own "no action" ring
-- (Interface\Buttons\UI-Quickslot) so it reads as a drop target; otherwise the
-- plain occupied-style ring, unchanged from before this slice (SCOPE note).
if grid then
button:SetNormalTexture("Interface\\Buttons\\UI-Quickslot")
else
button:SetNormalTexture("Interface\\Buttons\\UI-Quickslot2")
end
end
end
-- Ref ActionButton_Update:221-228: the equipped-item green border.
if ( IsEquippedAction(action) ) then
button.border:SetVertexColor(0, 1.0, 0, 0.35)
button.border:Show()
else
button.border:Hide()
end
-- Ref ActionButton_Update:236-238, "Update Macro Text": the Name line under the icon,
-- written on EVERY update, occupied or not — `GetActionText` answers a MACRO slot's
-- macro name and nil for anything else, and a nil clears the line, so an emptied or
-- re-filled slot loses the old name through this same write. Guarded only because an
-- adopted foreign button may inherit a template that declares no `$parentName`.
-- (B340: the line was declared in the template and never set — decision 1636.)
if button.name then
button.name:SetText(GetActionText(action))
end
end
function BenillaActionBar_OnLoad(frame)
frame:RegisterEvent("PLAYER_ENTERING_WORLD")
frame:RegisterEvent("ACTIONBAR_SLOT_CHANGED")
frame:RegisterEvent("UPDATE_BONUS_ACTIONBAR")
frame:RegisterEvent("ACTIONBAR_SHOWGRID")
frame:RegisterEvent("ACTIONBAR_HIDEGRID")
-- The state/feedback events (ref ActionButton_OnLoad's per-button set, registered
-- bar-level here — the SCOPE note): the app fires each on the byte-mapped client
-- edge (wow-re action-button-state-api.md).
frame:RegisterEvent("ACTIONBAR_UPDATE_STATE")
frame:RegisterEvent("ACTIONBAR_UPDATE_USABLE")
frame:RegisterEvent("ACTIONBAR_UPDATE_COOLDOWN")
frame:RegisterEvent("PLAYER_TARGET_CHANGED")
frame:RegisterEvent("PLAYER_AURAS_CHANGED")
frame:RegisterEvent("PLAYER_ENTER_COMBAT")
frame:RegisterEvent("PLAYER_LEAVE_COMBAT")
frame:RegisterEvent("START_AUTOREPEAT_SPELL")
frame:RegisterEvent("STOP_AUTOREPEAT_SPELL")
-- The binding table moved (a rebind, a set switch, the seed) — repaint the HotKey
-- corners (0997; the ref's own UPDATE_BINDINGS consumer is UpdateHotkeys too).
frame:RegisterEvent("UPDATE_BINDINGS")
end
-- Run one ref per-button handler over the OCCUPIED buttons (the bar-level fan — SCOPE
-- note). The HasAction gate is the ref's own semantics, load-bearing, not an optimization:
-- ActionButton.lua registers these handlers only while the button has an action ("All
-- event handlers below this line are only set when the button has an action"), so an
-- empty button never runs them — UpdateUsable on an empty would land its 0.4 grey
-- SetVertexColor on the texture-less icon region, which draws as a solid grey plate
-- (the director's empty-wells report, 2026-07-10).
function BenillaActionBar_ForEach(fn)
for i = 1, table.getn(BENILLA_ACTION_BUTTONS) do
local button = BENILLA_ACTION_BUTTONS[i]
if HasAction(BenillaActionButton_Action(button)) then
fn(button)
end
end
end
function BenillaActionBar_OnEvent(frame)
-- The form/stance/stealth edge (ref BonusActionBar_OnEvent:20-35): start the overlay
-- slide, then FALL THROUGH to the tail — the roster repaint under the new state is
-- the same statement for this event as for any other. Deliberately in the handler
-- that repaints, not a second OnEvent on the overlay frame: one handler means the
-- slide state is set before any button paints, so there is no one-frame flash of the
-- new page before the suppression lands.
if event == "UPDATE_BONUS_ACTIONBAR" then
if GetBonusBarOffset() <= 0 then
HideBonusActionBar()
else
BonusActionBarFrame.lastBonusBar = GetBonusBarOffset()
ShowBonusActionBar()
end
end
-- ACTIONBAR_SLOT_CHANGED names its action in arg1; repaint just the button showing it.
if event == "ACTIONBAR_SLOT_CHANGED" and arg1 then
for i = 1, table.getn(BENILLA_ACTION_BUTTONS) do
local button = BENILLA_ACTION_BUTTONS[i]
if BenillaActionButton_Action(button) == arg1 then
BenillaActionButton_Update(button)
end
end
return
end
if event == "UPDATE_BINDINGS" then
BenillaActionBar_UpdateHotkeys()
return
end
-- The ref ActionButton_OnEvent dispatch (l.332-362), fanned bar-level:
if event == "ACTIONBAR_UPDATE_STATE" then
BenillaActionBar_ForEach(BenillaActionButton_UpdateState)
return
end
if event == "ACTIONBAR_UPDATE_USABLE" or event == "ACTIONBAR_UPDATE_COOLDOWN" then
BenillaActionBar_ForEach(BenillaActionButton_UpdateUsable)
BenillaActionBar_ForEach(BenillaActionButton_UpdateCooldown)
return
end
if event == "PLAYER_TARGET_CHANGED" or event == "PLAYER_AURAS_CHANGED" then
BenillaActionBar_ForEach(BenillaActionButton_UpdateUsable)
return
end
if event == "PLAYER_ENTER_COMBAT" then
BenillaActionBar_ForEach(function(b)
if IsAttackAction(BenillaActionButton_Action(b)) then
BenillaActionButton_StartFlash(b)
end
end)
return
end
if event == "PLAYER_LEAVE_COMBAT" then
BenillaActionBar_ForEach(function(b)
if IsAttackAction(BenillaActionButton_Action(b)) then
BenillaActionButton_StopFlash(b)
end
end)
return
end
if event == "START_AUTOREPEAT_SPELL" then
BenillaActionBar_ForEach(function(b)
if IsAutoRepeatAction(BenillaActionButton_Action(b)) then
BenillaActionButton_StartFlash(b)
end
end)
return
end
if event == "STOP_AUTOREPEAT_SPELL" then
BenillaActionBar_ForEach(function(b)
if b.flashing == 1 and not IsAttackAction(BenillaActionButton_Action(b)) then
BenillaActionButton_StopFlash(b)
end
end)
return
end
-- The payload edge, routed through the two PUBLISHED verbs rather than a private flag
-- — which is what makes Bartender2's `self:Hook("ActionButton_ShowGrid")` see a cursor
-- pick-up at all. The engine fires this pair strictly on the cursor's None↔Some edges
-- (`script/model.rs`), so the count it drives is balanced by construction.
if event == "ACTIONBAR_SHOWGRID" or event == "ACTIONBAR_HIDEGRID" then
local show = (event == "ACTIONBAR_SHOWGRID")
for i = 1, table.getn(BENILLA_ACTION_BUTTONS) do
if show then
ActionButton_ShowGrid(BENILLA_ACTION_BUTTONS[i])
else
ActionButton_HideGrid(BENILLA_ACTION_BUTTONS[i])
end
end
return -- each verb already repainted its own button
end
for i = 1, table.getn(BENILLA_ACTION_BUTTONS) do
BenillaActionButton_Update(BENILLA_ACTION_BUTTONS[i])
end
end
-- The XP bar (ref-MainMenuBar.lua MainMenuExpBar_Update): value = current XP within the
-- level, max = XP-to-level. Both from the player-global UnitXP feed (PLAYER_XP /
-- PLAYER_NEXT_LEVEL_XP). currXP >= 0 so min(0,currXP) is just 0.
function MainMenuExpBar_Update()
local bar = this
local currXP = UnitXP("player")
local nextXP = UnitXPMax("player")
bar:SetMinMaxValues(0, nextXP)
bar:SetValue(currXP)
end
function BenillaExpBar_OnLoad(bar)
bar:RegisterEvent("PLAYER_ENTERING_WORLD")
bar:RegisterEvent("PLAYER_XP_UPDATE")
-- The numerals seam (ref-MainMenuBar.xml l.117-120: TextStatusBar_Initialize() +
-- textLockable in the bar's own OnLoad; TextStatusBar.xml). textLockable is what lets
-- the statusBarText cvar pin the numerals on permanently.
if TextStatusBar_Initialize then -- engine tests load file subsets
TextStatusBar_Initialize()
bar.textLockable = 1
end
MainMenuExpBar_Update()
end
-- ref-MainMenuBar.xml l.121-126: repaint, then hand arg1/arg2 to the CVAR_UPDATE watcher.
function BenillaExpBar_OnEvent(bar)
MainMenuExpBar_Update()
if TextStatusBar_OnEvent then TextStatusBar_OnEvent(arg1, arg2) end
end
-- The XP bar's hover (ref-MainMenuBar.xml l.136-147, MainMenuExpBar's own OnEnter/OnLeave).
-- ref GlobalStrings.lua l.5461 / l.2789 / l.1929-1932 (enUS), in the `X = X or` FALLBACK
-- form: the app runs the real localized GlobalStrings.lua off the patch chain BEFORE this
-- file (ui_script/mod.rs load_global_strings), so the installed locale wins and these
-- copies only fill in for the test harnesses, which load no patch chain.
-- CombatText.xml's shape.
XPBAR_LABEL = XPBAR_LABEL or "XP Bar"
NEWBIE_TOOLTIP_XPBAR = NEWBIE_TOOLTIP_XPBAR or "The amount of experience (XP) you have earned. The color of the XP bar indicates your rest state: light blue for Rested, and purple for Normal. Rested players earn twice the experience they would normally gain from slaying a monster. Characters become less rested when they kill monsters, and more rested by spending time at or logging out at an inn or city."
EXHAUST_TOOLTIP1 = EXHAUST_TOOLTIP1 or "|cffffd200%s|r\n|cffffffff%d%% of normal experience\ngained from monsters.|r"
EXHAUST_TOOLTIP2 = EXHAUST_TOOLTIP2 or "\n|cffff0000You should rest at an Inn.|r"
EXHAUST_TOOLTIP4 = EXHAUST_TOOLTIP4 or "\n\n|cffffd200Resting|r\n|cffffffffYou must rest for %d additional\nminutes to become|r |cffffd200Fully Rested|r|cffffffff.|r"
-- The ref OnEnter's full body (ref-MainMenuBar.xml l.136-141), now that its machinery
-- exists (decision 1082): the on-bar numerals (TextStatusBar.xml), the 1 s armed timer
-- that appends the rest-state line to the held-open tooltip, and canAddRestStateLine —
-- the handshake ExhaustionToolTipText's newbie-tips branch reads. The trailing 1
-- is the ref's noNormalText: with detailed tips OFF this hover shows NOTHING rather than
-- a bare "XP Bar". Transcribed, though 1.12's default (0661) never takes it.
function BenillaExpBar_OnEnter(bar)
if TextStatusBar_UpdateTextString then -- engine tests load file subsets
TextStatusBar_UpdateTextString(bar)
ShowTextStatusBarText(bar)
end
ExhaustionTick.timer = 1
if not GameTooltip_AddNewbieTip then return end
BenillaTooltip_AddNewbieTipFor(bar, XPBAR_LABEL, 1.0, 1.0, 1.0, NEWBIE_TOOLTIP_XPBAR, 1)
GameTooltip.canAddRestStateLine = 1
end
function BenillaExpBar_OnLeave(bar)
if HideTextStatusBarText then HideTextStatusBarText(bar) end
if GameTooltip then GameTooltip:Hide() end
ExhaustionTick.timer = nil
end
-- ══ The reputation WATCH BAR (ref-ReputationFrame.xml l.869-1036 + ref-ReputationFrame.lua
-- l.3-14 and l.161-270) ═══════════════════════════════════════════════════════════════
--
-- **THE PLACEMENT IS A DELIBERATE CALL, and this is its justification.** The reference
-- declares `ReputationWatchBar` and defines its update, its two text verbs, MAX_PLAYER_LEVEL
-- and FACTION_BAR_COLORS in `ReputationFrame.{xml,lua}`, because FrameXML loads MainMenuBar
-- and ReputationFrame in one breath and neither can exist without the other. benilla's
-- manifest is not one breath: this file is entry 94, `ReputationFrame.xml` is 213, and the
-- engine's per-window test harnesses deliberately load FILE SUBSETS — the XP bar's harness
-- loads no character window, the character window's loads no action bar.
--
-- Every frame `ReputationWatchBar_Update` touches except the watch bar itself is declared
-- in THIS file: MainMenuExpBar, MainMenuBarMaxLevelBar, ExhaustionTick, MainMenuBarArtFrame
-- and MainMenuBar. So does every frame that reads the bar back: `ExhaustionTick_Update`
-- below consults `ReputationWatchBar:IsShown()` twice, which is the reference's own pair of
-- guards. Splitting the bar's FRAME from the bar's UPDATE across a 120-entry manifest gap
-- would leave each subset holding half a mechanism and would put a nil-guard on every one
-- of those sites — the exact "special case to dodge a problem" shape the contract forbids.
-- The bar therefore stays whole, in the file that owns its collaborators, and
-- `ReputationFrame.xml` — which owns the PANE — reaches `ReputationWatchBar_Update()` by
-- name at click time, the direction that always resolves.
--
-- What DID move is the half-mechanism. `BenillaMaxLevelBar_Update` covered "the
-- no-watched-faction branch only" of the reference's function, parked on the rail frame
-- because the watch bar was driven by nothing; it is gone, replaced in place by the whole
-- `ReputationWatchBar_Update(newLevel)`. `MainMenuBarMaxLevelBar` accordingly carries NO
-- scripts any more — the reference's rail carries none either, because this function is its
-- only driver — and the event fan that sat on it is now the watch bar's own OnLoad/OnEvent,
-- which is where the reference puts it.
MAX_PLAYER_LEVEL = 60 -- ref-ReputationFrame.lua l.14 (with the ref's own "Hard coded =(")
-- The eight rank colours, quoted from ref-ReputationFrame.lua l.3-12 — a table of the
-- client's own rgb facts, which have no other value they could take. Indexed by the 1-based
-- standingID both `GetFactionInfo` and `GetWatchedFactionInfo` report (benilla-ui
-- `script::reputation`), so the pane's 15 bars and this one bar can never disagree about a
-- colour. Declared HERE rather than in ReputationFrame.xml (its reference home) for the
-- placement reason above: this file must be able to index it with no reputation file
-- loaded, and ReputationFrame.xml cannot be loaded without this one.
FACTION_BAR_COLORS = {
[1] = { r = 0.8, g = 0.3, b = 0.22 },
[2] = { r = 0.8, g = 0.3, b = 0.22 },
[3] = { r = 0.75, g = 0.27, b = 0 },
[4] = { r = 0.9, g = 0.7, b = 0 },
[5] = { r = 0, g = 0.6, b = 0.1 },
[6] = { r = 0, g = 0.6, b = 0.1 },
[7] = { r = 0, g = 0.6, b = 0.1 },
[8] = { r = 0, g = 0.6, b = 0.1 },
}
-- THE TWO POSTURES the watched strip can take, as data. `ref-ReputationFrame.lua`
-- l.184-230 is the specification and every number below is quoted from it; the shape is
-- ours, because the reference's two arms differ in exactly four numbers and which of the
-- two end-art sets is up, and that is a table and one application rather than two
-- hand-written branches that have to be diffed to be read.
--
-- STACKED is the below-max-level posture: an 8px strip sitting 3 above MainMenuBar, wearing
-- the UI-ReputationWatchBar end art, with the XP bar still underneath it. REPLACING is the
-- max-level one: 13px on MainMenuBar's own TOP, wearing the dwarf XP-bar art instead so the
-- strip reads as the bar it took over from.
BENILLA_WATCHBAR_POSTURE = {
stacked = { height = 8, seat = "BOTTOM", seatY = -3, textY = 3, art = "ReputationWatchBarTexture" },
replacing = { height = 13, seat = "TOP", seatY = 0, textY = 1, art = "ReputationXPBarTexture" },
}
-- Seat the strip in one posture and take the other's art down with it. Both end-art sets
-- are four 256-wide slices of the 1024 strip, named `<set>0` .. `<set>3`.
function BenillaWatchBar_SetPosture(stacked)
local wear, drop = BENILLA_WATCHBAR_POSTURE.replacing, BENILLA_WATCHBAR_POSTURE.stacked
if stacked then
wear, drop = drop, wear
end
ReputationWatchStatusBar:SetHeight(wear.height)
ReputationWatchBar:ClearAllPoints()
ReputationWatchBar:SetPoint(wear.seat, MainMenuBar, "TOP", 0, wear.seatY)
ReputationWatchStatusBarText:SetPoint("CENTER", ReputationWatchBarOverlayFrame, "CENTER", 0, wear.textY)
for slice = 0, 3 do
getglobal(wear.art .. slice):Show()
getglobal(drop.art .. slice):Hide()
end
end
-- The XP strip and the rested tick that rides it go up and down together: with no strip
-- there is nothing for the tick to sit on. Every site the reference hides the strip from it
-- also hides the tick, and `ExhaustionTick_Update` enforces the same rule from the other
-- side on every event (its own last statement) — so it is one verb, not a pairing three
-- call sites have to remember.
function BenillaExpStrip_SetShown(shown)
if shown then
MainMenuExpBar:Show()
else
MainMenuExpBar:Hide()
ExhaustionTick:Hide()
end
end
-- The watched faction's strip on the main bar — `ref-ReputationFrame.lua`
-- `ReputationWatchBar_Update` (l.161-250) is the specification. THREE states: a watched
-- faction takes one of the two postures above (the level picks), and no watched faction
-- leaves the XP bar — or, at max level, the brass rail — holding that space instead.
--
-- `newLevel` is the reference's own parameter: PLAYER_LEVEL_UP carries the new level as
-- arg1 because the level FIELD may not have landed yet, and every other caller re-reads
-- UnitLevel. Note the two level tests are deliberately NOT the same test: watched forks on
-- `< MAX_PLAYER_LEVEL`, unwatched on `== MAX_PLAYER_LEVEL`, which is the reference's own
-- asymmetry and only differs above the cap, where nothing can go.
--
-- ONE GUARD ADDED, at the tail: `updateContainerFrameAnchors` is defined by
-- ContainerFrameAdapters.xml (manifest 206, below this file), and a harness that shows this
-- bar without it would take a nil call on a line that only ever runs on a visibility flip.
-- The `if <global> then` shape is this codebase's own subset-load idiom (CharacterFrame.xml's
-- `if UpdateMicroButtons then`). `UIParent_ManageFramePositions` needs no such guard:
-- UIParent.xml is manifest 42, above every consumer.
function ReputationWatchBar_Update(newLevel)
newLevel = newLevel or UnitLevel("player")
local name, reaction, rankMin, rankMax, standing = GetWatchedFactionInfo()
-- Whether the strip's visibility FLIPS is all the tail below needs, so read it once
-- here and compare once at the end rather than threading a flag through both arms.
local wasShown = ReputationWatchBar:IsShown()
if name then
-- The engine reports the rank window ABSOLUTE for exactly this reason (benilla-ui
-- `script::reputation`'s module doc): the strip's range is the window's WIDTH, and
-- its value the distance into it.
local width = rankMax - rankMin
local progress = standing - rankMin
ReputationWatchStatusBar:SetMinMaxValues(0, width)
ReputationWatchStatusBar:SetValue(progress)
ReputationWatchStatusBarText:SetText(name .. " " .. progress .. " / " .. width)
local color = FACTION_BAR_COLORS[reaction]
ReputationWatchStatusBar:SetStatusBarColor(color.r, color.g, color.b)
ReputationWatchBar:Show()
ReputationWatchStatusBar:SetFrameLevel(MainMenuBarArtFrame:GetFrameLevel() - 1)
-- Stacked, the XP bar is still the bar underneath; replacing, it goes away with its
-- tick. Either way the brass rail is not wanted — the strip fills that space.
local stacked = newLevel < MAX_PLAYER_LEVEL
BenillaWatchBar_SetPosture(stacked)
BenillaExpStrip_SetShown(stacked)
MainMenuBarMaxLevelBar:Hide()
else
-- Nothing watched (the arm decision 1094 shipped alone): the strip goes down, and
-- at the cap the brass rail is what takes the XP bar's place.
ReputationWatchBar:Hide()
local capped = newLevel == MAX_PLAYER_LEVEL
BenillaExpStrip_SetShown(not capped)
if capped then
MainMenuBarMaxLevelBar:Show()
else
MainMenuBarMaxLevelBar:Hide()
end
end
if wasShown ~= ReputationWatchBar:IsShown() then
UIParent_ManageFramePositions()
if updateContainerFrameAnchors then updateContainerFrameAnchors() end
end
end
-- The numerals lock, to ref-ReputationFrame.lua l.252-270. A two-key lock: `cvarLocked`
-- records that the statusBarText cvar (not a hover) asked for the text, and `textLocked`
-- that the text is currently pinned on — which is what the bar's own OnLeave reads before
-- hiding it again. Note the Show verb only ever shows the text AT MAX LEVEL, where the
-- strip has replaced the XP bar and is tall enough to hold numerals; below that it falls
-- through to the Hide one. Both function names, both field names and the FontString are
-- the addon-visible contract; the shapes below (early exits where the ref nests) are ours.
function ShowWatchedReputationBarText(lock)
if lock then ReputationWatchBar.cvarLocked = lock end
if UnitLevel("player") ~= MAX_PLAYER_LEVEL or not ReputationWatchBar:IsVisible() then
HideWatchedReputationBarText()
return
end
ReputationWatchBar.textLocked = 1
ReputationWatchStatusBarText:Show()
end
function HideWatchedReputationBarText(unlock)
-- Same gate as the ref's `unlock or not cvarLocked`, spun as the early exit.
if not unlock and ReputationWatchBar.cvarLocked then return end
ReputationWatchStatusBarText:Hide()
ReputationWatchBar.cvarLocked = nil
ReputationWatchBar.textLocked = nil
end
-- ── The watch bar's own handlers ────────────────────────────────────────────────────────
-- Chunk functions rather than the reference's inline bodies, so the departures below have
-- somewhere to be explained (ReputationFrame.xml's chunk-boundary rule).
-- `ref-ReputationFrame.xml` l.996-1035 is the specification.
--
-- PLAYER_ENTERING_WORLD is benilla's ADDITION (decision 1094's own): our faction feed only
-- fires once the catalog and the player's race/class are up, which can be a tick or two
-- after world entry, and the max-level rail must be right from the first frame. The arm it
-- lands in is idempotent, so the extra registration only ever costs a no-op. The event fan
-- is here rather than on `MainMenuBarMaxLevelBar`, where it sat while the watch bar was
-- driven by nothing — the rail is this function's output, not its own driver.
function BenillaWatchBar_OnLoad(bar)
bar:RegisterEvent("UPDATE_FACTION")
bar:RegisterEvent("PLAYER_LEVEL_UP")
bar:RegisterEvent("CVAR_UPDATE")
bar:RegisterEvent("PLAYER_ENTERING_WORLD")
end
-- Every event but one ends in a repaint of the bar, so the odd one out — the cvar that
-- pins the numerals on and off — is answered first and leaves.
--
-- TWO ARMS read as odd and are the reference's own:
-- · a faction push repaints the PANE, from the BAR's handler. The pane registers
-- UPDATE_FACTION itself (ReputationFrame.xml), so this is the reference double-driving
-- it; kept rather than "fixed", and harmless because the pane's repaint is idempotent.
-- Guarded on existence because this file loads WITHOUT ReputationFrame.xml in the
-- engine's per-window subset harnesses.
-- · the `statusBarText` token is DEAD CODE in shipped 1.12 and stays: the CVAR_UPDATE
-- token is the caller's third SetCVar argument, and 1.12's own options panels pass the
-- uppercase display name ("STATUS_BAR_TEXT"), never the cvar's own name (benilla-ui
-- `script::cvars`, decision 1140, which carves exactly this).
function BenillaWatchBar_OnEvent(bar, event)
if event == "CVAR_UPDATE" and arg1 == "statusBarText" then
if arg2 == "1" then ShowWatchedReputationBarText("lock") else HideWatchedReputationBarText("unlock") end
return
end
if event == "UPDATE_FACTION" and bar:IsVisible() and ReputationFrame_Update then
ReputationFrame_Update()
end
-- PLAYER_LEVEL_UP is the one event that knows the level; every other asks for nil and
-- lets the update re-read UnitLevel.
if event == "PLAYER_LEVEL_UP" then
ReputationWatchBar_Update(arg1)
else
ReputationWatchBar_Update()
end
end
-- Coming up with the cvar set means the numerals were meant to be pinned on all along.
function BenillaWatchBar_OnShow()
if GetCVar("statusBarText") == "1" then ShowWatchedReputationBarText("lock") end
UIParent_ManageFramePositions()
end
-- ── The exhaustion tick (ref-MainMenuBar.lua ExhaustionTick_*) ────────────────────────
-- The round marker riding the XP bar at the point where rested XP runs out, plus the
-- pale fill between the bar's value and that point. NOTHING of the specification is cut
-- here any more: the two ReputationWatchBar guards that used to be missing (the
-- max-level-plus-rep tick hide, ref-MainMenuBar.lua l.51-54, and the
-- rep-bar-replaced-the-xp-bar hide, l.69-71) were dropped as UNREACHABLE — the watch bar
-- could never show — and it shows now, so both are live in ExhaustionTick_Update below.
-- The MainMenuBarMaxLevelBar guard is live since 1094.
--
-- The rest-state palette, keyed by GetRestState's own state id (ref-MainMenuBar.lua
-- l.56-66 is the specification; the two rgb triples are its). Rested is the light blue,
-- normal the purple; the ids above 2 are the beta-era tiers the live server never sends and
-- the reference paints nothing for either.
BENILLA_REST_STATE_COLOR = {
[1] = { r = 0.0, g = 0.39, b = 0.88 },
[2] = { r = 0.58, g = 0.0, b = 0.55 },
}
function ExhaustionTick_OnLoad()
local tick = this
tick:RegisterEvent("PLAYER_ENTERING_WORLD")
tick:RegisterEvent("PLAYER_XP_UPDATE")
tick:RegisterEvent("UPDATE_EXHAUSTION")
tick:RegisterEvent("PLAYER_LEVEL_UP")
tick:RegisterEvent("PLAYER_UPDATE_RESTING")
end
function ExhaustionTick_Update()
local tick = this
if event == "PLAYER_ENTERING_WORLD" or event == "PLAYER_XP_UPDATE"
or event == "UPDATE_EXHAUSTION" or event == "PLAYER_LEVEL_UP" then
local playerCurrXP = UnitXP("player")
local playerMaxXP = UnitXPMax("player")
local exhaustionThreshold = GetXPExhaustion()
local exhaustionStateID = GetRestState()
-- The ref's beta-eras leftover: the tired/exhausted tiers (id >= 3) park the tick
-- at the bar's RIGHT end. The live server never produces them (GetRestState's
-- binding note) — kept because the ref keeps it.
if exhaustionStateID >= 3 then
tick:SetPoint("CENTER", "MainMenuExpBar", "RIGHT", 0, 0)
end
if not exhaustionThreshold then
tick:Hide()
ExhaustionLevelFillBar:Hide()
else
local exhaustionTickSet = max(((playerCurrXP + exhaustionThreshold)
/ playerMaxXP) * MainMenuExpBar:GetWidth(), 0)
tick:ClearAllPoints()
if exhaustionTickSet > MainMenuExpBar:GetWidth()
or MainMenuBarMaxLevelBar:IsShown() then
tick:Hide()
ExhaustionLevelFillBar:Hide()
else
tick:Show()
tick:SetPoint("CENTER", "MainMenuExpBar", "LEFT", exhaustionTickSet, 0)
ExhaustionLevelFillBar:Show()
ExhaustionLevelFillBar:SetPoint("TOPRIGHT", "MainMenuExpBar",
"TOPLEFT", exhaustionTickSet, 0)
end
end
-- ref-MainMenuBar.lua l.51-54: at max level with a watched faction the strip the
-- tick rides is the REPUTATION bar, which has no rested pool — hide the tick.
if UnitLevel("player") == MAX_PLAYER_LEVEL and ReputationWatchBar:IsShown() then ExhaustionTick:Hide() end
end
-- The rest-state paint (only these two events repaint it, the ref's own split): one
-- rest state, one hue, worn by all three pieces at once — the strip's fill, the pale
-- wash up to the tick at a sixth alpha, and the tick's own ADD highlight. A state with
-- no row paints nothing, which is what the reference's if/elseif does for every id but
-- these two (and GetRestState answers nil off a wire that has not spoken yet, which
-- indexes to the same nothing).
if event == "PLAYER_ENTERING_WORLD" or event == "UPDATE_EXHAUSTION" then
local rest = BENILLA_REST_STATE_COLOR[GetRestState()]
if rest then
MainMenuExpBar:SetStatusBarColor(rest.r, rest.g, rest.b, 1.0)
ExhaustionLevelFillBar:SetVertexColor(rest.r, rest.g, rest.b, 0.15)
ExhaustionTickHighlight:SetVertexColor(rest.r, rest.g, rest.b)
end
end
-- ref-MainMenuBar.lua l.69-71, the function's own last statement: the watch bar has
-- REPLACED the XP bar (max level), so there is no XP strip left for the tick to sit on
-- whatever the event was. Distinct from the guard above, which only runs on the four
-- position events; this one runs on every one.
if ReputationWatchBar:IsShown() and not MainMenuExpBar:IsShown() then ExhaustionTick:Hide() end
end
-- The tick's tooltip (ref-MainMenuBar.lua ExhaustionToolTipText, the live branches only —
-- its own dead commented-out code is not carried). Two callers, two postures: hovering
-- the TICK anchors the plate beside it (flipped to whichever side has room); the armed
-- 1 s timer from the BAR hover instead APPENDS the line to the already-open newbie tip,
-- gated on canAddRestStateLine. EXHAUST_TOOLTIP4 (time to fully rested) needs
-- GetTimeToWellRested, which is nil on this wire — the line simply never renders,
-- faithfully (the binding's note).
function ExhaustionToolTipText()
if SHOW_NEWBIE_TIPS ~= "1" then
local x, y = ExhaustionTick:GetCenter()
if not ExhaustionTick:IsVisible() then
GameTooltip_SetDefaultAnchor(GameTooltip, UIParent)
elseif x >= (GetScreenWidth() / 2) then
GameTooltip:SetOwner(ExhaustionTick, "ANCHOR_LEFT")
else
GameTooltip:SetOwner(ExhaustionTick, "ANCHOR_RIGHT")
end
end
local exhaustionThreshold = GetXPExhaustion()
local exhaustionStateID, exhaustionStateName, exhaustionStateMultiplier = GetRestState()
exhaustionStateMultiplier = exhaustionStateMultiplier * 100
-- nil while the wire has not answered; a number (minutes) once it has.
local exhaustionCountdown = GetTimeToWellRested() and GetTimeToWellRested() / 60
local tooltipText = format(EXHAUST_TOOLTIP1, exhaustionStateName,
exhaustionStateMultiplier)
local append = nil
if IsResting() then
if exhaustionThreshold and exhaustionCountdown then append = format(EXHAUST_TOOLTIP4, exhaustionCountdown) end
elseif exhaustionStateID == 4 or exhaustionStateID == 5 then
append = EXHAUST_TOOLTIP2
end
if append then
tooltipText = tooltipText .. append
end
if SHOW_NEWBIE_TIPS ~= "1" then
GameTooltip:SetText(tooltipText)
elseif GameTooltip.canAddRestStateLine then
GameTooltip:AddLine("\n" .. tooltipText)
GameTooltip.canAddRestStateLine = nil
GameTooltip:Show()
end
end
-- Driven from the XP BAR's OnUpdate (the ref wires it there, not on the tick — the timer
-- only ever counts while the mouse is on the bar, which is what arms it).
function ExhaustionTick_OnUpdate(elapsed)
if not ExhaustionTick.timer then return end
if ExhaustionTick.timer >= 0 then
ExhaustionTick.timer = ExhaustionTick.timer - elapsed
else
ExhaustionTick.timer = nil
ExhaustionToolTipText()
end
end
-- ref-MainMenuBar.xml l.104-110 (the overlay child's OnLoad): the numerals wiring lives
-- in the OVERLAY frame's own OnLoad because there its FontString is guaranteed
-- materialized; the bar's lockShow starts at the not-hovered refcount.
function BenillaExpBarOverlay_OnLoad()
if SetTextStatusBarText then -- engine tests load file subsets
MainMenuExpBar.lockShow = 0
SetTextStatusBarText(MainMenuExpBar, MainMenuBarExpText)
end
end
-- ── The performance ("ping") meter ────────────────────────────────────────────────────
-- ref-MainMenuBar.xml l.344-407: a 16x64 frame in the bar's own right-hand recess whose
-- 20x66 bar texture is TINTED green/yellow/red by the connection's latency, plus an
-- all-points button on top for the hover tooltip. The thresholds, the 10 s poll and the
-- inline handlers below are the ref's, re-expressed out of its XML into named functions.
-- The meter shows LATENCY ONLY: 1.12 never scales the bar's height, only its color.
PERFORMANCEBAR_LOW_LATENCY = 300
PERFORMANCEBAR_MEDIUM_LATENCY = 600
PERFORMANCEBAR_UPDATE_INTERVAL = 10
-- ref-GlobalStrings.lua l.2659 / l.2689 / l.2746 (enUS), fallback form — see the XP bar's
-- note above for why these never override a real localized install.
MAINMENUBAR_LATENCY_LABEL = MAINMENUBAR_LATENCY_LABEL or "Latency:"
MILLISECONDS_ABBR = MILLISECONDS_ABBR or "ms"
NEWBIE_TOOLTIP_LATENCY = NEWBIE_TOOLTIP_LATENCY or "The average time it takes to talk with the game server. A low latency will display as a green bar, higher latencies will be yellow or even red. Consistently high latencies may indicate a problem with your Internet connection."
function BenillaPerformanceBar_OnLoad(frame)
frame.hover = nil
-- 0, not the interval: the ref's first OnUpdate tick runs the poll immediately, so the
-- bar is never briefly untinted (its texture is grey until the first SetVertexColor).
frame.updateInterval = 0
end
function BenillaPerformanceBar_TooltipText()
local _, _, latency = GetNetStats()
return MAINMENUBAR_LATENCY_LABEL .. " " .. latency .. MILLISECONDS_ABBR
end
-- ref l.389 (the held-open refresh) and l.400 (OnEnter), both the same call:
-- BenillaTooltip_AddNewbieTipFor(MAINMENUBAR_LATENCY_LABEL.." "..latency..MILLISECONDS_ABBR,
-- 1.0, 1.0, 1.0, NEWBIE_TOOLTIP_LATENCY)
-- So the reference hover is TWO lines — the number, then the wrapped explanation — seated
-- at the default screen corner, not beside the meter (GameTooltip.xml, decision 0661).
function BenillaPerformanceBar_ShowTooltip(owner)
if not GameTooltip_AddNewbieTip then return end -- engine tests load file subsets
BenillaTooltip_AddNewbieTipFor(owner, BenillaPerformanceBar_TooltipText(), 1.0, 1.0, 1.0,
NEWBIE_TOOLTIP_LATENCY)
end
function BenillaPerformanceBar_OnUpdate(frame, elapsed)
if frame.updateInterval > 0 then
frame.updateInterval = frame.updateInterval - elapsed
return
end
frame.updateInterval = PERFORMANCEBAR_UPDATE_INTERVAL
local _, _, latency = GetNetStats()
local r, g, b = 0, 1, 0
if latency > PERFORMANCEBAR_MEDIUM_LATENCY then
r, g, b = 1, 0, 0
elseif latency > PERFORMANCEBAR_LOW_LATENCY then
r, g, b = 1, 1, 0
end
MainMenuBarPerformanceBar:SetVertexColor(r, g, b)
-- Held-open tooltips re-read the number on the same 10 s beat (the ref's own refresh).
if frame.hover then
BenillaPerformanceBar_ShowTooltip(frame)
end
end
function BenillaPerformanceBar_OnEnter(button)
MainMenuBarPerformanceBarFrame.hover = 1
BenillaPerformanceBar_ShowTooltip(button)
end
function BenillaPerformanceBar_OnLeave()
MainMenuBarPerformanceBarFrame.hover = nil
if GameTooltip then GameTooltip:Hide() end
end
]]></Script>
<!--
One action button (ref-ActionButtonTemplate.xml, now the FULL template — decision 0137
phase 4): a CheckButton, 36x36 (l.3-6); icon fills the button (BACKGROUND, owner-sized,
l.8-10); Flash (UI-QuickslotRed, hidden — the auto-attack/auto-repeat blink) + HotKey
(l.11-24) + Count (l.25-33) on ARTWORK; Border (UI-ActionButton-Border 62x62 CENTER, ADD,
hidden — the equipped-item green ring, l.48-59) on OVERLAY; the Cooldown child (the ref's
CooldownFrameTemplate Model at 36x36 CENTER (0,-1), l.62-73 — ours is the native Cooldown
widget playing the same machine); NormalTexture = the 66x66 UI-Quickslot2 ring centered at
(0,-1) (l.74-85); PushedTexture UI-Quickslot-Depress (l.86); HighlightTexture
ButtonHilight-Square ADD (l.87); CheckedTexture CheckButtonHilight ADD (l.88 — the
IsCurrentAction/IsAutoRepeatAction ring); Name (the macro-name line, l.34-46) on ARTWORK,
set from `GetActionText` at the tail of every update as the ref's `ActionButton_Update`
does (decision 1636).
-->
<!-- **The reference's own two-level split, which ours used to conflate into one template.**
`ActionButtonTemplate` (ref ActionButtonTemplate.xml:3) is REGIONS ONLY and carries no
scripts at all; `ActionBarButtonTemplate` (ref ActionBarFrame.xml:4) inherits it and adds
the handler set. Addons rely on both halves being separately inheritable: `zBar.xml:7` is
`<CheckButton name="zBarButtonTemplate" inherits="ActionBarButtonTemplate">` and then
wires its OWN OnLoad, and its per-button code reads
`getglobal(button:GetName().."NormalTexture")` — a derived name it only gets by inheriting
a template that declares `$parentNormalTexture`.
2 corpus addons name `ActionBarButtonTemplate` in an XML `inherits=`; with neither name
published they got a bare frame, no art, no regions, and no error (1203's silent shape).
`BenillaActionButtonTemplate` survives below as a pure alias so every one of our own
`inherits=` sites is untouched. -->
<CheckButton name="ActionButtonTemplate" virtual="true">
<Size><AbsDimension x="36" y="36"/></Size>
<Layers>
<Layer level="BACKGROUND">
<Texture name="$parentIcon"/>
</Layer>
<Layer level="ARTWORK">
<Texture name="$parentFlash" file="Interface\Buttons\UI-QuickslotRed" hidden="true"/>
<FontString name="$parentHotKey" inherits="NumberFontNormalSmallGray" justifyH="RIGHT">
<Size><AbsDimension x="36" y="10"/></Size>
<Anchors>
<Anchor point="TOPLEFT" relativePoint="TOPLEFT"><Offset><AbsDimension x="-2" y="-2"/></Offset></Anchor>
</Anchors>
</FontString>
<FontString name="$parentCount" inherits="NumberFontNormal" justifyH="RIGHT">
<Anchors>
<Anchor point="BOTTOMRIGHT" relativePoint="BOTTOMRIGHT"><Offset><AbsDimension x="-2" y="2"/></Offset></Anchor>
</Anchors>
</FontString>
<!-- ref ActionButtonTemplate.xml's `$parentName` — the macro-name line along the
button's bottom edge. `BenillaActionButton_Update` writes it from
`GetActionText` on every update (a MACRO slot's macro name, nil — cleared —
for any other kind); it is also part of the template's published contract, so
an addon indexing `<button>Name` gets the region. -->
<FontString name="$parentName" inherits="GameFontHighlightSmallOutline">
<Size><AbsDimension x="36" y="10"/></Size>
<Anchors>
<Anchor point="BOTTOM"><Offset><AbsDimension x="0" y="2"/></Offset></Anchor>
</Anchors>
</FontString>
</Layer>
<Layer level="OVERLAY">
<Texture name="$parentBorder" file="Interface\Buttons\UI-ActionButton-Border" hidden="true" alphaMode="ADD">
<Size><AbsDimension x="62" y="62"/></Size>
<Anchors>
<Anchor point="CENTER" relativePoint="CENTER"><Offset><AbsDimension x="0" y="0"/></Offset></Anchor>
</Anchors>
</Texture>
</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>
<NormalTexture name="$parentNormalTexture" file="Interface\Buttons\UI-Quickslot2">
<Size><AbsDimension x="66" y="66"/></Size>
<Anchors>
<Anchor point="CENTER" relativePoint="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"/>
<CheckedTexture file="Interface\Buttons\CheckButtonHilight" alphaMode="ADD"/>
</CheckButton>
<!-- The bar half: the reference's `ActionBarButtonTemplate`, regions inherited, handlers added. -->
<CheckButton name="ActionBarButtonTemplate" inherits="ActionButtonTemplate" virtual="true">
<Scripts>
<OnEvent>ActionButton_OnEvent()</OnEvent>
<OnClick>BenillaActionButton_OnClick(self)</OnClick>
<OnDragStart>BenillaActionButton_OnDragStart(self)</OnDragStart>
<OnReceiveDrag>BenillaActionButton_OnReceiveDrag(self)</OnReceiveDrag>
<OnUpdate>ActionButton_OnUpdate(arg1)</OnUpdate>
<OnEnter>BenillaActionButton_OnEnter(self)</OnEnter>
<OnLeave>if GameTooltip then GameTooltip:Hide() end</OnLeave>
</Scripts>
</CheckButton>
<!-- Pure alias, so every existing `inherits="BenillaActionButtonTemplate"` in this file,
MultiBars.xml, StanceBar.xml and PetActionBar.xml keeps resolving to exactly what it did
before the split. -->
<CheckButton name="BenillaActionButtonTemplate" inherits="ActionBarButtonTemplate" virtual="true"/>
<!-- The bar: 1024x53 anchored BOTTOM of the screen (ref-MainMenuBar.xml l.4-10). -->
<Frame name="MainMenuBar">
<Size><AbsDimension x="1024" y="53"/></Size>
<Anchors>
<Anchor point="BOTTOM" relativePoint="BOTTOM"><Offset><AbsDimension x="0" y="0"/></Offset></Anchor>
</Anchors>
<Frames>
<!--
The XP StatusBar: 1024x13 anchored TOP of the bar (ref-MainMenuBar.xml l.12-18).
BarTexture UI-StatusBar, BarColor purple (0.58,0,0.55) (l.158-159); a solid black
a=0.5 BACKGROUND (l.89-93); and the 4 dwarf-notch OVERLAY strips (256x10, BOTTOM
x=-384/-128/128/384 y=3, the tick TexCoord bands l.36-87) that frame the fill.
Drawn first among the bar's children ⇒ lowest, so the dwarf art overlaps it (the
real RaiseFrameLevel(art) order).
-->
<!-- enableMouse: the ref's MainMenuExpBar takes the mouse for its own hover (l.136-147).
The strip is the bar's top 13 px, clear of every button on it. -->
<StatusBar name="MainMenuExpBar" enableMouse="true">
<Size><AbsDimension x="1024" y="13"/></Size>
<Anchors>
<Anchor point="TOP" relativePoint="TOP"><Offset><AbsDimension x="0" y="0"/></Offset></Anchor>
</Anchors>
<Layers>
<Layer level="BACKGROUND">
<Texture>
<Color r="0.0" g="0.0" b="0.0" a="0.5"/>
</Texture>
</Layer>
<Layer level="BORDER">
<!-- The rested fill (ref-MainMenuBar.xml l.20-33 ExhaustionLevelFillBar):
the pale wash from the bar's LEFT edge to the exhaustion tick. 0-wide
until ExhaustionTick_Update stretches its TOPRIGHT to the tick
and tints it (SetVertexColor a=0.15 over this authored white). -->
<Texture name="ExhaustionLevelFillBar">
<Size><AbsDimension x="0" y="13"/></Size>
<Anchors>
<Anchor point="TOPLEFT" relativeTo="MainMenuExpBar"><Offset><AbsDimension x="0" y="0"/></Offset></Anchor>
</Anchors>
<Color r="1.0" g="1.0" b="1.0" a="1.0"/>
</Texture>
</Layer>
<Layer level="OVERLAY">
<Texture name="MainMenuXPBarTexture0" file="Interface\MainMenuBar\UI-MainMenuBar-Dwarf">
<Size><AbsDimension x="256" y="10"/></Size>
<Anchors><Anchor point="BOTTOM" relativePoint="BOTTOM"><Offset><AbsDimension x="-384" y="3"/></Offset></Anchor></Anchors>
<TexCoords left="0" right="1.0" top="0.79296875" bottom="0.83203125"/>
</Texture>
<Texture name="MainMenuXPBarTexture1" file="Interface\MainMenuBar\UI-MainMenuBar-Dwarf">
<Size><AbsDimension x="256" y="10"/></Size>
<Anchors><Anchor point="BOTTOM" relativePoint="BOTTOM"><Offset><AbsDimension x="-128" y="3"/></Offset></Anchor></Anchors>
<TexCoords left="0" right="1.0" top="0.54296875" bottom="0.58203125"/>
</Texture>
<Texture name="MainMenuXPBarTexture2" file="Interface\MainMenuBar\UI-MainMenuBar-Dwarf">
<Size><AbsDimension x="256" y="10"/></Size>
<Anchors><Anchor point="BOTTOM" relativePoint="BOTTOM"><Offset><AbsDimension x="128" y="3"/></Offset></Anchor></Anchors>
<TexCoords left="0" right="1.0" top="0.29296875" bottom="0.33203125"/>
</Texture>
<Texture name="MainMenuXPBarTexture3" file="Interface\MainMenuBar\UI-MainMenuBar-Dwarf">
<Size><AbsDimension x="256" y="10"/></Size>
<Anchors><Anchor point="BOTTOM" relativePoint="BOTTOM"><Offset><AbsDimension x="384" y="3"/></Offset></Anchor></Anchors>
<TexCoords left="0" right="1.0" top="0.04296875" bottom="0.08203125"/>
</Texture>
</Layer>
</Layers>
<Frames>
<!-- The numerals overlay (ref-MainMenuBar.xml l.96-113 MainMenuBarOverlayFrame):
a DIALOG-strata child so the "1234 / 5678" text draws over the dwarf-notch
art and the buttons; its own OnLoad wires the FontString to the bar
(BenillaExpBarOverlay_OnLoad's note). -->
<Frame name="MainMenuBarOverlayFrame" frameStrata="DIALOG">
<Anchors>
<Anchor point="TOPLEFT" relativeTo="MainMenuBar" relativePoint="TOPLEFT"/>
<Anchor point="BOTTOMRIGHT" relativeTo="MainMenuBar" relativePoint="BOTTOMRIGHT"/>
</Anchors>
<Layers>
<Layer level="ARTWORK">
<FontString name="MainMenuBarExpText" inherits="TextStatusBarText">
<Anchors>
<Anchor point="CENTER" relativeTo="MainMenuExpBar"><Offset><AbsDimension x="0" y="1"/></Offset></Anchor>
</Anchors>
</FontString>
</Layer>
</Layers>
<Scripts>
<OnLoad>BenillaExpBarOverlay_OnLoad()</OnLoad>
</Scripts>
</Frame>
</Frames>
<BarTexture file="Interface\TargetingFrame\UI-StatusBar"/>
<BarColor r="0.58" g="0.0" b="0.55"/>
<Scripts>
<OnLoad>BenillaExpBar_OnLoad(self)</OnLoad>
<OnEvent>BenillaExpBar_OnEvent(self)</OnEvent>
<OnEnter>BenillaExpBar_OnEnter(self)</OnEnter>
<OnLeave>BenillaExpBar_OnLeave(self)</OnLeave>
<OnUpdate>ExhaustionTick_OnUpdate(arg1)</OnUpdate>
<OnValueChanged>
if this:IsShown() and TextStatusBar_OnValueChanged then
TextStatusBar_OnValueChanged()
end
</OnValueChanged>
</Scripts>
</StatusBar>
<!--
The exhaustion tick (ref-MainMenuBar.xml l.415-442): a 32×32 button riding the XP
bar at the rested boundary, DIALOG strata so it draws over the bar art. Normal =
the brass marker, Highlight = the ADD glow ExhaustionTick_Update tints to
the rest state. Starts at the bar's CENTER exactly like the ref (its own first
Update re-seats or hides it before anything is seen).
-->
<Button name="ExhaustionTick" frameStrata="DIALOG">
<Size><AbsDimension x="32" y="32"/></Size>
<Anchors>
<Anchor point="CENTER" relativeTo="MainMenuExpBar"><Offset><AbsDimension x="0" y="0"/></Offset></Anchor>
</Anchors>
<Scripts>
<OnLoad>ExhaustionTick_OnLoad()</OnLoad>
<OnEvent>ExhaustionTick_Update()</OnEvent>
<OnEnter>ExhaustionToolTipText()</OnEnter>
<OnLeave>if GameTooltip then GameTooltip:Hide() end</OnLeave>
</Scripts>
<NormalTexture name="ExhaustionTickNormal" file="Interface\MainMenuBar\UI-ExhaustionTickNormal"/>
<HighlightTexture name="ExhaustionTickHighlight" file="Interface\MainMenuBar\UI-ExhaustionTickHighlight" alphaMode="ADD"/>
</Button>
<!--
The max-level rail (ref-MainMenuBar.xml l.161-225 MainMenuBarMaxLevelBar): the
flat brass rail that replaces the XP strip at MAX_PLAYER_LEVEL — four 256×7
quarters of UI-MainMenuBar-MaxLevel, riding just above the frame's own 7 px rect
(each quarter's BOTTOM sits on the frame's TOP, the ref's own geometry).
Shown and hidden by ReputationWatchBar_Update (above) and by nothing else — which
is the reference's own arrangement, and why this frame carries NO Scripts. It held
a benilla OnLoad/OnEvent pair while the watch bar was driven by nothing (decision
1094); those were a transcription of the WATCH BAR's own handlers parked here, and
they have gone back to it. The ref's OnShow/OnHide call
UIParent_ManageFramePositions, which benilla doesn't build
(DurabilityFrame.xml's note) — a named cut.
-->
<Frame name="MainMenuBarMaxLevelBar" enableMouse="true" hidden="true">
<Size><AbsDimension x="1024" y="7"/></Size>
<Anchors>
<Anchor point="TOP" relativePoint="TOP"><Offset><AbsDimension x="0" y="-11"/></Offset></Anchor>
</Anchors>
<Layers>
<Layer level="BACKGROUND">
<Texture name="MainMenuMaxLevelBar0" file="Interface\MainMenuBar\UI-MainMenuBar-MaxLevel">
<Size><AbsDimension x="256" y="7"/></Size>
<Anchors>
<Anchor point="BOTTOM" relativePoint="TOP"><Offset><AbsDimension x="-384" y="0"/></Offset></Anchor>
</Anchors>
<TexCoords left="0" right="1.0" top="0" bottom="0.21875"/>
</Texture>
<Texture name="MainMenuMaxLevelBar1" file="Interface\MainMenuBar\UI-MainMenuBar-MaxLevel">
<Size><AbsDimension x="256" y="7"/></Size>
<Anchors>
<Anchor point="LEFT" relativeTo="MainMenuMaxLevelBar0" relativePoint="RIGHT"/>
</Anchors>
<TexCoords left="0" right="1.0" top="0.25" bottom="0.46875"/>
</Texture>
<Texture name="MainMenuMaxLevelBar2" file="Interface\MainMenuBar\UI-MainMenuBar-MaxLevel">
<Size><AbsDimension x="256" y="7"/></Size>
<Anchors>
<Anchor point="LEFT" relativeTo="MainMenuMaxLevelBar1" relativePoint="RIGHT"/>
</Anchors>
<TexCoords left="0" right="1.0" top="0.5" bottom="0.71875"/>
</Texture>
<Texture name="MainMenuMaxLevelBar3" file="Interface\MainMenuBar\UI-MainMenuBar-MaxLevel">
<Size><AbsDimension x="256" y="7"/></Size>
<Anchors>
<Anchor point="LEFT" relativeTo="MainMenuMaxLevelBar2" relativePoint="RIGHT"/>
</Anchors>
<TexCoords left="0" right="1.0" top="0.75" bottom="0.96875"/>
</Texture>
</Layer>
</Layers>
</Frame>
<!--
The art frame: fills the bar (ref-MainMenuBar.xml l.225-229). Carries the 4 dwarf
strips (ARTWORK) + the 2 end caps and page number (OVERLAY); the buttons are its
children so they draw over the art.
-->
<Frame name="MainMenuBarArtFrame">
<Anchors>
<Anchor point="TOPLEFT" relativeTo="MainMenuBar" relativePoint="TOPLEFT"/>
<Anchor point="BOTTOMRIGHT" relativeTo="MainMenuBar" relativePoint="BOTTOMRIGHT"/>
</Anchors>
<Layers>
<!-- 4 dwarf strips: 256x43, BOTTOM x=-384/-128/128/384 y=0, TexCoord bands
(ref-MainMenuBar.xml l.232-283). Adjacent atlas rows ⇒ the joins are seamless. -->
<Layer level="ARTWORK">
<Texture name="MainMenuBarTexture0" file="Interface\MainMenuBar\UI-MainMenuBar-Dwarf">
<Size><AbsDimension x="256" y="43"/></Size>
<Anchors><Anchor point="BOTTOM" relativePoint="BOTTOM"><Offset><AbsDimension x="-384" y="0"/></Offset></Anchor></Anchors>
<TexCoords left="0" right="1.0" top="0.83203125" bottom="1.0"/>
</Texture>
<Texture name="MainMenuBarTexture1" file="Interface\MainMenuBar\UI-MainMenuBar-Dwarf">
<Size><AbsDimension x="256" y="43"/></Size>
<Anchors><Anchor point="BOTTOM" relativePoint="BOTTOM"><Offset><AbsDimension x="-128" y="0"/></Offset></Anchor></Anchors>
<TexCoords left="0" right="1.0" top="0.58203125" bottom="0.75"/>
</Texture>
<Texture name="MainMenuBarTexture2" file="Interface\MainMenuBar\UI-MainMenuBar-Dwarf">
<Size><AbsDimension x="256" y="43"/></Size>
<Anchors><Anchor point="BOTTOM" relativePoint="BOTTOM"><Offset><AbsDimension x="128" y="0"/></Offset></Anchor></Anchors>
<TexCoords left="0" right="1.0" top="0.33203125" bottom="0.5"/>
</Texture>
<Texture name="MainMenuBarTexture3" file="Interface\MainMenuBar\UI-MainMenuBar-Dwarf">
<Size><AbsDimension x="256" y="43"/></Size>
<Anchors><Anchor point="BOTTOM" relativePoint="BOTTOM"><Offset><AbsDimension x="384" y="0"/></Offset></Anchor></Anchors>
<TexCoords left="0" right="1.0" top="0.08203125" bottom="0.25"/>
</Texture>
</Layer>
<!-- End caps: UI-MainMenuBar-EndCap-Dwarf 128x128, BOTTOM x=-544 / +544; the RIGHT
cap mirrors its art with TexCoords (1.0, 0.0, 0.0, 1.0) so the lion faces
outward on both sides (ref-MainMenuBar.xml l.286-310; the mirrored-UV engine
fix landed in 87c97d7). Page number: GameFontNormalSmall CENTER (30,-5),
static "1" (l.311-319). -->
<Layer level="OVERLAY">
<Texture name="MainMenuBarLeftEndCap" file="Interface\MainMenuBar\UI-MainMenuBar-EndCap-Dwarf">
<Size><AbsDimension x="128" y="128"/></Size>
<Anchors><Anchor point="BOTTOM" relativePoint="BOTTOM"><Offset><AbsDimension x="-544" y="0"/></Offset></Anchor></Anchors>
</Texture>
<Texture name="MainMenuBarRightEndCap" file="Interface\MainMenuBar\UI-MainMenuBar-EndCap-Dwarf">
<Size><AbsDimension x="128" y="128"/></Size>
<Anchors><Anchor point="BOTTOM" relativePoint="BOTTOM"><Offset><AbsDimension x="544" y="0"/></Offset></Anchor></Anchors>
<TexCoords left="1.0" right="0.0" top="0.0" bottom="1.0"/>
</Texture>
<!-- The page numeral. `text="1"` is the LOAD SEED the ref gets from the up
arrow's OnLoad (`SetText(CURRENT_ACTIONBAR_PAGE)`, which is 1 there
too); every change after that comes through `ChangeActionBarPage`. -->
<FontString name="MainMenuBarPageNumber" inherits="GameFontNormalSmall" text="1">
<Anchors>
<Anchor point="CENTER" relativePoint="CENTER"><Offset><AbsDimension x="30" y="-5"/></Offset></Anchor>
</Anchors>
</FontString>
</Layer>
</Layers>
<Frames>
<!-- The 12 buttons: Button1 at the art frame BOTTOMLEFT (8,4); 2..12 chain LEFT of
the previous's RIGHT +6,0 (ref-ActionBarFrame.xml l.51-158). Replaces our old
fixed 38px stride with the real 42px chain (36 wide + 6 gap). -->
<CheckButton name="ActionButton1" inherits="BenillaActionButtonTemplate">
<Anchors><Anchor point="BOTTOMLEFT" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="8" y="4"/></Offset></Anchor></Anchors>
<Scripts><OnLoad>BenillaActionButton_OnLoad(self, 1)</OnLoad></Scripts>
</CheckButton>
<CheckButton name="ActionButton2" inherits="BenillaActionButtonTemplate">
<Anchors><Anchor point="LEFT" relativeTo="ActionButton1" relativePoint="RIGHT"><Offset><AbsDimension x="6" y="0"/></Offset></Anchor></Anchors>
<Scripts><OnLoad>BenillaActionButton_OnLoad(self, 2)</OnLoad></Scripts>
</CheckButton>
<CheckButton name="ActionButton3" inherits="BenillaActionButtonTemplate">
<Anchors><Anchor point="LEFT" relativeTo="ActionButton2" relativePoint="RIGHT"><Offset><AbsDimension x="6" y="0"/></Offset></Anchor></Anchors>
<Scripts><OnLoad>BenillaActionButton_OnLoad(self, 3)</OnLoad></Scripts>
</CheckButton>
<CheckButton name="ActionButton4" inherits="BenillaActionButtonTemplate">
<Anchors><Anchor point="LEFT" relativeTo="ActionButton3" relativePoint="RIGHT"><Offset><AbsDimension x="6" y="0"/></Offset></Anchor></Anchors>
<Scripts><OnLoad>BenillaActionButton_OnLoad(self, 4)</OnLoad></Scripts>
</CheckButton>
<CheckButton name="ActionButton5" inherits="BenillaActionButtonTemplate">
<Anchors><Anchor point="LEFT" relativeTo="ActionButton4" relativePoint="RIGHT"><Offset><AbsDimension x="6" y="0"/></Offset></Anchor></Anchors>
<Scripts><OnLoad>BenillaActionButton_OnLoad(self, 5)</OnLoad></Scripts>
</CheckButton>
<CheckButton name="ActionButton6" inherits="BenillaActionButtonTemplate">
<Anchors><Anchor point="LEFT" relativeTo="ActionButton5" relativePoint="RIGHT"><Offset><AbsDimension x="6" y="0"/></Offset></Anchor></Anchors>
<Scripts><OnLoad>BenillaActionButton_OnLoad(self, 6)</OnLoad></Scripts>
</CheckButton>
<CheckButton name="ActionButton7" inherits="BenillaActionButtonTemplate">
<Anchors><Anchor point="LEFT" relativeTo="ActionButton6" relativePoint="RIGHT"><Offset><AbsDimension x="6" y="0"/></Offset></Anchor></Anchors>
<Scripts><OnLoad>BenillaActionButton_OnLoad(self, 7)</OnLoad></Scripts>
</CheckButton>
<CheckButton name="ActionButton8" inherits="BenillaActionButtonTemplate">
<Anchors><Anchor point="LEFT" relativeTo="ActionButton7" relativePoint="RIGHT"><Offset><AbsDimension x="6" y="0"/></Offset></Anchor></Anchors>
<Scripts><OnLoad>BenillaActionButton_OnLoad(self, 8)</OnLoad></Scripts>
</CheckButton>
<CheckButton name="ActionButton9" inherits="BenillaActionButtonTemplate">
<Anchors><Anchor point="LEFT" relativeTo="ActionButton8" relativePoint="RIGHT"><Offset><AbsDimension x="6" y="0"/></Offset></Anchor></Anchors>
<Scripts><OnLoad>BenillaActionButton_OnLoad(self, 9)</OnLoad></Scripts>
</CheckButton>
<CheckButton name="ActionButton10" inherits="BenillaActionButtonTemplate">
<Anchors><Anchor point="LEFT" relativeTo="ActionButton9" relativePoint="RIGHT"><Offset><AbsDimension x="6" y="0"/></Offset></Anchor></Anchors>
<Scripts><OnLoad>BenillaActionButton_OnLoad(self, 10)</OnLoad></Scripts>
</CheckButton>
<CheckButton name="ActionButton11" inherits="BenillaActionButtonTemplate">
<Anchors><Anchor point="LEFT" relativeTo="ActionButton10" relativePoint="RIGHT"><Offset><AbsDimension x="6" y="0"/></Offset></Anchor></Anchors>
<Scripts><OnLoad>BenillaActionButton_OnLoad(self, 11)</OnLoad></Scripts>
</CheckButton>
<CheckButton name="ActionButton12" inherits="BenillaActionButtonTemplate">
<Anchors><Anchor point="LEFT" relativeTo="ActionButton11" relativePoint="RIGHT"><Offset><AbsDimension x="6" y="0"/></Offset></Anchor></Anchors>
<Scripts><OnLoad>BenillaActionButton_OnLoad(self, 12)</OnLoad></Scripts>
</CheckButton>
<!-- Page up/down: 32x32 at CENTER of the art frame's TOPLEFT +(522,-22)/(522,-42)
(ref-ActionBarFrame.xml l.159-217). Live now that the bar pages — they were
art with no OnClick for as long as `CURRENT_ACTIONBAR_PAGE` did not exist.
The HitRectInsets are LOAD-BEARING, not decoration: the two 32x32 squares
are stacked only 20px apart, so their raw rects OVERLAP by 12px and the
hit-test (later sibling wins) would give that band — the bottom third of
the visible up arrow — to the DOWN button. The ref's ±6/±7 insets shrink
each to the 20x18 arrow it actually draws, which separates them and stops
the down arrow's rect hanging below the screen edge and over the XP bar. -->
<Button name="ActionBarUpButton">
<Size><AbsDimension x="32" y="32"/></Size>
<Anchors>
<Anchor point="CENTER" relativeTo="MainMenuBarArtFrame" relativePoint="TOPLEFT"><Offset><AbsDimension x="522" y="-22"/></Offset></Anchor>
</Anchors>
<HitRectInsets><AbsInset left="6" right="6" top="7" bottom="7"/></HitRectInsets>
<NormalTexture file="Interface\MainMenuBar\UI-MainMenu-ScrollUpButton-Up"/>
<PushedTexture file="Interface\MainMenuBar\UI-MainMenu-ScrollUpButton-Down"/>
<HighlightTexture file="Interface\MainMenuBar\UI-MainMenu-ScrollUpButton-Highlight" alphaMode="ADD"/>
<Scripts><OnClick>ActionBar_PageUp() PlaySound("UChatScrollButton")</OnClick></Scripts>
</Button>
<Button name="ActionBarDownButton">
<Size><AbsDimension x="32" y="32"/></Size>
<Anchors>
<Anchor point="CENTER" relativeTo="MainMenuBarArtFrame" relativePoint="TOPLEFT"><Offset><AbsDimension x="522" y="-42"/></Offset></Anchor>
</Anchors>
<HitRectInsets><AbsInset left="6" right="6" top="7" bottom="7"/></HitRectInsets>
<NormalTexture file="Interface\MainMenuBar\UI-MainMenu-ScrollDownButton-Up"/>
<PushedTexture file="Interface\MainMenuBar\UI-MainMenu-ScrollDownButton-Down"/>
<HighlightTexture file="Interface\MainMenuBar\UI-MainMenu-ScrollDownButton-Highlight" alphaMode="ADD"/>
<Scripts><OnClick>ActionBar_PageDown() PlaySound("UChatScrollButton")</OnClick></Scripts>
</Button>
</Frames>
</Frame>
<!-- The performance ("ping") meter (ref-MainMenuBar.xml l.344-407). 16x64 pinned to the
bar's BOTTOMRIGHT +(-227,-10) — the empty recess between the last micro button and
the bag slots, which the bar art leaves TRANSPARENT for exactly this. The 20x66
texture hangs off the frame's TOPRIGHT (so it overspills 4 left and 2 below), and
the frame itself hangs 10 below the bar, putting the bar's grey column in the slot.
frameStrata="LOW" is load-bearing, not decoration: the meter draws UNDER the bar art
and shows through its transparent window. At the default MEDIUM it would paint over
the metal surround, since the texture is wider than the slot. (The ref gets the same
order via strata too — MainMenuBar's OnLoad RaiseFrameLevel can't reach across it.)
The hover button is the ref's own split: a HIGH-strata all-points button, because the
LOW frame beneath the art can't take the mouse itself. The ref declares it as a
sibling with parent="MainMenuBarPerformanceBarFrame"; nesting it is the same frame
tree, and its explicit strata survives the parent's LOW subtree-force (attributes
apply before nested <Frames> are built).
The -227 authored here is the NO-keyring position; MainMenuBar_UpdateKeyRing
(top of this file) re-points it to -235 while the keyring is shown, the ref's own
shift (MainMenuBar.lua l.180). Both offsets live in that one function. -->
<Frame name="MainMenuBarPerformanceBarFrame" frameStrata="LOW">
<Size><AbsDimension x="16" y="64"/></Size>
<Anchors>
<Anchor point="BOTTOMRIGHT" relativeTo="MainMenuBar" relativePoint="BOTTOMRIGHT"><Offset><AbsDimension x="-227" y="-10"/></Offset></Anchor>
</Anchors>
<Layers>
<Layer level="BACKGROUND">
<Texture name="MainMenuBarPerformanceBar" file="Interface\MainMenuBar\UI-MainMenuBar-PerformanceBar">
<Size><AbsDimension x="20" y="66"/></Size>
<Anchors><Anchor point="TOPRIGHT"/></Anchors>
</Texture>
</Layer>
</Layers>
<Frames>
<Button name="MainMenuBarPerformanceBarFrameButton" setAllPoints="true" frameStrata="HIGH">
<Scripts>
<OnEnter>BenillaPerformanceBar_OnEnter(self)</OnEnter>
<OnLeave>BenillaPerformanceBar_OnLeave()</OnLeave>
</Scripts>
</Button>
</Frames>
<Scripts>
<OnLoad>BenillaPerformanceBar_OnLoad(self)</OnLoad>
<OnUpdate>BenillaPerformanceBar_OnUpdate(self, arg1)</OnUpdate>
</Scripts>
</Frame>
</Frames>
<Scripts>
<OnLoad>BenillaActionBar_OnLoad(self)</OnLoad>
<OnEvent>BenillaActionBar_OnEvent(self)</OnEvent>
</Scripts>
</Frame>
<!-- **The bonus action bar** — declared with 1223 (addons reach for it by name: the largest
single session-start row in the corpus was `CT_BarMod\CT_BarModOptions.lua:154`'s
`getglobal("BonusActionButton" .. i):ClearAllPoints()`; five more addons index the frame,
1218's corrected ranking), and DRIVEN since 1524: it is the form/stance/stealth swap's
transition vehicle — the 505x43 replica of the button strip that slides up out of the
screen edge over 0.15s when a form comes up (igBonusBarOpen on landing), stays covering
the strip while the form holds (so `BonusActionBarFrame:IsShown()` reads
reference-faithfully), and slides back down carrying the old form's icons when it drops.
The machinery lives in this file's script block (ShowBonusActionBar / HideBonusActionBar /
BonusActionBar_OnUpdate, all ref names) and in BenillaActionBar_OnEvent's
UPDATE_BONUS_ACTIONBAR arm; benilla still models the bonus page by RE-PAGING THE MAIN BAR
(1223) — the overlay never replaces that, it covers the swap.
The buttons wire to the bonus page — `BenillaActionButton_OnLoad(self, i)` plus the ref's
own `isBonus` mark, which forks BenillaActionButton_Action to the live bonus page (with
lastBonusBar standing in during the down-slide). They share the ACTIONBUTTON command
prefix, the reference's binding story: vanilla's ACTIONBUTTON1-12 drive the bonus bar
while a form is up (ActionButtonDown/Up's IsShown fork above).
Geometry quoted from the reference: a 505x43 frame anchored TOPLEFT to MainMenuBar's
BOTTOMLEFT +(40,0) as declared — every shown appearance re-anchors at x=4 (the ref Lua's
own BONUSACTIONBAR_XPOS, whose art trims 4px of left TexCoord to line back up); button 1
at BOTTOMLEFT +(5,4), each next LEFT-of-previous-RIGHT +6; the two OVERLAY art strips are
the dwarf sheet rows the main bar itself draws (ref l.55-105). -->
<Frame name="BonusActionBarFrame" frameStrata="HIGH" parent="MainMenuBar" enableMouse="true" toplevel="true" hidden="true">
<Size><AbsDimension x="505" y="43"/></Size>
<Anchors>
<Anchor point="TOPLEFT" relativeTo="MainMenuBar" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="40" y="0"/></Offset></Anchor>
</Anchors>
<Layers>
<Layer level="OVERLAY">
<Texture name="BonusActionBarTexture0" file="Interface\MainMenuBar\UI-MainMenuBar-Dwarf">
<Size><AbsDimension x="252" y="43"/></Size>
<Anchors><Anchor point="TOPLEFT"/></Anchors>
<TexCoords left="0.015625" right="1.0" top="0.83203125" bottom="1.0"/>
</Texture>
<Texture name="BonusActionBarTexture1" file="Interface\MainMenuBar\UI-MainMenuBar-Dwarf">
<Size><AbsDimension x="253" y="43"/></Size>
<Anchors><Anchor point="LEFT" relativeTo="BonusActionBarTexture0" relativePoint="RIGHT"/></Anchors>
<TexCoords left="0" right="0.98828125" top="0.58203125" bottom="0.75"/>
</Texture>
</Layer>
</Layers>
<Frames>
<CheckButton name="BonusActionButton1" inherits="BenillaActionButtonTemplate" id="1">
<Anchors><Anchor point="BOTTOMLEFT" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="5" y="4"/></Offset></Anchor></Anchors>
<Scripts><OnLoad>BenillaActionButton_OnLoad(self, 1) self.isBonus = 1</OnLoad></Scripts>
</CheckButton>
<CheckButton name="BonusActionButton2" inherits="BenillaActionButtonTemplate" id="2">
<Anchors><Anchor point="LEFT" relativeTo="BonusActionButton1" relativePoint="RIGHT"><Offset><AbsDimension x="6" y="0"/></Offset></Anchor></Anchors>
<Scripts><OnLoad>BenillaActionButton_OnLoad(self, 2) self.isBonus = 1</OnLoad></Scripts>
</CheckButton>
<CheckButton name="BonusActionButton3" inherits="BenillaActionButtonTemplate" id="3">
<Anchors><Anchor point="LEFT" relativeTo="BonusActionButton2" relativePoint="RIGHT"><Offset><AbsDimension x="6" y="0"/></Offset></Anchor></Anchors>
<Scripts><OnLoad>BenillaActionButton_OnLoad(self, 3) self.isBonus = 1</OnLoad></Scripts>
</CheckButton>
<CheckButton name="BonusActionButton4" inherits="BenillaActionButtonTemplate" id="4">
<Anchors><Anchor point="LEFT" relativeTo="BonusActionButton3" relativePoint="RIGHT"><Offset><AbsDimension x="6" y="0"/></Offset></Anchor></Anchors>
<Scripts><OnLoad>BenillaActionButton_OnLoad(self, 4) self.isBonus = 1</OnLoad></Scripts>
</CheckButton>
<CheckButton name="BonusActionButton5" inherits="BenillaActionButtonTemplate" id="5">
<Anchors><Anchor point="LEFT" relativeTo="BonusActionButton4" relativePoint="RIGHT"><Offset><AbsDimension x="6" y="0"/></Offset></Anchor></Anchors>
<Scripts><OnLoad>BenillaActionButton_OnLoad(self, 5) self.isBonus = 1</OnLoad></Scripts>
</CheckButton>
<CheckButton name="BonusActionButton6" inherits="BenillaActionButtonTemplate" id="6">
<Anchors><Anchor point="LEFT" relativeTo="BonusActionButton5" relativePoint="RIGHT"><Offset><AbsDimension x="6" y="0"/></Offset></Anchor></Anchors>
<Scripts><OnLoad>BenillaActionButton_OnLoad(self, 6) self.isBonus = 1</OnLoad></Scripts>
</CheckButton>
<CheckButton name="BonusActionButton7" inherits="BenillaActionButtonTemplate" id="7">
<Anchors><Anchor point="LEFT" relativeTo="BonusActionButton6" relativePoint="RIGHT"><Offset><AbsDimension x="6" y="0"/></Offset></Anchor></Anchors>
<Scripts><OnLoad>BenillaActionButton_OnLoad(self, 7) self.isBonus = 1</OnLoad></Scripts>
</CheckButton>
<CheckButton name="BonusActionButton8" inherits="BenillaActionButtonTemplate" id="8">
<Anchors><Anchor point="LEFT" relativeTo="BonusActionButton7" relativePoint="RIGHT"><Offset><AbsDimension x="6" y="0"/></Offset></Anchor></Anchors>
<Scripts><OnLoad>BenillaActionButton_OnLoad(self, 8) self.isBonus = 1</OnLoad></Scripts>
</CheckButton>
<CheckButton name="BonusActionButton9" inherits="BenillaActionButtonTemplate" id="9">
<Anchors><Anchor point="LEFT" relativeTo="BonusActionButton8" relativePoint="RIGHT"><Offset><AbsDimension x="6" y="0"/></Offset></Anchor></Anchors>
<Scripts><OnLoad>BenillaActionButton_OnLoad(self, 9) self.isBonus = 1</OnLoad></Scripts>
</CheckButton>
<CheckButton name="BonusActionButton10" inherits="BenillaActionButtonTemplate" id="10">
<Anchors><Anchor point="LEFT" relativeTo="BonusActionButton9" relativePoint="RIGHT"><Offset><AbsDimension x="6" y="0"/></Offset></Anchor></Anchors>
<Scripts><OnLoad>BenillaActionButton_OnLoad(self, 10) self.isBonus = 1</OnLoad></Scripts>
</CheckButton>
<CheckButton name="BonusActionButton11" inherits="BenillaActionButtonTemplate" id="11">
<Anchors><Anchor point="LEFT" relativeTo="BonusActionButton10" relativePoint="RIGHT"><Offset><AbsDimension x="6" y="0"/></Offset></Anchor></Anchors>
<Scripts><OnLoad>BenillaActionButton_OnLoad(self, 11) self.isBonus = 1</OnLoad></Scripts>
</CheckButton>
<CheckButton name="BonusActionButton12" inherits="BenillaActionButtonTemplate" id="12">
<Anchors><Anchor point="LEFT" relativeTo="BonusActionButton11" relativePoint="RIGHT"><Offset><AbsDimension x="6" y="0"/></Offset></Anchor></Anchors>
<Scripts><OnLoad>BenillaActionButton_OnLoad(self, 12) self.isBonus = 1</OnLoad></Scripts>
</CheckButton>
</Frames>
<Scripts>
<OnLoad>BenillaBonusBar_OnLoad(self)</OnLoad>
<OnUpdate>BonusActionBar_OnUpdate(arg1)</OnUpdate>
</Scripts>
</Frame>
<!-- **The reputation watch bar** — built to `ref-ReputationFrame.xml:869-1036`, whose geometry
and art it quotes (those are the client's own facts) and whose handlers it implements in
this file's script block. It
shipped built-and-hidden with 1223 (three corpus addons index it by name — `FuBar_FuXPFu`,
`CT_BottomBar`, `zBarEx` — and FuXPFu dies at `FuBar_FuXPFu.lua:227` on the nil); it is
DRIVEN now, by `ReputationWatchBar_Update` in this file's script block, whose own comment
carries the reason the reputation watch-bar slice lives here rather than in
ReputationFrame.xml where the reference declares it.
Geometry quoted from the reference: a 1024x11 frame whose BOTTOM sits on MainMenuBar's TOP
at (0,-3), holding a 1024x8 `ReputationWatchStatusBar` at TOP. TWO sets of end art ride the
status bar and the update swaps between them: the four `ReputationWatchBarTexture*` slices
of `UI-ReputationWatchBar` for the stacked-above-the-XP-bar posture, and the four
`ReputationXPBarTexture*` slices of the dwarf `UI-MainMenuBar-Dwarf` sheet for the
replaces-the-XP-bar posture at max level, where the strip has to look like the bar it took
over from. The numerals live on their own DIALOG-strata overlay child so they draw over
both. -->
<Frame name="ReputationWatchBar" enableMouse="true" parent="MainMenuBar" hidden="true">
<Size><AbsDimension x="1024" y="11"/></Size>
<Anchors>
<Anchor point="BOTTOM" relativeTo="MainMenuBar" relativePoint="TOP"><Offset><AbsDimension x="0" y="-3"/></Offset></Anchor>
</Anchors>
<Frames>
<StatusBar name="ReputationWatchStatusBar" drawLayer="ARTWORK">
<Size><AbsDimension x="1024" y="8"/></Size>
<Anchors><Anchor point="TOP"/></Anchors>
<Layers>
<Layer level="OVERLAY">
<!-- ref l.890-929: four 256-wide quarters of the 1024 strip, each its own
band of the sheet. The TexCoords were MISSING while the frame was a
stub that could not show; without them every quarter draws the whole
sheet. -->
<Texture name="ReputationWatchBarTexture0" file="Interface\PaperDollInfoFrame\UI-ReputationWatchBar">
<Size><AbsDimension x="256" y="11"/></Size>
<Anchors><Anchor point="TOPLEFT"><Offset><AbsDimension x="0" y="2"/></Offset></Anchor></Anchors>
<TexCoords left="0" right="1.0" top="0" bottom="0.171875"/>
</Texture>
<Texture name="ReputationWatchBarTexture1" file="Interface\PaperDollInfoFrame\UI-ReputationWatchBar">
<Size><AbsDimension x="256" y="11"/></Size>
<Anchors><Anchor point="LEFT" relativeTo="ReputationWatchBarTexture0" relativePoint="RIGHT"/></Anchors>
<TexCoords left="0" right="1.0" top="0.171875" bottom="0.34375"/>
</Texture>
<Texture name="ReputationWatchBarTexture2" file="Interface\PaperDollInfoFrame\UI-ReputationWatchBar">
<Size><AbsDimension x="256" y="11"/></Size>
<Anchors><Anchor point="LEFT" relativeTo="ReputationWatchBarTexture1" relativePoint="RIGHT"/></Anchors>
<TexCoords left="0" right="1.0" top="0.34375" bottom="0.515625"/>
</Texture>
<Texture name="ReputationWatchBarTexture3" file="Interface\PaperDollInfoFrame\UI-ReputationWatchBar">
<Size><AbsDimension x="256" y="11"/></Size>
<Anchors><Anchor point="LEFT" relativeTo="ReputationWatchBarTexture2" relativePoint="RIGHT"/></Anchors>
<TexCoords left="0" right="1.0" top="0.515625" bottom="0.6875"/>
</Texture>
<!-- ref l.930-969: the max-level alternate, four bands of the SAME dwarf
sheet the XP bar's own notch art comes from, so the replaced bar reads
as the bar it replaced. Hidden until the max-level arm shows them. -->
<Texture name="ReputationXPBarTexture0" file="Interface\MainMenuBar\UI-MainMenuBar-Dwarf" hidden="true">
<Size><AbsDimension x="256" y="10"/></Size>
<Anchors><Anchor point="TOPLEFT"/></Anchors>
<TexCoords left="0" right="1.0" top="0.79296875" bottom="0.83203125"/>
</Texture>
<Texture name="ReputationXPBarTexture1" file="Interface\MainMenuBar\UI-MainMenuBar-Dwarf" hidden="true">
<Size><AbsDimension x="256" y="10"/></Size>
<Anchors><Anchor point="LEFT" relativeTo="ReputationXPBarTexture0" relativePoint="RIGHT"/></Anchors>
<TexCoords left="0" right="1.0" top="0.54296875" bottom="0.58203125"/>
</Texture>
<Texture name="ReputationXPBarTexture2" file="Interface\MainMenuBar\UI-MainMenuBar-Dwarf" hidden="true">
<Size><AbsDimension x="256" y="10"/></Size>
<Anchors><Anchor point="LEFT" relativeTo="ReputationXPBarTexture1" relativePoint="RIGHT"/></Anchors>
<TexCoords left="0" right="1.0" top="0.29296875" bottom="0.33203125"/>
</Texture>
<Texture name="ReputationXPBarTexture3" file="Interface\MainMenuBar\UI-MainMenuBar-Dwarf" hidden="true">
<Size><AbsDimension x="256" y="10"/></Size>
<Anchors><Anchor point="LEFT" relativeTo="ReputationXPBarTexture2" relativePoint="RIGHT"/></Anchors>
<TexCoords left="0" right="1.0" top="0.04296875" bottom="0.08203125"/>
</Texture>
</Layer>
<!-- ref l.971-975: the half-alpha black backing the fill rides over. -->
<Layer level="BACKGROUND">
<Texture name="ReputationWatchStatusBarBackground">
<Color r="0.0" g="0.0" b="0.0" a="0.5"/>
</Texture>
</Layer>
</Layers>
<!-- ref l.977-978. The purple is only the load-time default: every painted state
takes FACTION_BAR_COLORS[reaction] instead. -->
<BarTexture file="Interface\TargetingFrame\UI-StatusBar"/>
<BarColor r="0.58" g="0.0" b="0.55"/>
</StatusBar>
<!-- ref l.980-994: the numerals sit on a DIALOG-strata child of their own so they clear
both sets of end art. Hidden by default; the hover shows them, and at max level the
statusBarText cvar can pin them (ShowWatchedReputationBarText). -->
<Frame name="ReputationWatchBarOverlayFrame" frameStrata="DIALOG" setAllPoints="true">
<Layers>
<Layer level="ARTWORK">
<FontString name="ReputationWatchStatusBarText" inherits="NumberFontNormal" hidden="true">
<Anchors><Anchor point="CENTER"><Offset><AbsDimension x="0" y="3"/></Offset></Anchor></Anchors>
</FontString>
</Layer>
</Layers>
</Frame>
</Frames>
<!-- ref l.996-1035. The three that carry any thought are chunk functions in this file's
script block, where their departures are documented (`BenillaWatchBar_OnLoad` /
`_OnEvent` / `_OnShow`); the three below are one statement each and stay where the
reference has them. The numerals hover is the pair: coming onto the bar shows them
unconditionally, leaving takes them down UNLESS the statusBarText cvar has pinned them
on — `textLocked` is the pin, and the two text verbs above are what set it. -->
<Scripts>
<OnLoad>BenillaWatchBar_OnLoad(self)</OnLoad>
<OnEvent>BenillaWatchBar_OnEvent(self, event)</OnEvent>
<OnShow>BenillaWatchBar_OnShow()</OnShow>
<OnHide>UIParent_ManageFramePositions()</OnHide>
<OnEnter>ReputationWatchStatusBarText:Show()</OnEnter>
<OnLeave>
if not ReputationWatchBar.textLocked then
ReputationWatchStatusBarText:Hide()
end
</OnLeave>
</Scripts>
</Frame>
</Ui>