diff Bar.lua @ 90:7cabc8ac6c16

Updates for wow 3.0 - TOC update - updated changed APIs/frame names - rewrote state code per new SecureHandlers API - cleaned up Bar, ActionButton code - removed AceLibrary/Dewdrop, menu from bar right-click - fixed various small bugs Updated WowAce external locations Updated README.html
author Flick <flickerstreak@gmail.com>
date Wed, 15 Oct 2008 16:29:41 +0000
parents f32e2375e39b
children 39265b16d208
line wrap: on
line diff
--- a/Bar.lua	Mon Oct 13 23:32:33 2008 +0000
+++ b/Bar.lua	Wed Oct 15 16:29:41 2008 +0000
@@ -5,64 +5,48 @@
 local floor = math.floor
 local fmod = math.fmod
 local format = string.format
-local SecureStateHeader_Refresh = SecureStateHeader_Refresh
 
 ReAction:UpdateRevision("$Revision$")
 
+local Bar   = { }
+local proto = { __index = Bar }
+local weak  = { __mode = "k" }
 
------- 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 = { }
+  -- create new self
+  self = setmetatable( 
+    { 
+      config  = config,
+      name    = name,
+      buttons = setmetatable( { }, weak ),
+      width   = config.width or 480,
+      height  = config.height or 40
+    }, 
+    proto )
+  
+  -- The frame type is 'Button' in order to have an OnClick handler. However, the frame itself is
+  -- not mouse-clickable by the user.
+  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,
+                        "SecureHandlerStateTemplate, SecureHandlerClickTemplate")
+  f:SetFrameStrata("MEDIUM")
+  f:SetWidth(self.width)
+  f:SetWidth(self.height)
+  f:Show()
+  f:EnableMouse(false)
+  f:SetClampedToScreen(true)
 
-  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.
+  -- Override the default frame accessor to provide strict read-only access
   function self:GetFrame()
     return f
   end
 
-  function self:GetButtonFrame()
-    return bf
-  end
-
   self:ApplyAnchor()
   ReAction.RegisterCallback(self, "OnConfigModeChanged")
 
@@ -72,16 +56,10 @@
 function Bar:Destroy()
   local f = self:GetFrame()
   f:UnregisterAllEvents()
+  ReAction.UnregisterAllCallbacks(self)
   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)
@@ -89,22 +67,26 @@
 end
 
 function Bar:ApplyAnchor()
-  local f, config = self:GetFrame(), self.config
-  f:SetWidth(config.width)
-  f:SetHeight(config.height)
-  local point  = config.point
+  local f = self:GetFrame()
+  local c = self.config
+  local p = c.point
+
+  f:SetWidth(c.width)
+  f:SetHeight(c.height)
   f:ClearAllPoints()
-  if point then
-    local anchor = f:GetParent()
-    if config.anchor then
-      local bar = ReAction:GetBar(config.anchor)
+  
+  if p then
+    local a = f:GetParent()
+    if c.anchor then
+      local bar = ReAction:GetBar(c.anchor)
       if bar then
-        anchor = bar:GetFrame()
+        a = bar:GetFrame()
       else
-        anchor = _G[config.anchor]
+        a = _G[c.anchor]
       end
     end
-    f:SetPoint(point, anchor or f:GetParent(), config.relpoint, config.x or 0, config.y or 0)
+    local fr = a or f:GetParent()
+    f:SetPoint(p, a or f:GetParent(), c.relpoint, c.x or 0, c.y or 0)
   else
     f:SetPoint("CENTER")
   end
@@ -122,7 +104,11 @@
 
 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)
+  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()
@@ -131,9 +117,9 @@
 end
 
 function Bar:SetSize(w,h)
+  local f = self:GetFrame()
   self.config.width = w
   self.config.height = h
-  local f = self:GetFrame()
   f:SetWidth(w)
   f:SetHeight(h)
 end
@@ -182,121 +168,54 @@
 
 function Bar:GetFrame()
   -- this method is included for documentation purposes. It is overridden
-  -- in the New method for each object.
+  -- for each object in the :New() method.
   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")
+-- only ReAction:RenameBar() should call this function. Calling from any other
+-- context will desync the bar list in the ReAction class.
+function Bar:SetName(name)
+  self.name = name
+  self:SetLabel(self.name) -- Bar:SetLabel() defined in Overlay.lua
 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)
+function Bar:AddButton(idx, button)
+  local f = self:GetFrame()
+
+  -- store in a weak reverse-index array
+  self.buttons[button] = idx
+
+  -- Store a properly wrapped reference to the child frame as an attribute 
+  -- (accessible via "frameref-btn#")
+  f:SetFrameRef(format("btn%d",idx), button:GetFrame())
+end
+
+function Bar:RemoveButton(button)
+  local idx = self.buttons[button]
+  if idx then
+    self:GetFrame():SetAttribute(format("frameref-btn%d",idx),nil)
+    self.buttons[button] = nil
   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
+-- iterator returns button, idx and does NOT iterate in index order
+function Bar:IterateButtons()
   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()
+  if idx then 
+    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 b = 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
+    b:ClearAllPoints()
+    b:SetPoint("TOPLEFT",x/scale,y/scale)
+    b:SetScale(scale)
   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