diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml new file mode 100644 index 0000000..de9af6c --- /dev/null +++ b/.github/workflows/docs.yml @@ -0,0 +1,38 @@ +name: Generate docs +on: push + +permissions: + contents: write + +jobs: + docs: + runs-on: ubuntu-latest + steps: + - name: Clone project + uses: actions/checkout@v3 + + - name: Create docs directory + # lua-doc-parser should really handle this + run: | + mkdir .docs + + - name: Parse and generate docs + uses: p3lim/lua-doc-parser@v2 + with: + output: .docs + + - name: Inject docs into readme + run: | + ex README.md << EOF + /DOCSTART/+1,/DOCEND/-1 d + -1 r .docs/*.md + wq + EOF + + - name: Push docs + run: | + git config user.name 'GitHub Actions' + git config user.email noreply@github.com + git add README.md + git diff --quiet HEAD || git commit -m 'ci: update documentation' + git push diff --git a/README.md b/README.md index 89bddcc..11e7104 100644 --- a/README.md +++ b/README.md @@ -6,6 +6,19 @@ Yes, the name is a pun. Yes, you can use this if you want. No, you should not depend on it being stable. +## Documentation + +List of what this brings to the addon loading it. + +In each example `namespace` refers to the 2nd value of the addon vararg, e.g: +```lua +local _, namespace = ... +``` + + + + + ## Legal This repository is dedicated to the [public domain](https://en.wikipedia.org/wiki/Public-domain_software). diff --git a/modules/defer.lua b/modules/defer.lua index 1c0d28d..1ca02ee 100644 --- a/modules/defer.lua +++ b/modules/defer.lua @@ -49,9 +49,14 @@ local function deferMethod(object, method, ...) end end --- defer execution until after combat, or immediately if not in combat --- usage: addon:Defer(callback, arg1[, ...]) --- addon:Defer(object, 'method'[, arg1[, ...]]) +--[[ namespace:Defer(_callback_[, _..._]) +Defers a function `callback` (with optional arguments) until after combat ends. +Immediately triggers if player is not in combat. +--]] +--[[ namespace:Defer(_object_, _method_[, _..._]) +Defers a method `method` on `object` (with optional arguments) until after combat ends. +Immediately triggers if player is not in combat. +--]] function addon:Defer(...) if type(select(1, ...)) == 'table' then assert(type(select(2, ...)) == 'string', 'arg2 must be the name of a method') diff --git a/modules/event.lua b/modules/event.lua index 13237ed..309b602 100644 --- a/modules/event.lua +++ b/modules/event.lua @@ -1,5 +1,11 @@ local addonName, addon = ... +--[[ namespace.eventMixin +A multi-purpose [event](https://warcraft.wiki.gg/wiki/Events)-[mixin](https://en.wikipedia.org/wiki/Mixin). + +These methods are also available as methods directly on `namespace`. +--]] + local eventHandler = CreateFrame('Frame') local callbacks = {} @@ -37,6 +43,10 @@ local function IsUnitValid(unit) end local eventMixin = {} +--[[ namespace.eventMixin:RegisterEvent(_event_, _callback_) +Registers a [frame `event`](https://warcraft.wiki.gg/wiki/Events) with the `callback` function. +If the callback returns positive it will be unregistered. +--]] function eventMixin:RegisterEvent(event, callback) assert(IsEventValid(event), 'arg1 must be an event') assert(type(callback) == 'function', 'arg2 must be a function') @@ -55,6 +65,9 @@ function eventMixin:RegisterEvent(event, callback) end end +--[[ namespace.eventMixin:UnregisterEvent(_event_, _callback_) +Unregisters a [frame `event`](https://warcraft.wiki.gg/wiki/Events) from the `callback` function. +--]] function eventMixin:UnregisterEvent(event, callback) assert(IsEventValid(event), 'arg1 must be an event') assert(type(callback) == 'function', 'arg2 must be a function') @@ -73,6 +86,9 @@ function eventMixin:UnregisterEvent(event, callback) end end +--[[ namespace.eventMixin:IsEventRegistered(_event_, _callback_) +Checks if the [frame `event`](https://warcraft.wiki.gg/wiki/Events) is registered with the `callback` function. +--]] function eventMixin:IsEventRegistered(event, callback) assert(IsEventValid(event), 'arg1 must be an event') assert(type(callback) == 'function', 'arg2 must be a function') @@ -86,6 +102,10 @@ function eventMixin:IsEventRegistered(event, callback) end end +--[[ namespace.eventMixin:TriggerEvent(_event_[, _..._]) +Manually trigger the `event` (with optional arguments) on all registered callbacks. +If the callback returns positive it will be unregistered. +--]] function eventMixin:TriggerEvent(event, ...) if callbacks[event] then for _, data in next, callbacks[event] do @@ -120,6 +140,10 @@ local function getUnitEventHandler(unit) end local unitEventCallbacks = {} +--[[ namespace.eventMixin:RegisterUnitEvent(_event_, _unit_[, _unitN,..._], _callback_) +Registers a [`unit`](https://warcraft.wiki.gg/wiki/UnitId)-specific [frame `event`](https://warcraft.wiki.gg/wiki/Events) with the `callback` function. +If the callback returns positive it will be unregistered for that unit. +--]] function eventMixin:RegisterUnitEvent(event, ...) assert(IsEventValid(event), 'arg1 must be an event') local callback = select(select('#', ...), ...) @@ -152,6 +176,9 @@ function eventMixin:RegisterUnitEvent(event, ...) end end +--[[ namespace.eventMixin:UnregisterUnitEvent(_event_, _unit_[, _unitN,..._], _callback_) +Unregisters a [`unit`](https://warcraft.wiki.gg/wiki/UnitId)-specific [frame `event`](https://warcraft.wiki.gg/wiki/Events) from the `callback` function. +--]] function eventMixin:UnregisterUnitEvent(event, ...) assert(IsEventValid(event), 'arg1 must be an event') local callback = select(select('#', ...), ...) @@ -177,6 +204,9 @@ function eventMixin:UnregisterUnitEvent(event, ...) end end +--[[ namespace.eventMixin:IsUnitEventRegistered(_event_, _unit_[, _unitN,..._], _callback_) +Checks if the [`unit`](https://warcraft.wiki.gg/wiki/UnitId)-specific [frame `event`](https://warcraft.wiki.gg/wiki/Events) is registered with the `callback` function. +--]] function eventMixin:IsUnitEventRegistered(event, ...) assert(IsEventValid(event), 'arg1 must be an event') local callback = select(select('#', ...), ...) @@ -197,6 +227,10 @@ function eventMixin:IsUnitEventRegistered(event, ...) end end +--[[ namespace.eventMixin:TriggerEvent(_event_, _unit_[, _unitN,..._][, _..._]) +Manually trigger the [`unit`](https://warcraft.wiki.gg/wiki/UnitId)-specific `event` (with optional arguments) on all registered callbacks. +If the callback returns positive it will be unregistered. +--]] function eventMixin:TriggerUnitEvent(event, unit, ...) if unitEventCallbacks[unit] and unitEventCallbacks[unit][event] then for _, data in next, unitEventCallbacks[unit][event] do @@ -213,25 +247,10 @@ end -- special handling for combat events local combatEventCallbacks = {} -do - local function internalTrigger(_, event, _, ...) - if combatEventCallbacks[event] then - for _, data in next, combatEventCallbacks[event] do - local successful, ret = pcall(data.callback, data.owner, ...) - if not successful then - error(ret) - elseif ret then - eventMixin.UnregisterCombatEvent(data.owner, event, data.callback) - end - end - end - end - - function eventMixin:TriggerCombatEvent() - internalTrigger(CombatLogGetCurrentEventInfo()) - end -end - +--[[ namespace.eventMixin:RegisterCombatEvent(_subEvent_, _callback_) +Registers a [combat `subEvent`](https://warcraft.wiki.gg/wiki/COMBAT_LOG_EVENT) with the `callback` function. +If the callback returns positive it will be unregistered. +--]] function eventMixin:RegisterCombatEvent(event, callback) assert(type(event) == 'string', 'arg1 must be a string') assert(type(callback) == 'function', 'arg2 must be a function') @@ -250,6 +269,9 @@ function eventMixin:RegisterCombatEvent(event, callback) end end +--[[ namespace.eventMixin:UnregisterCombatEvent(_subEvent_, _callback_) +Unregisters a [combat `subEvent`](https://warcraft.wiki.gg/wiki/COMBAT_LOG_EVENT) from the `callback` function. +--]] function eventMixin:UnregisterCombatEvent(event, callback) assert(type(event) == 'string', 'arg1 must be a string') assert(type(callback) == 'function', 'arg2 must be a function') @@ -264,26 +286,48 @@ function eventMixin:UnregisterCombatEvent(event, callback) end end +--[[ namespace.eventMixin:TriggerCombatEvent(_subEvent_) +Manually trigger the [combat `subEvent`](https://warcraft.wiki.gg/wiki/COMBAT_LOG_EVENT) on all registered callbacks. +If the callback returns positive it will be unregistered. + +* Note: this is pretty useless on it's own, and should only ever be triggered by the event system. +--]] +do + local function internalTrigger(_, event, _, ...) + if combatEventCallbacks[event] then + for _, data in next, combatEventCallbacks[event] do + local successful, ret = pcall(data.callback, data.owner, ...) + if not successful then + error(ret) + elseif ret then + eventMixin.UnregisterCombatEvent(data.owner, event, data.callback) + end + end + end + end + + function eventMixin:TriggerCombatEvent() + internalTrigger(CombatLogGetCurrentEventInfo()) + end +end + -- expose mixin addon.eventMixin = eventMixin -- anonymous event registration addon = setmetatable(addon, { - __index = function(t, key) - if IsEventValid(key) then - -- addon:EVENT_NAME([arg1[, ...]]) - return function(_, ...) - eventMixin.TriggerEvent(t, key, ...) - end - else - -- default table behaviour - return rawget(t, key) - end - end, __newindex = function(t, key, value) if key == 'OnLoad' then - -- addon:OnLoad() = function() end - -- shorthand for ADDON_LOADED + --[[ namespace:OnLoad() + Shorthand for the [`ADDON_LOADED`](https://warcraft.wiki.gg/wiki/ADDON_LOADED) for the addon. + + Usage: + ```lua + function namespace:OnLoad() + -- I'm loaded! + end + ``` + --]] addon:RegisterEvent('ADDON_LOADED', function(self, name) if name == addonName then local successful, ret = pcall(value, self) @@ -295,13 +339,40 @@ addon = setmetatable(addon, { end end) elseif IsEventValid(key) then - -- addon:EVENT_NAME(...) = function() end + --[[ namespace:_event_ + Registers a to an anonymous function. + + Usage: + ```lua + function namespace:BAG_UPDATE(bagID) + -- do something + end + ``` + --]] eventMixin.RegisterEvent(t, key, value) else -- default table behaviour rawset(t, key, value) end end, + __index = function(t, key) + if IsEventValid(key) then + --[[ namespace:_event_([_..._]) + Manually trigger all registered anonymous `event` callbacks, with optional arguments. + + Usage: + ```lua + namespace:BAG_UPDATE(1) -- triggers the above example + ``` + --]] + return function(_, ...) + eventMixin.TriggerEvent(t, key, ...) + end + else + -- default table behaviour + return rawget(t, key) + end + end, }) -- mixin to namespace diff --git a/modules/input.lua b/modules/input.lua index ad83c92..b421f30 100644 --- a/modules/input.lua +++ b/modules/input.lua @@ -1,5 +1,15 @@ local addonName, addon = ... +--[[ namespace:RegisterSlash(_command_[, _commandN,..._], _callback_) +Registers chat slash `command`(s) with a `callback` function. + +Usage: +```lua +namespace:RegisterSlash('/hello', '/hi', function(input) + print('Hi') +end) +``` +--]] function addon:RegisterSlash(...) local name = addonName .. 'Slash' .. math.random() local failed diff --git a/modules/load.lua b/modules/load.lua index 6eed1e6..b6a068c 100644 --- a/modules/load.lua +++ b/modules/load.lua @@ -3,6 +3,9 @@ local _, addon = ... local IsAddOnLoaded = C_AddOns and C_AddOns.IsAddOnLoaded or IsAddOnLoaded local addonCallbacks = {} +--[[ namespace:HookAddOn(_addonName_, _callback_) +Registers a hook for when an addon with the name `addonName` loads with a `callback` function. +--]] function addon:HookAddOn(addonName, callback) if IsAddOnLoaded(addonName) then callback(self) @@ -23,6 +26,16 @@ function addon:ADDON_LOADED(addonName) end function addon:PLAYER_LOGIN() + --[[ namespace:OnLogin() + Shorthand for the [`PLAYER_LOGIN`](https://warcraft.wiki.gg/wiki/PLAYER_LOGIN). + + Usage: + ```lua + function namespace:OnLogin() + -- player has logged in! + end + ``` + --]] if addon.OnLogin then addon:OnLogin() end diff --git a/modules/locale.lua b/modules/locale.lua index 77fef65..1a03611 100644 --- a/modules/locale.lua +++ b/modules/locale.lua @@ -4,9 +4,25 @@ local _, addon = ... local localizations = {} local locale = GetLocale() --- usage: --- set: addon.L('deDE')['New string'] = 'Neue Saite' --- get: addon.L['New string'] +--[[ namespace.L(_locale_)[`string`] +Sets a localization `string` for the given `locale`. + +Usage: +```lua +local L = namespace.L('deDE') +L['New string'] = 'Neue saite' +``` +--]] +--[[ namespace.L[`string`] +Reads a localized `string` for the active locale. +If a localized string for the active locale is not available the `string` will be read back. + +Usage: +```lua +print(namespace.L['New string']) --> "Neue saite" on german clients, "New string" on all others +print(namespace.L['Unknown']) --> "Unknown" on all clients since there are no localizations +``` +--]] addon.L = setmetatable({}, { __index = function(_, key) local localeTable = localizations[locale] diff --git a/modules/misc.lua b/modules/misc.lua index b3e2366..7ff5c0a 100644 --- a/modules/misc.lua +++ b/modules/misc.lua @@ -2,14 +2,23 @@ local _, addon = ... -- game version API local _, _, _, interfaceVersion = GetBuildInfo() +--[[ namespace:IsRetail() +Checks if the current client is running the "retail" version. +--]] function addon:IsRetail() return interfaceVersion >= 100000 end +--[[ namespace:IsClassic() +Checks if the current client is running the "classic" version. +--]] function addon:IsClassic() return interfaceVersion >= 20000 and interfaceVersion < 100000 end +--[[ namespace:IsClassicEra() +Checks if the current client is running the "classic era" version (e.g. vanilla). +--]] function addon:IsClassicEra() return interfaceVersion < 20000 end @@ -18,6 +27,10 @@ end local hidden = CreateFrame('Frame') hidden:Hide() +--[[ namespace:Hide(_object_[, _child_,...]) +Forcefully hide an `object`, or its `child`. +It will recurse down to the last child if provided. +--]] function addon:Hide(object, ...) if type(object) == 'string' then object = _G[object] @@ -43,11 +56,17 @@ end -- random utilities do local GUID_PATTERN = '%w+%-.-%-.-%-.-%-.-%-(.-)%-' + --[[ namespace:ExtractIDFromGUID(_guid_) + Returns the integer `id` from the given [`guid`](https://warcraft.wiki.gg/wiki/GUID). + --]] function addon:ExtractIDFromGUID(guid) return tonumber(guid:match(GUID_PATTERN)) end end +--[[ namespace:GetNPCID(_unit_) +Returns the integer `id` of the given [`unit`](https://warcraft.wiki.gg/wiki/UnitId). +--]] function addon:GetNPCID(unit) if unit then local npcGUID = UnitGUID(unit) @@ -57,16 +76,37 @@ end do local ITEM_LINK_FORMAT = '|Hitem:%d|h' + --[[ namespace:GetItemLinkFromID(_itemID_) + Generates an [item link](https://warcraft.wiki.gg/wiki/ItemLink) from an `itemID`. + This is a crude generation and won't have valid data for complex items. + --]] function addon:GetItemLinkFromID(itemID) return ITEM_LINK_FORMAT:format(itemID) end end +--[[ namespace:GetPlayerMapID() +Returns the ID of the current map the zone the player is located in. +--]] function addon:GetPlayerMapID() -- TODO: maybe use HBD data if it's available return C_Map.GetBestMapForUnit('player') or -1 end +--[[ namespace:GetPlayerPosition(_mapID_) +Returns the `x` and `y` coordinates for the player in the given `mapID` (if they are valid). +--]] +function addon:GetPlayerPosition(mapID) + local pos = C_Map.GetPlayerMapPosition(mapID, 'player') + if pos then + return pos:GetXY() + end +end + +--[[ namespace:tsize(_table_) +Returns the number of entries in the `table`. +Works for associative tables as opposed to `#table`. +--]] function addon:tsize(tbl) -- would really like Lua 5.2 for this local size = 0 @@ -90,10 +130,10 @@ do return token end - --[[ addon:GetUnitAura(_unitID_, _spellID_, _filter_) - Returns the aura by spellID on the unit, if it exists. + --[[ namespace:GetUnitAura(_unit_, _spellID_, _filter_) + Returns the aura by `spellID` on the [`unit`](https://warcraft.wiki.gg/wiki/UnitId), if it exists. - * [`unitID`](https://wowpedia.fandom.com/wiki/UnitId) + * [`unitID`](https://warcraft.wiki.gg/wiki/UnitId) * `spellID` - spell ID to check for * `filter` - aura filter, see [UnitAura](https://warcraft.wiki.gg/wiki/API_UnitAura#Filters) --]] @@ -106,8 +146,3 @@ do return data end end - -function addon:GetPlayerPosition(mapID) - local pos = C_Map.GetPlayerMapPosition(mapID, 'player') - return pos and pos:GetXY() -end diff --git a/modules/output.lua b/modules/output.lua index eceb180..9d0477f 100644 --- a/modules/output.lua +++ b/modules/output.lua @@ -1,5 +1,8 @@ local addonName, addon = ... +--[[ namespace:Print(_..._) +Prints out a message in the chat frame, prefixed with the addon name in color. +--]] function addon:Print(...) -- can't use string join, it fails on nil values local msg = '' @@ -11,14 +14,23 @@ function addon:Print(...) DEFAULT_CHAT_FRAME:AddMessage('|cff33ff99' .. addonName .. '|r: ' .. msg:trim()) end +--[[ namespace:Printf(_fmt_, _..._) +Wrapper for `namespace:Print(...)` and `string.format`. +--]] function addon:Printf(fmt, ...) self:Print(fmt:format(...)) end +--[[ namespace:Dump(_object_[, _startKey_]) +Wrapper for `DevTools_Dump`. +--]] function addon:Dump(value, startKey) DevTools_Dump(value, startKey) end +--[[ namespace:DumpUI(_object_) +Similar to `namespace:Dump(object)`; a wrapper for the graphical version. +--]] function addon:DumpUI(value) UIParentLoadAddOn('Blizzard_DebugTools') DisplayTableInspectorWindow(value) diff --git a/modules/settings/panel.lua b/modules/settings/panel.lua index 6dece47..0edee22 100644 --- a/modules/settings/panel.lua +++ b/modules/settings/panel.lua @@ -61,6 +61,46 @@ local function internalRegisterSettings(savedvariable, settings) end local isRegistered +--[[ namespace:RegisterSettings(_savedvariables_, _settings_) +Registers a set of `settings` with the interface options panel. +The values will be stored by the `settings`' objects' `key` in `savedvariables`. + +Should be used with the options methods below. + +Usage: +```lua +namespace:RegisterSettings('MyAddOnDB', { + { + key = 'myToggle', + title = 'My Toggle', + tooltip = 'Longer description of the toggle in a tooltip', + default = false, + } + { + key = 'mySlider', + type = 'slider', + title = 'My Slider', + tooltip = 'Longer description of the slider in a tooltip', + default = 0.5, + minValue = 0.1, + maxValue = 1.0, + valueStep = 0.01, + valueFormat = formatter, -- callback function or a string for string.format + }, + { + key = 'myMenu', + type = 'menu', + title = 'My Menu', + tooltip = 'Longer description of the menu in a tooltip', + options = { + key1 = 'First option', + key2 = 'Second option', + key3 = 'Third option', + } + } +}) +``` +--]] function addon:RegisterSettings(savedvariable, settings) assert(not isRegistered, "can't register settings more than once") isRegistered = true @@ -80,6 +120,9 @@ function addon:RegisterSettings(savedvariable, settings) end end +--[[ namespace:RegisterSettingsSlash(_..._) +Wrapper for `namespace:RegisterSlash(...)`, except the callback is provided and will open the interface options for this addon. +--]] function addon:RegisterSettingsSlash(...) -- gotta do this dumb shit because `..., callback` is not valid Lua local data = {...} diff --git a/modules/settings/savedvariables.lua b/modules/settings/savedvariables.lua index 1df8157..a3723c9 100644 --- a/modules/settings/savedvariables.lua +++ b/modules/settings/savedvariables.lua @@ -2,41 +2,12 @@ local addonName, addon = ... -- callback system that addons can use to detect when the player changes -- any setting, but also used internally to update the settings panel -function addon:RegisterOptionCallback(key, callback, src) - if not self.callbacks then - self.callbacks = {} - end - if not self.callbacks[key] then - self.callbacks[key] = {} - end - - table.insert(self.callbacks[key], callback) -end - -function addon:TriggerOptionCallbacks(key, value) - if self.callbacks and self.callbacks[key] then - for _, callback in next, self.callbacks[key] do - callback(value) - end - end -end - -function addon:GetOption(key) - assert(self:AreOptionsLoaded(), "options aren't loaded") - return _G[self.optionsName][key] or self.optionsDefaults[key] -end - -function addon:GetOptionDefault(key) - return self.optionsDefaults and self.optionsDefaults[key] -end - -function addon:SetOption(key, value) - assert(self:AreOptionsLoaded(), "options aren't loaded") - _G[self.optionsName][key] = value - self:TriggerOptionCallbacks(key, value) -end +--[[ namespace:LoadOptions(_savedvariables_, _defaults_) +Loads a set of `savedvariables`, with `defaults` being set if they don't exist. +Will trigger `namespace:TriggerOptionCallback(key, value)` for each pair. +--]] function addon:LoadOptions(savedvariable, defaults) assert(not self.optionsName, "can't load options more than once") @@ -61,7 +32,60 @@ function addon:LoadOptions(savedvariable, defaults) end end +--[[ namespace:GetOption(_key_) +Returns the value for the given option `key`. +--]] +function addon:GetOption(key) + assert(self:AreOptionsLoaded(), "options aren't loaded") + return _G[self.optionsName][key] or self.optionsDefaults[key] +end + +--[[ namespace:GetOptionDefault(_key_) +Returns the default value for the given option `key`. +--]] +function addon:GetOptionDefault(key) + return self.optionsDefaults and self.optionsDefaults[key] +end + +--[[ namespace:SetOption(_key_, _value_) +Sets a new `value` to the given options `key`. +--]] +function addon:SetOption(key, value) + assert(self:AreOptionsLoaded(), "options aren't loaded") + _G[self.optionsName][key] = value + self:TriggerOptionCallbacks(key, value) +end + +--[[ namespace:AreOptionsLoaded() +Checks to see if the savedvariables has been loaded in the game. +--]] function addon:AreOptionsLoaded() -- prevent API from trying to retrieve or set options before they are loaded return self.optionsName and _G[self.optionsName] ~= nil end + +--[[ namespace:RegisterOptionCallback(_key_, _callback_) +Register a `callback` function with the option `key`. +--]] +function addon:RegisterOptionCallback(key, callback) + if not self.callbacks then + self.callbacks = {} + end + + if not self.callbacks[key] then + self.callbacks[key] = {} + end + + table.insert(self.callbacks[key], callback) +end + +--[[ namespace:TriggerOptionCallbacks(_key_, _value_) +Trigger all registered option callbacks for the given `key`, supplying the `value`. +--]] +function addon:TriggerOptionCallbacks(key, value) + if self.callbacks and self.callbacks[key] then + for _, callback in next, self.callbacks[key] do + callback(value) + end + end +end diff --git a/modules/widgets.lua b/modules/widgets.lua index 0bf778f..7c8378c 100644 --- a/modules/widgets.lua +++ b/modules/widgets.lua @@ -1,5 +1,8 @@ local _, addon = ... +--[[ namespace:CreateFrame(_..._) +A wrapper for [`CreateFrame`](https://warcraft.wiki.gg/wiki/API_CreateFrame), mixed in with `namespace.eventMixin`. +--]] function addon:CreateFrame(...) return Mixin(CreateFrame(...), addon.eventMixin) end @@ -20,6 +23,10 @@ local function onCVarUpdate(self, cvar) end end +--[[ namespace:CreateButton(...) +A wrapper for `namespace:CreateFrame(...)`, but will handle key direction preferences of the client. +Use this specifically to create clickable buttons. +--]] function addon:CreateButton(...) local button = addon:CreateFrame(...) button:RegisterEvent('CVAR_UPDATE', onCVarUpdate)