5842eb7a2b
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>
670 lines
25 KiB
Lua
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
|