Nenue@37: --[[ Nenue@37: -- KrakynTools Nenue@37: -- AddOn prototyping library. Nenue@37: -- Nenue@37: --- Bundles an object into the handler queue, returning the core object, and handlers for debug output and co-routine. Nenue@37: -- Addon name is determined in order of: (string first arg, second arg :GetName(), invoking filename). Nenue@37: -- Once an addon name/object is registered, subsequent calls will return a debugger that reports the file or plugin name. Nenue@37: -- @usage addon, print, wrap = KT.register(name, table) or KT.register(addon) or KT.register(addon, plugin) Nenue@37: -- @param name - name of addon, as found in global varargs Nenue@37: -- @param table - addon table from global varargs Nenue@37: -- Nenue@37: -- @param frame - frame object used by addon Nenue@37: -- @param plugin - string name of plugin or an object table to check for lib handlers Nenue@37: -- Nenue@37: -- Handlers: Nenue@37: -- :init() run immediately after KT sets itself up Nenue@37: -- :profile("Name-TruncatedRealm") called the first time SavedVars data becomes available Nenue@37: -- :variables() called upon variables being available Nenue@37: -- :event(event, ...) replaces the event callback Nenue@37: -- :ui() called by /ui when activating Nenue@37: -- Nenue@37: -- Embedded: Nenue@37: -- NOTES: Nenue@37: -- * `name' is passed as is into CreateFrame, so using nil produce an anonymous frame Nenue@37: -- * `coord' is a 4 or 8 size table unpacked into Texture:SetTexCoords() Nenue@37: -- Nenue@37: -- tab = frame:tab(name, tooltip, texture, coord) Nenue@37: -- produces a serial button that changes display tabs Nenue@37: -- Nenue@37: -- button = frame:button(name, text, tooltip, onClick) Nenue@37: -- produces a button with OnClick script Nenue@37: -- Nenue@37: -- uibutton = frame:uibutton(name, text, tooltip, onClick, texture, coord) Nenue@37: -- produces a header button with desired onClick Nenue@37: -- Nenue@37: -- Nenue@37: ]]-- Nenue@37: Nenue@37: local LIBKT_MAJOR, LIBKT_MINOR = "LibKraken", 2 Nenue@37: local KT = LibStub:NewLibrary(LIBKT_MAJOR, LIBKT_MINOR) Nenue@37: Nenue@37: --GLOBALS: KTErrorFrame, LibKTError, SlashCmdList, SLASH_RL1, SLASH_UI1 Nenue@37: local CreateFrame, debugstack, tostring, select = CreateFrame, debugstack, tostring, select Nenue@59: local max, unpack, tinsert, tremove = max, unpack, tinsert, tremove Nenue@59: local ipairs, pairs, xpcall = ipairs, pairs, xpcall Nenue@59: local type, assert = type, assert Nenue@59: local IsLoggedIn = IsLoggedIn Nenue@37: local db Nenue@37: Nenue@59: local print = DEVIAN_WORKSPACE and function(...) _G.print('LKT', ...) end or function() end Nenue@59: local noFunc = function() end Nenue@37: Nenue@37: KT.handler = CreateFrame('Frame', 'LibKTHostFrame', UIParent) Nenue@37: KT.addons = {} Nenue@37: KT.initStack = {} Nenue@59: local libInitialized = false Nenue@37: local registeredHandles = {} Nenue@59: local initialized = {} Nenue@59: local enabled = {} Nenue@37: local handlers = {} Nenue@37: Nenue@37: Nenue@37: Nenue@59: local debuggers = {} Nenue@59: local pending = {} Nenue@59: Nenue@59: Nenue@59: local Embed = function (target, template) Nenue@59: for k,v in pairs(template) do Nenue@59: if not target[k] then Nenue@59: target[k] = template[k] Nenue@37: end Nenue@37: end Nenue@37: end Nenue@37: Nenue@59: local LibKT_Error = function(msg) Nenue@37: local dstack = debugstack(2) Nenue@37: :gsub("Interface\\AddOns\\",'') Nenue@37: :gsub("<(.-)>", function(a) return '|cFF00FFFF<'.. a ..'>|r' end) Nenue@37: Nenue@37: Nenue@37: Nenue@37: KTErrorFrame.errmsg:SetText(msg) Nenue@37: KTErrorFrame.debugstack:SetText(dstack) Nenue@37: KTErrorFrame:SetHeight(KTErrorFrame.debugstack:GetStringHeight() + KTErrorFrame.errmsg:GetStringHeight() + 12) Nenue@37: KTErrorFrame:Show() Nenue@37: end Nenue@37: Nenue@59: local LibKT_OnLoad = function(self) Nenue@59: --- /rl Nenue@59: -- ReloadUI shortcut Nenue@59: SLASH_RL1 = "/rl" Nenue@59: SlashCmdList.RL = function () Nenue@59: ReloadUI() Nenue@59: end Nenue@59: libInitialized = true Nenue@59: end Nenue@37: Nenue@59: local LibKT_OnEvent = function(self, event, arg1) Nenue@59: print(event, arg1) Nenue@59: if (event == 'ADDON_LOADED' and arg1 ~= 'Blizzard_DebugTools') or event == 'PLAYER_LOGIN' then Nenue@59: if not libInitialized then Nenue@59: LibKT_OnLoad(self) Nenue@37: end Nenue@37: Nenue@37: Nenue@59: -- run any init blocks left in the queue Nenue@59: for i, addon in ipairs(KT.initStack) do Nenue@59: if not initialized[addon] then Nenue@59: print('|cFF0088FF'..tostring(addon)..'|r:init()') Nenue@59: if addon.init then Nenue@59: xpcall(addon.init, LibKT_Error) Nenue@59: end Nenue@37: Nenue@59: if addon.event then Nenue@59: addon:SetScript('OnEvent', addon.event) Nenue@59: end Nenue@37: Nenue@59: initialized[addon] = true Nenue@59: end Nenue@37: Nenue@59: if #addon.modules >= 1 then Nenue@59: for i, module in ipairs(addon.modules) do Nenue@59: if not initialized[module] then Nenue@59: print(i .. ' |cFF0088FF'..tostring(addon)..'|r.|cFF00FFFF'..tostring(module)..'|r:init()') Nenue@59: if module.init then Nenue@59: xpcall(module.init, LibKT_Error) Nenue@59: end Nenue@37: Nenue@59: if module.event then Nenue@59: module:SetScript('OnEvent', module.event) Nenue@59: end Nenue@59: initialized[module] = true Nenue@37: end Nenue@37: end Nenue@37: end Nenue@59: Nenue@37: end Nenue@37: Nenue@37: -- run any variables blocks if player variables are ready Nenue@59: if IsLoggedIn() then Nenue@59: Nenue@59: for i, addon in ipairs(KT.initStack) do Nenue@59: print('|cFF88FF00'..tostring(addon)..'|r') Nenue@59: if initialized[addon] then Nenue@59: if not enabled[addon] then Nenue@59: print('|cFF88FF00'..tostring(addon)..'|r:variables()') Nenue@59: if addon.variables then Nenue@59: xpcall(addon.variables, LibKT_Error) Nenue@59: end Nenue@59: enabled[addon] = true Nenue@59: end Nenue@59: Nenue@59: if addon.modules and enabled[addon] then Nenue@59: for i, module in ipairs(addon.modules) do Nenue@59: print(i .. ' |cFF88FF00'..tostring(module)..'|r') Nenue@59: if not enabled[module] then Nenue@59: if module.variables then Nenue@59: print(i..' |cFF88FF00'..tostring(addon)..'|r.|cFF00FFFF'.. tostring(module)..'|r:variables()') Nenue@59: xpcall(module.variables, LibKT_Error) Nenue@59: end Nenue@59: enabled[module] = true Nenue@59: end Nenue@37: end Nenue@37: end Nenue@37: end Nenue@37: end Nenue@37: end Nenue@37: end Nenue@37: end Nenue@37: Nenue@59: --- GUI bits Nenue@59: local defaultGUIAddon = {} Nenue@59: do Nenue@59: local GetButtonTemplate = function(name, parent, template, onClick) Nenue@59: if _G[name] then Nenue@59: return _G[name] Nenue@59: end Nenue@37: Nenue@59: local button = CreateFrame('Button', name, parent, template) Nenue@59: button:RegisterForClicks('AnyUp') Nenue@59: button:SetScript('OnClick', onClick) Nenue@59: return button Nenue@37: end Nenue@37: Nenue@59: local SetButtonAnchor = function(self, collector, anchor, growth) Nenue@59: if self:GetID() == 0 then Nenue@59: self:SetID(#collector) Nenue@59: print('registered TabButton #', self:GetID()) Nenue@59: end Nenue@37: Nenue@59: if self:GetID() == 1 then Nenue@59: self:SetPoint(unpack(anchor)) Nenue@59: else Nenue@59: growth[2] = collector[self:GetID()-1] Nenue@59: self:SetPoint(unpack(growth)) Nenue@59: end Nenue@37: end Nenue@37: Nenue@59: defaultGUIAddon.tab = function(self, name, tooltip, texture, coords) Nenue@59: local button = GetButtonTemplate(name, self, 'KTTabButton', self.SelectTab) Nenue@59: button.icon:SetTexture(texture) Nenue@59: button.tooltip = tooltip Nenue@59: button:SetSize(unpack(self.tabSize)) Nenue@59: if coords then Nenue@59: button.icon:SetTexCoord(unpack(coords)) Nenue@59: end Nenue@59: SetButtonAnchor(button, self.tabButtons, self.tabAnchor, self.tabGrowth) Nenue@59: return button Nenue@59: end Nenue@59: Nenue@59: defaultGUIAddon.button = function(self, name, text, tooltip, onClick) Nenue@59: local button = GetButtonTemplate(name, self, 'KTButton', onClick) Nenue@59: Nenue@59: button.tooltip = tooltip Nenue@59: button:SetText(text) Nenue@59: button:SetWidth(max(button:GetWidth(), button:GetFontString():GetStringWidth() + 12)) Nenue@59: Nenue@59: SetButtonAnchor(button, self.controls, self.controlsAnchor, self.controlsGrowth) Nenue@59: return button Nenue@59: end Nenue@59: Nenue@59: defaultGUIAddon.uibutton = function(self, name, text, tooltip, onClick, texture, coords) Nenue@59: local button = GetButtonTemplate(name, self, 'KTUIPanelButton', onClick) Nenue@59: Nenue@59: button.tooltip = tooltip Nenue@59: button:SetText(text) Nenue@59: Nenue@59: if self.UIPanelIcon then Nenue@59: local w, h, anchor, x, y = unpack(self.UIPanelIcon) Nenue@59: button.icon:SetTexture(texture) Nenue@59: button.icon:SetSize(w, h) Nenue@59: button.icon:ClearAllPoints() Nenue@59: button.icon:SetPoint(anchor, button, anchor, x, y) Nenue@59: end Nenue@59: Nenue@59: if not self.UIPanelSize then Nenue@59: button:SetWidth(button:GetFontString():GetStringWidth() + button.icon:GetWidth()/1.5) Nenue@59: else Nenue@59: button:SetSize(unpack(self.UIPanelSize)) Nenue@59: end Nenue@59: if coords then Nenue@59: button.icon:SetTexCoord(unpack(coords)) Nenue@59: end Nenue@59: SetButtonAnchor(button, self.UIPanels, self.UIPanelAnchor, self.UIPanelGrowth) Nenue@59: return button Nenue@37: end Nenue@37: end Nenue@37: Nenue@37: Nenue@59: local defaultAddon = {} Nenue@59: do Nenue@59: defaultAddon.print = function(module, ...) Nenue@59: local msg = '|cFF00FFFF'..module:GetName()..'|r:' Nenue@59: for i = 1, select('#', ...) do Nenue@59: msg = msg .. ' ' .. tostring(select(i, ...)) Nenue@59: end Nenue@59: DEFAULT_CHAT_FRAME:AddMessage(msg) Nenue@37: end Nenue@37: Nenue@37: Nenue@37: local tickerQueue = {} Nenue@37: local ticker Nenue@59: defaultAddon.tick = function() Nenue@37: Nenue@37: if #tickerQueue == 0 then Nenue@37: ticker:Cancel() Nenue@37: ticker = nil Nenue@37: end Nenue@37: local func = tremove(tickerQueue, 1) Nenue@37: if func then Nenue@37: --print('#', #tickerQueue) Nenue@37: func() Nenue@37: end Nenue@37: end Nenue@37: Nenue@59: defaultAddon.next = function(f) Nenue@37: if not ticker then Nenue@37: --print('create ticker') Nenue@59: ticker = C_Timer.NewTicker(.001, defaultAddon.tick) Nenue@37: end Nenue@37: tinsert(tickerQueue, f) Nenue@37: Nenue@37: return #tickerQueue Nenue@59: end Nenue@59: Nenue@59: Nenue@59: --- default OnEvent Nenue@59: Nenue@59: local processing = false Nenue@59: local isHandled = false Nenue@59: local nodebug = false Nenue@59: defaultAddon.event = function (addon, event, ...) Nenue@59: if processing then Nenue@59: local args = {...} Nenue@60: C_Timer.After(0, function() LibKT_OnEvent(addon, event, unpack(args)) end) Nenue@59: return Nenue@59: else Nenue@59: Nenue@59: end Nenue@59: --- reset state Nenue@59: processing = true Nenue@59: isHandled = false Nenue@59: nodebug = false Nenue@59: Nenue@59: Nenue@59: if addon.event then Nenue@59: nodebug = addon.event(addon, event, ...) Nenue@59: elseif addon[event] then Nenue@59: nodebug = addon[event](addon, event, ...) or nodebug Nenue@59: addon.missed = 0 Nenue@59: addon.handled = addon.handled + 1 Nenue@59: isHandled = true Nenue@59: else Nenue@59: addon.firstEvent = false Nenue@59: addon.unhandled = addon.unhandled + 1 Nenue@59: addon.missed = addon.missed + 1 Nenue@59: end Nenue@59: Nenue@59: if addon.modules then Nenue@59: for i, module in ipairs(addon.modules) do Nenue@59: --print(i, module, event) Nenue@59: if module.event then Nenue@59: module.event(module, event, ...) Nenue@59: elseif module[event] then Nenue@59: nodebug = module[event](addon, event, ...) or nodebug Nenue@59: addon.missed = 0 Nenue@59: addon.handled = addon.handled + 1 Nenue@59: isHandled = true Nenue@59: else Nenue@59: addon.firstEvent = false Nenue@59: addon.unhandled = addon.unhandled + 1 Nenue@59: addon.missed = addon.missed + 1 Nenue@59: end Nenue@59: end Nenue@59: end Nenue@59: --if nodebug then Nenue@59: processing = false Nenue@59: return Nenue@59: --else Nenue@59: -- KT.UpdateEventStatus(addon, event, ...) Nenue@59: -- processing = false Nenue@59: --end Nenue@37: Nenue@37: end Nenue@59: defaultAddon.wrap = function(addon, module, name) Nenue@59: local moduleName = name or tostring(module) Nenue@59: print(addon, module) Nenue@59: print('|cFF0088FF'..tostring(addon)..'|r:wrap(|cFF00FFFF'.. moduleName .. '|r|cFFFFFF00)|r') Nenue@59: Nenue@59: addon.modules = addon.modules or {} Nenue@59: tinsert(addon.modules, module) Nenue@59: Nenue@59: Nenue@59: if (module.DEVIAN_PNAME and DEVIAN_PNAME == module.DEVIAN_PNAME) or ((not module.DEVIAN_PNAME) and DEVIAN_WORKSPACE) then Nenue@59: debuggers[module] = function(...) _G.print(moduleName, ...) end Nenue@59: else Nenue@59: debuggers[module] = noFunc Nenue@59: end Nenue@59: return debuggers[module] Nenue@59: end Nenue@59: Nenue@59: defaultAddon.GetName = function(self) return tostring(self) end Nenue@59: end Nenue@59: Nenue@59: --- Frame registration Nenue@59: KT.register = function(addon, arg, noGUI) Nenue@59: --print('register(', addon, arg, ')') Nenue@59: local name, handler Nenue@59: if type(addon) == 'string' and type(arg) == 'table' then Nenue@59: name = addon Nenue@59: -- it's a string, i.e. file vararg was passed Nenue@59: if _G[addon] then Nenue@59: -- check if it's the name of a frame and use that Nenue@59: handler = _G[addon] Nenue@59: else Nenue@59: -- re-arrange Nenue@59: handler = arg Nenue@59: end Nenue@59: else Nenue@59: handler = addon Nenue@59: assert(type(handler) == 'table', 'Usage: KT.register(name, table) or KT.register(table, plugin)') Nenue@59: end Nenue@59: Nenue@59: Nenue@59: local printName Nenue@59: local isModule Nenue@59: Nenue@59: local scriptName = debugstack(2,1,0):match(".+\\(%S+)%.lua") Nenue@59: if registeredHandles[handler] then Nenue@59: -- addon is already register; treat second argument as plugin target Nenue@59: isModule = true Nenue@59: if type(arg) == 'table' then Nenue@59: local mt = getmetatable(arg) Nenue@59: setmetatable(arg, {__index = mt.__index, __tostring = function() return scriptName end}) Nenue@59: local debugger = handler:wrap(arg) Nenue@59: return handler, debugger Nenue@59: else Nenue@59: print(' + "|cFF00FFFF'..scriptName..'|r"') Nenue@59: end Nenue@59: else Nenue@59: -- new addon Nenue@59: --local scriptName = debugstack(2,1,0):match(".+\\(%S+)%.lua") Nenue@59: local mt = getmetatable(handler) Nenue@59: local nmt = {__index = mt.__index, __tostring = function() return scriptName end } Nenue@59: handler = setmetatable(handler, nmt) Nenue@59: Embed(handler, defaultAddon) Nenue@59: name = tostring(handler) Nenue@59: registeredHandles[handler] = name Nenue@59: if handler.SetScript then Nenue@59: handler:SetScript('OnEvent', handler.event) Nenue@59: end Nenue@59: handler.unhandled = 0 Nenue@59: handler.missed = 0 Nenue@59: handler.handled = 0 Nenue@59: handler.firstEvent = false Nenue@59: handler.modules = {} Nenue@59: tinsert(KT.initStack, handler) Nenue@59: Nenue@59: if not noGUI then Nenue@59: handler.UIPanelAnchor = {'TOPLEFT', handler, 'TOPLEFT', 12, -12 } Nenue@59: handler.UIPanelGrowth = {'TOPLEFT', 'TOPRIGHT', 14, 0} Nenue@59: Embed(handler, defaultGUIAddon) Nenue@59: end Nenue@59: Nenue@59: print('|cFF0088FF'..tostring(addon)..'|r') Nenue@59: end Nenue@59: Nenue@59: local debugFunc = noFunc Nenue@59: --@debug@ Nenue@59: local debugID = isModule and name or handler Nenue@59: if (handler.DEVIAN_PNAME and DEVIAN_PNAME == handler.DEVIAN_PNAME) or ((not handler.DEVIAN_PNAME) and DEVIAN_WORKSPACE) then Nenue@59: debuggers[debugID] = debuggers[debugID] or function(...) _G.print(name, ...) end Nenue@59: debugFunc = debuggers[debugID] Nenue@59: end Nenue@59: --@end-debug@ Nenue@59: return handler, debugFunc Nenue@37: end Nenue@37: Nenue@37: KT.handler:RegisterEvent('ADDON_LOADED') Nenue@37: KT.handler:RegisterEvent('PLAYER_LOGIN') Nenue@59: KT.handler:SetScript('OnEvent', LibKT_OnEvent)