Nenue@5: --[[ Nenue@5: -- KrakynTools Nenue@5: -- AddOn prototyping library. Nenue@5: -- Nenue@5: -- Implements Nenue@5: -- KT.register(frame) to hook the following (all optional): Nenue@5: -- frame:init() run immediately after KT sets itself up Nenue@5: -- frame:profile("Name-TruncatedRealm") called the first time SavedVars data becomes available Nenue@5: -- frame:variables() called upon variables being available Nenue@5: -- frame:event(event, ...) replaces the event callback Nenue@5: -- frame:ui() called by /ui when activating Nenue@5: -- Nenue@5: -- frame:tab(name, tooltip, texture, coord) Nenue@5: -- produces a serial button that changes display tabs Nenue@5: -- Nenue@5: -- frame:button(name, text, tooltip, onClick) Nenue@5: -- produces a button with OnClick script Nenue@5: -- Nenue@5: -- frame:uibutton(name, text, tooltip, onClick, texture, coord) Nenue@5: -- produces a header button with desired onClick Nenue@5: -- Nenue@5: ]]-- Nenue@5: Nenue@5: local LIBKT_MAJOR, LIBKT_MINOR = "LibKraken", 1 Nenue@5: local KT = LibStub:NewLibrary(LIBKT_MAJOR, LIBKT_MINOR) Nenue@5: Nenue@5: --GLOBALS: LibKT, KrakTool, KTErrorFrame, LibKTError, SlashCmdList, SLASH_RL1, SLASH_UI1 Nenue@5: local CreateFrame, debugstack, tostring, select = CreateFrame, debugstack, tostring, select Nenue@5: local print, max, unpack, tinsert = print, max, unpack, tinsert Nenue@5: local ipairs, xpcall, next, safecall = ipairs, xpcall, next, safecall Nenue@5: local UI_TOGGLE = false Nenue@5: local db Nenue@5: Nenue@5: Nenue@5: KT.handler = CreateFrame('Frame', 'LibKTHostFrame', UIParent) Nenue@5: KT.addons = {} Nenue@5: KT.initStack = {} Nenue@5: KT.varsStack = {} Nenue@5: local print = DEVIAN_WORKSPACE and function(...) print('LKT', ...) end or function() end Nenue@5: local registeredHandles = {} Nenue@5: Nenue@5: --- /rl Nenue@5: -- ReloadUI shortcut Nenue@5: SLASH_RL1 = "/rl" Nenue@5: SlashCmdList.RL = function () Nenue@5: ReloadUI() Nenue@5: end Nenue@5: Nenue@5: --- /kv addon ... Nenue@5: -- Dumps table values from "addon", using the arguments as index values. Nenue@5: -- Numerics are converted, and names that end with "()" will be called as such Nenue@5: SLASH_KV1 = "/kv" Nenue@5: SlashCmdList.KV = function (editbox, input) Nenue@5: Nenue@5: end Nenue@5: Nenue@5: Nenue@5: --- /ui Nenue@5: -- Run any addon:ui() methods Nenue@5: SLASH_UI1 = "/ui" Nenue@5: SlashCmdList.UI = function () Nenue@5: if UI_TOGGLE then Nenue@5: UI_TOGGLE = false Nenue@5: else Nenue@5: UI_TOGGLE = true Nenue@5: end Nenue@5: for i, frame in pairs(KT.frames) do Nenue@5: if UI_TOGGLE then Nenue@5: if frame.close then Nenue@5: frame.close() Nenue@5: else Nenue@5: frame:Hide() Nenue@5: end Nenue@5: else Nenue@5: if frame.ui then Nenue@5: frame.ui() Nenue@5: end Nenue@5: frame:Show() Nenue@5: end Nenue@5: end Nenue@5: end Nenue@5: Nenue@5: LibKTError = function(msg) Nenue@5: local dstack = debugstack(2) Nenue@5: :gsub("Interface\\AddOns\\",'') Nenue@5: :gsub("<(.-)>", function(a) return '|cFF00FFFF<'.. a ..'>|r' end) Nenue@5: Nenue@5: Nenue@5: Nenue@5: KTErrorFrame.errmsg:SetText(msg) Nenue@5: KTErrorFrame.debugstack:SetText(dstack) Nenue@5: KTErrorFrame:SetHeight(KTErrorFrame.debugstack:GetStringHeight() + KTErrorFrame.errmsg:GetStringHeight() + 12) Nenue@5: KTErrorFrame:Show() Nenue@5: end Nenue@5: Nenue@5: Nenue@5: local pending = {} Nenue@5: local processing = false Nenue@5: local isHandled = false Nenue@5: local nodebug = false Nenue@5: function KT.OnEvent (addon, event, ...) Nenue@5: if processing then Nenue@5: local args = {...} Nenue@5: C_Timer.After(0, function() KT.OnEvent(addon, event, unpack(args)) end) Nenue@5: return Nenue@5: else Nenue@5: Nenue@5: end Nenue@5: --- reset state Nenue@5: processing = true Nenue@5: isHandled = false Nenue@5: nodebug = false Nenue@5: Nenue@5: Nenue@5: if addon.event then Nenue@5: nodebug = addon.event(addon, event, ...) Nenue@5: end Nenue@5: Nenue@5: if addon[event] then Nenue@5: nodebug = addon[event](addon, event, ...) or nodebug Nenue@5: addon.missed = 0 Nenue@5: addon.handled = addon.handled + 1 Nenue@5: isHandled = true Nenue@5: else Nenue@5: addon.firstEvent = false Nenue@5: addon.unhandled = addon.unhandled + 1 Nenue@5: addon.missed = addon.missed + 1 Nenue@5: end Nenue@5: Nenue@5: for i, module in ipairs(addon.modules) do Nenue@5: --print(i, module) Nenue@5: if module[event] then Nenue@5: nodebug = module[event](addon, event, ...) or nodebug Nenue@5: addon.missed = 0 Nenue@5: addon.handled = addon.handled + 1 Nenue@5: isHandled = true Nenue@5: else Nenue@5: addon.firstEvent = false Nenue@5: addon.unhandled = addon.unhandled + 1 Nenue@5: addon.missed = addon.missed + 1 Nenue@5: end Nenue@5: Nenue@5: end Nenue@5: if nodebug then Nenue@5: processing = false Nenue@5: return Nenue@5: else Nenue@5: KT.UpdateEventStatus(addon, event, ...) Nenue@5: processing = false Nenue@5: end Nenue@5: Nenue@5: end Nenue@5: Nenue@5: KT.UpdateEventStatus = function(addon, event, ...) Nenue@5: print(addon:GetName(), event, ...) Nenue@5: Nenue@5: -- debug outputs Nenue@5: if addon.status then Nenue@5: addon.status:SetText(event .. '\n|cFF00FF00' .. addon.handled .. '|r |cFFFF8800' .. addon.missed .. '|r |cFFFF4400' .. addon.unhandled .. '|r') Nenue@5: if isHandled then Nenue@5: addon.status:SetTextColor(0,1,0) Nenue@5: if addon.log then Nenue@5: local logtext = event Nenue@5: for i = 1, select('#',...) do Nenue@5: logtext = logtext .. '\n' .. i .. ':' .. tostring(select(i,...)) Nenue@5: end Nenue@5: addon.log:SetText('|cFFFFFF00last|r\n' .. logtext) Nenue@5: local newWidth = addon.log:GetStringWidth() Nenue@5: Nenue@5: if addon.logfirst then Nenue@5: if not addon.firstEvent then Nenue@5: addon.firstEvent = event Nenue@5: addon.logfirst:SetText('|cFF00FF88first|r\n' .. logtext) Nenue@5: end Nenue@5: Nenue@5: newWidth = newWidth + addon.logfirst:GetStringWidth() Nenue@5: end Nenue@5: if addon.logdiff then Nenue@5: if not event ~= addon.firstEvent then Nenue@5: addon.firstEvent = event Nenue@5: addon.logdiff:SetText('|cFF0088FFdiff|r\n' .. logtext) Nenue@5: end Nenue@5: newWidth = newWidth + addon.logdiff:GetStringWidth() Nenue@5: end Nenue@5: --addon:SetWidth(newWidth) Nenue@5: end Nenue@5: else Nenue@5: addon.status:SetTextColor(1,0,0) Nenue@5: end Nenue@5: end Nenue@5: end Nenue@5: Nenue@7: KT.register = function(addon, nameOrModule, noGUI) Nenue@7: local name Nenue@5: if registeredHandles[addon] then Nenue@7: if type(nameOrModule) == 'table' then Nenue@7: tinsert(addon.modules, nameOrModule) Nenue@7: name = debugstack(2,1,0):match(".+\\(%S+)%.lua") Nenue@7: Nenue@7: print('auto-resolved module name', name, tostring(nameOrModule)) Nenue@7: else Nenue@7: name = nameOrModule Nenue@7: end Nenue@5: else Nenue@7: if not nameOrModule then Nenue@5: assert(type(addon) == 'table', 'Need a valid table.') Nenue@5: if addon.GetName then Nenue@5: name = addon:GetName() Nenue@5: else Nenue@7: name = debugstack(2,1,0):match(".+AddOns\\(%S+)\\") Nenue@7: print('auto-resolved addon name', name, tostring(nameOrModule)) Nenue@5: end Nenue@5: assert(type(name) == 'string', 'Unable to resolve a valid stub name.') Nenue@5: end Nenue@5: -- if calling again, assume name is a file handle Nenue@5: Nenue@5: registeredHandles[addon] = name Nenue@5: KT.addons[name] = addon Nenue@5: if addon.SetScript then Nenue@5: addon:SetScript('OnEvent', KT.OnEvent) Nenue@5: end Nenue@5: addon.unhandled = 0 Nenue@5: addon.missed = 0 Nenue@5: addon.handled = 0 Nenue@5: addon.firstEvent = false Nenue@5: addon.modules = {} Nenue@5: tinsert(KT.initStack, addon) Nenue@5: tinsert(KT.varsStack, addon) Nenue@5: Nenue@5: if addon.GetName and (not noGUI) then Nenue@5: addon.UIPanelAnchor = {'TOPLEFT', addon, 'TOPLEFT', 12, -12 } Nenue@5: addon.UIPanelGrowth = {'TOPLEFT', 'TOPRIGHT', 14, 0} Nenue@5: addon.button = KT.button Nenue@5: addon.uibutton = KT.uibutton Nenue@5: addon.tab = KT.tab Nenue@5: addon.print = KT.print Nenue@5: end Nenue@5: end Nenue@5: Nenue@14: return addon, (DEVIAN_WORKSPACE and function(...) _G.print(name, ...) end or function() end), KT.wrap Nenue@5: end Nenue@5: Nenue@5: Nenue@5: Nenue@5: local onEvent = function(self, event, arg1) Nenue@5: if (event == 'ADDON_LOADED' and arg1 ~= 'Blizzard_DebugTools') or event == 'PLAYER_LOGIN' then Nenue@5: -- run any init blocks left in the queue Nenue@5: while #KT.initStack >= 1 do Nenue@5: local addon = tremove(KT.initStack, 1) Nenue@5: print('KT', addon:GetName(), 'init') Nenue@5: if addon.init then Nenue@5: xpcall(addon.init, LibKTError) Nenue@5: for i, module in ipairs(addon.modules) do Nenue@5: if module.init then Nenue@5: xpcall(module.init, LibKTError) Nenue@5: end Nenue@5: end Nenue@5: end Nenue@5: end Nenue@5: Nenue@5: -- run any variables blocks if player variables are ready Nenue@5: if IsLoggedIn() and #KT.varsStack >= 1 then Nenue@5: while #KT.varsStack >= 1 do Nenue@5: local addon = tremove(KT.varsStack, 1) Nenue@5: print(addon:GetName()) Nenue@5: if addon.variables then Nenue@5: xpcall(addon.variables, LibKTError) Nenue@5: for i, module in ipairs(addon.modules) do Nenue@5: if module.variables then Nenue@5: xpcall(module.variables, LibKTError) Nenue@5: end Nenue@5: end Nenue@5: end Nenue@5: end Nenue@5: end Nenue@5: end Nenue@5: end Nenue@5: Nenue@5: KT.print = function(module, ...) Nenue@5: local msg = '|cFF00FFFF'..module:GetName()..'|r:' Nenue@5: for i = 1, select('#', ...) do Nenue@5: msg = msg .. ' ' .. tostring(select(i, ...)) Nenue@5: end Nenue@5: DEFAULT_CHAT_FRAME:AddMessage(msg) Nenue@5: end Nenue@5: Nenue@5: --- Button generators Nenue@5: Nenue@5: local GetButtonTemplate = function(name, parent, template, onClick) Nenue@5: if _G[name] then Nenue@5: return _G[name] Nenue@5: end Nenue@5: Nenue@5: local button = CreateFrame('Button', name, parent, template) Nenue@5: button:RegisterForClicks('AnyUp') Nenue@5: button:SetScript('OnClick', onClick) Nenue@5: return button Nenue@5: end Nenue@5: Nenue@5: local SetButtonAnchor = function(self, collector, anchor, growth) Nenue@5: if self:GetID() == 0 then Nenue@5: self:SetID(#collector) Nenue@5: print('registered TabButton #', self:GetID()) Nenue@5: end Nenue@5: Nenue@5: if self:GetID() == 1 then Nenue@5: self:SetPoint(unpack(anchor)) Nenue@5: else Nenue@5: growth[2] = collector[self:GetID()-1] Nenue@5: self:SetPoint(unpack(growth)) Nenue@5: end Nenue@5: end Nenue@5: Nenue@5: KT.tab = function(self, name, tooltip, texture, coords) Nenue@5: local button = GetButtonTemplate(name, self, 'KTTabButton', self.SelectTab) Nenue@5: button.icon:SetTexture(texture) Nenue@5: button.tooltip = tooltip Nenue@5: button:SetSize(unpack(self.tabSize)) Nenue@5: if coords then Nenue@5: button.icon:SetTexCoord(unpack(coords)) Nenue@5: end Nenue@5: SetButtonAnchor(button, self.tabButtons, self.tabAnchor, self.tabGrowth) Nenue@5: return button Nenue@5: end Nenue@5: Nenue@5: KT.button = function(self, name, text, tooltip, onClick) Nenue@5: local button = GetButtonTemplate(name, self, 'KTButton', onClick) Nenue@5: Nenue@5: button.tooltip = tooltip Nenue@5: button:SetText(text) Nenue@5: button:SetWidth(max(button:GetWidth(), button:GetFontString():GetStringWidth() + 12)) Nenue@5: Nenue@5: SetButtonAnchor(button, self.controls, self.controlsAnchor, self.controlsGrowth) Nenue@5: return button Nenue@5: end Nenue@5: Nenue@5: KT.uibutton = function(self, name, text, tooltip, onClick, texture, coords) Nenue@5: local button = GetButtonTemplate(name, self, 'KTUIPanelButton', onClick) Nenue@5: Nenue@5: button.tooltip = tooltip Nenue@5: button:SetText(text) Nenue@5: Nenue@5: if self.UIPanelIcon then Nenue@5: local w, h, anchor, x, y = unpack(self.UIPanelIcon) Nenue@5: button.icon:SetTexture(texture) Nenue@5: button.icon:SetSize(w, h) Nenue@5: button.icon:ClearAllPoints() Nenue@5: button.icon:SetPoint(anchor, button, anchor, x, y) Nenue@5: end Nenue@5: Nenue@5: if not self.UIPanelSize then Nenue@5: button:SetWidth(button:GetFontString():GetStringWidth() + button.icon:GetWidth()/1.5) Nenue@5: else Nenue@5: button:SetSize(unpack(self.UIPanelSize)) Nenue@5: end Nenue@5: if coords then Nenue@5: button.icon:SetTexCoord(unpack(coords)) Nenue@5: end Nenue@5: SetButtonAnchor(button, self.UIPanels, self.UIPanelAnchor, self.UIPanelGrowth) Nenue@5: return button Nenue@5: end Nenue@5: Nenue@14: --- Co-routine Handler kajigger Nenue@14: do Nenue@14: local tickerQueue = {} Nenue@14: local ticker Nenue@14: local instant = false Nenue@14: KT.tick = function() Nenue@14: Nenue@14: if #tickerQueue == 0 then Nenue@14: ticker:Cancel() Nenue@14: ticker = nil Nenue@14: end Nenue@14: local func = tremove(tickerQueue, 1) Nenue@14: if func then Nenue@14: --print('#', #tickerQueue) Nenue@14: func() Nenue@14: end Nenue@14: end Nenue@14: Nenue@14: KT.wrap = function(f) Nenue@14: if not ticker then Nenue@14: --print('create ticker') Nenue@14: ticker = C_Timer.NewTicker(.001, KT.tick) Nenue@14: end Nenue@14: tinsert(tickerQueue, f) Nenue@14: Nenue@14: return #tickerQueue Nenue@14: Nenue@14: end Nenue@14: end Nenue@5: Nenue@5: KT.handler:RegisterEvent('ADDON_LOADED') Nenue@5: KT.handler:RegisterEvent('PLAYER_LOGIN') Nenue@5: KT.handler:SetScript('OnEvent', onEvent)