flickerstreak@24: --[[ flickerstreak@53: ReAction Action button module. flickerstreak@24: flickerstreak@24: The button module implements standard action button functionality by wrapping Blizzard's flickerstreak@77: ActionButton frame and associated functions. flickerstreak@77: flickerstreak@77: It also provides support for multiple pages (interacting with the State module) as well flickerstreak@77: as optional action remapping for possessed targets (mind control). flickerstreak@24: flickerstreak@24: --]] flickerstreak@24: flickerstreak@24: -- local imports flickerstreak@24: local ReAction = ReAction flickerstreak@24: local L = ReAction.L flickerstreak@24: local _G = _G flickerstreak@24: local CreateFrame = CreateFrame flickerstreak@24: flickerstreak@77: ReAction:UpdateRevision("$Revision: 103 $") flickerstreak@77: flickerstreak@24: -- module declaration flickerstreak@24: local moduleID = "Action" flickerstreak@28: local module = ReAction:NewModule( moduleID ) flickerstreak@24: flickerstreak@77: -- Button class declaration flickerstreak@77: local Button = { } flickerstreak@77: flickerstreak@75: -- private -- flickerstreak@75: local function RefreshLite(bar) flickerstreak@75: local btns = module.buttons[bar] flickerstreak@75: if btns then flickerstreak@75: for _, b in ipairs(btns) do flickerstreak@75: b:Refresh() flickerstreak@75: end flickerstreak@75: end flickerstreak@75: end flickerstreak@75: flickerstreak@77: -- Event handlers flickerstreak@24: function module:OnInitialize() flickerstreak@28: self.db = ReAction.db:RegisterNamespace( moduleID, flickerstreak@24: { flickerstreak@28: profile = { flickerstreak@75: buttons = { }, flickerstreak@75: bars = { }, flickerstreak@28: } flickerstreak@24: } flickerstreak@24: ) flickerstreak@24: self.buttons = { } flickerstreak@49: flickerstreak@63: ReAction:RegisterBarOptionGenerator(self, "GetBarOptions") flickerstreak@63: flickerstreak@63: ReAction.RegisterCallback(self, "OnCreateBar", "OnRefreshBar") 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@63: flickerstreak@24: end flickerstreak@24: flickerstreak@24: function module:OnEnable() flickerstreak@53: ReAction:RegisterBarType(L["Action Bar"], flickerstreak@53: { flickerstreak@53: type = moduleID, flickerstreak@53: defaultButtonSize = 36, flickerstreak@53: defaultBarRows = 1, flickerstreak@53: defaultBarCols = 12, flickerstreak@53: defaultBarSpacing = 3 flickerstreak@53: }, true) flickerstreak@24: end flickerstreak@24: flickerstreak@24: function module:OnDisable() flickerstreak@53: ReAction:UnregisterBarType(L["Action Bar"]) flickerstreak@24: end flickerstreak@24: flickerstreak@63: function module:OnRefreshBar(event, bar, name) flickerstreak@53: if bar.config.type == moduleID then flickerstreak@48: if self.buttons[bar] == nil then flickerstreak@48: self.buttons[bar] = { } flickerstreak@48: end flickerstreak@48: local btns = self.buttons[bar] flickerstreak@48: local profile = self.db.profile flickerstreak@63: if profile.buttons[name] == nil then flickerstreak@63: profile.buttons[name] = {} flickerstreak@48: end flickerstreak@75: if profile.bars[name] == nil then flickerstreak@75: profile.bars[name] = {} flickerstreak@75: end flickerstreak@63: local btnCfg = profile.buttons[name] flickerstreak@75: local barCfg = profile.bars[name] flickerstreak@24: flickerstreak@48: local r, c = bar:GetButtonGrid() flickerstreak@48: local n = r*c flickerstreak@48: for i = 1, n do flickerstreak@48: if btnCfg[i] == nil then flickerstreak@48: btnCfg[i] = {} flickerstreak@48: end flickerstreak@48: if btns[i] == nil then flickerstreak@77: local b = Button:New(bar, i, btnCfg[i], barCfg) flickerstreak@77: btns[i] = b flickerstreak@77: bar:AddButton(i,b) flickerstreak@48: end flickerstreak@24: end flickerstreak@48: for i = n+1, #btns do flickerstreak@52: if btns[i] then flickerstreak@75: bar:RemoveButton(btns[i]) flickerstreak@52: btns[i] = btns[i]:Destroy() flickerstreak@52: if btnCfg[i] then flickerstreak@52: btnCfg[i] = nil flickerstreak@52: end flickerstreak@48: end flickerstreak@24: end flickerstreak@75: RefreshLite(bar) flickerstreak@24: end flickerstreak@24: end flickerstreak@24: flickerstreak@63: function module:OnDestroyBar(event, bar, name) flickerstreak@24: if self.buttons[bar] then flickerstreak@24: local btns = self.buttons[bar] flickerstreak@24: for _,b in pairs(btns) do flickerstreak@24: if b then flickerstreak@24: b:Destroy() flickerstreak@24: end flickerstreak@24: end flickerstreak@24: self.buttons[bar] = nil flickerstreak@24: end flickerstreak@24: end flickerstreak@24: flickerstreak@63: function module:OnEraseBar(event, bar, name) flickerstreak@63: self.db.profile.buttons[name] = nil flickerstreak@75: self.db.profile.bars[name] = nil flickerstreak@24: end flickerstreak@24: flickerstreak@63: function module:OnRenameBar(event, bar, oldname, newname) flickerstreak@48: local b = self.db.profile.buttons flickerstreak@48: b[newname], b[oldname] = b[oldname], nil flickerstreak@75: flickerstreak@75: b = self.db.profile.bars flickerstreak@75: b[newname], b[oldname] = b[oldname], nil flickerstreak@48: end flickerstreak@48: flickerstreak@63: function module:OnConfigModeChanged(event, mode) flickerstreak@77: for _, bar in pairs(self.buttons) do flickerstreak@77: for _, b in pairs(bar) do flickerstreak@77: b:ShowGrid(mode) flickerstreak@77: b:ShowActionIDLabel(mode) flickerstreak@24: end flickerstreak@24: end flickerstreak@24: end flickerstreak@24: flickerstreak@50: flickerstreak@60: ---- Options ---- flickerstreak@77: local Handler = { } flickerstreak@77: flickerstreak@77: local options = { flickerstreak@77: hideEmpty = { flickerstreak@77: name = L["Hide Empty Buttons"], flickerstreak@77: desc = L["Hide buttons when empty. This option is not supported for multi-state bars"], flickerstreak@77: order = 1, flickerstreak@77: type = "toggle", flickerstreak@77: get = "GetHideEmpty", flickerstreak@77: set = "SetHideEmpty", flickerstreak@77: }, flickerstreak@77: } flickerstreak@77: flickerstreak@60: function module:GetBarOptions(bar) flickerstreak@63: return { flickerstreak@63: type = "group", flickerstreak@63: name = L["Action Buttons"], flickerstreak@77: handler = Handler:New(bar), flickerstreak@77: hidden = "Hidden", flickerstreak@77: args = options flickerstreak@63: } flickerstreak@59: end flickerstreak@59: flickerstreak@77: -- options handler private flickerstreak@77: do flickerstreak@77: local function GetBarConfig( bar ) flickerstreak@77: return module.db.profile.bars[bar:GetName()] flickerstreak@77: end flickerstreak@77: flickerstreak@77: function Handler:New(bar) flickerstreak@77: return setmetatable( { bar = bar }, { __index = Handler } ) flickerstreak@77: end flickerstreak@77: flickerstreak@77: function Handler:Hidden() flickerstreak@77: return self.bar.config.type ~= moduleID flickerstreak@77: end flickerstreak@77: flickerstreak@77: function Handler:SetHideEmpty(info, value) flickerstreak@77: local c = GetBarConfig(self.bar) flickerstreak@77: if value ~= c.hideEmpty then flickerstreak@77: for b in self.bar:IterateButtons() do flickerstreak@77: b:ShowGrid(not value) flickerstreak@77: end flickerstreak@77: c.hideEmpty = value flickerstreak@77: end flickerstreak@77: end flickerstreak@77: flickerstreak@77: function Handler:GetHideEmpty() flickerstreak@77: return GetBarConfig(self.bar).hideEmpty flickerstreak@77: end flickerstreak@77: end flickerstreak@77: flickerstreak@77: flickerstreak@77: ------ Button class ------ flickerstreak@50: flickerstreak@24: -- use-count of action IDs flickerstreak@53: local nActionIDs = 120 flickerstreak@24: local ActionIDList = setmetatable( {}, { flickerstreak@24: __index = function(self, idx) flickerstreak@24: if idx == nil then flickerstreak@53: for i = 1, nActionIDs do flickerstreak@24: if rawget(self,i) == nil then flickerstreak@24: rawset(self,i,1) flickerstreak@24: return i flickerstreak@24: end flickerstreak@24: end flickerstreak@52: error("ran out of action IDs") flickerstreak@24: else flickerstreak@24: local c = rawget(self,idx) or 0 flickerstreak@24: rawset(self,idx,c+1) flickerstreak@24: return idx flickerstreak@24: end flickerstreak@24: end, flickerstreak@24: __newindex = function(self,idx,value) flickerstreak@24: if value == nil then flickerstreak@24: value = rawget(self,idx) flickerstreak@24: if value == 1 then flickerstreak@24: value = nil flickerstreak@24: elseif value then flickerstreak@24: value = value - 1 flickerstreak@24: end flickerstreak@24: end flickerstreak@24: rawset(self,idx,value) flickerstreak@24: end flickerstreak@24: }) flickerstreak@24: flickerstreak@77: function Button:New( bar, idx, config, barConfig ) flickerstreak@77: -- create new self flickerstreak@77: self = setmetatable( { }, {__index = Button} ) flickerstreak@75: self.bar, self.idx, self.config, self.barConfig = bar, idx, config, barConfig flickerstreak@24: flickerstreak@56: config.name = config.name or ("ReAction_%s_%d"):format(bar:GetName(),idx) flickerstreak@49: self.name = config.name flickerstreak@24: config.actionID = ActionIDList[config.actionID] -- gets a free one if none configured flickerstreak@75: self.nPages = 1 flickerstreak@24: flickerstreak@77: local f = CreateFrame("CheckButton", self.name, bar:GetButtonFrame(), "ActionBarButtonTemplate") flickerstreak@24: flickerstreak@24: f:SetAttribute("action", config.actionID) flickerstreak@75: -- install mind control action support for all buttons here just for simplicity flickerstreak@75: if self.idx <= 12 then flickerstreak@75: f:SetAttribute("action-mc", 120 + self.idx) flickerstreak@75: end flickerstreak@49: flickerstreak@77: self.frame = f flickerstreak@77: self.normalTexture = getglobal(format("%sNormalTexture",f:GetName())) flickerstreak@49: flickerstreak@77: -- initialize the hide state flickerstreak@77: self:ShowGrid(not barConfig.hideEmpty) flickerstreak@77: if ReAction:GetConfigMode() then flickerstreak@77: self:ShowGrid(true) flickerstreak@49: end flickerstreak@50: flickerstreak@77: -- show the ID label if applicable flickerstreak@77: self:ShowActionIDLabel(ReAction:GetConfigMode()) flickerstreak@77: flickerstreak@77: self:Refresh() flickerstreak@77: return self flickerstreak@24: end flickerstreak@24: flickerstreak@24: function Button:Destroy() flickerstreak@24: local f = self.frame flickerstreak@24: f:UnregisterAllEvents() flickerstreak@24: f:Hide() flickerstreak@24: f:SetParent(UIParent) flickerstreak@24: f:ClearAllPoints() flickerstreak@28: if self.name then flickerstreak@28: _G[self.name] = nil flickerstreak@24: end flickerstreak@52: if self.config.actionID then flickerstreak@52: ActionIDList[self.config.actionID] = nil flickerstreak@52: end flickerstreak@75: if self.config.pages then flickerstreak@75: for _, id in ipairs(self.config.pages) do flickerstreak@75: ActionIDList[id] = nil flickerstreak@75: end flickerstreak@75: end flickerstreak@24: self.frame = nil flickerstreak@24: self.config = nil flickerstreak@24: self.bar = nil flickerstreak@24: end flickerstreak@24: flickerstreak@75: function Button:Refresh() flickerstreak@75: local f = self.frame flickerstreak@75: self.bar:PlaceButton(self, 36, 36) flickerstreak@75: if self.barConfig.mckeybinds then flickerstreak@75: f:SetAttribute("bindings-mc", self.barConfig.mckeybinds[self.idx]) flickerstreak@75: end flickerstreak@75: self:RefreshPages() flickerstreak@24: end flickerstreak@24: flickerstreak@24: function Button:GetFrame() flickerstreak@24: return self.frame flickerstreak@24: end flickerstreak@24: flickerstreak@24: function Button:GetName() flickerstreak@24: return self.name flickerstreak@24: end flickerstreak@24: flickerstreak@24: function Button:GetActionID() flickerstreak@75: return SecureButton_GetModifiedAttribute(self.frame, "action") flickerstreak@24: end flickerstreak@28: flickerstreak@75: function Button:RefreshPages() flickerstreak@75: local nPages = 1 --self.bar:GetNumPages() flickerstreak@75: if nPages ~= self.nPages then flickerstreak@75: local f = self:GetFrame() flickerstreak@75: local c = self.config.pages flickerstreak@75: if nPages > 1 and not c then flickerstreak@75: c = { } flickerstreak@75: self.config.pages = c flickerstreak@75: end flickerstreak@75: for i = 1, nPages do flickerstreak@75: c[i] = ActionIDList[c[i]] -- gets a free one if none configured flickerstreak@75: f:SetAttribute(("action-page%d"):format(i)) flickerstreak@75: end flickerstreak@75: for i = nPages+1, #c do flickerstreak@75: ActionIDList[c[i]] = nil flickerstreak@75: c[i] = nil flickerstreak@75: f:SetAttribute(("action-page%d"):format(i)) flickerstreak@75: end flickerstreak@75: flickerstreak@75: -- TODO: flickerstreak@75: -- apply next-page, prev-page, and direct-page keybinds (via bar:SetStateKeybind abstraction) flickerstreak@75: end flickerstreak@75: end flickerstreak@28: flickerstreak@77: function Button:ShowGrid( show ) flickerstreak@77: if not InCombatLockdown() then flickerstreak@77: -- new in 2.4.1: can't call ActionButton_ShowGrid/HideGrid because they won't update the attribute flickerstreak@77: local f = self.frame flickerstreak@77: local count = f:GetAttribute("showgrid") flickerstreak@77: if show then flickerstreak@77: count = count + 1 flickerstreak@77: else flickerstreak@77: count = count - 1 flickerstreak@28: end flickerstreak@77: if count < 0 then flickerstreak@77: count = 0 flickerstreak@77: end flickerstreak@77: f:SetAttribute("showgrid",count) flickerstreak@77: flickerstreak@77: if count >= 1 and not f:GetAttribute("statehidden") then flickerstreak@77: self.normalTexture:SetVertexColor(1.0, 1.0, 1.0, 0.5); flickerstreak@77: f:Show() flickerstreak@77: elseif count < 1 and not HasAction(self:GetActionID()) then flickerstreak@77: f:Hide() flickerstreak@77: end flickerstreak@28: end flickerstreak@77: end flickerstreak@77: flickerstreak@77: function Button:ShowActionIDLabel( show ) flickerstreak@77: if show then flickerstreak@77: local id = self:GetActionID() flickerstreak@77: if not self.actionIDLabel and id and id ~= 0 then flickerstreak@77: local f = self:GetFrame() flickerstreak@77: local label = f: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(id)) flickerstreak@77: self.actionIDLabel = label flickerstreak@77: f:HookScript("OnAttributeChanged", flickerstreak@77: function(frame, attr, value) flickerstreak@77: if attr == "state-parent" then flickerstreak@77: label:SetText(tostring(self:GetActionID())) flickerstreak@77: end flickerstreak@77: end) flickerstreak@77: end flickerstreak@77: self.actionIDLabel:Show() flickerstreak@77: elseif self.actionIDLabel then flickerstreak@77: self.actionIDLabel:Hide() flickerstreak@77: end flickerstreak@77: end flickerstreak@77: