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