Files
benilla-octo/crates/benilla-app/assets/ui/OptionsFrame.xml
T
SamandClaude Fable 5.1 fad9ba02c3 zone channels survive a border crossing, and nameplates survive a login
- crossing a zone border prints one Changed Channel line instead of a leave and a join, and General and LocalDefense keep printing in the new zone; walking out of a capital keeps Trade's number and its registration instead of dropping it (2130)
- outside a capital the channel numbers are the reference's: a fresh character holds 1 General, 2 Trade (suspended until a city) and 3 LocalDefense, so /2 reaches the same channel it does on the real client (2137)
- logging into a character reads that character's zone, not the previous one's, so a login in the Eastern Plaguelands no longer joins and leaves Stormwind's channels first; a chat cache with General saved as a custom name gets its id back (2130)
- the Show Tutorials option survives a /reload: it loaded unchecked and clicking it reset every dismissed tutorial account-wide; a world entry no longer switches off nameplates you had turned on (2131, 2132)

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-11 20:51:41 +07:00

4069 lines
302 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>
<!--
The OPTIONS window (decision 0950) — the shell (slice A) and the real PAGES: Audio
(0957), Graphics (0959), Controls (0961), Nameplates (0992, with the dropdown row
shape). Opened from the game menu's Options button.
Setting rows ride the CVar store (0954): rows read GetCVar on show, write SetCVar on
interaction, and Defaults resets the visible page's CVars to their registered defaults.
Pages without rows are still empty and keep Defaults disabled. One row defers — uiScale
stages a pending value and commits through the era Apply button (0961), because a live
write would rescale the very slider being dragged.
PROVENANCE (0985 — the three-source split, stated the same way in every era-shaped
window; GameMenuFrame.xml carries the sibling block):
· STRUCTURE & BEHAVIOR — the 1.15 era client (wow-ui-source-era @ 1.15.9.68940):
layout, geometry grammar, interaction, functionality — the single all-in-one
window, the category tree, the search, the Apply flow. If 1.15 has the mechanism,
it STAYS, even where the older clients had none — except a DIRECTED CUT the
director makes on look, each recorded (0989: the slider steppers and the corner
close X; the split bends to taste only by record, never silently).
· CHROME MECHANISM — the 1.13/1.14 clients' own UI source (wow-ui-source @ 1.14.4,
their System window family; 0981/0984): HOW surfaces are textured — the dialog
backdrop, the outline boxes, the title plate, the select/hover washes and their
tints, the hairlines. Geometry may bend where 1.15 furniture needs a seat (the
-52 band below); the mechanism holds.
· ART BYTES — the 1.12 MPQ chain only, or a plain color fill (0978 retired every
era-atlas extraction and the `era:` scheme; one install, nothing later-client at
runtime).
So: the 1.12 OptionsFrame family (four separate panels) collapses into the era's
single window — a category list, one page area — every texture 1.12-native. The era
art names in the bullets below stay as GEOMETRY CITATIONS, not drawn art:
· **Root** — Blizzard_SettingsPanel.xml `SettingsPanel`: 920x724, CENTER, strata HIGH,
hidden. Skin per `SettingsFrameTemplate` (Classic/Blizzard_SettingsPanelTemplates.xml):
FlatPanelBackgroundTemplate at TOPLEFT (7,-18) → BOTTOMRIGHT (-3,3), the
`ButtonFrameTemplateNoPortrait` nine-slice (Classic/NineSliceLayouts.lua l.132 — right
edge x=-8/x1=8), the `_UI-Frame-TitleTileBg` title band (256x17, TexCoords
.2890625/.421875, TOPLEFT (8,-3) → TOPRIGHT (0,-3)), title GameFontNormal TOP (0,-5).
· **Category list** — Blizzard_CategoryList.xml + .lua: rows/headers STRETCH to the
scroll target's width (199, no scroll bar); header 30 tall (label 14px white at
LEFT (20,-1), Options_CategoryHeader_<n> wash at TOPLEFT at atlas size, n cycling
per header); row 20 tall (label GameFontNormal TOPLEFT (36,+1); bg CENTER —
options_list_active selected / options_list_hover hover, at atlas size); 2 between
elements, 18-tall spacer between groups (so 22 row-to-header).
· **Page area** — Blizzard_SettingsList.xml: title 20px white TOPLEFT (7,-22), the
options_horizontaldivider at TOP (0,-50), Defaults 96x22 at TOPRIGHT (-36,-16).
· **Tabs** — SharedXML Shared/Tabs/MinimalTab.{xml,lua}: a bottom-anchored three-slice
of Options_Tab[_Active]_{Left,Middle,Right}, GameTab TOPLEFT (32,-27) h37, AddOnsTab
5 right of it; width text+40; selected = white small text at BOTTOM+6, unselected =
gold small at BOTTOM+4.
· **Search box** — SharedXML SearchBoxTemplate → InputBoxVisualTemplate
(SecureUIPanelTemplates.xml l.42-67): the common-search-border three-slice (8x20 caps,
left hung 5 outside, middle stretched, all centered on the 22 box), 10x10 magnifier at
LEFT (1,-1), 17x17 clear button at RIGHT (-3), TextInsets 16/20, instructions label
GameFontDisableSmall tinted 0.35 at TOPLEFT (16,0) → BOTTOMRIGHT (-20,0).
THE 1.12 ART MAPPING (0978 base set, 0981 chrome — every file verified present in the
5875 MPQ chain; the CHROME recipe is the 1.14 System window's own, read verbatim from
wow-ui-source @1.14.4 OptionsFrameTemplates.xml + InterfaceOptionsFrame.xml, whose
every piece is 1.12-native or a color):
· window chrome — BACKDROP_DIALOG_32_32, the 1.14 options frames' backdrop: tiled
UI-DialogBox-Background ground (uniform black at 60% alpha — the world reads
through, the reference screenshot's actual mechanism; 0978 misread that ground as
parchment) + UI-DialogBox-Border rope edge 32, insets 11/12/12/11. Title plate
UI-DialogBox-Header at the options frames' 300x68 TOP (0,+12) seat, title on it.
ON TOP of that tile, inside the rope, a plain black 0.7 fill (1476) takes the
composite ground to 0.88: at the bare tile the world reached the option labels
hard enough to break them over bright terrain. The read-through stays, dimmed.
· the era's one dark Options_InnerFrame panel → TWO 1.14 OptionsFrameListTemplate
boxes: tooltip-border backdrops with the FILL AT ALPHA 0 (outlines — the ground
shows through) and the border tinted LIGHTGRAY_FONT_COLOR 0.6, tops at -52 (1.14's
own -40 band deepened 12 to hold the era search seat — 0984), tabs seated on the
category box's top edge (BOTTOMLEFT → box TOPLEFT (6,-3), the file-folder tuck).
· category rows — the 1.14 list-button wash, verbatim mechanism (0984,
OptionsFrameTemplates.{xml l.216-229, lua l.107-131}): ONE UI-QuestLogTitleHighlight
quad in ADD blend at the era plate seat (187x21 centered), steel-blue (.196,.388,.8)
as the hover and locked GOLD (1,1,0) as the selection, white hover/selected text.
The Options_CategoryHeader_<n> washes have no 1.12 counterpart and are DROPPED —
headers are their label alone.
· checkboxes: the UI-CheckBox four-state set at the era 30x29 seat (no HighlightTexture
— hover feedback stays the era row-wide wash, unchanged behavior).
· sliders: the 1.12 OptionsSliderTemplate groove — UI-SliderBar-Background/-Border
backdrop, its exact edge 8 / tile 8 / insets 3/3/6/6 — on a 17-tall Groove frame at
the era track seat, and the 32x32 UI-SliderBar-Button-Horizontal thumb. NO steppers
(0989, a directed cut of era structure): the bar is the whole control, 1.12's own
look — and the whole bar is live (thumb drag, or press anywhere on the track to
seat the thumb there and keep dragging; the engine's 0989 track-press law).
· tabs: UI-Character-ActiveTab / -InActiveTab flipped vertically via TexCoords (1.12
ships only bottom-tab plates; the flip turns them top-tab), one stretched piece per
plate instead of the era three-slice, at the era plate heights (26 active / 23 not).
(1.14's own UI-OptionsFrame-{Active,InActive}Tab three-slice is a 2.x file — not in
the 1.12 chain, verified absent.)
· search box: the classic Common-Input-Border three-slice (the file IS in 1.12
interface.MPQ — 0950's pull list mislabeled it era-only), the InputBoxTemplate UVs.
The era magnifier has no 1.12 counterpart and is DROPPED (the "Search" placeholder
carries the affordance) — and with it the era's 16-left TextInsets seat, which
exists to clear that magnifier: text and caret sit at 5, snug to the border ink
(0989). The clear-X wears the UI-Panel-MinimizeButton ball at its 17x17 seat,
half-lit, full-lit on hover (the era ClearButtonMixin's alpha law).
· divider: a 1-unit white hairline at 0.3 alpha, a color fill — what the era's
options_horizontaldivider atlas member actually is (decoded: a 630x1 pure-white row,
alpha peaking ~0.4, mean ~0.26). 0978's stretched UI-DialogBox-Divider is retired.
SCOPE / divergences (noted, not bugs):
- **No corner close X (0989, a directed cut)** — the era HAS one
(UIPanelCloseButtonNoScripts: the UI-Panel-MinimizeButton family at TOPRIGHT (+4,+4),
Classic SharedUIPanelTemplates.xml l.172-180), and 0981/0984 wore it; the director
cut it. The red Close button and ESC close the window, and the freed corner returns
the search box to the era's full-width TOPRIGHT seat.
- **Tabs are STATIC MinimalTab plates** (slice B): Game wears the selected state
permanently, AddOns the unselected gold — the era swaps these in code
(MinimalTabMixin), and with nothing behind AddOns the selection can never change, so
the two states are authored rather than driven. The mixin port comes with the addon
list. Width is era's text+40 law, fitted once from each tab's own `<OnShow>` (2028 —
the engine's measurer answers in the call that asks); the era's
measure-at-the-medium-font quirk is deliberately dropped (~6 units of padding).
- **Search WORKS (0984)** — the era SettingsPanel mechanism re-expressed (the Script
block's search section): label-tag substring match scored by matched length,
results grouped under clickable category heads ordered by best score, a matched
child pulls its parent row in, "Search Results" title with Defaults hidden while
the search holds the page, the no-results line, and the search ends on a category
click or hide (the era's own ClearSearchBox law). The result rows are the LIVE
rows, reflowed — values, writes and dependencies all work mid-search. The two
strings ("Search Results", "No results found.") are ours: the era's
SETTINGS_SEARCH_* globals live in the client binary, not the dump.
- **The PAGE AREA scrolls; the category tree does not.** The static tree (9 rows, 2
headers) fits its 199px column outright, and every CVar page fits its area, so nothing
on a settled page has a bar. The page area is a real ScrollFrame all the same (B217 —
the era's SettingsList is a ScrollBox with a MinimalScrollBar, and results and pages
ride the same one): a SEARCH chains group heads and matched rows from every category
into one column, which for a broad query outran the page and drew out through the
window onto the world. The body is the scroll child, sized to the visible rect until
the content outgrows it (OptionsScroll_Fit). The KEYBINDINGS page (decision 1008 — the
1.15 Settings panel's own Keybindings category, module + provenance in
KeyBindingsPage.xml) keeps its OWN faux-scroll list inside that body — it fills the
body exactly, so the outer scroll's range on it is 0 and the two bars are never live
together; its search results reflow live binding rows under a Keybindings group head
whose click jumps to the page (the era redirect law), capped at the static pool's size.
Both bars wear the shared kit's trough (the recessed channel — 1.12 authors that art
per window, so it is opt-in there and declared beside each bar here).
- **Row tooltips are 1.12's own strings on the era's seat (B223, decision 1054)**: every
row whose setting exists in a 1.12 panel raises that panel's `OPTION_TOOLTIP_*` text —
the description ALONE, wrapped, in the engine's default gold (the 1.12/1.14 option-row
plate, and this window's own since 1008's Character Specific row), seated the era way
(ANCHOR_RIGHT off the label half, x -10). The era's name-line-then-description pair
(`Settings.InitTooltip` → `GameTooltip_AddHighlightLine(name)`) is cut: its title would
repeat the label the cursor is already on. **Auto Loot** is the one row with no 1.12
string — no 1.12 row exists for it (its label is era's too) — and raises no plate.
- **Setting-page cuts (0957 Audio, 0959 Graphics)**: Defaults resets the VISIBLE page only (the
era confirm dialog is not modeled); era rows with no 1.12 CVar +
benilla knob behind them (output device, Dialog volume, loop music, channels, cache;
the whole era Graphics dropdown zoo) don't ship. A slider shows the 1.12 panel's
range even where the benilla knob's clamp is wider (uiScale's env clamp is 0.5..1.5):
a config value outside the page range displays clamped — thumb and readout agree on
the nearest expressible value, the engine keeps running the config's truth, and
nothing writes back until the player actually moves the row.
- **Apply-flow cuts (0961)**: the era's close-with-pending 3-button confirm dialog
(GAME_SETTINGS_CONFIRM_DISCARD) is not modeled — hiding the window silently discards
pending edits (the era's own hide path wipes without committing too); the 8-second
"keep these settings?" revert timer (CommitFlag.Revertable) is not modeled either —
Apply commits outright.
- Era rows this slice does not ship: the Accessibility group, and Languages/Network under
System — pages with no benilla knobs behind them yet (0950's "only rows backed by real
knobs" rule). Nameplates joined the tree in slice B on the promise of its knobs; the
page went LIVE in 0992 (the three UnitName* checkbox rows).
- **The window rides a scale knob** (slice B): fonts ride frame scale engine-wide now
(the 0219 §2 divergence closed), so ERA_WINDOW_SCALE × the era checkFit shrink
(UIPanelUpdateScaleForFit, re-expressed in this file) sizes the whole window; 0.78 is
the director's-eye starting point, live-tunable. The knob is SHARED: it stands in for
the era client's UIParent scale (smaller than our 1.12 768-canvas on modern screens),
so every era-shaped window wears it — GameMenuFrame.xml reads it in its OnShow.
-->
<!-- The two era fonts this window needs beyond Fonts.xml's 1.12 set (the era
GameFontHighlightMedium / GameFontHighlightHuge — Blizzard_Fonts_Shared: FRIZQT 14 and
20, white). Named Options* rather than the era names on purpose: Fonts.xml is the
COMPLETE 1.12 set and stays that way; these are this window's own, defined with
MasterFont's exact shadow inlined (its only content — Fonts.xml l.62). -->
<Font name="OptionsFontHighlightMedium" font="Fonts\FRIZQT__.TTF" virtual="true">
<Shadow>
<Offset><AbsDimension x="1" y="-1"/></Offset>
<Color r="0" g="0" b="0"/>
</Shadow>
<FontHeight><AbsValue val="14"/></FontHeight>
<Color r="1.0" g="1.0" b="1.0"/>
</Font>
<Font name="OptionsFontHighlightHuge" font="Fonts\FRIZQT__.TTF" virtual="true">
<Shadow>
<Offset><AbsDimension x="1" y="-1"/></Offset>
<Color r="0" g="0" b="0"/>
</Shadow>
<FontHeight><AbsValue val="20"/></FontHeight>
<Color r="1.0" g="1.0" b="1.0"/>
</Font>
<Script><![CDATA[
-- ══ WHAT THIS FILE NO LONGER DECLARES, AND WHERE IT COMES FROM NOW (decision 2115) ══════
--
-- benilla's options window is THIS file and it is the one the player opens. But the
-- reference's own `UIOptionsFrame.xml` is now loaded too — hidden, off the player's chain,
-- at the manifest's l.21 seat above this row — so that a third-party addon reaching for
-- `UIOptionsFrame`, `UIOptionsFrameCheckButton42` or `UIOptionsFrame_Load()` finds the real
-- thing instead of an alias onto us. Three things this file used to carry are the stock
-- file's, and are gone from here rather than duplicated:
--
-- · **The 30 options uvars.** `UIOptionsFrame_Init` (stock UIOptionsFrame.lua l.93-185)
-- assigns every one its factory value and `RegisterForSave`s it — SIMPLE_CHAT,
-- CHAT_LOCKED, REMOVE_CHAT_DELAY, SHOW_NEWBIE_TIPS, LOCK_ACTIONBAR,
-- SHOW_BUFF_DURATIONS, ALWAYS_SHOW_MULTIBARS, SHOW_PARTY_PETS, QUEST_FADING_DISABLE,
-- SHOW_PARTY_BACKGROUND, HIDE_PARTY_INTERFACE, SHOW_TARGET_OF_TARGET(+_STATE),
-- HIDE_OUTDOOR_WORLD_STATE, AUTO_QUEST_WATCH, the fourteen COMBAT_TEXT_* rows and
-- COMBAT_TEXT_FLOAT_MODE, plus NAMEPLATES_ON/FRIENDNAMEPLATES_ON. Every value we used
-- to write here was already the reference's (1804 made them so), so this is a pure
-- de-duplication and the DEFAULT IS NOW THE REFERENCE'S BY CONSTRUCTION: our rows read
-- the global at their OnLoad (`OptionsGlobalRow_OnLoad`'s `udefault`), and the stock
-- file has assigned it long before, so Defaults still walks back to the shipped value.
-- Five it declares that we never did come free: SIMPLE_CHAT, SHOW_PARTY_PETS,
-- SHOW_PARTY_BACKGROUND, HIDE_PARTY_INTERFACE, and the nameplate pair's save rows.
--
-- · **`UIOptionsFrameCheckButtons`.** The reference's option table, whose home is that
-- file. Stock `MultiActionBars.lua:10` writes five rows into it at ITS load ("Hack to
-- get around load order dependencies"), which is why the stock window's row must sit
-- above MultiActionBars.xml in benilla.toc — it does, at l.21 exactly as the
-- reference's toc has it. Our rows never read this table; they keep their own store
-- (`OptionsRow_Get`'s three kinds).
--
-- · **`UpdateNameplates`.** `UIOptionsFrame.lua` l.768-780 — the reference's own home
-- for it, and the four verbs it calls (Show/HideNameplates, Show/HideFriendNameplates)
-- are ours in `script/cvars.rs`, unchanged. **One line of that body came back here**
-- (decision 2132) — see the re-declaration below; the de-duplication otherwise stands.
--
-- ── THE ONE ASSIGNMENT THAT STAYS, AND WHY IT IS AN OVERRIDE ────────────────────────────
-- `AUTO_QUEST_WATCH` is the string `"1"` ON PURPOSE (decision 1136): the reference assigns
-- the NUMBER 1 (`UIOptionsFrame.lua` l.122) while every consumer — stock
-- `QuestLogFrame.lua:64/68` and the checkbox's own load — compares against the STRING, and
-- in Lua 5.0 `1 ~= "1"`. So on a fresh 1.12 install the box paints unchecked and the
-- advertised auto-watch is dead until the player clicks it. We ship the string so the
-- advertised default is the real one — a recorded deviation under 1804's rule, not a quiet
-- one.
--
-- **This file loads AFTER the stock one** (manifest l.21 vs this window's seat before
-- GameMenuFrame), so this line runs after `UIOptionsFrame_Init` and wins. It is a
-- re-assignment, not a declaration: the `RegisterForSave` is the stock file's, and a second
-- one would be redundant — but it is kept, because `RegisterForSave` is idempotent and a
-- reader of this line should not have to know the stock file registered it to know the
-- global is persisted.
AUTO_QUEST_WATCH = "1"
RegisterForSave("AUTO_QUEST_WATCH")
-- The family's apply, ref UIOptionsFrame_Save l.371-376 verbatim: the addon is
-- LoadOnDemand, so turning the master on loads it, and the update runs only once it is
-- there. Every combat-text row's `applyFunc` is this (1964).
function CombatText_UpdateOrLoad()
if ( SHOW_COMBAT_TEXT == "1" ) then
UIParentLoadAddOn("Blizzard_CombatText")
end
if ( CombatText_UpdateDisplayedMessages ) then
CombatText_UpdateDisplayedMessages()
end
end
-- The Remove Chat Hover Delay row's applyFunc: the reference's own line
-- (UIOptionsFrame.lua l.216, `SetChatMouseOverDelay(REMOVE_CHAT_DELAY)`) under the name the
-- row resolves at apply time. `SetChatMouseOverDelay` is FloatingChatFrame.lua's.
function ChatFrame_ApplyMouseOverDelay()
if SetChatMouseOverDelay then -- a VM that loaded this file without the chat files — a test's
SetChatMouseOverDelay(REMOVE_CHAT_DELAY)
end
end
-- ── Show Tutorials' SETTER: the reference's own Save arm (decision 2077) ────────────
-- UIOptionsFrame.lua l.318-327, verbatim in behaviour. Its CheckButtons row is a bare
-- `{ index = 28 }` with no cvar and no uvar, which is why decision 1140's census filed it
-- as an "index-only entry with no store at all" and left it out; that reading is wrong.
-- The store is not in the TABLE, it is in the three special-case arms `UIOptionsFrame_Load`
-- (l.239), `_Save` (l.318) and `_SetDefaults` (l.645) keep for this key — the getter is
-- `TutorialsEnabled()` and the setter is the Reset/Clear pair, because "disable tutorials"
-- in 1.12 literally means "mark every bit of the acknowledged bank", not a CVar (1976;
-- wow-re censused all 210 `CVar::Register` sites and there is no tutorial CVar).
--
-- The `~=` guard is the reference's and it matters: `TutorialsEnabled()` returns the NUMBER
-- 1 or nil, so re-ticking an already-on row does not re-`ResetTutorials()` — which would
-- re-arm every tutorial the player has already dismissed.
function OptionsFrame_SetTutorialsEnabled(value)
if ( tonumber(value) ~= TutorialsEnabled() ) then
if ( value == "1" ) then
ResetTutorials()
TutorialFrameCheckButton:SetChecked(1)
else
ClearTutorials()
end
TutorialFrame_HideAllAlerts()
end
end
-- ── `UpdateNameplates` — OURS AGAIN, and the deviation is ONE LINE (decision 2132) ──────
-- The stock body is `UIOptionsFrame.lua` l.768-780, running off the chain since 2115.
-- This file loads after it (manifest l.21 vs this window's seat), so this re-declaration
-- wins. It is transcribed verbatim except that l.776 is dropped, and both halves of the
-- reason are benilla facts, not taste:
--
-- · **l.774-776 is a stock typo.** `if ( FRIENDNAMEPLATES_ON ) then
-- ShowFriendNameplates(); HideFriendNameplates();` — show, then hide, so BOTH branches
-- end hidden. On 1.12 that only clears volatile engine bits, and the friendly global is
-- not even saved (its l.182 registers `NAMEPLATES_ON` a second time where
-- `FRIENDNAMEPLATES_ON` was meant). On benilla the four verbs write the CVar pair, and
-- the CVar pair IS the store (`vplates.rs`, `script/cvars.rs`) — so those two lines
-- erase a persisted setting at every world entry and untick this window's own
-- Friendly Name Plates row.
-- · **There is no volatile level here to replay into.** The reference clears
-- `[0xc4da34]` on every EnterWorld and this function restores it; `VPlateMode` is never
-- cleared, so the replay has nothing to restore. What it still owes is the other
-- direction — an addon that sets `NAMEPLATES_ON` and calls this expects plates to move
-- — which is why the body stays rather than becoming a no-op (1203's posture).
--
-- The globals it reads are kept true by the host (`vplates::push_plate_globals`), seeded
-- ahead of `VARIABLES_LOADED` on the load edge, so in the steady state every branch here
-- writes a value the CVar already holds and queues nothing at all.
function UpdateNameplates()
if ( NAMEPLATES_ON ) then
ShowNameplates()
else
HideNameplates()
end
if ( FRIENDNAMEPLATES_ON ) then
ShowFriendNameplates()
else
HideFriendNameplates()
end
end
-- The static category tree (era Game tab, Classic tocs), in list order. Keys are the row
-- name suffixes; the visible label doubles as the page title (the era does the same —
-- Settings.RegisterCategory names both from one string).
OPTIONS_CATEGORY_KEYS = {
"Controls", "Interface", "ActionBars", "Combat", "Chat", "Keybindings", "Nameplates",
"Graphics", "Audio",
}
-- The era-window scale (shared, director-owned — the 0219 WORLDMAP_WINDOWED_SCALE
-- pattern): the era client's UIParent draws smaller against the screen than our 1.12
-- 768-canvas does on modern displays, and this knob is that mapping — every era-shaped
-- window wears it (this window here; GameMenuFrame in its OnShow — in the era both draw
-- at the SAME UIParent px-per-unit, the parity the director's eye checked). At 1.0 the
-- 724-tall window fills ~85% of the screen's height in our 768-virtual space, where the
-- reference at typical resolutions reads nearer two-thirds.
ERA_WINDOW_SCALE = 0.78
function OptionsFrame_UpdateScale()
-- The era panel is registered `checkFit = 1`: the panel manager also shrinks it to
-- fit a small screen (UIPanelUpdateScaleForFit: min over width/height ratios
-- against the panel + 20 units of air each way, capped at 1) — re-expressed here.
local k = ERA_WINDOW_SCALE
local wFit = GetScreenWidth() / 940
local hFit = GetScreenHeight() / 744
if wFit < k then k = wFit end
if hFit < k then k = hFit end
OptionsFrame:SetScale(k)
end
-- The era tab width law (MinimalTab.lua l.7): label width + 40, measured ONCE per show —
-- the 1.12 CharacterFrameTabButtonTemplate's own seat (its <OnShow> calls
-- PanelTemplates_TabResize; decision 1993), and it has to be a show rather than the era's
-- OnLoad for a reason that outlives the measure: GetStringWidth answers in the REGION'S
-- OWN units, so its number depends on the owner's effective scale (glyph advances snap to
-- whole physical pixels, so the same label is not proportionally the same width at two
-- scales) — and this window's scale is set by OptionsFrame_UpdateScale in its own <OnShow>,
-- after every OnLoad has already run. Fired from the tabs' <OnShow>, which the engine
-- runs pre-order behind the window's own, the scale is the drawn one. No poll and no latch:
-- the engine's font measurer answers inside the Lua call that asked (benilla-ui
-- script/measure.rs), and a re-show re-fits at whatever scale the window then wears.
-- Deliberate divergence: the era measures with the MEDIUM font it is about to swap away
-- (an accident of its code order, ~6 units of extra padding); we measure the small font
-- the label actually wears.
function OptionsTab_FitWidth(tab)
tab:SetWidth(getglobal(tab:GetName() .. "Text"):GetStringWidth() + 40)
end
-- Each row's OnLoad: remember the key + title, paint the label, size the wash plate —
-- the era sized it via SetAtlas(name, true) at atlas size (187x21, centered: 6 units
-- in from either edge of the 199 row); with plain SetTexture the size is authored
-- here once — and set the 1.14 base tint: OptionsListButton_OnLoad's own
-- SetVertexColor(.196, .388, .8), the steel-blue every unselected row's hover wears
-- (OptionsFrameTemplates.lua l.130-131). The whole row identity lives here so adding
-- a category is one XML row + one key in the table above.
function OptionsCategoryRow_OnLoad(row, key, label)
row.categoryKey = key
row.pageTitle = label
row:SetText(label)
local bg = getglobal(row:GetName() .. "Bg")
bg:SetWidth(187)
bg:SetHeight(21)
bg:SetVertexColor(0.196, 0.388, 0.8)
end
-- The selection — 1.14's OptionsList_SelectButton/_ClearSelection pair, re-expressed
-- (OptionsFrameTemplates.lua l.107-121): the selected row's wash goes GOLD (1,1,0)
-- and locks on; every other row drops back to the blue hover tint, wash off. The
-- white selected label is LockHighlight's own text state, carried by the font swap.
-- Also the era law on top (SettingsPanelMixin:SelectCategory clears the search box —
-- Blizzard_SettingsPanel.lua l.793): picking a category ENDS a live search; the
-- SetText("") round-trips through OnTextChanged -> restore -> back here, so this call
-- returns early and lets the re-entry do the work once.
-- Then the page swap: a category with rows (OPTIONS_PAGE_ROWS) shows its body
-- refreshed from the CVars and Defaults goes live with it; every other page is still
-- empty, Defaults disabled.
function OptionsFrame_SelectCategory(key)
OptionsFrame.selectedCategory = key
if OptionsFrameSearchBox:GetText() ~= "" then
OptionsFrameSearchBox:SetText("")
return -- the OnTextChanged restore re-entered with this same key and finished
end
for _, k in ipairs(OPTIONS_CATEGORY_KEYS) do
local row = getglobal("OptionsFrameCategoryListRow" .. k)
local bg = getglobal("OptionsFrameCategoryListRow" .. k .. "Bg")
if k == key then
bg:SetVertexColor(1, 1, 0)
bg:Show()
row:SetTextFontObject("GameFontHighlight")
OptionsFrameContainerTitle:SetText(row.pageTitle)
else
bg:SetVertexColor(0.196, 0.388, 0.8)
bg:Hide()
row:SetTextFontObject("GameFontNormal")
end
end
for k in pairs(OPTIONS_PAGE_ROWS) do
local body = getglobal("OptionsFrameContainerBody" .. k)
if k == key then body:Show() else body:Hide() end
end
-- The Keybindings page (KeyBindingsPage.xml, decision 1008) body-swaps like the
-- CVar pages but runs its own machinery: entering rebuilds the section tree;
-- leaving disarms a straggling capture (the host seam must never outlive the page)
-- and clears its message line. Its Defaults is the confirmed Reset To Default (the
-- button's OnClick forks), and the Unbind Key button exists only here.
local kb = getglobal("OptionsFrameContainerBodyKeybindings")
if key == "Keybindings" then
kb:Show()
KeyBindingsPage_Refresh()
OptionsFrameContainerUnbind:Show()
OptionsFrameContainerDefaults:Enable()
else
kb:Hide()
KeyBindings_SetSelected(nil)
KeyBindings_Output("")
OptionsFrameContainerUnbind:Hide()
if OPTIONS_PAGE_ROWS[key] then
OptionsPage_Refresh(key)
OptionsFrameContainerDefaults:Enable()
else
OptionsFrameContainerDefaults:Disable()
end
end
end
-- ── Search (0984): the era SettingsPanel mechanism, re-expressed ────────────────────
-- Blizzard_SettingsPanel.lua l.566-692 + Blizzard_Settings.lua l.65-77, cut to our
-- static rows: a setting's search tag is its LABEL uppercased (the era's default tag
-- is exactly the setting name — SettingControls.lua l.773); a query matches by plain
-- substring, scored by matched-word length; matches group under their category, the
-- groups ordered by their best score; each group opens with a clickable category
-- header (SettingsListSearchCategoryTemplate — click = jump to the category, which
-- ends the search); no match shows the section-header "no results" line. The results
-- ARE the live rows — the era reflows the real initializers into a fresh vertical
-- layout, so ours re-anchor the real row frames (values, writes, dependencies all
-- work in place) and the restore path lays every page back down by the one layout
-- law below.
-- The era's word list (l.569-572): the WHOLE query first — a phrase match outranks
-- its own words — then every comma/space-separated token.
function OptionsSearch_Words(text)
local up = string.upper(text)
local words = { up }
for w in string.gfind(up, "([^, ]+)") do
table.insert(words, w)
end
return words
end
-- The era score (Blizzard_Settings.lua l.65-77 MatchesSearchTags): first word that
-- hits the tag scores last-first (matched length - 1); no hit is no match. Plain
-- find — the era passes plain=true, no pattern magic.
function OptionsSearch_Score(tag, words)
for _, w in ipairs(words) do
local first, last = string.find(tag, w, 1, true)
if first then return last - first end
end
return nil
end
-- The era gather+group (l.575-636): rows bucketed by score, scores walked high to
-- low; a category's group is born the first time one of its rows surfaces — so the
-- group ORDER is best-score order — and collects that category's matches as the walk
-- descends. Returns the ordered group list { key, rkeys } .. .
function OptionsSearch_FindMatches(text)
local words = OptionsSearch_Words(text)
local buckets, scores = {}, {}
for _, key in ipairs(OPTIONS_CATEGORY_KEYS) do
if OPTIONS_PAGE_ROWS[key] then
for _, rkey in ipairs(OPTIONS_PAGE_ROWS[key]) do
local row = getglobal("OptionsFrameContainerBody" .. key .. rkey)
local s = OptionsSearch_Score(row.searchTag, words)
if s then
if not buckets[s] then
buckets[s] = {}
table.insert(scores, s)
end
table.insert(buckets[s], { key = key, rkey = rkey })
end
end
end
end
-- The keybinding entries join the same buckets (the era's searchable fake category
-- whose results REDIRECT to the Keybindings page — KeyBindingsPage.xml's search
-- note). Their rkey is the BINDING INDEX; the display fork below tells them apart
-- by the group key.
for _, m in ipairs(KeyBindings_SearchMatches(words)) do
if not buckets[m.score] then
buckets[m.score] = {}
table.insert(scores, m.score)
end
table.insert(buckets[m.score], { key = "Keybindings", rkey = m.index })
end
table.sort(scores, function(a, b) return a > b end)
local groups, order = {}, {}
for _, s in ipairs(scores) do
for _, m in ipairs(buckets[s]) do
if not groups[m.key] then
groups[m.key] = {}
table.insert(order, m.key)
end
table.insert(groups[m.key], m.rkey)
end
end
return order, groups
end
-- ── The page scroll (B217) ──────────────────────────────────────────────────────────
-- The body is the scroll child (the ScrollFrame above it), so what these two do is
-- decide how TALL it is: the content's own extent, or the visible page rect, whichever
-- is larger. Below the rect nothing changes at all — range 0, no bar, every page seated
-- exactly where its XML puts it — and above it the overflow becomes a scroll instead of
-- a spill.
--
-- The measured set is every element that takes a seat in the ONE layout law's chain:
-- each page's rows, the search group heads, and the keybinding result pool. All of them
-- hang off the body's TOP, so their extent is independent of the height being computed —
-- which is what makes this converge. Nothing anchored to the body's BOTTOM may join them
-- (the Keybindings page's output line is the live example): growing the body would move
-- it down, which would grow the body. IsVisible, not IsShown: a hidden page's rows keep
-- their own shown flag, and only what the player can actually see may set the height.
function OptionsScroll_ContentBottom()
local bottom = nil
local function take(name)
local f = getglobal(name)
if f and f:IsVisible() then
local b = f:GetBottom()
if b and (not bottom or b < bottom) then bottom = b end
end
end
for _, key in ipairs(OPTIONS_CATEGORY_KEYS) do
if OPTIONS_PAGE_ROWS[key] then
take("OptionsFrameContainerBodySearchHead" .. key)
for _, rkey in ipairs(OPTIONS_PAGE_ROWS[key]) do
take("OptionsFrameContainerBody" .. key .. rkey)
end
end
end
take("OptionsFrameContainerBodySearchHeadKeybindings")
for i = 1, KEYBINDINGS_SEARCH_ROWS do
take("OptionsFrameContainerBodyKeybindSearch" .. i)
end
return bottom
end
-- Size the body to that, from the scroll frame's OnUpdate. Frame-late by nature, and for
-- a reason that has nothing to do with the text measure: RECTS resolve after Lua runs, so
-- a pass that reads GetHeight() to reflow answers off the previous resolve however it is
-- driven. Write-gated: SetWidth/SetHeight invalidate layout, so an every-frame write
-- would fight the resolver. UpdateScrollChildRect re-drives the bar through
-- OnScrollRangeChanged, which is what shows and hides it (and its trough).
function OptionsScroll_Fit()
local sf = OptionsFrameContainerScroll
local body = OptionsFrameContainerBody
local fw, fh = sf:GetWidth(), sf:GetHeight()
if not fw or fw <= 0 or not fh or fh <= 0 then return end
if math.abs(body:GetWidth() - fw) >= 0.5 then body:SetWidth(fw) end
local h = fh
local top, bottom = body:GetTop(), OptionsScroll_ContentBottom()
if top and bottom then
local content = (top - bottom) + BENILLA_SCROLL_PAD
if content > h then h = content end
end
if math.abs(body:GetHeight() - h) >= 0.5 then
body:SetHeight(h)
sf:UpdateScrollChildRect()
end
end
-- THE one layout law (era SettingsList: verticalPad 10, padLeft 25/padRight 0 →
-- first element at (10,-12) off the body, spacing 9 → chain at (0,-9)). The rows'
-- authored XML anchors state exactly this chain as the initial layout; search
-- reflows steal rows out of it, and this function is how ANY element — row or search
-- head — takes a seat in it. prev is a global frame NAME (nil = the chain head).
function OptionsSearch_Place(name, prev)
local f = getglobal(name)
f:ClearAllPoints()
if prev then
f:SetPoint("TOPLEFT", prev, "BOTTOMLEFT", 0, -9)
f:SetPoint("TOPRIGHT", prev, "BOTTOMRIGHT", 0, -9)
else
f:SetPoint("TOPLEFT", "OptionsFrameContainerBody", "TOPLEFT", 10, -12)
f:SetPoint("TOPRIGHT", "OptionsFrameContainerBody", "TOPRIGHT", -20, -12)
end
end
-- Lay a page's rows back down on their authored chain (the XML anchors' law,
-- re-stated after a search reflow; the pin test holds the two equal).
function OptionsPage_Layout(key)
local prev = nil
for _, rkey in ipairs(OPTIONS_PAGE_ROWS[key]) do
local name = "OptionsFrameContainerBody" .. key .. rkey
OptionsSearch_Place(name, prev)
getglobal(name):Show()
prev = name
end
end
-- The results view (era OnSearchTextChanged's hasText arm, l.659-691): every page
-- body SHOWS (they are bare row hosts sharing one rect) with every row hidden, then
-- the groups chain down — category head, then its matches; a matched CHILD row pulls
-- its parent row in first (l.665-670, the era parentInitializer rule — our Audio
-- volumes ride under Master). Title reads "Search Results", Defaults hides while the
-- search holds the page (l.684-688), and an empty result shows the no-results line
-- (l.680-682).
function OptionsSearch_Display(text)
-- A new result set starts at the top (the era's own ScrollBox reset on a search).
OptionsFrameContainerScroll:SetVerticalScroll(0)
for k in pairs(OPTIONS_PAGE_ROWS) do
getglobal("OptionsFrameContainerBody" .. k):Show()
for _, rkey in ipairs(OPTIONS_PAGE_ROWS[k]) do
getglobal("OptionsFrameContainerBody" .. k .. rkey):Hide()
end
getglobal("OptionsFrameContainerBodySearchHead" .. k):Hide()
end
-- The Keybindings furniture leaves the stage with the rest: its body hides (the
-- search paints matched bindings on the shared pool below, not the page list), a
-- straggling capture disarms, and the pool starts blank.
getglobal("OptionsFrameContainerBodyKeybindings"):Hide()
getglobal("OptionsFrameContainerBodySearchHeadKeybindings"):Hide()
for i = 1, KEYBINDINGS_SEARCH_ROWS do
getglobal("OptionsFrameContainerBodyKeybindSearch" .. i):Hide()
end
KeyBindingsPage.searchPainted = nil
KeyBindings_SetSelected(nil)
OptionsFrameContainerUnbind:Hide()
OptionsFrameContainerTitle:SetText("Search Results")
OptionsFrameContainerDefaults:Hide()
local order, groups = OptionsSearch_FindMatches(text)
if table.getn(order) == 0 then
OptionsFrameContainerBodyNoResults:Show()
return
end
OptionsFrameContainerBodyNoResults:Hide()
local prev = nil
for _, key in ipairs(order) do
local head = "OptionsFrameContainerBodySearchHead" .. key
OptionsSearch_Place(head, prev)
getglobal(head):Show()
prev = head
if key == "Keybindings" then
-- Matched bindings painted LIVE onto the static pool (the era reflows its
-- real rows the same way; the pool caps the count — the SCOPE note in
-- KeyBindingsPage.xml — and the head above jumps to the full page).
KeyBindingsPage.searchPainted = {}
local shown = 0
for _, index in ipairs(groups[key]) do
if shown < KEYBINDINGS_SEARCH_ROWS then
shown = shown + 1
local name = "OptionsFrameContainerBodyKeybindSearch" .. shown
KeyBindings_PaintBindRow(name, index)
OptionsSearch_Place(name, prev)
getglobal(name):Show()
KeyBindingsPage.searchPainted[name] = index
prev = name
end
end
else
local placed = {}
for _, rkey in ipairs(groups[key]) do
local row = getglobal("OptionsFrameContainerBody" .. key .. rkey)
if row.parentRow and not placed[row.parentRow] then
placed[row.parentRow] = 1
local pname = "OptionsFrameContainerBody" .. key .. row.parentRow
OptionsSearch_Place(pname, prev)
getglobal(pname):Show()
prev = pname
end
if not placed[rkey] then
placed[rkey] = 1
local name = "OptionsFrameContainerBody" .. key .. rkey
OptionsSearch_Place(name, prev)
row:Show()
prev = name
end
end
OptionsPage_Refresh(key)
end
end
end
-- Ending a search: every page back on its authored chain, heads away, and the
-- selection re-applied — SelectCategory re-shows the one body, retitles, and brings
-- Defaults back (the era's no-text arm, l.655-658).
function OptionsSearch_Restore()
OptionsFrameContainerScroll:SetVerticalScroll(0)
for k in pairs(OPTIONS_PAGE_ROWS) do
OptionsPage_Layout(k)
getglobal("OptionsFrameContainerBodySearchHead" .. k):Hide()
end
for i = 1, KEYBINDINGS_SEARCH_ROWS do
getglobal("OptionsFrameContainerBodyKeybindSearch" .. i):Hide()
end
getglobal("OptionsFrameContainerBodySearchHeadKeybindings"):Hide()
KeyBindingsPage.searchPainted = nil
OptionsFrameContainerBodyNoResults:Hide()
OptionsFrameContainerDefaults:Show()
OptionsFrame_SelectCategory(OptionsFrame.selectedCategory or "Controls")
end
-- The text gate (era OnSearchTextChanged l.640-652): a repeat of the same text is a
-- no-op, and the FIRST change (searchText still nil — load-time initialization)
-- arms without acting; text clearing restores only if a search was actually live.
function OptionsSearch_OnTextChanged(text)
if text == OptionsFrame.searchText then return end
local had = OptionsFrame.searchText and OptionsFrame.searchText ~= ""
OptionsFrame.searchText = text
if text == "" then
if had then OptionsSearch_Restore() end
else
OptionsSearch_Display(text)
end
end
-- ── The setting pages: 1.12 rows in the era Settings grammar ────────────────────────
-- One machinery, every page (Audio 0957, the first). Semantics (rows, CVars, labels,
-- dependencies, the click-sound quirk) are the 1.12 client's own panels, quoted
-- verbatim (SoundOptionsFrame.lua / OptionsFrame.lua + GlobalStrings.lua); geometry is
-- the era settings list (Blizzard_SettingControls.xml/.lua + Blizzard_SettingsList.lua
-- @1.15.9 — 26-tall rows on a 9 spacing, label LEFT+37 → CENTER-85, control at
-- CENTER-80, child rows +15 indent in the small font). Era rows with no 1.12 CVar +
-- benilla knob behind them (Enable Sound=Sound_EnableAllSound, Dialog, output device,
-- channels…) do not ship — the 0950 honest-tree rule applied to settings.
-- The options strings that are NOT the reference's (1639, 1650). Every other
-- tooltip on this window resolves an OPTION_TOOLTIP_* out of 1.12's GlobalStrings, and a
-- benilla row with no counterpart there has so far simply gone mute (the nameplate pair).
-- Render Scale cannot afford to: the director's own reaction to the dial was "not really
-- sure what this does". So it gets a description, under a BENILLA_ prefix rather than an
-- invented OPTION_TOOLTIP_ -- the reference's namespace stays the reference's, and an
-- addon reading GlobalStrings still finds exactly what 1.12 defined.
BENILLA_TOOLTIP_RENDER_SCALE =
"Draws the 3D world at a fraction of the window's resolution, or larger than it. "
.. "The interface is unaffected at any setting.\n\n"
.. "Below 100% trades sharpness for frame rate; above 100% is supersampling, which "
.. "smooths edges at the cost of frame rate. Applies when you click Apply."
-- Enable Sound in Background (1847). An era row: 1.12 hardcodes the behaviour and has no
-- checkbox, so there is no OPTION_TOOLTIP_* to resolve and the BENILLA_ prefix applies
-- for the same reason Render Scale's does -- the reference's namespace stays the
-- reference's. The last line is not padding: mute is not pause on either client, and a
-- player who expects a paused fight to resume where it stopped is owed the difference.
BENILLA_TOOLTIP_BACKGROUND_SOUND =
"Keeps the game's sound playing while you are working in another window.\n\n"
.. "With this off, switching away silences the game and switching back restores it. "
.. "Sounds keep running while they are silenced, so a long one may have finished by "
.. "the time you return."
-- Display Mode (1650). 1.12's own OPTION_TOOLTIP_WINDOWED_MODE cannot ride this row: it
-- is written for the checkbox it belonged to ("Check to play in a non-fullscreen
-- window..."), and its second half describes a desktop-gamma consequence of an exclusive
-- mode-set we do not do. The row also has to answer the question a player arrives with —
-- "where is Fullscreen?" — which no reference string was ever written to answer.
BENILLA_TOOLTIP_DISPLAY_MODE =
"Windowed (Fullscreen) fills the screen with a borderless window; Windowed runs in "
.. "an ordinary one.\n\n"
.. "There is no exclusive fullscreen mode. WoW itself removed one in 8.0.1, and "
.. "modern Classic offers these same two."
OPTIONS_PAGE_ROWS = {
Controls = {
"RowStickyTarget", "RowBlockTrades", "RowAutoLoot", "RowInvertMouse",
"RowMouseSpeed", "RowCameraFollowStyle", "RowMaxCameraDistance",
},
Audio = {
"RowEnableAll", "RowMaster", "RowSound", "RowMusic", "RowAmbience",
"RowEnableMusic", "RowEnableAmbience", "RowEnableErrorSpeech",
"RowBackgroundSound",
},
Graphics = {
"RowUiScale", "RowFarclip", "RowWorldDetail",
"RowDisplayMode", "RowVerticalSync", "RowRenderScale",
},
Nameplates = {
"RowEnemyPlates", "RowFriendlyPlates",
"RowPlayerNames", "RowNpcNames", "RowOwnName",
},
-- The stock globals that were already defined AND consumed when 1134 opened the store
-- (1136; Buff Durations 1139), plus the two equipment-display toggles (1472), in the
-- reference's own on-screen order. Its Basic Options Display box is three COLUMNS read
-- left to right: A gives Status Text (CheckButton2), B gives Show Cloak → Show Helm
-- (31 → 20, sitting under the name toggles that live on our Nameplates page), C gives
-- the 42 → 39 → 66 chain. The **Help** box closes the page, and since 2077 it is
-- the box's own order rather than its one shippable row: 28 (Show Tutorials), 44
-- (Show Loading Screen Tips, anchored under 28 there) then 29 (Show Detailed
-- Tooltips). 28 was left out by 1140's census as an "index-only entry
-- with no store at all"; that reading missed the three special-case arms
-- `UIOptionsFrame_Load`/`_Save`/`_SetDefaults` keep for the key, which are its store.
-- The target-of-target pair (decision 1576) breaks that left-to-right reading, and
-- it has to: 1.12 keeps it in a box this window has no page for — Advanced Options'
-- "Raid & Party" — so there is no column order to preserve, only a neighbour to pick.
-- It sits under Status Text because that is the other unit-frame readout on the page.
Interface = {
"RowStatusText", "RowTargetOfTarget", "RowTargetOfTargetMode",
"RowShowCloak", "RowShowHelm",
"RowInstantQuestText", "RowBuffDurations", "RowAutoQuestWatch",
"RowProfanityFilter",
"RowShowTutorials", "RowTipOfTheDay", "RowNewbieTips",
},
-- 1.12's Advanced Options **Chat** box (decision 1589, B246 — "no chat section in
-- options"). The page was called `Social` and held exactly the two chat-bubble
-- switches 1139 put on it; it is the reference's own `CHAT_LABEL` box now, and grew
-- into it. Order is the reference's own anchor chain (UIOptionsFrame.xml l.751-840:
-- CheckButton 7 → 8 → 13 → 18 → 37 → 46 → 38 → 51 → 68), with the rows we have no
-- mechanism for skipped rather than stubbed:
-- · 7 Simple Chat — needs FCF_Set_SimpleChat's re-layout (0288 §2)
-- · 8 Lock Chat Settings — the ONE row here whose machinery now exists: 1594
-- built the move, the resize and the blanket `CHAT_LOCKED` switch this
-- checkbox drives (`FCF_Set_ChatLocked`/`FCF_Get_ChatLocked`, gating both
-- verbs). It is unbuilt because nobody has asked for it, not because it
-- would be inert — and the per-window lock the tab menu carries is the one
-- a player actually reaches for. Ships when it is wanted
-- · 51 Auto-join Guild Recruitment Channel — **this note used to say the row is
-- inert in the reference too, and that was wrong** (corrected by 2115; it is
-- exactly the mistake 2077 corrected for SHOW_TUTORIALS). The table entry is a
-- bare `{ index = 51 }`, but the store is not in the TABLE — it is in the three
-- special-case arms `UIOptionsFrame_Load` (l.243-248, `GetGuildRecruitmentMode`),
-- `_Save` (l.326-331, `SetGuildRecruitmentMode`) and `_SetDefaults` (l.649) keep
-- for the key. Both verbs are ours now (2115), over the chat cache's
-- `OPTION_GUILD_RECRUITMENT_CHANNEL` latch. The row is unshipped here because
-- nobody has asked for it, not because it would be inert
-- Row 68, Disable Spam Filter, **shipped by 2077** — and the note it replaces was
-- wrong twice over, both corrected at the bytes by wow-re's `text-filter-law.md`.
-- The filter is not a word list, it is 28 compiled PCRE patterns from
-- `SpamMessages.dbc`; and it does not drop `CHAT_MSG_FILTERED` lines, it is armed for
-- every type EXCEPT that one (`0x49aad2`'s `jne` skips the disarm, which the older
-- note read backwards). It drops a matching line silently, with nothing in its place.
-- 1139's note about the 37 → 46 → 38 gap is closed by this record: 46 is Loot Spam,
-- and it has a knob now.
Chat = {
"RowGuildMemberAlert", "RowRemoveChatDelay", "RowChatBubbles", "RowLootSpam",
"RowPartyChatBubbles", "RowSpamFilter",
},
-- 1.12's Advanced Options "Action Bars" group, whole (decision 1500 completing 1136).
-- Its six rows, the four bar toggles then the grid switch then the lock. That is the
-- DIRECTOR's order, not the reference's: 1.12's box is two columns and leads with the
-- lock (CheckButton32 at its TOPLEFT, then 33/40/34 down the left, 35/36 down the
-- right), which does not survive being flattened into one column — the five switches
-- that decide what is on screen belong above the one that decides whether it can be
-- dragged. 1136 could ship only the lock, because the other five had
-- nothing behind them: the two bottom bars were unconditionally on and the two vertical
-- ones unconditionally hidden (0270/1219). 1500 made all four bars options, which is
-- what gave the five rows a store to read.
--
-- Two DIFFERENT stores on one page, and the split is verified rather than chosen: the
-- four bar rows are `func` rows over the server's own PLAYER_FIELD_BYTES byte 2 (no
-- local store exists to save), while Always Show ActionBars is a `uvar` row — the
-- engine binding takes exactly four arguments and silently drops the fifth the
-- reference passes it, so that switch has no server side at all. Decision 1500 and
-- wow-re action-bar-toggles.md carry the byte-level evidence.
ActionBars = {
"RowMultiBar1", "RowMultiBar2", "RowMultiBar3", "RowMultiBar4",
"RowAlwaysShowMultibars", "RowLockActionBar",
},
-- The floating-combat-text family (decision 1134), in the reference's own on-screen
-- order — its AdvancedOptionsCombatText box's anchor chain, master → scroll mode →
-- the twelve, NOT its check-button index order (which jumps 58→60→63…59→69).
Combat = {
"RowCombatText", "RowFloatMode", "RowLowHealthMana", "RowAuras", "RowAuraFade",
"RowCombatState", "RowDodgeParryMiss", "RowResistances", "RowReputation",
"RowReactives", "RowFriendlyNames", "RowComboPoints", "RowEnergyGains",
"RowHonorGained",
},
}
-- `tip` is the GlobalStrings KEY of the row's description (B223, decision 1054) — the key,
-- not the text: it resolves at HOVER, the way the 1.12 Interface panel resolves its own
-- (UIOptionsFrame.lua l.265, `getglobal("OPTION_TOOLTIP_"..key)`), so the row is wired
-- before GlobalStrings has to exist. nil = no 1.12 string for this setting (Auto Loot).
function OptionsRow_OnLoad(row, cvar, label, tip, child)
row.cvar = cvar
row.tip = tip
-- The search tag: the era's default is exactly the setting's display name
-- (SettingControls.lua l.773 AddSearchTags(name)), uppercased at registration
-- (Blizzard_Settings.lua l.60).
row.searchTag = string.upper(label)
-- Kept, not just applied: the disabled-state font (OptionsRow_SetEnabled) has to pick
-- the SAME size the indent chose, or greying a child row also promotes it.
row.child = child
local l = getglobal(row:GetName() .. "Label")
l:SetText(label)
if child then
-- era child rows: +15 label indent and the small font
-- (SettingControls.lua l.319-322); the control itself does not indent.
l:SetFontObject("GameFontNormalSmall")
l:ClearAllPoints()
l:SetPoint("LEFT", row:GetName(), "LEFT", 52, 0)
l:SetPoint("RIGHT", row:GetName(), "CENTER", -85, 0)
end
end
-- ── The row's STORE: a CVar, a saved-variable global, or an API pair (1134, 1472) ──
-- 1.12's own Interface panel has THREE stores, not one: each UIOptionsFrameCheckButtons
-- entry carries `cvar = "name"` (an engine knob), `uvar = "GLOBAL"` (a RegisterForSave'd
-- Lua global), or a `func`/`setFunc` pair (an engine getter/setter with no client-side
-- store at all) — and UIOptionsFrame_Load/_Save fork on exactly that field
-- (UIOptionsFrame.lua l.230-377; the func fork at l.237-238 and l.296-297). Values are
-- strings in all three — "1"/"0" for a toggle — so only the accessor differs, and every row
-- in this file reads and writes through this trio instead of calling Get/SetCVar directly.
--
-- The third kind is what Show Helm / Show Cloak are: the preference is a bit of the
-- character's own server-side PLAYER_FLAGS, so there is nothing local to save and nothing
-- to restore at load — the getter IS the state (decision 1472).
function OptionsRow_Get(row)
if row.uvar then return getglobal(row.uvar) end
if row.getFunc then
if row.getFunc() then return "1" else return "0" end
end
return GetCVar(row.cvar)
end
-- The write. A global's consumers are Lua and do not poll, so a uvar row also runs its
-- `applyFunc` — the reference's own per-uvar side effects (SIMPLE_CHAT → FCF_Set_*,
-- HIDE_PARTY_INTERFACE → RaidOptionsFrame_UpdatePartyFrames, the combat-text family →
-- CombatText_UpdateDisplayedMessages), moved from its batched Okay to our live write
-- because this window commits on the click (0961's grammar), not on a Save button.
-- `cvarEvent` is a CVar row's counterpart to a global row's `applyFunc`, and it is the
-- reference's own third SetCVar argument (decision 1140): the token the engine hands back
-- as `CVAR_UPDATE`'s arg1. 1.12 passes its CheckButtons table KEY there
-- (`SetCVar(value.cvar, value.value, index)`), so a consumer that wants to react the
-- INSTANT the switch moves — rather than on its next repaint — has something to listen
-- for. nil on a row nobody listens to, which is most of them: the host polls its knob.
function OptionsRow_Set(row, value)
if row.uvar then
setglobal(row.uvar, value)
-- `applyFunc` is a global's NAME: the rows are read at this file's load, which since
-- 1938 sits at the reference's slot — before every file an apply lives in — so the
-- function is looked up when the row is applied, not when it is declared. A name
-- with no global behind it (a page harness without that definer) applies nothing,
-- which is what a nil capture always did.
local apply = row.applyFunc
if type(apply) == "string" then apply = getglobal(apply) end
if apply then apply() end
return
end
-- An API row hands the setter the row's own "1"/"0" STRING, which is what 1.12 does
-- (UIOptionsFrame_Save l.290-297 builds value.value as a string and passes it
-- straight through) — so the engine binding reads its argument numerically and "0"
-- means off. No applyFunc: the setter IS the side effect.
if row.setFunc then
row.setFunc(value)
return
end
SetCVar(row.cvar, value, row.cvarEvent)
end
-- The registered default — GetCVarDefault's counterpart for a global. `udefault` is the
-- value the defining XML assigned at FILE SCOPE, captured at this row's OnLoad: the whole
-- UI's XML loads before the saved-variables chunk executes over it (decision 1128's load
-- order), so at OnLoad the global still holds its shipped default. That makes the
-- assignment the single source of the default — the panel restates nothing and so cannot
-- drift from it, where the reference's table carries a second hand-written copy.
function OptionsRow_Default(row)
if row.uvar then return row.udefault end
if row.getFunc then return row.fdefault end
return GetCVarDefault(row.cvar)
end
-- A row whose store is a saved-variable global rather than a CVar. Same signature as
-- OptionsRow_OnLoad with the global's name in the cvar seat; the caller adds `applyFunc`
-- afterwards when the setting needs one.
function OptionsGlobalRow_OnLoad(row, uvar, label, tip, child)
OptionsRow_OnLoad(row, nil, label, tip, child)
row.uvar = uvar
row.udefault = getglobal(uvar)
end
-- A row with no store: an engine getter/setter pair, 1.12's own `func`/`setFunc` shape
-- (decision 1472). `default` has to be stated because there is no registered default to
-- ask for — the reference states it the same way, as a hand-written arm of
-- UIOptionsFrame_SetDefaults (l.631-634, SHOW_CLOAK and SHOW_HELM both SetChecked(1)).
function OptionsFuncRow_OnLoad(row, getFunc, setFunc, default, label, tip, child)
OptionsRow_OnLoad(row, nil, label, tip, child)
row.getFunc = getFunc
row.setFunc = setFunc
row.fdefault = default
end
-- ── THE LOAD-TIME LADDER IS THE REFERENCE'S OWN AGAIN (decision 2115) ──────────────
--
-- The problem it solves is real and unchanged (decision 1139): the whole UI's XML runs
-- BEFORE the saved chunk executes over it (1128's load order), so every file-scope
-- consumer of a `RegisterForSave`'d global already ran against the SHIPPED default, and
-- the saved value landing afterwards changes the variable while leaving the world it
-- configured alone. A CVar row needs nothing — the host reads its own table at boot.
--
-- 1.12 closes it with a hand-written ladder in `UIOptionsFrame_OnEvent`'s VARIABLES_LOADED
-- arm (UIOptionsFrame.lua l.193-227): `BuffButtons_UpdatePositions`, the SIMPLE_CHAT and
-- CHAT_LOCKED arms, `SetChatMouseOverDelay(REMOVE_CHAT_DELAY)`,
-- `MultiActionBar_ShowAllGrids`, `RaidOptionsFrame_UpdatePartyFrames`, and
-- `UIParentLoadAddOn("Blizzard_CombatText")`. **That arm runs here now**, off the player's
-- chain, because the stock file is loaded (hidden) at the manifest's l.21 seat — so this
-- window used to re-express a ladder that did not exist and now would be a SECOND copy of
-- one that does.
--
-- So the walk is gone, and with it `OptionsFrame_ApplySavedSettings`, this window's
-- VARIABLES_LOADED registration, and the multibar grid row's `loadFunc`. What the walk did
-- that the stock arm does not, checked one by one rather than assumed:
--
-- · `BuffButtons_UpdatePositions`, `SetChatMouseOverDelay`, `MultiActionBar_ShowAllGrids`
-- and `UIParentLoadAddOn("Blizzard_CombatText")` — the stock arm does all four, and it
-- runs FIRST (its frame registers at the l.21 seat, ours would register 30 rows later).
-- · `CombatText_UpdateDisplayedMessages` — the stock arm does not call it, and does not
-- need to: `Blizzard_CombatText`'s own `CombatText_OnLoad` calls it as its first act
-- (Blizzard_CombatText.lua l.81-84), and the addon loads INSIDE the stock arm, by
-- which time the saved uvars are already in place.
-- · `TargetofTarget_Update` — the stock arm does not call it either, and at
-- VARIABLES_LOADED there is no target, so `TargetFrame.lua:504`'s guard takes the else
-- branch and hides a frame that is already hidden. The row keeps it as its `applyFunc`,
-- which is the click behaviour and the only thing it was ever needed for.
--
-- The property the walk carried that still matters is `OptionsRow_Set`'s, and it stays
-- there: an `applyFunc` is a NAME, resolved when the row is applied, so a row whose
-- defining file a test harness never loaded applies nothing instead of raising.
-- A slider row's own range and readout, when the template's 0..1 / 5% / percent (the
-- volume shape) isn't it — called after OptionsRow_OnLoad in the row's OnLoad. The
-- refreshing guard: SetMinMaxValues re-clamps the held value and fires OnValueChanged,
-- and that clamp is wiring, not a player gesture — without the guard every load would
-- SetCVar the template's 0 clamped into the new range.
-- ── OptionsFrameSliders — ref OptionsFrame.lua l.24-34 ──────────────────────────────
-- The reference's options-slider table: one row per graphics slider, carrying the bounds
-- the slider is built with. It is PUBLISHED, so addons read and write it — pfUI's
-- `hdgraphic` module opens with `_G.OptionsFrameSliders[3].maxValue = 15`
-- (modules/hdgraphic.lua l.3) and died on the nil, taking the module with it.
--
-- **It is the real thing, not a shape put here to satisfy that write.** The three rows
-- below are the source of truth for our own Graphics page: each row's OnLoad reads its
-- bounds from here rather than carrying literals, and the page re-applies them on every
-- OnShow, which is what makes an addon's write actually move the slider. A table nothing
-- read would be a name with no behaviour behind it (1134 §4) — worse than absent, because
-- the write would appear to succeed.
--
-- **Three rows, not the reference's nine.** Ours are its rows 1-3 in its own order, and the
-- `func` strings match our CVars exactly (uiscale/farclip/WorldDetail) — which is why
-- pfUI's index 3 lands on Environment Detail here as it does there. The other six
-- (TerrainMip, BaseMip, Gamma, anisotropic, spellEffectLevel, weatherDensity) have no
-- benilla knob behind them; listing them would publish six rows whose writes do nothing.
-- 0959/1649 own which knobs this page ships, and this table follows that, not the reverse.
--
-- `benillaRow` is ours: the reference addresses its sliders as OptionsFrameSlider1..9, a
-- numbering our page does not use, so each row names the child it drives instead.
--
-- NOT bound, and named rather than faked: pfUI's module goes on to hook `SetWorldDetail`
-- (unbound) and drive `ConsoleExec("frillDensity ...")`, `lodDist`, `nearClip`, `maxLOD`.
-- `ConsoleExec` itself IS bound (1948 — it writes a registered CVar or pushes a console
-- command the app dispatches), but none of those four CVars is registered, so those
-- writes reach nothing. Neither verb is called at load, so the module loads and its
-- slider write lands; its EXTENDED arm cannot work here, and registering a CVar nothing
-- reads would be exactly the silent pretence 1203 exists to forbid.
OptionsFrameSliders = {
{ text = "UI Scale", func = "uiscale",
minValue = 0.64, maxValue = 1.0, valueStep = 0.01, benillaRow = "RowUiScale" },
{ text = "Terrain Distance", func = "farclip",
minValue = 177, maxValue = 777, valueStep = 60, benillaRow = "RowFarclip" },
{ text = "Environment Detail", func = "WorldDetail",
minValue = 0, maxValue = 2, valueStep = 1, benillaRow = "RowWorldDetail" },
}
function OptionsSliderRow_Setup(row, min, max, step, fmt, stops)
local slider = getglobal(row:GetName() .. "ControlSlider")
row.fmt = fmt
-- A grid that IS a short named ladder passes its names here (Environment Detail's
-- Low/Medium/High over 0/1/2): the groove is still the reference's slider, and the
-- readout says the stop rather than its index. 1.12's own slider printed no value at
-- all — it labelled the two ENDS — but every slider row in this kit carries a readout,
-- and "1" is the one thing that seat could say that means nothing to a player.
row.stops = stops
row.refreshing = 1
slider:SetMinMaxValues(min, max)
slider:SetValueStep(step)
row.refreshing = nil
end
-- ── The dropdown rows (0992): the 1.12 kit under the era row grammar ────────────────
-- A dropdown row's choices: entries = { {text=..., value=...}, ... }, values the CVar's
-- own strings. The kit does the rest: every open re-runs the init through the stored
-- initialize (Toggle's own law), and AddButton auto-checks the entry matching the
-- capsule's selectedValue — which OptionsDropdown_ShowValue keeps equal to the CVar.
function OptionsDropdownRow_Setup(row, entries)
row.entries = entries
local dd = getglobal(row:GetName() .. "Dropdown")
UIDropDownMenu_SetWidth(180, dd)
UIDropDownMenu_Initialize(dd, OptionsDropdownRow_InitMenu)
-- The capsule's toggle button is a mouse-enabled child — it steals hover from the
-- row, so it carries the row wash like the checkbox rows' box does. Hooked here
-- (SetScript) because the capsule template is the shared kit's, not this file's.
local btn = getglobal(dd:GetName() .. "Button")
btn:SetScript("OnEnter", function() OptionsRow_Hover(this:GetParent():GetParent(), 1) end)
btn:SetScript("OnLeave", function() OptionsRow_Hover(this:GetParent():GetParent(), nil) end)
end
function OptionsDropdownRow_InitMenu()
local dd = getglobal(UIDROPDOWNMENU_INIT_MENU)
local row = dd:GetParent()
for _, e in ipairs(row.entries) do
local info = {}
info.text = e.text
info.value = e.value
info.func = OptionsDropdownEntry_OnClick
UIDropDownMenu_AddButton(info)
end
end
-- The entry click (`this` = the list row, the kit's own calling law): the write, then
-- the capsule redisplay. The kit closes the list and plays its own click sound.
function OptionsDropdownEntry_OnClick()
local dd = UIDropDownMenu_GetCurrentDropDown()
local row = dd:GetParent()
OptionsRow_Set(row, this.value)
OptionsDropdown_ShowValue(row)
end
-- Paint the capsule from the CVar. selectedValue is written as a FIELD, not through
-- SetSelectedValue: that helper's Refresh walks the SHARED DropDownList1 buttons, which
-- may still hold another dropdown's rows on this refresh path (the list is closed) —
-- a foreign row matching by value would repaint our capsule with its text. An off-grid
-- CVar value (an env A/B, a hand edit) displays the NEAREST stop — 0959's out-of-range
-- law, dropdown-flavored: control and check agree on the nearest expressible value,
-- the engine keeps running the config's truth, nothing writes back until the player
-- actually picks an entry. (A non-numeric stray shows raw and checks nothing.)
function OptionsDropdown_ShowValue(row)
local dd = getglobal(row:GetName() .. "Dropdown")
local v = OptionsRow_Get(row)
local match
for _, e in ipairs(row.entries) do
-- `alt` is a second value the same entry ANSWERS to (1502): 1.12's own Camera
-- Following Style dropdown writes "3" for Never where the engine's tables are
-- indexed 0/1/2, so a config or addon carrying that value means Never — and the
-- nearest-stop fallback below, which assumes the values are ordinal, would show
-- "Always" for it. A categorical dropdown says which strays are its own.
-- `e.alt ~= nil` guards the nil-eats-everything case: a row whose store is not
-- there yet reads nil, and every entry without an `alt` would otherwise ANSWER to
-- it (nil == nil), leaving the capsule showing the last entry and the row wearing
-- its description. An entry answers to a second value only if it has one.
if e.value == v or (e.alt ~= nil and e.alt == v) then match = e end
end
if not match and tonumber(v) then
local best
for _, e in ipairs(row.entries) do
local d = tonumber(e.value) and math.abs(tonumber(e.value) - tonumber(v))
if d and (not best or d < best) then
best = d
match = e
end
end
end
if match then
dd.selectedValue = match.value
UIDropDownMenu_SetText(match.text, dd)
-- A dropdown whose ENTRIES each carry their own description repaints the row's
-- plate as the selection moves — the reference's own shape for exactly this row
-- (UIOptionsFrameCameraDropDown_OnClick: `tooltip = getglobal("OPTION_TOOLTIP_
-- CAMERA"..GetSelectedID())`). Rows whose entries carry no `tip` keep the single
-- key OptionsRow_OnLoad gave them.
if match.tip then row.tip = match.tip end
else
dd.selectedValue = v
UIDropDownMenu_SetText(v, dd)
end
end
-- The right-hand readout text (era MinimalSlider's RIGHT label seat): rounded percent
-- (era FormatPercentage, Audio.lua l.406-411) unless the row asked for the raw number.
function OptionsRow_FormatValue(row, value)
if row.fmt == "raw" then
return string.format("%d", math.floor(value + 0.5))
end
-- A named ladder reads out its stop's name (see OptionsSliderRow_Setup). The index is
-- clamped, not wrapped: the slider itself has already clamped a config value from
-- outside the range, so this only ever guards a stops table shorter than the grid.
if row.stops then
local i = math.floor(value + 0.5) + 1
if i < 1 then i = 1 end
if i > table.getn(row.stops) then i = table.getn(row.stops) end
return row.stops[i]
end
-- A row whose stored value is a FACTOR but whose meaning is a distance: show the
-- distance (decision 1140 — cameraDistanceMaxFactor over the 15 yd base). The number
-- the player is choosing is "how far out the wheel stops", not "times 1.4".
if row.fmt == "yards" then
return string.format("%d yd", math.floor(value * 15 + 0.5))
end
return string.format("%d%%", math.floor(value * 100 + 0.5))
end
-- The era row-wide hover wash (HoverBackgroundTemplate, white a=0.1): the row and every
-- control on it show the SAME texture, so crossing from label to slider doesn't blink.
-- ONE seam carries the wash AND the plate (B223): every surface that lights the row also
-- raises its tooltip, so the two can never disagree — and the crossing is still seamless,
-- since the engine fires OnLeave before OnEnter within a move (pointer.rs), leaving the
-- hide/re-show inside a single frame.
function OptionsRow_Hover(row, on)
local hover = getglobal(row:GetName() .. "Hover")
if on then
hover:Show()
OptionsRow_ShowTooltip(row)
else
hover:Hide()
GameTooltip:Hide()
end
end
-- The row's descriptive plate (B223, decision 1054).
-- STRING: 1.12's own per-option text, by GlobalStrings key (see OptionsRow_OnLoad). A row
-- with no 1.12 string raises NOTHING — a plate whose only line echoes the label the cursor
-- is on is noise, and inventing a description would be inventing fidelity.
-- SHAPE: the 1.12/1.14 option-row plate — the description alone, wrapped, in the tooltip's
-- default gold (OptionsFrameTemplates.xml l.52-64: SetOwner + SetText(text, …, 1)); this
-- window already wears it on the Keybindings page's Character Specific row (1008). The era
-- pair (a white name line over the description) is cut — see the SCOPE note.
-- SEAT: the era's (DefaultTooltipMixin.OnEnter — ANCHOR_RIGHT, x -10, off the row's LABEL
-- half), carried by the $parentTip region, so the plate opens at the same column on every
-- row whichever surface the cursor crossed.
function OptionsRow_ShowTooltip(row)
local text = row.tip and getglobal(row.tip)
if text then
GameTooltip:SetOwner(getglobal(row:GetName() .. "Tip"), "ANCHOR_RIGHT", -10, 0)
GameTooltip:SetText(text, nil, nil, nil, nil, 1)
else
-- A mute row PUTS AWAY whatever the last row raised. Entering a row asserts "this
-- row's plate is what shows", so an early return isn't enough: the ordinary cursor
-- path is saved by the outgoing row's own OnLeave, but any caller that enters
-- without one (a driven hover, a re-layout under the cursor) would leave the
-- neighbour's description standing over a row it doesn't describe — a plate saying
-- the wrong thing is worse than a row saying nothing. 1.12's own handler has the
-- bare early-out; this is deliberate, and it is why the mute case has a test.
GameTooltip:Hide()
end
end
function OptionsCheckbox_OnClick(box)
local row = box:GetParent()
local checked = box:GetChecked()
-- Click kits: the 1.12 panels disagree with EACH OTHER — the interface panel's
-- PlayClickSound maps checked→CheckBoxOn (OptionsFrame.lua l.509), while the sound
-- panel's own OnClick maps checked→CheckBoxOff (SoundOptionsFrame.lua, the 0957
-- quirk) — so each page keeps its own panel's mapping (soundQuirk on Audio rows).
local kitOn = checked
if row.soundQuirk then kitOn = not kitOn end
if kitOn then
PlaySound("igMainMenuOptionCheckBoxOn")
else
PlaySound("igMainMenuOptionCheckBoxOff")
end
-- The write: checked = 1 — unless the row is value-INVERTED (Sticky Targeting:
-- 1.12 checks it when deselectOnClick is "0", era wraps the same CVar in
-- NegateBoolean; both references agree).
local on = checked
if row.invert then on = not on end
if on then
OptionsRow_Set(row, "1")
else
OptionsRow_Set(row, "0")
end
OptionsPage_UpdateDependencies(OptionsFrame.selectedCategory)
end
function OptionsSlider_OnValueChanged(slider, value)
local control = slider:GetParent()
local row = control:GetParent()
if not row.refreshing then
-- era obeyStepOnDrag (MinimalSlider.xml l.5-7): user moves snap to the step
-- grid — anchored at the range MINIMUM, the reference slider's own anchor
-- (farclip's 1.12 grid is 177+n*60, not the multiples of 60). A refresh
-- writes the stored value raw (era SetValue does not snap), so an off-grid
-- config value still lands where it said.
local step = slider:GetValueStep()
if step and step > 0 then
local minv = slider:GetMinMaxValues()
local snapped = minv + math.floor((value - minv) / step + 0.5) * step
if math.abs(snapped - value) > 0.00001 then
slider:SetValue(snapped) -- re-enters once with the snapped value
return
end
value = snapped -- the clean grid number, not the f32-rounded echo
end
end
getglobal(control:GetName() .. "Value"):SetText(OptionsRow_FormatValue(row, value))
if (row.cvar or row.uvar) and not row.refreshing then
-- Two decimals cover every page's grid (the 5% volumes, uiScale's 0.01, whole
-- yards); trailing zeros stripped so a value at the registered default
-- compares equal to it as a STRING and stays out of the config file ("0.40"
-- would read as moved-off-"0.4").
local str = string.gsub(string.format("%.2f", value), "%.?0+$", "")
if row.deferred then
-- era CommitFlag.Apply, re-expressed (Blizzard_Setting.lua's staged
-- SetValue half): the control shows the new value, the ENGINE keeps the
-- old one, and the CVar moves only when Apply commits — dragging a
-- slider that rescales itself mid-drag is exactly what this flag
-- exists to prevent. Dragging back onto the committed value clears the
-- pending (era's IsModified: a pending EQUAL to current isn't one).
if str == OptionsRow_Get(row) then
row.pending = nil
else
row.pending = str
end
OptionsApply_Update()
else
OptionsRow_Set(row, str)
end
end
end
function OptionsPage_Refresh(key)
local base = "OptionsFrameContainerBody" .. key
for _, rkey in ipairs(OPTIONS_PAGE_ROWS[key]) do
local row = getglobal(base .. rkey)
local box = getglobal(base .. rkey .. "Check")
if box then
-- SetChecked takes the CVar string exactly like the 1.12 panel: the numeric
-- read means "0" unchecks (button-check-and-state-texture.md). A
-- value-inverted row checks on "0" instead — UIOptionsFrame.lua's own
-- GAMEFIELD_DESELECT arm (`checked = 1` when the CVar reads "0").
if row.invert then
local checked
if OptionsRow_Get(row) == "0" then checked = 1 end
box:SetChecked(checked)
else
box:SetChecked(OptionsRow_Get(row))
end
elseif row.entries then
OptionsDropdown_ShowValue(row)
else
local slider = getglobal(base .. rkey .. "ControlSlider")
row.refreshing = 1
-- Pending first (era GetValue returns the pendingValue while one is
-- staged): coming back to a page with an un-applied edit shows the edit.
slider:SetValue(tonumber(row.pending or OptionsRow_Get(row) or "0") or 0)
row.refreshing = nil
-- SetValue fires only on a CHANGE — write the readout unconditionally,
-- from the slider's OWN value: in range that IS the CVar (SetValue stores
-- raw), out of range the thumb and the number at least agree on the
-- nearest expressible value.
getglobal(base .. rkey .. "ControlValue"):SetText(
OptionsRow_FormatValue(row, slider:GetValue()))
end
end
OptionsPage_UpdateDependencies(key)
OptionsApply_Update()
end
-- Per-page dependency rules. Audio's is the 1.12 rule, verbatim: Enable All Sound off
-- greys Enable Ambience — and only it, of our rows
-- (SoundOptionsFrame_UpdateDependencies disables buttons 2 and 4, Ambience and Error
-- Speech; Enable Music stays live, the panel's own quirk).
function OptionsPage_UpdateDependencies(key)
if key == "Combat" then
-- The 1.12 rule, verbatim (UIOptionsFrame_UpdateDependencies' "Additional combat
-- text dependancies" block): the master off greys EVERY sub-toggle and the scroll
-- dropdown. Combo Points carries the reference's second gate too — it is disabled
-- for anyone who is not a rogue or a druid, since nobody else has combo points.
local on = SHOW_COMBAT_TEXT ~= "0"
local _, class = UnitClass("player")
class = class and string.upper(class)
local combo = class == "ROGUE" or class == "DRUID"
for _, rkey in ipairs(OPTIONS_PAGE_ROWS.Combat) do
if rkey ~= "RowCombatText" then
local live = on
if rkey == "RowComboPoints" and not combo then live = nil end
OptionsRow_SetEnabled(
getglobal("OptionsFrameContainerBodyCombat" .. rkey), live)
end
end
return
end
if key == "Interface" then
-- 1.12's own rule for this pair (UIOptionsFrame_UpdateDependencies l.694-700):
-- the mode picker is dead while the switch is unticked. It is the only
-- cross-row dependency on this page.
OptionsRow_SetEnabled(OptionsFrameContainerBodyInterfaceRowTargetOfTargetMode,
SHOW_TARGET_OF_TARGET ~= "0")
return
end
if key == "ActionBars" then
-- 1.12's own rule for this pair, verbatim in effect (UIOptionsFrame.lua l.722-726):
-- Show Right ActionBar 2 is dead while Show Right ActionBar is unticked, because
-- MultiBarLeft does not appear without MultiBarRight under it (stock
-- MultiActionBar_Update's conjunction). It is the only cross-row dependency on this page — the two bottom
-- bars, the grid switch and the lock are all independent.
OptionsRow_SetEnabled(OptionsFrameContainerBodyActionBarsRowMultiBar4,
SHOW_MULTI_ACTIONBAR_3)
return
end
if key ~= "Audio" then return end
-- 1.12's own rule, both halves: SoundOptionsFrame_UpdateDependencies greys check
-- button 2 (Enable Ambience) AND button 4 (Enable Error Speech) while the master
-- enable is unticked. Enable Music is deliberately left alive — the quirk the
-- reference's own file has.
local on = GetCVar("MasterSoundEffects") ~= "0"
for _, row in {"RowEnableAmbience", "RowEnableErrorSpeech"} do
local box = getglobal("OptionsFrameContainerBodyAudio"..row.."Check")
local label = getglobal("OptionsFrameContainerBodyAudio"..row.."Label")
if on then
box:Enable()
label:SetFontObject("GameFontNormal")
else
box:Disable()
label:SetFontObject("GameFontDisable")
end
end
end
-- Grey or wake one row, whichever control it wears (the Audio rule above predates this and
-- reaches its one checkbox by name). A dropdown row greys the way 1.12's own
-- OptionsFrame_DisableDropDown does — the capsule's toggle BUTTON disabled so the list
-- cannot open, and its text in GRAY_FONT_COLOR — since the chain's own
-- UIDropDownMenu_DisableButton addresses a LIST row (level, id), not the capsule, and
-- 1.12 has no `UIDropDownMenu_DisableDropDown` at all.
function OptionsRow_SetEnabled(row, on)
local name = row:GetName()
local box = getglobal(name .. "Check")
local dd = getglobal(name .. "Dropdown")
if box then
if on then box:Enable() else box:Disable() end
elseif dd then
local text = getglobal(dd:GetName() .. "Text")
if on then
getglobal(dd:GetName() .. "Button"):Enable()
text:SetTextColor(HIGHLIGHT_FONT_COLOR.r, HIGHLIGHT_FONT_COLOR.g,
HIGHLIGHT_FONT_COLOR.b)
else
getglobal(dd:GetName() .. "Button"):Disable()
text:SetTextColor(GRAY_FONT_COLOR.r, GRAY_FONT_COLOR.g, GRAY_FONT_COLOR.b)
end
end
local label = getglobal(name .. "Label")
if on then
label:SetFontObject(row.child and "GameFontNormalSmall" or "GameFontNormal")
else
label:SetFontObject(row.child and "GameFontDisableSmall" or "GameFontDisable")
end
end
-- Defaults, live on any page with rows: every row's CVar back to its registered
-- default (era resets per-setting the same way; the era all-pages confirm dialog is
-- not modeled — Defaults resets the VISIBLE page). A pending edit dies with it — the
-- default write supersedes what Apply would have committed.
function OptionsPage_Defaults(key)
local base = "OptionsFrameContainerBody" .. key
for _, rkey in ipairs(OPTIONS_PAGE_ROWS[key]) do
local row = getglobal(base .. rkey)
row.pending = nil
OptionsRow_Set(row, OptionsRow_Default(row))
end
OptionsPage_Refresh(key)
end
-- ── The Apply flow (0961): era CommitFlag.Apply, cut to our rows ────────────────────
-- Pending edits are PANEL-wide like era's modified table: they survive a category
-- switch (the row redisplays its pending on return) and commit together; only hiding
-- the window discards them (era wipes on hide too — its close-time 3-button confirm
-- dialog and the 8-second revert timer are not modeled, SCOPE note).
-- The era Apply gate (HasUnappliedSettings + SetApplyButtonEnabled): the button is
-- enabled AND shown together — it EXISTS only while something is pending.
function OptionsApply_Update()
local pending
for key, rows in pairs(OPTIONS_PAGE_ROWS) do
local base = "OptionsFrameContainerBody" .. key
for _, rkey in ipairs(rows) do
if getglobal(base .. rkey).pending then pending = 1 end
end
end
if pending then
OptionsFrameApplyButton:Enable()
OptionsFrameApplyButton:Show()
else
OptionsFrameApplyButton:Disable()
OptionsFrameApplyButton:Hide()
end
end
-- Commit (era SettingsPanelMixin:CommitSettings, cut to our rows): every pending
-- value everywhere becomes a real SetCVar, and the button goes away.
function OptionsPage_Apply()
for key, rows in pairs(OPTIONS_PAGE_ROWS) do
local base = "OptionsFrameContainerBody" .. key
for _, rkey in ipairs(rows) do
local row = getglobal(base .. rkey)
if row.pending then
OptionsRow_Set(row, row.pending)
row.pending = nil
end
end
end
OptionsApply_Update()
end
-- Hide = discard (era's WipeModifiedTable-on-hide; the confirm dialog is cut): the
-- next open re-reads the committed CVars, so the sliders land back on the truth.
function OptionsPage_DiscardPending()
for key, rows in pairs(OPTIONS_PAGE_ROWS) do
local base = "OptionsFrameContainerBody" .. key
for _, rkey in ipairs(rows) do
getglobal(base .. rkey).pending = nil
end
end
OptionsApply_Update()
end
]]></Script>
<!-- The red panel button (era UIPanelButtonTemplate seat, 96x22): GameMenuButtonTemplate's
four-state UI-Panel-Button art + white/white/grey label trio, copied at this window's
size (GameMenuFrame.xml l.145 is the shared original; templates don't cross files). -->
<Button name="OptionsRedButtonTemplate" virtual="true">
<Size><AbsDimension x="96" y="22"/></Size>
<ButtonText name="$parentText"/>
<NormalTexture file="Interface\Buttons\UI-Panel-Button-Up"><TexCoords left="0" right="0.625" top="0" bottom="0.6875"/></NormalTexture>
<PushedTexture file="Interface\Buttons\UI-Panel-Button-Down"><TexCoords left="0" right="0.625" top="0" bottom="0.6875"/></PushedTexture>
<DisabledTexture file="Interface\Buttons\UI-Panel-Button-Disabled"><TexCoords left="0" right="0.625" top="0" bottom="0.6875"/></DisabledTexture>
<HighlightTexture file="Interface\Buttons\UI-Panel-Button-Highlight" alphaMode="ADD"><TexCoords left="0" right="0.625" top="0" bottom="0.6875"/></HighlightTexture>
<NormalFont inherits="GameFontHighlight"/>
<HighlightFont inherits="GameFontHighlight"/>
<DisabledFont inherits="GameFontDisable"/>
</Button>
<!-- One category row (era Blizzard_CategoryList.xml's category button): the template's
175 width is never rendered in the era either — the ScrollBox stretches every row to
the scroll target's width, 199 with no scroll bar (CategoryList.lua l.176-197 +
ScrollBoxViewUtil), and ours is the no-bar case. Gold label centered in the row (the
full-row box below).
The select/hover wash is the 1.14 OptionsListButtonTemplate mechanism VERBATIM (0984;
wow-ui-source @1.14.4 OptionsFrameTemplates.xml l.216-229 + .lua l.107-131 — 0978's
two-texture BLEND washes drew as opaque bars, the director's report): ONE
UI-QuestLogTitleHighlight quad in ADD blend, tinted steel-blue (.196,.388,.8) as the
hover and locked GOLD (1,1,0) on the selected row — 1.14's SetVertexColor pair around
LockHighlight/UnlockHighlight, carried here as Show/Hide on the Bg (our selection is
Lua-driven, not button-state-driven). Geometry stays the era plate seat (187x21
centered, 6 units in from either edge of the 199 row — sized once in
OptionsCategoryRow_OnLoad); hover text goes white via HighlightFont, 1.14's own trio. -->
<Button name="OptionsCategoryRowTemplate" virtual="true">
<Size><AbsDimension x="199" y="20"/></Size>
<Layers>
<Layer level="BACKGROUND">
<Texture name="$parentBg" file="Interface\QuestFrame\UI-QuestLogTitleHighlight" alphaMode="ADD" hidden="true">
<Anchors><Anchor point="CENTER"/></Anchors>
</Texture>
</Layer>
</Layers>
<!-- The era label is a box spanning the WHOLE row — TOPLEFT (36,+1) AND BOTTOMRIGHT
(0,+1), era CategoryList.xml l.72-77 — so the default MIDDLE vertical justify
CENTERS the text in the 20-tall row (1 unit high of true center, the era's own
nudge). The first cut had only the TOPLEFT corner: the auto-sized label hung from
the row TOP, ~4 units high of center ("text not vertically centered" — director,
2026-08-04). justifyH LEFT is the era's own attribute; a full-row box without it
would center horizontally. -->
<ButtonText name="$parentText" justifyH="LEFT">
<Anchors>
<Anchor point="TOPLEFT"><Offset><AbsDimension x="36" y="1"/></Offset></Anchor>
<Anchor point="BOTTOMRIGHT"><Offset><AbsDimension x="0" y="1"/></Offset></Anchor>
</Anchors>
</ButtonText>
<NormalFont inherits="GameFontNormal"/>
<HighlightFont inherits="GameFontHighlight"/>
<DisabledFont inherits="GameFontDisable"/>
<Scripts>
<OnClick>OptionsFrame_SelectCategory(this.categoryKey)</OnClick>
<!-- 1.14's highlight shows on hover with the blue tint standing (the selected row
holds its locked gold — the guard is UnlockHighlight's job here). -->
<OnEnter>
if OptionsFrame.selectedCategory ~= this.categoryKey then
getglobal(this:GetName() .. "Bg"):Show()
end
</OnEnter>
<OnLeave>
if OptionsFrame.selectedCategory ~= this.categoryKey then
getglobal(this:GetName() .. "Bg"):Hide()
end
</OnLeave>
</Scripts>
</Button>
<!-- A category group header (era SettingsCategoryListHeaderTemplate geometry: 30 tall,
stretched to the 199 row width like the rows, 14px white label at LEFT (20,-1)). The
era's Options_CategoryHeader_<n> background wash has no 1.12 counterpart and is
dropped (header mapping note) — the header is its label alone. -->
<Frame name="OptionsCategoryHeaderTemplate" virtual="true">
<Size><AbsDimension x="199" y="30"/></Size>
<Layers>
<Layer level="OVERLAY">
<FontString name="$parentLabel" inherits="OptionsFontHighlightMedium">
<Anchors><Anchor point="LEFT"><Offset><AbsDimension x="20" y="-1"/></Offset></Anchor></Anchors>
</FontString>
</Layer>
</Layers>
</Frame>
<!-- A search-results group header (era SettingsListSearchCategoryTemplate —
Blizzard_SettingsList.xml l.65-87 + .lua l.14-33): 45 tall, the category's name in
GameFontNormalLarge at TOPLEFT (16,-16), clickable — the click selects its category,
which ends the search (the era's own jump; SelectCategory clears the box). Unanchored
until a search chains it in (OptionsSearch_Place). The era's mouseover is a 0.1-gray
ADD overlay; this wears the setting rows' white-0.1 wash instead — the same read, the
file's one hover idiom (0984 notes the divergence). -->
<Button name="OptionsSearchHeadTemplate" virtual="true" hidden="true">
<Size><AbsDimension y="45"/></Size>
<Layers>
<Layer level="BACKGROUND">
<Texture name="$parentHover" hidden="true">
<Anchors>
<Anchor point="TOPLEFT"/>
<Anchor point="BOTTOMRIGHT"/>
</Anchors>
<Color r="1" g="1" b="1" a="0.1"/>
</Texture>
</Layer>
<Layer level="ARTWORK">
<FontString name="$parentLabel" inherits="GameFontNormalLarge" justifyH="LEFT">
<Anchors><Anchor point="TOPLEFT"><Offset><AbsDimension x="16" y="-16"/></Offset></Anchor></Anchors>
</FontString>
</Layer>
</Layers>
<Scripts>
<OnClick>OptionsFrame_SelectCategory(this.categoryKey)</OnClick>
<OnEnter>getglobal(this:GetName() .. "Hover"):Show()</OnEnter>
<OnLeave>getglobal(this:GetName() .. "Hover"):Hide()</OnLeave>
</Scripts>
</Button>
<!-- One SETTING row, checkbox flavor (era SettingsCheckboxControlTemplate over
SettingsListElementTemplate, Blizzard_SettingControls.xml l.50-112): 26 tall, stretched
to the page width by its instance anchors; label GameFontNormal in a LEFT(37) →
CENTER(-85) box (the era mixin's runtime anchors, SettingControls.lua l.322-323); the
30x29 box at LEFT = row CENTER-80 (l.473). The checkbox art is the 1.12 UI-CheckBox
set at the era state seats (0978 mapping; the era used its minimal set): Up normal,
Down pushed, Check checked, Check-Disabled disabled+checked; no HighlightTexture —
hover feedback stays the era row-wide Hover wash (HoverBackgroundTemplate: white
a=0.1, hung 10 left / 5 right of the row), so the box doesn't double-light. -->
<Frame name="OptionsCheckboxRowTemplate" virtual="true" enableMouse="true">
<Size><AbsDimension y="26"/></Size>
<Layers>
<Layer level="BACKGROUND">
<Texture name="$parentHover" hidden="true">
<Anchors>
<Anchor point="TOPLEFT"><Offset><AbsDimension x="-10" y="0"/></Offset></Anchor>
<Anchor point="BOTTOMRIGHT"><Offset><AbsDimension x="-5" y="0"/></Offset></Anchor>
</Anchors>
<Color r="1" g="1" b="1" a="0.1"/>
</Texture>
</Layer>
<Layer level="OVERLAY">
<FontString name="$parentLabel" inherits="GameFontNormal" justifyH="LEFT">
<Anchors>
<Anchor point="LEFT"><Offset><AbsDimension x="37" y="0"/></Offset></Anchor>
<Anchor point="RIGHT" relativePoint="CENTER"><Offset><AbsDimension x="-85" y="0"/></Offset></Anchor>
</Anchors>
</FontString>
</Layer>
</Layers>
<Frames>
<!-- The tooltip's SEAT (B223): the era's own anchor region, verbatim geometry — the
row's LABEL half, TOPLEFT out to (BOTTOM, -80), i.e. the control column
(SettingsListElementTemplate's $parentTooltip, Blizzard_SettingControls.xml
l.57-61). The era hangs its hover wash AND its mouse scripts here; ours borrows
the RECT only — the row lights and raises as one surface (OptionsRow_Hover) — so
this frame stays mouse-less and can never steal the row's own hover. -->
<Frame name="$parentTip">
<Anchors>
<Anchor point="TOPLEFT"/>
<Anchor point="BOTTOMRIGHT" relativePoint="BOTTOM"><Offset><AbsDimension x="-80" y="0"/></Offset></Anchor>
</Anchors>
</Frame>
<CheckButton name="$parentCheck">
<Size><AbsDimension x="30" y="29"/></Size>
<Anchors><Anchor point="LEFT" relativePoint="CENTER"><Offset><AbsDimension x="-80" y="0"/></Offset></Anchor></Anchors>
<NormalTexture file="Interface\Buttons\UI-CheckBox-Up"/>
<PushedTexture file="Interface\Buttons\UI-CheckBox-Down"/>
<CheckedTexture file="Interface\Buttons\UI-CheckBox-Check"/>
<DisabledCheckedTexture file="Interface\Buttons\UI-CheckBox-Check-Disabled"/>
<Scripts>
<OnClick>OptionsCheckbox_OnClick(this)</OnClick>
<OnEnter>OptionsRow_Hover(this:GetParent(), 1)</OnEnter>
<OnLeave>OptionsRow_Hover(this:GetParent(), nil)</OnLeave>
</Scripts>
</CheckButton>
</Frames>
<Scripts>
<OnEnter>OptionsRow_Hover(this, 1)</OnEnter>
<OnLeave>OptionsRow_Hover(this, nil)</OnLeave>
</Scripts>
</Frame>
<!-- One SETTING row, slider flavor (era SettingsSliderControlTemplate →
MinimalSliderWithSteppersTemplate, SettingControls.lua l.550-552 + MinimalSlider.xml):
the same 26-tall row and label box; the 250x40 control at LEFT = row CENTER(-80,+3)
(its 40 height overrides the row symmetrically). Inside: the Slider inset 19 each
side — spanning the groove exactly — hit area the full height, the way era's is
(mouse-enabled by the widget's own construction, 0250's law — no attr needed). The
TRACK is the 0978 mapping — the 1.12 OptionsSliderTemplate groove verbatim
(extracted OptionsFrameTemplates.xml l.74: UI-SliderBar-Background/-Border backdrop,
edge 8, tile 8, insets 3/3/6/6) on a 17-tall Groove frame at the era track seat (the
era's own three-slice was 17 tall, centered), with the 1.12 32x32
UI-SliderBar-Button-Horizontal thumb. NO steppers — 0984 wore them at 26x26 as kept
era structure; 0989 cut them by the director's call (a recorded exception to 0985's
keep): the bar alone is the control, and the whole bar is live — thumb drag always
worked but the visible knob is a small target, so 0989's engine track-press law
seats the thumb under a press anywhere on the bar and drags on from there. The
readout is era's RIGHT label seat — 6 right of the control (MinimalSlider.xml
l.73-77), rounded-percent formatted.
THE +3 LIVES ON THE ART, NOT ON THE CONTROL (B232, decision 1641). It seats the
17-tall groove and the 32 px thumb inside the 26-tall row, so it belongs to the two
frames that draw them — $parentGroove and $parentSlider. It used to sit on
$parentControl itself, where the readout inherited it: every slider row drew its number
3 units above its own label, one line asked to read as two. The art's seat is unchanged
to the pixel; the text is what moved. -->
<Frame name="OptionsSliderRowTemplate" virtual="true" enableMouse="true">
<Size><AbsDimension y="26"/></Size>
<Layers>
<Layer level="BACKGROUND">
<Texture name="$parentHover" hidden="true">
<Anchors>
<Anchor point="TOPLEFT"><Offset><AbsDimension x="-10" y="0"/></Offset></Anchor>
<Anchor point="BOTTOMRIGHT"><Offset><AbsDimension x="-5" y="0"/></Offset></Anchor>
</Anchors>
<Color r="1" g="1" b="1" a="0.1"/>
</Texture>
</Layer>
<Layer level="OVERLAY">
<FontString name="$parentLabel" inherits="GameFontNormal" justifyH="LEFT">
<Anchors>
<Anchor point="LEFT"><Offset><AbsDimension x="37" y="0"/></Offset></Anchor>
<Anchor point="RIGHT" relativePoint="CENTER"><Offset><AbsDimension x="-85" y="0"/></Offset></Anchor>
</Anchors>
</FontString>
</Layer>
</Layers>
<Frames>
<!-- The tooltip's seat — the era label-half region (the checkbox template's comment is
the recipe). The era anchors a SLIDER row's plate off the slider itself instead
(SettingControls.lua l.556, ANCHOR_RIGHT +20); ours keeps the one column, so a
walk down a page doesn't make the plate hop 250 units per row. -->
<Frame name="$parentTip">
<Anchors>
<Anchor point="TOPLEFT"/>
<Anchor point="BOTTOMRIGHT" relativePoint="BOTTOM"><Offset><AbsDimension x="-80" y="0"/></Offset></Anchor>
</Anchors>
</Frame>
<Frame name="$parentControl">
<Size><AbsDimension x="250" y="40"/></Size>
<Anchors><Anchor point="LEFT" relativePoint="CENTER"><Offset><AbsDimension x="-80" y="0"/></Offset></Anchor></Anchors>
<Layers>
<Layer level="OVERLAY">
<FontString name="$parentValue" inherits="GameFontNormal">
<Anchors><Anchor point="LEFT" relativePoint="RIGHT"><Offset><AbsDimension x="6" y="0"/></Offset></Anchor></Anchors>
</FontString>
</Layer>
</Layers>
<Frames>
<!-- The groove: the 1.12 slider's own backdrop on a frame at the era track
seat (17 tall, vertically centered, spanning the slider's run).
Declared before the Slider so the thumb draws above it. -->
<Frame name="$parentGroove">
<Size><AbsDimension y="17"/></Size>
<Anchors>
<Anchor point="LEFT"><Offset><AbsDimension x="19" y="3"/></Offset></Anchor>
<Anchor point="RIGHT"><Offset><AbsDimension x="-19" y="3"/></Offset></Anchor>
</Anchors>
<Backdrop bgFile="Interface\Buttons\UI-SliderBar-Background" edgeFile="Interface\Buttons\UI-SliderBar-Border" tile="true">
<EdgeSize><AbsValue val="8"/></EdgeSize>
<TileSize><AbsValue val="8"/></TileSize>
<BackgroundInsets><AbsInset left="3" right="3" top="6" bottom="6"/></BackgroundInsets>
</Backdrop>
</Frame>
<Slider name="$parentSlider" orientation="HORIZONTAL" minValue="0" maxValue="1" valueStep="0.05">
<Anchors>
<Anchor point="TOPLEFT"><Offset><AbsDimension x="19" y="3"/></Offset></Anchor>
<Anchor point="BOTTOMRIGHT"><Offset><AbsDimension x="-19" y="3"/></Offset></Anchor>
</Anchors>
<ThumbTexture name="$parentThumb" file="Interface\Buttons\UI-SliderBar-Button-Horizontal">
<Size><AbsDimension x="32" y="32"/></Size>
</ThumbTexture>
<Scripts>
<OnValueChanged>OptionsSlider_OnValueChanged(this, arg1)</OnValueChanged>
<OnEnter>OptionsRow_Hover(this:GetParent():GetParent(), 1)</OnEnter>
<OnLeave>OptionsRow_Hover(this:GetParent():GetParent(), nil)</OnLeave>
</Scripts>
</Slider>
</Frames>
</Frame>
</Frames>
<Scripts>
<OnEnter>OptionsRow_Hover(this, 1)</OnEnter>
<OnLeave>OptionsRow_Hover(this, nil)</OnLeave>
</Scripts>
</Frame>
<!-- One SETTING row, dropdown flavor (0992 — the first): the same era 26-tall row and
label box; the control is the 1.12 dropdown CAPSULE verbatim (UIDropDownMenuTemplate —
the kit every 1.12 dropdown wears, UIDropDownMenu.xml), seated so its box ink opens at
the era control column (the capsule's Left cap carries ~10 units of air before its
ink, hence the -71 anchor against the sliders' CENTER-80 + 19 groove seat; width 180
makes the ink span read as the slider track's). SHAPE provenance: the era's own
control for an enumerated setting is a dropdown (SettingsDropdownControlTemplate — its
Graphics page is a zoo of them), while 1.12 shaped this row's setting as a 3-stop
slider; the era grammar wins the shape by the director's call ("some with dropdown"),
recorded in 0992. The list the capsule opens rides the kit's own law — shared
DropDownList1, FULLSCREEN_DIALOG strata (above this HIGH window), the owner's
effective scale (the kit's 0992 correction), the 2-second hover-away close. -->
<Frame name="OptionsDropdownRowTemplate" virtual="true" enableMouse="true">
<Size><AbsDimension y="26"/></Size>
<Layers>
<Layer level="BACKGROUND">
<Texture name="$parentHover" hidden="true">
<Anchors>
<Anchor point="TOPLEFT"><Offset><AbsDimension x="-10" y="0"/></Offset></Anchor>
<Anchor point="BOTTOMRIGHT"><Offset><AbsDimension x="-5" y="0"/></Offset></Anchor>
</Anchors>
<Color r="1" g="1" b="1" a="0.1"/>
</Texture>
</Layer>
<Layer level="OVERLAY">
<FontString name="$parentLabel" inherits="GameFontNormal" justifyH="LEFT">
<Anchors>
<Anchor point="LEFT"><Offset><AbsDimension x="37" y="0"/></Offset></Anchor>
<Anchor point="RIGHT" relativePoint="CENTER"><Offset><AbsDimension x="-85" y="0"/></Offset></Anchor>
</Anchors>
</FontString>
</Layer>
</Layers>
<Frames>
<!-- The tooltip's seat — the era label-half region (the checkbox template's comment is
the recipe). -->
<Frame name="$parentTip">
<Anchors>
<Anchor point="TOPLEFT"/>
<Anchor point="BOTTOMRIGHT" relativePoint="BOTTOM"><Offset><AbsDimension x="-80" y="0"/></Offset></Anchor>
</Anchors>
</Frame>
<Frame name="$parentDropdown" inherits="UIDropDownMenuTemplate">
<Anchors><Anchor point="LEFT" relativePoint="CENTER"><Offset><AbsDimension x="-71" y="-2"/></Offset></Anchor></Anchors>
</Frame>
</Frames>
<Scripts>
<OnEnter>OptionsRow_Hover(this, 1)</OnEnter>
<OnLeave>OptionsRow_Hover(this, nil)</OnLeave>
</Scripts>
</Frame>
<!-- `toplevel` restored (ref OptionsFrame.xml l.3) — same class as the B180/Bagnon dialog
(decision 1318). NOTE our strata is HIGH where the ref is DIALOG; left as-is, flagged in
the record rather than silently changed. -->
<Frame name="OptionsFrame" parent="UIParent" frameStrata="HIGH" toplevel="true" enableMouse="true" hidden="true">
<Size><AbsDimension x="920" y="724"/></Size>
<Anchors><Anchor point="CENTER"/></Anchors>
<!-- The 1.14 System-window dialog, verbatim (BACKDROP_DIALOG_32_32 — wow-ui-source
@1.14.4 Backdrop.lua l.17-25, the backdrop OptionsFrameTemplate/
InterfaceOptionsFrame wear): the tiled UI-DialogBox-Background ground — a uniform
pure-black 60%-alpha tile, so the world reads through the window exactly like the
reference screenshot (0981: the reference was never parchment) — inside the rope
border. Both files are 1.12's own. -->
<Backdrop bgFile="Interface\DialogFrame\UI-DialogBox-Background" edgeFile="Interface\DialogFrame\UI-DialogBox-Border" tile="true">
<TileSize><AbsValue val="32"/></TileSize>
<EdgeSize><AbsValue val="32"/></EdgeSize>
<BackgroundInsets><AbsInset left="11" right="12" top="12" bottom="11"/></BackgroundInsets>
</Backdrop>
<Layers>
<Layer level="BACKGROUND">
<!-- THE GROUND DIM (1476) — a plain black fill over the tiled ground, because
the tile alone is not a readable page. UI-DialogBox-Background decodes to a
uniform (0,0,0,153) — pure black at 0.6 — so 40% of the world reaches the
text: over snow, noon water or a lit sky the page ground lands near mid-grey
and the option labels stop reading (the director's report). 0.6 is right for
a dialog you glance at; the options window is a page you READ, and the era's
own answer is an OPAQUE FlatPanelBackgroundTemplate panel — so darkening
toward it is the era structure 0985 already gives us, wearing a plain colour
fill, which 0985 names as an art byte we may author.
Alpha 0.7 over the 0.6 tile composites to 0.88 (the world reaches the page at
0.12), not 1.0: the faint read-through is the window's character, and 0981's
"the reference was never parchment" finding stands — this dims the ground, it
does not replace it.
SEAT: inset 15 on all four sides, NOT the backdrop's own 11/12/12/11 bg
insets. Regions sort AFTER the frame's own draw slot, which is where the
backdrop's bg AND its 8 border pieces both live (extract.rs) — so this fill
paints OVER the rope, and at the bg insets it would smear 0.7 black across
the border's inner half. The rope's ink is measured, not guessed: every edge
slice of UI-DialogBox-Border is opaque only in its outer ~11 units and dead
by texel 16 of 32, and the corner slices' ink ends by 14 (decoded). 15 clears
every inked texel and leaves at most a 1-unit lighter hairline inside the
rope, where the rope reads as frame anyway. -->
<Texture name="$parentGroundDim">
<Anchors>
<Anchor point="TOPLEFT"><Offset><AbsDimension x="15" y="-15"/></Offset></Anchor>
<Anchor point="BOTTOMRIGHT"><Offset><AbsDimension x="-15" y="15"/></Offset></Anchor>
</Anchors>
<Color r="0" g="0" b="0" a="0.7"/>
</Texture>
</Layer>
<Layer level="ARTWORK">
<!-- The carved title plate at the 1.14 options frames' own seat: 300x68 at
TOP (0,+12) — OptionsFrameTemplates.xml l.244-255 (GameMenuFrame's plate
is the smaller 256x64 dialog cut). -->
<Texture name="$parentHeader" file="Interface\DialogFrame\UI-DialogBox-Header">
<Size><AbsDimension x="300" y="68"/></Size>
<Anchors><Anchor point="TOP"><Offset><AbsDimension x="0" y="12"/></Offset></Anchor></Anchors>
</Texture>
</Layer>
<Layer level="OVERLAY">
<!-- Title ON the plate (GameMenuFrame's seat), not the era's bare TOP (0,-5)
band seat — the plate is where a 1.12 dialog carries its name. GameFontNormal
rather than the menu's GameFontNormalMed1: that font object is declared
beside its consumer in GameMenuFrame.xml (0952's residue convention), which
loads AFTER this file. -->
<FontString name="$parentTitleText" inherits="GameFontNormal" text="Options">
<Anchors>
<Anchor point="TOP" relativeTo="$parentHeader"><Offset><AbsDimension x="0" y="-14"/></Offset></Anchor>
</Anchors>
</FontString>
</Layer>
</Layers>
<Frames>
<!-- ── The two boxes — 1.14's OptionsFrameListTemplate / $parentPanelContainer
idiom, verbatim mechanism (OptionsFrameTemplates.xml l.113-116 + l.286-289 +
SharedTooltipTemplates.xml l.117-121): a TOOLTIP-BORDER backdrop with the
FILL AT ALPHA 0 — the boxes are outlines; the window's translucent ground
reads through them — and the border tinted LIGHTGRAY_FONT_COLOR (0.6, the
engine-side constant; the 0981 record cites it). Declared FIRST so every
sibling draws above them. Anchors are window-absolute (the boxed frames are
declared later; no forward refs): both tops at -52 — 0981 took 1.14's -40
band verbatim, but 1.14's band never held a search box; ours holds the
era's (structure is the era's — 0985's split), and at -40 the box line ran
through the search's seat (the director's "make some space up there"). -52
is the depth that gives the era search seat its clearances while the tabs
stay tucked on the box line below. -->
<Frame name="$parentCategoryBox">
<Anchors>
<Anchor point="TOPLEFT"><Offset><AbsDimension x="10" y="-52"/></Offset></Anchor>
<Anchor point="BOTTOMRIGHT"><Offset><AbsDimension x="-695" y="38"/></Offset></Anchor>
</Anchors>
<Backdrop bgFile="Interface\Tooltips\UI-Tooltip-Background" edgeFile="Interface\Tooltips\UI-Tooltip-Border" tile="true">
<EdgeSize><AbsValue val="16"/></EdgeSize>
<TileSize><AbsValue val="16"/></TileSize>
<BackgroundInsets><AbsInset left="5" right="5" top="5" bottom="5"/></BackgroundInsets>
</Backdrop>
<Scripts>
<OnLoad>
this:SetBackdropColor(0, 0, 0, 0)
this:SetBackdropBorderColor(0.6, 0.6, 0.6)
</OnLoad>
</Scripts>
</Frame>
<Frame name="$parentPageBox">
<Anchors>
<Anchor point="TOPLEFT"><Offset><AbsDimension x="225" y="-52"/></Offset></Anchor>
<Anchor point="BOTTOMRIGHT"><Offset><AbsDimension x="-14" y="39"/></Offset></Anchor>
</Anchors>
<Backdrop bgFile="Interface\Tooltips\UI-Tooltip-Background" edgeFile="Interface\Tooltips\UI-Tooltip-Border" tile="true">
<EdgeSize><AbsValue val="16"/></EdgeSize>
<TileSize><AbsValue val="16"/></TileSize>
<BackgroundInsets><AbsInset left="5" right="5" top="5" bottom="5"/></BackgroundInsets>
</Backdrop>
<Scripts>
<OnLoad>
this:SetBackdropColor(0, 0, 0, 0)
this:SetBackdropBorderColor(0.6, 0.6, 0.6)
</OnLoad>
</Scripts>
</Frame>
<!-- ── Tabs — seated the 1.14 way: the tab's BOTTOM sits on the category box's
top edge, tucked 3 units INTO it (InterfaceOptionsFrame.xml l.152-158:
Tab1 BOTTOMLEFT → Categories TOPLEFT (6,-3)) — the file-folder read, no dead
band above (0981; 0978 floated them at the era's TOPLEFT (32,-27) over a
24-unit bare strip, the director's gap report). Era width/text law kept
(width text+40 measured once per show, selected white small text at BOTTOM+6
vs gold at BOTTOM+4),
authored STATIC: Game is the only tab with anything behind it, so it wears
the selected state permanently and AddOns the unselected one (still dead to
clicks; the mixin port comes with an addon list). The PLATE is the 0978
mapping, kept: one UI-Character-{Active,InActive}Tab piece, bottom-anchored
at the era plate height (26/23), flipped vertically via TexCoords (1.12 only
ships bottom-tab plates; the flip turns them top-tab). -->
<Button name="$parentGameTab">
<Size><AbsDimension x="72" y="37"/></Size>
<Anchors>
<Anchor point="BOTTOMLEFT" relativeTo="$parentCategoryBox" relativePoint="TOPLEFT"><Offset><AbsDimension x="6" y="-3"/></Offset></Anchor>
</Anchors>
<Layers>
<Layer level="ARTWORK">
<Texture name="$parentPlate" file="Interface\PaperDollInfoFrame\UI-Character-ActiveTab">
<Size><AbsDimension y="26"/></Size>
<Anchors>
<Anchor point="BOTTOMLEFT"/>
<Anchor point="BOTTOMRIGHT"/>
</Anchors>
<TexCoords left="0" right="1" top="1" bottom="0"/>
</Texture>
</Layer>
</Layers>
<ButtonText name="$parentText" text="Game">
<Anchors><Anchor point="BOTTOM"><Offset><AbsDimension x="0" y="6"/></Offset></Anchor></Anchors>
</ButtonText>
<NormalFont inherits="GameFontHighlightSmall"/>
<HighlightFont inherits="GameFontHighlightSmall"/>
<DisabledFont inherits="GameFontHighlightSmall"/>
<Scripts>
<OnShow>OptionsTab_FitWidth(self)</OnShow>
</Scripts>
</Button>
<Button name="$parentAddOnsTab">
<Size><AbsDimension x="87" y="37"/></Size>
<Anchors>
<Anchor point="TOPLEFT" relativeTo="$parentGameTab" relativePoint="TOPRIGHT"><Offset><AbsDimension x="5" y="0"/></Offset></Anchor>
</Anchors>
<Layers>
<Layer level="ARTWORK">
<Texture name="$parentPlate" file="Interface\PaperDollInfoFrame\UI-Character-InActiveTab">
<Size><AbsDimension y="23"/></Size>
<Anchors>
<Anchor point="BOTTOMLEFT"/>
<Anchor point="BOTTOMRIGHT"/>
</Anchors>
<TexCoords left="0" right="1" top="1" bottom="0"/>
</Texture>
</Layer>
</Layers>
<ButtonText name="$parentText" text="AddOns">
<Anchors><Anchor point="BOTTOM"><Offset><AbsDimension x="0" y="4"/></Offset></Anchor></Anchors>
</ButtonText>
<NormalFont inherits="GameFontNormalSmall"/>
<HighlightFont inherits="GameFontNormalSmall"/>
<DisabledFont inherits="GameFontNormalSmall"/>
<!-- No addon list behind it: dead to clicks, but wearing the era's unselected
GOLD (not disabled grey) — the director's capture is the call. -->
<Scripts>
<OnLoad>self:Disable()</OnLoad>
<OnShow>OptionsTab_FitWidth(self)</OnShow>
</Scripts>
</Button>
<!-- ── The category list (era CategoryList, static — it fits) ──────────────────── -->
<Frame name="$parentCategoryList">
<Size><AbsDimension x="199"/></Size>
<!-- Top rides the box line: box top -52 + the same 12-unit interior pad the
-76 seat gave the era's -64 box (the era's own list-to-box air). -->
<Anchors>
<Anchor point="TOPLEFT"><Offset><AbsDimension x="18" y="-64"/></Offset></Anchor>
<Anchor point="BOTTOMLEFT"><Offset><AbsDimension x="18" y="46"/></Offset></Anchor>
</Anchors>
<!-- The era list rhythm (CreateScrollBoxListLinearView spacing=2 + the 18-tall
SettingsCategoryListSpacerTemplate between groups): 2 between every
element, 2+18+2 = 22 from a group's last row to the next header. -->
<Frames>
<Frame name="$parentHeaderGameplay" inherits="OptionsCategoryHeaderTemplate">
<Anchors><Anchor point="TOPLEFT"/></Anchors>
<Scripts>
<OnLoad>getglobal(self:GetName() .. "Label"):SetText("Gameplay")</OnLoad>
</Scripts>
</Frame>
<Button name="$parentRowControls" inherits="OptionsCategoryRowTemplate">
<Anchors><Anchor point="TOPLEFT" relativeTo="$parentHeaderGameplay" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="0" y="-2"/></Offset></Anchor></Anchors>
<Scripts><OnLoad>OptionsCategoryRow_OnLoad(self, "Controls", "Controls")</OnLoad></Scripts>
</Button>
<Button name="$parentRowInterface" inherits="OptionsCategoryRowTemplate">
<Anchors><Anchor point="TOPLEFT" relativeTo="$parentRowControls" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="0" y="-2"/></Offset></Anchor></Anchors>
<Scripts><OnLoad>OptionsCategoryRow_OnLoad(self, "Interface", "Interface")</OnLoad></Scripts>
</Button>
<Button name="$parentRowActionBars" inherits="OptionsCategoryRowTemplate">
<Anchors><Anchor point="TOPLEFT" relativeTo="$parentRowInterface" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="0" y="-2"/></Offset></Anchor></Anchors>
<Scripts><OnLoad>OptionsCategoryRow_OnLoad(self, "ActionBars", "Action Bars")</OnLoad></Scripts>
</Button>
<Button name="$parentRowCombat" inherits="OptionsCategoryRowTemplate">
<Anchors><Anchor point="TOPLEFT" relativeTo="$parentRowActionBars" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="0" y="-2"/></Offset></Anchor></Anchors>
<Scripts><OnLoad>OptionsCategoryRow_OnLoad(self, "Combat", "Combat")</OnLoad></Scripts>
</Button>
<Button name="$parentRowChat" inherits="OptionsCategoryRowTemplate">
<Anchors><Anchor point="TOPLEFT" relativeTo="$parentRowCombat" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="0" y="-2"/></Offset></Anchor></Anchors>
<Scripts><OnLoad>OptionsCategoryRow_OnLoad(self, "Chat", "Chat")</OnLoad></Scripts>
</Button>
<Button name="$parentRowKeybindings" inherits="OptionsCategoryRowTemplate">
<Anchors><Anchor point="TOPLEFT" relativeTo="$parentRowChat" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="0" y="-2"/></Offset></Anchor></Anchors>
<Scripts><OnLoad>OptionsCategoryRow_OnLoad(self, "Keybindings", "Keybindings")</OnLoad></Scripts>
</Button>
<!-- Nameplates — the era Gameplay group's own last row (NAMEPLATE_OPTIONS_LABEL,
order 15), backed by benilla's real nameplate knobs when the pages land. -->
<Button name="$parentRowNameplates" inherits="OptionsCategoryRowTemplate">
<Anchors><Anchor point="TOPLEFT" relativeTo="$parentRowKeybindings" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="0" y="-2"/></Offset></Anchor></Anchors>
<Scripts><OnLoad>OptionsCategoryRow_OnLoad(self, "Nameplates", "Nameplates")</OnLoad></Scripts>
</Button>
<Frame name="$parentHeaderSystem" inherits="OptionsCategoryHeaderTemplate">
<Anchors>
<Anchor point="TOPLEFT" relativeTo="$parentRowNameplates" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="0" y="-22"/></Offset></Anchor>
</Anchors>
<Scripts>
<OnLoad>getglobal(self:GetName() .. "Label"):SetText("System")</OnLoad>
</Scripts>
</Frame>
<Button name="$parentRowGraphics" inherits="OptionsCategoryRowTemplate">
<Anchors><Anchor point="TOPLEFT" relativeTo="$parentHeaderSystem" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="0" y="-2"/></Offset></Anchor></Anchors>
<Scripts><OnLoad>OptionsCategoryRow_OnLoad(self, "Graphics", "Graphics")</OnLoad></Scripts>
</Button>
<Button name="$parentRowAudio" inherits="OptionsCategoryRowTemplate">
<Anchors><Anchor point="TOPLEFT" relativeTo="$parentRowGraphics" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="0" y="-2"/></Offset></Anchor></Anchors>
<Scripts><OnLoad>OptionsCategoryRow_OnLoad(self, "Audio", "Audio")</OnLoad></Scripts>
</Button>
</Frames>
</Frame>
<!-- ── The page area (era SettingsList's header; the body is next slice's) ─────── -->
<Frame name="$parentContainer">
<Anchors>
<Anchor point="TOPLEFT" relativeTo="$parentCategoryList" relativePoint="TOPRIGHT"><Offset><AbsDimension x="16" y="0"/></Offset></Anchor>
<Anchor point="BOTTOMRIGHT"><Offset><AbsDimension x="-22" y="47"/></Offset></Anchor>
</Anchors>
<Layers>
<Layer level="ARTWORK">
<!-- The era options_horizontaldivider seat (TOP, -50) wearing what
that atlas member actually IS — decoded from the era sheet
(fdid 1318750, a 630x1 row): pure white at ~0.3 alpha, a
hairline. Authored as a 1-unit color fill (0981; no art file —
0978's UI-DialogBox-Divider ink band stretched thin read as
artifacts, the director's separator report). Span keeps the
0978 seat: the container minus 20 units each side. -->
<Texture name="$parentDivider">
<Size><AbsDimension y="1"/></Size>
<Anchors>
<Anchor point="TOPLEFT"><Offset><AbsDimension x="20" y="-50"/></Offset></Anchor>
<Anchor point="TOPRIGHT"><Offset><AbsDimension x="-20" y="-50"/></Offset></Anchor>
</Anchors>
<Color r="1" g="1" b="1" a="0.3"/>
</Texture>
</Layer>
<Layer level="OVERLAY">
<!-- The page title — OptionsFrame_SelectCategory writes it. -->
<FontString name="$parentTitle" inherits="OptionsFontHighlightHuge">
<Anchors><Anchor point="TOPLEFT"><Offset><AbsDimension x="7" y="-22"/></Offset></Anchor></Anchors>
</FontString>
</Layer>
</Layers>
<Frames>
<!-- Live on any page with rows (OPTIONS_PAGE_ROWS) and on the Keybindings
page, where it is 1.12's Reset To Default behind the era confirm
(KeyBindingsPage.xml's popup); OptionsFrame_SelectCategory
enables/disables it with the page swap. -->
<Button name="$parentDefaults" inherits="OptionsRedButtonTemplate" text="Defaults">
<Anchors><Anchor point="TOPRIGHT"><Offset><AbsDimension x="-36" y="-16"/></Offset></Anchor></Anchors>
<Scripts>
<OnLoad>self:Disable()</OnLoad>
<OnClick>
if OptionsFrame.selectedCategory == "Keybindings" then
StaticPopup_Show("CONFIRM_RESET_KEYBINDINGS")
else
OptionsPage_Defaults(OptionsFrame.selectedCategory)
end
</OnClick>
</Scripts>
</Button>
<!-- Unbind Key (1.12's own button, reseated in the header band): exists only
on the Keybindings page, live only while a capsule is selected
(KeyBindings_UpdateUnbindKey drives the enable). -->
<Button name="$parentUnbind" inherits="OptionsRedButtonTemplate" hidden="true">
<Anchors><Anchor point="RIGHT" relativeTo="$parentDefaults" relativePoint="LEFT"><Offset><AbsDimension x="-2" y="0"/></Offset></Anchor></Anchors>
<Scripts>
<OnLoad>
self:SetText(KeyBindings_String("UNBIND", "Unbind Key"))
self:Disable()
</OnLoad>
<OnClick>
PlaySound("igMainMenuOptionCheckBoxOn")
KeyBindings_Unbind()
</OnClick>
</Scripts>
</Button>
<!-- ── The page area SCROLLS (the real-scroll kit, decision 0112) ────────────
The body below IS this frame's scroll child, so every page, row, search
head and keybinding capsule under it pans and CLIPS to the visible page
rect instead of drawing past the window. 0950's header said "MinimalScrollBar
arrives when one outgrows the space"; the SEARCH is what outgrew it first —
a query that matches across categories chains five group heads and every
matched row into one column, which for a broad query runs ~150 units past
the body's own bottom and out through the dialog border onto the world
(B217). The era's answer is the same one: its SettingsList is a ScrollBox
with a MinimalScrollBar (Blizzard_SettingsList.xml l.48-58), and results and
pages ride the same one.
The body is sized by OptionsScroll_Fit each frame: normally EXACTLY this
frame's rect — range 0, no bar, every page seated where it has always been,
including the Keybindings page that fills the body and runs its own faux
list inside it — and grown to the measured content only when a layout
outruns it. So nothing about the fitting pages moves; the scroll appears
only where the overflow is.
enableMouse so a wheel spin over blank page area lands here rather than
falling through to the window (hit-testing only sees mouse-enabled frames);
a spin over the Keybindings page still hits ITS handler first, which is the
nearer ancestor, so the inner list keeps the wheel where it owns it. -->
<ScrollFrame name="$parentScroll" enableMouse="true">
<Anchors>
<Anchor point="TOPLEFT"><Offset><AbsDimension x="0" y="-50"/></Offset></Anchor>
<Anchor point="BOTTOMRIGHT"/>
</Anchors>
<Scripts>
<!-- The loader fires OnLoad bottom-up, so the body already exists. -->
<OnLoad>self:SetScrollChild(OptionsFrameContainerBody)</OnLoad>
<OnUpdate>OptionsScroll_Fit()</OnUpdate>
<OnMouseWheel>BenillaScroll_OnMouseWheel(this, arg1)</OnMouseWheel>
<OnVerticalScroll>BenillaScroll_SyncBar(this)</OnVerticalScroll>
<OnScrollRangeChanged>BenillaScroll_SyncBar(this)</OnScrollRangeChanged>
</Scripts>
<Frames>
<!-- The bar's trough (the shared kit's). No anchors: it seats itself on
the bar named by dropping this frame's "Trough" suffix, at the ref's
own hang — the socket law is one law and lives in
BenillaScrollTrough_Seat, not restated here. That puts it in the
SAME gutter the Keybindings page's own bar uses — the two are never
live together (this one's range is 0 on that page, since the page
fills the body), so the gutter holds exactly one bar at a time.
Declared before the body and the bar so it is beneath both by
link-stamp as well as by layer. -->
<Frame name="$parentBarTrough" inherits="BenillaScrollTroughTemplate"/>
<!-- The page body: one child frame per category with rows, shown by
OptionsFrame_SelectCategory (Audio 0957, Graphics 0959). Named in
full rather than `$parent…`: it keeps the name every page, row and
search seat is derived from, across the scroll frame that now sits
between it and the container. Its SIZE is authored here because a
scroll child's anchors are the engine's (SetScrollChild pins it
TOPLEFT, offset by the scroll — layout.rs) and only its own width
and height are read; these are the container's own 665 x (613-50),
the seat it held before, and OptionsScroll_Fit keeps them true
against the live rect from there on. -->
<Frame name="OptionsFrameContainerBody">
<Size><AbsDimension x="665" y="563"/></Size>
<Layers>
<Layer level="OVERLAY">
<!-- The empty-search line (era SETTINGS_SEARCH_NOTHING_FOUND
on the SettingsListSectionHeaderTemplate seat:
GameFontHighlightLarge, the header's (7,-16) inside the
first 45-tall slot -> (17,-28) off the body with the
layout pads). Shown only by a no-match search. -->
<FontString name="$parentNoResults" inherits="GameFontHighlightLarge" justifyH="LEFT" hidden="true" text="No results found.">
<Anchors><Anchor point="TOPLEFT"><Offset><AbsDimension x="17" y="-28"/></Offset></Anchor></Anchors>
</FontString>
</Layer>
</Layers>
<Frames>
<!-- The Controls page (0961): the gameplay checkboxes with benilla
knobs behind them, in the era Controls page's own row order
(Controls.lua @1.15.9: Sticky Targeting #1, Auto Loot #6,
Invert Mouse #12 — its Mouse section header is cut while one
row survives under it). Sticky Targeting is value-INVERTED in
BOTH references (1.12 UIOptionsFrame checks it on
deselectOnClick=="0"; era wraps the CVar in NegateBoolean);
labels are 1.12 GlobalStrings where the row exists there
(GAMEFIELD_DESELECT_TEXT, INVERT_MOUSE), era's for Auto Loot
(no 1.12 row exists) — and the tooltip keys are those same
1.12 keys through the panel's own `_TEXT`-stripping lookup
(UIOptionsFrame.lua l.265), which is why Sticky Targeting's
description is GAMEFIELD_DESELECT's. -->
<Frame name="$parentControls" hidden="true">
<Anchors>
<Anchor point="TOPLEFT"/>
<Anchor point="BOTTOMRIGHT"/>
</Anchors>
<Frames>
<Frame name="$parentRowStickyTarget" inherits="OptionsCheckboxRowTemplate">
<Anchors>
<Anchor point="TOPLEFT"><Offset><AbsDimension x="10" y="-12"/></Offset></Anchor>
<Anchor point="TOPRIGHT"><Offset><AbsDimension x="-20" y="-12"/></Offset></Anchor>
</Anchors>
<Scripts><OnLoad>
OptionsRow_OnLoad(self, "deselectOnClick", "Sticky Targeting",
"OPTION_TOOLTIP_GAMEFIELD_DESELECT")
self.invert = 1
</OnLoad></Scripts>
</Frame>
<!-- Block Trades (decision 1764). Seated here because
this page is where 1.12's Basic Options GENERAL box
lands, and Sticky Targeting above is the other row we
ship from it — the reference's own box order runs 45
(Sticky Targeting) down the left column and 14 (this)
down the right, so the two belong adjacent when the
box is flattened to one column. Its tooltip key IS the
reference's, unlike the row above. -->
<Frame name="$parentRowBlockTrades" inherits="OptionsCheckboxRowTemplate">
<Anchors>
<Anchor point="TOPLEFT" relativeTo="$parentRowStickyTarget" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="0" y="-9"/></Offset></Anchor>
<Anchor point="TOPRIGHT" relativeTo="$parentRowStickyTarget" relativePoint="BOTTOMRIGHT"><Offset><AbsDimension x="0" y="-9"/></Offset></Anchor>
</Anchors>
<Scripts><OnLoad>OptionsRow_OnLoad(self, "BlockTrades", "Block Trades",
"OPTION_TOOLTIP_BLOCK_TRADES")</OnLoad></Scripts>
</Frame>
<Frame name="$parentRowAutoLoot" inherits="OptionsCheckboxRowTemplate">
<Anchors>
<Anchor point="TOPLEFT" relativeTo="$parentRowBlockTrades" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="0" y="-9"/></Offset></Anchor>
<Anchor point="TOPRIGHT" relativeTo="$parentRowBlockTrades" relativePoint="BOTTOMRIGHT"><Offset><AbsDimension x="0" y="-9"/></Offset></Anchor>
</Anchors>
<!-- No tip: 1.12 has no Auto Loot row, so no
OPTION_TOOLTIP_* for it either (verified — the
5875 GlobalStrings has no AUTO_LOOT key at all,
and no 1.12 panel names the CVar). -->
<Scripts><OnLoad>OptionsRow_OnLoad(self, "autoLootDefault", "Auto Loot")</OnLoad></Scripts>
</Frame>
<Frame name="$parentRowInvertMouse" inherits="OptionsCheckboxRowTemplate">
<Anchors>
<Anchor point="TOPLEFT" relativeTo="$parentRowAutoLoot" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="0" y="-9"/></Offset></Anchor>
<Anchor point="TOPRIGHT" relativeTo="$parentRowAutoLoot" relativePoint="BOTTOMRIGHT"><Offset><AbsDimension x="0" y="-9"/></Offset></Anchor>
</Anchors>
<Scripts><OnLoad>OptionsRow_OnLoad(self, "mouseInvertPitch", "Invert Mouse",
"OPTION_TOOLTIP_INVERT_MOUSE")</OnLoad></Scripts>
</Frame>
<!-- Mouse Sensitivity (decision 1140). Last because 1.12
puts its four sliders in their own box BELOW the
checkbox columns (UIOptionsFrameSliders), and this is
the only one of the four whose knob we have: the other
three drive auto-follow yaw speed, mouse-look yaw speed
and the max-follow-distance factor, none of which our
camera carries yet. Range and step are the reference's
own (0.5 .. 1.5 by 0.05); the percent readout is the
era's default and reads a multiplier exactly right. -->
<Frame name="$parentRowMouseSpeed" inherits="OptionsSliderRowTemplate">
<Anchors>
<Anchor point="TOPLEFT" relativeTo="$parentRowInvertMouse" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="0" y="-9"/></Offset></Anchor>
<Anchor point="TOPRIGHT" relativeTo="$parentRowInvertMouse" relativePoint="BOTTOMRIGHT"><Offset><AbsDimension x="0" y="-9"/></Offset></Anchor>
</Anchors>
<Scripts><OnLoad>
OptionsRow_OnLoad(self, "mousespeed", "Mouse Sensitivity",
"OPTION_TOOLTIP_MOUSE_SENSITIVITY")
OptionsSliderRow_Setup(self, 0.5, 1.5, 0.05)
</OnLoad></Scripts>
</Frame>
<!-- Max Camera Distance (decision 1140), the second of
1.12's four sliders whose knob we have. The stored
value is the FACTOR over `cameraDistanceMax`'s 15 yd
base — the reference's own arrangement, and the reason
the readout is raw yards here rather than a percent:
"22" means the wheel stops at 22 yards, which is the
thing being chosen. The slider rests at 1.0 (15 yd),
1.12's own registered default — it sat at the top of
the range (30 yd) from 1140 until 1804. -->
<!-- Camera Following Style (1493, values re-pinned by
1502): 1.12's own dropdown
(UIOptionsFrameCameraDropDown) in its own order —
Smart, Always, Never — each entry carrying the
reference's own description key (keyed by POSITION,
OPTION_TOOLTIP_CAMERA1..3) so the row's plate changes
with the selection, the way that dropdown's does.
The VALUES are the ENGINE's, not that dropdown's: the
reference writes 1/2/3 and 3 is not a style at all —
its validator accepts it while the terrain-tilt
consumer indexes 360 bytes off the end of its table.
0/1/2 is what the client's own tables are indexed by
(wow-re camera-smooth-style.md §2/§4). Seated at the
head of the camera block because it decides whether
the camera follows at all; the distance slider under
it decides how far. -->
<Frame name="$parentRowCameraFollowStyle" inherits="OptionsDropdownRowTemplate">
<Anchors>
<Anchor point="TOPLEFT" relativeTo="$parentRowMouseSpeed" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="0" y="-9"/></Offset></Anchor>
<Anchor point="TOPRIGHT" relativeTo="$parentRowMouseSpeed" relativePoint="BOTTOMRIGHT"><Offset><AbsDimension x="0" y="-9"/></Offset></Anchor>
</Anchors>
<Scripts><OnLoad>
OptionsRow_OnLoad(self, "cameraSmoothStyle",
"Camera Following Style", "OPTION_TOOLTIP_CAMERA1")
OptionsDropdownRow_Setup(self, {
{ text = "Smart", value = "1", tip = "OPTION_TOOLTIP_CAMERA1" },
{ text = "Always", value = "2", tip = "OPTION_TOOLTIP_CAMERA2" },
{ text = "Never", value = "0", alt = "3", tip = "OPTION_TOOLTIP_CAMERA3" },
})
</OnLoad></Scripts>
</Frame>
<Frame name="$parentRowMaxCameraDistance" inherits="OptionsSliderRowTemplate">
<Anchors>
<Anchor point="TOPLEFT" relativeTo="$parentRowCameraFollowStyle" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="0" y="-9"/></Offset></Anchor>
<Anchor point="TOPRIGHT" relativeTo="$parentRowCameraFollowStyle" relativePoint="BOTTOMRIGHT"><Offset><AbsDimension x="0" y="-9"/></Offset></Anchor>
</Anchors>
<Scripts><OnLoad>
OptionsRow_OnLoad(self, "cameraDistanceMaxFactor",
"Max Camera Distance", "OPTION_TOOLTIP_MAX_FOLLOW_DIST")
OptionsSliderRow_Setup(self, 1.0, 2.0, 0.1, "yards")
</OnLoad></Scripts>
</Frame>
</Frames>
</Frame>
<!-- The Audio page. Row seats transcribe the era settings list
(Blizzard_SettingsList.lua l.43-47 + .xml: scroll box hung
-15/-20 off the header block with padLeft 25 / padRight 0 and
verticalPad 10 → first row at (10,-12); spacing 9 → 35 pitch).
Row ORDER is the era Audio page's grammar (volume block first,
then the enables) over the 1.12 row set: the three category
volumes ride as CHILDREN of Master Volume exactly like era's,
in the 1.12 panel's own slider order (Sound, Music, Ambience —
SoundOptionsFrame.lua's index order). -->
<Frame name="$parentAudio" hidden="true">
<Anchors>
<Anchor point="TOPLEFT"/>
<Anchor point="BOTTOMRIGHT"/>
</Anchors>
<Frames>
<Frame name="$parentRowEnableAll" inherits="OptionsCheckboxRowTemplate">
<Anchors>
<Anchor point="TOPLEFT"><Offset><AbsDimension x="10" y="-12"/></Offset></Anchor>
<Anchor point="TOPRIGHT"><Offset><AbsDimension x="-20" y="-12"/></Offset></Anchor>
</Anchors>
<Scripts><OnLoad>
OptionsRow_OnLoad(self, "MasterSoundEffects", "Enable All Sound",
"OPTION_TOOLTIP_ENABLE_ALL_SOUND")
self.soundQuirk = 1
</OnLoad></Scripts>
</Frame>
<Frame name="$parentRowMaster" inherits="OptionsSliderRowTemplate">
<Anchors>
<Anchor point="TOPLEFT" relativeTo="$parentRowEnableAll" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="0" y="-9"/></Offset></Anchor>
<Anchor point="TOPRIGHT" relativeTo="$parentRowEnableAll" relativePoint="BOTTOMRIGHT"><Offset><AbsDimension x="0" y="-9"/></Offset></Anchor>
</Anchors>
<Scripts><OnLoad>OptionsRow_OnLoad(self, "MasterVolume", "Master Volume",
"OPTION_TOOLTIP_MASTER_VOLUME")</OnLoad></Scripts>
</Frame>
<Frame name="$parentRowSound" inherits="OptionsSliderRowTemplate">
<Anchors>
<Anchor point="TOPLEFT" relativeTo="$parentRowMaster" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="0" y="-9"/></Offset></Anchor>
<Anchor point="TOPRIGHT" relativeTo="$parentRowMaster" relativePoint="BOTTOMRIGHT"><Offset><AbsDimension x="0" y="-9"/></Offset></Anchor>
</Anchors>
<Scripts><OnLoad>
OptionsRow_OnLoad(self, "SoundVolume", "Sound Volume",
"OPTION_TOOLTIP_SOUND_VOLUME", 1)
-- era parentInitializer (search pulls Master in
-- above a matched child, SettingsPanel.lua l.665)
self.parentRow = "RowMaster"
</OnLoad></Scripts>
</Frame>
<Frame name="$parentRowMusic" inherits="OptionsSliderRowTemplate">
<Anchors>
<Anchor point="TOPLEFT" relativeTo="$parentRowSound" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="0" y="-9"/></Offset></Anchor>
<Anchor point="TOPRIGHT" relativeTo="$parentRowSound" relativePoint="BOTTOMRIGHT"><Offset><AbsDimension x="0" y="-9"/></Offset></Anchor>
</Anchors>
<Scripts><OnLoad>
OptionsRow_OnLoad(self, "MusicVolume", "Music Volume",
"OPTION_TOOLTIP_MUSIC_VOLUME", 1)
self.parentRow = "RowMaster"
</OnLoad></Scripts>
</Frame>
<Frame name="$parentRowAmbience" inherits="OptionsSliderRowTemplate">
<Anchors>
<Anchor point="TOPLEFT" relativeTo="$parentRowMusic" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="0" y="-9"/></Offset></Anchor>
<Anchor point="TOPRIGHT" relativeTo="$parentRowMusic" relativePoint="BOTTOMRIGHT"><Offset><AbsDimension x="0" y="-9"/></Offset></Anchor>
</Anchors>
<Scripts><OnLoad>
OptionsRow_OnLoad(self, "AmbienceVolume", "Ambience Volume",
"OPTION_TOOLTIP_AMBIENCE_VOLUME", 1)
self.parentRow = "RowMaster"
</OnLoad></Scripts>
</Frame>
<Frame name="$parentRowEnableMusic" inherits="OptionsCheckboxRowTemplate">
<Anchors>
<Anchor point="TOPLEFT" relativeTo="$parentRowAmbience" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="0" y="-9"/></Offset></Anchor>
<Anchor point="TOPRIGHT" relativeTo="$parentRowAmbience" relativePoint="BOTTOMRIGHT"><Offset><AbsDimension x="0" y="-9"/></Offset></Anchor>
</Anchors>
<Scripts><OnLoad>
OptionsRow_OnLoad(self, "EnableMusic", "Enable Music",
"OPTION_TOOLTIP_ENABLE_MUSIC")
self.soundQuirk = 1
</OnLoad></Scripts>
</Frame>
<Frame name="$parentRowEnableAmbience" inherits="OptionsCheckboxRowTemplate">
<Anchors>
<Anchor point="TOPLEFT" relativeTo="$parentRowEnableMusic" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="0" y="-9"/></Offset></Anchor>
<Anchor point="TOPRIGHT" relativeTo="$parentRowEnableMusic" relativePoint="BOTTOMRIGHT"><Offset><AbsDimension x="0" y="-9"/></Offset></Anchor>
</Anchors>
<Scripts><OnLoad>
OptionsRow_OnLoad(self, "EnableAmbience", "Enable Ambience",
"OPTION_TOOLTIP_ENABLE_AMBIENCE")
self.soundQuirk = 1
</OnLoad></Scripts>
</Frame>
<!-- 1.12's fourth Sound checkbox, ENABLE_ERROR_SPEECH
(SoundOptionsFrame.lua l.2) — the race/sex refusal
lines your character says aloud, decision 1815. Its
label and tooltip are the reference's own
GlobalStrings entries. -->
<Frame name="$parentRowEnableErrorSpeech" inherits="OptionsCheckboxRowTemplate">
<Anchors>
<Anchor point="TOPLEFT" relativeTo="$parentRowEnableAmbience" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="0" y="-9"/></Offset></Anchor>
<Anchor point="TOPRIGHT" relativeTo="$parentRowEnableAmbience" relativePoint="BOTTOMRIGHT"><Offset><AbsDimension x="0" y="-9"/></Offset></Anchor>
</Anchors>
<Scripts><OnLoad>
OptionsRow_OnLoad(self, "EnableErrorSpeech", "Enable Error Speech",
"OPTION_TOOLTIP_ENABLE_ERROR_SPEECH")
self.soundQuirk = 1
</OnLoad></Scripts>
</Frame>
<!-- Sound in the background (decision 1847). The one
Audio row 1.12 has no checkbox for: it mutes on its
window-activation event unconditionally, so this row
offers the way out the reference never did, shipped
OFF at the reference's own behaviour. Era's label and
era's CVar spelling, the autoLootDefault posture.
soundQuirk like every box on this page - the mapping
belongs to the panel, not to the row's provenance. -->
<Frame name="$parentRowBackgroundSound" inherits="OptionsCheckboxRowTemplate">
<Anchors>
<Anchor point="TOPLEFT" relativeTo="$parentRowEnableErrorSpeech" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="0" y="-9"/></Offset></Anchor>
<Anchor point="TOPRIGHT" relativeTo="$parentRowEnableErrorSpeech" relativePoint="BOTTOMRIGHT"><Offset><AbsDimension x="0" y="-9"/></Offset></Anchor>
</Anchors>
<Scripts><OnLoad>
OptionsRow_OnLoad(self, "Sound_EnableSoundWhenGameIsInBG",
"Enable Sound in Background",
"BENILLA_TOOLTIP_BACKGROUND_SOUND")
self.soundQuirk = 1
</OnLoad></Scripts>
</Frame>
</Frames>
</Frame>
<!-- The Graphics page (0959): the 1.12 OptionsFrameSliders rows
that have a benilla knob behind them, in the table's own order
(OptionsFrame.lua l.25-27). UI Scale: uiscale 0.64..1.0 step
0.01 (UI_SCALE "UI Scale", percent readout — era's own
FormatPercentageRounded on its uiscale row); 1.12's useUiScale
gate has no benilla knob (our scale always applies; off ≡ 1.0)
so the checkbox doesn't ship. Terrain Distance: farclip
177..777 step 60 = (max-min)/10 (FARCLIP "Terrain Distance",
raw-yards readout — era's law when a slider has no formatter);
retired by the director in 0961 and back by their call in
1513, now that the one number moves the far-clip wall AND how
much terrain is resident, as it does in the reference.
Environment Detail is the same table's WorldDetail row
(0..2 step 1, label ENVIRONMENT_DETAIL) over the clutter-density
knob. 0992 wore it as a dropdown; 1649 puts it back on the
SLIDER the reference actually uses, keeping 0992's stop names
in the readout seat. -->
<Frame name="$parentGraphics" hidden="true">
<Anchors>
<Anchor point="TOPLEFT"/>
<Anchor point="BOTTOMRIGHT"/>
</Anchors>
<Frames>
<Frame name="$parentRowUiScale" inherits="OptionsSliderRowTemplate">
<Anchors>
<Anchor point="TOPLEFT"><Offset><AbsDimension x="10" y="-12"/></Offset></Anchor>
<Anchor point="TOPRIGHT"><Offset><AbsDimension x="-20" y="-12"/></Offset></Anchor>
</Anchors>
<Scripts><OnLoad>
OptionsRow_OnLoad(self, "uiScale", "UI Scale",
"OPTION_TOOLTIP_UI_SCALE")
OptionsSliderRow_Setup(self, OptionsFrameSliders[1].minValue,
OptionsFrameSliders[1].maxValue,
OptionsFrameSliders[1].valueStep)
-- era registers its uiscale row CommitFlag.Apply
-- (Graphics.lua l.684): the one row whose live
-- write would rescale the very slider mid-drag —
-- it stages and commits through the Apply button.
self.deferred = 1
</OnLoad></Scripts>
</Frame>
<Frame name="$parentRowFarclip" inherits="OptionsSliderRowTemplate">
<Anchors>
<Anchor point="TOPLEFT" relativeTo="$parentRowUiScale" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="0" y="-9"/></Offset></Anchor>
<Anchor point="TOPRIGHT" relativeTo="$parentRowUiScale" relativePoint="BOTTOMRIGHT"><Offset><AbsDimension x="0" y="-9"/></Offset></Anchor>
</Anchors>
<Scripts><OnLoad>
OptionsRow_OnLoad(self, "farclip", "Terrain Distance",
"OPTION_TOOLTIP_FARCLIP")
OptionsSliderRow_Setup(self, OptionsFrameSliders[2].minValue,
OptionsFrameSliders[2].maxValue,
OptionsFrameSliders[2].valueStep, "raw")
</OnLoad></Scripts>
</Frame>
<Frame name="$parentRowWorldDetail" inherits="OptionsSliderRowTemplate">
<Anchors>
<Anchor point="TOPLEFT" relativeTo="$parentRowFarclip" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="0" y="-9"/></Offset></Anchor>
<Anchor point="TOPRIGHT" relativeTo="$parentRowFarclip" relativePoint="BOTTOMRIGHT"><Offset><AbsDimension x="0" y="-9"/></Offset></Anchor>
</Anchors>
<Scripts><OnLoad>
OptionsRow_OnLoad(self, "WorldDetail", "Environment Detail",
"OPTION_TOOLTIP_ENVIRONMENT_DETAIL")
-- The stop names are the era preset ladder's (its
-- quality dropdowns say Low/Medium/High); 1.12's
-- slider had no stop labels to quote.
OptionsSliderRow_Setup(self, OptionsFrameSliders[3].minValue,
OptionsFrameSliders[3].maxValue,
OptionsFrameSliders[3].valueStep, nil,
{ "Low", "Medium", "High" })
</OnLoad></Scripts>
</Frame>
<Frame name="$parentRowDisplayMode" inherits="OptionsDropdownRowTemplate">
<Anchors>
<Anchor point="TOPLEFT" relativeTo="$parentRowWorldDetail" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="0" y="-9"/></Offset></Anchor>
<Anchor point="TOPRIGHT" relativeTo="$parentRowWorldDetail" relativePoint="BOTTOMRIGHT"><Offset><AbsDimension x="0" y="-9"/></Offset></Anchor>
</Anchors>
<Scripts><OnLoad>
-- Display Mode (1650): MODERN CLASSIC's row, not
-- 1.12's, because 1627 already gave us modern
-- Classic's two states and a checkbox could only
-- name one of them.
--
-- Blizzard_SettingsDefinitions_Shared/Graphics.lua
-- (live, classic AND classic_era, all identical):
-- a BOOLEAN proxy rendered as a dropdown, with
-- exactly two entries -- container:Add(true,
-- VIDEO_OPTIONS_WINDOWED_FULLSCREEN) and
-- container:Add(false, VIDEO_OPTIONS_WINDOWED).
-- There is no Fullscreen entry anywhere in it;
-- 8.0.1 removed the exclusive mode those clients
-- had, which is the same reason 1627 ships none.
--
-- It rides gxWindow, OUR persisted CVar, not the
-- gxMaximize the modern client uses -- that client
-- deleted gxWindow entirely and had to move; we
-- did not, and repointing would orphan every
-- config.toml already holding the row for nothing.
-- Polarity is gxWindow's own: "0" is not-windowed,
-- which 1627 redefined as the borderless window.
--
-- Not deferred, for the same reason as Vertical
-- Sync below: the reference row carries
-- gxRestart = 1 and ours applies on the click.
OptionsRow_OnLoad(self, "gxWindow", "Display Mode",
"BENILLA_TOOLTIP_DISPLAY_MODE")
OptionsDropdownRow_Setup(self, {
{ text = "Windowed (Fullscreen)", value = "0" },
{ text = "Windowed", value = "1" },
})
</OnLoad></Scripts>
</Frame>
<Frame name="$parentRowVerticalSync" inherits="OptionsCheckboxRowTemplate">
<Anchors>
<Anchor point="TOPLEFT" relativeTo="$parentRowDisplayMode" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="0" y="-9"/></Offset></Anchor>
<Anchor point="TOPRIGHT" relativeTo="$parentRowDisplayMode" relativePoint="BOTTOMRIGHT"><Offset><AbsDimension x="0" y="-9"/></Offset></Anchor>
</Anchors>
<Scripts><OnLoad>
-- Vertical Sync: 1.12's own Video Options checkbox
-- 5 (gxVSync), over the window's present mode.
-- The reference row carries gxRestart = 1; ours
-- does not and must not be marked deferred --
-- wgpu swaps the presentation interval live, so
-- the box takes effect as you click it.
OptionsRow_OnLoad(self, "gxVSync", "Vertical Sync",
"OPTION_TOOLTIP_VERTICAL_SYNC")
</OnLoad></Scripts>
</Frame>
<Frame name="$parentRowRenderScale" inherits="OptionsSliderRowTemplate">
<Anchors>
<Anchor point="TOPLEFT" relativeTo="$parentRowVerticalSync" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="0" y="-9"/></Offset></Anchor>
<Anchor point="TOPRIGHT" relativeTo="$parentRowVerticalSync" relativePoint="BOTTOMRIGHT"><Offset><AbsDimension x="0" y="-9"/></Offset></Anchor>
</Anchors>
<Scripts><OnLoad>
-- Render Scale (decision 1639) -- benilla's own
-- row, no 1.12 counterpart: the reference renders
-- straight to the backbuffer and has no second
-- image to size. It CLOSES the page, and it is
-- the only antialiasing control on it: the
-- reference's own Multisampling row was pulled
-- 2026-08-27 (decision 1648, the director's call).
-- gxMultisample survives as a CVar and $WOW_MSAA
-- -- what went is the row, because multisampling
-- cannot touch the alpha-tested cutouts that carry
-- most of this game's visible aliasing, and above
-- 100% this row can.
--
-- 50-200%, narrower than the CVar's own clamp
-- (RENDER_SCALE_RANGE, 0.25-4.0). That clamp stops
-- an absurd value and leaves room for the
-- supersampling instrument; this is what a player
-- should be offered, and it is where every engine
-- puts the same slider.
--
-- DEFERRED, and for the reason the flag exists.
-- Committing live would rebuild the world's render
-- target on every drag tick -- tens of megabytes,
-- thirty times across one sweep of the handle.
-- Staging to Apply makes that exactly one rebuild,
-- which is the same answer uiScale's row already
-- reaches for a different reason.
OptionsRow_OnLoad(self, "renderScale", "Render Scale",
"BENILLA_TOOLTIP_RENDER_SCALE")
OptionsSliderRow_Setup(self, 0.5, 2.0, 0.05)
self.deferred = 1
</OnLoad></Scripts>
</Frame>
</Frames>
<!-- Re-apply the slider bounds from OptionsFrameSliders every time the page is shown.
The rows read the table at OnLoad, which is long before any addon runs; an addon that
writes a bound (pfUI's hdgraphic raises Environment Detail's maxValue to 15) would
otherwise never move the slider it just rewrote. The reference does the same loop in
OptionsFrame_Load; ours lives here rather than under that name because our page
implements almost none of the rest of that function's contract and publishing a
reference name over a partial body is how a gap hides.
fmt and stops are read back off the row rather than the table: they are OUR readout
grammar (0992/1649's stop names), set at OnLoad, and no addon addresses them. -->
<Scripts>
<OnShow>
for i = 1, table.getn(OptionsFrameSliders) do
local info = OptionsFrameSliders[i]
local row = info.benillaRow and getglobal(this:GetName() .. info.benillaRow)
if ( row ) then
OptionsSliderRow_Setup(row, info.minValue, info.maxValue,
info.valueStep, row.fmt, row.stops)
end
end
</OnShow>
</Scripts>
</Frame>
<!-- The COMBAT page (decision 1134) — the first page whose rows
write saved-variable GLOBALS rather than CVars, and the first
thing that can change any of the ~20 stock globals 1128
ported. It is 1.12's AdvancedOptionsCombatText box, whole: the
master, the scroll-mode dropdown, and the twelve sub-toggles,
in the reference box's own anchor-chain order, with its
GlobalStrings labels and OPTION_TOOLTIP_* keys verbatim and
each sub-toggle at the reference's default (the assignment in
CombatText.xml, which is also where the Defaults button reads
from). Every row runs CombatText_UpdateDisplayedMessages as
its applyFunc — the reference's own post-Save call, moved to
the click because this window writes live (0961). -->
<Frame name="$parentCombat" hidden="true">
<Anchors>
<Anchor point="TOPLEFT"/>
<Anchor point="BOTTOMRIGHT"/>
</Anchors>
<Frames>
<Frame name="$parentRowCombatText" inherits="OptionsCheckboxRowTemplate">
<Anchors>
<Anchor point="TOPLEFT"><Offset><AbsDimension x="10" y="-12"/></Offset></Anchor>
<Anchor point="TOPRIGHT"><Offset><AbsDimension x="-20" y="-12"/></Offset></Anchor>
</Anchors>
<Scripts><OnLoad>
OptionsGlobalRow_OnLoad(self, "SHOW_COMBAT_TEXT",
"Enable Floating Combat Text", "OPTION_TOOLTIP_SHOW_COMBAT_TEXT")
self.applyFunc = "CombatText_UpdateOrLoad" -- by NAME, resolved when applied: this file loads before its definer now (1938)
</OnLoad></Scripts>
</Frame>
<!-- The scroll mode. 1.12's own dropdown, values and all
(UIOptionsFrameCombatTextDropDown_Initialize: "1" up,
"2" down, "3" arc), worn as an era dropdown row. -->
<Frame name="$parentRowFloatMode" inherits="OptionsDropdownRowTemplate">
<Anchors>
<Anchor point="TOPLEFT" relativeTo="$parentRowCombatText" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="0" y="-9"/></Offset></Anchor>
<Anchor point="TOPRIGHT" relativeTo="$parentRowCombatText" relativePoint="BOTTOMRIGHT"><Offset><AbsDimension x="0" y="-9"/></Offset></Anchor>
</Anchors>
<Scripts><OnLoad>
OptionsGlobalRow_OnLoad(self, "COMBAT_TEXT_FLOAT_MODE",
"Combat Text Float Mode", "OPTION_TOOLTIP_COMBAT_TEXT_MODE", 1)
OptionsDropdownRow_Setup(self, {
{ text = "Scroll Up", value = "1" },
{ text = "Scroll Down", value = "2" },
{ text = "Arc", value = "3" },
})
self.parentRow = "RowCombatText"
self.applyFunc = "CombatText_UpdateOrLoad" -- by NAME, resolved when applied: this file loads before its definer now (1938)
</OnLoad></Scripts>
</Frame>
<Frame name="$parentRowLowHealthMana" inherits="OptionsCheckboxRowTemplate">
<Anchors>
<Anchor point="TOPLEFT" relativeTo="$parentRowFloatMode" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="0" y="-9"/></Offset></Anchor>
<Anchor point="TOPRIGHT" relativeTo="$parentRowFloatMode" relativePoint="BOTTOMRIGHT"><Offset><AbsDimension x="0" y="-9"/></Offset></Anchor>
</Anchors>
<Scripts><OnLoad>
OptionsGlobalRow_OnLoad(self, "COMBAT_TEXT_SHOW_LOW_HEALTH_MANA",
"Show Low Mana and Health",
"OPTION_TOOLTIP_COMBAT_TEXT_SHOW_LOW_HEALTH_MANA", 1)
self.parentRow = "RowCombatText"
self.applyFunc = "CombatText_UpdateOrLoad" -- by NAME, resolved when applied: this file loads before its definer now (1938)
</OnLoad></Scripts>
</Frame>
<Frame name="$parentRowAuras" inherits="OptionsCheckboxRowTemplate">
<Anchors>
<Anchor point="TOPLEFT" relativeTo="$parentRowLowHealthMana" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="0" y="-9"/></Offset></Anchor>
<Anchor point="TOPRIGHT" relativeTo="$parentRowLowHealthMana" relativePoint="BOTTOMRIGHT"><Offset><AbsDimension x="0" y="-9"/></Offset></Anchor>
</Anchors>
<Scripts><OnLoad>
OptionsGlobalRow_OnLoad(self, "COMBAT_TEXT_SHOW_AURAS",
"Show Auras", "OPTION_TOOLTIP_COMBAT_TEXT_SHOW_AURAS", 1)
self.parentRow = "RowCombatText"
self.applyFunc = "CombatText_UpdateOrLoad" -- by NAME, resolved when applied: this file loads before its definer now (1938)
</OnLoad></Scripts>
</Frame>
<Frame name="$parentRowAuraFade" inherits="OptionsCheckboxRowTemplate">
<Anchors>
<Anchor point="TOPLEFT" relativeTo="$parentRowAuras" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="0" y="-9"/></Offset></Anchor>
<Anchor point="TOPRIGHT" relativeTo="$parentRowAuras" relativePoint="BOTTOMRIGHT"><Offset><AbsDimension x="0" y="-9"/></Offset></Anchor>
</Anchors>
<Scripts><OnLoad>
OptionsGlobalRow_OnLoad(self, "COMBAT_TEXT_SHOW_AURA_FADE",
"Show Fading Auras", "OPTION_TOOLTIP_COMBAT_TEXT_SHOW_AURA_FADE", 1)
self.parentRow = "RowCombatText"
self.applyFunc = "CombatText_UpdateOrLoad" -- by NAME, resolved when applied: this file loads before its definer now (1938)
</OnLoad></Scripts>
</Frame>
<Frame name="$parentRowCombatState" inherits="OptionsCheckboxRowTemplate">
<Anchors>
<Anchor point="TOPLEFT" relativeTo="$parentRowAuraFade" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="0" y="-9"/></Offset></Anchor>
<Anchor point="TOPRIGHT" relativeTo="$parentRowAuraFade" relativePoint="BOTTOMRIGHT"><Offset><AbsDimension x="0" y="-9"/></Offset></Anchor>
</Anchors>
<Scripts><OnLoad>
OptionsGlobalRow_OnLoad(self, "COMBAT_TEXT_SHOW_COMBAT_STATE",
"Show Combat State",
"OPTION_TOOLTIP_COMBAT_TEXT_SHOW_COMBAT_STATE", 1)
self.parentRow = "RowCombatText"
self.applyFunc = "CombatText_UpdateOrLoad" -- by NAME, resolved when applied: this file loads before its definer now (1938)
</OnLoad></Scripts>
</Frame>
<Frame name="$parentRowDodgeParryMiss" inherits="OptionsCheckboxRowTemplate">
<Anchors>
<Anchor point="TOPLEFT" relativeTo="$parentRowCombatState" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="0" y="-9"/></Offset></Anchor>
<Anchor point="TOPRIGHT" relativeTo="$parentRowCombatState" relativePoint="BOTTOMRIGHT"><Offset><AbsDimension x="0" y="-9"/></Offset></Anchor>
</Anchors>
<Scripts><OnLoad>
OptionsGlobalRow_OnLoad(self, "COMBAT_TEXT_SHOW_DODGE_PARRY_MISS",
"Show Dodge/Parries/Misses",
"OPTION_TOOLTIP_COMBAT_TEXT_SHOW_DODGE_PARRY_MISS", 1)
self.parentRow = "RowCombatText"
self.applyFunc = "CombatText_UpdateOrLoad" -- by NAME, resolved when applied: this file loads before its definer now (1938)
</OnLoad></Scripts>
</Frame>
<Frame name="$parentRowResistances" inherits="OptionsCheckboxRowTemplate">
<Anchors>
<Anchor point="TOPLEFT" relativeTo="$parentRowDodgeParryMiss" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="0" y="-9"/></Offset></Anchor>
<Anchor point="TOPRIGHT" relativeTo="$parentRowDodgeParryMiss" relativePoint="BOTTOMRIGHT"><Offset><AbsDimension x="0" y="-9"/></Offset></Anchor>
</Anchors>
<Scripts><OnLoad>
OptionsGlobalRow_OnLoad(self, "COMBAT_TEXT_SHOW_RESISTANCES",
"Show Damage Reduction",
"OPTION_TOOLTIP_COMBAT_TEXT_SHOW_RESISTANCES", 1)
self.parentRow = "RowCombatText"
self.applyFunc = "CombatText_UpdateOrLoad" -- by NAME, resolved when applied: this file loads before its definer now (1938)
</OnLoad></Scripts>
</Frame>
<Frame name="$parentRowReputation" inherits="OptionsCheckboxRowTemplate">
<Anchors>
<Anchor point="TOPLEFT" relativeTo="$parentRowResistances" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="0" y="-9"/></Offset></Anchor>
<Anchor point="TOPRIGHT" relativeTo="$parentRowResistances" relativePoint="BOTTOMRIGHT"><Offset><AbsDimension x="0" y="-9"/></Offset></Anchor>
</Anchors>
<Scripts><OnLoad>
OptionsGlobalRow_OnLoad(self, "COMBAT_TEXT_SHOW_REPUTATION",
"Show Reputation",
"OPTION_TOOLTIP_COMBAT_TEXT_SHOW_REPUTATION", 1)
self.parentRow = "RowCombatText"
self.applyFunc = "CombatText_UpdateOrLoad" -- by NAME, resolved when applied: this file loads before its definer now (1938)
</OnLoad></Scripts>
</Frame>
<Frame name="$parentRowReactives" inherits="OptionsCheckboxRowTemplate">
<Anchors>
<Anchor point="TOPLEFT" relativeTo="$parentRowReputation" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="0" y="-9"/></Offset></Anchor>
<Anchor point="TOPRIGHT" relativeTo="$parentRowReputation" relativePoint="BOTTOMRIGHT"><Offset><AbsDimension x="0" y="-9"/></Offset></Anchor>
</Anchors>
<Scripts><OnLoad>
OptionsGlobalRow_OnLoad(self, "COMBAT_TEXT_SHOW_REACTIVES",
"Show Reactive Spells and Abilities",
"OPTION_TOOLTIP_COMBAT_TEXT_SHOW_REACTIVES", 1)
self.parentRow = "RowCombatText"
self.applyFunc = "CombatText_UpdateOrLoad" -- by NAME, resolved when applied: this file loads before its definer now (1938)
</OnLoad></Scripts>
</Frame>
<Frame name="$parentRowFriendlyNames" inherits="OptionsCheckboxRowTemplate">
<Anchors>
<Anchor point="TOPLEFT" relativeTo="$parentRowReactives" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="0" y="-9"/></Offset></Anchor>
<Anchor point="TOPRIGHT" relativeTo="$parentRowReactives" relativePoint="BOTTOMRIGHT"><Offset><AbsDimension x="0" y="-9"/></Offset></Anchor>
</Anchors>
<Scripts><OnLoad>
OptionsGlobalRow_OnLoad(self, "COMBAT_TEXT_SHOW_FRIENDLY_NAMES",
"Show Friendly Healer Names",
"OPTION_TOOLTIP_COMBAT_TEXT_SHOW_FRIENDLY_NAMES", 1)
self.parentRow = "RowCombatText"
self.applyFunc = "CombatText_UpdateOrLoad" -- by NAME, resolved when applied: this file loads before its definer now (1938)
</OnLoad></Scripts>
</Frame>
<!-- Combo Points carries the reference's SECOND gate: greyed
for anyone who is not a rogue or a druid — see
OptionsPage_UpdateDependencies. -->
<Frame name="$parentRowComboPoints" inherits="OptionsCheckboxRowTemplate">
<Anchors>
<Anchor point="TOPLEFT" relativeTo="$parentRowFriendlyNames" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="0" y="-9"/></Offset></Anchor>
<Anchor point="TOPRIGHT" relativeTo="$parentRowFriendlyNames" relativePoint="BOTTOMRIGHT"><Offset><AbsDimension x="0" y="-9"/></Offset></Anchor>
</Anchors>
<Scripts><OnLoad>
OptionsGlobalRow_OnLoad(self, "COMBAT_TEXT_SHOW_COMBO_POINTS",
"Show Combo Points",
"OPTION_TOOLTIP_COMBAT_TEXT_SHOW_COMBO_POINTS", 1)
self.parentRow = "RowCombatText"
self.applyFunc = "CombatText_UpdateOrLoad" -- by NAME, resolved when applied: this file loads before its definer now (1938)
</OnLoad></Scripts>
</Frame>
<Frame name="$parentRowEnergyGains" inherits="OptionsCheckboxRowTemplate">
<Anchors>
<Anchor point="TOPLEFT" relativeTo="$parentRowComboPoints" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="0" y="-9"/></Offset></Anchor>
<Anchor point="TOPRIGHT" relativeTo="$parentRowComboPoints" relativePoint="BOTTOMRIGHT"><Offset><AbsDimension x="0" y="-9"/></Offset></Anchor>
</Anchors>
<Scripts><OnLoad>
OptionsGlobalRow_OnLoad(self, "COMBAT_TEXT_SHOW_MANA",
"Show Energy Gains",
"OPTION_TOOLTIP_COMBAT_TEXT_SHOW_MANA", 1)
self.parentRow = "RowCombatText"
self.applyFunc = "CombatText_UpdateOrLoad" -- by NAME, resolved when applied: this file loads before its definer now (1938)
</OnLoad></Scripts>
</Frame>
<Frame name="$parentRowHonorGained" inherits="OptionsCheckboxRowTemplate">
<Anchors>
<Anchor point="TOPLEFT" relativeTo="$parentRowEnergyGains" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="0" y="-9"/></Offset></Anchor>
<Anchor point="TOPRIGHT" relativeTo="$parentRowEnergyGains" relativePoint="BOTTOMRIGHT"><Offset><AbsDimension x="0" y="-9"/></Offset></Anchor>
</Anchors>
<Scripts><OnLoad>
OptionsGlobalRow_OnLoad(self, "COMBAT_TEXT_SHOW_HONOR_GAINED",
"Show Honor Gained",
"OPTION_TOOLTIP_COMBAT_TEXT_SHOW_HONOR_GAINED", 1)
self.parentRow = "RowCombatText"
self.applyFunc = "CombatText_UpdateOrLoad" -- by NAME, resolved when applied: this file loads before its definer now (1938)
</OnLoad></Scripts>
</Frame>
</Frames>
</Frame>
<!-- The ACTION BARS page (1136's lock row; the five switches above
it are 1500). 1.12's Advanced Options group, whole.
The four bar rows are API (`func`) rows — the third store, the
one Show Cloak / Show Helm opened in 1472 — because there is
nothing local to save: the preference is four bits of this
CHARACTER's server-side PLAYER_FIELD_BYTES byte 2, written with
CMSG_SET_ACTIONBAR_TOGGLES. The getter reads the live Lua
global (the client never re-reads the field and nothing fires
on a change to it, so the global IS the state between logins);
the setter writes the global, re-derives the bars, and re-sends
the whole byte. Default OFF for all four — a fresh character's
byte is 0.
Always Show ActionBars is a SAVED-VARIABLE row instead, and the
split is a verified consequence of the engine binding rather
than an authoring choice: `SetActionBarToggles` takes exactly
four arguments and silently DROPS the fifth that the reference
passes it (`cmp esi,4` at 0x4e770e), so that switch has no
server store to write and the reference RegisterForSave's it
locally too (UIOptionsFrame.lua l.106-107). Decision 1500 and
wow-re action-bar-toggles.md carry the whole byte-level finding.
The getters and setters are written here as closures rather
than captured from the bar file by name, deliberately: a row's
OnLoad captures a function VALUE, so a named import would make
this page depend on load ORDER — and a harness (or a manifest
reordering) that loaded the window without the bars would give
every row a nil store instead of an unticked box. A closure
resolves its callee when the player clicks.
Labels and OPTION_TOOLTIP_* keys are 1.12's GlobalStrings
verbatim (SHOW_MULTIBAR1..4_TEXT l.3444-3447,
ALWAYS_SHOW_MULTIBARS_TEXT l.51) — including the leading
"Show ", which is part of the reference's own display text.
The lock itself had to be BUILT before it could have a row:
ActionBar.xml and PetActionBar.xml guard their drag ends on it,
the way the reference does. -->
<Frame name="$parentActionBars" hidden="true">
<Anchors>
<Anchor point="TOPLEFT"/>
<Anchor point="BOTTOMRIGHT"/>
</Anchors>
<Frames>
<Frame name="$parentRowMultiBar1" inherits="OptionsCheckboxRowTemplate">
<Anchors>
<Anchor point="TOPLEFT"><Offset><AbsDimension x="10" y="-12"/></Offset></Anchor>
<Anchor point="TOPRIGHT"><Offset><AbsDimension x="-20" y="-12"/></Offset></Anchor>
</Anchors>
<Scripts><OnLoad>
OptionsFuncRow_OnLoad(self,
function() return SHOW_MULTI_ACTIONBAR_1 end,
function(value)
local on
if value and value ~= "0" and value ~= 0 then on = 1 end
SHOW_MULTI_ACTIONBAR_1 = on
MultiActionBar_Update()
SetActionBarToggles(SHOW_MULTI_ACTIONBAR_1, SHOW_MULTI_ACTIONBAR_2,
SHOW_MULTI_ACTIONBAR_3, SHOW_MULTI_ACTIONBAR_4)
UIParent_ManageFramePositions()
end,
"0", "Show Bottom Left ActionBar", "OPTION_TOOLTIP_SHOW_MULTIBAR1")
</OnLoad></Scripts>
</Frame>
<Frame name="$parentRowMultiBar2" inherits="OptionsCheckboxRowTemplate">
<Anchors>
<Anchor point="TOPLEFT" relativeTo="$parentRowMultiBar1" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="0" y="-9"/></Offset></Anchor>
<Anchor point="TOPRIGHT" relativeTo="$parentRowMultiBar1" relativePoint="BOTTOMRIGHT"><Offset><AbsDimension x="0" y="-9"/></Offset></Anchor>
</Anchors>
<Scripts><OnLoad>
OptionsFuncRow_OnLoad(self,
function() return SHOW_MULTI_ACTIONBAR_2 end,
function(value)
local on
if value and value ~= "0" and value ~= 0 then on = 1 end
SHOW_MULTI_ACTIONBAR_2 = on
MultiActionBar_Update()
SetActionBarToggles(SHOW_MULTI_ACTIONBAR_1, SHOW_MULTI_ACTIONBAR_2,
SHOW_MULTI_ACTIONBAR_3, SHOW_MULTI_ACTIONBAR_4)
UIParent_ManageFramePositions()
end,
"0", "Show Bottom Right ActionBar", "OPTION_TOOLTIP_SHOW_MULTIBAR2")
</OnLoad></Scripts>
</Frame>
<Frame name="$parentRowMultiBar3" inherits="OptionsCheckboxRowTemplate">
<Anchors>
<Anchor point="TOPLEFT" relativeTo="$parentRowMultiBar2" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="0" y="-9"/></Offset></Anchor>
<Anchor point="TOPRIGHT" relativeTo="$parentRowMultiBar2" relativePoint="BOTTOMRIGHT"><Offset><AbsDimension x="0" y="-9"/></Offset></Anchor>
</Anchors>
<Scripts><OnLoad>
OptionsFuncRow_OnLoad(self,
function() return SHOW_MULTI_ACTIONBAR_3 end,
function(value)
local on
if value and value ~= "0" and value ~= 0 then on = 1 end
SHOW_MULTI_ACTIONBAR_3 = on
MultiActionBar_Update()
SetActionBarToggles(SHOW_MULTI_ACTIONBAR_1, SHOW_MULTI_ACTIONBAR_2,
SHOW_MULTI_ACTIONBAR_3, SHOW_MULTI_ACTIONBAR_4)
UIParent_ManageFramePositions()
end,
"0", "Show Right ActionBar", "OPTION_TOOLTIP_SHOW_MULTIBAR3")
</OnLoad></Scripts>
</Frame>
<!-- Bar 4 is a CHILD row: MultiBarLeft only shows when bar 3
is also on (MultiBars.xml's conjunction — its column
hangs off MultiBarRight's edge), so the switch is
meaningless on its own. `child` carries the era's
indent-and-small-font subordination; the GREYING is
OptionsPage_UpdateDependencies' ActionBars arm, which
is the reference's own rule for this exact pair
(UIOptionsFrame.lua l.722-726 disables CheckButton36
while 35 is unchecked). -->
<Frame name="$parentRowMultiBar4" inherits="OptionsCheckboxRowTemplate">
<Anchors>
<Anchor point="TOPLEFT" relativeTo="$parentRowMultiBar3" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="0" y="-9"/></Offset></Anchor>
<Anchor point="TOPRIGHT" relativeTo="$parentRowMultiBar3" relativePoint="BOTTOMRIGHT"><Offset><AbsDimension x="0" y="-9"/></Offset></Anchor>
</Anchors>
<Scripts><OnLoad>
OptionsFuncRow_OnLoad(self,
function() return SHOW_MULTI_ACTIONBAR_4 end,
function(value)
local on
if value and value ~= "0" and value ~= 0 then on = 1 end
SHOW_MULTI_ACTIONBAR_4 = on
MultiActionBar_Update()
SetActionBarToggles(SHOW_MULTI_ACTIONBAR_1, SHOW_MULTI_ACTIONBAR_2,
SHOW_MULTI_ACTIONBAR_3, SHOW_MULTI_ACTIONBAR_4)
UIParent_ManageFramePositions()
end,
"0", "Show Right ActionBar 2", "OPTION_TOOLTIP_SHOW_MULTIBAR4", 1)
</OnLoad></Scripts>
</Frame>
<Frame name="$parentRowAlwaysShowMultibars" inherits="OptionsCheckboxRowTemplate">
<Anchors>
<Anchor point="TOPLEFT" relativeTo="$parentRowMultiBar4" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="0" y="-9"/></Offset></Anchor>
<Anchor point="TOPRIGHT" relativeTo="$parentRowMultiBar4" relativePoint="BOTTOMRIGHT"><Offset><AbsDimension x="0" y="-9"/></Offset></Anchor>
</Anchors>
<Scripts><OnLoad>
OptionsGlobalRow_OnLoad(self, "ALWAYS_SHOW_MULTIBARS",
"Always Show ActionBars", "OPTION_TOOLTIP_ALWAYS_SHOW_MULTIBARS")
-- A CLICK runs MultiActionBar_UpdateGridVisibility, the
-- reference's own click arm (UIOptionsFrame.xml:683).
-- The LOAD arm — which only ever SHOWS, because every hide
-- is a counted decrement on each well (ActionButton_HideGrid)
-- and a load-time hide against wells nobody opened would push
-- the count negative — is the stock file's
-- VARIABLES_LOADED arm now (UIOptionsFrame.lua l.218-220),
-- which is why this row carries no `loadFunc` (2115).
self.applyFunc = "MultiActionBar_UpdateGridVisibility"
</OnLoad></Scripts>
</Frame>
<Frame name="$parentRowLockActionBar" inherits="OptionsCheckboxRowTemplate">
<Anchors>
<Anchor point="TOPLEFT" relativeTo="$parentRowAlwaysShowMultibars" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="0" y="-9"/></Offset></Anchor>
<Anchor point="TOPRIGHT" relativeTo="$parentRowAlwaysShowMultibars" relativePoint="BOTTOMRIGHT"><Offset><AbsDimension x="0" y="-9"/></Offset></Anchor>
</Anchors>
<Scripts><OnLoad>
OptionsGlobalRow_OnLoad(self, "LOCK_ACTIONBAR",
"Lock ActionBars", "OPTION_TOOLTIP_LOCK_ACTIONBAR")
</OnLoad></Scripts>
</Frame>
</Frames>
</Frame>
<!-- The INTERFACE page (decision 1136; the Buff Durations row is
1139, the two equipment-display rows 1472) — the second
store's second page, and the one 1134 §3 named as ready: stock
globals already defined AND consumed, needing only a row. Rows
in the reference's own on-screen order — Basic Options' anchor
chain runs CheckButton42 → 39 → 62 → 66 in Display, then 29 in
Help, and we hold all of those but 62
(HIDE_OUTDOOR_WORLD_STATE: there is no zone-objective tracker
to hide); Show Cloak → Show Helm (31 → 20) are that box's
middle column, seated after Status Text because the two rows
above them there — the player/NPC name toggles — live on our
Nameplates page. Labels and OPTION_TOOLTIP_* keys are its
GlobalStrings verbatim.
Only Buff Durations carries an `applyFunc`, and the split is
the honest one rather than an omission: the other three are
read at the moment their consumer acts — the tooltip as it is
built, the quest panel as it shows, the auto-watch as progress
arrives — so the write IS the apply. A setting is owed an
applyFunc exactly when something DERIVED from it is stated
once and kept: the buff bar's row pitch, the combat-text
family's event registrations. -->
<Frame name="$parentInterface" hidden="true">
<Anchors>
<Anchor point="TOPLEFT"/>
<Anchor point="BOTTOMRIGHT"/>
</Anchors>
<Frames>
<!-- Status Bar Text (decision 1140). First because it is
first in the reference's file: its own Display column
runs CheckButton12 -> **2** -> 21, and 2 is declared
ahead of the 42 chain below it. The one row in this
window carrying a `cvarEvent`: TextStatusBar.xml has
had a CVAR_UPDATE watcher since 1082 and nothing to
wake it. -->
<Frame name="$parentRowStatusText" inherits="OptionsCheckboxRowTemplate">
<Anchors>
<Anchor point="TOPLEFT"><Offset><AbsDimension x="10" y="-12"/></Offset></Anchor>
<Anchor point="TOPRIGHT"><Offset><AbsDimension x="-20" y="-12"/></Offset></Anchor>
</Anchors>
<Scripts><OnLoad>
OptionsRow_OnLoad(self, "statusBarText",
"Status Bar Text", "OPTION_TOOLTIP_STATUS_BAR")
self.cvarEvent = "STATUS_BAR_TEXT"
</OnLoad></Scripts>
</Frame>
<!-- The target-of-target pair (decision 1576): the switch
and, indented under it, when the frame is allowed to
stand. Both are `uvar` rows — 1.12 keeps these two as
saved globals, not CVars (UnitFrames.xml declares and
RegisterForSave's them) — and both carry the same
applyFunc, because the saved chunk lands after the XML
that read it: without the re-run, a saved "1" would
leave the frame hidden until the next target change.
The mode row is a CHILD (indent + small font + greyed
with the switch off), which is the reference's own
relationship: its dropdown hangs off CheckButton50's
BOTTOMLEFT and OptionsFrame_DisableDropDown greys it
from the same test (UIOptionsFrame.lua l.694-700). -->
<Frame name="$parentRowTargetOfTarget" inherits="OptionsCheckboxRowTemplate">
<Anchors>
<Anchor point="TOPLEFT" relativeTo="$parentRowStatusText" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="0" y="-9"/></Offset></Anchor>
<Anchor point="TOPRIGHT" relativeTo="$parentRowStatusText" relativePoint="BOTTOMRIGHT"><Offset><AbsDimension x="0" y="-9"/></Offset></Anchor>
</Anchors>
<Scripts><OnLoad>
OptionsGlobalRow_OnLoad(self, "SHOW_TARGET_OF_TARGET",
"Show Target of Target", "OPTION_TOOLTIP_SHOW_TARGET_OF_TARGET")
self.applyFunc = "TargetofTarget_Update" -- by NAME, resolved when applied: this file loads before its definer now (1938)
</OnLoad></Scripts>
</Frame>
<!-- The label is ours: the reference declares a Label
FontString for this dropdown and never sets its text,
so in 1.12 the picker sits under the checkbox with no
name at all. An indented row in this window has to say
what it picks, and the row above supplies the subject.
The five values are the reference's own strings, and
its own order (its OPTION_TOOLTIP_TARGETOFTARGET1..5
describe them in exactly this sequence). -->
<Frame name="$parentRowTargetOfTargetMode" inherits="OptionsDropdownRowTemplate">
<Anchors>
<Anchor point="TOPLEFT" relativeTo="$parentRowTargetOfTarget" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="0" y="-9"/></Offset></Anchor>
<Anchor point="TOPRIGHT" relativeTo="$parentRowTargetOfTarget" relativePoint="BOTTOMRIGHT"><Offset><AbsDimension x="0" y="-9"/></Offset></Anchor>
</Anchors>
<Scripts><OnLoad>
OptionsGlobalRow_OnLoad(self, "SHOW_TARGET_OF_TARGET_STATE",
"Show When", "OPTION_TOOLTIP_TARGETOFTARGET5", 1)
OptionsDropdownRow_Setup(self, {
{ text = "Raid", value = "1", tip = "OPTION_TOOLTIP_TARGETOFTARGET1" },
{ text = "Party", value = "2", tip = "OPTION_TOOLTIP_TARGETOFTARGET2" },
{ text = "Solo", value = "3", tip = "OPTION_TOOLTIP_TARGETOFTARGET3" },
{ text = "Party or Raid", value = "4", tip = "OPTION_TOOLTIP_TARGETOFTARGET4" },
{ text = "Always", value = "5", tip = "OPTION_TOOLTIP_TARGETOFTARGET5" },
})
self.applyFunc = "TargetofTarget_Update" -- by NAME, resolved when applied: this file loads before its definer now (1938)
</OnLoad></Scripts>
</Frame>
<!-- The two equipment-display toggles (decision 1472,
B123). Neither has a store: the preference is a bit of
this character's own server-side PLAYER_FLAGS, so the
row reads the engine getter and writes the engine
setter — 1.12's own `func`/`setFunc` pair for exactly
these two rows (UIOptionsFrame.lua l.18-19), and the
reason there is no CVar to register. Default "1" for
both, the reference's own hand-written arm. -->
<Frame name="$parentRowShowCloak" inherits="OptionsCheckboxRowTemplate">
<Anchors>
<Anchor point="TOPLEFT" relativeTo="$parentRowTargetOfTargetMode" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="0" y="-9"/></Offset></Anchor>
<Anchor point="TOPRIGHT" relativeTo="$parentRowTargetOfTargetMode" relativePoint="BOTTOMRIGHT"><Offset><AbsDimension x="0" y="-9"/></Offset></Anchor>
</Anchors>
<Scripts><OnLoad>
OptionsFuncRow_OnLoad(self, ShowingCloak, ShowCloak, "1",
"Show Cloak", "OPTION_TOOLTIP_SHOW_CLOAK")
</OnLoad></Scripts>
</Frame>
<Frame name="$parentRowShowHelm" inherits="OptionsCheckboxRowTemplate">
<Anchors>
<Anchor point="TOPLEFT" relativeTo="$parentRowShowCloak" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="0" y="-9"/></Offset></Anchor>
<Anchor point="TOPRIGHT" relativeTo="$parentRowShowCloak" relativePoint="BOTTOMRIGHT"><Offset><AbsDimension x="0" y="-9"/></Offset></Anchor>
</Anchors>
<Scripts><OnLoad>
OptionsFuncRow_OnLoad(self, ShowingHelm, ShowHelm, "1",
"Show Helm", "OPTION_TOOLTIP_SHOW_HELM")
</OnLoad></Scripts>
</Frame>
<Frame name="$parentRowInstantQuestText" inherits="OptionsCheckboxRowTemplate">
<Anchors>
<Anchor point="TOPLEFT" relativeTo="$parentRowShowHelm" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="0" y="-9"/></Offset></Anchor>
<Anchor point="TOPRIGHT" relativeTo="$parentRowShowHelm" relativePoint="BOTTOMRIGHT"><Offset><AbsDimension x="0" y="-9"/></Offset></Anchor>
</Anchors>
<Scripts><OnLoad>
OptionsGlobalRow_OnLoad(self, "QUEST_FADING_DISABLE",
"Instant Quest Text", "OPTION_TOOLTIP_SHOW_QUEST_FADING")
</OnLoad></Scripts>
</Frame>
<!-- The reference's own next link in that chain
(CheckButton39, anchored under 42), and the one row on
this page that DOES carry an applyFunc: the timer text
is re-decided by the bar's per-frame poll, but the row
PITCH is stated once and has to be re-stated when the
switch moves (decision 1139). -->
<Frame name="$parentRowBuffDurations" inherits="OptionsCheckboxRowTemplate">
<Anchors>
<Anchor point="TOPLEFT" relativeTo="$parentRowInstantQuestText" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="0" y="-9"/></Offset></Anchor>
<Anchor point="TOPRIGHT" relativeTo="$parentRowInstantQuestText" relativePoint="BOTTOMRIGHT"><Offset><AbsDimension x="0" y="-9"/></Offset></Anchor>
</Anchors>
<Scripts><OnLoad>
OptionsGlobalRow_OnLoad(self, "SHOW_BUFF_DURATIONS",
"Buff Durations", "OPTION_TOOLTIP_SHOW_BUFF_DURATION")
self.applyFunc = "BuffButtons_UpdatePositions" -- by NAME, resolved when applied: this file loads before its definer now (1938)
</OnLoad></Scripts>
</Frame>
<Frame name="$parentRowAutoQuestWatch" inherits="OptionsCheckboxRowTemplate">
<Anchors>
<Anchor point="TOPLEFT" relativeTo="$parentRowBuffDurations" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="0" y="-9"/></Offset></Anchor>
<Anchor point="TOPRIGHT" relativeTo="$parentRowBuffDurations" relativePoint="BOTTOMRIGHT"><Offset><AbsDimension x="0" y="-9"/></Offset></Anchor>
</Anchors>
<Scripts><OnLoad>
OptionsGlobalRow_OnLoad(self, "AUTO_QUEST_WATCH",
"Automatic Quest Tracking", "OPTION_TOOLTIP_AUTO_QUEST_WATCH")
</OnLoad></Scripts>
</Frame>
<!-- Column C's last link (decision 2077): 1.12 anchors
CheckButton5 TOP to CheckButton66's BOTTOM, so
Profanity Filter closes the 42 -> 39 -> 66 chain the
three rows above are. It reads as a chat setting and
the reference files it under Display; the reference's
seat wins, as everywhere else on this page.
What it actually does is wider than its neighbours
suggest: `profanityFilter` gates INSIDE the shared
masker (`0x4a1a66`), so one tick covers all thirteen
of its call sites. benilla wires the largest — the
fourteen social chat types — and `crate::text_filter`
names the twelve still to come. -->
<Frame name="$parentRowProfanityFilter" inherits="OptionsCheckboxRowTemplate">
<Anchors>
<Anchor point="TOPLEFT" relativeTo="$parentRowAutoQuestWatch" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="0" y="-9"/></Offset></Anchor>
<Anchor point="TOPRIGHT" relativeTo="$parentRowAutoQuestWatch" relativePoint="BOTTOMRIGHT"><Offset><AbsDimension x="0" y="-9"/></Offset></Anchor>
</Anchors>
<Scripts><OnLoad>OptionsRow_OnLoad(self, "profanityFilter", "Profanity Filter",
"OPTION_TOOLTIP_PROFANITY_FILTER")</OnLoad></Scripts>
</Frame>
<!-- The HELP box opens here (decision 2077). 1.12 seats
CheckButton28 at its TOPLEFT and hangs 44 (Show
Loading Screen Tips) under it, with 29 (this page's
Show Detailed Tooltips) off to the right of the Reset
Tutorials button — so 28 -> 44 -> 29 is the box's own
reading order flattened into our column, which is the
order below.
The Reset Tutorials BUTTON itself is not here: it is
not a checkbox, and `ResetTutorials()` is exactly what
ticking this row already does. -->
<Frame name="$parentRowShowTutorials" inherits="OptionsCheckboxRowTemplate">
<Anchors>
<Anchor point="TOPLEFT" relativeTo="$parentRowProfanityFilter" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="0" y="-9"/></Offset></Anchor>
<Anchor point="TOPRIGHT" relativeTo="$parentRowProfanityFilter" relativePoint="BOTTOMRIGHT"><Offset><AbsDimension x="0" y="-9"/></Offset></Anchor>
</Anchors>
<!-- A `func` row, like Show Cloak / Show Helm: there is
no store, the bank IS the state. Its default is the
reference's own hand-written one
(UIOptionsFrame_SetDefaults l.645,
`OptionsFrame_EnableCheckBox(checkButton, 1, 1)`). -->
<Scripts><OnLoad>
OptionsFuncRow_OnLoad(self, TutorialsEnabled,
OptionsFrame_SetTutorialsEnabled, "1",
"Show Tutorials", "OPTION_TOOLTIP_SHOW_TUTORIALS")
</OnLoad></Scripts>
</Frame>
<!-- CheckButton44, anchored under 28 in the reference's own
Help box. Its LABEL is 1.12's own and it is not the
name the tooltip uses: `SHOW_TIPOFTHEDAY_TEXT` reads
"Show Loading Screen Tips", while the description
under `OPTION_TOOLTIP_SHOW_TIPOFTHEDAY` still calls it
the tip of the day. Both are the reference's strings,
so both stand. -->
<Frame name="$parentRowTipOfTheDay" inherits="OptionsCheckboxRowTemplate">
<Anchors>
<Anchor point="TOPLEFT" relativeTo="$parentRowShowTutorials" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="0" y="-9"/></Offset></Anchor>
<Anchor point="TOPRIGHT" relativeTo="$parentRowShowTutorials" relativePoint="BOTTOMRIGHT"><Offset><AbsDimension x="0" y="-9"/></Offset></Anchor>
</Anchors>
<Scripts><OnLoad>OptionsRow_OnLoad(self, "showGameTips",
"Show Loading Screen Tips",
"OPTION_TOOLTIP_SHOW_TIPOFTHEDAY")</OnLoad></Scripts>
</Frame>
<Frame name="$parentRowNewbieTips" inherits="OptionsCheckboxRowTemplate">
<Anchors>
<Anchor point="TOPLEFT" relativeTo="$parentRowTipOfTheDay" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="0" y="-9"/></Offset></Anchor>
<Anchor point="TOPRIGHT" relativeTo="$parentRowTipOfTheDay" relativePoint="BOTTOMRIGHT"><Offset><AbsDimension x="0" y="-9"/></Offset></Anchor>
</Anchors>
<Scripts><OnLoad>
OptionsGlobalRow_OnLoad(self, "SHOW_NEWBIE_TIPS",
"Show Detailed Tooltips", "OPTION_TOOLTIP_SHOW_NEWBIE_TIPS")
</OnLoad></Scripts>
</Frame>
</Frames>
</Frame>
<!-- The SOCIAL page (decision 1139) — the last category in this
window that opened onto nothing. Both rows are CVar rows, not
globals: `ChatBubbles`/`ChatBubblesParty` are 1.12's own
registrar CVars, and chat_bubble.rs held them as a pair of
`const bool` from 0598 — a gate transcribed faithfully with no
way to reach it, which is the same shape as the action-bar lock
and fixed the same way (the knob first, then the row).
1.12's own order, with one gap named: its Advanced Options chat
chain runs CheckButton37 (Chat Bubbles) → 46 (Loot Spam) → 38
(Party Chat Bubbles), and 46 is not ours to ship — no
`showLootSpam` knob exists. So these two are adjacent here and
in the reference's relative order. Neither is a dependency child:
the client gates party lines on their own CVar, so party bubbles
survive turning say/yell bubbles off. -->
<!-- The Chat page (decision 1589 — B246, "no chat section in
options"). 1.12's Advanced Options CHAT_LABEL box, in its own
anchor-chain order; which of its nine rows we ship and why the
other five are absent is the OPTIONS_PAGE_ROWS entry's note.
The window's *look* — background tint, opacity, font size —
is deliberately NOT here: the reference puts it on the chat
tab's right-click menu (ChatFrame.xml), and inventing a second
home for it would be a divergence, not a convenience. -->
<Frame name="$parentChat" hidden="true">
<Anchors>
<Anchor point="TOPLEFT"/>
<Anchor point="BOTTOMRIGHT"/>
</Anchors>
<Frames>
<Frame name="$parentRowGuildMemberAlert" inherits="OptionsCheckboxRowTemplate">
<Anchors>
<Anchor point="TOPLEFT"><Offset><AbsDimension x="10" y="-12"/></Offset></Anchor>
<Anchor point="TOPRIGHT"><Offset><AbsDimension x="-20" y="-12"/></Offset></Anchor>
</Anchors>
<Scripts><OnLoad>
<!-- Registered "0" — the one row on this page that
ships its feature OFF, and byte-read rather
than chosen (cvars.rs cites the site). No
applyFunc: the guild sign-on handler reads the
knob as each event lands. -->
OptionsRow_OnLoad(self, "guildMemberNotify",
"Guild Member Alert",
"OPTION_TOOLTIP_GUILDMEMBER_ALERT")
</OnLoad></Scripts>
</Frame>
<Frame name="$parentRowRemoveChatDelay" inherits="OptionsCheckboxRowTemplate">
<Anchors>
<Anchor point="TOPLEFT" relativeTo="$parentRowGuildMemberAlert" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="0" y="-9"/></Offset></Anchor>
<Anchor point="TOPRIGHT" relativeTo="$parentRowGuildMemberAlert" relativePoint="BOTTOMRIGHT"><Offset><AbsDimension x="0" y="-9"/></Offset></Anchor>
</Anchors>
<Scripts><OnLoad>
<!-- A uvar, like the reference's (CheckButton18).
Its applyFunc is the whole feature: the two
fade constants live in ChatFrame.xml and
SetChatMouseOverDelay is what moves them. The
row needs one because nothing re-reads the
global per frame — the fade reads the
constants, and only this writes them. -->
OptionsGlobalRow_OnLoad(self, "REMOVE_CHAT_DELAY",
"Remove Chat Hover Delay",
"OPTION_TOOLTIP_REMOVE_CHAT_DELAY")
self.applyFunc = "ChatFrame_ApplyMouseOverDelay" -- by NAME, resolved when applied: this file loads before its definer now (1938)
</OnLoad></Scripts>
</Frame>
<Frame name="$parentRowChatBubbles" inherits="OptionsCheckboxRowTemplate">
<Anchors>
<Anchor point="TOPLEFT" relativeTo="$parentRowRemoveChatDelay" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="0" y="-9"/></Offset></Anchor>
<Anchor point="TOPRIGHT" relativeTo="$parentRowRemoveChatDelay" relativePoint="BOTTOMRIGHT"><Offset><AbsDimension x="0" y="-9"/></Offset></Anchor>
</Anchors>
<Scripts><OnLoad>
OptionsRow_OnLoad(self, "ChatBubbles",
"Show Chat Bubbles", "OPTION_TOOLTIP_CHAT_BUBBLES")
</OnLoad></Scripts>
</Frame>
<Frame name="$parentRowLootSpam" inherits="OptionsCheckboxRowTemplate">
<Anchors>
<Anchor point="TOPLEFT" relativeTo="$parentRowChatBubbles" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="0" y="-9"/></Offset></Anchor>
<Anchor point="TOPRIGHT" relativeTo="$parentRowChatBubbles" relativePoint="BOTTOMRIGHT"><Offset><AbsDimension x="0" y="-9"/></Offset></Anchor>
</Anchors>
<Scripts><OnLoad>
<!-- "Detailed Loot Information" is the label; the
CVar is `showLootSpam` and it is about GROUP
LOOT ROLLS, not about loot messages generally
(its own tooltip: "Uncheck this to hide
individual loot roll messages and only show
the winner"). No applyFunc: the loot-roll
lines read the CVar as each one is composed. -->
OptionsRow_OnLoad(self, "showLootSpam",
"Detailed Loot Information",
"OPTION_TOOLTIP_SHOW_LOOT_SPAM")
</OnLoad></Scripts>
</Frame>
<Frame name="$parentRowPartyChatBubbles" inherits="OptionsCheckboxRowTemplate">
<Anchors>
<Anchor point="TOPLEFT" relativeTo="$parentRowLootSpam" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="0" y="-9"/></Offset></Anchor>
<Anchor point="TOPRIGHT" relativeTo="$parentRowLootSpam" relativePoint="BOTTOMRIGHT"><Offset><AbsDimension x="0" y="-9"/></Offset></Anchor>
</Anchors>
<Scripts><OnLoad>
OptionsRow_OnLoad(self, "ChatBubblesParty",
"Show Party Chat Bubbles", "OPTION_TOOLTIP_PARTY_CHAT_BUBBLES")
</OnLoad></Scripts>
</Frame>
<!-- CheckButton 68, the last link in the reference's chat
chain, and the row this page's note has been promising
since 1589 ("the row lands with the filter"). It lands
with decision 2077.
**Value-INVERTED**, like Sticky Targeting: the label
says *Disable* Spam Filter, so the box is checked when
`spamFilter` reads "0". That is the reference's own
pair of arms — `UIOptionsFrame.lua` l.253 checks it on
`GetCVar == "0"` and l.337 flips the value before
writing.
The filter behind it drops a line matching any of
`SpamMessages.dbc`'s 28 gold-seller patterns, silently
and with nothing in its place — every chat type except
CHAT_MSG_FILTERED, never for a GM-tagged line or a GM
viewer. See `crate::text_filter`. -->
<Frame name="$parentRowSpamFilter" inherits="OptionsCheckboxRowTemplate">
<Anchors>
<Anchor point="TOPLEFT" relativeTo="$parentRowPartyChatBubbles" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="0" y="-9"/></Offset></Anchor>
<Anchor point="TOPRIGHT" relativeTo="$parentRowPartyChatBubbles" relativePoint="BOTTOMRIGHT"><Offset><AbsDimension x="0" y="-9"/></Offset></Anchor>
</Anchors>
<Scripts><OnLoad>
OptionsRow_OnLoad(self, "spamFilter", "Disable Spam Filter",
"OPTION_TOOLTIP_DISABLE_SPAM_FILTER")
self.invert = 1
</OnLoad></Scripts>
</Frame>
</Frames>
</Frame>
<!-- The Nameplates page (0992 — the page 0952 promised when the
category row joined). Two groups, plates first: the V-key
HEALTH-BAR plates (vplates.rs VPlateMode — the engine bitmask
[0xc4da34] bit 0 / bit 3), then the three 1.12 UnitName*
checkboxes over the overhead-name gates (nameplates.rs
NameConfig), rows in the 1.12 Interface Options' own index
order (21/30/67), labels its GlobalStrings verbatim
(SHOW_PLAYER_NAMES/SHOW_NPC_NAMES/SHOW_OWN_NAME).
The plate pair leads because it is what "Nameplates" means to
a player pressing V, and it is the pair that had no surface at
all — 1.12's own options UI has NO nameplate row (its
UIOptionsFrame keeps NAMEPLATES_ON/FRIENDNAMEPLATES_ON as
RegisterForSave'd globals driven only by the keybinding, and
says so in a comment), so the rows are era grammar over a
1.12 knob — 0959's law, the WorldDetail dropdown's posture.
Labels take the 1.12 binding names' own two-word spelling
("Name Plates", BINDING_NAME_NAMEPLATES) with the era's
enemy/friendly split naming the two bits; no 1.12
OPTION_TOOLTIP string exists for either (nil, like Auto
Loot). -->
<Frame name="$parentNameplates" hidden="true">
<Anchors>
<Anchor point="TOPLEFT"/>
<Anchor point="BOTTOMRIGHT"/>
</Anchors>
<Frames>
<Frame name="$parentRowEnemyPlates" inherits="OptionsCheckboxRowTemplate">
<Anchors>
<Anchor point="TOPLEFT"><Offset><AbsDimension x="10" y="-12"/></Offset></Anchor>
<Anchor point="TOPRIGHT"><Offset><AbsDimension x="-20" y="-12"/></Offset></Anchor>
</Anchors>
<Scripts><OnLoad>OptionsRow_OnLoad(self, "nameplateShowEnemies",
"Enemy Name Plates")</OnLoad></Scripts>
</Frame>
<Frame name="$parentRowFriendlyPlates" inherits="OptionsCheckboxRowTemplate">
<Anchors>
<Anchor point="TOPLEFT" relativeTo="$parentRowEnemyPlates" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="0" y="-9"/></Offset></Anchor>
<Anchor point="TOPRIGHT" relativeTo="$parentRowEnemyPlates" relativePoint="BOTTOMRIGHT"><Offset><AbsDimension x="0" y="-9"/></Offset></Anchor>
</Anchors>
<Scripts><OnLoad>OptionsRow_OnLoad(self, "nameplateShowFriends",
"Friendly Name Plates")</OnLoad></Scripts>
</Frame>
<Frame name="$parentRowPlayerNames" inherits="OptionsCheckboxRowTemplate">
<Anchors>
<Anchor point="TOPLEFT" relativeTo="$parentRowFriendlyPlates" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="0" y="-9"/></Offset></Anchor>
<Anchor point="TOPRIGHT" relativeTo="$parentRowFriendlyPlates" relativePoint="BOTTOMRIGHT"><Offset><AbsDimension x="0" y="-9"/></Offset></Anchor>
</Anchors>
<Scripts><OnLoad>OptionsRow_OnLoad(self, "UnitNamePlayer", "Player Names",
"OPTION_TOOLTIP_SHOW_PLAYER_NAMES")</OnLoad></Scripts>
</Frame>
<Frame name="$parentRowNpcNames" inherits="OptionsCheckboxRowTemplate">
<Anchors>
<Anchor point="TOPLEFT" relativeTo="$parentRowPlayerNames" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="0" y="-9"/></Offset></Anchor>
<Anchor point="TOPRIGHT" relativeTo="$parentRowPlayerNames" relativePoint="BOTTOMRIGHT"><Offset><AbsDimension x="0" y="-9"/></Offset></Anchor>
</Anchors>
<Scripts><OnLoad>OptionsRow_OnLoad(self, "UnitNameNPC", "NPC Names",
"OPTION_TOOLTIP_SHOW_NPC_NAMES")</OnLoad></Scripts>
</Frame>
<Frame name="$parentRowOwnName" inherits="OptionsCheckboxRowTemplate">
<Anchors>
<Anchor point="TOPLEFT" relativeTo="$parentRowNpcNames" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="0" y="-9"/></Offset></Anchor>
<Anchor point="TOPRIGHT" relativeTo="$parentRowNpcNames" relativePoint="BOTTOMRIGHT"><Offset><AbsDimension x="0" y="-9"/></Offset></Anchor>
</Anchors>
<Scripts><OnLoad>OptionsRow_OnLoad(self, "UnitNameOwn", "Show Own Name",
"OPTION_TOOLTIP_SHOW_OWN_NAME")</OnLoad></Scripts>
</Frame>
</Frames>
</Frame>
<!-- The KEYBINDINGS page body (decision 1008 — the era Settings
panel's Keybindings category folded into this window; the module,
provenance and commit law: KeyBindingsPage.xml). First the
Character Specific row (the era page's first setting row, confirm
on uncheck only), then the section list — 19 dual-mode rows under
the shared faux-scroll kit, sections collapsed by default — and
the 1.12 window's message line (KEY_BOUND / the red steal / the
refused key string) at the foot. -->
<!-- enableMouse: the page catches the wheel ITSELF on any point no
child claims (hit-testing only sees mouse-enabled frames —
pointer.rs; rows are plain Frames, so a spin over a row's name
would otherwise fall through to the window and die). -->
<Frame name="$parentKeybindings" hidden="true" enableMouse="true">
<Anchors>
<Anchor point="TOPLEFT"/>
<Anchor point="BOTTOMRIGHT"/>
</Anchors>
<Layers>
<Layer level="OVERLAY">
<FontString name="$parentOutput" inherits="GameFontNormal">
<Anchors><Anchor point="BOTTOM"><Offset><AbsDimension x="0" y="8"/></Offset></Anchor></Anchors>
</FontString>
</Layer>
</Layers>
<Frames>
<!-- Character Specific Key Bindings — the era checkbox-row
geometry (label left, the UI-CheckBox set at the
control column) over 1.12's set model; the click law
is KeyBindings_CharacterToggle. This bespoke row's
column sits at CENTER-40, not the shared CENTER-80:
1.12's string is longer than the era's and clipped at
the shared split ("…Key Bindin…" — the first capture);
no other control shares this page's column, so the
shift is free. -->
<Frame name="$parentCharacterRow" enableMouse="true">
<Size><AbsDimension y="26"/></Size>
<Anchors>
<Anchor point="TOPLEFT"><Offset><AbsDimension x="10" y="-12"/></Offset></Anchor>
<Anchor point="TOPRIGHT"><Offset><AbsDimension x="-20" y="-12"/></Offset></Anchor>
</Anchors>
<Layers>
<Layer level="OVERLAY">
<FontString name="$parentLabel" inherits="GameFontNormal" justifyH="LEFT">
<Anchors>
<Anchor point="LEFT"><Offset><AbsDimension x="37" y="0"/></Offset></Anchor>
<Anchor point="RIGHT" relativePoint="CENTER"><Offset><AbsDimension x="-45" y="0"/></Offset></Anchor>
</Anchors>
</FontString>
</Layer>
</Layers>
<Frames>
<CheckButton name="$parentCheck">
<Size><AbsDimension x="30" y="29"/></Size>
<Anchors><Anchor point="LEFT" relativePoint="CENTER"><Offset><AbsDimension x="-40" y="0"/></Offset></Anchor></Anchors>
<NormalTexture file="Interface\Buttons\UI-CheckBox-Up"/>
<PushedTexture file="Interface\Buttons\UI-CheckBox-Down"/>
<CheckedTexture file="Interface\Buttons\UI-CheckBox-Check"/>
<DisabledCheckedTexture file="Interface\Buttons\UI-CheckBox-Check-Disabled"/>
<Scripts>
<OnClick>KeyBindings_CharacterToggle(this)</OnClick>
<OnEnter>
GameTooltip:SetOwner(this, "ANCHOR_RIGHT")
GameTooltip:SetText(KeyBindings_String("CHARACTER_SPECIFIC_KEYBINDING_TOOLTIP",
"Click this to toggle between general key bindings and key bindings specific to this character."), nil, nil, nil, nil, 1)
</OnEnter>
<OnLeave>GameTooltip:Hide()</OnLeave>
</Scripts>
</CheckButton>
</Frames>
<Scripts>
<OnLoad>
getglobal(this:GetName() .. "Label"):SetText(
KeyBindings_String("CHARACTER_SPECIFIC_KEYBINDINGS", "Character Specific Key Bindings"))
</OnLoad>
</Scripts>
</Frame>
<Frame name="$parentRow1" inherits="KeyBindingRowTemplate">
<Anchors>
<Anchor point="TOPLEFT"><Offset><AbsDimension x="10" y="-47"/></Offset></Anchor>
<Anchor point="TOPRIGHT"><Offset><AbsDimension x="-32" y="-47"/></Offset></Anchor>
</Anchors>
</Frame>
<Frame name="$parentRow2" inherits="KeyBindingRowTemplate">
<Anchors>
<Anchor point="TOPLEFT" relativeTo="$parentRow1" relativePoint="BOTTOMLEFT"/>
<Anchor point="TOPRIGHT" relativeTo="$parentRow1" relativePoint="BOTTOMRIGHT"/>
</Anchors>
</Frame>
<Frame name="$parentRow3" inherits="KeyBindingRowTemplate">
<Anchors>
<Anchor point="TOPLEFT" relativeTo="$parentRow2" relativePoint="BOTTOMLEFT"/>
<Anchor point="TOPRIGHT" relativeTo="$parentRow2" relativePoint="BOTTOMRIGHT"/>
</Anchors>
</Frame>
<Frame name="$parentRow4" inherits="KeyBindingRowTemplate">
<Anchors>
<Anchor point="TOPLEFT" relativeTo="$parentRow3" relativePoint="BOTTOMLEFT"/>
<Anchor point="TOPRIGHT" relativeTo="$parentRow3" relativePoint="BOTTOMRIGHT"/>
</Anchors>
</Frame>
<Frame name="$parentRow5" inherits="KeyBindingRowTemplate">
<Anchors>
<Anchor point="TOPLEFT" relativeTo="$parentRow4" relativePoint="BOTTOMLEFT"/>
<Anchor point="TOPRIGHT" relativeTo="$parentRow4" relativePoint="BOTTOMRIGHT"/>
</Anchors>
</Frame>
<Frame name="$parentRow6" inherits="KeyBindingRowTemplate">
<Anchors>
<Anchor point="TOPLEFT" relativeTo="$parentRow5" relativePoint="BOTTOMLEFT"/>
<Anchor point="TOPRIGHT" relativeTo="$parentRow5" relativePoint="BOTTOMRIGHT"/>
</Anchors>
</Frame>
<Frame name="$parentRow7" inherits="KeyBindingRowTemplate">
<Anchors>
<Anchor point="TOPLEFT" relativeTo="$parentRow6" relativePoint="BOTTOMLEFT"/>
<Anchor point="TOPRIGHT" relativeTo="$parentRow6" relativePoint="BOTTOMRIGHT"/>
</Anchors>
</Frame>
<Frame name="$parentRow8" inherits="KeyBindingRowTemplate">
<Anchors>
<Anchor point="TOPLEFT" relativeTo="$parentRow7" relativePoint="BOTTOMLEFT"/>
<Anchor point="TOPRIGHT" relativeTo="$parentRow7" relativePoint="BOTTOMRIGHT"/>
</Anchors>
</Frame>
<Frame name="$parentRow9" inherits="KeyBindingRowTemplate">
<Anchors>
<Anchor point="TOPLEFT" relativeTo="$parentRow8" relativePoint="BOTTOMLEFT"/>
<Anchor point="TOPRIGHT" relativeTo="$parentRow8" relativePoint="BOTTOMRIGHT"/>
</Anchors>
</Frame>
<Frame name="$parentRow10" inherits="KeyBindingRowTemplate">
<Anchors>
<Anchor point="TOPLEFT" relativeTo="$parentRow9" relativePoint="BOTTOMLEFT"/>
<Anchor point="TOPRIGHT" relativeTo="$parentRow9" relativePoint="BOTTOMRIGHT"/>
</Anchors>
</Frame>
<Frame name="$parentRow11" inherits="KeyBindingRowTemplate">
<Anchors>
<Anchor point="TOPLEFT" relativeTo="$parentRow10" relativePoint="BOTTOMLEFT"/>
<Anchor point="TOPRIGHT" relativeTo="$parentRow10" relativePoint="BOTTOMRIGHT"/>
</Anchors>
</Frame>
<Frame name="$parentRow12" inherits="KeyBindingRowTemplate">
<Anchors>
<Anchor point="TOPLEFT" relativeTo="$parentRow11" relativePoint="BOTTOMLEFT"/>
<Anchor point="TOPRIGHT" relativeTo="$parentRow11" relativePoint="BOTTOMRIGHT"/>
</Anchors>
</Frame>
<Frame name="$parentRow13" inherits="KeyBindingRowTemplate">
<Anchors>
<Anchor point="TOPLEFT" relativeTo="$parentRow12" relativePoint="BOTTOMLEFT"/>
<Anchor point="TOPRIGHT" relativeTo="$parentRow12" relativePoint="BOTTOMRIGHT"/>
</Anchors>
</Frame>
<Frame name="$parentRow14" inherits="KeyBindingRowTemplate">
<Anchors>
<Anchor point="TOPLEFT" relativeTo="$parentRow13" relativePoint="BOTTOMLEFT"/>
<Anchor point="TOPRIGHT" relativeTo="$parentRow13" relativePoint="BOTTOMRIGHT"/>
</Anchors>
</Frame>
<Frame name="$parentRow15" inherits="KeyBindingRowTemplate">
<Anchors>
<Anchor point="TOPLEFT" relativeTo="$parentRow14" relativePoint="BOTTOMLEFT"/>
<Anchor point="TOPRIGHT" relativeTo="$parentRow14" relativePoint="BOTTOMRIGHT"/>
</Anchors>
</Frame>
<Frame name="$parentRow16" inherits="KeyBindingRowTemplate">
<Anchors>
<Anchor point="TOPLEFT" relativeTo="$parentRow15" relativePoint="BOTTOMLEFT"/>
<Anchor point="TOPRIGHT" relativeTo="$parentRow15" relativePoint="BOTTOMRIGHT"/>
</Anchors>
</Frame>
<Frame name="$parentRow17" inherits="KeyBindingRowTemplate">
<Anchors>
<Anchor point="TOPLEFT" relativeTo="$parentRow16" relativePoint="BOTTOMLEFT"/>
<Anchor point="TOPRIGHT" relativeTo="$parentRow16" relativePoint="BOTTOMRIGHT"/>
</Anchors>
</Frame>
<Frame name="$parentRow18" inherits="KeyBindingRowTemplate">
<Anchors>
<Anchor point="TOPLEFT" relativeTo="$parentRow17" relativePoint="BOTTOMLEFT"/>
<Anchor point="TOPRIGHT" relativeTo="$parentRow17" relativePoint="BOTTOMRIGHT"/>
</Anchors>
</Frame>
<Frame name="$parentRow19" inherits="KeyBindingRowTemplate">
<Anchors>
<Anchor point="TOPLEFT" relativeTo="$parentRow18" relativePoint="BOTTOMLEFT"/>
<Anchor point="TOPRIGHT" relativeTo="$parentRow18" relativePoint="BOTTOMRIGHT"/>
</Anchors>
</Frame>
<!-- That bar's TROUGH — the recessed channel it rides in (the
shared kit's template; 1.12 authors this art per window,
never in the bar template). Named off the bar, which is
how BenillaScrollBar_SetShown finds it, and carrying no
anchors of its own: it seats itself on that bar at the
ref's hang (BenillaScrollTrough_Seat), which is what
lands the channel's arrow sockets on the arrows. Sits
here, BEFORE the faux frame, so it draws beneath it. -->
<Frame name="$parentScrollFrameScrollBarTrough" inherits="BenillaScrollTroughTemplate"/>
<!-- The faux scroll over the rows (the shared kit, 0997's own
seam). The kit's bar hangs 6px RIGHT of this frame's edge,
so the frame ends at the rows' own -32 and the 16-wide bar
rides the -26..-10 gutter inside the body (the TrainerFrame
seating). The wheel handler is NOT here: a spin over a row
bubbles up the PARENT chain (pointer.rs), and this frame is
the rows' sibling — it lives on the body below. -->
<ScrollFrame name="$parentScrollFrame" inherits="FauxScrollFrameTemplate">
<Anchors>
<Anchor point="TOPLEFT"><Offset><AbsDimension x="10" y="-47"/></Offset></Anchor>
<Anchor point="BOTTOMRIGHT"><Offset><AbsDimension x="-32" y="28"/></Offset></Anchor>
</Anchors>
<Scripts>
<OnLoad>ScrollFrame_OnLoad()</OnLoad>
<!-- The reference's kit writes `frame.offset` ONLY
from here (its `updateFunc` field does not
exist); without this the bar moves and the list
does not follow. `KEYBINDINGS_ROW_HEIGHT` is a
file-scope global in KeyBindingsPage.xml, so it
is visible to this handler's own chunk — a
`local` would not be (1860). -->
<OnVerticalScroll>FauxScrollFrame_OnVerticalScroll(KEYBINDINGS_ROW_HEIGHT, KeyBindingsPage_Update)</OnVerticalScroll>
<!-- **The armed guard has to be restated HERE.**
While a capsule is armed the wheel is a BIND,
never a scroll (1.12's law; the body below
guards its own arm the same way). Our retired
`FauxScrollFrameTemplate` was a plain `<Frame>`
with no wheel handler, so the body's guard was
the only path. The reference's template
inherits `UIPanelScrollFrameTemplate`, which
carries `<OnMouseWheel>` of its own — and that
one fires on this frame, ahead of the body,
bypassing the guard entirely. An instance's
handler REPLACES the inherited one, so this is
the reference's verb with our law back in front
of it (1860). -->
<OnMouseWheel>
if not KeyBindingsPage.selected then
ScrollFrameTemplate_OnMouseWheel(arg1)
end
</OnMouseWheel>
</Scripts>
</ScrollFrame>
</Frames>
<!-- The page's wheel: rows and the bar all bubble here (the
QuestLog/Trainer lesson — a handler on the sibling catcher
never sees a spin over a row). The guard is 1.12's law:
while a capsule is armed the wheel is a BIND (the host
seam swallows it) — never a scroll. -->
<Scripts>
<OnMouseWheel>
if not KeyBindingsPage.selected then
BenillaFauxScrollFrame_OnMouseWheel(getglobal(this:GetName() .. "ScrollFrame"), arg1)
end
</OnMouseWheel>
</Scripts>
</Frame>
<!-- The search-results group headers — one per page WITH rows
(the OPTIONS_PAGE_ROWS set; a page that gains rows gains its
head here). Unanchored and hidden until a search chains them
in; the label is the category's pageTitle, the era
GetQualifiedName. -->
<Button name="$parentSearchHeadControls" inherits="OptionsSearchHeadTemplate">
<Scripts><OnLoad>
self.categoryKey = "Controls"
getglobal(self:GetName() .. "Label"):SetText("Controls")
</OnLoad></Scripts>
</Button>
<Button name="$parentSearchHeadAudio" inherits="OptionsSearchHeadTemplate">
<Scripts><OnLoad>
self.categoryKey = "Audio"
getglobal(self:GetName() .. "Label"):SetText("Audio")
</OnLoad></Scripts>
</Button>
<Button name="$parentSearchHeadGraphics" inherits="OptionsSearchHeadTemplate">
<Scripts><OnLoad>
self.categoryKey = "Graphics"
getglobal(self:GetName() .. "Label"):SetText("Graphics")
</OnLoad></Scripts>
</Button>
<Button name="$parentSearchHeadCombat" inherits="OptionsSearchHeadTemplate">
<Scripts><OnLoad>
self.categoryKey = "Combat"
getglobal(self:GetName() .. "Label"):SetText("Combat")
</OnLoad></Scripts>
</Button>
<Button name="$parentSearchHeadActionBars" inherits="OptionsSearchHeadTemplate">
<Scripts><OnLoad>
self.categoryKey = "ActionBars"
getglobal(self:GetName() .. "Label"):SetText("Action Bars")
</OnLoad></Scripts>
</Button>
<Button name="$parentSearchHeadInterface" inherits="OptionsSearchHeadTemplate">
<Scripts><OnLoad>
self.categoryKey = "Interface"
getglobal(self:GetName() .. "Label"):SetText("Interface")
</OnLoad></Scripts>
</Button>
<Button name="$parentSearchHeadChat" inherits="OptionsSearchHeadTemplate">
<Scripts><OnLoad>
self.categoryKey = "Chat"
getglobal(self:GetName() .. "Label"):SetText("Chat")
</OnLoad></Scripts>
</Button>
<Button name="$parentSearchHeadNameplates" inherits="OptionsSearchHeadTemplate">
<Scripts><OnLoad>
self.categoryKey = "Nameplates"
getglobal(self:GetName() .. "Label"):SetText("Nameplates")
</OnLoad></Scripts>
</Button>
<Button name="$parentSearchHeadKeybindings" inherits="OptionsSearchHeadTemplate">
<Scripts><OnLoad>
self.categoryKey = "Keybindings"
getglobal(self:GetName() .. "Label"):SetText("Keybindings")
</OnLoad></Scripts>
</Button>
<!-- The keybinding search-result pool (decision 1008): the era
reflows LIVE binding rows into its results; ours is this static
pool of the same template, painted and chained by the search
display (capped at KEYBINDINGS_SEARCH_ROWS — the SCOPE note in
KeyBindingsPage.xml). Unanchored until a search places them;
the capsules run the full capture law in place. -->
<Frame name="$parentKeybindSearch1" inherits="KeyBindingRowTemplate" hidden="true"/>
<Frame name="$parentKeybindSearch2" inherits="KeyBindingRowTemplate" hidden="true"/>
<Frame name="$parentKeybindSearch3" inherits="KeyBindingRowTemplate" hidden="true"/>
<Frame name="$parentKeybindSearch4" inherits="KeyBindingRowTemplate" hidden="true"/>
<Frame name="$parentKeybindSearch5" inherits="KeyBindingRowTemplate" hidden="true"/>
<Frame name="$parentKeybindSearch6" inherits="KeyBindingRowTemplate" hidden="true"/>
<Frame name="$parentKeybindSearch7" inherits="KeyBindingRowTemplate" hidden="true"/>
<Frame name="$parentKeybindSearch8" inherits="KeyBindingRowTemplate" hidden="true"/>
</Frames>
</Frame>
<!-- The bar: the shared Slider (drag + arrows, decision 0250) on the
real-scroll handler, hidden until the content overflows. Seated
INSIDE the frame's right edge rather than 6 units outside it (the
UIPanelScrollFrameTemplate shape the faux kit uses): outside would
put it past the page box, and the -20 right inset the rows already
carry leaves the -26..-10 gutter free — the very gutter the
Keybindings list's bar rides.
Inset 21/20 vertically, not the ref's 16/16, because HERE the
scroll frame IS the page rect: the trough seats itself 21 above and
20 below the bar (BenillaScrollTrough_Seat), so these are the
offsets that put the whole channel — caps and all — exactly on the
page rect instead of poking 5 units up through the header divider
and 4 down past the container. The bar↔trough relationship is the
ref's either way; this only chooses which of the two is flush. -->
<Slider name="$parentBar" inherits="BenillaScrollBarTemplate" hidden="true">
<Anchors>
<Anchor point="TOPRIGHT"><Offset><AbsDimension x="-10" y="-21"/></Offset></Anchor>
<Anchor point="BOTTOMRIGHT"><Offset><AbsDimension x="-10" y="20"/></Offset></Anchor>
</Anchors>
<Scripts><OnValueChanged>BenillaScrollBar_OnValueChanged(self, arg1)</OnValueChanged></Scripts>
</Slider>
</Frames>
</ScrollFrame>
</Frames>
</Frame>
<!-- ── The search box (era SearchBoxTemplate GEOMETRY: a 20-tall three-slice
vertically centered on the 22 box — left cap 8x20 hung 5 outside, right cap
8x20 flush, middle stretched between — a 17x17 clear button at RIGHT (-3)
that appears with text). The slices wear the classic Common-Input-Border
(0978 mapping) at the InputBoxTemplate UVs (caps u 0..1/16 and 15/16..1,
ink v 0..0.625 of the 128x32 file). The era magnifier is DROPPED (no 1.12
counterpart; the "Search" placeholder carries the affordance) — and with it
the era's 16-left TextInsets seat that cleared it: text and caret sit at 5
(0989). The clear X wears the UI-Panel-MinimizeButton ball, half-lit,
full on hover (the era ClearButtonMixin alpha law). -->
<EditBox name="$parentSearchBox" autoFocus="false" letters="64">
<Size><AbsDimension x="350" y="22"/></Size>
<!-- The era seat VERBATIM (SettingsPanel.xml l.76-81: 350x22, BOTTOMRIGHT ->
Container TOPRIGHT (4, +20)). 0981 rebased y to -4 (the box line ran
through it — the crowding report); 0984 restored y and bent x/width
(-18/300) around the corner X ball; 0989 cut that X, so the last bend
unwinds. The 1.14 title plate's tail (decoded: art ends x=539) still
clears — this seat's left edge lands at x=552, 13 free. -->
<Anchors>
<Anchor point="BOTTOMRIGHT" relativeTo="$parentContainer" relativePoint="TOPRIGHT"><Offset><AbsDimension x="4" y="20"/></Offset></Anchor>
</Anchors>
<Layers>
<Layer level="BACKGROUND">
<Texture name="$parentBorderLeft" file="Interface\Common\Common-Input-Border">
<Size><AbsDimension x="8" y="20"/></Size>
<Anchors><Anchor point="LEFT"><Offset><AbsDimension x="-5" y="0"/></Offset></Anchor></Anchors>
<TexCoords left="0" right="0.0625" top="0" bottom="0.625"/>
</Texture>
<Texture name="$parentBorderRight" file="Interface\Common\Common-Input-Border">
<Size><AbsDimension x="8" y="20"/></Size>
<Anchors><Anchor point="RIGHT"><Offset><AbsDimension x="0" y="0"/></Offset></Anchor></Anchors>
<TexCoords left="0.9375" right="1" top="0" bottom="0.625"/>
</Texture>
<Texture name="$parentBorderMiddle" file="Interface\Common\Common-Input-Border">
<Size><AbsDimension y="20"/></Size>
<Anchors>
<Anchor point="LEFT" relativeTo="$parentBorderLeft" relativePoint="RIGHT"/>
<Anchor point="RIGHT" relativeTo="$parentBorderRight" relativePoint="LEFT"/>
</Anchors>
<TexCoords left="0.0625" right="0.9375" top="0" bottom="0.625"/>
</Texture>
</Layer>
<Layer level="ARTWORK">
<!-- The "Search" placeholder (era instructions label: GameFontDisableSmall
tinted 0.35). OnTextChanged below toggles it on first text. -->
<FontString name="$parentInstructions" inherits="GameFontDisableSmall" justifyH="LEFT" text="Search">
<Anchors>
<Anchor point="TOPLEFT"><Offset><AbsDimension x="5" y="0"/></Offset></Anchor>
<Anchor point="BOTTOMRIGHT"><Offset><AbsDimension x="-20" y="0"/></Offset></Anchor>
</Anchors>
<Color r="0.35" g="0.35" b="0.35"/>
</FontString>
</Layer>
</Layers>
<Frames>
<!-- The clear X (era ClearButtonMixin: shown while text exists, half-alpha
icon, click wipes). Shown/hidden by the box's OnTextChanged below. -->
<Button name="$parentClearButton" hidden="true">
<Size><AbsDimension x="17" y="17"/></Size>
<Anchors><Anchor point="RIGHT"><Offset><AbsDimension x="-3" y="0"/></Offset></Anchor></Anchors>
<Layers>
<Layer level="ARTWORK">
<Texture name="$parentIcon" file="Interface\Buttons\UI-Panel-MinimizeButton-Up" alpha="0.5">
<Size><AbsDimension x="17" y="17"/></Size>
<Anchors><Anchor point="CENTER"/></Anchors>
</Texture>
</Layer>
</Layers>
<Scripts>
<OnClick>
OptionsFrameSearchBox:SetText("")
OptionsFrameSearchBox:ClearFocus()
</OnClick>
<OnEnter>getglobal(this:GetName() .. "Icon"):SetAlpha(1.0)</OnEnter>
<OnLeave>getglobal(this:GetName() .. "Icon"):SetAlpha(0.5)</OnLeave>
</Scripts>
</Button>
</Frames>
<TextInsets><AbsInset left="5" right="20" top="0" bottom="0"/></TextInsets>
<Scripts>
<!-- Placeholder + clear-button toggle, then the search proper (0984). -->
<OnTextChanged>
if this:GetText() == "" then
OptionsFrameSearchBoxInstructions:Show()
OptionsFrameSearchBoxClearButton:Hide()
else
OptionsFrameSearchBoxInstructions:Hide()
OptionsFrameSearchBoxClearButton:Show()
end
OptionsSearch_OnTextChanged(this:GetText())
</OnTextChanged>
<OnEscapePressed>this:ClearFocus()</OnEscapePressed>
</Scripts>
<FontString inherits="GameFontHighlightSmall"/>
</EditBox>
<!-- ── Close (no corner X — 0989's directed cut, header SCOPE note; the red
button and ESC are the window's exits) ─────────────────────────────────── -->
<!-- The red Close (era SettingsPanel.CloseButton). -->
<Button name="$parentCloseButton" inherits="OptionsRedButtonTemplate" text="Close">
<Anchors><Anchor point="BOTTOMRIGHT"><Offset><AbsDimension x="-16" y="16"/></Offset></Anchor></Anchors>
<Scripts>
<OnClick>
PlaySound("igMainMenuClose")
HideUIPanel(OptionsFrame)
</OnClick>
</Scripts>
</Button>
<!-- The era Apply (SettingsPanel.ApplyButton: 96x22, RIGHT of Close's LEFT at -2,
Blizzard_SettingsPanel.xml l.52-57): exists only while a deferred row holds a
pending value — era enables and SHOWS it together (SetApplyButtonEnabled) —
and commits every pending edit on click. -->
<Button name="$parentApplyButton" inherits="OptionsRedButtonTemplate" text="Apply" hidden="true">
<Anchors><Anchor point="RIGHT" relativeTo="$parentCloseButton" relativePoint="LEFT"><Offset><AbsDimension x="-2" y="0"/></Offset></Anchor></Anchors>
<Scripts>
<OnLoad>self:Disable()</OnLoad>
<OnClick>OptionsPage_Apply()</OnClick>
</Scripts>
</Button>
</Frames>
<Scripts>
<!-- Controls is the default page; a re-opened window keeps its last selection (the
era remembers too — Settings.OpenToCategory's default seat). The scale re-fits
on every show — the era panel manager re-runs checkFit the same way. -->
<OnShow>
OptionsFrame_UpdateScale()
OptionsFrame_SelectCategory(this.selectedCategory or "Controls")
</OnShow>
<!-- Hiding discards pending edits (the era wipes its modified table on hide; its
close-time confirm dialog is cut — SCOPE note), ends a live search (the
era Flush's ClearSearchBox, SettingsPanel.lua l.187-193), and disarms a
straggling bind capture — the host seam swallowing all input with no window
on screen would be a lockout (KeyBindingsPage.xml, decision 1008). -->
<OnHide>
OptionsFrameSearchBox:SetText("")
OptionsPage_DiscardPending()
KeyBindings_SetSelected(nil)
KeyBindings_Output("")
</OnHide>
</Scripts>
</Frame>
<!-- ══ THE TWO SURVIVING ALIASES, AND THE ONE THAT HAD TO GO (decision 2115) ══════════════
The reference has THREE options windows — `OptionsFrame` (video), `SoundOptionsFrame` and
`UIOptionsFrame` (interface) — and benilla's one window answers to the first name outright,
because that is what this frame is called. What happens to the other two is not one
question but two:
**`UIOptionsFrame` is NO LONGER ALIASED, and must never be.** The reference's own
`UIOptionsFrame.xml` is loaded, hidden, at the manifest's l.21 seat, so that name is a real
frame with real children — which is the whole point of 2115: pfUI's
`skins/blizzard/options-interface.lua` does `UIOptionsFrame:SetWidth(1024)` and then walks
`UIOptionsFrameCheckButton1..69`. Under the old alias that `SetWidth` landed on THIS window,
the one the player opens. Nothing of ours may `Show()` the stock frame either; it is
`hidden="true"` in its own file and that is what keeps it off the screen.
**`SoundOptionsFrame` stays aliased**, because we do not load the reference's
`SoundOptionsFrame.xml` at all (2115 says why: nothing in the addon corpus needs it beyond
one optional pfUI skin). It answers exactly two stock consumers, both of which read all
three window names UNGUARDED and both of which are asking "is an options window open":
`MainMenuBarMicroButtons.lua` l.47-52 (`UpdateMicroButtons`, deciding whether the Main Menu
micro button is pushed — decision 1987) and `UIParent.lua:996` (`IsOptionFrameOpen`, on the
path of every window close). While ours is up the honest answer to both is yes, and the
alias is how they get it. `OptionsFrame`, being this frame's own name, answers them too.
**`OptionsFrameCancel` stays**, and it is the ESC ladder's: the stock `ToggleGameMenu`
(UIParent.lua l.1465-1497, off the chain since 1988) closes an open options window with
`OptionsFrame:IsVisible()` → `OptionsFrameCancel:Click()`. Ours has a corner X whose click
is the same discard-and-hide (`HideUIPanel(OptionsFrame)`, and OnHide discards the pending
page), so that button answers to the reference's name.
**One behaviour changes because the alias went, and it is the right one.**
`FloatingChatFrame.lua` l.842/860 gate the chat tab and dock hover on
`not UIOptionsFrame:IsShown()`. That now reads the hidden stock frame, so chat-tab hover
works while OUR options window is open. The reference's gate exists because ITS interface
window is `frameStrata="FULLSCREEN"` with `setAllPoints` and covers the chat; ours is a
panel that does not. No adapter is added for it (2115).
BENILLA_ALLOW_OWN_UI=1 -->
<Script><![CDATA[
SoundOptionsFrame = OptionsFrame
OptionsFrameCancel = OptionsFrameCloseButton
]]></Script>
</Ui>