tags.lua 20.1 KB
Newer Older
Tukz's avatar
Tukz committed
1
-- Credits: Vika, Cladhaire, Tekkub
Tukz's avatar
Tukz committed
2 3
--[[
# Element: Tags
Tukz's avatar
Tukz committed
4

Tukz's avatar
Tukz committed
5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58
Provides a system for text-based display of information by binding a tag string to a font string widget which in turn is
tied to a unit frame.

## Widget

A FontString to hold a tag string. Unlike other elements, this widget must not have a preset name.

## Notes

A `Tag` is a Lua string consisting of a function name surrounded by square brackets. The tag will be replaced by the
output of the function and displayed as text on the font string widget with that the tag has been registered. Literals
can be pre- or appended by separating them with a `>` before or `<` after the function name. The literals will be only
displayed when the function returns a non-nil value. I.e. `"[perhp<%]"` will display the current health as a percentage
of the maximum health followed by the % sign.

A `Tag String` is a Lua string consisting of one or multiple tags with optional literals between them. Each tag will be
updated individually and the output will follow the tags order. Literals will be displayed in the output string
regardless of whether the surrounding tag functions return a value. I.e. `"[curhp]/[maxhp]"` will resolve to something
like `2453/5000`.

A `Tag Function` is used to replace a single tag in a tag string by its output. A tag function receives only two
arguments - the unit and the realUnit of the unit frame used to register the tag (see Options for further details). The
tag function is called when the unit frame is shown or when a specified event has fired. It the tag is registered on an
eventless frame (i.e. one holding the unit "targettarget"), then the tag function is called in a set time interval.

A number of built-in tag functions exist. The layout can also define its own tag functions by adding them to the
`oUF.Tags.Methods` table. The events upon which the function will be called are specified in a white-space separated
list added to the `oUF.Tags.Events` table. Should an event fire without unit information, then it should also be listed
in the `oUF.Tags.SharedEvents` table as follows: `oUF.Tags.SharedEvents.EVENT_NAME = true`.

## Options

.overrideUnit    - if specified on the font string widget, the frame's realUnit will be passed as the second argument to
                   every tag function whose name is contained in the relevant tag string. Otherwise the second argument
                   is always nil (boolean)
.frequentUpdates - defines how often the correspondig tag function(s) should be called. This will override the events for
                   the tag(s), if any. If the value is a number, it is taken as a time interval in seconds. If the value
                   is a boolean, the time interval is set to 0.5 seconds (number or boolean)

## Attributes

.parent - the unit frame on which the tag has been registered

## Examples

    -- define the tag function
    oUF.Tags.Methods['mylayout:threatname'] = function(unit, realUnit)
        local color = _TAGS['threatcolor'](unit)
        local name = _TAGS['name'](unit, realUnit)
        return string.format('%s%s|r', color, name)
    end

    -- add the events
    oUF.Tags.Events['mylayout:threatname'] = 'UNIT_NAME_UPDATE UNIT_THREAT_SITUATION_UPDATE'
Tukz's avatar
Tukz committed
59

Tukz's avatar
Tukz committed
60 61 62 63 64 65 66 67 68 69
    -- create the text widget
    local info = self.Health:CreateFontString(nil, 'OVERLAY', 'GameFontNormal')
    info:SetPoint('LEFT')

    -- register the tag on the text widget with oUF
    self:Tag(info, '[mylayout:threatname]')
--]]

local _, ns = ...
local oUF = ns.oUF
Azilroka's avatar
Azilroka committed
70 71
local Private = oUF.Private

Tukz's avatar
Tukz committed
72
local unitExists = Private.unitExists
Tukz's avatar
Tukz committed
73

Tukz's avatar
Tukz committed
74 75 76 77
local _PATTERN = '%[..-%]+'

local _ENV = {
	Hex = function(r, g, b)
Tukz's avatar
Tukz committed
78 79 80 81 82 83
		if(type(r) == 'table') then
			if(r.r) then
				r, g, b = r.r, r.g, r.b
			else
				r, g, b = unpack(r)
			end
Tukz's avatar
Tukz committed
84
		end
Tukz's avatar
Tukz committed
85
		return string.format('|cff%02x%02x%02x', r * 255, g * 255, b * 255)
Tukz's avatar
Tukz committed
86 87
	end,
}
Tukz's avatar
Tukz committed
88 89 90 91
_ENV.ColorGradient = function(...)
	return _ENV._FRAME:ColorGradient(...)
end

Tukz's avatar
Tukz committed
92 93 94
local _PROXY = setmetatable(_ENV, {__index = _G})

local tagStrings = {
Tukz's avatar
Tukz committed
95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124
	['affix'] = [[function(u)
		local c = UnitClassification(u)
		if(c == 'minus') then
			return 'Affix'
		end
	end]],

	['classification'] = [[function(u)
		local c = UnitClassification(u)
		if(c == 'rare') then
			return 'Rare'
		elseif(c == 'rareelite') then
			return 'Rare Elite'
		elseif(c == 'elite') then
			return 'Elite'
		elseif(c == 'worldboss') then
			return 'Boss'
		elseif(c == 'minus') then
			return 'Affix'
		end
	end]],

	['cpoints'] = [[function(u)
		local cp = UnitPower(u, Enum.PowerType.ComboPoints)

		if(cp > 0) then
			return cp
		end
	end]],

Tukz's avatar
Tukz committed
125
	['creature'] = [[function(u)
Tukz's avatar
Tukz committed
126 127 128
		return UnitCreatureFamily(u) or UnitCreatureType(u)
	end]],

Tukz's avatar
Tukz committed
129 130 131 132
	['curmana'] = [[function(unit)
		return UnitPower(unit, Enum.PowerType.Mana)
	end]],

Tukz's avatar
Tukz committed
133
	['dead'] = [[function(u)
Tukz's avatar
Tukz committed
134 135 136 137 138 139 140
		if(UnitIsDead(u)) then
			return 'Dead'
		elseif(UnitIsGhost(u)) then
			return 'Ghost'
		end
	end]],

Tukz's avatar
Tukz committed
141 142 143 144 145 146 147 148 149
	['deficit:name'] = [[function(u)
		local missinghp = _TAGS['missinghp'](u)
		if(missinghp) then
			return '-' .. missinghp
		else
			return _TAGS['name'](u)
		end
	end]],

Tukz's avatar
Tukz committed
150 151
	['difficulty'] = [[function(u)
		if UnitCanAttack('player', u) then
Tukz's avatar
Tukz committed
152
			local l = UnitEffectiveLevel(u)
Tukz's avatar
Tukz committed
153
			return Hex(GetCreatureDifficultyColor((l > 0) and l or 999))
Tukz's avatar
Tukz committed
154 155 156
		end
	end]],

Tukz's avatar
Tukz committed
157 158 159 160 161 162 163 164 165 166 167 168 169 170
	['group'] = [[function(unit)
		local name, server = UnitName(unit)
		if(server and server ~= '') then
			name = string.format('%s-%s', name, server)
		end

		for i=1, GetNumGroupMembers() do
			local raidName, _, group = GetRaidRosterInfo(i)
			if( raidName == name ) then
				return group
			end
		end
	end]],

Tukz's avatar
Tukz committed
171
	['leader'] = [[function(u)
Tukz's avatar
Tukz committed
172 173 174 175 176
		if(UnitIsGroupLeader(u)) then
			return 'L'
		end
	end]],

Tukz's avatar
Tukz committed
177
	['leaderlong']  = [[function(u)
Tukz's avatar
Tukz committed
178 179 180 181 182
		if(UnitIsGroupLeader(u)) then
			return 'Leader'
		end
	end]],

Tukz's avatar
Tukz committed
183
	['level'] = [[function(u)
Tukz's avatar
Tukz committed
184
		local l = UnitLevel(u)
Tukz's avatar
Tukz committed
185

Tukz's avatar
Tukz committed
186 187 188 189 190 191 192
		if(l > 0) then
			return l
		else
			return '??'
		end
	end]],

Tukz's avatar
Tukz committed
193 194 195 196
	['maxmana'] = [[function(unit)
		return UnitPowerMax(unit, Enum.PowerType.Mana)
	end]],

Tukz's avatar
Tukz committed
197
	['missinghp'] = [[function(u)
Tukz's avatar
Tukz committed
198 199 200 201 202 203
		local current = UnitHealthMax(u) - UnitHealth(u)
		if(current > 0) then
			return current
		end
	end]],

Tukz's avatar
Tukz committed
204
	['missingpp'] = [[function(u)
Tukz's avatar
Tukz committed
205 206 207 208 209 210
		local current = UnitPowerMax(u) - UnitPower(u)
		if(current > 0) then
			return current
		end
	end]],

Tukz's avatar
Tukz committed
211
	['name'] = [[function(u, r)
Tukz's avatar
Tukz committed
212 213 214
		return UnitName(r or u)
	end]],

Tukz's avatar
Tukz committed
215
	['offline'] = [[function(u)
Tukz's avatar
Tukz committed
216 217 218 219 220
		if(not UnitIsConnected(u)) then
			return 'Offline'
		end
	end]],

Tukz's avatar
Tukz committed
221
	['perhp'] = [[function(u)
Tukz's avatar
Tukz committed
222 223 224 225
		local m = UnitHealthMax(u)
		if(m == 0) then
			return 0
		else
Tukz's avatar
Tukz committed
226
			return math.floor(UnitHealth(u) / m * 100 + .5)
Tukz's avatar
Tukz committed
227 228 229
		end
	end]],

Tukz's avatar
Tukz committed
230
	['perpp'] = [[function(u)
Tukz's avatar
Tukz committed
231 232 233 234
		local m = UnitPowerMax(u)
		if(m == 0) then
			return 0
		else
Tukz's avatar
Tukz committed
235
			return math.floor(UnitPower(u) / m * 100 + .5)
Tukz's avatar
Tukz committed
236 237 238
		end
	end]],

Tukz's avatar
Tukz committed
239
	['plus'] = [[function(u)
Tukz's avatar
Tukz committed
240 241 242 243 244 245
		local c = UnitClassification(u)
		if(c == 'elite' or c == 'rareelite') then
			return '+'
		end
	end]],

Tukz's avatar
Tukz committed
246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264
	['powercolor'] = [[function(u)
		local pType, pToken, altR, altG, altB = UnitPowerType(u)
		local t = _COLORS.power[pToken]

		if(not t) then
			if(altR) then
				if(altR > 1 or altG > 1 or altB > 1) then
					return Hex(altR / 255, altG / 255, altB / 255)
				else
					return Hex(altR, altG, altB)
				end
			else
				return Hex(_COLORS.power[pType])
			end
		end

		return Hex(t)
	end]],

Tukz's avatar
Tukz committed
265
	['pvp'] = [[function(u)
Tukz's avatar
Tukz committed
266 267 268 269 270
		if(UnitIsPVP(u)) then
			return 'PvP'
		end
	end]],

Tukz's avatar
Tukz committed
271
	['raidcolor'] = [[function(u)
Azilroka's avatar
Azilroka committed
272 273 274 275 276 277 278 279 280 281 282 283
		local _, class = UnitClass(u)
		if(class) then
			return Hex(_COLORS.class[class])
		else
			local id = u:match('arena(%d)$')
			if(id) then
				local specID = GetArenaOpponentSpec(tonumber(id))
				if(specID and specID > 0) then
					_, _, _, _, _, class = GetSpecializationInfoByID(specID)
					return Hex(_COLORS.class[class])
				end
			end
Tukz's avatar
Tukz committed
284 285 286
		end
	end]],

Tukz's avatar
Tukz committed
287
	['rare'] = [[function(u)
Tukz's avatar
Tukz committed
288 289 290 291 292 293
		local c = UnitClassification(u)
		if(c == 'rare' or c == 'rareelite') then
			return 'Rare'
		end
	end]],

Tukz's avatar
Tukz committed
294
	['resting'] = [[function(u)
Tukz's avatar
Tukz committed
295 296 297 298 299
		if(u == 'player' and IsResting()) then
			return 'zzz'
		end
	end]],

Tukz's avatar
Tukz committed
300 301
	['runes'] = [[function()
		local amount = 0
Tukz's avatar
Tukz committed
302

Tukz's avatar
Tukz committed
303 304 305 306 307
		for i = 1, 6 do
			local _, _, ready = GetRuneCooldown(i)
			if(ready) then
				amount = amount + 1
			end
Tukz's avatar
Tukz committed
308 309
		end

Tukz's avatar
Tukz committed
310
		return amount
Tukz's avatar
Tukz committed
311 312
	end]],

Tukz's avatar
Tukz committed
313 314 315 316
	['sex'] = [[function(u)
		local s = UnitSex(u)
		if(s == 2) then
			return 'Male'
Tukz's avatar
Tukz committed
317
		elseif(s == 3) then
Tukz's avatar
Tukz committed
318
			return 'Female'
Tukz's avatar
Tukz committed
319 320 321
		end
	end]],

Tukz's avatar
Tukz committed
322
	['shortclassification'] = [[function(u)
Tukz's avatar
Tukz committed
323 324 325
		local c = UnitClassification(u)
		if(c == 'rare') then
			return 'R'
Tukz's avatar
Tukz committed
326
		elseif(c == 'rareelite') then
Tukz's avatar
Tukz committed
327 328 329 330 331
			return 'R+'
		elseif(c == 'elite') then
			return '+'
		elseif(c == 'worldboss') then
			return 'B'
Tukz's avatar
Tukz committed
332 333
		elseif(c == 'minus') then
			return '-'
Tukz's avatar
Tukz committed
334 335 336
		end
	end]],

Tukz's avatar
Tukz committed
337 338 339
	['smartclass'] = [[function(u)
		if(UnitIsPlayer(u)) then
			return _TAGS['class'](u)
Tukz's avatar
Tukz committed
340 341
		end

Tukz's avatar
Tukz committed
342
		return _TAGS['creature'](u)
Tukz's avatar
Tukz committed
343 344
	end]],

Tukz's avatar
Tukz committed
345 346 347 348
	['smartlevel'] = [[function(u)
		local c = UnitClassification(u)
		if(c == 'worldboss') then
			return 'Boss'
Tukz's avatar
Tukz committed
349
		else
Tukz's avatar
Tukz committed
350 351 352 353 354 355 356
			local plus = _TAGS['plus'](u)
			local level = _TAGS['level'](u)
			if(plus) then
				return level .. plus
			else
				return level
			end
Tukz's avatar
Tukz committed
357 358 359
		end
	end]],

Tukz's avatar
Tukz committed
360 361 362 363 364 365 366 367 368
	['status'] = [[function(u)
		if(UnitIsDead(u)) then
			return 'Dead'
		elseif(UnitIsGhost(u)) then
			return 'Ghost'
		elseif(not UnitIsConnected(u)) then
			return 'Offline'
		else
			return _TAGS['resting'](u)
Azilroka's avatar
Azilroka committed
369 370
		end
	end]],
Tukz's avatar
Tukz committed
371 372 373 374 375 376 377 378 379 380 381 382 383 384
}

local tags = setmetatable(
	{
		curhp = UnitHealth,
		curpp = UnitPower,
		maxhp = UnitHealthMax,
		maxpp = UnitPowerMax,
		class = UnitClass,
		faction = UnitFactionGroup,
		race = UnitRace,
	},
	{
		__index = function(self, key)
Tukz's avatar
Tukz committed
385 386 387 388
			local tagString = tagStrings[key]
			if(tagString) then
				self[key] = tagString
				tagStrings[key] = nil
Tukz's avatar
Tukz committed
389
			end
Tukz's avatar
Tukz committed
390 391

			return rawget(self, key)
Tukz's avatar
Tukz committed
392 393 394
		end,
		__newindex = function(self, key, val)
			if(type(val) == 'string') then
Tukz's avatar
Tukz committed
395 396 397 398 399
				local func, err = loadstring('return ' .. val)
				if(func) then
					val = func()
				else
					error(err, 3)
Tukz's avatar
Tukz committed
400
				end
Tukz's avatar
Tukz committed
401
			end
Tukz's avatar
Tukz committed
402

Tukz's avatar
Tukz committed
403 404 405 406 407 408 409 410
			assert(type(val) == 'function', 'Tag function must be a function or a string that evaluates to a function.')

			-- We don't want to clash with any custom envs
			if(getfenv(val) == _G) then
				-- pcall is needed for cases when Blizz functions are passed as
				-- strings, for intance, 'UnitPowerMax', an attempt to set a
				-- custom env will result in an error
				pcall(setfenv, val, _PROXY)
Tukz's avatar
Tukz committed
411
			end
Tukz's avatar
Tukz committed
412 413

			rawset(self, key, val)
Tukz's avatar
Tukz committed
414 415 416 417 418 419
		end,
	}
)

_ENV._TAGS = tags

Tukz's avatar
Tukz committed
420 421 422 423 424 425 426 427 428 429 430 431 432 433 434
local vars = setmetatable({}, {
	__newindex = function(self, key, val)
		if(type(val) == 'string') then
			local func = loadstring('return ' .. val)
			if(func) then
				val = func() or val
			end
		end

		rawset(self, key, val)
	end,
})

_ENV._VARS = vars

Tukz's avatar
Tukz committed
435
local tagEvents = {
Tukz's avatar
Tukz committed
436 437 438 439 440 441
	['affix']               = 'UNIT_CLASSIFICATION_CHANGED',
	['classification']      = 'UNIT_CLASSIFICATION_CHANGED',
	['cpoints']             = 'UNIT_POWER_FREQUENT PLAYER_TARGET_CHANGED',
	['curhp']               = 'UNIT_HEALTH UNIT_MAXHEALTH',
	['curmana']             = 'UNIT_POWER_UPDATE UNIT_MAXPOWER',
	['curpp']               = 'UNIT_POWER_UPDATE UNIT_MAXPOWER',
Tukz's avatar
Tukz committed
442
	['dead']                = 'UNIT_HEALTH',
Tukz's avatar
Tukz committed
443 444 445 446
	['deficit:name']        = 'UNIT_HEALTH UNIT_MAXHEALTH UNIT_NAME_UPDATE',
	['difficulty']          = 'UNIT_FACTION',
	['faction']             = 'NEUTRAL_FACTION_SELECT_RESULT',
	['group']               = 'GROUP_ROSTER_UPDATE',
Tukz's avatar
Tukz committed
447 448 449 450
	['leader']              = 'PARTY_LEADER_CHANGED',
	['leaderlong']          = 'PARTY_LEADER_CHANGED',
	['level']               = 'UNIT_LEVEL PLAYER_LEVEL_UP',
	['maxhp']               = 'UNIT_MAXHEALTH',
Tukz's avatar
Tukz committed
451 452
	['maxmana']             = 'UNIT_POWER_UPDATE UNIT_MAXPOWER',
	['maxpp']               = 'UNIT_MAXPOWER',
Tukz's avatar
Tukz committed
453
	['missinghp']           = 'UNIT_HEALTH UNIT_MAXHEALTH',
Tukz's avatar
Tukz committed
454
	['missingpp']           = 'UNIT_MAXPOWER UNIT_POWER_UPDATE',
Tukz's avatar
Tukz committed
455
	['name']                = 'UNIT_NAME_UPDATE',
Tukz's avatar
Tukz committed
456
	['offline']             = 'UNIT_HEALTH UNIT_CONNECTION',
Tukz's avatar
Tukz committed
457
	['perhp']               = 'UNIT_HEALTH UNIT_MAXHEALTH',
Tukz's avatar
Tukz committed
458
	['perpp']               = 'UNIT_MAXPOWER UNIT_POWER_UPDATE',
Tukz's avatar
Tukz committed
459
	['plus']                = 'UNIT_CLASSIFICATION_CHANGED',
Tukz's avatar
Tukz committed
460 461
	['powercolor']          = 'UNIT_DISPLAYPOWER',
	['pvp']                 = 'UNIT_FACTION',
Tukz's avatar
Tukz committed
462
	['rare']                = 'UNIT_CLASSIFICATION_CHANGED',
Tukz's avatar
Tukz committed
463
	['resting']             = 'PLAYER_UPDATE_RESTING',
Tukz's avatar
Tukz committed
464
	['shortclassification'] = 'UNIT_CLASSIFICATION_CHANGED',
Tukz's avatar
Tukz committed
465 466
	['smartlevel']          = 'UNIT_LEVEL PLAYER_LEVEL_UP UNIT_CLASSIFICATION_CHANGED',
	['status']              = 'UNIT_HEALTH PLAYER_UPDATE_RESTING UNIT_CONNECTION',
Tukz's avatar
Tukz committed
467 468 469
}

local unitlessEvents = {
Tukz's avatar
Tukz committed
470 471 472 473
	ARENA_PREP_OPPONENT_SPECIALIZATIONS = true,
	GROUP_ROSTER_UPDATE = true,
	NEUTRAL_FACTION_SELECT_RESULT = true,
	PARTY_LEADER_CHANGED = true,
Tukz's avatar
Tukz committed
474 475
	PLAYER_LEVEL_UP = true,
	PLAYER_TARGET_CHANGED = true,
Tukz's avatar
Tukz committed
476
	PLAYER_UPDATE_RESTING = true,
Azilroka's avatar
Azilroka committed
477
	RUNE_POWER_UPDATE = true,
Tukz's avatar
Tukz committed
478 479 480
}

local events = {}
Tukz's avatar
Tukz committed
481 482
local eventFrame = CreateFrame('Frame')
eventFrame:SetScript('OnEvent', function(self, event, unit)
Tukz's avatar
Tukz committed
483 484
	local strings = events[event]
	if(strings) then
Tukz's avatar
Tukz committed
485 486 487
		for _, fs in next, strings do
			if(fs:IsVisible() and (unitlessEvents[event] or fs.parent.unit == unit or (fs.extraUnits and fs.extraUnits[unit]))) then
				fs:UpdateTag()
Tukz's avatar
Tukz committed
488 489 490 491 492
			end
		end
	end
end)

Tukz's avatar
Tukz committed
493
local onUpdates = {}
Tukz's avatar
Tukz committed
494 495
local eventlessUnits = {}

Tukz's avatar
Tukz committed
496
local function createOnUpdate(timer)
Tukz's avatar
Tukz committed
497
	if(not onUpdates[timer]) then
Tukz's avatar
Tukz committed
498
		local total = timer
Tukz's avatar
Tukz committed
499
		local frame = CreateFrame('Frame')
Tukz's avatar
Tukz committed
500 501 502 503
		local strings = eventlessUnits[timer]

		frame:SetScript('OnUpdate', function(self, elapsed)
			if(total >= timer) then
Tukz's avatar
Tukz committed
504
				for _, fs in next, strings do
Tukz's avatar
Tukz committed
505
					if(fs.parent:IsShown() and unitExists(fs.parent.unit)) then
Tukz's avatar
Tukz committed
506 507 508 509 510 511 512 513 514 515
						fs:UpdateTag()
					end
				end

				total = 0
			end

			total = total + elapsed
		end)

Tukz's avatar
Tukz committed
516
		onUpdates[timer] = frame
Tukz's avatar
Tukz committed
517 518 519
	end
end

Azilroka's avatar
Azilroka committed
520 521 522 523 524 525
--[[ Tags: frame:UpdateTags()
Used to update all tags on a frame.

* self - the unit frame from which to update the tags
--]]
local function Update(self)
Azilroka's avatar
Azilroka committed
526
	if(self.__tags) then
Tukz's avatar
Tukz committed
527
		for fs in next, self.__tags do
Azilroka's avatar
Azilroka committed
528 529
			fs:UpdateTag()
		end
Tukz's avatar
Tukz committed
530 531 532
	end
end

Tukz's avatar
Tukz committed
533 534 535 536
local tagPool = {}
local funcPool = {}
local tmp = {}

Tukz's avatar
Tukz committed
537 538 539 540
local function getTagName(tag)
	local tagStart = (tag:match('>+()') or 2)
	local tagEnd = tag:match('.*()<+')
	tagEnd = (tagEnd and tagEnd - 1) or -2
Tukz's avatar
Tukz committed
541

Tukz's avatar
Tukz committed
542
	return tag:sub(tagStart, tagEnd), tagStart, tagEnd
Tukz's avatar
Tukz committed
543 544
end

Tukz's avatar
Tukz committed
545
local function getTagFunc(tagstr)
Tukz's avatar
Tukz committed
546 547 548 549 550 551 552 553
	local func = tagPool[tagstr]
	if(not func) then
		local format, numTags = tagstr:gsub('%%', '%%%%'):gsub(_PATTERN, '%%s')
		local args = {}

		for bracket in tagstr:gmatch(_PATTERN) do
			local tagFunc = funcPool[bracket] or tags[bracket:sub(2, -2)]
			if(not tagFunc) then
Tukz's avatar
Tukz committed
554
				local tagName, tagStart, tagEnd = getTagName(bracket)
Tukz's avatar
Tukz committed
555 556 557

				local tag = tags[tagName]
				if(tag) then
Tukz's avatar
Tukz committed
558 559
					tagStart = tagStart - 2
					tagEnd = tagEnd + 2
Tukz's avatar
Tukz committed
560

Tukz's avatar
Tukz committed
561 562 563
					if(tagStart ~= 0 and tagEnd ~= 0) then
						local prefix = bracket:sub(2, tagStart)
						local suffix = bracket:sub(tagEnd, -2)
Tukz's avatar
Tukz committed
564

Tukz's avatar
Tukz committed
565 566
						tagFunc = function(unit, realUnit)
							local str = tag(unit, realUnit)
Tukz's avatar
Tukz committed
567
							if(str) then
Tukz's avatar
Tukz committed
568
								return prefix .. str .. suffix
Tukz's avatar
Tukz committed
569 570
							end
						end
Tukz's avatar
Tukz committed
571 572
					elseif(tagStart ~= 0) then
						local prefix = bracket:sub(2, tagStart)
Tukz's avatar
Tukz committed
573

Tukz's avatar
Tukz committed
574 575
						tagFunc = function(unit, realUnit)
							local str = tag(unit, realUnit)
Tukz's avatar
Tukz committed
576
							if(str) then
Tukz's avatar
Tukz committed
577
								return prefix .. str
Tukz's avatar
Tukz committed
578 579
							end
						end
Tukz's avatar
Tukz committed
580 581
					elseif(tagEnd ~= 0) then
						local suffix = bracket:sub(tagEnd, -2)
Tukz's avatar
Tukz committed
582

Tukz's avatar
Tukz committed
583 584
						tagFunc = function(unit, realUnit)
							local str = tag(unit, realUnit)
Tukz's avatar
Tukz committed
585
							if(str) then
Tukz's avatar
Tukz committed
586
								return str .. suffix
Tukz's avatar
Tukz committed
587 588 589 590 591 592 593 594 595 596 597
							end
						end
					end

					funcPool[bracket] = tagFunc
				end
			end

			if(tagFunc) then
				table.insert(args, tagFunc)
			else
Tukz's avatar
Tukz committed
598
				return error(string.format('Attempted to use invalid tag %s.', bracket), 3)
Tukz's avatar
Tukz committed
599 600 601 602 603 604 605 606 607 608 609 610
			end
		end

		if(numTags == 1) then
			func = function(self)
				local parent = self.parent
				local realUnit
				if(self.overrideUnit) then
					realUnit = parent.realUnit
				end

				_ENV._COLORS = parent.colors
Tukz's avatar
Tukz committed
611
				_ENV._FRAME = parent
Tukz's avatar
Tukz committed
612 613 614 615 616 617 618 619 620 621 622 623 624 625 626
				return self:SetFormattedText(
					format,
					args[1](parent.unit, realUnit) or ''
				)
			end
		elseif(numTags == 2) then
			func = function(self)
				local parent = self.parent
				local unit = parent.unit
				local realUnit
				if(self.overrideUnit) then
					realUnit = parent.realUnit
				end

				_ENV._COLORS = parent.colors
Tukz's avatar
Tukz committed
627
				_ENV._FRAME = parent
Tukz's avatar
Tukz committed
628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643
				return self:SetFormattedText(
					format,
					args[1](unit, realUnit) or '',
					args[2](unit, realUnit) or ''
				)
			end
		elseif(numTags == 3) then
			func = function(self)
				local parent = self.parent
				local unit = parent.unit
				local realUnit
				if(self.overrideUnit) then
					realUnit = parent.realUnit
				end

				_ENV._COLORS = parent.colors
Tukz's avatar
Tukz committed
644
				_ENV._FRAME = parent
Tukz's avatar
Tukz committed
645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661
				return self:SetFormattedText(
					format,
					args[1](unit, realUnit) or '',
					args[2](unit, realUnit) or '',
					args[3](unit, realUnit) or ''
				)
			end
		else
			func = function(self)
				local parent = self.parent
				local unit = parent.unit
				local realUnit
				if(self.overrideUnit) then
					realUnit = parent.realUnit
				end

				_ENV._COLORS = parent.colors
Tukz's avatar
Tukz committed
662
				_ENV._FRAME = parent
Tukz's avatar
Tukz committed
663 664 665 666 667 668 669 670 671 672 673 674
				for i, func in next, args do
					tmp[i] = func(unit, realUnit) or ''
				end

				-- We do 1, numTags because tmp can hold several unneeded variables.
				return self:SetFormattedText(format, unpack(tmp, 1, numTags))
			end
		end

		tagPool[tagstr] = func
	end

Tukz's avatar
Tukz committed
675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735
	return func
end

local function registerEvent(fontstr, event)
	if(not events[event]) then events[event] = {} end

	eventFrame:RegisterEvent(event)
	table.insert(events[event], fontstr)
end

local function registerEvents(fontstr, tagstr)
	for tag in tagstr:gmatch(_PATTERN) do
		tag = getTagName(tag)
		local tagevents = tagEvents[tag]
		if(tagevents) then
			for event in tagevents:gmatch('%S+') do
				registerEvent(fontstr, event)
			end
		end
	end
end

local function unregisterEvents(fontstr)
	for event, data in next, events do
		for i, tagfsstr in next, data do
			if(tagfsstr == fontstr) then
				if(#data == 1) then
					eventFrame:UnregisterEvent(event)
				end

				table.remove(data, i)
			end
		end
	end
end

local taggedFS = {}

--[[ Tags: frame:Tag(fs, tagstr, ...)
Used to register a tag on a unit frame.

* self   - the unit frame on which to register the tag
* fs     - the font string to display the tag (FontString)
* tagstr - the tag string (string)
* ...    - additional optional unitID(s) the tag should update for
--]]
local function Tag(self, fs, tagstr, ...)
	if(not fs or not tagstr) then return end

	if(not self.__tags) then
		self.__tags = {}
		table.insert(self.__elements, Update)
	elseif(self.__tags[fs]) then
		-- We don't need to remove it from the __tags table as Untag handles
		-- that for us.
		self:Untag(fs)
	end

	fs.parent = self
	fs.UpdateTag = getTagFunc(tagstr)

Tukz's avatar
Tukz committed
736
	if(self.__eventless or fs.frequentUpdates) then
Tukz's avatar
Tukz committed
737 738 739 740 741 742 743 744 745 746 747 748
		local timer
		if(type(fs.frequentUpdates) == 'number') then
			timer = fs.frequentUpdates
		else
			timer = .5
		end

		if(not eventlessUnits[timer]) then eventlessUnits[timer] = {} end
		table.insert(eventlessUnits[timer], fs)

		createOnUpdate(timer)
	else
Tukz's avatar
Tukz committed
749
		registerEvents(fs, tagstr)
Tukz's avatar
Tukz committed
750 751 752 753 754 755 756

		if(...) then
			if(not fs.extraUnits) then
				fs.extraUnits = {}
			end

			for index = 1, select('#', ...) do
Tukz's avatar
Tukz committed
757
				fs.extraUnits[select(index, ...)] = true
Tukz's avatar
Tukz committed
758 759
			end
		end
Tukz's avatar
Tukz committed
760 761
	end

Tukz's avatar
Tukz committed
762 763
	taggedFS[fs] = tagstr
	self.__tags[fs] = true
Tukz's avatar
Tukz committed
764 765
end

Tukz's avatar
Tukz committed
766 767 768 769 770 771 772
--[[ Tags: frame:Untag(fs)
Used to unregister a tag from a unit frame.

* self - the unit frame from which to unregister the tag
* fs   - the font string holding the tag (FontString)
--]]
local function Untag(self, fs)
Azilroka's avatar
Azilroka committed
773
	if(not fs or not self.__tags) then return end
Tukz's avatar
Tukz committed
774

Tukz's avatar
Tukz committed
775
	unregisterEvents(fs)
Tukz's avatar
Tukz committed
776
	for _, timers in next, eventlessUnits do
Tukz's avatar
Tukz committed
777
		for i, fontstr in next, timers do
Tukz's avatar
Tukz committed
778
			if(fs == fontstr) then
Tukz's avatar
Tukz committed
779
				table.remove(timers, i)
Tukz's avatar
Tukz committed
780 781 782 783 784
			end
		end
	end

	fs.UpdateTag = nil
Tukz's avatar
Tukz committed
785 786 787

	taggedFS[fs] = nil
	self.__tags[fs] = nil
Tukz's avatar
Tukz committed
788 789 790 791 792 793
end

oUF.Tags = {
	Methods = tags,
	Events = tagEvents,
	SharedEvents = unitlessEvents,
Tukz's avatar
Tukz committed
794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831
	Vars = vars,
	RefreshMethods = function(self, tag)
		if(not tag) then return end

		funcPool['[' .. tag .. ']'] = nil

		tag = '%[' .. tag .. '%]'
		for tagstr, func in next, tagPool do
			if(tagstr:match(tag)) then
				tagPool[tagstr] = nil

				for fs in next, taggedFS do
					if(fs.UpdateTag == func) then
						fs.UpdateTag = getTagFunc(tagstr)

						if(fs:IsVisible()) then
							fs:UpdateTag()
						end
					end
				end
			end
		end
	end,
	RefreshEvents = function(self, tag)
		if(not tag) then return end

		tag = '%[' .. tag .. '%]'
		for tagstr in next, tagPool do
			if(tagstr:match(tag)) then
				for fs, ts in next, taggedFS do
					if(ts == tagstr) then
						unregisterEvents(fs)
						registerEvents(fs, tagstr)
					end
				end
			end
		end
	end,
Tukz's avatar
Tukz committed
832
}
Tukz's avatar
Tukz committed
833

Tukz's avatar
Tukz committed
834 835
oUF:RegisterMetaFunction('Tag', Tag)
oUF:RegisterMetaFunction('Untag', Untag)
Azilroka's avatar
Azilroka committed
836
oUF:RegisterMetaFunction('UpdateTags', Update)