a8541fa1ee
ChallengesFrame is a HIGH-strata window parented straight to UIParent and mouse-enabled itself, not one of the three that hang off the Collections container -- so it gets its own module and its own enable switch rather than joining the collections group, and it deliberately skips ApplyWindowScale and CollectionTabs: the first scales Collections, which would resize the other three windows and not this one, and the second skins the tab row those three share. The panel is built as a backdrop instead of through SetTemplate. A template's backdrop is drawn as regions of the frame and can only match its rect, and the title sits above that rect -- the native art carries a banner up there -- so a panel sized to the frame left the title floating over the open world. A backdrop has points of its own, so its top edge is pushed up over the title, measured live rather than hardcoded, with the hit rect and the close button following it. The nine-slice is stripped rather than run through HideArt for a related reason: the title is one of its regions, and Hide() would take it down with the border. The tab row is the first stock Blizzard flavour the plugin skins, so the shared layer grows to cover it. TAB_TEXTURES picks up the Middle pieces those name their body after, and the OnUpdate art re-check watches the Disabled set as well -- that pair is how a stock tab draws its selected state, so watching only the inactive half missed art returning on whichever tab was open. Native panel tabs also interlock, each anchored 16px back into the one before it so the ornate end caps overlap, which reads as one merged bar once the art is gone; the overlap is absorbed by narrowing each tab rather than by spacing the row out, which would have grown it 80px past a frame with 16px of slack. The row is pulled up flush against the panel it belongs to, since the native tops that spanned that gap went with the art. Inside the Trials tab: the search box, the filter dropdown and the scroll bar. The dropdown is the same widget as the vanity and wardrobe ones and takes the existing handler unchanged. The search box needs its border cleared by file, not stripped -- an EditBox draws its caret and selection highlight as plain textures of its own, and a blind strip leaves a box you can type in with no cursor in it. The scroll bar needed a handler of its own. HandleScrollBar reaches for the thumb through GetThumbTexture and then calls SetTexture on it, but these bars are hand-built: the thumb is a Button with art of its own, and the arrows are named off the scroll frame rather than off the bar, so it finds neither. Both pieces are also arted lazily -- at skin time their textures read back nil, which is why clearing them, noop'ing their SetTexture and alpha'ing them all failed in turn -- so the art is suppressed from OnUpdate, the same answer the tab art needed. Colours follow ElvUI's own proportional scroll bars, and the track is left empty rather than given a backdrop.
549 lines
16 KiB
Lua
549 lines
16 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,
|
|
},
|
|
-- Deliberately not inside the collections group: that switch covers
|
|
-- the three windows that share the Collections container and its tab
|
|
-- row, and this one is a separate window on UIParent with a tab row
|
|
-- of its own.
|
|
challenges = true,
|
|
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"),
|
|
},
|
|
},
|
|
},
|
|
},
|
|
challenges = {
|
|
order = 4,
|
|
type = "group",
|
|
name = "Challenges",
|
|
args = {
|
|
header = {
|
|
order = 1,
|
|
type = "header",
|
|
name = "Challenges",
|
|
},
|
|
enable = {
|
|
order = 2,
|
|
type = "toggle",
|
|
name = "Enable",
|
|
desc = "Skin the Challenges window and its tabs. Requires a UI reload.",
|
|
get = function() return CoA.db.profile.skins.challenges end,
|
|
set = function(_, value)
|
|
CoA.db.profile.skins.challenges = value
|
|
E:StaticPopup_Show("CONFIG_RL")
|
|
end,
|
|
},
|
|
},
|
|
},
|
|
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
|
|
|
|
-- Its own switch rather than a place in the group above: the Challenges
|
|
-- window isn't reachable from the talent frame's tabs, so turning it off
|
|
-- doesn't leave a half-skinned set behind.
|
|
if self.InitializeChallengesFrame and skins.challenges then
|
|
self:InitializeChallengesFrame()
|
|
end
|
|
|
|
if self.InitializeInterfaceOptions and skins.interfaceOptions then
|
|
self:InitializeInterfaceOptions()
|
|
end
|
|
end
|
|
|
|
local function InitializeCallback()
|
|
CoA:Initialize()
|
|
end
|
|
|
|
E:RegisterModule(CoA:GetName(), InitializeCallback)
|