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@77: PetActionButton frame and associated functions. 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@80: ReAction:UpdateRevision("$Revision$") flickerstreak@77: flickerstreak@93: -- libraries flickerstreak@93: local KB = LibStub("LibKeyBound-1.0") flickerstreak@93: flickerstreak@28: -- module declaration flickerstreak@28: local moduleID = "PetAction" flickerstreak@28: local module = ReAction:NewModule( moduleID ) flickerstreak@28: flickerstreak@77: -- Button class declaration flickerstreak@77: local Button = { } flickerstreak@77: 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@63: flickerstreak@63: ReAction:RegisterBarOptionGenerator(self, "GetBarOptions") flickerstreak@63: flickerstreak@63: ReAction.RegisterCallback(self, "OnCreateBar") flickerstreak@63: ReAction.RegisterCallback(self, "OnDestroyBar") flickerstreak@63: ReAction.RegisterCallback(self, "OnRefreshBar") flickerstreak@63: ReAction.RegisterCallback(self, "OnEraseBar") flickerstreak@63: ReAction.RegisterCallback(self, "OnRenameBar") flickerstreak@63: ReAction.RegisterCallback(self, "OnConfigModeChanged") flickerstreak@93: flickerstreak@93: KB.RegisterCallback(self, "LIBKEYBOUND_ENABLED") flickerstreak@93: KB.RegisterCallback(self, "LIBKEYBOUND_DISABLED") flickerstreak@93: KB.RegisterCallback(self, "LIBKEYBOUND_MODE_COLOR_CHANGED","LIBKEYBOUND_ENABLED") 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@63: function module:OnCreateBar(event, bar, name) flickerstreak@53: if bar.config.type == moduleID then flickerstreak@53: -- auto show/hide when pet exists flickerstreak@53: bar:GetFrame():SetAttribute("unit","pet") flickerstreak@90: if not ReAction:GetConfigMode() then flickerstreak@90: RegisterUnitWatch(bar:GetFrame()) flickerstreak@90: end flickerstreak@63: self:OnRefreshBar(event, bar, name) flickerstreak@28: end flickerstreak@28: end flickerstreak@28: flickerstreak@63: function module:OnRefreshBar(event, bar, name) 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@63: if profile.buttons[name] == nil then flickerstreak@63: profile.buttons[name] = {} flickerstreak@53: end flickerstreak@63: local btnCfg = profile.buttons[name] 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@94: local success, r = pcall(Button.New,Button,bar,i,btnCfg[i]) flickerstreak@94: if success and r then flickerstreak@94: btns[i] = r flickerstreak@94: bar:AddButton(i,r) flickerstreak@94: else flickerstreak@94: n = i - 1 flickerstreak@94: bar:ClipNButtons(n) flickerstreak@94: break flickerstreak@94: end flickerstreak@53: end flickerstreak@77: btns[i]:Refresh() flickerstreak@53: end flickerstreak@53: for i = n+1, #btns do flickerstreak@53: if btns[i] then flickerstreak@77: bar:RemoveButton(btns[i]) 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@63: function module:OnDestroyBar(event, bar, name) 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@63: function module:OnEraseBar(event, bar, name) flickerstreak@63: self.db.profile.buttons[name] = nil flickerstreak@53: end flickerstreak@53: flickerstreak@63: function module:OnRenameBar(event, bar, 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@63: function module:OnConfigModeChanged(event, mode) flickerstreak@77: for _, buttons in pairs(self.buttons) do flickerstreak@77: for _, b in pairs(buttons) do flickerstreak@77: b:ShowActionIDLabel(mode) flickerstreak@77: end flickerstreak@77: end flickerstreak@63: for _, bar in ReAction:IterateBars() do flickerstreak@53: if bar and self.buttons[bar] then 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@93: function module:LIBKEYBOUND_ENABLED(evt) flickerstreak@93: for _, buttons in pairs(self.buttons) do flickerstreak@93: for _, b in pairs(buttons) do flickerstreak@93: b:SetKeybindMode(true) flickerstreak@93: end flickerstreak@93: end flickerstreak@93: end flickerstreak@93: flickerstreak@93: function module:LIBKEYBOUND_DISABLED(evt) flickerstreak@93: for _, buttons in pairs(self.buttons) do flickerstreak@93: for _, b in pairs(buttons) do flickerstreak@93: b:SetKeybindMode(false) flickerstreak@93: end flickerstreak@93: end flickerstreak@93: end flickerstreak@93: flickerstreak@53: flickerstreak@60: ---- Options ---- flickerstreak@60: function module:GetBarOptions(bar) flickerstreak@91: if bar.config.type == moduleID then flickerstreak@91: return { flickerstreak@91: type = "group", flickerstreak@91: name = L["Pet Buttons"], flickerstreak@91: args = { flickerstreak@91: } flickerstreak@60: } flickerstreak@91: end flickerstreak@59: end flickerstreak@59: flickerstreak@53: flickerstreak@28: flickerstreak@77: ------ Button class ------ flickerstreak@77: 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@93: local trash = CreateFrame("Frame") flickerstreak@93: local KBAttach, GetActionName, GetHotkey, SetKey, FreeKey, ClearBindings, GetBindings flickerstreak@93: do flickerstreak@93: local buttonLookup = setmetatable({},{__mode="kv"}) flickerstreak@93: flickerstreak@93: -- Use KeyBound-1.0 for binding, but use Override bindings instead of flickerstreak@93: -- regular bindings to support multiple profile use. This is a little flickerstreak@93: -- weird with the KeyBound dialog box (which has per-char selector as well flickerstreak@93: -- as an OK/Cancel box) but it's the least amount of effort to implement. flickerstreak@93: function GetActionName(f) flickerstreak@93: local b = buttonLookup[f] flickerstreak@93: if b then flickerstreak@93: return format("%s:%s", b.bar:GetName(), b.idx) flickerstreak@93: end flickerstreak@93: end flickerstreak@93: flickerstreak@93: function GetHotkey(f) flickerstreak@93: local b = buttonLookup[f] flickerstreak@93: if b then flickerstreak@93: return KB:ToShortKey(b:GetConfig().hotkey) flickerstreak@93: end flickerstreak@93: end flickerstreak@93: flickerstreak@93: function SetKey(f, key) flickerstreak@93: local b = buttonLookup[f] flickerstreak@93: if b then flickerstreak@93: local c = b:GetConfig() flickerstreak@93: if c.hotkey then flickerstreak@93: SetOverrideBinding(f, false, c.hotkey, nil) flickerstreak@93: end flickerstreak@93: if key then flickerstreak@93: SetOverrideBindingClick(f, false, key, f:GetName(), nil) flickerstreak@93: end flickerstreak@93: c.hotkey = key flickerstreak@93: b:DisplayHotkey(GetHotkey(f)) flickerstreak@93: end flickerstreak@93: end flickerstreak@93: flickerstreak@93: function FreeKey(f, key) flickerstreak@93: local b = buttonLookup[f] flickerstreak@93: if b then flickerstreak@93: local c = b:GetConfig() flickerstreak@93: if c.hotkey == key then flickerstreak@93: local action = f:GetActionName() flickerstreak@93: SetOverrideBinding(f, false, c.hotkey, nil) flickerstreak@93: c.hotkey = nil flickerstreak@93: b:DisplayHotkey(nil) flickerstreak@93: return action flickerstreak@93: end flickerstreak@93: end flickerstreak@93: return ReAction:FreeOverrideHotkey(key) flickerstreak@93: end flickerstreak@93: flickerstreak@93: function ClearBindings(f) flickerstreak@93: SetKey(f, nil) flickerstreak@93: end flickerstreak@93: flickerstreak@93: function GetBindings(f) flickerstreak@93: local b = buttonLookup[f] flickerstreak@93: if b then flickerstreak@93: return b:GetConfig().hotkey flickerstreak@93: end flickerstreak@93: end flickerstreak@93: flickerstreak@93: function KBAttach( button ) flickerstreak@93: local f = button:GetFrame() flickerstreak@93: f.GetActionName = GetActionName flickerstreak@93: f.GetHotkey = GetHotkey flickerstreak@93: f.SetKey = SetKey flickerstreak@93: f.FreeKey = FreeKey flickerstreak@93: f.ClearBindings = ClearBindings flickerstreak@93: f.GetBindings = GetBindings flickerstreak@93: buttonLookup[f] = button flickerstreak@93: f:SetKey(button:GetConfig().hotkey) flickerstreak@93: ReAction:RegisterKeybindFrame(f) flickerstreak@93: if ReAction:GetKeybindMode() then flickerstreak@93: button.border:SetVertexColor(KB:GetColorKeyBoundMode()) flickerstreak@93: button.border:Show() flickerstreak@93: end flickerstreak@93: end flickerstreak@93: end flickerstreak@93: flickerstreak@90: local meta = { __index = Button } flickerstreak@28: flickerstreak@77: function Button:New( bar, idx, config ) flickerstreak@77: -- create new self flickerstreak@90: self = setmetatable( flickerstreak@90: { flickerstreak@90: bar = bar, flickerstreak@90: idx = idx, flickerstreak@90: config = config, flickerstreak@90: }, meta ) flickerstreak@28: flickerstreak@85: local name = config.name or ("ReAction_%s_%s_%d"):format(bar:GetName(),moduleID,idx) flickerstreak@53: config.name = name flickerstreak@77: self.name = 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@90: -- doesn't overwrite existing globals. Can't set them to nil in the global table, flickerstreak@90: -- as it causes taint. flickerstreak@90: local parent = bar:GetFrame() flickerstreak@53: local f = frameRecycler[name] flickerstreak@53: if f then flickerstreak@77: f:SetParent(parent) flickerstreak@53: else flickerstreak@77: f = CreateFrame("CheckButton", name, parent, "PetActionButtonTemplate") flickerstreak@93: -- ditch the old hotkey text because it's tied in ActionButton_Update() to the flickerstreak@93: -- standard binding. We use override bindings. flickerstreak@93: local hotkey = _G[name.."HotKey"] flickerstreak@93: hotkey:SetParent(trash) flickerstreak@93: hotkey = f:CreateFontString(nil, "ARTWORK", "NumberFontNormalSmallGray") flickerstreak@93: hotkey:SetWidth(36) flickerstreak@93: hotkey:SetHeight(18) flickerstreak@93: hotkey:SetJustifyH("RIGHT") flickerstreak@93: hotkey:SetJustifyV("TOP") flickerstreak@93: hotkey:SetPoint("TOPLEFT",f,"TOPLEFT",-2,-2) flickerstreak@93: f.hotkey = hotkey flickerstreak@93: f:HookScript("OnDragStart", function() self:Update() end) flickerstreak@93: f:HookScript("OnReceiveDrag", function() self:Update() end) 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@93: self.frame = f flickerstreak@93: self.icon = _G[("%sIcon"):format(name)] flickerstreak@93: self.acTex = _G[("%sAutoCastable"):format(name)] flickerstreak@93: self.acModel = _G[("%sShine"):format(name)] flickerstreak@53: self.cooldown = _G[("%sCooldown"):format(name)] flickerstreak@93: self.hotkey = f.hotkey flickerstreak@93: self.border = _G[("%sBorder"):format(name)] flickerstreak@53: flickerstreak@53: flickerstreak@77: 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@93: KBAttach(self) flickerstreak@88: flickerstreak@77: self:Refresh() flickerstreak@93: self:SetKeybindMode(ReAction:GetKeybindMode()) flickerstreak@93: flickerstreak@77: return self 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@77: function Button:Refresh() flickerstreak@77: self.bar:PlaceButton(self, 30, 30) flickerstreak@53: self:Update() flickerstreak@53: self:UpdateHotkey() flickerstreak@94: self.frame:Show() 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@88: function Button:GetConfig() flickerstreak@88: return self.config flickerstreak@88: end flickerstreak@88: 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@91: AutoCastShine_AutoCastStart(self.acModel) flickerstreak@53: else flickerstreak@91: AutoCastShine_AutoCastStop(self.acModel) flickerstreak@53: end flickerstreak@53: flickerstreak@53: if texture then flickerstreak@91: if GetPetActionSlotUsable(id) 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@93: self:DisplayHotkey(GetHotkey(self.frame)) flickerstreak@53: end flickerstreak@28: flickerstreak@77: function Button:ShowActionIDLabel(show) flickerstreak@77: if show then flickerstreak@77: -- store the action ID label in the frame due to frame recycling flickerstreak@77: if not self.actionIDLabel and self:GetActionID() then flickerstreak@77: local label = self.frame:CreateFontString(nil,"OVERLAY","GameFontNormalLarge") flickerstreak@77: label:SetAllPoints() flickerstreak@77: label:SetJustifyH("CENTER") flickerstreak@77: label:SetShadowColor(0,0,0,1) flickerstreak@77: label:SetShadowOffset(2,-2) flickerstreak@77: label:SetText(tostring(self:GetActionID())) flickerstreak@77: self.actionIDLabel = label flickerstreak@28: end flickerstreak@77: self.actionIDLabel:Show() flickerstreak@77: elseif self.actionIDLabel then flickerstreak@77: self.actionIDLabel:Hide() flickerstreak@28: end flickerstreak@77: end flickerstreak@93: flickerstreak@93: flickerstreak@93: function Button:SetKeybindMode(mode) flickerstreak@93: if mode then flickerstreak@93: self.border:SetVertexColor(KB:GetColorKeyBoundMode()) flickerstreak@93: self.border:Show() flickerstreak@93: else flickerstreak@93: self.border:Hide() flickerstreak@93: end flickerstreak@93: end flickerstreak@93: flickerstreak@93: function Button:DisplayHotkey( key ) flickerstreak@93: self.hotkey:SetText(key or "") flickerstreak@93: end