Files
moon-well-ui/data/addon/ForeverUI/Settings.lua
T
Adrestie 5842eb7a2b ForeverUI : code en anglais, commentaires resserres, code mort retire
Traduction
- Identifiants en anglais dans tout le module : addon, ecrans d'accueil,
  outils Python, banc d'essai et patcheur C#.
- Fichiers au nom francais renommes (Gabarits.lua -> Templates.lua,
  Textes_*.lua -> Texts_*.lua, deployer.py -> deploy.py,
  tools/patcheur -> tools/patcher, etc.).
- Commandes /fui et options de deploy.py en anglais (--check, --server,
  --remove-dbc).
- Cles des variables sauvegardees et de data/dbc/statistics.json
  traduites, sans reprise des anciennes cles.
- API de la micro-barre : ForeverUI.AddMicroButton(def) avec les champs
  name, atlasSet, after, tooltip, onClick, ready, et
  ForeverUI.UpdateMicro(name, isOpen).
- Commentaires reecrits en anglais, courts, sans dates ni historique ;
  les fonctions au nom peu parlant et leurs parametres sont expliques.

Code mort
- Exports, fonctions et variables jamais lus retires.
- Retires a la demande des modules : ForeverUI.PvPJauge, les exports
  FondEntete / TuilerEntete / Polices de TradeSkill, l'art legacy-up-c60,
  honorfill(half)blue et inv_misc_book_09, leurs entrees d'atlas et les
  parties d'outils correspondantes.

Verifications : liaisons des noms comparees avant et apres, banc vert,
chemins de textures inchanges, generateurs reproduisant les fichiers,
patcheur identique octet pour octet.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-29 09:15:52 +02:00

670 lines
25 KiB
Lua

-- ForeverUI: camelot art on the client Video, Sound and Interface options windows.
-- camelot has no such windows (it uses SettingsPanel): the client screens, logic and CVars stay,
-- with the same art pieces and numbers as the glue options (ForeverUIGlueOptions.lua).
-- Panels of other addons (AddOns tab) keep their controls; only the window, list and tabs change.
local ForeverUI = ForeverUI or {}
_G.ForeverUI = ForeverUI
local Tpl = ForeverUI.Templates
local truthy = Tpl.Truthy
local R = {}
ForeverUI.Settings = R
-- Numbers kept in tables: Lua 5.1 allows at most 60 upvalues per function.
local N = {
box = { 0.4, 0.4, 0.4 }, -- OptionsBoxTemplate border
handle = { 16, 15 }, -- camelot: 20 x 19
disabledHandle = 0.7, -- MinimalSliderWithSteppers
button = { 96, 22 },
buttonEdge = 16,
buttonGap = 2,
menu = { left = 16, top = -19, right = -17, height = 25, background = 7, text = 13, arrowY = -5 },
tab = { height = 37, margin = 40, text = 4, selectedText = 6, gap = 5 },
watcher = 0.1,
}
-- ------------------------------------------------------------ Controls
-- Sets one state texture of a button to a camelot atlas, stretched over the button.
-- set, get: texture setter and getter names; name: atlas; mode: optional blend mode.
local function applyState(b, set, get, name, mode)
b[set](b, Tpl.Art(name)[1])
local t = b[get](b)
Tpl.Place(t, name)
t:ClearAllPoints()
t:SetAllPoints(b)
if mode then
t:SetBlendMode(mode)
end
return t
end
-- SettingsCheckboxTemplate at the client checkbox size (26), no hover glow.
function R.Checkbox(c)
if c.foreverCell then return end
c.foreverCell = true
applyState(c, "SetNormalTexture", "GetNormalTexture", "checkbox-minimal")
applyState(c, "SetPushedTexture", "GetPushedTexture", "checkbox-minimal")
local glow = c:GetHighlightTexture()
if glow then
glow:SetTexture(nil)
glow:SetAlpha(0)
end
applyState(c, "SetCheckedTexture", "GetCheckedTexture", "checkmark-minimal")
applyState(c, "SetDisabledCheckedTexture", "GetDisabledCheckedTexture", "checkmark-minimal-disabled")
end
-- OptionsBoxTemplate: camelot tooltip border in the client grey (0.4), no background (the
-- client has none).
function R.Box(f)
if f.foreverNineSlice then return end
local p = ForeverUI.Tooltips.Skin(f)
for name, t in pairs(p) do
if name == "Center" then
t:SetAlpha(0)
else
t:SetVertexColor(N.box[1], N.box[2], N.box[3], 1)
end
end
end
-- MinimalSliderTemplate on the client slider, at its size (17 high, the track height):
-- Left / Right at atlas size, Middle between them, thumb Minimal_SliderBar_Button (16 x 15).
R.sliders = {}
function R.Cursor(s)
if s.foreverCursor then return end
s.foreverCursor = true
s:SetBackdrop(nil)
local g = s:CreateTexture(nil, "ARTWORK")
Tpl.Place(g, "minimal_sliderbar_left", true)
g:SetPoint("LEFT", s, "LEFT")
local d = s:CreateTexture(nil, "ARTWORK")
Tpl.Place(d, "minimal_sliderbar_right", true)
d:SetPoint("RIGHT", s, "RIGHT")
local m = s:CreateTexture(nil, "ARTWORK")
Tpl.Place(m, "_minimal_sliderbar_middle", true)
m:SetPoint("TOPLEFT", g, "TOPRIGHT")
m:SetPoint("TOPRIGHT", d, "TOPLEFT")
s:SetThumbTexture(Tpl.Art("minimal_sliderbar_button")[1])
local button = s:GetThumbTexture()
Tpl.Place(button, "minimal_sliderbar_button")
button:SetWidth(N.handle[1])
button:SetHeight(N.handle[2])
R.sliders[#R.sliders + 1] = s
end
-- ------------------------------------------------------------ Dropdown menu
-- WowStyle2DropdownTemplate (SettingsDropdownControl), inside the visible part of the 3.3.5
-- frame (CharacterCreate-LabelFrame: opaque from 16 to 111 of 128 wide, 19 to 45 of 64 high):
-- background common-dropdown-c-button from (-7, 7) to (7, -7) with its hover, pressed, open
-- and disabled states; hover arrow at BOTTOM (0, -5), desaturated when disabled; text centered
-- from 13 to -13, shifted (2, -1) when pressed, in GameFontNormalSmall (size 10) instead of
-- GameFontNormal. The list: DropDown.lua, style 2.
local function isOpen(dd)
return DropDownList1 and DropDownList1:IsShown() and UIDROPDOWNMENU_OPEN_MENU == dd
end
-- Draws the dropdown state: background atlas, hover arrow, text color and pressed offset.
local function paintMenu(dd)
local b = dd.foreverButton
local active = truthy(b:IsEnabled())
local n = "common-dropdown-c-button"
if not active then
n = n .. "-disabled"
elseif b.foreverMouseDown and b.foreverHovered then
n = n .. "-pressedhover-1"
elseif b.foreverHovered then
n = n .. "-hover-1"
elseif b.foreverMouseDown then
n = n .. "-pressed-1"
elseif isOpen(dd) then
n = n .. "-open"
end
dd.foreverBackground:Place(n)
Tpl.SetShown(dd.foreverArrow, b.foreverHovered)
dd.foreverArrow:SetDesaturated(not active)
local text = _G[dd:GetName() .. "Text"]
-- The client greys the text with SetVertexColor: reset it and use our own color.
text:SetVertexColor(1, 1, 1)
if active then
text:SetTextColor(1, 0.82, 0)
else
text:SetTextColor(0.5, 0.5, 0.5)
end
-- camelot: SetDisplacedRegions(2, -1, Text)
local dx, dy = 0, 0
if b.foreverMouseDown and active then
dx, dy = 2, -1
end
text:ClearAllPoints()
text:SetPoint("LEFT", b, "LEFT", N.menu.text + dx, dy)
text:SetPoint("RIGHT", b, "RIGHT", -N.menu.text + dx, dy)
end
-- Skins a client UIDropDownMenu as a camelot dropdown.
function R.Menu(dd)
if dd.foreverButton then return end
dd.foreverStyle = 2
local name = dd:GetName()
local left, right = _G[name .. "Left"], _G[name .. "Right"]
for _, suffix in ipairs({ "Left", "Middle", "Right" }) do
_G[name .. suffix]:SetAlpha(0)
end
local b = _G[name .. "Button"]
dd.foreverButton = b
b:ClearAllPoints()
b:SetPoint("TOPLEFT", left, "TOPLEFT", N.menu.left, N.menu.top)
b:SetPoint("RIGHT", right, "RIGHT", N.menu.right, 0)
b:SetHeight(N.menu.height)
Tpl.ClearArt(b)
-- Background as a region of the dropdown, below its text; the button (child frame) holds only
-- the arrow.
local text = _G[name .. "Text"]
text:SetHeight(20)
text:SetFontObject(GameFontNormalSmall)
text:SetJustifyH("CENTER")
dd.foreverBackground = Tpl.StretchedAtlas(dd, "common-dropdown-c-button", "BACKGROUND")
dd.foreverBackground.rect:SetPoint("TOPLEFT", b, "TOPLEFT", -N.menu.background, N.menu.background)
dd.foreverBackground.rect:SetPoint("BOTTOMRIGHT", b, "BOTTOMRIGHT", N.menu.background, -N.menu.background)
dd.foreverArrow = b:CreateTexture(nil, "OVERLAY")
Tpl.Place(dd.foreverArrow, "common-dropdown-c-button-hover-arrow", true)
dd.foreverArrow:SetPoint("BOTTOM", b, "BOTTOM", 0, N.menu.arrowY)
dd.foreverPaint = function() paintMenu(dd) end
b:HookScript("OnEnter", function(self)
self.foreverHovered = true
paintMenu(dd)
-- The dropdown's own hover script (its tooltip) stays the client's: the button forwards it.
local onEnter = dd:GetScript("OnEnter")
if onEnter then onEnter(dd) end
end)
b:HookScript("OnLeave", function(self)
self.foreverHovered = false
paintMenu(dd)
local onLeave = dd:GetScript("OnLeave")
if onLeave then onLeave(dd) end
end)
b:HookScript("OnMouseDown", function(self) self.foreverMouseDown = true; paintMenu(dd) end)
b:HookScript("OnMouseUp", function(self) self.foreverMouseDown = false; paintMenu(dd) end)
b:HookScript("OnDisable", function() paintMenu(dd) end)
b:HookScript("OnEnable", function() paintMenu(dd) end)
dd:HookScript("OnShow", function() paintMenu(dd) end)
paintMenu(dd)
end
-- The client resets the dropdown in UIDropDownMenu_EnableDropDown / _DisableDropDown:
-- repaint after it.
for _, name in ipairs({ "UIDropDownMenu_EnableDropDown", "UIDropDownMenu_DisableDropDown" }) do
if type(_G[name]) == "function" then
hooksecurefunc(name, function(dd)
if dd and dd.foreverPaint then dd.foreverPaint() end
end)
end
end
-- Button text, converted like the list lines (Tpl.Utf8Text).
hooksecurefunc("UIDropDownMenu_SetText", function(dd, text)
local fs = dd and dd.foreverButton and _G[dd:GetName() .. "Text"]
local clean = Tpl.Utf8Text(text)
if fs and clean ~= text then
fs:SetText(clean)
end
end)
-- ------------------------------------------------------------ Panels
-- Skins a whole panel: each control by its template, recursing into frames that hold others
-- (group boxes, sub-frames).
function R.Panel(frame)
for _, c in ipairs({ frame:GetChildren() }) do
local name = c:GetName()
local kind = c:GetObjectType()
if kind == "CheckButton" then
R.Checkbox(c)
elseif kind == "Slider" then
R.Cursor(c)
elseif name and _G[name .. "Button"] and _G[name .. "Middle"] and _G[name .. "Left"] then
R.Menu(c)
else
local background = c.GetBackdrop and c:GetBackdrop()
if background and type(background.edgeFile) == "string"
and string.find(string.lower(background.edgeFile), "ui%-tooltip%-border") then
R.Box(c)
end
if kind == "Frame" then
R.Panel(c)
end
end
end
end
-- Slider thumb at 0.7 alpha when disabled (MinimalSliderWithSteppers: ConfigureSlider). The
-- client disables sliders through a function stored on each one, so poll them while the
-- windows are open.
local watcher = CreateFrame("Frame")
watcher.t = 0
watcher:SetScript("OnUpdate", function(self, elapsed)
self.t = self.t + (elapsed or 0)
if self.t < N.watcher then
return
end
self.t = 0
local f1, f2, f3 = VideoOptionsFrame, AudioOptionsFrame, InterfaceOptionsFrame
if not ((f1 and f1:IsShown()) or (f2 and f2:IsShown()) or (f3 and f3:IsShown())) then
return
end
for _, s in ipairs(R.sliders) do
local active = not s.IsEnabled or truthy(s:IsEnabled())
s:GetThumbTexture():SetAlpha(active and 1 or N.disabledHandle)
end
end)
R.watcher = watcher
-- ------------------------------------------------------------ Categories
-- camelot SettingsCategoryListButtonMixin:UpdateStateInternal, run after the client
-- (OptionsList_DisplayButton resets fonts and toggle; OptionsList_SelectButton /
-- ClearSelection set the selection).
local function paintRow(list, b)
local el = b.element
local selected = el ~= nil and list.selection == el
Tpl.SetShown(b.foreverActive, selected)
Tpl.SetShown(b.foreverHover, not selected and b.foreverHovered)
local font
if selected or (el and el.parent) then
font = GameFontHighlight
else
font = GameFontNormal
end
b:SetNormalFontObject(font)
b:SetHighlightFontObject(font)
-- Toggle: common-button-dropdown-open / -closed
local t = b.toggle
if t and el and el.hasChildren then
local state = el.collapsed and "closed" or "open"
t:SetNormalTexture(ForeverUI.AtlasEntry("common-button-dropdown-" .. state)[1])
ForeverUI.SetAtlas(t:GetNormalTexture(), "common-button-dropdown-" .. state, true)
t:SetPushedTexture(ForeverUI.AtlasEntry("common-button-dropdown-" .. state .. "pressed")[1])
ForeverUI.SetAtlas(t:GetPushedTexture(), "common-button-dropdown-" .. state .. "pressed", true)
end
end
function R.PaintList(list)
for _, b in ipairs(list.buttons or {}) do
paintRow(list, b)
end
end
-- List scroll bar: MinimalScrollBar art (ScrollBar.lua pieces: arrows 17 x 11 with hover,
-- track top / middle / bottom, thumb top / middle / flipped top, 8 wide) laid over the client
-- bar. The client bar stays in place at alpha 0 and takes clicks, drags and the wheel through
-- its secure path: scrolling from addon code would run OptionsCategoryFrame_Update outside it
-- and taint the rows (button.element) the click reads. The thumb keeps the client's fixed size.
local BAR = {
width = 8, tip = 8,
upArrow = "minimal-scrollbar-arrow-top-c60", upArrowHover = "minimal-scrollbar-arrow-top-over-c60",
downArrow = "minimal-scrollbar-arrow-bottom-c60", downArrowHover = "minimal-scrollbar-arrow-bottom-over-c60",
trackTop = "minimal-scrollbar-track-top-c60", trackMiddle = "!minimal-scrollbar-track-middle-c60",
trackBottom = "minimal-scrollbar-track-bottom-c60",
cursorTip = "minimal-scrollbar-thumb-top-c60", cursorMiddle = "minimal-scrollbar-thumb-middle-c60",
}
-- Arrow art over a client scroll button. v: overlay frame; atlas, hover: normal and hover art.
local function arrow(v, button, atlas, hover)
-- At atlas size (17 x 11): a texture without a size takes the size of its sheet.
local t = v:CreateTexture(nil, "ARTWORK")
ForeverUI.SetAtlas(t, atlas)
t:SetPoint("CENTER", button, "CENTER", 0, 0)
button:HookScript("OnEnter", function() ForeverUI.SetAtlas(t, hover, true) end)
button:HookScript("OnLeave", function() ForeverUI.SetAtlas(t, atlas, true) end)
return t
end
function R.Bar(list)
local scroll = list.scrollFrame
local sb = scroll and _G[scroll:GetName() .. "ScrollBar"]
if not sb then return end
scroll:SetBackdrop(nil)
sb:SetAlpha(0)
local v = CreateFrame("Frame", nil, scroll)
v:SetFrameLevel(sb:GetFrameLevel() + 5)
v:SetAllPoints(scroll)
local b = BAR
v.top = arrow(v, _G[sb:GetName() .. "ScrollUpButton"], b.upArrow, b.upArrowHover)
v.down = arrow(v, _G[sb:GetName() .. "ScrollDownButton"], b.downArrow, b.downArrowHover)
local function slice(atlas, layer)
local t = v:CreateTexture(nil, layer)
ForeverUI.SetAtlas(t, atlas)
t:SetWidth(b.width)
return t
end
local ph = slice(b.trackTop, "BACKGROUND")
ph:SetPoint("TOP", sb, "TOP", 0, 0)
local pb = slice(b.trackBottom, "BACKGROUND")
pb:SetPoint("BOTTOM", sb, "BOTTOM", 0, 0)
local pm = slice(b.trackMiddle, "BACKGROUND")
pm:SetPoint("TOP", ph, "BOTTOM", 0, 0)
pm:SetPoint("BOTTOM", pb, "TOP", 0, 0)
local thumb = sb:GetThumbTexture()
local ch = slice(b.cursorTip, "ARTWORK")
ch:SetHeight(b.tip)
ch:SetPoint("TOP", thumb, "TOP", 0, 0)
-- Same piece, flipped, for the bottom.
local cb = slice(b.cursorTip, "ARTWORK")
local e = ForeverUI.AtlasEntry(b.cursorTip)
cb:SetTexCoord(e[2], e[3], e[5], e[4])
cb:SetHeight(b.tip)
cb:SetPoint("BOTTOM", thumb, "BOTTOM", 0, 0)
local cm = slice(b.cursorMiddle, "ARTWORK")
cm:SetPoint("TOP", ch, "BOTTOM", 0, 0)
cm:SetPoint("BOTTOM", cb, "TOP", 0, 0)
v.cursor = { ch, cm, cb }
list.foreverBar = v
end
-- Skins a client options category list: no border, camelot selection and hover, scroll bar.
function R.List(list)
if list.foreverCategories then return end
list.foreverCategories = true
local name = list:GetName()
-- No border: hide the eight border textures.
for _, suffix in ipairs({ "TopLeft", "TopRight", "BottomLeft", "BottomRight", "Left", "Right", "Top", "Bottom" }) do
local t = _G[name .. suffix]
if t then
t:SetAlpha(0)
end
end
for _, b in ipairs(list.buttons or {}) do
local glow = b:GetHighlightTexture()
if glow then
glow:SetTexture(nil)
glow:SetAlpha(0)
end
-- Selection and hover extend 6 px past each side of the row (187 for 175) and follow its
-- width: the client narrows the row when the scroll bar shows (OptionsList_DisplayScrollBar).
b.foreverActive = b:CreateTexture(nil, "BACKGROUND")
ForeverUI.SetAtlas(b.foreverActive, "options_list_active")
b.foreverActive:SetPoint("LEFT", b, "LEFT", -6, 0)
b.foreverActive:SetPoint("RIGHT", b, "RIGHT", 6, 0)
b.foreverActive:Hide()
b.foreverHover = b:CreateTexture(nil, "BACKGROUND")
ForeverUI.SetAtlas(b.foreverHover, "options_list_hover")
b.foreverHover:SetPoint("LEFT", b, "LEFT", -6, 0)
b.foreverHover:SetPoint("RIGHT", b, "RIGHT", 6, 0)
b.foreverHover:Hide()
b:HookScript("OnEnter", function(self)
self.foreverHovered = true
paintRow(list, self)
end)
b:HookScript("OnLeave", function(self)
self.foreverHovered = false
paintRow(list, self)
end)
end
R.Bar(list)
R.PaintList(list)
end
local function afterListSelect(list)
if list and list.foreverCategories then
R.PaintList(list)
end
end
hooksecurefunc("OptionsList_DisplayButton", function(b)
local list = b and b:GetParent()
if list and list.foreverCategories then
paintRow(list, b)
end
end)
hooksecurefunc("OptionsList_SelectButton", afterListSelect)
hooksecurefunc("OptionsList_ClearSelection", afterListSelect)
hooksecurefunc("OptionsCategoryFrame_Update", afterListSelect)
for _, name in ipairs({ "InterfaceCategoryList_Update", "InterfaceAddOnsList_Update" }) do
if type(_G[name]) == "function" then
hooksecurefunc(name, function()
afterListSelect(InterfaceOptionsFrameCategories)
afterListSelect(InterfaceOptionsFrameAddOns)
end)
end
end
-- ------------------------------------------------------------ Tabs
-- camelot MinimalTabTemplate on a client tab (PanelTemplates: the selected tab is disabled).
local function paintTab(o)
local selected = not truthy(o:IsEnabled())
local suffix = selected and "active_" or ""
for _, side in ipairs({ "left", "middle", "right" }) do
-- At atlas size (useAtlasSize): without a size, a texture takes the size of its sheet
-- (1024 x 1024).
ForeverUI.SetAtlas(o.foreverArt[side], "options_tab_" .. suffix .. side)
end
local text = o:GetFontString()
if text then
text:ClearAllPoints()
text:SetPoint("BOTTOM", o, "BOTTOM", 0, selected and N.tab.selectedText or N.tab.text)
end
o:SetNormalFontObject((selected or o.foreverHovered) and GameFontHighlightSmall or GameFontNormalSmall)
o:SetWidth((text and text:GetStringWidth() or 0) + N.tab.margin)
end
function R.Tab(o)
if o.foreverArt then return end
local name = o:GetName()
for _, suffix in ipairs({ "Left", "Middle", "Right", "LeftDisabled", "MiddleDisabled", "RightDisabled" }) do
local t = _G[name .. suffix]
if t then t:SetAlpha(0) end
end
local glow = o:GetHighlightTexture()
if glow then
glow:SetTexture(nil)
glow:SetAlpha(0)
end
o:SetHeight(N.tab.height)
local a = {}
a.left = o:CreateTexture(nil, "BACKGROUND")
a.left:SetPoint("BOTTOMLEFT", o, "BOTTOMLEFT")
a.right = o:CreateTexture(nil, "BACKGROUND")
a.right:SetPoint("BOTTOMRIGHT", o, "BOTTOMRIGHT")
a.middle = o:CreateTexture(nil, "BACKGROUND")
a.middle:SetPoint("TOPLEFT", a.left, "TOPRIGHT")
a.middle:SetPoint("TOPRIGHT", a.right, "TOPLEFT")
o.foreverArt = a
o:SetDisabledFontObject(GameFontHighlightSmall)
o:HookScript("OnEnter", function(self) self.foreverHovered = true; paintTab(self) end)
o:HookScript("OnLeave", function(self) self.foreverHovered = false; paintTab(self) end)
o:HookScript("OnShow", paintTab)
o:HookScript("OnEnable", paintTab)
o:HookScript("OnDisable", paintTab)
paintTab(o)
end
-- ------------------------------------------------------------ Inner frame
-- camelot Options_InnerFrame (SettingsPanel, OVERLAY, TOPLEFT (17, -64), 886 x 618 on the
-- optionsc60 sheet): frame around the category list and the settings, with a dark translucent
-- fill, a brown edge, an inward gradient (29 on the sides, 82 at the top, 167 at the bottom)
-- and the list separator (columns 199-200). camelot: top 12 above the list, right edge 17 from
-- the window, tabs on its top. Cut here into 5 x 3 pieces: edges and gradients at their size,
-- separator in place, the rest stretched.
local INNER = {
cols = { 0, 29, 199, 201, 857, 886 },
ranks = { 0, 82, 451, 618 },
left = -1, top = 12, down = -4, right = -17,
}
-- host: parent of the textures; f: the window; list: the category list the frame fits on.
-- Returns the rectangle (an empty frame, for anchors).
function R.Interior(host, f, list)
local I = INNER
local e = ForeverUI.AtlasEntry("options_innerframe")
local rect = CreateFrame("Frame", nil, host)
rect:SetPoint("TOPLEFT", list, "TOPLEFT", I.left, I.top)
rect:SetPoint("BOTTOMLEFT", list, "BOTTOMLEFT", I.left, I.down)
rect:SetPoint("RIGHT", f, "RIGHT", I.right, 0)
local W, H = e[6], e[7]
local du, dv = (e[3] - e[2]) / W, (e[5] - e[4]) / H
-- Separator where camelot has it: list width from the frame's left edge.
local sep = list:GetWidth() - I.left - 1
local xs = { 0, I.cols[2], sep, sep + 2 }
local pieces = {}
for c = 1, 5 do
for r = 1, 3 do
local t = host:CreateTexture(nil, "ARTWORK")
t:SetTexture(e[1])
t:SetTexCoord(e[2] + I.cols[c] * du, e[2] + I.cols[c + 1] * du,
e[4] + I.ranks[r] * dv, e[4] + I.ranks[r + 1] * dv)
if c == 5 then
t:SetPoint("RIGHT", rect, "RIGHT", 0, 0)
t:SetWidth(I.cols[6] - I.cols[5])
elseif c == 4 then
t:SetPoint("LEFT", rect, "LEFT", xs[4], 0)
t:SetPoint("RIGHT", rect, "RIGHT", -(I.cols[6] - I.cols[5]), 0)
else
t:SetPoint("LEFT", rect, "LEFT", xs[c], 0)
t:SetWidth(xs[c + 1] - xs[c])
end
if r == 1 then
t:SetPoint("TOP", rect, "TOP", 0, 0)
t:SetHeight(I.ranks[2])
elseif r == 3 then
t:SetPoint("BOTTOM", rect, "BOTTOM", 0, 0)
t:SetHeight(I.ranks[4] - I.ranks[3])
else
t:SetPoint("TOP", rect, "TOP", 0, -I.ranks[2])
t:SetPoint("BOTTOM", rect, "BOTTOM", 0, I.ranks[4] - I.ranks[3])
end
pieces[#pieces + 1] = t
end
end
rect.pieces = pieces
return rect
end
-- ------------------------------------------------------------ Windows
-- f: client window; rightButtons: right to left, in client order; default: the Defaults
-- button; lists: its category lists; inner: camelot inner frame instead of an inset.
function R.Window(f, rightButtons, default, lists, inner)
local name = f:GetName()
f:SetBackdrop(nil)
_G[name .. "Header"]:SetAlpha(0)
local title = _G[name .. "HeaderText"]
title:SetAlpha(0)
-- camelot window at the client window's level: its child frames (lists, panels, buttons)
-- draw in front.
local win = CreateFrame("Frame", nil, f)
win:SetFrameLevel(f:GetFrameLevel())
win:SetAllPoints(f)
local skin = Tpl.Window(win, title:GetText())
skin.stripes:Hide()
f.foreverSkin = skin
-- The close button hides the window (HideUIPanel) instead of clicking Cancel: running the
-- client's cancel code (BlizzardOptionsPanel_Cancel) from an addon would taint the CVars and
-- UI variables it rewrites. An unconfirmed change is not applied; the client reads it again
-- on the next open.
local closeButton = CreateFrame("Button", name .. "ForeverUICloseButton", f)
closeButton:SetFrameLevel(f:GetFrameLevel() + 20)
Tpl.CloseButton(closeButton, f)
closeButton:SetScript("OnClick", function()
HideUIPanel(f)
end)
f.foreverCloseButton = closeButton
-- Panel frame: an inset, or the camelot inner frame.
local container = _G[name .. "PanelContainer"]
container:SetBackdrop(nil)
if inner then
f.foreverInner = R.Interior(win, f, lists[1])
else
Tpl.Inset(win, container)
end
for _, list in ipairs(lists) do
R.List(list)
end
-- OptionsFrame_OnShow redraws the list through categoryFrame:update(), a reference taken at
-- load time that a function hook does not see: repaint after the window opens.
f:HookScript("OnShow", function()
for _, list in ipairs(lists) do
R.PaintList(list)
end
end)
-- Buttons: UIPanelButtonTemplate 96 x 22.
local previous
for _, b in ipairs(rightButtons) do
b:SetWidth(N.button[1])
b:SetHeight(N.button[2])
Tpl.PanelButton(b)
b:ClearAllPoints()
if previous then
b:SetPoint("RIGHT", previous, "LEFT", -N.buttonGap, 0)
else
b:SetPoint("BOTTOMRIGHT", f, "BOTTOMRIGHT", -N.buttonEdge, N.buttonEdge)
end
previous = b
end
default:SetWidth(N.button[1])
default:SetHeight(N.button[2])
Tpl.PanelButton(default)
default:ClearAllPoints()
default:SetPoint("BOTTOMLEFT", f, "BOTTOMLEFT", N.buttonEdge, N.buttonEdge)
return skin
end
-- ------------------------------------------------------------ Setup
local PANELS = {
"VideoOptionsResolutionPanel", "VideoOptionsEffectsPanel", "VideoOptionsStereoPanel",
"AudioOptionsSoundPanel", "AudioOptionsVoicePanel",
"InterfaceOptionsControlsPanel", "InterfaceOptionsCombatPanel", "InterfaceOptionsDisplayPanel",
"InterfaceOptionsObjectivesPanel", "InterfaceOptionsSocialPanel", "InterfaceOptionsActionBarsPanel",
"InterfaceOptionsNamesPanel", "InterfaceOptionsCombatTextPanel", "InterfaceOptionsStatusTextPanel",
"InterfaceOptionsUnitFramePanel", "InterfaceOptionsBuffsPanel", "InterfaceOptionsBattlenetPanel",
"InterfaceOptionsCameraPanel", "InterfaceOptionsMousePanel", "InterfaceOptionsFeaturesPanel",
"InterfaceOptionsHelpPanel", "InterfaceOptionsLanguagesPanel",
}
if VideoOptionsFrame then
R.Window(VideoOptionsFrame, { VideoOptionsFrameApply, VideoOptionsFrameCancel, VideoOptionsFrameOkay },
VideoOptionsFrameDefaults, { VideoOptionsFrameCategoryFrame }, true)
end
if AudioOptionsFrame then
R.Window(AudioOptionsFrame, { AudioOptionsFrameCancel, AudioOptionsFrameOkay },
AudioOptionsFrameDefaults, { AudioOptionsFrameCategoryFrame }, true)
end
-- Interface window in the camelot layout. In 3.3.5 the lists start at -40, leaving 19 px under
-- the title bar for 26 px tabs. As in camelot: lists at -76, tabs (37 high) on top of the
-- inner frame (-64), 15 from its left edge; the window grows by 36 to keep the list height and
-- the gap to the bottom buttons.
local INTERFACE = { listsY = -76, extraHeight = 36, tabX = 15 }
if InterfaceOptionsFrame then
local f = InterfaceOptionsFrame
f:SetHeight(f:GetHeight() + INTERFACE.extraHeight)
for _, l in ipairs({ InterfaceOptionsFrameCategories, InterfaceOptionsFrameAddOns }) do
local _, _, _, x = l:GetPoint(1)
l:ClearAllPoints()
l:SetPoint("TOPLEFT", f, "TOPLEFT", x or 22, INTERFACE.listsY)
end
R.Window(f, { InterfaceOptionsFrameCancel, InterfaceOptionsFrameOkay },
InterfaceOptionsFrameDefaults, { InterfaceOptionsFrameCategories, InterfaceOptionsFrameAddOns }, true)
for _, s in ipairs({ "Tab1TabSpacer", "Tab2TabSpacer1", "Tab2TabSpacer2" }) do
local t = _G["InterfaceOptionsFrame" .. s]
if t then t:SetAlpha(0) end
end
R.Tab(InterfaceOptionsFrameTab1)
R.Tab(InterfaceOptionsFrameTab2)
InterfaceOptionsFrameTab1:ClearAllPoints()
InterfaceOptionsFrameTab1:SetPoint("BOTTOMLEFT", f.foreverInner, "TOPLEFT", INTERFACE.tabX, 0)
InterfaceOptionsFrameTab2:ClearAllPoints()
InterfaceOptionsFrameTab2:SetPoint("TOPLEFT", InterfaceOptionsFrameTab1, "TOPRIGHT", N.tab.gap, 0)
end
for _, name in ipairs(PANELS) do
local p = _G[name]
if p then
R.Panel(p)
end
end