List of commits:
Subject Hash Author Date (UTC)
feat(choir)!: Decorate unit buttons 4dc5ed44a9519b275f4256cfe4281110b7a94c9a Vladyslav Bondarenko 2021-10-25 21:20:56
feat(choir)!: Copy action bar binding 70ce056ffda7f12d913ce9a42b128ea257bdd0dc Vladyslav Bondarenko 2021-10-23 23:34:56
feat!: Initial commit 45c4e781e5ff0a69f8b0bea3a869e2384c7ca454 Vladyslav Bondarenko 2021-10-23 06:03:14
Commit 4dc5ed44a9519b275f4256cfe4281110b7a94c9a - feat(choir)!: Decorate unit buttons
Render unit health deficit, button key binding, unit name, unit class
color.

Also simplify unit button secure on click handler.

Currently unit health ratio indicator that is a health bar does not
function in combat. Different implementation is required.
Author: Vladyslav Bondarenko
Author date (UTC): 2021-10-25 21:20
Committer name: Vladyslav Bondarenko
Committer date (UTC): 2021-10-26 02:35
Parent(s): 4af70fceb61605434f9103b88d80f3758de3208e
Signer:
Signing key:
Signing status: N
Tree: 8e55546125000a42e32d622fcc12979920a6ad40
File Lines added Lines deleted
.luacheckrc 9 0
choir.lua 183 42
File .luacheckrc changed (mode: 100644) (index 0a45ba8..df7e370)
... ... stds.wow = {
3 3 globals = {}, --[[ these globals can be set and accessed ]]-- globals = {}, --[[ these globals can be set and accessed ]]--
4 4 read_globals = { read_globals = {
5 5 'CreateFrame', 'CreateFrame',
6 'GetBindingKey',
7 'GetClassColor',
8 'GetUnitClass',
6 9 'NumberFont_OutlineThick_Mono_Small', 'NumberFont_OutlineThick_Mono_Small',
10 'RAID_CLASS_COLORS',
7 11 'SetOverrideBinding', 'SetOverrideBinding',
8 12 'UIParent', 'UIParent',
13 'UnitClass',
14 'UnitHealth',
15 'UnitHealthMax',
16 'UnitIsDead',
17 'UnitIsGhost',
9 18 'UnitName', 'UnitName',
10 19 'strtrim', 'strtrim',
11 20 } --[[ these globals can only be accessed ]]-- } --[[ these globals can only be accessed ]]--
File choir.lua changed (mode: 100644) (index aa1ed74..5ac0a5f)
1 local function getDefaultUnitButtonBarColor()
2 return 0, 1, 0
3 end
4
1 5 local function createLabel(ownerFrame, fontObject) local function createLabel(ownerFrame, fontObject)
2 6 assert (ownerFrame ~= nil) assert (ownerFrame ~= nil)
3 7
 
... ... local function createBackground(ownerFrame)
20 24 return background return background
21 25 end end
22 26
27 local function createUnitButtonBar(self)
28 assert (self ~= nil)
29
30 local b = self:CreateTexture(self:GetName() .. 'Bar', 'OVERLAY')
31 local padding = 4
32 b:SetPoint('BOTTOMLEFT', padding, padding)
33 b:SetPoint('TOPRIGHT', -padding, -padding)
34 b:SetTexture(getDefaultUnitButtonBarColor(), 1)
35
36 self.bar = b
37
38 return b
39 end
40
41 local function getUnitHealthDeficit(unitDesignation)
42 assert (unitDesignation ~= nil)
43
44 local m = UnitHealthMax(unitDesignation) or 0
45 local c = UnitHealth(unitDesignation) or 0
46
47 return math.abs(c) - math.abs(m)
48 end
49
50 local function getUnitHealthRatio(unitDesignation)
51 assert (unitDesignation ~= nil)
52
53 local m = math.abs(math.max(UnitHealthMax(unitDesignation) or 1, 1))
54 local c = math.abs(UnitHealth(unitDesignation) or 1)
55
56 return c / m
57 end
58
59 local function updateUnitButtonText(self)
60 assert (self ~= nil)
61
62 local label = self.text
63 assert (label ~= nil)
64
65 local unitDesignation = self:GetAttribute('unit')
66 assert (unitDesignation ~= nil)
67
68 local n = UnitName(unitDesignation) or unitDesignation
69
70 local key = GetBindingKey('CLICK ' .. self:GetName() .. ':LeftButton')
71 if not key then
72 key = self:GetAttribute('choirBindingKey')
73 end
74 if key then
75 n = n .. ' <' .. key .. '>'
76 end
77
78 local d = getUnitHealthDeficit(unitDesignation)
79 local t
80 if UnitIsDead(unitDesignation) then
81 t = n .. '\n\r' .. '(Dead)'
82 elseif UnitIsGhost(unitDesignation) then
83 t = n .. '\n\r' .. '(Ghost)'
84 elseif d < 0 then
85 t = n .. '\n\r' .. tostring(math.floor(d))
86 else
87 t = n
88 end
89
90 label:SetText(t)
91 end
92
93 local function getClassColor(classDesignation)
94 local t = RAID_CLASS_COLORS[classDesignation]
95 if not t then
96 return nil
97 end
98 return t['r'], t['g'], t['b']
99 end
100
101 local function updateUnitButtonBar(self)
102 assert (self ~= nil)
103
104 local bar = self.bar
105 assert (bar ~= nil)
106
107 local unitDesignation = self:GetAttribute('unit')
108 assert (unitDesignation ~= nil)
109
110 --[[ Apply bar color update ]]--
111 local _, classDesignation = UnitClass(unitDesignation)
112 local r, g, b = getClassColor(classDesignation)
113 --[[ TODO Range indicator ]]--
114 local a = 1
115 if not r or not g or not b then
116 r, g, b = getDefaultUnitButtonBarColor()
117 end
118 bar:SetTexture(r, g, b, a)
119
120 --[[ Apply bar width update ]]--
121
122 self:SetAttribute('choirUnitHealthRatio', getUnitHealthRatio(unitDesignation))
123 local secureHandler = self.secureHandler
124 secureHandler:Execute([=[
125 local u = self:GetFrameRef('ChoirUnitButton')
126 local unitDesignation = u:GetAttribute('unit')
127
128 local ratio = 1
129 if UnitExists(unitDesignation) then
130 ratio = u:GetAttribute('choirUnitHealthRatio')
131 end
132 ratio = math.min(math.max(0, ratio), 1)
133
134 local bar = self:GetFrameRef('ChoirBar')
135 local padding = 4
136 bar:SetPoint('BOTTOMLEFT', self, 'BOTTOMLEFT', padding, padding)
137 bar:SetPoint('TOPRIGHT', self, 'TOPLEFT', ratio * u:GetWidth() - padding, -padding)
138 ]=])
139 end
140
141 local function unitButtonEventProcessor(self)
142 updateUnitButtonText(self)
143 updateUnitButtonBar(self)
144 end
145
23 146 local function createUnitButton(parentFrame, frameName, unit) local function createUnitButton(parentFrame, frameName, unit)
24 147 assert (parentFrame ~= nil) assert (parentFrame ~= nil)
25 148 assert (frameName ~= nil) assert (frameName ~= nil)
 
... ... local function createUnitButton(parentFrame, frameName, unit)
29 152 u:SetAttribute('type', 'target') u:SetAttribute('type', 'target')
30 153 u:SetAttribute('unit', unit) u:SetAttribute('unit', unit)
31 154
32 u:SetSize(12 * 6, 12 * 2)
155 u:SetSize(12 * 8, 12 * 4)
33 156
34 157 u.text = createLabel(u) u.text = createLabel(u)
35 158
36 159 local b = createBackground(u) local b = createBackground(u)
37 b:SetTexture(1.0, 0.0, 0.4, 1.0)
38 160 u.background = b u.background = b
39 161
40 u:SetScript('OnEvent', function(self)
41 assert (self ~= nil)
42 local label = self.text
43 assert (label ~= nil)
44 local unitDesignation = self:GetAttribute('unit')
45 assert (unitDesignation ~= nil)
46 label:SetText(UnitName(unitDesignation) or unitDesignation)
47 end)
162 local bar = createUnitButtonBar(u)
163 u.bar = bar
164
165 local secureHandler = CreateFrame('BUTTON', frameName .. 'SecureHeader', u, 'SecureHandlerBaseTemplate')
166 secureHandler:SetAllPoints()
167 secureHandler:SetFrameRef('ChoirUnitButton', u)
168 secureHandler:SetFrameRef('ChoirBar', bar)
169 u.secureHandler = secureHandler
170
171 u:SetScript('OnEvent', unitButtonEventProcessor)
48 172 u:RegisterEvent('PARTY_CONVERTED_TO_RAID') u:RegisterEvent('PARTY_CONVERTED_TO_RAID')
49 173 u:RegisterEvent('PARTY_MEMBERS_CHANGED') u:RegisterEvent('PARTY_MEMBERS_CHANGED')
50 174 u:RegisterEvent('PLAYER_ALIVE') u:RegisterEvent('PLAYER_ALIVE')
51 175 u:RegisterEvent('RAID_ROSTER_UPDATE') u:RegisterEvent('RAID_ROSTER_UPDATE')
52 176 u:RegisterEvent('UPDATE_BATTLEFIELD_SCORE') u:RegisterEvent('UPDATE_BATTLEFIELD_SCORE')
53 177 u:RegisterEvent('ADDON_LOADED') u:RegisterEvent('ADDON_LOADED')
178 --[[ UNIT_HEALTH event with the current implementation does produce unnecessary calls.
179 -- Optimization is desireable. Code maintanence takes priority currently. ]]--
180 u:RegisterEvent('UNIT_HEALTH')
54 181
55 182 assert (u ~= nil) assert (u ~= nil)
56 183 return u return u
 
... ... local function createSpoiler(spoilerParent, spoilerDesignation)
63 190 -- the children table must be sorted and be iterated over in a specific order. -- the children table must be sorted and be iterated over in a specific order.
64 191 -- #GetChildList method seems to return the children in order they were created, -- #GetChildList method seems to return the children in order they were created,
65 192 -- but that might not be the case for #GetChildren variant or not at all. ]]-- -- but that might not be the case for #GetChildren variant or not at all. ]]--
66 --[[ TODO Check if it is possible to decouple the binding logic into a hooked script or something.
67 -- That way the sibling and children toggling could be re-used. ]]--
68 193 spoiler:SetAttribute('_onclick', [=[ spoiler:SetAttribute('_onclick', [=[
69 local debugFlag = true
70
194 --[[ Toggle sibling frames, which are other spoilers that contain unit buttons ]]--
71 195 local parentFrame = self:GetParent() local parentFrame = self:GetParent()
72 196 local siblingTable = parentFrame:GetChildList(newtable()) local siblingTable = parentFrame:GetChildList(newtable())
73 197 local i = 0 local i = 0
 
... ... local function createSpoiler(spoilerParent, spoilerDesignation)
78 202 sibling:Hide() sibling:Hide()
79 203 end end
80 204
205 --[[ Apply override bindings to children which are unit buttons of a specific raid group ]]--
81 206 self:Show() self:Show()
82 207 local j = 0 local j = 0
83 208 local childTable = self:GetChildList(newtable()) local childTable = self:GetChildList(newtable())
84 209 while (j < #childTable) do while (j < #childTable) do
85 210 j = j + 1 j = j + 1
86 211 local child = childTable[j] local child = childTable[j]
87 local unitDesignation = child:GetAttribute('unit')
88 if unitDesignation ~= nil and UnitExists(unitDesignation) and not debugFlag then
89 child:Show()
90 elseif debugFlag then
91 child:Show()
92 else
93 child:Hide()
94 end
95
96 local key = GetBindingKey('CLICK ChoirUnitButton' .. tostring(j) .. ':LeftButton')
97
98 local actionBarSize = 12
99 if not key then
100 if j >= 1 and j <= actionBarSize then
101 key = GetBindingKey('ACTIONBUTTON' .. tostring(j))
102 elseif j > actionBarSize and j <= actionBarSize * 6 then
103 local r = j % actionBarSize
104 local n = j - r * actionBarSize
105 key = GetBindingKey('MULTIACTIONBAR' .. r .. 'BUTTON' .. n)
106 end
107 end
212 child:Show()
108 213
214 local key = child:GetAttribute('choirBindingKey')
109 215 if key then if key then
110 216 self:SetBindingClick(true, key, child) self:SetBindingClick(true, key, child)
111 217 end end
 
... ... local function createSpoiler(spoilerParent, spoilerDesignation)
115 221 return spoiler return spoiler
116 222 end end
117 223
224 local function getButtonBindingKeyExplicit(buttonRef)
225 assert (buttonRef ~= nil)
226
227 local key = GetBindingKey('CLICK ' .. buttonRef:GetName() .. ':LeftButton')
228 return key
229 end
230
231 local function getButtonBindingKeyDefault(buttonNumber)
232 assert (buttonNumber ~= nil)
233 assert ('number' == type(buttonNumber))
234 buttonNumber = math.abs(math.floor(buttonNumber))
235
236 local key
237 local actionBarSize = 12
238 if buttonNumber >= 1 and buttonNumber <= actionBarSize then
239 key = GetBindingKey('ACTIONBUTTON' .. tostring(buttonNumber))
240 elseif buttonNumber > actionBarSize and buttonNumber <= actionBarSize * 6 then
241 local r = buttonNumber % actionBarSize
242 local n = buttonNumber - r * actionBarSize
243 key = GetBindingKey('MULTIACTIONBAR' .. r .. 'BUTTON' .. n)
244 else
245 key = nil
246 end
247
248 return key
249 end
250
118 251 local function createGroup(rootFrame, groupNumber, unitTable) local function createGroup(rootFrame, groupNumber, unitTable)
119 252 assert (rootFrame ~= nil) assert (rootFrame ~= nil)
120 253
 
... ... local function createGroup(rootFrame, groupNumber, unitTable)
140 273 assert (#u == groupSize) assert (#u == groupSize)
141 274
142 275 local spoiler = createSpoiler(rootFrame, 'ChoirSpoiler' .. tostring(groupNumber)) local spoiler = createSpoiler(rootFrame, 'ChoirSpoiler' .. tostring(groupNumber))
143 spoiler:SetSize(12 * 6, 12 * 2)
144 spoiler:SetPoint('CENTER', 0, 144)
276 spoiler:SetSize(12 * 6, 12 * 4)
277 spoiler:SetPoint('CENTER', 12 * 6 * 6 / -2, 144)
145 278
146 279 local i = 0 local i = 0
147 local marginLeft = spoiler:GetWidth()
280 local marginLeft = 0
148 281 local padding = 4 local padding = 4
149 282 while (i < #u) do while (i < #u) do
150 283 i = i + 1 i = i + 1
 
... ... local function createGroup(rootFrame, groupNumber, unitTable)
152 285 assert (unitDesignation ~= nil) assert (unitDesignation ~= nil)
153 286 local memberNumber = (groupNumber - 1) * groupSize + i local memberNumber = (groupNumber - 1) * groupSize + i
154 287 local b = createUnitButton(spoiler, 'ChoirUnitButton' .. tostring(memberNumber), unitDesignation) local b = createUnitButton(spoiler, 'ChoirUnitButton' .. tostring(memberNumber), unitDesignation)
155 b:SetPoint('BOTTOMLEFT', marginLeft + padding, 0)
288 b:SetPoint('BOTTOMLEFT', marginLeft, 0)
156 289 marginLeft = marginLeft + b:GetWidth() + padding marginLeft = marginLeft + b:GetWidth() + padding
157 290
158 291 spoiler:WrapScript(b, 'OnClick', [=[ spoiler:WrapScript(b, 'OnClick', [=[
159 print('OnClick', self:GetName())
160 --[[ Assume that the parent frame is the relevant spoiler frame ]]--
292 --[[ Assume that parent is the spoiler frame
293 -- which was created with createSpoiler and
294 -- has required scripts hooked to it ]]--
161 295 local spoilerFrame = self:GetParent() local spoilerFrame = self:GetParent()
162 296 spoilerFrame:ClearBindings() spoilerFrame:ClearBindings()
163 297 spoilerFrame:Hide() spoilerFrame:Hide()
164 298 ]=]) ]=])
165 299 b:Hide() b:Hide()
300
301 local key = getButtonBindingKeyExplicit(b) or getButtonBindingKeyDefault(i)
302 if key then
303 b:SetAttribute('choirBindingKey', key)
304 end
166 305
167 306 _G['BINDING_NAME_CLICK ' .. b:GetName() .. ':LeftButton'] = 'Unit ' .. tostring(i) _G['BINDING_NAME_CLICK ' .. b:GetName() .. ':LeftButton'] = 'Unit ' .. tostring(i)
168 307 end end
 
... ... end
207 346
208 347 local function main() local function main()
209 348 local rootFrame = CreateFrame('FRAME', 'ChoirFrame', UIParent) local rootFrame = CreateFrame('FRAME', 'ChoirFrame', UIParent)
210 rootFrame:RegisterEvent('ADDON_LOADED')
349 --[[ NOTE The add-on requires key bindings data.
350 -- Therefore trigger the add-on initialization after the key bindings were loaded. ]]--
351 rootFrame:RegisterEvent('PLAYER_LOGIN')
211 352 rootFrame:SetScript('OnEvent', init) rootFrame:SetScript('OnEvent', init)
212 353 end end
213 354 main() main()
Hints:
Before first commit, do not forget to setup your git environment:
git config --global user.name "your_name_here"
git config --global user.email "your@email_here"

Clone this repository using HTTP(S):
git clone https://rocketgit.com/user/vrtc/choir

Clone this repository using ssh (do not forget to upload a key first):
git clone ssh://rocketgit@ssh.rocketgit.com/user/vrtc/choir

Clone this repository using git:
git clone git://git.rocketgit.com/user/vrtc/choir

You are allowed to anonymously push to this repository.
This means that your pushed commits will automatically be transformed into a merge request:
... clone the repository ...
... make some changes and some commits ...
git push origin main