Files
benilla-octo/crates/benilla-app/assets/ui/OptionsFrame.xml
T
SamandClaude Fable 5 67c0c000fb the world redraws on resize, and terrain takes its DXT blocks
- the world froze on every resize or graphics change: the backdrop was resized by writing a fresh image through the existing handle, and a mutated `Image` is a whole new GPU texture while a material's bind group is captured once and never re-captured — so the camera drew into a texture the UI never sampled, and the last frame stretched over the window forever; a resized world target is now a NEW asset (publish, retire the old one, and the UI pass forgets materials for removed images so a window drag does not leak a 46 MB target per frame), and a resized capture now matches the native render bit for bit
- terrain tile layer arrays take the DXT blocks when the tile's whole layer set allows it — one array has one format, so eligibility is the tile's, not a texture's — and the install's own census says narrow is enough: 87 % of Azeroth's tiles and 92 % of Kalimdor's pass, layer bytes 1.57 GB → 0.63 and 2.23 → 0.76; the decode-tax comment is corrected on the way past — decoding costs 8x DXT1's bytes and 4x DXT3/DXT5's, not 4x and 2x
- Environment Detail is the reference's slider, not a dropdown — row 27 of 1.12's own slider table, with the readout keeping the stop names because a bare "1" means nothing to a player — and the shipped ground cover comes off High to Medium: the x3 clutter density had been matched to a configured install, and Medium is the nearest stop no sparser than what a fresh install draws on capable hardware
- Display Mode is modern Classic's two-entry dropdown — Windowed (Fullscreen) / Windowed, a boolean at Blizzard's own source, where the three-mode list is pre-8.0.1 folklore — riding `gxWindow` so saved configs keep meaning, with the label-against-value polarity pinned by the row's first test; the Multisampling row comes off the Graphics page for now (the CVar, the device clamp and the bindings all stay): with alpha-to-coverage set nowhere, MSAA touches polygon silhouettes only, and render scale on the same page measures four times the visual change

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

3580 lines
259 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 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 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[
-- 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. 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, 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 (rects
-- resolve after Lua runs, so a reflow answers a frame later — the OptionsTab_FitWidth
-- idiom), and 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."
-- 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", "RowAutoLoot", "RowInvertMouse", "RowMouseSpeed",
"RowCameraFollowStyle", "RowMaxCameraDistance",
},
Audio = {
"RowEnableAll", "RowMaster", "RowSound", "RowMusic", "RowAmbience",
"RowEnableMusic", "RowEnableAmbience",
},
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. Help (29) closes the page, as before.
-- 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",
"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 — carries **no cvar, uvar or func in the
-- reference either** (UIOptionsFrame.lua l.51 is a bare `{ index = 51 }`), so
-- it is inert there; a working row here would be an invention, not a fix
-- · 68 Disable Spam Filter — benilla filters no chat text. Unlike 51 this is a REAL
-- omission and not an inert row: `spamFilter` is registered "1" at `0x402e8e`
-- with a change callback, and its one reader is the chat chokepoint `0x49a870`,
-- which drops a `CHAT_MSG_FILTERED` line on a word-list hit unless the type is
-- whisper (§5, wow-re `system/ui/scratch/spam-filter-cvar.md`). The row lands
-- with the filter.
-- 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",
},
-- 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. MultiBars.xml's
-- header carries 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)
if row.applyFunc then row.applyFunc() 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 other end of `applyFunc`, and the reason a saved "off" used to come back on
-- (decision 1139). A CVar row needs nothing here — the host reads its own table at boot.
-- A GLOBAL row does: the whole UI's XML runs BEFORE the saved chunk executes over it
-- (1128's load order), so every file-scope consumer already ran against the SHIPPED
-- default — CombatText_OnLoad armed six event registrations from SHOW_COMBAT_TEXT = "1",
-- BuffFrame seated its rows from the durations-shown geometry — and the saved value
-- landing afterwards changed the variable while leaving the world it configured alone.
--
-- 1.12 closes this with a hand-written ladder in UIOptionsFrame's VARIABLES_LOADED arm
-- (l.204-220, "Option specific function calls": BuffButtons_UpdatePositions, the
-- SIMPLE_CHAT/CHAT_LOCKED arms, MultiActionBar_ShowAllGrids). We already hold each side
-- effect on its own row, so the ladder is a walk: run every distinct applyFunc once, in
-- no particular order (they are independent by construction — each one re-derives one
-- feature's state from one global). A row whose defining file was not loaded has a nil
-- applyFunc and is skipped, which is what keeps a partial test harness honest.
function OptionsFrame_ApplySavedSettings()
local ran = {}
for key, rows in pairs(OPTIONS_PAGE_ROWS) do
for _, rkey in ipairs(rows) do
local row = getglobal("OptionsFrameContainerBody" .. key .. rkey)
local apply = row and row.applyFunc
if apply and not ran[apply] then
ran[apply] = 1
apply()
end
end
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, 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 (MultiBars.xml'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
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
-- 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 our kit's
-- UIDropDownMenu_DisableButton addresses a LIST row (level, id), not the capsule.
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" 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, 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="$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>
<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>
<!-- 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. Our default is the factor fully
raised (30 yd), where 1.12 ships 1.0. -->
<!-- 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>
</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, 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>
<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, 177, 777, 60, "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, 0, 2, 1, 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>
</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_UpdateDisplayedMessages
</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_UpdateDisplayedMessages
</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_UpdateDisplayedMessages
</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_UpdateDisplayedMessages
</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_UpdateDisplayedMessages
</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_UpdateDisplayedMessages
</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_UpdateDisplayedMessages
</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_UpdateDisplayedMessages
</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_UpdateDisplayedMessages
</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_UpdateDisplayedMessages
</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_UpdateDisplayedMessages
</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_UpdateDisplayedMessages
</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_UpdateDisplayedMessages
</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_UpdateDisplayedMessages
</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). MultiBars.xml's
header carries the whole byte-level finding.
The getters and setters are written here as closures rather
than captured from MultiBars.xml 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) BenillaMultiBar_SetShown(1, value) 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) BenillaMultiBar_SetShown(2, value) 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) BenillaMultiBar_SetShown(3, value) 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) BenillaMultiBar_SetShown(4, value) 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")
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
</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
</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
</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>
<Frame name="$parentRowNewbieTips" 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>
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
</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>
</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. -->
<Frame 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()
this.updateFunc = KeyBindingsPage_Update
</OnLoad>
</Scripts>
</Frame>
</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>
<!-- The window is where the saved globals get applied, not just edited: the chunk
lands after every file-scope consumer has already run (decision 1139). This is
1.12's own seat for it — UIOptionsFrame registers the same event for the same
reason — and the window is hidden at the time, which is the point: the event is
the UI's, not the panel's. -->
<OnLoad>this:RegisterEvent("VARIABLES_LOADED")</OnLoad>
<OnEvent>
if event == "VARIABLES_LOADED" then
OptionsFrame_ApplySavedSettings()
end
</OnEvent>
<!-- 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>
</Ui>