Files
benilla-octo/crates/benilla/assets/ui/OptionsFrame.xml
T
SamandClaude Fable 5 77dc8f8ead pets get their action bar, macros arrive, and hunters draw the bow
- a hunter's or warlock's pet has its action bar now: attack, follow, stay, the pet's own spells with cooldowns and autocast sparkles
- the macro system lands: the macro window, the icon chooser, /cast and /macro, and macros dragged onto the action bar
- hunter shots look right at last: the bow draws and releases on the shot, and the arrow lays a spark trail its whole flight
- the options window re-dresses in 1.12's own art with the classic dialog look, and gets a working search box and clickable steppers

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

1583 lines
100 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). 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.
· 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 via an OnUpdate measure poll; the era's
measure-at-the-medium-font quirk is deliberately dropped (~6 units of padding).
- **Search WORKS (0984)** — the era SettingsPanel mechanism transcribed (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.
- **No scroll bars**: the static category tree (9 rows, 2 headers) fits its 199px column
outright, and the deepest page (Audio, 7 rows) fits its area. MinimalScrollBar arrives
when a page outgrows the space.
- **Setting-page cuts (0957 Audio, 0959 Graphics)**: setting-row tooltips (the era
OPTION_TOOLTIP_* hovers) are not wired; 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 in slice B (real nameplate knobs exist to back its
page).
- **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, transcribed 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[
-- 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", "Social", "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) — transcribed 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. Era measures at OnLoad;
-- our measure round-trip answers a frame late, so OnUpdate polls until the width is real
-- (the chat-header converge idiom), then latches. 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)
if tab.fitted then return end
local w = getglobal(tab:GetName() .. "Text"):GetStringWidth()
if w and w > 0 then
tab:SetWidth(w + 40)
tab.fitted = true
end
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, verbatim
-- (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
if OPTIONS_PAGE_ROWS[key] then
OptionsPage_Refresh(key)
OptionsFrameContainerDefaults:Enable()
else
OptionsFrameContainerDefaults:Disable()
end
end
-- ── Search (0984): the era SettingsPanel mechanism, transcribed ─────────────────────
-- 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.gmatch(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
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 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)
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
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
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
-- 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()
for k in pairs(OPTIONS_PAGE_ROWS) do
OptionsPage_Layout(k)
getglobal("OptionsFrameContainerBodySearchHead" .. k):Hide()
end
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.
OPTIONS_PAGE_ROWS = {
Controls = { "RowStickyTarget", "RowAutoLoot", "RowInvertMouse" },
Audio = {
"RowEnableAll", "RowMaster", "RowSound", "RowMusic", "RowAmbience",
"RowEnableMusic", "RowEnableAmbience",
},
Graphics = { "RowUiScale" },
}
function OptionsRow_OnLoad(row, cvar, label, child)
row.cvar = cvar
-- 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)
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
-- 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.
function OptionsSliderRow_Setup(row, min, max, step, fmt)
local slider = getglobal(row:GetName() .. "ControlSlider")
row.fmt = fmt
row.refreshing = 1
slider:SetMinMaxValues(min, max)
slider:SetValueStep(step)
row.refreshing = nil
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
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.
function OptionsRow_Hover(row, on)
local hover = getglobal(row:GetName() .. "Hover")
if on then hover:Show() else hover: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
SetCVar(row.cvar, 1)
else
SetCVar(row.cvar, 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 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, transcribed (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 == GetCVar(row.cvar) then
row.pending = nil
else
row.pending = str
end
OptionsApply_Update()
else
SetCVar(row.cvar, 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 GetCVar(row.cvar) == "0" then checked = 1 end
box:SetChecked(checked)
else
box:SetChecked(GetCVar(row.cvar))
end
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 GetCVar(row.cvar) 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 ~= "Audio" then return end
local on = GetCVar("MasterSoundEffects") ~= "0"
local box = OptionsFrameContainerBodyAudioRowEnableAmbienceCheck
local label = OptionsFrameContainerBodyAudioRowEnableAmbienceLabel
if on then
box:Enable()
label:SetFontObject("GameFontNormal")
else
box:Disable()
label:SetFontObject("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
SetCVar(row.cvar, GetCVarDefault(row.cvar))
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
SetCVar(row.cvar, 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>
<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. -->
<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>
<Frame name="$parentControl">
<Size><AbsDimension x="250" y="40"/></Size>
<Anchors><Anchor point="LEFT" relativePoint="CENTER"><Offset><AbsDimension x="-80" y="3"/></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="0"/></Offset></Anchor>
<Anchor point="RIGHT"><Offset><AbsDimension x="-19" y="0"/></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="0"/></Offset></Anchor>
<Anchor point="BOTTOMRIGHT"><Offset><AbsDimension x="-19" y="0"/></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>
<Frame name="OptionsFrame" frameStrata="HIGH" 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="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, 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). The era's width law is
text + 40, measured at OnLoad — our measure round-trip answers a frame late,
so OnUpdate polls until the width is real, the chat-header idiom. -->
<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>
<OnUpdate>OptionsTab_FitWidth(this)</OnUpdate>
</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>
<OnUpdate>OptionsTab_FitWidth(this)</OnUpdate>
</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="$parentRowSocial" inherits="OptionsCategoryRowTemplate">
<Anchors><Anchor point="TOPLEFT" relativeTo="$parentRowCombat" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="0" y="-2"/></Offset></Anchor></Anchors>
<Scripts><OnLoad>OptionsCategoryRow_OnLoad(self, "Social", "Social")</OnLoad></Scripts>
</Button>
<Button name="$parentRowKeybindings" inherits="OptionsCategoryRowTemplate">
<Anchors><Anchor point="TOPLEFT" relativeTo="$parentRowSocial" 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);
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>OptionsPage_Defaults(OptionsFrame.selectedCategory)</OnClick>
</Scripts>
</Button>
<!-- The page body: one child frame per category with rows, shown by
OptionsFrame_SelectCategory (Audio 0957, Graphics 0959). -->
<Frame name="$parentBody">
<Anchors>
<Anchor point="TOPLEFT"><Offset><AbsDimension x="0" y="-50"/></Offset></Anchor>
<Anchor point="BOTTOMRIGHT"/>
</Anchors>
<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). -->
<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")
self.invert = 1
</OnLoad></Scripts>
</Frame>
<Frame name="$parentRowAutoLoot" 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, "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")</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")
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")</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", 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", 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", 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")
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")
self.soundQuirk = 1
</OnLoad></Scripts>
</Frame>
</Frames>
</Frame>
<!-- The Graphics page (0959; farclip row retired 0961 — the
director's call: the page holds UI Scale alone for now, the
farclip CVar/knob/env/debug-panel plumbing all stay). Range/
step/label are the 1.12 OptionsFrameSliders table verbatim
(OptionsFrame.lua l.25): 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. -->
<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")
OptionsSliderRow_Setup(self, 0.64, 1.0, 0.01)
-- 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>
</Frames>
</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>
</Frames>
</Frame>
</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) and ends a live search (the
era Flush's ClearSearchBox, SettingsPanel.lua l.187-193). -->
<OnHide>
OptionsFrameSearchBox:SetText("")
OptionsPage_DiscardPending()
</OnHide>
</Scripts>
</Frame>
</Ui>