PageRenderTime 175ms CodeModel.GetById 144ms app.highlight 23ms RepoModel.GetById 1ms app.codeStats 1ms

/Tukui/modules/unitframes/core/oUF/ouf.lua

http://github.com/Asphyxia/Tukui
Lua | 575 lines | 439 code | 108 blank | 28 comment | 93 complexity | f15fa35b2f4bc76b58c8e41458dd4377 MD5 | raw file
  1local parent, ns = ...
  2local global = GetAddOnMetadata(parent, 'X-oUF')
  3local _VERSION = GetAddOnMetadata(parent, 'version')
  4
  5local oUF = ns.oUF
  6local Private = oUF.Private
  7
  8local argcheck = Private.argcheck
  9
 10local print = Private.print
 11local error = Private.error
 12local OnEvent = Private.OnEvent
 13
 14local styles, style = {}
 15local callback, units, objects = {}, {}, {}
 16
 17local select = select
 18local UnitExists = UnitExists
 19
 20local conv = {
 21	['playerpet'] = 'pet',
 22	['playertarget'] = 'target',
 23}
 24local elements = {}
 25
 26-- updating of "invalid" units.
 27local enableTargetUpdate = function(object)
 28	local total = 0
 29	object.onUpdateFrequency = object.onUpdateFrequency or .5
 30
 31	object:SetScript('OnUpdate', function(self, elapsed)
 32		if(not self.unit) then
 33			return
 34		elseif(total > self.onUpdateFrequency) then
 35			self:UpdateAllElements'OnUpdate'
 36			total = 0
 37		end
 38
 39		total = total + elapsed
 40	end)
 41end
 42Private.enableTargetUpdate = enableTargetUpdate
 43
 44local iterateChildren = function(...)
 45	for l = 1, select("#", ...) do
 46		local obj = select(l, ...)
 47
 48		if(type(obj) == 'table' and obj.isChild) then
 49			local unit = SecureButton_GetModifiedUnit(obj)
 50			local subUnit = conv[unit] or unit
 51			units[subUnit] = obj
 52			obj.unit = subUnit
 53			obj.id = subUnit:match'^.-(%d+)'
 54			obj:UpdateAllElements"PLAYER_ENTERING_WORLD"
 55		end
 56	end
 57end
 58
 59local OnAttributeChanged = function(self, name, value)
 60	if(name == "unit" and value) then
 61		if(self.unit and (self.unit == value or self.realUnit == value)) then
 62			return
 63		else
 64			if(self.hasChildren) then
 65				iterateChildren(self:GetChildren())
 66			end
 67
 68			if(not self:GetAttribute'oUF-onlyProcessChildren') then
 69				local unit = SecureButton_GetModifiedUnit(self)
 70				unit = conv[unit] or unit
 71				units[unit] = self
 72				self.unit = unit
 73				self.id = unit:match"^.-(%d+)"
 74				self:UpdateAllElements"PLAYER_ENTERING_WORLD"
 75			end
 76		end
 77	end
 78end
 79
 80local frame_metatable = {
 81	__index = CreateFrame"Button"
 82}
 83Private.frame_metatable = frame_metatable
 84
 85for k, v in pairs{
 86	EnableElement = function(self, name, unit)
 87		argcheck(name, 2, 'string')
 88		argcheck(unit, 3, 'string', 'nil')
 89
 90		local element = elements[name]
 91		if(not element) then return end
 92
 93		if(element.enable(self, unit or self.unit)) then
 94			table.insert(self.__elements, element.update)
 95		end
 96	end,
 97
 98	DisableElement = function(self, name)
 99		argcheck(name, 2, 'string')
100		local element = elements[name]
101		if(not element) then return end
102
103		for k, update in next, self.__elements do
104			if(update == element.update) then
105				table.remove(self.__elements, k)
106
107				-- We need to run a new update cycle incase we knocked ourself out of sync.
108				-- The main reason we do this is to make sure the full update is completed
109				-- if an element for some reason removes itself _during_ the update
110				-- progress.
111				self:UpdateAllElements('DisableElement', name)
112				break
113			end
114		end
115
116		return element.disable(self)
117	end,
118
119	Enable = RegisterUnitWatch,
120	Disable = function(self)
121		UnregisterUnitWatch(self)
122		self:Hide()
123	end,
124
125	UpdateAllElements = function(self, event)
126		local unit = self.unit
127		if(not UnitExists(unit)) then return end
128
129		if(self.PreUpdate) then
130			self:PreUpdate(event)
131		end
132
133		for _, func in next, self.__elements do
134			func(self, event, unit)
135		end
136
137		if(self.PostUpdate) then
138			self:PostUpdate(event)
139		end
140	end,
141} do
142	frame_metatable.__index[k] = v
143end
144
145local updateActiveUnit = function(self, event, unit)
146	-- Calculate units to work with
147	local realUnit, modUnit = SecureButton_GetUnit(self), SecureButton_GetModifiedUnit(self)
148
149	-- _GetUnit() doesn't rewrite playerpet -> pet like _GetModifiedUnit does.
150	if(realUnit == 'playerpet') then
151		realUnit = 'pet'
152	end
153
154	if(modUnit == "pet" and realUnit ~= "pet") then
155		modUnit = "vehicle"
156	end
157
158	-- Drop out if the event unit doesn't match any of the frame units.
159	if(not UnitExists(modUnit) or unit and unit ~= realUnit and unit ~= modUnit) then return end
160
161	if(modUnit ~= realUnit) then
162		self.realUnit = realUnit
163	else
164		self.realUnit = nil
165	end
166
167	-- Change the active unit and run a full update.
168	if(self.unit ~= modUnit) then
169		self.unit = modUnit
170		self:UpdateAllElements('RefreshUnit')
171
172		return true
173	end
174end
175
176local OnShow = function(self)
177	if(not updateActiveUnit(self, 'OnShow')) then
178		return self:UpdateAllElements'OnShow'
179	end
180end
181
182local initObject = function(unit, style, styleFunc, header, ...)
183	local num = select('#', ...)
184	for i=1, num do
185		local object = select(i, ...)
186		local objectUnit = object:GetAttribute'oUF-guessUnit' or unit
187		local suffix = object:GetAttribute'unitsuffix'
188
189		object.__elements = {}
190		object.style = style
191		object = setmetatable(object, frame_metatable)
192
193		-- Expose the frame through oUF.objects.
194		table.insert(objects, object)
195
196		-- We have to force update the frames when PEW fires.
197		object:RegisterEvent("PLAYER_ENTERING_WORLD", object.UpdateAllElements)
198
199		-- Handle the case where someone has modified the unitsuffix attribute in
200		-- oUF-initialConfigFunction.
201		if(suffix and not objectUnit:match(suffix)) then
202			objectUnit = objectUnit .. suffix
203		end
204
205		if(not (suffix == 'target' or objectUnit and objectUnit:match'target')) then
206			object:RegisterEvent('UNIT_ENTERED_VEHICLE', updateActiveUnit)
207			object:RegisterEvent('UNIT_EXITED_VEHICLE', updateActiveUnit)
208
209			-- We don't need to register UNIT_PET for the player unit. We rigester it
210			-- mainly because UNIT_EXITED_VEHICLE and UNIT_ENTERED_VEHICLE doesn't always
211			-- have pet information when they fire for party and raid units.
212			if(objectUnit ~= 'player') then
213				object:RegisterEvent('UNIT_PET', updateActiveUnit)
214			end
215		end
216
217		if(not header) then
218			-- No header means it's a frame created through :Spawn().
219			object:SetAttribute("*type1", "target")
220			object:SetAttribute('*type2', 'menu')
221
222			-- No need to enable this for *target frames.
223			if(not (unit:match'target' or suffix == 'target')) then
224				object:SetAttribute('toggleForVehicle', true)
225			end
226
227			-- Other boss and target units are handled by :HandleUnit().
228			if(suffix == 'target') then
229				enableTargetUpdate(object)
230			elseif(not (unit:match'%w+target' or unit:match'(boss)%d?$' == 'boss')) then
231				object:SetScript('OnEvent', Private.OnEvent)
232			end
233		else
234			-- Used to update frames when they change position in a group.
235			object:RegisterEvent('PARTY_MEMBERS_CHANGED', object.UpdateAllElements)
236
237			if(num > 1) then
238				if(object:GetParent() == header) then
239					object.hasChildren = true
240				else
241					object.isChild = true
242				end
243			end
244
245			if(suffix == 'target') then
246				enableTargetUpdate(object)
247			else
248				object:SetScript('OnEvent', Private.OnEvent)
249			end
250		end
251
252		styleFunc(object, objectUnit, not header)
253
254		object:SetScript("OnAttributeChanged", OnAttributeChanged)
255		object:SetScript("OnShow", OnShow)
256
257		for element in next, elements do
258			object:EnableElement(element, objectUnit)
259		end
260
261		for _, func in next, callback do
262			func(object)
263		end
264
265		-- Make Clique happy
266		_G.ClickCastFrames = ClickCastFrames or {}
267		ClickCastFrames[object] = true
268	end
269end
270
271local walkObject = function(object, unit)
272	local parent = object:GetParent()
273	local style = parent.style or style
274	local styleFunc = styles[style]
275
276	local header = parent:GetAttribute'oUF-headerType' and parent
277
278	-- Check if we should leave the main frame blank.
279	if(object:GetAttribute'oUF-onlyProcessChildren') then
280		object.hasChildren = true
281		object:SetScript('OnAttributeChanged', OnAttributeChanged)
282		return initObject(unit, style, styleFunc, header, object:GetChildren())
283	end
284
285	return initObject(unit, style, styleFunc, header, object, object:GetChildren())
286end
287
288function oUF:RegisterInitCallback(func)
289	table.insert(callback, func)
290end
291
292function oUF:RegisterMetaFunction(name, func)
293	argcheck(name, 2, 'string')
294	argcheck(func, 3, 'function', 'table')
295
296	if(frame_metatable.__index[name]) then
297		return
298	end
299
300	frame_metatable.__index[name] = func
301end
302
303function oUF:RegisterStyle(name, func)
304	argcheck(name, 2, 'string')
305	argcheck(func, 3, 'function', 'table')
306
307	if(styles[name]) then return error("Style [%s] already registered.", name) end
308	if(not style) then style = name end
309
310	styles[name] = func
311end
312
313function oUF:SetActiveStyle(name)
314	argcheck(name, 2, 'string')
315	if(not styles[name]) then return error("Style [%s] does not exist.", name) end
316
317	style = name
318end
319
320do
321	local function iter(_, n)
322		-- don't expose the style functions.
323		return (next(styles, n))
324	end
325
326	function oUF.IterateStyles()
327		return iter, nil, nil
328	end
329end
330
331local getCondition
332do
333	local conditions = {
334		raid40 = '[@raid26,exists] show;',
335		raid25 = '[@raid11,exists] show;',
336		raid10 = '[@raid6,exists] show;',
337		raid = '[group:raid] show;',
338		party = '[group:party,nogroup:raid] show;',
339		solo = '[@player,exists,nogroup:party] show;',
340	}
341
342	function getCondition(...)
343		local cond = ''
344
345		for i=1, select('#', ...) do
346			local short = select(i, ...)
347
348			local condition = conditions[short]
349			if(condition) then
350				cond = cond .. condition
351			end
352		end
353
354		return cond .. 'hide'
355	end
356end
357
358local generateName = function(unit, ...)
359	local name = 'oUF_' .. style:gsub('[^%a%d_]+', '')
360
361	local raid, party, groupFilter
362	for i=1, select('#', ...), 2 do
363		local att, val = select(i, ...)
364		if(att == 'showRaid') then
365			raid = true
366		elseif(att == 'showParty') then
367			party = true
368		elseif(att == 'groupFilter') then
369			groupFilter = val
370		end
371	end
372
373	local append
374	if(raid) then
375		if(groupFilter) then
376			if(type(groupFilter) == 'number' and groupFilter > 0) then
377				append = groupFilter
378			elseif(groupFilter:match'TANK') then
379				append = 'MainTank'
380			elseif(groupFilter:match'ASSIST') then
381				append = 'MainAssist'
382			else
383				local _, count = groupFilter:gsub(',', '')
384				if(count == 0) then
385					append = groupFilter
386				else
387					append = 'Raid'
388				end
389			end
390		else
391			append = 'Raid'
392		end
393	elseif(party) then
394		append = 'Party'
395	elseif(unit) then
396		append = unit:gsub("^%l", string.upper)
397	end
398
399	if(append) then
400		name = name .. append
401	end
402
403	-- Change oUF_LilyRaidRaid into oUF_LilyRaid
404	name = name:gsub('(%u%l+)([%u%l]*)%1', '%1')
405
406	local base = name
407	local i = 2
408	while(_G[name]) do
409		name = base .. i
410		i = i + 1
411	end
412
413	return name
414end
415
416do
417	local styleProxy = function(self, frame, ...)
418		return walkObject(_G[frame])
419	end
420
421	-- There has to be an easier way to do this.
422	local initialConfigFunction = [[
423		local header = self:GetParent()
424		local frames = table.new()
425		table.insert(frames, self)
426		self:GetChildList(frames)
427		for i=1, #frames do
428			local frame = frames[i]
429			local unit
430			-- There's no need to do anything on frames with onlyProcessChildren
431			if(not frame:GetAttribute'oUF-onlyProcessChildren') then
432				RegisterUnitWatch(frame)
433
434				-- Attempt to guess what the header is set to spawn.
435				local groupFilter = header:GetAttribute'groupFilter'
436
437				if(type(groupFilter) == 'string' and groupFilter:match('MAIN[AT]')) then
438					local role = groupFilter:match('MAIN([AT])')
439					if(role == 'T') then
440						unit = 'maintank'
441					else
442						unit = 'mainassist'
443					end
444				elseif(header:GetAttribute'showRaid') then
445					unit = 'raid'
446				elseif(header:GetAttribute'showParty') then
447					unit = 'party'
448				end
449
450				local headerType = header:GetAttribute'oUF-headerType'
451				local suffix = frame:GetAttribute'unitsuffix'
452				if(unit and suffix) then
453					if(headerType == 'pet' and suffix == 'target') then
454						unit = unit .. headerType .. suffix
455					else
456						unit = unit .. suffix
457					end
458				elseif(unit and headerType == 'pet') then
459					unit = unit .. headerType
460				end
461
462				frame:SetAttribute('*type1', 'target')
463				frame:SetAttribute('*type2', 'menu')
464				frame:SetAttribute('toggleForVehicle', true)
465				frame:SetAttribute('oUF-guessUnit', unit)
466			end
467
468			local body = header:GetAttribute'oUF-initialConfigFunction'
469			if(body) then
470				frame:Run(body, unit)
471			end
472		end
473
474		header:CallMethod('styleFunction', self:GetName())
475
476		local clique = header:GetFrameRef("clickcast_header")
477		if(clique) then
478			clique:SetAttribute("clickcast_button", self)
479			clique:RunAttribute("clickcast_register")
480		end
481	]]
482
483	function oUF:SpawnHeader(overrideName, template, visibility, ...)
484		if(not style) then return error("Unable to create frame. No styles have been registered.") end
485
486		template = (template or 'SecureGroupHeaderTemplate')
487
488		local isPetHeader = template:match'PetHeader'
489		local name = overrideName or generateName(nil, ...)
490		local header = CreateFrame('Frame', name, UIParent, template)
491
492		header:SetAttribute("template", "oUF_ClickCastUnitTemplate")
493		for i=1, select("#", ...), 2 do
494			local att, val = select(i, ...)
495			if(not att) then break end
496			header:SetAttribute(att, val)
497		end
498
499		header.style = style
500		header.styleFunction = styleProxy
501
502		-- We set it here so layouts can't directly override it.
503		header:SetAttribute('initialConfigFunction', initialConfigFunction)
504		header:SetAttribute('oUF-headerType', isPetHeader and 'pet' or 'group')
505
506		if(Clique) then
507			SecureHandlerSetFrameRef(header, 'clickcast_header', Clique.header)
508		end
509
510		if(header:GetAttribute'showParty') then
511			self:DisableBlizzard'party'
512		end
513
514		if(visibility) then
515			local type, list = string.split(' ', visibility, 2)
516			if(list and type == 'custom') then
517				RegisterAttributeDriver(header, 'state-visibility', list)
518			else
519				local condition = getCondition(string.split(',', visibility))
520				RegisterAttributeDriver(header, 'state-visibility', condition)
521			end
522		end
523
524		return header
525	end
526end
527
528function oUF:Spawn(unit, overrideName)
529	argcheck(unit, 2, 'string')
530	if(not style) then return error("Unable to create frame. No styles have been registered.") end
531
532	unit = unit:lower()
533
534	local name = overrideName or generateName(unit)
535	local object = CreateFrame("Button", name, UIParent, "SecureUnitButtonTemplate")
536	object.unit = unit
537	object.id = unit:match"^.-(%d+)"
538
539	units[unit] = object
540	walkObject(object, unit)
541
542	object:SetAttribute("unit", unit)
543	RegisterUnitWatch(object)
544
545	self:DisableBlizzard(unit)
546	self:HandleUnit(object)
547
548	return object
549end
550
551function oUF:AddElement(name, update, enable, disable)
552	argcheck(name, 2, 'string')
553	argcheck(update, 3, 'function', 'nil')
554	argcheck(enable, 4, 'function', 'nil')
555	argcheck(disable, 5, 'function', 'nil')
556
557	if(elements[name]) then return error('Element [%s] is already registered.', name) end
558	elements[name] = {
559		update = update;
560		enable = enable;
561		disable = disable;
562	}
563end
564
565oUF.version = _VERSION
566oUF.units = units
567oUF.objects = objects
568
569if(global) then
570	if(parent ~= 'oUF' and global == 'oUF') then
571		error("%s is doing it wrong and setting its global to oUF.", parent)
572	else
573		_G[global] = oUF
574	end
575end