1a62b4ed1f
ElvUI skins the stock options windows from four hardcoded name lists, so every widget the client added since upstream last synced stays native -- 53 of them here, 39 in four panels upstream never enumerated at all (Battlenet, RaidEffects, Stereo, Voice). Reported cases were the Auto Accept Trades checkbox and the Spell Queue Window slider. Sweep the three panel containers on show and skin by object type instead of carrying a parallel name list that would go stale the same way. Panels belonging to other addons are skipped by matching frames against INTERFACEOPTIONS_ADDONCATEGORIES, rebuilt on every sweep so a load-on-demand addon that registers late is still caught. Two guards worth naming. Sliders are gated on frame.template because HandleSliderFrame has no idempotence check of its own and HookScript stacks, so a second pass over one ElvUI already skinned would run its OnDisable/OnEnable handlers twice. Dropdowns are detected structurally, by a Middle texture plus a Button, because HandleDropDownBox uses that button unguarded and a false positive is a Lua error rather than a cosmetic miss. No Button branch: nothing needs one, and it would flatten other addons' panel tabs into ElvUI pill buttons.
514 lines
15 KiB
Lua
514 lines
15 KiB
Lua
local E, L, V, P, G = unpack(ElvUI)
|
|
local EP = E.Libs.EP
|
|
local ACH = LibStub("LibAceConfigHelper")
|
|
local AceDB = LibStub("AceDB-3.0")
|
|
local AceDBOptions = LibStub("AceDBOptions-3.0")
|
|
|
|
local AddOnName = ...
|
|
|
|
BINDING_HEADER_COA = "Conquest of Azeroth"
|
|
|
|
-- TODO: hook up server restart frame (RestartTimerFrame)
|
|
|
|
local CoA = E:NewModule("CoA", "AceEvent-3.0", "AceTimer-3.0")
|
|
E.CoA = CoA
|
|
|
|
local defaults = {
|
|
profile = {
|
|
skins = {
|
|
extraActionButton = true,
|
|
instanceSwap = true,
|
|
collections = {
|
|
enable = true,
|
|
-- Multipliers on each frame's own scale, so 1 is "as the server
|
|
-- built it" rather than a fixed size.
|
|
advancementScale = 1,
|
|
vanityScale = 1,
|
|
wardrobeScale = 1,
|
|
},
|
|
interfaceOptions = true,
|
|
},
|
|
extraActionButtonSize = 52,
|
|
instanceButtonFont = "PT Sans Narrow",
|
|
instanceButtonFontSize = 12,
|
|
instanceButtonFontOutline = "OUTLINE",
|
|
dispelHighlightOnlyMine = false,
|
|
dispelHighlightDetectTalents = true,
|
|
hideResourceSegmentBar = false,
|
|
hideResourceOrb = false,
|
|
hideResourceBar = false,
|
|
hideMultiCastActionBar = false,
|
|
},
|
|
}
|
|
|
|
function CoA:RefreshConfig()
|
|
if self.UpdateExtraActionButtonSize then self:UpdateExtraActionButtonSize() end
|
|
if self.UpdateInstanceButtonFont then self:UpdateInstanceButtonFont() end
|
|
if self.UpdateDispelHighlight then self:UpdateDispelHighlight() end
|
|
if self.UpdateClassResourceVisibility then self:UpdateClassResourceVisibility() end
|
|
if self.UpdateFrameScales then self:UpdateFrameScales() end
|
|
end
|
|
|
|
CoA:RegisterEvent("ADDON_LOADED", function(_, addon)
|
|
if addon ~= AddOnName then return end
|
|
CoA:UnregisterEvent("ADDON_LOADED")
|
|
|
|
CoA.db = AceDB:New("ElvUI_CoADB", defaults)
|
|
|
|
-- One-time migration: earlier versions pinned every character to a single
|
|
-- shared "Default" profile. Move each character to its own name-based
|
|
-- profile (copying over the settings it already had) the first time it logs in.
|
|
if not CoA.db.char.migratedSharedProfile then
|
|
local charProfile = CoA.db.keys.char
|
|
|
|
if CoA.db:GetCurrentProfile() == "Default" and charProfile ~= "Default" then
|
|
CoA.db:SetProfile(charProfile)
|
|
CoA.db:CopyProfile("Default", true)
|
|
end
|
|
|
|
CoA.db.char.migratedSharedProfile = true
|
|
end
|
|
|
|
CoA.db.RegisterCallback(CoA, "OnProfileChanged", "RefreshConfig")
|
|
CoA.db.RegisterCallback(CoA, "OnProfileCopied", "RefreshConfig")
|
|
CoA.db.RegisterCallback(CoA, "OnProfileReset", "RefreshConfig")
|
|
end)
|
|
|
|
-- One slider per window rather than a single shared one: the three don't ship
|
|
-- at the same scale, so a shared value would only line them up by flattening
|
|
-- the difference the server built in.
|
|
local function scaleOption(order, name, key)
|
|
return {
|
|
order = order,
|
|
type = "range",
|
|
name = name,
|
|
min = 0.5,
|
|
max = 1.5,
|
|
step = 0.01,
|
|
get = function() return CoA.db.profile.skins.collections[key] end,
|
|
set = function(_, value)
|
|
CoA.db.profile.skins.collections[key] = value
|
|
|
|
if CoA.UpdateFrameScales then
|
|
CoA:UpdateFrameScales()
|
|
end
|
|
end,
|
|
}
|
|
end
|
|
|
|
local function getOptions()
|
|
local profiles = AceDBOptions:GetOptionsTable(CoA.db)
|
|
profiles.order = 5
|
|
|
|
local options = {
|
|
order = 55,
|
|
type = "group",
|
|
childGroups = "tab",
|
|
name = string.format("|cff1784d1%s|r", "Conquest of Azeroth"),
|
|
args = {
|
|
-- Frame skins live under their own tab, laid out as a vertical tree
|
|
-- rather than more horizontal tabs: one entry per skinned frame, and
|
|
-- there are a lot more of those coming.
|
|
skins = {
|
|
order = 1,
|
|
type = "group",
|
|
childGroups = "tree",
|
|
name = "Skins",
|
|
args = {
|
|
extraActionButton = {
|
|
order = 1,
|
|
type = "group",
|
|
name = "Extra Action Button",
|
|
args = {
|
|
header = {
|
|
order = 1,
|
|
type = "header",
|
|
name = "Extra Action Button",
|
|
},
|
|
-- Skinning is one-way: the native art is stripped and
|
|
-- replaced in place, so turning a skin off can only take
|
|
-- effect on the next load. Hence the reload prompt.
|
|
enable = {
|
|
order = 2,
|
|
type = "toggle",
|
|
name = "Enable",
|
|
desc = "Skin the Extra Action Button. Requires a UI reload.",
|
|
get = function() return CoA.db.profile.skins.extraActionButton end,
|
|
set = function(_, value)
|
|
CoA.db.profile.skins.extraActionButton = value
|
|
E:StaticPopup_Show("CONFIG_RL")
|
|
end,
|
|
},
|
|
desc = {
|
|
order = 3,
|
|
type = "description",
|
|
name = "You can move this element with Toggle Anchors.\n",
|
|
},
|
|
size = {
|
|
order = 4,
|
|
disabled = function() return not CoA.db.profile.skins.extraActionButton end,
|
|
type = "range",
|
|
name = "Size",
|
|
desc = "Adjust the width/height of the Extra Action Button, in pixels.",
|
|
min = 30,
|
|
max = 100,
|
|
step = 1,
|
|
get = function() return CoA.db.profile.extraActionButtonSize end,
|
|
set = function(_, value)
|
|
CoA.db.profile.extraActionButtonSize = value
|
|
|
|
if CoA.UpdateExtraActionButtonSize then
|
|
CoA:UpdateExtraActionButtonSize()
|
|
end
|
|
end,
|
|
},
|
|
},
|
|
},
|
|
instanceSwap = {
|
|
order = 2,
|
|
type = "group",
|
|
name = "Instance Swap",
|
|
args = {
|
|
header = {
|
|
order = 1,
|
|
type = "header",
|
|
name = "Instance Swap",
|
|
},
|
|
enable = {
|
|
order = 2,
|
|
type = "toggle",
|
|
name = "Enable",
|
|
desc = "Skin the Instance Swap button. Requires a UI reload.",
|
|
get = function() return CoA.db.profile.skins.instanceSwap end,
|
|
set = function(_, value)
|
|
CoA.db.profile.skins.instanceSwap = value
|
|
E:StaticPopup_Show("CONFIG_RL")
|
|
end,
|
|
},
|
|
desc = {
|
|
order = 3,
|
|
type = "description",
|
|
name = "You can move this element with Toggle Anchors.\n",
|
|
},
|
|
instanceFont = {
|
|
order = 4,
|
|
disabled = function() return not CoA.db.profile.skins.instanceSwap end,
|
|
type = "group",
|
|
inline = true,
|
|
name = "Instance Font",
|
|
args = {
|
|
font = ACH:SharedMediaFont("Font", nil, 1, nil,
|
|
function() return CoA.db.profile.instanceButtonFont end,
|
|
function(_, value)
|
|
CoA.db.profile.instanceButtonFont = value
|
|
|
|
if CoA.UpdateInstanceButtonFont then
|
|
CoA:UpdateInstanceButtonFont()
|
|
end
|
|
end),
|
|
fontSize = {
|
|
order = 2,
|
|
type = "range",
|
|
name = "Font Size",
|
|
min = 8,
|
|
max = 32,
|
|
step = 1,
|
|
get = function() return CoA.db.profile.instanceButtonFontSize end,
|
|
set = function(_, value)
|
|
CoA.db.profile.instanceButtonFontSize = value
|
|
|
|
if CoA.UpdateInstanceButtonFont then
|
|
CoA:UpdateInstanceButtonFont()
|
|
end
|
|
end,
|
|
},
|
|
fontOutline = ACH:FontFlags("Font Outline", nil, 3, nil,
|
|
function() return CoA.db.profile.instanceButtonFontOutline end,
|
|
function(_, value)
|
|
CoA.db.profile.instanceButtonFontOutline = value
|
|
|
|
if CoA.UpdateInstanceButtonFont then
|
|
CoA:UpdateInstanceButtonFont()
|
|
end
|
|
end),
|
|
},
|
|
},
|
|
},
|
|
},
|
|
-- One entry for the whole talent window: its own frame, the
|
|
-- tab row along its bottom, and the Vanity and Wardrobe
|
|
-- windows those tabs open. They're separate frames but one
|
|
-- feature to the player, and they're skinned as a set --
|
|
-- named for Collections, the container they all hang off.
|
|
collections = {
|
|
order = 3,
|
|
type = "group",
|
|
name = "Advancement/Vanity/Wardrobe",
|
|
args = {
|
|
header = {
|
|
order = 1,
|
|
type = "header",
|
|
name = "Advancement/Vanity/Wardrobe",
|
|
},
|
|
enable = {
|
|
order = 2,
|
|
type = "toggle",
|
|
name = "Enable",
|
|
desc = "Skin the talent window, its tabs, and the Vanity and Wardrobe windows. Requires a UI reload.",
|
|
get = function() return CoA.db.profile.skins.collections.enable end,
|
|
set = function(_, value)
|
|
CoA.db.profile.skins.collections.enable = value
|
|
E:StaticPopup_Show("CONFIG_RL")
|
|
end,
|
|
},
|
|
scale = {
|
|
order = 3,
|
|
type = "group",
|
|
inline = true,
|
|
name = "Scale",
|
|
disabled = function() return not CoA.db.profile.skins.collections.enable end,
|
|
args = {
|
|
advancementScale = scaleOption(1, "Advancement", "advancementScale"),
|
|
vanityScale = scaleOption(2, "Vanity", "vanityScale"),
|
|
wardrobeScale = scaleOption(3, "Wardrobe", "wardrobeScale"),
|
|
},
|
|
},
|
|
},
|
|
},
|
|
interfaceOptions = {
|
|
order = 5,
|
|
type = "group",
|
|
name = "Interface Options",
|
|
args = {
|
|
header = {
|
|
order = 1,
|
|
type = "header",
|
|
name = "Interface Options",
|
|
},
|
|
enable = {
|
|
order = 2,
|
|
type = "toggle",
|
|
name = "Enable",
|
|
desc = "Skin the options the CoA client adds to the stock Interface, Video and Audio windows, which ElvUI's own pass doesn't cover. Requires a UI reload.",
|
|
get = function() return CoA.db.profile.skins.interfaceOptions end,
|
|
set = function(_, value)
|
|
CoA.db.profile.skins.interfaceOptions = value
|
|
E:StaticPopup_Show("CONFIG_RL")
|
|
end,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
classResources = {
|
|
order = 2,
|
|
type = "group",
|
|
name = "Class Resources",
|
|
args = {
|
|
header = {
|
|
order = 1,
|
|
type = "header",
|
|
name = "Class Resources",
|
|
},
|
|
desc = {
|
|
order = 2,
|
|
type = "description",
|
|
name = "You can move these elements with Toggle Anchors.\n",
|
|
},
|
|
hideResourceSegmentBar = {
|
|
order = 3,
|
|
type = "toggle",
|
|
name = "Hide CoAResourceSegmentBar",
|
|
width = "full",
|
|
get = function() return CoA.db.profile.hideResourceSegmentBar end,
|
|
set = function(_, value)
|
|
CoA.db.profile.hideResourceSegmentBar = value
|
|
|
|
if CoA.UpdateClassResourceVisibility then
|
|
CoA:UpdateClassResourceVisibility()
|
|
end
|
|
end,
|
|
},
|
|
hideResourceOrb = {
|
|
order = 4,
|
|
type = "toggle",
|
|
name = "Hide CoAResourceOrb",
|
|
width = "full",
|
|
get = function() return CoA.db.profile.hideResourceOrb end,
|
|
set = function(_, value)
|
|
CoA.db.profile.hideResourceOrb = value
|
|
|
|
if CoA.UpdateClassResourceVisibility then
|
|
CoA:UpdateClassResourceVisibility()
|
|
end
|
|
end,
|
|
},
|
|
hideResourceBar = {
|
|
order = 5,
|
|
type = "toggle",
|
|
name = "Hide CoAResourceBar",
|
|
width = "full",
|
|
get = function() return CoA.db.profile.hideResourceBar end,
|
|
set = function(_, value)
|
|
CoA.db.profile.hideResourceBar = value
|
|
|
|
if CoA.UpdateClassResourceVisibility then
|
|
CoA:UpdateClassResourceVisibility()
|
|
end
|
|
end,
|
|
},
|
|
hideMultiCastActionBar = {
|
|
order = 6,
|
|
type = "toggle",
|
|
name = "Hide CoAMultiCastActionBarFrame",
|
|
width = "full",
|
|
get = function() return CoA.db.profile.hideMultiCastActionBar end,
|
|
set = function(_, value)
|
|
CoA.db.profile.hideMultiCastActionBar = value
|
|
|
|
if CoA.UpdateClassResourceVisibility then
|
|
CoA:UpdateClassResourceVisibility()
|
|
end
|
|
end,
|
|
},
|
|
hideAll = {
|
|
order = 7,
|
|
type = "execute",
|
|
name = "Hide All",
|
|
func = function()
|
|
CoA.db.profile.hideResourceSegmentBar = true
|
|
CoA.db.profile.hideResourceOrb = true
|
|
CoA.db.profile.hideResourceBar = true
|
|
CoA.db.profile.hideMultiCastActionBar = true
|
|
|
|
if CoA.UpdateClassResourceVisibility then
|
|
CoA:UpdateClassResourceVisibility()
|
|
end
|
|
end,
|
|
},
|
|
showAll = {
|
|
order = 8,
|
|
type = "execute",
|
|
name = "Show All",
|
|
func = function()
|
|
CoA.db.profile.hideResourceSegmentBar = false
|
|
CoA.db.profile.hideResourceOrb = false
|
|
CoA.db.profile.hideResourceBar = false
|
|
CoA.db.profile.hideMultiCastActionBar = false
|
|
|
|
if CoA.UpdateClassResourceVisibility then
|
|
CoA:UpdateClassResourceVisibility()
|
|
end
|
|
end,
|
|
},
|
|
},
|
|
},
|
|
dispelHighlight = {
|
|
order = 3,
|
|
type = "group",
|
|
name = "Debuff Highlighting",
|
|
args = {
|
|
header = {
|
|
order = 1,
|
|
type = "header",
|
|
name = "Debuff Highlighting",
|
|
},
|
|
onlyMine = {
|
|
order = 2,
|
|
type = "toggle",
|
|
name = "Only Highlight If Dispellable By Me",
|
|
desc = "Suppress the debuff highlight on unitframes for debuff types your class cannot dispel.",
|
|
get = function() return CoA.db.profile.dispelHighlightOnlyMine end,
|
|
set = function(_, value)
|
|
CoA.db.profile.dispelHighlightOnlyMine = value
|
|
|
|
if CoA.UpdateDispelHighlight then
|
|
CoA:UpdateDispelHighlight()
|
|
end
|
|
end,
|
|
},
|
|
detectTalents = {
|
|
order = 3,
|
|
type = "toggle",
|
|
name = "Detect Talents",
|
|
desc = "Read the talents that grant extra dispel types from your current build. Turn this off to fall back to what your class can dispel without any talent.",
|
|
disabled = function() return not CoA.db.profile.dispelHighlightOnlyMine end,
|
|
get = function() return CoA.db.profile.dispelHighlightDetectTalents end,
|
|
set = function(_, value)
|
|
CoA.db.profile.dispelHighlightDetectTalents = value
|
|
|
|
if CoA.UpdateDispelHighlight then
|
|
CoA:UpdateDispelHighlight()
|
|
end
|
|
end,
|
|
},
|
|
},
|
|
},
|
|
profiles = profiles,
|
|
},
|
|
}
|
|
|
|
E.Options.args.CoA = options
|
|
end
|
|
|
|
local function RegisterMoverCategory()
|
|
for _, layout in ipairs(E.ConfigModeLayouts) do
|
|
if layout == "COA" then return end
|
|
end
|
|
|
|
tinsert(E.ConfigModeLayouts, "COA")
|
|
E.ConfigModeLocalizedStrings.COA = "CoA"
|
|
end
|
|
|
|
function CoA:Initialize()
|
|
RegisterMoverCategory()
|
|
|
|
EP:RegisterPlugin(AddOnName, getOptions)
|
|
|
|
-- ADDON_LOADED normally beats module init, but fall back to the defaults
|
|
-- rather than error out if a skin gets initialized before the DB exists.
|
|
local skins = self.db and self.db.profile.skins or defaults.profile.skins
|
|
|
|
if self.InitializeExtraActionBar and skins.extraActionButton then
|
|
self:InitializeExtraActionBar()
|
|
end
|
|
|
|
if self.InitializeLayerPicker and skins.instanceSwap then
|
|
self:InitializeLayerPicker()
|
|
end
|
|
|
|
if self.InitializeDispelHighlight then
|
|
self:InitializeDispelHighlight()
|
|
end
|
|
|
|
if self.InitializeClassResources then
|
|
self:InitializeClassResources()
|
|
end
|
|
|
|
-- One switch for all three: the vanity and wardrobe windows are the talent
|
|
-- frame's own tabs, so skinning one without the others reads as a bug.
|
|
if skins.collections.enable then
|
|
if self.InitializeTalentFrame then
|
|
self:InitializeTalentFrame()
|
|
end
|
|
|
|
if self.InitializeVanityFrame then
|
|
self:InitializeVanityFrame()
|
|
end
|
|
|
|
if self.InitializeWardrobeFrame then
|
|
self:InitializeWardrobeFrame()
|
|
end
|
|
end
|
|
|
|
if self.InitializeInterfaceOptions and skins.interfaceOptions then
|
|
self:InitializeInterfaceOptions()
|
|
end
|
|
end
|
|
|
|
local function InitializeCallback()
|
|
CoA:Initialize()
|
|
end
|
|
|
|
E:RegisterModule(CoA:GetName(), InitializeCallback)
|