List of commits:
Subject Hash Author Date (UTC)
fix(choir)!: Health bar in combat a6622578dd5a1b4e4babf699a4cf1e4eb6bb70d6 Vladyslav Bondarenko 2021-10-28 07:42:57
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 a6622578dd5a1b4e4babf699a4cf1e4eb6bb70d6 - fix(choir)!: Health bar in combat
Render the unit health bar on the unit button properly, especially in
combat.

Apparently, the texture and frame width changes need not be executed
from a restricted environment to be applied in combat.

Additionally, change the appearance of the health bar and the unit button.
This is done firstly, to decouple logic. Secondly, to ensure that the
health bar is rendered correctly with the new frame layout.

Note, that in previous commits when key binding override was removed from
spoiler frame secure handler callback, it also solved the potential
issue with the ordering of children. The required order is ensured
when the frame is allocated in the #init function.
Author: Vladyslav Bondarenko
Author date (UTC): 2021-10-28 07:42
Committer name: Vladyslav Bondarenko
Committer date (UTC): 2021-10-28 07:42
Parent(s): 54cb7800865bd4bc3c0aadf2775bfac24a1a2971
Signer:
Signing key:
Signing status: N
Tree: fc831ee579a7b8281f935bcc0a88f33afc88a73a
File Lines added Lines deleted
.luacheckrc 1 0
choir.lua 105 70
File .luacheckrc changed (mode: 100644) (index df7e370..700131a)
... ... stds.wow = {
11 11 'SetOverrideBinding', 'SetOverrideBinding',
12 12 'UIParent', 'UIParent',
13 13 'UnitClass', 'UnitClass',
14 'UnitExists',
14 15 'UnitHealth', 'UnitHealth',
15 16 'UnitHealthMax', 'UnitHealthMax',
16 17 'UnitIsDead', 'UnitIsDead',
File choir.lua changed (mode: 100644) (index 5ac0a5f..3571a23)
... ... local function createBackground(ownerFrame)
24 24 return background return background
25 25 end end
26 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 27 local function getUnitHealthDeficit(unitDesignation) local function getUnitHealthDeficit(unitDesignation)
42 28 assert (unitDesignation ~= nil) assert (unitDesignation ~= nil)
43 29
 
... ... local function getUnitHealthRatio(unitDesignation)
56 42 return c / m return c / m
57 43 end end
58 44
45 local function updateUnitButtonBarText(bar, unitDesignation)
46 assert (bar ~= nil)
47
48 local label = bar.text
49 assert (label ~= nil)
50
51 local t
52 local d = getUnitHealthDeficit(unitDesignation) or 0
53 if UnitIsDead(unitDesignation) then
54 t = '(Dead)'
55 elseif UnitIsGhost(unitDesignation) then
56 t = '(Ghost)'
57 elseif d < 0 then
58 t = tostring(math.floor(d))
59 else
60 t = nil
61 end
62
63 label:SetText(t)
64 end
65
59 66 local function updateUnitButtonText(self) local function updateUnitButtonText(self)
60 67 assert (self ~= nil) assert (self ~= nil)
61 68
 
... ... local function updateUnitButtonText(self)
75 82 n = n .. ' <' .. key .. '>' n = n .. ' <' .. key .. '>'
76 83 end end
77 84
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)
85 label:SetText(n)
91 86 end end
92 87
93 88 local function getClassColor(classDesignation) local function getClassColor(classDesignation)
89 assert (classDesignation ~= nil)
90 assert ('string' == type(classDesignation))
91 classDesignation = strtrim(classDesignation)
92 assert (string.len(classDesignation) >= 2)
93 assert (string.len(classDesignation) <= 64)
94
94 95 local t = RAID_CLASS_COLORS[classDesignation] local t = RAID_CLASS_COLORS[classDesignation]
95 96 if not t then if not t then
96 97 return nil return nil
 
... ... local function getClassColor(classDesignation)
98 99 return t['r'], t['g'], t['b'] return t['r'], t['g'], t['b']
99 100 end end
100 101
101 local function updateUnitButtonBar(self)
102 assert (self ~= nil)
103
104 local bar = self.bar
102 local function updateUnitButtonBarOverlay(bar, unitDesignation)
105 103 assert (bar ~= nil) assert (bar ~= nil)
106 104
107 local unitDesignation = self:GetAttribute('unit')
108 105 assert (unitDesignation ~= nil) assert (unitDesignation ~= nil)
106 assert ('string' == type(unitDesignation))
107 unitDesignation = strtrim(unitDesignation)
108 assert (string.len(unitDesignation) >= 2)
109 assert (string.len(unitDesignation) <= 32)
109 110
110 111 --[[ Apply bar color update ]]-- --[[ Apply bar color update ]]--
111 local _, classDesignation = UnitClass(unitDesignation)
112 local r, g, b = getClassColor(classDesignation)
112 local r, g, b = getDefaultUnitButtonBarColor()
113 113 --[[ TODO Range indicator ]]-- --[[ TODO Range indicator ]]--
114 114 local a = 1 local a = 1
115 if not r or not g or not b then
116 r, g, b = getDefaultUnitButtonBarColor()
115 local _, classDesignation = UnitClass(unitDesignation)
116 if classDesignation then
117 r, g, b = getClassColor(classDesignation)
117 118 end end
118 bar:SetTexture(r, g, b, a)
119
120 local overlay = bar.overlay
121 assert (overlay ~= nil)
122 overlay:SetTexture(r, g, b, a)
119 123
120 124 --[[ Apply bar width update ]]-- --[[ Apply bar width update ]]--
125 local ratio = 1
126 if UnitExists(unitDesignation) then
127 ratio = getUnitHealthRatio(unitDesignation) or 1
128 end
129 ratio = math.min(math.max(0, ratio or 1), 1)
130 overlay:SetPoint('BOTTOMLEFT', bar, 'BOTTOMLEFT', 0, 0)
131 overlay:SetPoint('TOPRIGHT', bar, 'TOPRIGHT', (ratio - 1) * bar:GetWidth(), 0)
132 end
133
134 local function createUnitButtonBar(unitButton)
135 assert (unitButton ~= nil)
121 136
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')
137 local n = unitButton:GetName() or ''
138 local bar = CreateFrame('FRAME', n .. 'Bar', unitButton)
139 bar:SetPoint('BOTTOMLEFT', unitButton, 'BOTTOMLEFT', 4, 4)
140 bar:SetPoint('TOPRIGHT', unitButton, 'TOPRIGHT', -4, 12 - unitButton:GetHeight())
127 141
128 local ratio = 1
129 if UnitExists(unitDesignation) then
130 ratio = u:GetAttribute('choirUnitHealthRatio')
142 local b = bar:CreateTexture(bar:GetName() .. 'Overlay', 'OVERLAY')
143 b:SetAllPoints()
144 b:SetTexture(getDefaultUnitButtonBarColor(), 1)
145 bar.overlay = b
146
147 local t = createLabel(bar)
148 bar.text = t
149
150 bar.unitButton = unitButton
151
152 bar:SetScript('OnEvent', function(healthBarFrame, eventCategory, targetUnit)
153 assert (healthBarFrame ~= nil)
154 assert ('UNIT_HEALTH' == eventCategory)
155 assert (targetUnit ~= nil)
156 assert (unitButton ~= nil)
157
158 local u = unitButton:GetAttribute('unit')
159 assert (u ~= nil)
160 if targetUnit == u then
161 updateUnitButtonBarOverlay(healthBarFrame, targetUnit)
162 updateUnitButtonBarText(healthBarFrame, targetUnit)
131 163 end end
132 ratio = math.min(math.max(0, ratio), 1)
164 end)
165 bar:RegisterEvent('UNIT_HEALTH')
133 166
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 ]=])
167 return bar
139 168 end end
140 169
141 local function unitButtonEventProcessor(self)
142 updateUnitButtonText(self)
143 updateUnitButtonBar(self)
170 local function unitButtonEventProcessor(unitButton)
171 assert (unitButton ~= nil)
172
173 updateUnitButtonText(unitButton)
174
175 local bar = unitButton.bar
176 assert (unitButton ~= nil)
177
178 local unitDesignation = unitButton:GetAttribute('unit')
179 assert (unitDesignation ~= nil)
180 assert ('string' == type(unitDesignation))
181 unitDesignation = strtrim(unitDesignation)
182 assert (string.len(unitDesignation) >= 2)
183 assert (string.len(unitDesignation) <= 32)
184
185 updateUnitButtonBarOverlay(bar, unitDesignation)
186 updateUnitButtonBarText(bar, unitDesignation)
144 187 end end
145 188
146 189 local function createUnitButton(parentFrame, frameName, unit) local function createUnitButton(parentFrame, frameName, unit)
 
... ... local function createUnitButton(parentFrame, frameName, unit)
148 191 assert (frameName ~= nil) assert (frameName ~= nil)
149 192 assert (unit ~= nil) assert (unit ~= nil)
150 193
194 --[[ TODO Add debuff indicator, at least the presence of a debuff of a category not the exact effect ]]--
195 --[[ TODO Add ability to cleanse without targeting ]]--
151 196 local u = CreateFrame('BUTTON', frameName, parentFrame, 'SecureUnitButtonTemplate') local u = CreateFrame('BUTTON', frameName, parentFrame, 'SecureUnitButtonTemplate')
152 197 u:SetAttribute('type', 'target') u:SetAttribute('type', 'target')
153 198 u:SetAttribute('unit', unit) u:SetAttribute('unit', unit)
154 199
155 u:SetSize(12 * 8, 12 * 4)
200 u:SetSize(12 * 8, 12 * 3)
156 201
157 u.text = createLabel(u)
202 local t = createLabel(u)
203 t:SetPoint('BOTTOMLEFT', u, 'BOTTOMLEFT', 4, u:GetHeight() - 24 - 4)
204 t:SetPoint('TOPRIGHT', u, 'TOPRIGHT', -4, -4)
205 u.text = t
158 206
159 207 local b = createBackground(u) local b = createBackground(u)
160 208 u.background = b u.background = b
 
... ... local function createUnitButton(parentFrame, frameName, unit)
162 210 local bar = createUnitButtonBar(u) local bar = createUnitButtonBar(u)
163 211 u.bar = bar u.bar = bar
164 212
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 213 u:SetScript('OnEvent', unitButtonEventProcessor) u:SetScript('OnEvent', unitButtonEventProcessor)
172 214 u:RegisterEvent('PARTY_CONVERTED_TO_RAID') u:RegisterEvent('PARTY_CONVERTED_TO_RAID')
173 215 u:RegisterEvent('PARTY_MEMBERS_CHANGED') u:RegisterEvent('PARTY_MEMBERS_CHANGED')
 
... ... local function createUnitButton(parentFrame, frameName, unit)
175 217 u:RegisterEvent('RAID_ROSTER_UPDATE') u:RegisterEvent('RAID_ROSTER_UPDATE')
176 218 u:RegisterEvent('UPDATE_BATTLEFIELD_SCORE') u:RegisterEvent('UPDATE_BATTLEFIELD_SCORE')
177 219 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')
181 220
182 221 assert (u ~= nil) assert (u ~= nil)
183 222 return u return u
 
... ... end
186 225 local function createSpoiler(spoilerParent, spoilerDesignation) local function createSpoiler(spoilerParent, spoilerDesignation)
187 226 local spoiler = CreateFrame('BUTTON', spoilerDesignation, spoilerParent, 'SecureHandlerClickTemplate') local spoiler = CreateFrame('BUTTON', spoilerDesignation, spoilerParent, 'SecureHandlerClickTemplate')
188 227 spoiler:EnableMouse(true) spoiler:EnableMouse(true)
189 --[[ WARNING To assign the temporary override bindings to the buttons under a spoiler correctly,
190 -- the children table must be sorted and be iterated over in a specific order.
191 -- #GetChildList method seems to return the children in order they were created,
192 -- but that might not be the case for #GetChildren variant or not at all. ]]--
193 228 spoiler:SetAttribute('_onclick', [=[ spoiler:SetAttribute('_onclick', [=[
194 229 --[[ Toggle sibling frames, which are other spoilers that contain unit buttons ]]-- --[[ Toggle sibling frames, which are other spoilers that contain unit buttons ]]--
195 230 local parentFrame = self:GetParent() local parentFrame = self:GetParent()
 
... ... local function createGroup(rootFrame, groupNumber, unitTable)
297 332 spoilerFrame:Hide() spoilerFrame:Hide()
298 333 ]=]) ]=])
299 334 b:Hide() b:Hide()
300
335
301 336 local key = getButtonBindingKeyExplicit(b) or getButtonBindingKeyDefault(i) local key = getButtonBindingKeyExplicit(b) or getButtonBindingKeyDefault(i)
302 337 if key then if key then
303 338 b:SetAttribute('choirBindingKey', key) b:SetAttribute('choirBindingKey', key)
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