MxW_Addon/Libraries/oUF/ouf.lua

600 lines
15 KiB
Lua

local parent, ns = ...
local global = GetAddOnMetadata(parent, 'X-oUF')
local _VERSION = GetAddOnMetadata(parent, 'version')
local oUF = ns.oUF
local Private = oUF.Private
local argcheck = Private.argcheck
local print = Private.print
local error = Private.error
local styles, style = {}
local callback, objects, headers = {}, {}, {}
local elements = {}
local activeElements = {}
-- updating of "invalid" units.
local enableTargetUpdate = function(object)
object.onUpdateFrequency = object.onUpdateFrequency or .5
object.__eventless = true
local total = 0
object:SetScript('OnUpdate', function(self, elapsed)
if(not self.unit) then
return
elseif(total > self.onUpdateFrequency) then
self:UpdateAllElements'OnUpdate'
total = 0
end
total = total + elapsed
end)
end
Private.enableTargetUpdate = enableTargetUpdate
local updateActiveUnit = function(self, event, unit)
-- Calculate units to work with
local realUnit, modUnit = SecureButton_GetUnit(self), SecureButton_GetModifiedUnit(self)
-- _GetUnit() doesn't rewrite playerpet -> pet like _GetModifiedUnit does.
if(realUnit == 'playerpet') then
realUnit = 'pet'
elseif(realUnit == 'playertarget') then
realUnit = 'target'
end
if(modUnit == 'pet' and realUnit ~= 'pet') then
modUnit = 'vehicle'
end
if(not UnitExists(modUnit)) then return end
-- Change the active unit and run a full update.
if Private.UpdateUnits(self, modUnit, realUnit) then
self:UpdateAllElements('RefreshUnit')
return true
end
end
local iterateChildren = function(...)
for l = 1, select("#", ...) do
local obj = select(l, ...)
if(type(obj) == 'table' and obj.isChild) then
updateActiveUnit(obj, "iterateChildren")
end
end
end
local OnAttributeChanged = function(self, name, value)
if(name == "unit" and value) then
if(self.hasChildren) then
iterateChildren(self:GetChildren())
end
if(not self:GetAttribute'oUF-onlyProcessChildren') then
updateActiveUnit(self, "OnAttributeChanged")
end
end
end
local frame_metatable = {
__index = CreateFrame"Button"
}
Private.frame_metatable = frame_metatable
for k, v in pairs{
UpdateElement = function(self, name)
local unit = self.unit
if(not unit or not UnitExists(unit)) then return end
local element = elements[name]
if(not element or not self:IsElementEnabled(name) or not activeElements[self]) then return end
if(element.update) then
element.update(self, 'OnShow', unit)
end
end,
EnableElement = function(self, name, unit)
argcheck(name, 2, 'string')
argcheck(unit, 3, 'string', 'nil')
local element = elements[name]
if(not element or self:IsElementEnabled(name) or not activeElements[self]) then return end
if(element.enable(self, unit or self.unit)) then
activeElements[self][name] = true
if(element.update) then
table.insert(self.__elements, element.update)
end
end
end,
DisableElement = function(self, name)
argcheck(name, 2, 'string')
local enabled = self:IsElementEnabled(name)
if(not enabled) then return end
local update = elements[name].update
for k, func in next, self.__elements do
if(func == update) then
table.remove(self.__elements, k)
break
end
end
activeElements[self][name] = nil
-- We need to run a new update cycle in-case we knocked ourself out of sync.
-- The main reason we do this is to make sure the full update is completed
-- if an element for some reason removes itself _during_ the update
-- progress.
self:UpdateAllElements('DisableElement', name)
return elements[name].disable(self)
end,
IsElementEnabled = function(self, name)
argcheck(name, 2, 'string')
local element = elements[name]
if(not element) then return end
local active = activeElements[self]
return active and active[name]
end,
Enable = RegisterUnitWatch,
Disable = function(self)
UnregisterUnitWatch(self)
self:Hide()
end,
UpdateAllElements = function(self, event)
local unit = self.unit
if(not UnitExists(unit)) then return end
assert(type(event) == 'string', 'Invalid argument "event" in UpdateAllElements.')
if(self.PreUpdate) then
self:PreUpdate(event)
end
for _, func in next, self.__elements do
func(self, event, unit)
end
if(self.PostUpdate) then
self:PostUpdate(event)
end
end,
} do
frame_metatable.__index[k] = v
end
local OnShow = function(self)
if(not updateActiveUnit(self, 'OnShow')) then
return self:UpdateAllElements'OnShow'
end
end
local UpdatePet = function(self, event, unit)
local petUnit
if(unit == 'target') then
return
elseif(unit == 'player') then
petUnit = 'pet'
else
-- Convert raid26 -> raidpet26
petUnit = unit:gsub('^(%a+)(%d+)', '%1pet%2')
end
if(self.unit ~= petUnit) then return end
if(not updateActiveUnit(self, event)) then
return self:UpdateAllElements(event)
end
end
local initObject = function(unit, style, styleFunc, header, ...)
local num = select('#', ...)
for i=1, num do
local object = select(i, ...)
local objectUnit = object:GetAttribute'oUF-guessUnit' or unit
local suffix = object:GetAttribute'unitsuffix'
object.__elements = {}
object.style = style
object = setmetatable(object, frame_metatable)
-- Expose the frame through oUF.objects.
table.insert(objects, object)
-- We have to force update the frames when PEW fires.
object:RegisterEvent("PLAYER_ENTERING_WORLD", object.UpdateAllElements)
-- Handle the case where someone has modified the unitsuffix attribute in
-- oUF-initialConfigFunction.
if(suffix and not objectUnit:match(suffix)) then
objectUnit = objectUnit .. suffix
end
if(not (suffix == 'target' or objectUnit and objectUnit:match'target')) then
object:RegisterEvent('UNIT_ENTERED_VEHICLE', updateActiveUnit)
object:RegisterEvent('UNIT_EXITED_VEHICLE', updateActiveUnit)
-- We don't need to register UNIT_PET for the player unit. We register it
-- mainly because UNIT_EXITED_VEHICLE and UNIT_ENTERED_VEHICLE doesn't always
-- have pet information when they fire for party and raid units.
if(objectUnit ~= 'player') then
object:RegisterEvent('UNIT_PET', UpdatePet, true)
end
end
if(not header) then
-- No header means it's a frame created through :Spawn().
object:SetAttribute("*type1", "target")
object:SetAttribute('*type2', 'togglemenu')
-- No need to enable this for *target frames.
if(not (unit:match'target' or suffix == 'target')) then
object:SetAttribute('toggleForVehicle', true)
end
-- Other boss and target units are handled by :HandleUnit().
if(suffix == 'target') then
enableTargetUpdate(object)
else
oUF:HandleUnit(object)
end
else
-- Used to update frames when they change position in a group.
object:RegisterEvent('GROUP_ROSTER_UPDATE', object.UpdateAllElements)
if(num > 1) then
if(object:GetParent() == header) then
object.hasChildren = true
else
object.isChild = true
end
end
if(suffix == 'target') then
enableTargetUpdate(object)
end
end
Private.UpdateUnits(object, objectUnit)
styleFunc(object, objectUnit, not header)
object:SetScript("OnAttributeChanged", OnAttributeChanged)
object:SetScript("OnShow", OnShow)
activeElements[object] = {}
for element in next, elements do
object:EnableElement(element, objectUnit)
end
for _, func in next, callback do
func(object)
end
-- Make Clique happy
_G.ClickCastFrames = ClickCastFrames or {}
ClickCastFrames[object] = true
end
end
local walkObject = function(object, unit)
local parent = object:GetParent()
local style = parent.style or style
local styleFunc = styles[style]
local header = parent:GetAttribute'oUF-headerType' and parent
-- Check if we should leave the main frame blank.
if(object:GetAttribute'oUF-onlyProcessChildren') then
object.hasChildren = true
object:SetScript('OnAttributeChanged', OnAttributeChanged)
return initObject(unit, style, styleFunc, header, object:GetChildren())
end
return initObject(unit, style, styleFunc, header, object, object:GetChildren())
end
function oUF:RegisterInitCallback(func)
table.insert(callback, func)
end
function oUF:RegisterMetaFunction(name, func)
argcheck(name, 2, 'string')
argcheck(func, 3, 'function', 'table')
if(frame_metatable.__index[name]) then
return
end
frame_metatable.__index[name] = func
end
function oUF:RegisterStyle(name, func)
argcheck(name, 2, 'string')
argcheck(func, 3, 'function', 'table')
if(styles[name]) then return error("Style [%s] already registered.", name) end
if(not style) then style = name end
styles[name] = func
end
function oUF:SetActiveStyle(name)
argcheck(name, 2, 'string')
if(not styles[name]) then return error("Style [%s] does not exist.", name) end
style = name
end
do
local function iter(_, n)
-- don't expose the style functions.
return (next(styles, n))
end
function oUF.IterateStyles()
return iter, nil, nil
end
end
local getCondition
do
local conditions = {
raid40 = '[@raid26,exists] show;',
raid25 = '[@raid11,exists] show;',
raid10 = '[@raid6,exists] show;',
raid = '[group:raid] show;',
party = '[group:party,nogroup:raid] show;',
solo = '[@player,exists,nogroup:party] show;',
}
function getCondition(...)
local cond = ''
for i=1, select('#', ...) do
local short = select(i, ...)
local condition = conditions[short]
if(condition) then
cond = cond .. condition
end
end
return cond .. 'hide'
end
end
local generateName = function(unit, ...)
local name = 'oUF_' .. style:gsub('[^%a%d_]+', '')
local raid, party, groupFilter
for i=1, select('#', ...), 2 do
local att, val = select(i, ...)
if(att == 'showRaid') then
raid = true
elseif(att == 'showParty') then
party = true
elseif(att == 'groupFilter') then
groupFilter = val
end
end
local append
if(raid) then
if(groupFilter) then
if(type(groupFilter) == 'number' and groupFilter > 0) then
append = groupFilter
elseif(groupFilter:match'TANK') then
append = 'MainTank'
elseif(groupFilter:match'ASSIST') then
append = 'MainAssist'
else
local _, count = groupFilter:gsub(',', '')
if(count == 0) then
append = 'Raid' .. groupFilter
else
append = 'Raid'
end
end
else
append = 'Raid'
end
elseif(party) then
append = 'Party'
elseif(unit) then
append = unit:gsub("^%l", string.upper)
end
if(append) then
name = name .. append
end
-- Change oUF_LilyRaidRaid into oUF_LilyRaid
name = name:gsub('(%u%l+)([%u%l]*)%1', '%1')
-- Change oUF_LilyTargettarget into oUF_LilyTargetTarget
name = name:gsub('t(arget)', 'T%1')
local base = name
local i = 2
while(_G[name]) do
name = base .. i
i = i + 1
end
return name
end
do
local styleProxy = function(self, frame, ...)
return walkObject(_G[frame])
end
-- There has to be an easier way to do this.
local initialConfigFunction = [[
local header = self:GetParent()
local frames = table.new()
table.insert(frames, self)
self:GetChildList(frames)
for i=1, #frames do
local frame = frames[i]
local unit
-- There's no need to do anything on frames with onlyProcessChildren
if(not frame:GetAttribute'oUF-onlyProcessChildren') then
RegisterUnitWatch(frame)
-- Attempt to guess what the header is set to spawn.
local groupFilter = header:GetAttribute'groupFilter'
if(type(groupFilter) == 'string' and groupFilter:match('MAIN[AT]')) then
local role = groupFilter:match('MAIN([AT])')
if(role == 'T') then
unit = 'maintank'
else
unit = 'mainassist'
end
elseif(header:GetAttribute'showRaid') then
unit = 'raid'
elseif(header:GetAttribute'showParty') then
unit = 'party'
end
local headerType = header:GetAttribute'oUF-headerType'
local suffix = frame:GetAttribute'unitsuffix'
if(unit and suffix) then
if(headerType == 'pet' and suffix == 'target') then
unit = unit .. headerType .. suffix
else
unit = unit .. suffix
end
elseif(unit and headerType == 'pet') then
unit = unit .. headerType
end
frame:SetAttribute('*type1', 'target')
frame:SetAttribute('*type2', 'togglemenu')
frame:SetAttribute('toggleForVehicle', true)
frame:SetAttribute('oUF-guessUnit', unit)
end
local body = header:GetAttribute'oUF-initialConfigFunction'
if(body) then
frame:Run(body, unit)
end
end
header:CallMethod('styleFunction', self:GetName())
local clique = header:GetFrameRef("clickcast_header")
if(clique) then
clique:SetAttribute("clickcast_button", self)
clique:RunAttribute("clickcast_register")
end
]]
function oUF:SpawnHeader(overrideName, template, visibility, ...)
if(not style) then return error("Unable to create frame. No styles have been registered.") end
template = (template or 'SecureGroupHeaderTemplate')
local isPetHeader = template:match'PetHeader'
local name = overrideName or generateName(nil, ...)
local header = CreateFrame('Frame', name, oUF_PetBattleFrameHider, template)
header:SetAttribute("template", "oUF_ClickCastUnitTemplate")
for i=1, select("#", ...), 2 do
local att, val = select(i, ...)
if(not att) then break end
header:SetAttribute(att, val)
end
header.style = style
header.styleFunction = styleProxy
-- Expose the header through oUF.headers.
table.insert(headers, header)
-- We set it here so layouts can't directly override it.
header:SetAttribute('initialConfigFunction', initialConfigFunction)
header:SetAttribute('oUF-headerType', isPetHeader and 'pet' or 'group')
if(Clique) then
SecureHandlerSetFrameRef(header, 'clickcast_header', Clique.header)
end
if(header:GetAttribute'showParty') then
self:DisableBlizzard'party'
end
if(visibility) then
local type, list = string.split(' ', visibility, 2)
if(list and type == 'custom') then
RegisterAttributeDriver(header, 'state-visibility', list)
else
local condition = getCondition(string.split(',', visibility))
RegisterAttributeDriver(header, 'state-visibility', condition)
end
end
return header
end
end
function oUF:Spawn(unit, overrideName, overrideTemplate)
argcheck(unit, 2, 'string')
if(not style) then return error("Unable to create frame. No styles have been registered.") end
unit = unit:lower()
local name = overrideName or generateName(unit)
local object = CreateFrame("Button", name, oUF_PetBattleFrameHider, overrideTemplate or "SecureUnitButtonTemplate")
Private.UpdateUnits(object, unit)
self:DisableBlizzard(unit)
walkObject(object, unit)
object:SetAttribute("unit", unit)
RegisterUnitWatch(object)
return object
end
function oUF:AddElement(name, update, enable, disable)
argcheck(name, 2, 'string')
argcheck(update, 3, 'function', 'nil')
argcheck(enable, 4, 'function', 'nil')
argcheck(disable, 5, 'function', 'nil')
if(elements[name]) then return error('Element [%s] is already registered.', name) end
elements[name] = {
update = update;
enable = enable;
disable = disable;
}
end
oUF.version = _VERSION
oUF.objects = objects
oUF.headers = headers
if(global) then
if(parent ~= 'oUF' and global == 'oUF') then
error("%s is doing it wrong and setting its global to oUF.", parent)
else
_G[global] = oUF
end
end