Mercurial > wow > reaction
diff Bar.lua @ 75:06cd74bdc7da
- Cleaned up Bar interface
- Move all attribute setting from Bar into State
- Separated Moonkin and Tree of Life
- Removed PossessBar module
- Added some infrastructure for paged/mind control support to Action
author | Flick <flickerstreak@gmail.com> |
---|---|
date | Mon, 16 Jun 2008 18:46:08 +0000 |
parents | dd01feae0d89 |
children | da8ba8783924 |
line wrap: on
line diff
--- a/Bar.lua Tue Jun 10 22:25:15 2008 +0000 +++ b/Bar.lua Mon Jun 16 18:46:08 2008 +0000 @@ -3,84 +3,91 @@ local _G = _G local CreateFrame = CreateFrame local floor = math.floor +local fmod = math.fmod +local format = string.format local SecureStateHeader_Refresh = SecureStateHeader_Refresh - -- update ReAction revision if this file is newer local revision = tonumber(("$Revision$"):match("%d+")) if revision > ReAction.revision then ReAction.revision = revision end + ------ BAR CLASS ------ local Bar = { _classID = {} } local function Constructor( self, name, config ) - self.name, self.config = name, config - self.buttons = setmetatable({},{__mode="k"}) - 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("Frame",nil,parent,"SecureStateHeaderTemplate") + local f = CreateFrame("Button",name and format("ReAction-%s",name),parent,"SecureStateHeaderTemplate, SecureActionButtonTemplate") + + -- The frame itself is read-only + function self:GetFrame() + return f + end + + -- 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") f:SetFrameStrata("MEDIUM") - config.width = config.width or 480 - config.height = config.height or 40 f:SetWidth(config.width) f:SetWidth(config.height) + f:Show() + self:ApplyAnchor() ReAction.RegisterCallback(self, "OnConfigModeChanged") - - self.frame = f - self:ApplyAnchor() - f:Show() - self:RefreshLayout() end function Bar:Destroy() - local f = self.frame + local f = self:GetFrame() f:UnregisterAllEvents() f:Hide() f:SetParent(UIParent) f:ClearAllPoints() ReAction.UnregisterAllCallbacks(self) - if self.statedriver then - UnregisterStateDriver(f, "reaction") + for driver in pairs(self.statedrivers) do + UnregisterStateDriver(f, driver) end self.labelString = nil self.controlFrame = nil - self.frame = nil self.config = nil end function Bar:OnConfigModeChanged(event, mode) - self:ShowControls(mode) -- ShowControls() defined in Overlay.lua -end - -function Bar:RefreshLayout() - ReAction:RefreshBar(self) + self:ShowControls(mode) -- Bar:ShowControls() defined in Overlay.lua end function Bar:ApplyAnchor() - local f, config = self.frame, self.config + local f, config = self:GetFrame(), self.config f:SetWidth(config.width) f:SetHeight(config.height) - local anchor = config.anchor + local point = config.point f:ClearAllPoints() - if anchor then - local anchorTo = f:GetParent() - if config.anchorTo then - local bar = ReAction:GetBar(config.anchorTo) + if point then + local anchor = f:GetParent() + if config.anchor then + local bar = ReAction:GetBar(config.anchor) if bar then - anchorTo = bar:GetFrame() + anchor = bar:GetFrame() else - anchorTo = _G[config.anchorTo] + anchor = _G[config.anchor] end end - f:SetPoint(anchor, anchorTo or f:GetParent(), config.relativePoint, config.x or 0, config.y or 0) + f:SetPoint(point, anchor or f:GetParent(), config.relpoint, config.x or 0, config.y or 0) else f:SetPoint("CENTER") end @@ -88,9 +95,9 @@ function Bar:SetAnchor(point, frame, relativePoint, x, y) local c = self.config - c.anchor = point or c.anchor - c.anchorTo = frame and frame:GetName() or c.anchorTo - c.relativePoint = relativePoint or c.relativePoint + 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() @@ -98,20 +105,20 @@ function Bar:GetAnchor() local c = self.config - return (c.anchor or "CENTER"), (c.anchorTo or self.frame:GetParent():GetName()), (c.relativePoint or c.anchor or "CENTER"), (c.x or 0), (c.y or 0) -end - -function Bar:GetFrame() - return self.frame + 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() - return self.frame:GetWidth() or 200, self.frame:GetHeight() or 200 + 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() @@ -126,6 +133,7 @@ self.config.btnWidth = w self.config.btnHeight = h end + ReAction:RefreshBar(self) end function Bar:GetButtonGrid() @@ -143,188 +151,128 @@ cfg.btnColumns = c cfg.spacing = s end + ReAction:RefreshBar(self) end function Bar:GetName() return self.name 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:PlaceButton(f, idx, baseW, baseH) +function Bar:AddButton(idx, button) + self.buttons[button] = idx + 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), mod((idx-1),c) -- zero-based + 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) - self.buttons[f] = true 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() --- multi-state functions -- -function Bar:GetNumPages() - return self.config.nPages or 1 + -- 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 - -- - -- 'rule' is a rule-string to pass to RegisterStateDriver - -- 'states' is a { ["statename"] = <don't care> } table of all state names - -- 'keybinds' is a { ["statename"] = keybind } table of all keybound states - -- -function Bar:SetStateDriver( rule, states, keybinds ) - local f = self.frame - local kbprefix = "" - do - local tmp = { } - for s, k in pairs(keybinds) do - if k and #k > 0 then -- filter out false table entries - -- if in a keybound state, set the stack to the new state but stay in the keybound state. - -- use $s as a placeholder for the current state, it will be gsub()'d in later - table.insert(tmp,("%s:$s set() %s"):format(s,s)) +-- 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 - table.insert(tmp,kbprefix) -- to get a trailing ';' if the table is not empty - kbprefix = table.concat(tmp,";") - end - for state in pairs(states) do - -- For all states: if in a keybound state, stay there (with stack manipulation, see above). - -- Otherwise, go to the state - f:SetAttribute(("statemap-reaction-%s"):format(state),("%s%s"):format(kbprefix:gsub("%$s",state),state)) - - local binding = keybinds[state] - self:SetStateKeybind(binding, state) -- set the binding even if nil, to clear it unconditionally - if binding then - -- for key bindings, use the state-stack to toggle between the last state and the keybound state - -- use a different 'virtual state' passed to attribute 'reaction-state' for key bindings, "<state>_binding" - f:SetAttribute(("statemap-reaction-%s_binding"):format(state), ("%s:pop();*:set(%s)"):format(state,state)) - end - end - - if rule and #rule > 0 then - self.stateDriver = true - RegisterStateDriver(f, "reaction", rule) - elseif self.statedriver then - self.statedriver = false - UnregisterStateDriver(f, "reaction") + RegisterStateDriver(f, name, rule) + self.statedrivers[name] = true + elseif self.statedrivers[name] then + UnregisterStateDriver(f, name) + self.statedrivers[name] = nil end end -function Bar:SetHideStates(s) - for f in pairs(self.buttons) do - if f:GetParent() == self.frame then - f:SetAttribute("hidestates",s) +-- Set an attribute on the frame (or its buttons 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 - SecureStateHeader_Refresh(self.frame) -end - -function Bar:SetStateKeybind(key, state, defaultstate) - -- Lazily create a tiny offscreen button which sends "<state>_binding" values to the - -- bar frame's state-reaction attribute, by using an override binding to generate a - -- click on the button with a virtual mouse button "state". - -- This gets around making the bar itself a clickable button, which is not desirable - local f = self.statebuttonframe - if key then - if not f then - f = CreateFrame("Button",self:GetName().."_statebutton",self.frame,"SecureActionButtonTemplate") - f:SetPoint("BOTTOMRIGHT",UIParent,"TOPLEFT") - f:SetWidth(1) - f:SetHeight(1) - f:SetAttribute("type*","attribute") - f:SetAttribute("attribute-name*","state-reaction") - f:SetAttribute("attribute-frame*",self.frame) - f:Show() - f.bindings = { } - self.statebuttonframe = f - end - f:SetAttribute(("attribute-value-%s"):format(state),("%s_binding"):format(state)) - -- clear the old binding, if any, for this state - if f.bindings[state] then - SetOverrideBinding(self.frame, false, f.bindings[state], nil) - end - SetOverrideBindingClick(self.frame, false, key, f:GetName(), state) -- the state name is used as the virtual button - f.bindings[state] = key - elseif f then - key = f.bindings[state] - if key then - SetOverrideBinding(self.frame, false, key, nil) - f.bindings[state] = nil - end - end -end - -function Bar:SetStatePageMap(state, map) -- map is a { ["statename"] = pagenumber } table - local f = self.frame - local tmp = { } - for s, p in pairs(map) do - table.insert(tmp, ("%s:page%d"):format(s,p)) - end - local spec = table.concat(tmp,";") - local current = f:GetAttribute("statebutton") - if spec ~= f:GetAttribute("statebutton") then - f:SetAttribute("statebutton", spec) - end - SecureStateHeader_Refresh(f) -end - -function Bar:SetStateKeybindOverrideMap(states) -- 'states' is an array of state-names that should have keybind overrides enabled - local f = self.frame - for i = 1, #states do - local s = states[i] - states[i] = ("%s:%s"):format(s,s) - end - table.insert(states,"_defaultbindings") - f:SetAttribute("statebindings",table.concat(states,";")) - SecureStateHeader_Refresh(f) - for b in pairs(self.buttons) do - -- TODO: signal child frames that they should maintain multiple bindings - end -end - -local _ofskeys = { "point", "relpoint", "x", "y" } -function Bar:SetStateAnchorMap( map ) -- 'map' is a { ["statename"] = { point=point, relpoint=relpoint, x=x, y=y } } table - local f = self.frame - local c = self.config - local default = { point = c.anchor, relpoint = c.relativePoint, x = c.x, y = c.y } - for _, key in pairs(_ofskeys) do - local t = { } - for state, info in pairs(map) do - if info[key] then - table.insert(t, ("%s:%s"):format(state, info[key])) + if doButtons then + for b in pairs(self.buttons) do + local f = b.GetFrame and b:GetFrame() + if f then + f:SetAttribute(attribute, value) end end - if #t > 0 and default[key] then table.insert(t, tostring(default[key])) end - f:SetAttribute(("headofs%s"):format(key), table.concat(t,";") or "") + else + self:GetFrame():SetAttribute(attribute, value) end - SecureStateHeader_Refresh(f) + SecureStateHeader_Refresh(self:GetFrame()) end -function Bar:SetStateScaleMap( map ) -- 'map' is a { ["statename"] = scalevalue } table - local f = self.frame - local t = { } - for state, scale in pairs(map) do - table.insert( t, ("%s:%s"):format(state,scale) ) - end - if #t > 0 then table.insert(t, "1.0") end - f:SetAttribute("headscale",table.concat(t,";") or "") - SecureStateHeader_Refresh(f) -end - - ------ Export as a class-factory ------ ReAction.Bar = { prototype = Bar, - new = function(self, ...) + New = function(self, ...) local x = { } for k,v in pairs(Bar) do x[k] = v