flickerstreak@25: local ReAction = ReAction flickerstreak@25: local L = ReAction.L flickerstreak@25: local _G = _G flickerstreak@25: local CreateFrame = CreateFrame flickerstreak@33: local floor = math.floor flickerstreak@75: local fmod = math.fmod flickerstreak@75: local format = string.format flickerstreak@71: local SecureStateHeader_Refresh = SecureStateHeader_Refresh flickerstreak@33: flickerstreak@77: ReAction:UpdateRevision("$Revision$") flickerstreak@25: flickerstreak@75: flickerstreak@28: ------ BAR CLASS ------ flickerstreak@28: local Bar = { _classID = {} } flickerstreak@77: ReAction.Bar = Bar -- export to ReAction flickerstreak@25: flickerstreak@77: function Bar:New( name, config ) flickerstreak@77: -- create new self flickerstreak@77: self = setmetatable( { }, {__index = Bar} ) flickerstreak@25: if type(config) ~= "table" then flickerstreak@28: error("ReAction.Bar: config table required") flickerstreak@25: end flickerstreak@75: config.width = config.width or 480 flickerstreak@75: config.height = config.height or 40 flickerstreak@75: flickerstreak@75: self.name, self.config = name, config flickerstreak@75: self.buttons = setmetatable({},{__mode="k"}) flickerstreak@75: self.statedrivers = { } flickerstreak@75: self.keybinds = { } flickerstreak@25: flickerstreak@54: local parent = config.parent and (ReAction:GetBar(config.parent) or _G[config.parent]) or UIParent flickerstreak@75: local f = CreateFrame("Button",name and format("ReAction-%s",name),parent,"SecureStateHeaderTemplate, SecureActionButtonTemplate") flickerstreak@77: f:SetFrameStrata("MEDIUM") flickerstreak@77: f:SetWidth(config.width) flickerstreak@77: f:SetWidth(config.height) flickerstreak@77: f:Show() flickerstreak@75: flickerstreak@75: -- The bar itself is also a Button derived from SecureActionButtonTemplate, so it has an OnClick handler flickerstreak@75: -- which we can use as a virtual button for keybinds, which will send attribute-value changes to itself. flickerstreak@75: -- However, we don't ever want the user to be able to click it directly. flickerstreak@75: f:EnableMouse(false) flickerstreak@75: f:SetAttribute("type","attribute") flickerstreak@77: flickerstreak@77: -- Buttons are contained in an anonymous intermediate sub-frame. This arrangement is to specifically flickerstreak@77: -- address the issue of the interaction with hidestates and auto-hiding empty action buttons (the two flickerstreak@77: -- don't play nicely together). It also has the fringe benefit of making show/hide faster because a flickerstreak@77: -- single frame is shown/hidden instead of potentially dozens. Unfortunately it does add an extra layer flickerstreak@77: -- of indirection to all state changes, as a secondary (trivial) statemap must be invoked. This flickerstreak@77: -- complicates frame setup slightly. flickerstreak@77: local bf = CreateFrame("Frame", nil, f, "SecureStateHeaderTemplate") flickerstreak@77: bf:SetAllPoints() flickerstreak@77: bf:Show() flickerstreak@77: bf:SetAttribute("useparent*",true) -- this facilitates SecureButton_GetModifiedAttribute() flickerstreak@77: bf:SetAttribute("statemap-parent","*:=") -- however some methods don't use it, so propagate the state too flickerstreak@77: f:SetAttribute("addchild",bf) flickerstreak@77: flickerstreak@77: -- Both frames are read-only. Override the default accessors for this object. flickerstreak@77: function self:GetFrame() flickerstreak@77: return f flickerstreak@77: end flickerstreak@77: flickerstreak@77: function self:GetButtonFrame() flickerstreak@77: return bf flickerstreak@77: end flickerstreak@25: flickerstreak@75: self:ApplyAnchor() flickerstreak@63: ReAction.RegisterCallback(self, "OnConfigModeChanged") flickerstreak@77: flickerstreak@77: return self flickerstreak@25: end flickerstreak@25: flickerstreak@25: function Bar:Destroy() flickerstreak@75: local f = self:GetFrame() flickerstreak@25: f:UnregisterAllEvents() flickerstreak@25: f:Hide() flickerstreak@25: f:SetParent(UIParent) flickerstreak@25: f:ClearAllPoints() flickerstreak@63: ReAction.UnregisterAllCallbacks(self) flickerstreak@75: for driver in pairs(self.statedrivers) do flickerstreak@75: UnregisterStateDriver(f, driver) flickerstreak@72: end flickerstreak@25: self.labelString = nil flickerstreak@25: self.controlFrame = nil flickerstreak@25: self.config = nil flickerstreak@25: end flickerstreak@25: flickerstreak@63: function Bar:OnConfigModeChanged(event, mode) flickerstreak@75: self:ShowControls(mode) -- Bar:ShowControls() defined in Overlay.lua flickerstreak@25: end flickerstreak@25: flickerstreak@25: function Bar:ApplyAnchor() flickerstreak@75: local f, config = self:GetFrame(), self.config flickerstreak@25: f:SetWidth(config.width) flickerstreak@25: f:SetHeight(config.height) flickerstreak@75: local point = config.point flickerstreak@51: f:ClearAllPoints() flickerstreak@75: if point then flickerstreak@75: local anchor = f:GetParent() flickerstreak@75: if config.anchor then flickerstreak@75: local bar = ReAction:GetBar(config.anchor) flickerstreak@52: if bar then flickerstreak@75: anchor = bar:GetFrame() flickerstreak@52: else flickerstreak@75: anchor = _G[config.anchor] flickerstreak@52: end flickerstreak@25: end flickerstreak@75: f:SetPoint(point, anchor or f:GetParent(), config.relpoint, config.x or 0, config.y or 0) flickerstreak@25: else flickerstreak@25: f:SetPoint("CENTER") flickerstreak@25: end flickerstreak@25: end flickerstreak@25: flickerstreak@51: function Bar:SetAnchor(point, frame, relativePoint, x, y) flickerstreak@51: local c = self.config flickerstreak@75: c.point = point or c.point flickerstreak@75: c.anchor = frame and frame:GetName() or c.anchor flickerstreak@75: c.relpoint = relativePoint or c.relpoint flickerstreak@51: c.x = x or c.x flickerstreak@51: c.y = y or c.y flickerstreak@51: self:ApplyAnchor() flickerstreak@51: end flickerstreak@51: flickerstreak@51: function Bar:GetAnchor() flickerstreak@51: local c = self.config flickerstreak@75: return (c.point or "CENTER"), (c.anchor or self:GetFrame():GetParent():GetName()), (c.relpoint or c.point or "CENTER"), (c.x or 0), (c.y or 0) flickerstreak@25: end flickerstreak@25: flickerstreak@25: function Bar:GetSize() flickerstreak@75: local f = self:GetFrame() flickerstreak@75: return f:GetWidth(), f:GetHeight() flickerstreak@25: end flickerstreak@25: flickerstreak@25: function Bar:SetSize(w,h) flickerstreak@25: self.config.width = w flickerstreak@25: self.config.height = h flickerstreak@75: local f = self:GetFrame() flickerstreak@75: f:SetWidth(w) flickerstreak@75: f:SetHeight(h) flickerstreak@25: end flickerstreak@25: flickerstreak@25: function Bar:GetButtonSize() flickerstreak@25: local w = self.config.btnWidth or 32 flickerstreak@25: local h = self.config.btnHeight or 32 flickerstreak@25: -- TODO: get from modules? flickerstreak@25: return w,h flickerstreak@25: end flickerstreak@25: flickerstreak@25: function Bar:SetButtonSize(w,h) flickerstreak@25: if w > 0 and h > 0 then flickerstreak@25: self.config.btnWidth = w flickerstreak@25: self.config.btnHeight = h flickerstreak@25: end flickerstreak@75: ReAction:RefreshBar(self) flickerstreak@25: end flickerstreak@25: flickerstreak@25: function Bar:GetButtonGrid() flickerstreak@25: local cfg = self.config flickerstreak@25: local r = cfg.btnRows or 1 flickerstreak@25: local c = cfg.btnColumns or 1 flickerstreak@25: local s = cfg.spacing or 4 flickerstreak@25: return r,c,s flickerstreak@25: end flickerstreak@25: flickerstreak@25: function Bar:SetButtonGrid(r,c,s) flickerstreak@25: if r > 0 and c > 0 and s > 0 then flickerstreak@25: local cfg = self.config flickerstreak@25: cfg.btnRows = r flickerstreak@25: cfg.btnColumns = c flickerstreak@25: cfg.spacing = s flickerstreak@25: end flickerstreak@75: ReAction:RefreshBar(self) flickerstreak@25: end flickerstreak@25: flickerstreak@25: function Bar:GetName() flickerstreak@25: return self.name flickerstreak@25: end flickerstreak@25: flickerstreak@77: function Bar:GetFrame() flickerstreak@77: -- this method is included for documentation purposes. It is overridden flickerstreak@77: -- in the New method for each object. flickerstreak@77: error("Invalid Bar object: used without initialization") flickerstreak@77: end flickerstreak@77: flickerstreak@77: function Bar:GetButtonFrame() flickerstreak@77: -- this method is included for documentation purposes. It is overridden flickerstreak@77: -- in the New method for each object. flickerstreak@77: error("Invalid Bar object: used without initialization") flickerstreak@77: end flickerstreak@77: flickerstreak@75: -- only ReAction:RenameBar() should call this function flickerstreak@33: function Bar:SetName(name) flickerstreak@33: self.name = name flickerstreak@75: -- controlLabelString is defined in Overlay.lua flickerstreak@33: if self.controlLabelString then flickerstreak@33: self.controlLabelString:SetText(self.name) flickerstreak@33: end flickerstreak@33: end flickerstreak@33: flickerstreak@75: function Bar:AddButton(idx, button) flickerstreak@77: -- store in a reverse-index array flickerstreak@75: self.buttons[button] = idx flickerstreak@77: self:GetButtonFrame():SetAttribute("addchild",button:GetFrame()) flickerstreak@75: SecureStateHeader_Refresh(self:GetFrame()) flickerstreak@75: end flickerstreak@75: flickerstreak@75: function Bar:RemoveButton(button) flickerstreak@75: self.buttons[button] = nil flickerstreak@75: end flickerstreak@75: flickerstreak@75: function Bar:IterateButtons() -- iterator returns button, idx flickerstreak@75: return pairs(self.buttons) flickerstreak@75: end flickerstreak@75: flickerstreak@75: function Bar:PlaceButton(button, baseW, baseH) flickerstreak@75: local idx = self.buttons[button] flickerstreak@75: if not idx then return end flickerstreak@25: local r, c, s = self:GetButtonGrid() flickerstreak@25: local bh, bw = self:GetButtonSize() flickerstreak@75: local row, col = floor((idx-1)/c), fmod((idx-1),c) -- zero-based flickerstreak@25: local x, y = col*bw + (col+0.5)*s, row*bh + (row+0.5)*s flickerstreak@25: local scale = bw/baseW flickerstreak@75: local f = button:GetFrame() flickerstreak@25: flickerstreak@25: f:ClearAllPoints() flickerstreak@25: f:SetPoint("TOPLEFT",x/scale,-y/scale) flickerstreak@25: f:SetScale(scale) flickerstreak@25: end flickerstreak@25: flickerstreak@75: -- Creates (or updates) a named binding which binds a key press to a call to SetAttribute() flickerstreak@75: -- pass a nil key to unbind flickerstreak@75: function Bar:SetAttributeBinding( name, key, attribute, value ) flickerstreak@75: if not name then flickerstreak@75: error("usage - Bar:SetAttributeBinding(name [, key, attribute, value]") flickerstreak@75: end flickerstreak@75: local f = self:GetFrame() flickerstreak@71: flickerstreak@75: -- clear the old binding, if any flickerstreak@75: if self.keybinds[name] then flickerstreak@75: SetOverrideBinding(f, false, self.keybinds[name], nil) flickerstreak@75: end flickerstreak@75: if key then flickerstreak@75: f:SetAttribute(format("attribute-name-%s",name), attribute) flickerstreak@75: f:SetAttribute(format("attribute-value-%s",name), value) flickerstreak@75: SetOverrideBindingClick(f, false, key, f:GetName(), name) -- binding name is the virtual mouse button flickerstreak@75: end flickerstreak@75: self.keybinds[name] = key flickerstreak@68: end flickerstreak@28: flickerstreak@75: -- Sets up a state driver 'name' for the bar, using the provided 'rule' flickerstreak@75: -- Also sets attributes 'statemap--'= for each entry in the passed map flickerstreak@75: -- if 'rule' is nil or an empty string, the driver is unregistered. flickerstreak@75: function Bar:SetStateDriver( name, rule, map ) flickerstreak@75: local f = self:GetFrame() flickerstreak@75: if rule and #rule > 0 then flickerstreak@75: if map then flickerstreak@75: for key, value in pairs(map) do flickerstreak@75: f:SetAttribute( format("statemap-%s-%s",name,key), value ) flickerstreak@72: end flickerstreak@72: end flickerstreak@75: RegisterStateDriver(f, name, rule) flickerstreak@75: self.statedrivers[name] = true flickerstreak@75: elseif self.statedrivers[name] then flickerstreak@75: UnregisterStateDriver(f, name) flickerstreak@75: self.statedrivers[name] = nil flickerstreak@72: end flickerstreak@72: end flickerstreak@72: flickerstreak@77: -- Set an attribute on the frame (or buttonFrame if 'buttonFrame' = true) flickerstreak@75: -- Either or both 'map' and 'default' can be passed: flickerstreak@75: -- - If 'map' is omitted, then 'default' is set to the attribute. flickerstreak@75: -- - If 'map' is provided, then it is interpreted as an unordered flickerstreak@75: -- table of the form { ["statename"] = ["value"] }, and will be flickerstreak@75: -- converted into a SecureStateHeaderTemplate style state-parsed flickerstreak@75: -- string, e.g. ":;:". If 'default' flickerstreak@75: -- is also provided, then its value will be converted to a string flickerstreak@75: -- and appended. flickerstreak@77: function Bar:SetStateAttribute( attribute, map, default, buttonFrame ) flickerstreak@77: local f = buttonFrame and self:GetButtonFrame() or self:GetFrame() flickerstreak@75: local value = default flickerstreak@75: if map then flickerstreak@75: local tmp = { } flickerstreak@75: for state, value in pairs(map) do flickerstreak@75: table.insert(tmp, format("%s:%s",tostring(state),tostring(value))) flickerstreak@68: end flickerstreak@75: if default then flickerstreak@75: table.insert(tmp, tostring(default)) flickerstreak@75: end flickerstreak@75: value = table.concat(tmp,";") flickerstreak@68: end flickerstreak@77: f:SetAttribute(attribute, value) flickerstreak@77: SecureStateHeader_Refresh(f) flickerstreak@71: end