local function getDefaultUnitButtonBarColor()
return 0, 1, 0
end
local function createLabel(ownerFrame, fontObject)
assert (ownerFrame ~= nil)
local t = ownerFrame:CreateFontString((ownerFrame:GetName() or '') .. 'Text', 'OVERLAY')
fontObject = fontObject or NumberFont_OutlineThick_Mono_Small
assert (fontObject ~= nil)
t:SetFontObject(fontObject)
t:SetAllPoints()
return t
end
local function createBackground(ownerFrame)
assert (ownerFrame ~= nil)
local background = ownerFrame:CreateTexture(ownerFrame:GetName() .. 'Background', 'BACKGROUND')
background:SetAllPoints()
background:SetTexture(0, 0, 0, 0.8)
return background
end
local function createUnitButtonBar(self)
assert (self ~= nil)
local b = self:CreateTexture(self:GetName() .. 'Bar', 'OVERLAY')
local padding = 4
b:SetPoint('BOTTOMLEFT', padding, padding)
b:SetPoint('TOPRIGHT', -padding, -padding)
b:SetTexture(getDefaultUnitButtonBarColor(), 1)
self.bar = b
return b
end
local function getUnitHealthDeficit(unitDesignation)
assert (unitDesignation ~= nil)
local m = UnitHealthMax(unitDesignation) or 0
local c = UnitHealth(unitDesignation) or 0
return math.abs(c) - math.abs(m)
end
local function getUnitHealthRatio(unitDesignation)
assert (unitDesignation ~= nil)
local m = math.abs(math.max(UnitHealthMax(unitDesignation) or 1, 1))
local c = math.abs(UnitHealth(unitDesignation) or 1)
return c / m
end
local function updateUnitButtonText(self)
assert (self ~= nil)
local label = self.text
assert (label ~= nil)
local unitDesignation = self:GetAttribute('unit')
assert (unitDesignation ~= nil)
local n = UnitName(unitDesignation) or unitDesignation
local key = GetBindingKey('CLICK ' .. self:GetName() .. ':LeftButton')
if not key then
key = self:GetAttribute('choirBindingKey')
end
if key then
n = n .. ' <' .. key .. '>'
end
local d = getUnitHealthDeficit(unitDesignation)
local t
if UnitIsDead(unitDesignation) then
t = n .. '\n\r' .. '(Dead)'
elseif UnitIsGhost(unitDesignation) then
t = n .. '\n\r' .. '(Ghost)'
elseif d < 0 then
t = n .. '\n\r' .. tostring(math.floor(d))
else
t = n
end
label:SetText(t)
end
local function getClassColor(classDesignation)
local t = RAID_CLASS_COLORS[classDesignation]
if not t then
return nil
end
return t['r'], t['g'], t['b']
end
local function updateUnitButtonBar(self)
assert (self ~= nil)
local bar = self.bar
assert (bar ~= nil)
local unitDesignation = self:GetAttribute('unit')
assert (unitDesignation ~= nil)
--[[ Apply bar color update ]]--
local _, classDesignation = UnitClass(unitDesignation)
local r, g, b = getClassColor(classDesignation)
--[[ TODO Range indicator ]]--
local a = 1
if not r or not g or not b then
r, g, b = getDefaultUnitButtonBarColor()
end
bar:SetTexture(r, g, b, a)
--[[ Apply bar width update ]]--
self:SetAttribute('choirUnitHealthRatio', getUnitHealthRatio(unitDesignation))
local secureHandler = self.secureHandler
secureHandler:Execute([=[
local u = self:GetFrameRef('ChoirUnitButton')
local unitDesignation = u:GetAttribute('unit')
local ratio = 1
if UnitExists(unitDesignation) then
ratio = u:GetAttribute('choirUnitHealthRatio')
end
ratio = math.min(math.max(0, ratio), 1)
local bar = self:GetFrameRef('ChoirBar')
local padding = 4
bar:SetPoint('BOTTOMLEFT', self, 'BOTTOMLEFT', padding, padding)
bar:SetPoint('TOPRIGHT', self, 'TOPLEFT', ratio * u:GetWidth() - padding, -padding)
]=])
end
local function unitButtonEventProcessor(self)
updateUnitButtonText(self)
updateUnitButtonBar(self)
end
local function createUnitButton(parentFrame, frameName, unit)
assert (parentFrame ~= nil)
assert (frameName ~= nil)
assert (unit ~= nil)
local u = CreateFrame('BUTTON', frameName, parentFrame, 'SecureUnitButtonTemplate')
u:SetAttribute('type', 'target')
u:SetAttribute('unit', unit)
u:SetSize(12 * 8, 12 * 4)
u.text = createLabel(u)
local b = createBackground(u)
u.background = b
local bar = createUnitButtonBar(u)
u.bar = bar
local secureHandler = CreateFrame('BUTTON', frameName .. 'SecureHeader', u, 'SecureHandlerBaseTemplate')
secureHandler:SetAllPoints()
secureHandler:SetFrameRef('ChoirUnitButton', u)
secureHandler:SetFrameRef('ChoirBar', bar)
u.secureHandler = secureHandler
u:SetScript('OnEvent', unitButtonEventProcessor)
u:RegisterEvent('PARTY_CONVERTED_TO_RAID')
u:RegisterEvent('PARTY_MEMBERS_CHANGED')
u:RegisterEvent('PLAYER_ALIVE')
u:RegisterEvent('RAID_ROSTER_UPDATE')
u:RegisterEvent('UPDATE_BATTLEFIELD_SCORE')
u:RegisterEvent('ADDON_LOADED')
--[[ UNIT_HEALTH event with the current implementation does produce unnecessary calls.
-- Optimization is desireable. Code maintanence takes priority currently. ]]--
u:RegisterEvent('UNIT_HEALTH')
assert (u ~= nil)
return u
end
local function createSpoiler(spoilerParent, spoilerDesignation)
local spoiler = CreateFrame('BUTTON', spoilerDesignation, spoilerParent, 'SecureHandlerClickTemplate')
spoiler:EnableMouse(true)
--[[ WARNING To assign the temporary override bindings to the buttons under a spoiler correctly,
-- the children table must be sorted and be iterated over in a specific order.
-- #GetChildList method seems to return the children in order they were created,
-- but that might not be the case for #GetChildren variant or not at all. ]]--
spoiler:SetAttribute('_onclick', [=[
--[[ Toggle sibling frames, which are other spoilers that contain unit buttons ]]--
local parentFrame = self:GetParent()
local siblingTable = parentFrame:GetChildList(newtable())
local i = 0
while (i < #siblingTable) do
i = i + 1
local sibling = siblingTable[i]
sibling:ClearBindings()
sibling:Hide()
end
--[[ Apply override bindings to children which are unit buttons of a specific raid group ]]--
self:Show()
local j = 0
local childTable = self:GetChildList(newtable())
while (j < #childTable) do
j = j + 1
local child = childTable[j]
child:Show()
local key = child:GetAttribute('choirBindingKey')
if key then
self:SetBindingClick(true, key, child)
end
end
]=])
return spoiler
end
local function getButtonBindingKeyExplicit(buttonRef)
assert (buttonRef ~= nil)
local key = GetBindingKey('CLICK ' .. buttonRef:GetName() .. ':LeftButton')
return key
end
local function getButtonBindingKeyDefault(buttonNumber)
assert (buttonNumber ~= nil)
assert ('number' == type(buttonNumber))
buttonNumber = math.abs(math.floor(buttonNumber))
local key
local actionBarSize = 12
if buttonNumber >= 1 and buttonNumber <= actionBarSize then
key = GetBindingKey('ACTIONBUTTON' .. tostring(buttonNumber))
elseif buttonNumber > actionBarSize and buttonNumber <= actionBarSize * 6 then
local r = buttonNumber % actionBarSize
local n = buttonNumber - r * actionBarSize
key = GetBindingKey('MULTIACTIONBAR' .. r .. 'BUTTON' .. n)
else
key = nil
end
return key
end
local function createGroup(rootFrame, groupNumber, unitTable)
assert (rootFrame ~= nil)
assert (groupNumber ~= nil)
groupNumber = math.floor(math.abs(groupNumber))
assert ((groupNumber >= 1 and groupNumber <= 8) or unitTable ~= nil)
local groupSize = 5
local u
if unitTable then
u = unitTable
else
local q = 0
u = {}
while (q < groupSize) do
q = q + 1
u[q] = 'raid' .. tostring((groupNumber - 1) * groupSize + q)
end
end
assert (u ~= nil)
assert ('table' == type(u))
assert (#u == groupSize)
local spoiler = createSpoiler(rootFrame, 'ChoirSpoiler' .. tostring(groupNumber))
spoiler:SetSize(12 * 6, 12 * 4)
spoiler:SetPoint('CENTER', 12 * 6 * 6 / -2, 144)
local i = 0
local marginLeft = 0
local padding = 4
while (i < #u) do
i = i + 1
local unitDesignation = u[i]
assert (unitDesignation ~= nil)
local memberNumber = (groupNumber - 1) * groupSize + i
local b = createUnitButton(spoiler, 'ChoirUnitButton' .. tostring(memberNumber), unitDesignation)
b:SetPoint('BOTTOMLEFT', marginLeft, 0)
marginLeft = marginLeft + b:GetWidth() + padding
spoiler:WrapScript(b, 'OnClick', [=[
--[[ Assume that parent is the spoiler frame
-- which was created with createSpoiler and
-- has required scripts hooked to it ]]--
local spoilerFrame = self:GetParent()
spoilerFrame:ClearBindings()
spoilerFrame:Hide()
]=])
b:Hide()
local key = getButtonBindingKeyExplicit(b) or getButtonBindingKeyDefault(i)
if key then
b:SetAttribute('choirBindingKey', key)
end
_G['BINDING_NAME_CLICK ' .. b:GetName() .. ':LeftButton'] = 'Unit ' .. tostring(i)
end
_G['BINDING_NAME_CLICK ' .. spoiler:GetName() .. ':LeftButton'] = 'Group ' .. tostring(groupNumber)
return spoiler
end
local function init(rootFrame)
assert (rootFrame ~= nil)
rootFrame:UnregisterAllEvents()
rootFrame:SetSize(1024, 768)
rootFrame:SetPoint('CENTER', 0, 0)
createGroup(rootFrame, 1)
createGroup(rootFrame, 2)
createGroup(rootFrame, 3)
createGroup(rootFrame, 4)
createGroup(rootFrame, 5)
createGroup(rootFrame, 6)
createGroup(rootFrame, 7)
createGroup(rootFrame, 8)
--[[ TODO Add Esc key that closes the spoiler without clicking any of the children buttons ]]--
--[[ TODO Generalize the interface to be used with any kind of child frames,
-- especially nested spoilers and spell buttons. ]]--
--[[ NOTE To get a saved variables kind of table from a restricted frame snippet, use ```
-- local env = GetManagedEnvironment(headerRef)
-- local t = env['MySavedVariableTable']
-- ```
-- See http://www.iriel.org/wow/docs/SecureHeadersGuide-4.0-r1.pdf ]]--
local spoilerParty = createGroup(rootFrame, 9, {'player', 'party1', 'party2', 'party3', 'party4'})
_G['BINDING_NAME_CLICK ' .. spoilerParty:GetName() .. ':LeftButton'] = 'Player party'
BINDING_HEADER_CHOIR = 'Choir'
print('[Choir]: init')
end
local function main()
local rootFrame = CreateFrame('FRAME', 'ChoirFrame', UIParent)
--[[ NOTE The add-on requires key bindings data.
-- Therefore trigger the add-on initialization after the key bindings were loaded. ]]--
rootFrame:RegisterEvent('PLAYER_LOGIN')
rootFrame:SetScript('OnEvent', init)
end
main()