flickerstreak@28: --[[ flickerstreak@53: ReAction Pet Action button module flickerstreak@53: flickerstreak@53: The button module implements standard action button functionality by wrapping Blizzard's flickerstreak@53: PetActionButton frame and associated functions. It also provides some button layout flickerstreak@53: modification tools. flickerstreak@28: flickerstreak@28: --]] flickerstreak@28: flickerstreak@28: -- local imports flickerstreak@28: local ReAction = ReAction flickerstreak@28: local L = ReAction.L flickerstreak@28: local _G = _G flickerstreak@53: local CreateFrame = CreateFrame flickerstreak@28: flickerstreak@28: -- module declaration flickerstreak@28: local moduleID = "PetAction" flickerstreak@28: local module = ReAction:NewModule( moduleID ) flickerstreak@28: flickerstreak@28: -- module methods flickerstreak@28: function module:OnInitialize() flickerstreak@53: self.db = ReAction.db:RegisterNamespace( moduleID, flickerstreak@28: { flickerstreak@28: profile = { flickerstreak@28: buttons = { } flickerstreak@28: } flickerstreak@28: } flickerstreak@28: ) flickerstreak@53: self.buttons = { } flickerstreak@60: self.options = setmetatable({},{__mode="k"}) flickerstreak@28: end flickerstreak@28: flickerstreak@28: function module:OnEnable() flickerstreak@53: ReAction:RegisterBarType(L["Pet Action Bar"], flickerstreak@53: { flickerstreak@53: type = moduleID , flickerstreak@53: defaultButtonSize = 30, flickerstreak@53: defaultBarRows = 1, flickerstreak@53: defaultBarCols = 10, flickerstreak@53: defaultBarSpacing = 8 flickerstreak@53: }) flickerstreak@28: end flickerstreak@28: flickerstreak@28: function module:OnDisable() flickerstreak@53: ReAction:UnregisterBarType(L["Pet Action Bar"]) flickerstreak@28: end flickerstreak@28: flickerstreak@53: function module:ApplyToBar(bar) flickerstreak@53: if bar.config.type == moduleID then flickerstreak@53: -- auto show/hide when pet exists flickerstreak@53: bar:GetFrame():SetAttribute("unit","pet") flickerstreak@53: RegisterUnitWatch(bar:GetFrame()) flickerstreak@53: self:RefreshBar(bar) flickerstreak@28: end flickerstreak@28: end flickerstreak@28: flickerstreak@53: function module:RefreshBar(bar) flickerstreak@53: if bar.config.type == moduleID then flickerstreak@53: if self.buttons[bar] == nil then flickerstreak@53: self.buttons[bar] = { } flickerstreak@53: end flickerstreak@53: local btns = self.buttons[bar] flickerstreak@53: local profile = self.db.profile flickerstreak@53: local barName = bar:GetName() flickerstreak@53: if profile.buttons[barName] == nil then flickerstreak@53: profile.buttons[barName] = {} flickerstreak@53: end flickerstreak@53: local btnCfg = profile.buttons[barName] flickerstreak@53: flickerstreak@53: local r, c = bar:GetButtonGrid() flickerstreak@53: local n = r*c flickerstreak@53: for i = 1, n do flickerstreak@53: if btnCfg[i] == nil then flickerstreak@53: btnCfg[i] = {} flickerstreak@53: end flickerstreak@53: if btns[i] == nil then flickerstreak@53: local ok, b = pcall(self.BtnClass.new, self.BtnClass, bar, i, btnCfg[i]) flickerstreak@53: if ok and b then flickerstreak@53: btns[i] = b flickerstreak@53: end flickerstreak@53: else flickerstreak@53: btns[i]:Refresh(bar,i) flickerstreak@53: end flickerstreak@53: end flickerstreak@53: for i = n+1, #btns do flickerstreak@53: if btns[i] then flickerstreak@53: btns[i] = btns[i]:Destroy() flickerstreak@53: if btnCfg[i] then flickerstreak@53: btnCfg[i] = nil flickerstreak@53: end flickerstreak@53: end flickerstreak@53: end flickerstreak@53: end flickerstreak@53: end flickerstreak@53: flickerstreak@53: function module:RemoveFromBar(bar) flickerstreak@53: if self.buttons[bar] then flickerstreak@53: local btns = self.buttons[bar] flickerstreak@53: for _,b in pairs(btns) do flickerstreak@53: if b then flickerstreak@53: b:Destroy() flickerstreak@53: end flickerstreak@53: end flickerstreak@53: self.buttons[bar] = nil flickerstreak@53: end flickerstreak@53: end flickerstreak@53: flickerstreak@53: function module:EraseBarConfig(barName) flickerstreak@53: self.db.profile.buttons[barName] = nil flickerstreak@53: end flickerstreak@53: flickerstreak@53: function module:RenameBarConfig(oldname, newname) flickerstreak@53: local b = self.db.profile.buttons flickerstreak@53: b[newname], b[oldname] = b[oldname], nil flickerstreak@53: end flickerstreak@53: flickerstreak@53: flickerstreak@53: function module:ApplyConfigMode(mode,bars) flickerstreak@53: for _, bar in pairs(bars) do flickerstreak@53: if bar and self.buttons[bar] then flickerstreak@53: for _, b in pairs(self.buttons[bar]) do flickerstreak@53: if b then flickerstreak@53: if mode then flickerstreak@53: self:showActionIDLabel(b) flickerstreak@53: else flickerstreak@53: self:hideActionIDLabel(b) flickerstreak@53: end flickerstreak@53: end flickerstreak@53: end flickerstreak@53: local f = bar:GetFrame() flickerstreak@53: if mode then flickerstreak@53: UnregisterUnitWatch(f) flickerstreak@53: f:Show() flickerstreak@53: else flickerstreak@53: RegisterUnitWatch(f) flickerstreak@53: end flickerstreak@53: end flickerstreak@53: end flickerstreak@53: end flickerstreak@53: flickerstreak@53: function module:showActionIDLabel(button) flickerstreak@53: -- store the action ID label in the frame due to frame recycling flickerstreak@53: if not button:GetFrame().actionIDLabel and button:GetActionID() then flickerstreak@53: local label = button:GetFrame():CreateFontString(nil,"OVERLAY","GameFontNormalLarge") flickerstreak@53: label:SetAllPoints() flickerstreak@53: label:SetJustifyH("CENTER") flickerstreak@53: label:SetShadowColor(0,0,0,1) flickerstreak@53: label:SetShadowOffset(2,-2) flickerstreak@53: label:SetText(tostring(button:GetActionID())) flickerstreak@53: button:GetFrame().actionIDLabel = label flickerstreak@53: end flickerstreak@53: button:GetFrame().actionIDLabel:Show() flickerstreak@53: end flickerstreak@53: flickerstreak@53: function module:hideActionIDLabel(button) flickerstreak@53: if button:GetFrame().actionIDLabel then flickerstreak@53: button:GetFrame().actionIDLabel:Hide() flickerstreak@53: end flickerstreak@53: end flickerstreak@53: flickerstreak@60: ---- Options ---- flickerstreak@60: function module:GetBarOptions(bar) flickerstreak@60: if not self.options[bar] then flickerstreak@60: self.options[bar] = { flickerstreak@60: type = "group", flickerstreak@60: name = L["Pet Buttons"], flickerstreak@60: hidden = function() return bar.config.type ~= moduleID end, flickerstreak@60: args = { flickerstreak@60: } flickerstreak@60: } flickerstreak@60: end flickerstreak@60: return self.options[bar] flickerstreak@59: end flickerstreak@59: flickerstreak@53: flickerstreak@28: flickerstreak@28: -- use-count of action IDs flickerstreak@53: local nActionIDs = NUM_PET_ACTION_SLOTS flickerstreak@28: local ActionIDList = setmetatable( {}, { flickerstreak@28: __index = function(self, idx) flickerstreak@28: if idx == nil then flickerstreak@53: for i = 1, nActionIDs do flickerstreak@28: if rawget(self,i) == nil then flickerstreak@28: rawset(self,i,1) flickerstreak@28: return i flickerstreak@28: end flickerstreak@28: end flickerstreak@53: error("ran out of pet action IDs") flickerstreak@28: else flickerstreak@28: local c = rawget(self,idx) or 0 flickerstreak@28: rawset(self,idx,c+1) flickerstreak@28: return idx flickerstreak@28: end flickerstreak@28: end, flickerstreak@28: __newindex = function(self,idx,value) flickerstreak@28: if value == nil then flickerstreak@28: value = rawget(self,idx) flickerstreak@28: if value == 1 then flickerstreak@28: value = nil flickerstreak@28: elseif value then flickerstreak@28: value = value - 1 flickerstreak@28: end flickerstreak@28: end flickerstreak@28: rawset(self,idx,value) flickerstreak@28: end flickerstreak@28: }) flickerstreak@28: flickerstreak@53: local frameRecycler = {} flickerstreak@28: flickerstreak@53: flickerstreak@53: ------ Button class ------ flickerstreak@28: local Button = { } flickerstreak@28: flickerstreak@28: local function Constructor( self, bar, idx, config ) flickerstreak@28: self.bar, self.idx, self.config = bar, idx, config flickerstreak@28: flickerstreak@28: local barFrame = bar:GetFrame() flickerstreak@28: flickerstreak@56: local name = config.name or ("ReAction_%s_Pet_%d"):format(bar:GetName(),idx) flickerstreak@53: config.name = name flickerstreak@53: self.name = config.name flickerstreak@28: config.actionID = ActionIDList[config.actionID] -- gets a free one if none configured flickerstreak@28: flickerstreak@53: -- have to recycle frames with the same name: flickerstreak@53: -- otherwise you either get references to old textures because named CreateFrame() flickerstreak@53: -- doesn't overwrite existing globals (below) flickerstreak@53: -- or, if you set them to nil in the global table, you get taint because of the flickerstreak@53: -- crappy PetActionBar code. flickerstreak@53: local f = frameRecycler[name] flickerstreak@53: if f then flickerstreak@53: f:SetParent(barFrame) flickerstreak@53: f:Show() flickerstreak@53: else flickerstreak@53: f = CreateFrame("CheckButton", name, barFrame, "PetActionButtonTemplate") flickerstreak@53: end flickerstreak@53: if config.actionID then flickerstreak@53: f:SetID(config.actionID) -- PetActionButtonTemplate isn't a proper SecureActionButton flickerstreak@53: end flickerstreak@53: f:SetFrameStrata("MEDIUM") flickerstreak@53: flickerstreak@28: barFrame:SetAttribute("addchild",f) flickerstreak@28: flickerstreak@53: self.frame = f flickerstreak@53: self.icon = _G[("%sIcon"):format(name)] flickerstreak@53: self.acTex = _G[("%sAutoCastable"):format(name)] flickerstreak@53: self.acModel = _G[("%sAutoCast"):format(name)] flickerstreak@53: self.cooldown = _G[("%sCooldown"):format(name)] flickerstreak@53: self.hotkey = _G[("%sHotKey"):format(name)] flickerstreak@53: flickerstreak@53: f:HookScript("OnDragStart", function() self:Update() end) flickerstreak@53: f:HookScript("OnReceiveDrag", function() self:Update() end) flickerstreak@53: flickerstreak@53: f:RegisterEvent("PLAYER_CONTROL_LOST"); flickerstreak@53: f:RegisterEvent("PLAYER_CONTROL_GAINED"); flickerstreak@53: f:RegisterEvent("PLAYER_FARSIGHT_FOCUS_CHANGED"); flickerstreak@53: f:RegisterEvent("UNIT_PET"); flickerstreak@53: f:RegisterEvent("UNIT_FLAGS"); flickerstreak@53: f:RegisterEvent("UNIT_AURA"); flickerstreak@53: f:RegisterEvent("PET_BAR_UPDATE"); flickerstreak@53: f:RegisterEvent("PET_BAR_UPDATE_COOLDOWN"); flickerstreak@53: flickerstreak@53: f:SetScript("OnEvent", flickerstreak@53: function(event,arg1) flickerstreak@53: if event =="PET_BAR_UPDATE_COOLDOWN" then flickerstreak@53: self:UpdateCooldown() flickerstreak@53: elseif event == "UPDATE_BINDINGS" then flickerstreak@53: self:UpdateHotkey() flickerstreak@53: else flickerstreak@53: self:Update() flickerstreak@53: end flickerstreak@53: end) flickerstreak@28: flickerstreak@28: self:Refresh(bar,idx) flickerstreak@28: end flickerstreak@28: flickerstreak@28: function Button:Destroy() flickerstreak@28: local f = self.frame flickerstreak@28: f:UnregisterAllEvents() flickerstreak@28: f:Hide() flickerstreak@28: f:SetParent(UIParent) flickerstreak@28: f:ClearAllPoints() flickerstreak@28: if self.name then flickerstreak@53: frameRecycler[self.name] = f flickerstreak@28: _G[self.name] = nil flickerstreak@28: end flickerstreak@53: if self.config.actionID then flickerstreak@53: ActionIDList[self.config.actionID] = nil flickerstreak@53: end flickerstreak@28: self.frame = nil flickerstreak@28: self.config = nil flickerstreak@28: self.bar = nil flickerstreak@28: end flickerstreak@28: flickerstreak@53: function Button:Refresh(bar,idx) flickerstreak@53: bar:PlaceButton(self.frame, idx, 30, 30) flickerstreak@53: self:Update() flickerstreak@53: self:UpdateHotkey() flickerstreak@53: end flickerstreak@53: flickerstreak@53: function Button:GetFrame() flickerstreak@53: return self.frame flickerstreak@53: end flickerstreak@53: flickerstreak@53: function Button:GetName() flickerstreak@53: return self.name flickerstreak@53: end flickerstreak@53: flickerstreak@53: function Button:GetActionID() flickerstreak@53: return self.config.actionID flickerstreak@53: end flickerstreak@53: flickerstreak@53: function Button:Update() flickerstreak@53: local id = self.frame:GetID() flickerstreak@53: local name, subtext, texture, isToken, isActive, autoCastAllowed, autoCastEnabled = GetPetActionInfo(id); flickerstreak@53: local f = self.frame flickerstreak@53: --ReAction:Print(("id %d: '%s', '%s', '%s', '%s', '%s', '%s', '%s'"):format(tostring(id), tostring(name),tostring(subtext),tostring(texture),tostring(isToken),tostring(isActive),tostring(autoCastAllowed),tostring(autoCastEnabled))) flickerstreak@53: flickerstreak@53: if isToken then flickerstreak@53: self.icon:SetTexture(_G[texture]); flickerstreak@53: f.tooltipName = _G[name]; flickerstreak@53: else flickerstreak@53: self.icon:SetTexture(texture); flickerstreak@53: f.tooltipName = name; flickerstreak@53: end flickerstreak@53: flickerstreak@53: f.isToken = isToken; flickerstreak@53: f.tooltipSubtext = subtext; flickerstreak@53: f:SetChecked( isActive and 1 or 0); flickerstreak@53: flickerstreak@53: if autoCastAllowed then flickerstreak@53: self.acTex:Show(); flickerstreak@53: else flickerstreak@53: self.acTex:Hide(); flickerstreak@53: end flickerstreak@53: flickerstreak@53: if autoCastEnabled then flickerstreak@53: self.acModel:Show(); flickerstreak@53: else flickerstreak@53: self.acModel:Hide(); flickerstreak@53: end flickerstreak@53: flickerstreak@53: if texture then flickerstreak@53: if GetPetActionsUsable() then flickerstreak@53: SetDesaturation(self.icon,nil) flickerstreak@53: else flickerstreak@53: SetDesaturation(self.icon,1) flickerstreak@53: end flickerstreak@53: self.icon:Show(); flickerstreak@53: f:SetNormalTexture("Interface\\Buttons\\UI-Quickslot2"); flickerstreak@53: else flickerstreak@53: self.icon:Hide(); flickerstreak@53: f:SetNormalTexture("Interface\\Buttons\\UI-Quickslot"); flickerstreak@53: end flickerstreak@53: flickerstreak@53: self:UpdateCooldown() flickerstreak@53: end flickerstreak@53: flickerstreak@53: function Button:UpdateCooldown() flickerstreak@53: local start, duration, enable = GetPetActionCooldown(self.frame:GetID()); flickerstreak@53: CooldownFrame_SetTimer(self.cooldown, start, duration, enable); flickerstreak@53: end flickerstreak@53: flickerstreak@53: function Button:UpdateHotkey() flickerstreak@53: flickerstreak@53: end flickerstreak@28: flickerstreak@28: -- export as a class-factory to module flickerstreak@28: module.BtnClass = { flickerstreak@28: new = function(self, ...) flickerstreak@28: local x = { } flickerstreak@28: for k,v in pairs(Button) do flickerstreak@28: x[k] = v flickerstreak@28: end flickerstreak@28: Constructor(x, ...) flickerstreak@28: return x flickerstreak@28: end flickerstreak@28: }