Compare with Previous | Blame | View Log
local parent, ns = ... local global = GetAddOnMetadata(parent, 'X-oUF') local _VERSION = GetAddOnMetadata(parent, 'version') local function argcheck(value, num, ...) assert(type(num) == 'number', "Bad argument #2 to 'argcheck' (number expected, got "..type(num)..")") for i=1,select("#", ...) do if type(value) == select(i, ...) then return end end local types = strjoin(", ", ...) local name = string.match(debugstack(2,2,0), ": in function [`<](.-)['>]") error(("Bad argument #%d to '%s' (%s expected, got %s"):format(num, name, types, type(value)), 3) end local print = function(...) print("|cff33ff99oUF:|r", ...) end local error = function(...) print("|cffff0000Error:|r "..string.format(...)) end local dummy = function() end -- Colors local colors = { happiness = { [1] = {1, 0, 0}, -- need.... | unhappy [2] = {1, 1, 0}, -- new..... | content [3] = {0, 1, 0}, -- colors.. | happy }, smooth = { 1, 0, 0, 1, 1, 0, 0, 1, 0 }, disconnected = {.6, .6, .6}, tapped = {.6,.6,.6}, class = {}, reaction = {}, } -- We do this because people edit the vars directly, and changing the default -- globals makes SPICE FLOW! if(IsAddOnLoaded'!ClassColors' and CUSTOM_CLASS_COLORS) then local updateColors = function() for eclass, color in next, CUSTOM_CLASS_COLORS do colors.class[eclass] = {color.r, color.g, color.b} end local oUF = ns.oUF or _G[parent] if(oUF) then for _, obj in next, oUF.objects do obj:UpdateAllElements("CUSTOM_CLASS_COLORS") end end end updateColors() CUSTOM_CLASS_COLORS:RegisterCallback(updateColors) else for eclass, color in next, RAID_CLASS_COLORS do colors.class[eclass] = {color.r, color.g, color.b} end end for eclass, color in next, FACTION_BAR_COLORS do colors.reaction[eclass] = {color.r, color.g, color.b} end -- add-on object local oUF = {} local event_metatable = { __call = function(funcs, self, ...) for _, func in next, funcs do func(self, ...) end end, } local styles, style = {} local callback, units, objects = {}, {}, {} local select = select local UnitExists = UnitExists local conv = { ['playerpet'] = 'pet', ['playertarget'] = 'target', } local elements = {} local enableTargetUpdate = function(object) -- updating of "invalid" units. local OnTargetUpdate do local timer = 0 OnTargetUpdate = function(self, elapsed) if(not self.unit) then return elseif(timer >= .5) then self:UpdateAllElements'OnTargetUpdate' timer = 0 end timer = timer + elapsed end end object:SetScript("OnUpdate", OnTargetUpdate) end -- Events local OnEvent = function(self, event, ...) if(not self:IsShown()) then return end return self[event](self, event, ...) end local iterateChildren = function(...) for l = 1, select("#", ...) do local obj = select(l, ...) if(type(obj) == 'table' and obj.isChild) then local unit = SecureButton_GetModifiedUnit(obj) local subUnit = conv[unit] or unit units[subUnit] = obj obj.unit = subUnit obj:UpdateAllElements"PLAYER_ENTERING_WORLD" end end end local OnAttributeChanged = function(self, name, value) if(name == "unit" and value) then units[value] = self if(self.unit and self.unit == value) then return else if(self.hasChildren) then iterateChildren(self:GetChildren()) end self.unit = SecureButton_GetModifiedUnit(self) self.id = value:match"^.-(%d+)" self:UpdateAllElements"PLAYER_ENTERING_WORLD" end end end do local HandleFrame = function(baseName) local frame if(type(baseName) == 'string') then frame = _G[baseName] else frame = baseName end if(frame) then frame:UnregisterAllEvents() frame.Show = dummy frame:Hide() local health = frame.healthbar if(health) then health:UnregisterAllEvents() end local power = frame.manabar if(power) then power:UnregisterAllEvents() end local spell = frame.spellbar if(spell) then spell:UnregisterAllEvents() end end end function oUF:DisableBlizzard(unit, object) if(not unit) then return end local baseName if(unit == 'player') then HandleFrame(PlayerFrame) -- For the damn vehicle support: PlayerFrame:RegisterEvent('UNIT_ENTERING_VEHICLE') PlayerFrame:RegisterEvent('UNIT_ENTERED_VEHICLE') PlayerFrame:RegisterEvent('UNIT_EXITING_VEHICLE') PlayerFrame:RegisterEvent('UNIT_EXITED_VEHICLE') elseif(unit == 'pet') then baseName = PetFrame elseif(unit == 'target') then if(object) then object:RegisterEvent('PLAYER_TARGET_CHANGED', object.UpdateAllElements) end HandleFrame(TargetFrame) return HandleFrame(ComboFrame) elseif(unit == 'mouseover') then if(object) then return object:RegisterEvent('UPDATE_MOUSEOVER_UNIT', object.UpdateAllElements) end elseif(unit == 'focus') then if(object) then object:RegisterEvent('PLAYER_FOCUS_CHANGED', object.UpdateAllElements) end baseName = FocusFrame elseif(unit:match'%w+target') then if(unit == 'targettarget') then baseName = TargetFrameToT end enableTargetUpdate(object) elseif(unit:match'(boss)%d?$' == 'boss') then enableTargetUpdate(object) local id = unit:match'boss(%d)' if(id) then baseName = 'Boss' .. id .. 'TargetFrame' else for i=1, 3 do HandleFrame(('Boss%dTargetFrame'):format(i)) end end elseif(unit:match'(party)%d?$' == 'party') then local id = unit:match'party(%d)' if(id) then baseName = 'PartyMemberFrame' .. id else for i=1, 4 do HandleFrame(('PartyMemberFrame%d'):format(i)) end end end if(baseName) then return HandleFrame(baseName) end end end local frame_metatable = { __index = CreateFrame"Button" } for k, v in pairs{ colors = colors; EnableElement = function(self, name, unit) argcheck(name, 2, 'string') argcheck(unit, 3, 'string', 'nil') local element = elements[name] if(not element) then return end local updateFunc = element.update local elementTable = self[name] if(type(elementTable) == 'table' and elementTable.Update) then updateFunc = elementTable.Update end if(element.enable(self, unit or self.unit)) then table.insert(self.__elements, updateFunc) end end, DisableElement = function(self, name) argcheck(name, 2, 'string') local element = elements[name] if(not element) then return end local updateFunc = element.update local elementTable = self[name] if(type(elementTable) == 'table' and elementTable.Update) then updateFunc = elementTable.Update end for k, update in next, self.__elements do if(update == updateFunc) then table.remove(self.__elements, k) -- We need to run a new update cycle incase 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) break end end return element.disable(self) end, UpdateElement = function(self, name) argcheck(name, 2, 'string') local element = elements[name] if(not element) then return end local updateFunc = element.update local elementTable = self[name] if(type(elementTable) == 'table' and elementTable.Update) then updateFunc = elementTable.Update end updateFunc(self, 'UpdateElement', self.unit) 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 for _, func in next, self.__elements do func(self, event, unit) end end, } do frame_metatable.__index[k] = v end do local RegisterEvent = frame_metatable.__index.RegisterEvent function frame_metatable.__index:RegisterEvent(event, func) argcheck(event, 2, 'string') if(type(func) == 'string' and type(self[func]) == 'function') then func = self[func] end local curev = self[event] if(curev and func) then if(type(curev) == 'function') then self[event] = setmetatable({curev, func}, event_metatable) else for _, infunc in next, curev do if(infunc == func) then return end end table.insert(curev, func) end elseif(self:IsEventRegistered(event)) then return else if(func) then self[event] = func elseif(not self[event]) then return error("Handler for event [%s] on unit [%s] does not exist.", event, self.unit or 'unknown') end RegisterEvent(self, event) end end end do local UnregisterEvent = frame_metatable.__index.UnregisterEvent function frame_metatable.__index:UnregisterEvent(event, func) argcheck(event, 2, 'string') local curev = self[event] if(type(curev) == 'table' and func) then for k, infunc in next, curev do if(infunc == func) then curev[k] = nil if(#curev == 0) then table.remove(curev, k) UnregisterEvent(self, event) end break end end else self[event] = nil UnregisterEvent(self, event) end end end local ColorGradient do local inf = math.huge -- http://www.wowwiki.com/ColorGradient function ColorGradient(perc, ...) -- Translate divison by zeros into 0, so we don't blow select. -- We check perc against itself because we rely on the fact that NaN can't equal NaN. if(perc ~= perc or perc == inf) then perc = 0 end if perc >= 1 then local r, g, b = select(select('#', ...) - 2, ...) return r, g, b elseif perc <= 0 then local r, g, b = ... return r, g, b end local num = select('#', ...) / 3 local segment, relperc = math.modf(perc*(num-1)) local r1, g1, b1, r2, g2, b2 = select((segment*3)+1, ...) return r1 + (r2-r1)*relperc, g1 + (g2-g1)*relperc, b1 + (b2-b1)*relperc end end frame_metatable.__index.ColorGradient = ColorGradient oUF.ColorGradient = ColorGradient local initObject = function(unit, style, styleFunc, ...) local num = select('#', ...) for i=1, num do local object = select(i, ...) object.__elements = {} object = setmetatable(object, frame_metatable) -- Attempt to guess what the header is set to spawn. local parent = object:GetParent() if(not unit) then if(parent:GetAttribute'showRaid') then unit = 'raid' elseif(parent:GetAttribute'showParty') then unit = 'party' end end -- Run it before the style function so they can override it. object:SetAttribute("*type1", "target") object.style = style if(num > 1) then if(i == 1) then object.hasChildren = true else object.isChild = true end end -- Register it early so it won't be executed after the layouts PEW, if they -- have one. object:RegisterEvent("PLAYER_ENTERING_WORLD", object.UpdateAllElements) styleFunc(object, unit) local height = object:GetAttribute'initial-height' local width = object:GetAttribute'initial-width' local scale = object:GetAttribute'initial-scale' local suffix = object:GetAttribute'unitsuffix' local combat = InCombatLockdown() if(height) then object:SetAttribute('initial-height', height) if(not combat) then object:SetHeight(height) end end if(width) then object:SetAttribute("initial-width", width) if(not combat) then object:SetWidth(width) end end if(scale) then object:SetAttribute("initial-scale", scale) if(not combat) then object:SetScale(scale) end end local showPlayer if(i == 1) then showPlayer = parent:GetAttribute'showPlayer' or parent:GetAttribute'showSolo' end if(suffix and suffix:match'target' and (i ~= 1 and not showPlayer)) then enableTargetUpdate(object) else object:SetScript("OnEvent", OnEvent) end object:SetScript("OnAttributeChanged", OnAttributeChanged) object:SetScript("OnShow", object.UpdateAllElements) for element in next, elements do object:EnableElement(element, unit) end for _, func in next, callback do func(object) end -- We could use ClickCastFrames only, but it will probably contain frames that -- we don't care about. table.insert(objects, object) _G.ClickCastFrames = ClickCastFrames or {} ClickCastFrames[object] = true end end local walkObject = function(object, unit) local style = object:GetParent().style or style local styleFunc = styles[style] return initObject(unit, style, styleFunc, 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(groupFilter:match'TANK') then append = 'MainTank' elseif(groupFilter:match'ASSIST') then append = 'MainAssist' else local _, count = groupFilter:gsub(',', '') if(count == 0) then append = 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 local base = name local i = 2 while(_G[name]) do name = base .. i i = i + 1 end return name end function oUF:SpawnHeader(overrideName, template, visibility, ...) if(not style) then return error("Unable to create frame. No styles have been registered.") end local name = overrideName or generateName(nil, ...) local header = CreateFrame('Frame', name, UIParent, template or 'SecureGroupHeaderTemplate') header.initialConfigFunction = walkObject header.style = style header:SetAttribute("template", "SecureUnitButtonTemplate") for i=1, select("#", ...), 2 do local att, val = select(i, ...) if(not att) then break end header:SetAttribute(att, val) 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 RegisterStateDriver(header, 'visibility', list) else local condition = getCondition(string.split(',', visibility)) RegisterStateDriver(header, 'visibility', condition) end end return header end function oUF:Spawn(unit, overrideName) 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, UIParent, "SecureUnitButtonTemplate") object.unit = unit object.id = unit:match"^.-(%d+)" units[unit] = object walkObject(object, unit) object:SetAttribute("unit", unit) RegisterUnitWatch(object) self:DisableBlizzard(unit, object) return object end do local _QUEUE = {} local _FACTORY = CreateFrame'Frame' _FACTORY:SetScript('OnEvent', OnEvent) _FACTORY:RegisterEvent'PLAYER_LOGIN' _FACTORY.active = true function _FACTORY:PLAYER_LOGIN() if(not self.active) then return end for _, func in next, _QUEUE do func(oUF) end end function oUF:Factory(func) argcheck(func, 2, 'function') table.insert(_QUEUE, func) end function oUF:EnableFactory() _FACTORY.active = true end function oUF:DisableFactory() _FACTORY.active = nil end 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.units = units oUF.objects = objects oUF.colors = colors oUF.error = error if(global) then if(parent ~= 'oUF' and global == 'oUF' and IsAddOnLoaded'oUF') then error("%s attempted to override oUF's default global with its internal oUF.", parent) else _G[global] = oUF end end ns.oUF = oUF