From 7e19b3071f0d7a9c7b465987d15fee49a4356aee Mon Sep 17 00:00:00 2001 From: Tercio Jose Date: Tue, 5 Sep 2023 10:43:11 -0300 Subject: [PATCH] Framework update --- Libs/DF/definitions.lua | 3 +- Libs/DF/fw.lua | 50 +++- Libs/DF/icon.lua | 417 +++++++++++++++++++++++++++-- Libs/DF/schedules.lua | 67 ++++- Libs/DF/unitframe.lua | 31 ++- Libs/LibLuaServer/LibLuaServer.lua | 39 +++ core/meta.lua | 4 +- 7 files changed, 562 insertions(+), 49 deletions(-) diff --git a/Libs/DF/definitions.lua b/Libs/DF/definitions.lua index 6ec46ec4..d25d9308 100644 --- a/Libs/DF/definitions.lua +++ b/Libs/DF/definitions.lua @@ -21,6 +21,7 @@ ---@field GetDefaultBackdropColor fun(self:table) : red, green, blue, alpha return the standard backdrop color used by blizzard on their own frames ---@field Msg fun(self:table, message:string, ...) show a message in the chat frame ---@field MsgWarning fun(self:table, message:string, ...) show a warning message in the chat frame +---@field CreateButton fun(self:table, parent:frame, func:function, width:number, height:number, text:string?, param1:any, param2:any, texture:atlasname|texturepath|textureid|nil, member:string?, name:string?, shortMethod:any, buttonTemplate:table?, textTemplate:table?) : df_button ---@field CreateCloseButton fun(self:table, parent:frame, frameName:string?) : df_closebutton ---@field CreateTabButton fun(self:table, parent:frame, frameName:string?) : df_tabbutton ---@field CreateRoundedPanel fun(self:table, parent:frame, frameName:string?, optionsTable:df_roundedpanel_options?) : df_roundedpanel @@ -43,7 +44,7 @@ ---@field CommaValue fun(self:table, value:number) : string convert a number to a string with commas, e.g. 1000000 -> 1,000,000 ---@field SplitTextInLines fun(self:table, text:string) : string[] split a text into lines ---@field UnitGroupRolesAssigned fun(unitId: unit, bUseSupport:boolean, specId: specializationid) : string there's no self here ----@field +---@field SetAnchor fun(self:table, widget:uiobject, anchorTable:df_anchor, anchorTo:uiobject) ---@field ---@field ---@field diff --git a/Libs/DF/fw.lua b/Libs/DF/fw.lua index edb561e8..717fbde5 100644 --- a/Libs/DF/fw.lua +++ b/Libs/DF/fw.lua @@ -1,6 +1,6 @@ -local dversion = 460 +local dversion = 463 local major, minor = "DetailsFramework-1.0", dversion local DF, oldminor = LibStub:NewLibrary(major, minor) @@ -233,8 +233,8 @@ end ---return the role of the unit, this is safe to use for all versions of wow ---@param unitId string ----@param bUseSupport boolean ----@param specId number +---@param bUseSupport boolean? +---@param specId number? ---@return string function DF.UnitGroupRolesAssigned(unitId, bUseSupport, specId) if (not DF.IsTimewalkWoW()) then --Was function exist check. TBC has function, returns NONE. -Flamanis 5/16/2022 @@ -1501,6 +1501,11 @@ function DF:CheckPoints(point1, point2, point3, point4, point5, object) return point1 or "topleft", point2, point3 or "topleft", point4 or 0, point5 or 0 end +---@class df_anchor : table +---@field side number 1-8: topleft to top (clockwise); 9: center; 10-13: inside left right top bottom; 14-17: inside topleft, bottomleft bottomright topright +---@field x number +---@field y number + local anchoringFunctions = { function(frame, anchorTo, offSetX, offSetY) --1 TOP LEFT frame:ClearAllPoints() @@ -1547,30 +1552,54 @@ local anchoringFunctions = { frame:SetPoint("center", anchorTo, "center", offSetX, offSetY) end, - function(frame, anchorTo, offSetX, offSetY) --10 + function(frame, anchorTo, offSetX, offSetY) --10 INSIDE LEFT frame:ClearAllPoints() frame:SetPoint("left", anchorTo, "left", offSetX, offSetY) end, - function(frame, anchorTo, offSetX, offSetY) --11 + function(frame, anchorTo, offSetX, offSetY) --11 INSIDE RIGHT frame:ClearAllPoints() frame:SetPoint("right", anchorTo, "right", offSetX, offSetY) end, - function(frame, anchorTo, offSetX, offSetY) --12 + function(frame, anchorTo, offSetX, offSetY) --12 INSIDE TOP frame:ClearAllPoints() frame:SetPoint("top", anchorTo, "top", offSetX, offSetY) end, - function(frame, anchorTo, offSetX, offSetY) --13 + function(frame, anchorTo, offSetX, offSetY) --13 INSIDE BOTTOM frame:ClearAllPoints() frame:SetPoint("bottom", anchorTo, "bottom", offSetX, offSetY) - end + end, + + function(frame, anchorTo, offSetX, offSetY) --14 INSIDE TOPLEFT to TOPLEFT + frame:ClearAllPoints() + frame:SetPoint("topleft", anchorTo, "topleft", offSetX, offSetY) + end, + + function(frame, anchorTo, offSetX, offSetY) --15 INSIDE BOTTOMLEFT to BOTTOMLEFT + frame:ClearAllPoints() + frame:SetPoint("bottomleft", anchorTo, "bottomleft", offSetX, offSetY) + end, + + function(frame, anchorTo, offSetX, offSetY) --16 INSIDE BOTTOMRIGHT to BOTTOMRIGHT + frame:ClearAllPoints() + frame:SetPoint("bottomright", anchorTo, "bottomright", offSetX, offSetY) + end, + + function(frame, anchorTo, offSetX, offSetY) --17 INSIDE TOPRIGHT to TOPRIGHT + frame:ClearAllPoints() + frame:SetPoint("topright", anchorTo, "topright", offSetX, offSetY) + end, } -function DF:SetAnchor(widget, config, anchorTo) +---set the anchor point using a df_anchor table +---@param widget uiobject +---@param anchorTable df_anchor +---@param anchorTo uiobject +function DF:SetAnchor(widget, anchorTable, anchorTo) anchorTo = anchorTo or widget:GetParent() - anchoringFunctions[config.side](widget, anchorTo, config.x, config.y) + anchoringFunctions[anchorTable.side](widget, anchorTo, anchorTable.x, anchorTable.y) end ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -2762,6 +2791,7 @@ end elseif (widgetTable.type == "textentry") then local textentry = DF:CreateTextEntry(parent, widgetTable.func or widgetTable.set, 120, 18, nil, "$parentWidget" .. index, nil, buttonTemplate) + textentry.align = widgetTable.align or "left" local descPhraseId = getDescripttionPhraseID(widgetTable, languageAddonId, languageTable) DetailsFramework.Language.RegisterTableKeyWithDefault(languageAddonId, textentry, "have_tooltip", descPhraseId, widgetTable.desc) diff --git a/Libs/DF/icon.lua b/Libs/DF/icon.lua index ce7dd04d..4c9407c3 100644 --- a/Libs/DF/icon.lua +++ b/Libs/DF/icon.lua @@ -8,17 +8,99 @@ end local unpack = unpack local CreateFrame = CreateFrame local PixelUtil = PixelUtil +local GetTime = GetTime + +---@class df_icon : frame +---@field spellId number +---@field startTime number +---@field duration number +---@field count number +---@field width number +---@field height number +---@field debuffType string +---@field caster string +---@field canStealOrPurge boolean +---@field isBuff boolean +---@field spellName string +---@field timeRemaining number +---@field expirationTime number +---@field lastUpdateCooldown number +---@field identifierKey string +---@field endTime number +---@field nextUpdate number +---@field currentCoords table +---@field textureWidth number +---@field textureHeight number +---@field stacks number +---@field Texture texture +---@field Border texture +---@field StackText fontstring +---@field StackTextShadow fontstring +---@field Desc fontstring +---@field Cooldown cooldown +---@field CountdownText fontstring +---@field SimpleCooldownTexture texture +---@field SimpleCountdownText fontstring +---@field parentIconRow df_iconrow +---@field cooldownLooper timer + +---@class df_iconrow : frame +---@field options table +---@field NextIcon number +---@field IconPool df_icon[] +---@field AuraCache table +---@field shownAmount number +---@field CreateIcon fun(self:df_iconrow, iconName:string?, bIsSimple:boolean?):frame +---@field GetIcon fun(self:df_iconrow, bIsSimple:boolean?):frame +---@field AddSpecificIcon fun(self:df_iconrow, identifierKey:string, spellId:number?, borderColor:table?, startTime:number?, duration:number?, forceTexture:string|number|nil, descText:table?, count:number?, debuffType:string?, caster:string?, canStealOrPurge:boolean?, spellName:string?, isBuff:boolean?) +---@field AddSpecificIconSimple fun(self:df_iconrow, identifierKey:string, spellId:number?, borderColor:table?, startTime:number?, duration:number?, forceTexture:string|number|nil, descText:table?, count:number?, debuffType:string?, caster:string?, canStealOrPurge:boolean?, spellName:string?, isBuff:boolean?, modRate:number?, iconSettings:table?) +---@field SetIcon fun(self:df_iconrow, spellId:number?, borderColor:table?, startTime:number?, duration:number?, forceTexture:string?, descText:table?, count:number?, debuffType:string?, caster:string?, canStealOrPurge:boolean?, spellName:string?, isBuff:boolean?, modRate:number?):frame +---@field SetIconSimple fun(self:df_iconrow, spellId:number?, borderColor:table?, startTime:number?, duration:number?, iconTexture:any, descText:table?, count:number?, debuffType:string?, caster:string?, canStealOrPurge:boolean?, spellName:string?, isBuff:boolean?, modRate:number?, iconSettings:table?):frame +---@field SetAuraIconSimpleWithIconTemplate fun(self:df_iconrow, aI:aurainfo, iconTemplateTable:df_icontemplate) +---@field SetStacks fun(self:df_iconrow, iconFrame:table, bIsShown:boolean, stacksAmount:number?) is shown false to hide the stack text +---@field OnIconTick fun(self:df_icon, deltaTime:number) +---@field OnIconSimpleTick fun(self:df_icon) +---@field FormatCooldownTime fun(thisTime:number):string +---@field FormatCooldownTimeDecimal fun(formattedTime:number):string +---@field RemoveSpecificIcon fun(self:df_iconrow, identifierKey:any) +---@field ClearIcons fun(self:df_iconrow, resetBuffs:boolean?, resetDebuffs:boolean?) +---@field AlignAuraIcons fun(self:df_iconrow) +---@field GetIconGrowDirection fun(self:df_iconrow):number +---@field OnOptionChanged fun(self:df_iconrow, optionName:string) + +---@class df_icontemplate : table +---@field id any +---@field texture texturepath|textureid +---@field startTime number? +---@field duration number? +---@field count number? +---@field coords table? +---@field width number? +---@field height number? +---@field points table? +---@field borderColor table? +---@field borderTexture texturepath|textureid|nil +---@field scale number? +---@field alpha number? local spellIconCache = {} local spellNameCache = {} +local emptyTable = {} +local white = {1, 1, 1, 1} + +local iconFrameOnHideScript = function(self) + if (self.cooldownLooper) then + self.cooldownLooper:Cancel() + end +end detailsFramework.IconMixin = { ---create a new icon frame - ---@param self frame the parent frame + ---@param self df_iconrow the parent frame ---@param iconName string the name of the icon frame - ---@return frame + ---@return df_icon CreateIcon = function(self, iconName, bIsSimple) - ---@type frame + ---@type df_icon local iconFrame = CreateFrame("frame", iconName, self, not bIsSimple and "BackdropTemplate") ---@type texture @@ -56,13 +138,36 @@ detailsFramework.IconMixin = { iconFrame.CountdownText:SetPoint("center", iconFrame, "center", 0, 0) iconFrame.CountdownText:Hide() + if (bIsSimple) then + --create a overlay texture which will indicate the cooldown time + iconFrame.SimpleCooldownTexture = iconFrame:CreateTexture(self:GetName() .. "SimpleCooldownTexture", "overlay", nil, 7) + iconFrame.SimpleCooldownTexture:SetTexture([[Interface\Azerite\AzeriteTooltipBackground]], "CLAMP", "CLAMP", "NEAREST") + iconFrame.SimpleCooldownTexture:SetPoint("bottomleft", iconFrame.Texture, "bottomleft", 0, 0) + iconFrame.SimpleCooldownTexture:SetPoint("bottomright", iconFrame.Texture, "bottomright", 0, 0) + iconFrame.SimpleCooldownTexture:SetHeight(1) + iconFrame.SimpleCooldownTexture:Hide() + + iconFrame.SimpleCountdownText = iconFrame:CreateFontString(self:GetName() .. "SimpleCooldownText", "overlay", "GameFontNormal") + iconFrame.SimpleCountdownText:SetPoint("center", iconFrame, "center", 0, 0) + iconFrame.SimpleCountdownText:Hide() + end + + iconFrame.stacks = 0 + iconFrame:SetScript("OnHide", iconFrameOnHideScript) + return iconFrame end, + ---get an icon frame from the pool + ---@param self df_iconrow the parent frame + ---@param bIsSimple boolean if true, the icon will be created with a simple template + ---@return df_icon GetIcon = function(self, bIsSimple) + ---@type df_icon local iconFrame = self.IconPool[self.NextIcon] if (not iconFrame) then + ---@type df_icon iconFrame = self:CreateIcon("$parentIcon" .. self.NextIcon, bIsSimple) iconFrame.parentIconRow = self @@ -79,7 +184,6 @@ detailsFramework.IconMixin = { iconFrame.Border:SetDrawLayer("overlay", 7) iconFrame.StackText:SetTextColor(detailsFramework:ParseColors(self.options.stack_text_color)) - iconFrame.StackText:SetPoint(self.options.stack_text_anchor or "center", iconFrame, self.options.stack_text_rel_anchor or "center", self.options.stack_text_x_offset or 0, self.options.stack_text_y_offset or 0) detailsFramework:SetFontSize(iconFrame.StackText, self.options.stack_text_size) detailsFramework:SetFontFace(iconFrame.StackText, self.options.stack_text_font) detailsFramework:SetFontOutline(iconFrame.StackText, self.options.stack_text_outline) @@ -133,19 +237,24 @@ detailsFramework.IconMixin = { return iconFrame end, - --adds only if not existing already in the cache - AddSpecificIcon = function(self, identifierKey, spellId, borderColor, startTime, duration, forceTexture, descText, count, debuffType, caster, canStealOrPurge, spellName, isBuff) + ---adds only if not existing already in the cache + ---@param self df_iconrow the parent frame + AddSpecificIcon = function(self, identifierKey, spellId, borderColor, startTime, duration, forceTexture, descText, count, debuffType, caster, canStealOrPurge, spellName, isBuff, modRate) if (not identifierKey or identifierKey == "") then return end if (not self.AuraCache[identifierKey]) then - local icon = self:SetIcon(spellId, borderColor, startTime, duration, forceTexture, descText, count, debuffType, caster, canStealOrPurge, spellName, isBuff or false) + ---@type df_icon + local icon = self:SetIcon(spellId, borderColor, startTime, duration, forceTexture, descText, count, debuffType, caster, canStealOrPurge, spellName, isBuff or false, modRate) icon.identifierKey = identifierKey self.AuraCache[identifierKey] = true end end, + ---set an icon frame + ---@param self df_iconrow the parent frame + ---@return df_icon? SetIcon = function(self, spellId, borderColor, startTime, duration, forceTexture, descText, count, debuffType, caster, canStealOrPurge, spellName, isBuff, modRate) local actualSpellName, _, spellIcon = GetSpellInfo(spellId) @@ -157,6 +266,7 @@ detailsFramework.IconMixin = { modRate = modRate or 1 if (spellIcon) then + ---@type df_icon local iconFrame = self:GetIcon() iconFrame.Texture:SetTexture(spellIcon) iconFrame.Texture:SetTexCoord(unpack(self.options.texcoord)) @@ -237,7 +347,13 @@ detailsFramework.IconMixin = { iconFrame.StackText:Hide() end - PixelUtil.SetSize(iconFrame, self.options.icon_width, self.options.icon_height) + iconFrame.stacks = count or 0 + + iconFrame.width = self.options.icon_width + iconFrame.height = self.options.icon_height + iconFrame.textureWidth = iconFrame.Texture:GetWidth() + iconFrame.textureHeight = iconFrame.Texture:GetHeight() + PixelUtil.SetSize(iconFrame, iconFrame.width, iconFrame.height) iconFrame:Show() --update the size of the frame @@ -276,15 +392,101 @@ detailsFramework.IconMixin = { iconFrame.StackTextShadow:Show() iconFrame.StackText:SetText(stacksAmount) iconFrame.StackTextShadow:SetText(stacksAmount) + iconFrame.stacks = stacksAmount else iconFrame.StackText:Hide() iconFrame.StackTextShadow:Hide() + iconFrame.stacks = 0 end end, - SetIconSimple = function(self, spellId, borderColor, startTime, duration, iconTexture, descText, count, debuffType, caster, canStealOrPurge, spellName, isBuff, modRate) + ---this function return true if an update is required to be performed, return false if the icons shown are the same as the ones received in the auraList + ---'amount' are the amount of auras the check in the list received against the icons shown, the function which called this function know how much icons it's showing + ---'auraList' are the list of auras which can be a simple array or an array of sub-arrays + ---if valid, 'auraInfoIndex' is the index where the auraInfo is found on the sub-array + ---@param self df_iconrow + ---@param auraList table + ---@param amount number + ---@param auraInfoIndex number? + ---@return boolean? + NeedUpdate = function(self, auraList, amount, auraInfoIndex) + if (not auraList or not amount) then + return false, "no aura list or amount" + end + + local iconPool = self.IconPool + local iconPoolAmount = #iconPool + local currentlyShown = self.shownAmount + local auraListAmount = #auraList + local amountToShow = math.min(amount, auraListAmount) + + --quick exit if the parameters already have divergences + if (amountToShow ~= currentlyShown) then + return true + end + + --the amount of icons shown is the same as the amount of auras that are needed to show + --now check if it's showing the same auras + + for i = 1, amount do + local auraInfo = auraInfoIndex and auraList[i][auraInfoIndex] or auraList[i] + local iconFrame = iconPool[i] + if (iconFrame.Texture.texture ~= auraInfo.icon) then + return true + end + + local auraStartTime = auraInfo.expirationTime - auraInfo.duration + local iconFrameStartTime = iconFrame.startTime + + --if they are more than 200 miliseconds apart, then it's a different aura + if (math.abs(auraStartTime - iconFrameStartTime) > 0.2) then + return true, "aura start has changed" + end + end + + return false, "nothing changed" + end, + + ---adds only if not existing already in the cache + ---@param self df_iconrow the parent frame + AddSpecificIconSimple = function(self, identifierKey, spellId, borderColor, startTime, duration, forceTexture, descText, count, debuffType, caster, canStealOrPurge, spellName, isBuff, modRate, iconSettings) + if (not identifierKey or identifierKey == "") then + return + end + + if (not self.AuraCache[identifierKey]) then + ---@type df_icon + local icon = self:SetIconSimple(spellId, borderColor, startTime, duration, forceTexture, descText, count, debuffType, caster, canStealOrPurge, spellName, isBuff or false, modRate, iconSettings) + icon.identifierKey = identifierKey + self.AuraCache[identifierKey] = true + end + end, + + ---set an icon frame with a simple template + ---@param self df_iconrow the parent frame + ---@param aI aurainfo + ---@param iconTemplateTable df_icontemplate + SetAuraIconSimpleWithIconTemplate = function(self, aI, iconTemplateTable) + local startTime = aI.expirationTime - aI.duration + ---@type df_icon + local iconFrame = self:SetIconSimple(aI.spellId, nil, startTime, aI.duration, aI.icon, nil, aI.applications, aI.dispelName, aI.sourceUnit, aI.isStealable, aI.name, aI.isHelpful, aI.timeMod, iconTemplateTable) + if (iconFrame) then + iconFrame.expirationTime = aI.expirationTime + end + end, + + AddSpecificIconWithTemplate = function(self, iconTemplateTable) + self:AddSpecificIconSimple(iconTemplateTable.id, iconTemplateTable.id, nil, iconTemplateTable.startTime, iconTemplateTable.duration, nil, nil, iconTemplateTable.count, nil, nil, nil, nil, nil, nil, iconTemplateTable) + end, + + ---set an icon frame with a simple template + ---@param self df_iconrow the parent frame + ---@return df_icon? + SetIconSimple = function(self, spellId, borderColor, startTime, duration, iconTexture, descText, count, debuffType, caster, canStealOrPurge, spellName, isBuff, modRate, iconSettings) local actualSpellName, spellIcon = spellNameCache[spellId], spellIconCache[spellId] + iconSettings = iconSettings or emptyTable + if (not actualSpellName) then actualSpellName, _, spellIcon = GetSpellInfo(spellId) spellIconCache[spellId] = spellIcon @@ -295,23 +497,85 @@ detailsFramework.IconMixin = { spellIcon = iconTexture end + if (not spellIcon) then + if (iconSettings.texture) then + spellIcon = iconSettings.texture + end + end + if (spellIcon) then spellName = spellName or actualSpellName or "unknown_aura" modRate = modRate or 1 local bIsSimple = true - local iconFrame = self:GetIcon(bIsSimple, bIsSimple) + ---@type df_icon + local iconFrame = self:GetIcon(bIsSimple) + self.shownAmount = self.NextIcon - 1 + + if (iconFrame.Texture.texture ~= spellIcon or (iconSettings.coords and iconSettings.coords ~= iconFrame.currentCoords)) then + iconFrame.Texture:SetTexture(spellIcon, "CLAMP", "CLAMP", iconSettings.textureFilter or "LINEAR") + + if (iconSettings.coords) then + iconFrame.Texture:SetTexCoord(unpack(iconSettings.coords)) + iconFrame.currentCoords = iconSettings.coords + + elseif (self.options.texcoord ~= iconFrame.currentCoords) then + iconFrame.Texture:SetTexCoord(unpack(self.options.texcoord)) + iconFrame.currentCoords = self.options.texcoord + else + iconFrame.Texture:SetTexCoord(0, 1, 0, 1) + end + + iconFrame.Texture:ClearAllPoints() + if (iconSettings.points) then + for i = 1, #iconSettings.points do + local point = iconSettings.points[i] + iconFrame.Texture:SetPoint(point[1], iconFrame, point[2], point[3], point[4]) + end + + if (iconSettings.width) then + iconFrame.Texture:SetWidth(iconSettings.width) + else + iconFrame.Texture:SetWidth(self.options.icon_width) + end + + if (iconSettings.height) then + iconFrame.Texture:SetHeight(iconSettings.height) + else + iconFrame.Texture:SetHeight(self.options.icon_height) + end + else + if (iconSettings.width) then + iconFrame.Texture:SetWidth(iconSettings.width) + iconFrame.Texture:SetHeight(iconSettings.height or iconSettings.width) + PixelUtil.SetPoint(iconFrame.Texture, "center", iconFrame, "center", 0, 0) + else + PixelUtil.SetPoint(iconFrame.Texture, "topleft", iconFrame, "topleft", 1, -1) + PixelUtil.SetPoint(iconFrame.Texture, "bottomright", iconFrame, "bottomright", -1, 1) + end + end - if (iconFrame.Texture.texture ~= spellIcon) then - iconFrame.Texture:SetTexture(spellIcon, "clamp", "clamp", "nearest") iconFrame.Texture.texture = spellIcon + else + if (self.options.texcoord ~= iconFrame.currentCoords) then + iconFrame.Texture:SetTexCoord(unpack(self.options.texcoord)) + iconFrame.currentCoords = self.options.texcoord + else + iconFrame.Texture:SetTexCoord(0, 1, 0, 1) + end end if (borderColor) then iconFrame.Border:Show() iconFrame.Border:SetVertexColor(unpack(borderColor)) else - iconFrame.Border:Hide() + if (iconSettings.borderColor) then + iconFrame.Border:Show() + iconFrame.Border:SetTexture(iconSettings.borderTexture or 130759) + iconFrame.Border:SetVertexColor(unpack(iconSettings.borderColor or white)) + else + iconFrame.Border:Hide() + end end if (count and count > 1 and self.options.stack_text) then @@ -320,9 +584,30 @@ detailsFramework.IconMixin = { self:SetStacks(iconFrame, false) end - PixelUtil.SetSize(iconFrame, self.options.icon_width, self.options.icon_height) + iconFrame.stacks = count or 0 + + if (iconSettings.scale) then + iconFrame.Texture:SetScale(iconSettings.scale) + else + iconFrame.Texture:SetScale(1) + end + + if (iconSettings.alpha) then + iconFrame.Texture:SetAlpha(iconSettings.alpha) + else + iconFrame.Texture:SetAlpha(1) + end + + --cache size + iconFrame.width = self.options.icon_width + iconFrame.height = self.options.icon_height + + PixelUtil.SetSize(iconFrame, iconFrame.width, iconFrame.height) iconFrame:Show() + iconFrame.textureWidth = iconFrame.Texture:GetWidth() + iconFrame.textureHeight = iconFrame.Texture:GetHeight() + --update the size of the frame self:SetWidth((self.options.left_padding * 2) + (self.options.icon_padding * (self.NextIcon-2)) + (self.options.icon_width * (self.NextIcon - 1))) self:SetHeight(self.options.icon_height + (self.options.top_padding * 2)) @@ -331,6 +616,7 @@ detailsFramework.IconMixin = { iconFrame.spellId = spellId iconFrame.startTime = startTime iconFrame.duration = duration + iconFrame.endTime = (startTime and duration and startTime + duration) or 0 iconFrame.count = count iconFrame.debuffType = debuffType iconFrame.caster = caster @@ -338,6 +624,18 @@ detailsFramework.IconMixin = { iconFrame.isBuff = isBuff iconFrame.spellName = spellName + if (startTime and duration and duration > 0) then + local endTime = startTime + duration + local now = GetTime() + if (endTime > now) then + iconFrame.SimpleCooldownTexture:Show() + iconFrame.SimpleCooldownTexture:SetHeight(1) + self:SetSimpleCooldown(iconFrame) + end + else + iconFrame.SimpleCooldownTexture:Hide() + end + iconFrame.identifierKey = nil -- only used for "specific" add/remove --add the spell into the cache @@ -349,10 +647,44 @@ detailsFramework.IconMixin = { --show the frame self:Show() + self:AlignAuraIcons() + return iconFrame end end, + ---@param self df_iconrow the parent frame + ---@param iconFrame df_icon + SetSimpleCooldown = function(self, iconFrame) + if (iconFrame.cooldownLooper) then + iconFrame.cooldownLooper:Cancel() + end + + self.OnIconSimpleTick(iconFrame) + + local amountOfLoops = math.floor(iconFrame.duration / 0.5) + local loopEndCallback = nil + local checkPointCallback = nil + + local newLooper = detailsFramework.Schedules.NewLooper(0.5, self.OnIconSimpleTick, amountOfLoops, loopEndCallback, checkPointCallback, iconFrame) + iconFrame.cooldownLooper = newLooper + end, + + ---@param iconFrame df_icon + OnIconSimpleTick = function(iconFrame) + local now = GetTime() + local percent = (now - iconFrame.startTime) / iconFrame.duration + --percent = abs(percent - 1) + + local newHeight = math.min(iconFrame.textureHeight * percent, iconFrame.textureHeight) + iconFrame.SimpleCooldownTexture:SetHeight(newHeight) + + iconFrame.SimpleCountdownText:SetText(iconFrame.parentIconRow.FormatCooldownTime(iconFrame.duration - (now - iconFrame.startTime))) + --self.SimpleCountdownText:Show() + end, + + ---@param self df_icon + ---@param deltaTime number OnIconTick = function(self, deltaTime) local now = GetTime() if (self.lastUpdateCooldown + 0.05) <= now then @@ -370,17 +702,17 @@ detailsFramework.IconMixin = { end end, - FormatCooldownTime = function(formattedTime) - if (formattedTime >= 3600) then - formattedTime = math.floor(formattedTime / 3600) .. "h" + FormatCooldownTime = function(thisTime) + if (thisTime >= 3600) then + thisTime = math.floor(thisTime / 3600) .. "h" - elseif (formattedTime >= 60) then - formattedTime = math.floor(formattedTime / 60) .. "m" + elseif (thisTime >= 60) then + thisTime = math.floor(thisTime / 60) .. "m" else - formattedTime = math.floor(formattedTime) + thisTime = math.floor(thisTime) end - return formattedTime + return thisTime end, FormatCooldownTimeDecimal = function(formattedTime) @@ -401,16 +733,21 @@ detailsFramework.IconMixin = { end end, + ---@param self df_iconrow the parent frame + ---@param identifierKey any RemoveSpecificIcon = function(self, identifierKey) if (not identifierKey or identifierKey == "") then return end - table.wipe(self.AuraCache) + if (not self.AuraCache[identifierKey]) then + return + end + self.AuraCache[identifierKey] = nil local iconPool = self.IconPool - local countStillShown = 0 + --find and hide the icon frame for i = 1, self.NextIcon -1 do local iconFrame = iconPool[i] if (iconFrame.identifierKey and iconFrame.identifierKey == identifierKey) then @@ -422,13 +759,13 @@ detailsFramework.IconMixin = { self.AuraCache[iconFrame.spellName] = true self.AuraCache.canStealOrPurge = self.AuraCache.canStealOrPurge or iconFrame.canStealOrPurge self.AuraCache.hasEnrage = self.AuraCache.hasEnrage or iconFrame.debuffType == "" --yes, enrages are empty-string... - countStillShown = countStillShown + 1 end end self:AlignAuraIcons() end, + ---@param self df_iconrow the parent frame ClearIcons = function(self, resetBuffs, resetDebuffs) resetBuffs = resetBuffs ~= false resetDebuffs = resetDebuffs ~= false @@ -461,18 +798,27 @@ detailsFramework.IconMixin = { self:AlignAuraIcons() end, + ---@param self df_iconrow the parent frame AlignAuraIcons = function(self) local iconPool = self.IconPool local iconAmount = #iconPool local countStillShown = 0 - table.sort(iconPool, function(i1, i2) return i1:IsShown() and not i2:IsShown() end) - if iconAmount == 0 then self:Hide() else - --re-anchor not hidden + table.sort(iconPool, function(i1, i2) return i1:IsShown() and not i2:IsShown() end) + local shownAmount = 0 for i = 1, iconAmount do + if iconPool[i]:IsShown() then + shownAmount = shownAmount + 1 + end + end + + local width = 0 + + --re-anchor not hidden + for i = 1, shownAmount do local iconFrame = iconPool[i] local anchor = self.options.anchor local anchorTo = i == 1 and self or self.IconPool[i - 1] @@ -482,12 +828,14 @@ detailsFramework.IconMixin = { countStillShown = countStillShown + (iconFrame:IsShown() and 1 or 0) iconFrame:ClearAllPoints() + if (growDirection == 1) then --grow to right if (i == 1) then PixelUtil.SetPoint(iconFrame, "left", anchorTo, "left", xPadding, 0) else PixelUtil.SetPoint(iconFrame, "left", anchorTo, "right", xPadding, 0) end + elseif (growDirection == 2) then --grow to left if (i == 1) then PixelUtil.SetPoint(iconFrame, "right", anchorTo, "right", xPadding, 0) @@ -495,12 +843,21 @@ detailsFramework.IconMixin = { PixelUtil.SetPoint(iconFrame, "right", anchorTo, "left", xPadding, 0) end end + + width = width + (iconFrame.width * iconFrame:GetScale()) + xPadding end + + if (self.options.center_alignment) then + self:SetWidth(width) + end + + self.shownAmount = shownAmount end self.NextIcon = countStillShown + 1 end, + ---@param self df_iconrow the parent frame GetIconGrowDirection = function(self) local side = self.options.anchor.side @@ -533,6 +890,7 @@ detailsFramework.IconMixin = { end end, + ---@param self df_iconrow the parent frame OnOptionChanged = function(self, optionName) if (self.SetBackdropColor) then self:SetBackdropColor(unpack(self.options.backdrop_color)) @@ -580,6 +938,7 @@ local default_icon_row_options = { backdrop_border_color = {0, 0, 0, 1}, anchor = {side = 6, x = 2, y = 0}, grow_direction = 1, --1 = to right 2 = to left + center_alignment = false, --if true if will align the icons with grow_direction and then set the iconRow width to match the length used by all icons surpress_blizzard_cd_timer = false, surpress_tulla_omni_cc = false, on_tick_cooldown_update = true, @@ -589,16 +948,16 @@ local default_icon_row_options = { cooldown_edge_texture = "Interface\\Cooldown\\edge", } ----comment ---@param parent frame ---@param name string? ---@param options table? ----@return frame +---@return df_iconrow function detailsFramework:CreateIconRow(parent, name, options) local newIconRowFrame = CreateFrame("frame", name, parent, "BackdropTemplate") newIconRowFrame.IconPool = {} newIconRowFrame.NextIcon = 1 newIconRowFrame.AuraCache = {} + newIconRowFrame.shownAmount = 0 detailsFramework:Mixin(newIconRowFrame, detailsFramework.IconMixin) detailsFramework:Mixin(newIconRowFrame, detailsFramework.OptionsFunctions) diff --git a/Libs/DF/schedules.lua b/Libs/DF/schedules.lua index 2969b573..f2d19c59 100644 --- a/Libs/DF/schedules.lua +++ b/Libs/DF/schedules.lua @@ -12,12 +12,74 @@ DF.Schedules = DF.Schedules or {} ---@class df_schedule : table ---@field NewTicker fun(time: number, callback: function, ...: any): timer +---@field NewLooper fun(time: number, callback: function, loopAmount: number, loopEndCallback: function?, checkPointCallback: function?, ...: any): timer ---@field NewTimer fun(time: number, callback: function, ...: any): timer ---@field Cancel fun(ticker: timer) ---@field After fun(time: number, callback: function) ---@field SetName fun(object: timer, name: string) ---@field RunNextTick fun(callback: function) +local triggerScheduledLoop = function(tickerObject) + if (tickerObject:IsCancelled()) then + return + end + + local payload = tickerObject.payload + local callback = tickerObject.callback + + local result, errortext = pcall(callback, unpack(payload)) + if (not result) then + DF:Msg("error on scheduler: ",tickerObject.path , tickerObject.name, errortext) + end + + local checkPointCallback = tickerObject.checkPointCallback + if (checkPointCallback) then + if (GetTime() >= tickerObject.nextCheckPoint) then + local checkPointResult = checkPointCallback(unpack(payload)) + if (not checkPointResult) then + tickerObject:Cancel() + if (tickerObject.loopEndCallback) then + tickerObject.loopEndCallback() + end + return + end + tickerObject.nextCheckPoint = GetTime() + 1 + end + end + + tickerObject.currentLoop = tickerObject.currentLoop + 1 + + if (tickerObject.currentLoop == tickerObject.lastLoop) then + tickerObject:Cancel() + if (tickerObject.loopEndCallback) then + tickerObject.loopEndCallback() + end + end + + return result +end + +---start a loop which will tick @loopAmount of times, then call @loopEndCallback if exists +---checkPointCallback will be called every time the loop ticks, if it returns false, the loop will be cancelled +---@param time number +---@param callback function +---@param loopAmount number +---@param loopEndCallback function? +---@param checkPointCallback function? +---@vararg any +function DF.Schedules.NewLooper(time, callback, loopAmount, loopEndCallback, checkPointCallback, ...) + local payload = {...} + local newLooper = C_Timer.NewTicker(time, triggerScheduledLoop, loopAmount) + newLooper.payload = payload + newLooper.callback = callback + newLooper.loopEndCallback = loopEndCallback + newLooper.checkPointCallback = checkPointCallback + newLooper.nextCheckPoint = GetTime() + 1 + newLooper.lastLoop = loopAmount + newLooper.currentLoop = 1 + return newLooper +end + --run a scheduled function with its payload local triggerScheduledTick = function(tickerObject) local payload = tickerObject.payload @@ -25,18 +87,17 @@ local triggerScheduledTick = function(tickerObject) local result, errortext = pcall(callback, unpack(payload)) if (not result) then - DF:Msg("error on scheduler: ", tickerObject.path, tickerObject.name, errortext) + DF:Msg("error on scheduler: ",tickerObject.path , tickerObject.name, errortext) end return result end ---schedule to repeat a task with an interval of @time +--schedule to repeat a task with an interval of @time, keep ticking until cancelled function DF.Schedules.NewTicker(time, callback, ...) local payload = {...} local newTicker = C_Timer.NewTicker(time, triggerScheduledTick) newTicker.payload = payload newTicker.callback = callback - newTicker.expireAt = GetTime() + time --debug newTicker.path = debugstack() diff --git a/Libs/DF/unitframe.lua b/Libs/DF/unitframe.lua index eb1461e9..bd675443 100644 --- a/Libs/DF/unitframe.lua +++ b/Libs/DF/unitframe.lua @@ -80,6 +80,7 @@ local cleanfunction = function() end ---@field oldHealth number ---@field currentHealth number ---@field currentHealthMax number +---@field nextShieldHook number ---@field WidgetType string ---@field Settings df_healthbarsettings ---@field background texture @@ -128,6 +129,7 @@ local cleanfunction = function() end OnShow = {}, OnHealthChange = {}, OnHealthMaxChange = {}, + OnAbsorbOverflow = {}, } --use the hook already existing @@ -358,17 +360,36 @@ local cleanfunction = function() end --if the absorb percent pass 100%, show the glow if ((healthPercent + damageAbsorbPercent) > 1) then + self.nextShieldHook = self.nextShieldHook or 0 + + if (GetTime() >= self.nextShieldHook) then + self:RunHooksForWidget("OnAbsorbOverflow", self, self.displayedUnit, healthPercent + damageAbsorbPercent - 1) + self.nextShieldHook = GetTime() + 0.2 + end + self.shieldAbsorbGlow:Show() else self.shieldAbsorbGlow:Hide() + if (self.nextShieldHook) then + self:RunHooksForWidget("OnAbsorbOverflow", self, self.displayedUnit, 0) + self.nextShieldHook = nil + end end else self.shieldAbsorbIndicator:Hide() self.shieldAbsorbGlow:Hide() + if (self.nextShieldHook) then + self:RunHooksForWidget("OnAbsorbOverflow", self, self.displayedUnit, 0) + self.nextShieldHook = nil + end end else self.shieldAbsorbIndicator:Hide() self.shieldAbsorbGlow:Hide() + if (self.nextShieldHook) then + self:RunHooksForWidget("OnAbsorbOverflow", self, self.displayedUnit, 0) + self.nextShieldHook = nil + end end end @@ -431,19 +452,19 @@ function detailsFramework:CreateHealthBar(parent, name, settingsOverride) --artwork --healing incoming - healthBar.incomingHealIndicator = healthBar:CreateTexture(nil, "artwork") + healthBar.incomingHealIndicator = healthBar:CreateTexture(nil, "artwork", nil, 5) healthBar.incomingHealIndicator:SetDrawLayer("artwork", 4) --current shields on the unit - healthBar.shieldAbsorbIndicator = healthBar:CreateTexture(nil, "artwork") + healthBar.shieldAbsorbIndicator = healthBar:CreateTexture(nil, "artwork", nil, 3) healthBar.shieldAbsorbIndicator:SetDrawLayer("artwork", 5) --debuff absorbing heal - healthBar.healAbsorbIndicator = healthBar:CreateTexture(nil, "artwork") + healthBar.healAbsorbIndicator = healthBar:CreateTexture(nil, "artwork", nil, 4) healthBar.healAbsorbIndicator:SetDrawLayer("artwork", 6) --the shield fills all the bar, show that cool glow - healthBar.shieldAbsorbGlow = healthBar:CreateTexture(nil, "artwork") + healthBar.shieldAbsorbGlow = healthBar:CreateTexture(nil, "artwork", nil, 6) healthBar.shieldAbsorbGlow:SetDrawLayer("artwork", 7) --statusbar texture - healthBar.barTexture = healthBar:CreateTexture(nil, "artwork") + healthBar.barTexture = healthBar:CreateTexture(nil, "artwork", nil, 1) end --mixins diff --git a/Libs/LibLuaServer/LibLuaServer.lua b/Libs/LibLuaServer/LibLuaServer.lua index 6b21ef04..1cb065b8 100644 --- a/Libs/LibLuaServer/LibLuaServer.lua +++ b/Libs/LibLuaServer/LibLuaServer.lua @@ -181,6 +181,13 @@ ---| "Minimap" ---| "GameTooltip" +---@alias audiochannels +---| "Master" +---| "SFX" +---| "Music" +---| "Ambience" +---| "Dialog" + ---@class aurainfo : table ---@field applications number ---@field auraInstanceID number @@ -403,6 +410,38 @@ ---@field UnregisterEvent fun(self: frame, event: string) unregister for an event ---@field HookScript fun(self: frame, event: string, handler: function) run a function after the frame's script has been executed, carrying the same arguments +---@class cooldown : frame +---@field Clear fun(self: cooldown) +---@field GetCooldownDuration fun(self: cooldown) : number @returns duration +---@field GetCooldownTimes fun(self: cooldown) : number, number @returns startTime, duration +---@field GetCooldownDisplayDuration fun(self: cooldown) : number @returns duration +---@field GetDrawBling fun(self: cooldown) : boolean @returns drawBling +---@field GetDrawEdge fun(self: cooldown) : boolean @returns drawEdge +---@field GetDrawSwipe fun(self: cooldown) : boolean @returns drawSwipe +---@field GetEdgeScale fun(self: cooldown) : number @returns scale +---@field GetReverse fun(self: cooldown) : boolean @returns reverse +---@field GetRotation fun(self: cooldown) : number @returns radians +---@field IsPaused fun(self: cooldown) : boolean +---@field Pause fun(self: cooldown) +---@field Resume fun(self: cooldown) +---@field SetBlingTexture fun(self: cooldown, texture: textureid|texturepath, r: red|number?, g: green|number?, b: blue|number?, a: alpha|number?) +---@field SetCooldown fun(self: cooldown, startTime: gametime, duration: number, modRate: number?) set the cooldown to start at startTime and last for duration seconds +---@field SetCooldownDuration fun(self: cooldown, duration: number, modRate: number?) +---@field SetCooldownUNIX fun(self: cooldown, startTime: unixtime, duration: number, modRate: number?) +---@field SetCountdownAbbrevThreshold fun(self: cooldown, seconds: number) +---@field SetCountdownFont fun(self: cooldown, font: string) +---@field SetDrawBling fun(self: cooldown, draw: boolean) +---@field SetDrawEdge fun(self: cooldown, draw: boolean) +---@field SetDrawSwipe fun(self: cooldown, draw: boolean) +---@field SetEdgeScale fun(self: cooldown, scale: number) +---@field SetEdgeTexture fun(self: cooldown, texture: textureid|texturepath, r: red|number?, g: green|number?, b: blue|number?, a: alpha|number?) +---@field SetHideCountdownNumbers fun(self: cooldown, hide: boolean) +---@field SetReverse fun(self: cooldown, reverse: boolean) +---@field SetRotation fun(self: cooldown, radians: number) +---@field SetSwipeColor fun(self: cooldown, r: red|number, g: green|number, b: blue|number, a: alpha|number?) +---@field SetSwipeTexture fun(self: cooldown, texture: textureid|texturepath, r: red|number?, g: green|number?, b: blue|number?, a: alpha|number?) +---@field SetUseCircularEdge fun(self: cooldown, use: boolean) + ---@class button : frame ---@field Click fun(self: button) ---@field SetNormalTexture fun(self: button, texture: textureid|texturepath) diff --git a/core/meta.lua b/core/meta.lua index a28e6929..b24857ae 100644 --- a/core/meta.lua +++ b/core/meta.lua @@ -773,11 +773,13 @@ local classTypeUtility = Details.atributos.misc if (canCollect) then amountCleaned = amountCleaned + 1 + if (containerId == 1 or containerId == 2) then --damage or healing Details222.TimeMachine.RemoveActor(actorObject) end + --remove the actor from the container - Details:DestroyActor(actorObject, actorContainer, combatObject) + Details:DestroyActor(actorObject, actorContainer, combatObject) --a window showing 'Auras & Void Zones' did not refreshed and had an actor pointing to here end end end