view 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 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


-- 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 )
  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")

  -- 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")
  f:SetWidth(config.width)
  f:SetWidth(config.height)
  f:Show()

  self:ApplyAnchor()
  ReAction.RegisterCallback(self, "OnConfigModeChanged")
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: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

-- 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)
  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), 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 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
  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
  else
    self:GetFrame():SetAttribute(attribute, value)
  end
  SecureStateHeader_Refresh(self:GetFrame())
end


------ Export as a class-factory ------
ReAction.Bar = {
  prototype = Bar,
  New = function(self, ...)
    local x = { }
    for k,v in pairs(Bar) do
      x[k] = v
    end
    Constructor(x, ...)
    return x
  end
}