--[[ # Element: Auras Handles creation and updating of aura icons. ## Widget Auras - A Frame to hold `Button`s representing both buffs and debuffs. Buffs - A Frame to hold `Button`s representing buffs. Debuffs - A Frame to hold `Button`s representing debuffs. ## Notes At least one of the above widgets must be present for the element to work. ## Options .disableMouse - Disables mouse events (boolean) .disableCooldown - Disables the cooldown spiral (boolean) .size - Aura icon size. Defaults to 16 (number) .spacing - Spacing between each icon. Defaults to 0 (number) .['spacing-x'] - Horizontal spacing between each icon. Takes priority over `spacing` (number) .['spacing-y'] - Vertical spacing between each icon. Takes priority over `spacing` (number) .['growth-x'] - Horizontal growth direction. Defaults to 'RIGHT' (string) .['growth-y'] - Vertical growth direction. Defaults to 'UP' (string) .initialAnchor - Anchor point for the icons. Defaults to 'BOTTOMLEFT' (string) .filter - Custom filter list for auras to display. Defaults to 'HELPFUL' for buffs and 'HARMFUL' for debuffs (string) ## Options Auras .numBuffs - The maximum number of buffs to display. Defaults to 32 (number) .numDebuffs - The maximum number of debuffs to display. Defaults to 40 (number) .numTotal - The maximum number of auras to display. Prioritizes buffs over debuffs. Defaults to the sum of .numBuffs and .numDebuffs (number) .gap - Controls the creation of an invisible icon between buffs and debuffs. Defaults to false (boolean) .buffFilter - Custom filter list for buffs to display. Takes priority over `filter` (string) .debuffFilter - Custom filter list for debuffs to display. Takes priority over `filter` (string) ## Options Buffs .num - Number of buffs to display. Defaults to 32 (number) ## Options Debuffs .num - Number of debuffs to display. Defaults to 40 (number) ## Attributes button.filter - the filter list used to determine the visibility of the aura (string) button.isDebuff - indicates if the button holds a debuff (boolean) ## Examples -- Position and size local Buffs = CreateFrame('Frame', nil, self) Buffs:SetPoint('RIGHT', self, 'LEFT') Buffs:SetSize(16 * 2, 16 * 16) -- Register with oUF self.Buffs = Buffs --]] local ns = oUF local oUF = ns.oUF local tinsert, getn = table.insert, table.getn local floor, min, mod = math.floor, math.min, math.mod local CreateFrame = CreateFrame local GetTime = GetTime local UnitAura = UnitAura local GetPlayerBuff = GetPlayerBuff local GetPlayerBuffTexture = GetPlayerBuffTexture local GetPlayerBuffApplications = GetPlayerBuffApplications local GetPlayerBuffDispelType = GetPlayerBuffDispelType local GetPlayerBuffTimeLeft = GetPlayerBuffTimeLeft local VISIBLE = 1 local HIDDEN = 0 local function UpdateTooltip(self) if self:GetParent().__owner.unit == "player" then GameTooltip:SetPlayerBuff(self:GetID()) elseif self.filter == 'HELPFUL' then GameTooltip:SetUnitBuff(self:GetParent().__owner.unit, self:GetID(), self.filter) else GameTooltip:SetUnitDebuff(self:GetParent().__owner.unit, self:GetID(), self.filter) end end local function onEnter(self) if not self:IsVisible() then return end GameTooltip:SetOwner(self, 'ANCHOR_BOTTOMRIGHT') self:UpdateTooltip() end local function onLeave() GameTooltip:Hide() end local function createAuraIcon(element, index) local button = CreateFrame('Button', '$parentButton' .. index, element) button:RegisterForClicks('RightButtonUp') local cd = CreateFrame('Frame', '$parentCooldown', button, 'oUF_CooldownFrameTemplate') cd:SetAllPoints() local icon = button:CreateTexture(nil, 'BORDER') icon:SetAllPoints() local count = button:CreateFontString(nil, 'OVERLAY', 'NumberFontNormal') count:SetPoint('BOTTOMRIGHT', button, 'BOTTOMRIGHT', -1, 0) local overlay = button:CreateTexture(nil, 'OVERLAY') overlay:SetTexture([[Interface\Buttons\UI-Debuff-Overlays]]) overlay:SetAllPoints() overlay:SetTexCoord(.296875, .5703125, 0, .515625) button.overlay = overlay button.UpdateTooltip = UpdateTooltip button:SetScript('OnEnter', function() onEnter(this) end) button:SetScript('OnLeave', function() onLeave(this) end) button.icon = icon button.count = count button.cd = cd --[[ Callback: Auras:PostCreateIcon(button) Called after a new aura button has been created. * self - the widget holding the aura buttons * button - the newly created aura button (Button) --]] if element.PostCreateIcon then element:PostCreateIcon(button) end return button end local function customFilter(element, unit, button, name) if name then return true end end local function updateIcon(element, unit, index, offset, filter, isDebuff, visible) local texture, count, dispelType, duration, expiration = UnitAura(unit, index, filter) if unit == "player" then local idx = GetPlayerBuff(index - 1, filter) if idx < 0 then return end index = idx texture = GetPlayerBuffTexture(idx) count = GetPlayerBuffApplications(idx) dispelType = GetPlayerBuffDispelType(idx) duration = GetPlayerBuffTimeLeft(idx) end if element.forceShow then name, rank, texture = GetSpellInfo(26993) count, dispelType, duration, expiration = 5, 'Magic', 0, 60 end if not texture then return end local position = visible + offset + 1 local button = element[position] if not button then --[[ Override: Auras:CreateIcon(position) Used to create the aura button at a given position. * self - the widget holding the aura buttons * position - the position at which the aura button is to be created (number) ## Returns * button - the button used to represent the aura (Button) --]] button = (element.CreateIcon or createAuraIcon) (element, position) tinsert(element, button) element.createdIcons = element.createdIcons + 1 end button.filter = filter button.isDebuff = isDebuff --[[ Override: Auras:CustomFilter(unit, button, ...) Defines a custom filter that controls if the aura button should be shown. * self - the widget holding the aura buttons * unit - the unit on which the aura is cast (string) * button - the button displaying the aura (Button) * ... - the return values from [UnitAura](http://wowprogramming.com/docs/api/UnitAura) ## Returns * show - indicates whether the aura button should be shown (boolean) --]] local show = true if not element.forceShow then -- show = (element.CustomFilter or customFilter) (element, unit, button, name, rank, texture, count, dispelType, duration, expiration) end if not show then return HIDDEN end -- We might want to consider delaying the creation of an actual cooldown -- object to this point, but I think that will just make things needlessly -- complicated. if button.cd and not element.disableCooldown then if duration and duration > 0 then -- button.cd:SetCooldown(GetTime() - (duration - expiration), duration) button.cd:Show() else button.cd:Hide() end end if button.overlay then if (isDebuff and element.showDebuffType) or (not isDebuff and element.showBuffType) or element.showType then local color = DebuffTypeColor[dispelType] or DebuffTypeColor.none button.overlay:SetVertexColor(color.r, color.g, color.b) button.overlay:Show() else button.overlay:Hide() end end if button.icon then button.icon:SetTexture(texture) end -- if button.count then button.count:SetText(count > 1 and count) end local size = element.size or 16 button:SetWidth(size) button:SetHeight(size) button:EnableMouse(not element.disableMouse) button:SetID(index) button:Show() --[[ Callback: Auras:PostUpdateIcon(unit, button, index, position) Called after the aura button has been updated. * self - the widget holding the aura buttons * unit - the unit on which the aura is cast (string) * button - the updated aura button (Button) * index - the index of the aura (number) * position - the actual position of the aura button (number) --]] if element.PostUpdateIcon then element:PostUpdateIcon(unit, button, index, position) end return VISIBLE end local function SetPosition(element, from, to) local sizex = (element.size or 16) + (element['spacing-x'] or element.spacing or 0) local sizey = (element.size or 16) + (element['spacing-y'] or element.spacing or 0) local anchor = element.initialAnchor or 'BOTTOMLEFT' local growthx = (element['growth-x'] == 'LEFT' and -1) or 1 local growthy = (element['growth-y'] == 'DOWN' and -1) or 1 local cols = floor(element:GetWidth() / sizex + 0.5) for i = from, to do local button = element[i] -- Bail out if the to range is out of scope. if(not button) then break end local col = mod((i - 1), cols) local row = floor((i - 1) / cols) button:ClearAllPoints() button:SetPoint(anchor, element, anchor, col * sizex * growthx, row * sizey * growthy) end end local function filterIcons(element, unit, filter, limit, isDebuff, offset, dontHide) if not offset then offset = 0 end local index = 1 local visible = 0 local hidden = 0 while visible < limit do local result = updateIcon(element, unit, index, offset, filter, isDebuff, visible) if not result then break elseif result == VISIBLE then visible = visible + 1 elseif result == HIDDEN then hidden = hidden + 1 end index = index + 1 end if not dontHide then for i = visible + offset + 1, getn(element) do element[i]:Hide() end end return visible, hidden end local function UpdateAuras(self, event, unit) if self.unit ~= unit then return end local auras = self.Auras if auras then --[[ Callback: Auras:PreUpdate(unit) Called before the element has been updated. * self - the widget holding the aura buttons * unit - the unit for which the update has been triggered (string) --]] if auras.PreUpdate then auras:PreUpdate(unit) end local numBuffs = auras.numBuffs or 32 local numDebuffs = auras.numDebuffs or 40 local max = auras.numTotal or numBuffs + numDebuffs local visibleBuffs, hiddenBuffs = filterIcons(auras, unit, auras.buffFilter or auras.filter or 'HELPFUL', min(numBuffs, max), nil, 0, true) local hasGap if visibleBuffs ~= 0 and auras.gap then hasGap = true visibleBuffs = visibleBuffs + 1 local button = auras[visibleBuffs] if not button then button = (auras.CreateIcon or createAuraIcon) (auras, visibleBuffs) tinsert(auras, button) auras.createdIcons = auras.createdIcons + 1 end -- Prevent the button from displaying anything. if button.cd then button.cd:Hide() end if button.icon then button.icon:SetTexture() end if button.overlay then button.overlay:Hide() end if button.stealable then button.stealable:Hide() end if button.count then button.count:SetText() end button:EnableMouse(false) button:Show() --[[ Callback: Auras:PostUpdateGapIcon(unit, gapButton, visibleBuffs) Called after an invisible aura button has been created. Only used by Auras when the `gap` option is enabled. * self - the widget holding the aura buttons * unit - the unit that has the invisible aura button (string) * gapButton - the invisible aura button (Button) * visibleBuffs - the number of currently visible aura buttons (number) --]] if(auras.PostUpdateGapIcon) then auras:PostUpdateGapIcon(unit, button, visibleBuffs) end end local visibleDebuffs, hiddenDebuffs = filterIcons(auras, unit, auras.debuffFilter or auras.filter or 'HARMFUL', min(numDebuffs, max - visibleBuffs), true, visibleBuffs) auras.visibleDebuffs = visibleDebuffs if hasGap and visibleDebuffs == 0 then auras[visibleBuffs]:Hide() visibleBuffs = visibleBuffs - 1 end auras.visibleBuffs = visibleBuffs auras.visibleAuras = auras.visibleBuffs + auras.visibleDebuffs local fromRange, toRange --[[ Callback: Auras:PreSetPosition(max) Called before the aura buttons have been (re-)anchored. * self - the widget holding the aura buttons * max - the maximum possible number of aura buttons (number) ## Returns * from - the offset of the first aura button to be (re-)anchored (number) * to - the offset of the last aura button to be (re-)anchored (number) --]] if auras.PreSetPosition then fromRange, toRange = auras:PreSetPosition(max) end if fromRange or auras.createdIcons > auras.anchoredIcons then --[[ Override: Auras:SetPosition(from, to) Used to (re-)anchor the aura buttons. Called when new aura buttons have been created or if :PreSetPosition is defined. * self - the widget that holds the aura buttons * from - the offset of the first aura button to be (re-)anchored (number) * to - the offset of the last aura button to be (re-)anchored (number) --]] (auras.SetPosition or SetPosition) (auras, fromRange or auras.anchoredIcons + 1, toRange or auras.createdIcons) auras.anchoredIcons = auras.createdIcons end --[[ Callback: Auras:PostUpdate(unit) Called after the element has been updated. * self - the widget holding the aura buttons * unit - the unit for which the update has been triggered (string) --]] if auras.PostUpdate then auras:PostUpdate(unit) end end local buffs = self.Buffs if buffs then if buffs.PreUpdate then buffs:PreUpdate(unit) end local numBuffs = buffs.num or 32 local visibleBuffs, hiddenBuffs = filterIcons(buffs, unit, buffs.filter or 'HELPFUL', numBuffs) buffs.visibleBuffs = visibleBuffs local fromRange, toRange if buffs.PreSetPosition then fromRange, toRange = buffs:PreSetPosition(numBuffs) end if fromRange or buffs.createdIcons > buffs.anchoredIcons then (buffs.SetPosition or SetPosition) (buffs, fromRange or buffs.anchoredIcons + 1, toRange or buffs.createdIcons) buffs.anchoredIcons = buffs.createdIcons end if buffs.PostUpdate then buffs:PostUpdate(unit) end end local debuffs = self.Debuffs if debuffs then if debuffs.PreUpdate then debuffs:PreUpdate(unit) end local numDebuffs = debuffs.num or 40 local visibleDebuffs, hiddenDebuffs = filterIcons(debuffs, unit, debuffs.filter or 'HARMFUL', numDebuffs, true) debuffs.visibleDebuffs = visibleDebuffs local fromRange, toRange if debuffs.PreSetPosition then fromRange, toRange = debuffs:PreSetPosition(numDebuffs) end if fromRange or debuffs.createdIcons > debuffs.anchoredIcons then (debuffs.SetPosition or SetPosition) (debuffs, fromRange or debuffs.anchoredIcons + 1, toRange or debuffs.createdIcons) debuffs.anchoredIcons = debuffs.createdIcons end if debuffs.PostUpdate then debuffs:PostUpdate(unit) end end end local function Update(self, event, unit) if self.unit ~= unit then return end UpdateAuras(self, event, unit) -- Assume no event means someone wants to re-anchor things. This is usually -- done by UpdateAllElements and :ForceUpdate. if event == 'ForceUpdate' or not event then local buffs = self.Buffs if buffs then (buffs.SetPosition or SetPosition) (buffs, 1, buffs.createdIcons) end local debuffs = self.Debuffs if debuffs then (debuffs.SetPosition or SetPosition) (debuffs, 1, debuffs.createdIcons) end local auras = self.Auras if auras then (auras.SetPosition or SetPosition) (auras, 1, auras.createdIcons) end end end local function ForceUpdate(element) return Update(element.__owner, 'ForceUpdate', element.__owner.unit) end local function Enable(self) if self.Buffs or self.Debuffs or self.Auras then self:RegisterEvent('PLAYER_AURAS_CHANGED', UpdateAuras) self:RegisterEvent('UNIT_AURA', UpdateAuras) local buffs = self.Buffs if buffs then buffs.__owner = self buffs.ForceUpdate = ForceUpdate buffs.createdIcons = buffs.createdIcons or 0 buffs.anchoredIcons = 0 --buffs:Show() end local debuffs = self.Debuffs if debuffs then debuffs.__owner = self debuffs.ForceUpdate = ForceUpdate debuffs.createdIcons = debuffs.createdIcons or 0 debuffs.anchoredIcons = 0 --debuffs:Show() end local auras = self.Auras if auras then auras.__owner = self auras.ForceUpdate = ForceUpdate auras.createdIcons = auras.createdIcons or 0 auras.anchoredIcons = 0 --auras:Show() end return true end end local function Disable(self) if self.Buffs or self.Debuffs or self.Auras then self:UnregisterEvent('PLAYER_AURAS_CHANGED', UpdateAuras) self:UnregisterEvent('UNIT_AURA', UpdateAuras) if self.Buffs then self.Buffs:Hide() end if self.Debuffs then self.Debuffs:Hide() end if self.Auras then self.Auras:Hide() end end end oUF:AddElement('Auras', Update, Enable, Disable)