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 insert, remove = 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 + GetTime() end) end Private.enableTargetUpdate = enableTargetUpdate local function updateActiveUnit(self, event, unit) return true end local function iterateChildren(...) for i = 1, getn(arg) do local obj = arg[i] if(type(obj) == "table" and obj.isChild) then updateActiveUnit(obj, "iterateChildren") end end end local function onAttributeChanged(self, name, value) if(name == "unit" and value) then if(self.hasChildren) then iterateChildren(self:GetChildren()) end if(not self.onlyProcessChildren) then updateActiveUnit(self, "OnAttributeChanged") end --[[ if(self.unit and self.unit == value) then return else if(self.hasChildren) then iterateChildren(self:GetChildren()) end end ]] end end 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, 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 insert(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 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") 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) insert(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 = getn(arg) for i = 1, num do local object = arg[i] local objectUnit = object.guessUnit or unit local suffix = match(objectUnit or unit, "%w+target") object.__elements = {} object.__registeredEvents = {} object.style = style object = setmetatable(object, frame_metatable) -- Expose the frame through oUF.objects. insert(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("OnAttributeChanged", onAttributeChanged) 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 --object:SetScript("OnAttributeChanged", onAttributeChanged) 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) insert(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, getn(arg) do local short = select(i, unpack(arg)) 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, getn(arg), 2 do local att, val = select(i, unpack(arg)) 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, getn(arg), 2 do local att, val = select(i, unpack(arg)) 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. insert(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