local ns = oUF local oUF = ns.oUF local Private = oUF.Private local argcheck = Private.argcheck local print = Private.print local error = Private.error local gsub, lower, match, split, upper = string.gsub, string.lower, string.match, string.split, string.upper local tinsert, tremove = table.insert, table.remove local styles, style = {} local callback, units, objects, headers = {}, {}, {}, {} local elements = {} local activeElements = {} -- updating of "invalid" units. local function enableTargetUpdate(object) object.onUpdateFrequency = object.onUpdateFrequency or .5 object.__eventless = true local total = 0 object:SetScript('OnUpdate', function() if(not this.unit) then return elseif(total > this.onUpdateFrequency) then this:UpdateAllElements('OnUpdate') total = 0 end total = total + arg1 end) end Private.enableTargetUpdate = enableTargetUpdate local frame_metatable = { __index = CreateFrame('Button') } Private.frame_metatable = frame_metatable for k, v in next, { 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, --[[ frame:EnableElement(name, unit) Used to activate an element for the given unit frame. * self - unit frame for which the element should be enabled * name - name of the element to be enabled (string) * unit - unit to be passed to the element's Enable function. Defaults to the frame's unit (string?) --]] EnableElement = function(self, name, unit) local unit = unit or self.unit if not unit then return end argcheck(name, 2, 'string') argcheck(unit or self.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 tinsert(self.__elements, element.update) end end end, --[[ frame:DisableElement(name) Used to deactivate an element for the given unit frame. * self - unit frame for which the element should be disabled * name - name of the element to be disabled (string) --]] 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 tremove(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') return elements[name].disable(self) end, --[[ frame:IsElementEnabled(name) Used to check if an element is enabled on the given frame. * self - unit frame * name - name of the element (string) --]] 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, --[[ frame:Enable(asState) Used to toggle the visibility of a unit frame based on the existence of its unit. This is a reference to `RegisterUnitWatch`. * self - unit frame * asState - if true, the frame's "state-unitexists" attribute will be set to a boolean value denoting whether the unit exists; if false, the frame will be shown if its unit exists, and hidden if it does not (boolean) --]] Enable = RegisterUnitWatch, --[[ frame:Disable() Used to UnregisterUnitWatch for the given frame and hide it. * self - unit frame --]] Disable = function(self) UnregisterUnitWatch(self) self:Hide() end, --[[ frame:UpdateAllElements(event) Used to update all enabled elements on the given frame. * self - unit frame * event - event name to pass to the elements' update functions (string) --]] 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, SetUnit = function(self, unit) if unit ~= unit then self.unit = unit end end, } do frame_metatable.__index[k] = v end local secureDropdown local function InitializeSecureMenu() local unit = SecureTemplatesDropdown.unit if(not unit) then return end local unitType = match(unit, '^([a-z]+)[0-9]+$') or unit local menu if(unitType == 'party') then menu = 'PARTY' elseif(UnitIsUnit(unit, 'player')) then menu = 'SELF' elseif(UnitIsUnit(unit, 'pet')) then menu = 'PET' elseif(UnitIsPlayer(unit)) then if(UnitInRaid(unit) or UnitInParty(unit)) then menu = 'PARTY' else menu = 'PLAYER' end elseif(UnitIsUnit(unit, 'target')) then menu = 'RAID_TARGET_ICON' end if(menu) then UnitPopup_ShowMenu(SecureTemplatesDropdown, menu, unit) end end local function togglemenu(self, unit) if(not secureDropdown) then secureDropdown = CreateFrame('Frame', 'SecureTemplatesDropdown', nil, 'UIDropDownMenuTemplate') secureDropdown:SetID(1) tinsert(UnitPopupFrames, secureDropdown:GetName()) UIDropDownMenu_Initialize(secureDropdown, InitializeSecureMenu, 'MENU') end if(secureDropdown.openedFor and secureDropdown.openedFor ~= self) then CloseDropDownMenus() end secureDropdown.unit = lower(unit) secureDropdown.openedFor = self ToggleDropDownMenu(1, nil, secureDropdown, 'cursor') end local function onShow(self) return self:UpdateAllElements('OnShow') end local function initObject(unit, style, styleFunc, header, ...) local num = arg.n for i = 1, num do local object = arg[i] local objectUnit = object.guessUnit or unit local suffix = objectUnit and match(objectUnit or unit, '%w+target') object.__elements = {} object.__registeredEvents = {} object.style = style object = setmetatable(object, frame_metatable) -- Expose the frame through oUF.objects. tinsert(objects, object) -- We have to force update the frames when PEW fires. object:RegisterEvent('PLAYER_ENTERING_WORLD', object.UpdateAllElements) object:SetScript('OnClick', function() if arg1 == 'RightButton' then togglemenu(this, object.unit) else TargetUnit(this.unit) end end) if(not header) then -- Other target units are handled by :HandleUnit(). if(suffix == 'target') then enableTargetUpdate(object) else oUF:HandleUnit(object, unit) end else -- Used to update frames when they change position in a group. object:RegisterEvent('RAID_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('OnShow', function() onShow(this) end) activeElements[object] = {} for element in next, elements do object:EnableElement(element, objectUnit) end for _, func in next, callback do func(object) end -- Make Clique kinda happy _G.ClickCastFrames = ClickCastFrames or {} ClickCastFrames[object] = true end end local function walkObject(object, unit) local parent = object:GetParent() local style = parent.style or style local styleFunc = styles[style] local header = parent.headerType and parent -- Check if we should leave the main frame blank. if(object.onlyProcessChildren) then object.hasChildren = true return initObject(unit, style, styleFunc, header, object:GetChildren()) end return initObject(unit, style, styleFunc, header, object, object:GetChildren()) end --[[ oUF:RegisterInitCallback(func) Used to add a function to a table to be executed upon unit frame/header initialization. * self - the global oUF object * func - function to be added --]] function oUF:RegisterInitCallback(func) tinsert(callback, func) end --[[ oUF:RegisterMetaFunction(name, func) Used to make a (table of) function(s) available to all unit frames. * self - the global oUF object * name - unique name of the function (string) * func - function or a table of functions (function or table) --]] 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 --[[ oUF:RegisterStyle(name, func) Used to register a style with oUF. This will also set the active style if it hasn't been set yet. * self - the global oUF object * name - name of the style * func - function(s) defining the style (function or table) --]] 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 --[[ oUF:SetActiveStyle(name) Used to set the active style. * self - the global oUF object * name - name of the style (string) --]] 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 --[[ oUF:IterateStyles() Returns an iterator over all registered styles. * self - the global oUF object --]] function oUF.IterateStyles() return iter, nil, nil end end local getCondition do local conditions = { raid40 = '[target=raid26,exists] show;', raid25 = '[target=raid11,exists] show;', raid10 = '[target=raid6,exists] show;', raid = '[group:raid] show;', party = '[group:party,nogroup:raid] show;', solo = '[target=player,exists,nogroup:party] show;', } function getCondition(...) local cond = '' for i = 1, arg.n do local short = arg[i] local condition = conditions[short] if(condition) then cond = cond .. condition end end return cond .. 'hide' end end local function generateName(unit, ...) local name = 'oUF_' .. gsub(style, gsub('^oUF_?', ""), gsub('[^%a%d_]+', '')) local raid, party, groupFilter for i = 1, arg.n, 2 do local att, val = arg[i], arg[i+1] 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(match(groupFilter, 'TANK')) then append = 'MainTank' elseif(match(groupFilter, 'ASSIST')) then append = 'MainAssist' else local _, count = gsub(groupFilter, ',', '') 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 = gsub(unit, '^%l', 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 do local function styleProxy(self, frame, ...) return walkObject(_G[frame]) end -- There has to be an easier way to do this. local initialConfigFunction = function(self) local header = self:GetParent() local unit if(not self.onlyProcessChildren) then local groupFilter = header:GetAttribute('groupFilter') if(header:GetAttribute('showRaid')) then unit = 'raid' elseif(header:GetAttribute('showParty')) then unit = 'party' end self.menu = togglemenu -- self:SetAttribute('type1', 'target') -- self:SetAttribute('type2', 'menu') self.guessUnit = unit self.unit = 'player' -- self.onlyProcessChildren = true end styleProxy(nil, self:GetName()) end local setAttribute = function(self, name, value) if self.attributes[name] ~= value then self.attributes[name] = value if self:IsVisible() then SecureGroupHeader_Update(self) end end end local getAttribute = function(self, name) return self.attributes[name] end --[[ oUF:SpawnHeader(overrideName, template, visibility, ...) Used to create a group header and apply the currently active style to it. * self - the global oUF object * overrideName - unique global name to be used for the header. Defaults to an auto-generated name based on the name of the active style and other arguments passed to `:SpawnHeader` (string?) * template - name of a template to be used for creating the header. Defaults to `'oUF_GroupHeaderTemplate'` (string?) * visibility - macro conditional(s) which define when to display the header (string). * ... - further argument pairs. Consult [Group Headers](http://wowprogramming.com/docs/secure_template/Group_Headers) for possible values. In addition to the standard group headers, oUF implements some of its own attributes. These can be supplied by the layout, but are optional. * oUF-initialConfigFunction - can contain code that will be securely run at the end of the initial secure configuration (string?) * oUF-onlyProcessChildren - can be used to force headers to only process children (boolean?) --]] 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 'oUF_GroupHeaderTemplate') local isPetHeader = match(template, 'PetHeader') local name = overrideName or generateName(nil, unpack(arg)) local header = CreateFrame('Frame', name, UIParent, template) header:Hide() header.attributes = {} header.SetAttribute = setAttribute header.GetAttribute = getAttribute -- header:SetAttribute('template', 'SecureUnitButtonTemplate') for i = 1, arg.n, 2 do local att, val = arg[i], arg[i+1] if(not att) then break end header:SetAttribute(att, val) end header.style = style header.styleFunction = styleProxy header.visibility = visibility -- Expose the header through oUF.headers. tinsert(headers, header) header.initialConfigFunction = initialConfigFunction header.headerType = isPetHeader and 'pet' or 'group' if(header:GetAttribute('showParty')) then self:DisableBlizzard('party') end if(visibility) then local type, list = split(' ', visibility, 2) if(list and type == 'custom') then RegisterStateDriver(header, 'visibility', list) header.visibility = list else local condition = getCondition(split(',', visibility)) RegisterStateDriver(header, 'visibility', condition) header.visibility = condition end end return header end end --[[ oUF:Spawn(unit, overrideName) Used to create a single unit frame and apply the currently active style to it. * self - the global oUF object * unit - the frame's unit (string) * overrideName - unique global name to use for the unit frame. Defaults to an auto-generated name based on the unit (string?) --]] 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 = lower(unit) local name = overrideName or generateName(unit) local object = CreateFrame('Button', name, UIParent) object:Hide() Private.UpdateUnits(object, unit) self:DisableBlizzard(unit) units[unit] = object walkObject(object, unit) object.unit = unit return object end --[[ oUF:AddElement(name, update, enable, disable) Used to register an element with oUF. * self - the global oUF object * name - unique name of the element (string) * update - used to update the element (function?) * enable - used to enable the element for a given unit frame and unit (function?) * disable - used to disable the element for a given unit frame (function?) --]] 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.units Array containing all unit frames with existing units created by oUF:Spawn. --]] oUF.units = units --[[ oUF.objects Array containing all unit frames created by `oUF:Spawn`. --]] oUF.objects = objects --[[ oUF.headers Array containing all group headers created by `oUF:SpawnHeader`. --]] oUF.headers = headers