annotate LibKraken/LibKraken.lua @ 62:04c23ceaf9e0

- fix taint caused by loading in combat
author Nenue
date Mon, 05 Sep 2016 14:56:38 -0400
parents 256585077cdd
children
rev   line source
Nenue@37 1 --[[
Nenue@37 2 -- KrakynTools
Nenue@37 3 -- AddOn prototyping library.
Nenue@37 4 --
Nenue@37 5 --- Bundles an object into the handler queue, returning the core object, and handlers for debug output and co-routine.
Nenue@37 6 -- Addon name is determined in order of: (string first arg, second arg :GetName(), invoking filename).
Nenue@37 7 -- Once an addon name/object is registered, subsequent calls will return a debugger that reports the file or plugin name.
Nenue@37 8 -- @usage addon, print, wrap = KT.register(name, table) or KT.register(addon) or KT.register(addon, plugin)
Nenue@37 9 -- @param name - name of addon, as found in global varargs
Nenue@37 10 -- @param table - addon table from global varargs
Nenue@37 11 --
Nenue@37 12 -- @param frame - frame object used by addon
Nenue@37 13 -- @param plugin - string name of plugin or an object table to check for lib handlers
Nenue@37 14 --
Nenue@37 15 -- Handlers:
Nenue@37 16 -- :init() run immediately after KT sets itself up
Nenue@37 17 -- :profile("Name-TruncatedRealm") called the first time SavedVars data becomes available
Nenue@37 18 -- :variables() called upon variables being available
Nenue@37 19 -- :event(event, ...) replaces the event callback
Nenue@37 20 -- :ui() called by /ui when activating
Nenue@37 21 --
Nenue@37 22 -- Embedded:
Nenue@37 23 -- NOTES:
Nenue@37 24 -- * `name' is passed as is into CreateFrame, so using nil produce an anonymous frame
Nenue@37 25 -- * `coord' is a 4 or 8 size table unpacked into Texture:SetTexCoords()
Nenue@37 26 --
Nenue@37 27 -- tab = frame:tab(name, tooltip, texture, coord)
Nenue@37 28 -- produces a serial button that changes display tabs
Nenue@37 29 --
Nenue@37 30 -- button = frame:button(name, text, tooltip, onClick)
Nenue@37 31 -- produces a button with OnClick script
Nenue@37 32 --
Nenue@37 33 -- uibutton = frame:uibutton(name, text, tooltip, onClick, texture, coord)
Nenue@37 34 -- produces a header button with desired onClick
Nenue@37 35 --
Nenue@37 36 --
Nenue@37 37 ]]--
Nenue@37 38
Nenue@37 39 local LIBKT_MAJOR, LIBKT_MINOR = "LibKraken", 2
Nenue@37 40 local KT = LibStub:NewLibrary(LIBKT_MAJOR, LIBKT_MINOR)
Nenue@37 41
Nenue@37 42 --GLOBALS: KTErrorFrame, LibKTError, SlashCmdList, SLASH_RL1, SLASH_UI1
Nenue@37 43 local CreateFrame, debugstack, tostring, select = CreateFrame, debugstack, tostring, select
Nenue@59 44 local max, unpack, tinsert, tremove = max, unpack, tinsert, tremove
Nenue@59 45 local ipairs, pairs, xpcall = ipairs, pairs, xpcall
Nenue@59 46 local type, assert = type, assert
Nenue@59 47 local IsLoggedIn = IsLoggedIn
Nenue@37 48 local db
Nenue@37 49
Nenue@59 50 local print = DEVIAN_WORKSPACE and function(...) _G.print('LKT', ...) end or function() end
Nenue@59 51 local noFunc = function() end
Nenue@37 52
Nenue@37 53 KT.handler = CreateFrame('Frame', 'LibKTHostFrame', UIParent)
Nenue@37 54 KT.addons = {}
Nenue@37 55 KT.initStack = {}
Nenue@59 56 local libInitialized = false
Nenue@37 57 local registeredHandles = {}
Nenue@59 58 local initialized = {}
Nenue@59 59 local enabled = {}
Nenue@37 60 local handlers = {}
Nenue@37 61
Nenue@37 62
Nenue@37 63
Nenue@59 64 local debuggers = {}
Nenue@59 65 local pending = {}
Nenue@59 66
Nenue@59 67
Nenue@59 68 local Embed = function (target, template)
Nenue@59 69 for k,v in pairs(template) do
Nenue@59 70 if not target[k] then
Nenue@62 71 print(tostring(target),'<-', k)
Nenue@59 72 target[k] = template[k]
Nenue@37 73 end
Nenue@37 74 end
Nenue@37 75 end
Nenue@37 76
Nenue@59 77 local LibKT_Error = function(msg)
Nenue@37 78 local dstack = debugstack(2)
Nenue@37 79 :gsub("Interface\\AddOns\\",'')
Nenue@37 80 :gsub("<(.-)>", function(a) return '|cFF00FFFF<'.. a ..'>|r' end)
Nenue@37 81
Nenue@37 82
Nenue@37 83
Nenue@37 84 KTErrorFrame.errmsg:SetText(msg)
Nenue@37 85 KTErrorFrame.debugstack:SetText(dstack)
Nenue@37 86 KTErrorFrame:SetHeight(KTErrorFrame.debugstack:GetStringHeight() + KTErrorFrame.errmsg:GetStringHeight() + 12)
Nenue@37 87 KTErrorFrame:Show()
Nenue@37 88 end
Nenue@37 89
Nenue@59 90 local LibKT_OnLoad = function(self)
Nenue@59 91 --- /rl
Nenue@59 92 -- ReloadUI shortcut
Nenue@59 93 SLASH_RL1 = "/rl"
Nenue@59 94 SlashCmdList.RL = function ()
Nenue@59 95 ReloadUI()
Nenue@59 96 end
Nenue@59 97 libInitialized = true
Nenue@59 98 end
Nenue@37 99
Nenue@59 100 local LibKT_OnEvent = function(self, event, arg1)
Nenue@59 101 print(event, arg1)
Nenue@59 102 if (event == 'ADDON_LOADED' and arg1 ~= 'Blizzard_DebugTools') or event == 'PLAYER_LOGIN' then
Nenue@59 103 if not libInitialized then
Nenue@59 104 LibKT_OnLoad(self)
Nenue@37 105 end
Nenue@37 106
Nenue@37 107
Nenue@59 108 -- run any init blocks left in the queue
Nenue@59 109 for i, addon in ipairs(KT.initStack) do
Nenue@59 110 if not initialized[addon] then
Nenue@59 111 print('|cFF0088FF'..tostring(addon)..'|r:init()')
Nenue@59 112 if addon.init then
Nenue@59 113 xpcall(addon.init, LibKT_Error)
Nenue@59 114 end
Nenue@37 115
Nenue@59 116 if addon.event then
Nenue@59 117 addon:SetScript('OnEvent', addon.event)
Nenue@59 118 end
Nenue@37 119
Nenue@59 120 initialized[addon] = true
Nenue@59 121 end
Nenue@37 122
Nenue@59 123 if #addon.modules >= 1 then
Nenue@59 124 for i, module in ipairs(addon.modules) do
Nenue@59 125 if not initialized[module] then
Nenue@59 126 print(i .. ' |cFF0088FF'..tostring(addon)..'|r.|cFF00FFFF'..tostring(module)..'|r:init()')
Nenue@59 127 if module.init then
Nenue@59 128 xpcall(module.init, LibKT_Error)
Nenue@59 129 end
Nenue@37 130
Nenue@59 131 if module.event then
Nenue@59 132 module:SetScript('OnEvent', module.event)
Nenue@59 133 end
Nenue@59 134 initialized[module] = true
Nenue@37 135 end
Nenue@37 136 end
Nenue@37 137 end
Nenue@59 138
Nenue@37 139 end
Nenue@37 140
Nenue@37 141 -- run any variables blocks if player variables are ready
Nenue@59 142 if IsLoggedIn() then
Nenue@59 143
Nenue@59 144 for i, addon in ipairs(KT.initStack) do
Nenue@59 145 print('|cFF88FF00'..tostring(addon)..'|r')
Nenue@59 146 if initialized[addon] then
Nenue@59 147 if not enabled[addon] then
Nenue@59 148 print('|cFF88FF00'..tostring(addon)..'|r:variables()')
Nenue@59 149 if addon.variables then
Nenue@59 150 xpcall(addon.variables, LibKT_Error)
Nenue@59 151 end
Nenue@59 152 enabled[addon] = true
Nenue@59 153 end
Nenue@59 154
Nenue@59 155 if addon.modules and enabled[addon] then
Nenue@59 156 for i, module in ipairs(addon.modules) do
Nenue@59 157 print(i .. ' |cFF88FF00'..tostring(module)..'|r')
Nenue@59 158 if not enabled[module] then
Nenue@59 159 if module.variables then
Nenue@59 160 print(i..' |cFF88FF00'..tostring(addon)..'|r.|cFF00FFFF'.. tostring(module)..'|r:variables()')
Nenue@59 161 xpcall(module.variables, LibKT_Error)
Nenue@59 162 end
Nenue@59 163 enabled[module] = true
Nenue@59 164 end
Nenue@37 165 end
Nenue@37 166 end
Nenue@37 167 end
Nenue@37 168 end
Nenue@37 169 end
Nenue@37 170 end
Nenue@37 171 end
Nenue@37 172
Nenue@59 173 --- GUI bits
Nenue@59 174 local defaultGUIAddon = {}
Nenue@59 175 do
Nenue@59 176 local GetButtonTemplate = function(name, parent, template, onClick)
Nenue@59 177 if _G[name] then
Nenue@59 178 return _G[name]
Nenue@59 179 end
Nenue@37 180
Nenue@59 181 local button = CreateFrame('Button', name, parent, template)
Nenue@59 182 button:RegisterForClicks('AnyUp')
Nenue@59 183 button:SetScript('OnClick', onClick)
Nenue@59 184 return button
Nenue@37 185 end
Nenue@37 186
Nenue@59 187 local SetButtonAnchor = function(self, collector, anchor, growth)
Nenue@59 188 if self:GetID() == 0 then
Nenue@59 189 self:SetID(#collector)
Nenue@59 190 print('registered TabButton #', self:GetID())
Nenue@59 191 end
Nenue@37 192
Nenue@59 193 if self:GetID() == 1 then
Nenue@59 194 self:SetPoint(unpack(anchor))
Nenue@59 195 else
Nenue@59 196 growth[2] = collector[self:GetID()-1]
Nenue@59 197 self:SetPoint(unpack(growth))
Nenue@59 198 end
Nenue@37 199 end
Nenue@37 200
Nenue@59 201 defaultGUIAddon.tab = function(self, name, tooltip, texture, coords)
Nenue@59 202 local button = GetButtonTemplate(name, self, 'KTTabButton', self.SelectTab)
Nenue@59 203 button.icon:SetTexture(texture)
Nenue@59 204 button.tooltip = tooltip
Nenue@59 205 button:SetSize(unpack(self.tabSize))
Nenue@59 206 if coords then
Nenue@59 207 button.icon:SetTexCoord(unpack(coords))
Nenue@59 208 end
Nenue@59 209 SetButtonAnchor(button, self.tabButtons, self.tabAnchor, self.tabGrowth)
Nenue@59 210 return button
Nenue@59 211 end
Nenue@59 212
Nenue@59 213 defaultGUIAddon.button = function(self, name, text, tooltip, onClick)
Nenue@59 214 local button = GetButtonTemplate(name, self, 'KTButton', onClick)
Nenue@59 215
Nenue@59 216 button.tooltip = tooltip
Nenue@59 217 button:SetText(text)
Nenue@59 218 button:SetWidth(max(button:GetWidth(), button:GetFontString():GetStringWidth() + 12))
Nenue@59 219
Nenue@59 220 SetButtonAnchor(button, self.controls, self.controlsAnchor, self.controlsGrowth)
Nenue@59 221 return button
Nenue@59 222 end
Nenue@59 223
Nenue@59 224 defaultGUIAddon.uibutton = function(self, name, text, tooltip, onClick, texture, coords)
Nenue@59 225 local button = GetButtonTemplate(name, self, 'KTUIPanelButton', onClick)
Nenue@59 226
Nenue@59 227 button.tooltip = tooltip
Nenue@59 228 button:SetText(text)
Nenue@59 229
Nenue@59 230 if self.UIPanelIcon then
Nenue@59 231 local w, h, anchor, x, y = unpack(self.UIPanelIcon)
Nenue@59 232 button.icon:SetTexture(texture)
Nenue@59 233 button.icon:SetSize(w, h)
Nenue@59 234 button.icon:ClearAllPoints()
Nenue@59 235 button.icon:SetPoint(anchor, button, anchor, x, y)
Nenue@59 236 end
Nenue@59 237
Nenue@59 238 if not self.UIPanelSize then
Nenue@59 239 button:SetWidth(button:GetFontString():GetStringWidth() + button.icon:GetWidth()/1.5)
Nenue@59 240 else
Nenue@59 241 button:SetSize(unpack(self.UIPanelSize))
Nenue@59 242 end
Nenue@59 243 if coords then
Nenue@59 244 button.icon:SetTexCoord(unpack(coords))
Nenue@59 245 end
Nenue@59 246 SetButtonAnchor(button, self.UIPanels, self.UIPanelAnchor, self.UIPanelGrowth)
Nenue@59 247 return button
Nenue@37 248 end
Nenue@37 249 end
Nenue@37 250
Nenue@37 251
Nenue@59 252 local defaultAddon = {}
Nenue@59 253 do
Nenue@59 254 defaultAddon.print = function(module, ...)
Nenue@59 255 local msg = '|cFF00FFFF'..module:GetName()..'|r:'
Nenue@59 256 for i = 1, select('#', ...) do
Nenue@59 257 msg = msg .. ' ' .. tostring(select(i, ...))
Nenue@59 258 end
Nenue@59 259 DEFAULT_CHAT_FRAME:AddMessage(msg)
Nenue@37 260 end
Nenue@37 261
Nenue@37 262
Nenue@37 263 local tickerQueue = {}
Nenue@37 264 local ticker
Nenue@59 265 defaultAddon.tick = function()
Nenue@37 266
Nenue@37 267 if #tickerQueue == 0 then
Nenue@37 268 ticker:Cancel()
Nenue@37 269 ticker = nil
Nenue@37 270 end
Nenue@37 271 local func = tremove(tickerQueue, 1)
Nenue@37 272 if func then
Nenue@37 273 --print('#', #tickerQueue)
Nenue@37 274 func()
Nenue@37 275 end
Nenue@37 276 end
Nenue@37 277
Nenue@59 278 defaultAddon.next = function(f)
Nenue@37 279 if not ticker then
Nenue@37 280 --print('create ticker')
Nenue@59 281 ticker = C_Timer.NewTicker(.001, defaultAddon.tick)
Nenue@37 282 end
Nenue@37 283 tinsert(tickerQueue, f)
Nenue@37 284
Nenue@37 285 return #tickerQueue
Nenue@59 286 end
Nenue@59 287
Nenue@59 288
Nenue@59 289 --- default OnEvent
Nenue@59 290
Nenue@62 291 local EmbedEventScript = function (handler)
Nenue@62 292 if not handler.SetScript then
Nenue@62 293 return
Nenue@62 294 end
Nenue@62 295 print('|cFF00FF88binding', handler:GetName())
Nenue@62 296 -- enclose so .event can be overridden post-register
Nenue@62 297 handler:SetScript('OnEvent', function(self, event,...)
Nenue@62 298 print('|cFFFF4400' .. tostring(self) .. '|r', event, ...)
Nenue@62 299 print(self.event)
Nenue@62 300 print(self[event])
Nenue@62 301 self.event(self, event, ...)
Nenue@62 302 end)
Nenue@62 303 handler.unhandled = 0
Nenue@62 304 handler.missed = 0
Nenue@62 305 handler.handled = 0
Nenue@62 306 handler.firstEvent = false
Nenue@62 307 end
Nenue@62 308
Nenue@62 309
Nenue@59 310 local isHandled = false
Nenue@59 311 local nodebug = false
Nenue@62 312 defaultAddon.event = function (self, event, ...)
Nenue@62 313 print('what')
Nenue@59 314 --- reset state
Nenue@62 315 if self[event] then
Nenue@62 316 print('|cFFFFFF00'.. tostring(self) .. '|r', event)
Nenue@62 317 print(self.debug)
Nenue@62 318 self.debug(event, ...)
Nenue@62 319 self[event](self, event, ...)
Nenue@62 320 self.missed = 0
Nenue@62 321 self.handled = self.handled + 1
Nenue@59 322 isHandled = true
Nenue@59 323 else
Nenue@62 324 self.firstEvent = false
Nenue@62 325 self.unhandled = self.unhandled + 1
Nenue@62 326 self.missed = self.missed + 1
Nenue@59 327 end
Nenue@59 328 return
Nenue@37 329 end
Nenue@59 330 defaultAddon.wrap = function(addon, module, name)
Nenue@59 331 local moduleName = name or tostring(module)
Nenue@59 332 print(addon, module)
Nenue@59 333 print('|cFF0088FF'..tostring(addon)..'|r:wrap(|cFF00FFFF'.. moduleName .. '|r|cFFFFFF00)|r')
Nenue@59 334
Nenue@59 335 addon.modules = addon.modules or {}
Nenue@59 336 tinsert(addon.modules, module)
Nenue@59 337
Nenue@59 338
Nenue@59 339 if (module.DEVIAN_PNAME and DEVIAN_PNAME == module.DEVIAN_PNAME) or ((not module.DEVIAN_PNAME) and DEVIAN_WORKSPACE) then
Nenue@59 340 debuggers[module] = function(...) _G.print(moduleName, ...) end
Nenue@59 341 else
Nenue@59 342 debuggers[module] = noFunc
Nenue@59 343 end
Nenue@62 344 module.debug = debuggers[module]
Nenue@62 345
Nenue@62 346 Embed(module, defaultAddon)
Nenue@62 347 EmbedEventScript(module)
Nenue@62 348
Nenue@59 349 return debuggers[module]
Nenue@59 350 end
Nenue@59 351
Nenue@59 352 defaultAddon.GetName = function(self) return tostring(self) end
Nenue@59 353 end
Nenue@59 354
Nenue@59 355 --- Frame registration
Nenue@59 356 KT.register = function(addon, arg, noGUI)
Nenue@59 357 --print('register(', addon, arg, ')')
Nenue@59 358 local name, handler
Nenue@59 359 if type(addon) == 'string' and type(arg) == 'table' then
Nenue@59 360 name = addon
Nenue@59 361 -- it's a string, i.e. file vararg was passed
Nenue@59 362 if _G[addon] then
Nenue@59 363 -- check if it's the name of a frame and use that
Nenue@59 364 handler = _G[addon]
Nenue@59 365 else
Nenue@59 366 -- re-arrange
Nenue@59 367 handler = arg
Nenue@59 368 end
Nenue@59 369 else
Nenue@59 370 handler = addon
Nenue@59 371 assert(type(handler) == 'table', 'Usage: KT.register(name, table) or KT.register(table, plugin)')
Nenue@59 372 end
Nenue@59 373
Nenue@59 374
Nenue@59 375 local printName
Nenue@59 376 local isModule
Nenue@59 377
Nenue@59 378 local scriptName = debugstack(2,1,0):match(".+\\(%S+)%.lua")
Nenue@59 379 if registeredHandles[handler] then
Nenue@59 380 -- addon is already register; treat second argument as plugin target
Nenue@59 381 isModule = true
Nenue@59 382 if type(arg) == 'table' then
Nenue@59 383 local mt = getmetatable(arg)
Nenue@59 384 setmetatable(arg, {__index = mt.__index, __tostring = function() return scriptName end})
Nenue@59 385 local debugger = handler:wrap(arg)
Nenue@59 386 return handler, debugger
Nenue@59 387 else
Nenue@59 388 print(' + "|cFF00FFFF'..scriptName..'|r"')
Nenue@59 389 end
Nenue@59 390 else
Nenue@59 391 -- new addon
Nenue@59 392 --local scriptName = debugstack(2,1,0):match(".+\\(%S+)%.lua")
Nenue@59 393 local mt = getmetatable(handler)
Nenue@59 394 local nmt = {__index = mt.__index, __tostring = function() return scriptName end }
Nenue@59 395 handler = setmetatable(handler, nmt)
Nenue@59 396 Embed(handler, defaultAddon)
Nenue@59 397 name = tostring(handler)
Nenue@59 398 registeredHandles[handler] = name
Nenue@59 399 if handler.SetScript then
Nenue@62 400 handler:SetScript('OnEvent', function(...) handler.event(...) end)
Nenue@62 401 handler.unhandled = 0
Nenue@62 402 handler.missed = 0
Nenue@62 403 handler.handled = 0
Nenue@62 404 handler.firstEvent = false
Nenue@59 405 end
Nenue@59 406 handler.modules = {}
Nenue@59 407 tinsert(KT.initStack, handler)
Nenue@59 408
Nenue@59 409 if not noGUI then
Nenue@59 410 handler.UIPanelAnchor = {'TOPLEFT', handler, 'TOPLEFT', 12, -12 }
Nenue@59 411 handler.UIPanelGrowth = {'TOPLEFT', 'TOPRIGHT', 14, 0}
Nenue@59 412 Embed(handler, defaultGUIAddon)
Nenue@59 413 end
Nenue@59 414
Nenue@59 415 print('|cFF0088FF'..tostring(addon)..'|r')
Nenue@59 416 end
Nenue@59 417
Nenue@59 418 local debugFunc = noFunc
Nenue@59 419 --@debug@
Nenue@59 420 local debugID = isModule and name or handler
Nenue@59 421 if (handler.DEVIAN_PNAME and DEVIAN_PNAME == handler.DEVIAN_PNAME) or ((not handler.DEVIAN_PNAME) and DEVIAN_WORKSPACE) then
Nenue@59 422 debuggers[debugID] = debuggers[debugID] or function(...) _G.print(name, ...) end
Nenue@59 423 debugFunc = debuggers[debugID]
Nenue@59 424 end
Nenue@62 425
Nenue@62 426 handler.debug = debugFunc
Nenue@59 427 --@end-debug@
Nenue@59 428 return handler, debugFunc
Nenue@37 429 end
Nenue@37 430
Nenue@37 431 KT.handler:RegisterEvent('ADDON_LOADED')
Nenue@37 432 KT.handler:RegisterEvent('PLAYER_LOGIN')
Nenue@59 433 KT.handler:SetScript('OnEvent', LibKT_OnEvent)