view Button.lua @ 1:c11ca1d8ed91

Version 0.1
author Flick <flickerstreak@gmail.com>
date Tue, 20 Mar 2007 21:03:57 +0000
parents
children 8e0ff8ae4c08
line wrap: on
line source
-- private constants
local namePrefix = "ReActionButton_"
local _G = getfenv(0)
local ACTION_FREE = { }
local MAX_ACTIONS = 120

local hotKeyDefaultColor    = { r=1.0, g=1.0, b=1.0, a=1.0 }
local hotKeyDisabledColor   = { r=0.6, g=0.6, b=0.6, a=1.0 }
local hotKeyOutOfRangeColor = { r=1.0, g=0.2, b=0.2, a=1.0 }

local hotKeyModifierColors = {
  S = { r=0.6, g=0.6, b=1.0, a=1.0 },  -- shift
  C = { r=1.0, g=0.82, b=0, a=1.0 },  -- ctrl
  A = { r=0.1, g=1.0, b=0.1, a=1.0 },  -- alt
}

local equippedActionBorderColor = { r=0, g=1.0, b=0, a=0.35 }

local actionUsableColor        = { r=1.0, g=1.0, b=1.0, a=1.0 }
local actionNotUsableColor     = { r=0.4, g=0.4, b=0.4, a=1.0 }
local actionNotEnoughManaColor = { r=0.2, g=0.2, b=0.7, a=1.0 }
local actionOutOfRangeColor    = { r=1.0, g=0.2, b=0.2, a=1.0 }

-- private variables
local kbValidate = AceLibrary("AceConsole-2.0").keybindingValidateFunc
local actionButtonTbl = { }



-- ReActionButton is a class prototype object.
ReActionButton = AceLibrary("AceOO-2.0").Class("AceEvent-2.0")

-------------------
-- Class methods
-------------------

-- In addition to supporting the 'new' creation method (in which case an action ID must be specified directly),
-- ReActionButton supports the 'acquire'/'release' creation method, which recycles objects and manages actionID assignment.

function ReActionButton:acquire(parent, config, barIdx)
  local id = nil
  for i = 1, MAX_ACTIONS do
    if actionButtonTbl[i] == nil or actionButtonTbl[i].inUse == false then
      id = i
      break
    end
  end

  if id == nil then return nil end  -- all buttons and action ids are in use

  local hint = config.actionIDs[barIdx]
  if hint and (actionButtonTbl[hint] == nil or actionButtonTbl[hint].inUse == false) then
    id = hint
  end

  if actionButtonTbl[id] == nil then 
    actionButtonTbl[id] = { }
  end
  local t = actionButtonTbl[id]

  t.inUse = true
  if t.button then
    t.button:Configure(parent,config,barIdx,id)
  else
    t.button = self:new(parent,config,barIdx,id)
  end

  if actionButtonTbl[t.button:GetActionID()].inUse ~= true then
  end

  return t.button
end

function ReActionButton:release( b )
  if b then
    actionButtonTbl[b:GetActionID()].inUse = false
    b:Recycle()
  end
end



function ReActionButton:ShowAllActionIDs()
  for _, b in ipairs(actionButtonTbl) do
    b:ShowActionID()
  end
end

function ReActionButton:HideAllActionIDs()
  for _, b in ipairs(actionButtonTbl) do
    b:HideActionID()
  end
end


----------------------
-- Instance methods
----------------------
function ReActionButton.prototype:init( parentFrame, config, barIdx, id )
  ReActionButton.super.prototype.init(self)

  -- create the button widget
  self.name = namePrefix.."_"..id
  self.button = CreateFrame("CheckButton", self.name, parentFrame, "ReActionButtonTemplate")
  
  -- create the actionID label on the control widget
  local actionIDLabel = self.button:CreateFontString(nil,"ARTWORK", "NumberFontNormalSmall")
  actionIDLabel:SetPoint("BOTTOMLEFT",0,0)
  actionIDLabel:Hide()

  -- store references to the various sub-frames so we don't have to look it up all the time
  self.frames      = {
    hotkey         = _G[self.name.."HotKey"],
    count          = _G[self.name.."Count"],
    cooldown       = _G[self.name.."Cooldown"],
--    nCooldown      = _G[self.name.."CooldownNumeric"],
    macro          = _G[self.name.."Name"],
    icon           = _G[self.name.."Icon"],
    border         = _G[self.name.."Border"],
    normalTexture  = _G[self.name.."NormalTexture"],
    flash          = _G[self.name.."Flash"],
    actionID       = actionIDLabel
  }

  -- provide a reference back to this object for the frame to use in event handlers
  self.button.rxnBtn = self

  -- initialize
  self:Configure(parentFrame, config, barIdx, id)
end

function ReActionButton.prototype:Recycle()
  local b = self.button
  local action = self:GetActionID()

  self.config.actionIDs[self.barIdx] = nil

  self:SetKeyBinding(nil)
  self:UpdateDisplay()
  b:UnregisterAllEvents()
  b:Hide()
  b:ClearAllPoints()
  b:SetParent(ReActionButtonRecycleFrame)
end



-- set the button location
function ReActionButton.prototype:PlaceButton(point, x, y, sz)
  local b = self.button
  local scale = sz / 36
  b:ClearAllPoints()
  b:SetScale( scale )
  b:SetPoint(point,x/scale,y/scale)
end


function ReActionButton.prototype:ShowActionID()
  self.frames.actionID:Show()
end

function ReActionButton.prototype:HideActionID()
  self.frames.actionID:Hide()
end



-- configuration and setup
function ReActionButton.prototype:Configure( parentFrame, config, barIdx, id )
  self.config   = config
  self.barIdx   = barIdx
  self.showGrid = config.showGrid and 1 or 0

  self.button:ClearAllPoints()
  self.button:SetParent(parentFrame)

  self:SetupAttributes()
  self:RegisterStaticEvents()

  if id then
    -- set action ID
    self:SetActionID(id)
  else
    self:ApplyActionID()
  end
  self:ApplyLayout()
  self:ApplyStyle()

  self:UpdateDisplay()
end

function ReActionButton.prototype:SetupAttributes()
  local b = self.button
  b:SetAttribute("type", "action")
	b:SetAttribute("shift-type*", ATTRIBUTE_NOOP)
  b:SetAttribute("alt-type*", ATTRIBUTE_NOOP)
	b:SetAttribute("checkselfcast", true)
	b:SetAttribute("useparent-unit", true)
end

function ReActionButton.prototype:RegisterStaticEvents()
	self:RegisterEvent("PLAYER_ENTERING_WORLD")
	self:RegisterEvent("ACTIONBAR_SLOT_CHANGED")
	self:RegisterEvent("UPDATE_BINDINGS")
  self:RegisterEvent("ACTIONBAR_SHOWGRID")
  self:RegisterEvent("ACTIONBAR_HIDEGRID")
end

function ReActionButton.prototype:RegisterActionEvents()
  self:RegisterEvent("ACTIONBAR_UPDATE_STATE")
  self:RegisterEvent("ACTIONBAR_UPDATE_USABLE")
  self:RegisterEvent("ACTIONBAR_UPDATE_COOLDOWN", "ACTIONBAR_UPDATE_USABLE")
  self:RegisterEvent("UPDATE_INVENTORY_ALERTS", "ACTIONBAR_UPDATE_USABLE")
  self:RegisterEvent("PLAYER_AURAS_CHANGED", "ACTIONBAR_UPDATE_USABLE")
  self:RegisterEvent("PLAYER_TARGET_CHANGED", "ACTIONBAR_UPDATE_USABLE")
  self:RegisterEvent("UNIT_INVENTORY_CHANGED")
  self:RegisterEvent("CRAFT_SHOW")
  self:RegisterEvent("CRAFT_CLOSE", "CRAFT_SHOW")
  self:RegisterEvent("TRADE_SKILL_SHOW", "CRAFT_SHOW")
  self:RegisterEvent("TRADE_SKILL_CLOSE", "CRAFT_SHOW")
  self:RegisterEvent("PLAYER_ENTER_COMBAT", "CRAFT_SHOW")
  self:RegisterEvent("PLAYER_LEAVE_COMBAT")
  self:RegisterEvent("START_AUTOREPEAT_SPELL")
  self:RegisterEvent("STOP_AUTOREPEAT_SPELL")

  self.button:SetScript("OnUpdate", function() self:OnUpdate(arg1) end)
  self.actionEventsRegistered = true
end

function ReActionButton.prototype:UnregisterActionEvents()
  self:UnregisterEvent("ACTIONBAR_UPDATE_STATE")
  self:UnregisterEvent("ACTIONBAR_UPDATE_USABLE")
  self:UnregisterEvent("ACTIONBAR_UPDATE_COOLDOWN")
  self:UnregisterEvent("UPDATE_INVENTORY_ALERTS")
  self:UnregisterEvent("PLAYER_AURAS_CHANGED")
  self:UnregisterEvent("PLAYER_TARGET_CHANGED")
  self:UnregisterEvent("UNIT_INVENTORY_CHANGED")
  self:UnregisterEvent("CRAFT_SHOW")
  self:UnregisterEvent("CRAFT_CLOSE")
  self:UnregisterEvent("TRADE_SKILL_SHOW")
  self:UnregisterEvent("TRADE_SKILL_CLOSE")
  self:UnregisterEvent("PLAYER_ENTER_COMBAT")
  self:UnregisterEvent("PLAYER_LEAVE_COMBAT")
  self:UnregisterEvent("START_AUTOREPEAT_SPELL")
  self:UnregisterEvent("STOP_AUTOREPEAT_SPELL")

  self.button:SetScript("OnUpdate", nil)
  self.actionEventsRegistered = false
end


-- event handlers
function ReActionButton.prototype:ACTIONBAR_SLOT_CHANGED()
  if arg1 == 0 or arg1 == self:GetActionID() then
    self:UpdateDisplay()
  end
end

function ReActionButton.prototype:PLAYER_ENTERING_WORLD()
  self:UpdateDisplay()
end

function ReActionButton.prototype:UPDATE_BINDINGS()
  self:UpdateDisplay()
end

function ReActionButton.prototype:ACTIONBAR_SHOWGRID()
  self:ShowGridTmp()
end

function ReActionButton.prototype:ACTIONBAR_HIDEGRID()
  self:HideGridTmp()
end

function ReActionButton.prototype:ACTIONBAR_UPDATE_STATE()
  self:UpdateCheckedState()
end

function ReActionButton.prototype:ACTIONBAR_UPDATE_USABLE()
  self:UpdateUsable()
  self:UpdateCooldown()
  self:ColorHotKey()
end

function ReActionButton.prototype:UNIT_INVENTORY_CHANGED()
  if arg1 == "player" then
    self:UpdateDisplay()
  end
end

function ReActionButton.prototype:CRAFT_SHOW()
  self:UpdateCheckedState()
end

function ReActionButton.prototype:PLAYER_ENTER_COMBAT()
  if IsAttackAction(self:GetActionID()) then
    self:StartFlash()
  end
end

function ReActionButton.prototype:PLAYER_LEAVE_COMBAT()
  if IsAttackAction(self:GetActionID()) then
    self:StopFlash()
  end
end

function ReActionButton.prototype:START_AUTOREPEAT_SPELL()
  if IsAutoRepeatAction(self:GetActionID()) then
    self:StartFlash()
  end
end

function ReActionButton.prototype:STOP_AUTOREPEAT_SPELL()
  if self:IsFlashing() and not IsAttackAction(self:GetActionID()) then
    self:StopFlash()
  end
end


-- OnUpdate handler
function ReActionButton.prototype:OnUpdate(elapsed)
  local action = self:GetActionID()
  local f = self.frames

  -- handle flashing
	if self:IsFlashing() then
		self.flashtime = self.flashtime - elapsed
		if self.flashtime <= 0 then
			local overtime = -self.flashtime
			if overtime >= ATTACK_BUTTON_FLASH_TIME then
				overtime = 0
			end
			self.flashtime = ATTACK_BUTTON_FLASH_TIME - overtime

			if f.flash:IsVisible() then
        f.flash:Hide()
      else
        f.flash:Show()
			end
		end
	end
	
	-- Handle range indicator
	if self.rangeTimer then
		self.rangeTimer = self.rangeTimer - elapsed
		if self.rangeTimer <= 0 then
      self:ColorHotKey()
      self:UpdateUsable()
			self.rangeTimer = TOOLTIP_UPDATE_TIME
		end
	end

  -- handle toltip update
  if self.tooltipTime then
    self.tooltipTime = self.tooltipTime - elapsed
    if self.tooltipTime <= 0 then
      if GameTooltip:IsOwned(self.button) then
        self:UpdateTooltip()
      else
        self.tooltipTime = nil
      end
    end
  end
end




-- keybinding functions
function ReActionButton.prototype:SetKeyBinding( k )
  if k == nil or kbValidate(k) then
    local current = self:GetKeyBinding()
  	ClearOverrideBindings(self.button)
    if current then
      SetBinding(current,nil)
    end
    if k then
      SetBindingClick(k, self.name, "LeftButton")
    end
  end
end

function ReActionButton.prototype:GetKeyBinding()
  return GetBindingKey("CLICK "..self.name..":LeftButton")
end

function ReActionButton.prototype:ShowAssignKeybinding()
  local f = ReActionKeybindFrame
  f:ClearAllPoints()
  f:SetPoint("BOTTOM", self.button, "TOP", 0, 10)
  ReActionKeybindFrameButton.keybindTarget = self
  local k = self:GetKeyBinding()
  if k then
    local txt = GetBindingText(k, "KEY_")
    ReActionKeybindFrameButton:SetText(txt or "")
  end
  f:Show()
end


local mouseButtonConvert = {
  LeftButton = "BUTTON1",
  RightButton = "BUTTON2",
  MiddleButton = "BUTTON3",
  Button4 = "BUTTON4",
  Button5 = "BUTTON5"
}

function ReActionButton.prototype:HandleKeybindAssign(button, key, mouseButton)
  mouseButton = mouseButton and mouseButtonConvert[mouseButton]
  if mouseButton ~= "BUTTON1" and mouseButton ~= "BUTTON2" then
    key = key or mouseButton
    if key == nil or key == "UNKNOWN" or key == "SHIFT" or key == "CTRL" or key == "ALT" then 
      return
    end
    if key == "ESCAPE" then
      ReActionKeybindFrame:Hide()
      return
    end
		if IsShiftKeyDown() then 
      key = "SHIFT-"..key
    end
		if IsControlKeyDown() then
			key = "CTRL-"..key
		end
		if IsAltKeyDown() then
			keyPressed = "ALT-"..key
		end
    local oldAction = GetBindingAction(key)
    local oldKey = self:GetKeyBinding()
    if oldAction then
      -- can't pop a modal dialog box, will need to think something up
    end
    if oldKey == key then
      SetBinding(key,nil)
      key = nil
    end
    self:SetKeyBinding(key)
    button:SetText(key and GetBindingText(key, "KEY_") or "")
    self:UpdateDisplay()
    SaveBindings(2) -- 2 = character specific bindings... hmm...
  end
  button.selected = false
  this:SetButtonState("NORMAL")
end


-- action ID functions
function ReActionButton.prototype:SetActionID( id )
  self.config.actionIDs[self.barIdx] = tonumber(id) -- force data integrity
  self:ApplyActionID()
end

function ReActionButton.prototype:GetActionID()
  return self.config.actionIDs[self.barIdx]
end

function ReActionButton.prototype:ApplyActionID()
  local action = tonumber(self:GetActionID())
  self.button:SetAttribute("action",action or nil)
  self.frames.actionID:SetText(action or "")
end

function ReActionButton.prototype:ShowActionID()
  self.frames.actionID:Show()
end

function ReActionButton.prototype:HideActionID()
  self.frames.actionID:Hide()
end

function ReActionButton:ShowAllActionIDs() -- class-wide function
  for _, tbl in pairs(actionButtonTbl) do
    if tbl.button then tbl.button:ShowActionID() end
  end
end

function ReActionButton:HideAllActionIDs() -- class-wide function
  for _, tbl in pairs(actionButtonTbl) do
    if tbl.button then tbl.button:HideActionID() end
  end
end


-- action transfer functions
function ReActionButton.prototype:ShouldPickupAction(mouseButton)
	return IsShiftKeyDown() and not SecureButton_GetModifiedAttribute(self.button, "type", mouseButton)
end

function ReActionButton.prototype:ShowGridTmp()
  self.showGrid = self.showGrid + 1
  self:UpdateVisibility()
end

function ReActionButton.prototype:HideGridTmp()
  self.showGrid = self.showGrid - 1
  self:UpdateVisibility()
end

function ReActionButton.prototype:ShowGrid()
  self.config.showGrid = true
  self:ShowGridTmp()
end

function ReActionButton.prototype:HideGrid()
  self.config.showGrid = false
  self:HideGridTmp()
end



-- layout & style functions
function ReActionButton.prototype:ApplyLayout()
  local f = self.frames

  if self.config.keyBindLoc then
    local h = f.hotkey
    local loc = self.config.keyBindLoc
    h:ClearAllPoints()
    h:SetPoint(loc,0,0)
    local j
    if string.match(loc,"LEFT") then
      j = "LEFT"
    elseif string.match(loc,"RIGHT") then
      j = "RIGHT"
    else
      j = "CENTER"
    end
    h:SetJustifyH(j)
  end

  if self.config.stackCountLoc then
    local c = f.count
    local loc = self.config.stackCountLoc
    c:ClearAllPoints()
    c:SetPoint(loc,0,0)
    local j
    if string.match(loc,"LEFT") then
      j = "LEFT"
    elseif string.match(loc,"RIGHT") then
      j = "RIGHT"
    else
      j = "CENTER"
    end
    c:SetJustifyH(j)
  end

  if self.config.showKeyBind then
    f.hotkey:Show()
  else
    f.hotkey:Hide()
  end
    
  if self.config.showStackCount then
    f.count:Show()
  else
    f.count:Hide()
  end

--[[
  if self.config.showNumericCooldown then
    f.nCooldown:Show()
  else
    f.nCooldown:Hide()
  end
]]

  if self.config.showMacroName then
    f.macro:Show()
  else
    f.macro:Hide()
  end
end

function ReActionButton.prototype:ApplyStyle()
  local f = self.frames
  -- for now, just a static style
  f.hotkey:SetFontObject(NumberFontNormal)
  f.count:SetFontObject(NumberFontNormalYellow)
end



-- start/stop flashing
function ReActionButton.prototype:StartFlash()
  self.flashing = true
  self.flashtime = 0
  self:UpdateCheckedState()
end

function ReActionButton.prototype:StopFlash()
  self.flashing = false
  self.frames.flash:Hide()
  self:UpdateCheckedState()
end

function ReActionButton.prototype:IsFlashing()
  return self.flashing
end





-- set the tooltip
function ReActionButton.prototype:SetTooltip()
  GameTooltip_SetDefaultAnchor(GameTooltip, self.button)
  self:UpdateTooltip()
end

function ReActionButton.prototype:ClearTooltip()
  tooltipTime = nil
  GameTooltip:Hide()
end



-- colorize the hotkey
function ReActionButton.prototype:ColorHotKey()
  local action = self:GetActionID()
  local c = hotKeyDefaultColor

  if action and HasAction(action) then
    if IsActionInRange(action) == 0 then
      c = hotKeyOutOfRangeColor
    elseif self.config.keyBindColorCode then
      local modKey = string.match( self.frames.hotkey:GetText() or "", "([ACS])%-")
      c = modKey and hotKeyModifierColors[modKey] or c
    end
  else
    c = hotKeyDisabledColor
  end

  self.frames.hotkey:SetTextColor(c.r, c.g, c.b)
end




-- display update functions
function ReActionButton.prototype:UpdateDisplay()
  self:UpdateIcon()
  self:UpdateHotkey()
  self:UpdateCount()
  self:UpdateMacroText()
  self:UpdateUsable()
  self:UpdateCooldown()
  self:UpdateFlash()
  self:UpdateEvents()
  self:UpdateVisibility()
  self:UpdateTooltip()
end

function ReActionButton.prototype:UpdateIcon()
  local f = self.frames
  local b = self.button

  local action = self:GetActionID()
  local texture = action and GetActionTexture(action)

  if action and texture then
    f.icon:SetTexture(texture)
    f.icon:Show()
    self.rangeTimer = -1
    b:SetNormalTexture("Interface\\Buttons\\UI-Quickslot2")
  else
    f.icon:Hide()
    f.cooldown:Hide()
    self.rangeTimer = nil
    b:SetNormalTexture("Interface\\Buttons\\UI-Quickslot")
  end

  self:UpdateCheckedState()

  -- Add a green border if action is an equipped item
  if action and IsEquippedAction(action) then
    local c = equippedActionBorderColor
    f.border:SetVertexColor(c.r, c.g, c.b, c.a or 1)
    f.border:Show()
  else
    f.border:Hide()
  end
end

function ReActionButton.prototype:UpdateCheckedState()
  local action = self:GetActionID()
	if action and (IsCurrentAction(action) or IsAutoRepeatAction(action)) then
		self.button:SetChecked(1)
	else
		self.button:SetChecked(0)
	end
end


function ReActionButton.prototype:UpdateHotkey()
  local action = self:GetActionID()
  local b = self.button
  local f = self.frames
  local key = self:GetKeyBinding()
  local txt = GetBindingText(key, "KEY_",1)

  if txt then
    f.hotkey:SetText(string.upper(txt))
    self:ColorHotKey()
  else
    f.hotkey:SetText("")
  end
end

function ReActionButton.prototype:UpdateCount()
  local action = self:GetActionID()
	if action and (IsConsumableAction(action) or IsStackableAction(action)) then
		self.frames.count:SetText(GetActionCount(action))
	else
		self.frames.count:SetText("")
	end
end

function ReActionButton.prototype:UpdateMacroText()
  local action = self:GetActionID()
	self.frames.macro:SetText(action and GetActionText(action) or "")
end

function ReActionButton.prototype:UpdateUsable()
  local f = self.frames
  local action = self:GetActionID()
	local isUsable, notEnoughMana
  if action then
    isUsable, notEnoughMana = IsUsableAction(action)
  end
	if isUsable then
    local c = actionUsableColor
    if IsActionInRange(action) == 0 then
      c = actionOutOfRangeColor
    else
  		f.normalTexture:SetVertexColor(c.r, c.g, c.b, c.a)
    end
		f.icon:SetVertexColor(c.r, c.g, c.b, c.a)
	elseif notEnoughMana then
    local c = actionNotEnoughManaColor
		f.icon:SetVertexColor(c.r, c.g, c.b, c.a)
		f.normalTexture:SetVertexColor(c.r, c.g, c.b, c.a)
  else
    local c = actionNotUsableColor
		f.icon:SetVertexColor(c.r, c.g, c.b, c.a)
		f.normalTexture:SetVertexColor(1.0, 1.0, 1.0)
	end
end

function ReActionButton.prototype:UpdateCooldown()
  local action = self:GetActionID()
  if action then
  	local start, duration, enable = GetActionCooldown(self:GetActionID())
  	CooldownFrame_SetTimer(self.frames.cooldown, start, duration, enable)
    -- do numeric cooldown stuff here
  end
end

function ReActionButton.prototype:UpdateFlash()
  local b = self.button
  local action = self:GetActionID()
	if action and ((IsAttackAction(action) and IsCurrentAction(action)) or IsAutoRepeatAction(action)) then
    self:StartFlash()
	else
    self:StopFlash()
	end
end

function ReActionButton.prototype:UpdateVisibility()
  local action = self:GetActionID()
  local b = self.button

  if b:GetAttribute("statehidden") then
    b:Hide()
  elseif action and HasAction(action) then
    b:GetNormalTexture():SetAlpha(1.0)
    b:Show()
  elseif self.showGrid > 0 then
    b:GetNormalTexture():SetAlpha(0.5)
    self.frames.cooldown:Hide()
    b:Show()
  else
    b:Hide()
  end
end

function ReActionButton.prototype:UpdateEvents()
  local action = self:GetActionID()
	if action and HasAction(action) then
    self:RegisterActionEvents()
  elseif self.actionEventsRegistered then
    self:UnregisterActionEvents()
	end
end

function ReActionButton.prototype:UpdateTooltip()
  local action = self:GetActionID()
	if action and GameTooltip:SetAction(action) then
		self.tooltipTime = TOOLTIP_UPDATE_TIME
	else
		self.tooltipTime = nil
	end
end