view classes/Bar.lua @ 154:df67685b340e

Fixed some bar/overlay encapsulation
author Flick <flickerstreak@gmail.com>
date Fri, 08 May 2009 17:30:22 +0000
parents d5e11e924053
children 806a61b331a0
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

ReAction:UpdateRevision("$Revision$")

local KB = LibStub("LibKeyBound-1.0")

---- Bar class ----
local Bar   = { }
local weak  = { __mode = "k" }
local frameList = { }

ReAction.Bar = Bar -- export to ReAction

function Bar:New( name, config )
  if type(config) ~= "table" then
    error("ReAction.Bar: config table required")
  end

  -- create new self
  self = setmetatable( 
    { 
      config  = config,
      name    = name,
      buttons = setmetatable( { }, weak ),
      width   = config.width or 480,
      height  = config.height or 40,
    }, 
    {__index = self} )
  
  -- 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
  name = name and "ReAction-"..name
  local f = name and frameList[name]
  if not f then
    f = CreateFrame("Button", name, parent, "SecureHandlerStateTemplate, SecureHandlerClickTemplate")
    if name then
      frameList[name] = f
    end
  end
  f:SetFrameStrata("MEDIUM")
  f:SetWidth(self.width)
  f:SetHeight(self.height)
  f:SetAlpha(config.alpha or 1.0)
  f:Show()
  f:EnableMouse(false)
  f:SetClampedToScreen(true)

  f:SetAttribute("_onstate-showgrid",
    -- function(self,stateid,newstate)
    [[
      control:ChildUpdate(stateid,newstate)
      control:CallMethod("UpdateShowGrid")
    ]])
  f.UpdateShowGrid = function(frame)
    for button in self:IterateButtons() do
      button:UpdateShowGrid()
    end
  end
  ReAction.gridProxy:AddFrame(f)

  -- Override the default frame accessor to provide strict read-only access
  function self:GetFrame()
    return f
  end

  self:ApplyAnchor()
  self:SetConfigMode(ReAction:GetConfigMode())
  self:SetKeybindMode(ReAction:GetKeybindMode())

  ReAction.RegisterCallback(self, "OnConfigModeChanged")
  KB.RegisterCallback(self, "LIBKEYBOUND_ENABLED")
  KB.RegisterCallback(self, "LIBKEYBOUND_DISABLED")
  KB.RegisterCallback(self, "LIBKEYBOUND_MODE_COLOR_CHANGED","LIBKEYBOUND_ENABLED")

  return self
end

function Bar:Destroy()
  local f = self:GetFrame()
  f:UnregisterAllEvents()
  self:ShowControls(false)
  ReAction.UnregisterAllCallbacks(self)
  KB.UnregisterAllCallbacks(self)
  ReAction.gridProxy:RemoveFrame(f)
  f:SetParent(UIParent)
  f:ClearAllPoints()
  f:Hide()
end

--
-- Events
--

function Bar:OnConfigModeChanged(event, mode)
  self:SetConfigMode(mode)
end

function Bar:LIBKEYBOUND_ENABLED(evt)
  self:SetKeybindMode(true)
end

function Bar:LIBKEYBOUND_DISABLED(evt)
  self:SetKeybindMode(false)
end

--
-- Accessors
--

function Bar:GetName()
  return self.name
end

-- 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
  if self.overlay then
    self.overlay:SetLabel(self.name)
  end
end

function Bar:GetFrame()
  -- this method is included for documentation purposes. It is overridden
  -- for each object in the :New() method.
  error("Invalid Bar object: used without initialization")
end

function Bar:GetConfig()
  return self.config
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:SetAnchor(point, frame, relativePoint, x, y)
  local c = self.config
  c.point = point or c.point
  c.anchor = frame or c.anchor
  c.relpoint = relativePoint or c.relpoint
  c.x = x or c.x
  c.y = y or c.y
  self:ApplyAnchor()
  ReAction:RefreshBar(self)
end

function Bar:GetSize()
  local f = self:GetFrame()
  return f:GetWidth(), f:GetHeight()
end

function Bar:SetSize(w,h)
  local f = self:GetFrame()
  self.config.width = w
  self.config.height = h
  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:GetNumButtons()
  local r,c = self:GetButtonGrid()
  return r*c
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:GetAlpha()
  return self.config.alpha or 1.0
end

function Bar:SetAlpha(value)
  self.config.alpha = value
  self:GetFrame():SetAlpha(value or 1.0)
  ReAction:RefreshBar(self)
end

-- iterator returns button, idx and does NOT iterate in index order
function Bar:IterateButtons()
  return pairs(self.buttons)
end

--
-- Methods
--

function Bar:SetConfigMode(mode)
  self:ShowControls(mode)
  if self.unitwatch then
    if mode then
      UnregisterUnitWatch(self:GetFrame())
      self:GetFrame():Show()
    else
      RegisterUnitWatch(self:GetFrame())
    end
  end
  for b in self:IterateButtons() do
    b:ShowGridTemp(mode)
    b:UpdateActionIDLabel(mode)
  end
end

function Bar:SetKeybindMode(mode)
  if self.unitwatch then
    if mode then
      UnregisterUnitWatch(self:GetFrame())
      self:GetFrame():Show()
    else
      RegisterUnitWatch(self:GetFrame())
    end
  end
  for b in self:IterateButtons() do
    b:SetKeybindMode(mode)
  end
end

function Bar:ApplyAnchor()
  local f = self:GetFrame()
  local c = self.config
  local p = c.point

  f:SetWidth(c.width)
  f:SetHeight(c.height)
  f:ClearAllPoints()
  
  if p then
    local a = f:GetParent()
    if c.anchor then
      local bar = ReAction:GetBar(c.anchor)
      if bar then
        a = bar:GetFrame()
      else
        a = _G[c.anchor]
      end
    end
    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
end

function Bar:ClipNButtons( n )
  local cfg = self.config
  local r = cfg.btnRows or 1
  local c = cfg.btnColumns or 1

  cfg.btnRows = ceil(n/c)
  cfg.btnColumns = min(n,c)
end

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:PlaceButton(button, baseW, baseH)
  local idx = self.buttons[button]
  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()

    b:ClearAllPoints()
    b:SetPoint("TOPLEFT",x/scale,y/scale)
    b:SetScale(scale)
  end
end

function Bar:SkinButton()
  -- does nothing by default
end

function Bar:ShowControls(show)
  local f = self.overlay
  if show then
    if not f then
      f = Bar.Overlay:New(self) -- see Overlay.lua
      self.overlay = f
    end
    f:Show()
  elseif f then
    f:Hide()
  end
end

function Bar:RefreshControls()
  if self.overlay and self.overlay:IsShown() then
    self.overlay:RefreshControls()
  end
end

function Bar:SetLabelSubtext(text)
  if self.overlay then 
    self.overlay:SetLabelSubtext(text) 
  end
end

--
-- Secure state functions
--

-- pass unit=nil to set up the unit elsewhere, if you want something more complex
function Bar:RegisterUnitWatch( unit, enable )
  local f = self:GetFrame()
  if unit then
    f:SetAttribute("unit",unit)
  end
  if not ReAction:GetConfigMode() then
    if enable then
      RegisterUnitWatch(f)
    elseif self.unitwatch then
      UnregisterUnitWatch(f)
    end
  end
  self.unitwatch = enable
end