List of commits:
Subject Hash Author Date (UTC)
fix(choir): Update only existing units when necessary 82b7e0cfd4354263f27ea206f555e9771f2e8f8c Vladyslav Bondarenko 2021-12-31 07:45:00
feat(choir): Render unit threat situation dee7167e3a7e453425248a1062560f9bfd8b3bb4 Vladyslav Bondarenko 2021-12-28 01:39:09
fix(choir): Render dungeon role correctly for Cata 590a4abfd82659624688c4c1658de87693d3cd30 Vladyslav Bondarenko 2021-12-28 00:31:52
fix(choir): Show raid frame at player login 21a86d3840fb810c4ff4c67490aae7d5251c1bae Vladyslav Bondarenko 2021-11-24 06:07:10
feat(choir)!: Add interface options for raid frame ce677d3d0e7cfe05d1da5b159f52a0808db9ce9a Vladyslav Bondarenko 2021-11-24 06:05:37
feat(choir): Add conf spell shortcut default button c8e2c3493896f5ae589c516f742bbafd528140db Vladyslav Bondarenko 2021-11-24 02:28:19
feat(choir)!: Add configuration menu 12319ce873aecea4bfc0addec7c5ac5c1f225237 Vladyslav Bondarenko 2021-11-21 09:26:05
feat(choir): Add unit game tooltip 3ec490e489bb105f5096d5bb3e56814873deec3f Vladyslav Bondarenko 2021-11-20 09:34:12
fix(choir): Raid group frame arrange correctly b592f3f018ee521a821447ccf837aded703ad447 Vladyslav Bondarenko 2021-11-18 22:02:52
feat(choir): Render group role indicator for units 5f3a7c24d3f79035b8faa156355a2d017688134e Vladyslav Bondarenko 2021-11-18 22:02:27
feat(choir): Adjust raid group frame position f99a54133a1761034baca93bfa8814c614191818 Vladyslav Bondarenko 2021-11-18 00:35:35
feat(choir): Add debuff buttons for raid fe66339420a9eb5e4823cc4fe45e0d97b2983073 Vladyslav Bondarenko 2021-11-17 00:55:54
feat(choir): Add texture to health bars ddaeb29d7ecfd95a98bcc85b3f4f7676c7ccd4d7 Vladyslav Bondarenko 2021-11-14 21:40:26
fix(choir)!: Obscure critical error 0f203b4d69f57240e97c66f1ab4510c6ac3e9276 Vladyslav Bondarenko 2021-11-11 12:52:02
feat(choir): Toggle button visibility given roster d193e7b5eb38cb3ac69d74eec1f96e00d90b6098 Vladyslav Bondarenko 2021-11-10 23:46:24
feat(choir)!: Add permanent raid frame 1839c35af4212c09038e972547d6b5893a9ce219 Vladyslav Bondarenko 2021-11-10 16:04:25
feat(choir)!: Employ Clearcasting subset feat 733c81538c3c965f07993fd7ddc482e724121b75 Vladyslav Bondarenko 2021-11-04 22:40:48
fix(choir): Improve shortcut binding keys eb636de6e3f7bada9f0064ffe4db1db3f6433f6c Vladyslav Bondarenko 2021-10-31 18:55:10
fix(choir): Improve choirBindingKey attribute handling 6c5c2214cc1809e5e5e59cd3672da3cca3f2701f Vladyslav Bondarenko 2021-10-31 13:23:24
feat(choir)!: Add spell shortcut prototype d34f22a6983ffc41122acb40d22e3cb29c208a3c Vladyslav Bondarenko 2021-10-31 12:39:39
Commit 82b7e0cfd4354263f27ea206f555e9771f2e8f8c - fix(choir): Update only existing units when necessary
Performance issues persist. Apply more minor improvements. Specifically
ensure that updates are applied only when necessary.

Major portion of event handling system was replaced. It is done so that
more granular control over which frames are responsible of handling
specific categories of events. It reduces the amount of redundant calls,
updates and redraws. For that reason the appearance of the frames to the
user is also slightly changed.

Checking for unit existance before proceeding with the event handling
proved to be a significant performance improvement.

Also add buff row for raid frames since it is useful for disc priest and
probably other healers with healing-over-time effects.

Aso color-code the health bar to warn about critical character health
conditions. Some abilities behave differently depending on this
property, therefore it is important to report to the user.
Author: Vladyslav Bondarenko
Author date (UTC): 2021-12-31 07:45
Committer name: Vladyslav Bondarenko
Committer date (UTC): 2022-01-04 20:27
Parent(s): df423d71b6848ecce896ff4bfde0da0c39f998a4
Signer:
Signing key:
Signing status: N
Tree: 354383c92ebacf77609a08887d48a0b7f586b203
File Lines added Lines deleted
.luacheckrc 1 0
choir.lua 378 277
choir.toc 1 1
share/Minimalist.tga 0 0
File .luacheckrc changed (mode: 100644) (index eb53a2c..cc885a2)
... ... stds.choir = {
61 61 'ChoirSpoiler8', 'ChoirSpoiler8',
62 62 'ChoirSpoiler9', 'ChoirSpoiler9',
63 63 'ClearcastingFrame', 'ClearcastingFrame',
64 'UnifontRegular16',
64 65 } }
65 66 } }
66 67
File choir.lua changed (mode: 100644) (index 50eb78a..1adb44c)
1 --[[ TODO Add pet target button ]]--
2 --[[ TODO Add close spoiler button ]]--
3 --[[ TODO Add next and previous spoiler button ]]--
4 --[[ TODO Add new spoiler for enemy team in arena ]]--
5 --[[ TODO Add permanent unit buttons for player, target, focus and pet units ]]--
6 --[[ TODO Add offline indicator ]]--
7 --[[ TODO Add rest indicator ]]--
8 --[[ TODO Add right-click context menu ]]--
9 --[[ TODO Add role check indicator ]]--
10 --[[ FIXME Raid frame and spoiler overlap when raid roster updates ]]--
11 --[[ FIXME Range indicator sometimes does not update in time ]]--
12
1 13 local function trace(...) local function trace(...)
2 14 print(date('%X'), '[|cFF5F87AFChoir|r]:', ...) print(date('%X'), '[|cFF5F87AFChoir|r]:', ...)
3 15 end end
4 16
5 local function getDefaultUnitButtonBarColor()
6 return 0, 1, 0
7 end
8
9 local function createClearcastingSubset(unitButton)
10 assert (unitButton ~= nil)
11
12 if not ClearcastingFrame then
13 trace('could not access Clearcasting module')
14 return
15 end
16
17 local createSubset = ClearcastingFrame.createSubset
18 assert (createSubset ~= nil)
19
20 --[[ FIXME Update indicator unit designation when unit button attribute changes at runtime.
21 -- This is not an expected use-case. Rather it is a matter of robustness. ]]--
22 local unitDesignation = unitButton:GetAttribute('unit')
23 assert (unitDesignation ~= nil)
24
25 local harmSubset = createSubset(unitButton, unitButton:GetName() .. 'HarmfulSubsetFrame',
26 unitDesignation, 'HARMFUL',
27 5, 1)
28 harmSubset:SetPoint('BOTTOMLEFT', 0, 0)
29
30 --[[ TODO Track tank defensives in addition to player (healer) buffs. ]]--
31 local helpSubset = createSubset(unitButton, unitButton:GetName() .. 'HelpfulSubsetFrame',
32 unitDesignation, 'HELPFUL',
33 5, 1)
34 helpSubset:SetPoint('BOTTOMLEFT', 0, harmSubset:GetHeight())
35
36 return harmSubset, helpSubset
37 end
38
39 local function createClearcastingSubsetRaid(unitButton)
40 assert (unitButton ~= nil)
41
42 if not ClearcastingFrame then
43 trace('could not access Clearcasting module')
44 return
45 end
46
47 local createSubset = ClearcastingFrame.createSubset
48 assert (createSubset ~= nil)
49
50 --[[ FIXME Update indicator unit designation when unit button attribute changes at runtime.
51 -- This is not an expected use-case. Rather it is a matter of robustness. ]]--
52 local unitDesignation = unitButton:GetAttribute('unit')
53 assert (unitDesignation ~= nil)
54
55 local harmSubset = createSubset(unitButton, unitButton:GetName() .. 'HarmfulSubsetFrame',
56 unitDesignation, 'HARMFUL',
57 2, 1, 16, 32)
58 harmSubset:SetPoint('BOTTOMLEFT', 0, 0)
59
60 --[[
61 local t = {harmSubset:GetChildren()}
62 local i = 0
63 while (i < #t) do
64 i = i + 1
65 local b = t[i]
66 local label = b.text or _G[b:GetName() .. 'Text']
67 label:Hide()
68 end
69 ]]--
70
71 return harmSubset
72 end
73
74 17 local function createBindingKeyHandler(button) local function createBindingKeyHandler(button)
75 18 assert (button ~= nil) assert (button ~= nil)
76 19
 
... ... local function createBackground(ownerFrame)
142 85 return background return background
143 86 end end
144 87
145 local function createUnitButtonRoleWidget(unitButton)
88 local function getRoleTexCoord(isTank, isHealer, isDamager)
89 local size = 64
90 if isTank then
91 return 0 / size, 19 / size, 22 / size, 41 / size
92 elseif isHealer then
93 return 20 / size, 39 / size, 1 / size, 20 / size
94 elseif isDamager then
95 return 20 / size, 39 / size, 22 / size, 41 / size
96 else
97 error('invalid argument')
98 end
99 end
100
101 local function roleWidgetEventProcessor(roleWidget)
102 assert (roleWidget ~= nil)
103
104 local unitButton = roleWidget:GetParent()
105 assert (unitButton ~= nil)
106
107 local unitDesignation = unitButton:GetAttribute('unit')
108 assert (unitDesignation ~= nil)
109
110 if not UnitExists(unitDesignation) then
111 return
112 end
113
114 local isTank, isHealer, isDamager = UnitGroupRolesAssigned(unitDesignation)
115
116 --[[ Corner-case for Interface >= 40000 ]]--
117 if 'string' == type(isTank) then
118 local roleDesignation = isTank
119 isTank = 'TANK' == roleDesignation
120 isHealer = 'HEALER' == roleDesignation
121 isDamager = 'DAMAGER' == roleDesignation
122 end
123
124
125 if isTank or isHealer or isDamager then
126 roleWidget:Show()
127 local artwork = roleWidget.artwork
128 assert (artwork ~= nil)
129 artwork:SetTexCoord(getRoleTexCoord(isTank, isHealer, isDamager))
130 else
131 roleWidget:Hide()
132 end
133 end
134
135 local function createRoleWidget(unitButton)
146 136 assert (unitButton ~= nil) assert (unitButton ~= nil)
147 137
148 local widgetSize = 12
149 local roleWidget = unitButton:CreateTexture(unitButton:GetName() .. 'RoleWidget', 'OVERLAY')
150 roleWidget:SetPoint('TOPRIGHT', unitButton, 'TOPRIGHT', 0, 0)
151 roleWidget:SetPoint('BOTTOMLEFT', unitButton, 'TOPRIGHT', -widgetSize, -widgetSize)
152 roleWidget:SetTexture("Interface\\LFGFrame\\UI-LFG-ICON-PORTRAITROLES.blp")
138 local n = unitButton:GetName() .. 'RoleWidgetFrame'
139
140 local widgetSize = 24
141 local roleWidget = CreateFrame('FRAME', n, unitButton)
142 roleWidget:SetPoint('TOPLEFT', 0, 0)
143 roleWidget:SetPoint('TOPRIGHT', 0, 0)
144 roleWidget:SetSize(unitButton:GetWidth(), widgetSize / 2)
145
146 local artwork = roleWidget:CreateTexture(roleWidget:GetName() .. 'Artwork', 'ARTWORK')
147 artwork:SetPoint('TOPRIGHT', roleWidget, 'TOPRIGHT', widgetSize * 0.0, widgetSize * 0.2)
148 artwork:SetPoint('BOTTOMLEFT', roleWidget, 'TOPRIGHT', -widgetSize * 1.0, -widgetSize * 0.8)
149 artwork:SetTexture("Interface\\LFGFrame\\UI-LFG-ICON-PORTRAITROLES.blp")
150
151 roleWidget.artwork = artwork
152
153 roleWidget:RegisterEvent('LFG_ROLE_UPDATE')
154 roleWidget:RegisterEvent('PLAYER_ENTERING_BATTLEGROUND')
155 roleWidget:RegisterEvent('PLAYER_ENTERING_WORLD')
156 roleWidget:RegisterEvent('PLAYER_ROLES_ASSIGNED')
157 roleWidget:RegisterEvent('PLAYER_TALENT_UPDATE')
158 roleWidget:RegisterEvent('ZONE_CHANGED_NEW_AREA')
159
160 roleWidget:SetScript('OnEvent', roleWidgetEventProcessor)
153 161
154 162 return roleWidget return roleWidget
155 163 end end
 
... ... local function getUnitHealthRatio(unitDesignation)
172 180 return c / m return c / m
173 181 end end
174 182
175 local function updateUnitButtonBarText(bar, unitDesignation)
176 assert (bar ~= nil)
183 local function getClassColor(classDesignation)
184 assert (classDesignation ~= nil)
185 assert ('string' == type(classDesignation))
186 classDesignation = strtrim(classDesignation)
187 assert (string.len(classDesignation) >= 2)
188 assert (string.len(classDesignation) <= 64)
177 189
178 local label = bar.text
190 local t = RAID_CLASS_COLORS[classDesignation]
191 if not t then
192 return nil
193 end
194 return t['r'], t['g'], t['b']
195 end
196
197 local function healthBarEventProcessor(healthBarFrame, eventCategory, targetUnitDesignation)
198 assert (healthBarFrame ~= nil)
199 assert (eventCategory == 'UNIT_HEALTH' or eventCategory == 'UNIT_COMBAT')
200
201 local unitButton = healthBarFrame:GetParent()
202 assert (unitButton ~= nil)
203
204 local unitDesignation = unitButton:GetAttribute('unit')
205 assert (unitDesignation ~= nil)
206
207 if not UnitExists(unitDesignation) then
208 return
209 end
210
211 --[[ Assume UNIT_HEALTH event is processed for the relevant unit. ]]--
212 if targetUnitDesignation ~= unitDesignation then
213 return
214 end
215
216 local overlay = healthBarFrame.overlay
217 assert (overlay ~= nil)
218
219 --[[ Apply health bar width changes. ]]--
220 local ratio = 1
221 if UnitExists(unitDesignation) then
222 ratio = getUnitHealthRatio(unitDesignation) or 1
223 end
224 ratio = math.min(math.max(0, ratio or 1), 1)
225 overlay:SetPoint('BOTTOMLEFT', healthBarFrame, 'BOTTOMLEFT', 0, 0)
226 overlay:SetPoint('TOPRIGHT', healthBarFrame, 'TOPRIGHT', (ratio - 1) * healthBarFrame:GetWidth(), 0)
227
228 --[[ Apply health bar color changes. ]]--
229 local a = 1
230 local rangeSpell = ChoirRangeSpellName
231 --[[ NOTE IsSpellInRange returns either 0, 1 or nil ]]--
232 if rangeSpell and 1 ~= IsSpellInRange(rangeSpell, unitDesignation) then
233 a = 0.5
234 end
235
236 if ratio <= 0.35 then
237 overlay:SetVertexColor(1, 0, 0, a)
238 elseif ratio <= 0.5 then
239 overlay:SetVertexColor(1, 1, 0, a)
240 else
241 overlay:SetVertexColor(0, 1, 0, a)
242 end
243
244 --[[ Apply health bar text content. ]]--
245 local label = healthBarFrame.label
179 246 assert (label ~= nil) assert (label ~= nil)
180 247
181 248 local t local t
 
... ... local function updateUnitButtonBarText(bar, unitDesignation)
199 266 label:SetText(t) label:SetText(t)
200 267 end end
201 268
202 local function updateUnitButtonText(self)
203 assert (self ~= nil)
204
205 local label = self.text
206 assert (label ~= nil)
269 local function createHealthBar(unitButton, width, height)
270 assert (unitButton ~= nil)
207 271
208 local unitDesignation = self:GetAttribute('unit')
209 assert (unitDesignation ~= nil)
272 assert (width ~= nil)
273 assert ('number' == type(width))
274 assert (width >= 12)
275 assert (width <= 288)
210 276
211 local n = UnitName(unitDesignation) or unitDesignation
277 assert (height ~= nil)
278 assert ('number' == type(height))
279 assert (height >= 12)
280 assert (height <= 288)
212 281
213 local key = GetBindingKey('CLICK ' .. self:GetName() .. ':LeftButton')
214 if not key then
215 key = self:GetAttribute('choirBindingKey')
216 end
217 if key then
218 n = n .. ' <' .. key .. '>'
219 end
282 local n = (unitButton:GetName() or '') .. 'HealthBarFrame'
283 local healthBarFrame = CreateFrame('FRAME', n, unitButton)
284 healthBarFrame:SetSize(width, height)
220 285
221 local m = math.floor(self:GetWidth() / 8)
286 local b = healthBarFrame:CreateTexture(n .. 'Overlay', 'OVERLAY')
287 b:SetAllPoints()
288 b:SetTexture("Interface\\AddOns\\choir\\share\\Minimalist.tga")
289 b:SetVertexColor(0, 1, 0, 1)
222 290
223 label:SetText(string.sub(n, 1, m))
291 local t = createLabel(healthBarFrame, UnifontRegular16)
224 292
225 local r = 1
226 local g = 1
227 local b = 1
228 local threatStatus = UnitThreatSituation(unitDesignation)
229 if threatStatus then
230 r, g, b = GetThreatStatusColor(threatStatus)
231 end
232 label:SetTextColor(r, g, b)
233 end
293 healthBarFrame.label = t
294 healthBarFrame.overlay = b
234 295
235 local function getClassColor(classDesignation)
236 assert (classDesignation ~= nil)
237 assert ('string' == type(classDesignation))
238 classDesignation = strtrim(classDesignation)
239 assert (string.len(classDesignation) >= 2)
240 assert (string.len(classDesignation) <= 64)
296 healthBarFrame:RegisterEvent('UNIT_COMBAT')
297 healthBarFrame:RegisterEvent('UNIT_HEALTH')
298 healthBarFrame:SetScript('OnEvent', healthBarEventProcessor)
241 299
242 local t = RAID_CLASS_COLORS[classDesignation]
243 if not t then
244 return nil
245 end
246 return t['r'], t['g'], t['b']
300 return healthBarFrame
247 301 end end
248 302
249 local function updateUnitButtonBarOverlay(bar, unitDesignation)
250 assert (bar ~= nil)
303 local function headerEventProcessor(headerFrame)
304 assert (headerFrame ~= nil)
251 305
252 assert (unitDesignation ~= nil)
253 assert ('string' == type(unitDesignation))
254 unitDesignation = strtrim(unitDesignation)
255 assert (string.len(unitDesignation) >= 2)
256 assert (string.len(unitDesignation) <= 32)
306 local unitButton = headerFrame:GetParent()
307 assert (unitButton ~= nil)
257 308
258 --[[ Apply bar color update ]]--
259 local r, g, b = getDefaultUnitButtonBarColor()
309 local unitDesignation = unitButton:GetAttribute('unit')
310 assert (unitDesignation ~= nil)
260 311
261 local _, classDesignation = UnitClass(unitDesignation)
262 if classDesignation then
263 r, g, b = getClassColor(classDesignation)
312 if not UnitExists(unitDesignation) then
313 return
264 314 end end
265 315
266 --[[ TODO Add line of sight indicator ]]--
267 local a = 1
268 local rangeSpell = ChoirRangeSpellName
269 --[[ NOTE IsSpellInRange returns either 0, 1 or nil ]]--
270 if rangeSpell and 1 ~= IsSpellInRange(rangeSpell, unitDesignation) then
271 a = 1 / 2
316 local n
317 local unitName = UnitName(unitDesignation)
318 if not unitName or string.len(unitName) < 2 then
319 n = unitDesignation
320 else
321 n = unitName
272 322 end end
273 323
274 local overlay = bar.overlay
275 assert (overlay ~= nil)
276 overlay:SetVertexColor(r, g, b, a)
277
278 --[[ Apply bar width update ]]--
279 local ratio = 1
280 if UnitExists(unitDesignation) then
281 ratio = getUnitHealthRatio(unitDesignation) or 1
324 local m = math.floor(headerFrame:GetWidth() / 16)
325 local sanitizedName = string.sub(n, 1, m)
326 if string.len(sanitizedName) < string.len(unitName) then
327 sanitizedName = sanitizedName .. '…'
282 328 end end
283 ratio = math.min(math.max(0, ratio or 1), 1)
284 overlay:SetPoint('BOTTOMLEFT', bar, 'BOTTOMLEFT', 0, 0)
285 overlay:SetPoint('TOPRIGHT', bar, 'TOPRIGHT', (ratio - 1) * bar:GetWidth(), 0)
286 end
287 329
288 local function createUnitButtonBar(unitButton)
289 assert (unitButton ~= nil)
290
291 local n = unitButton:GetName() or ''
292 local padding = 4
293 local marginTop = 24
294 local barHeight = 12
295 local bar = CreateFrame('FRAME', n .. 'Bar', unitButton)
296 bar:SetPoint('BOTTOMLEFT', unitButton, 'TOPLEFT', padding, -padding - marginTop - barHeight)
297 bar:SetPoint('TOPRIGHT', unitButton, 'TOPRIGHT', -padding, -padding - marginTop)
298
299 local b = bar:CreateTexture(bar:GetName() .. 'Overlay', 'OVERLAY')
300 b:SetAllPoints()
301 --b:SetTexture("Interface\\TARGETINGFRAME\\BarFill2")
302 b:SetTexture("Interface\\TARGETINGFRAME\\UI-StatusBar")
303 --b:SetTexture("Interface\\TARGETINGFRAME\\UI-TargetingFrame-BarFill")
304 b:SetVertexColor(getDefaultUnitButtonBarColor(), 1)
305 bar.overlay = b
306
307 local t = createLabel(bar)
308 bar.text = t
309
310 bar.unitButton = unitButton
311
312 --[[ Update health indicator ]]--
313 bar:SetScript('OnEvent', function(healthBarFrame)
314 assert (healthBarFrame ~= nil)
315 assert (unitButton ~= nil)
316
317 local u = unitButton:GetAttribute('unit')
318 assert (u ~= nil)
319 updateUnitButtonBarOverlay(healthBarFrame, u)
320 updateUnitButtonBarText(healthBarFrame, u)
321 end)
322 bar:RegisterEvent('PLAYER_FOCUS_CHANGED')
323 bar:RegisterEvent('PLAYER_TARGET_CHANGED')
324 bar:RegisterEvent('UNIT_HEALTH')
325 --[[ NOTE UNIT_SPELLCAST_* family of events are relied on to render range indicator correctly,
326 -- instead of the more expensive update hook. ]]--
327 bar:RegisterEvent('UNIT_SPELLCAST_FAILED')
328 bar:RegisterEvent('UNIT_SPELLCAST_FAILED_QUIET')
329 bar:RegisterEvent('UNIT_SPELLCAST_SENT')
330 bar:RegisterEvent('UNIT_SPELLCAST_START')
331
332 return bar
333 end
330 local key = GetBindingKey('CLICK ' .. unitButton:GetName() .. ':LeftButton')
331 if not key then
332 key = unitButton:GetAttribute('choirBindingKey')
333 end
334 334
335 local function getRoleTexCoord(isTank, isHealer, isDamager)
336 local size = 64
337 if isTank then
338 return 0 / size, 19 / size, 22 / size, 41 / size
339 elseif isHealer then
340 return 20 / size, 39 / size, 1 / size, 20 / size
341 elseif isDamager then
342 return 20 / size, 39 / size, 22 / size, 41 / size
335 local y
336 if key then
337 y = ' <' .. key .. '>'
343 338 else else
344 error('invalid argument')
339 y = ''
345 340 end end
346 end
347 341
348 local function updateUnitButtonRoleWidget(roleWidget, unitDesignation)
349 assert (roleWidget ~= nil)
350 assert (unitDesignation ~= nil)
342 sanitizedName = sanitizedName .. y
351 343
352 local isTank, isHealer, isDamager = UnitGroupRolesAssigned(unitDesignation)
344 local label = headerFrame.label
345 assert (label ~= nil)
353 346
354 --[[ Corner-case for Interface >= 40000 ]]--
355 if 'string' == type(isTank) then
356 local roleDesignation = isTank
357 isTank = 'TANK' == roleDesignation
358 isHealer = 'HEALER' == roleDesignation
359 isDamager = 'DAMAGER' == roleDesignation
360 end
347 label:SetText(sanitizedName)
361 348
362 if isTank or isHealer or isDamager then
363 roleWidget:Show()
364 roleWidget:SetTexCoord(getRoleTexCoord(isTank, isHealer, isDamager))
365 else
366 roleWidget:Hide()
349 local _, classDesignation = UnitClass(unitDesignation)
350 local r = 1
351 local g = 1
352 local b = 1
353 if classDesignation then
354 r, g, b = getClassColor(classDesignation)
367 355 end end
356 label:SetTextColor(r, g, b)
368 357 end end
369 358
370 local function unitButtonEventProcessor(unitButton)
359 local function createHeader(unitButton, width, height)
371 360 assert (unitButton ~= nil) assert (unitButton ~= nil)
372 361
373 updateUnitButtonText(unitButton)
362 assert (width ~= nil)
363 assert ('number' == type(width))
364 assert (width >= 12)
365 assert (width <= 288)
374 366
375 local bar = unitButton.bar
376 assert (unitButton ~= nil)
367 assert (height ~= nil)
368 assert ('number' == type(height))
369 assert (height >= 12)
370 assert (height <= 288)
377 371
378 local unitDesignation = unitButton:GetAttribute('unit')
379 assert (unitDesignation ~= nil)
380 assert ('string' == type(unitDesignation))
381 unitDesignation = strtrim(unitDesignation)
382 assert (string.len(unitDesignation) >= 2)
383 assert (string.len(unitDesignation) <= 32)
372 local n = (unitButton:GetName() or '') .. 'HeaderFrame'
373 local headerFrame = CreateFrame('FRAME', n, unitButton)
374 headerFrame:SetSize(width, height)
375
376 local t = createLabel(headerFrame, UnifontRegular16)
377 assert (t ~= nil)
384 378
385 updateUnitButtonBarOverlay(bar, unitDesignation)
386 updateUnitButtonBarText(bar, unitDesignation)
387 updateUnitButtonRoleWidget(unitButton.roleWidget, unitDesignation)
379 headerFrame.label = t
380
381 headerFrame:RegisterEvent('PARTY_CONVERTED_TO_RAID')
382 headerFrame:RegisterEvent('PARTY_MEMBERS_CHANGED')
383 headerFrame:RegisterEvent('PLAYER_ENTERING_WORLD')
384 headerFrame:RegisterEvent('RAID_ROSTER_UPDATE')
385 headerFrame:SetScript('OnEvent', headerEventProcessor)
386
387 assert (headerFrame ~= nil)
388 return headerFrame
388 389 end end
389 390
390 391 local function createInheritanceHandler(unitButton) local function createInheritanceHandler(unitButton)
 
... ... local function createUnitButtonTooltip(unitButton)
454 455 end) end)
455 456 end end
456 457
457 local function createUnitButton(parentFrame, frameName, unit, width, height)
458 local function threatWidgetEventProcessor(threatWidget)
459 assert (threatWidget ~= nil)
460
461 local unitButton = threatWidget:GetParent()
462 assert (unitButton ~= nil)
463
464 local u = unitButton:GetAttribute('unit')
465 assert (u ~= nil)
466
467 if not UnitExists(u) then
468 return
469 end
470
471 local r = 0
472 local g = 0
473 local b = 0
474 local a = 0
475 local threatStatus = UnitThreatSituation(u)
476 if threatStatus then
477 r, g, b = GetThreatStatusColor(threatStatus)
478 a = 1
479 end
480 local background = threatWidget.background
481 assert (background ~= nil)
482 background:SetVertexColor(r, g, b, a)
483 end
484
485 local function createThreatWidget(unitButton, width, height)
486 local t = CreateFrame('FRAME', unitButton:GetName() .. 'ThreatFrame', unitButton)
487 t:SetSize(width, height)
488
489 local background = t:CreateTexture(t:GetName() .. 'Background', 'BACKGROUND')
490 background:SetAllPoints()
491 background:SetTexture("Interface\\AddOns\\choir\\share\\Minimalist.tga")
492 t.background = background
493
494 t:RegisterEvent('UNIT_THREAT_SITUATION_UPDATE')
495 t:RegisterEvent('PLAYER_ENTERING_WORLD')
496 t:SetScript('OnEvent', threatWidgetEventProcessor)
497
498 return t
499 end
500
501 local function createClearcastingSubset(unitButton, targetFilter)
502 assert (unitButton ~= nil)
503
504 assert (targetFilter ~= nil)
505 assert ('string' == type(targetFilter))
506
507 local unitDesignation = unitButton:GetAttribute('unit')
508 assert (unitDesignation ~= nil)
509
510 targetFilter = string.upper(strtrim(targetFilter))
511 local n = unitButton:GetName() .. 'Clearcasting' .. targetFilter
512
513 local width = unitButton:GetWidth()
514 local buttonSize = 24
515 local columnQuantity = math.floor(width / buttonSize)
516 local rowQuantity = 1
517
518 assert (ClearcastingFrame ~= nil)
519 local createSubset = ClearcastingFrame.createSubset
520 assert (createSubset ~= nil)
521
522 return createSubset(unitButton, n,
523 unitDesignation, targetFilter,
524 columnQuantity, rowQuantity)
525 end
526
527 local function createUnitButton(parentFrame, frameName, unit,
528 someFilterDescriptorFirst, someFilterDescriptorLast, width, height)
458 529 assert (parentFrame ~= nil) assert (parentFrame ~= nil)
459 530 assert (frameName ~= nil) assert (frameName ~= nil)
460 531 assert (unit ~= nil) assert (unit ~= nil)
461 532
462 --[[ TODO Add children buttons that are secure spell buttons on the same target as the unit button.
463 -- Set override bindings to those buttons as ALT-A, CTRL-A, SHIFT-A, where A is the unit button key.
464 -- The spells assigned to the buttons let be something like Cleanse, Dispel Magic or Flash Heal.
465 -- That way, user may cast something without targeting or hiding the selection spoiler.
466 -- The problem is that it is class dependant.]]--
467 533 local u = CreateFrame('BUTTON', frameName, parentFrame, 'SecureUnitButtonTemplate') local u = CreateFrame('BUTTON', frameName, parentFrame, 'SecureUnitButtonTemplate')
468 534
469 535 createBindingKeyHandler(u) createBindingKeyHandler(u)
470 536 createInheritanceHandler(u) createInheritanceHandler(u)
471 537
538 u:SetAttribute('type', 'target')
539
540 local padding = 4
472 541 if not width then if not width then
473 width = 24 * 5 + 3 * 6
542 width = 16
474 543 end end
475 assert (width >= 12 and width <= 144)
544 assert (width >= 12 and width <= 288)
476 545
477 546 if not height then if not height then
478 height = 40 * 2 + 3 * 3 + 24 * 2
547 height = 12
479 548 end end
480 assert (height >= 12 and height <= 144)
549 assert (height >= 12 and height <= 288)
481 550
482 551 u:SetSize(width, height) u:SetSize(width, height)
483 552
484 local t = createLabel(u)
485 t:SetPoint('BOTTOMLEFT', u, 'TOPLEFT', 4, -24 - 4)
486 t:SetPoint('TOPRIGHT', u, 'TOPRIGHT', -4, -4)
487 u.text = t
553 createUnitButtonExistanceHandler(u)
554 createUnitButtonTooltip(u)
488 555
489 local b = createBackground(u)
490 u.background = b
556 u:SetAttribute('unit', unit)
491 557
492 local bar = createUnitButtonBar(u)
493 u.bar = bar
558 local roleWidget = createRoleWidget(u)
559 local headerFrame = createHeader(u, width, 24)
560 local healthBarFrame = createHealthBar(u, width, 24)
561 local threatWidget = createThreatWidget(u, width, 6)
494 562
495 local roleWidget = createUnitButtonRoleWidget(u)
496 u.roleWidget = roleWidget
563 local buffRowFirst
564 local buffRowLast
565 if someFilterDescriptorFirst then
566 buffRowFirst = createClearcastingSubset(u, someFilterDescriptorFirst)
567 end
568 if someFilterDescriptorLast then
569 buffRowLast = createClearcastingSubset(u, someFilterDescriptorLast)
570 end
497 571
498 createUnitButtonExistanceHandler(u)
499 createUnitButtonTooltip(u)
572 local sectionTable = {roleWidget, headerFrame, healthBarFrame, threatWidget, buffRowFirst, buffRowLast}
500 573
501 u:SetAttribute('type', 'target')
502 u:SetAttribute('unit', unit)
574 local i = 0
575 local j = 0
576 local marginLeft = 0
577 local marginRight = 0
578 local y = 0
579 local sectionHeight = 0
580 while (i < #sectionTable) do
581 i = i + 1
582
583 local section = sectionTable[i]
584 if section then
585 j = j + 1
503 586
504 u:SetScript('OnEvent', unitButtonEventProcessor)
505 u:RegisterEvent('PARTY_CONVERTED_TO_RAID')
506 u:RegisterEvent('PARTY_MEMBERS_CHANGED')
507 u:RegisterEvent('PLAYER_ALIVE')
508 u:RegisterEvent('RAID_ROSTER_UPDATE')
509 u:RegisterEvent('UPDATE_BATTLEFIELD_SCORE')
510 u:RegisterEvent('ADDON_LOADED')
511 u:RegisterEvent('LFG_ROLE_UPDATE')
512 u:RegisterEvent('UNIT_COMBAT')
587 width = math.max(width, section:GetWidth())
588 sectionHeight = sectionHeight + section:GetHeight()
589
590 section:SetPoint('TOPLEFT', marginLeft, y)
591 section:SetPoint('BOTTOMRIGHT', u, 'TOPRIGHT', -marginRight, y - section:GetHeight())
592 y = y - section:GetHeight() - padding
593 end
594 end
595 sectionHeight = sectionHeight + (j + 1) * padding
596 height = math.max(height, sectionHeight)
597 u:SetSize(width, height)
598
599 createBackground(u)
513 600
514 601 assert (u ~= nil) assert (u ~= nil)
515 602 return u return u
 
... ... local function createGroup(rootFrame, groupNumber, unitTable)
726 813 local i = 0 local i = 0
727 814 local marginLeft = 0 local marginLeft = 0
728 815 local padding = 4 local padding = 4
816 local buttonWidth = 12 * 16
817 local buttonHeight = 12 * 12
729 818 while (i < #u) do while (i < #u) do
730 819 i = i + 1 i = i + 1
731 820 local unitDesignation = u[i] local unitDesignation = u[i]
732 821 assert (unitDesignation ~= nil) assert (unitDesignation ~= nil)
733 822 local memberNumber = (groupNumber - 1) * groupSize + i local memberNumber = (groupNumber - 1) * groupSize + i
734 local b = createUnitButton(spoiler, 'ChoirUnitButton' .. tostring(memberNumber), unitDesignation)
823 local b = createUnitButton(spoiler, 'ChoirUnitButton' .. tostring(memberNumber), unitDesignation,
824 'HELPFUL', 'HARMFUL', buttonWidth, buttonHeight)
735 825 b:SetPoint('BOTTOMLEFT', marginLeft, 0) b:SetPoint('BOTTOMLEFT', marginLeft, 0)
736 826 marginLeft = marginLeft + b:GetWidth() + padding marginLeft = marginLeft + b:GetWidth() + padding
737 827
 
... ... local function createGroup(rootFrame, groupNumber, unitTable)
752 842 _G['BINDING_NAME_CLICK ' .. b:GetName() .. ':LeftButton'] = 'Unit ' .. tostring(i) _G['BINDING_NAME_CLICK ' .. b:GetName() .. ':LeftButton'] = 'Unit ' .. tostring(i)
753 843
754 844 createUnitButtonSpellShortcut(b, i) createUnitButtonSpellShortcut(b, i)
755 createClearcastingSubset(b)
756 845 end end
757 spoiler:SetSize(marginLeft, 144 + 24)
846 spoiler:SetSize(marginLeft, 12 * 20)
758 847 spoiler:SetPoint('CENTER', 0, 12 * 6) spoiler:SetPoint('CENTER', 0, 12 * 6)
759 848
760 local title = createLabel(spoiler)
849 local title = createLabel(spoiler, UnifontRegular16)
761 850 title:SetPoint('TOPRIGHT', 0, 0) title:SetPoint('TOPRIGHT', 0, 0)
762 851 title:SetPoint('BOTTOMLEFT', spoiler, 'TOPLEFT', spoiler:GetWidth() / 2 - title:GetWidth() / 2, -24) title:SetPoint('BOTTOMLEFT', spoiler, 'TOPLEFT', spoiler:GetWidth() / 2 - title:GetWidth() / 2, -24)
763 852 title:SetText('Group ' .. tostring(groupNumber)) title:SetText('Group ' .. tostring(groupNumber))
 
... ... end
773 862 local function arrangeEveryRaidGroupFrame(raidFrame) local function arrangeEveryRaidGroupFrame(raidFrame)
774 863 assert (raidFrame ~= nil) assert (raidFrame ~= nil)
775 864
865 local activeRaidGroupQuantity = 0
866 local marginLeft = 12 * 6
867 local maxRowQuantity = 4
868 local padding = 0
869 local row = 0
870
776 871 local i = 0 local i = 0
777 872 local t = {raidFrame:GetChildren()} local t = {raidFrame:GetChildren()}
873 local x = 0
778 874 local y = 0 local y = 0
779 local padding = 0
780 875 while (i < #t) do while (i < #t) do
781 876 i = i + 1 i = i + 1
782 877 local raidGroupFrame = t[i] local raidGroupFrame = t[i]
783 878 if raidGroupFrame and raidGroupFrame:IsShown() then if raidGroupFrame and raidGroupFrame:IsShown() then
784 raidGroupFrame:SetPoint('BOTTOMLEFT', raidFrame, 'BOTTOMLEFT', 0, y)
879 raidGroupFrame:SetPoint('BOTTOMLEFT', raidFrame, 'BOTTOMLEFT', x, y)
880 x = math.floor(activeRaidGroupQuantity / maxRowQuantity) * (marginLeft + raidGroupFrame:GetWidth() + padding)
785 881 y = y + raidGroupFrame:GetHeight() + padding y = y + raidGroupFrame:GetHeight() + padding
882 row = row + 1
883 if row > maxRowQuantity then
884 row = 0
885 y = 0
886 end
887 activeRaidGroupQuantity = activeRaidGroupQuantity + 1
786 888 end end
787 889 end end
788 890 end end
 
... ... local function createRaidGroupLabel(groupFrame, groupNumber)
839 941 local labelWidth = 60 local labelWidth = 60
840 942
841 943 local groupLabel = groupFrame:CreateFontString(groupFrame:GetName() .. 'Label', 'OVERLAY') local groupLabel = groupFrame:CreateFontString(groupFrame:GetName() .. 'Label', 'OVERLAY')
842 local fontObject = NumberFont_OutlineThick_Mono_Small
944 local fontObject = UnifontRegular16 or NumberFont_OutlineThick_Mono_Small
843 945 assert (fontObject ~= nil) assert (fontObject ~= nil)
844 946 groupLabel:SetFontObject(fontObject) groupLabel:SetFontObject(fontObject)
845 947 groupLabel:SetPoint('BOTTOMLEFT', groupFrame, 'BOTTOMLEFT', 0, 0) groupLabel:SetPoint('BOTTOMLEFT', groupFrame, 'BOTTOMLEFT', 0, 0)
 
... ... local function createRaidGroupFrame(raidFrame, groupNumber, unitSetOverride)
874 976 assert (#unitSetOverride == maxPartySize) assert (#unitSetOverride == maxPartySize)
875 977 end end
876 978
877 local buttonWidth = 48
878 local buttonHeight = 24 + 12 + 8 + 36
979 local buttonWidth = 12 * 6
980 local buttonHeight = 12 * 12
879 981 local padding = 2 local padding = 2
880 982 local labelWidth = 60 local labelWidth = 60
881 983
 
... ... local function createRaidGroupFrame(raidFrame, groupNumber, unitSetOverride)
904 1006 assert (unitDesignation ~= nil) assert (unitDesignation ~= nil)
905 1007
906 1008 local n = groupFrame:GetName() .. 'RaidUnitButton' .. tostring(i) local n = groupFrame:GetName() .. 'RaidUnitButton' .. tostring(i)
907 local b = createUnitButton(groupFrame, n, unitDesignation, buttonWidth, buttonHeight)
1009 local b = createUnitButton(groupFrame, n, unitDesignation,
1010 'PLAYER HELPFUL', 'HARMFUL', buttonWidth, buttonHeight)
908 1011 b:SetPoint('BOTTOMLEFT', labelWidth + (i - 1) * (padding + b:GetWidth()), 0) b:SetPoint('BOTTOMLEFT', labelWidth + (i - 1) * (padding + b:GetWidth()), 0)
909 createClearcastingSubsetRaid(b)
910 1012 end end
911 1013
912 1014 groupFrame:RegisterEvent('PARTY_CONVERTED_TO_RAID') groupFrame:RegisterEvent('PARTY_CONVERTED_TO_RAID')
 
... ... local function createRaidFrame(rootFrame, spoilerHolder)
937 1039 local maxPartySize = 5 local maxPartySize = 5
938 1040 local maxSubgroupQuantity = 8 local maxSubgroupQuantity = 8
939 1041
940 local buttonWidth = 48
941 local buttonHeight = 24 + 12 + 8 + 36
1042 local buttonWidth = 72
1043 local buttonHeight = 12 * 6
942 1044 local padding = 2 local padding = 2
943 1045
944 1046 local labelWidth = 60 local labelWidth = 60
 
... ... local function createRaidFrame(rootFrame, spoilerHolder)
946 1048 raidFrame:SetSize(labelWidth + (padding + buttonWidth) * maxPartySize, raidFrame:SetSize(labelWidth + (padding + buttonWidth) * maxPartySize,
947 1049 (padding + buttonHeight) * (maxSubgroupQuantity / 2)) (padding + buttonHeight) * (maxSubgroupQuantity / 2))
948 1050
949 --[[ TODO Add any debuff indicator ]]--
950 1051 local j = 0 local j = 0
951 1052 while (j < maxSubgroupQuantity) do while (j < maxSubgroupQuantity) do
952 1053 j = j + 1 j = j + 1
File choir.toc changed (mode: 100644) (index 3a926ca..58608bc)
1 ##Dependencies: Clearcasting
1 ##Dependencies: Clearcasting, Unifont
2 2 ##Interface: 30300 ##Interface: 30300
3 3 ##Notes: Raid targeting aid for healers ##Notes: Raid targeting aid for healers
4 4 ##SavedVariablesPerCharacter: ChoirRangeSpellName, ChoirShortcutSpellNameList, ChoirShortcutBindingKeyMap, ChoirConfRaidFlag, ChoirConfRaidX, ChoirConfRaidY ##SavedVariablesPerCharacter: ChoirRangeSpellName, ChoirShortcutSpellNameList, ChoirShortcutBindingKeyMap, ChoirConfRaidFlag, ChoirConfRaidX, ChoirConfRaidY
File share/Minimalist.tga added (mode: 100644) (index 0000000..030bc83)
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