local dversion = 612 local major, minor = "DetailsFramework-1.0", dversion local DF, oldminor = LibStub:NewLibrary(major, minor) if (not DF) then DetailsFrameworkCanLoad = false return end _G["DetailsFramework"] = DF ---@cast DF detailsframework local detailsFramework = DF --store functions to call when the PLAYER_LOGIN event is triggered detailsFramework.OnLoginSchedules = {} local dfFrame = CreateFrame("frame") dfFrame:RegisterEvent("PLAYER_LOGIN") dfFrame:SetScript("OnEvent", function(self, event, ...) if (event == "PLAYER_LOGIN") then C_Timer.After(0, function() for _, func in ipairs(detailsFramework.OnLoginSchedules) do func() end end) end end) DetailsFrameworkCanLoad = true local SharedMedia = LibStub:GetLibrary("LibSharedMedia-3.0") local _ local type = type local unpack = unpack local IS_WOW_PROJECT_MAINLINE = WOW_PROJECT_ID == WOW_PROJECT_MAINLINE local IS_WOW_PROJECT_NOT_MAINLINE = WOW_PROJECT_ID ~= WOW_PROJECT_MAINLINE local UnitPlayerControlled = UnitPlayerControlled local UnitIsTapDenied = UnitIsTapDenied -- TWW compatibility: local GetSpellInfo = GetSpellInfo or function(spellID) if not spellID then return nil end local si = C_Spell.GetSpellInfo(spellID) if si then return si.name, nil, si.iconID, si.castTime, si.minRange, si.maxRange, si.spellID, si.originalIconID end end local GetSpellBookItemName = GetSpellBookItemName or C_SpellBook.GetSpellBookItemName local GetNumSpellTabs = GetNumSpellTabs or C_SpellBook.GetNumSpellBookSkillLines local GetSpellTabInfo = GetSpellTabInfo or function(tabLine) local skillLine = C_SpellBook.GetSpellBookSkillLineInfo(tabLine) if skillLine then return skillLine.name, skillLine.iconID, skillLine.itemIndexOffset, skillLine.numSpellBookItems, skillLine.isGuild, skillLine.offSpecID end end local SpellBookItemTypeMap = Enum.SpellBookItemType and {[Enum.SpellBookItemType.Spell] = "SPELL", [Enum.SpellBookItemType.None] = "NONE", [Enum.SpellBookItemType.Flyout] = "FLYOUT", [Enum.SpellBookItemType.FutureSpell] = "FUTURESPELL", [Enum.SpellBookItemType.PetAction] = "PETACTION" } or {} local GetSpellBookItemInfo = GetSpellBookItemInfo or function(...) local si = C_SpellBook.GetSpellBookItemInfo(...) if si then return SpellBookItemTypeMap[si.itemType] or "NONE", (si.itemType == Enum.SpellBookItemType.Flyout or si.itemType == Enum.SpellBookItemType.PetAction) and si.actionID or si.spellID or si.actionID, si end end local SPELLBOOK_BANK_PLAYER = Enum.SpellBookSpellBank and Enum.SpellBookSpellBank.Player or "player" local SPELLBOOK_BANK_PET = Enum.SpellBookSpellBank and Enum.SpellBookSpellBank.Pet or "pet" local IsPassiveSpell = IsPassiveSpell or C_Spell.IsSpellPassive local GetOverrideSpell = C_SpellBook and C_SpellBook.GetOverrideSpell or C_Spell.GetOverrideSpell or GetOverrideSpell local HasPetSpells = HasPetSpells or C_SpellBook.HasPetSpells local GetSpecialization = GetSpecialization or C_SpecializationInfo.GetSpecialization local GetSpecializationInfo = GetSpecializationInfo or C_SpecializationInfo.GetSpecializationInfo local spellBookPetEnum = Enum.SpellBookSpellBank and Enum.SpellBookSpellBank.Pet or "pet" SMALL_NUMBER = 0.000001 ALPHA_BLEND_AMOUNT = 0.8400251 --cache this stuff local g, b, d, t = GetBuildInfo() DF.BuildYear = tonumber(d:match("%d+$") or 0) DF.GamePatch = g --string "10.2.7" DF.BuildId = b --string "55000" DF.Toc = t --number 100000 DF.Exp = floor(DF.Toc/10000) local buildInfo = DF.Toc DF.dversion = dversion DF.AuthorInfo = { Author = "", Name = "Terciob", --terciob Discord = "https://discord.gg/AGSzAZX", Support = "www.patreon.com", SearchVideos = "www.youtube.com", } function DF:Msg(msg, ...) print("|cFFFFFFAA" .. (self.__name or "Details!Framework:") .. "|r ", msg, ...) end function DF:MsgWarning(msg, ...) print("|cFFFFFFAA" .. (self.__name or "Details!Framework") .. "|r |cFFFFAA00[Warning]|r", msg, ...) end DF.DefaultRoundedCornerPreset = { roundness = 6, color = {.1, .1, .1, 0.98}, border_color = {.05, .05, .05, 0.834}, } DF.internalFunctions = DF.internalFunctions or {} local PixelUtil = PixelUtil or DFPixelUtil if (not PixelUtil) then --check if is in classic, TBC, or WotLK wow, if it is, build a replacement for PixelUtil local gameVersion = GetBuildInfo() if (gameVersion:match("%d") == "1" or gameVersion:match("%d") == "2" or gameVersion:match("%d") == "3") then PixelUtil = { SetWidth = function(self, width) self:SetWidth(width) end, SetHeight = function(self, height) self:SetHeight(height) end, SetSize = function(self, width, height) self:SetSize(width, height) end, SetPoint = function(self, ...) self:SetPoint(...) end, } end end ---return r, g, b, a for the default backdrop color used in addons ---@return number ---@return number ---@return number ---@return number function DF:GetDefaultBackdropColor() return 0.1215, 0.1176, 0.1294, 0.8 end ---return if the wow version the player is playing is dragonflight ---@return boolean function DF.IsDragonflight() if (buildInfo < 110000 and buildInfo >= 100000) then return true end return false end ---return if the wow version the player is playing is dragonflight or an expansion after it ---@return boolean function DF.IsDragonflightAndBeyond() return buildInfo >= 100000 end function DF.ExpansionHasEvoker() return buildInfo >= 100000 end ---return true if the wow version is Dragonflight or below ---@return boolean function DF.IsDragonflightOrBelow() return buildInfo < 110000 end ---return if the wow version the player is playing is a classic version of wow ---@return boolean function DF.IsTimewalkWoW() if (buildInfo < 60000) then return true end return false end ---return if the wow version the player is playing is the vanilla version of wow ---@return boolean function DF.IsClassicWow() if (buildInfo < 20000) then return true end return false end ---return true if the player is playing in the TBC version of wow ---@return boolean function DF.IsTBCWow() if (buildInfo < 30000 and buildInfo >= 20000) then return true end return false end ---return true if the player is playing in the WotLK version of wow ---@return boolean function DF.IsWotLKWow() if (buildInfo < 40000 and buildInfo >= 30000) then return true end return false end ---return true if the player is playing in the Cataclysm version of wow ---@return boolean function DF.IsCataWow() if (buildInfo < 50000 and buildInfo >= 40000) then return true end return false end ---return true if the player is playing in the Mists version of wow ---@return boolean function DF.IsPandaWow() if (buildInfo < 60000 and buildInfo >= 50000) then return true end return false end ---return true if the player is playing in the Warlords of Draenor version of wow ---@return boolean function DF.IsWarlordsWow() if (buildInfo < 70000 and buildInfo >= 60000) then return true end return false end ---return true if the player is playing in the Legion version of wow ---@return boolean function DF.IsLegionWow() if (buildInfo < 80000 and buildInfo >= 70000) then return true end return false end ---return true if the player is playing in the BFA version of wow ---@return boolean function DF.IsBFAWow() if (buildInfo < 90000 and buildInfo >= 80000) then return true end return false end ---return true if the player is playing in the Shadowlands version of wow ---@return boolean function DF.IsShadowlandsWow() if (buildInfo < 100000 and buildInfo >= 90000) then return true end return false end ---return if the wow version the player is playing is dragonflight ---@return boolean function DF.IsDragonflightWow() if (buildInfo < 110000 and buildInfo >= 100000) then return true end return false end ---return if the wow version the player is playing is the war within ---@return boolean function DF.IsWarWow() if (buildInfo < 120000 and buildInfo >= 110000) then return true end return false end function DF.IsTWWWow() return DF.IsWarWow() end ---return true if the player is playing in the WotLK version of wow with the retail api ---@return boolean function DF.IsNonRetailWowWithRetailAPI() local _, _, _, buildInfo = GetBuildInfo() if (buildInfo < 60000 and buildInfo >= 30401) or (buildInfo < 20000 and buildInfo >= 11404) then return true end return false end DF.IsWotLKWowWithRetailAPI = DF.IsNonRetailWowWithRetailAPI -- this is still in use function DF.ExpansionHasAugEvoker() return DF.IsDragonflightWow() or DF.IsWarWow() end ---for classic wow, get the role using the texture from the talents frame local roleBySpecTextureName = { DruidBalance = "DAMAGER", DruidFeralCombat = "DAMAGER", DruidRestoration = "HEALER", HunterBeastMastery = "DAMAGER", HunterMarksmanship = "DAMAGER", HunterSurvival = "DAMAGER", MageArcane = "DAMAGER", MageFrost = "DAMAGER", MageFire = "DAMAGER", PaladinCombat = "DAMAGER", PaladinHoly = "HEALER", PaladinProtection = "TANK", PriestHoly = "HEALER", PriestDiscipline = "HEALER", PriestShadow = "DAMAGER", RogueAssassination = "DAMAGER", RogueCombat = "DAMAGER", RogueSubtlety = "DAMAGER", ShamanElementalCombat = "DAMAGER", ShamanEnhancement = "DAMAGER", ShamanRestoration = "HEALER", WarlockCurses = "DAMAGER", WarlockDestruction = "DAMAGER", WarlockSummoning = "DAMAGER", WarriorArm = "DAMAGER", WarriorArms = "DAMAGER", WarriorFury = "DAMAGER", WarriorProtection = "TANK", DeathKnightBlood = "TANK", DeathKnightFrost = "DAMAGER", DeathKnightUnholy = "DAMAGER", } ---classic, tbc and wotlk role guesser based on the weights of each talent tree ---@return string function DF:GetRoleByClassicTalentTree() if (not DF.IsTimewalkWoW()) then return "NONE" end --amount of tabs existing local numTabs = GetNumTalentTabs() or 3 --store the background textures for each tab local pointsPerSpec = {} for i = 1, (MAX_TALENT_TABS or 3) do if (i <= numTabs) then --tab information local id, name, description, iconTexture, pointsSpent, fileName = GetTalentTabInfo(i) if DF.IsClassicWow() and not fileName then --On pre 1.15.3 name, iconTexture, pointsSpent, fileName = id, name, description, iconTexture end if (name) then table.insert(pointsPerSpec, {name, pointsSpent, fileName}) end end end local MIN_SPECS = 4 --put the spec with more talent point to the top table.sort(pointsPerSpec, function(t1, t2) return t1[2] > t2[2] end) --get the spec with more points spent local spec = pointsPerSpec[1] if (spec and spec[2] >= MIN_SPECS) then local specName = spec[1] local spentPoints = spec[2] local specTexture = spec[3] local role = roleBySpecTextureName[specTexture] return role or "NONE" end return "DAMAGER" end local roleStringToNumber = { ["NONE"] = 0, ["TANK"] = 1, ["HEALER"] = 2, ["DAMAGER"] = 3, ["SUPPORT"] = 4, } local roleNumberToString = { [0] = "NONE", [1] = "TANK", [2] = "HEALER", [3] = "DAMAGER", [4] = "SUPPORT", } function DF:ConvertRole(value, valueType) if (valueType) then if (type(valueType) == "string") then valueType = roleNumberToString[valueType] or valueType end if (type(valueType) == "number") then valueType = roleStringToNumber[valueType] or valueType end end if (type(value) == "string") then return roleStringToNumber[value] or 0 elseif (type(value) == "number") then return roleNumberToString[value] or "NONE" end return value 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? ---@return string function DF.UnitGroupRolesAssigned(unitId, bUseSupport, specId) local role if (specId == 1473 and bUseSupport) then return "SUPPORT" end if (UnitGroupRolesAssigned) then role = UnitGroupRolesAssigned(unitId) end if (role == "NONE") then if (GetSpecialization) then if (UnitIsUnit(unitId, "player")) then local specializationIndex = GetSpecialization() or 0 local id, name, description, icon, role, primaryStat = GetSpecializationInfo(specializationIndex) if (id == 1473 and bUseSupport) then return "SUPPORT" end return id and role or "NONE" end else --attempt to guess the role by the player spec local classLoc, className = UnitClass(unitId) if (className == "MAGE" or className == "ROGUE" or className == "HUNTER" or className == "WARLOCK") then return "DAMAGER" end if (Details) then --attempt to get the role from Details! Damage Meter local guid = UnitGUID(unitId) if (guid) then role = Details.cached_roles[guid] if (role) then return role end end end if (UnitIsUnit(unitId, "player")) then role = DF:GetRoleByClassicTalentTree() end end end return role end ---return the specializationid of the player it self ---@return number|nil function DF.GetSpecialization() if (GetSpecialization) then return GetSpecialization() end return nil end ---return the specializationid using the specId ---@param specId unknown function DF.GetSpecializationInfoByID(specId) if (GetSpecializationInfoByID) then return GetSpecializationInfoByID(specId) end return nil end function DF.GetSpecializationInfo(...) if (GetSpecializationInfo) then return GetSpecializationInfo(...) end return nil end function DF.GetSpecializationRole(...) if (GetSpecializationRole) then return GetSpecializationRole(...) end return nil end --[=[ dump of C_EncounterJournal ["GetEncountersOnMap"] = function, ["SetPreviewMythicPlusLevel"] = function, ["GetLootInfoByIndex"] = function, ["GetSlotFilter"] = function, ["IsEncounterComplete"] = function, ["SetTab"] = function, ["ResetSlotFilter"] = function, ["OnOpen"] = function, ["InstanceHasLoot"] = function, ["GetSectionIconFlags"] = function, ["SetPreviewPvpTier"] = function, ["GetEncounterJournalLink"] = function, ["GetInstanceForGameMap"] = function, ["GetSectionInfo"] = function, ["GetLootInfo"] = function, ["GetDungeonEntrancesForMap"] = function, ["OnClose"] = function, ["SetSlotFilter"] = function, --]=] --build dummy encounter journal functions if they doesn't exists --this is done for compatibility with classic and if in the future EJ_ functions are moved to C_ ---@class EncounterJournal : table ---@field EJ_GetInstanceForMap fun(mapId: number) ---@field EJ_GetInstanceInfo fun(journalInstanceID: number) ---@field EJ_SelectInstance fun(journalInstanceID: number) ---@field EJ_GetEncounterInfoByIndex fun(index: number, journalInstanceID: number?) ---@field EJ_GetEncounterInfo fun(journalEncounterID: number) ---@field EJ_SelectEncounter fun(journalEncounterID: number) ---@field EJ_GetSectionInfo fun(sectionID: number) ---@field EJ_GetCreatureInfo fun(index: number, journalEncounterID: number?) ---@field EJ_SetDifficulty fun(difficultyID: number) ---@field EJ_GetNumLoot fun(): number DF.EncounterJournal = { EJ_GetInstanceForMap = EJ_GetInstanceForMap or function() return nil end, EJ_GetInstanceInfo = EJ_GetInstanceInfo or function() return nil end, EJ_SelectInstance = EJ_SelectInstance or function() return nil end, EJ_GetEncounterInfoByIndex = EJ_GetEncounterInfoByIndex or function() return nil end, EJ_GetEncounterInfo = EJ_GetEncounterInfo or function() return nil end, EJ_SelectEncounter = EJ_SelectEncounter or function() return nil end, EJ_GetSectionInfo = EJ_GetSectionInfo or function() return nil end, EJ_GetCreatureInfo = EJ_GetCreatureInfo or function() return nil end, EJ_SetDifficulty = EJ_SetDifficulty or function() return nil end, EJ_GetNumLoot = EJ_GetNumLoot or function() return 0 end, } --will always give a very random name for our widgets local init_counter = math.random(1, 1000000) DF.LabelNameCounter = DF.LabelNameCounter or init_counter DF.PictureNameCounter = DF.PictureNameCounter or init_counter DF.BarNameCounter = DF.BarNameCounter or init_counter DF.DropDownCounter = DF.DropDownCounter or init_counter DF.PanelCounter = DF.PanelCounter or init_counter DF.SimplePanelCounter = DF.SimplePanelCounter or init_counter DF.ButtonCounter = DF.ButtonCounter or init_counter DF.SliderCounter = DF.SliderCounter or init_counter DF.SwitchCounter = DF.SwitchCounter or init_counter DF.SplitBarCounter = DF.SplitBarCounter or init_counter DF.FRAMELEVEL_OVERLAY = 750 DF.FRAMELEVEL_BACKGROUND = 150 DF.FrameWorkVersion = tostring(dversion) function DF:PrintVersion() print("Details! Framework Version:", DF.FrameWorkVersion) end --get the working folder do local path = string.match(debugstack(1, 1, 0), "AddOns\\(.+)fw.lua") if (path) then DF.folder = "Interface\\AddOns\\" .. path else --if not found, try to use the last valid one DF.folder = DF.folder or "" end end DF.debug = false function DF:GetFrameworkFolder() return DF.folder end function DF:SetFrameworkDebugState(state) DF.debug = state end DF.embeds = DF.embeds or {} local embedFunctions = { "RemoveRealName", "table", "BuildDropDownFontList", "SetFontSize", "SetFontFace", "SetFontColor", "GetFontSize", "GetFontFace", "SetFontOutline", "trim", "Msg", "CreateFlashAnimation", "Fade", "NewColor", "IsHtmlColor", "ParseColors", "BuildMenu", "ShowTutorialAlertFrame", "GetNpcIdFromGuid", "SetAsOptionsPanel", "GetPlayerRole", "GetCharacterTalents", "GetCharacterPvPTalents", "CreateDropDown", "CreateButton", "CreateColorPickButton", "CreateLabel", "CreateBar", "CreatePanel", "CreateFillPanel", "ColorPick", "IconPick", "CreateSimplePanel", "CreateChartPanel", "CreateImage", "CreateScrollBar", "CreateSwitch", "CreateSlider", "CreateSplitBar", "CreateTextEntry", "Create1PxPanel", "CreateOptionsFrame", "NewSpecialLuaEditorEntry", "ShowPromptPanel", "ShowTextPromptPanel", "GetTemplate", "InstallTemplate", "GetFrameworkFolder", "ShowPanicWarning", "SetFrameworkDebugState", "FindHighestParent", "OpenInterfaceProfile", "CreateInCombatTexture", "CreateAnimationHub", "CreateAnimation", "CreateScrollBox", "CreateBorder", "FormatNumber", "IntegerToTimer", "QuickDispatch", "Dispatch", "CommaValue", "RemoveRealmName", "Trim", "CreateGlowOverlay", "CreateAnts", "CreateFrameShake", "RegisterScriptComm", "SendScriptComm", } function DF:Embed(target) for k, v in pairs(embedFunctions) do target[v] = self[v] end self.embeds[target] = true return target end function DF:FadeFrame(frame, t) if (t == 0) then frame.hidden = false frame.faded = false frame.fading_out = false frame.fading_in = false frame:Show() frame:SetAlpha(1) elseif (t == 1) then frame.hidden = true frame.faded = true frame.fading_out = false frame.fading_in = false frame:SetAlpha(0) frame:Hide() end end ------------------------------------------------------------------------------------------------------------ function DF:RandomBool(odds) if (odds) then local chance = math.random() return chance <= odds else return math.random(1, 2) == 1 end end function DF:SetTexCoordFromAtlasInfo(texture, atlasInfo) texture:SetTexCoord(atlasInfo.leftTexCoord, atlasInfo.rightTexCoord, atlasInfo.topTexCoord, atlasInfo.bottomTexCoord) end ------------------------------------------------------------------------------------------------------------ --table DF.table = {} ---find a value inside a table and return the index ---@param t table ---@param value any ---@return integer|nil function DF.table.find(t, value) for i = 1, #t do if (t[i] == value) then return i end end end ---find a value inside a sub table ---@param index number ---@param value any ---@return integer|nil function DF.table.findsubtable(t, index, value) for i = 1, #t do if (type(t[i]) == "table") then if (t[i][index] == value) then return i end end end end ---Loop through parent of the passed object, making a string with parentKeys separated by a dot. ---The loop continues until a parentKey is not found or if the frame has no parent (reach UIParent). ---@param self table ---@param object any ---@return string function DF:GetParentKeyPath(object) local parentKey = object:GetParentKey() if (not parentKey) then return "" end local path = "" .. parentKey local parent = object:GetParent() while (parent) do parentKey = parent:GetParentKey() if (parentKey) then path = parentKey .. "." .. path else return path end parent = parent:GetParent() end return path end ---Loop through the parent of the passed object, creating a string with parent names and parent keys separated by dots, if the object has no name. ---The loop continues until a parentName is not found or if the frame has no parent (reach UIParent). ---@param self table ---@param object any ---@return string function DF:GetParentNamePath(object) local parent = object local path = "" while (parent) do local parentName = parent:GetName() if (not parentName) then local parentOfParent = parent:GetParent() if (parentOfParent) then local parentKey = parentOfParent:GetParentKey() if (parentKey) then parentName = parentKey else local result = path:gsub("%.$", "") return result end end end if (parentName) then path = parentName .. "." .. path else local result = path:gsub("%.$", "") return result end parent = parent:GetParent() end local result = path:gsub("%.$", "") return result end ---get a value from a table using a path, e.g. getfrompath(tbl, "a.b.c") is the same as tbl.a.b.c ---@param t table ---@param path string ---@param subOffset number? ---@return any function DF.table.getfrompath(t, path, subOffset) if (path:match("%.") or path:match("%[")) then local value local offset = 0 for key in path:gmatch("[%w_]+") do value = t[key] or t[tonumber(key)] --check if the value is nil, if it is, the key does not exists in the table if (not value) then return end --update t for the next iteration t = value offset = offset + 1 if (subOffset == offset) then return value end end return value else return t[path] or t[tonumber(path)] end end ---set the value of a table using a path, e.g. setfrompath(tbl, "a.b.c", 10) is the same as tbl.a.b.c = 10 ---@param t table ---@param path string ---@param value any ---@return boolean? function DF.table.setfrompath(t, path, value) if (path:match("%.") or path:match("%[")) then local lastTable local lastKey --for key in path:gmatch("[%w_]+") do for key in path:gmatch("[^%.%[%]]+") do lastTable = t lastKey = key --update t for the next iteration t = t[key] or t[tonumber(key)] end if (lastTable and lastKey) then lastTable[lastKey] = value return true end else t[path] = value return true end return false end ---return the amount of keys in a table ---@param t table ---@return number function DF.table.countkeys(t) local count = 0 for _ in pairs(t) do count = count + 1 end return count end ---find the value inside the table, and it it's not found, add it ---@param t table ---@param index integer|any ---@param value any ---@return boolean function DF.table.addunique(t, index, value) if (not value) then value = index index = #t + 1 end for i = 1, #t do if (t[i] == value) then return false end end table.insert(t, index, value) return true end ---get the table 't' and reverse the order of the values within it ---@param t table ---@return table function DF.table.reverse(t) local new = {} local index = 1 for i = #t, 1, -1 do new[index] = t[i] index = index + 1 end return new end ---remove a value from an array table ---@param t table ---@param value any ---@return boolean function DF.table.remove(t, value) local bRemoved = false local removedAmount = 0 for i = 1, #t do if (t[i] == value) then table.remove(t, i) bRemoved = true removedAmount = removedAmount + 1 end end return bRemoved, removedAmount end ---copy the values from table2 to table1 overwriting existing values, ignores __index and __newindex, keys pointing to a UIObject are preserved ---@param t1 table ---@param t2 table ---@return table function DF.table.duplicate(t1, t2) for key, value in pairs(t2) do if (key ~= "__index" and key ~= "__newindex") then --preserve a UIObject passing it to the new table with copying it if (type(value) == "table" and table.GetObjectType and table:GetObjectType()) then t1[key] = value elseif (type(value) == "table") then t1[key] = t1[key] or {} DF.table.copy(t1[key], t2[key]) else t1[key] = value end end end return t1 end ---copy the values from table2 to table1 overwriting existing values, ignores __index and __newindex, threat UIObjects as regular tables ---@param t1 table ---@param t2 table ---@return table function DF.table.copy(t1, t2) for key, value in pairs(t2) do if (key ~= "__index" and key ~= "__newindex") then if (type(value) == "table") then t1[key] = t1[key] or {} DF.table.copy(t1[key], t2[key]) else t1[key] = value end end end return t1 end ---copy from table2 to table1 overwriting values but do not copy data that cannot be compressed ---@param t1 table ---@param t2 table ---@return table function DF.table.copytocompress(t1, t2) for key, value in pairs(t2) do if (key ~= "__index" and type(value) ~= "function") then if (type(value) == "table") then if (not value.GetObjectType) then t1[key] = t1[key] or {} DF.table.copytocompress(t1[key], t2[key]) end else t1 [key] = value end end end return t1 end ---remove from table1 the values that are also on table2 ---@param table1 table the table to have the values removed ---@param table2 table the reference table function DF.table.removeduplicate(table1, table2) for key, value in pairs(table2) do if (type(value) == "table") then if (type(table1[key]) == "table") then DF.table.removeduplicate(table1[key], value) if (not next(table1[key])) then table1[key] = nil end end else if (type(table1[key]) == "number" and type(value) == "number") then if (DF:IsNearlyEqual(table1[key], value, 0.0001)) then table1[key] = nil end else if (table1[key] == value) then table1[key] = nil end end end end end ---add the indexes of table2 into the end of the table table1 ---@param t1 table ---@param t2 table ---@return table function DF.table.append(t1, t2) for i = 1, #t2 do t1[#t1+1] = t2[i] end return t1 end ---receive a table and N arguments, add each argument to the table ---@param t1 table ---@vararg any function DF.table.inserts(t1, ...) for i = 1, select("#", ...) do t1[#t1+1] = select(i, ...) end return t1 end ---copy values that does exist on table2 but not on table1 ---@param t1 table ---@param t2 table ---@return table function DF.table.deploy(t1, t2) for key, value in pairs(t2) do if (type(value) == "table") then --check the t1 type as sometimes the key isn't the same type on both tables if (t1[key] == nil or type(t1[key]) == "table") then t1[key] = t1[key] or {} DF.table.deploy(t1[key], t2[key]) end elseif (t1[key] == nil) then t1[key] = value end end return t1 end --/run print (DetailsFramework.table.dump({{1, 2}, {2, 3}, {4, 5}})) local function tableToString(t, resultString, deep, seenTables) resultString = resultString or "" deep = deep or 0 seenTables = seenTables or {} if seenTables[t] then resultString = resultString .. "--CIRCULAR REFERENCE\n" return resultString end local space = string.rep(" ", deep) seenTables[t] = true for key, value in pairs(t) do local valueType = type(value) if (type(key) == "function") then key = "#function#" elseif (type(key) == "table") then key = "#table#" end if (type(key) ~= "string" and type(key) ~= "number") then key = "unknown?" end if (valueType == "table") then local sUIObjectType = value.GetObjectType and value:GetObjectType() if (sUIObjectType) then if (type(key) == "number") then resultString = resultString .. space .. "[" .. key .. "] = |cFFa9ffa9 " .. sUIObjectType .. " {|r\n" else resultString = resultString .. space .. "[\"" .. key .. "\"] = |cFFa9ffa9 " .. sUIObjectType .. " {|r\n" end else if (type(key) == "number") then resultString = resultString .. space .. "[" .. key .. "] = |cFFa9ffa9 {|r\n" else resultString = resultString .. space .. "[\"" .. key .. "\"] = |cFFa9ffa9 {|r\n" end end resultString = resultString .. tableToString(value, nil, deep + 1, seenTables) resultString = resultString .. space .. "|cFFa9ffa9},|r\n" elseif (valueType == "string") then resultString = resultString .. space .. "[\"" .. key .. "\"] = \"|cFFfff1c1" .. value .. "|r\",\n" elseif (valueType == "number") then if (type(key) == "number") then resultString = resultString .. space .. "[" .. key .. "] = |cFFffc1f4" .. value .. "|r,\n" else resultString = resultString .. space .. "[\"" .. key .. "\"] = |cFF94CEA8" .. value .. "|r,\n" end elseif (valueType == "function") then resultString = resultString .. space .. "[\"" .. key .. "\"] = |cFFC586C0function|r,\n" elseif (valueType == "boolean") then resultString = resultString .. space .. "[\"" .. key .. "\"] = |cFF99d0ff" .. (value and "true" or "false") .. "|r,\n" end end return resultString end local function tableToStringSafe(t) local seenTables = {} return tableToString(t, nil, 0, seenTables) end ---get the contends of table 't' and return it as a string ---@param t table ---@param resultString string ---@param deep integer ---@return string function DF.table.dump(t, resultString, deep) return tableToStringSafe(t) end ---grab a text and split it into lines adding each line to an array table ---@param text string ---@return table function DF:SplitTextInLines(text) local lines = {} local position = 1 local startScope, endScope = text:find("\n", position, true) while (startScope) do if (startScope ~= 1) then table.insert(lines, text:sub(position, startScope-1)) end position = endScope + 1 startScope, endScope = text:find("\n", position, true) end if (position <= #text) then table.insert(lines, text:sub(position)) end return lines end DF.strings = {} ---receive an array and output a string with the values separated by commas ---if bDoCompression is true, the string will be compressed using LibDeflate ---@param t table ---@param bDoCompression boolean|nil ---@return string function DF.strings.tabletostring(t, bDoCompression) local newString = "" for i = 1, #t do newString = newString .. t[i] .. "," end newString = newString:sub(1, -2) if (bDoCompression) then local LibDeflate = LibStub:GetLibrary("LibDeflate") if (LibDeflate) then newString = LibDeflate:CompressDeflate(newString, {level = 9}) end end return newString end function DF.strings.stringtotable(thisString, bDoCompression) if (bDoCompression) then local LibDeflate = LibStub:GetLibrary("LibDeflate") if (LibDeflate) then thisString = LibDeflate:DecompressDeflate(thisString) end end local newTable = {strsplit(",", thisString)} return newTable end local symbol_1K, symbol_10K, symbol_1B if (GetLocale() == "koKR") then symbol_1K, symbol_10K, symbol_1B = "천", "만", "억" elseif (GetLocale() == "zhCN") then symbol_1K, symbol_10K, symbol_1B = "千", "万", "亿" elseif (GetLocale() == "zhTW") then symbol_1K, symbol_10K, symbol_1B = "千", "萬", "億" end ---get the game localization and return which symbol need to be used after formatting numbers, this is for asian languages ---@return string ---@return string ---@return string function DF:GetAsianNumberSymbols() if (GetLocale() == "koKR") then return "천", "만", "억" elseif (GetLocale() == "zhCN") then return "千", "万", "亿" elseif (GetLocale() == "zhTW") then return "千", "萬", "億" else --return korean as default (if the language is western) return "천", "만", "억" end end if (symbol_1K) then ---if symbol_1K is valid, the game has an Asian localization, 'DF.FormatNumber' will use Asian symbols to format numbers ---@param number number ---@return string function DF.FormatNumber(number) if (number > 99999999) then return format("%.2f", number/100000000) .. symbol_1B elseif (number > 999999) then return format("%.2f", number/10000) .. symbol_10K elseif (number > 99999) then return floor(number/10000) .. symbol_10K elseif (number > 9999) then return format("%.1f", (number/10000)) .. symbol_10K elseif (number > 999) then return format("%.1f", (number/1000)) .. symbol_1K end return format("%.1f", number) end else ---if symbol_1K isn't valid, 'DF.FormatNumber' will use western symbols to format numbers ---@param number number ---@return string|number function DF.FormatNumber(number) if (number > 999999999) then return format("%.2f", number/1000000000) .. "B" elseif (number > 999999) then return format("%.2f", number/1000000) .. "M" elseif (number > 99999) then return floor(number/1000) .. "K" elseif (number > 999) then return format("%.1f", (number/1000)) .. "K" end return floor(number) end end ---format a number with commas ---@param self table ---@param value number ---@return string function DF:CommaValue(value) if (not value) then return "0" end value = floor(value) if (value == 0) then return "0" end --source http://richard.warburton.it local left, num, right = string.match(value, '^([^%d]*%d)(%d*)(.-)$') return left .. (num:reverse():gsub('(%d%d%d)','%1,'):reverse()) .. right end ---call the function 'callback' for each group member passing the unitID and the extra arguments ---@param self table ---@param callback function ---@vararg any function DF:GroupIterator(callback, ...) if (IsInRaid()) then for i = 1, GetNumGroupMembers() do DF:QuickDispatch(callback, "raid" .. i, ...) end elseif (IsInGroup()) then for i = 1, GetNumGroupMembers() - 1 do DF:QuickDispatch(callback, "party" .. i, ...) end DF:QuickDispatch(callback, "player", ...) else DF:QuickDispatch(callback, "player", ...) end end ---receives an object and a percent amount, then calculate the return value by multiplying the min value of the object width or height by the percent received ---@param uiObject uiobject ---@param percent number ---@return number function DF:GetSizeFromPercent(uiObject, percent) local width, height = uiObject:GetSize() local minValue = math.min(width, height) return minValue * percent end ---get an integer an format it as string with the time format 16:45 ---@param self table ---@param value number ---@return string function DF:IntegerToTimer(value) --~formattime return "" .. math.floor(value/60) .. ":" .. string.format("%02.f", value%60) end ---remove the realm name from a name ---@param self table ---@param name string ---@return string, number function DF:RemoveRealmName(name) return name:gsub(("%-.*"), "") end ---remove the owner name of the pet or guardian ---@param self table ---@param name string ---@return string, number function DF:RemoveOwnerName(name) return name:gsub((" <.*"), "") end ---remove realm and owner names also remove brackets from spell actors ---@param self table ---@param name string ---@return string function DF:CleanUpName(name) name = DF:RemoveRealmName(name) name = DF:RemoveOwnerName(name) name = name:gsub("%[%*%]%s", "") --remove texture escape sequence name = name:gsub("|T.-|t", "") return name end ---remove the realm name from a name ---@param self table ---@param name string ---@return string, number function DF:RemoveRealName(name) return name:gsub(("%-.*"), "") end ---get the UIObject of type 'FontString' named fontString and set the font size to the maximum value of the arguments ---@param self table ---@param fontString fontstring ---@vararg number function DF:SetFontSize(fontString, ...) local font, _, flags = fontString:GetFont() fontString:SetFont(font, math.max(...), flags) end ---get the UIObject of type 'FontString' named fontString and set the font to the argument fontface ---@param self table ---@param fontString fontstring ---@param fontface string function DF:SetFontFace(fontString, fontface) if (fontface == "DEFAULT") then DF:SetFontDefault(fontString) return end local font = SharedMedia:Fetch("font", fontface, true) if (font) then fontface = font end local _, size, flags = fontString:GetFont() return fontString:SetFont(fontface, size, flags) end local dummyFontString = UIParent:CreateFontString(nil, "background", "GameFontNormal") local defaultFontFile = dummyFontString:GetFont() function DF:GetTextWidth(text, size) if (size) then DF:SetFontSize(dummyFontString, size) else DF:SetFontSize(dummyFontString, 12) end dummyFontString:SetText(text) return dummyFontString:GetStringWidth() end ---get the UIObject of type 'FontString' and set the default game font into it ---@param self table ---@param fontString fontstring function DF:SetFontDefault(fontString) local _, size, flags = fontString:GetFont() return fontString:SetFont(defaultFontFile, size, flags) end ---get the FontString passed and set the font color ---@param self table ---@param fontString fontstring ---@param r any ---@param g number? ---@param b number? ---@param a number? function DF:SetFontColor(fontString, r, g, b, a) r, g, b, a = DF:ParseColors(r, g, b, a) fontString:SetTextColor(r, g, b, a) end ---get the FontString passed and set the font shadow color and offset ---@param self table ---@param fontString fontstring ---@param r any ---@param g number? ---@param b number? ---@param a number? ---@param x number? ---@param y number? function DF:SetFontShadow(fontString, r, g, b, a, x, y) r, g, b, a = DF:ParseColors(r, g, b, a) fontString:SetShadowColor(r, g, b, a) local offSetX, offSetY = fontString:GetShadowOffset() x = x or offSetX y = y or offSetY fontString:SetShadowOffset(x, y) end ---get the FontString object passed and set the rotation of the text shown ---@param self table ---@param fontString fontstring ---@param degrees number function DF:SetFontRotation(fontString, degrees) --deprecated, use fontString:SetRotation(degrees) | retail use fontString:SetRotation(math.rad(degrees)) if (type(degrees) == "number") then if (not fontString.__rotationAnimation) then fontString.__rotationAnimation = DF:CreateAnimationHub(fontString) fontString.__rotationAnimation.rotator = DF:CreateAnimation(fontString.__rotationAnimation, "rotation", 1, 0, 0) fontString.__rotationAnimation.rotator:SetEndDelay(10^8) fontString.__rotationAnimation.rotator:SetSmoothProgress(1) end fontString.__rotationAnimation.rotator:SetDegrees(degrees) fontString.__rotationAnimation:Play() fontString.__rotationAnimation:Pause() end end ---receives a string and a color and return the string wrapped with the color using |c and |r scape codes ---@param self table ---@param text string ---@param color any ---@return string function DF:AddColorToText(text, color) --wrap text with a color local r, g, b = DF:ParseColors(color) if (not r) then return text end local hexColor = DF:FormatColor("hex", r, g, b) text = "|c" .. hexColor .. text .. "|r" return text end function DF:GetClassColorByClassId(classId) local classInfo = C_CreatureInfo.GetClassInfo(classId) if (classInfo) then local color = RAID_CLASS_COLORS[classInfo.classFile] if (color) then return color.r, color.g, color.b else return 1, 1, 1 end end return 1, 1, 1 end ---receives a string 'text' and a class name and return the string wrapped with the class color using |c and |r scape codes ---@param self table ---@param text string ---@param className class ---@return string function DF:AddClassColorToText(text, className) if (type(className) == "number") then className = DF.ClassIndexToFileName[className] end if (type(className) ~= "string") then return DF:RemoveRealName(text) elseif (className == "UNKNOW" or className == "PET") then return DF:RemoveRealName(text) end local color = RAID_CLASS_COLORS[className] if (color) then text = "|c" .. color.colorStr .. DF:RemoveRealName(text) .. "|r" else return DF:RemoveRealName(text) end return text end ---returns the class icon texture coordinates and texture file path ---@param class string|number ---@return number, number, number, number, string function DF:GetClassTCoordsAndTexture(class) if (type(class) == "number") then class = DF.ClassIndexToFileName[class] end local l, r, t, b = unpack(CLASS_ICON_TCOORDS[class]) return l, r, t, b, [[Interface\WORLDSTATEFRAME\Icons-Classes]] end ---create a string with the spell icon and the spell name using |T|t scape codes to add the icon inside the string ---@param self table ---@param spellId any ---@return string function DF:MakeStringFromSpellId(spellId) local spellName, _, spellIcon = GetSpellInfo(spellId) if (spellName) then return "|T" .. spellIcon .. ":16:16:0:0:64:64:4:60:4:60|t " .. spellName end return "" end ---wrap 'text' with the class icon of 'playerName' using |T|t scape codes ---@param text string ---@param playerName string ---@param englishClassName string this is the english class name, not the localized one, english class name is upper case ---@param useSpec boolean|nil ---@param iconSize number|nil ---@return string function DF:AddClassIconToText(text, playerName, englishClassName, useSpec, iconSize) local size = iconSize or 16 local spec if (useSpec) then if (Details) then local GUID = UnitGUID(playerName) if (GUID) then spec = Details.cached_specs[GUID] if (spec) then spec = spec end end else if (type(useSpec) == "number") then local specId, specName = GetSpecializationInfoByID(useSpec) end end end if (spec) then --if spec is valid, the user has Details! installed local specString = "" local L, R, T, B = unpack(Details.class_specs_coords[spec]) if (L) then specString = "|TInterface\\AddOns\\Details\\images\\spec_icons_normal:" .. size .. ":" .. size .. ":0:0:512:512:" .. (L * 512) .. ":" .. (R * 512) .. ":" .. (T * 512) .. ":" .. (B * 512) .. "|t" return specString .. " " .. text end end if (englishClassName) then local classString = "" --Details.class_coords uses english class names as keys and the values are tables containing texture coordinates local L, R, T, B = unpack(Details.class_coords[englishClassName]) if (L) then local imageSize = 128 classString = "|TInterface\\AddOns\\Details\\images\\classes_small:" .. size .. ":" .. size .. ":0:0:" .. imageSize .. ":" .. imageSize .. ":" .. (L * imageSize) .. ":" .. (R * imageSize) .. ":" .. (T * imageSize) .. ":" .. (B * imageSize) .. "|t" return classString .. " " .. text end end return text end function DF:AddClassIconToString(text, engClass, size) size = size or 16 local tcoords = CLASS_ICON_TCOORDS[engClass] if (tcoords) then local l, r, t, b = unpack(tcoords) return "|TInterface\\Glues\\CharacterCreate\\UI-CharacterCreate-Classes:" .. size .. ":" .. size .. ":0:0:256:256:" .. (l * 256) .. ":" .. (r * 256) .. ":" .. (t * 256) .. ":" .. (b * 256) .. "|t " .. text end end function DF:AddSpecIconToString(text, specId, size) size = size or 16 if (not specId) then --get the player specId local specIndex = GetSpecialization() specId = GetSpecializationInfo(specIndex) if (not specId) then return end end local id, name, description, icon = GetSpecializationInfoByID(specId) if (id) then return "|T" .. icon .. ":" .. size .. ":" .. size .. ":0:0|t " .. text end end ---create a table with information about a texture (deprecated, use: DetailsFramework:CreateAtlas()) ---@param texture any ---@param textureWidth any ---@param textureHeight any ---@param imageWidth any ---@param imageHeight any ---@param left any ---@param right any ---@param top any ---@param bottom any ---@return table function DF:CreateTextureInfo(texture, textureWidth, textureHeight, left, right, top, bottom, imageWidth, imageHeight) local textureInfo = { texture = texture, width = textureWidth or 16, height = textureHeight or 16, coords = {left or 0, right or 1, top or 0, bottom or 1}, } textureInfo.imageWidth = imageWidth or textureInfo.width textureInfo.imageHeight = imageHeight or textureInfo.height return textureInfo end ---add a texture to the start or end of a string ---@param text string ---@param textureInfo table ---@param bAddSpace any ---@param bAddAfterText any ---@return string function DF:AddTextureToText(text, textureInfo, bAddSpace, bAddAfterText) local texture = textureInfo.texture local textureWidth = textureInfo.width local textureHeight = textureInfo.height local imageWidth = textureInfo.imageWidth or textureWidth local imageHeight = textureInfo.imageHeight or textureHeight local left, right, top, bottom = unpack(textureInfo.coords) left = left or 0 right = right or 1 top = top or 0 bottom = bottom or 1 if (bAddAfterText) then local newString = text .. (bAddSpace and " " or "") .. "|T" .. texture .. ":" .. textureWidth .. ":" .. textureHeight .. ":0:0:" .. imageWidth .. ":" .. imageHeight .. ":" .. (left * imageWidth) .. ":" .. (right * imageWidth) .. ":" .. (top * imageHeight) .. ":" .. (bottom * imageHeight) .. "|t" return newString else local newString = "|T" .. texture .. ":" .. textureWidth .. ":" .. textureHeight .. ":0:0:" .. imageWidth .. ":" .. imageHeight .. ":" .. (left * imageWidth) .. ":" .. (right * imageWidth) .. ":" .. (top * imageHeight) .. ":" .. (bottom * imageHeight) .. "|t" .. (bAddSpace and " " or "") .. text return newString end end ---return the size of a fontstring ---@param fontString table ---@return number function DF:GetFontSize(fontString) local _, size = fontString:GetFont() return size end ---return the font of a fontstring ---@param fontString table ---@return string function DF:GetFontFace(fontString) local fontface = fontString:GetFont() return fontface end local ValidOutlines = { ["NONE"] = true, ["MONOCHROME"] = true, ["OUTLINE"] = true, ["THICKOUTLINE"] = true, ["OUTLINEMONOCHROME"] = true, ["THICKOUTLINEMONOCHROME"] = true, } DF.FontOutlineFlags = { {"", "None"}, {"MONOCHROME", "Monochrome"}, {"OUTLINE", "Outline"}, {"THICKOUTLINE", "Thick Outline"}, {"OUTLINEMONOCHROME", "Outline & Monochrome"}, {"THICKOUTLINEMONOCHROME", "Thick Outline & Monochrome"}, } ---set the outline of a fontstring, outline is a black border around the text, can be "NONE", "MONOCHROME", "OUTLINE" or "THICKOUTLINE" ---@param fontString table ---@param outline outline function DF:SetFontOutline(fontString, outline) local font, fontSize = fontString:GetFont() if (outline) then if (type(outline) == "string") then outline = outline:upper() end if (ValidOutlines[outline]) then outline = outline elseif (type(outline) == "boolean" and outline) then outline = "OUTLINE" elseif (type(outline) == "boolean" and not outline) then outline = "" --"NONE" elseif (outline == 1) then outline = "OUTLINE" elseif (outline == 2) then outline = "THICKOUTLINE" end end outline = (not outline or outline == "NONE") and "" or outline fontString:SetFont(font, fontSize, outline) end ---remove spaces from the start and end of the string ---@param string string ---@return string function DF:Trim(string) return DF:trim(string) end function DF:trim(string) local from = string:match"^%s*()" return from > #string and "" or string:match(".*%S", from) end ---truncate removing at a maximum of 10 character from the string ---@param fontString table ---@param maxWidth number function DF:TruncateTextSafe(fontString, maxWidth) local text = fontString:GetText() local numIterations = 10 while (fontString:GetStringWidth() > maxWidth) do text = strsub(text, 1, #text-1) fontString:SetText(text) if (#text <= 1) then break end numIterations = numIterations - 1 if (numIterations <= 0) then break end end text = DF:CleanTruncateUTF8String(text) fontString:SetText(text) end ---truncate removing characters from the string until the maxWidth is reach ---@param fontString table ---@param maxWidth number function DF:TruncateText(fontString, maxWidth) local text = fontString:GetText() while (fontString:GetStringWidth() > maxWidth) do text = strsub(text, 1, #text - 1) fontString:SetText(text) if (string.len(text) <= 1) then break end end text = DF:CleanTruncateUTF8String(text) fontString:SetText(text) end ---truncate removing text through a binary search with a max of 10 iterations ---@param fontString table ---@param maxWidth number function DF:TruncateTextSafeBinarySearch(fontString, maxWidth) local text = fontString:GetText() if text == nil or text == '' then return end if fontString:GetUnboundedStringWidth() > maxWidth then local left = 1 local right = #text local numIterations = 10 while left <= right and numIterations > 0 do local middle = math.floor((left + right) * 0.5) local substring = strsub(text, 1, middle) fontString:SetText(substring) if fontString:GetUnboundedStringWidth() <= maxWidth then left = middle + 1 else right = middle - 1 end numIterations = numIterations - 1 end text = strsub(text, 1, right) end fontString:SetText(DF:CleanTruncateUTF8String(text)) end ---truncate removing characters from the string until the maxWidth is reach ---@param fontString table ---@param maxWidth number function DF:TruncateTextBinarySearch(fontString, maxWidth) local text = fontString:GetText() if text == nil or text == '' then return end if fontString:GetUnboundedStringWidth() > maxWidth then local left = 1 local right = #text while left <= right do local middle = math.floor((left + right) * 0.5) local substring = strsub(text, 1, middle) fontString:SetText(substring) if fontString:GetUnboundedStringWidth() <= maxWidth then left = middle + 1 else right = middle - 1 end end text = strsub(text, 1, right) end fontString:SetText(DF:CleanTruncateUTF8String(text)) end ---@param text string ---@return string function DF:CleanTruncateUTF8String(text) if type(text) == "string" and text ~= "" then local b1 = (#text > 0) and strbyte(strsub(text, #text, #text)) or nil local b2 = (#text > 1) and strbyte(strsub(text, #text-1, #text)) or nil local b3 = (#text > 2) and strbyte(strsub(text, #text-2, #text)) or nil if b1 and b1 >= 194 and b1 <= 244 then text = strsub (text, 1, #text - 1) elseif b2 and b2 >= 224 and b2 <= 244 then text = strsub (text, 1, #text - 2) elseif b3 and b3 >= 240 and b3 <= 244 then text = strsub (text, 1, #text - 3) end end return text end ---truncate the amount of numbers used to show the fraction part of a number ---@param number number ---@param fractionDigits number ---@return number function DF:TruncateNumber(number, fractionDigits) fractionDigits = fractionDigits or 2 local truncatedNumber = number --local truncatedNumber = format("%." .. fractionDigits .. "f", number) --4x slower than: --http://lua-users.org/wiki/SimpleRound local mult = 10 ^ fractionDigits if (number >= 0) then truncatedNumber = floor(number * mult + 0.5) / mult else truncatedNumber = ceil(number * mult + 0.5) / mult end return truncatedNumber end function DF:GetCursorPosition() local x, y = GetCursorPosition() local scale = UIParent:GetEffectiveScale() return x / scale, y / scale end ---attempt to get the ID of an npc from a GUID ---@param GUID string ---@return number function DF:GetNpcIdFromGuid(GUID) local npcId = select(6, strsplit("-", GUID )) if (npcId) then npcId = tonumber(npcId) return npcId or 0 end return 0 end function DF.SortOrder1(t1, t2) return t1[1] > t2[1] end function DF.SortOrder2(t1, t2) return t1[2] > t2[2] end function DF.SortOrder3(t1, t2) return t1[3] > t2[3] end function DF.SortOrder1R(t1, t2) return t1[1] < t2[1] end function DF.SortOrder2R(t1, t2) return t1[2] < t2[2] end function DF.SortOrder3R(t1, t2) return t1[3] < t2[3] end ---return a list of spells from the player spellbook ---@return table spellNamesInSpellBook ---@return spellid[] spellIdsInSpellBook function DF:GetSpellBookSpells() local spellNamesInSpellBook = {} local spellIdsInSpellBook = {} for i = 1, GetNumSpellTabs() do local tabName, tabTexture, offset, numSpells, isGuild, offspecId = GetSpellTabInfo(i) if (offspecId == 0 and tabTexture ~= 136830) then --don't add spells found in the General tab offset = offset + 1 local tabEnd = offset + numSpells for j = offset, tabEnd - 1 do local spellType, spellId = GetSpellBookItemInfo(j, SPELLBOOK_BANK_PLAYER) if (spellId) then if (spellType ~= "FLYOUT") then local spellName = GetSpellInfo(spellId) if (spellName) then spellNamesInSpellBook[spellName] = true spellIdsInSpellBook[#spellIdsInSpellBook+1] = spellId end else local _, _, numSlots, isKnown = GetFlyoutInfo(spellId) if (isKnown and numSlots > 0) then for k = 1, numSlots do local spellID, overrideSpellID, isKnown = GetFlyoutSlotInfo(spellId, k) if (isKnown) then local spellName = GetSpellInfo(spellID) spellNamesInSpellBook[spellName] = true spellIdsInSpellBook[#spellIdsInSpellBook+1] = spellID end end end end end end end end return spellNamesInSpellBook, spellIdsInSpellBook end function DF:GetHeroTalentId() local configId = C_ClassTalents.GetActiveConfigID() if (not configId) then return 0 end local configInfo = C_Traits.GetConfigInfo(configId) for treeIndex, treeId in ipairs(configInfo.treeIDs) do local treeNodes = C_Traits.GetTreeNodes(treeId) for nodeIdIndex, treeNodeID in ipairs(treeNodes) do local traitNodeInfo = C_Traits.GetNodeInfo(configId, treeNodeID) if (traitNodeInfo) then local activeEntry = traitNodeInfo.activeEntry if (activeEntry) then local entryId = activeEntry.entryID local rank = activeEntry.rank if (rank > 0) then local entryInfo = C_Traits.GetEntryInfo(configId, entryId) if (not entryInfo.definitionID and entryInfo.subTreeID) then return entryInfo.subTreeID end end end end end end return 0 end ---return a table of passive talents, format: [spellId] = true ---@return {Name: string, ID: number, Texture: any, IsSelected: boolean}[] function DF:GetAllTalents() local allTalents = {} local configId = C_ClassTalents.GetActiveConfigID() if (configId) then local configInfo = C_Traits.GetConfigInfo(configId) --get the spells from the SPEC from talents for treeIndex, treeId in ipairs(configInfo.treeIDs) do local treeNodes = C_Traits.GetTreeNodes(treeId) for nodeIdIndex, treeNodeID in ipairs(treeNodes) do local traitNodeInfo = C_Traits.GetNodeInfo(configId, treeNodeID) if (traitNodeInfo) then local activeEntry = traitNodeInfo.activeEntry local entryIds = traitNodeInfo.entryIDs for i = 1, #entryIds do local entryId = entryIds[i] --number local traitEntryInfo = C_Traits.GetEntryInfo(configId, entryId) local borderTypes = Enum.TraitNodeEntryType if (traitEntryInfo.type) then -- == borderTypes.SpendCircle local definitionId = traitEntryInfo.definitionID if definitionId then local traitDefinitionInfo = C_Traits.GetDefinitionInfo(definitionId) local spellId = traitDefinitionInfo.overriddenSpellID or traitDefinitionInfo.spellID local spellName, _, spellTexture = GetSpellInfo(spellId) if (spellName) then local talentInfo = {Name = spellName, ID = spellId, Texture = spellTexture, IsSelected = (activeEntry and activeEntry.rank and activeEntry.rank > 0) or false} allTalents[#allTalents+1] = talentInfo end end end end end end end end return allTalents end ---return a table where keys are spellIds (number) and the value is true ---@return table function DF:GetAvailableSpells() local completeListOfSpells = {} --this line might not be compatible with classic local specId, specName, _, specIconTexture = GetSpecializationInfo(GetSpecialization()) --local classNameLoc, className, classId = UnitClass("player") --not in use local locPlayerRace, playerRace, playerRaceId = UnitRace("player") --get racials from the general tab local generalTabIndex = 1 local tabName, tabTexture, offset, numSpells, isGuild, offspecId = GetSpellTabInfo(generalTabIndex) offset = offset + 1 local tabEnd = offset + numSpells for entryOffset = offset, tabEnd - 1 do local spellType, spellId = GetSpellBookItemInfo(entryOffset, SPELLBOOK_BANK_PLAYER) local spellData = LIB_OPEN_RAID_COOLDOWNS_INFO[spellId] if (spellData) then local raceId = spellData.raceid if (raceId) then if (type(raceId) == "table") then if (raceId[playerRaceId]) then spellId = GetOverrideSpell(spellId) local spellName = GetSpellInfo(spellId) local bIsPassive = IsPassiveSpell(spellId, SPELLBOOK_BANK_PLAYER) if (spellName and not bIsPassive) then completeListOfSpells[spellId] = true end end elseif (type(raceId) == "number") then if (raceId == playerRaceId) then spellId = GetOverrideSpell(spellId) local spellName = GetSpellInfo(spellId) local bIsPassive = IsPassiveSpell(spellId, SPELLBOOK_BANK_PLAYER) if (spellName and not bIsPassive) then completeListOfSpells[spellId] = true end end end end end end local spellBookPlayerEnum = Enum.SpellBookSpellBank and Enum.SpellBookSpellBank.Player or "player" --get spells from the Spec spellbook for i = 1, GetNumSpellTabs() do --called "lines" in new v11 api local tabName, tabTexture, offset, numSpells, isGuild, offSpecId, shouldHide, specID = GetSpellTabInfo(i) if (tabTexture == specIconTexture) then offset = offset + 1 local tabEnd = offset + numSpells --local bIsOffSpec = offSpecId ~= 0 for entryOffset = offset, tabEnd - 1 do local spellType, spellId = GetSpellBookItemInfo(entryOffset, spellBookPlayerEnum) if (spellId) then if (spellType == "SPELL" or spellType == 1) then spellId = GetOverrideSpell(spellId) local spellName = GetSpellInfo(spellId) local bIsPassive = IsPassiveSpell(entryOffset, spellBookPlayerEnum) if (spellName and not bIsPassive) then completeListOfSpells[spellId] = true --bIsOffSpec == false end end end end end end local CONST_SPELLBOOK_CLASSSPELLS_TABID = 2 local CONST_SPELLBOOK_GENERAL_TABID = 1 --get class shared spells from the spell book --[= local tabName, tabTexture, offset, numSpells, isGuild, offSpecId = GetSpellTabInfo(CONST_SPELLBOOK_CLASSSPELLS_TABID) offset = offset + 1 local tabEnd = offset + numSpells --local bIsOffSpec = offSpecId ~= 0 for entryOffset = offset, tabEnd - 1 do local spellType, spellId = GetSpellBookItemInfo(entryOffset, spellBookPlayerEnum) if (spellId) then if (spellType == "SPELL" or spellType == 1) then spellId = GetOverrideSpell(spellId) local spellName = GetSpellInfo(spellId) local bIsPassive = IsPassiveSpell(spellId, spellBookPlayerEnum) if (spellName and not bIsPassive) then completeListOfSpells[spellId] = true --bIsOffSpec == false end end end end --]=] local getNumPetSpells = function() --'HasPetSpells' contradicts the name and return the amount of pet spells available instead of a boolean return HasPetSpells() end --get pet spells from the pet spellbook local numPetSpells = getNumPetSpells() if (numPetSpells) then for i = 1, numPetSpells do local spellName, _, unmaskedSpellId = GetSpellBookItemName(i, spellBookPetEnum) if (unmaskedSpellId) then unmaskedSpellId = GetOverrideSpell(unmaskedSpellId) local bIsPassive = IsPassiveSpell(i, spellBookPetEnum) if (spellName and not bIsPassive) then completeListOfSpells[unmaskedSpellId] = true end end end end return completeListOfSpells end ------------------------------------------------------------------------------------------------------------------------ --flash animation local onFinishFlashAnimation = function(self) if (self.showWhenDone) then self.frame:SetAlpha(1) else self.frame:SetAlpha(0) self.frame:Hide() end if (self.onFinishFunc) then self:onFinishFunc(self.frame) end end local stopAnimation_Method = function(self) local FlashAnimation = self.FlashAnimation FlashAnimation:Stop() end local startFlash_Method = function(self, fadeInTime, fadeOutTime, flashDuration, showWhenDone, flashInHoldTime, flashOutHoldTime, loopType) local flashAnimation = self.FlashAnimation local fadeIn = flashAnimation.fadeIn local fadeOut = flashAnimation.fadeOut fadeIn:Stop() fadeOut:Stop() fadeIn:SetDuration(fadeInTime or 1) fadeIn:SetEndDelay(flashInHoldTime or 0) fadeOut:SetDuration(fadeOutTime or 1) fadeOut:SetEndDelay(flashOutHoldTime or 0) flashAnimation.duration = flashDuration flashAnimation.loopTime = flashAnimation:GetDuration() flashAnimation.finishAt = GetTime() + flashDuration flashAnimation.showWhenDone = showWhenDone flashAnimation:SetLooping(loopType or "REPEAT") self:Show() self:SetAlpha(0) flashAnimation:Play() end ---create a flash animation for a frame ---@param frame table ---@param onFinishFunc function? ---@param onLoopFunc function? function DF:CreateFlashAnimation(frame, onFinishFunc, onLoopFunc) local flashAnimation = frame:CreateAnimationGroup() flashAnimation.fadeOut = flashAnimation:CreateAnimation("Alpha") flashAnimation.fadeOut:SetOrder(1) flashAnimation.fadeOut:SetFromAlpha(0) flashAnimation.fadeOut:SetToAlpha(1) flashAnimation.fadeIn = flashAnimation:CreateAnimation("Alpha") flashAnimation.fadeIn:SetOrder(2) flashAnimation.fadeIn:SetFromAlpha(1) flashAnimation.fadeIn:SetToAlpha(0) frame.FlashAnimation = flashAnimation flashAnimation.frame = frame flashAnimation.onFinishFunc = onFinishFunc flashAnimation:SetScript("OnLoop", onLoopFunc) flashAnimation:SetScript("OnFinished", onFinishFlashAnimation) frame.Flash = startFlash_Method frame.Stop = stopAnimation_Method return flashAnimation end local onStartPunchAnimation = function(animationGroup) local parent = animationGroup:GetParent() animationGroup.parentWidth = parent:GetWidth() animationGroup.parentHeight = parent:GetHeight() end local onStopPunchAnimation = function(animationGroup) local parent = animationGroup:GetParent() parent:SetWidth(animationGroup.parentWidth) parent:SetHeight(animationGroup.parentHeight) end function DF:CreatePunchAnimation(frame, scale) scale = scale or 1.1 scale = math.min(scale, 1.9) local animationHub = DF:CreateAnimationHub(frame, onStartPunchAnimation, onStopPunchAnimation) local scaleUp = DF:CreateAnimation(animationHub, "scale", 1, 0.05, 1, 1, scale, scale, "center", 0, 0) local scaleDown = DF:CreateAnimation(animationHub, "scale", 2, 0.05, 1, 1, 1-(scale - 1), 1-(scale - 1), "center", 0, 0) return animationHub end ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- --anchoring function DF:CheckPoints(point1, point2, point3, point4, point5, object) if (not point1 and not point2) then return "topleft", object.widget:GetParent(), "topleft", 0, 0 end if (type(point1) == "string") then local frameGlobal = _G[point1] if (frameGlobal and type(frameGlobal) == "table" and frameGlobal.GetObjectType) then return DF:CheckPoints(frameGlobal, point2, point3, point4, point5, object) end elseif (type(point2) == "string") then local frameGlobal = _G[point2] if (frameGlobal and type(frameGlobal) == "table" and frameGlobal.GetObjectType) then return DF:CheckPoints(point1, frameGlobal, point3, point4, point5, object) end end if (type(point1) == "string" and type(point2) == "table") then --setpoint("left", frame, _, _, _) if (not point3 or type(point3) == "number") then --setpoint("left", frame, 10, 10) point1, point2, point3, point4, point5 = point1, point2, point1, point3, point4 end elseif (type(point1) == "string" and type(point2) == "number") then --setpoint("topleft", x, y) point1, point2, point3, point4, point5 = point1, object.widget:GetParent(), point1, point2, point3 elseif (type(point1) == "number") then --setpoint(x, y) point1, point2, point3, point4, point5 = "topleft", object.widget:GetParent(), "topleft", point1, point2 elseif (type(point1) == "table") then --setpoint(frame, x, y) point1, point2, point3, point4, point5 = "topleft", point1, "topleft", point2, point3 end if (not point2) then point2 = object.widget:GetParent() elseif (point2.dframework) then point2 = point2.widget end 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 DF.AnchorPoints = { "Top Left", "Left", "Bottom Left", "Bottom", "Bottom Right", "Right", "Top Right", "Top", "Center", --9 "Inside Left", --10 "Inside Right", --11 "Inside Top", --12 "Inside Bottom", --13 "Inside Top Left", --14 "Inside Bottom Left", --15 "Inside Bottom Right", --16 "Inside Top Right", --17 } DF.AnchorPointsByIndex = { "topleft", --1 "left", --2 "bottomleft", --3 "bottom", --4 "bottomright", --5 "right", --6 "topright", --7 "top", --8 "center", --9 } DF.AnchorPointsToInside = { [9] = 9, [8] = 12, [7] = 17, [6] = 11, [5] = 16, [4] = 13, [3] = 15, [2] = 10, [1] = 14, } DF.InsidePointsToAnchor = { [9] = 9, [12] = 8, [17] = 7, [11] = 6, [16] = 5, [13] = 4, [15] = 3, [10] = 2, [14] = 1, } function DF:ConvertAnchorPointToInside(anchorPoint) return DF.AnchorPointsToInside[anchorPoint] or anchorPoint end local calcPointCoords = function(ninePointsWidget, ninePointsRef, anchorTable, coordIndex, newAnchorSide) --get the location of the topleft corner relative to the bottomleft corner of the screen ---@type df_coordinate local widgetPointCoords = ninePointsWidget[coordIndex] --get the topleft coords of the reference widget ---@type df_coordinate local refPointCoords = ninePointsRef[coordIndex] --calculate the offset of the x and y axis local x = refPointCoords.x - widgetPointCoords.x local y = refPointCoords.y - widgetPointCoords.y anchorTable.x = x anchorTable.y = y anchorTable.side = newAnchorSide print("new anchor side", newAnchorSide, "x", x, "y", y) end function DF:ConvertAnchorOffsets(widget, referenceWidget, anchorTable, newAnchorSide) if (anchorTable.side == newAnchorSide) then return anchorTable end local ninePoints = DF.Math.GetNinePoints(widget) local refNinePoints = DF.Math.GetNinePoints(referenceWidget) --the numeration from 1 to 9 is the index within a ninePoints table anchorTable.side = newAnchorSide if (newAnchorSide == 14) then --inside topleft anchorTable.x = ninePoints[1].x - refNinePoints[1].x anchorTable.y = ninePoints[1].y - refNinePoints[1].y --print("inside topleft", anchorTable.x, anchorTable.y) elseif (newAnchorSide == 15) then --inside bottomleft anchorTable.x = ninePoints[3].x - refNinePoints[3].x anchorTable.y = ninePoints[3].y - refNinePoints[3].y elseif (newAnchorSide == 16) then --inside bottomright anchorTable.x = refNinePoints[5].x - ninePoints[5].x anchorTable.y = refNinePoints[5].y - ninePoints[5].y elseif (newAnchorSide == 17) then --inside topright anchorTable.x = refNinePoints[7].x - ninePoints[7].x anchorTable.y = refNinePoints[7].y - ninePoints[7].y elseif (newAnchorSide == 10) then --inside left calcPointCoords(ninePoints, refNinePoints, anchorTable, 2, newAnchorSide) elseif (newAnchorSide == 11) then --inside right calcPointCoords(ninePoints, refNinePoints, anchorTable, 6, newAnchorSide) elseif (newAnchorSide == 12) then --inside top calcPointCoords(ninePoints, refNinePoints, anchorTable, 8, newAnchorSide) elseif (newAnchorSide == 13) then --inside bottom calcPointCoords(ninePoints, refNinePoints, anchorTable, 4, newAnchorSide) elseif (newAnchorSide == 9) then --center calcPointCoords(ninePoints, refNinePoints, anchorTable, 9, newAnchorSide) else --print("not implemented") end end local anchoringFunctions = { function(frame, anchorTo, offSetX, offSetY) --1 TOP LEFT frame:ClearAllPoints() frame:SetPoint("bottomleft", anchorTo, "topleft", offSetX, offSetY) end, function(frame, anchorTo, offSetX, offSetY) --2 LEFT frame:ClearAllPoints() frame:SetPoint("right", anchorTo, "left", offSetX, offSetY) end, function(frame, anchorTo, offSetX, offSetY) --3 BOTTOM LEFT frame:ClearAllPoints() frame:SetPoint("topleft", anchorTo, "bottomleft", offSetX, offSetY) end, function(frame, anchorTo, offSetX, offSetY) --4 BOTTOM frame:ClearAllPoints() frame:SetPoint("top", anchorTo, "bottom", offSetX, offSetY) end, function(frame, anchorTo, offSetX, offSetY) --5 BOTTOM RIGHT frame:ClearAllPoints() frame:SetPoint("topright", anchorTo, "bottomright", offSetX, offSetY) end, function(frame, anchorTo, offSetX, offSetY) --6 RIGHT frame:ClearAllPoints() frame:SetPoint("left", anchorTo, "right", offSetX, offSetY) end, function(frame, anchorTo, offSetX, offSetY) --7 TOP RIGHT frame:ClearAllPoints() frame:SetPoint("bottomright", anchorTo, "topright", offSetX, offSetY) end, function(frame, anchorTo, offSetX, offSetY) --8 TOP frame:ClearAllPoints() frame:SetPoint("bottom", anchorTo, "top", offSetX, offSetY) end, function(frame, anchorTo, offSetX, offSetY) --9 CENTER frame:ClearAllPoints() frame:SetPoint("center", anchorTo, "center", offSetX, offSetY) end, function(frame, anchorTo, offSetX, offSetY) --10 INSIDE LEFT frame:ClearAllPoints() frame:SetPoint("left", anchorTo, "left", offSetX, offSetY) end, function(frame, anchorTo, offSetX, offSetY) --11 INSIDE RIGHT frame:ClearAllPoints() frame:SetPoint("right", anchorTo, "right", offSetX, offSetY) end, function(frame, anchorTo, offSetX, offSetY) --12 INSIDE TOP frame:ClearAllPoints() frame:SetPoint("top", anchorTo, "top", offSetX, offSetY) end, function(frame, anchorTo, offSetX, offSetY) --13 INSIDE BOTTOM frame:ClearAllPoints() frame:SetPoint("bottom", anchorTo, "bottom", offSetX, offSetY) 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, } ---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[anchorTable.side](widget, anchorTo, anchorTable.x, anchorTable.y) end ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- --colors ---add a new color name, the color can be query using DetailsFramework:ParseColors(colorName) ---@param colorName string ---@param red number ---@param green number ---@param blue number ---@param alpha number? ---@return table function DF:NewColor(colorName, red, green, blue, alpha) assert(type(colorName) == "string", "DetailsFramework:NewColor(): colorName must be a string.") red, green, blue, alpha = DetailsFramework:ParseColors(red, green, blue, alpha) local colorTable = DetailsFramework:FormatColor("table", red, green, blue, alpha) DF.alias_text_colors[colorName] = colorTable return colorTable end local colorTableMixin = { GetColor = function(self) return self.r, self.g, self.b, self.a end, SetColor = function(self, r, g, b, a) r, g, b, a = DF:ParseColors(r, g, b, a) self.r = r or self.r self.g = g or self.g self.b = b or self.b self.a = a or self.a end, IsColorTable = true, } ---* takes in a color in one format and converts it to another specified format. ---* here are the parameters it accepts: ---* newFormat (string): The format to convert the color to. It can be one of the following: "commastring", "tablestring", "table", "tablemembers", "numbers", "hex". ---* r (number|string): The red component of the color or a string representing the color. ---* g (number|nil): The green component of the color. This is optional if r is a string. ---* b (number|nil): The blue component of the color. This is optional if r is a string. ---* a (number|nil): The alpha component of the color. This is optional and defaults to 1 if not provided. ---* decimalsAmount (number|nil): The number of decimal places to round the color components to. This is optional and defaults to 4 if not provided. ---* The function returns the color in the new format. The return type depends on the newFormat parameter. It can be a string, a table, or four separate number values (for the "numbers" format). ---* For the "hex" format, it returns a string representing the color in hexadecimal format. ---@param newFormat string ---@param r number|string ---@param g number|nil ---@param b number|nil ---@param a number|nil ---@param decimalsAmount number|nil ---@return string|table|number|nil ---@return number|nil ---@return number|nil ---@return number|nil function DF:FormatColor(newFormat, r, g, b, a, decimalsAmount) a = a or 1 r, g, b, a = DF:ParseColors(r, g, b, a) decimalsAmount = decimalsAmount or 4 r = DF:TruncateNumber(r, decimalsAmount) g = DF:TruncateNumber(g, decimalsAmount) b = DF:TruncateNumber(b, decimalsAmount) a = DF:TruncateNumber(a, decimalsAmount) if (newFormat == "commastring") then return r .. ", " .. g .. ", " .. b .. ", " .. a elseif (newFormat == "tablestring") then return "{" .. r .. ", " .. g .. ", " .. b .. ", " .. a .. "}" elseif (newFormat == "table") then return {r, g, b, a} elseif (newFormat == "tablemembers") then return {["r"] = r, ["g"] = g, ["b"] = b, ["a"] = a} elseif (newFormat == "numbers") then return r, g, b, a elseif (newFormat == "hex") then return format("%.2x%.2x%.2x%.2x", a * 255, r * 255, g * 255, b * 255) end end function DF:CreateColorTable(r, g, b, a) local t = { r = r or 1, g = g or 1, b = b or 1, a = a or 1, } DF:Mixin(t, colorTableMixin) return t end ---return true if DF.alias_text_colors has the colorName as a key ---DF.alias_text_colors is a table where key is a color name and value is an indexed table with the r g b values ---@param colorName any ---@return unknown function DF:IsHtmlColor(colorName) return DF.alias_text_colors[colorName] end ---return the brightness of a color from zero to one ---@param r number ---@param g number ---@param b number ---@return number function DF:GetColorBrightness(r, g, b) r, g, b = DF:ParseColors(r, g, b) return 0.2134 * r + 0.7152 * g + 0.0721 * b end ---return the hue of a color from red to blue to green to yellow and back to red ---@param r number ---@param g number ---@param b number ---@return number function DF:GetColorHue(r, g, b) r, g, b = DF:ParseColors(r, g, b) local minValue, maxValue = math.min(r, g, b), math.max(r, g, b) if (maxValue == minValue) then return 0 elseif (maxValue == r) then return (g - b) / (maxValue - minValue) % 6 elseif (maxValue == g) then return (b - r) / (maxValue - minValue) + 2 else return (r - g) / (maxValue - minValue) + 4 end end ---get the values passed and return r g b a color values ---the function accept color name, tables with r g b a members, indexed tables with r g b a values, numbers, html hex color ---@param red any ---@param green any ---@param blue any ---@param alpha any ---@return number ---@return number ---@return number ---@return number function DF:ParseColors(red, green, blue, alpha) local firstParameter = red --the first value passed is a table? if (type(firstParameter) == "table") then local colorTable = red if (colorTable.IsColorTable) then --using colorTable mixin return colorTable:GetColor() elseif (not colorTable[1] and colorTable.r) then --{["r"] = 1, ["g"] = 1, ["b"] = 1} red, green, blue, alpha = colorTable.r, colorTable.g, colorTable.b, colorTable.a else --{1, .7, .2, 1} red, green, blue, alpha = unpack(colorTable) end --the first value passed is a string? elseif (type(firstParameter) == "string") then local colorString = red --hexadecimal if (string.find(colorString, "#")) then colorString = colorString:gsub("#","") if (string.len(colorString) == 8) then --with alpha red, green, blue, alpha = tonumber("0x" .. colorString:sub(3, 4))/255, tonumber("0x" .. colorString:sub(5, 6))/255, tonumber("0x" .. colorString:sub(7, 8))/255, tonumber("0x" .. colorString:sub(1, 2))/255 else red, green, blue, alpha = tonumber("0x" .. colorString:sub(1, 2))/255, tonumber("0x" .. colorString:sub(3, 4))/255, tonumber("0x" .. colorString:sub(5, 6))/255, 1 end else --name of the color local colorTable = DF.alias_text_colors[colorString] if (colorTable) then red, green, blue, alpha = unpack(colorTable) --string with number separated by comma elseif (colorString:find(",")) then local r, g, b, a = strsplit(",", colorString) red, green, blue, alpha = tonumber(r), tonumber(g), tonumber(b), tonumber(a) else --no color found within the string, return default color red, green, blue, alpha = unpack(DF.alias_text_colors.none) end end end if (not red or type(red) ~= "number") then red = 1 end if (not green) or type(green) ~= "number" then green = 1 end if (not blue or type(blue) ~= "number") then blue = 1 end if (not alpha or type(alpha) ~= "number") then alpha = 1 end --saturate the values before returning to make sure they are on the 0 to 1 range return Saturate(red), Saturate(green), Saturate(blue), Saturate(alpha) end ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- --tutorials function DF:ShowTutorialAlertFrame(maintext, desctext, clickfunc) local TutorialAlertFrame = _G.DetailsFrameworkAlertFrame if (not TutorialAlertFrame) then TutorialAlertFrame = CreateFrame("frame", "DetailsFrameworkAlertFrame", UIParent, "MicroButtonAlertTemplate") TutorialAlertFrame.isFirst = true TutorialAlertFrame:SetPoint("left", UIParent, "left", -20, 100) TutorialAlertFrame:SetFrameStrata("TOOLTIP") TutorialAlertFrame:Hide() TutorialAlertFrame:SetScript("OnMouseUp", function(self) if (self.clickfunc and type(self.clickfunc) == "function") then self.clickfunc() end self:Hide() end) TutorialAlertFrame:Hide() end -- TutorialAlertFrame.label = type(maintext) == "string" and maintext or type(desctext) == "string" and desctext or "" MicroButtonAlert_SetText (TutorialAlertFrame, alert.label) -- TutorialAlertFrame.clickfunc = clickfunc TutorialAlertFrame:Show() end function DF:CreateOptionsFrame(name, title, template) --deprecated? template = template or 1 if (template == 2) then local newOptionsFrame = CreateFrame("frame", name, UIParent, "ButtonFrameTemplate") table.insert(UISpecialFrames, name) newOptionsFrame:SetSize(500, 200) newOptionsFrame.RefreshOptions = DF.internalFunctions.RefreshOptionsPanel newOptionsFrame.widget_list = {} newOptionsFrame:SetScript("OnMouseDown", function(self, button) if (button == "RightButton") then if (self.moving) then self.moving = false self:StopMovingOrSizing() end return newOptionsFrame:Hide() elseif (button == "LeftButton" and not self.moving) then self.moving = true self:StartMoving() end end) newOptionsFrame:SetScript("OnMouseUp", function(self) if (self.moving) then self.moving = false self:StopMovingOrSizing() end end) newOptionsFrame:SetMovable(true) newOptionsFrame:EnableMouse(true) newOptionsFrame:SetFrameStrata("DIALOG") newOptionsFrame:SetToplevel(true) newOptionsFrame:Hide() newOptionsFrame:SetPoint("center", UIParent, "center") newOptionsFrame.TitleText:SetText(title) return newOptionsFrame elseif (template == 1) then local newOptionsFrame = CreateFrame("frame", name, UIParent) table.insert(UISpecialFrames, name) newOptionsFrame:SetSize(500, 200) newOptionsFrame.RefreshOptions = DF.internalFunctions.RefreshOptionsPanel newOptionsFrame.widget_list = {} newOptionsFrame:SetScript("OnMouseDown", function(self, button) if (button == "RightButton") then if (self.moving) then self.moving = false self:StopMovingOrSizing() end return newOptionsFrame:Hide() elseif (button == "LeftButton" and not self.moving) then self.moving = true self:StartMoving() end end) newOptionsFrame:SetScript("OnMouseUp", function(self) if (self.moving) then self.moving = false self:StopMovingOrSizing() end end) newOptionsFrame:SetMovable(true) newOptionsFrame:EnableMouse(true) newOptionsFrame:SetFrameStrata("DIALOG") newOptionsFrame:SetToplevel(true) newOptionsFrame:Hide() newOptionsFrame:SetPoint("center", UIParent, "center") newOptionsFrame:SetBackdrop({bgFile = "Interface\\Tooltips\\UI-Tooltip-Background", tile = true, tileSize = 16, edgeFile = [[Interface\Buttons\WHITE8X8]], edgeSize = 1, insets = {left = 1, right = 1, top = 1, bottom = 1}}) newOptionsFrame:SetBackdropColor(0, 0, 0, .7) local textureTitle = newOptionsFrame:CreateTexture(nil, "artwork") textureTitle:SetTexture([[Interface\CURSOR\Interact]]) textureTitle:SetTexCoord(0, 1, 0, 1) textureTitle:SetVertexColor(1, 1, 1, 1) textureTitle:SetPoint("topleft", newOptionsFrame, "topleft", 2, -3) textureTitle:SetWidth(36) textureTitle:SetHeight(36) local titleLabel = DF:NewLabel(newOptionsFrame, nil, "$parentTitle", nil, title, nil, 20, "yellow") titleLabel:SetPoint("left", textureTitle, "right", 2, -1) DF:SetFontOutline (titleLabel, true) local closeButton = CreateFrame("Button", nil, newOptionsFrame, "UIPanelCloseButton") closeButton:SetWidth(32) closeButton:SetHeight(32) closeButton:SetPoint("TOPRIGHT", newOptionsFrame, "TOPRIGHT", -3, -3) closeButton:SetFrameLevel(newOptionsFrame:GetFrameLevel()+1) return newOptionsFrame end end ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ --~templates local latinLanguageIds = {"enUS", "deDE", "esES", "esMX", "frFR", "itIT", "ptBR"} local latinLanguageIdsMap = { ["enUS"] = true, ["deDE"] = true, ["esES"] = true, ["esMX"] = true, ["frFR"] = true, ["itIT"] = true, ["ptBR"] = true, } local alphbets = { [latinLanguageIds] = {"A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z", "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z"}, ["zhCN"] = {}, } --fonts DF.font_templates = DF.font_templates or {} --detect which language is the client and select the font accordingly local clientLanguage = GetLocale() if (clientLanguage == "enGB") then clientLanguage = "enUS" end DF.ClientLanguage = clientLanguage ---returns which region the language the client is running, return "western", "russia" or "asia" ---@return string function DF:GetClientRegion() if (clientLanguage == "zhCN" or clientLanguage == "koKR" or clientLanguage == "zhTW") then return "asia" elseif (clientLanguage == "ruRU") then return "russia" else return "western" end end DF.registeredFontPaths = DF.registeredFontPaths or {} -- ~language ~locale ~fontpath ---get a font path to be used for a specific language ---@param languageId string enUS, deDE, esES, esMX, frFR, itIT, ptBR, ruRU, zhCN, zhTW, koKR ---@return string function DF:GetBestFontPathForLanguage(languageId) local fontPath = DF.registeredFontPaths[languageId] if (fontPath) then return fontPath end --font paths gotten from creating a FontString with template "GameFontNormal" and getting the font returned from FontString:GetFont() if (languageId == "enUS" or languageId == "deDE" or languageId == "esES" or languageId == "esMX" or languageId == "frFR" or languageId == "itIT" or languageId == "ptBR") then return [[Fonts\FRIZQT__.TTF]] elseif (languageId == "ruRU") then return [[Fonts\FRIZQT___CYR.TTF]] elseif (languageId == "zhCN") then return [[Fonts\ARKai_T.ttf]] elseif (languageId == "zhTW") then return [[Fonts\blei00d.TTF]] elseif (languageId == "koKR") then return [[Fonts\2002.TTF]] end --the locale passed doesn't exists, so pass the enUS return [[Fonts\FRIZQT__.TTF]] end ---return true if the language paren is latin: enUS, deDE, esES, esMX, frFR, itIT, ptBR ---@param languageId string ---@return boolean function DF:IsLatinLanguage(languageId) return latinLanguageIdsMap[languageId] end ---return a font name to use for the client language ---@param self table ---@param languageId string? ---@param western string? ---@param cyrillic string? ---@param china string? ---@param korean string? ---@param taiwan string? function DF:GetBestFontForLanguage(languageId, western, cyrillic, china, korean, taiwan) if (not languageId) then languageId = DF.ClientLanguage end if (languageId == "enUS" or languageId == "deDE" or languageId == "esES" or languageId == "esMX" or languageId == "frFR" or languageId == "itIT" or languageId == "ptBR") then return western or "Friz Quadrata TT" elseif (languageId == "ruRU") then return cyrillic or "Friz Quadrata TT" elseif (languageId == "zhCN") then return china or "AR CrystalzcuheiGBK Demibold" elseif (languageId == "koKR") then return korean or "2002" elseif (languageId == "zhTW") then return taiwan or "AR CrystalzcuheiGBK Demibold" end end local templateOnEnter = function(frame) if (frame.onenter_backdrop) then local r, g, b, a = detailsFramework:ParseColors(frame.onenter_backdrop) frame:SetBackdropColor(r, g, b, a) end if (frame.onenter_backdrop_border_color) then local r, g, b, a = detailsFramework:ParseColors(frame.onenter_backdrop_border_color) frame:SetBackdropBorderColor(r, g, b, a) end end local templateOnLeave = function(frame) if (frame.onleave_backdrop) then local r, g, b, a = detailsFramework:ParseColors(frame.onleave_backdrop) frame:SetBackdropColor(r, g, b, a) end if (frame.onleave_backdrop_border_color) then local r, g, b, a = detailsFramework:ParseColors(frame.onleave_backdrop_border_color) frame:SetBackdropBorderColor(r, g, b, a) end end DF.TemplateOnEnter = templateOnEnter DF.TemplateOnLeave = templateOnLeave ---set a details framework template into a regular frame ---@param self table ---@param frame uiobject ---@param template string function detailsFramework:SetTemplate(frame, template) template = detailsFramework:ParseTemplate("button", template) if (frame.SetWidth) then if (template.width) then PixelUtil.SetWidth(frame, template.width) end if (template.height) then PixelUtil.SetHeight(frame, template.height) end end if (frame.SetBackdrop) then if (template.backdrop) then frame:SetBackdrop(template.backdrop) end if (template.backdropcolor) then local r, g, b, a = detailsFramework:ParseColors(template.backdropcolor) frame:SetBackdropColor(r, g, b, a) frame.onleave_backdrop = {r, g, b, a} end if (template.backdropbordercolor) then local r, g, b, a = detailsFramework:ParseColors(template.backdropbordercolor) frame:SetBackdropBorderColor(r, g, b, a) frame.onleave_backdrop_border_color = {r, g, b, a} end if (template.onentercolor) then local r, g, b, a = detailsFramework:ParseColors(template.onentercolor) frame.onenter_backdrop = {r, g, b, a} frame:HookScript("OnEnter", templateOnEnter) frame.__has_onentercolor_script = true end if (template.onleavecolor) then local r, g, b, a = detailsFramework:ParseColors(template.onleavecolor) frame.onleave_backdrop = {r, g, b, a} frame:HookScript("OnLeave", templateOnLeave) frame.__has_onleavecolor_script = true end if (template.onenterbordercolor) then local r, g, b, a = detailsFramework:ParseColors(template.onenterbordercolor) frame.onenter_backdrop_border_color = {r, g, b, a} if (not frame.__has_onentercolor_script) then frame:HookScript("OnEnter", templateOnEnter) end end if (template.onleavebordercolor) then local r, g, b, a = detailsFramework:ParseColors(template.onleavebordercolor) frame.onleave_backdrop_border_color = {r, g, b, a} if (not frame.__has_onleavecolor_script) then frame:HookScript("OnLeave", templateOnLeave) end end elseif (frame.SetColorTexture) then if (template.backdropcolor) then local r, g, b, a = detailsFramework:ParseColors(template.backdropcolor) frame:SetColorTexture(r, g, b, a) end end if (frame.SetIcon) then if (template.icon) then local iconInfo = template.icon frame:SetIcon(iconInfo.texture, iconInfo.width, iconInfo.height, iconInfo.layout, iconInfo.texcoord, iconInfo.color, iconInfo.textdistance, iconInfo.leftpadding) end end if (frame.SetTextColor) then if (template.textsize) then detailsFramework:SetFontSize(frame, template.textsize) end if (template.textfont) then detailsFramework:SetFontFace(frame, template.textfont) end if (template.textcolor) then detailsFramework:SetFontColor(frame, template.textcolor) end --horizontal alignment if (template.textalign and frame.SetJustifyH) then template.textalign = string.lower(template.textalign) if (template.textalign == "left" or template.textalign == "<") then frame:SetJustifyH("LEFT") elseif (template.textalign == "center" or template.textalign == "|") then frame:SetJustifyH("CENTER") elseif (template.textalign == "right" or template.textalign == ">") then frame:SetJustifyH("RIGHT") end end end end --DF.font_templates ["ORANGE_FONT_TEMPLATE"] = {color = "orange", size = 11, font = "Accidental Presidency"} --DF.font_templates ["OPTIONS_FONT_TEMPLATE"] = {color = "yellow", size = 12, font = "Accidental Presidency"} --DF.font_templates["ORANGE_FONT_TEMPLATE"] = {color = "orange", size = 10, font = DF:GetBestFontForLanguage()} DF.font_templates["ORANGE_FONT_TEMPLATE"] = {color = {1, 0.8235, 0, 1}, size = 11, font = DF:GetBestFontForLanguage()} --DF.font_templates["OPTIONS_FONT_TEMPLATE"] = {color = "yellow", size = 9.6, font = DF:GetBestFontForLanguage()} DF.font_templates["OPTIONS_FONT_TEMPLATE"] = {color = {1, 1, 1, 0.9}, size = 9.6, font = DF:GetBestFontForLanguage()} DF.font_templates["SMALL_SILVER"] = {color = "silver", size = 9, font = DF:GetBestFontForLanguage()} --~templates --dropdowns DF.dropdown_templates = DF.dropdown_templates or {} DF.dropdown_templates["OPTIONS_DROPDOWN_TEMPLATE"] = { backdrop = { edgeFile = [[Interface\Buttons\WHITE8X8]], edgeSize = 1, bgFile = [[Interface\Tooltips\UI-Tooltip-Background]], tileSize = 64, tile = true }, --backdropcolor = {0.1, 0.1, 0.1, .7}, backdropcolor = {0.2, 0.2, 0.2, .7}, onentercolor = {0.3, 0.3, 0.3, .7}, backdropbordercolor = {0, 0, 0, .4}, onenterbordercolor = {0.3, 0.3, 0.3, 0.8}, dropicon = "Interface\\BUTTONS\\arrow-Down-Down", dropiconsize = {16, 16}, dropiconpoints = {-2, -3}, } DF.dropdown_templates["OPTIONS_DROPDOWNDARK_TEMPLATE"] = { backdrop = { edgeFile = [[Interface\Buttons\WHITE8X8]], edgeSize = 1, bgFile = [[Interface\Tooltips\UI-Tooltip-Background]], tileSize = 64, tile = true }, backdropcolor = {0.1215, 0.1176, 0.1294, 0.8000}, backdropbordercolor = {.2, .2, .2, 1}, onentercolor = {.5, .5, .5, .9}, onenterbordercolor = {.4, .4, .4, 1}, dropicon = "Interface\\BUTTONS\\arrow-Down-Down", dropiconsize = {16, 16}, dropiconpoints = {-2, -3}, } DF.dropdown_templates["OLD_DROPDOWN_TEMPLATE"] = { height = 24, backdrop = { edgeFile = "Interface\\Buttons\\UI-SliderBar-Border", edgeSize = 8, bgFile = [[Interface\Tooltips\UI-Tooltip-Background]], tileSize = 64, tile = true, insets = {left = 4, right = 4, top = 4, bottom = 4} }, backdropcolor = {0.1215, 0.1176, 0.1294, 0.4000}, backdropbordercolor = {1, 1, 1, 1}, onentercolor = {.5, .5, .5, .9}, onenterbordercolor = {1, 1, 1, 1}, dropicon = "Interface\\BUTTONS\\arrow-Down-Down", dropiconsize = {16, 16}, dropiconpoints = {-2, -3}, } --switches DF.switch_templates = DF.switch_templates or {} DF.switch_templates["OPTIONS_CHECKBOX_TEMPLATE"] = { backdrop = {edgeFile = [[Interface\Buttons\WHITE8X8]], edgeSize = 1, bgFile = [[Interface\Tooltips\UI-Tooltip-Background]], tileSize = 64, tile = true}, backdropcolor = {1, 1, 1, .5}, backdropbordercolor = {0, 0, 0, 1}, width = 18, height = 18, enabled_backdropcolor = {1, 1, 1, .5}, disabled_backdropcolor = {1, 1, 1, .2}, onenterbordercolor = {1, 1, 1, 1}, } DF.switch_templates["OPTIONS_CIRCLECHECKBOX_TEMPLATE"] = { width = 18, height = 18, is_checkbox = true, --will call SetAsCheckBox() checked_texture = [[Interface\CHARACTERFRAME\TempPortraitAlphaMaskSmall]], checked_size_percent = 0.7, checked_xoffset = 0, checked_yoffset = 0, checked_color = "dark3", rounded_corner = { color = {.075, .075, .075, 1}, border_color = {.2, .2, .2, 1}, roundness = 8, }, } DF.switch_templates["OPTIONS_CHECKBOX_BRIGHT_TEMPLATE"] = { backdrop = {edgeFile = [[Interface\Buttons\WHITE8X8]], edgeSize = 1, bgFile = [[Interface\Tooltips\UI-Tooltip-Background]], tileSize = 64, tile = true}, backdropcolor = {1, 1, 1, .5}, backdropbordercolor = {0, 0, 0, 1}, width = 18, height = 18, enabled_backdropcolor = {1, 1, 1, .5}, disabled_backdropcolor = {1, 1, 1, .5}, onenterbordercolor = {1, 1, 1, 1}, } --buttons DF.button_templates = DF.button_templates or {} DF.button_templates["OPTIONS_BUTTON_TEMPLATE"] = { backdrop = {edgeFile = [[Interface\Buttons\WHITE8X8]], edgeSize = 1, bgFile = [[Interface\Tooltips\UI-Tooltip-Background]], tileSize = 64, tile = true}, backdropcolor = {1, 1, 1, .5}, backdropbordercolor = {0, 0, 0, 1}, } DF.button_templates["OPTIONS_CIRCLEBUTTON_TEMPLATE"] = { rounded_corner = { color = {.075, .075, .075, 1}, border_color = {.2, .2, .2, 1}, roundness = 8, }, } DF.button_templates["OPTIONS_BUTTON_GOLDENBORDER_TEMPLATE"] = { backdrop = {edgeFile = [[Interface\Buttons\WHITE8X8]], edgeSize = 1, bgFile = [[Interface\Tooltips\UI-Tooltip-Background]], tileSize = 64, tile = true}, backdropcolor = {1, 1, 1, .5}, backdropbordercolor = {1, 0.785, 0, 1}, } DF.button_templates["STANDARD_GRAY"] = { backdrop = {edgeFile = [[Interface\Buttons\WHITE8X8]], edgeSize = 1, bgFile = [[Interface\Tooltips\UI-Tooltip-Background]], tileSize = 64, tile = true}, backdropcolor = {0.2, 0.2, 0.2, 0.502}, backdropbordercolor = {0, 0, 0, 0.5}, onentercolor = {0.4, 0.4, 0.4, 0.502}, } DF.button_templates["OPAQUE_DARK"] = { backdrop = {edgeFile = [[Interface\Buttons\WHITE8X8]], edgeSize = 1, bgFile = [[Interface\Buttons\WHITE8X8]], tileSize = 8, tile = true}, backdropcolor = {0.2, 0.2, 0.2, 1}, backdropbordercolor = {0, 0, 0, 1}, onentercolor = {0.4, 0.4, 0.4, 1}, } --sliders DF.slider_templates = DF.slider_templates or {} DF.slider_templates["OPTIONS_SLIDER_TEMPLATE"] = { backdrop = {edgeFile = [[Interface\Buttons\WHITE8X8]], edgeSize = 1, bgFile = [[Interface\Tooltips\UI-Tooltip-Background]], tileSize = 64, tile = true}, --original color wow10: --backdropcolor = {1, 1, 1, .5}, --backdropbordercolor = {0, 0, 0, 1}, --onentercolor = {1, 1, 1, .5}, --onenterbordercolor = {1, 1, 1, 1}, backdropcolor = {0.2, 0.2, 0.2, .7}, onentercolor = {0.3, 0.3, 0.3, .7}, backdropbordercolor = {0, 0, 0, .4}, --0.7 original alpha wow10 onenterbordercolor = {0.3, 0.3, 0.3, 0.8}, thumbtexture = [[Interface\Tooltips\UI-Tooltip-Background]], thumbwidth = 16, thumbheight = 14, --thumbcolor = {0, 0, 0, 0.5}, thumbcolor = {.8, .8, .8, 0.5}, } DF.slider_templates["OPTIONS_SLIDERDARK_TEMPLATE"] = { backdrop = {edgeFile = [[Interface\Buttons\WHITE8X8]], edgeSize = 1, bgFile = [[Interface\Tooltips\UI-Tooltip-Background]], tileSize = 64, tile = true}, backdropcolor = {0.05, 0.05, 0.05, .7}, onentercolor = {0.3, 0.3, 0.3, .7}, backdropbordercolor = {0, 0, 0, 1}, onenterbordercolor = {0, 0, 0, 1}, thumbtexture = [[Interface\Tooltips\UI-Tooltip-Background]], thumbwidth = 24, thumbheight = 14, thumbcolor = {.8, .8, .8, 0.5}, } DF.slider_templates["MODERN_SLIDER_TEMPLATE"] = { thumbtexture = "Minimal_SliderBar_Button", --atlas name thumbwidth = 20, thumbheight = 19, thumbcolor = {1, 1, 1, 0.924}, slider_left = "Minimal_SliderBar_Left", slider_right = "Minimal_SliderBar_Right", slider_middle = "_Minimal_SliderBar_Middle", amount_color = "white", amount_size = 12, amount_outline = "outline", } local templateTables = {DF.dropdown_templates, DF.button_templates, DF.switch_templates, DF.slider_templates, DF.font_templates} ---template categories: "font", "dropdown", "button", "switch", "slider" ---receives a template category and a template name or table ---if a template name has been passed, the function will iterate over all template tables to find a template with the name passed ---@param self table ---@param templateCategory templatecategory ---@param template string|table ---@return table function DF:ParseTemplate(templateCategory, template) if (type(template) == "string") then local objectType = templateCategory if (objectType == "label") then templateCategory = "font" elseif (objectType == "dropdown" or objectType == "textentry") then templateCategory = "dropdown" elseif (objectType == "button") then templateCategory = "button" elseif (objectType == "switch") then templateCategory = "switch" elseif (objectType == "slider") then templateCategory = "slider" end local templateTable = DF:GetTemplate(templateCategory, template) if (templateTable) then return templateTable end --iterate over all template tables to find a template with the name passed for i = 1, #templateTables do local tTable = templateTables[i] if (tTable[template]) then return tTable[template] end end else return template end ---@cast template table return template end ---register a new template to be used with SetTemplate calls ---@param templateCategory templatecategory ---@param templateName string ---@param template table ---@param parentName any ---@return table function DF:InstallTemplate(templateCategory, templateName, template, parentName) local newTemplate = {} --if has a parent, just copy the parent to the new template if (parentName and type(parentName) == "string") then local parentTemplate = DF:GetTemplate(templateCategory, parentName) if (parentTemplate) then DF.table.copy(newTemplate, parentTemplate) end end --copy the template passed into the new template DF.table.copy(newTemplate, template) templateCategory = string.lower(templateCategory) local templateTable if (templateCategory == "font") then templateTable = DF.font_templates local font = template.font if (font) then --fonts passed into the template has default to western --the framework will get the game client language and change the font if needed font = DF:GetBestFontForLanguage(nil, font) end elseif (templateCategory == "dropdown") then templateTable = DF.dropdown_templates elseif (templateCategory == "button") then templateTable = DF.button_templates elseif (templateCategory == "switch") then templateTable = DF.switch_templates elseif (templateCategory == "slider") then templateTable = DF.slider_templates end templateTable[templateName] = newTemplate return newTemplate end function DF:GetTemplate(widgetType, templateName) widgetType = string.lower(widgetType) local templateTable if (widgetType == "font") then templateTable = DF.font_templates elseif (widgetType == "dropdown") then templateTable = DF.dropdown_templates elseif (widgetType == "button") then templateTable = DF.button_templates elseif (widgetType == "switch") then templateTable = DF.switch_templates elseif (widgetType == "slider") then templateTable = DF.slider_templates end return templateTable[templateName] end ---get the name of the parent of the passed frame ---@param frame frame ---@return string function DF:GetParentName(frame) local parentName = frame:GetName() if (not parentName) then error("Details! FrameWork: called $parent but parent was no name.", 2) end return parentName end function DF:Error(errortext) print("|cFFFF2222Details! Framework Error|r:", errortext, self.GetName and self:GetName(), self.WidgetType, debugstack (2, 3, 0)) end ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ --members DF.GlobalWidgetControlNames = { textentry = "DF_TextEntryMetaFunctions", button = "DF_ButtonMetaFunctions", panel = "DF_PanelMetaFunctions", dropdown = "DF_DropdownMetaFunctions", label = "DF_LabelMetaFunctions", normal_bar = "DF_NormalBarMetaFunctions", image = "DF_ImageMetaFunctions", slider = "DF_SliderMetaFunctions", split_bar = "DF_SplitBarMetaFunctions", aura_tracker = "DF_AuraTracker", healthBar = "DF_healthBarMetaFunctions", timebar = "DF_TimeBarMetaFunctions", } function DF:AddMemberForWidget(widgetName, memberType, memberName, func) if (DF.GlobalWidgetControlNames[widgetName]) then if (type(memberName) == "string" and (memberType == "SET" or memberType == "GET")) then if (func) then local widgetControlObject = _G [DF.GlobalWidgetControlNames[widgetName]] if (memberType == "SET") then widgetControlObject["SetMembers"][memberName] = func elseif (memberType == "GET") then widgetControlObject["GetMembers"][memberName] = func end else if (DF.debug) then error("Details! Framework: AddMemberForWidget invalid function.") end end else if (DF.debug) then error("Details! Framework: AddMemberForWidget unknown memberName or memberType.") end end else if (DF.debug) then error("Details! Framework: AddMemberForWidget unknown widget type: " .. (widgetName or "") .. ".") end end end ----------------------------- function DF:OpenInterfaceProfile() -- OptionsFrame1/2 should be registered if created with DF:CreateAddOn, so open to them directly if self.OptionsFrame1 then if SettingsPanel then --SettingsPanel:OpenToCategory(self.OptionsFrame1.name) local category = SettingsPanel:GetCategoryList():GetCategory(self.OptionsFrame1.name) if category then SettingsPanel:Open() SettingsPanel:SelectCategory(category) if self.OptionsFrame2 and category:HasSubcategories() then for _, subcategory in pairs(category:GetSubcategories()) do if subcategory:GetName() == self.OptionsFrame2.name then SettingsPanel:SelectCategory(subcategory) break end end end end return elseif InterfaceOptionsFrame_OpenToCategory then InterfaceOptionsFrame_OpenToCategory (self.OptionsFrame1) if self.OptionsFrame2 then InterfaceOptionsFrame_OpenToCategory (self.OptionsFrame2) end return end end -- fallback (broken as of ElvUI Skins in version 12.18+... maybe fix/change will come) InterfaceOptionsFrame_OpenToCategory (self.__name) InterfaceOptionsFrame_OpenToCategory (self.__name) for i = 1, 100 do local button = _G ["InterfaceOptionsFrameAddOnsButton" .. i] if (button) then local text = _G ["InterfaceOptionsFrameAddOnsButton" .. i .. "Text"] if (text) then text = text:GetText() if (text == self.__name) then local toggle = _G ["InterfaceOptionsFrameAddOnsButton" .. i .. "Toggle"] if (toggle) then if (toggle:GetNormalTexture():GetTexture():find("PlusButton")) then --is minimized, need expand toggle:Click() _G ["InterfaceOptionsFrameAddOnsButton" .. i+1]:Click() elseif (toggle:GetNormalTexture():GetTexture():find("MinusButton")) then --isn't minimized _G ["InterfaceOptionsFrameAddOnsButton" .. i+1]:Click() end end break end end else self:Msg("Couldn't not find the profile panel.") break end end end ----------------------------- ---copy all members from #2 ... to #1 object ---@param object table ---@param ... any ---@return any function DF:Mixin(object, ...) --safe copy from blizz api for i = 1, select("#", ...) do local mixin = select(i, ...) for key, value in pairs(mixin) do object[key] = value end end return object end ----------------------------- --animations ---create an animation 'hub' which is an animationGroup but with some extra functions --tags: create, animation, hub, group, animationgroup, createanimationhub --prompt example: create an animation group for the object 'variable name' with the start animation function doing 'what to do' and the finish animation function doing 'what to do' ---@param parent uiobject ---@param onPlay function? ---@param onFinished function? ---@return animationgroup function DF:CreateAnimationHub(parent, onPlay, onFinished) local newAnimation = parent:CreateAnimationGroup() newAnimation:SetScript("OnPlay", onPlay) newAnimation:SetScript("OnFinished", onFinished) newAnimation:SetScript("OnStop", onFinished) newAnimation.NextAnimation = 1 return newAnimation end ---animation descriptions: --tags: animation, create, alpha, scale, translation, rotation, path, vertexcolor, color, animation type, animation duration, animation order, animation object, return variable --prompt example: create a new animation of type 'alpha' for the animation group 'variable name', with an order of 'number', a duration of 'number', from alpha 'number' to alpha 'number' ---* Create a new animation for an animation hub created with CreateAnimationHub(). ---* Alpha: CreateAnimation(animGroup, "Alpha", order, duration, fromAlpha, toAlpha). ---* Scale: CreateAnimation(animGroup, "Scale", order, duration, fromScaleX, fromScaleY, toScaleX, toScaleY, originPoint, x, y). ---* Translation: CreateAnimation(animGroup, "Translation", order, duration, xOffset, yOffset). ---* Rotation: CreateAnimation(animGroup, "Rotation", order, duration, degrees, originPoint, x, y). ---* Path: CreateAnimation(animGroup, "Path", order, duration, xOffset, yOffset, curveType). ---* VertexColor: CreateAnimation(animGroup, "VertexColor", order, duration, r1, g1, b1, a1, r2, g2, b2, a2). ---@param animationGroup animationgroup the animation group created with CreateAnimationHub() ---@param animationType animationtype "Alpha", "Scale", "Translation", "Rotation", "Path", "VertexColor" ---@param order number the order of the animation, the lower the number, the earlier the animation will play ---@param duration number the duration of the animation in seconds ---@param arg1 any for Alpha: fromAlpha, for Scale: fromScaleX, for Translation: xOffset, for Rotation: degrees, for Path: xOffset, for VertexColor: r1 ---@param arg2 any for Alpha: toAlpha, for Scale: fromScaleY, for Translation: yOffset, for Rotation: originPoint, for Path: yOffset, for VertexColor: g1 ---@param arg3 any for Scale: toScaleX, for VertexColor: blue1, for Rotation: originXOffset, for Path: curveType, for VertexColor: b1 ---@param arg4 any for Scale: toScaleY, for VertexColor: a1, for Rotation: originYOffset, for VertexColor: a1 ---@param arg5 any for Scale: originPoint, for VertexColor: r2 ---@param arg6 any for Scale: originXOffset, for VertexColor: g2 ---@param arg7 any for Scale: originYOffset, for VertexColor: b2 ---@param arg8 any for VertexColor: a2 ---@return animation function DF:CreateAnimation(animationGroup, animationType, order, duration, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8) ---@type animation local anim = animationGroup:CreateAnimation(animationType) --set the order of the animation, the 'order' parameter isn't passed, it will use the NextAnimation property of the animationGroup anim:SetOrder(order or animationGroup.NextAnimation) --set the duration of the animation anim:SetDuration(duration) animationType = string.upper(animationType) if (animationType == "ALPHA") then anim:SetFromAlpha(arg1) anim:SetToAlpha(arg2) elseif (animationType == "SCALE") then if (DF.IsDragonflight() or DF.IsNonRetailWowWithRetailAPI() or DF.IsWarWow()) then anim:SetScaleFrom(arg1, arg2) anim:SetScaleTo(arg3, arg4) else anim:SetFromScale(arg1, arg2) anim:SetToScale(arg3, arg4) end anim:SetOrigin(arg5 or "center", arg6 or 0, arg7 or 0) --point, originXOffset, originYOffset elseif (animationType == "ROTATION") then anim:SetDegrees(arg1) --degree anim:SetOrigin(arg2 or "center", arg3 or 0, arg4 or 0) --originPoint, originXOffset, originYOffset elseif (animationType == "TRANSLATION") then anim:SetOffset(arg1, arg2) elseif (animationType == "PATH") then local newControlPoint = anim:CreateControlPoint() anim:SetCurveType(arg4 or "SMOOTH") newControlPoint:SetOffset(arg2, arg3) newControlPoint:SetOrder(#anim:GetControlPoints()) elseif (animationType == "VERTEXCOLOR" or animationType == "COLOR") then local r1, g1, b1, a1 = arg1, arg2, arg3, arg4 local r2, g2, b2, a2 = arg5, arg6, arg7, arg8 if ((type(r1) == "table" or type(r1) == "string") and (type(g1) == "table" or type(g1) == "string")) then r2, g2, b2, a2 = DF:ParseColors(g1) r1, g1, b1, a1 = DF:ParseColors(r1) elseif ((type(r1) == "table" or type(r1) == "string")) then r1, g1, b1, a1 = DF:ParseColors(r1) elseif ((type(r2) == "table" or type(r2) == "string")) then r2, g2, b2, a2 = DF:ParseColors(r2) end --CreateColor is a function declared in the game api that return a table with the color values in keys r, g, b, a anim:SetStartColor(CreateColor(r1, g1, b1, a1)) anim:SetEndColor(CreateColor(r2, g2, b2, a2)) end animationGroup.NextAnimation = animationGroup.NextAnimation + 1 return anim end ---receives an uiobject, when its parent get hover overed, starts the fade in animation ---start the fade out animation when the mouse leaves the parent ---@param UIObject uiobject ---@param fadeInTime number ---@param fadeOutTime number ---@param fadeInAlpha number ---@param fadeOutAlpha number function DF:CreateFadeAnimation(UIObject, fadeInTime, fadeOutTime, fadeInAlpha, fadeOutAlpha) fadeInTime = fadeInTime or 0.1 fadeOutTime = fadeOutTime or 0.1 fadeInAlpha = fadeInAlpha or 1 fadeOutAlpha = fadeOutAlpha or 0 local fadeInAnimationHub = DF:CreateAnimationHub(UIObject, function() UIObject:Show(); UIObject:SetAlpha(fadeOutAlpha) end, function() UIObject:SetAlpha(fadeInAlpha) end) local fadeIn = DF:CreateAnimation(fadeInAnimationHub, "Alpha", 1, fadeInTime, fadeOutAlpha, fadeInAlpha) local fadeOutAnimationHub = DF:CreateAnimationHub(UIObject, nil, function() UIObject:Hide(); UIObject:SetAlpha(0) end) local fadeOut = DF:CreateAnimation(fadeOutAnimationHub, "Alpha", 2, fadeOutTime, fadeInAlpha, fadeOutAlpha) local scriptFrame --hook the parent OnEnter and OnLeave if (UIObject:IsObjectType("FontString") or UIObject:IsObjectType("Texture")) then scriptFrame = UIObject:GetParent() else scriptFrame = UIObject end ---@cast scriptFrame frame scriptFrame:HookScript("OnEnter", function() fadeOutAnimationHub:Stop(); fadeInAnimationHub:Play() end) scriptFrame:HookScript("OnLeave", function() fadeInAnimationHub:Stop(); fadeOutAnimationHub:Play() end) end ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ --frame shakes --frame shakes rely on OnUpdate scripts, we are using a built-in OnUpdate so is guarantee it'll run local FrameshakeUpdateFrame = DetailsFrameworkFrameshakeControl or CreateFrame("frame", "DetailsFrameworkFrameshakeControl", UIParent) --store the frame which has frame shakes registered FrameshakeUpdateFrame.RegisteredFrames = FrameshakeUpdateFrame.RegisteredFrames or {} FrameshakeUpdateFrame.RegisterFrame = function(newFrame) --add the frame into the registered frames to update DF.table.addunique(FrameshakeUpdateFrame.RegisteredFrames, newFrame) end --forward declared local frameshake_DoUpdate FrameshakeUpdateFrame:SetScript("OnUpdate", function(self, deltaTime) for i = 1, #FrameshakeUpdateFrame.RegisteredFrames do local parent = FrameshakeUpdateFrame.RegisteredFrames [i] --check if there's a shake running if (parent.__frameshakes.enabled > 0) then --update all shakes for this frame for i = 1, #parent.__frameshakes do local shakeObject = parent.__frameshakes [i] if (shakeObject.IsPlaying) then frameshake_DoUpdate(parent, shakeObject, deltaTime) end end end end end) local frameshake_ShakeFinished = function(parent, shakeObject) if (shakeObject.IsPlaying) then shakeObject.IsPlaying = false shakeObject.TimeLeft = 0 shakeObject.IsFadingOut = false shakeObject.IsFadingIn = false --update the amount of shake running on this frame parent.__frameshakes.enabled = parent.__frameshakes.enabled - 1 --restore the default anchors, in case where deltaTime was too small that didn't triggered an update for i = 1, #shakeObject.Anchors do local anchor = shakeObject.Anchors [i] --automatic anchoring and reanching needs to the reviwed in the future if (#anchor == 1) then local anchorTo = unpack(anchor) parent:ClearAllPoints() parent:SetPoint(anchorTo) elseif (#anchor == 2) then local anchorTo, point1 = unpack(anchor) parent:ClearAllPoints() parent:SetPoint(anchorTo, point1) elseif (#anchor == 3) then local anchorTo, point1, point2 = unpack(anchor) parent:SetPoint(anchorTo, point1, point2) elseif (#anchor == 5) then local anchorName1, anchorTo, anchorName2, point1, point2 = unpack(anchor) parent:SetPoint(anchorName1, anchorTo, anchorName2, point1, point2) end end end end --already declared above the update function frameshake_DoUpdate = function(parent, shakeObject, deltaTime) --check delta time deltaTime = deltaTime or 0 --update time left shakeObject.TimeLeft = max(shakeObject.TimeLeft - deltaTime, 0) if (shakeObject.TimeLeft > 0) then --update fade in and out if (shakeObject.IsFadingIn) then shakeObject.IsFadingInTime = shakeObject.IsFadingInTime + deltaTime end if (shakeObject.IsFadingOut) then shakeObject.IsFadingOutTime = shakeObject.IsFadingOutTime + deltaTime end --check if can disable fade in if (shakeObject.IsFadingIn and shakeObject.IsFadingInTime > shakeObject.FadeInTime) then shakeObject.IsFadingIn = false end --check if can enable fade out if (not shakeObject.IsFadingOut and shakeObject.TimeLeft < shakeObject.FadeOutTime) then shakeObject.IsFadingOut = true shakeObject.IsFadingOutTime = shakeObject.FadeOutTime - shakeObject.TimeLeft end --update position local scaleShake = min(shakeObject.IsFadingIn and (shakeObject.IsFadingInTime / shakeObject.FadeInTime) or 1, shakeObject.IsFadingOut and (1 - shakeObject.IsFadingOutTime / shakeObject.FadeOutTime) or 1) if (scaleShake > 0) then --delate the time by the frequency on both X and Y offsets shakeObject.XSineOffset = shakeObject.XSineOffset + (deltaTime * shakeObject.Frequency) shakeObject.YSineOffset = shakeObject.YSineOffset + (deltaTime * shakeObject.Frequency) --calc the new position local newX, newY if (shakeObject.AbsoluteSineX) then --absoluting only the sine wave, passing a negative scale will reverse the absolute direction newX = shakeObject.Amplitude * math.abs(math.sin(shakeObject.XSineOffset)) * scaleShake * shakeObject.ScaleX else newX = shakeObject.Amplitude * math.sin(shakeObject.XSineOffset) * scaleShake * shakeObject.ScaleX end if (shakeObject.AbsoluteSineY) then newY = shakeObject.Amplitude * math.abs(math.sin(shakeObject.YSineOffset)) * scaleShake * shakeObject.ScaleY else newY = shakeObject.Amplitude * math.sin(shakeObject.YSineOffset) * scaleShake * shakeObject.ScaleY end --apply the offset to the frame anchors for i = 1, #shakeObject.Anchors do local anchor = shakeObject.Anchors [i] if (#anchor == 1 or #anchor == 3) then local anchorTo, point1, point2 = unpack(anchor) point1 = point1 or 0 point2 = point2 or 0 parent:SetPoint(anchorTo, point1 + newX, point2 + newY) elseif (#anchor == 5) then local anchorName1, anchorTo, anchorName2, point1, point2 = unpack(anchor) parent:SetPoint(anchorName1, anchorTo, anchorName2, point1 + newX, point2 + newY) end end end else frameshake_ShakeFinished(parent, shakeObject) end end local frameshake_stop = function(parent, shakeObject) frameshake_ShakeFinished(parent, shakeObject) end --scale direction scales the X and Y coordinates, scale strength scales the amplitude and frequency local frameshake_play = function(parent, shakeObject, scaleDirection, scaleAmplitude, scaleFrequency, scaleDuration) --check if is already playing if (shakeObject.TimeLeft > 0) then --reset the time left shakeObject.TimeLeft = shakeObject.Duration if (shakeObject.IsFadingOut) then if (shakeObject.FadeInTime > 0) then shakeObject.IsFadingIn = true --scale the current fade out into fade in, so it starts the fade in at the point where it was fading out shakeObject.IsFadingInTime = shakeObject.FadeInTime * (1 - shakeObject.IsFadingOutTime / shakeObject.FadeOutTime) else shakeObject.IsFadingIn = false shakeObject.IsFadingInTime = 0 end --disable fade out and enable fade in shakeObject.IsFadingOut = false shakeObject.IsFadingOutTime = 0 end else --create a new random offset shakeObject.XSineOffset = math.pi * 2 * math.random() shakeObject.YSineOffset = math.pi * 2 * math.random() --store the initial position if case it needs a reset shakeObject.StartedXSineOffset = shakeObject.XSineOffset shakeObject.StartedYSineOffset = shakeObject.YSineOffset --check if there's a fade in time if (shakeObject.FadeInTime > 0) then shakeObject.IsFadingIn = true else shakeObject.IsFadingIn = false end shakeObject.IsFadingInTime = 0 shakeObject.IsFadingOut = false shakeObject.IsFadingOutTime = 0 --apply custom scale shakeObject.ScaleX = (scaleDirection or 1) * shakeObject.OriginalScaleX shakeObject.ScaleY = (scaleDirection or 1) * shakeObject.OriginalScaleY shakeObject.Frequency = (scaleFrequency or 1) * shakeObject.OriginalFrequency shakeObject.Amplitude = (scaleAmplitude or 1) * shakeObject.OriginalAmplitude shakeObject.Duration = (scaleDuration or 1) * shakeObject.OriginalDuration --update the time left shakeObject.TimeLeft = shakeObject.Duration --check if is dynamic points if (shakeObject.IsDynamicAnchor) then wipe(shakeObject.Anchors) for i = 1, parent:GetNumPoints() do local p1, p2, p3, p4, p5 = parent:GetPoint(i) shakeObject.Anchors[#shakeObject.Anchors+1] = {p1, p2, p3, p4, p5} end end --update the amount of shake running on this frame parent.__frameshakes.enabled = parent.__frameshakes.enabled + 1 if (parent:HasScript("OnUpdate") and not parent:GetScript("OnUpdate")) then parent:SetScript("OnUpdate", function()end) end end shakeObject.IsPlaying = true frameshake_DoUpdate(parent, shakeObject) end local frameshake_SetConfig = function(parent, shakeObject, duration, amplitude, frequency, absoluteSineX, absoluteSineY, scaleX, scaleY, fadeInTime, fadeOutTime) shakeObject.Amplitude = amplitude or shakeObject.Amplitude shakeObject.Frequency = frequency or shakeObject.Frequency shakeObject.Duration = duration or shakeObject.Duration shakeObject.FadeInTime = fadeInTime or shakeObject.FadeInTime shakeObject.FadeOutTime = fadeOutTime or shakeObject.FadeOutTime shakeObject.ScaleX = scaleX or shakeObject.ScaleX shakeObject.ScaleY = scaleY or shakeObject.ScaleY if (absoluteSineX ~= nil) then shakeObject.AbsoluteSineX = absoluteSineX end if (absoluteSineY ~= nil) then shakeObject.AbsoluteSineY = absoluteSineY end shakeObject.OriginalScaleX = shakeObject.ScaleX shakeObject.OriginalScaleY = shakeObject.ScaleY shakeObject.OriginalFrequency = shakeObject.Frequency shakeObject.OriginalAmplitude = shakeObject.Amplitude shakeObject.OriginalDuration = shakeObject.Duration end ---@class df_frameshake : table ---@field Amplitude number ---@field Frequency number ---@field Duration number ---@field FadeInTime number ---@field FadeOutTime number ---@field ScaleX number ---@field ScaleY number ---@field AbsoluteSineX boolean ---@field AbsoluteSineY boolean ---@field IsPlaying boolean ---@field TimeLeft number ---@field OriginalScaleX number ---@field OriginalScaleY number ---@field OriginalFrequency number ---@field OriginalAmplitude number ---@field OriginalDuration number ---@field PlayFrameShake fun(parent:uiobject, shakeObject:df_frameshake, scaleDirection:number?, scaleAmplitude:number?, scaleFrequency:number?, scaleDuration:number?) ---@field StopFrameShake fun(parent:uiobject, shakeObject:df_frameshake) ---@field SetFrameShakeSettings fun(parent:uiobject, shakeObject:df_frameshake, duration:number?, amplitude:number?, frequency:number?, absoluteSineX:boolean?, absoluteSineY:boolean?, scaleX:number?, scaleY:number?, fadeInTime:number?, fadeOutTime:number?) ---create a frame shake object ---@param parent uiobject ---@param duration number? ---@param amplitude number? ---@param frequency number? ---@param absoluteSineX boolean? ---@param absoluteSineY boolean? ---@param scaleX number? ---@param scaleY number? ---@param fadeInTime number? ---@param fadeOutTime number? ---@param anchorPoints table? ---@return df_frameshake function DF:CreateFrameShake(parent, duration, amplitude, frequency, absoluteSineX, absoluteSineY, scaleX, scaleY, fadeInTime, fadeOutTime, anchorPoints) --create the shake table local frameShake = { Amplitude = amplitude or 2, Frequency = frequency or 5, Duration = duration or 0.3, FadeInTime = fadeInTime or 0.01, FadeOutTime = fadeOutTime or 0.01, ScaleX = scaleX or 0.2, ScaleY = scaleY or 1, AbsoluteSineX = absoluteSineX, AbsoluteSineY = absoluteSineY, -- IsPlaying = false, TimeLeft = 0, } frameShake.OriginalScaleX = frameShake.ScaleX frameShake.OriginalScaleY = frameShake.ScaleY frameShake.OriginalFrequency = frameShake.Frequency frameShake.OriginalAmplitude = frameShake.Amplitude frameShake.OriginalDuration = frameShake.Duration if (type(anchorPoints) ~= "table") then frameShake.IsDynamicAnchor = true frameShake.Anchors = {} else frameShake.Anchors = anchorPoints end --inject frame shake table into the frame if (not parent.__frameshakes) then parent.__frameshakes = { enabled = 0, } parent.PlayFrameShake = frameshake_play parent.StopFrameShake = frameshake_stop parent.UpdateFrameShake = frameshake_DoUpdate parent.SetFrameShakeSettings = frameshake_SetConfig --register the frame within the frame shake updater FrameshakeUpdateFrame.RegisterFrame (parent) end table.insert(parent.__frameshakes, frameShake) return frameShake end ----------------------------- --glow overlay local glow_overlay_play = function(self) if (not self:IsShown()) then self:Show() end if (self.animOut) then if (self.animOut:IsPlaying()) then self.animOut:Stop() end if (not self.animIn:IsPlaying()) then self.animIn:Stop() self.animIn:Play() end elseif (self.ProcStartAnim) then if (not self.ProcStartAnim:IsPlaying()) then self.ProcStartAnim:Play() end if (not self.ProcLoop:IsPlaying()) then --self.ProcLoop:Play() end end end local glow_overlay_stop = function(self) if (self.animOut) then if (self.animOut:IsPlaying()) then self.animOut:Stop() end if (self.animIn:IsPlaying()) then self.animIn:Stop() end elseif (self.ProcStartAnim) then if (self.ProcStartAnim:IsPlaying()) then self.ProcStartAnim:Stop() end end if (self:IsShown()) then self:Hide() end end local glow_overlay_setcolor = function(self, antsColor, glowColor) if (antsColor) then local r, g, b, a = DF:ParseColors(antsColor) self.AntsColor = {r, g, b, a} if (self.ants) then self.ants:SetVertexColor(r, g, b, a) elseif (self.ProcLoopFlipbook) then self.ProcLoopFlipbook:SetVertexColor(r, g, b) --no alpha because of animation local anim1 = self.ProcLoop:GetAnimations() anim1:SetToAlpha(a) end end if (glowColor) then local r, g, b, a = DF:ParseColors(glowColor) self.GlowColor = {r, g, b, a} if (self.outerGlow) then self.outerGlow:SetVertexColor(r, g, b, a) elseif (self.ProcStartFlipbook) then self.ProcStartFlipbook:SetVertexColor(r, g, b) --no alpha because of animation local anim1, anim2, anim3 = self.ProcStartAnim:GetAnimations() anim1:SetToAlpha(a) anim3:SetFromAlpha(a) end end end local glow_overlay_onshow = function(self) glow_overlay_play(self) end local glow_overlay_onhide = function(self) glow_overlay_stop(self) end ---create a glow overlay around a frame, return a frame and also add parent.overlay to the parent frame ---@param self table ---@param parent frame ---@param antsColor any ---@param glowColor any function DF:CreateGlowOverlay(parent, antsColor, glowColor) local parentName = parent:GetName() local frameName = parentName and (parentName .. "Glow2") or "OverlayActionGlow" .. math.random(1, 10000000) if (frameName and string.len(frameName) > 50) then --shorten to work around too long names frameName = string.sub(frameName, string.len(frameName)-49) end local glowFrame if (buildInfo >= 110107) then --24-05-2025: in the 11.1.7 patch, the template used here does not exist anymore, replacement used glowFrame = CreateFrame("frame", frameName, parent, "ActionButtonSpellAlertTemplate") else glowFrame = CreateFrame("frame", frameName, parent, "ActionBarButtonSpellActivationAlert") end --local glowFrame = CreateFrame("frame", frameName, parent) glowFrame:HookScript("OnShow", glow_overlay_onshow) glowFrame:HookScript("OnHide", glow_overlay_onhide) glowFrame.Play = glow_overlay_play glowFrame.Stop = glow_overlay_stop glowFrame.SetColor = glow_overlay_setcolor glowFrame:SetColor(antsColor, glowColor) glowFrame:Hide() parent.overlay = glowFrame local frameWidth, frameHeight = parent:GetSize() local scale = 1.4 --make the height/width available before the next frame: glowFrame:SetSize(frameWidth * scale, frameHeight * scale) glowFrame:SetPoint("topleft", parent, "topleft", -frameWidth * 0.32, frameHeight * 0.36) glowFrame:SetPoint("bottomright", parent, "bottomright", frameWidth * 0.32, -frameHeight * 0.36) if (glowFrame.outerGlow) then glowFrame.outerGlow:SetScale(1.2) end if (glowFrame.ProcStartFlipbook) then glowFrame.ProcStartAnim:Stop() glowFrame.ProcStartFlipbook:ClearAllPoints() glowFrame.ProcStartFlipbook:SetPoint("TOPLEFT", glowFrame, "TOPLEFT", -frameWidth * scale, frameHeight * scale) glowFrame.ProcStartFlipbook:SetPoint("BOTTOMRIGHT", glowFrame, "BOTTOMRIGHT", frameWidth * scale, -frameHeight * scale) end glowFrame:EnableMouse(false) return glowFrame end --custom glow with ants animation local ants_set_texture_offset = function(self, leftOffset, rightOffset, topOffset, bottomOffset) leftOffset = leftOffset or 0 rightOffset = rightOffset or 0 topOffset = topOffset or 0 bottomOffset = bottomOffset or 0 self:ClearAllPoints() self:SetPoint("topleft", leftOffset, topOffset) self:SetPoint("bottomright", rightOffset, bottomOffset) end ---create an "ant" animation around the frame, the name "ant" comes from the animation looking like small bright dots moving around the frame ---@param parent frame ---@param antTable df_anttable ---@param leftOffset number? ---@param rightOffset number? ---@param topOffset number? ---@param bottomOffset number? ---@return frame function DF:CreateAnts(parent, antTable, leftOffset, rightOffset, topOffset, bottomOffset) leftOffset = leftOffset or 0 rightOffset = rightOffset or 0 topOffset = topOffset or 0 bottomOffset = bottomOffset or 0 local antsFrame = CreateFrame("frame", nil, parent) antsFrame:SetPoint("topleft", leftOffset, topOffset) antsFrame:SetPoint("bottomright", rightOffset, bottomOffset) antsFrame.SetOffset = ants_set_texture_offset local texture = antsFrame:CreateTexture(nil, "overlay") texture:SetAllPoints() texture:SetTexture(antTable.Texture) texture:SetBlendMode(antTable.BlendMode or "ADD") texture:SetVertexColor(DF:ParseColors(antTable.Color or "white")) antsFrame.Texture = texture antsFrame.AntTable = antTable antsFrame:SetScript("OnUpdate", function(self, deltaTime) AnimateTexCoords(texture, self.AntTable.TextureWidth, self.AntTable.TextureHeight, self.AntTable.TexturePartsWidth, self.AntTable.TexturePartsHeight, self.AntTable.AmountParts, deltaTime, self.AntTable.Throttle or 0.025) end) return antsFrame end --[=[ --test ants do local f = DF:CreateAnts (UIParent) end --]=] ----------------------------- --borders local default_border_color1 = .5 local default_border_color2 = .3 local default_border_color3 = .1 local SetBorderAlpha = function(self, alpha1, alpha2, alpha3) self.Borders.Alpha1 = alpha1 or self.Borders.Alpha1 self.Borders.Alpha2 = alpha2 or self.Borders.Alpha2 self.Borders.Alpha3 = alpha3 or self.Borders.Alpha3 for _, texture in ipairs(self.Borders.Layer1) do texture:SetAlpha(self.Borders.Alpha1) end for _, texture in ipairs(self.Borders.Layer2) do texture:SetAlpha(self.Borders.Alpha2) end for _, texture in ipairs(self.Borders.Layer3) do texture:SetAlpha(self.Borders.Alpha3) end end local SetBorderColor = function(self, r, g, b) for _, texture in ipairs(self.Borders.Layer1) do texture:SetColorTexture(r, g, b) end for _, texture in ipairs(self.Borders.Layer2) do texture:SetColorTexture(r, g, b) end for _, texture in ipairs(self.Borders.Layer3) do texture:SetColorTexture(r, g, b) end end local SetLayerVisibility = function(self, layer1Shown, layer2Shown, layer3Shown) for _, texture in ipairs(self.Borders.Layer1) do texture:SetShown(layer1Shown) end for _, texture in ipairs(self.Borders.Layer2) do texture:SetShown(layer2Shown) end for _, texture in ipairs(self.Borders.Layer3) do texture:SetShown(layer3Shown) end end ---create a border using three textures for each side of the frame, each texture has a different transparency creating a smooth gradient effect ---the parent frame receives three new methods: SetBorderAlpha(a1, a2, a3), SetBorderColor(r, g, b), SetLayerVisibility(layer1Shown, layer2Shown, layer3Shown) ---@param self table ---@param parent frame ---@param alpha1 number? ---@param alpha2 number? ---@param alpha3 number? function DF:CreateBorder(parent, alpha1, alpha2, alpha3) parent.Borders = { Layer1 = {}, Layer2 = {}, Layer3 = {}, Alpha1 = alpha1 or default_border_color1, Alpha2 = alpha2 or default_border_color2, Alpha3 = alpha3 or default_border_color3, } parent.SetBorderAlpha = SetBorderAlpha parent.SetBorderColor = SetBorderColor parent.SetLayerVisibility = SetLayerVisibility do local leftBorder1 = parent:CreateTexture(nil, "background") PixelUtil.SetPoint(leftBorder1, "topleft", parent, "topleft", -1, 1) PixelUtil.SetPoint(leftBorder1, "bottomleft", parent, "bottomleft", -1, -1) leftBorder1:SetColorTexture(0, 0, 0, alpha1 or default_border_color1) local leftBorder2 = parent:CreateTexture(nil, "background") PixelUtil.SetPoint(leftBorder2, "topleft", parent, "topleft", -2, 2) PixelUtil.SetPoint(leftBorder2, "bottomleft", parent, "bottomleft", -2, -2) leftBorder2:SetColorTexture(0, 0, 0, alpha2 or default_border_color2) local leftBorder3 = parent:CreateTexture(nil, "background") PixelUtil.SetPoint(leftBorder3, "topleft", parent, "topleft", -3, 3) PixelUtil.SetPoint(leftBorder3, "bottomleft", parent, "bottomleft", -3, -3) leftBorder3:SetColorTexture(0, 0, 0, alpha3 or default_border_color3) table.insert(parent.Borders.Layer1, leftBorder1) table.insert(parent.Borders.Layer2, leftBorder2) table.insert(parent.Borders.Layer3, leftBorder3) end do local topBorder1 = parent:CreateTexture(nil, "background") PixelUtil.SetPoint(topBorder1, "topleft", parent, "topleft", 0, 1) PixelUtil.SetPoint(topBorder1, "topright", parent, "topright", 1, 1) topBorder1:SetColorTexture(0, 0, 0, alpha1 or default_border_color1) local topBorder2 = parent:CreateTexture(nil, "background") PixelUtil.SetPoint(topBorder2, "topleft", parent, "topleft", -1, 2) PixelUtil.SetPoint(topBorder2, "topright", parent, "topright", 2, 2) topBorder2:SetColorTexture(0, 0, 0, alpha2 or default_border_color2) local topBorder3 = parent:CreateTexture(nil, "background") PixelUtil.SetPoint(topBorder3, "topleft", parent, "topleft", -2, 3) PixelUtil.SetPoint(topBorder3, "topright", parent, "topright", 3, 3) topBorder3:SetColorTexture(0, 0, 0, alpha3 or default_border_color3) table.insert(parent.Borders.Layer1, topBorder1) table.insert(parent.Borders.Layer2, topBorder2) table.insert(parent.Borders.Layer3, topBorder3) end do local rightBorder1 = parent:CreateTexture(nil, "background") PixelUtil.SetPoint(rightBorder1, "topright", parent, "topright", 1, 0) PixelUtil.SetPoint(rightBorder1, "bottomright", parent, "bottomright", 1, -1) rightBorder1:SetColorTexture(0, 0, 0, alpha1 or default_border_color1) local rightBorder2 = parent:CreateTexture(nil, "background") PixelUtil.SetPoint(rightBorder2, "topright", parent, "topright", 2, 1) PixelUtil.SetPoint(rightBorder2, "bottomright", parent, "bottomright", 2, -2) rightBorder2:SetColorTexture(0, 0, 0, alpha2 or default_border_color2) local rightBorder3 = parent:CreateTexture(nil, "background") PixelUtil.SetPoint(rightBorder3, "topright", parent, "topright", 3, 2) PixelUtil.SetPoint(rightBorder3, "bottomright", parent, "bottomright", 3, -3) rightBorder3:SetColorTexture(0, 0, 0, alpha3 or default_border_color3) table.insert(parent.Borders.Layer1, rightBorder1) table.insert(parent.Borders.Layer2, rightBorder2) table.insert(parent.Borders.Layer3, rightBorder3) end do local bottomBorder1 = parent:CreateTexture(nil, "background") PixelUtil.SetPoint(bottomBorder1, "bottomleft", parent, "bottomleft", 0, -1) PixelUtil.SetPoint(bottomBorder1, "bottomright", parent, "bottomright", 0, -1) bottomBorder1:SetColorTexture(0, 0, 0, alpha1 or default_border_color1) local bottomBorder2 = parent:CreateTexture(nil, "background") PixelUtil.SetPoint(bottomBorder2, "bottomleft", parent, "bottomleft", -1, -2) PixelUtil.SetPoint(bottomBorder2, "bottomright", parent, "bottomright", 1, -2) bottomBorder2:SetColorTexture(0, 0, 0, alpha2 or default_border_color2) local bottomBorder3 = parent:CreateTexture(nil, "background") PixelUtil.SetPoint(bottomBorder3, "bottomleft", parent, "bottomleft", -2, -3) PixelUtil.SetPoint(bottomBorder3, "bottomright", parent, "bottomright", 2, -3) bottomBorder3:SetColorTexture(0, 0, 0, alpha3 or default_border_color3) table.insert(parent.Borders.Layer1, bottomBorder1) table.insert(parent.Borders.Layer2, bottomBorder2) table.insert(parent.Borders.Layer3, bottomBorder3) end end --DFNamePlateBorder as copy from "NameplateFullBorderTemplate" -> DF:CreateFullBorder (name, parent) ---@class df_nameplate_border_mixin : table ---@field SetVertexColor fun(self:border_frame, r:number, g:number, b:number, a:number) ---@field GetVertexColor fun(self:border_frame):number, number, number r, g, b ---@field SetBorderSizes fun(self:border_frame, borderSize:number, borderSizeMinPixels:number, upwardExtendHeightPixels:number, upwardExtendHeightMinPixels:number) ---@field UpdateSizes fun(self:border_frame) ---@field Left texture ---@field Right texture ---@field Bottom texture ---@field Top texture ---@field Textures texture[] ---@field borderSize number ---@field borderSizeMinPixels number ---@field upwardExtendHeightPixels number ---@field upwardExtendHeightMinPixels number local DFNamePlateBorderTemplateMixin = {} DF.NameplateBorderMixin = DFNamePlateBorderTemplateMixin function DFNamePlateBorderTemplateMixin:SetVertexColor(r, g, b, a) for i, texture in ipairs(self.Textures) do texture:SetVertexColor(r, g, b, a); end end function DFNamePlateBorderTemplateMixin:GetVertexColor() for i, texture in ipairs(self.Textures) do return texture:GetVertexColor(); end end function DFNamePlateBorderTemplateMixin:SetBorderSizes(borderSize, borderSizeMinPixels, upwardExtendHeightPixels, upwardExtendHeightMinPixels) self.borderSize = borderSize; self.borderSizeMinPixels = borderSizeMinPixels; self.upwardExtendHeightPixels = upwardExtendHeightPixels; self.upwardExtendHeightMinPixels = upwardExtendHeightMinPixels; end function DFNamePlateBorderTemplateMixin:UpdateSizes() local borderSize = self.borderSize or 1; local minPixels = self.borderSizeMinPixels or 2; local upwardExtendHeightPixels = self.upwardExtendHeightPixels or borderSize; local upwardExtendHeightMinPixels = self.upwardExtendHeightMinPixels or minPixels; PixelUtil.SetWidth(self.Left, borderSize, minPixels); PixelUtil.SetPoint(self.Left, "TOPRIGHT", self, "TOPLEFT", 0, upwardExtendHeightPixels, 0, upwardExtendHeightMinPixels); PixelUtil.SetPoint(self.Left, "BOTTOMRIGHT", self, "BOTTOMLEFT", 0, -borderSize, 0, minPixels); PixelUtil.SetWidth(self.Right, borderSize, minPixels); PixelUtil.SetPoint(self.Right, "TOPLEFT", self, "TOPRIGHT", 0, upwardExtendHeightPixels, 0, upwardExtendHeightMinPixels); PixelUtil.SetPoint(self.Right, "BOTTOMLEFT", self, "BOTTOMRIGHT", 0, -borderSize, 0, minPixels); PixelUtil.SetHeight(self.Bottom, borderSize, minPixels); PixelUtil.SetPoint(self.Bottom, "TOPLEFT", self, "BOTTOMLEFT", 0, 0); PixelUtil.SetPoint(self.Bottom, "TOPRIGHT", self, "BOTTOMRIGHT", 0, 0); if self.Top then PixelUtil.SetHeight(self.Top, borderSize, minPixels); PixelUtil.SetPoint(self.Top, "BOTTOMLEFT", self, "TOPLEFT", 0, 0); PixelUtil.SetPoint(self.Top, "BOTTOMRIGHT", self, "TOPRIGHT", 0, 0); end end ---@class border_frame : frame, df_nameplate_border_mixin function DF:CreateFullBorder(name, parent) local border = CreateFrame("Frame", name, parent) border:SetAllPoints() border:SetIgnoreParentScale(true) border:SetFrameLevel(border:GetParent():GetFrameLevel()) border.Textures = {} Mixin(border, DFNamePlateBorderTemplateMixin) local left = border:CreateTexture("$parentLeft", "BACKGROUND", nil, -8) --left:SetDrawLayer("BACKGROUND", -8) left:SetColorTexture(1, 1, 1, 1) left:SetWidth(1.0) left:SetPoint("TOPRIGHT", border, "TOPLEFT", 0, 1.0) left:SetPoint("BOTTOMRIGHT", border, "BOTTOMLEFT", 0, -1.0) border.Left = left table.insert(border.Textures, left) local right = border:CreateTexture("$parentRight", "BACKGROUND", nil, -8) --right:SetDrawLayer("BACKGROUND", -8) right:SetColorTexture(1, 1, 1, 1) right:SetWidth(1.0) right:SetPoint("TOPLEFT", border, "TOPRIGHT", 0, 1.0) right:SetPoint("BOTTOMLEFT", border, "BOTTOMRIGHT", 0, -1.0) border.Right = right table.insert(border.Textures, right) local bottom = border:CreateTexture("$parentBottom", "BACKGROUND", nil, -8) --bottom:SetDrawLayer("BACKGROUND", -8) bottom:SetColorTexture(1, 1, 1, 1) bottom:SetHeight(1.0) bottom:SetPoint("TOPLEFT", border, "BOTTOMLEFT", 0, 0) bottom:SetPoint("TOPRIGHT", border, "BOTTOMRIGHT", 0, 0) border.Bottom = bottom table.insert(border.Textures, bottom) local top = border:CreateTexture("$parentTop", "BACKGROUND", nil, -8) --top:SetDrawLayer("BACKGROUND", -8) top:SetColorTexture(1, 1, 1, 1) top:SetHeight(1.0) top:SetPoint("BOTTOMLEFT", border, "TOPLEFT", 0, 0) top:SetPoint("BOTTOMRIGHT", border, "TOPRIGHT", 0, 0) border.Top = top table.insert(border.Textures, top) return border end function DF:CreateBorderSolid (parent, size) end function DF:CreateBorderWithSpread(parent, alpha1, alpha2, alpha3, size, spread) parent.Borders = { Layer1 = {}, Layer2 = {}, Layer3 = {}, Alpha1 = alpha1 or default_border_color1, Alpha2 = alpha2 or default_border_color2, Alpha3 = alpha3 or default_border_color3, } parent.SetBorderAlpha = SetBorderAlpha parent.SetBorderColor = SetBorderColor parent.SetLayerVisibility = SetLayerVisibility size = size or 1 local minPixels = 1 local spread = 0 --left local border1 = parent:CreateTexture(nil, "background") border1:SetColorTexture(0, 0, 0, alpha1 or default_border_color1) PixelUtil.SetPoint(border1, "topleft", parent, "topleft", -1 + spread, 1 + (-spread), 0, 0) PixelUtil.SetPoint(border1, "bottomleft", parent, "bottomleft", -1 + spread, -1 + spread, 0, 0) PixelUtil.SetWidth (border1, size, minPixels) local border2 = parent:CreateTexture(nil, "background") PixelUtil.SetPoint(border2, "topleft", parent, "topleft", -2 + spread, 2 + (-spread)) PixelUtil.SetPoint(border2, "bottomleft", parent, "bottomleft", -2 + spread, -2 + spread) border2:SetColorTexture(0, 0, 0, alpha2 or default_border_color2) PixelUtil.SetWidth (border2, size, minPixels) local border3 = parent:CreateTexture(nil, "background") PixelUtil.SetPoint(border3, "topleft", parent, "topleft", -3 + spread, 3 + (-spread)) PixelUtil.SetPoint(border3, "bottomleft", parent, "bottomleft", -3 + spread, -3 + spread) border3:SetColorTexture(0, 0, 0, alpha3 or default_border_color3) PixelUtil.SetWidth (border3, size, minPixels) table.insert(parent.Borders.Layer1, border1) table.insert(parent.Borders.Layer2, border2) table.insert(parent.Borders.Layer3, border3) --top local border1 = parent:CreateTexture(nil, "background") PixelUtil.SetPoint(border1, "topleft", parent, "topleft", 0 + spread, 1 + (-spread)) PixelUtil.SetPoint(border1, "topright", parent, "topright", 1 + (-spread), 1 + (-spread)) border1:SetColorTexture(0, 0, 0, alpha1 or default_border_color1) PixelUtil.SetHeight(border1, size, minPixels) local border2 = parent:CreateTexture(nil, "background") PixelUtil.SetPoint(border2, "topleft", parent, "topleft", -1 + spread, 2 + (-spread)) PixelUtil.SetPoint(border2, "topright", parent, "topright", 2 + (-spread), 2 + (-spread)) border2:SetColorTexture(0, 0, 0, alpha2 or default_border_color2) PixelUtil.SetHeight(border2, size, minPixels) local border3 = parent:CreateTexture(nil, "background") PixelUtil.SetPoint(border3, "topleft", parent, "topleft", -2 + spread, 3 + (-spread)) PixelUtil.SetPoint(border3, "topright", parent, "topright", 3 + (-spread), 3 + (-spread)) border3:SetColorTexture(0, 0, 0, alpha3 or default_border_color3) PixelUtil.SetHeight(border3, size, minPixels) table.insert(parent.Borders.Layer1, border1) table.insert(parent.Borders.Layer2, border2) table.insert(parent.Borders.Layer3, border3) --right local border1 = parent:CreateTexture(nil, "background") PixelUtil.SetPoint(border1, "topright", parent, "topright", 1 + (-spread), 0 + (-spread)) PixelUtil.SetPoint(border1, "bottomright", parent, "bottomright", 1 + (-spread), -1 + spread) border1:SetColorTexture(0, 0, 0, alpha1 or default_border_color1) PixelUtil.SetWidth (border1, size, minPixels) local border2 = parent:CreateTexture(nil, "background") PixelUtil.SetPoint(border2, "topright", parent, "topright", 2 + (-spread), 1 + (-spread)) PixelUtil.SetPoint(border2, "bottomright", parent, "bottomright", 2 + (-spread), -2 + spread) border2:SetColorTexture(0, 0, 0, alpha2 or default_border_color2) PixelUtil.SetWidth (border2, size, minPixels) local border3 = parent:CreateTexture(nil, "background") PixelUtil.SetPoint(border3, "topright", parent, "topright", 3 + (-spread), 2 + (-spread)) PixelUtil.SetPoint(border3, "bottomright", parent, "bottomright", 3 + (-spread), -3 + spread) border3:SetColorTexture(0, 0, 0, alpha3 or default_border_color3) PixelUtil.SetWidth (border3, size, minPixels) table.insert(parent.Borders.Layer1, border1) table.insert(parent.Borders.Layer2, border2) table.insert(parent.Borders.Layer3, border3) local border1 = parent:CreateTexture(nil, "background") PixelUtil.SetPoint(border1, "bottomleft", parent, "bottomleft", 0 + spread, -1 + spread) PixelUtil.SetPoint(border1, "bottomright", parent, "bottomright", 0 + (-spread), -1 + spread) border1:SetColorTexture(0, 0, 0, alpha1 or default_border_color1) PixelUtil.SetHeight(border1, size, minPixels) local border2 = parent:CreateTexture(nil, "background") PixelUtil.SetPoint(border2, "bottomleft", parent, "bottomleft", -1 + spread, -2 + spread) PixelUtil.SetPoint(border2, "bottomright", parent, "bottomright", 1 + (-spread), -2 + spread) border2:SetColorTexture(0, 0, 0, alpha2 or default_border_color2) PixelUtil.SetHeight(border2, size, minPixels) local border3 = parent:CreateTexture(nil, "background") PixelUtil.SetPoint(border3, "bottomleft", parent, "bottomleft", -2 + spread, -3 + spread) PixelUtil.SetPoint(border3, "bottomright", parent, "bottomright", 2 + (-spread), -3 + spread) border3:SetColorTexture(0, 0, 0, alpha3 or default_border_color3) PixelUtil.SetHeight(border3, size, minPixels) table.insert(parent.Borders.Layer1, border1) table.insert(parent.Borders.Layer2, border2) table.insert(parent.Borders.Layer3, border3) end function DF:ReskinSlider(slider, heightOffset) if (slider.slider) then slider.cima:SetNormalTexture([[Interface\Buttons\Arrow-Up-Up]]) slider.cima:SetPushedTexture([[Interface\Buttons\Arrow-Up-Down]]) slider.cima:SetDisabledTexture([[Interface\Buttons\Arrow-Up-Disabled]]) slider.cima:GetNormalTexture():ClearAllPoints() slider.cima:GetPushedTexture():ClearAllPoints() slider.cima:GetDisabledTexture():ClearAllPoints() slider.cima:GetNormalTexture():SetPoint("center", slider.cima, "center", 1, 1) slider.cima:GetPushedTexture():SetPoint("center", slider.cima, "center", 1, 1) slider.cima:GetDisabledTexture():SetPoint("center", slider.cima, "center", 1, 1) slider.cima:SetSize(16, 16) slider.baixo:SetNormalTexture([[Interface\Buttons\Arrow-Down-Up]]) slider.baixo:SetPushedTexture([[Interface\Buttons\Arrow-Down-Down]]) slider.baixo:SetDisabledTexture([[Interface\Buttons\Arrow-Down-Disabled]]) slider.baixo:GetNormalTexture():ClearAllPoints() slider.baixo:GetPushedTexture():ClearAllPoints() slider.baixo:GetDisabledTexture():ClearAllPoints() slider.baixo:GetNormalTexture():SetPoint("center", slider.baixo, "center", 1, -5) slider.baixo:GetPushedTexture():SetPoint("center", slider.baixo, "center", 1, -5) slider.baixo:GetDisabledTexture():SetPoint("center", slider.baixo, "center", 1, -5) slider.baixo:SetSize(16, 16) slider.slider:cimaPoint(0, 13) slider.slider:baixoPoint(0, -13) slider.slider.thumb:SetTexture([[Interface\AddOns\Details\images\icons2]]) slider.slider.thumb:SetTexCoord(482/512, 492/512, 104/512, 120/512) slider.slider.thumb:SetSize(12, 12) slider.slider.thumb:SetVertexColor(0.6, 0.6, 0.6, 0.95) else --up button local offset = 1 --space between the scrollbox and the scrollar local backgroundColor_Red = 0.1 local backgroundColor_Green = 0.1 local backgroundColor_Blue = 0.1 local backgroundColor_Alpha = 1 local backdrop_Alpha = 0.3 do local normalTexture = slider.ScrollBar.ScrollUpButton.Normal normalTexture:SetTexture([[Interface\Buttons\Arrow-Up-Up]]) normalTexture:SetTexCoord(0, 1, .2, 1) normalTexture:SetPoint("topleft", slider.ScrollBar.ScrollUpButton, "topleft", offset, 0) normalTexture:SetPoint("bottomright", slider.ScrollBar.ScrollUpButton, "bottomright", offset, 0) local pushedTexture = slider.ScrollBar.ScrollUpButton.Pushed pushedTexture:SetTexture([[Interface\Buttons\Arrow-Up-Down]]) pushedTexture:SetTexCoord(0, 1, .2, 1) pushedTexture:SetPoint("topleft", slider.ScrollBar.ScrollUpButton, "topleft", offset, 0) pushedTexture:SetPoint("bottomright", slider.ScrollBar.ScrollUpButton, "bottomright", offset, 0) local disabledTexture = slider.ScrollBar.ScrollUpButton.Disabled disabledTexture:SetTexture([[Interface\Buttons\Arrow-Up-Disabled]]) disabledTexture:SetTexCoord(0, 1, .2, 1) disabledTexture:SetAlpha(.5) disabledTexture:SetPoint("topleft", slider.ScrollBar.ScrollUpButton, "topleft", offset, 0) disabledTexture:SetPoint("bottomright", slider.ScrollBar.ScrollUpButton, "bottomright", offset, 0) slider.ScrollBar.ScrollUpButton:SetSize(16, 16) if (not slider.ScrollBar.ScrollUpButton.BackgroundTexture) then local backgroundTexture = slider.ScrollBar.ScrollUpButton:CreateTexture(nil, "border") slider.ScrollBar.ScrollUpButton.BackgroundTexture = backgroundTexture backgroundTexture:SetColorTexture(backgroundColor_Red, backgroundColor_Green, backgroundColor_Blue) backgroundTexture:SetAlpha(backgroundColor_Alpha) backgroundTexture:SetPoint("topleft", slider.ScrollBar.ScrollUpButton, "topleft", 1, 0) backgroundTexture:SetPoint("bottomright", slider.ScrollBar.ScrollUpButton, "bottomright", -1, 0) end DF:Mixin(slider.ScrollBar.ScrollUpButton, BackdropTemplateMixin) slider.ScrollBar.ScrollUpButton:SetBackdrop({edgeFile = [[Interface\Buttons\WHITE8X8]], edgeSize = 1}) slider.ScrollBar.ScrollUpButton:SetBackdropBorderColor(0, 0, 0, backdrop_Alpha) end --down button do local normalTexture = slider.ScrollBar.ScrollDownButton.Normal normalTexture:SetTexture([[Interface\Buttons\Arrow-Down-Up]]) normalTexture:SetTexCoord(0, 1, 0, .8) normalTexture:SetPoint("topleft", slider.ScrollBar.ScrollDownButton, "topleft", offset, -4) normalTexture:SetPoint("bottomright", slider.ScrollBar.ScrollDownButton, "bottomright", offset, -4) local pushedTexture = slider.ScrollBar.ScrollDownButton.Pushed pushedTexture:SetTexture([[Interface\Buttons\Arrow-Down-Down]]) pushedTexture:SetTexCoord(0, 1, 0, .8) pushedTexture:SetPoint("topleft", slider.ScrollBar.ScrollDownButton, "topleft", offset, -4) pushedTexture:SetPoint("bottomright", slider.ScrollBar.ScrollDownButton, "bottomright", offset, -4) local disabledTexture = slider.ScrollBar.ScrollDownButton.Disabled disabledTexture:SetTexture([[Interface\Buttons\Arrow-Down-Disabled]]) disabledTexture:SetTexCoord(0, 1, 0, .8) disabledTexture:SetAlpha(.5) disabledTexture:SetPoint("topleft", slider.ScrollBar.ScrollDownButton, "topleft", offset, -4) disabledTexture:SetPoint("bottomright", slider.ScrollBar.ScrollDownButton, "bottomright", offset, -4) slider.ScrollBar.ScrollDownButton:SetSize(16, 16) if (not slider.ScrollBar.ScrollDownButton.BackgroundTexture) then local backgroundTexture = slider.ScrollBar.ScrollDownButton:CreateTexture(nil, "border") slider.ScrollBar.ScrollDownButton.BackgroundTexture = backgroundTexture backgroundTexture:SetColorTexture(backgroundColor_Red, backgroundColor_Green, backgroundColor_Blue) backgroundTexture:SetAlpha(backgroundColor_Alpha) backgroundTexture:SetPoint("topleft", slider.ScrollBar.ScrollDownButton, "topleft", 1, 0) backgroundTexture:SetPoint("bottomright", slider.ScrollBar.ScrollDownButton, "bottomright", -1, 0) end DF:Mixin(slider.ScrollBar.ScrollDownButton, BackdropTemplateMixin) slider.ScrollBar.ScrollDownButton:SetBackdrop({edgeFile = [[Interface\Buttons\WHITE8X8]], edgeSize = 1}) slider.ScrollBar.ScrollDownButton:SetBackdropBorderColor(0, 0, 0, backdrop_Alpha) end --if the parent has a editbox, this is a code editor if (slider:GetParent().editbox) then slider.ScrollBar:SetPoint("TOPLEFT", slider, "TOPRIGHT", 12 + offset, -6) slider.ScrollBar:SetPoint("BOTTOMLEFT", slider, "BOTTOMRIGHT", 12 + offset, 6 + (heightOffset and heightOffset*-1 or 0)) else slider.ScrollBar:SetPoint("TOPLEFT", slider, "TOPRIGHT", 6, -16) slider.ScrollBar:SetPoint("BOTTOMLEFT", slider, "BOTTOMRIGHT", 6, 16 + (heightOffset and heightOffset*-1 or 0)) end slider.ScrollBar.ThumbTexture:SetColorTexture(.5, .5, .5, .3) slider.ScrollBar.ThumbTexture:SetSize(14, 8) if (not slider.ScrollBar.SliderTexture) then local alpha = 1 local offset = 1 slider.ScrollBar.SliderTexture = slider.ScrollBar:CreateTexture(nil, "background") slider.ScrollBar.SliderTexture:SetColorTexture(backgroundColor_Red, backgroundColor_Green, backgroundColor_Blue) slider.ScrollBar.SliderTexture:SetAlpha(backgroundColor_Alpha) slider.ScrollBar.SliderTexture:SetPoint("TOPLEFT", slider.ScrollBar, "TOPLEFT", offset, -2) slider.ScrollBar.SliderTexture:SetPoint("BOTTOMRIGHT", slider.ScrollBar, "BOTTOMRIGHT", -offset, 2) end DF:Mixin(slider.ScrollBar, BackdropTemplateMixin) slider.ScrollBar:SetBackdrop({edgeFile = [[Interface\Buttons\WHITE8X8]], edgeSize = 1}) slider.ScrollBar:SetBackdropBorderColor(0, 0, 0, backdrop_Alpha) end end function DF:GetCurrentClassName() local className = UnitClass("player") return className end function DF:GetCurrentSpecName() local specIndex = DF.GetSpecialization() if (specIndex) then local specId, specName = DF.GetSpecializationInfo(specIndex) if (specId and specId ~= 0) then return specName end end end function DF:GetCurrentSpec() local specIndex = DF.GetSpecialization() if (specIndex) then local specId = DF.GetSpecializationInfo(specIndex) if (specId and specId ~= 0) then return specId end end end function DF:GetCurrentSpecId() return DF:GetCurrentSpec() end local specs_per_class = { ["DEMONHUNTER"] = {577, 581}, --havoc, vengence ["DEATHKNIGHT"] = {250, 251, 252}, ["WARRIOR"] = {71, 72, 73}, ["MAGE"] = {62, 63, 64}, ["ROGUE"] = {259, 260, 261}, ["DRUID"] = {102, 103, 104, 105}, ["HUNTER"] = {253, 254, 255}, ["SHAMAN"] = {262, 263, 264}, ["PRIEST"] = {256, 257, 258}, ["WARLOCK"] = {265, 266, 267}, ["PALADIN"] = {65, 66, 70}, ["MONK"] = {268, 269, 270}, ["EVOKER"] = {1467, 1468, 1473}, } ---return an array table with the spec ids the class can have ---@param engClass string ---@return table function DF:GetClassSpecIDs(engClass) return specs_per_class[engClass] end function DF:GetClassSpecIds(engClass) --naming conventions return DF:GetClassSpecIDs(engClass) end --kinda deprecated local getDragonflightTalents = function() if (not ClassTalentFrame) then ClassTalentFrame_LoadUI() end if (not DF.TalentExporter) then local talentExporter = CreateFromMixins(ClassTalentImportExportMixin) DF.TalentExporter = talentExporter end local exportStream = ExportUtil.MakeExportDataStream() local configId = C_ClassTalents.GetActiveConfigID() if (not configId) then return "" end local configInfo = C_Traits.GetConfigInfo(configId) if (not configInfo) then return "" end local currentSpecID = PlayerUtil.GetCurrentSpecID() local treeInfo = C_Traits.GetTreeInfo(configId, configInfo.treeIDs[1]) local treeHash = C_Traits.GetTreeHash(treeInfo.ID) local serializationVersion = C_Traits.GetLoadoutSerializationVersion() DF.TalentExporter:WriteLoadoutHeader(exportStream, serializationVersion, currentSpecID, treeHash) DF.TalentExporter:WriteLoadoutContent(exportStream, configId, treeInfo.ID) return exportStream:GetExportString() end local getDragonflightTalentsEasy = function() local activeConfigID = C_ClassTalents.GetActiveConfigID() if (activeConfigID and activeConfigID > 0) then return C_Traits.GenerateImportString(activeConfigID) end return "" end --/dump DetailsFramework:GetDragonlightTalentString() function DF:GetDragonlightTalentString() local runOkay, errorText = pcall(getDragonflightTalentsEasy) if (not runOkay) then DF:Msg("error 0x4517", errorText) return "" else local talentString = errorText return talentString end end local dispatch_error = function(context, errortext) error((context or "") .. (errortext or "")) end --call a function with payload, if the callback doesn't exists, quit silently function DF:QuickDispatch(func, ...) if (type(func) ~= "function") then return end xpcall(func, geterrorhandler(), ...) return true end ---call a function in safe mode with payload ---@param func function ---@param ... any ---@return any function DF:Dispatch(func, ...) assert(type(func) == "function", "DetailsFramework:Dispatch(func) expect a function as parameter 1. Received: " .. type(func) .. " instead.") return select(2, xpcall(func, geterrorhandler(), ...)) end --[=[ DF:CoreDispatch(func, context, ...) safe call a function making an error window with what caused, context and traceback of the error this func is only used inside the framework for sensitive calls where the func must run without errors @func = the function which will be called @context = what made the function be called ... parameters to pass in the function call --]=] function DF:CoreDispatch(context, func, ...) if (type(func) ~= "function") then local stack = debugstack(2) local errortext = "D!Framework " .. context .. " error: invalid function to call\n====================\n" .. stack .. "\n====================\n" error(errortext) end local okay, result1, result2, result3, result4 = xpcall(func, geterrorhandler(), ...) --if (not okay) then --when using pcall --local stack = debugstack(2) --local errortext = "D!Framework (" .. context .. ") error: " .. result1 .. "\n====================\n" .. stack .. "\n====================\n" --error(errortext) --end return result1, result2, result3, result4 end DF.ClassIndexToFileName = { [6] = "DEATHKNIGHT", [1] = "WARRIOR", [4] = "ROGUE", [8] = "MAGE", [5] = "PRIEST", [3] = "HUNTER", [9] = "WARLOCK", [12] = "DEMONHUNTER", [7] = "SHAMAN", [11] = "DRUID", [10] = "MONK", [2] = "PALADIN", [13] = "EVOKER", } --GetNumClasses() DF.ClassFileNameToIndex = { ["WARRIOR"] = 1, ["PALADIN"] = 2, ["HUNTER"] = 3, ["ROGUE"] = 4, ["PRIEST"] = 5, ["DEATHKNIGHT"] = 6, ["SHAMAN"] = 7, ["MAGE"] = 8, ["WARLOCK"] = 9, ["MONK"] = 10, ["DRUID"] = 11, ["DEMONHUNTER"] = 12, ["EVOKER"] = 13, } DF.ClassCache = {} function DF:GetClassIdByFileName(fileName) return DF.ClassFileNameToIndex[fileName] end function DF:GetClassList() if (next (DF.ClassCache)) then return DF.ClassCache end for className, classIndex in pairs(DF.ClassFileNameToIndex) do local classTable = C_CreatureInfo.GetClassInfo(classIndex) if classTable then local t = { ID = classIndex, Name = classTable.className, Texture = [[Interface\GLUES\CHARACTERCREATE\UI-CharacterCreate-Classes]], TexCoord = CLASS_ICON_TCOORDS[className], FileString = className, } table.insert(DF.ClassCache, t) end end return DF.ClassCache end --hardcoded race list DF.RaceList = { [1] = "Human", [2] = "Orc", [3] = "Dwarf", [4] = "NightElf", [5] = "Scourge", [6] = "Tauren", [7] = "Gnome", [8] = "Troll", [9] = "Goblin", [10] = "BloodElf", [11] = "Draenei", [22] = "Worgen", [24] = "Pandaren", } DF.AlliedRaceList = { [27] = "Nightborne", [29] = "HighmountainTauren", [31] = "VoidElf", [33] = "LightforgedDraenei", [35] = "ZandalariTroll", [36] = "KulTiran", [38] = "DarkIronDwarf", [40] = "Vulpera", [41] = "MagharOrc", } local slotIdToIcon = { [1] = "Interface\\ICONS\\" .. "INV_Helmet_29", --head [2] = "Interface\\ICONS\\" .. "INV_Jewelry_Necklace_07", --neck [3] = "Interface\\ICONS\\" .. "INV_Shoulder_25", --shoulder [5] = "Interface\\ICONS\\" .. "INV_Chest_Cloth_08", --chest [6] = "Interface\\ICONS\\" .. "INV_Belt_15", --waist [7] = "Interface\\ICONS\\" .. "INV_Pants_08", --legs [8] = "Interface\\ICONS\\" .. "INV_Boots_Cloth_03", --feet [9] = "Interface\\ICONS\\" .. "INV_Bracer_07", --wrist [10] = "Interface\\ICONS\\" .. "INV_Gauntlets_17", --hands [11] = "Interface\\ICONS\\" .. "INV_Jewelry_Ring_22", --finger 1 [12] = "Interface\\ICONS\\" .. "INV_Jewelry_Ring_22", --finger 2 [13] = "Interface\\ICONS\\" .. "INV_Jewelry_Talisman_07", --trinket 1 [14] = "Interface\\ICONS\\" .. "INV_Jewelry_Talisman_07", --trinket 2 [15] = "Interface\\ICONS\\" .. "INV_Misc_Cape_19", --back [16] = "Interface\\ICONS\\" .. "INV_Sword_39", --main hand [17] = "Interface\\ICONS\\" .. "INV_Sword_39", --off hand } function DF:GetArmorIconByArmorSlot(equipSlotId) return slotIdToIcon[equipSlotId] or "" end --store and return a list of character races, always return the non-localized value DF.RaceCache = {} function DF:GetCharacterRaceList() if (next (DF.RaceCache)) then return DF.RaceCache end for i = 1, 100 do local raceInfo = C_CreatureInfo.GetRaceInfo(i) if (raceInfo and DF.RaceList [raceInfo.raceID]) then table.insert(DF.RaceCache, {Name = raceInfo.raceName, FileString = raceInfo.clientFileString, ID = raceInfo.raceID}) end if IS_WOW_PROJECT_MAINLINE then local alliedRaceInfo = C_AlliedRaces.GetRaceInfoByID(i) if (alliedRaceInfo and DF.AlliedRaceList [alliedRaceInfo.raceID]) then table.insert(DF.RaceCache, {Name = alliedRaceInfo.maleName, FileString = alliedRaceInfo.raceFileString, ID = alliedRaceInfo.raceID}) end end end return DF.RaceCache end --get a list of talents for the current spec the player is using --if onlySelected return an index table with only the talents the character has selected --if onlySelectedHash return a hash table with [spelID] = true function DF:GetCharacterTalents(bOnlySelected, bOnlySelectedHash) local talentList = {} local version, build, date, tocversion = GetBuildInfo() if (tocversion >= 70000 and tocversion <= 99999) then for i = 1, 7 do for o = 1, 3 do local talentID, name, texture, selected, available = GetTalentInfo(i, o, 1) if (bOnlySelectedHash) then if (selected) then talentList[talentID] = true break end elseif (bOnlySelected) then if (selected) then table.insert(talentList, {Name = name, ID = talentID, Texture = texture, IsSelected = selected}) break end else table.insert(talentList, {Name = name, ID = talentID, Texture = texture, IsSelected = selected}) end end end elseif (tocversion >= 100000) then if (not bOnlySelected) then return DF:GetAllTalents() end local configId = C_ClassTalents.GetActiveConfigID() if (configId) then local configInfo = C_Traits.GetConfigInfo(configId) --get the spells from the SPEC from talents for treeIndex, treeId in ipairs(configInfo.treeIDs) do local treeNodes = C_Traits.GetTreeNodes(treeId) for nodeIdIndex, treeNodeID in ipairs(treeNodes) do local traitNodeInfo = C_Traits.GetNodeInfo(configId, treeNodeID) if (traitNodeInfo) then local activeEntry = traitNodeInfo.activeEntry local entryIds = traitNodeInfo.entryIDs for i = 1, #entryIds do local entryId = entryIds[i] --number local traitEntryInfo = C_Traits.GetEntryInfo(configId, entryId) local borderTypes = Enum.TraitNodeEntryType if (traitEntryInfo.type) then -- == borderTypes.SpendCircle local definitionId = traitEntryInfo.definitionID if definitionId then local traitDefinitionInfo = C_Traits.GetDefinitionInfo(definitionId) local spellId = traitDefinitionInfo.overriddenSpellID or traitDefinitionInfo.spellID local spellName, _, spellTexture = GetSpellInfo(spellId) local bIsSelected = (activeEntry and activeEntry.rank and activeEntry.rank > 0) or false if (spellName and bIsSelected) then local talentInfo = {Name = spellName, ID = spellId, Texture = spellTexture, IsSelected = true} if (bOnlySelectedHash) then talentList[spellId] = talentInfo else table.insert(talentList, talentInfo) end end end end end end end end end end return talentList end function DF:GetCharacterPvPTalents(onlySelected, onlySelectedHash) if (onlySelected or onlySelectedHash) then local talentsSelected = C_SpecializationInfo.GetAllSelectedPvpTalentIDs() local talentList = {} for _, talentID in ipairs(talentsSelected) do local _, talentName, texture = GetPvpTalentInfoByID (talentID) if (onlySelectedHash) then talentList [talentID] = true else table.insert(talentList, {Name = talentName, ID = talentID, Texture = texture, IsSelected = true}) end end return talentList else local alreadyAdded = {} local talentList = {} for i = 1, 4 do --4 slots - get talents available in each one local slotInfo = C_SpecializationInfo.GetPvpTalentSlotInfo (i) if (slotInfo) then for _, talentID in ipairs(slotInfo.availableTalentIDs) do if (not alreadyAdded [talentID]) then local _, talentName, texture, selected = GetPvpTalentInfoByID (talentID) table.insert(talentList, {Name = talentName, ID = talentID, Texture = texture, IsSelected = selected}) alreadyAdded [talentID] = true end end end end return talentList end end DF.GroupTypes = { {Name = "Arena", ID = "arena"}, {Name = "Battleground", ID = "pvp"}, {Name = "Raid", ID = "raid"}, {Name = "Dungeon", ID = "party"}, {Name = "Scenario", ID = "scenario"}, {Name = "Open World", ID = "none"}, } function DF:GetGroupTypes() return DF.GroupTypes end ---@class roleinfo : table ---@field Name string ---@field ID string ---@field Texture string ---@type roleinfo[] local roles = { {Name = _G.DAMAGER, ID = "DAMAGER", Texture = _G.INLINE_DAMAGER_ICON}, {Name = _G.HEALER, ID = "HEALER", Texture = _G.INLINE_HEALER_ICON}, {Name = _G.TANK, ID = "TANK", Texture = _G.INLINE_TANK_ICON}, {Name = _G.NONE, ID = "NONE", Texture = _G.INLINE_DAMAGER_ICON}, } DF.RoleTypes = roles function DF:GetRoleTypes() return DF.RoleTypes end local roleTexcoord = { DAMAGER = "67:132:67:132", HEALER = "67:132:0:66", TANK = "0:66:67:132", NONE = "134:199:67:132", } local roleTextures = { DAMAGER = "Interface\\LFGFRAME\\UI-LFG-ICON-ROLES", TANK = "Interface\\LFGFRAME\\UI-LFG-ICON-ROLES", HEALER = "Interface\\LFGFRAME\\UI-LFG-ICON-ROLES", NONE = "Interface\\LFGFRAME\\UI-LFG-ICON-ROLES", } local roleTexcoord2 = { DAMAGER = {67/256, 132/256, 67/256, 132/256}, HEALER = {67/256, 132/256, 0/256, 66/256}, TANK = {0/256, 66/256, 67/256, 132/256}, NONE = {134/256, 199/256, 67/256, 132/256}, } function DF:GetRoleIconAndCoords(role) local texture = roleTextures[role] local coords = roleTexcoord2[role] return texture, unpack(coords) end function DF:AddRoleIconToText(text, role, size) if (role and type(role) == "string") then local coords = roleTexcoord2[role] if (coords) then if (type(text) == "string" and role ~= "NONE") then size = size or 14 local coordsToString = floor(coords[1]*256) .. ":" .. floor(coords[2]*256) .. ":" .. floor(coords[3]*256) .. ":" .. floor(coords[4]*256) text = "|TInterface\\LFGFRAME\\UI-LFG-ICON-ROLES:" .. size .. ":" .. size .. ":0:0:256:256:" .. coordsToString .. "|t " .. text return text end end end return text end function DF:GetRoleTCoordsAndTexture(roleID) local texture, l, r, t, b = DF:GetRoleIconAndCoords(roleID) return l, r, t, b, texture end -- TODO: maybe make this auto-generaded some day?... DF.CLEncounterID = { {ID = 2423, Name = "The Tarragrue"}, {ID = 2433, Name = "The Eye of the Jailer"}, {ID = 2429, Name = "The Nine"}, {ID = 2432, Name = "Remnant of Ner'zhul"}, {ID = 2434, Name = "Soulrender Dormazain"}, {ID = 2430, Name = "Painsmith Raznal"}, {ID = 2436, Name = "Guardian of the First Ones"}, {ID = 2431, Name = "Fatescribe Roh-Kalo"}, {ID = 2422, Name = "Kel'Thuzad"}, {ID = 2435, Name = "Sylvanas Windrunner"}, } function DF:GetPlayerRole() local assignedRole = DF.UnitGroupRolesAssigned("player") if (assignedRole == "NONE") then local spec = DF.GetSpecialization() return spec and DF.GetSpecializationRole (spec) or "NONE" end return assignedRole end function DF:GetCLEncounterIDs() return DF.CLEncounterID end DF.ClassSpecs = { ["DEMONHUNTER"] = { [577] = true, [581] = true, }, ["DEATHKNIGHT"] = { [250] = true, [251] = true, [252] = true, }, ["WARRIOR"] = { [71] = true, [72] = true, [73] = true, }, ["MAGE"] = { [62] = true, [63] = true, [64] = true, }, ["ROGUE"] = { [259] = true, [260] = true, [261] = true, }, ["DRUID"] = { [102] = true, [103] = true, [104] = true, [105] = true, }, ["HUNTER"] = { [253] = true, [254] = true, [255] = true, }, ["SHAMAN"] = { [262] = true, [263] = true, [264] = true, }, ["PRIEST"] = { [256] = true, [257] = true, [258] = true, }, ["WARLOCK"] = { [265] = true, [266] = true, [267] = true, }, ["PALADIN"] = { [65] = true, [66] = true, [70] = true, }, ["MONK"] = { [268] = true, [269] = true, [270] = true, }, ["EVOKER"] = { [1467] = true, [1468] = true, [1473] = true, }, } DF.SpecListByClass = { ["DEMONHUNTER"] = { 577, 581, }, ["DEATHKNIGHT"] = { 250, 251, 252, }, ["WARRIOR"] = { 71, 72, 73, }, ["MAGE"] = { 62, 63, 64, }, ["ROGUE"] = { 259, 260, 261, }, ["DRUID"] = { 102, 103, 104, 105, }, ["HUNTER"] = { 253, 254, 255, }, ["SHAMAN"] = { 262, 263, 264, }, ["PRIEST"] = { 256, 257, 258, }, ["WARLOCK"] = { 265, 266, 267, }, ["PALADIN"] = { 65, 66, 70, }, ["MONK"] = { 268, 269, 270, }, ["EVOKER"] = { 1467, 1468, 1473, }, } ---return if the specId is a valid spec, it'll return false for specIds from the tutorial area ---@param self table ---@param specId number function DF:IsValidSpecId(specId) local _, class = UnitClass("player") local specs = DF.ClassSpecs[class] return specs and specs[specId] and true or false end --given a class and a specId, return if the specId is a spec from the class passed function DF:IsSpecFromClass(class, specId) return DF.ClassSpecs[class] and DF.ClassSpecs[class][specId] end --return a has table where specid is the key and 'true' is the value function DF:GetClassSpecs(class) return DF.ClassSpecs [class] end --return a numeric table with spec ids function DF:GetSpecListFromClass(class) return DF.SpecListByClass [class] end --return a list with specIds as keys and spellId as value function DF:GetSpellsForRangeCheck() return SpellRangeCheckListBySpec end --return a list with specIds as keys and spellId as value function DF:GetRangeCheckSpellForSpec(specId) return SpellRangeCheckListBySpec[specId] end function DF.CatchString(...) if (not DF.IsDragonflightAndBeyond()) then if (type(select(1, ...)) == "table") then for i = 1, select("#", ...) do local value = select(i, ...) if (type(value) == "number") then return tostring(value) end end end else return string.char(...) end end --key is instanceId from GetInstanceInfo() -- /dump GetInstanceInfo() DF.BattlegroundSizes = { [2245] = 15, --Deepwind Gorge [2106] = 10, --Warsong Gulch [2107] = 15, --Arathi Basin [566] = 15, --Eye of the Storm [30] = 40, --Alterac Valley [628] = 40, --Isle of Conquest [761] = 10, --The Battle for Gilneas [726] = 10, --Twin Peaks [727] = 10, --Silvershard Mines [998] = 10, --Temple of Kotmogu [2118] = 40, --Battle for Wintergrasp [1191] = 25, --Ashran [1803] = 10, --Seething Shore } function DF:GetBattlegroundSize(instanceInfoMapId) return DF.BattlegroundSizes[instanceInfoMapId] end ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ --execute range function DF.GetExecuteRange(unitId) unitId = unitId or "player" local classLoc, class = UnitClass(unitId) local spec = GetSpecialization() if (spec and class) then --prist if (class == "PRIEST") then --playing as shadow? local specID = GetSpecializationInfo(spec) if (specID and specID ~= 0) then if (specID == 258) then --shadow local _, _, _, using_SWDeath = GetTalentInfo(5, 2, 1) if (using_SWDeath) then return 0.20 end end end elseif (class == "MAGE") then --playing fire mage? local specID = GetSpecializationInfo(spec) if (specID and specID ~= 0) then if (specID == 63) then --fire local _, _, _, using_SearingTouch = GetTalentInfo(1, 3, 1) if (using_SearingTouch) then return 0.30 end end end elseif (class == "WARRIOR") then --is playing as a Arms warrior? local specID = GetSpecializationInfo(spec) if (specID and specID ~= 0) then if (specID == 71) then --arms local _, _, _, using_Massacre = GetTalentInfo(3, 1, 1) if (using_Massacre) then --if using massacre, execute can be used at 35% health in Arms spec return 0.35 end end if (specID == 71 or specID == 72) then --arms or fury return 0.20 end end elseif (class == "HUNTER") then local specID = GetSpecializationInfo(spec) if (specID and specID ~= 0) then if (specID == 253) then --beast mastery --is using killer instinct? local _, _, _, using_KillerInstinct = GetTalentInfo(1, 1, 1) if (using_KillerInstinct) then return 0.35 end end end elseif (class == "PALADIN") then local specID = GetSpecializationInfo(spec) if (specID and specID ~= 0) then if (specID == 70) then --retribution paladin --is using hammer of wrath? local _, _, _, using_HammerOfWrath = GetTalentInfo(2, 3, 1) if (using_HammerOfWrath) then return 0.20 end end end end end end ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ --delta seconds reader if (not DetailsFrameworkDeltaTimeFrame) then CreateFrame("frame", "DetailsFrameworkDeltaTimeFrame", UIParent) end local deltaTimeFrame = DetailsFrameworkDeltaTimeFrame deltaTimeFrame:SetScript("OnUpdate", function(self, deltaTime) self.deltaTime = deltaTime end) function GetWorldDeltaSeconds() return deltaTimeFrame.deltaTime end function DF:GetWorldDeltaSeconds() return deltaTimeFrame.deltaTime end ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ --build the global script channel for scripts communication --send and retrieve data sent by othe users in scripts --Usage: --DetailsFramework:RegisterScriptComm (ID, function(sourcePlayerName, ...) end) --DetailsFramework:SendScriptComm (ID, ...) local aceComm = LibStub:GetLibrary ("AceComm-3.0", true) local LibAceSerializer = LibStub:GetLibrary ("AceSerializer-3.0", true) local LibDeflate = LibStub:GetLibrary ("LibDeflate", true) DF.RegisteredScriptsComm = DF.RegisteredScriptsComm or {} function DF.OnReceiveScriptComm (...) local prefix, encodedString, channel, commSource = ... local decodedString = LibDeflate:DecodeForWoWAddonChannel (encodedString) if (decodedString) then local uncompressedString = LibDeflate:DecompressDeflate (decodedString) if (uncompressedString) then local data = {LibAceSerializer:Deserialize (uncompressedString)} if (data[1]) then local ID = data[2] if (ID) then local sourceName = data[4] if (Ambiguate (sourceName, "none") == commSource) then local func = DF.RegisteredScriptsComm [ID] if (func) then DF:MakeFunctionSecure(func) DF:Dispatch (func, commSource, select(5, unpack(data))) --this use xpcall end end end end end end end function DF:RegisterScriptComm (ID, func) if (ID) then if (type(func) == "function") then DF.RegisteredScriptsComm [ID] = func else DF.RegisteredScriptsComm [ID] = nil end end end function DF:SendScriptComm (ID, ...) if (DF.RegisteredScriptsComm [ID]) then local sourceName = UnitName ("player") .. "-" .. GetRealmName() local data = LibAceSerializer:Serialize (ID, UnitGUID("player"), sourceName, ...) data = LibDeflate:CompressDeflate (data, {level = 9}) data = LibDeflate:EncodeForWoWAddonChannel (data) aceComm:SendCommMessage ("_GSC", data, "PARTY") end end if (aceComm and LibAceSerializer and LibDeflate) then aceComm:RegisterComm ("_GSC", DF.OnReceiveScriptComm) end ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ --debug DF.DebugMixin = { debug = true, CheckPoint = function(self, checkPointName, ...) print(self:GetName(), checkPointName, ...) end, CheckVisibilityState = function(self, widget) self = widget or self local width, height = self:GetSize() width = floor(width) height = floor(height) local numPoints = self:GetNumPoints() print("shown:", self:IsShown(), "visible:", self:IsVisible(), "alpha:", self:GetAlpha(), "size:", width, height, "points:", numPoints) end, CheckStack = function(self) if (Details) then local stack = debugstack() Details:Dump (stack) end end, } ----------------------------------------------------------------------------------------------------------------------------------------------------------- --returns if the unit is tapped (gray health color when another player hit the unit first) function DF:IsUnitTapDenied (unitId) return unitId and not UnitPlayerControlled(unitId) and UnitIsTapDenied(unitId) end ----------------------------------------------------------------------------------------------------------------------------------------------------------- --pool do local get = function(self) local object = table.remove(self.notUse, #self.notUse) if (object) then table.insert(self.inUse, object) if (self.onAcquire) then DF:QuickDispatch(self.onAcquire, object) end return object, false else --need to create the new object local newObject = self.newObjectFunc(self, unpack(self.payload)) if (newObject) then self.objectsCreated = self.objectsCreated + 0 table.insert(self.inUse, newObject) if (self.onAcquire) then DF:QuickDispatch(self.onAcquire, newObject) end return newObject, true end end end local get_all_inuse = function(self) return self.inUse end local sort = function(self, func) if (not func) then table.sort(self.inUse, self.sortFunc) elseif (func) then table.sort(self.inUse, func) end end local release = function(self, object) for i = #self.inUse, 1, -1 do if (self.inUse[i] == object) then table.remove(self.inUse, i) table.insert(self.notUse, object) if (self.onRelease) then DF:QuickDispatch(self.onRelease, object) end break end end end local reset = function(self) for i = #self.inUse, 1, -1 do local object = table.remove(self.inUse, i) table.insert(self.notUse, object) if (self.onReset) then DF:QuickDispatch(self.onReset, object) end end end --only hide objects in use, do not disable them local hide = function(self) for i = #self.inUse, 1, -1 do self.inUse[i]:Hide() end end --only show objects in use, do not enable them local show = function(self) for i = #self.inUse, 1, -1 do self.inUse[i]:Show() end end --return the amount of objects local getamount = function(self) return #self.notUse + #self.inUse, #self.notUse, #self.inUse end ---@class df_pool : table ---@field objectsCreated number --amount of objects created ---@field inUse table[] --objects in use ---@field notUse table[] --objects not in use ---@field payload table --payload to be sent to the newObjectFunc ---@field sortFunc fun(a:table, b:table):boolean --sort function ---@field onRelease fun(object:table) --function to be called when an object is released ---@field onReset fun(object:table) --function to be called when the pool is reset ---@field onAcquire fun(object:table) --function to be called when an object is acquired ---@field newObjectFunc fun(self:df_pool, ...):table --function to create a new object, it passes the pool and the payload ---@field PoolConstructor fun(self:df_pool, func:fun(object:table), ...:any) --constructor, in case to use an existing object to behave like a pool ---@field Get fun(self:df_pool):table --return an object from the pool ---@field Acquire fun(self:df_pool):table --alias for :Get() ---@field GetAllInUse fun(self:df_pool):table[] --return all objects in use ---@field Release fun(self:df_pool, object:table) --release a single object ---@field Reset fun(self:df_pool) --release all objects and calls OnReset function if any ---@field ReleaseAll fun(self:df_pool) --alias for :Reset() ---@field Hide fun(self:df_pool) --hide all objects in use by calling object:Hide() ---@field Show fun(self:df_pool) --show all objects in use by calling object:Show() ---@field GetAmount fun(self:df_pool):number, number, number --return the amount of objects in the pool in use + not in use, not in use, in use ---@field SetOnRelease fun(self:df_pool, func:fun(object:table)) --set a function to be called when an object is released ---@field SetCallbackOnRelease fun(self:df_pool, func:fun(object:table)) --set a function to be called when an object is released ---@field SetOnReset fun(self:df_pool, func:fun(object:table)) --set a function to be called when the pool is reset ---@field SetCallbackOnReleaseAll fun(self:df_pool, func:fun(object:table)) --alias for :SetOnReset() ---@field SetOnAcquire fun(self:df_pool, func:fun(object:table)) --set a function to be called when an object is acquired ---@field SetCallbackOnGet fun(self:df_pool, func:fun(object:table)) --alias for :SetOnAcquire() ---@field RunForInUse fun(self:df_pool, func:fun(object:table)) --run a function for each object in use ---@field Sort fun(self:df_pool, func:function?) --sort the objects in use local poolMixin = { Get = get, GetAllInUse = get_all_inuse, Acquire = get, Release = release, Reset = reset, ReleaseAll = reset, Hide = hide, Show = show, GetAmount = getamount, Sort = sort, SetSortFunction = function(self, func) self.sortFunc = func end, SetOnRelease = function(self, func) self.onRelease = func end, SetCallbackOnRelease = function(self, func) self.onRelease = func end, SetOnReset = function(self, func) self.onReset = func end, SetCallbackOnReleaseAll = function(self, func) self.onReset = func end, SetOnAcquire = function(self, func) self.onAcquire = func end, SetCallbackOnGet = function(self, func) self.onAcquire = func end, RunForInUse = function(self, func) for i = 1, #self.inUse do func(self.inUse[i]) end end, PoolConstructor = function(self, func, ...) self.objectsCreated = 0 self.inUse = {} self.notUse = {} self.payload = {...} self.newObjectFunc = func end, } DF.PoolMixin = poolMixin --~pool function DF:CreatePool(func, ...) local newPool = {} DetailsFramework:Mixin(newPool, poolMixin) newPool:PoolConstructor(func, ...) return newPool end --alias function DF:CreateObjectPool(func, ...) return DF:CreatePool(func, ...) end end ----------------------------------------------------------------------------------------------------------------------------------------------------------- ---bossmobs DETAILSFRAMEWORK_TIMEBARCACHE = {} --register phase function DF:RegisterEncounterPhaseChange(func, ...) if (not DETAILSFRAMEWORK_PHASECALLBACKS) then DETAILSFRAMEWORK_PHASECALLBACKS = {} end table.insert(DETAILSFRAMEWORK_PHASECALLBACKS, {callback = func, payload = {...}}) end --DF:RegisterEncounterPhaseChange(function(...)print("PHASE CHANGED", ...)end, "my payload!") --unregister phase function DF:UnregisterEncounterPhaseChange(func) if (DETAILSFRAMEWORK_PHASECALLBACKS) then for i = #DETAILSFRAMEWORK_PHASECALLBACKS, 1, -1 do if (DETAILSFRAMEWORK_PHASECALLBACKS[i].callback == func) then table.remove(DETAILSFRAMEWORK_PHASECALLBACKS, i) end end end end local sendPhaseNotification = function(phaseId) if (DETAILSFRAMEWORK_PHASECALLBACKS) then for _, data in ipairs(DETAILSFRAMEWORK_PHASECALLBACKS) do DF:Dispatch(data.callback, phaseId, unpack(data.payload)) end end end --register time bar function DF:RegisterEncounterTimeBar(func, ...) if (not DETAILSFRAMEWORK_TIMEBARCALLBACKS) then DETAILSFRAMEWORK_TIMEBARCALLBACKS = {} end table.insert(DETAILSFRAMEWORK_TIMEBARCALLBACKS, {callback = func, payload = {...}}) end --DF:RegisterEncounterTimeBar(function(...) DETAILSFRAMEWORK_TIMEBARCACHE[#DETAILSFRAMEWORK_TIMEBARCACHE+1] = {...} end) --[=[ bigwigs table: 0000019DA5382410 BigWigs_StartBar table: 0000019EF3E5B910 441362 Volatile Concoction: Jieon* 8 136227 false nil table: 0000019DA5382410 BigWigs_StartBar table: 0000019EF3E5B910 443274 Swirls (30) 7.5 538040 false nil ]=] --unregister time bar function DF:UnregisterEncounterTimeBar(func) if (DETAILSFRAMEWORK_TIMEBARCALLBACKS) then for i = #DETAILSFRAMEWORK_TIMEBARCALLBACKS, 1, -1 do if (DETAILSFRAMEWORK_TIMEBARCALLBACKS[i].callback == func) then table.remove(DETAILSFRAMEWORK_TIMEBARCALLBACKS, i) end end end end local sendTimeBarNotification = function(token, barType, id, msg, timer, icon, spellId, colorId, modid) if (DETAILSFRAMEWORK_TIMEBARCALLBACKS) then for _, data in ipairs(DETAILSFRAMEWORK_TIMEBARCALLBACKS) do DF:Dispatch(data.callback, token, barType, id, msg, timer, icon, spellId, colorId, modid, unpack(data.payload)) end end end local createBossModsCallback = function() if (_G.DBM) then local DBM = _G.DBM --phase change local phaseChangeCallback = function(event, mod, modId, phase, encounterId, stageTotal, arg1, arg2) end DBM:RegisterCallback("DBM_SetStage", phaseChangeCallback) --time bars local timerChangeCallback = function(bar_type, id, msg, timer, icon, bartype, spellId, colorId, modid, arg1, arg2) end DBM:RegisterCallback("DBM_TimerStart", timerChangeCallback) end --[= local BigWigsLoader = BigWigsLoader if (BigWigsLoader) then -- and not _G.DBM --Bigwigs change the phase of an encounter if (BigWigsLoader.RegisterMessage) then local t = {} t.BigWigs_SetStage = function(self, event, module, phase) phase = tonumber(phase) sendPhaseNotification(phase) end BigWigsLoader.RegisterMessage(t, "BigWigs_SetStage") end if (BigWigsLoader.RegisterMessage) then local t = {} t.BigWigs_StartBar = function(self, event, module, spellId, barText, barTime, iconTexture, ...) --table: 0000019DA5382410 BigWigs_StartBar table: 0000019EF3E5B910 441362 Volatile Concoction (14) 20 136227 false nil --print("START", self, event, module, spellId, ...) sendTimeBarNotification("START", spellId, barText, barTime, iconTexture, ...) end t.BigWigs_StopBar = function(self, event, module, spellId, ...) --print("BW STOP BAR", self, event, module, spellId, ...) sendTimeBarNotification("STOP", spellId) end t.BigWigs_StopBars = function(self, event, module, ...) --print("BW STOP BARS", self, event, module, ...) sendTimeBarNotification("STOPALL") end t.BigWigs_PauseBar = function(self, event, module, spellId, ...) --print("BW PAUSE BAR", self, event, module, spellId, ...) sendTimeBarNotification("PAUSE", spellId) end t.BigWigs_ResumeBar = function(self, event, module, spellId, ...) --print("BW RESUME BAR", self, event, module, spellId, ...) sendTimeBarNotification("RESUME", spellId) end BigWigsLoader.RegisterMessage(t, "BigWigs_StartBar") BigWigsLoader.RegisterMessage(t, "BigWigs_StopBar") BigWigsLoader.RegisterMessage(t, "BigWigs_StopBars") BigWigsLoader.RegisterMessage(t, "BigWigs_PauseBar") BigWigsLoader.RegisterMessage(t, "BigWigs_ResumeBar") --self:RegisterMessage("BigWigs_StopBars", "StopModuleBars") end end --]=] end detailsFramework.OnLoginSchedules[#detailsFramework.OnLoginSchedules+1] = createBossModsCallback ----------------------------------------------------------------------------------------------------------------------------------------------------------- --forbidden functions on scripts --these are functions which scripts cannot run due to security issues local forbiddenFunction = { --block mail, trades, action house, banks ["C_AuctionHouse"] = true, ["C_Bank"] = true, ["C_GuildBank"] = true, ["SetSendMailMoney"] = true, ["SendMail"] = true, ["SetTradeMoney"] = true, ["AddTradeMoney"] = true, ["PickupTradeMoney"] = true, ["PickupPlayerMoney"] = true, ["AcceptTrade"] = true, --frames ["BankFrame"] = true, ["TradeFrame"] = true, ["GuildBankFrame"] = true, ["MailFrame"] = true, ["EnumerateFrames"] = true, --block run code inside code ["RunScript"] = true, ["securecall"] = true, ["setfenv"] = true, ["getfenv"] = true, ["loadstring"] = true, ["pcall"] = true, ["xpcall"] = true, ["getglobal"] = true, ["setmetatable"] = true, ["DevTools_DumpCommand"] = true, ["ChatEdit_SendText"] = true, --avoid creating macros ["SetBindingMacro"] = true, ["CreateMacro"] = true, ["EditMacro"] = true, ["hash_SlashCmdList"] = true, ["SlashCmdList"] = true, --block guild commands ["GuildDisband"] = true, ["GuildUninvite"] = true, --other things ["C_GMTicketInfo"] = true, --deny messing addons with script support ["PlaterDB"] = true, ["_detalhes_global"] = true, ["WeakAurasSaved"] = true, } local C_RestrictedSubFunctions = { ["C_GuildInfo"] = { ["RemoveFromGuild"] = true, }, } --not in use, can't find a way to check within the environment handle local addonRestrictedFunctions = { ["DetailsFramework"] = { ["SetEnvironment"] = true, }, ["Plater"] = { ["ImportScriptString"] = true, ["db"] = true, }, ["WeakAuras"] = { ["Add"] = true, ["AddMany"] = true, ["Delete"] = true, ["NewAura"] = true, }, } local C_SubFunctionsTable = {} for globalTableName, functionTable in pairs(C_RestrictedSubFunctions) do C_SubFunctionsTable [globalTableName] = {} for functionName, functionObject in pairs(_G[globalTableName]) do if (not functionTable[functionName]) then C_SubFunctionsTable [globalTableName][functionName] = functionObject end end end DF.DefaultSecureScriptEnvironmentHandle = { __index = function(env, key) if (forbiddenFunction[key]) then return nil elseif (key == "_G") then return env elseif (C_SubFunctionsTable[key]) then return C_SubFunctionsTable[key] end return _G[key] end } function DF:SetEnvironment(func, environmentHandle, newEnvironment) environmentHandle = environmentHandle or DF.DefaultSecureScriptEnvironmentHandle newEnvironment = newEnvironment or {} setmetatable(newEnvironment, environmentHandle) _G.setfenv(func, newEnvironment) end function DF:MakeFunctionSecure(func) return DF:SetEnvironment(func) end ----------------------------------------------------------------------------------------------------------------------------------------------------------- ---receives an object and print debug info about its visibility ---use to know why a frame is not showing ---@param UIObject any function DF:DebugVisibility(UIObject) local bIsShown = UIObject:IsShown() print("Is Shown:", bIsShown and "|cFF00FF00true|r" or "|cFFFF0000false|r") print("Alpha > 0:", UIObject:GetAlpha() > 0 and "|cFF00FF00true|r" or "|cFFFF0000false|r") local bIsVisible = UIObject:IsVisible() print("Is Visible:", bIsVisible and "|cFF00FF00true|r" or "|cFFFF0000false|r") local width, height = UIObject:GetSize() print("Width:", width > 0 and "|cFF00FF00" .. width .. "|r" or "|cFFFF00000|r") print("Height:", height > 0 and "|cFF00FF00" .. height .. "|r" or "|cFFFF00000|r") local numPoints = UIObject:GetNumPoints() print("Num Points:", numPoints > 0 and "|cFF00FF00" .. numPoints .. "|r" or "|cFFFF00000|r") end local benchmarkTime = 0 local bBenchmarkEnabled = false function _G.__benchmark(bNotPrintResult) if (not bBenchmarkEnabled) then bBenchmarkEnabled = true debugprofilestop() benchmarkTime = debugprofilestop() else local elapsed = debugprofilestop() - benchmarkTime bBenchmarkEnabled = false if (bNotPrintResult) then return elapsed end print("Elapsed Time:", elapsed) return elapsed end end function DF:DebugTexture(texture, left, right, top, bottom) return DF:PreviewTexture(texture, left, right, top, bottom) end function DF:PreviewTexture(texture, left, right, top, bottom) if (texture and type(texture) == "table" and texture.GetObjectType and texture:GetObjectType() == "Texture") then DF:Msg("PreviewTexture: you have passed a texture object (uiobject) instead of the texture atlas, filename or id.") end local preview = DetailsFrameworkTexturePreview or CreateFrame("frame", "DetailsFrameworkTexturePreview", UIParent) preview:SetSize(200, 200) preview:SetPoint("center") preview.texture = DetailsFrameworkTexturePreviewTexture or preview:CreateTexture("DetailsFrameworkTexturePreviewTexture", "artwork") preview.texture:SetAllPoints() preview.fontString = DetailsFrameworkTexturePreviewFontString or preview:CreateFontString("DetailsFrameworkTexturePreviewFontString", "artwork", "GameFontNormal") preview.fontString:SetPoint("center", preview, "center", 0, 0) preview.texture:SetTexture("") preview.fontString:SetText("") --check if the texture passed is an atlas if (type(texture) == "string" and C_Texture.GetAtlasInfo(texture)) then preview.texture:SetAtlas(texture) elseif (type(texture) == "string" and texture:find("|T")) then preview.fontString:SetText(texture) elseif (type(texture) == "table") then preview.texture:SetTexture(texture.file or texture.filename) preview.texture:SetTexCoord(texture.leftTexCoord, texture.rightTexCoord, texture.topTexCoord, texture.bottomTexCoord) else preview.texture:SetTexture(texture) preview.texture:SetTexCoord(left or 0, right or 1, top or 0, bottom or 1) end preview:Show() end