big commit

This commit is contained in:
Bunny67
2017-12-16 20:18:25 +03:00
parent 7f59921969
commit 5585bc44b6
103 changed files with 21575 additions and 2175 deletions
@@ -35,6 +35,10 @@ local AceAddon, oldminor = LibStub:NewLibrary(MAJOR, MINOR)
if not AceAddon then return end -- No Upgrade needed.
local AceCore = LibStub("AceCore-3.0")
local new, del = AceCore.new, AceCore.del
local wipe, truncate = AceCore.wipe, AceCore.truncate
AceAddon.frame = AceAddon.frame or CreateFrame("Frame", "AceAddon30Frame") -- Our very own frame
AceAddon.addons = AceAddon.addons or {} -- addons in general
AceAddon.statuses = AceAddon.statuses or {} -- statuses of addon.
@@ -43,9 +47,9 @@ AceAddon.enablequeue = AceAddon.enablequeue or {} -- addons that are initialized
AceAddon.embeds = AceAddon.embeds or setmetatable({}, {__index = function(tbl, key) tbl[key] = {} return tbl[key] end }) -- contains a list of libraries embedded in an addon
-- Lua APIs
local getn, tinsert, tconcat, tremove = table.getn, table.insert, table.concat, table.remove
local fmt, tostring = string.format, tostring
local select, pairs, next, type, unpack = select, pairs, next, type, unpack
local tinsert, tconcat, tremove, tgetn = table.insert, table.concat, table.remove, table.getn
local strfmt, tostring = string.format, tostring
local pairs, next, type, unpack = pairs, next, type, unpack
local loadstring, assert, error = loadstring, assert, error
local setmetatable, getmetatable, rawset, rawget = setmetatable, getmetatable, rawset, rawget
@@ -56,54 +60,7 @@ local setmetatable, getmetatable, rawset, rawget = setmetatable, getmetatable, r
--[[
xpcall safecall implementation
]]
local xpcall = xpcall
local function errorhandler(err)
return geterrorhandler()(err)
end
local function CreateDispatcher(argCount)
local code = [[
local method, ARGS
local function call() return method(ARGS) end
local function dispatch(func, ...)
method = func
if not method then return end
ARGS = unpack(arg)
return xpcall(call, function(err) return geterrorhandler()(err) end)
end
return dispatch
]]
local ARGS = {}
for i = 1, argCount do
ARGS[i] = "arg"..i
end
code = string.gsub(code, "ARGS", tconcat(ARGS, ", "))
return assert(loadstring(code, "safecall Dispatcher["..argCount.."]"))(xpcall, errorhandler)
end
local Dispatchers = setmetatable({}, {__index=function(self, argCount)
local dispatcher = CreateDispatcher(argCount)
rawset(self, argCount, dispatcher)
return dispatcher
end})
Dispatchers[0] = function(func)
return xpcall(func, errorhandler)
end
local function safecall(func, ...)
-- we check to see if the func is passed is actually a function here and don't error when it isn't
-- this safecall is used for optional functions like OnInitialize OnEnable etc. When they are not
-- present execution should continue without hinderance
if type(func) == "function" then
--print(Dispatchers[getn(arg)](func, unpack(arg)))
return func(unpack(arg))
end
end
local safecall = AceCore.safecall
-- local functions that will be implemented further down
local Enable, Disable, EnableModule, DisableModule, Embed, NewModule, GetModule, GetName, SetDefaultModuleState, SetDefaultModuleLibraries, SetEnabledState, SetDefaultModulePrototype
@@ -113,7 +70,7 @@ local function addontostring( self ) return self.name end
-- Check if the addon is queued for initialization
local function queuedForInitialization(addon)
for i = 1, getn(AceAddon.initializequeue) do
for i = 1, tgetn(AceAddon.initializequeue) do
if AceAddon.initializequeue[i] == addon then
return true
end
@@ -136,24 +93,34 @@ end
-- -- Create a Addon object based on the table of a frame
-- local MyFrame = CreateFrame("Frame")
-- MyAddon = LibStub("AceAddon-3.0"):NewAddon(MyFrame, "MyAddon", "AceEvent-3.0")
function AceAddon:NewAddon(objectorname, ...)
function AceAddon:NewAddon(objectorname,a0,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10)
local object,name
local i=1
if type(objectorname)=="table" then
object=objectorname
name=unpack(arg)
i=2
name=a0
else
name=objectorname
end
if type(name)~="string" then
error(("Usage: NewAddon([object,] name, [lib, lib, lib, ...]): 'name' - string expected got '%s'."):format(type(name)), 2)
error(strfmt("Usage: NewAddon([object,] name, [lib, lib, lib, ...]): 'name' - string expected got '%s'.", type(name)), 2)
end
if self.addons[name] then
error(("Usage: NewAddon([object,] name, [lib, lib, lib, ...]): 'name' - Addon '%s' already exists."):format(name), 2)
error(strfmt("Usage: NewAddon([object,] name, [lib, lib, lib, ...]): 'name' - Addon '%s' already exists.", name), 2)
end
local args = new()
args[1] = a1
args[2] = a2
args[3] = a3
args[4] = a4
args[5] = a5
args[6] = a6
args[7] = a7
args[8] = a8
args[9] = a9
args[10] = a10
object = object or {}
object.name = name
@@ -170,14 +137,18 @@ function AceAddon:NewAddon(objectorname, ...)
object.orderedModules = {}
object.defaultModuleLibraries = {}
Embed( object ) -- embed NewModule, GetModule methods
self:EmbedLibraries(object, unpack(arg))
if type(objectorname)=="table" then
self:EmbedLibraries(object,nil,args)
elseif a0 then
self:EmbedLibraries(object,a0,args)
end
del(args)
-- add to queue of addons to be initialized upon ADDON_LOADED
tinsert(self.initializequeue, object)
return object
end
--- Get the addon object by its name from the internal AceAddon registry.
-- Throws an error if the addon object cannot be found (except if silent is set).
-- @param name unique name of the addon object
@@ -187,7 +158,7 @@ end
-- MyAddon = LibStub("AceAddon-3.0"):GetAddon("MyAddon")
function AceAddon:GetAddon(name, silent)
if not silent and not self.addons[name] then
error(("Usage: GetAddon(name): 'name' - Cannot find an AceAddon '%s'."):format(tostring(name)), 2)
error(strfmt("Usage: GetAddon(name): 'name' - Cannot find an AceAddon '%s'.", tostring(name)), 2)
end
return self.addons[name]
end
@@ -200,10 +171,12 @@ end
-- @paramsig addon, [lib, ...]
-- @param addon addon object to embed the libs in
-- @param lib List of libraries to embed into the addon
function AceAddon:EmbedLibraries(addon, ...)
for i=1, getn(arg) do
local libname = arg[i]
self:EmbedLibrary(addon, libname, false, 4)
function AceAddon:EmbedLibraries(addon,a1,arg)
if a1 then self:EmbedLibrary(addon, a1, false, 4) end
-- 10 is the max number of variable arguments in the function NewAddon and NewModule
for i=1,10 do
if not arg[i] then return end
self:EmbedLibrary(addon, arg[i], false, 4)
end
end
@@ -221,13 +194,13 @@ end
function AceAddon:EmbedLibrary(addon, libname, silent, offset)
local lib = LibStub:GetLibrary(libname, true)
if not lib and not silent then
error(("Usage: EmbedLibrary(addon, libname, silent, offset): 'libname' - Cannot find a library instance of %q."):format(tostring(libname)), offset or 2)
error(strfmt("Usage: EmbedLibrary(addon, libname, silent, offset): 'libname' - Cannot find a library instance of %q.", tostring(libname)), offset or 2)
elseif lib and type(lib.Embed) == "function" then
lib:Embed(addon)
tinsert(self.embeds[addon], libname)
return true
elseif lib then
error(("Usage: EmbedLibrary(addon, libname, silent, offset): 'libname' - Library '%s' is not Embed capable"):format(libname), offset or 2)
error(strfmt("Usage: EmbedLibrary(addon, libname, silent, offset): 'libname' - Library '%s' is not Embed capable", libname), offset or 2)
end
end
@@ -244,7 +217,7 @@ end
-- MyModule = MyAddon:GetModule("MyModule")
function GetModule(self, name, silent)
if not self.modules[name] and not silent then
error(("Usage: GetModule(name, silent): 'name' - Cannot find module '%s'."):format(tostring(name)), 2)
error(strfmt("Usage: GetModule(name, silent): 'name' - Cannot find module '%s'.", tostring(name)), 2)
end
return self.modules[name]
end
@@ -267,26 +240,39 @@ local function IsModuleTrue(self) return true end
-- -- Create a module with a prototype
-- local prototype = { OnEnable = function(self) print("OnEnable called!") end }
-- MyModule = MyAddon:NewModule("MyModule", prototype, "AceEvent-3.0", "AceHook-3.0")
function NewModule(self, name, prototype, ...)
if type(name) ~= "string" then error(("Usage: NewModule(name, [prototype, [lib, lib, lib, ...]): 'name' - string expected got '%s'."):format(type(name)), 2) end
if type(prototype) ~= "string" and type(prototype) ~= "table" and type(prototype) ~= "nil" then error(("Usage: NewModule(name, [prototype, [lib, lib, lib, ...]): 'prototype' - table (prototype), string (lib) or nil expected got '%s'."):format(type(prototype)), 2) end
function NewModule(self, name, prototype, a1,a2,a3,a4,a5,a6,a7,a8,a9,a10)
if type(name) ~= "string" then error(strfmt("Usage: NewModule(name, [prototype, [lib, lib, lib, ...]): 'name' - string expected got '%s'.", type(name)), 2) end
if type(prototype) ~= "string" and type(prototype) ~= "table" and type(prototype) ~= "nil" then error(strfmt("Usage: NewModule(name, [prototype, [lib, lib, lib, ...]): 'prototype' - table (prototype), string (lib) or nil expected got '%s'.", type(prototype)), 2) end
if self.modules[name] then error(("Usage: NewModule(name, [prototype, [lib, lib, lib, ...]): 'name' - Module '%s' already exists."):format(name), 2) end
if self.modules[name] then error(strfmt("Usage: NewModule(name, [prototype, [lib, lib, lib, ...]): 'name' - Module '%s' already exists.", name), 2) end
-- modules are basically addons. We treat them as such. They will be added to the initializequeue properly as well.
-- NewModule can only be called after the parent addon is present thus the modules will be initialized after their parent is.
local module = AceAddon:NewAddon(fmt("%s_%s", self.name or tostring(self), name))
local module = AceAddon:NewAddon(strfmt("%s_%s", self.name or tostring(self), name))
module.IsModule = IsModuleTrue
module:SetEnabledState(self.defaultModuleState)
module.moduleName = name
local args = new()
args[1] = a1
args[2] = a2
args[3] = a3
args[4] = a4
args[5] = a5
args[6] = a6
args[7] = a7
args[8] = a8
args[9] = a9
args[10] = a10
if type(prototype) == "string" then
AceAddon:EmbedLibraries(module, prototype, unpack(arg))
AceAddon:EmbedLibraries(module, prototype, args)
else
AceAddon:EmbedLibraries(module, unpack(arg))
AceAddon:EmbedLibraries(module, nil, args)
end
AceAddon:EmbedLibraries(module, unpack(self.defaultModuleLibraries))
del(args)
AceAddon:EmbedLibraries(module, nil, self.defaultModuleLibraries)
if not prototype or type(prototype) == "string" then
prototype = self.defaultModulePrototype or nil
@@ -298,7 +284,7 @@ function NewModule(self, name, prototype, ...)
setmetatable(module, mt) -- More of a Base class type feel.
end
safecall(self.OnModuleCreated, self, module) -- Was in Ace2 and I think it could be a cool thing to have handy.
safecall(self.OnModuleCreated, 2, self, module) -- Was in Ace2 and I think it could be a cool thing to have handy.
self.modules[name] = module
tinsert(self.orderedModules, module)
@@ -399,11 +385,25 @@ end
-- MyAddon:SetDefaultModuleLibraries("AceEvent-3.0")
-- -- Create a module
-- MyModule = MyAddon:NewModule("MyModule")
function SetDefaultModuleLibraries(self, ...)
function SetDefaultModuleLibraries(self,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10)
if next(self.modules) then
error("Usage: SetDefaultModuleLibraries(...): cannot change the module defaults after a module has been registered.", 2)
end
self.defaultModuleLibraries = {unpack(arg)}
local args = self.defaultModuleLibraries or {}
args[1] = a1
args[2] = a2
args[3] = a3
args[4] = a4
args[5] = a5
args[6] = a6
args[7] = a7
args[8] = a8
args[9] = a9
args[10] = a10
truncate(tmp,10)
self.defaultModuleLibraries = args
end
--- Set the default state in which new modules are being created.
@@ -446,7 +446,7 @@ function SetDefaultModulePrototype(self, prototype)
error("Usage: SetDefaultModulePrototype(prototype): cannot change the module defaults after a module has been registered.", 2)
end
if type(prototype) ~= "table" then
error(("Usage: SetDefaultModulePrototype(prototype): 'prototype' - table expected got '%s'."):format(type(prototype)), 2)
error(strfmt("Usage: SetDefaultModulePrototype(prototype): 'prototype' - table expected got '%s'.", type(prototype)), 2)
end
self.defaultModulePrototype = prototype
end
@@ -530,12 +530,12 @@ end
-- **Note:** Do not call this function manually, unless you're absolutely sure that you know what you are doing.
-- @param addon addon object to intialize
function AceAddon:InitializeAddon(addon)
safecall(addon.OnInitialize, addon)
safecall(addon.OnInitialize, 1, addon)
local embeds = self.embeds[addon]
for i = 1, getn(embeds) do
for i = 1, tgetn(embeds) do
local lib = LibStub:GetLibrary(embeds[i], true)
if lib then safecall(lib.OnEmbedInitialize, lib, addon) end
if lib then safecall(lib.OnEmbedInitialize, 2, lib, addon) end
end
-- we don't call InitializeAddon on modules specifically, this is handled
@@ -559,19 +559,19 @@ function AceAddon:EnableAddon(addon)
-- set the statuses first, before calling the OnEnable. this allows for Disabling of the addon in OnEnable.
self.statuses[addon.name] = true
safecall(addon.OnEnable, addon)
safecall(addon.OnEnable, 1, addon)
-- make sure we're still enabled before continueing
if self.statuses[addon.name] then
local embeds = self.embeds[addon]
for i = 1, getn(embeds) do
for i = 1, tgetn(embeds) do
local lib = LibStub:GetLibrary(embeds[i], true)
if lib then safecall(lib.OnEmbedEnable, lib, addon) end
if lib then safecall(lib.OnEmbedEnable, 2, lib, addon) end
end
-- enable possible modules.
local modules = addon.orderedModules
for i = 1, getn(modules) do
for i = 1, tgetn(modules) do
self:EnableAddon(modules[i])
end
end
@@ -594,18 +594,18 @@ function AceAddon:DisableAddon(addon)
-- set statuses first before calling OnDisable, this allows for aborting the disable in OnDisable.
self.statuses[addon.name] = false
safecall( addon.OnDisable, addon )
safecall(addon.OnDisable, 1, addon)
-- make sure we're still disabling...
if not self.statuses[addon.name] then
local embeds = self.embeds[addon]
for i = 1, getn(embeds) do
for i = 1, tgetn(embeds) do
local lib = LibStub:GetLibrary(embeds[i], true)
if lib then safecall(lib.OnEmbedDisable, lib, addon) end
if lib then safecall(lib.OnEmbedDisable, 2, lib, addon) end
end
-- disable possible modules.
local modules = addon.orderedModules
for i = 1, getn(modules) do
for i = 1, tgetn(modules) do
self:DisableAddon(modules[i])
end
end
@@ -636,13 +636,22 @@ function AceAddon:IterateAddonStatus() return pairs(self.statuses) end
function AceAddon:IterateEmbedsOnAddon(addon) return pairs(self.embeds[addon]) end
function AceAddon:IterateModulesOfAddon(addon) return pairs(addon.modules) end
local IsLoggedIn
local onEvent
do
local IsLoggedIn = false
-- Event Handling
local function onEvent()
function onEvent()
-- 2011-08-17 nevcairiel - ignore the load event of Blizzard_DebugTools, so a potential startup error isn't swallowed up
-- Ace3v: When the ADDON_LOADED event is triggerd, global arg1 is the loaded addon name
-- so onEvent(event, arg1) won't work because it will cover the global variables
if (event == "ADDON_LOADED" and arg1 ~= "Blizzard_DebugTools") or event == "PLAYER_LOGIN" then
-- if a addon loads another addon, recursion could happen here, so we need to validate the table on every iteration
while(getn(AceAddon.initializequeue) > 0) do
-- Ace3v: When an Ace3 addons is loaded, then he initializeque should not be empty unless
-- the addon does not use the library. If an addon that does not use Ace3 library
-- is loaded, we will also receive the ADDON_LOADED event but in this case the
-- queue will be empty so we have nothing to do.
while(tgetn(AceAddon.initializequeue) > 0) do
local addon = tremove(AceAddon.initializequeue, 1)
-- this might be an issue with recursion - TODO: validate
if event == "ADDON_LOADED" then addon.baseName = arg1 end
@@ -655,13 +664,14 @@ local function onEvent()
end
if IsLoggedIn then
while(getn(AceAddon.enablequeue) > 0) do
while(tgetn(AceAddon.enablequeue) > 0) do
local addon = tremove(AceAddon.enablequeue, 1)
AceAddon:EnableAddon(addon)
end
end
end
end
end -- onEvent
AceAddon.frame:RegisterEvent("ADDON_LOADED")
AceAddon.frame:RegisterEvent("PLAYER_LOGIN")
@@ -670,4 +680,4 @@ AceAddon.frame:SetScript("OnEvent", onEvent)
-- upgrade embeded
for name, addon in pairs(AceAddon.addons) do
Embed(addon, true)
end
end
@@ -16,87 +16,110 @@ local AceConsole, oldminor = LibStub:NewLibrary(MAJOR, MINOR)
if not AceConsole then return end -- No upgrade needed
local AceCore = LibStub("AceCore-3.0")
AceConsole.embeds = AceConsole.embeds or {} -- table containing objects AceConsole is embedded in.
AceConsole.commands = AceConsole.commands or {} -- table containing commands registered
AceConsole.weakcommands = AceConsole.weakcommands or {} -- table containing self, command => func references for weak commands that don't persist through enable/disable
-- Lua APIs
local tconcat, tostring, select = table.concat, tostring, select
local tinsert, tconcat, tgetn, tsetn = table.insert, table.concat, table.getn, table.setn
local tostring = tostring
local type, pairs, error = type, pairs, error
local format, strfind, strsub = string.format, string.find, string.sub
local max = math.max
local strupper, strlower = string.upper, string.lower
-- WoW APIs
local _G = getfenv()
local _G = AceCore._G
-- Global vars/functions that we don't upvalue since they might get hooked, or upgraded
-- List them here for Mikk's FindGlobals script
-- GLOBALS: DEFAULT_CHAT_FRAME, SlashCmdList, hash_SlashCmdList
local tmp={}
local function Print(self,frame,...)
local n=0
local Print
do
local tmp = {}
function Print(self, frame, arg)
if self ~= AceConsole then
n=n+1
tmp[n] = "|cff33ff99"..tostring( self ).."|r:"
tmp[1] = "|cff33ff99"..tostring(self).."|r:"
else
tmp[1] = ''
end
for i=1, getn(arg) do
n=n+1
tmp[n] = tostring(arg[i])
if type(arg) == "string" then
frame:AddMessage(tmp[1]..arg)
else -- arg is table and may contain frame as first element if argument frame is nil
local b, e = frame and 1 or 2, tgetn(arg)
if e >= b then
frame = frame or arg[1]
for i=0,e-b do
tmp[2+i] = tostring(arg[b+i])
end
frame:AddMessage(tconcat(tmp," ",1,e-b+2)) -- explicitly, because the length is not affected by assignment
end
end
frame:AddMessage( tconcat(tmp," ",1,n) )
end
end -- Print
--- Print to DEFAULT_CHAT_FRAME or given ChatFrame (anything with an .AddMessage function)
-- @paramsig [chatframe ,] ...
-- @param chatframe Custom ChatFrame to print to (or any frame with an .AddMessage function)
-- @param ... List of any values to be printed
function AceConsole:Print(...)
local frame = arg
local frame = arg[1]
if type(frame) == "table" and frame.AddMessage then -- Is first argument something with an .AddMessage member?
return Print(self, frame, arg[2])
return Print(self, nil, arg)
else
return Print(self, DEFAULT_CHAT_FRAME, arg)
end
end
--- Formatted (using format()) print to DEFAULT_CHAT_FRAME or given ChatFrame (anything with an .AddMessage function)
-- @paramsig [chatframe ,] "format"[, ...]
-- @param chatframe Custom ChatFrame to print to (or any frame with an .AddMessage function)
-- @param format Format string - same syntax as standard Lua format()
-- @param ... Arguments to the format string
function AceConsole:Printf(...)
local frame = arg
if type(frame) == "table" and frame.AddMessage then -- Is first argument something with an .AddMessage member?
return Print(self, frame, format(arg[2]))
function AceConsole:Printf(a1, ...)
local frame, succ, s
if type(a1) == "table" and a1.AddMessage then -- Is first argument something with an .AddMessage member?
frame, succ, s = a1, pcall(format, unpack(arg))
else
return Print(self, DEFAULT_CHAT_FRAME, format(arg))
frame, succ, s = DEFAULT_CHAT_FRAME, pcall(format, a1, unpack(arg))
end
if not succ then error(s,2) end
return Print(self, frame, s)
end
--- Register a simple chat command
-- @param command Chat command to be registered WITHOUT leading "/"
-- @param func Function to call when the slash command is being used (funcref or methodname)
-- @param persist if false, the command will be soft disabled/enabled when aceconsole is used as a mixin (default: true)
function AceConsole:RegisterChatCommand( command, func, persist )
if type(command)~="string" then error([[Usage: AceConsole:RegisterChatCommand( "command", func[, persist ]): 'command' - expected a string]], 2) end
if type(command)~="string" then error([[Usage: AceConsole:RegisterChatCommand(command, func[, persist ]): 'command' - expected a string]], 2) end
if persist==nil then persist=true end -- I'd rather have my addon's "/addon enable" around if the author screws up. Having some extra slash regged when it shouldnt be isn't as destructive. True is a better default. /Mikk
local name = "ACECONSOLE_"..string.upper(command)
local name = "ACECONSOLE_"..strupper(command)
if type( func ) == "string" then
local t = type(func)
if t == "string" then
-- Ace3v: prevent user from using AceConSole as self
if self == AceConsole then
error([[Usage: RegisterChatCommand(command, func[, persist]): 'self' - use your own 'self']], 2)
end
SlashCmdList[name] = function(input, editBox)
self[func](self, input, editBox)
end
else
elseif t == "function" then
SlashCmdList[name] = func
else
error([[Usage: AceConsole:RegisterChatCommand(command, func[, persist ]): 'func' - expected a string or a function]], 2)
end
_G["SLASH_"..name.."1"] = "/"..string.lower(command)
_G["SLASH_"..name.."1"] = "/"..strlower(command)
AceConsole.commands[command] = name
-- non-persisting commands are registered for enabling disabling
if not persist then
@@ -113,7 +136,6 @@ function AceConsole:UnregisterChatCommand( command )
if name then
SlashCmdList[name] = nil
_G["SLASH_" .. name .. "1"] = nil
hash_SlashCmdList["/" .. command:upper()] = nil
AceConsole.commands[command] = nil
end
end
@@ -122,18 +144,16 @@ end
-- @return Iterator (pairs) over all commands
function AceConsole:IterateChatCommands() return pairs(AceConsole.commands) end
local function nils(n, ...)
if n>1 then
return nil, nils(n-1, arg)
elseif n==1 then
return nil, arg
local function nils(n,argc,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10)
if n >= 1 then
return nil, nils(n-1,argc,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10)
elseif not argc or argc == 0 then
return
else
return arg
return a1, nils(0,argc-1,a2,a3,a4,a5,a6,a7,a8,a9,a10)
end
end
--- Retreive one or more space-separated arguments from a string.
-- Treats quoted strings and itemlinks as non-spaced.
-- @param str The raw argument string
@@ -150,7 +170,7 @@ function AceConsole:GetArgs(str, numargs, startpos)
-- find start of new arg
pos = strfind(str, "[^ ]", pos)
if not pos then -- whoops, end of string
return nils(numargs, 1e9)
return nils(numargs, 1, 1e9)
end
if numargs<1 then
@@ -205,7 +225,7 @@ function AceConsole:GetArgs(str, numargs, startpos)
end
-- search aborted, we hit end of string. return it all as one argument. (yes, even if it's an unterminated quote or hyperlink)
return strsub(str, startpos), nils(numargs-1, 1e9)
return strsub(str, startpos), nils(numargs-1, 1, 1e9)
end
@@ -202,4 +202,4 @@ function AceCore.truncate(t,e)
end
end
tsetn(t,e)
end
end
@@ -2,7 +2,7 @@
-- All dispatching is done using **CallbackHandler-1.0**. AceEvent is a simple wrapper around
-- CallbackHandler, and dispatches all game events or addon message to the registrees.
--
-- **AceEvent-3.0** can be embeded into your addon, either explicitly by calling AceEvent:Embed(MyAddon) or by
-- **AceEvent-3.0** can be embeded into your addon, either explicitly by calling AceEvent:Embed(MyAddon) or by
-- specifying it as an embeded library in your AceAddon. All functions will be available on your addon object
-- and can be accessed directly, without having to explicitly call AceEvent itself.\\
-- It is recommended to embed AceEvent, otherwise you'll have to specify a custom `self` on all calls you
@@ -18,29 +18,29 @@ if not AceEvent then return end
-- Lua APIs
local pairs = pairs
local CallbackHandler = LibStub("CallbackHandler-1.0")
local CallbackHandler = LibStub:GetLibrary("CallbackHandler-1.0")
AceEvent.frame = AceEvent.frame or CreateFrame("Frame", "AceEvent30Frame") -- our event frame
AceEvent.embeds = AceEvent.embeds or {} -- what objects embed this lib
-- APIs and registry for blizzard events, using CallbackHandler lib
if not AceEvent.events then
AceEvent.events = CallbackHandler:New(AceEvent,
AceEvent.events = CallbackHandler:New(AceEvent,
"RegisterEvent", "UnregisterEvent", "UnregisterAllEvents")
end
function AceEvent.events:OnUsed(target, eventname)
function AceEvent.events:OnUsed(target, eventname)
AceEvent.frame:RegisterEvent(eventname)
end
function AceEvent.events:OnUnused(target, eventname)
function AceEvent.events:OnUnused(target, eventname)
AceEvent.frame:UnregisterEvent(eventname)
end
-- APIs and registry for IPC messages, using CallbackHandler lib
if not AceEvent.messages then
AceEvent.messages = CallbackHandler:New(AceEvent,
AceEvent.messages = CallbackHandler:New(AceEvent,
"RegisterMessage", "UnregisterMessage", "UnregisterAllMessages"
)
AceEvent.SendMessage = AceEvent.messages.Fire
@@ -126,4 +126,4 @@ end)
--- Finally: upgrade our old embeds
for target, v in pairs(AceEvent.embeds) do
AceEvent:Embed(target)
end
end
@@ -537,4 +537,4 @@ end
--- Upgrade our old embeded
for target, v in pairs( AceHook.embeded ) do
AceHook:Embed( target )
end
end
@@ -134,4 +134,4 @@ function AceLocale:GetLocale(application, silent)
error("Usage: GetLocale(application[, silent]): 'application' - No locales registered for '"..tostring(application).."'", 2)
end
return AceLocale.apps[application]
end
end
@@ -1,9 +1,9 @@
--- **AceSerializer-3.0** can serialize any variable (except functions or userdata) into a string format,
-- that can be send over the addon comm channel. AceSerializer was designed to keep all data intact, especially
-- that can be send over the addon comm channel. AceSerializer was designed to keep all data intact, especially
-- very large numbers or floating point numbers, and table structures. The only caveat currently is, that multiple
-- references to the same table will be send individually.
--
-- **AceSerializer-3.0** can be embeded into your addon, either explicitly by calling AceSerializer:Embed(MyAddon) or by
-- **AceSerializer-3.0** can be embeded into your addon, either explicitly by calling AceSerializer:Embed(MyAddon) or by
-- specifying it as an embeded library in your AceAddon. All functions will be available on your addon object
-- and can be accessed directly, without having to explicitly call AceSerializer itself.\\
-- It is recommended to embed AceSerializer, otherwise you'll have to specify a custom `self` on all calls you
@@ -40,7 +40,7 @@ local function SerializeStringHelper(ch) -- Used by SerializeValue for strings
return "\126\122"
elseif n<=32 then -- nonprint + space
return "\126"..strchar(n+64)
elseif n==94 then -- value separator
elseif n==94 then -- value separator
return "\126\125"
elseif n==126 then -- our own escape character
return "\126\124"
@@ -54,12 +54,12 @@ end
local function SerializeValue(v, res, nres)
-- We use "^" as a value separator, followed by one byte for type indicator
local t=type(v)
if t=="string" then -- ^S = string (escaped to remove nonprints, "^"s, etc)
res[nres+1] = "^S"
res[nres+2] = gsub(v,"[%c \94\126\127]", SerializeStringHelper)
nres=nres+2
elseif t=="number" then -- ^N = number (just tostring()ed) or ^F (float components)
local str = tostring(v)
if tonumber(str)==v --[[not in 4.3 or str==serNaN]] then
@@ -79,7 +79,7 @@ local function SerializeValue(v, res, nres)
res[nres+4] = tostring(e-53) -- adjust exponent to counteract mantissa manipulation
nres=nres+4
end
elseif t=="table" then -- ^T...^t = table (list of key,value pairs)
nres=nres+1
res[nres] = "^T"
@@ -89,7 +89,7 @@ local function SerializeValue(v, res, nres)
end
nres=nres+1
res[nres] = "^t"
elseif t=="boolean" then -- ^B = true, ^b = false
nres=nres+1
if v then
@@ -97,15 +97,15 @@ local function SerializeValue(v, res, nres)
else
res[nres] = "^b" -- false
end
elseif t=="nil" then -- ^Z = nil (zero, "N" was taken :P)
nres=nres+1
res[nres] = "^Z"
else
error(MAJOR..": Cannot serialize a value of type '"..t.."'") -- can't produce error on right level, this is wildly recursive
end
return nres
end
@@ -127,7 +127,7 @@ function AceSerializer:Serialize(...)
end
serializeTbl[nres+1] = "^^" -- "^^" = End of serialized data
return tconcat(serializeTbl, "", 1, nres+1)
end
@@ -174,9 +174,9 @@ local function DeserializeValue(iter,single,ctl,data)
ctl,data = iter()
end
if not ctl then
if not ctl then
error("Supplied data misses AceSerializer terminator ('^^')")
end
end
if ctl=="^^" then
-- ignore extraneous data
@@ -184,7 +184,7 @@ local function DeserializeValue(iter,single,ctl,data)
end
local res
if ctl=="^S" then
res = gsub(data, "~.", DeserializeStringHelper)
elseif ctl=="^N" then
@@ -217,7 +217,7 @@ local function DeserializeValue(iter,single,ctl,data)
ctl,data = iter()
if ctl=="^t" then break end -- ignore ^t's data
k = DeserializeValue(iter,true,ctl,data)
if k==nil then
if k==nil then
error("Invalid AceSerializer table format (no table end marker)")
end
ctl,data = iter()
@@ -230,7 +230,7 @@ local function DeserializeValue(iter,single,ctl,data)
else
error("Invalid AceSerializer control code '"..ctl.."'")
end
if not single then
return res,DeserializeValue(iter)
else
@@ -1,12 +1,12 @@
--- **AceTimer-3.0** provides a central facility for registering timers.
-- AceTimer supports one-shot timers and repeating timers. All timers are stored in an efficient
-- data structure that allows easy dispatching and fast rescheduling. Timers can be registered, rescheduled
-- data structure that allows easy dispatching and fast rescheduling. Timers can be registered
-- or canceled at any time, even from within a running timer, without conflict or large overhead.\\
-- AceTimer is currently limited to firing timers at a frequency of 0.1s. This constant may change
-- in the future, but for now it seemed like a good compromise in efficiency and accuracy.
-- AceTimer is currently limited to firing timers at a frequency of 0.01s as this is what the WoW timer API
-- restricts us to.
--
-- All `:Schedule` functions will return a handle to the current timer, which you will need to store if you
-- need to cancel or reschedule the timer you just registered.
-- need to cancel the timer you just registered.
--
-- **AceTimer-3.0** can be embeded into your addon, either explicitly by calling AceTimer:Embed(MyAddon) or by
-- specifying it as an embeded library in your AceAddon. All functions will be available on your addon object
@@ -15,54 +15,33 @@
-- make into AceTimer.
-- @class file
-- @name AceTimer-3.0
-- @release $Id: AceTimer-3.0.lua 895 2009-12-06 16:28:55Z nevcairiel $
-- @release $Id: AceTimer-3.0.lua 1119 2014-10-14 17:23:29Z nevcairiel $
--[[
Basic assumptions:
* In a typical system, we do more re-scheduling per second than there are timer pulses per second
* Regardless of timer implementation, we cannot guarantee timely delivery due to FPS restriction (may be as low as 10)
This implementation:
CON: The smallest timer interval is constrained by HZ (currently 1/10s).
PRO: It will still correctly fire any timer slower than HZ over a length of time, e.g. 0.11s interval -> 90 times over 10 seconds
PRO: In lag bursts, the system simly skips missed timer intervals to decrease load
CON: Algorithms depending on a timer firing "N times per minute" will fail
PRO: (Re-)scheduling is O(1) with a VERY small constant. It's a simple linked list insertion in a hash bucket.
CAUTION: The BUCKETS constant constrains how many timers can be efficiently handled. With too many hash collisions, performance will decrease.
Major assumptions upheld:
- ALLOWS scheduling multiple timers with the same funcref/method
- ALLOWS scheduling more timers during OnUpdate processing
- ALLOWS unscheduling ANY timer (including the current running one) at any time, including during OnUpdate processing
]]
local MAJOR, MINOR = "AceTimer-3.0", 6
local MAJOR, MINOR = "AceTimer-3.0", 17 -- Bump minor on changes
local AceTimer, oldminor = LibStub:NewLibrary(MAJOR, MINOR)
if not AceTimer then return end -- No upgrade needed
AceTimer.hash = AceTimer.hash or {} -- Array of [0..BUCKET-1] = linked list of timers (using .next member)
-- Linked list gets around ACE-88 and ACE-90.
AceTimer.selfs = AceTimer.selfs or {} -- Array of [self]={[handle]=timerobj, [handle2]=timerobj2, ...}
local AceCore = LibStub("AceCore-3.0")
local safecall = AceCore.safecall
AceTimer.counter = AceTimer.counter or {}
AceTimer.hash = AceTimer.hash or {} -- Array of [1..BUCKETS] = linked list of timers (using .next member)
AceTimer.activeTimers = AceTimer.activeTimers or {} -- Active timer list
AceTimer.frame = AceTimer.frame or CreateFrame("Frame", "AceTimer30Frame")
local counter = AceTimer.counter
local activeTimers = AceTimer.activeTimers -- Upvalue our private data
local timerFrame = AceTimer.frame
-- Lua APIs
local assert, error, loadstring = assert, error, loadstring
local setmetatable, rawset, rawget = setmetatable, rawset, rawget
local select, pairs, type, next, tostring = select, pairs, type, next, tostring
local floor, max, min = math.floor, math.max, math.min
local tconcat = table.concat
local type, unpack, next, error = type, unpack, next, error
local floor, max, min, mod = math.floor, math.max, math.min, math.mod
local tostring = tostring
-- WoW APIs
local GetTime = GetTime
-- Global vars/functions that we don't upvalue since they might get hooked, or upgraded
-- List them here for Mikk's FindGlobals script
-- GLOBALS: DEFAULT_CHAT_FRAME, geterrorhandler
-- Simple ONE-SHOT timer cache. Much more efficient than a full compost for our purposes.
local timerCache = nil
--[[
Timers will not be fired more often than HZ-1 times per second.
Keep at intended speed PLUS ONE or we get bitten by floating point rounding errors (n.5 + 0.1 can be n.599999)
@@ -70,6 +49,7 @@ local timerCache = nil
If this number is ever changed, all entries need to be rehashed on lib upgrade.
]]
local HZ = 11
local minDelay = 1/(HZ-1)
--[[
Prime for good distribution
@@ -82,214 +62,122 @@ for i=1,BUCKETS do
hash[i] = hash[i] or false -- make it an integer-indexed array; it's faster than hashes
end
--[[
xpcall safecall implementation
]]
local xpcall = xpcall
local function errorhandler(err)
return geterrorhandler()(err)
end
local function CreateDispatcher(argCount)
local code = [[
local method, ARGS
local function call() return method(ARGS) end
local function dispatch(func, ...)
method = func
if not method then return end
ARGS = unpack(arg)
return xpcall(call, function(err) return geterrorhandler()(err) end)
end
return dispatch
]]
local ARGS = {}
for i = 1, argCount do ARGS[i] = "arg"..i end
code = gsub(code, "ARGS", tconcat(ARGS, ", "))
return assert(loadstring(code, "safecall Dispatcher["..argCount.."]"))(xpcall, errorhandler)
end
local Dispatchers = setmetatable({}, {
__index=function(self, argCount)
local dispatcher = CreateDispatcher(argCount)
rawset(self, argCount, dispatcher)
return dispatcher
local new, del
do
local list = setmetatable({}, {__mode = "k"})
function new(self, loop, func, delay, argc,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10)
local name = loop and "ScheduleRepeatingTimer" or "ScheduleTimer"
if self == AceTimer then
error(MAJOR..": " .. name .. "(callback, delay, argc, args...): use your own 'self'", 3)
end
})
Dispatchers[0] = function(func)
return xpcall(func, errorhandler)
end
local function safecall(func, ...)
return Dispatchers[getn(arg)](func, unpack(arg))
end
local lastint = floor(GetTime() * HZ)
-- --------------------------------------------------------------------
-- OnUpdate handler
--
-- traverse buckets, always chasing "now", and fire timers that have expired
local function OnUpdate()
local now = GetTime()
local nowint = floor(now * HZ)
-- Have we passed into a new hash bucket?
if nowint == lastint then return end
local soon = now + 1 -- +1 is safe as long as 1 < HZ < BUCKETS/2
-- Pass through each bucket at most once
-- Happens on e.g. instance loads, but COULD happen on high local load situations also
for curint = (max(lastint, nowint - BUCKETS) + 1), nowint do -- loop until we catch up with "now", usually only 1 iteration
local curbucket = (math.mod(curint, BUCKETS))+1
-- Yank the list of timers out of the bucket and empty it. This allows reinsertion in the currently-processed bucket from callbacks.
local nexttimer = hash[curbucket]
hash[curbucket] = false -- false rather than nil to prevent the array from becoming a hash
while nexttimer do
local timer = nexttimer
nexttimer = timer.next
local when = timer.when
if when < soon then
-- Call the timer func, either as a method on given object, or a straight function ref
local callback = timer.callback
if type(callback) == "string" then
safecall(timer.object[callback], timer.object, timer.arg)
elseif callback then
safecall(callback, timer.arg)
else
-- probably nilled out by CancelTimer
timer.delay = nil -- don't reschedule it
end
local delay = timer.delay -- NOW make a local copy, can't do it earlier in case the timer cancelled itself in the callback
if not delay then
-- single-shot timer (or cancelled)
AceTimer.selfs[timer.object][tostring(timer)] = nil
timerCache = timer
else
-- repeating timer
local newtime = when + delay
if newtime < now then -- Keep lag from making us firing a timer unnecessarily. (Note that this still won't catch too-short-delay timers though.)
newtime = now + delay
end
timer.when = newtime
-- add next timer execution to the correct bucket
local bucket = (math.mod(floor(newtime * HZ), BUCKETS)) + 1
timer.next = hash[bucket]
hash[bucket] = timer
end
else -- if when>=soon
-- reinsert (yeah, somewhat expensive, but shouldn't be happening too often either due to hash distribution)
timer.next = hash[curbucket]
hash[curbucket] = timer
end -- if when<soon ... else
end -- while nexttimer do
end -- for curint=lastint,nowint
lastint = nowint
end
-- ---------------------------------------------------------------------
-- Reg( callback, delay, arg, repeating )
--
-- callback( function or string ) - direct function ref or method name in our object for the callback
-- delay(int) - delay for the timer
-- arg(variant) - any argument to be passed to the callback function
-- repeating(boolean) - repeating timer, or oneshot
--
-- returns the handle of the timer for later processing (canceling etc)
local function Reg(self, callback, delay, arg, repeating)
if type(callback) ~= "string" and type(callback) ~= "function" then
local error_origin = repeating and "ScheduleRepeatingTimer" or "ScheduleTimer"
error(MAJOR..": " .. error_origin .. "(callback, delay, arg): 'callback' - function or method name expected.", 3)
if not func or not delay then
error(MAJOR..": " .. name .. "(callback, delay, argc, args...): 'callback' and 'delay' must have set values.", 3)
end
if type(callback) == "string" then
if type(self)~="table" then
local error_origin = repeating and "ScheduleRepeatingTimer" or "ScheduleTimer"
error(MAJOR..": " .. error_origin .. "(\"methodName\", delay, arg): 'self' - must be a table.", 3)
end
if type(self[callback]) ~= "function" then
local error_origin = repeating and "ScheduleRepeatingTimer" or "ScheduleTimer"
error(MAJOR..": " .. error_origin .. "(\"methodName\", delay, arg): 'methodName' - method not found on target object.", 3)
if argc and (type(argc) ~= "number" or floor(argc) ~= argc) then
error(MAJOR..": " .. name .. "(callback, delay, argc, args...): 'argc' must be an integer.", 3)
end
if type(func) == "string" then
if type(self) ~= "table" then
error(MAJOR..": " .. name .. "(callback, delay, argc, args...): 'self' - must be a table.", 3)
elseif type(self[func]) ~= "function" then
error(MAJOR..": " .. name .. "(callback, delay, argc, args...): Tried to register '"..func.."' as the callback, but it is not a method.", 3)
end
elseif type(func) ~= "function" then
error(MAJOR..": " .. name .. "(callback, delay, argc, args...): Tried to register '"..tostring(func).."' as the callback, but it is not a function.", 3)
end
if delay < (1 / (HZ - 1)) then
delay = 1 / (HZ - 1)
if delay < minDelay then
delay = minDelay
end
-- Create and stuff timer in the correct hash bucket
local now = GetTime()
local timer = timerCache or {} -- Get new timer object (from cache if available)
timerCache = nil
local timer = next(list) or {}
list[timer] = nil
timer.object = self
timer.callback = callback
timer.delay = (repeating and delay)
timer.arg = arg
timer.when = now + delay
timer.func = func
timer.delay = delay
timer.status = loop and "loop" or "once"
timer.ends = now + delay
timer.argsCount = argc or 0
timer[1] = a1
timer[2] = a2
timer[3] = a3
timer[4] = a4
timer[5] = a5
timer[6] = a6
timer[7] = a7
timer[8] = a8
timer[9] = a9
timer[10] = a10
local bucket = (math.mod(floor((now+delay)*HZ), BUCKETS)) + 1
local bucket = floor(mod((now+delay)*HZ,BUCKETS)) + 1
timer.next = hash[bucket]
hash[bucket] = timer
-- Insert timer in our self->handle->timer registry
local handle = tostring(timer)
local id = tostring(timer) -- user has only access to the id but not the table itself
activeTimers[id] = timer
local selftimers = AceTimer.selfs[self]
if not selftimers then
selftimers = {}
AceTimer.selfs[self] = selftimers
end
selftimers[handle] = timer
selftimers.__ops = (selftimers.__ops or 0) + 1
counter[self] = (counter[self] or 0) + 1
return handle
timerFrame:Show()
return id
end
function del(t)
local id = tostring(t)
activeTimers[id] = nil
if not next(activeTimers) then
timerFrame:Hide()
end
local self = t.object
for k in pairs(t) do t[k] = nil end
list[t] = true
if counter[self] then
counter[self] = counter[self] - 1
else
counter[self] = nil
end
end
end -- new, del
--- Schedule a new one-shot timer.
-- The timer will fire once in `delay` seconds, unless canceled before.
-- @param callback Callback function for the timer pulse (funcref or method name).
-- @param delay Delay for the timer, in seconds.
-- @param arg An optional argument to be passed to the callback function.
-- @param argc The numbers of arguments to be passed to the callback function
-- @param a1,...,a10 The arguments
-- @usage
-- MyAddon = LibStub("AceAddon-3.0"):NewAddon("TimerTest", "AceTimer-3.0")
-- MyAddOn = LibStub("AceAddon-3.0"):NewAddon("MyAddOn", "AceTimer-3.0")
--
-- function MyAddon:OnEnable()
-- function MyAddOn:OnEnable()
-- self:ScheduleTimer("TimerFeedback", 5)
-- end
--
-- function MyAddon:TimerFeedback()
-- function MyAddOn:TimerFeedback()
-- print("5 seconds passed")
-- end
function AceTimer:ScheduleTimer(callback, delay, arg)
return Reg(self, callback, delay, arg)
function AceTimer:ScheduleTimer(func, delay, argc,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10)
return new(self, nil, func, delay, argc,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10)
end
--- Schedule a repeating timer.
-- The timer will fire every `delay` seconds, until canceled.
-- @param callback Callback function for the timer pulse (funcref or method name).
-- @param delay Delay for the timer, in seconds.
-- @param arg An optional argument to be passed to the callback function.
-- @param argc The numbers of arguments to be passed to the callback function
-- @param a1,...,a10 The arguments
-- @usage
-- MyAddon = LibStub("AceAddon-3.0"):NewAddon("TimerTest", "AceTimer-3.0")
-- MyAddOn = LibStub("AceAddon-3.0"):NewAddon("MyAddOn", "AceTimer-3.0")
--
-- function MyAddon:OnEnable()
-- function MyAddOn:OnEnable()
-- self.timerCount = 0
-- self.testTimer = self:ScheduleRepeatingTimer("TimerFeedback", 5)
-- end
--
-- function MyAddon:TimerFeedback()
-- function MyAddOn:TimerFeedback()
-- self.timerCount = self.timerCount + 1
-- print(("%d seconds passed"):format(5 * self.timerCount))
-- -- run 30 seconds in total
@@ -297,131 +185,65 @@ end
-- self:CancelTimer(self.testTimer)
-- end
-- end
function AceTimer:ScheduleRepeatingTimer(callback, delay, arg)
return Reg(self, callback, delay, arg, true)
function AceTimer:ScheduleRepeatingTimer(func, delay, argc,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10)
return new(self, true, func, delay, argc,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10)
end
--- Cancels a timer with the given handle, registered by the same addon object as used for `:ScheduleTimer`
-- Both one-shot and repeating timers can be canceled with this function, as long as the `handle` is valid
--- Cancels a timer with the given id, registered by the same addon object as used for `:ScheduleTimer`
-- Both one-shot and repeating timers can be canceled with this function, as long as the `id` is valid
-- and the timer has not fired yet or was canceled before.
-- @param handle The handle of the timer, as returned by `:ScheduleTimer` or `:ScheduleRepeatingTimer`
-- @param silent If true, no error is raised if the timer handle is invalid (expired or already canceled)
-- @return True if the timer was successfully cancelled.
function AceTimer:CancelTimer(handle, silent)
if not handle then return end -- nil handle -> bail out without erroring
if type(handle) ~= "string" then
error(MAJOR..": CancelTimer(handle): 'handle' - expected a string", 2) -- for now, anyway
end
local selftimers = AceTimer.selfs[self]
local timer = selftimers and selftimers[handle]
if silent then
if timer then
timer.callback = nil -- don't run it again
timer.delay = nil -- if this is the currently-executing one: don't even reschedule
-- The timer object is removed in the OnUpdate loop
end
return not not timer -- might return "true" even if we double-cancel. we'll live.
-- @param id The id of the timer, as returned by `:ScheduleTimer` or `:ScheduleRepeatingTimer`
function AceTimer:CancelTimer(id)
local timer = activeTimers[id]
if not timer then
return false
else
if not timer then
geterrorhandler()(MAJOR..": CancelTimer(handle[, silent]): '"..tostring(handle).."' - no such timer registered")
return false
end
if not timer.callback then
geterrorhandler()(MAJOR..": CancelTimer(handle[, silent]): '"..tostring(handle).."' - timer already cancelled or expired")
return false
end
timer.callback = nil -- don't run it again
timer.delay = nil -- if this is the currently-executing one: don't even reschedule
-- Ace3v: the timer will always be collected in the next update but not here
-- this is necessary for AceBucket to determinate if the bucket has been unregistered
-- in the callback
timer.status = nil
activeTimers[id] = nil
return true
end
end
--- Cancels all timers registered to the current addon object ('self')
function AceTimer:CancelAllTimers()
if not(type(self) == "string" or type(self) == "table") then
error(MAJOR..": CancelAllTimers(): 'self' - must be a string or a table",2)
if type(self) ~= "table" then
error(MAJOR..": CancelAllTimers(): 'self' - must be a table",2)
end
if self == AceTimer then
error(MAJOR..": CancelAllTimers(): supply a meaningful 'self'", 2)
end
local selftimers = AceTimer.selfs[self]
if selftimers then
for handle,v in pairs(selftimers) do
if type(v) == "table" then -- avoid __ops, etc
AceTimer.CancelTimer(self, handle, true)
end
for k,v in pairs(activeTimers) do
if v.object == self then
AceTimer.CancelTimer(self, k)
end
end
end
--- Returns the time left for a timer with the given handle, registered by the current addon object ('self').
-- This function will raise a warning when the handle is invalid, but not stop execution.
-- @param handle The handle of the timer, as returned by `:ScheduleTimer` or `:ScheduleRepeatingTimer`
-- @return The time left on the timer, or false if the handle is invalid.
function AceTimer:TimeLeft(handle)
if not handle then return end
if type(handle) ~= "string" then
error(MAJOR..": TimeLeft(handle): 'handle' - expected a string", 2) -- for now, anyway
end
local selftimers = AceTimer.selfs[self]
local timer = selftimers and selftimers[handle]
--- Returns the time left for a timer with the given id, registered by the current addon object ('self').
-- This function will return 0 when the id is invalid.
-- @param id The id of the timer, as returned by `:ScheduleTimer` or `:ScheduleRepeatingTimer`
-- @return The time left on the timer.
function AceTimer:TimeLeft(id)
local timer = activeTimers[id]
if not timer then
geterrorhandler()(MAJOR..": TimeLeft(handle): '"..tostring(handle).."' - no such timer registered")
return false
return 0
else
return timer.ends - GetTime()
end
return timer.when - GetTime()
end
-- ---------------------------------------------------------------------
-- PLAYER_REGEN_ENABLED: Run through our .selfs[] array step by step
-- and clean it out - otherwise the table indices can grow indefinitely
-- if an addon starts and stops a lot of timers. AceBucket does this!
--
-- See ACE-94 and tests/AceTimer-3.0-ACE-94.lua
local lastCleaned = nil
local function OnEvent()
if event~="PLAYER_REGEN_ENABLED" then
return
function AceTimer:TimerStatus(id)
local timer = activeTimers[id]
if not timer then
return nil
else
return timer.status
end
-- Get the next 'self' to process
local selfs = AceTimer.selfs
local self = next(selfs, lastCleaned)
if not self then
self = next(selfs)
end
lastCleaned = self
if not self then -- should only happen if .selfs[] is empty
return
end
-- Time to clean it out?
local list = selfs[self]
if (list.__ops or 0) < 250 then -- 250 slosh indices = ~10KB wasted (worst case!). For one 'self'.
return
end
-- Create a new table and copy all members over
local newlist = {}
local n=0
for k,v in pairs(list) do
newlist[k] = v
if type(v)=="table" and v.callback then -- if the timer is actually live: count it
n=n+1
end
end
newlist.__ops = 0 -- Reset operation count
-- And since we now have a count of the number of live timers, check that it's reasonable. Emit a warning if not.
if n>BUCKETS then
DEFAULT_CHAT_FRAME:AddMessage(MAJOR..": Warning: The addon/module '"..tostring(self).."' has "..n.." live timers. Surely that's not intended?")
end
selfs[self] = newlist
end
-- ---------------------------------------------------------------------
@@ -443,32 +265,115 @@ function AceTimer:Embed(target)
return target
end
-- AceTimer:OnEmbedDisable( target )
-- AceTimer:OnEmbedDisable(target)
-- target (object) - target object that AceTimer is embedded in.
--
-- cancel all timers registered for the object
function AceTimer:OnEmbedDisable( target )
function AceTimer:OnEmbedDisable(target)
target:CancelAllTimers()
end
for addon in pairs(AceTimer.embeds) do
AceTimer:Embed(addon)
end
-- ---------------------------------------------------------------------
-- Debug tools (expose copies of internals to test suites)
AceTimer.debug = AceTimer.debug or {}
AceTimer.debug.HZ = HZ
AceTimer.debug.BUCKETS = BUCKETS
-- --------------------------------------------------------------------
-- OnUpdate handler
--
-- traverse buckets, always chasing "now", and fire timers that have expired
local lastint = floor(GetTime() * HZ)
local function OnUpdate()
local now = GetTime()
local nowint = floor(now * HZ)
-- ---------------------------------------------------------------------
-- Finishing touchups
-- Have we passed into a new hash bucket?
if nowint == lastint then return end
AceTimer.frame:SetScript("OnUpdate", OnUpdate)
AceTimer.frame:SetScript("OnEvent", OnEvent)
AceTimer.frame:RegisterEvent("PLAYER_REGEN_ENABLED")
local soon = now + 1 -- +1 is safe as long as 1 < HZ < BUCKETS/2
-- In theory, we could hide&show the frame based on there being timers or not.
-- However, this job is fairly expensive, and the chance that there will
-- actually be zero timers running is diminuitive to say the least.
-- Pass through each bucket at most once
-- Happens on e.g. instance loads, but COULD happen on high local load situations also
for curint = (max(lastint, nowint-BUCKETS) + 1), nowint do -- loop until we catch up with "now", usually only 1 iteratio
local curbucket = mod(curint,BUCKETS) + 1 -- Ace3v: both int so no floor here
-- Yank the list of timers out of the bucket and empty it. This allows reinsertion in the currently-processed bucket from callbacks.
local nexttimer = hash[curbucket]
hash[curbucket] = false -- false rather than nil to prevent the array from becoming a hash
while nexttimer do
local timer = nexttimer
nexttimer = timer.next
local status = timer.status
if not status then
del(timer)
else
local ends = timer.ends
if (status == "loop" or status == "once") and ends < soon then
local object = timer.object
local callback = timer.func
if type(callback) == "string" then
callback = (type(object) == "table") and object[callback]
if type(callback) == "function" then
safecall(callback, timer.argsCount+1, object,
timer[1], timer[2], timer[3], timer[4], timer[5],
timer[6], timer[7], timer[8], timer[9], timer[10])
else
status = "once"
end
elseif type(callback) == "function" then
safecall(callback, timer.argsCount,
timer[1], timer[2], timer[3], timer[4], timer[5],
timer[6], timer[7], timer[8], timer[9], timer[10])
else
-- probably nilled out by CancelTimer
status = "once" -- don't reschedule it
end
if status == "once" then
del(timer)
else
local delay = timer.delay
local newends = ends + delay
if newends < now then -- Keep lag from making us firing a timer unnecessarily. (Note that this still won't catch too-short-delay timers though.)
newends = now + delay
end
timer.ends = newends
-- add next timer execution to the correct bucket
local bucket = floor(mod(newends*HZ,BUCKETS)) + 1
timer.next = hash[bucket]
hash[bucket] = timer
end
else
-- reinsert (yeah, somewhat expensive, but shouldn't be happening too often either due to hash distribution)
timer.next = hash[curbucket]
hash[curbucket] = timer
end
end
end
end
lastint = nowint
end
local lastchecked = nil
local function OnEvent()
if event ~= "PLAYER_REGEN_ENABLED" then return end
local addon = next(counter, lastchecked)
if not addon then
addon = next(counter)
end
lastchecked = addon
if not addon then -- should only happen if counter is empty
return
end
local n = counter[addon]
if n > BUCKETS then
DEFAULT_CHAT_FRAME:AddMessage(MAJOR..": Warning: The addon/module '"..tostring(addon).."' has "..tostring(n).." live timers. Surely that's not intended?")
end
end
timerFrame:SetScript("OnUpdate", OnUpdate)
timerFrame:SetScript("OnEvent", OnEvent)
timerFrame:RegisterEvent("PLAYER_REGEN_ENABLED")
timerFrame:Hide()
@@ -15,6 +15,7 @@
<Include file="LibSimpleSticky\LibSimpleSticky.xml"/>
<Include file="LibDataBroker\LibDataBroker-1.1.xml"/>
<Include file="LibElvUIPlugin-1.0\LibElvUIPlugin-1.0.xml"/>
<Include file="oUF\oUF.xml"/>
<!--
<Include file="UTF8\UTF8.xml"/>
<Include file="LibCompress\LibCompress.xml"/>
+25
View File
@@ -0,0 +1,25 @@
Copyright (c) 2006-2017 Trond A Ekseth <troeks@gmail.com>
Copyright (c) 2016-2017 Val Voronov <i.lightspark@gmail.com>
Copyright (c) 2016-2017 Adrian L Lange <contact@p3lim.net>
Copyright (c) 2016-2017 Rainrider <rainrider.wow@gmail.com>
Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation
files (the "Software"), to deal in the Software without
restriction, including without limitation the rights to use,
copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following
conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,72 @@
local ns = oUF
local oUF = ns.oUF
local hiddenParent = CreateFrame("Frame")
-- sourced from FrameXML/PartyMemberFrame.lua
local MAX_PARTY_MEMBERS = MAX_PARTY_MEMBERS or 4
local hiddenParent = CreateFrame('Frame', nil, UIParent)
hiddenParent:SetAllPoints()
hiddenParent:Hide()
local function handleFrame(baseName)
local frame
if(type(baseName) == 'string') then
frame = _G[baseName]
else
frame = baseName
end
if(frame) then
frame:UnregisterAllEvents()
frame:Hide()
-- Keep frame hidden without causing taint
frame:SetParent(hiddenParent)
local health = frame.healthBar or frame.healthbar
if(health) then
health:UnregisterAllEvents()
end
local power = frame.manabar
if(power) then
power:UnregisterAllEvents()
end
local spell = frame.castBar or frame.spellbar
if(spell) then
spell:UnregisterAllEvents()
end
local buffFrame = frame.BuffFrame
if(buffFrame) then
buffFrame:UnregisterAllEvents()
end
end
end
function oUF:DisableBlizzard(unit)
if(not unit) then return end
if(unit == 'player') then
handleFrame(PlayerFrame)
elseif(unit == 'pet') then
handleFrame(PetFrame)
elseif(unit == 'target') then
handleFrame(TargetFrame)
handleFrame(ComboFrame)
elseif(unit == 'targettarget') then
handleFrame(TargetofTargetFrame)
elseif(unit:match('party%d?$')) then
local id = unit:match('party(%d)')
if(id) then
handleFrame('PartyMemberFrame' .. id)
else
for i = 1, MAX_PARTY_MEMBERS do
handleFrame(string.format('PartyMemberFrame%d', i))
end
end
end
end
+234
View File
@@ -0,0 +1,234 @@
local ns = oUF
local oUF = ns.oUF
local Private = oUF.Private
local frame_metatable = Private.frame_metatable
local colors = {
smooth = {
1, 0, 0,
1, 1, 0,
0, 1, 0
},
health = {49 / 255, 207 / 255, 37 / 255},
disconnected = {.6, .6, .6},
tapped = {.6, .6, .6},
class = {},
reaction = {},
power = {},
}
-- We do this because people edit the vars directly, and changing the default
-- globals makes SPICE FLOW!
local function customClassColors()
if(CUSTOM_CLASS_COLORS) then
local function updateColors()
for classToken, color in next, CUSTOM_CLASS_COLORS do
colors.class[classToken] = {color.r, color.g, color.b}
end
for _, obj in next, oUF.objects do
obj:UpdateAllElements('CUSTOM_CLASS_COLORS')
end
end
updateColors()
CUSTOM_CLASS_COLORS:RegisterCallback(updateColors)
return true
end
end
if(not customClassColors()) then
for classToken, color in next, RAID_CLASS_COLORS do
colors.class[classToken] = {color.r, color.g, color.b}
end
local eventHandler = CreateFrame('Frame')
eventHandler:RegisterEvent('ADDON_LOADED')
eventHandler:SetScript('OnEvent', function(self)
if(customClassColors()) then
self:UnregisterEvent('ADDON_LOADED')
self:SetScript('OnEvent', nil)
end
end)
end
for eclass, color in next, FACTION_BAR_COLORS do
colors.reaction[eclass] = {color.r, color.g, color.b}
end
colors.power = {}
colors.power[0] = {0.00, 0.00, 1.00}
colors.power[1] = {1.00, 0.00, 0.00}
colors.power[2] = {1.00, 0.50, 0.25}
colors.power[3] = {1.00, 1.00, 0.00}
colors.power[4] = {0.00, 1.00, 1.00}
local function colorsAndPercent(a, b, ...)
if(a <= 0 or b == 0) then
return nil, unpack(arg)
elseif(a >= b) then
return nil, select(select('#', unpack(arg)) - 2, unpack(arg))
end
local num = select('#', unpack(arg)) / 3
local segment, relperc = math.modf((a / b) * (num - 1))
return relperc, select((segment * 3) + 1, unpack(arg))
end
-- http://www.wowwiki.com/ColorGradient
--[[ Colors: oUF:RGBColorGradient(a, b, ...)
Used to convert a percent value (the quotient of `a` and `b`) into a gradient from 2 or more RGB colors. If more than 2
colors are passed, the gradient will be between the two colors which perc lies in an evenly divided range. A RGB color
is a sequence of 3 consecutive RGB percent values (in the range [0-1]). If `a` is negative or `b` is zero then the first
RGB color (the first 3 RGB values passed to the function) is returned. If `a` is bigger than or equal to `b`, then the
last 3 RGB values are returned.
* self - the global oUF object
* a - value used as numerator to calculate the percentage (number)
* b - value used as denominator to calculate the percentage (number)
* ... - a list of RGB percent values. At least 6 values should be passed (number [0-1])
--]]
local function RGBColorGradient(...)
local relperc, r1, g1, b1, r2, g2, b2 = colorsAndPercent(unpack(arg))
if(relperc) then
return r1 + (r2 - r1) * relperc, g1 + (g2 - g1) * relperc, b1 + (b2 - b1) * relperc
else
return r1, g1, b1
end
end
-- HCY functions are based on http://www.chilliant.com/rgb2hsv.html
local function getY(r, g, b)
return 0.299 * r + 0.587 * g + 0.114 * b
end
--[[ Colors: oUF:RGBToHCY(r, g, b)
Used to convert a color from RGB to HCY color space.
* self - the global oUF object
* r - red color component (number [0-1])
* g - green color component (number [0-1])
* b - blue color component (number [0-1])
--]]
function oUF:RGBToHCY(r, g, b)
local min, max = min(r, g, b), max(r, g, b)
local chroma = max - min
local hue
if(chroma > 0) then
if(r == max) then
hue = math.mod((g - b) / chroma, 6)
elseif(g == max) then
hue = (b - r) / chroma + 2
elseif(b == max) then
hue = (r - g) / chroma + 4
end
hue = hue / 6
end
return hue, chroma, getY(r, g, b)
end
local math_abs = math.abs
--[[ Colors: oUF:HCYtoRGB(hue, chroma, luma)
Used to convert a color from HCY to RGB color space.
* self - the global oUF object
* hue - hue color component (number [0-1])
* chroma - chroma color component (number [0-1])
* luma - luminance color component (number [0-1])
--]]
function oUF:HCYtoRGB(hue, chroma, luma)
local r, g, b = 0, 0, 0
if(hue and luma > 0) then
local h2 = hue * 6
local x = chroma * (1 - math_abs(math.mod(h2, 2 - 1)))
if(h2 < 1) then
r, g, b = chroma, x, 0
elseif(h2 < 2) then
r, g, b = x, chroma, 0
elseif(h2 < 3) then
r, g, b = 0, chroma, x
elseif(h2 < 4) then
r, g, b = 0, x, chroma
elseif(h2 < 5) then
r, g, b = x, 0, chroma
else
r, g, b = chroma, 0, x
end
local y = getY(r, g, b)
if(luma < y) then
chroma = chroma * (luma / y)
elseif(y < 1) then
chroma = chroma * (1 - luma) / (1 - y)
end
r = (r - y) * chroma + luma
g = (g - y) * chroma + luma
b = (b - y) * chroma + luma
end
return r, g, b
end
--[[ Colors: oUF:HCYColorGradient(a, b, ...)
Used to convert a percent value (the quotient of `a` and `b`) into a gradient from 2 or more HCY colors. If more than 2
colors are passed, the gradient will be between the two colors which perc lies in an evenly divided range. A HCY color
is a sequence of 3 consecutive values in the range [0-1]. If `a` is negative or `b` is zero then the first
HCY color (the first 3 HCY values passed to the function) is returned. If `a` is bigger than or equal to `b`, then the
last 3 HCY values are returned.
* self - the global oUF object
* a - value used as numerator to calculate the percentage (number)
* b - value used as denominator to calculate the percentage (number)
* ... - a list of HCY color values. At least 6 values should be passed (number [0-1])
--]]
local function HCYColorGradient(...)
local relperc, r1, g1, b1, r2, g2, b2 = colorsAndPercent(unpack(arg))
if(not relperc) then
return r1, g1, b1
end
local h1, c1, y1 = self:RGBToHCY(r1, g1, b1)
local h2, c2, y2 = self:RGBToHCY(r2, g2, b2)
local c = c1 + (c2 - c1) * relperc
local y = y1 + (y2 - y1) * relperc
if(h1 and h2) then
local dh = h2 - h1
if(dh < -0.5) then
dh = dh + 1
elseif(dh > 0.5) then
dh = dh - 1
end
return self:HCYtoRGB(math.mod(h1 + dh * relperc, 1), c, y)
else
return self:HCYtoRGB(h1 or h2, c, y)
end
end
--[[ Colors: oUF:ColorGradient(a, b, ...) or frame:ColorGradient(a, b, ...)
Used as a proxy to call the proper gradient function depending on the user's preference. If `oUF.useHCYColorGradient` is
set to true, `:HCYColorGradient` will be called, else `:RGBColorGradient`.
* self - the global oUF object or a unit frame
* a - value used as numerator to calculate the percentage (number)
* b - value used as denominator to calculate the percentage (number)
* ... - a list of color values. At least 6 values should be passed (number [0-1])
--]]
local function ColorGradient(...)
return (oUF.useHCYColorGradient and HCYColorGradient or RGBColorGradient)(unpack(arg))
end
Private.colors = colors
oUF.colors = colors
oUF.ColorGradient = ColorGradient
oUF.RGBColorGradient = RGBColorGradient
oUF.HCYColorGradient = HCYColorGradient
oUF.useHCYColorGradient = false
frame_metatable.__index.colors = colors
frame_metatable.__index.ColorGradient = ColorGradient
@@ -0,0 +1,103 @@
--[[
# Element: Assistant Indicator
Toggles the visibility of an indicator based on the unit's raid assistant status.
## Widget
AssistantIndicator - Any UI widget.
## Notes
A default texture will be applied if the widget is a Texture and doesn't have a texture or a color set.
## Examples
-- Position and size
local AssistantIndicator = self:CreateTexture(nil, 'OVERLAY')
AssistantIndicator:SetSize(16, 16)
AssistantIndicator:SetPoint('TOP', self)
-- Register it with oUF
self.AssistantIndicator = AssistantIndicator
--]]
local ns = oUF
local oUF = ns.oUF
local UnitInRaid = UnitInRaid
local UnitIsPartyLeader = UnitIsPartyLeader
local UnitIsRaidOfficer = UnitIsRaidOfficer
local function Update(self, event)
local element = self.AssistantIndicator
--[[ Callback: AssistantIndicator:PreUpdate()
Called before the element has been updated.
* self - the AssistantIndicator element
--]]
if(element.PreUpdate) then
element:PreUpdate()
end
local unit = self.unit
local isAssistant = UnitInRaid(unit) and UnitIsRaidOfficer(unit) and not UnitIsPartyLeader(unit)
if(isAssistant) then
element:Show()
else
element:Hide()
end
--[[ Callback: AssistantIndicator:PostUpdate(isAssistant)
Called after the element has been updated.
* self - the AssistantIndicator element
* isAssistant - indicates whether the unit is a raid assistant (boolean)
--]]
if(element.PostUpdate) then
return element:PostUpdate(isAssistant)
end
end
local function Path(self, ...)
--[[ Override: AssistantIndicator.Override(self, event, ...)
Used to completely override the element's update process.
* self - the parent object
* event - the event triggering the update (string)
* ... - the arguments accompanying the event (string)
--]]
return (self.AssistantIndicator.Override or Update) (self, ...)
end
local function ForceUpdate(element)
return Path(element.__owner, 'ForceUpdate')
end
local function Enable(self)
local element = self.AssistantIndicator
if(element) then
element.__owner = self
element.ForceUpdate = ForceUpdate
self:RegisterEvent('PARTY_MEMBERS_CHANGED', Path)
if(element:IsObjectType('Texture') and not element:GetTexture()) then
element:SetTexture([[Interface\GroupFrame\UI-Group-AssistantIcon]])
end
return true
end
end
local function Disable(self)
local element = self.AssistantIndicator
if(element) then
element:Hide()
self:UnregisterEvent('PARTY_MEMBERS_CHANGED', Path)
end
end
oUF:AddElement('AssistantIndicator', Path, Enable, Disable)
@@ -0,0 +1,516 @@
--[[
# Element: Auras
Handles creation and updating of aura icons.
## Widget
Auras - A Frame to hold `Button`s representing both buffs and debuffs.
Buffs - A Frame to hold `Button`s representing buffs.
Debuffs - A Frame to hold `Button`s representing debuffs.
## Notes
At least one of the above widgets must be present for the element to work.
## Options
.disableMouse - Disables mouse events (boolean)
.disableCooldown - Disables the cooldown spiral (boolean)
.size - Aura icon size. Defaults to 16 (number)
.spacing - Spacing between each icon. Defaults to 0 (number)
.['spacing-x'] - Horizontal spacing between each icon. Takes priority over `spacing` (number)
.['spacing-y'] - Vertical spacing between each icon. Takes priority over `spacing` (number)
.['growth-x'] - Horizontal growth direction. Defaults to 'RIGHT' (string)
.['growth-y'] - Vertical growth direction. Defaults to 'UP' (string)
.initialAnchor - Anchor point for the icons. Defaults to 'BOTTOMLEFT' (string)
.filter - Custom filter list for auras to display. Defaults to 'HELPFUL' for buffs and 'HARMFUL' for
debuffs (string)
## Options Auras
.numBuffs - The maximum number of buffs to display. Defaults to 32 (number)
.numDebuffs - The maximum number of debuffs to display. Defaults to 40 (number)
.numTotal - The maximum number of auras to display. Prioritizes buffs over debuffs. Defaults to the sum of
.numBuffs and .numDebuffs (number)
.gap - Controls the creation of an invisible icon between buffs and debuffs. Defaults to false (boolean)
.buffFilter - Custom filter list for buffs to display. Takes priority over `filter` (string)
.debuffFilter - Custom filter list for debuffs to display. Takes priority over `filter` (string)
## Options Buffs
.num - Number of buffs to display. Defaults to 32 (number)
## Options Debuffs
.num - Number of debuffs to display. Defaults to 40 (number)
## Attributes
button.filter - the filter list used to determine the visibility of the aura (string)
button.isDebuff - indicates if the button holds a debuff (boolean)
## Examples
-- Position and size
local Buffs = CreateFrame('Frame', nil, self)
Buffs:SetPoint('RIGHT', self, 'LEFT')
Buffs:SetSize(16 * 2, 16 * 16)
-- Register with oUF
self.Buffs = Buffs
--]]
local ns = oUF
local oUF = ns.oUF
local tinsert = table.insert
local floor = math.floor
local CreateFrame = CreateFrame
local GetTime = GetTime
local UnitAura = UnitAura
local VISIBLE = 1
local HIDDEN = 0
local function UpdateTooltip(self)
if self.filter == 'HELPFUL' then
GameTooltip:SetUnitBuff(self:GetParent().__owner.unit, self:GetID(), self.filter)
else
GameTooltip:SetUnitDebuff(self:GetParent().__owner.unit, self:GetID(), self.filter)
end
end
local function onEnter(self)
if(not self:IsVisible()) then return end
GameTooltip:SetOwner(self, 'ANCHOR_BOTTOMRIGHT')
self:UpdateTooltip()
end
local function onLeave()
GameTooltip:Hide()
end
local function createAuraIcon(element, index)
local button = CreateFrame('Button', '$parentButton' .. index, element)
button:RegisterForClicks('RightButtonUp')
local cd = CreateFrame('Cooldown', '$parentCooldown', button, 'oUF_CooldownFrameTemplate')
cd:SetAllPoints()
local icon = button:CreateTexture(nil, 'BORDER')
icon:SetAllPoints()
local count = button:CreateFontString(nil, 'OVERLAY', 'NumberFontNormal')
count:SetPoint('BOTTOMRIGHT', button, 'BOTTOMRIGHT', -1, 0)
local overlay = button:CreateTexture(nil, 'OVERLAY')
overlay:SetTexture([[Interface\Buttons\UI-Debuff-Overlays]])
overlay:SetAllPoints()
overlay:SetTexCoord(.296875, .5703125, 0, .515625)
button.overlay = overlay
button.UpdateTooltip = UpdateTooltip
button:SetScript('OnEnter', onEnter)
button:SetScript('OnLeave', onLeave)
button.icon = icon
button.count = count
button.cd = cd
--[[ Callback: Auras:PostCreateIcon(button)
Called after a new aura button has been created.
* self - the widget holding the aura buttons
* button - the newly created aura button (Button)
--]]
if(element.PostCreateIcon) then element:PostCreateIcon(button) end
return button
end
local function customFilter(element, unit, button, name)
if(name) then
return true
end
end
local function updateIcon(element, unit, index, offset, filter, isDebuff, visible)
local name, rank, texture, count, dispelType, duration, expiration = UnitAura(unit, index, filter)
if element.forceShow then
name, rank, texture = GetSpellInfo(26993)
count, dispelType, duration, expiration = 5, 'Magic', 0, 60
end
if(name) then
local position = visible + offset + 1
local button = element[position]
if(not button) then
--[[ Override: Auras:CreateIcon(position)
Used to create the aura button at a given position.
* self - the widget holding the aura buttons
* position - the position at which the aura button is to be created (number)
## Returns
* button - the button used to represent the aura (Button)
--]]
button = (element.CreateIcon or createAuraIcon) (element, position)
tinsert(element, button)
element.createdIcons = element.createdIcons + 1
end
button.filter = filter
button.isDebuff = isDebuff
--[[ Override: Auras:CustomFilter(unit, button, ...)
Defines a custom filter that controls if the aura button should be shown.
* self - the widget holding the aura buttons
* unit - the unit on which the aura is cast (string)
* button - the button displaying the aura (Button)
* ... - the return values from [UnitAura](http://wowprogramming.com/docs/api/UnitAura)
## Returns
* show - indicates whether the aura button should be shown (boolean)
--]]
local show = true
if not element.forceShow then
show = (element.CustomFilter or customFilter) (element, unit, button, name, rank, texture, count, dispelType, duration, expiration)
end
if(show) then
-- We might want to consider delaying the creation of an actual cooldown
-- object to this point, but I think that will just make things needlessly
-- complicated.
if(button.cd and not element.disableCooldown) then
if(duration and duration > 0) then
button.cd:SetCooldown(GetTime() - (duration - expiration), duration)
button.cd:Show()
else
button.cd:Hide()
end
end
if(button.overlay) then
if((isDebuff and element.showDebuffType) or (not isDebuff and element.showBuffType) or element.showType) then
local color = DebuffTypeColor[dispelType] or DebuffTypeColor.none
button.overlay:SetVertexColor(color.r, color.g, color.b)
button.overlay:Show()
else
button.overlay:Hide()
end
end
if(button.icon) then button.icon:SetTexture(texture) end
if(button.count) then button.count:SetText(count > 1 and count) end
local size = element.size or 16
button:SetSize(size, size)
button:EnableMouse(not element.disableMouse)
button:SetID(index)
button:Show()
--[[ Callback: Auras:PostUpdateIcon(unit, button, index, position)
Called after the aura button has been updated.
* self - the widget holding the aura buttons
* unit - the unit on which the aura is cast (string)
* button - the updated aura button (Button)
* index - the index of the aura (number)
* position - the actual position of the aura button (number)
--]]
if(element.PostUpdateIcon) then
element:PostUpdateIcon(unit, button, index, position)
end
return VISIBLE
else
return HIDDEN
end
end
end
local function SetPosition(element, from, to)
local sizex = (element.size or 16) + (element['spacing-x'] or element.spacing or 0)
local sizey = (element.size or 16) + (element['spacing-y'] or element.spacing or 0)
local anchor = element.initialAnchor or 'BOTTOMLEFT'
local growthx = (element['growth-x'] == 'LEFT' and -1) or 1
local growthy = (element['growth-y'] == 'DOWN' and -1) or 1
local cols = floor(element:GetWidth() / sizex + 0.5)
for i = from, to do
local button = element[i]
-- Bail out if the to range is out of scope.
if(not button) then break end
local col = (i - 1) % cols
local row = floor((i - 1) / cols)
button:ClearAllPoints()
button:SetPoint(anchor, element, anchor, col * sizex * growthx, row * sizey * growthy)
end
end
local function filterIcons(element, unit, filter, limit, isDebuff, offset, dontHide)
if(not offset) then offset = 0 end
local index = 1
local visible = 0
local hidden = 0
while(visible < limit) do
local result = updateIcon(element, unit, index, offset, filter, isDebuff, visible)
if(not result) then
break
elseif(result == VISIBLE) then
visible = visible + 1
elseif(result == HIDDEN) then
hidden = hidden + 1
end
index = index + 1
end
if(not dontHide) then
for i = visible + offset + 1, #element do
element[i]:Hide()
end
end
return visible, hidden
end
local function UpdateAuras(self, event, unit)
if(self.unit ~= unit) then return end
local auras = self.Auras
if(auras) then
--[[ Callback: Auras:PreUpdate(unit)
Called before the element has been updated.
* self - the widget holding the aura buttons
* unit - the unit for which the update has been triggered (string)
--]]
if(auras.PreUpdate) then auras:PreUpdate(unit) end
local numBuffs = auras.numBuffs or 32
local numDebuffs = auras.numDebuffs or 40
local max = auras.numTotal or numBuffs + numDebuffs
local visibleBuffs, hiddenBuffs = filterIcons(auras, unit, auras.buffFilter or auras.filter or 'HELPFUL', math.min(numBuffs, max), nil, 0, true)
local hasGap
if(visibleBuffs ~= 0 and auras.gap) then
hasGap = true
visibleBuffs = visibleBuffs + 1
local button = auras[visibleBuffs]
if(not button) then
button = (auras.CreateIcon or createAuraIcon) (auras, visibleBuffs)
tinsert(auras, button)
auras.createdIcons = auras.createdIcons + 1
end
-- Prevent the button from displaying anything.
if(button.cd) then button.cd:Hide() end
if(button.icon) then button.icon:SetTexture() end
if(button.overlay) then button.overlay:Hide() end
if(button.stealable) then button.stealable:Hide() end
if(button.count) then button.count:SetText() end
button:EnableMouse(false)
button:Show()
--[[ Callback: Auras:PostUpdateGapIcon(unit, gapButton, visibleBuffs)
Called after an invisible aura button has been created. Only used by Auras when the `gap` option is enabled.
* self - the widget holding the aura buttons
* unit - the unit that has the invisible aura button (string)
* gapButton - the invisible aura button (Button)
* visibleBuffs - the number of currently visible aura buttons (number)
--]]
if(auras.PostUpdateGapIcon) then
auras:PostUpdateGapIcon(unit, button, visibleBuffs)
end
end
local visibleDebuffs, hiddenDebuffs = filterIcons(auras, unit, auras.debuffFilter or auras.filter or 'HARMFUL', math.min(numDebuffs, max - visibleBuffs), true, visibleBuffs)
auras.visibleDebuffs = visibleDebuffs
if(hasGap and visibleDebuffs == 0) then
auras[visibleBuffs]:Hide()
visibleBuffs = visibleBuffs - 1
end
auras.visibleBuffs = visibleBuffs
auras.visibleAuras = auras.visibleBuffs + auras.visibleDebuffs
local fromRange, toRange
--[[ Callback: Auras:PreSetPosition(max)
Called before the aura buttons have been (re-)anchored.
* self - the widget holding the aura buttons
* max - the maximum possible number of aura buttons (number)
## Returns
* from - the offset of the first aura button to be (re-)anchored (number)
* to - the offset of the last aura button to be (re-)anchored (number)
--]]
if(auras.PreSetPosition) then
fromRange, toRange = auras:PreSetPosition(max)
end
if(fromRange or auras.createdIcons > auras.anchoredIcons) then
--[[ Override: Auras:SetPosition(from, to)
Used to (re-)anchor the aura buttons.
Called when new aura buttons have been created or if :PreSetPosition is defined.
* self - the widget that holds the aura buttons
* from - the offset of the first aura button to be (re-)anchored (number)
* to - the offset of the last aura button to be (re-)anchored (number)
--]]
(auras.SetPosition or SetPosition) (auras, fromRange or auras.anchoredIcons + 1, toRange or auras.createdIcons)
auras.anchoredIcons = auras.createdIcons
end
--[[ Callback: Auras:PostUpdate(unit)
Called after the element has been updated.
* self - the widget holding the aura buttons
* unit - the unit for which the update has been triggered (string)
--]]
if(auras.PostUpdate) then auras:PostUpdate(unit) end
end
local buffs = self.Buffs
if(buffs) then
if(buffs.PreUpdate) then buffs:PreUpdate(unit) end
local numBuffs = buffs.num or 32
local visibleBuffs, hiddenBuffs = filterIcons(buffs, unit, buffs.filter or 'HELPFUL', numBuffs)
buffs.visibleBuffs = visibleBuffs
local fromRange, toRange
if(buffs.PreSetPosition) then
fromRange, toRange = buffs:PreSetPosition(numBuffs)
end
if(fromRange or buffs.createdIcons > buffs.anchoredIcons) then
(buffs.SetPosition or SetPosition) (buffs, fromRange or buffs.anchoredIcons + 1, toRange or buffs.createdIcons)
buffs.anchoredIcons = buffs.createdIcons
end
if(buffs.PostUpdate) then buffs:PostUpdate(unit) end
end
local debuffs = self.Debuffs
if(debuffs) then
if(debuffs.PreUpdate) then debuffs:PreUpdate(unit) end
local numDebuffs = debuffs.num or 40
local visibleDebuffs, hiddenDebuffs = filterIcons(debuffs, unit, debuffs.filter or 'HARMFUL', numDebuffs, true)
debuffs.visibleDebuffs = visibleDebuffs
local fromRange, toRange
if(debuffs.PreSetPosition) then
fromRange, toRange = debuffs:PreSetPosition(numDebuffs)
end
if(fromRange or debuffs.createdIcons > debuffs.anchoredIcons) then
(debuffs.SetPosition or SetPosition) (debuffs, fromRange or debuffs.anchoredIcons + 1, toRange or debuffs.createdIcons)
debuffs.anchoredIcons = debuffs.createdIcons
end
if(debuffs.PostUpdate) then debuffs:PostUpdate(unit) end
end
end
local function Update(self, event, unit)
if(self.unit ~= unit) then return end
UpdateAuras(self, event, unit)
-- Assume no event means someone wants to re-anchor things. This is usually
-- done by UpdateAllElements and :ForceUpdate.
if(event == 'ForceUpdate' or not event) then
local buffs = self.Buffs
if(buffs) then
(buffs.SetPosition or SetPosition) (buffs, 1, buffs.createdIcons)
end
local debuffs = self.Debuffs
if(debuffs) then
(debuffs.SetPosition or SetPosition) (debuffs, 1, debuffs.createdIcons)
end
local auras = self.Auras
if(auras) then
(auras.SetPosition or SetPosition) (auras, 1, auras.createdIcons)
end
end
end
local function ForceUpdate(element)
return Update(element.__owner, 'ForceUpdate', element.__owner.unit)
end
local function Enable(self)
if(self.Buffs or self.Debuffs or self.Auras) then
self:RegisterEvent('UNIT_AURA', UpdateAuras)
local buffs = self.Buffs
if(buffs) then
buffs.__owner = self
buffs.ForceUpdate = ForceUpdate
buffs.createdIcons = buffs.createdIcons or 0
buffs.anchoredIcons = 0
--buffs:Show()
end
local debuffs = self.Debuffs
if(debuffs) then
debuffs.__owner = self
debuffs.ForceUpdate = ForceUpdate
debuffs.createdIcons = debuffs.createdIcons or 0
debuffs.anchoredIcons = 0
--debuffs:Show()
end
local auras = self.Auras
if(auras) then
auras.__owner = self
auras.ForceUpdate = ForceUpdate
auras.createdIcons = auras.createdIcons or 0
auras.anchoredIcons = 0
--auras:Show()
end
return true
end
end
local function Disable(self)
if(self.Buffs or self.Debuffs or self.Auras) then
self:UnregisterEvent('UNIT_AURA', UpdateAuras)
if(self.Buffs) then self.Buffs:Hide() end
if(self.Debuffs) then self.Debuffs:Hide() end
if(self.Auras) then self.Auras:Hide() end
end
end
oUF:AddElement('Auras', Update, Enable, Disable)
@@ -0,0 +1,585 @@
--[[
# Element: Castbar
Handles the visibility and updating of spell castbars.
Based upon oUF_Castbar by starlon.
## Widget
Castbar - A `StatusBar` to represent spell cast/channel progress.
## Sub-Widgets
.Text - A `FontString` to represent spell name.
.Icon - A `Texture` to represent spell icon.
.Time - A `FontString` to represent spell duration.
.SafeZone - A `Texture` to represent latency.
## Notes
A default texture will be applied to the StatusBar and Texture widgets if they don't have a texture or a color set.
## Options
.timeToHold - indicates for how many seconds the castbar should be visible after a _FAILED or _INTERRUPTED
event. Defaults to 0 (number)
## Examples
-- Position and size
local Castbar = CreateFrame('StatusBar', nil, self)
Castbar:SetSize(20, 20)
Castbar:SetPoint('TOP')
Castbar:SetPoint('LEFT')
Castbar:SetPoint('RIGHT')
-- Add a background
local Background = Castbar:CreateTexture(nil, 'BACKGROUND')
Background:SetAllPoints(Castbar)
Background:SetTexture(1, 1, 1, .5)
-- Add a spark
local Spark = Castbar:CreateTexture(nil, 'OVERLAY')
Spark:SetSize(20, 20)
Spark:SetBlendMode('ADD')
-- Add a timer
local Time = Castbar:CreateFontString(nil, 'OVERLAY', 'GameFontNormalSmall')
Time:SetPoint('RIGHT', Castbar)
-- Add spell text
local Text = Castbar:CreateFontString(nil, 'OVERLAY', 'GameFontNormalSmall')
Text:SetPoint('LEFT', Castbar)
-- Add spell icon
local Icon = Castbar:CreateTexture(nil, 'OVERLAY')
Icon:SetSize(20, 20)
Icon:SetPoint('TOPLEFT', Castbar, 'TOPLEFT')
-- Add safezone
local SafeZone = Castbar:CreateTexture(nil, 'OVERLAY')
-- Register it with oUF
Castbar.bg = Background
Castbar.Spark = Spark
Castbar.Time = Time
Castbar.Text = Text
Castbar.Icon = Icon
Castbar.SafeZone = SafeZone
self.Castbar = Castbar
--]]
local ns = oUF
local oUF = ns.oUF
local GetNetStats = GetNetStats
local GetTime = GetTime
local UnitCastingInfo = UnitCastingInfo
local UnitChannelInfo = UnitChannelInfo
local UnitIsUnit = UnitIsUnit
local tradeskillCastTime, tradeskillCastDuration, tradeskillCurrent, tradeskillTotal, mergeTradeskill = 0, 0, 0, 0, false
local function updateSafeZone(self)
local safeZone = self.SafeZone
local width = self:GetWidth()
local _, _, ms = GetNetStats()
-- Guard against GetNetStats returning latencies of 0.
if(ms ~= 0) then
local safeZoneRatio = (ms / 1e3) / self.max
if(safeZoneRatio > 1) then
safeZoneRatio = 1
end
safeZone:SetWidth(width * safeZoneRatio)
safeZone:Show()
else
safeZone:Hide()
end
end
local function UNIT_SPELLCAST_SENT(self, event, unit, spell, rank, target)
local element = self.Castbar
element.curTarget = (target and target ~= '') and target or nil
if element.isTradeSkill then
element.tradeSkillCastName = spell
end
end
local function UNIT_SPELLCAST_START(self, event, unit)
if(self.unit ~= unit and self.realUnit ~= unit) then return end
local element = self.Castbar
local name, _, text, texture, startTime, endTime, isTradeSkill = UnitCastingInfo(unit)
if(not name) then
return element:Hide()
end
endTime = endTime / 1e3
startTime = startTime / 1e3
local max = endTime - startTime
element.castName = name
element.duration = GetTime() - startTime
element.max = max
element.delay = 0
element.casting = true
element.holdTime = 0
element.isTradeSkill = isTradeSkill
if(mergeTradeskill and isTradeSkill and UnitIsUnit(unit, 'player')) then
element.duration = element.duration + (element.max * tradeskillCurrent)
element.max = max * tradeskillTotal
if(unit == 'player') then
tradeskillCurrent = tradeskillCurrent + 1
tradeskillCastDuration = element.duration
tradeskillCastTime = max
end
element:SetValue(element.duration)
else
element:SetValue(0)
end
element:SetMinMaxValues(0, element.max)
if(element.Text) then element.Text:SetText(text) end
if(element.Icon) then element.Icon:SetTexture(texture) end
if(element.Time) then element.Time:SetText() end
local sf = element.SafeZone
if(sf) then
sf:ClearAllPoints()
sf:SetPoint('RIGHT')
sf:SetPoint('TOP')
sf:SetPoint('BOTTOM')
updateSafeZone(element)
end
--[[ Callback: Castbar:PostCastStart(unit, name)
Called after the element has been updated upon a spell cast start.
* self - the Castbar widget
* unit - unit for which the update has been triggered (string)
* name - name of the spell being cast (string)
--]]
if(element.PostCastStart) then
element:PostCastStart(unit, name)
end
element:Show()
end
local function UNIT_SPELLCAST_FAILED(self, event, unit, spellname)
if(not self.casting) then return end
if(self.unit ~= unit and self.realUnit ~= unit) then return end
local element = self.Castbar
if(spellname and (element.castName ~= spellname and element.tradeSkillCastName ~= spellname)) then
return
end
if(mergeTradeskill and UnitIsUnit(unit, 'player')) then
mergeTradeskill = false
element.tradeSkillCastName = nil
end
local text = element.Text
if(text) then
text:SetText(FAILED)
end
element.casting = nil
element.holdTime = element.timeToHold or 0
--[[ Callback: Castbar:PostCastFailed(unit, name)
Called after the element has been updated upon a failed spell cast.
* self - the Castbar widget
* unit - unit for which the update has been triggered (string)
* name - name of the failed spell (string)
--]]
if(element.PostCastFailed) then
return element:PostCastFailed(unit, spellname)
end
end
local function UNIT_SPELLCAST_FAILED_QUIET(self, event, unit, spellname)
if(not self.casting) then return end
if(self.unit ~= unit and self.realUnit ~= unit) then return end
local element = self.Castbar
if(spellname and (element.castName ~= spellname and element.tradeSkillCastName ~= spellname)) then
return
end
if(mergeTradeskill and UnitIsUnit(unit, 'player')) then
mergeTradeskill = false
element.tradeSkillCastName = nil
end
element.casting = nil
element:SetValue(0)
element:Hide()
end
local function UNIT_SPELLCAST_INTERRUPTED(self, event, unit, spellname)
if(self.unit ~= unit and self.realUnit ~= unit) then return end
local element = self.Castbar
if(spellname and element.castName ~= spellname) then
return
end
local text = element.Text
if(text) then
text:SetText(INTERRUPTED)
end
element.casting = nil
element.channeling = nil
element.holdTime = element.timeToHold or 0
--[[ Callback: Castbar:PostCastInterrupted(unit, name)
Called after the element has been updated upon an interrupted spell cast.
* self - the Castbar widget
* unit - unit for which the update has been triggered (string)
* name - name of the interrupted spell (string)
--]]
if(element.PostCastInterrupted) then
return element:PostCastInterrupted(unit, spellname)
end
end
local function UNIT_SPELLCAST_DELAYED(self, event, unit)
if(self.unit ~= unit and self.realUnit ~= unit) then return end
local element = self.Castbar
local name, _, _, _, startTime = UnitCastingInfo(unit)
if(not startTime or not element:IsShown()) then return end
local duration = GetTime() - (startTime / 1000)
if(duration < 0) then duration = 0 end
element.delay = element.delay + element.duration - duration
element.duration = duration
element:SetValue(duration)
--[[ Callback: Castbar:PostCastDelayed(unit, name)
Called after the element has been updated when a spell cast has been delayed.
* self - the Castbar widget
* unit - unit that the update has been triggered (string)
* name - name of the delayed spell (string)
--]]
if(element.PostCastDelayed) then
return element:PostCastDelayed(unit, name)
end
end
local function UNIT_SPELLCAST_STOP(self, event, unit, spellname)
if(self.unit ~= unit and self.realUnit ~= unit) then return end
local element = self.Castbar
if(spellname and (element.castName ~= spellname)) then
return
end
if(mergeTradeskill and UnitIsUnit(unit, 'player')) then
if(tradeskillCurrent == tradeskillTotal) then
mergeTradeskill = false
end
else
element.casting = nil
end
--[[ Callback: Castbar:PostCastStop(unit, name)
Called after the element has been updated when a spell cast has finished.
* self - the Castbar widget
* unit - unit for which the update has been triggered (string)
* name - name of the spell (string)
--]]
if(element.PostCastStop) then
return element:PostCastStop(unit, spellname)
end
end
local function UNIT_SPELLCAST_CHANNEL_START(self, event, unit)
if(self.unit ~= unit and self.realUnit ~= unit) then return end
local element = self.Castbar
local name, _, _, texture, startTime, endTime = UnitChannelInfo(unit)
if(not name) then
return
end
endTime = endTime / 1e3
startTime = startTime / 1e3
local max = (endTime - startTime)
local duration = endTime - GetTime()
element.duration = duration
element.max = max
element.delay = 0
element.startTime = startTime
element.endTime = endTime
element.extraTickRatio = 0
element.channeling = true
element.holdTime = 0
-- We have to do this, as it's possible for spell casts to never have _STOP
-- executed or be fully completed by the OnUpdate handler before CHANNEL_START
-- is called.
element.casting = nil
element.castName = nil
element:SetMinMaxValues(0, max)
element:SetValue(duration)
if(element.Text) then element.Text:SetText(name) end
if(element.Icon) then element.Icon:SetTexture(texture) end
if(element.Time) then element.Time:SetText() end
local sf = element.SafeZone
if(sf) then
sf:ClearAllPoints()
sf:SetPoint('LEFT')
sf:SetPoint('TOP')
sf:SetPoint('BOTTOM')
updateSafeZone(element)
end
--[[ Callback: Castbar:PostChannelStart(unit, name)
Called after the element has been updated upon a spell channel start.
* self - the Castbar widget
* unit - unit for which the update has been triggered (string)
* name - name of the channeled spell (string)
--]]
if(element.PostChannelStart) then
element:PostChannelStart(unit, name)
end
element:Show()
end
local function UNIT_SPELLCAST_CHANNEL_UPDATE(self, event, unit)
if(self.unit ~= unit and self.realUnit ~= unit) then return end
local element = self.Castbar
local name, _, _, _, startTime, endTime = UnitChannelInfo(unit)
if(not name or not element:IsShown()) then
return
end
local duration = (endTime / 1000) - GetTime()
element.delay = element.delay + element.duration - duration
element.duration = duration
element.max = (endTime - startTime) / 1000
element.startTime = startTime / 1000
element.endTime = endTime / 1000
element:SetMinMaxValues(0, element.max)
element:SetValue(duration)
--[[ Callback: Castbar:PostChannelUpdate(unit, name)
Called after the element has been updated after a channeled spell has been delayed or interrupted.
* self - the Castbar widget
* unit - unit for which the update has been triggered (string)
* name - name of the channeled spell (string)
--]]
if(element.PostChannelUpdate) then
return element:PostChannelUpdate(unit, name)
end
end
local function UNIT_SPELLCAST_CHANNEL_STOP(self, event, unit, spellname)
if(self.unit ~= unit and self.realUnit ~= unit) then return end
local element = self.Castbar
if(element:IsShown()) then
element.channeling = nil
--[[ Callback: Castbar:PostChannelUpdate(unit, name)
Called after the element has been updated after a channeled spell has been completed.
* self - the Castbar widget
* unit - unit for which the update has been triggered (string)
* name - name of the channeled spell (string)
--]]
if(element.PostChannelStop) then
return element:PostChannelStop(unit, spellname)
end
end
end
local function onUpdate(self, elapsed)
if(self.casting) then
local duration = self.duration + elapsed
if(duration >= self.max or (tradeskillTotal > 1 and duration >= (tradeskillCastDuration + tradeskillCastTime * 1.25))) then
self.casting = nil
self:Hide()
tradeskillTotal = 0
if(self.PostCastStop) then self:PostCastStop(self.__owner.unit) end
return
end
if(self.Time) then
if(self.delay ~= 0) then
if(self.CustomDelayText) then
self:CustomDelayText(duration)
else
self.Time:SetFormattedText('%.1f|cffff0000-%.1f|r', duration, self.delay)
end
else
if(self.CustomTimeText) then
self:CustomTimeText(duration)
else
self.Time:SetFormattedText('%.1f', duration)
end
end
end
self.duration = duration
self:SetValue(duration)
if(self.Spark) then
self.Spark:SetPoint('CENTER', self, 'LEFT', (duration / self.max) * self:GetWidth(), 0)
end
elseif(self.channeling) then
local duration = self.duration - elapsed
if(duration <= 0) then
self.channeling = nil
self:Hide()
if(self.PostChannelStop) then self:PostChannelStop(self.__owner.unit) end
return
end
if(self.Time) then
if(self.delay ~= 0) then
if(self.CustomDelayText) then
self:CustomDelayText(duration)
else
self.Time:SetFormattedText('%.1f|cffff0000-%.1f|r', duration, self.delay)
end
else
if(self.CustomTimeText) then
self:CustomTimeText(duration)
else
self.Time:SetFormattedText('%.1f', duration)
end
end
end
self.duration = duration
self:SetValue(duration)
if(self.Spark) then
self.Spark:SetPoint('CENTER', self, 'LEFT', (duration / self.max) * self:GetWidth(), 0)
end
elseif(self.holdTime > 0) then
self.holdTime = self.holdTime - elapsed
else
self.casting = nil
self.castName = nil
self.channeling = nil
tradeskillTotal = 0
self:Hide()
end
end
local function Update(self, ...)
UNIT_SPELLCAST_START(self, ...)
return UNIT_SPELLCAST_CHANNEL_START(self, ...)
end
local function ForceUpdate(element)
return Update(element.__owner, 'ForceUpdate', element.__owner.unit)
end
local function Enable(self, unit)
local element = self.Castbar
if(element) then
element.__owner = self
element.ForceUpdate = ForceUpdate
if(not (unit and unit:match('%wtarget$'))) then
self:RegisterEvent('UNIT_SPELLCAST_START', UNIT_SPELLCAST_START)
self:RegisterEvent('UNIT_SPELLCAST_FAILED', UNIT_SPELLCAST_FAILED)
self:RegisterEvent('UNIT_SPELLCAST_STOP', UNIT_SPELLCAST_STOP)
self:RegisterEvent('UNIT_SPELLCAST_INTERRUPTED', UNIT_SPELLCAST_INTERRUPTED)
self:RegisterEvent('UNIT_SPELLCAST_DELAYED', UNIT_SPELLCAST_DELAYED)
self:RegisterEvent('UNIT_SPELLCAST_CHANNEL_START', UNIT_SPELLCAST_CHANNEL_START)
self:RegisterEvent('UNIT_SPELLCAST_CHANNEL_UPDATE', UNIT_SPELLCAST_CHANNEL_UPDATE)
self:RegisterEvent('UNIT_SPELLCAST_CHANNEL_STOP', UNIT_SPELLCAST_CHANNEL_STOP)
self:RegisterEvent('UNIT_SPELLCAST_SENT', UNIT_SPELLCAST_SENT, true)
self:RegisterEvent('UNIT_SPELLCAST_FAILED_QUIET', UNIT_SPELLCAST_FAILED_QUIET)
end
element.holdTime = 0
element:SetScript('OnUpdate', element.OnUpdate or onUpdate)
if(self.unit == 'player') then
CastingBarFrame:UnregisterAllEvents()
CastingBarFrame.Show = CastingBarFrame.Hide
CastingBarFrame:Hide()
PetCastingBarFrame:UnregisterAllEvents()
PetCastingBarFrame.Show = PetCastingBarFrame.Hide
PetCastingBarFrame:Hide()
end
if(element:IsObjectType('StatusBar') and not element:GetStatusBarTexture()) then
element:SetStatusBarTexture([[Interface\TargetingFrame\UI-StatusBar]])
end
local spark = element.Spark
if(spark and spark:IsObjectType('Texture') and not spark:GetTexture()) then
spark:SetTexture([[Interface\CastingBar\UI-CastingBar-Spark]])
end
local safeZone = element.SafeZone
if(safeZone and safeZone:IsObjectType('Texture') and not safeZone:GetTexture()) then
safeZone:SetTexture(1, 0, 0)
end
element:Hide()
return true
end
end
local function Disable(self)
local element = self.Castbar
if(element) then
element:Hide()
self:UnregisterEvent('UNIT_SPELLCAST_START', UNIT_SPELLCAST_START)
self:UnregisterEvent('UNIT_SPELLCAST_FAILED', UNIT_SPELLCAST_FAILED)
self:UnregisterEvent('UNIT_SPELLCAST_STOP', UNIT_SPELLCAST_STOP)
self:UnregisterEvent('UNIT_SPELLCAST_INTERRUPTED', UNIT_SPELLCAST_INTERRUPTED)
self:UnregisterEvent('UNIT_SPELLCAST_DELAYED', UNIT_SPELLCAST_DELAYED)
self:UnregisterEvent('UNIT_SPELLCAST_CHANNEL_START', UNIT_SPELLCAST_CHANNEL_START)
self:UnregisterEvent('UNIT_SPELLCAST_CHANNEL_UPDATE', UNIT_SPELLCAST_CHANNEL_UPDATE)
self:UnregisterEvent('UNIT_SPELLCAST_CHANNEL_STOP', UNIT_SPELLCAST_CHANNEL_STOP)
self:UnregisterEvent('UNIT_SPELLCAST_SENT', UNIT_SPELLCAST_SENT)
self:UnregisterEvent('UNIT_SPELLCAST_FAILED_QUIET', UNIT_SPELLCAST_FAILED_QUIET)
element:SetScript('OnUpdate', nil)
end
end
hooksecurefunc('DoTradeSkill', function(_, num)
tradeskillCastTime = 0
tradeskillCastDuration = 0
tradeskillCurrent = 0
tradeskillTotal = num or 1
mergeTradeskill = true
end)
oUF:AddElement('Castbar', Update, Enable, Disable)
@@ -0,0 +1,102 @@
--[[
# Element: Combat Indicator
Toggles the visibility of an indicator based on the player's combat status.
## Widget
CombatIndicator - Any UI widget.
## Notes
A default texture will be applied if the widget is a Texture and doesn't have a texture or a color set.
## Examples
-- Position and size
local CombatIndicator = self:CreateTexture(nil, 'OVERLAY')
CombatIndicator:SetSize(16, 16)
CombatIndicator:SetPoint('TOP', self)
-- Register it with oUF
self.CombatIndicator = CombatIndicator
--]]
local ns = oUF
local oUF = ns.oUF
local UnitAffectingCombat = UnitAffectingCombat
local function Update(self, event)
local element = self.CombatIndicator
--[[ Callback: CombatIndicator:PreUpdate()
Called before the element has been updated.
* self - the CombatIndicator element
--]]
if(element.PreUpdate) then
element:PreUpdate()
end
local inCombat = UnitAffectingCombat('player')
if(inCombat) then
element:Show()
else
element:Hide()
end
--[[ Callback: CombatIndicator:PostUpdate(inCombat)
Called after the element has been updated.
* self - the CombatIndicator element
* inCombat - indicates if the player is affecting combat (boolean)
--]]
if(element.PostUpdate) then
return element:PostUpdate(inCombat)
end
end
local function Path(self, ...)
--[[ Override: CombatIndicator.Override(self, event)
Used to completely override the internal update function.
* self - the parent object
* event - the event triggering the update (string)
--]]
return (self.CombatIndicator.Override or Update) (self, ...)
end
local function ForceUpdate(element)
return Path(element.__owner, 'ForceUpdate')
end
local function Enable(self, unit)
local element = self.CombatIndicator
if(element and unit == 'player') then
element.__owner = self
element.ForceUpdate = ForceUpdate
self:RegisterEvent('PLAYER_REGEN_DISABLED', Path)
self:RegisterEvent('PLAYER_REGEN_ENABLED', Path)
if(element:IsObjectType('Texture') and not element:GetTexture()) then
element:SetTexture([[Interface\CharacterFrame\UI-StateIcon]])
element:SetTexCoord(.5, 1, 0, .49)
end
return true
end
end
local function Disable(self)
local element = self.CombatIndicator
if(element) then
element:Hide()
self:UnregisterEvent('PLAYER_REGEN_DISABLED', Path)
self:UnregisterEvent('PLAYER_REGEN_ENABLED', Path)
end
end
oUF:AddElement('CombatIndicator', Path, Enable, Disable)
@@ -0,0 +1,118 @@
--[[
# Element: ComboPoints
Handles the visibility and updating of the player's combo points.
## Widget
ComboPoints - An `table` consisting of as many Textures as the theoretical maximum return of [GetComboPoints](http://wowprogramming.com/docs/api/GetComboPoints).
## Notes
A default texture will be applied if the widget is a Texture and doesn't have a texture or a color set.
## Examples
local ComboPoints = {}
for index = 1, 10 do
local Bar = CreateFrame('StatusBar', nil, self)
-- Position and size.
Bar:SetSize(16, 16)
Bar:SetPoint('TOPLEFT', self, 'BOTTOMLEFT', (index - 1) * Bar:GetWidth(), 0)
ComboPoints[index] = Bar
end
-- Register with oUF
self.ComboPoints = ComboPoints
--]]
local ns = oUF
local oUF = ns.oUF
local GetComboPoints = GetComboPoints
local MAX_COMBO_POINTS = MAX_COMBO_POINTS
local function Update(self, event)
local element = self.ComboPoints
--[[ Callback: ComboPoints:PreUpdate()
Called before the element has been updated.
* self - the ComboPoints element
--]]
if(element.PreUpdate) then
element:PreUpdate()
end
local cp = GetComboPoints('player', 'target')
for i = 1, MAX_COMBO_POINTS do
if(i <= cp) then
element[i]:Show()
else
element[i]:Hide()
end
end
--[[ Callback: ComboPoints:PostUpdate(role)
Called after the element has been updated.
* self - the ComboPoints element
* cpoint - the current amount of combo points (number)
--]]
if(element.PostUpdate) then
return element:PostUpdate(cp)
end
end
local function Path(self, ...)
--[[ Override: ComboPoints.Override(self, event, ...)
Used to completely override the internal update function.
* self - the parent object
* event - the event triggering the update (string)
* ... - the arguments accompanying the event
--]]
return (self.ComboPoints.Override or Update) (self, ...)
end
local function ForceUpdate(element)
return Path(element.__owner, 'ForceUpdate', element.__owner.unit)
end
local function Enable(self)
local element = self.ComboPoints
if(element) then
element.__owner = self
element.ForceUpdate = ForceUpdate
self:RegisterEvent('PLAYER_COMBO_POINTS', Path, true)
self:RegisterEvent('PLAYER_TARGET_CHANGED', Path, true)
for index = 1, MAX_COMBO_POINTS do
local cp = element[index]
if(cp:IsObjectType('Texture') and not cp:GetTexture()) then
cp:SetTexture([[Interface\ComboFrame\ComboPoint]])
cp:SetTexCoord(0, 0.375, 0, 1)
end
end
return true
end
end
local function Disable(self)
local element = self.ComboPoints
if(element) then
for index = 1, MAX_COMBO_POINTS do
element[index]:Hide()
end
self:UnregisterEvent('PLAYER_COMBO_POINTS', Path)
self:UnregisterEvent('PLAYER_TARGET_CHANGED', Path)
end
end
oUF:AddElement('ComboPoints', Path, Enable, Disable)
@@ -0,0 +1,22 @@
<Ui xmlns="http://www.blizzard.com/wow/ui/">
<Script file="power.lua"/>
<!--<Script file="auras.lua"/>-->
<Script file="health.lua"/>
<!--<Script file="raidtargetindicator.lua"/>
<Script file="leaderindicator.lua"/>
<Script file="combatindicator.lua"/>
<Script file="restingindicator.lua"/>
<Script file="pvpindicator.lua"/>-->
<Script file="portrait.lua"/>
<!--
<Script file="range.lua"/>
<Script file="castbar.lua"/>-->
<Script file="tags.lua"/>
<!-- <Script file="masterlooterindicator.lua"/>
<Script file="assistantindicator.lua"/>
<Script file="readycheckindicator.lua"/>
<Script file="combopoints.lua"/>
<Script file="raidroleindicator.lua"/>
<Script file="happinessindicator.lua"/>
-->
</Ui>
@@ -0,0 +1,116 @@
--[[
# Element: HappinessIndicator
Handles the visibility and updating of player pet happiness.
## Widget
HappinessIndicator - A `Texture` used to display the current happiness level.
The element works by changing the texture's vertex color.
## Notes
A default texture will be applied if the widget is a Texture and doesn't have a texture or a color set.
## Examples
-- Position and size
local HappinessIndicator = self:CreateTexture(nil, 'OVERLAY')
HappinessIndicator:SetSize(16, 16)
HappinessIndicator:SetPoint('TOPRIGHT', self)
-- Register it with oUF
self.HappinessIndicator = HappinessIndicator
--]]
local ns = oUF
local oUF = ns.oUF
local GetPetHappiness = GetPetHappiness
local HasPetUI = HasPetUI
local function Update(self, event, unit)
if(not unit or self.unit ~= unit) then return end
local element = self.HappinessIndicator
--[[ Callback: HappinessIndicator:PreUpdate()
Called before the element has been updated.
* self - the ComboPoints element
--]]
if(element.PreUpdate) then
element:PreUpdate()
end
local _, hunterPet = HasPetUI()
local happiness, damagePercentage = GetPetHappiness()
if(hunterPet and happiness) then
if(happiness == 1) then
element:SetTexCoord(0.375, 0.5625, 0, 0.359375)
elseif(happiness == 2) then
element:SetTexCoord(0.1875, 0.375, 0, 0.359375)
elseif(happiness == 3) then
element:SetTexCoord(0, 0.1875, 0, 0.359375)
end
element:Show()
else
return element:Hide()
end
--[[ Callback: HappinessIndicator:PostUpdate(role)
Called after the element has been updated.
* self - the ComboPoints element
* unit - the unit for which the update has been triggered (string)
* happiness - the numerical happiness value of the pet (1 = unhappy, 2 = content, 3 = happy) (number)
* damagePercentage - damage modifier, happiness affects this (unhappy = 75%, content = 100%, happy = 125%) (number)
--]]
if(element.PostUpdate) then
return element:PostUpdate(unit, happiness, damagePercentage)
end
end
local function Path(self, ...)
--[[ Override: HappinessIndicator.Override(self, event, ...)
Used to completely override the internal update function.
* self - the parent object
* event - the event triggering the update (string)
* ... - the arguments accompanying the event
--]]
return (self.HappinessIndicator.Override or Update) (self, ...)
end
local function ForceUpdate(element)
return Path(element.__owner, 'ForceUpdate', element.__owner.unit)
end
local function Enable(self)
local element = self.HappinessIndicator
if(element) then
element.__owner = self
element.ForceUpdate = ForceUpdate
self:RegisterEvent('UNIT_HAPPINESS', Path)
if(element:IsObjectType('Texture') and not element:GetTexture()) then
element:SetTexture([[Interface\PetPaperDollFrame\UI-PetHappiness]])
end
return true
end
end
local function Disable(self)
local element = self.HappinessIndicator
if(element) then
element:Hide()
self:UnregisterEvent('UNIT_HAPPINESS', Path)
end
end
oUF:AddElement('HappinessIndicator', Path, Enable, Disable)
@@ -0,0 +1,292 @@
--[[
# Element: Health Bar
Handles the updating of a status bar that displays the unit's health.
## Widget
Health - A `StatusBar` used to represent the unit's health.
## Sub-Widgets
.bg - A `Texture` used as a background. It will inherit the color of the main StatusBar.
## Notes
A default texture will be applied if the widget is a StatusBar and doesn't have a texture set.
## Options
.frequentUpdates - Indicates whether to use OnUpdate script instead of UNIT_HEALTH to update the bar (boolean)
.smoothGradient - 9 color values to be used with the .colorSmooth option (table)
The following options are listed by priority. The first check that returns true decides the color of the bar.
.colorTapping - Use `self.colors.tapping` to color the bar if the unit isn't tapped by the player (boolean)
.colorDisconnected - Use `self.colors.disconnected` to color the bar if the unit is offline (boolean)
.colorHappiness - Use `self.colors.happiness` to color the bar if the unit is pet based on pet happiness (boolean)
.colorClass - Use `self.colors.class[class]` to color the bar based on unit class. `class` is defined by the
second return of [UnitClass](http://wowprogramming.com/docs/api/UnitClass) (boolean)
.colorClassNPC - Use `self.colors.class[class]` to color the bar if the unit is a NPC (boolean)
.colorClassPet - Use `self.colors.class[class]` to color the bar if the unit is player controlled, but not a player
(boolean)
.colorReaction - Use `self.colors.reaction[reaction]` to color the bar based on the player's reaction towards the
unit. `reaction` is defined by the return value of
[UnitReaction](http://wowprogramming.com/docs/api/UnitReaction) (boolean)
.colorSmooth - Use `smoothGradient` if present or `self.colors.smooth` to color the bar with a smooth gradient
based on the player's current health percentage (boolean)
.colorHealth - Use `self.colors.health` to color the bar. This flag is used to reset the bar color back to default
if none of the above conditions are met (boolean)
## Sub-Widgets Options
.multiplier - Used to tint the background based on the main widgets R, G and B values. Defaults to 1 (number)[0-1]
## Attributes
.disconnected - Indicates whether the unit is disconnected (boolean)
## Examples
-- Position and size
local Health = CreateFrame('StatusBar', nil, self)
Health:SetHeight(20)
Health:SetPoint('TOP')
Health:SetPoint('LEFT')
Health:SetPoint('RIGHT')
-- Add a background
local Background = Health:CreateTexture(nil, 'BACKGROUND')
Background:SetAllPoints(Health)
Background:SetTexture(1, 1, 1, .5)
-- Options
Health.frequentUpdates = true
Health.colorTapping = true
Health.colorDisconnected = true
Health.colorClass = true
Health.colorReaction = true
Health.colorHealth = true
-- Make the background darker.
Background.multiplier = .5
-- Register it with oUF
Health.bg = Background
self.Health = Health
--]]
local ns = oUF
local oUF = ns.oUF
local unpack = unpack
local GetPetHappiness = GetPetHappiness
local UnitClass = UnitClass
local UnitHealth = UnitHealth
local UnitHealthMax = UnitHealthMax
local UnitIsConnected = UnitIsConnected
local UnitIsPlayer = UnitIsPlayer
local UnitIsTapped = UnitIsTapped
local UnitIsTappedByPlayer = UnitIsTappedByPlayer
local UnitIsUnit = UnitIsUnit
local UnitPlayerControlled = UnitPlayerControlled
local UnitReaction = UnitReaction
local updateFrequentUpdates
local function UpdateColor(element, unit, cur, max)
local parent = element.__owner
if element.frequentUpdates ~= element.__frequentUpdates then
element.__frequentUpdates = element.frequentUpdates
updateFrequentUpdates(parent, unit)
end
local r, g, b, t
if(element.colorTapping and not UnitPlayerControlled(unit) and (UnitIsTapped(unit) and not UnitIsTappedByPlayer(unit))) then
t = parent.colors.tapped
elseif(element.colorDisconnected and element.disconnected) then
t = parent.colors.disconnected
elseif(element.colorHappiness and UnitIsUnit(unit, 'pet') and GetPetHappiness()) then
t = parent.colors.happiness[GetPetHappiness()]
elseif(element.colorClass and UnitIsPlayer(unit)) or
(element.colorClassNPC and not UnitIsPlayer(unit)) or
(element.colorClassPet and UnitPlayerControlled(unit) and not UnitIsPlayer(unit)) then
local _, class = UnitClass(unit)
t = parent.colors.class[class]
elseif(element.colorReaction and UnitReaction(unit, 'player')) then
t = parent.colors.reaction[UnitReaction(unit, 'player')]
elseif(element.colorSmooth) then
r, g, b = parent.ColorGradient(cur, max, unpack(element.smoothGradient or parent.colors.smooth))
elseif(element.colorHealth) then
t = parent.colors.health
end
if(t) then
r, g, b = t[1], t[2], t[3]
end
if(r or g or b) then
element:SetStatusBarColor(r, g, b)
local bg = element.bg
if(bg) then
local mu = bg.multiplier or 1
bg:SetVertexColor(r * mu, g * mu, b * mu)
end
end
end
local function Update(self, event, unit)
if(not unit or self.unit ~= unit) then return end
local element = self.Health
--[[ Callback: Health:PreUpdate(unit)
Called before the element has been updated.
* self - the Health element
* unit - the unit for which the update has been triggered (string)
--]]
if(element.PreUpdate) then
element:PreUpdate(unit)
end
local cur, max = UnitHealth(unit), UnitHealthMax(unit)
local disconnected = not UnitIsConnected(unit)
element:SetMinMaxValues(0, max)
if(disconnected) then
element:SetValue(max)
else
element:SetValue(cur)
end
element.disconnected = disconnected
--[[ Override: Health:UpdateColor(unit, cur, max)
Used to completely override the internal function for updating the widgets' colors.
* self - the Health element
* unit - the unit for which the update has been triggered (string)
* cur - the unit's current health value (number)
* max - the unit's maximum possible health value (number)
--]]
element:UpdateColor(unit, cur, max)
--[[ Callback: Health:PostUpdate(unit, cur, max)
Called after the element has been updated.
* self - the Health element
* unit - the unit for which the update has been triggered (string)
* cur - the unit's current health value (number)
* max - the unit's maximum possible health value (number)
--]]
if(element.PostUpdate) then
return element:PostUpdate(unit, cur, max)
end
end
local function Path(self, ...)
--[[ Override: Health.Override(self, event, unit)
Used to completely override the internal update function.
* self - the parent object
* event - the event triggering the update (string)
* unit - the unit accompanying the event (string)
--]]
return (self.Health.Override or Update) (self, unpack(arg))
end
local function ForceUpdate(element)
return Path(element.__owner, 'ForceUpdate', element.__owner.unit)
end
local function onHealthUpdate()
if(this.disconnected) then return end
local unit = this.__owner.unit
local health = UnitHealth(unit)
if(health ~= this.min) then
this.min = health
return Path(this.__owner, 'OnHealthUpdate', unit)
end
end
function updateFrequentUpdates(self, unit)
if(not unit or string.match(unit, '%w+target$')) then return end
local element = self.Health
if(element.frequentUpdates and not element:GetScript('OnUpdate')) then
element:SetScript('OnUpdate', onHealthUpdate)
if((unit == 'party' or string.match(unit, 'party%d?$'))) then
self:RegisterEvent('UNIT_HEALTH', Path)
elseif(self:IsEventRegistered("UNIT_HEALTH")) then
self:UnregisterEvent('UNIT_HEALTH', Path)
end
elseif(not element.frequentUpdates and element:GetScript('OnUpdate')) then
element:SetScript('OnUpdate', nil)
self:RegisterEvent('UNIT_HEALTH', Path)
end
end
local function Enable(self, unit)
local element = self.Health
if(element) then
element.__owner = self
element.ForceUpdate = ForceUpdate
element.__frequentUpdates = element.frequentUpdates
updateFrequentUpdates(self, unit)
if(element.frequentUpdates and (unit and not string.match(unit, '%w+target$'))) then
element:SetScript('OnUpdate', onHealthUpdate)
-- The party frames need this to handle disconnect states correctly.
if(unit == 'party') then
self:RegisterEvent('UNIT_HEALTH', Path)
end
else
self:RegisterEvent('UNIT_HEALTH', Path)
end
self:RegisterEvent('UNIT_MAXHEALTH', Path)
self:RegisterEvent('UNIT_CONNECTION', Path)
self:RegisterEvent('UNIT_FACTION', Path) -- For tapping
self:RegisterEvent('UNIT_HAPPINESS', Path)
if(element:IsObjectType('StatusBar') and not element:GetStatusBarTexture()) then
element:SetStatusBarTexture([[Interface\TargetingFrame\UI-StatusBar]])
end
if(not element.UpdateColor) then
element.UpdateColor = UpdateColor
end
element:Show()
return true
end
end
local function Disable(self)
local element = self.Health
if(element) then
if(element:GetScript('OnUpdate')) then
element:SetScript('OnUpdate', nil)
end
element:Hide()
self:UnregisterEvent('UNIT_HEALTH', Path)
self:UnregisterEvent('UNIT_MAXHEALTH', Path)
self:UnregisterEvent('UNIT_CONNECTION', Path)
self:UnregisterEvent('UNIT_FACTION', Path)
self:UnregisterEvent('UNIT_HAPPINESS', Path)
end
end
oUF:AddElement('Health', Path, Enable, Disable)
@@ -0,0 +1,107 @@
--[[
# Element: Leader Indicator
Toggles the visibility of an indicator based on the unit's leader status.
## Widget
LeaderIndicator - Any UI widget.
## Notes
A default texture will be applied if the widget is a Texture and doesn't have a texture or a color set.
## Examples
-- Position and size
local LeaderIndicator = self:CreateTexture(nil, 'OVERLAY')
LeaderIndicator:SetSize(16, 16)
LeaderIndicator:SetPoint('BOTTOM', self, 'TOP')
-- Register it with oUF
self.LeaderIndicator = LeaderIndicator
--]]
local ns = oUF
local oUF = ns.oUF
local UnitInParty = UnitInParty
local UnitInRaid = UnitInRaid
local UnitIsPartyLeader = UnitIsPartyLeader
local function Update(self, event)
local element = self.LeaderIndicator
--[[ Callback: LeaderIndicator:PreUpdate()
Called before the element has been updated.
* self - the LeaderIndicator element
--]]
if(element.PreUpdate) then
element:PreUpdate()
end
local unit = self.unit
local isLeader = (UnitInParty(unit) or UnitInRaid(unit)) and UnitIsPartyLeader(unit)
if(isLeader) then
element:Show()
else
element:Hide()
end
--[[ Callback: LeaderIndicator:PostUpdate(isLeader)
Called after the element has been updated.
* self - the LeaderIndicator element
* isLeader - indicates whether the element is shown (boolean)
--]]
if(element.PostUpdate) then
return element:PostUpdate(isLeader)
end
end
local function Path(self, ...)
--[[ Override: LeaderIndicator.Override(self, event, ...)
Used to completely override the internal update function.
* self - the parent object
* event - the event triggering the update (string)
* ... - the arguments accompanying the event
--]]
return (self.LeaderIndicator.Override or Update) (self, ...)
end
local function ForceUpdate(element)
return Path(element.__owner, 'ForceUpdate')
end
local function Enable(self)
local element = self.LeaderIndicator
if(element) then
element.__owner = self
element.ForceUpdate = ForceUpdate
self:RegisterEvent('PARTY_LEADER_CHANGED', Path)
self:RegisterEvent('PARTY_MEMBERS_CHANGED', Path)
self:RegisterEvent('RAID_ROSTER_UPDATE', Path)
if(element:IsObjectType('Texture') and not element:GetTexture()) then
element:SetTexture([[Interface\GroupFrame\UI-Group-LeaderIcon]])
end
return true
end
end
local function Disable(self)
local element = self.LeaderIndicator
if(element) then
element:Hide()
self:UnregisterEvent('PARTY_LEADER_CHANGED', Path)
self:UnregisterEvent('PARTY_MEMBERS_CHANGED', Path)
self:UnregisterEvent('RAID_ROSTER_UPDATE', Path)
end
end
oUF:AddElement('LeaderIndicator', Path, Enable, Disable)
@@ -0,0 +1,126 @@
--[[
# Element: Master Looter Indicator
Toggles the visibility of an indicator based on the unit's master looter status.
## Widget
MasterLooterIndicator - Any UI widget.
## Notes
A default texture will be applied if the widget is a Texture and doesn't have a texture or a color set.
## Examples
-- Position and size
local MasterLooterIndicator = self:CreateTexture(nil, 'OVERLAY')
MasterLooterIndicator:SetSize(16, 16)
MasterLooterIndicator:SetPoint('TOPRIGHT', self)
-- Register it with oUF
self.MasterLooterIndicator = MasterLooterIndicator
--]]
local ns = oUF
local oUF = ns.oUF
local GetLootMethod = GetLootMethod
local UnitInParty = UnitInParty
local UnitInRaid = UnitInRaid
local UnitIsUnit = UnitIsUnit
local function Update(self, event)
local unit = self.unit
local element = self.MasterLooterIndicator
--[[ Callback: MasterLooterIndicator:PreUpdate()
Called before the element has been updated.
* self - the MasterLooterIndicator element
--]]
if(element.PreUpdate) then
element:PreUpdate()
end
local isShown = false
if(UnitInParty(unit) or UnitInRaid(unit)) then
local method, partyIndex, raidIndex = GetLootMethod()
if(method == 'master') then
local mlUnit
if(partyIndex) then
if(partyIndex == 0) then
mlUnit = 'player'
else
mlUnit = 'party' .. partyIndex
end
elseif(raidIndex) then
mlUnit = 'raid' .. raidIndex
end
if(mlUnit and UnitIsUnit(unit, mlUnit)) then
isShown = true
end
end
end
if isShown then
element:Show()
else
element:Hide()
end
--[[ Callback: MasterLooterIndicator:PostUpdate(isShown)
Called after the element has been updated.
* self - the MasterLooterIndicator element
* isShown - indicates whether the element is shown (boolean)
--]]
if(element.PostUpdate) then
return element:PostUpdate(isShown)
end
end
local function Path(self, ...)
--[[ Override: MasterLooterIndicator.Override(self, event, ...)
Used to completely override the internal update function.
* self - the parent object
* event - the event triggering the update (string)
* ... - the arguments accompanying the event
--]]
return (self.MasterLooterIndicator.Override or Update) (self, ...)
end
local function ForceUpdate(element)
return Path(element.__owner, 'ForceUpdate')
end
local function Enable(self, unit)
local element = self.MasterLooterIndicator
if(element) then
element.__owner = self
element.ForceUpdate = ForceUpdate
self:RegisterEvent('PARTY_LOOT_METHOD_CHANGED', Path, true)
self:RegisterEvent('PARTY_MEMBERS_CHANGED', Path, true)
if(element:IsObjectType('Texture') and not element:GetTexture()) then
element:SetTexture([[Interface\GroupFrame\UI-Group-MasterLooter]])
end
return true
end
end
local function Disable(self)
local element = self.MasterLooterIndicator
if(element) then
element:Hide()
self:UnregisterEvent('PARTY_LOOT_METHOD_CHANGED', Path)
self:UnregisterEvent('PARTY_MEMBERS_CHANGED', Path)
end
end
oUF:AddElement('MasterLooterIndicator', Path, Enable, Disable)
@@ -0,0 +1,148 @@
--[[
# Element: Portraits
Handles the updating of the unit's portrait.
## Widget
Portrait - A `PlayerModel` or a `Texture` used to represent the unit's portrait.
## Notes
A question mark model will be used if the widget is a PlayerModel and the client doesn't have the model information for
the unit.
## Examples
-- 3D Portrait
-- Position and size
local Portrait = CreateFrame('PlayerModel', nil, self)
Portrait:SetSize(32, 32)
Portrait:SetPoint('RIGHT', self, 'LEFT')
-- Register it with oUF
self.Portrait = Portrait
-- 2D Portrait
local Portrait = self:CreateTexture(nil, 'OVERLAY')
Portrait:SetSize(32, 32)
Portrait:SetPoint('RIGHT', self, 'LEFT')
-- Register it with oUF
self.Portrait = Portrait
--]]
local ns = oUF
local oUF = ns.oUF
local SetPortraitTexture = SetPortraitTexture
local UnitExists = UnitExists
local UnitName = UnitName
local UnitIsConnected = UnitIsConnected
local UnitIsUnit = UnitIsUnit
local UnitIsVisible = UnitIsVisible
local function Update(self, event, unit)
if(not unit or not UnitIsUnit(self.unit, unit)) then return end
local element = self.Portrait
--[[ Callback: Portrait:PreUpdate(unit)
Called before the element has been updated.
* self - the Portrait element
* unit - the unit for which the update has been triggered (string)
--]]
if(element.PreUpdate) then element:PreUpdate(unit) end
local name = UnitName(unit)
local isAvailable = UnitIsConnected(unit) and UnitIsVisible(unit)
if(event ~= 'OnUpdate' or element.name ~= name or element.state ~= isAvailable) then
if(element:IsObjectType('PlayerModel')) then
if(not isAvailable) then
element:SetModelScale(4.25)
element:SetCamera(0)
element:SetPosition(0, 0, -1.5)
element:SetModel([[Interface\Buttons\TalkToMeQuestionMark.m2]])
elseif(element.name ~= name or event == 'UNIT_MODEL_CHANGED') then
element:ClearModel()
element:SetUnit(unit)
element:SetModelScale(1)
element:SetCamera(0)
element:SetPosition(0, 0, 0)
else
element:SetCamera(0)
end
else
SetPortraitTexture(element, unit)
end
element.name = name
element.state = isAvailable
end
--[[ Callback: Portrait:PostUpdate(unit)
Called after the element has been updated.
* self - the Portrait element
* unit - the unit for which the update has been triggered (string)
--]]
if(element.PostUpdate) then
return element:PostUpdate(unit)
end
end
local function Path(self, ...)
--[[ Override: Portrait.Override(self, event, unit)
Used to completely override the internal update function.
* self - the parent object
* event - the event triggering the update (string)
* unit - the unit accompanying the event (string)
--]]
return (self.Portrait.Override or Update) (self, unpack(arg))
end
local function ForceUpdate(element)
return Path(element.__owner, 'ForceUpdate', element.__owner.unit)
end
local function Enable(self, unit)
local element = self.Portrait
if(element) then
element.__owner = self
element.ForceUpdate = ForceUpdate
self:RegisterEvent('UNIT_PORTRAIT_UPDATE', Path)
self:RegisterEvent('UNIT_MODEL_CHANGED', Path)
self:RegisterEvent('UNIT_CONNECTION', Path)
-- The quest log uses PARTY_MEMBER_{ENABLE,DISABLE} to handle updating of
-- party members overlapping quests. This will probably be enough to handle
-- model updating.
--
-- DISABLE isn't used as it fires when we most likely don't have the
-- information we want.
if(unit == 'party') then
self:RegisterEvent('PARTY_MEMBER_ENABLE', Path)
end
element:Show()
return true
end
end
local function Disable(self)
local element = self.Portrait
if(element) then
element:Hide()
self:UnregisterEvent('UNIT_PORTRAIT_UPDATE', Path)
self:UnregisterEvent('UNIT_MODEL_CHANGED', Path)
self:UnregisterEvent('PARTY_MEMBER_ENABLE', Path)
self:UnregisterEvent('UNIT_CONNECTION', Path)
end
end
oUF:AddElement('Portrait', Path, Enable, Disable)
@@ -0,0 +1,326 @@
--[[
# Element: Power Bar
Handles the updating of a status bar that displays the unit's power.
## Widget
Power - A `StatusBar` used to represent the unit's power.
## Sub-Widgets
.bg - A `Texture` used as a background. It will inherit the color of the main StatusBar.
## Notes
A default texture will be applied if the widget is a StatusBar and doesn't have a texture or a color set.
## Options
.frequentUpdates - Indicates whether to use OnUpdate script instead of UNIT_POWER to update the bar. Only valid for the
player and pet units (boolean)
.smoothGradient - 9 color values to be used with the .colorSmooth option (table)
The following options are listed by priority. The first check that returns true decides the color of the bar.
.colorTapping - Use `self.colors.tapping` to color the bar if the unit isn't tapped by the player (boolean)
.colorDisconnected - Use `self.colors.disconnected` to color the bar if the unit is offline (boolean)
.colorHappiness - Use `self.colors.happiness` to color the bar if the unit is pet based on pet happiness (boolean)
.colorPower - Use `self.colors.power[token]` to color the bar based on the unit's power type. This method will
fall-back to `:GetAlternativeColor()` if it can't find a color matching the token. If this function
isn't defined, then it will attempt to color based upon the alternative power colors returned by
[UnitPowerType](http://wowprogramming.com/docs/api/UnitPowerType). Finally, if these aren't
defined, then it will attempt to color the bar based upon `self.colors.power[type]` (boolean)
.colorClass - Use `self.colors.class[class]` to color the bar based on unit class. `class` is defined by the
second return of [UnitClass](http://wowprogramming.com/docs/api/UnitClass) (boolean)
.colorClassNPC - Use `self.colors.class[class]` to color the bar if the unit is a NPC (boolean)
.colorClassPet - Use `self.colors.class[class]` to color the bar if the unit is player controlled, but not a player
(boolean)
.colorReaction - Use `self.colors.reaction[reaction]` to color the bar based on the player's reaction towards the
unit. `reaction` is defined by the return value of
[UnitReaction](http://wowprogramming.com/docs/api/UnitReaction) (boolean)
.colorSmooth - Use `smoothGradient` if present or `self.colors.smooth` to color the bar with a smooth gradient
based on the player's current power percentage (boolean)
## Sub-Widget Options
.multiplier - A multiplier used to tint the background based on the main widgets R, G and B values. Defaults to 1
(number)[0-1]
## Attributes
.disconnected - Indicates whether the unit is disconnected (boolean)
.tapped - Indicates whether the unit is tapped by the player (boolean)
## Examples
-- Position and size
local Power = CreateFrame('StatusBar', nil, self)
Power:SetHeight(20)
Power:SetPoint('BOTTOM')
Power:SetPoint('LEFT')
Power:SetPoint('RIGHT')
-- Add a background
local Background = Power:CreateTexture(nil, 'BACKGROUND')
Background:SetAllPoints(Power)
Background:SetTexture(1, 1, 1, .5)
-- Options
Power.frequentUpdates = true
Power.colorTapping = true
Power.colorDisconnected = true
Power.colorPower = true
Power.colorClass = true
Power.colorReaction = true
-- Make the background darker.
Background.multiplier = .5
-- Register it with oUF
Power.bg = Background
self.Power = Power
--]]
local ns = oUF
local oUF = ns.oUF
local unpack = unpack
local GetPetHappiness = GetPetHappiness
local UnitClass = UnitClass
local UnitIsConnected = UnitIsConnected
local UnitIsPlayer = UnitIsPlayer
local UnitIsTapped = UnitIsTapped
local UnitIsTappedByPlayer = UnitIsTappedByPlayer
local UnitIsUnit = UnitIsUnit
local UnitMana = UnitMana
local UnitManaMax = UnitManaMax
local UnitPlayerControlled = UnitPlayerControlled
local UnitPowerType = UnitPowerType
local UnitReaction = UnitReaction
local updateFrequentUpdates
local function UpdateColor(element, unit, cur, min, max)
local parent = element.__owner
if element.frequentUpdates ~= element.__frequentUpdates then
element.__frequentUpdates = element.frequentUpdates
updateFrequentUpdates(element, unit)
end
local ptype = UnitPowerType(unit)
local r, g, b, t
if(element.colorTapping and element.tapped) then
t = parent.colors.tapped
elseif(element.colorDisconnected and element.disconnected) then
t = parent.colors.disconnected
elseif(element.colorHappiness and UnitIsUnit(unit, 'pet') and GetPetHappiness()) then
t = parent.colors.happiness[GetPetHappiness()]
elseif(element.colorPower) then
t = parent.colors.power[ptype]
elseif(element.colorClass and UnitIsPlayer(unit)) or
(element.colorClassNPC and not UnitIsPlayer(unit)) or
(element.colorClassPet and UnitPlayerControlled(unit) and not UnitIsPlayer(unit)) then
local _, class = UnitClass(unit)
t = parent.colors.class[class]
elseif(element.colorReaction and UnitReaction(unit, 'player')) then
t = parent.colors.reaction[UnitReaction(unit, 'player')]
elseif(element.colorSmooth) then
local adjust = 0 - (min or 0)
r, g, b = parent.ColorGradient(cur + adjust, max + adjust, unpack(element.smoothGradient or parent.colors.smooth))
end
if(t) then
r, g, b = t[1], t[2], t[3]
end
t = parent.colors.power[ptype]
element:SetStatusBarTexture(element.texture)
if(r or g or b) then
element:SetStatusBarColor(r, g, b)
end
local bg = element.bg
if(bg and b) then
local mu = bg.multiplier or 1
bg:SetVertexColor(r * mu, g * mu, b * mu)
end
end
local function Update(self, event, unit)
if(self.unit ~= unit) then return end
local element = self.Power
--[[ Callback: Power:PreUpdate(unit)
Called before the element has been updated.
* self - the Power element
* unit - the unit for which the update has been triggered (string)
--]]
if(element.PreUpdate) then
element:PreUpdate(unit)
end
local cur, max = UnitMana(unit), UnitManaMax(unit)
local disconnected = not UnitIsConnected(unit)
local tapped = not UnitPlayerControlled(unit) and (UnitIsTapped(unit) and not UnitIsTappedByPlayer(unit))
element:SetMinMaxValues(0, max)
if(disconnected) then
element:SetValue(max)
else
element:SetValue(cur)
end
element.disconnected = disconnected
element.tapped = tapped
--[[ Override: Power:UpdateColor(unit, cur, max)
Used to completely override the internal function for updating the widget's colors.
* self - the Power element
* unit - the unit for which the update has been triggered (string)
* cur - the unit's current power value (number)
* max - the unit's maximum possible power value (number)
--]]
element:UpdateColor(unit, cur, max)
--[[ Callback: Power:PostUpdate(unit, cur, max)
Called after the element has been updated.
* self - the Power element
* unit - the unit for which the update has been triggered (string)
* cur - the unit's current power value (number)
* max - the unit's maximum possible power value (number)
--]]
if(element.PostUpdate) then
return element:PostUpdate(unit, cur, max)
end
end
local function Path(self, ...)
--[[ Override: Power.Override(self, event, unit, ...)
Used to completely override the internal update function.
* self - the parent object
* event - the event triggering the update (string)
* unit - the unit accompanying the event (string)
* ... - the arguments accompanying the event
--]]
return (self.Power.Override or Update) (self, unpack(arg))
end
local function ForceUpdate(element)
return Path(element.__owner, 'ForceUpdate', element.__owner.unit)
end
local function onPowerUpdate()
if(this.disconnected) then return end
local unit = this.__owner.unit
local power = UnitMana(unit)
if(power ~= this.min) then
this.min = power
return Path(this.__owner, 'OnPowerUpdate', unit)
end
end
function updateFrequentUpdates(self, unit)
if(not unit or (unit ~= 'player' and unit ~= 'pet')) then return end
local element = self.Power
if(element.frequentUpdates and not element:GetScript('OnUpdate')) then
element:SetScript('OnUpdate', onPowerUpdate)
self:UnregisterEvent('UNIT_MANA', Path)
self:UnregisterEvent('UNIT_RAGE', Path)
self:UnregisterEvent('UNIT_FOCUS', Path)
self:UnregisterEvent('UNIT_ENERGY', Path)
elseif(not element.frequentUpdates and element:GetScript('OnUpdate')) then
element:SetScript('OnUpdate', nil)
self:RegisterEvent('UNIT_MANA', Path)
self:RegisterEvent('UNIT_RAGE', Path)
self:RegisterEvent('UNIT_FOCUS', Path)
self:RegisterEvent('UNIT_ENERGY', Path)
end
end
local function Enable(self, unit)
local element = self.Power
if(element) then
element.__owner = self
element.ForceUpdate = ForceUpdate
element.__frequentUpdates = element.frequentUpdates
updateFrequentUpdates(self, unit)
if(element.frequentUpdates and (unit == 'player' or unit == 'pet')) then
element:SetScript('OnUpdate', onPowerUpdate)
else
self:RegisterEvent('UNIT_MANA', Path)
self:RegisterEvent('UNIT_RAGE', Path)
self:RegisterEvent('UNIT_FOCUS', Path)
self:RegisterEvent('UNIT_ENERGY', Path)
end
self:RegisterEvent('UNIT_MAXMANA', Path)
self:RegisterEvent('UNIT_MAXRAGE', Path)
self:RegisterEvent('UNIT_MAXFOCUS', Path)
self:RegisterEvent('UNIT_MAXENERGY', Path)
self:RegisterEvent('UNIT_MAXRUNIC_POWER', Path)
self:RegisterEvent('UNIT_DISPLAYPOWER', Path)
self:RegisterEvent('UNIT_CONNECTION', Path)
self:RegisterEvent('UNIT_HAPPINESS', Path)
self:RegisterEvent('UNIT_FACTION', Path) -- For tapping
if(element:IsObjectType('StatusBar')) then
element.texture = element:GetStatusBarTexture() and element:GetStatusBarTexture():GetTexture() or [[Interface\TargetingFrame\UI-StatusBar]]
element:SetStatusBarTexture(element.texture)
end
if(not element.UpdateColor) then
element.UpdateColor = UpdateColor
end
element:Show()
return true
end
end
local function Disable(self)
local element = self.Power
if(element) then
element:Hide()
if(element:GetScript('OnUpdate')) then
element:SetScript('OnUpdate', nil)
else
self:UnregisterEvent('UNIT_MANA', Path)
self:UnregisterEvent('UNIT_RAGE', Path)
self:UnregisterEvent('UNIT_FOCUS', Path)
self:UnregisterEvent('UNIT_ENERGY', Path)
self:UnregisterEvent('UNIT_RUNIC_POWER', Path)
end
self:UnregisterEvent('UNIT_MAXMANA', Path)
self:UnregisterEvent('UNIT_MAXRAGE', Path)
self:UnregisterEvent('UNIT_MAXFOCUS', Path)
self:UnregisterEvent('UNIT_MAXENERGY', Path)
self:UnregisterEvent('UNIT_MAXRUNIC_POWER', Path)
self:UnregisterEvent('UNIT_DISPLAYPOWER', Path)
self:UnregisterEvent('UNIT_CONNECTION', Path)
self:UnregisterEvent('UNIT_HAPPINESS', Path)
self:UnregisterEvent('UNIT_FACTION', Path)
end
end
oUF:AddElement('Power', Path, Enable, Disable)
@@ -0,0 +1,118 @@
--[[
# Element: PvP Icon
Handles the visibility and updating of an indicator based on the unit's PvP status.
## Widget
PvPIndicator - A `Texture` used to display faction, FFA PvP status icon.
## Notes
This element updates by changing the texture.
## Examples
-- Position and size
local PvPIndicator = self:CreateTexture(nil, 'ARTWORK', nil, 1)
PvPIndicator:SetSize(30, 30)
PvPIndicator:SetPoint('RIGHT', self, 'LEFT')
-- Register it with oUF
self.PvPIndicator = PvPIndicator
--]]
local ns = oUF
local oUF = ns.oUF
local UnitFactionGroup = UnitFactionGroup
local UnitIsPVP = UnitIsPVP
local UnitIsPVPFreeForAll = UnitIsPVPFreeForAll
local FFA_ICON = [[Interface\TargetingFrame\UI-PVP-FFA]]
local FACTION_ICON = [[Interface\TargetingFrame\UI-PVP-]]
local function Update(self, event, unit)
if(unit ~= self.unit) then return end
local element = self.PvPIndicator
--[[ Callback: PvPIndicator:PreUpdate(unit)
Called before the element has been updated.
* self - the PvPIndicator element
* unit - the unit for which the update has been triggered (string)
--]]
if(element.PreUpdate) then
element:PreUpdate(unit)
end
local status
local factionGroup = UnitFactionGroup(unit)
if(UnitIsPVPFreeForAll(unit)) then
element:SetTexture(FFA_ICON)
element:SetTexCoord(0, 0.65625, 0, 0.65625)
status = 'ffa'
elseif(factionGroup and factionGroup ~= 'Neutral' and UnitIsPVP(unit)) then
element:SetTexture(FACTION_ICON .. factionGroup)
element:SetTexCoord(0, 0.65625, 0, 0.65625)
status = factionGroup
end
if(status) then
element:Show()
else
element:Hide()
end
--[[ Callback: PvPIndicator:PostUpdate(unit, status)
Called after the element has been updated.
* self - the PvPIndicator element
* unit - the unit for which the update has been triggered (string)
* status - the unit's current PvP status or faction accounting for mercenary mode (string)['ffa', 'Alliance',
'Horde']
--]]
if(element.PostUpdate) then
return element:PostUpdate(unit, status)
end
end
local function Path(self, ...)
--[[Override: PvPIndicator.Override(self, event, ...)
Used to completely override the internal update function.
* self - the parent object
* event - the event triggering the update (string)
* ... - the arguments accompanying the event
--]]
return (self.PvPIndicator.Override or Update) (self, ...)
end
local function ForceUpdate(element)
return Path(element.__owner, 'ForceUpdate', element.__owner.unit)
end
local function Enable(self)
local element = self.PvPIndicator
if(element) then
element.__owner = self
element.ForceUpdate = ForceUpdate
self:RegisterEvent('UNIT_FACTION', Path)
return true
end
end
local function Disable(self)
local element = self.PvPIndicator
if(element) then
element:Hide()
self:UnregisterEvent('UNIT_FACTION', Path)
end
end
oUF:AddElement('PvPIndicator', Path, Enable, Disable)
@@ -0,0 +1,114 @@
--[[
# Element: Raid Role Indicator
Handles the visibility and updating of an indicator based on the unit's raid assignment (main tank or main assist).
## Widget
RaidRoleIndicator - A `Texture` representing the unit's raid assignment.
## Notes
This element updates by changing the texture.
## Examples
-- Position and size
local RaidRoleIndicator = self:CreateTexture(nil, 'OVERLAY')
RaidRoleIndicator:SetSize(16, 16)
RaidRoleIndicator:SetPoint('TOPLEFT')
-- Register it with oUF
self.RaidRoleIndicator = RaidRoleIndicator
--]]
local ns = oUF
local oUF = ns.oUF
local GetPartyAssignment = GetPartyAssignment
local UnitInRaid = UnitInRaid
local MAINTANK_ICON = [[Interface\GROUPFRAME\UI-GROUP-MAINTANKICON]]
local MAINASSIST_ICON = [[Interface\GROUPFRAME\UI-GROUP-MAINASSISTICON]]
local function Update(self, event)
local unit = self.unit
local element = self.RaidRoleIndicator
--[[ Callback: RaidRoleIndicator:PreUpdate()
Called before the element has been updated.
* self - the RaidRoleIndicator element
--]]
if(element.PreUpdate) then
element:PreUpdate()
end
local role, isShown
if(UnitInRaid(unit)) then
if(GetPartyAssignment('MAINTANK', unit)) then
isShown = true
element:SetTexture(MAINTANK_ICON)
role = 'MAINTANK'
elseif(GetPartyAssignment('MAINASSIST', unit)) then
isShown = true
element:SetTexture(MAINASSIST_ICON)
role = 'MAINASSIST'
end
end
if isShown then
element:Show()
else
element:Hide()
end
--[[ Callback: RaidRoleIndicator:PostUpdate(role)
Called after the element has been updated.
* self - the RaidRoleIndicator element
* role - the unit's raid assignment (string?)['MAINTANK', 'MAINASSIST']
--]]
if(element.PostUpdate) then
return element:PostUpdate(role)
end
end
local function Path(self, ...)
--[[ Override: RaidRoleIndicator.Override(self, event, ...)
Used to completely override the internal update function.
* self - the parent object
* event - the event triggering the update (string)
* ... - the arguments accompanying the event
--]]
return (self.RaidRoleIndicator.Override or Update)(self, ...)
end
local function ForceUpdate(element)
return Path(element.__owner, 'ForceUpdate')
end
local function Enable(self)
local element = self.RaidRoleIndicator
if(element) then
element.__owner = self
element.ForceUpdate = ForceUpdate
self:RegisterEvent('PARTY_MEMBERS_CHANGED', Path, true)
return true
end
end
local function Disable(self)
local element = self.RaidRoleIndicator
if(element) then
element:Hide()
self:UnregisterEvent('PARTY_MEMBERS_CHANGED', Path)
end
end
oUF:AddElement('RaidRoleIndicator', Path, Enable, Disable)
@@ -0,0 +1,102 @@
--[[
# Element: Raid Target Indicator
Handles the visibility and updating of an indicator based on the unit's raid target assignment.
## Widget
RaidTargetIndicator - A `Texture` used to display the raid target icon.
## Notes
A default texture will be applied if the widget is a Texture and doesn't have a texture set.
## Examples
-- Position and size
local RaidTargetIndicator = self:CreateTexture(nil, 'OVERLAY')
RaidTargetIndicator:SetSize(16, 16)
RaidTargetIndicator:SetPoint('TOPRIGHT', self)
-- Register it with oUF
self.RaidTargetIndicator = RaidTargetIndicator
--]]
local ns = oUF
local oUF = ns.oUF
local GetRaidTargetIndex = GetRaidTargetIndex
local SetRaidTargetIconTexture = SetRaidTargetIconTexture
local function Update(self, event)
local element = self.RaidTargetIndicator
--[[ Callback: RaidTargetIndicator:PreUpdate()
Called before the element has been updated.
* self - the RaidTargetIndicator element
--]]
if(element.PreUpdate) then
element:PreUpdate()
end
local index = GetRaidTargetIndex(self.unit)
if(index) then
SetRaidTargetIconTexture(element, index)
element:Show()
else
element:Hide()
end
--[[ Callback: RaidTargetIndicator:PostUpdate(index)
Called after the element has been updated.
* self - the RaidTargetIndicator element
* index - the index of the raid target marker (number?)[1-8]
--]]
if(element.PostUpdate) then
return element:PostUpdate(index)
end
end
local function Path(self, ...)
--[[ Override: RaidTargetIndicator.Override(self, event)
Used to completely override the internal update function.
* self - the parent object
* event - the event triggering the update (string)
--]]
return (self.RaidTargetIndicator.Override or Update) (self, ...)
end
local function ForceUpdate(element)
if(not element.__owner.unit) then return end
return Path(element.__owner, 'ForceUpdate')
end
local function Enable(self)
local element = self.RaidTargetIndicator
if(element) then
element.__owner = self
element.ForceUpdate = ForceUpdate
self:RegisterEvent('RAID_TARGET_UPDATE', Path, true)
if(element:IsObjectType('Texture') and not element:GetTexture()) then
element:SetTexture([[Interface\TargetingFrame\UI-RaidTargetingIcons]])
end
return true
end
end
local function Disable(self)
local element = self.RaidTargetIndicator
if(element) then
element:Hide()
self:UnregisterEvent('RAID_TARGET_UPDATE', Path)
end
end
oUF:AddElement('RaidTargetIndicator', Path, Enable, Disable)
@@ -0,0 +1,137 @@
--[[
# Element: Range Fader
Changes the opacity of a unit frame based on whether the frame's unit is in the player's range.
## Widget
Range - A table containing opacity values.
## Notes
Offline units are handled as if they are in range.
## Options
.outsideAlpha - Opacity when the unit is out of range. Defaults to 0.55 (number)[0-1].
.insideAlpha - Opacity when the unit is within range. Defaults to 1 (number)[0-1].
## Examples
-- Register with oUF
self.Range = {
insideAlpha = 1,
outsideAlpha = 1/2,
}
--]]
local ns = oUF
local oUF = ns.oUF
local _FRAMES = {}
local OnRangeFrame
local UnitInRange, UnitIsConnected = UnitInRange, UnitIsConnected
local function Update(self, event)
local element = self.Range
local unit = self.unit
--[[ Callback: Range:PreUpdate()
Called before the element has been updated.
* self - the Range element
--]]
if(element.PreUpdate) then
element:PreUpdate()
end
local inRange
local connected = UnitIsConnected(unit)
if(connected) then
inRange = UnitInRange(unit)
if(not inRange) then
self:SetAlpha(element.outsideAlpha)
else
self:SetAlpha(element.insideAlpha)
end
else
self:SetAlpha(element.insideAlpha)
end
--[[ Callback: Range:PostUpdate(object, inRange, isConnected)
Called after the element has been updated.
* self - the Range element
* object - the parent object
* inRange - indicates if the unit was within 40 yards of the player (boolean)
* isConnected - indicates if the unit is online (boolean)
--]]
if(element.PostUpdate) then
return element:PostUpdate(self, inRange, connected)
end
end
local function Path(self, ...)
--[[ Override: Range.Override(self, event)
Used to completely override the internal update function.
* self - the parent object
* event - the event triggering the update (string)
--]]
return (self.Range.Override or Update) (self, ...)
end
-- Internal updating method
local timer = 0
local function OnRangeUpdate(_, elapsed)
timer = timer + elapsed
if(timer >= .20) then
for _, object in next, _FRAMES do
if(object:IsShown()) then
Path(object, 'OnUpdate')
end
end
timer = 0
end
end
local function Enable(self)
local element = self.Range
if(element) then
element.__owner = self
element.insideAlpha = element.insideAlpha or 1
element.outsideAlpha = element.outsideAlpha or 0.55
if(not OnRangeFrame) then
OnRangeFrame = CreateFrame('Frame')
OnRangeFrame:SetScript('OnUpdate', OnRangeUpdate)
end
table.insert(_FRAMES, self)
OnRangeFrame:Show()
return true
end
end
local function Disable(self)
local element = self.Range
if(element) then
for index, frame in next, _FRAMES do
if(frame == self) then
table.remove(_FRAMES, index)
break
end
end
self:SetAlpha(element.insideAlpha)
if(#_FRAMES == 0) then
OnRangeFrame:Hide()
end
end
end
oUF:AddElement('Range', nil, Enable, Disable)
@@ -0,0 +1,150 @@
--[[
# Element: Ready Check Indicator
Handles the visibility and updating of an indicator based on the unit's ready check status.
## Widget
ReadyCheckIndicator - A `Texture` representing ready check status.
## Notes
This element updates by changing the texture.
Default textures will be applied if the layout does not provide custom ones. See Options.
## Options
.finishedTime - For how many seconds the icon should stick after a check has completed. Defaults to 10 (number).
.fadeTime - For how many seconds the icon should fade away after the stick duration has completed. Defaults to
1.5 (number).
.readyTexture - Path to an alternate texture for the ready check 'ready' status.
.notReadyTexture - Path to an alternate texture for the ready check 'notready' status.
.waitingTexture - Path to an alternate texture for the ready check 'waiting' status.
## Attributes
.status - the unit's ready check status (string?)['ready', 'noready', 'waiting']
## Examples
-- Position and size
local ReadyCheckIndicator = self:CreateTexture(nil, 'OVERLAY')
ReadyCheckIndicator:SetSize(16, 16)
ReadyCheckIndicator:SetPoint('TOP')
-- Register with oUF
self.ReadyCheckIndicator = ReadyCheckIndicator
--]]
local ns = oUF
local oUF = ns.oUF
local GetReadyCheckStatus = GetReadyCheckStatus
local UnitExists = UnitExists
local function OnFinished(self)
local element = self:GetParent()
element:Hide()
--[[ Callback: ReadyCheckIndicator:PostUpdateFadeOut()
Called after the element has been faded out.
* self - the ReadyCheckIndicator element
--]]
if(element.PostUpdateFadeOut) then
element:PostUpdateFadeOut()
end
end
local function Update(self, event)
local element = self.ReadyCheckIndicator
--[[ Callback: ReadyCheckIndicator:PreUpdate()
Called before the element has been updated.
* self - the ReadyCheckIndicator element
--]]
if(element.PreUpdate) then
element:PreUpdate()
end
local unit = self.unit
local status = GetReadyCheckStatus(unit)
if(UnitExists(unit) and status) then
if(status == 'ready') then
element:SetTexture(element.readyTexture)
elseif(status == 'notready') then
element:SetTexture(element.notReadyTexture)
else
element:SetTexture(element.waitingTexture)
end
element.status = status
element:Show()
elseif(event ~= 'READY_CHECK_FINISHED') then
element.status = nil
element:Hide()
end
if(event == 'READY_CHECK_FINISHED') then
if(element.status == 'waiting') then
element:SetTexture(element.notReadyTexture)
end
end
--[[ Callback: ReadyCheckIndicator:PostUpdate(status)
Called after the element has been updated.
* self - the ReadyCheckIndicator element
* status - the unit's ready check status (string?)['ready', 'notready', 'waiting']
--]]
if(element.PostUpdate) then
return element:PostUpdate(status)
end
end
local function Path(self, ...)
--[[ Override: ReadyCheckIndicator.Override(self, event, ...)
Used to completely override the internal update function.
* self - the parent object
* event - the event triggering the update (string)
* ... - the arguments accompanying the event
--]]
return (self.ReadyCheckIndicator.Override or Update) (self, ...)
end
local function ForceUpdate(element)
return Path(element.__owner, 'ForceUpdate')
end
local function Enable(self, unit)
local element = self.ReadyCheckIndicator
if(element and (unit and (unit:sub(1, 5) == 'party' or unit:sub(1, 4) == 'raid'))) then
element.__owner = self
element.ForceUpdate = ForceUpdate
element.readyTexture = element.readyTexture or READY_CHECK_READY_TEXTURE
element.notReadyTexture = element.notReadyTexture or READY_CHECK_NOT_READY_TEXTURE
element.waitingTexture = element.waitingTexture or READY_CHECK_WAITING_TEXTURE
self:RegisterEvent('READY_CHECK', Path, true)
self:RegisterEvent('READY_CHECK_CONFIRM', Path, true)
self:RegisterEvent('READY_CHECK_FINISHED', Path, true)
return true
end
end
local function Disable(self)
local element = self.ReadyCheckIndicator
if(element) then
element:Hide()
self:UnregisterEvent('READY_CHECK', Path)
self:UnregisterEvent('READY_CHECK_CONFIRM', Path)
self:UnregisterEvent('READY_CHECK_FINISHED', Path)
end
end
oUF:AddElement('ReadyCheckIndicator', Path, Enable, Disable)
@@ -0,0 +1,100 @@
--[[
# Element: Resting Indicator
Toggles the visibility of an indicator based on the player's resting status.
## Widget
RestingIndicator - Any UI widget.
## Notes
A default texture will be applied if the widget is a Texture and doesn't have a texture or a color set.
## Examples
-- Position and size
local RestingIndicator = self:CreateTexture(nil, 'OVERLAY')
RestingIndicator:SetSize(16, 16)
RestingIndicator:SetPoint('TOPLEFT', self)
-- Register it with oUF
self.RestingIndicator = RestingIndicator
--]]
local ns = oUF
local oUF = ns.oUF
local IsResting = IsResting
local function Update(self, event)
local element = self.RestingIndicator
--[[ Callback: RestingIndicator:PreUpdate()
Called before the element has been updated.
* self - the RestingIndicator element
--]]
if(element.PreUpdate) then
element:PreUpdate()
end
local isResting = IsResting()
if(isResting) then
element:Show()
else
element:Hide()
end
--[[ Callback: RestingIndicator:PostUpdate(isResting)
Called after the element has been updated.
* self - the RestingIndicator element
* isResting - indicates if the player is resting (boolean)
--]]
if(element.PostUpdate) then
return element:PostUpdate(isResting)
end
end
local function Path(self, ...)
--[[ Override: RestingIndicator.Override(self, event)
Used to completely override the internal update function.
* self - the parent object
* event - the event triggering the update (string)
--]]
return (self.RestingIndicator.Override or Update) (self, ...)
end
local function ForceUpdate(element)
return Path(element.__owner, 'ForceUpdate')
end
local function Enable(self, unit)
local element = self.RestingIndicator
if(element and unit == 'player') then
element.__owner = self
element.ForceUpdate = ForceUpdate
self:RegisterEvent('PLAYER_UPDATE_RESTING', Path, true)
if(element:IsObjectType('Texture') and not element:GetTexture()) then
element:SetTexture([[Interface\CharacterFrame\UI-StateIcon]])
element:SetTexCoord(0, 0.5, 0, 0.421875)
end
return true
end
end
local function Disable(self)
local element = self.RestingIndicator
if(element) then
element:Hide()
self:UnregisterEvent('PLAYER_UPDATE_RESTING', Path)
end
end
oUF:AddElement('RestingIndicator', Path, Enable, Disable)
@@ -0,0 +1,828 @@
-- Credits: Vika, Cladhaire, Tekkub
--[[
# Element: Tags
Provides a system for text-based display of information by binding a tag string to a font string widget which in turn is
tied to a unit frame.
## Widget
A FontString to hold a tag string. Unlike other elements, this widget must not have a preset name.
## Notes
A `Tag` is a Lua string consisting of a function name surrounded by square brackets. The tag will be replaced by the
output of the function and displayed as text on the font string widget with that the tag has been registered. Literals
can be pre- or appended by separating them with a `>` before or `<` after the function name. The literals will be only
displayed when the function returns a non-nil value. I.e. `"[perhp<%]"` will display the current health as a percentage
of the maximum health followed by the % sign.
A `Tag String` is a Lua string consisting of one or multiple tags with optional literals between them. Each tag will be
updated individually and the output will follow the tags order. Literals will be displayed in the output string
regardless of whether the surrounding tag functions return a value. I.e. `"[curhp]/[maxhp]"` will resolve to something
like `2453/5000`.
A `Tag Function` is used to replace a single tag in a tag string by its output. A tag function receives only two
arguments - the unit and the realUnit of the unit frame used to register the tag (see Options for further details). The
tag function is called when the unit frame is shown or when a specified event has fired. It the tag is registered on an
eventless frame (i.e. one holding the unit "targettarget"), then the tag function is called in a set time interval.
A number of built-in tag functions exist. The layout can also define its own tag functions by adding them to the
`oUF.Tags.Methods` table. The events upon which the function will be called are specified in a white-space separated
list added to the `oUF.Tags.Events` table. Should an event fire without unit information, then it should also be listed
in the `oUF.Tags.SharedEvents` table as follows: `oUF.Tags.SharedEvents.EVENT_NAME = true`.
## Options
.overrideUnit - if specified on the font string widget, the frame's realUnit will be passed as the second argument to
every tag function whose name is contained in the relevant tag string. Otherwise the second argument
is always nil (boolean)
.frequentUpdates - defines how often the correspondig tag function(s) should be called. This will override the events for
the tag(s), if any. If the value is a number, it is taken as a time interval in seconds. If the value
is a boolean, the time interval is set to 0.5 seconds (number or boolean)
## Attributes
.parent - the unit frame on which the tag has been registered
## Examples
-- define the tag function
oUF.Tags.Methods['mylayout:threatname'] = function(unit, realUnit)
local color = _TAGS['threatcolor'](unit)
local name = _TAGS['name'](unit, realUnit)
return string.format('%s%s|r', color, name)
end
-- add the events
oUF.Tags.Events['mylayout:threatname'] = 'UNIT_NAME_UPDATE UNIT_THREAT_SITUATION_UPDATE'
-- create the text widget
local info = self.Health:CreateFontString(nil, 'OVERLAY', 'GameFontNormal')
info:SetPoint('LEFT')
-- register the tag on the text widget with oUF
self:Tag(info, '[mylayout:threatname]')
--]]
local ns = oUF
local oUF = ns.oUF
local _G = _G
local unpack = unpack
local floor = math.floor
local format = string.format
local tinsert, tremove = table.insert, table.remove
local GetComboPoints = GetComboPoints
local GetNumRaidMembers = GetNumRaidMembers
local GetPetHappiness = GetPetHappiness
local GetQuestDifficultyColor = GetQuestDifficultyColor
local GetRaidRosterInfo = GetRaidRosterInfo
local IsResting = IsResting
local UnitCanAttack = UnitCanAttack
local UnitClass = UnitClass
local UnitClassification = UnitClassification
local UnitCreatureFamily = UnitCreatureFamily
local UnitCreatureType = UnitCreatureType
local UnitFactionGroup = UnitFactionGroup
local UnitIsConnected = UnitIsConnected
local UnitIsDead = UnitIsDead
local UnitIsGhost = UnitIsGhost
local UnitIsPVP = UnitIsPVP
local UnitIsPartyLeader = UnitIsPartyLeader
local UnitIsPlayer = UnitIsPlayer
local UnitLevel = UnitLevel
local UnitMana = UnitMana
local UnitManaMax = UnitManaMax
local UnitName = UnitName
local UnitPowerType = UnitPowerType
local UnitRace = UnitRace
local UnitSex = UnitSex
local _PATTERN = '%[..-%]+'
local _ENV = {
Hex = function(r, g, b)
if(type(r) == 'table') then
if(r.r) then
r, g, b = r.r, r.g, r.b
else
r, g, b = unpack(r)
end
end
if not r or type(r) == 'string' then
return '|cffFFFFFF'
end
return format('|cff%02x%02x%02x', r * 255, g * 255, b * 255)
end,
ColorGradient = oUF.ColorGradient,
}
local _PROXY = setmetatable(_ENV, {__index = _G})
local tagStrings = {
['creature'] = [[function(u)
return UnitCreatureFamily(u) or UnitCreatureType(u)
end]],
['dead'] = [[function(u)
if(UnitIsDead(u)) then
return 'Dead'
elseif(UnitIsGhost(u)) then
return 'Ghost'
end
end]],
['difficulty'] = [[function(u)
if UnitCanAttack('player', u) then
local l = UnitLevel(u)
return Hex(GetQuestDifficultyColor((l > 0) and l or 999))
end
end]],
['leader'] = [[function(u)
if(UnitIsPartyLeader(u)) then
return 'L'
end
end]],
['leaderlong'] = [[function(u)
if(UnitIsPartyLeader(u)) then
return 'Leader'
end
end]],
['level'] = [[function(u)
local l = UnitLevel(u)
if(l > 0) then
return l
else
return '??'
end
end]],
['missinghp'] = [[function(u)
local current = UnitHealthMax(u) - UnitHealth(u)
if(current > 0) then
return current
end
end]],
['missingpp'] = [[function(u)
local current = UnitManaMax(u) - UnitMana(u)
if(current > 0) then
return current
end
end]],
['name'] = [[function(u, r)
return UnitName(r or u)
end]],
['offline'] = [[function(u)
if(not UnitIsConnected(u)) then
return 'Offline'
end
end]],
['perhp'] = [[function(u)
local m = UnitHealthMax(u)
if(m == 0) then
return 0
else
return floor(UnitHealth(u) / m * 100 + .5)
end
end]],
['perpp'] = [[function(u)
local m = UnitManaMax(u)
if(m == 0) then
return 0
else
return floor(UnitMana(u) / m * 100 + .5)
end
end]],
['plus'] = [[function(u)
local c = UnitClassification(u)
if(c == 'elite' or c == 'rareelite') then
return '+'
end
end]],
['pvp'] = [[function(u)
if(UnitIsPVP(u)) then
return 'PvP'
end
end]],
['raidcolor'] = [[function(u)
local _, x = UnitClass(u)
if(x) then
return Hex(_COLORS.class[x])
end
end]],
['rare'] = [[function(u)
local c = UnitClassification(u)
if(c == 'rare' or c == 'rareelite') then
return 'Rare'
end
end]],
['resting'] = [[function(u)
if(u == 'player' and IsResting()) then
return 'zzz'
end
end]],
['sex'] = [[function(u)
local s = UnitSex(u)
if(s == 2) then
return 'Male'
elseif(s == 3) then
return 'Female'
end
end]],
['smartclass'] = [[function(u)
if(UnitIsPlayer(u)) then
return _TAGS['class'](u)
end
return _TAGS['creature'](u)
end]],
['status'] = [[function(u)
if(UnitIsDead(u)) then
return 'Dead'
elseif(UnitIsGhost(u)) then
return 'Ghost'
elseif(not UnitIsConnected(u)) then
return 'Offline'
else
return _TAGS['resting'](u)
end
end]],
['cpoints'] = [[function(u)
local cp = GetComboPoints('player', 'target')
if(cp > 0) then
return cp
end
end]],
['smartlevel'] = [[function(u)
local c = UnitClassification(u)
if(c == 'worldboss') then
return 'Boss'
else
local plus = _TAGS['plus'](u)
local level = _TAGS['level'](u)
if(plus) then
return level .. plus
else
return level
end
end
end]],
['classification'] = [[function(u)
local c = UnitClassification(u)
if(c == 'rare') then
return 'Rare'
elseif(c == 'rareelite') then
return 'Rare Elite'
elseif(c == 'elite') then
return 'Elite'
elseif(c == 'worldboss') then
return 'Boss'
end
end]],
['shortclassification'] = [[function(u)
local c = UnitClassification(u)
if(c == 'rare') then
return 'R'
elseif(c == 'rareelite') then
return 'R+'
elseif(c == 'elite') then
return '+'
elseif(c == 'worldboss') then
return 'B'
end
end]],
['group'] = [[function(unit)
local name, server = UnitName(unit)
if(server and server ~= '') then
name = format('%s-%s', name, server)
end
for i=1, GetNumRaidMembers() do
local raidName, _, group = GetRaidRosterInfo(i)
if(raidName == name) then
return group
end
end
end]],
['deficit:name'] = [[function(u)
local missinghp = _TAGS['missinghp'](u)
if(missinghp) then
return '-' .. missinghp
else
return _TAGS['name'](u)
end
end]],
['curmana'] = [[function(unit)
return UnitMana(unit, SPELL_POWER_MANA)
end]],
['maxmana'] = [[function(unit)
return UnitManaMax(unit, SPELL_POWER_MANA)
end]],
['happiness'] = [[function(u)
if(UnitIsUnit(u, 'pet')) then
local happiness = GetPetHappiness()
if(happiness == 1) then
return ':<'
elseif(happiness == 2) then
return ':|'
elseif(happiness == 3) then
return ':D'
end
end
end]],
['powercolor'] = [[function(u)
local pType, pToken, altR, altG, altB = UnitPowerType(u)
local t = _COLORS.power[pToken]
if(not t) then
if(altR) then
if(altR > 1 or altG > 1 or altB > 1) then
return Hex(altR / 255, altG / 255, altB / 255)
else
return Hex(altR, altG, altB)
end
else
return Hex(_COLORS.power[pType])
end
end
return Hex(t)
end]],
}
local tags = setmetatable(
{
curhp = UnitHealth,
curpp = UnitMana,
maxhp = UnitHealthMax,
maxpp = UnitManaMax,
class = UnitClass,
faction = UnitFactionGroup,
race = UnitRace,
},
{
__index = function(self, key)
local tagFunc = tagStrings[key]
if(tagFunc) then
local func, err = loadstring('return ' .. tagFunc)
if(func) then
func = func()
-- Want to trigger __newindex, so no rawset.
self[key] = func
tagStrings[key] = nil
return func
else
error(err, 3)
end
end
end,
__newindex = function(self, key, val)
if(type(val) == 'string') then
tagStrings[key] = val
elseif(type(val) == 'function') then
-- So we don't clash with any custom envs.
if(getfenv(val) == _G) then
setfenv(val, _PROXY)
end
rawset(self, key, val)
end
end,
}
)
_ENV._TAGS = tags
local tagEvents = {
['curhp'] = 'UNIT_HEALTH',
['maxhp'] = 'UNIT_MAXHEALTH',
['curpp'] = 'UNIT_ENERGY UNIT_FOCUS UNIT_MANA UNIT_RAGE',
['maxpp'] = 'UNIT_MAXENERGY UNIT_MAXFOCUS UNIT_MAXMANA UNIT_MAXRAGE',
['dead'] = 'UNIT_HEALTH',
['leader'] = 'PARTY_LEADER_CHANGED',
['leaderlong'] = 'PARTY_LEADER_CHANGED',
['level'] = 'UNIT_LEVEL PLAYER_LEVEL_UP',
['missinghp'] = 'UNIT_HEALTH UNIT_MAXHEALTH',
['missingpp'] = 'UNIT_MAXENERGY UNIT_MAXFOCUS UNIT_MAXMANA UNIT_MAXRAGE UNIT_ENERGY UNIT_FOCUS UNIT_MANA UNIT_RAGE',
['name'] = 'UNIT_NAME_UPDATE',
['offline'] = 'UNIT_HEALTH',
['perhp'] = 'UNIT_HEALTH UNIT_MAXHEALTH',
['perpp'] = 'UNIT_MAXENERGY UNIT_MAXFOCUS UNIT_MAXMANA UNIT_MAXRAGE UNIT_ENERGY UNIT_FOCUS UNIT_MANA UNIT_RAGE',
['plus'] = 'UNIT_CLASSIFICATION_CHANGED',
['pvp'] = 'UNIT_FACTION',
['rare'] = 'UNIT_CLASSIFICATION_CHANGED',
['resting'] = 'PLAYER_UPDATE_RESTING',
['status'] = 'UNIT_HEALTH PLAYER_UPDATE_RESTING',
['cpoints'] = 'PLAYER_COMBO_POINTS PLAYER_TARGET_CHANGED',
['smartlevel'] = 'UNIT_LEVEL PLAYER_LEVEL_UP UNIT_CLASSIFICATION_CHANGED',
['classification'] = 'UNIT_CLASSIFICATION_CHANGED',
['shortclassification'] = 'UNIT_CLASSIFICATION_CHANGED',
['group'] = 'PARTY_MEMBERS_CHANGED RAID_ROSTER_UPDATE',
['curmana'] = 'UNIT_MANA UNIT_MAXMANA',
['maxmana'] = 'UNIT_MANA UNIT_MAXMANA',
['happiness'] = 'UNIT_HAPPINESS',
['powercolor'] = 'UNIT_DISPLAYPOWER',
}
local unitlessEvents = {
PLAYER_LEVEL_UP = true,
PLAYER_UPDATE_RESTING = true,
PLAYER_TARGET_CHANGED = true,
RAID_ROSTER_UPDATE = true,
PARTY_MEMBERS_CHANGED = true,
PARTY_LEADER_CHANGED = true,
PLAYER_COMBO_POINTS = true,
}
local events = {}
local frame = CreateFrame('Frame')
frame:SetScript('OnEvent', function(self, event, unit)
local strings = events[event]
if(strings) then
for _, fontstring in next, strings do
if(fontstring:IsVisible() and (unitlessEvents[event] or fontstring.parent.unit == unit)) then
fontstring:UpdateTag()
end
end
end
end)
local onUpdates = {}
local eventlessUnits = {}
local function createOnUpdate(timer)
local OnUpdate = onUpdates[timer]
if(not OnUpdate) then
local total = timer
local frame = CreateFrame('Frame')
local strings = eventlessUnits[timer]
frame:SetScript('OnUpdate', function()
if(total >= timer) then
for _, fs in next, strings do
if(fs.parent:IsShown() and UnitExists(fs.parent.unit)) then
fs:UpdateTag()
end
end
total = 0
end
total = total + arg1
end)
onUpdates[timer] = frame
end
end
local function onShow(self)
for _, fs in next, self.__tags do
fs:UpdateTag()
end
end
local function getTagName(tag)
local tagStart = (string.match(tag, '>+()') or 2)
local tagEnd = string.match(tag, '.*()<+')
tagEnd = (tagEnd and tagEnd - 1) or -2
return string.sub(tag, tagStart, tagEnd), tagStart, tagEnd
end
local function registerEvent(fontstr, event)
if(not events[event]) then events[event] = {} end
frame:RegisterEvent(event)
tinsert(events[event], fontstr)
end
local function registerEvents(fontstr, tagstr)
for tag in string.gmatch(tagstr, _PATTERN) do
tag = getTagName(tag)
local tagevents = tagEvents[tag]
if(tagevents) then
for event in string.gmatch(tagevents, '%S+') do
registerEvent(fontstr, event)
end
end
end
end
local function unregisterEvents(fontstr)
for event, data in next, events do
for i, tagfsstr in next, data do
if(tagfsstr == fontstr) then
if(getn(data) == 1) then
frame:UnregisterEvent(event)
end
tremove(data, i)
end
end
end
end
local OnEnter = function(self)
for _, fs in pairs(self.__mousetags) do
fs:SetAlpha(1)
end
end
local OnLeave = function(self)
for _, fs in pairs(self.__mousetags) do
fs:SetAlpha(0)
end
end
local onUpdateDelay = {}
local tagPool = {}
local funcPool = {}
local tmp = {}
local escapeSequences = {
["||c"] = "|c",
["||r"] = "|r",
["||T"] = "|T",
["||t"] = "|t",
}
--[[ Tags: frame:Tag(fs, tagstr)
Used to register a tag on a unit frame.
* self - the unit frame on which to register the tag
* fs - the font string to display the tag (FontString)
* tagstr - the tag string (string)
--]]
local function Tag(self, fs, tagstr)
if(not fs or not tagstr) then return end
if(not self.__tags) then
self.__tags = {}
self.__mousetags = {}
tinsert(self.__elements, onShow)
else
-- Since people ignore everything that's good practice - unregister the tag
-- if it already exists.
for _, tag in pairs(self.__tags) do
if(fs == tag) then
-- We don't need to remove it from the __tags table as Untag handles
-- that for us.
self:Untag(fs)
end
end
end
fs.parent = self
for escapeSequence, replacement in pairs(escapeSequences) do
while string.find(tagstr, escapeSequence) do
tagstr = string.gsub(tagstr, escapeSequence, replacement)
end
end
if string.find(tagstr, '%[mouseover%]') then
tinsert(self.__mousetags, fs)
fs:SetAlpha(0)
if not self.__HookFunc then
self:HookScript('OnEnter', OnEnter)
self:HookScript('OnLeave', OnLeave)
self.__HookFunc = true;
end
tagstr = string.gsub(tagstr, '%[mouseover%]', '')
else
for index, fontString in pairs(self.__mousetags) do
if fontString == fs then
self.__mousetags[index] = nil;
fs:SetAlpha(1)
end
end
end
local containsOnUpdate
for tag in string.gmatch(tagstr, _PATTERN) do
tag = getTagName(tag)
if not tagEvents[tag] then
containsOnUpdate = onUpdateDelay[tag] or 0.15;
end
end
local func = tagPool[tagstr]
if(not func) then
local format, numTags = string.gsub(string.gsub(tagstr, '%%', '%%%%'), _PATTERN, '%%s')
local args = {}
for bracket in string.gmatch(tagstr, _PATTERN) do
local tagFunc = funcPool[bracket] or tags[string.sub(bracket, 2, -2)]
if(not tagFunc) then
local tagName, tagStart, tagEnd = getTagName(bracket)
local tag = tags[tagName]
if(tag) then
tagStart = tagStart - 2
tagEnd = tagEnd + 2
if(tagStart ~= 0 and tagEnd ~= 0) then
local prefix = string.sub(bracket, 2, tagStart)
local suffix = string.sub(bracket, tagEnd, -2)
tagFunc = function(unit, realUnit)
local str = tag(unit, realUnit)
if(str) then
return prefix .. str .. suffix
end
end
elseif(tagStart ~= 0) then
local prefix = string.sub(bracket, 2, tagStart)
tagFunc = function(unit, realUnit)
local str = tag(unit, realUnit)
if(str) then
return prefix .. str
end
end
elseif(tagEnd ~= 0) then
local suffix = string.sub(bracket, tagEnd, -2)
tagFunc = function(unit, realUnit)
local str = tag(unit, realUnit)
if(str) then
return str .. suffix
end
end
end
funcPool[bracket] = tagFunc
end
end
if(tagFunc) then
tinsert(args, tagFunc)
else
numTags = -1
func = function(self)
return self:SetText(bracket)
end
end
end
if(numTags == 1) then
func = function(self)
local parent = self.parent
local realUnit
if(self.overrideUnit) then
realUnit = parent.realUnit
end
_ENV._COLORS = parent.colors
return self:SetText(string.format(
format,
args[1](parent.unit, realUnit) or ''
))
end
elseif(numTags == 2) then
func = function(self)
local parent = self.parent
local unit = parent.unit
local realUnit
if(self.overrideUnit) then
realUnit = parent.realUnit
end
_ENV._COLORS = parent.colors
return self:SetText(string.format(
format,
args[1](unit, realUnit) or '',
args[2](unit, realUnit) or ''
))
end
elseif(numTags == 3) then
func = function(self)
local parent = self.parent
local unit = parent.unit
local realUnit
if(self.overrideUnit) then
realUnit = parent.realUnit
end
_ENV._COLORS = parent.colors
return self:SetText(string.format(
format,
args[1](unit, realUnit) or '',
args[2](unit, realUnit) or '',
args[3](unit, realUnit) or ''
))
end
elseif numTags ~= -1 then
func = function(self)
local parent = self.parent
local unit = parent.unit
local realUnit
if(self.overrideUnit) then
realUnit = parent.realUnit
end
_ENV._COLORS = parent.colors
for i, func in next, args do
tmp[i] = func(unit, realUnit) or ''
end
-- We do 1, numTags because tmp can hold several unneeded variables.
return self:SetText(string.format(format, unpack(tmp, 1, numTags)))
end
end
if numTags ~= -1 then
tagPool[tagstr] = func
end
end
fs.UpdateTag = func
local unit = self.unit
if(self.__eventless or fs.frequentUpdates) or containsOnUpdate then
local timer
if(type(fs.frequentUpdates) == 'number') then
timer = fs.frequentUpdates
elseif containsOnUpdate then
timer = containsOnUpdate
else
timer = .5
end
if(not eventlessUnits[timer]) then eventlessUnits[timer] = {} end
tinsert(eventlessUnits[timer], fs)
createOnUpdate(timer)
else
registerEvents(fs, tagstr)
end
tinsert(self.__tags, fs)
end
--[[ Tags: frame:Untag(fs)
Used to unregister a tag from a unit frame.
* self - the unit frame from which to unregister the tag
* fs - the font string holding the tag (FontString)
--]]
local function Untag(self, fs)
if(not fs) then return end
unregisterEvents(fs)
for _, timers in next, eventlessUnits do
for i, fontstr in next, timers do
if(fs == fontstr) then
tremove(timers, i)
end
end
end
for i, fontstr in next, self.__tags do
if(fontstr == fs) then
tremove(self.__tags, i)
end
end
fs.UpdateTag = nil
end
oUF.Tags = {
Methods = tags,
Events = tagEvents,
SharedEvents = unitlessEvents,
OnUpdateThrottle = onUpdateDelay,
}
oUF:RegisterMetaFunction('Tag', Tag)
oUF:RegisterMetaFunction('Untag', Untag)
+145
View File
@@ -0,0 +1,145 @@
local ns = oUF
local oUF = ns.oUF
local Private = oUF.Private
local argcheck = Private.argcheck
local error = Private.error
local frame_metatable = Private.frame_metatable
-- Original event methods
local registerEvent = frame_metatable.__index.RegisterEvent
local unregisterEvent = frame_metatable.__index.UnregisterEvent
function Private.UpdateUnits(frame, unit, realUnit)
if(unit == realUnit) then
realUnit = nil
end
if(frame.unit ~= unit or frame.realUnit ~= realUnit) then
frame.unit = unit
frame.realUnit = realUnit
frame.id = string.match(unit, '^.-(%d+)')
return true
end
end
local function onEvent()
if(this:IsVisible() or event == 'UNIT_COMBO_POINTS') then
--print(this, event, arg and unpack(arg))
--print(this, event, arg1, arg2, arg3, arg4, arg5)
return this[event](this, event, arg1, arg2, arg3, arg4, arg5)
end
end
local event_metatable = {
__call = function(funcs, self, ...)
for _, func in next, funcs do
func(self, unpack(arg))
end
end,
}
--[[ Events: frame:RegisterEvent(event, func, unitless)
Used to register a frame for a game event and add an event handler. OnUpdate polled frames are prevented from
registering events.
* self - frame that will be registered for the given event.
* event - name of the event to register (string)
* func - function that will be executed when the event fires. If a string is passed, then a function by that name
must be defined on the frame. Multiple functions can be added for the same frame and event
(string or function)
* unitless - indicates that the event does not fire for a specific unit, so the event arguments won't be
matched to the frame unit(s). Events that do not start with UNIT_ or are not known to be unit events are
automatically considered unitless (boolean)
--]]
function frame_metatable.__index:RegisterEvent(event, func)
-- Block OnUpdate polled frames from registering events.
-- UNIT_PORTRAIT_UPDATE and UNIT_MODEL_CHANGED which are used for
-- portrait updates.
if(self.__eventless and event ~= 'UNIT_PORTRAIT_UPDATE' and event ~= 'UNIT_MODEL_CHANGED') then return end
argcheck(event, 2, 'string')
if(type(func) == 'string' and type(self[func]) == 'function') then
func = self[func]
end
local curev = self[event]
local kind = type(curev)
if(curev and func) then
if(kind == 'function' and curev ~= func) then
self[event] = setmetatable({curev, func}, event_metatable)
elseif(kind == 'table') then
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(type(func) == 'function') then
self[event] = func
elseif(not self[event]) then
return error("Style [%s] attempted to register event [%s] on unit [%s] with a handler that doesn't exist.", self.style, event, self.unit or 'unknown')
end
if not self:GetScript('OnEvent') then
self:SetScript('OnEvent', onEvent)
end
registerEvent(self, event)
self.__registeredEvents[event] = true
end
end
--[[ Events: frame:IsEventRegistered(event, func)
Used to determine if a game event is registered.
* self - the frame registered for the event
* event - name of the registered event (string)
--]]
function frame_metatable.__index:IsEventRegistered(event)
argcheck(event, 2, 'string')
return self.__registeredEvents[event] and true or false
end
--[[ Events: frame:UnregisterEvent(event, func)
Used to remove a function from the event handler list for a game event.
* self - the frame registered for the event
* event - name of the registered event (string)
* func - function to be removed from the list of event handlers. If this is the only handler for the given event, then
the frame will be unregistered for the event
--]]
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
table.remove(curev, k)
local n = getn(curev)
if(n == 1) then
local _, handler = next(curev)
self[event] = handler
elseif(n == 0) then
-- This should not happen
unregisterEvent(self, event)
self.__registeredEvents[event] = nil
end
break
end
end
elseif(curev == func) then
self[event] = nil
unregisterEvent(self, event)
self.__registeredEvents[event] = nil
end
end
@@ -0,0 +1,71 @@
local ns = oUF
local oUF = ns.oUF
local Private = oUF.Private
local argcheck = Private.argcheck
local queue = {}
local factory = CreateFrame('Frame')
factory:SetScript('OnEvent', function()
return this[event](this, event, arg and unpack(arg))
end)
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
-- Avoid creating dupes.
wipe(queue)
end
--[[ Factory: oUF:Factory(func)
Used to call a function directly if the current character is logged in and the factory is active. Else the function is
queued up to be executed at a later time (upon PLAYER_LOGIN by default).
* self - the global oUF object
* func - function to be executed or delayed (function)
--]]
function oUF:Factory(func)
argcheck(func, 2, 'function')
-- Call the function directly if we're active and logged in.
if(IsLoggedIn() and factory.active) then
return func(self)
else
table.insert(queue, func)
end
end
--[[ Factory: oUF:EnableFactory()
Used to enable the factory.
* self - the global oUF object
--]]
function oUF:EnableFactory()
factory.active = true
end
--[[ Factory: oUF:DisableFactory()
Used to disable the factory.
* self - the global oUF object
--]]
function oUF:DisableFactory()
factory.active = nil
end
--[[ Factory: oUF:RunFactoryQueue()
Used to try to execute queued up functions. The current player must be logged in and the factory must be active for
this to succeed.
* self - the global oUF object
--]]
function oUF:RunFactoryQueue()
factory:PLAYER_LOGIN()
end
@@ -0,0 +1,4 @@
local ns = oUF
-- It's named Private for a reason!
ns.oUF.Private = nil
+5
View File
@@ -0,0 +1,5 @@
oUF = {}
local ns = oUF
ns.oUF = {}
ns.oUF.Private = {}
+58
View File
@@ -0,0 +1,58 @@
<Ui xmlns="http://www.blizzard.com/wow/ui/">
<Script file="init.lua"/>
<Script file="private.lua"/>
<Script file="ouf.lua"/>
<Script file="events.lua"/>
<Script file="factory.lua"/>
<Script file="blizzard.lua"/>
<Script file="units.lua"/>
<Script file="colors.lua"/>
<Script file="finalize.lua"/>
<Include file="elements\elements.xml"/>
<Cooldown name="oUF_CooldownFrameTemplate" inherits="CooldownFrameTemplate" drawEdge="true" virtual="true"/>
<!--
Sub-object as a child of the parent unit frame:
<Button name="oUF_HeaderTargetTemplate" inherits="SecureUnitButtonTemplate" virtual="true">
<Frames>
<Button name="$parentTarget" inherits="SecureUnitButtonTemplate">
<Attributes>
<Attribute name="unitsuffix" type="string" value="target"/>
<Attribute name="useparent-unit" type="boolean" value="true"/>
</Attributes>
</Button>
</Frames>
</Button>
Separate unit template example:
<Button name="oUF_HeaderSeparateSubOjectsTemplate" inherits="SecureUnitButtonTemplate" virtual="true">
<Attributes>
<Attribute name="oUF-onlyProcessChildren" type="boolean" value="true"/>
</Attributes>
<Frames>
<Button name="$parentUnit" inherits="SecureUnitButtonTemplate">
<Attributes>
<Attribute name="useparent-unit" type="boolean" value="true"/>
</Attributes>
</Button>
<Button name="$parentPet" inherits="SecureUnitButtonTemplate">
<Attributes>
<Attribute name="unitsuffix" type="string" value="pet"/>
<Attribute name="useparent-unit" type="boolean" value="true"/>
</Attributes>
</Button>
<Button name="$parentTarget" inherits="SecureUnitButtonTemplate">
<Attributes>
<Attribute name="unitsuffix" type="string" value="target"/>
<Attribute name="useparent-unit" type="boolean" value="true"/>
</Attributes>
</Button>
</Frames>
</Button>
-->
</Ui>
+754
View File
@@ -0,0 +1,754 @@
local ns = oUF
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, units, objects, headers = {}, {}, {}, {}
local elements = {}
local activeElements = {}
local pairs = pairs
local unitExistsWatchers = {}
local unitExistsCache = setmetatable({},{
__index = function(t, k)
local v = UnitExists(k) or false
t[k] = v
return v
end
})
local function updateUnitWatch(frame)
local unit = frame.unit
local exists = (unit and unitExistsCache[unit])
if exists then
frame:Show()
else
frame:Hide()
end
end
local unitWatch = CreateFrame("Frame")
unitWatch:Hide()
local timer = 0
unitWatch:SetScript("OnUpdate", function()
timer = timer - arg1
if timer <= 0 then
timer = 0.2
for k in pairs(unitExistsCache) do
unitExistsCache[k] = nil
end
for frame in pairs(unitExistsWatchers) do
updateUnitWatch(frame)
end
end
end)
unitWatch:SetScript("OnEvent", function() timer = 0 end)
unitWatch:RegisterEvent("PLAYER_TARGET_CHANGED")
unitWatch:RegisterEvent("PLAYER_FOCUS_CHANGED")
unitWatch:RegisterEvent("PLAYER_REGEN_DISABLED")
unitWatch:RegisterEvent("PLAYER_REGEN_ENABLED")
unitWatch:RegisterEvent("UNIT_PET")
unitWatch:RegisterEvent("RAID_ROSTER_UPDATE")
unitWatch:RegisterEvent("PARTY_MEMBERS_CHANGED")
local function RegisterUnitWatch(frame)
unitExistsWatchers[frame] = true
unitWatch:Show()
updateUnitWatch(frame)
end
local function UnregisterUnitWatch(frame)
unitExistsWatchers[frame] = nil
end
-- 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, table.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)
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,
--[[ 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
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')
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,
} do
frame_metatable.__index[k] = v
end
local secureDropdown
local function InitializeSecureMenu()
local unit = SecureTemplatesDropdown.unit
if(not unit) then return end
local unitType = string.match(unit, '^([a-z]+)[0-9]+$') or unit
local menu
if(unitType == 'party') then
menu = 'PARTY'
elseif(unitType == 'focus') then
menu = 'RAID_TARGET_ICON'
-- elseif(unitType == 'arenapet' or unitType == 'arena') then
elseif(unitType == 'arena') then
menu = 'RAID_TARGET_ICON'
elseif(UnitIsUnit(unit, 'player')) then
menu = 'SELF'
elseif(UnitIsUnit(unit, 'pet')) then
menu = 'PET'
elseif(UnitIsPlayer(unit)) then
if(UnitInRaid(unit)) then
menu = 'RAID_PLAYER'
elseif(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)
table.insert(UnitPopupFrames, secureDropdown:GetName())
UIDropDownMenu_Initialize(secureDropdown, InitializeSecureMenu, 'MENU')
end
if(secureDropdown.openedFor and secureDropdown.openedFor ~= self) then
CloseDropDownMenus()
end
secureDropdown.unit = string.lower(unit)
secureDropdown.openedFor = self
ToggleDropDownMenu(1, nil, secureDropdown, 'cursor')
end
local function onShow()
return this:UpdateAllElements('OnShow')
end
local function initObject(unit, style, styleFunc, header, ...)
local num = table.getn(arg)
for i = 1, num do
local object = arg[i]
local objectUnit = object.guessUnit or unit
local suffix = "ery"
object.__elements = {}
object.__registeredEvents = {}
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)
if(not header) then
-- No header means it's a frame created through :Spawn().
object.menu = togglemenu
object:SetScript("OnClick", function()
TargetUnit(unit)
end)
-- Other 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('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', 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 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)
table.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, select('#', 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_' .. style:gsub('^oUF_?', ''):gsub('[^%a%d_]+', '')
local raid, party, groupFilter
for i = 1, select('#', 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(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
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()
for i = 1, select('#', self), 1 do
local frame = select(i, self)
local unit
-- There's no need to do anything on frames with onlyProcessChildren
if(not frame.onlyProcessChildren) then
-- 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.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.menu = togglemenu
frame:SetAttribute('type1', 'target')
frame:SetAttribute('type2', 'menu')
frame.guessUnit = unit
end
end
header:styleFunction(self:GetName())
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 `'SecureGroupHeaderTemplate'`
(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 'SecureGroupHeaderTemplate')
local isPetHeader = string.match(template, 'PetHeader')
local name = overrideName or generateName(nil, unpack(arg))
local header = CreateFrame('Frame', name, UIParent, template)
header:SetAttribute('template', 'SecureUnitButtonTemplate')
for i = 1, select('#', 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.
table.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 = string.split(' ', visibility, 2)
if(list and type == 'custom') then
RegisterStateDriver(header, 'visibility', list)
header.visibility = list
else
local condition = getCondition(string.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 = string.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
@@ -0,0 +1,22 @@
local ns = oUF
local Private = ns.oUF.Private
function Private.argcheck(value, num, ...)
assert(type(num) == 'number', "Bad argument #2 to 'argcheck' (number expected, got " .. type(num) .. ')')
for i = 1, select('#', arg) do
if(type(value) == select(i, unpack(arg))) then return end
end
local types = strjoin(', ', unpack(arg))
local name = string.match(debugstack(2,2,0), ": in function [`<](.-)['>]")
error(string.format("Bad argument #%d to '%s' (%s expected, got %s)", num, name, types, type(value)), 3)
end
function Private.print(...)
print('|cff33ff99oUF:|r', unpack(arg))
end
function Private.error(...)
Private.print('|cffff0000Error:|r ' .. string.format(unpack(arg)))
end
+20
View File
@@ -0,0 +1,20 @@
local ns = oUF
local oUF = ns.oUF
local Private = oUF.Private
local enableTargetUpdate = Private.enableTargetUpdate
-- Handles unit specific actions.
function oUF:HandleUnit(object, unit)
local unit = object.unit or unit
if(unit == 'target') then
object:RegisterEvent('PLAYER_TARGET_CHANGED', object.UpdateAllElements)
elseif(unit == 'mouseover') then
object:RegisterEvent('UPDATE_MOUSEOVER_UNIT', object.UpdateAllElements)
elseif(unit == 'focus') then
object:RegisterEvent('PLAYER_FOCUS_CHANGED', object.UpdateAllElements)
elseif(string.match(unit, '%w+target')) then
enableTargetUpdate(object)
end
end