view Bar.lua @ 74:00e28094e1a3

Some README updates
author Flick <flickerstreak@gmail.com>
date Tue, 10 Jun 2008 22:25:15 +0000
parents dd01feae0d89
children 06cd74bdc7da
line wrap: on
line source
local ReAction = ReAction
local L = ReAction.L
local _G = _G
local CreateFrame = CreateFrame
local floor = math.floor
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

  local parent = config.parent and (ReAction:GetBar(config.parent) or _G[config.parent]) or UIParent
  local f = CreateFrame("Frame",nil,parent,"SecureStateHeaderTemplate")
  f:SetFrameStrata("MEDIUM")
  config.width = config.width or 480
  config.height = config.height or 40
  f:SetWidth(config.width)
  f:SetWidth(config.height)

  ReAction.RegisterCallback(self, "OnConfigModeChanged")

  self.frame = f
  self:ApplyAnchor()
  f:Show()
  self:RefreshLayout()
end

function Bar:Destroy()
  local f = self.frame
  f:UnregisterAllEvents()
  f:Hide()
  f:SetParent(UIParent)
  f:ClearAllPoints()
  ReAction.UnregisterAllCallbacks(self)
  if self.statedriver then
    UnregisterStateDriver(f, "reaction")
  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)
end

function Bar:ApplyAnchor()
  local f, config = self.frame, self.config
  f:SetWidth(config.width)
  f:SetHeight(config.height)
  local anchor = config.anchor
  f:ClearAllPoints()
  if anchor then
    local anchorTo = f:GetParent()
    if config.anchorTo then
      local bar = ReAction:GetBar(config.anchorTo)
      if bar then
        anchorTo = bar:GetFrame()
      else
        anchorTo = _G[config.anchorTo]
      end
    end
    f:SetPoint(anchor, anchorTo or f:GetParent(), config.relativePoint, 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.anchor = point or c.anchor
  c.anchorTo = frame and frame:GetName() or c.anchorTo
  c.relativePoint = relativePoint or c.relativePoint
  c.x = x or c.x
  c.y = y or c.y
  self:ApplyAnchor()
end

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
end

function Bar:GetSize()
  return self.frame:GetWidth() or 200, self.frame:GetHeight() or 200
end

function Bar:SetSize(w,h)
  self.config.width = w
  self.config.height = 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
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
end

function Bar:GetName()
  return self.name
end

function Bar:SetName(name)
  self.name = name
  if self.controlLabelString then
    self.controlLabelString:SetText(self.name)
  end
end

function Bar:PlaceButton(f, idx, baseW, baseH)
  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 x, y = col*bw + (col+0.5)*s, row*bh + (row+0.5)*s
  local scale = bw/baseW

  f:ClearAllPoints()
  f:SetPoint("TOPLEFT",x/scale,-y/scale)
  f:SetScale(scale)
  self.buttons[f] = true
end


-- multi-state functions --
function Bar:GetNumPages()
  return self.config.nPages or 1
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))
      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")
  end
end

function Bar:SetHideStates(s)
  for f in pairs(self.buttons) do
    if f:GetParent() == self.frame then
      f:SetAttribute("hidestates",s)
    end
  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]))
      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 "")
  end
  SecureStateHeader_Refresh(f)
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, ...)
    local x = { }
    for k,v in pairs(Bar) do
      x[k] = v
    end
    Constructor(x, ...)
    return x
  end
}