commit e2015fd9bb7b009e1384513c7f266ed3e3db42c5 Author: Gitea Date: Fri Nov 13 14:27:50 2020 -0500 initial commit diff --git a/Bindings.xml b/Bindings.xml new file mode 100644 index 0000000..750abbe --- /dev/null +++ b/Bindings.xml @@ -0,0 +1,14 @@ + + + RaidMark_HotkeyPressed(keystate) + + + HideLeftChat() + + + HideRightChat() + + + HideBothChat() + + diff --git a/Core/API.lua b/Core/API.lua new file mode 100644 index 0000000..e83c69b --- /dev/null +++ b/Core/API.lua @@ -0,0 +1,634 @@ +------------------------------------------------------------------------ +-- Collection of functions that can be used in multiple places +------------------------------------------------------------------------ +local E, L, V, P, G = unpack(select(2, ...)) + +local _G = _G +local wipe, date = wipe, date +local format, select, type, ipairs, pairs = format, select, type, ipairs, pairs +local strmatch, strfind, tonumber, tostring = strmatch, strfind, tonumber, tostring +local strlen, CreateFrame = strlen, CreateFrame +local GetAddOnEnableState = GetAddOnEnableState +local GetBattlefieldArenaFaction = GetBattlefieldArenaFaction +local GetCVar, SetCVar = GetCVar, SetCVar +local GetCVarBool = GetCVarBool +local GetFunctionCPUUsage = GetFunctionCPUUsage +local GetInstanceInfo = GetInstanceInfo +local GetSpecialization = GetSpecialization +local GetSpecializationRole = GetSpecializationRole +local InCombatLockdown = InCombatLockdown +local IsAddOnLoaded = IsAddOnLoaded +local IsWargame = IsWargame +local PLAYER_FACTION_GROUP = PLAYER_FACTION_GROUP +local RequestBattlefieldScoreData = RequestBattlefieldScoreData +local UIParentLoadAddOn = UIParentLoadAddOn +local UnitAttackPower = UnitAttackPower +local UnitFactionGroup = UnitFactionGroup +local UnitGroupRolesAssigned = UnitGroupRolesAssigned +local UnitHasVehicleUI = UnitHasVehicleUI +local UnitIsMercenary = UnitIsMercenary +local UnitStat = UnitStat +local C_PetBattles_IsInBattle = C_PetBattles.IsInBattle +local C_PvP_IsRatedBattleground = C_PvP.IsRatedBattleground +local FACTION_HORDE = FACTION_HORDE +local FACTION_ALLIANCE = FACTION_ALLIANCE +local ERR_NOT_IN_COMBAT = ERR_NOT_IN_COMBAT +-- GLOBALS: ElvDB + +function E:ClassColor(class, usePriestColor) + if not class then return end + + local color = (_G.CUSTOM_CLASS_COLORS and _G.CUSTOM_CLASS_COLORS[class]) or _G.RAID_CLASS_COLORS[class] + if type(color) ~= 'table' then return end + + if not color.colorStr then + color.colorStr = E:RGBToHex(color.r, color.g, color.b, 'ff') + elseif strlen(color.colorStr) == 6 then + color.colorStr = 'ff'..color.colorStr + end + + if usePriestColor and class == 'PRIEST' and tonumber(color.colorStr, 16) > tonumber(E.PriestColors.colorStr, 16) then + return E.PriestColors + else + return color + end +end + +do -- other non-english locales require this + E.UnlocalizedClasses = {} + for k, v in pairs(_G.LOCALIZED_CLASS_NAMES_MALE) do E.UnlocalizedClasses[v] = k end + for k, v in pairs(_G.LOCALIZED_CLASS_NAMES_FEMALE) do E.UnlocalizedClasses[v] = k end + + function E:UnlocalizedClassName(className) + return (className and className ~= '') and E.UnlocalizedClasses[className] + end +end + +function E:IsFoolsDay() + return strfind(date(), '04/01/') and not E.global.aprilFools +end + +do + local essenceTextureID = 2975691 + function E:ScanTooltipTextures() + local tt = E.ScanTooltip + + if not tt.gems then + tt.gems = {} + else + wipe(tt.gems) + end + + if not tt.essences then + tt.essences = {} + else + for _, essences in pairs(tt.essences) do + wipe(essences) + end + end + + local step = 1 + for i = 1, 10 do + local tex = _G['ElvUI_ScanTooltipTexture'..i] + local texture = tex and tex:IsShown() and tex:GetTexture() + if texture then + if texture == essenceTextureID then + local selected = (tt.gems[i-1] ~= essenceTextureID and tt.gems[i-1]) or nil + if not tt.essences[step] then tt.essences[step] = {} end + + tt.essences[step][1] = selected --essence texture if selected or nil + tt.essences[step][2] = tex:GetAtlas() --atlas place 'tooltip-heartofazerothessence-major' or 'tooltip-heartofazerothessence-minor' + tt.essences[step][3] = texture --border texture placed by the atlas + --`CollectEssenceInfo` will add 4 (hex quality color) and 5 (essence name) + + step = step + 1 + + if selected then + tt.gems[i-1] = nil + end + else + tt.gems[i] = texture + end + end + end + + return tt.gems, tt.essences + end +end + +function E:GetPlayerRole() + local assignedRole = UnitGroupRolesAssigned('player') + if assignedRole == 'NONE' then + return E.myspec and GetSpecializationRole(E.myspec) + end + + return assignedRole +end + +function E:CheckRole() + self.myspec = GetSpecialization() + self.myrole = E:GetPlayerRole() + + -- myrole = group role; TANK, HEALER, DAMAGER + -- role = class role; Tank, Melee, Caster + + local role + if type(self.ClassRole[self.myclass]) == 'string' then + role = self.ClassRole[self.myclass] + elseif self.myspec then + role = self.ClassRole[self.myclass][self.myspec] + end + + if not role then + local playerint = select(2, UnitStat('player', 4)) + local playeragi = select(2, UnitStat('player', 2)) + local base, posBuff, negBuff = UnitAttackPower('player') + local playerap = base + posBuff + negBuff + + role = ((playerap > playerint) or (playeragi > playerint)) and 'Melee' or 'Caster' + end + + if self.role ~= role then + self.role = role + self.callbacks:Fire('RoleChanged') + end + + local dispel = self.DispelClasses[self.myclass] + if self.myrole and (self.myclass ~= 'PRIEST' and dispel ~= nil) then + dispel.Magic = (self.myrole == 'HEALER') + end +end + +function E:IsDispellableByMe(debuffType) + local dispel = self.DispelClasses[self.myclass] + return dispel and dispel[debuffType] +end + +do + local function SetOriginalHeight(f) + if InCombatLockdown() then + E:RegisterEventForObject('PLAYER_REGEN_ENABLED', SetOriginalHeight, SetOriginalHeight) + return + end + + E.UIParent:SetHeight(E.UIParent.origHeight) + + if f == SetOriginalHeight then + E:UnregisterEventForObject('PLAYER_REGEN_ENABLED', SetOriginalHeight, SetOriginalHeight) + end + end + + local function SetModifiedHeight(f) + if InCombatLockdown() then + E:RegisterEventForObject('PLAYER_REGEN_ENABLED', SetModifiedHeight, SetModifiedHeight) + return + end + + E.UIParent:SetHeight(E.UIParent.origHeight - (_G.OrderHallCommandBar:GetHeight() + E.Border)) + + if f == SetModifiedHeight then + E:UnregisterEventForObject('PLAYER_REGEN_ENABLED', SetModifiedHeight, SetModifiedHeight) + end + end + + --This function handles disabling of OrderHall Bar or resizing of ElvUIParent if needed + function E:HandleCommandBar() + if E.global.general.commandBarSetting == 'DISABLED' then + _G.OrderHallCommandBar:UnregisterAllEvents() + _G.OrderHallCommandBar:SetScript('OnShow', _G.OrderHallCommandBar.Hide) + _G.OrderHallCommandBar:Hide() + _G.UIParent:UnregisterEvent('UNIT_AURA') --Only used for OrderHall Bar + elseif E.global.general.commandBarSetting == 'ENABLED_RESIZEPARENT' then + _G.OrderHallCommandBar:HookScript('OnShow', SetModifiedHeight) + _G.OrderHallCommandBar:HookScript('OnHide', SetOriginalHeight) + end + end +end + +do + local Masque = E.Libs.Masque + local MasqueGroupState = {} + local MasqueGroupToTableElement = { + ['ActionBars'] = {'actionbar', 'actionbars'}, + ['Pet Bar'] = {'actionbar', 'petBar'}, + ['Stance Bar'] = {'actionbar', 'stanceBar'}, + ['Buffs'] = {'auras', 'buffs'}, + ['Debuffs'] = {'auras', 'debuffs'}, + } + + function E:MasqueCallback(Group, _, _, _, _, Disabled) + if not E.private then return end + local element = MasqueGroupToTableElement[Group] + if element then + if Disabled then + if E.private[element[1]].masque[element[2]] and MasqueGroupState[Group] == 'enabled' then + E.private[element[1]].masque[element[2]] = false + E:StaticPopup_Show('CONFIG_RL') + end + MasqueGroupState[Group] = 'disabled' + else + MasqueGroupState[Group] = 'enabled' + end + end + end + + if Masque then + Masque:Register('ElvUI', E.MasqueCallback) + end +end + +do + local CPU_USAGE = {} + local function CompareCPUDiff(showall, minCalls) + local greatestUsage, greatestCalls, greatestName, newName, newFunc + local greatestDiff, lastModule, mod, usage, calls, diff = 0 + + for name, oldUsage in pairs(CPU_USAGE) do + newName, newFunc = strmatch(name, '^([^:]+):(.+)$') + if not newFunc then + E:Print('CPU_USAGE:', name, newFunc) + else + if newName ~= lastModule then + mod = E:GetModule(newName, true) or E + lastModule = newName + end + usage, calls = GetFunctionCPUUsage(mod[newFunc], true) + diff = usage - oldUsage + if showall and (calls > minCalls) then + E:Print('Name('..name..') Calls('..calls..') MS('..(usage or 0)..') Diff('..(diff > 0 and format('%.3f', diff) or 0)..')') + end + if (diff > greatestDiff) and calls > minCalls then + greatestName, greatestUsage, greatestCalls, greatestDiff = name, usage, calls, diff + end + end + end + + if greatestName then + E:Print(greatestName.. ' had the CPU usage of: '..(greatestUsage > 0 and format('%.3f', greatestUsage) or 0)..'ms. And has been called '.. greatestCalls..' times.') + else + E:Print('CPU Usage: No CPU Usage differences found.') + end + + wipe(CPU_USAGE) + end + + function E:GetTopCPUFunc(msg) + if not GetCVarBool('scriptProfile') then + E:Print('For `/cpuusage` to work, you need to enable script profiling via: `/console scriptProfile 1` then reload. Disable after testing by setting it back to 0.') + return + end + + local module, showall, delay, minCalls = strmatch(msg, '^(%S+)%s*(%S*)%s*(%S*)%s*(.*)$') + local checkCore, mod = (not module or module == '') and 'E' + + showall = (showall == 'true' and true) or false + delay = (delay == 'nil' and nil) or tonumber(delay) or 5 + minCalls = (minCalls == 'nil' and nil) or tonumber(minCalls) or 15 + + wipe(CPU_USAGE) + if module == 'all' then + for moduName, modu in pairs(self.modules) do + for funcName, func in pairs(modu) do + if funcName ~= 'GetModule' and type(func) == 'function' then + CPU_USAGE[moduName..':'..funcName] = GetFunctionCPUUsage(func, true) + end + end + end + else + if not checkCore then + mod = self:GetModule(module, true) + if not mod then + self:Print(module..' not found, falling back to checking core.') + mod, checkCore = self, 'E' + end + else + mod = self + end + for name, func in pairs(mod) do + if (name ~= 'GetModule') and type(func) == 'function' then + CPU_USAGE[(checkCore or module)..':'..name] = GetFunctionCPUUsage(func, true) + end + end + end + + self:Delay(delay, CompareCPUDiff, showall, minCalls) + self:Print('Calculating CPU Usage differences (module: '..(checkCore or module)..', showall: '..tostring(showall)..', minCalls: '..tostring(minCalls)..', delay: '..tostring(delay)..')') + end +end + +function E:Dump(object, inspect) + if GetAddOnEnableState(E.myname, 'Blizzard_DebugTools') == 0 then + E:Print('Blizzard_DebugTools is disabled.') + return + end + + local debugTools = IsAddOnLoaded('Blizzard_DebugTools') + if not debugTools then UIParentLoadAddOn('Blizzard_DebugTools') end + + if inspect then + local tableType = type(object) + if tableType == 'table' then + _G.DisplayTableInspectorWindow(object) + else + E:Print('Failed: ', tostring(object), ' is type: ', tableType,'. Requires table object.') + end + else + _G.DevTools_Dump(object) + end +end + +function E:AddNonPetBattleFrames() + if InCombatLockdown() then + E:UnregisterEventForObject('PLAYER_REGEN_DISABLED', E.AddNonPetBattleFrames, E.AddNonPetBattleFrames) + return + elseif E:IsEventRegisteredForObject('PLAYER_REGEN_DISABLED', E.AddNonPetBattleFrames) then + E:UnregisterEventForObject('PLAYER_REGEN_DISABLED', E.AddNonPetBattleFrames, E.AddNonPetBattleFrames) + end + + for object, data in pairs(E.FrameLocks) do + local parent, strata + if type(data) == 'table' then + parent, strata = data.parent, data.strata + elseif data == true then + parent = _G.UIParent + end + + local obj = _G[object] or object + obj:SetParent(parent) + if strata then + obj:SetFrameStrata(strata) + end + end +end + +function E:RemoveNonPetBattleFrames() + if InCombatLockdown() then + E:RegisterEventForObject('PLAYER_REGEN_DISABLED', E.RemoveNonPetBattleFrames, E.RemoveNonPetBattleFrames) + return + elseif E:IsEventRegisteredForObject('PLAYER_REGEN_DISABLED', E.RemoveNonPetBattleFrames) then + E:UnregisterEventForObject('PLAYER_REGEN_DISABLED', E.RemoveNonPetBattleFrames, E.RemoveNonPetBattleFrames) + end + + for object in pairs(E.FrameLocks) do + local obj = _G[object] or object + obj:SetParent(E.HiddenFrame) + end +end + +function E:RegisterPetBattleHideFrames(object, originalParent, originalStrata) + if not object or not originalParent then + E:Print('Error. Usage: RegisterPetBattleHideFrames(object, originalParent, originalStrata)') + return + end + + object = _G[object] or object + + --If already doing pokemon + if C_PetBattles_IsInBattle() then + object:SetParent(E.HiddenFrame) + end + + E.FrameLocks[object] = { + parent = originalParent, + strata = originalStrata or nil, + } +end + +function E:UnregisterPetBattleHideFrames(object) + if not object then + E:Print('Error. Usage: UnregisterPetBattleHideFrames(object)') + return + end + + object = _G[object] or object + + --Check if object was registered to begin with + if not E.FrameLocks[object] then return end + + --Change parent of object back to original parent + local originalParent = E.FrameLocks[object].parent + if originalParent then + object:SetParent(originalParent) + end + + --Change strata of object back to original + local originalStrata = E.FrameLocks[object].strata + if originalStrata then + object:SetFrameStrata(originalStrata) + end + + --Remove object from table + E.FrameLocks[object] = nil +end + +function E:RegisterObjectForVehicleLock(object, originalParent) + if not object or not originalParent then + E:Print('Error. Usage: RegisterObjectForVehicleLock(object, originalParent)') + return + end + + object = _G[object] or object + --Entering/Exiting vehicles will often happen in combat. + --For this reason we cannot allow protected objects. + if object.IsProtected and object:IsProtected() then + E:Print('Error. Object is protected and cannot be changed in combat.') + return + end + + --Check if we are already in a vehicles + if UnitHasVehicleUI('player') then + object:SetParent(E.HiddenFrame) + end + + --Add object to table + E.VehicleLocks[object] = originalParent +end + +function E:UnregisterObjectForVehicleLock(object) + if not object then + E:Print('Error. Usage: UnregisterObjectForVehicleLock(object)') + return + end + + object = _G[object] or object + --Check if object was registered to begin with + if not E.VehicleLocks[object] then + return + end + + --Change parent of object back to original parent + local originalParent = E.VehicleLocks[object] + if originalParent then + object:SetParent(originalParent) + end + + --Remove object from table + E.VehicleLocks[object] = nil +end + +function E:EnterVehicleHideFrames(_, unit) + if unit ~= 'player' then return end + for object in pairs(E.VehicleLocks) do + object:SetParent(E.HiddenFrame) + end +end + +function E:ExitVehicleShowFrames(_, unit) + if unit ~= 'player' then return end + for object, originalParent in pairs(E.VehicleLocks) do + object:SetParent(originalParent) + end +end + +function E:RequestBGInfo() + RequestBattlefieldScoreData() +end + +function E:PLAYER_ENTERING_WORLD(_, initLogin, isReload) + self:CheckRole() + + if initLogin or not ElvDB.LuaErrorDisabledAddOns then + ElvDB.LuaErrorDisabledAddOns = {} + end + + if initLogin or isReload then + self:CheckIncompatible() + end + + if not self.MediaUpdated then + self:UpdateMedia() + self.MediaUpdated = true + end + + local _, instanceType = GetInstanceInfo() + if instanceType == 'pvp' then + self.BGTimer = self:ScheduleRepeatingTimer('RequestBGInfo', 5) + self:RequestBGInfo() + elseif self.BGTimer then + self:CancelTimer(self.BGTimer) + self.BGTimer = nil + end +end + +function E:PLAYER_REGEN_ENABLED() + if self.CVarUpdate then + for cvarName, value in pairs(self.LockedCVars) do + if not self.IgnoredCVars[cvarName] and (GetCVar(cvarName) ~= value) then + SetCVar(cvarName, value) + end + end + + self.CVarUpdate = nil + end + + if self.ShowOptionsUI then + self:ToggleOptionsUI() + + self.ShowOptionsUI = nil + end +end + +function E:PLAYER_REGEN_DISABLED() + local err + + if IsAddOnLoaded('ElvUI_OptionsUI') then + local ACD = self.Libs.AceConfigDialog + if ACD and ACD.OpenFrames and ACD.OpenFrames.ElvUI then + ACD:Close('ElvUI') + err = true + end + end + + if self.CreatedMovers then + for name in pairs(self.CreatedMovers) do + local mover = _G[name] + if mover and mover:IsShown() then + mover:Hide() + err = true + end + end + end + + if err then + self:Print(ERR_NOT_IN_COMBAT) + end +end + +function E:GetUnitBattlefieldFaction(unit) + local englishFaction, localizedFaction = UnitFactionGroup(unit) + + -- this might be a rated BG or wargame and if so the player's faction might be altered + -- should also apply if `player` is a mercenary. + if unit == 'player' then + if C_PvP_IsRatedBattleground() or IsWargame() then + englishFaction = PLAYER_FACTION_GROUP[GetBattlefieldArenaFaction()] + localizedFaction = (englishFaction == 'Alliance' and FACTION_ALLIANCE) or FACTION_HORDE + elseif UnitIsMercenary(unit) then + if englishFaction == 'Alliance' then + englishFaction, localizedFaction = 'Horde', FACTION_HORDE + else + englishFaction, localizedFaction = 'Alliance', FACTION_ALLIANCE + end + end + end + + return englishFaction, localizedFaction +end + +function E:NEUTRAL_FACTION_SELECT_RESULT() + E.myfaction, E.myLocalizedFaction = UnitFactionGroup('player') +end + +function E:PLAYER_LEVEL_UP(_, level) + E.mylevel = level +end + +function E:LoadAPI() + E:RegisterEvent('PLAYER_LEVEL_UP') + E:RegisterEvent('PLAYER_ENTERING_WORLD') + E:RegisterEvent('PLAYER_REGEN_ENABLED') + E:RegisterEvent('PLAYER_REGEN_DISABLED') + E:RegisterEvent('NEUTRAL_FACTION_SELECT_RESULT') + E:RegisterEvent('PET_BATTLE_CLOSE', 'AddNonPetBattleFrames') + E:RegisterEvent('PET_BATTLE_OPENING_START', 'RemoveNonPetBattleFrames') + E:RegisterEvent('PLAYER_SPECIALIZATION_CHANGED', 'CheckRole') + E:RegisterEvent('UNIT_ENTERED_VEHICLE', 'EnterVehicleHideFrames') + E:RegisterEvent('UNIT_EXITED_VEHICLE', 'ExitVehicleShowFrames') + E:RegisterEvent('UI_SCALE_CHANGED', 'PixelScaleChanged') + + do -- setup cropIcon texCoords + local opt = E.db.general.cropIcon + local modifier = 0.04 * opt + for i, v in ipairs(E.TexCoords) do + if i % 2 == 0 then + E.TexCoords[i] = v - modifier + else + E.TexCoords[i] = v + modifier + end + end + end + + if not strfind(date(), '04/01/') then + E.global.aprilFools = nil + end + + if _G.OrderHallCommandBar then + E:HandleCommandBar() + else + local frame = CreateFrame('Frame') + frame:RegisterEvent('ADDON_LOADED') + frame:SetScript('OnEvent', function(Frame, event, addon) + if event == 'ADDON_LOADED' and addon == 'Blizzard_OrderHallUI' then + if InCombatLockdown() then + Frame:RegisterEvent('PLAYER_REGEN_ENABLED') + else + E:HandleCommandBar() + end + Frame:UnregisterEvent(event) + elseif event == 'PLAYER_REGEN_ENABLED' then + E:HandleCommandBar() + Frame:UnregisterEvent(event) + end + end) + end +end diff --git a/Core/Animation.lua b/Core/Animation.lua new file mode 100644 index 0000000..3e551e6 --- /dev/null +++ b/Core/Animation.lua @@ -0,0 +1,353 @@ +------------------------------------------------------------------------ +-- Animation Functions +------------------------------------------------------------------------ +local E, L, V, P, G = unpack(select(2, ...)); --Import: Engine, Locales, PrivateDB, ProfileDB, GlobalDB + +local _G = _G +local random, next, unpack, strsub = random, next, unpack, strsub + +E.AnimShake = {{-9,7,-7,12}, {-5,9,-9,5}, {-5,7,-7,5}, {-9,9,-9,9}, {-5,7,-7,5}, {-9,7,-9,5}} +E.AnimShakeH = {-5,5,-2,5,-2,5} + +function E:FlashLoopFinished(requested) + if not requested then self:Play() end +end + +function E:RandomAnimShake(index) + local s = E.AnimShake[index] + return random(s[1], s[2]), random(s[3], s[4]) +end + +--TEST +--[[local t = UIParent:CreateFontString(nil, 'OVERLAY', 'GameTooltipText') +t:SetText(0) +t:Point('CENTER') +t:FontTemplate(nil, 20) +E:SetUpAnimGroup(t, 'Number', 10, 5) + +local b = CreateFrame('BUTTON', nil, UIParent) +b:Point('CENTER', 0, -100) +b:SetTemplate() +b:Size(40,30) +b:EnableMouse(true) +b:SetScript('OnClick', function() + if t:GetText() == 10 then + t.NumberAnim:SetChange(0) + t.NumberAnimGroup:Play() + else + t.NumberAnim:SetChange(10) + t.NumberAnimGroup:Play() + end +end)]] + +function E:SetUpAnimGroup(obj, Type, ...) + if not Type then Type = 'Flash' end + + if strsub(Type, 1, 5) == 'Flash' then + obj.anim = obj:CreateAnimationGroup('Flash') + obj.anim.fadein = obj.anim:CreateAnimation('ALPHA', 'FadeIn') + obj.anim.fadein:SetFromAlpha(0) + obj.anim.fadein:SetToAlpha(1) + obj.anim.fadein:SetOrder(2) + + obj.anim.fadeout = obj.anim:CreateAnimation('ALPHA', 'FadeOut') + obj.anim.fadeout:SetFromAlpha(1) + obj.anim.fadeout:SetToAlpha(0) + obj.anim.fadeout:SetOrder(1) + + if Type == 'FlashLoop' then + obj.anim:SetScript('OnFinished', E.FlashLoopFinished) + end + elseif strsub(Type, 1, 5) == 'Shake' then + local shake = obj:CreateAnimationGroup(Type) + shake:SetLooping('REPEAT') + shake.path = shake:CreateAnimation('Path') + + if Type == 'Shake' then + shake.path:SetDuration(0.7) + obj.shake = shake + elseif Type == 'ShakeH' then + shake.path:SetDuration(2) + obj.shakeh = shake + end + + for i = 1, 6 do + shake.path[i] = shake.path:CreateControlPoint() + shake.path[i]:SetOrder(i) + + if Type == 'Shake' then + shake.path[i]:SetOffset(E:RandomAnimShake(i)) + else + shake.path[i]:SetOffset(E.AnimShakeH[i], 0) + end + end + elseif Type == 'Elastic' then + local width, height, duration, loop = ... + obj.elastic = _G.CreateAnimationGroup(obj) + + for i = 1, 4 do + local anim = obj.elastic:CreateAnimation(i < 3 and 'width' or 'height') + anim:SetChange((i==1 and width*0.45) or (i==2 and width) or (i==3 and height*0.45) or height) + anim:SetEasing('inout-elastic') + anim:SetDuration(duration) + obj.elastic[i] = anim + end + + obj.elastic[1]:SetScript('OnFinished', function(anim) anim:Stop() obj.elastic[2]:Play() end) + obj.elastic[3]:SetScript('OnFinished', function(anim) anim:Stop() obj.elastic[4]:Play() end) + obj.elastic[2]:SetScript('OnFinished', function(anim) anim:Stop() if loop then obj.elastic[1]:Play() end end) + obj.elastic[4]:SetScript('OnFinished', function(anim) anim:Stop() if loop then obj.elastic[3]:Play() end end) + elseif Type == 'Number' then + local endingNumber, duration = ... + obj.NumberAnimGroup = _G.CreateAnimationGroup(obj) + obj.NumberAnim = obj.NumberAnimGroup:CreateAnimation('number') + obj.NumberAnim:SetChange(endingNumber) + obj.NumberAnim:SetEasing('in-circular') + obj.NumberAnim:SetDuration(duration) + else + local x, y, duration, customName = ... + if not customName then customName = 'anim' end + + local anim = obj:CreateAnimationGroup('Move_In') + obj[customName] = anim + + anim.in1 = anim:CreateAnimation('Translation') + anim.in1:SetDuration(0) + anim.in1:SetOrder(1) + anim.in1:SetOffset(x, y) + + anim.in2 = anim:CreateAnimation('Translation') + anim.in2:SetDuration(duration) + anim.in2:SetOrder(2) + anim.in2:SetSmoothing('OUT') + anim.in2:SetOffset(-x, -y) + + anim.out1 = obj:CreateAnimationGroup('Move_Out') + anim.out1:SetScript('OnFinished', function() obj:Hide() end) + + anim.out2 = anim.out1:CreateAnimation('Translation') + anim.out2:SetDuration(duration) + anim.out2:SetOrder(1) + anim.out2:SetSmoothing('IN') + anim.out2:SetOffset(x, y) + end +end + +function E:Elasticize(obj, width, height) + if not obj.elastic then + if not width then width = obj:GetWidth() end + if not height then height = obj:GetHeight() end + E:SetUpAnimGroup(obj, 'Elastic', width, height, 2, false) + end + + obj.elastic[1]:Play() + obj.elastic[3]:Play() +end + +function E:StopElasticize(obj) + if obj.elastic then + obj.elastic[1]:Stop(true) + obj.elastic[3]:Stop(true) + end +end + +function E:Shake(obj) + if not obj.shake then + E:SetUpAnimGroup(obj, 'Shake') + end + + obj.shake:Play() +end + +function E:StopShake(obj) + if obj.shake then + obj.shake:Finish() + end +end + +function E:ShakeHorizontal(obj) + if not obj.shakeh then + E:SetUpAnimGroup(obj, 'ShakeH') + end + + obj.shakeh:Play() +end + +function E:StopShakeHorizontal(obj) + if obj.shakeh then + obj.shakeh:Finish() + end +end + +function E:Flash(obj, duration, loop) + if not obj.anim then + E:SetUpAnimGroup(obj, loop and 'FlashLoop' or 'Flash') + end + + if not obj.anim:IsPlaying() then + obj.anim.fadein:SetDuration(duration) + obj.anim.fadeout:SetDuration(duration) + obj.anim:Play() + end +end + +function E:StopFlash(obj) + if obj.anim and obj.anim:IsPlaying() then + obj.anim:Stop() + end +end + +function E:SlideIn(obj, customName) + if not customName then customName = 'anim' end + if not obj[customName] then return end + + obj[customName].out1:Stop() + obj[customName]:Play() + obj:Show() +end + +function E:SlideOut(obj, customName) + if not customName then customName = 'anim' end + if not obj[customName] then return end + + obj[customName]:Finish() + obj[customName]:Stop() + obj[customName].out1:Play() +end + +local FADEFRAMES, FADEMANAGER = {}, CreateFrame('FRAME') +FADEMANAGER.delay = 0.025 + +function E:UIFrameFade_OnUpdate(elapsed) + FADEMANAGER.timer = (FADEMANAGER.timer or 0) + elapsed + + if FADEMANAGER.timer > FADEMANAGER.delay then + FADEMANAGER.timer = 0 + + for frame, info in next, FADEFRAMES do + -- Reset the timer if there isn't one, this is just an internal counter + if frame:IsVisible() then + info.fadeTimer = (info.fadeTimer or 0) + (elapsed + FADEMANAGER.delay) + else + info.fadeTimer = info.timeToFade + 1 + end + + -- If the fadeTimer is less then the desired fade time then set the alpha otherwise hold the fade state, call the finished function, or just finish the fade + if info.fadeTimer < info.timeToFade then + if info.mode == 'IN' then + frame:SetAlpha((info.fadeTimer / info.timeToFade) * info.diffAlpha + info.startAlpha) + else + frame:SetAlpha(((info.timeToFade - info.fadeTimer) / info.timeToFade) * info.diffAlpha + info.endAlpha) + end + else + frame:SetAlpha(info.endAlpha) + + -- If there is a fadeHoldTime then wait until its passed to continue on + if info.fadeHoldTime and info.fadeHoldTime > 0 then + info.fadeHoldTime = info.fadeHoldTime - elapsed + else + -- Complete the fade and call the finished function if there is one + E:UIFrameFadeRemoveFrame(frame) + + if info.finishedFunc then + if info.finishedArgs then + info.finishedFunc(unpack(info.finishedArgs)) + else -- optional method + info.finishedFunc(info.finishedArg1, info.finishedArg2, info.finishedArg3, info.finishedArg4, info.finishedArg5) + end + + if not info.finishedFuncKeep then + info.finishedFunc = nil + end + end + end + end + end + + if not next(FADEFRAMES) then + FADEMANAGER:SetScript('OnUpdate', nil) + end + end +end + +-- Generic fade function +function E:UIFrameFade(frame, info) + if not frame or frame:IsForbidden() then return end + + frame.fadeInfo = info + + if not info.mode then + info.mode = 'IN' + end + + if info.mode == 'IN' then + if not info.startAlpha then info.startAlpha = 0 end + if not info.endAlpha then info.endAlpha = 1 end + if not info.diffAlpha then info.diffAlpha = info.endAlpha - info.startAlpha end + else + if not info.startAlpha then info.startAlpha = 1 end + if not info.endAlpha then info.endAlpha = 0 end + if not info.diffAlpha then info.diffAlpha = info.startAlpha - info.endAlpha end + end + + frame:SetAlpha(info.startAlpha) + + if not frame:IsProtected() then + frame:Show() + end + + if not FADEFRAMES[frame] then + FADEFRAMES[frame] = info -- read below comment + FADEMANAGER:SetScript('OnUpdate', E.UIFrameFade_OnUpdate) + else + FADEFRAMES[frame] = info -- keep these both, we need this updated in the event its changed to another ref from a plugin or sth, don't move it up! + end +end + +-- Convenience function to do a simple fade in +function E:UIFrameFadeIn(frame, timeToFade, startAlpha, endAlpha) + if not frame or frame:IsForbidden() then return end + + if frame.FadeObject then + frame.FadeObject.fadeTimer = nil + else + frame.FadeObject = {} + end + + frame.FadeObject.mode = 'IN' + frame.FadeObject.timeToFade = timeToFade + frame.FadeObject.startAlpha = startAlpha + frame.FadeObject.endAlpha = endAlpha + frame.FadeObject.diffAlpha = endAlpha - startAlpha + + E:UIFrameFade(frame, frame.FadeObject) +end + +-- Convenience function to do a simple fade out +function E:UIFrameFadeOut(frame, timeToFade, startAlpha, endAlpha) + if not frame or frame:IsForbidden() then return end + + if frame.FadeObject then + frame.FadeObject.fadeTimer = nil + else + frame.FadeObject = {} + end + + frame.FadeObject.mode = 'OUT' + frame.FadeObject.timeToFade = timeToFade + frame.FadeObject.startAlpha = startAlpha + frame.FadeObject.endAlpha = endAlpha + frame.FadeObject.diffAlpha = startAlpha - endAlpha + + E:UIFrameFade(frame, frame.FadeObject) +end + +function E:UIFrameFadeRemoveFrame(frame) + if frame and FADEFRAMES[frame] then + if frame.FadeObject then + frame.FadeObject.fadeTimer = nil + end + + FADEFRAMES[frame] = nil + end +end diff --git a/Core/AprilFools.lua b/Core/AprilFools.lua new file mode 100644 index 0000000..7fe8c35 --- /dev/null +++ b/Core/AprilFools.lua @@ -0,0 +1,355 @@ +------------------------------------------------------------------------ +-- Collection of previous april fools pranks +-- Harlem Shake: Try it out with the command /harlemshake +-- Hello Kitty: Try it out with the command /hellokitty (pay attention to the popups, read what it says) +------------------------------------------------------------------------ +local E, L, V, P, G = unpack(select(2, ...)); --Import: Engine, Locales, PrivateDB, ProfileDB, GlobalDB +local UF = E:GetModule('UnitFrames') +local AB = E:GetModule('ActionBars') + +local _G = _G +local pairs = pairs +local wipe, tinsert = wipe, tinsert + +local CreateFrame = CreateFrame +local DoEmote = DoEmote +local GetCVar, SetCVar = GetCVar, SetCVar +local NUM_PET_ACTION_SLOTS = NUM_PET_ACTION_SLOTS +local PlayMusic, StopMusic = PlayMusic, StopMusic +-- GLOBALS: ElvUI_StaticPopup1, ElvUI_StaticPopup1Button1, ElvUI_StanceBar + +--Harlem Shake (Activate with command: /harlemshake) +--People really seemed to like this one. We got a lot of positive responses. +do + function E:StopHarlemShake() + E.isMassiveShaking = nil + StopMusic() + SetCVar('Sound_EnableAllSound', self.oldEnableAllSound) + SetCVar('Sound_EnableMusic', self.oldEnableMusic) + + self:StopShakeHorizontal(ElvUI_StaticPopup1) + for _, object in pairs(self.massiveShakeObjects) do + if object then + self:StopShake(object) + end + end + + if E.massiveShakeTimer then + E:CancelTimer(E.massiveShakeTimer) + end + + E.global.aprilFools = true + E:StaticPopup_Hide('HARLEM_SHAKE') + wipe(self.massiveShakeObjects) + DoEmote('Dance') + end + + function E:DoTheHarlemShake() + E.isMassiveShaking = true + ElvUI_StaticPopup1Button1:Enable() + + for _, object in pairs(self.massiveShakeObjects) do + if object and not object:IsForbidden() and object:IsShown() then + self:Shake(object) + end + end + + E.massiveShakeTimer = E:ScheduleTimer('StopHarlemShake', 42.5) + end + + function E:BeginHarlemShake() + DoEmote('Dance') + ElvUI_StaticPopup1Button1:Disable() + self:ShakeHorizontal(ElvUI_StaticPopup1) + self.oldEnableAllSound = GetCVar('Sound_EnableAllSound') + self.oldEnableMusic = GetCVar('Sound_EnableMusic') + + SetCVar('Sound_EnableAllSound', 1) + SetCVar('Sound_EnableMusic', 1) + PlayMusic(E.Media.Sounds.HarlemShake) + E:ScheduleTimer('DoTheHarlemShake', 15.5) + + self.massiveShakeObjects = {} + tinsert(self.massiveShakeObjects, _G.GameTooltip) + tinsert(self.massiveShakeObjects, _G.Minimap) + tinsert(self.massiveShakeObjects, _G.ObjectiveTrackerFrame) + tinsert(self.massiveShakeObjects, _G.LeftChatPanel) + tinsert(self.massiveShakeObjects, _G.RightChatPanel) + + for unit in pairs(UF.units) do + tinsert(self.massiveShakeObjects, UF[unit]) + end + for _, header in pairs(UF.headers) do + tinsert(self.massiveShakeObjects, header) + end + + for _, bar in pairs(AB.handledBars) do + for i = 1, #bar.buttons do + tinsert(self.massiveShakeObjects, bar.buttons[i]) + end + end + + if ElvUI_StanceBar then + for i = 1, #ElvUI_StanceBar.buttons do + tinsert(self.massiveShakeObjects, ElvUI_StanceBar.buttons[i]) + end + end + + for i = 1, NUM_PET_ACTION_SLOTS do + local button = _G['PetActionButton'..i] + if button then + tinsert(self.massiveShakeObjects, button) + end + end + end + + function E:HarlemShakeToggle() + self:StaticPopup_Show('HARLEM_SHAKE') + end +end + +--Hello Kitty (Activate with command: /hellokitty) +--This is one of those pranks where you either love it or hate it I think +--Unfortunately there was a bug which caused some of the hello kitty changes to stick, +-- when they should have reverted to the original settings. This bug was fixed later on. +do + local function OnDragStart(self) + self:StartMoving() + end + + local function OnDragStop(self) + self:StopMovingOrSizing() + end + + local function OnUpdate(self, elapsed) + if self.elapsed and self.elapsed > 0.1 then + self.tex:SetTexCoord((self.curFrame - 1) * 0.1, 0, (self.curFrame - 1) * 0.1, 1, self.curFrame * 0.1, 0, self.curFrame * 0.1, 1) + + if self.countUp then + self.curFrame = self.curFrame + 1 + else + self.curFrame = self.curFrame - 1 + end + + if self.curFrame > 10 then + self.countUp = false + self.curFrame = 9 + elseif self.curFrame < 1 then + self.countUp = true + self.curFrame = 2 + end + self.elapsed = 0 + else + self.elapsed = (self.elapsed or 0) + elapsed + end + end + + function E:SetupHelloKitty() + if not self.db.tempSettings then + self.db.tempSettings = {} + end + + --Store old settings + local t = self.db.tempSettings + local c = self.db.general.backdropcolor + if self:HelloKittyFixCheck() then + E:HelloKittyFix() + else + self.oldEnableAllSound = GetCVar('Sound_EnableAllSound') + self.oldEnableMusic = GetCVar('Sound_EnableMusic') + + t.backdropcolor = {r = c.r, g = c.g, b = c.b} + c = self.db.general.backdropfadecolor + t.backdropfadecolor = {r = c.r, g = c.g, b = c.b, a = c.a} + c = self.db.general.bordercolor + t.bordercolor = {r = c.r, g = c.g, b = c.b} + c = self.db.general.valuecolor + t.valuecolor = {r = c.r, g = c.g, b = c.b} + + t.panelBackdropNameLeft = self.db.chat.panelBackdropNameLeft + t.panelBackdropNameRight = self.db.chat.panelBackdropNameRight + + c = self.db.unitframe.colors.health + t.health = {r = c.r, g = c.g, b = c.b} + t.healthclass = self.db.unitframe.colors.healthclass + + c = self.db.unitframe.colors.castColor + t.castColor = {r = c.r, g = c.g, b = c.b} + t.transparentCastbar = self.db.unitframe.colors.transparentCastbar + + c = self.db.unitframe.colors.auraBarBuff + t.auraBarBuff = {r = c.r, g = c.g, b = c.b} + t.transparentAurabars = self.db.unitframe.colors.transparentAurabars + + --Apply new settings + self.db.general.backdropfadecolor = {r =131/255, g =36/255, b = 130/255, a = 0.36} + self.db.general.backdropcolor = {r = 223/255, g = 76/255, b = 188/255} + self.db.general.bordercolor = {r = 223/255, g = 217/255, b = 47/255} + self.db.general.valuecolor = {r = 223/255, g = 217/255, b = 47/255} + + self.db.chat.panelBackdropNameLeft = E.Media.Textures.HelloKittyChat + self.db.chat.panelBackdropNameRight = E.Media.Textures.HelloKittyChat + + self.db.unitframe.colors.castColor = {r = 223/255, g = 76/255, b = 188/255} + self.db.unitframe.colors.transparentCastbar = true + + self.db.unitframe.colors.auraBarBuff = {r = 223/255, g = 76/255, b = 188/255} + self.db.unitframe.colors.transparentAurabars = true + + self.db.unitframe.colors.health = {r = 223/255, g = 76/255, b = 188/255} + self.db.unitframe.colors.healthclass = false + + SetCVar('Sound_EnableAllSound', 1) + SetCVar('Sound_EnableMusic', 1) + PlayMusic(E.Media.Sounds.HelloKitty) + E:StaticPopup_Show('HELLO_KITTY_END') + + self.db.general.kittys = true + self:CreateKittys() + + self:StaggeredUpdateAll(nil, true) + end + end + + function E:RestoreHelloKitty() + --Store old settings + self.db.general.kittys = false + if _G.HelloKittyLeft then + _G.HelloKittyLeft:Hide() + _G.HelloKittyRight:Hide() + end + + if not(self.db.tempSettings) then return end + if self:HelloKittyFixCheck() then + self:HelloKittyFix() + self.db.tempSettings = nil + return + end + local c = self.db.tempSettings.backdropcolor + self.db.general.backdropcolor = {r = c.r, g = c.g, b = c.b} + + c = self.db.tempSettings.backdropfadecolor + self.db.general.backdropfadecolor = {r = c.r, g = c.g, b = c.b, a = (c.a or 0.8)} + + c = self.db.tempSettings.bordercolor + self.db.general.bordercolor = {r = c.r, g = c.g, b = c.b} + + c = self.db.tempSettings.valuecolor + self.db.general.valuecolor = {r = c.r, g = c.g, b = c.b} + + self.db.chat.panelBackdropNameLeft = self.db.tempSettings.panelBackdropNameLeft + self.db.chat.panelBackdropNameRight = self.db.tempSettings.panelBackdropNameRight + + c = self.db.tempSettings.health + self.db.unitframe.colors.health = {r = c.r, g = c.g, b = c.b} + self.db.unitframe.colors.healthclass = self.db.tempSettings.healthclass + + c = self.db.tempSettings.castColor + self.db.unitframe.colors.castColor = {r = c.r, g = c.g, b = c.b} + self.db.unitframe.colors.transparentCastbar = self.db.tempSettings.transparentCastbar + + c = self.db.tempSettings.auraBarBuff + self.db.unitframe.colors.auraBarBuff = {r = c.r, g = c.g, b = c.b} + self.db.unitframe.colors.transparentAurabars = self.db.tempSettings.transparentAurabars + + self.db.tempSettings = nil + + self:StaggeredUpdateAll(nil, true) + end + + function E:CreateKittys() + if _G.HelloKittyLeft then + _G.HelloKittyLeft:Show() + _G.HelloKittyRight:Show() + return + end + local helloKittyLeft = CreateFrame('Frame', 'HelloKittyLeft', _G.UIParent) + helloKittyLeft:Size(120, 128) + helloKittyLeft:SetMovable(true) + helloKittyLeft:EnableMouse(true) + helloKittyLeft:RegisterForDrag('LeftButton') + helloKittyLeft:Point('BOTTOMLEFT', _G.LeftChatPanel, 'BOTTOMRIGHT', 2, -4) + helloKittyLeft.tex = helloKittyLeft:CreateTexture(nil, 'OVERLAY') + helloKittyLeft.tex:SetAllPoints() + helloKittyLeft.tex:SetTexture(E.Media.Textures.HelloKitty) + helloKittyLeft.tex:SetTexCoord(0, 0, 0, 1, 0, 0, 0, 1) + helloKittyLeft.curFrame = 1 + helloKittyLeft.countUp = true + helloKittyLeft:SetClampedToScreen(true) + helloKittyLeft:SetScript('OnDragStart', OnDragStart) + helloKittyLeft:SetScript('OnDragStop', OnDragStop) + helloKittyLeft:SetScript('OnUpdate', OnUpdate) + + local helloKittyRight = CreateFrame('Frame', 'HelloKittyRight', _G.UIParent) + helloKittyRight:Size(120, 128) + helloKittyRight:SetMovable(true) + helloKittyRight:EnableMouse(true) + helloKittyRight:RegisterForDrag('LeftButton') + helloKittyRight:Point('BOTTOMRIGHT', _G.RightChatPanel, 'BOTTOMLEFT', -2, -4) + helloKittyRight.tex = helloKittyRight:CreateTexture(nil, 'OVERLAY') + helloKittyRight.tex:SetAllPoints() + helloKittyRight.tex:SetTexture(E.Media.Textures.HelloKitty) + helloKittyRight.tex:SetTexCoord(0, 0, 0, 1, 0, 0, 0, 1) + helloKittyRight.curFrame = 10 + helloKittyRight.countUp = false + helloKittyRight:SetClampedToScreen(true) + helloKittyRight:SetScript('OnDragStart', OnDragStart) + helloKittyRight:SetScript('OnDragStop', OnDragStop) + helloKittyRight:SetScript('OnUpdate', OnUpdate) + end + + --When it bugged out for a user the command '/hellokittyfix' attempted to restore the changed settings to default + function E:HelloKittyFixCheck(secondCheck) + local t = self.db.tempSettings + if not t and not secondCheck then t = self.db.general end + if t and t.backdropcolor then + return self:Round(t.backdropcolor.r, 2) == 0.87 and self:Round(t.backdropcolor.g, 2) == 0.3 and self:Round(t.backdropcolor.b, 2) == 0.74 + end + end + + function E:HelloKittyFix() + local c = P.general.backdropcolor + self.db.general.backdropcolor = {r = c.r, g = c.g, b = c.b} + + c = P.general.backdropfadecolor + self.db.general.backdropfadecolor = {r = c.r, g = c.g, b = c.b, a = (c.a or 0.8)} + + c = P.general.bordercolor + self.db.general.bordercolor = {r = c.r, g = c.g, b = c.b} + + c = P.general.valuecolor + self.db.general.valuecolor = {r = c.r, g = c.g, b = c.b} + + self.db.chat.panelBackdropNameLeft = '' + self.db.chat.panelBackdropNameRight = '' + + c = P.unitframe.colors.health + self.db.unitframe.colors.health = {r = c.r, g = c.g, b = c.b} + + c = P.unitframe.colors.castColor + self.db.unitframe.colors.castColor = {r = c.r, g = c.g, b = c.b} + self.db.unitframe.colors.transparentCastbar = false + + c = P.unitframe.colors.castColor + self.db.unitframe.colors.auraBarBuff = {r = c.r, g = c.g, b = c.b} + self.db.unitframe.colors.transparentAurabars = false + + if _G.HelloKittyLeft then + _G.HelloKittyLeft:Hide() + _G.HelloKittyRight:Hide() + self.db.general.kittys = nil + return + end + + self.db.tempSettings = nil + self:StaggeredUpdateAll(nil, true) + end + + function E:HelloKittyToggle() + if _G.HelloKittyLeft and _G.HelloKittyLeft:IsShown() then + self:RestoreHelloKitty() + else + self:StaticPopup_Show('HELLO_KITTY') + end + end +end diff --git a/Core/Commands.lua b/Core/Commands.lua new file mode 100644 index 0000000..e5f8ebc --- /dev/null +++ b/Core/Commands.lua @@ -0,0 +1,300 @@ +local E, L, V, P, G = unpack(select(2, ...)) --Import: Engine, Locales, PrivateDB, ProfileDB, GlobalDB +local DT = E:GetModule('DataTexts') +local AB = E:GetModule('ActionBars') + +local _G = _G +local tonumber, type, pairs, select = tonumber, type, pairs, select +local lower, split, format, wipe, next, print = strlower, strsplit, format, wipe, next, print + +local debugprofilestop = debugprofilestop +local EnableAddOn = EnableAddOn +local GetAddOnCPUUsage = GetAddOnCPUUsage +local GetAddOnInfo = GetAddOnInfo +local GetNumAddOns = GetNumAddOns +local GetCVarBool = GetCVarBool +local DisableAddOn = DisableAddOn +local GetGuildRosterInfo = GetGuildRosterInfo +local GetGuildRosterLastOnline = GetGuildRosterLastOnline +local GetNumGuildMembers = GetNumGuildMembers +local GuildControlGetNumRanks = GuildControlGetNumRanks +local GuildControlGetRankName = GuildControlGetRankName +local GuildUninvite = GuildUninvite +local ResetCPUUsage = ResetCPUUsage +local SendChatMessage = SendChatMessage +local ReloadUI = ReloadUI +local SetCVar = SetCVar +local UpdateAddOnCPUUsage = UpdateAddOnCPUUsage +-- GLOBALS: ElvUIGrid, ElvDB + +function E:Grid(msg) + msg = msg and tonumber(msg) + if type(msg) == 'number' and (msg <= 256 and msg >= 4) then + E.db.gridSize = msg + E:Grid_Show() + elseif ElvUIGrid and ElvUIGrid:IsShown() then + E:Grid_Hide() + else + E:Grid_Show() + end +end + +function E:LuaError(msg) + local switch = lower(msg) + if switch == 'on' or switch == '1' then + for i=1, GetNumAddOns() do + local name = GetAddOnInfo(i) + if name ~= 'ElvUI' and name ~= 'ElvUI_OptionsUI' and E:IsAddOnEnabled(name) then + DisableAddOn(name, E.myname) + ElvDB.LuaErrorDisabledAddOns[name] = i + end + end + + SetCVar('scriptErrors', 1) + ReloadUI() + elseif switch == 'off' or switch == '0' then + if switch == 'off' then + SetCVar('scriptErrors', 0) + E:Print('Lua errors off.') + end + + if next(ElvDB.LuaErrorDisabledAddOns) then + for name in pairs(ElvDB.LuaErrorDisabledAddOns) do + EnableAddOn(name, E.myname) + end + + wipe(ElvDB.LuaErrorDisabledAddOns) + ReloadUI() + end + else + E:Print('/luaerror on - /luaerror off') + end +end + +local function OnCallback(command) + _G.MacroEditBox:GetScript('OnEvent')(_G.MacroEditBox, 'EXECUTE_CHAT_LINE', command) +end + +function E:DelayScriptCall(msg) + local secs, command = msg:match('^(%S+)%s+(.*)$') + secs = tonumber(secs) + if not secs or (#command == 0) then + self:Print('usage: /in ') + self:Print('example: /in 1.5 /say hi') + else + E:Delay(secs, OnCallback, command) + end +end + +-- make this a locale later? +local MassKickMessage = 'Guild Cleanup Results: Removed all guild members below rank %s, that have a minimal level of %s, and have not been online for at least: %s days.' +function E:MassGuildKick(msg) + local minLevel, minDays, minRankIndex = split(',', msg) + minRankIndex = tonumber(minRankIndex) + minLevel = tonumber(minLevel) + minDays = tonumber(minDays) + + if not minLevel or not minDays then + E:Print('Usage: /cleanguild , , []') + return + end + + if minDays > 31 then + E:Print('Maximum days value must be below 32.') + return + end + + if not minRankIndex then + minRankIndex = GuildControlGetNumRanks() - 1 + end + + for i = 1, GetNumGuildMembers() do + local name, _, rankIndex, level, _, _, note, officerNote, connected, _, classFileName = GetGuildRosterInfo(i) + local minLevelx = minLevel + + if classFileName == 'DEATHKNIGHT' then + minLevelx = minLevelx + 55 + end + + if not connected then + local years, months, days = GetGuildRosterLastOnline(i) + if days ~= nil and ((years > 0 or months > 0 or days >= minDays) and rankIndex >= minRankIndex) + and note ~= nil and officerNote ~= nil and (level <= minLevelx) then + GuildUninvite(name) + end + end + end + + SendChatMessage(format(MassKickMessage, GuildControlGetRankName(minRankIndex), minLevel, minDays), 'GUILD') +end + +local num_frames = 0 +local function OnUpdate() + num_frames = num_frames + 1 +end +local f = CreateFrame('Frame') +f:Hide() +f:SetScript('OnUpdate', OnUpdate) + +local toggleMode, debugTimer, cpuImpactMessage = false, 0, 'Consumed %sms per frame. Each frame took %sms to render.' +function E:GetCPUImpact() + if not GetCVarBool('scriptProfile') then + E:Print('For `/cpuimpact` to work, you need to enable script profiling via: `/console scriptProfile 1` then reload. Disable after testing by setting it back to 0.') + return + end + + if not toggleMode then + ResetCPUUsage() + toggleMode, num_frames, debugTimer = true, 0, debugprofilestop() + self:Print('CPU Impact being calculated, type /cpuimpact to get results when you are ready.') + f:Show() + else + f:Hide() + local ms_passed = debugprofilestop() - debugTimer + UpdateAddOnCPUUsage() + + local per, passed = ((num_frames == 0 and 0) or (GetAddOnCPUUsage('ElvUI') / num_frames)), ((num_frames == 0 and 0) or (ms_passed / num_frames)) + self:Print(format(cpuImpactMessage, per and per > 0 and format('%.3f', per) or 0, passed and passed > 0 and format('%.3f', passed) or 0)) + toggleMode = false + end +end + +function E:EHelp() + print(L["EHELP_COMMANDS"]) +end + +local BLIZZARD_ADDONS = { + 'Blizzard_AchievementUI', + 'Blizzard_AdventureMap', + 'Blizzard_ArchaeologyUI', + 'Blizzard_ArenaUI', + 'Blizzard_ArtifactUI', + 'Blizzard_AuctionUI', + 'Blizzard_AuthChallengeUI', + 'Blizzard_BarbershopUI', + 'Blizzard_BattlefieldMinimap', + 'Blizzard_BindingUI', + 'Blizzard_BlackMarketUI', + 'Blizzard_BoostTutorial', + 'Blizzard_Calendar', + 'Blizzard_ChallengesUI', + 'Blizzard_ClassTrial', + 'Blizzard_ClientSavedVariables', + 'Blizzard_Collections', + 'Blizzard_CombatLog', + 'Blizzard_CombatText', + 'Blizzard_CompactRaidFrames', + 'Blizzard_CUFProfiles', + 'Blizzard_DeathRecap', + 'Blizzard_DebugTools', + 'Blizzard_EncounterJournal', + 'Blizzard_FlightMap', + 'Blizzard_GarrisonTemplates', + 'Blizzard_GarrisonUI', + 'Blizzard_GlyphUI', + 'Blizzard_GMChatUI', + 'Blizzard_GMSurveyUI', + 'Blizzard_GuildBankUI', + 'Blizzard_GuildControlUI', + 'Blizzard_GuildUI', + 'Blizzard_InspectUI', + 'Blizzard_ItemSocketingUI', + 'Blizzard_ItemUpgradeUI', + 'Blizzard_LookingForGuildUI', + 'Blizzard_MacroUI', + 'Blizzard_MapCanvas', + 'Blizzard_MovePad', + 'Blizzard_NamePlates', + 'Blizzard_ObjectiveTracker', + 'Blizzard_ObliterumUI', + 'Blizzard_OrderHallUI', + 'Blizzard_PetBattleUI', + 'Blizzard_PVPUI', + 'Blizzard_QuestChoice', + 'Blizzard_RaidUI', + 'Blizzard_SecureTransferUI', + 'Blizzard_SharedMapDataProviders', + 'Blizzard_SocialUI', + 'Blizzard_StoreUI', + 'Blizzard_TalentUI', + 'Blizzard_TalkingHeadUI', + 'Blizzard_TimeManager', + 'Blizzard_TokenUI', + 'Blizzard_TradeSkillUI', + 'Blizzard_TrainerUI', + 'Blizzard_Tutorial', + 'Blizzard_TutorialTemplates', + 'Blizzard_VoidStorageUI', + 'Blizzard_WowTokenUI' +} +function E:EnableBlizzardAddOns() + for _, addon in pairs(BLIZZARD_ADDONS) do + local reason = select(5, GetAddOnInfo(addon)) + if reason == 'DISABLED' then + EnableAddOn(addon) + E:Print('The following addon was re-enabled:', addon) + end + end +end + +do -- Blizzard Commands + local SlashCmdList = _G.SlashCmdList + + -- DeveloperConsole (without starting with `-console`) + if not SlashCmdList.DEVCON then + local DevConsole = _G.DeveloperConsole + if DevConsole then + _G.SLASH_DEVCON1 = '/devcon' + SlashCmdList.DEVCON = function() + DevConsole:Toggle() + end + end + end + + -- ReloadUI: /rl, /reloadui, /reload NOTE: /reload is from SLASH_RELOAD + if not SlashCmdList.RELOADUI then + _G.SLASH_RELOADUI1 = '/rl' + _G.SLASH_RELOADUI2 = '/reloadui' + SlashCmdList.RELOADUI = _G.ReloadUI + end +end + +function E:DBConvertProfile() + E.db.dbConverted = nil + E:DBConversions() + ReloadUI() +end + +function E:LoadCommands() + self:RegisterChatCommand('in', 'DelayScriptCall') + self:RegisterChatCommand('ec', 'ToggleOptionsUI') + self:RegisterChatCommand('elvui', 'ToggleOptionsUI') + self:RegisterChatCommand('cpuimpact', 'GetCPUImpact') + self:RegisterChatCommand('cpuusage', 'GetTopCPUFunc') + -- cpuusage args: module, showall, delay, minCalls + --- Example1: /cpuusage all + --- Example2: /cpuusage Bags true + --- Example3: /cpuusage UnitFrames nil 50 25 + ---- Note: showall, delay, and minCalls will default if not set + ---- arg1 can be 'all' this will scan all registered modules! + + self:RegisterChatCommand('hdt', DT.HyperDT) + self:RegisterChatCommand('bgstats', DT.ToggleBattleStats) + self:RegisterChatCommand('hellokitty', 'HelloKittyToggle') + self:RegisterChatCommand('hellokittyfix', 'HelloKittyFix') + self:RegisterChatCommand('harlemshake', 'HarlemShakeToggle') + self:RegisterChatCommand('luaerror', 'LuaError') + self:RegisterChatCommand('egrid', 'Grid') + self:RegisterChatCommand('moveui', 'ToggleMoveMode') + self:RegisterChatCommand('resetui', 'ResetUI') + self:RegisterChatCommand('cleanguild', 'MassGuildKick') + self:RegisterChatCommand('enableblizzard', 'EnableBlizzardAddOns') + self:RegisterChatCommand('estatus', 'ShowStatusReport') + self:RegisterChatCommand('ehelp', 'EHelp') + self:RegisterChatCommand('ecommands', 'EHelp') + self:RegisterChatCommand('efixdb', 'DBConvertProfile') + -- self:RegisterChatCommand('aprilfools', '') --Don't need this until next april fools + + if E.private.actionbar.enable then + self:RegisterChatCommand('kb', AB.ActivateBindMode) + end +end diff --git a/Core/Config.lua b/Core/Config.lua new file mode 100644 index 0000000..c89813c --- /dev/null +++ b/Core/Config.lua @@ -0,0 +1,1226 @@ +local E, L, V, P, G = unpack(select(2, ...)); --Import: Engine, Locales, PrivateDB, ProfileDB, GlobalDB +local S = E:GetModule('Skins') + +local _G = _G +local unpack, sort, gsub, wipe = unpack, sort, gsub, wipe +local strupper, ipairs, tonumber = strupper, ipairs, tonumber +local floor, select, type, min = floor, select, type, min +local pairs, tinsert, tContains = pairs, tinsert, tContains +local strsplit = strsplit + +local hooksecurefunc = hooksecurefunc +local EnableAddOn = EnableAddOn +local LoadAddOn = LoadAddOn +local GetAddOnMetadata = GetAddOnMetadata +local GetAddOnInfo = GetAddOnInfo +local CreateFrame = CreateFrame +local IsAddOnLoaded = IsAddOnLoaded +local InCombatLockdown = InCombatLockdown +local IsControlKeyDown = IsControlKeyDown +local IsAltKeyDown = IsAltKeyDown +local EditBox_ClearFocus = EditBox_ClearFocus +local ERR_NOT_IN_COMBAT = ERR_NOT_IN_COMBAT +local RESET = RESET +-- GLOBALS: ElvUIMoverPopupWindow, ElvUIMoverNudgeWindow, ElvUIMoverPopupWindowDropDown + +local ConfigTooltip = CreateFrame('GameTooltip', 'ElvUIConfigTooltip', E.UIParent, 'GameTooltipTemplate, BackdropTemplate') + +local grid +E.ConfigModeLayouts = { + 'ALL', + 'GENERAL', + 'SOLO', + 'PARTY', + 'ARENA', + 'RAID', + 'ACTIONBARS' +} + +E.ConfigModeLocalizedStrings = { + ALL = _G.ALL, + GENERAL = _G.GENERAL, + SOLO = _G.SOLO, + PARTY = _G.PARTY, + ARENA = _G.ARENA, + RAID = _G.RAID, + ACTIONBARS = _G.ACTIONBARS_LABEL +} + +function E:Grid_Show() + if not grid then + E:Grid_Create() + elseif grid.boxSize ~= E.db.gridSize then + grid:Hide() + E:Grid_Create() + else + grid:Show() + end +end + +function E:Grid_Hide() + if grid then + grid:Hide() + end +end + +function E:ToggleMoveMode(which) + if InCombatLockdown() then return end + local mode = not E.ConfigurationMode + + if not which or which == '' then + E.ConfigurationMode = mode + which = 'all' + else + E.ConfigurationMode = true + mode = true + end + + self:ToggleMovers(mode, which) + + if mode then + E:Grid_Show() + _G.ElvUIGrid:SetAlpha(0.4) + + if not ElvUIMoverPopupWindow then + E:CreateMoverPopup() + end + + ElvUIMoverPopupWindow:Show() + _G.UIDropDownMenu_SetSelectedValue(ElvUIMoverPopupWindowDropDown, strupper(which)) + + if IsAddOnLoaded('ElvUI_OptionsUI') then + E:Config_CloseWindow() + end + else + E:Grid_Hide() + _G.ElvUIGrid:SetAlpha(1) + + if ElvUIMoverPopupWindow then + ElvUIMoverPopupWindow:Hide() + end + end +end + +function E:Grid_GetRegion() + if grid then + if grid.regionCount and grid.regionCount > 0 then + local line = select(grid.regionCount, grid:GetRegions()) + grid.regionCount = grid.regionCount - 1 + line:SetAlpha(1) + return line + else + return grid:CreateTexture() + end + end +end + +function E:Grid_Create() + if not grid then + grid = CreateFrame('Frame', 'ElvUIGrid', E.UIParent) + grid:SetFrameStrata('BACKGROUND') + else + grid.regionCount = 0 + local numRegions = grid:GetNumRegions() + for i = 1, numRegions do + local region = select(i, grid:GetRegions()) + if region and region.IsObjectType and region:IsObjectType('Texture') then + grid.regionCount = grid.regionCount + 1 + region:SetAlpha(0) + end + end + end + + local width, height = E.UIParent:GetSize() + local size, half = E.mult / 2, height / 2 + + local gSize = E.db.gridSize + local gHalf = gSize / 2 + + local ratio = width / height + local hHeight = height * ratio + local wStep = width / gSize + local hStep = hHeight / gSize + + grid.boxSize = gSize + grid:SetPoint('CENTER', E.UIParent) + grid:Size(width, height) + grid:Show() + + for i = 0, gSize do + local tx = E:Grid_GetRegion() + if i == gHalf then + tx:SetColorTexture(1, 0, 0) + tx:SetDrawLayer('BACKGROUND', 1) + else + tx:SetColorTexture(0, 0, 0) + tx:SetDrawLayer('BACKGROUND', 0) + end + + local iwStep = i*wStep + tx:ClearAllPoints() + tx:SetPoint('TOPLEFT', grid, 'TOPLEFT', iwStep - size, 0) + tx:SetPoint('BOTTOMRIGHT', grid, 'BOTTOMLEFT', iwStep + size, 0) + end + + do + local tx = E:Grid_GetRegion() + tx:SetColorTexture(1, 0, 0) + tx:SetDrawLayer('BACKGROUND', 1) + tx:ClearAllPoints() + tx:SetPoint('TOPLEFT', grid, 'TOPLEFT', 0, -half + size) + tx:SetPoint('BOTTOMRIGHT', grid, 'TOPRIGHT', 0, -(half + size)) + end + + local hSteps = floor((height/2)/hStep) + for i = 1, hSteps do + local ihStep = i*hStep + + local tx = E:Grid_GetRegion() + tx:SetColorTexture(0, 0, 0) + tx:SetDrawLayer('BACKGROUND', 0) + tx:ClearAllPoints() + tx:SetPoint('TOPLEFT', grid, 'TOPLEFT', 0, -(half+ihStep) + size) + tx:SetPoint('BOTTOMRIGHT', grid, 'TOPRIGHT', 0, -(half+ihStep + size)) + + tx = E:Grid_GetRegion() + tx:SetColorTexture(0, 0, 0) + tx:SetDrawLayer('BACKGROUND', 0) + tx:ClearAllPoints() + tx:SetPoint('TOPLEFT', grid, 'TOPLEFT', 0, -(half-ihStep) + size) + tx:SetPoint('BOTTOMRIGHT', grid, 'TOPRIGHT', 0, -(half-ihStep + size)) + end +end + +local function ConfigMode_OnClick(self) + E:ToggleMoveMode(self.value) +end + +local function ConfigMode_Initialize() + local info = _G.UIDropDownMenu_CreateInfo() + info.func = ConfigMode_OnClick + + for _, configMode in ipairs(E.ConfigModeLayouts) do + info.text = E.ConfigModeLocalizedStrings[configMode] + info.value = configMode + _G.UIDropDownMenu_AddButton(info) + end + + local dd = ElvUIMoverPopupWindowDropDown + _G.UIDropDownMenu_SetSelectedValue(dd, dd.selectedValue or 'ALL') +end + +function E:NudgeMover(nudgeX, nudgeY) + local mover = ElvUIMoverNudgeWindow.child + local x, y, point = E:CalculateMoverPoints(mover, nudgeX, nudgeY) + + mover:ClearAllPoints() + mover:SetPoint(point, E.UIParent, point, x, y) + E:SaveMoverPosition(mover.name) + + --Update coordinates in Nudge Window + E:UpdateNudgeFrame(mover, x, y) +end + +function E:UpdateNudgeFrame(mover, x, y) + if not (x and y) then + x, y = E:CalculateMoverPoints(mover) + end + + x = E:Round(x) + y = E:Round(y) + + local ElvUIMoverNudgeWindow = ElvUIMoverNudgeWindow + ElvUIMoverNudgeWindow.xOffset:SetText(x) + ElvUIMoverNudgeWindow.yOffset:SetText(y) + ElvUIMoverNudgeWindow.xOffset.currentValue = x + ElvUIMoverNudgeWindow.yOffset.currentValue = y + ElvUIMoverNudgeWindow.title:SetText(mover.textString) +end + +function E:AssignFrameToNudge() + ElvUIMoverNudgeWindow.child = self + E:UpdateNudgeFrame(self) +end + +function E:CreateMoverPopup() + local r, g, b = unpack(E.media.rgbvaluecolor) + + local f = CreateFrame('Frame', 'ElvUIMoverPopupWindow', _G.UIParent, 'BackdropTemplate') + f:SetFrameStrata('DIALOG') + f:SetToplevel(true) + f:EnableMouse(true) + f:SetMovable(true) + f:SetFrameLevel(99) + f:SetClampedToScreen(true) + f:Size(370, 190) + f:SetTemplate('Transparent') + f:Point('BOTTOM', _G.UIParent, 'CENTER', 0, 100) + f:Hide() + + local header = CreateFrame('Button', nil, f, 'BackdropTemplate') + header:SetTemplate(nil, true) + header:Size(100, 25) + header:SetPoint('CENTER', f, 'TOP') + header:SetFrameLevel(header:GetFrameLevel() + 2) + header:EnableMouse(true) + header:RegisterForClicks('AnyUp', 'AnyDown') + header:SetScript('OnMouseDown', function() f:StartMoving() end) + header:SetScript('OnMouseUp', function() f:StopMovingOrSizing() end) + f.header = header + + local title = header:CreateFontString(nil, 'OVERLAY') + title:FontTemplate() + title:Point('CENTER', header, 'CENTER') + title:SetText('ElvUI') + f.title = title + + local desc = f:CreateFontString(nil, 'ARTWORK') + desc:SetFontObject('GameFontHighlight') + desc:SetJustifyV('TOP') + desc:SetJustifyH('LEFT') + desc:Point('TOPLEFT', 18, -20) + desc:Point('BOTTOMRIGHT', -18, 48) + desc:SetText(L["DESC_MOVERCONFIG"]) + f.desc = desc + + local snapName = f:GetName()..'CheckButton' + local snapping = CreateFrame('CheckButton', snapName, f, 'OptionsCheckButtonTemplate') + snapping:SetScript('OnShow', function(cb) cb:SetChecked(E.db.general.stickyFrames) end) + snapping:SetScript('OnClick', function(cb) E.db.general.stickyFrames = cb:GetChecked() end) + snapping.text = _G[snapName..'Text'] + snapping.text:SetText(L["Sticky Frames"]) + f.snapping = snapping + + local lock = CreateFrame('Button', f:GetName()..'CloseButton', f, 'OptionsButtonTemplate, BackdropTemplate') + lock.Text:SetText(L["Lock"]) + lock:SetScript('OnClick', function() + E:ToggleMoveMode() + + if E.ConfigurationToggled then + E.ConfigurationToggled = nil + + if IsAddOnLoaded('ElvUI_OptionsUI') then + E:Config_OpenWindow() + end + end + end) + f.lock = lock + + local align = CreateFrame('EditBox', f:GetName()..'EditBox', f, 'InputBoxTemplate, BackdropTemplate') + align:Size(24, 17) + align:SetAutoFocus(false) + align:SetScript('OnEscapePressed', function(eb) + eb:SetText(E.db.gridSize) + EditBox_ClearFocus(eb) + end) + align:SetScript('OnEnterPressed', function(eb) + local text = eb:GetText() + if tonumber(text) then + if tonumber(text) <= 256 and tonumber(text) >= 4 then + E.db.gridSize = tonumber(text) + else + eb:SetText(E.db.gridSize) + end + else + eb:SetText(E.db.gridSize) + end + E:Grid_Show() + EditBox_ClearFocus(eb) + end) + align:SetScript('OnEditFocusLost', function(eb) + eb:SetText(E.db.gridSize) + end) + align:SetScript('OnEditFocusGained', align.HighlightText) + align:SetScript('OnShow', function(eb) + EditBox_ClearFocus(eb) + eb:SetText(E.db.gridSize) + end) + + align.text = align:CreateFontString(nil, 'OVERLAY', 'GameFontNormal') + align.text:Point('RIGHT', align, 'LEFT', -4, 0) + align.text:SetText(L["Grid Size:"]) + f.align = align + + --position buttons + snapping:Point('BOTTOMLEFT', 14, 10) + lock:Point('BOTTOMRIGHT', -14, 14) + align:Point('TOPRIGHT', lock, 'TOPLEFT', -4, -2) + + S:HandleCheckBox(snapping) + S:HandleButton(lock) + S:HandleEditBox(align) + + f:RegisterEvent('PLAYER_REGEN_DISABLED') + f:SetScript('OnEvent', function(mover) + if mover:IsShown() then + mover:Hide() + E:Grid_Hide() + E:ToggleMoveMode() + end + end) + + local dropDown = CreateFrame('Frame', f:GetName()..'DropDown', f, 'UIDropDownMenuTemplate') + dropDown:Point('BOTTOMRIGHT', lock, 'TOPRIGHT', 8, -5) + S:HandleDropDownBox(dropDown, 165) + dropDown.text = dropDown:CreateFontString(nil, 'OVERLAY', 'GameFontNormal') + dropDown.text:Point('RIGHT', dropDown.backdrop, 'LEFT', -2, 0) + dropDown.text:SetText(L["Config Mode:"]) + f.dropDown = dropDown + + _G.UIDropDownMenu_Initialize(dropDown, ConfigMode_Initialize) + + local nudgeFrame = CreateFrame('Frame', 'ElvUIMoverNudgeWindow', E.UIParent, 'BackdropTemplate') + nudgeFrame:SetFrameStrata('DIALOG') + nudgeFrame:Size(200, 110) + nudgeFrame:SetTemplate('Transparent') + nudgeFrame:CreateShadow(5) + nudgeFrame.shadow:SetBackdropBorderColor(r, g, b, 0.9) + nudgeFrame:SetFrameLevel(500) + nudgeFrame:EnableMouse(true) + nudgeFrame:SetClampedToScreen(true) + nudgeFrame:SetPropagateKeyboardInput(true) + nudgeFrame:SetScript('OnKeyDown', function(_, btn) + local Mod = IsAltKeyDown() or IsControlKeyDown() + if btn == 'NUMPAD4' then + E:NudgeMover(-1 * (Mod and 10 or 1)) + elseif btn == 'NUMPAD6' then + E:NudgeMover(1 * (Mod and 10 or 1)) + elseif btn == 'NUMPAD8' then + E:NudgeMover(nil, 1 * (Mod and 10 or 1)) + elseif btn == 'NUMPAD2' then + E:NudgeMover(nil, -1 * (Mod and 10 or 1)) + end + end) + + ElvUIMoverPopupWindow:HookScript('OnHide', function() ElvUIMoverNudgeWindow:Hide() end) + + desc = nudgeFrame:CreateFontString(nil, 'ARTWORK') + desc:SetFontObject('GameFontHighlight') + desc:SetJustifyV('TOP') + desc:SetJustifyH('LEFT') + desc:Point('TOPLEFT', 18, -15) + desc:Point('BOTTOMRIGHT', -18, 28) + desc:SetJustifyH('CENTER') + nudgeFrame.desc = desc + + header = CreateFrame('Button', nil, nudgeFrame, 'BackdropTemplate') + header:SetTemplate(nil, true) + header:Size(100, 25) + header:SetPoint('CENTER', nudgeFrame, 'TOP') + header:SetFrameLevel(header:GetFrameLevel() + 2) + nudgeFrame.header = header + + title = header:CreateFontString(nil, 'OVERLAY') + title:FontTemplate() + title:Point('CENTER', header, 'CENTER') + title:SetText(L["Nudge"]) + nudgeFrame.title = title + + local xOffset = CreateFrame('EditBox', nudgeFrame:GetName()..'XEditBox', nudgeFrame, 'InputBoxTemplate') + xOffset:Size(50, 17) + xOffset:SetAutoFocus(false) + xOffset.currentValue = 0 + xOffset:SetScript('OnEscapePressed', function(eb) + eb:SetText(E:Round(xOffset.currentValue)) + EditBox_ClearFocus(eb) + end) + xOffset:SetScript('OnEnterPressed', function(eb) + local num = eb:GetText() + if tonumber(num) then + local diff = num - xOffset.currentValue + xOffset.currentValue = num + E:NudgeMover(diff) + end + eb:SetText(E:Round(xOffset.currentValue)) + EditBox_ClearFocus(eb) + end) + xOffset:SetScript('OnEditFocusLost', function(eb) + eb:SetText(E:Round(xOffset.currentValue)) + end) + xOffset:SetScript('OnEditFocusGained', xOffset.HighlightText) + xOffset:SetScript('OnShow', function(eb) + EditBox_ClearFocus(eb) + eb:SetText(E:Round(xOffset.currentValue)) + end) + + xOffset.text = xOffset:CreateFontString(nil, 'OVERLAY', 'GameFontNormal') + xOffset.text:Point('RIGHT', xOffset, 'LEFT', -4, 0) + xOffset.text:SetText('X:') + xOffset:Point('BOTTOMRIGHT', nudgeFrame, 'CENTER', -6, 8) + S:HandleEditBox(xOffset) + nudgeFrame.xOffset = xOffset + + local yOffset = CreateFrame('EditBox', nudgeFrame:GetName()..'YEditBox', nudgeFrame, 'InputBoxTemplate') + yOffset:Size(50, 17) + yOffset:SetAutoFocus(false) + yOffset.currentValue = 0 + yOffset:SetScript('OnEscapePressed', function(eb) + eb:SetText(E:Round(yOffset.currentValue)) + EditBox_ClearFocus(eb) + end) + yOffset:SetScript('OnEnterPressed', function(eb) + local num = eb:GetText() + if tonumber(num) then + local diff = num - yOffset.currentValue + yOffset.currentValue = num + E:NudgeMover(nil, diff) + end + eb:SetText(E:Round(yOffset.currentValue)) + EditBox_ClearFocus(eb) + end) + yOffset:SetScript('OnEditFocusLost', function(eb) + eb:SetText(E:Round(yOffset.currentValue)) + end) + yOffset:SetScript('OnEditFocusGained', yOffset.HighlightText) + yOffset:SetScript('OnShow', function(eb) + EditBox_ClearFocus(eb) + eb:SetText(E:Round(yOffset.currentValue)) + end) + + yOffset.text = yOffset:CreateFontString(nil, 'OVERLAY', 'GameFontNormal') + yOffset.text:Point('RIGHT', yOffset, 'LEFT', -4, 0) + yOffset.text:SetText('Y:') + yOffset:Point('BOTTOMLEFT', nudgeFrame, 'CENTER', 16, 8) + S:HandleEditBox(yOffset) + nudgeFrame.yOffset = yOffset + + local resetButton = CreateFrame('Button', nudgeFrame:GetName()..'ResetButton', nudgeFrame, 'UIPanelButtonTemplate, BackdropTemplate') + resetButton:SetText(RESET) + resetButton:Point('TOP', nudgeFrame, 'CENTER', 0, 2) + resetButton:Size(100, 25) + resetButton:SetScript('OnClick', function() + if ElvUIMoverNudgeWindow.child.textString then + E:ResetMovers(ElvUIMoverNudgeWindow.child.textString) + end + end) + S:HandleButton(resetButton) + nudgeFrame.resetButton = resetButton + + local upButton = CreateFrame('Button', nudgeFrame:GetName()..'UpButton', nudgeFrame, 'BackdropTemplate') + upButton:Point('BOTTOMRIGHT', nudgeFrame, 'BOTTOM', -6, 4) + upButton:SetScript('OnClick', function() E:NudgeMover(nil, 1) end) + S:HandleNextPrevButton(upButton) + S:HandleButton(upButton) + upButton:Size(22) + nudgeFrame.upButton = upButton + + local downButton = CreateFrame('Button', nudgeFrame:GetName()..'DownButton', nudgeFrame, 'BackdropTemplate') + downButton:Point('BOTTOMLEFT', nudgeFrame, 'BOTTOM', 6, 4) + downButton:SetScript('OnClick', function() E:NudgeMover(nil, -1) end) + S:HandleNextPrevButton(downButton) + S:HandleButton(downButton) + downButton:Size(22) + nudgeFrame.downButton = downButton + + local leftButton = CreateFrame('Button', nudgeFrame:GetName()..'LeftButton', nudgeFrame, 'BackdropTemplate') + leftButton:Point('RIGHT', upButton, 'LEFT', -6, 0) + leftButton:SetScript('OnClick', function() E:NudgeMover(-1) end) + S:HandleNextPrevButton(leftButton) + S:HandleButton(leftButton) + leftButton:Size(22) + nudgeFrame.leftButton = leftButton + + local rightButton = CreateFrame('Button', nudgeFrame:GetName()..'RightButton', nudgeFrame, 'BackdropTemplate') + rightButton:Point('LEFT', downButton, 'RIGHT', 6, 0) + rightButton:SetScript('OnClick', function() E:NudgeMover(1) end) + S:HandleNextPrevButton(rightButton) + S:HandleButton(rightButton) + rightButton:Size(22) + nudgeFrame.rightButton = rightButton +end + +function E:Config_ResetSettings() + E.configSavedPositionTop, E.configSavedPositionLeft = nil, nil + E.global.general.AceGUI = E:CopyTable({}, E.DF.global.general.AceGUI) +end + +function E:Config_GetPosition() + return E.configSavedPositionTop, E.configSavedPositionLeft +end + +function E:Config_GetSize() + return E.global.general.AceGUI.width, E.global.general.AceGUI.height +end + +function E:Config_UpdateSize(reset) + local frame = E:Config_GetWindow() + if not frame then return end + + local maxWidth, maxHeight = self.UIParent:GetSize() + frame:SetMinResize(800, 600) + frame:SetMaxResize(maxWidth-50, maxHeight-50) + + self.Libs.AceConfigDialog:SetDefaultSize(E.name, E:Config_GetDefaultSize()) + + local status = frame.obj and frame.obj.status + if status then + if reset then + E:Config_ResetSettings() + + status.top, status.left = E:Config_GetPosition() + status.width, status.height = E:Config_GetDefaultSize() + + frame.obj:ApplyStatus() + else + local top, left = E:Config_GetPosition() + if top and left then + status.top, status.left = top, left + + frame.obj:ApplyStatus() + end + end + + E:Config_UpdateLeftScroller(frame) + end +end + +function E:Config_GetDefaultSize() + local width, height = E:Config_GetSize() + local maxWidth, maxHeight = E.UIParent:GetSize() + width, height = min(maxWidth-50, width), min(maxHeight-50, height) + return width, height +end + +function E:Config_StopMoving() + local frame = self and self.GetParent and self:GetParent() + if frame and frame.obj and frame.obj.status then + E.configSavedPositionTop, E.configSavedPositionLeft = E:Round(frame:GetTop(), 2), E:Round(frame:GetLeft(), 2) + E.global.general.AceGUI.width, E.global.general.AceGUI.height = E:Round(frame:GetWidth(), 2), E:Round(frame:GetHeight(), 2) + E:Config_UpdateLeftScroller(frame) + end +end + +local function Config_ButtonOnEnter(self) + if ConfigTooltip:IsForbidden() or not self.desc then return end + + ConfigTooltip:SetOwner(self, 'ANCHOR_TOPRIGHT', 0, 2) + ConfigTooltip:AddLine(self.desc, 1, 1, 1, true) + ConfigTooltip:Show() +end + +local function Config_ButtonOnLeave() + if ConfigTooltip:IsForbidden() then return end + + ConfigTooltip:Hide() +end + +local function Config_StripNameColor(name) + if type(name) == 'function' then name = name() end + return E:StripString(name) +end + +local function Config_SortButtons(a,b) + local A1, B1 = a[1], b[1] + if A1 and B1 then + if A1 == B1 then + local A3, B3 = a[3], b[3] + if A3 and B3 and (A3.name and B3.name) then + return Config_StripNameColor(A3.name) < Config_StripNameColor(B3.name) + end + end + return A1 < B1 + end +end + +local function ConfigSliderOnMouseWheel(self, offset) + local _, maxValue = self:GetMinMaxValues() + if maxValue == 0 then return end + + local newValue = self:GetValue() - offset + if newValue < 0 then newValue = 0 end + if newValue > maxValue then return end + + self:SetValue(newValue) + self.buttons:Point('TOPLEFT', 0, newValue * 36) +end + +local function ConfigSliderOnValueChanged(self, value) + self:SetValue(value) + self.buttons:Point('TOPLEFT', 0, value * 36) +end + +function E:Config_SetButtonText(btn, noColor) + local name = btn.info.name + if type(name) == 'function' then name = name() end + + if noColor then + btn:SetText(name:gsub('|c[fF][fF]%x%x%x%x%x%x',''):gsub('|r','')) + else + btn:SetText(name) + end +end + +function E:Config_CreateSeparatorLine(frame, lastButton) + local line = frame.leftHolder.buttons:CreateTexture() + line:SetTexture(E.Media.Textures.White8x8) + line:SetVertexColor(1, .82, 0, .4) + line:Size(179, 2) + line:Point('TOP', lastButton, 'BOTTOM', 0, -6) + line.separator = true + return line +end + +function E:Config_SetButtonColor(btn, disabled) + if disabled then + btn:Disable() + btn:SetBackdropBorderColor(1, .82, 0, 1) + btn:SetBackdropColor(1, .82, 0, 0.4) + btn.Text:SetTextColor(1, 1, 1) + E:Config_SetButtonText(btn, true) + else + btn:Enable() + local r, g, b = unpack(E.media.bordercolor) + btn:SetBackdropBorderColor(r, g, b, 1) + r, g, b = unpack(E.media.backdropcolor) + btn:SetBackdropColor(r, g, b, 1) + btn.Text:SetTextColor(1, .82, 0) + E:Config_SetButtonText(btn) + end +end + +function E:Config_UpdateSliderPosition(btn) + local left = btn and btn.frame and btn.frame.leftHolder + if left and left.slider then + ConfigSliderOnValueChanged(left.slider, btn.sliderValue or 0) + end +end + +function E:Config_CreateButton(info, frame, unskinned, ...) + local btn = CreateFrame(...) + btn.frame = frame + btn.desc = info.desc + btn.info = info + + if not unskinned then + E.Skins:HandleButton(btn, nil, nil, nil, true) + end + + E:Config_SetButtonText(btn) + E:Config_SetButtonColor(btn, btn.info.key == 'general') + btn:HookScript('OnEnter', Config_ButtonOnEnter) + btn:HookScript('OnLeave', Config_ButtonOnLeave) + btn:SetScript('OnClick', info.func) + btn:Width(btn:GetTextWidth() + 40) + + return btn +end + +function E:Config_DialogOpened(name) + if name ~= 'ElvUI' then return end + + local frame = E:Config_GetWindow() + if frame and frame.leftHolder then + E:Config_WindowOpened(frame) + end +end + +function E:Config_UpdateLeftButtons() + local frame = E:Config_GetWindow() + if not (frame and frame.leftHolder) then return end + + local status = frame.obj.status + local selected = status and status.groups.selected + for _, btn in ipairs(frame.leftHolder.buttons) do + if type(btn) == 'table' and btn.IsObjectType and btn:IsObjectType('Button') then + local enabled = btn.info.key == selected + E:Config_SetButtonColor(btn, enabled) + + if enabled then + E:Config_UpdateSliderPosition(btn) + end + end + end +end + +function E:Config_UpdateLeftScroller(frame) + local left = frame and frame.leftHolder + if not left then return end + + local btns = left.buttons + local bottom = btns:GetBottom() + if not bottom then return end + btns:Point('TOPLEFT', 0, 0) + + local max = 0 + for _, btn in ipairs(btns) do + local button = type(btn) == 'table' and btn.IsObjectType and btn:IsObjectType('Button') + if button then + btn.sliderValue = nil + + local btm = btn:GetBottom() + if btm then + if bottom > btm then + max = max + 1 + btn.sliderValue = max + end + end + end + end + + local slider = left.slider + slider:SetMinMaxValues(0, max) + slider:SetValue(0) + + if max == 0 then + slider.thumb:Hide() + else + slider.thumb:Show() + end +end + +function E:Config_SaveOldPosition(frame) + if frame.GetNumPoints and not frame.oldPosition then + frame.oldPosition = {} + for i = 1, frame:GetNumPoints() do + tinsert(frame.oldPosition, {frame:GetPoint(i)}) + end + end +end + +function E:Config_RestoreOldPosition(frame) + local position = frame.oldPosition + if position then + frame:ClearAllPoints() + for i = 1, #position do + frame:Point(unpack(position[i])) + end + end +end + +function E:Config_CreateLeftButtons(frame, unskinned, options) + local opts = {} + for key, info in pairs(options) do + if (not info.order or info.order < 6) and not tContains(E.OriginalOptions, key) then + info.order = 6 + end + if key == 'profiles' then + info.desc = nil + end + tinsert(opts, {info.order, key, info}) + end + sort(opts, Config_SortButtons) + + local buttons, last, order = frame.leftHolder.buttons + for index, opt in ipairs(opts) do + local info = opt[3] + local key = opt[2] + + if (order == 2 or order == 5) and order < opt[1] then + last = E:Config_CreateSeparatorLine(frame, last) + end + + order = opt[1] + info.key = key + info.func = function() + local ACD = E.Libs.AceConfigDialog + if ACD then ACD:SelectGroup('ElvUI', key) end + end + + local btn = E:Config_CreateButton(info, frame, unskinned, 'Button', nil, buttons, 'UIPanelButtonTemplate, BackdropTemplate') + btn:Width(177) + + if not last then + btn:Point('TOP', buttons, 'TOP', 0, 0) + else + btn:Point('TOP', last, 'BOTTOM', 0, (last.separator and -6) or -4) + end + + buttons[index] = btn + last = btn + end +end + +function E:Config_CloseClicked() + if self.originalClose then + self.originalClose:Click() + end +end + +function E:Config_CloseWindow() + local ACD = E.Libs.AceConfigDialog + if ACD then + ACD:Close('ElvUI') + end + + if not ConfigTooltip:IsForbidden() then + ConfigTooltip:Hide() + end +end + +function E:Config_OpenWindow() + local ACD = E.Libs.AceConfigDialog + if ACD then ACD:Open('ElvUI') end + + if not ConfigTooltip:IsForbidden() then + ConfigTooltip:Hide() + end +end + +function E:Config_GetWindow() + local ACD = E.Libs.AceConfigDialog + local ConfigOpen = ACD and ACD.OpenFrames and ACD.OpenFrames[E.name] + return ConfigOpen and ConfigOpen.frame +end + +local ConfigLogoTop +E.valueColorUpdateFuncs[function(_, r, g, b) + if ConfigLogoTop then + ConfigLogoTop:SetVertexColor(r, g, b) + end + + if ElvUIMoverNudgeWindow and ElvUIMoverNudgeWindow.shadow then + ElvUIMoverNudgeWindow.shadow:SetBackdropBorderColor(r, g, b, 0.9) + end +end] = true + +function E:Config_WindowClosed() + if not self.bottomHolder then return end + + local frame = E:Config_GetWindow() + if not frame or frame ~= self then + self.bottomHolder:Hide() + self.leftHolder:Hide() + self.topHolder:Hide() + self.leftHolder.slider:Hide() + self.closeButton:Hide() + self.originalClose:Show() + + ConfigLogoTop = nil + + E:StopElasticize(self.leftHolder.LogoTop) + E:StopElasticize(self.leftHolder.LogoBottom) + + E:Config_RestoreOldPosition(self.topHolder.version) + E:Config_RestoreOldPosition(self.obj.content) + E:Config_RestoreOldPosition(self.obj.titlebg) + end +end + +function E:Config_WindowOpened(frame) + if frame and frame.bottomHolder and not ConfigLogoTop then + frame.bottomHolder:Show() + frame.leftHolder:Show() + frame.topHolder:Show() + frame.leftHolder.slider:Show() + frame.closeButton:Show() + frame.originalClose:Hide() + + frame.leftHolder.LogoTop:SetVertexColor(unpack(E.media.rgbvaluecolor)) + ConfigLogoTop = frame.leftHolder.LogoTop + + E:Elasticize(frame.leftHolder.LogoTop, 128, 64) + E:Elasticize(frame.leftHolder.LogoBottom, 128, 64) + + local unskinned = not E.private.skins.ace3Enable + local offset = unskinned and 14 or 8 + local version = frame.topHolder.version + E:Config_SaveOldPosition(version) + version:ClearAllPoints() + version:Point('LEFT', frame.topHolder, 'LEFT', unskinned and 8 or 6, unskinned and -4 or 0) + + local holderHeight = frame.bottomHolder:GetHeight() + local content = frame.obj.content + E:Config_SaveOldPosition(content) + content:ClearAllPoints() + content:Point('TOPLEFT', frame, 'TOPLEFT', offset, -(unskinned and 50 or 40)) + content:Point('BOTTOMRIGHT', frame, 'BOTTOMRIGHT', -offset, holderHeight + 3) + + local titlebg = frame.obj.titlebg + E:Config_SaveOldPosition(titlebg) + titlebg:ClearAllPoints() + titlebg:SetPoint('TOPLEFT', frame) + titlebg:SetPoint('TOPRIGHT', frame) + end +end + +function E:Config_CreateBottomButtons(frame, unskinned) + local L = E.Libs.ACL:GetLocale('ElvUI', E.global.general.locale or 'enUS') + + local last + for _, info in ipairs({ + { + var = 'ToggleAnchors', + name = L["Toggle Anchors"], + desc = L["Unlock various elements of the UI to be repositioned."], + func = function() + E:ToggleMoveMode() + E.ConfigurationToggled = true + end + }, + { + var = 'ResetAnchors', + name = L["Reset Anchors"], + desc = L["Reset all frames to their original positions."], + func = function() E:ResetUI() end + }, + { + var = 'RepositionWindow', + name = L["Reposition Window"], + desc = L["Reset the size and position of this frame."], + func = function() E:Config_UpdateSize(true) end + }, + { + var = 'Install', + name = L["Install"], + desc = L["Run the installation process."], + func = function() + E:Install() + E:ToggleOptionsUI() + end + }, + { + var = 'ToggleTutorials', + name = L["Toggle Tutorials"], + func = function() + E:Tutorials(true) + E:ToggleOptionsUI() + end + }, + { + var = 'ShowStatusReport', + name = L["ElvUI Status"], + desc = L["Shows a frame with needed info for support."], + func = function() + E:ShowStatusReport() + E:ToggleOptionsUI() + E.StatusReportToggled = true + end + } + }) do + local btn = E:Config_CreateButton(info, frame, unskinned, 'Button', nil, frame.bottomHolder, 'UIPanelButtonTemplate, BackdropTemplate') + local offset = unskinned and 14 or 8 + + if not last then + btn:Point('BOTTOMLEFT', frame.bottomHolder, 'BOTTOMLEFT', unskinned and 24 or offset, offset) + last = btn + else + btn:Point('LEFT', last, 'RIGHT', 4, 0) + last = btn + end + + frame.bottomHolder[info.var] = btn + end +end + +local pageNodes = {} +function E:Config_GetToggleMode(frame, msg) + local pages, msgStr + if msg and msg ~= '' then + pages = {strsplit(',', msg)} + msgStr = gsub(msg, ',', '\001') + end + + local empty = pages ~= nil + if not frame or empty then + if empty then + local ACD = E.Libs.AceConfigDialog + local pageCount, index, mainSel = #pages + if pageCount > 1 then + wipe(pageNodes) + index = 0 + + local main, mainNode, mainSelStr, sub, subNode, subSel + for i = 1, pageCount do + if i == 1 then + main = pages[i] and ACD and ACD.Status and ACD.Status.ElvUI + mainSel = main and main.status and main.status.groups and main.status.groups.selected + mainSelStr = mainSel and ('^'..E:EscapeString(mainSel)..'\001') + mainNode = main and main.children and main.children[pages[i]] + pageNodes[index+1], pageNodes[index+2] = main, mainNode + else + sub = pages[i] and pageNodes[i] and ((i == pageCount and pageNodes[i]) or pageNodes[i].children[pages[i]]) + subSel = sub and sub.status and sub.status.groups and sub.status.groups.selected + subNode = (mainSelStr and msgStr:match(mainSelStr..E:EscapeString(pages[i])..'$') and (subSel and subSel == pages[i])) or ((i == pageCount and not subSel) and mainSel and mainSel == msgStr) + pageNodes[index+1], pageNodes[index+2] = sub, subNode + end + index = index + 2 + end + else + local main = pages[1] and ACD and ACD.Status and ACD.Status.ElvUI + mainSel = main and main.status and main.status.groups and main.status.groups.selected + end + + if frame and ((not index and mainSel and mainSel == msg) or (index and pageNodes and pageNodes[index])) then + return 'Close' + else + return 'Open', pages + end + else + return 'Open' + end + else + return 'Close' + end +end + +function E:ToggleOptionsUI(msg) + if InCombatLockdown() then + self:Print(ERR_NOT_IN_COMBAT) + self.ShowOptionsUI = true + return + end + + if not IsAddOnLoaded('ElvUI_OptionsUI') then + local noConfig + local _, _, _, _, reason = GetAddOnInfo('ElvUI_OptionsUI') + + if reason ~= 'MISSING' then + EnableAddOn('ElvUI_OptionsUI') + LoadAddOn('ElvUI_OptionsUI') + + -- version check elvui options if it's actually enabled + if GetAddOnMetadata('ElvUI_OptionsUI', 'Version') ~= '1.07' then + self:StaticPopup_Show('CLIENT_UPDATE_REQUEST') + end + else + noConfig = true + end + + if noConfig then + self:Print('|cffff0000Error -- Addon "ElvUI_OptionsUI" not found.|r') + return + end + end + + local frame = E:Config_GetWindow() + local mode, pages = E:Config_GetToggleMode(frame, msg) + + local ACD = E.Libs.AceConfigDialog + if ACD then + if not ACD.OpenHookedElvUI then + hooksecurefunc(E.Libs.AceConfigDialog, 'Open', E.Config_DialogOpened) + ACD.OpenHookedElvUI = true + end + + ACD[mode](ACD, E.name) + end + + if not frame then + frame = E:Config_GetWindow() + end + + if mode == 'Open' and frame then + local ACR = E.Libs.AceConfigRegistry + if ACR and not ACR.NotifyHookedElvUI then + hooksecurefunc(E.Libs.AceConfigRegistry, 'NotifyChange', E.Config_UpdateLeftButtons) + ACR.NotifyHookedElvUI = true + E:Config_UpdateSize() + end + + if not frame.bottomHolder then -- window was released or never opened + frame:HookScript('OnHide', E.Config_WindowClosed) + + for i=1, frame:GetNumChildren() do + local child = select(i, frame:GetChildren()) + if child:IsObjectType('Button') and child:GetText() == _G.CLOSE then + frame.originalClose = child + child:Hide() + elseif child:IsObjectType('Frame') or child:IsObjectType('Button') then + if child:HasScript('OnMouseUp') then + child:HookScript('OnMouseUp', E.Config_StopMoving) + end + end + end + + local unskinned = not E.private.skins.ace3Enable + if unskinned then + for i=1, frame:GetNumRegions() do + local region = select(i, frame:GetRegions()) + if region:IsObjectType('Texture') and region:GetTexture() == 131080 then + region:SetAlpha(0) + end + end + end + + local bottom = CreateFrame('Frame', nil, frame, 'BackdropTemplate') + bottom:Point('BOTTOMLEFT', 2, 2) + bottom:Point('BOTTOMRIGHT', -2, 2) + bottom:Height(37) + frame.bottomHolder = bottom + + local close = CreateFrame('Button', nil, frame, 'UIPanelCloseButton, BackdropTemplate') + close:SetScript('OnClick', E.Config_CloseClicked) + close:SetFrameLevel(1000) + close:Point('TOPRIGHT', unskinned and -8 or 1, unskinned and -8 or 2) + close:Size(32, 32) + close.originalClose = frame.originalClose + frame.closeButton = close + + local left = CreateFrame('Frame', nil, frame, 'BackdropTemplate') + left:Point('BOTTOMRIGHT', bottom, 'BOTTOMLEFT', 181, 0) + left:Point('BOTTOMLEFT', bottom, 'TOPLEFT', 0, 1) + left:Point('TOPLEFT', unskinned and 10 or 2, unskinned and -6 or -2) + frame.leftHolder = left + + local top = CreateFrame('Frame', nil, frame, 'BackdropTemplate') + top.version = frame.obj.titletext + top:Point('TOPRIGHT', frame, -2, 0) + top:Point('TOPLEFT', left, 'TOPRIGHT', 1, 0) + top:Height(24) + frame.topHolder = top + + local LogoBottom = left:CreateTexture() + LogoBottom:SetTexture(E.Media.Textures.LogoBottomSmall) + LogoBottom:Point('CENTER', left, 'TOP', unskinned and 10 or 0, unskinned and -40 or -36) + LogoBottom:Size(128, 64) + left.LogoBottom = LogoBottom + + local LogoTop = left:CreateTexture() + LogoTop:SetTexture(E.Media.Textures.LogoTopSmall) + LogoTop:Point('CENTER', left, 'TOP', unskinned and 10 or 0, unskinned and -40 or -36) + LogoTop:Size(128, 64) + left.LogoTop = LogoTop + + local buttonsHolder = CreateFrame('Frame', nil, left) + buttonsHolder:Point('BOTTOMLEFT', bottom, 'TOPLEFT', 0, 1) + buttonsHolder:Point('TOPLEFT', left, 'TOPLEFT', 0, -70) + buttonsHolder:Point('BOTTOMRIGHT') + buttonsHolder:SetFrameLevel(5) + buttonsHolder:SetClipsChildren(true) + left.buttonsHolder = buttonsHolder + + local buttons = CreateFrame('Frame', nil, buttonsHolder, 'BackdropTemplate') + buttons:Point('BOTTOMLEFT', bottom, 'TOPLEFT', 0, 1) + buttons:Point('BOTTOMRIGHT') + buttons:Point('TOPLEFT', 0, 0) + left.buttons = buttons + + local slider = CreateFrame('Slider', nil, frame, 'BackdropTemplate') + slider:SetThumbTexture(E.Media.Textures.White8x8) + slider:SetScript('OnMouseWheel', ConfigSliderOnMouseWheel) + slider:SetScript('OnValueChanged', ConfigSliderOnValueChanged) + slider:SetOrientation('VERTICAL') + slider:SetObeyStepOnDrag(true) + slider:SetFrameLevel(4) + slider:SetValueStep(1) + slider:SetValue(0) + slider:Width(192) + slider:Point('BOTTOMLEFT', bottom, 'TOPLEFT', 0, 1) + slider:Point('TOPLEFT', buttons, 'TOPLEFT', 0, 0) + slider.buttons = buttons + left.slider = slider + + local thumb = slider:GetThumbTexture() + thumb:Point('LEFT', left, 'RIGHT', 2, 0) + thumb:SetVertexColor(1, 1, 1, 0.5) + thumb:Size(8, 12) + left.slider.thumb = thumb + + if not unskinned then + bottom:SetTemplate('Transparent') + left:SetTemplate('Transparent') + top:SetTemplate('Transparent') + E.Skins:HandleCloseButton(close) + end + + E:Config_CreateLeftButtons(frame, unskinned, E.Options.args) + E:Config_CreateBottomButtons(frame, unskinned) + E:Config_UpdateLeftScroller(frame) + E:Config_WindowOpened(frame) + end + + if ACD and pages then + ACD:SelectGroup(E.name, unpack(pages)) + end + end +end diff --git a/Core/Cooldowns.lua b/Core/Cooldowns.lua new file mode 100644 index 0000000..50dde63 --- /dev/null +++ b/Core/Cooldowns.lua @@ -0,0 +1,347 @@ +local E, L, V, P, G = unpack(select(2, ...)); --Import: Engine, Locales, PrivateDB, ProfileDB, GlobalDB +local AB = E:GetModule('ActionBars') +local LSM = E.Libs.LSM + +local next, ipairs, pairs = next, ipairs, pairs +local floor, tinsert = floor, tinsert + +local GetTime = GetTime +local CreateFrame = CreateFrame +local hooksecurefunc = hooksecurefunc + +local ICON_SIZE = 36 --the normal size for an icon (don't change this) +local FONT_SIZE = 20 --the base font size to use at a scale of 1 +local MIN_SCALE = 0.5 --the minimum scale we want to show cooldown counts at, anything below this will be hidden +local MIN_DURATION = 1.5 --the minimum duration to show cooldown text for + +function E:Cooldown_TextThreshold(cd, now) + if cd.parent and cd.parent.textThreshold and cd.endTime then + return (cd.endTime - now) >= cd.parent.textThreshold + end +end + +function E:Cooldown_BelowScale(cd) + if cd.parent then + if cd.parent.hideText then return true end + if cd.parent.skipScale then return end + end + + return cd.fontScale and (cd.fontScale < MIN_SCALE) +end + +function E:Cooldown_OnUpdate(elapsed) + local forced = elapsed == -1 + if forced then + self.nextUpdate = 0 + elseif self.nextUpdate > 0 then + self.nextUpdate = self.nextUpdate - elapsed + return + end + + if not E:Cooldown_IsEnabled(self) then + E:Cooldown_StopTimer(self) + else + local now = GetTime() + if self.endCooldown and now >= self.endCooldown then + E:Cooldown_StopTimer(self) + elseif E:Cooldown_BelowScale(self) then + self.text:SetText('') + if not forced then + self.nextUpdate = 500 + end + elseif E:Cooldown_TextThreshold(self, now) then + self.text:SetText('') + if not forced then + self.nextUpdate = 1 + end + elseif self.endTime then + local value, id, nextUpdate, remainder = E:GetTimeInfo(self.endTime - now, self.threshold, self.hhmmThreshold, self.mmssThreshold) + if not forced then + self.nextUpdate = nextUpdate + end + + local style = E.TimeFormats[id] + if style then + local which = (self.textColors and 2 or 1) + (self.showSeconds and 0 or 2) + if self.textColors then + self.text:SetFormattedText(style[which], value, self.textColors[id], remainder) + else + self.text:SetFormattedText(style[which], value, remainder) + end + end + + local color = not self.skipTextColor and self.timeColors[id] + if color then self.text:SetTextColor(color.r, color.g, color.b) end + end + end +end + +function E:Cooldown_OnSizeChanged(cd, width, force) + local scale = width and (floor(width + 0.5) / ICON_SIZE) + + -- dont bother updating when the fontScale is the same, unless we are passing the force arg + if scale and (scale == cd.fontScale) and (force ~= true) then return end + cd.fontScale = scale + + -- this is needed because of skipScale variable, we wont allow a font size under the minscale + if cd.fontScale and (cd.fontScale < MIN_SCALE) then + scale = MIN_SCALE + end + + if cd.customFont then -- override font + cd.text:FontTemplate(cd.customFont, (scale * cd.customFontSize), cd.customFontOutline) + elseif scale then -- default, no override + cd.text:FontTemplate(nil, (scale * FONT_SIZE), 'OUTLINE') + else -- this should never happen but just incase + cd.text:FontTemplate() + end +end + +function E:Cooldown_IsEnabled(cd) + if cd.forceEnabled then + return true + elseif cd.forceDisabled then + return false + elseif cd.reverseToggle ~= nil then + return cd.reverseToggle + else + return E.db.cooldown.enable + end +end + +function E:Cooldown_ForceUpdate(cd) + E.Cooldown_OnUpdate(cd, -1) + cd:Show() +end + +function E:Cooldown_StopTimer(cd) + cd:Hide() +end + +function E:Cooldown_Options(timer, db, parent) + local threshold, colors, icolors, hhmm, mmss, fonts + if parent and db.override then + threshold = db.threshold + icolors = db.useIndicatorColor and E.TimeIndicatorColors[parent.CooldownOverride] + colors = E.TimeColors[parent.CooldownOverride] + end + + if db.checkSeconds then + hhmm, mmss = db.hhmmThreshold, db.mmssThreshold + end + + timer.timeColors = colors or E.TimeColors + timer.threshold = threshold or E.db.cooldown.threshold or E.TimeThreshold + timer.textColors = icolors or (E.db.cooldown.useIndicatorColor and E.TimeIndicatorColors) + timer.hhmmThreshold = hhmm or (E.db.cooldown.checkSeconds and E.db.cooldown.hhmmThreshold) + timer.mmssThreshold = mmss or (E.db.cooldown.checkSeconds and E.db.cooldown.mmssThreshold) + timer.hideBlizzard = db.hideBlizzard or E.db.cooldown.hideBlizzard + + if db.reverse ~= nil then + timer.reverseToggle = (E.db.cooldown.enable and not db.reverse) or (db.reverse and not E.db.cooldown.enable) + else + timer.reverseToggle = nil + end + + if timer.CooldownOverride ~= 'auras' then + if (db ~= E.db.cooldown) and db.fonts and db.fonts.enable then + fonts = db.fonts -- custom fonts override default fonts + elseif E.db.cooldown.fonts and E.db.cooldown.fonts.enable then + fonts = E.db.cooldown.fonts -- default global font override + end + + if fonts and fonts.enable then + timer.customFont = LSM:Fetch('font', fonts.font) + timer.customFontSize = fonts.fontSize + timer.customFontOutline = fonts.fontOutline + else + timer.customFont = nil + timer.customFontSize = nil + timer.customFontOutline = nil + end + end +end + +function E:CreateCooldownTimer(parent) + local timer = CreateFrame('Frame', nil, parent) + timer:Hide() + timer:SetAllPoints() + timer.parent = parent + parent.timer = timer + + local text = timer:CreateFontString(nil, 'OVERLAY') + text:Point('CENTER', 1, 1) + text:SetJustifyH('CENTER') + timer.text = text + + -- can be used to modify elements created from this function + if parent.CooldownPreHook then + parent.CooldownPreHook(parent) + end + + -- cooldown override settings + local db = (parent.CooldownOverride and E.db[parent.CooldownOverride]) or E.db + if db and db.cooldown then + E:Cooldown_Options(timer, db.cooldown, parent) + + -- prevent LibActionBar from showing blizzard CD when the CD timer is created + if parent.CooldownOverride == 'actionbar' then + AB:ToggleCountDownNumbers(nil, nil, parent) + end + end + + E:ToggleBlizzardCooldownText(parent, timer) + + -- keep an eye on the size so we can rescale the font if needed + E:Cooldown_OnSizeChanged(timer, parent:GetWidth()) + parent:SetScript('OnSizeChanged', function(_, width) + E:Cooldown_OnSizeChanged(timer, width) + end) + + -- keep this after Cooldown_OnSizeChanged + timer:SetScript('OnUpdate', E.Cooldown_OnUpdate) + + return timer +end + +E.RegisteredCooldowns = {} +function E:OnSetCooldown(start, duration) + if not self.forceDisabled and (start and duration) and (duration > MIN_DURATION) then + local timer = self.timer or E:CreateCooldownTimer(self) + timer.start = start + timer.duration = duration + timer.endTime = start + duration + timer.endCooldown = timer.endTime - 0.05 + E:Cooldown_ForceUpdate(timer) + elseif self.timer then + E:Cooldown_StopTimer(self.timer) + end +end + +function E:RegisterCooldown(cooldown) + if not cooldown.isHooked then + hooksecurefunc(cooldown, 'SetCooldown', E.OnSetCooldown) + cooldown.isHooked = true + end + + if not cooldown.isRegisteredCooldown then + local module = (cooldown.CooldownOverride or 'global') + if not E.RegisteredCooldowns[module] then E.RegisteredCooldowns[module] = {} end + + tinsert(E.RegisteredCooldowns[module], cooldown) + cooldown.isRegisteredCooldown = true + end +end + +function E:ToggleBlizzardCooldownText(cd, timer, request) + -- we should hide the blizzard cooldown text when ours are enabled + if timer and cd and cd.SetHideCountdownNumbers then + local forceHide = cd.hideText or timer.hideBlizzard + if request then + return forceHide or E:Cooldown_IsEnabled(timer) + else + cd:SetHideCountdownNumbers(forceHide or E:Cooldown_IsEnabled(timer)) + end + end +end + +function E:GetCooldownColors(db) + if not db then db = E.db.cooldown end -- just incase someone calls this without a first arg use the global + local c13 = E:RGBToHex(db.hhmmColorIndicator.r, db.hhmmColorIndicator.g, db.hhmmColorIndicator.b) -- color for timers that are soon to expire + local c12 = E:RGBToHex(db.mmssColorIndicator.r, db.mmssColorIndicator.g, db.mmssColorIndicator.b) -- color for timers that are soon to expire + local c11 = E:RGBToHex(db.expireIndicator.r, db.expireIndicator.g, db.expireIndicator.b) -- color for timers that are soon to expire + local c10 = E:RGBToHex(db.secondsIndicator.r, db.secondsIndicator.g, db.secondsIndicator.b) -- color for timers that have seconds remaining + local c9 = E:RGBToHex(db.minutesIndicator.r, db.minutesIndicator.g, db.minutesIndicator.b) -- color for timers that have minutes remaining + local c8 = E:RGBToHex(db.hoursIndicator.r, db.hoursIndicator.g, db.hoursIndicator.b) -- color for timers that have hours remaining + local c7 = E:RGBToHex(db.daysIndicator.r, db.daysIndicator.g, db.daysIndicator.b) -- color for timers that have days remaining + local c6 = db.hhmmColor -- HH:MM color + local c5 = db.mmssColor -- MM:SS color + local c4 = db.expiringColor -- color for timers that are soon to expire + local c3 = db.secondsColor -- color for timers that have seconds remaining + local c2 = db.minutesColor -- color for timers that have minutes remaining + local c1 = db.hoursColor -- color for timers that have hours remaining + local c0 = db.daysColor -- color for timers that have days remaining + return c0, c1, c2, c3, c4, c5, c6, c7, c8, c9, c10, c11, c12, c13 +end + +function E:UpdateCooldownOverride(module) + local cooldowns = (module and E.RegisteredCooldowns[module]) + if not cooldowns or not next(cooldowns) then return end + + local blizzText + for _, parent in ipairs(cooldowns) do + local db = (parent.CooldownOverride and E.db[parent.CooldownOverride]) or E.db + if db and db.cooldown then + local timer = parent.isHooked and parent.isRegisteredCooldown and parent.timer + local cd = timer or parent + + -- cooldown override settings + E:Cooldown_Options(cd, db.cooldown, parent) + + -- update font on cooldowns + if timer and cd then -- has a parent, these are timers from RegisterCooldown + E:Cooldown_OnSizeChanged(cd, parent:GetWidth(), true) + + E:ToggleBlizzardCooldownText(parent, cd) + if (not blizzText) and parent.CooldownOverride == 'actionbar' then + blizzText = true + end + elseif cd.text then + if cd.customFont then + cd.text:FontTemplate(cd.customFont, cd.customFontSize, cd.customFontOutline) + elseif parent.CooldownOverride == 'auras' then + -- parent.auraType defined in `A:UpdateHeader` and `A:CreateIcon` + local fontDB = parent.auraType and db[parent.auraType] + if fontDB and fontDB.timeFont then + cd.text:FontTemplate(LSM:Fetch('font', fontDB.timeFont), fontDB.timeFontSize, fontDB.timeFontOutline) + end + end + + -- force update top aura cooldowns + if parent.CooldownOverride == 'auras' then + E:Cooldown_ForceUpdate(parent) + end + end + end + end + + if blizzText and AB.handledBars then + for _, bar in pairs(AB.handledBars) do + if bar then + AB:ToggleCountDownNumbers(bar) + end + end + end +end + +function E:UpdateCooldownSettings(module) + local db, timeColors, textColors = E.db.cooldown, E.TimeColors, E.TimeIndicatorColors + + -- update the module timecolors if the config called it but ignore 'global' and 'all': + -- global is the main call from config, all is the core file calls + local isModule = module and (module ~= 'global' and module ~= 'all') and E.db[module] and E.db[module].cooldown + if isModule then + if not E.TimeColors[module] then E.TimeColors[module] = {} end + if not E.TimeIndicatorColors[module] then E.TimeIndicatorColors[module] = {} end + db, timeColors, textColors = E.db[module].cooldown, E.TimeColors[module], E.TimeIndicatorColors[module] + end + + timeColors[0], timeColors[1], timeColors[2], timeColors[3], timeColors[4], timeColors[5], timeColors[6], textColors[0], textColors[1], textColors[2], textColors[3], textColors[4], textColors[5], textColors[6] = E:GetCooldownColors(db) + + if isModule then + E:UpdateCooldownOverride(module) + elseif module == 'global' then -- this is only a call from the config change + for key in pairs(E.RegisteredCooldowns) do + E:UpdateCooldownOverride(key) + end + end + + -- okay update the other override settings if it was one of the core file calls + if module and (module == 'all') then + E:UpdateCooldownSettings('bags') + E:UpdateCooldownSettings('nameplates') + E:UpdateCooldownSettings('actionbar') + E:UpdateCooldownSettings('unitframe') + E:UpdateCooldownSettings('auras') + end +end diff --git a/Core/Core.lua b/Core/Core.lua new file mode 100644 index 0000000..de1dd3f --- /dev/null +++ b/Core/Core.lua @@ -0,0 +1,1820 @@ +local ElvUI = select(2, ...) +ElvUI[2] = ElvUI[1].Libs.ACL:GetLocale('ElvUI', ElvUI[1]:GetLocale()) -- Locale doesn't exist yet, make it exist. +local E, L, V, P, G = unpack(ElvUI); --Import: Engine, Locales, PrivateDB, ProfileDB, GlobalDB + +local _G = _G +local tonumber, pairs, ipairs, error, unpack, select, tostring = tonumber, pairs, ipairs, error, unpack, select, tostring +local strsplit, strjoin, wipe, sort, tinsert, tremove, tContains = strsplit, strjoin, wipe, sort, tinsert, tremove, tContains +local format, find, strrep, strlen, sub, gsub = format, strfind, strrep, strlen, strsub, gsub +local assert, type, pcall, xpcall, next, print = assert, type, pcall, xpcall, next, print +local rawget, rawset, setmetatable = rawget, rawset, setmetatable + +local CreateFrame = CreateFrame +local GetCVar = GetCVar +local GetSpellInfo = GetSpellInfo +local GetCVarBool = GetCVarBool +local GetNumGroupMembers = GetNumGroupMembers +local GetSpecialization = GetSpecialization +local hooksecurefunc = hooksecurefunc +local InCombatLockdown = InCombatLockdown +local GetAddOnEnableState = GetAddOnEnableState +local UnitFactionGroup = UnitFactionGroup +local DisableAddOn = DisableAddOn +local IsInGroup = IsInGroup +local IsInGuild = IsInGuild +local IsInRaid = IsInRaid +local SetCVar = SetCVar +local ReloadUI = ReloadUI +local UnitGUID = UnitGUID + +local ERR_NOT_IN_COMBAT = ERR_NOT_IN_COMBAT +local LE_PARTY_CATEGORY_HOME = LE_PARTY_CATEGORY_HOME +local LE_PARTY_CATEGORY_INSTANCE = LE_PARTY_CATEGORY_INSTANCE +local C_ChatInfo_SendAddonMessage = C_ChatInfo.SendAddonMessage +-- GLOBALS: ElvUIPlayerBuffs, ElvUIPlayerDebuffs + +--Modules +local ActionBars = E:GetModule('ActionBars') +local AFK = E:GetModule('AFK') +local Auras = E:GetModule('Auras') +local Bags = E:GetModule('Bags') +local Blizzard = E:GetModule('Blizzard') +local Chat = E:GetModule('Chat') +local DataBars = E:GetModule('DataBars') +local DataTexts = E:GetModule('DataTexts') +local Layout = E:GetModule('Layout') +local Minimap = E:GetModule('Minimap') +local NamePlates = E:GetModule('NamePlates') +local Tooltip = E:GetModule('Tooltip') +local Totems = E:GetModule('Totems') +local UnitFrames = E:GetModule('UnitFrames') +local LSM = E.Libs.LSM + +--Constants +E.noop = function() end +E.title = format('|cff1784d1%s |r', 'ElvUI') +E.version = tonumber(GetAddOnMetadata('ElvUI', 'Version')) +E.myfaction, E.myLocalizedFaction = UnitFactionGroup('player') +E.mylevel = UnitLevel('player') +E.myLocalizedClass, E.myclass, E.myClassID = UnitClass('player') +E.myLocalizedRace, E.myrace = UnitRace('player') +E.myname = UnitName('player') +E.myrealm = GetRealmName() +E.mynameRealm = format('%s - %s', E.myname, E.myrealm) -- contains spaces/dashes in realm (for profile keys) +E.myspec = GetSpecialization() +E.wowpatch, E.wowbuild = GetBuildInfo() +E.wowbuild = tonumber(E.wowbuild) +E.isMacClient = IsMacClient() +E.IsRetail = WOW_PROJECT_ID == WOW_PROJECT_MAINLINE +E.screenwidth, E.screenheight = GetPhysicalScreenSize() +E.resolution = format('%dx%d', E.screenwidth, E.screenheight) +E.NewSign = [[|TInterface\OptionsFrame\UI-OptionsFrame-NewFeatureIcon:14:14|t]] -- not used by ElvUI yet, but plugins like BenikUI and MerathilisUI use it. +E.TexturePath = [[Interface\AddOns\ElvUI\Media\Textures\]] -- for plugins? +E.InfoColor = '|cff1784d1' +E.UserList = {} + +-- oUF Defines +E.oUF.Tags.Vars.E = E +E.oUF.Tags.Vars.L = L + +--Tables +E.media = {} +E.frames = {} +E.unitFrameElements = {} +E.statusBars = {} +E.texts = {} +E.snapBars = {} +E.RegisteredModules = {} +E.RegisteredInitialModules = {} +E.valueColorUpdateFuncs = {} +E.TexCoords = {0, 1, 0, 1} +E.FrameLocks = {} +E.VehicleLocks = {} +E.CreditsList = {} +E.LockedCVars = {} +E.IgnoredCVars = {} +E.UpdatedCVars = {} +E.InversePoints = { + TOP = 'BOTTOM', + BOTTOM = 'TOP', + TOPLEFT = 'BOTTOMLEFT', + TOPRIGHT = 'BOTTOMRIGHT', + LEFT = 'RIGHT', + RIGHT = 'LEFT', + BOTTOMLEFT = 'TOPLEFT', + BOTTOMRIGHT = 'TOPRIGHT', + CENTER = 'CENTER' +} + +E.ClassRole = { + HUNTER = 'Melee', + ROGUE = 'Melee', + MAGE = 'Caster', + PRIEST = 'Caster', + WARLOCK = 'Caster', + DEMONHUNTER = {'Melee', 'Tank'}, + WARRIOR = {'Melee', 'Melee', 'Tank'}, + DEATHKNIGHT = {'Tank', 'Melee', 'Melee'}, + MONK = {'Tank', 'Caster', 'Melee'}, + PALADIN = {'Caster', 'Tank', 'Melee'}, + SHAMAN = {'Caster', 'Melee', 'Caster'}, + DRUID = {'Caster', 'Melee', 'Tank', 'Caster'}, +} + +E.DispelClasses = { + PRIEST = { Magic = true, Disease = true }, + SHAMAN = { Magic = false, Curse = true }, + PALADIN = { Poison = true, Magic = false, Disease = true }, + DRUID = { Magic = false, Curse = true, Poison = true, Disease = false }, + MONK = { Magic = false, Disease = true, Poison = true }, + MAGE = { Curse = true } +} + +E.BadDispels = { + [34914] = 'Vampiric Touch', -- horrifies + [233490] = 'Unstable Affliction' -- silences +} + +--Workaround for people wanting to use white and it reverting to their class color. +E.PriestColors = { r = 0.99, g = 0.99, b = 0.99, colorStr = 'fffcfcfc' } + +-- Socket Type info from 8.2 +E.GemTypeInfo = { + Yellow = { r = 0.97, g = 0.82, b = 0.29 }, + Red = { r = 1.00, g = 0.47, b = 0.47 }, + Blue = { r = 0.47, g = 0.67, b = 1.00 }, + Hydraulic = { r = 1.00, g = 1.00, b = 1.00 }, + Cogwheel = { r = 1.00, g = 1.00, b = 1.00 }, + Meta = { r = 1.00, g = 1.00, b = 1.00 }, + Prismatic = { r = 1.00, g = 1.00, b = 1.00 }, + PunchcardRed = { r = 1.00, g = 0.47, b = 0.47 }, + PunchcardYellow = { r = 0.97, g = 0.82, b = 0.29 }, + PunchcardBlue = { r = 0.47, g = 0.67, b = 1.00 }, +} + +--This frame everything in ElvUI should be anchored to for Eyefinity support. +E.UIParent = CreateFrame('Frame', 'ElvUIParent', _G.UIParent) +E.UIParent:SetFrameLevel(_G.UIParent:GetFrameLevel()) +E.UIParent:SetSize(_G.UIParent:GetSize()) +E.UIParent:SetPoint('BOTTOM') +E.UIParent.origHeight = E.UIParent:GetHeight() +E.snapBars[#E.snapBars + 1] = E.UIParent + +E.HiddenFrame = CreateFrame('Frame') +E.HiddenFrame:Hide() + +do -- used in optionsUI + E.DEFAULT_FILTER = {} + for filter, tbl in pairs(G.unitframe.aurafilters) do + E.DEFAULT_FILTER[filter] = tbl.type + end +end + +do + local a1,a2 = '','[%s%-]' + function E:ShortenRealm(realm) + return gsub(realm, a2, a1) + end + + local a3 = format('%%-%s', E:ShortenRealm(E.myrealm)) + function E:StripMyRealm(name) + return gsub(name, a3, a1) + end +end + +function E:Print(...) + (E.db and _G[E.db.general.messageRedirect] or _G.DEFAULT_CHAT_FRAME):AddMessage(strjoin('', E.media.hexvaluecolor or '|cff00b3ff', 'ElvUI:|r ', ...)) -- I put DEFAULT_CHAT_FRAME as a fail safe. +end + +function E:GrabColorPickerValues(r, g, b) + -- we must block the execution path to `ColorCallback` in `AceGUIWidget-ColorPicker-ElvUI` + -- in order to prevent an infinite loop from `OnValueChanged` when passing into `E.UpdateMedia` which eventually leads here again. + _G.ColorPickerFrame.noColorCallback = true + + -- grab old values + local oldR, oldG, oldB = _G.ColorPickerFrame:GetColorRGB() + + -- set and define the new values + _G.ColorPickerFrame:SetColorRGB(r, g, b) + r, g, b = _G.ColorPickerFrame:GetColorRGB() + + -- swap back to the old values + if oldR then _G.ColorPickerFrame:SetColorRGB(oldR, oldG, oldB) end + + -- free it up.. + _G.ColorPickerFrame.noColorCallback = nil + + return r, g, b +end + +--Basically check if another class border is being used on a class that doesn't match. And then return true if a match is found. +function E:CheckClassColor(r, g, b) + r, g, b = E:GrabColorPickerValues(r, g, b) + + for class in pairs(_G.RAID_CLASS_COLORS) do + if class ~= E.myclass then + local colorTable = E:ClassColor(class, true) + local red, green, blue = E:GrabColorPickerValues(colorTable.r, colorTable.g, colorTable.b) + if red == r and green == g and blue == b then + return true + end + end + end +end + +function E:SetColorTable(t, data) + if not data.r or not data.g or not data.b then + error('SetColorTable: Could not unpack color values.') + end + + if t and (type(t) == 'table') then + t[1], t[2], t[3], t[4] = E:UpdateColorTable(data) + else + t = E:GetColorTable(data) + end + + return t +end + +function E:UpdateColorTable(data) + if not data.r or not data.g or not data.b then + error('UpdateColorTable: Could not unpack color values.') + end + + if data.r > 1 or data.r < 0 then data.r = 1 end + if data.g > 1 or data.g < 0 then data.g = 1 end + if data.b > 1 or data.b < 0 then data.b = 1 end + if data.a and (data.a > 1 or data.a < 0) then data.a = 1 end + + if data.a then + return data.r, data.g, data.b, data.a + else + return data.r, data.g, data.b + end +end + +function E:GetColorTable(data) + if not data.r or not data.g or not data.b then + error('GetColorTable: Could not unpack color values.') + end + + if data.r > 1 or data.r < 0 then data.r = 1 end + if data.g > 1 or data.g < 0 then data.g = 1 end + if data.b > 1 or data.b < 0 then data.b = 1 end + if data.a and (data.a > 1 or data.a < 0) then data.a = 1 end + + if data.a then + return {data.r, data.g, data.b, data.a} + else + return {data.r, data.g, data.b} + end +end + +function E:UpdateMedia() + if not E.db.general or not E.private.general then return end --Prevent rare nil value errors + + --Fonts + E.media.normFont = LSM:Fetch('font', E.db.general.font) + E.media.combatFont = LSM:Fetch('font', E.private.general.dmgfont) + + --Textures + E.media.blankTex = LSM:Fetch('background', 'ElvUI Blank') + E.media.normTex = LSM:Fetch('statusbar', E.private.general.normTex) + E.media.glossTex = LSM:Fetch('statusbar', E.private.general.glossTex) + + --Border Color + local border = E.db.general.bordercolor + if E:CheckClassColor(border.r, border.g, border.b) then + local classColor = E:ClassColor(E.myclass, true) + E.db.general.bordercolor.r = classColor.r + E.db.general.bordercolor.g = classColor.g + E.db.general.bordercolor.b = classColor.b + end + + E.media.bordercolor = {border.r, border.g, border.b} + + --UnitFrame Border Color + border = E.db.unitframe.colors.borderColor + if E:CheckClassColor(border.r, border.g, border.b) then + local classColor = E:ClassColor(E.myclass, true) + E.db.unitframe.colors.borderColor.r = classColor.r + E.db.unitframe.colors.borderColor.g = classColor.g + E.db.unitframe.colors.borderColor.b = classColor.b + end + E.media.unitframeBorderColor = {border.r, border.g, border.b} + + --Backdrop Color + E.media.backdropcolor = E:SetColorTable(E.media.backdropcolor, E.db.general.backdropcolor) + + --Backdrop Fade Color + E.media.backdropfadecolor = E:SetColorTable(E.media.backdropfadecolor, E.db.general.backdropfadecolor) + + --Value Color + local value = E.db.general.valuecolor + if E:CheckClassColor(value.r, value.g, value.b) then + value = E:ClassColor(E.myclass, true) + E.db.general.valuecolor.r = value.r + E.db.general.valuecolor.g = value.g + E.db.general.valuecolor.b = value.b + end + + --Chat Tab Selector Color + local selectorColor = E.db.chat.tabSelectorColor + if E:CheckClassColor(selectorColor.r, selectorColor.g, selectorColor.b) then + selectorColor = E:ClassColor(E.myclass, true) + E.db.chat.tabSelectorColor.r = selectorColor.r + E.db.chat.tabSelectorColor.g = selectorColor.g + E.db.chat.tabSelectorColor.b = selectorColor.b + end + + E.media.hexvaluecolor = E:RGBToHex(value.r, value.g, value.b) + E.media.rgbvaluecolor = {value.r, value.g, value.b} + + -- Chat Panel Background Texture + local LeftChatPanel, RightChatPanel = _G.LeftChatPanel, _G.RightChatPanel + if LeftChatPanel and LeftChatPanel.tex and RightChatPanel and RightChatPanel.tex then + LeftChatPanel.tex:SetTexture(E.db.chat.panelBackdropNameLeft) + RightChatPanel.tex:SetTexture(E.db.chat.panelBackdropNameRight) + + local a = E.db.general.backdropfadecolor.a or 0.5 + LeftChatPanel.tex:SetAlpha(a) + RightChatPanel.tex:SetAlpha(a) + end + + E:ValueFuncCall() + E:UpdateBlizzardFonts() +end + +do --Update font/texture paths when they are registered by the addon providing them + --This helps fix most of the issues with fonts or textures reverting to default because the addon providing them is loading after ElvUI. + --We use a wrapper to avoid errors in :UpdateMedia because 'self' is passed to the function with a value other than ElvUI. + local function LSMCallback() E:UpdateMedia() end + LSM.RegisterCallback(E, 'LibSharedMedia_Registered', LSMCallback) +end + +do + local function CVAR_UPDATE(name, value) + if not E.IgnoredCVars[name] then + local locked = E.LockedCVars[name] + if locked ~= nil and locked ~= value then + if InCombatLockdown() then + E.CVarUpdate = true + return + end + + SetCVar(name, locked) + end + + local func = E.UpdatedCVars[name] + if func then func(value) end + end + end + + hooksecurefunc('SetCVar', CVAR_UPDATE) + function E:LockCVar(name, value) + if GetCVar(name) ~= value then + SetCVar(name, value) + end + + E.LockedCVars[name] = value + end + + function E:UpdatedCVar(name, func) + E.UpdatedCVars[name] = func + end + + function E:IgnoreCVar(name, ignore) + E.IgnoredCVars[name] = (not not ignore) -- cast to bool, just in case + end +end + +function E:ValueFuncCall() + local hex, r, g, b = E.media.hexvaluecolor, unpack(E.media.rgbvaluecolor) + for func in pairs(E.valueColorUpdateFuncs) do func(hex, r, g, b) end +end + +function E:UpdateFrameTemplates() + for frame in pairs(E.frames) do + if frame and frame.template and not frame:IsForbidden() then + if not (frame.ignoreUpdates or frame.ignoreFrameTemplates) then + frame:SetTemplate(frame.template, frame.glossTex, nil, frame.forcePixelMode) + end + else + E.frames[frame] = nil + end + end + + for frame in pairs(E.unitFrameElements) do + if frame and frame.template and not frame:IsForbidden() then + if not (frame.ignoreUpdates or frame.ignoreFrameTemplates) then + frame:SetTemplate(frame.template, frame.glossTex, nil, frame.forcePixelMode, frame.isUnitFrameElement) + end + else + E.unitFrameElements[frame] = nil + end + end +end + +function E:UpdateBorderColors() + local r, g, b = unpack(E.media.bordercolor) + for frame in pairs(E.frames) do + if frame and frame.template and not frame:IsForbidden() then + if not (frame.ignoreUpdates or frame.forcedBorderColors) and (frame.template == 'Default' or frame.template == 'Transparent') then + frame:SetBackdropBorderColor(r, g, b) + end + else + E.frames[frame] = nil + end + end + + local r2, g2, b2 = unpack(E.media.unitframeBorderColor) + for frame in pairs(E.unitFrameElements) do + if frame and frame.template and not frame:IsForbidden() then + if not (frame.ignoreUpdates or frame.forcedBorderColors) and (frame.template == 'Default' or frame.template == 'Transparent') then + frame:SetBackdropBorderColor(r2, g2, b2) + end + else + E.unitFrameElements[frame] = nil + end + end +end + +function E:UpdateBackdropColors() + local r, g, b = unpack(E.media.backdropcolor) + local r2, g2, b2, a2 = unpack(E.media.backdropfadecolor) + + for frame in pairs(E.frames) do + if frame and frame.template and not frame:IsForbidden() then + if not frame.ignoreUpdates then + if frame.callbackBackdropColor then + frame:callbackBackdropColor() + elseif frame.template == 'Default' then + frame:SetBackdropColor(r, g, b) + elseif frame.template == 'Transparent' then + frame:SetBackdropColor(r2, g2, b2, frame.customBackdropAlpha or a2) + end + end + else + E.frames[frame] = nil + end + end + + for frame in pairs(E.unitFrameElements) do + if frame and frame.template and not frame:IsForbidden() then + if not frame.ignoreUpdates then + if frame.callbackBackdropColor then + frame:callbackBackdropColor() + elseif frame.template == 'Default' then + frame:SetBackdropColor(r, g, b) + elseif frame.template == 'Transparent' then + frame:SetBackdropColor(r2, g2, b2, frame.customBackdropAlpha or a2) + end + end + else + E.unitFrameElements[frame] = nil + end + end +end + +function E:UpdateFontTemplates() + for text in pairs(E.texts) do + if text then + text:FontTemplate(text.font, text.fontSize, text.fontStyle, true) + else + E.texts[text] = nil + end + end +end + +function E:RegisterStatusBar(statusBar) + E.statusBars[statusBar] = true +end + +function E:UnregisterStatusBar(statusBar) + E.statusBars[statusBar] = nil +end + +function E:UpdateStatusBars() + for statusBar in pairs(E.statusBars) do + if statusBar and statusBar:IsObjectType('StatusBar') then + statusBar:SetStatusBarTexture(E.media.normTex) + elseif statusBar and statusBar:IsObjectType('Texture') then + statusBar:SetTexture(E.media.normTex) + end + end +end + +do + local cancel = function(popup) + DisableAddOn(popup.addon) + ReloadUI() + end + + function E:IncompatibleAddOn(addon, module, info) + local popup = E.PopupDialogs.INCOMPATIBLE_ADDON + popup.button2 = info.name or module + popup.button1 = addon + popup.module = module + popup.addon = addon + popup.accept = info.accept + popup.cancel = info.cancel or cancel + + E:StaticPopup_Show('INCOMPATIBLE_ADDON', popup.button1, popup.button2) + end +end + +function E:IsAddOnEnabled(addon) + return GetAddOnEnableState(E.myname, addon) == 2 +end + +function E:IsIncompatible(module, addons) + for _, addon in ipairs(addons) do + if E:IsAddOnEnabled(addon) then + E:IncompatibleAddOn(addon, module, addons.info) + return true + end + end +end + +do + local ADDONS = { + ActionBar = { + info = { + enabled = function() return E.private.actionbar.enable end, + accept = function() E.private.actionbar.enable = false; ReloadUI() end, + name = 'ElvUI ActionBars' + }, + 'Bartender4', + 'Dominos' + }, + Chat = { + info = { + enabled = function() return E.private.chat.enable end, + accept = function() E.private.chat.enable = false; ReloadUI() end, + name = 'ElvUI Chat' + }, + 'Prat-3.0', + 'Chatter', + 'Glass' + }, + NamePlates = { + info = { + enabled = function() return E.private.nameplates.enable end, + accept = function() E.private.nameplates.enable = false; ReloadUI() end, + name = 'ElvUI NamePlates' + }, + 'TidyPlates', + 'TidyPlates_ThreatPlates', + 'Healers-Have-To-Die', + 'Kui_Nameplates', + 'Plater', + 'Aloft' + } + } + + E.INCOMPATIBLE_ADDONS = ADDONS -- let addons have the ability to alter this list to trigger our popup if they want + function E:AddIncompatible(module, addonName) + if ADDONS[module] then + tinsert(ADDONS[module], addonName) + else + print(module, 'is not in the incompatibility list.') + end + end + + function E:CheckIncompatible() + if E.global.ignoreIncompatible then return end + + for module, addons in pairs(ADDONS) do + if addons[1] and addons.info.enabled() and E:IsIncompatible(module, addons) then + break + end + end + end +end + +function E:CopyTable(current, default) + if type(current) ~= 'table' then + current = {} + end + + if type(default) == 'table' then + for option, value in pairs(default) do + current[option] = (type(value) == 'table' and E:CopyTable(current[option], value)) or value + end + end + + return current +end + +function E:RemoveEmptySubTables(tbl) + if type(tbl) ~= 'table' then + E:Print('Bad argument #1 to \'RemoveEmptySubTables\' (table expected)') + return + end + + for k, v in pairs(tbl) do + if type(v) == 'table' then + if next(v) == nil then + tbl[k] = nil + else + E:RemoveEmptySubTables(v) + end + end + end +end + +--Compare 2 tables and remove duplicate key/value pairs +--param cleanTable : table you want cleaned +--param checkTable : table you want to check against. +--param generatedKeys : table defined in `Distributor.lua` to allow user generated tables to be exported (customTexts, customCurrencies, etc). +--return : a copy of cleanTable with duplicate key/value pairs removed +function E:RemoveTableDuplicates(cleanTable, checkTable, generatedKeys) + if type(cleanTable) ~= 'table' then + E:Print('Bad argument #1 to \'RemoveTableDuplicates\' (table expected)') + return + end + if type(checkTable) ~= 'table' then + E:Print('Bad argument #2 to \'RemoveTableDuplicates\' (table expected)') + return + end + + local rtdCleaned = {} + local keyed = type(generatedKeys) == 'table' + for option, value in pairs(cleanTable) do + local default, genTable, genOption = checkTable[option] + if keyed then genTable = generatedKeys[option] else genOption = generatedKeys end + + -- we only want to add settings which are existing in the default table, unless it's allowed by generatedKeys + if default ~= nil or (genTable or genOption ~= nil) then + if type(value) == 'table' and type(default) == 'table' then + if genOption ~= nil then + rtdCleaned[option] = E:RemoveTableDuplicates(value, default, genOption) + else + rtdCleaned[option] = E:RemoveTableDuplicates(value, default, genTable or nil) + end + elseif cleanTable[option] ~= default then + -- add unique data to our clean table + rtdCleaned[option] = value + end + end + end + + --Clean out empty sub-tables + E:RemoveEmptySubTables(rtdCleaned) + + return rtdCleaned +end + +--Compare 2 tables and remove blacklisted key/value pairs +--param cleanTable : table you want cleaned +--param blacklistTable : table you want to check against. +--return : a copy of cleanTable with blacklisted key/value pairs removed +function E:FilterTableFromBlacklist(cleanTable, blacklistTable) + if type(cleanTable) ~= 'table' then + E:Print('Bad argument #1 to \'FilterTableFromBlacklist\' (table expected)') + return + end + if type(blacklistTable) ~= 'table' then + E:Print('Bad argument #2 to \'FilterTableFromBlacklist\' (table expected)') + return + end + + local tfbCleaned = {} + for option, value in pairs(cleanTable) do + if type(value) == 'table' and blacklistTable[option] and type(blacklistTable[option]) == 'table' then + tfbCleaned[option] = E:FilterTableFromBlacklist(value, blacklistTable[option]) + else + -- Filter out blacklisted keys + if blacklistTable[option] ~= true then + tfbCleaned[option] = value + end + end + end + + --Clean out empty sub-tables + E:RemoveEmptySubTables(tfbCleaned) + + return tfbCleaned +end + +local function keySort(a, b) + local A, B = type(a), type(b) + + if A == B then + if A == 'number' or A == 'string' then + return a < b + elseif A == 'boolean' then + return (a and 1 or 0) > (b and 1 or 0) + end + end + + return A < B +end + +do --The code in this function is from WeakAuras, credit goes to Mirrored and the WeakAuras Team + --Code slightly modified by Simpy, sorting from @sighol + local function recurse(tbl, level, ret) + local tkeys = {} + for i in pairs(tbl) do tinsert(tkeys, i) end + sort(tkeys, keySort) + + for _, i in ipairs(tkeys) do + local v = tbl[i] + + ret = ret..strrep(' ', level)..'[' + if type(i) == 'string' then ret = ret..'"'..i..'"' else ret = ret..i end + ret = ret..'] = ' + + if type(v) == 'number' then + ret = ret..v..',\n' + elseif type(v) == 'string' then + ret = ret..'"'..v:gsub('\\', '\\\\'):gsub('\n', '\\n'):gsub('"', '\\"'):gsub('\124', '\124\124')..'",\n' + elseif type(v) == 'boolean' then + if v then ret = ret..'true,\n' else ret = ret..'false,\n' end + elseif type(v) == 'table' then + ret = ret..'{\n' + ret = recurse(v, level + 1, ret) + ret = ret..strrep(' ', level)..'},\n' + else + ret = ret..'"'..tostring(v)..'",\n' + end + end + + return ret + end + + function E:TableToLuaString(inTable) + if type(inTable) ~= 'table' then + E:Print('Invalid argument #1 to E:TableToLuaString (table expected)') + return + end + + local ret = '{\n' + if inTable then ret = recurse(inTable, 1, ret) end + ret = ret..'}' + + return ret + end +end + +do --The code in this function is from WeakAuras, credit goes to Mirrored and the WeakAuras Team + --Code slightly modified by Simpy, sorting from @sighol + local lineStructureTable, profileFormat = {}, { + profile = 'E.db', + private = 'E.private', + global = 'E.global', + filters = 'E.global', + styleFilters = 'E.global' + } + + local function buildLineStructure(str) -- str is profileText + for _, v in ipairs(lineStructureTable) do + if type(v) == 'string' then + str = str..'["'..v..'"]' + else + str = str..'['..v..']' + end + end + + return str + end + + local sameLine + local function recurse(tbl, ret, profileText) + local tkeys = {} + for i in pairs(tbl) do tinsert(tkeys, i) end + sort(tkeys, keySort) + + local lineStructure = buildLineStructure(profileText) + for _, k in ipairs(tkeys) do + local v = tbl[k] + + if not sameLine then + ret = ret..lineStructure + end + + ret = ret..'[' + + if type(k) == 'string' then + ret = ret..'"'..k..'"' + else + ret = ret..k + end + + if type(v) == 'table' then + tinsert(lineStructureTable, k) + sameLine = true + ret = ret..']' + ret = recurse(v, ret, profileText) + else + sameLine = false + ret = ret..'] = ' + + if type(v) == 'number' then + ret = ret..v..'\n' + elseif type(v) == 'string' then + ret = ret..'"'..v:gsub('\\', '\\\\'):gsub('\n', '\\n'):gsub('"', '\\"'):gsub('\124', '\124\124')..'"\n' + elseif type(v) == 'boolean' then + if v then + ret = ret..'true\n' + else + ret = ret..'false\n' + end + else + ret = ret..'"'..tostring(v)..'"\n' + end + end + end + + tremove(lineStructureTable) + + return ret + end + + function E:ProfileTableToPluginFormat(inTable, profileType) + local profileText = profileFormat[profileType] + if not profileText then return end + + wipe(lineStructureTable) + + local ret = '' + if inTable and profileType then + sameLine = false + ret = recurse(inTable, ret, profileText) + end + + return ret + end +end + +do --Split string by multi-character delimiter (the strsplit / string.split function provided by WoW doesn't allow multi-character delimiter) + local splitTable = {} + function E:SplitString(str, delim) + assert(type (delim) == 'string' and strlen(delim) > 0, 'bad delimiter') + + local start = 1 + wipe(splitTable) -- results table + + -- find each instance of a string followed by the delimiter + while true do + local pos = find(str, delim, start, true) -- plain find + if not pos then break end + + tinsert(splitTable, sub(str, start, pos - 1)) + start = pos + strlen(delim) + end -- while + + -- insert final one (after last delimiter) + tinsert(splitTable, sub(str, start)) + + return unpack(splitTable) + end +end + +do + local SendMessageWaiting -- only allow 1 delay at a time regardless of eventing + function E:SendMessage() + if IsInRaid() then + C_ChatInfo_SendAddonMessage('ELVUI_VERSIONCHK', E.version, (not IsInRaid(LE_PARTY_CATEGORY_HOME) and IsInRaid(LE_PARTY_CATEGORY_INSTANCE)) and 'INSTANCE_CHAT' or 'RAID') + elseif IsInGroup() then + C_ChatInfo_SendAddonMessage('ELVUI_VERSIONCHK', E.version, (not IsInGroup(LE_PARTY_CATEGORY_HOME) and IsInGroup(LE_PARTY_CATEGORY_INSTANCE)) and 'INSTANCE_CHAT' or 'PARTY') + elseif IsInGuild() then + C_ChatInfo_SendAddonMessage('ELVUI_VERSIONCHK', E.version, 'GUILD') + end + + SendMessageWaiting = nil + end + + local SendRecieveGroupSize = 0 + local PLAYER_NAME = format('%s-%s', E.myname, E:ShortenRealm(E.myrealm)) + local function SendRecieve(_, event, prefix, message, _, sender) + if event == 'CHAT_MSG_ADDON' then + if sender == PLAYER_NAME then return end + if prefix == 'ELVUI_VERSIONCHK' then + local msg, ver = tonumber(message), E.version + local inCombat = InCombatLockdown() + + E.UserList[E:StripMyRealm(sender)] = msg + + if msg and (msg > ver) and not E.recievedOutOfDateMessage then -- you're outdated D: + E:Print(L["ElvUI is out of date. You can download the newest version from www.tukui.org. Get premium membership and have ElvUI automatically updated with the Tukui Client!"]) + + if msg and ((msg - ver) >= 0.05) and not inCombat then + E:StaticPopup_Show('ELVUI_UPDATE_AVAILABLE') + end + + E.recievedOutOfDateMessage = true + end + end + elseif event == 'GROUP_ROSTER_UPDATE' then + local num = GetNumGroupMembers() + if num ~= SendRecieveGroupSize then + if num > 1 and num > SendRecieveGroupSize then + if not SendMessageWaiting then + SendMessageWaiting = E:Delay(10, E.SendMessage) + end + end + SendRecieveGroupSize = num + end + elseif event == 'PLAYER_ENTERING_WORLD' then + if not SendMessageWaiting then + SendMessageWaiting = E:Delay(10, E.SendMessage) + end + end + end + + _G.C_ChatInfo.RegisterAddonMessagePrefix('ELVUI_VERSIONCHK') + + local f = CreateFrame('Frame') + f:RegisterEvent('CHAT_MSG_ADDON') + f:RegisterEvent('GROUP_ROSTER_UPDATE') + f:RegisterEvent('PLAYER_ENTERING_WORLD') + f:SetScript('OnEvent', SendRecieve) +end + +function E:UpdateStart(skipCallback, skipUpdateDB) + if not skipUpdateDB then + E:UpdateDB() + end + + E:UpdateMoverPositions() + E:UpdateMediaItems() + E:UpdateUnitFrames() + + if not skipCallback then + E.callbacks:Fire('StaggeredUpdate') + end +end + +do -- BFA Convert, deprecated.. + local function buffwatchConvert(spell) + if spell.sizeOverride then spell.sizeOverride = nil end + if spell.size then spell.size = nil end + + if not spell.sizeOffset then + spell.sizeOffset = 0 + end + + if spell.styleOverride then + spell.style = spell.styleOverride + spell.styleOverride = nil + elseif not spell.style then + spell.style = 'coloredIcon' + end + end + + local ttModSwap + do -- tooltip convert + local swap = {ALL = 'HIDE',NONE = 'SHOW'} + ttModSwap = function(val) return swap[val] end + end + + function E:DBConvertBFA() + --Fix issue where UIScale was incorrectly stored as string + E.global.general.UIScale = tonumber(E.global.general.UIScale) + + --Not sure how this one happens, but prevent it in any case + if E.global.general.UIScale <= 0 then + E.global.general.UIScale = G.general.UIScale + end + + --Combat & Resting Icon options update + if E.db.unitframe.units.player.combatIcon ~= nil then + E.db.unitframe.units.player.CombatIcon.enable = E.db.unitframe.units.player.combatIcon + E.db.unitframe.units.player.combatIcon = nil + end + if E.db.unitframe.units.player.restIcon ~= nil then + E.db.unitframe.units.player.RestIcon.enable = E.db.unitframe.units.player.restIcon + E.db.unitframe.units.player.restIcon = nil + end + + -- [Fader] Combat Fade options for Player + if E.db.unitframe.units.player.combatfade ~= nil then + local enabled = E.db.unitframe.units.player.combatfade + E.db.unitframe.units.player.fader.enable = enabled + + if enabled then -- use the old min alpha too + E.db.unitframe.units.player.fader.minAlpha = 0 + end + + E.db.unitframe.units.player.combatfade = nil + end + + -- [Fader] Range check options for Units + do + local outsideAlpha + if E.db.unitframe.OORAlpha ~= nil then + outsideAlpha = E.db.unitframe.OORAlpha + E.db.unitframe.OORAlpha = nil + end + + for _, unit in ipairs({'target','targettarget','targettargettarget','focus','focustarget','pet','pettarget','boss','arena','party','raid','raid40','raidpet','tank','assist'}) do + if E.db.unitframe.units[unit].rangeCheck ~= nil then + local enabled = E.db.unitframe.units[unit].rangeCheck + E.db.unitframe.units[unit].fader.enable = enabled + E.db.unitframe.units[unit].fader.range = enabled + + if outsideAlpha then + E.db.unitframe.units[unit].fader.minAlpha = outsideAlpha + end + + E.db.unitframe.units[unit].rangeCheck = nil + end + end + end + + --Remove stale font settings from Cooldown system for top auras + if E.db.auras.cooldown.fonts then + E.db.auras.cooldown.fonts = nil + end + + --Convert Nameplate Aura Duration to new Cooldown system + if E.db.nameplates.durationFont then + E.db.nameplates.cooldown.fonts.font = E.db.nameplates.durationFont + E.db.nameplates.cooldown.fonts.fontSize = E.db.nameplates.durationFontSize + E.db.nameplates.cooldown.fonts.fontOutline = E.db.nameplates.durationFontOutline + + E.db.nameplates.durationFont = nil + E.db.nameplates.durationFontSize = nil + E.db.nameplates.durationFontOutline = nil + end + + if E.db.nameplates.lowHealthThreshold > 0.8 then + E.db.nameplates.lowHealthThreshold = 0.8 + end + + if E.db.nameplates.units.TARGET.nonTargetTransparency ~= nil then + E.global.nameplate.filters.ElvUI_NonTarget.actions.alpha = E.db.nameplates.units.TARGET.nonTargetTransparency * 100 + E.db.nameplates.units.TARGET.nonTargetTransparency = nil + end + + --Removed additional table in nameplate filters cause it was basically useless + for _, unit in ipairs({'PLAYER','FRIENDLY_PLAYER','ENEMY_PLAYER','FRIENDLY_NPC','ENEMY_NPC'}) do + if E.db.nameplates.units[unit].buffs and E.db.nameplates.units[unit].buffs.filters ~= nil then + E.db.nameplates.units[unit].buffs.minDuration = E.db.nameplates.units[unit].buffs.filters.minDuration or P.nameplates.units[unit].buffs.minDuration + E.db.nameplates.units[unit].buffs.maxDuration = E.db.nameplates.units[unit].buffs.filters.maxDuration or P.nameplates.units[unit].buffs.maxDuration + E.db.nameplates.units[unit].buffs.priority = E.db.nameplates.units[unit].buffs.filters.priority or P.nameplates.units[unit].buffs.priority + E.db.nameplates.units[unit].buffs.filters = nil + end + if E.db.nameplates.units[unit].debuffs and E.db.nameplates.units[unit].debuffs.filters ~= nil then + E.db.nameplates.units[unit].debuffs.minDuration = E.db.nameplates.units[unit].debuffs.filters.minDuration or P.nameplates.units[unit].debuffs.minDuration + E.db.nameplates.units[unit].debuffs.maxDuration = E.db.nameplates.units[unit].debuffs.filters.maxDuration or P.nameplates.units[unit].debuffs.maxDuration + E.db.nameplates.units[unit].debuffs.priority = E.db.nameplates.units[unit].debuffs.filters.priority or P.nameplates.units[unit].debuffs.priority + E.db.nameplates.units[unit].debuffs.filters = nil + end + end + + --Moved target scale to a style filter + if E.db.nameplates.units.TARGET.scale ~= nil then + E.global.nameplate.filters.ElvUI_Target.actions.scale = E.db.nameplates.units.TARGET.scale + E.db.nameplates.units.TARGET.scale = nil + end + + --Convert cropIcon to tristate + local cropIcon = E.db.general.cropIcon + if type(cropIcon) == 'boolean' then + E.db.general.cropIcon = (cropIcon and 2) or 0 + end + + --Vendor Greys option is now in bags table + if E.db.general.vendorGrays ~= nil then + E.db.bags.vendorGrays.enable = E.db.general.vendorGrays + E.db.general.vendorGraysDetails = nil + E.db.general.vendorGrays = nil + end + + --Heal Prediction is now a table instead of a bool + for _, unit in ipairs({'player','target','focus','pet','arena','party','raid','raid40','raidpet'}) do + if type(E.db.unitframe.units[unit].healPrediction) ~= 'table' then + local enabled = E.db.unitframe.units[unit].healPrediction + E.db.unitframe.units[unit].healPrediction = {} + E.db.unitframe.units[unit].healPrediction.enable = enabled + else + local healPrediction = E.db.unitframe.units[unit].healPrediction + if healPrediction.reversedAbsorbs ~= nil then -- convert the newer setting if it existed + healPrediction.reversedAbsorbs = nil + healPrediction.absorbStyle = 'REVERSED' + + -- clear extras + healPrediction.showAbsorbAmount = nil + healPrediction.showOverAbsorbs = nil + elseif healPrediction.showAbsorbAmount ~= nil then -- convert the old setting into the new wrapped setting + healPrediction.showAbsorbAmount = nil + healPrediction.absorbStyle = 'WRAPPED' + + -- clear extras + healPrediction.showOverAbsorbs = nil + elseif healPrediction.showOverAbsorbs ~= nil then -- convert the over absorb toggle into the new setting + healPrediction.absorbStyle = 'NORMAL' + healPrediction.showOverAbsorbs = nil + end + end + end + + --Health Backdrop Multiplier + if E.db.unitframe.colors.healthmultiplier ~= nil then + if E.db.unitframe.colors.healthmultiplier > 0.75 then + E.db.unitframe.colors.healthMultiplier = 0.75 + else + E.db.unitframe.colors.healthMultiplier = E.db.unitframe.colors.healthmultiplier + end + + E.db.unitframe.colors.healthmultiplier = nil + end + + --Tooltip FactionColors Setting + for i = 1, 8 do + local oldTable = E.db.tooltip.factionColors[''..i] + if oldTable then + local newTable = E:CopyTable({}, P.tooltip.factionColors[i]) -- import full table + E.db.tooltip.factionColors[i] = E:CopyTable(newTable, oldTable) + E.db.tooltip.factionColors[''..i] = nil + end + end + + -- Wipe some old variables off profiles + if E.global.uiScaleInformed then E.global.uiScaleInformed = nil end + if E.global.nameplatesResetInformed then E.global.nameplatesResetInformed = nil end + if E.global.userInformedNewChanges1 then E.global.userInformedNewChanges1 = nil end + + -- cvar nameplate visibility stuff + if E.db.nameplates.visibility.nameplateShowAll ~= nil then + E.db.nameplates.visibility.showAll = E.db.nameplates.visibility.nameplateShowAll + E.db.nameplates.visibility.nameplateShowAll = nil + end + if E.db.nameplates.units.FRIENDLY_NPC.showAlways ~= nil then + E.db.nameplates.visibility.friendly.npcs = E.db.nameplates.units.FRIENDLY_NPC.showAlways + E.db.nameplates.units.FRIENDLY_NPC.showAlways = nil + end + if E.db.nameplates.units.FRIENDLY_PLAYER.minions ~= nil then + E.db.nameplates.visibility.friendly.minions = E.db.nameplates.units.FRIENDLY_PLAYER.minions + E.db.nameplates.units.FRIENDLY_PLAYER.minions = nil + end + if E.db.nameplates.units.ENEMY_NPC.minors ~= nil then + E.db.nameplates.visibility.enemy.minus = E.db.nameplates.units.ENEMY_NPC.minors + E.db.nameplates.units.ENEMY_NPC.minors = nil + end + if E.db.nameplates.units.ENEMY_PLAYER.minions ~= nil or E.db.nameplates.units.ENEMY_NPC.minions ~= nil then + E.db.nameplates.visibility.enemy.minions = E.db.nameplates.units.ENEMY_PLAYER.minions or E.db.nameplates.units.ENEMY_NPC.minions + E.db.nameplates.units.ENEMY_PLAYER.minions = nil + E.db.nameplates.units.ENEMY_NPC.minions = nil + end + + -- removed override stuff from aurawatch + if E.global.unitframe.buffwatch then + for _, spells in pairs(E.global.unitframe.buffwatch) do + for _, spell in pairs(spells) do + buffwatchConvert(spell) + end + end + end + + if E.db.unitframe.filters.buffwatch then + for _, spell in pairs(E.db.unitframe.filters.buffwatch) do + buffwatchConvert(spell) + end + end + + -- fix aurabars colors + local auraBarColors = E.global.unitframe.AuraBarColors + for spell, info in pairs(auraBarColors) do + if type(spell) == 'string' then + local spellID = select(7, GetSpellInfo(spell)) + if spellID and not auraBarColors[spellID] then + auraBarColors[spellID] = info + auraBarColors[spell] = nil + spell = spellID + end + end + + if type(info) == 'boolean' then + auraBarColors[spell] = { color = { r = 1, g = 1, b = 1 }, enable = info } + elseif type(info) == 'table' then + if info.r or info.g or info.b then + auraBarColors[spell] = { color = { r = info.r or 1, g = info.g or 1, b = info.b or 1 }, enable = true } + elseif info.color then -- azil created a void hole, delete it -x- + if info.color.color then info.color.color = nil end + if info.color.enable then info.color.enable = nil end + if info.color.a then info.color.a = nil end -- alpha isnt supported by this + end + end + end + + if E.db.unitframe.colors.debuffHighlight.blendMode == 'MOD' then + E.db.unitframe.colors.debuffHighlight.blendMode = P.unitframe.colors.debuffHighlight.blendMode + end + + do -- tooltip modifier code was dumb, change it but keep the past setting + local swap = ttModSwap(E.db.tooltip.modifierID) + if swap then E.db.tooltip.modifierID = swap end + + swap = ttModSwap(E.db.tooltip.visibility.bags) + if swap then E.db.tooltip.visibility.bags = swap end + + swap = ttModSwap(E.db.tooltip.visibility.unitFrames) + if swap then E.db.tooltip.visibility.unitFrames = swap end + + swap = ttModSwap(E.db.tooltip.visibility.actionbars) + if swap then E.db.tooltip.visibility.actionbars = swap end + + swap = ttModSwap(E.db.tooltip.visibility.combatOverride) + if swap then E.db.tooltip.visibility.combatOverride = swap end + + -- remove the old combat variable and just use the mod since it supports show/hide states + local hideInCombat = E.db.tooltip.visibility.combat + if hideInCombat ~= nil then + E.db.tooltip.visibility.combat = nil + + local override = E.db.tooltip.visibility.combatOverride + if hideInCombat and (override ~= 'SHIFT' and override ~= 'CTRL' and override ~= 'ALT') then -- wouldve been NONE but now it would be HIDE + E.db.tooltip.visibility.combatOverride = 'HIDE' + end + end + end + end +end + +function E:DBConvertSL() + if E.private.skins.cleanBossButton ~= nil then + E.db.actionbar.extraActionButton.clean = E.private.skins.cleanBossButton + E.private.skins.cleanBossButton = nil + end + + if E.global.unitframe.DebuffHighlightColors then + E:CopyTable(E.global.unitframe.AuraHighlightColors, E.global.unitframe.DebuffHighlightColors) + E.global.unitframe.DebuffHighlightColors = nil + end + + if E.db.unitframe.filters.buffwatch then + E.db.unitframe.filters.aurawatch = E:CopyTable({}, E.db.unitframe.filters.buffwatch) + E.db.unitframe.filters.buffwatch = nil + end + + if E.global.unitframe.buffwatch then + E:CopyTable(E.global.unitframe.aurawatch, E.global.unitframe.buffwatch) + E.global.unitframe.buffwatch = nil + end +end + +function E:UpdateDB() + E.private = E.charSettings.profile + E.global = E.data.global + E.db = E.data.profile + + E:DBConversions() + + Auras.db = E.db.auras + ActionBars.db = E.db.actionbar + Bags.db = E.db.bags + Chat.db = E.db.chat + DataBars.db = E.db.databars + DataTexts.db = E.db.datatexts + NamePlates.db = E.db.nameplates + Tooltip.db = E.db.tooltip + UnitFrames.db = E.db.unitframe + Totems.db = E.db.general.totems + + --Not part of staggered update +end + +function E:UpdateMoverPositions() + --The mover is positioned before it is resized, which causes issues for unitframes + --Allow movers to be 'pushed' outside the screen, when they are resized they should be back in the screen area. + --We set movers to be clamped again at the bottom of this function. + E:SetMoversClampedToScreen(false) + E:SetMoversPositions() + + --Not part of staggered update +end + +function E:UpdateUnitFrames() + if E.private.unitframe.enable then + UnitFrames:Update_AllFrames() + end + + --Not part of staggered update +end + +function E:UpdateMediaItems(skipCallback) + E:UpdateMedia() + E:UpdateFrameTemplates() + E:UpdateStatusBars() + + if not skipCallback then + E.callbacks:Fire('StaggeredUpdate') + end +end + +function E:UpdateLayout(skipCallback) + Layout:ToggleChatPanels() + Layout:BottomPanelVisibility() + Layout:TopPanelVisibility() + Layout:SetDataPanelStyle() + + if not skipCallback then + E.callbacks:Fire('StaggeredUpdate') + end +end + +function E:UpdateActionBars(skipCallback) + ActionBars:ExtraButtons_UpdateAlpha() + ActionBars:ExtraButtons_UpdateScale() + ActionBars:ExtraButtons_GlobalFade() + ActionBars:ToggleCooldownOptions() + ActionBars:UpdateButtonSettings() + ActionBars:UpdateMicroPositionDimensions() + ActionBars:UpdatePetCooldownSettings() + + if not skipCallback then + E.callbacks:Fire('StaggeredUpdate') + end +end + +function E:UpdateNamePlates(skipCallback) + NamePlates:ConfigureAll() + NamePlates:StyleFilterInitialize() + + if not skipCallback then + E.callbacks:Fire('StaggeredUpdate') + end +end + +function E:UpdateTooltip() + Tooltip:SetTooltipFonts() +end + +function E:UpdateBags(skipCallback) + Bags:Layout() + Bags:Layout(true) + Bags:SizeAndPositionBagBar() + Bags:UpdateCountDisplay() + Bags:UpdateItemLevelDisplay() + + if not skipCallback then + E.callbacks:Fire('StaggeredUpdate') + end +end + +function E:UpdateChat(skipCallback) + Chat:SetupChat() + Chat:UpdateEditboxAnchors() + + if not skipCallback then + E.callbacks:Fire('StaggeredUpdate') + end +end + +function E:UpdateDataBars(skipCallback) + DataBars:AzeriteBar_Toggle() + DataBars:ExperienceBar_Toggle() + DataBars:HonorBar_Toggle() + DataBars:ReputationBar_Toggle() + DataBars:ThreatBar_Toggle() + DataBars:UpdateAll() + + if not skipCallback then + E.callbacks:Fire('StaggeredUpdate') + end +end + +function E:UpdateDataTexts(skipCallback) + DataTexts:LoadDataTexts() + + if not skipCallback then + E.callbacks:Fire('StaggeredUpdate') + end +end + +function E:UpdateMinimap(skipCallback) + Minimap:UpdateSettings() + + if not skipCallback then + E.callbacks:Fire('StaggeredUpdate') + end +end + +function E:UpdateAuras(skipCallback) + if ElvUIPlayerBuffs then Auras:UpdateHeader(ElvUIPlayerBuffs) end + if ElvUIPlayerDebuffs then Auras:UpdateHeader(ElvUIPlayerDebuffs) end + + if not skipCallback then + E.callbacks:Fire('StaggeredUpdate') + end +end + +function E:UpdateMisc(skipCallback) + AFK:Toggle() + Blizzard:SetObjectiveFrameHeight() + + Totems:PositionAndSize() + + if not skipCallback then + E.callbacks:Fire('StaggeredUpdate') + end +end + +function E:UpdateEnd() + E:UpdateCooldownSettings('all') + + if E.RefreshGUI then + E:RefreshGUI() + end + + E:SetMoversClampedToScreen(true) -- Go back to using clamp after resizing has taken place. + + if not E.installSetup and not E.private.install_complete then + E:Install() + end + + if E.staggerUpdateRunning then + --We're doing a staggered update, but plugins expect the old UpdateAll to be called + --So call it, but skip updates inside it + E:UpdateAll(false) + end + + --Done updating, let code now + E.staggerUpdateRunning = false +end + +do + local staggerDelay = 0.02 + local staggerTable = {} + local function CallStaggeredUpdate() + local nextUpdate, nextDelay = staggerTable[1] + if nextUpdate then + tremove(staggerTable, 1) + + if nextUpdate == 'UpdateNamePlates' or nextUpdate == 'UpdateBags' then + nextDelay = 0.05 + end + + E:Delay(nextDelay or staggerDelay, E[nextUpdate]) + end + end + E:RegisterCallback('StaggeredUpdate', CallStaggeredUpdate) + + function E:StaggeredUpdateAll(event, installSetup) + if not E.initialized then + E:Delay(1, E.StaggeredUpdateAll, E, event, installSetup) + return + end + + E.installSetup = installSetup + if (installSetup or event and event == 'OnProfileChanged' or event == 'OnProfileCopied') and not E.staggerUpdateRunning then + tinsert(staggerTable, 'UpdateLayout') + if E.private.actionbar.enable then + tinsert(staggerTable, 'UpdateActionBars') + end + if E.private.nameplates.enable then + tinsert(staggerTable, 'UpdateNamePlates') + end + if E.private.bags.enable then + tinsert(staggerTable, 'UpdateBags') + end + if E.private.chat.enable then + tinsert(staggerTable, 'UpdateChat') + end + if E.private.tooltip.enable then + tinsert(staggerTable, 'UpdateTooltip') + end + tinsert(staggerTable, 'UpdateDataBars') + tinsert(staggerTable, 'UpdateDataTexts') + if E.private.general.minimap.enable then + tinsert(staggerTable, 'UpdateMinimap') + end + if ElvUIPlayerBuffs or ElvUIPlayerDebuffs then + tinsert(staggerTable, 'UpdateAuras') + end + tinsert(staggerTable, 'UpdateMisc') + tinsert(staggerTable, 'UpdateEnd') + + --Stagger updates + E.staggerUpdateRunning = true + E:UpdateStart() + else + --Fire away + E:UpdateAll(true) + end + end +end + +function E:UpdateAll(doUpdates) + if doUpdates then + E:UpdateStart(true) + + E:UpdateLayout() + E:UpdateTooltip() + E:UpdateActionBars() + E:UpdateBags() + E:UpdateChat() + E:UpdateDataBars() + E:UpdateDataTexts() + E:UpdateMinimap() + E:UpdateNamePlates() + E:UpdateAuras() + E:UpdateMisc() + E:UpdateEnd() + end +end + +do + E.ObjectEventTable, E.ObjectEventFrame = {}, CreateFrame('Frame') + local eventFrame, eventTable = E.ObjectEventFrame, E.ObjectEventTable + + eventFrame:SetScript('OnEvent', function(_, event, ...) + local objs = eventTable[event] + if objs then + for object, funcs in pairs(objs) do + for _, func in ipairs(funcs) do + func(object, event, ...) + end + end + end + end) + + function E:HasFunctionForObject(event, object, func) + if not (event and object and func) then + E:Print('Error. Usage: HasFunctionForObject(event, object, func)') + return + end + + local objs = eventTable[event] + local funcs = objs and objs[object] + return funcs and tContains(funcs, func) + end + + function E:IsEventRegisteredForObject(event, object) + if not (event and object) then + E:Print('Error. Usage: IsEventRegisteredForObject(event, object)') + return + end + + local objs = eventTable[event] + local funcs = objs and objs[object] + return funcs ~= nil, funcs + end + + --- Registers specified event and adds specified func to be called for the specified object. + -- Unless all parameters are supplied it will not register. + -- If the specified object has already been registered for the specified event + -- then it will just add the specified func to a table of functions that should be called. + -- When a registered event is triggered, then the registered function is called with + -- the object as first parameter, then event, and then all the parameters for the event itself. + -- @param event The event you want to register. + -- @param object The object you want to register the event for. + -- @param func The function you want executed for this object. + function E:RegisterEventForObject(event, object, func) + if not (event and object and func) then + E:Print('Error. Usage: RegisterEventForObject(event, object, func)') + return + end + + local objs = eventTable[event] + if not objs then + objs = {} + eventTable[event] = objs + pcall(eventFrame.RegisterEvent, eventFrame, event) + end + + local funcs = objs[object] + if not funcs then + objs[object] = {func} + elseif not tContains(funcs, func) then + tinsert(funcs, func) + end + end + + --- Unregisters specified function for the specified object on the specified event. + -- Unless all parameters are supplied it will not unregister. + -- @param event The event you want to unregister an object from. + -- @param object The object you want to unregister a func from. + -- @param func The function you want unregistered for the object. + function E:UnregisterEventForObject(event, object, func) + if not (event and object and func) then + E:Print('Error. Usage: UnregisterEventForObject(event, object, func)') + return + end + + local objs = eventTable[event] + local funcs = objs and objs[object] + if funcs then + for index, fnc in ipairs(funcs) do + if func == fnc then + tremove(funcs, index) + break + end + end + + if #funcs == 0 then + objs[object] = nil + end + + if not next(funcs) then + eventFrame:UnregisterEvent(event) + eventTable[event] = nil + end + end + end + + function E:UnregisterAllEventsForObject(object, func) + if not (object and func) then + E:Print('Error. Usage: UnregisterAllEventsForObject(object, func)') + return + end + + for event in pairs(eventTable) do + if E:IsEventRegisteredForObject(event, object) then + E:UnregisterEventForObject(event, object, func) + end + end + end +end + +function E:ResetAllUI() + E:ResetMovers() + + if E.db.lowresolutionset then + E:SetupResolution(true) + end + + if E.db.layoutSet then + E:SetupLayout(E.db.layoutSet, true) + end +end + +function E:ResetUI(...) + if InCombatLockdown() then E:Print(ERR_NOT_IN_COMBAT) return end + + if ... == '' or ... == ' ' or ... == nil then + E:StaticPopup_Show('RESETUI_CHECK') + return + end + + E:ResetMovers(...) +end + +do + local function errorhandler(err) + return _G.geterrorhandler()(err) + end + + function E:CallLoadFunc(func, ...) + xpcall(func, errorhandler, ...) + end +end + +function E:CallLoadedModule(obj, silent, object, index) + local name, func + if type(obj) == 'table' then name, func = unpack(obj) else name = obj end + local module = name and E:GetModule(name, silent) + + if not module then return end + if func and type(func) == 'string' then + E:CallLoadFunc(module[func], module) + elseif func and type(func) == 'function' then + E:CallLoadFunc(func, module) + elseif module.Initialize then + E:CallLoadFunc(module.Initialize, module) + end + + if object and index then object[index] = nil end +end + +function E:RegisterInitialModule(name, func) + E.RegisteredInitialModules[#E.RegisteredInitialModules + 1] = (func and {name, func}) or name +end + +function E:RegisterModule(name, func) + if E.initialized then + E:CallLoadedModule((func and {name, func}) or name) + else + E.RegisteredModules[#E.RegisteredModules + 1] = (func and {name, func}) or name + end +end + +function E:InitializeInitialModules() + for index, object in ipairs(E.RegisteredInitialModules) do + E:CallLoadedModule(object, true, E.RegisteredInitialModules, index) + end +end + +function E:InitializeModules() + for index, object in ipairs(E.RegisteredModules) do + E:CallLoadedModule(object, true, E.RegisteredModules, index) + end +end + +function E:DBConversions() + -- release converts, only one call per version + if E.db.dbConverted ~= E.version then + E.db.dbConverted = E.version + + E:DBConvertBFA() + E:DBConvertSL() + end + + -- development converts, always call +end + +function E:RefreshModulesDB() + -- this function is specifically used to reference the new database + -- onto the unitframe module, its useful dont delete! D: + wipe(UnitFrames.db) --old ref, dont need so clear it + UnitFrames.db = E.db.unitframe --new ref +end + +do + -- Shamelessly taken from AceDB-3.0 and stripped down by Simpy + function E:CopyDefaults(dest, src) + for k, v in pairs(src) do + if type(v) == 'table' then + if not rawget(dest, k) then rawset(dest, k, {}) end + if type(dest[k]) == 'table' then E:CopyDefaults(dest[k], v) end + elseif rawget(dest, k) == nil then + rawset(dest, k, v) + end + end + end + + function E:RemoveDefaults(db, defaults) + setmetatable(db, nil) + + for k, v in pairs(defaults) do + if type(v) == 'table' and type(db[k]) == 'table' then + E:RemoveDefaults(db[k], v) + if next(db[k]) == nil then db[k] = nil end + elseif db[k] == defaults[k] then + db[k] = nil + end + end + end +end + +function E:Initialize() + wipe(E.db) + wipe(E.global) + wipe(E.private) + + local playerGUID = UnitGUID('player') + local _, serverID = strsplit('-', playerGUID) + E.serverID = tonumber(serverID) + E.myguid = playerGUID + + E.data = E.Libs.AceDB:New('ElvDB', E.DF, true) + E.data.RegisterCallback(E, 'OnProfileChanged', 'StaggeredUpdateAll') + E.data.RegisterCallback(E, 'OnProfileCopied', 'StaggeredUpdateAll') + E.data.RegisterCallback(E, 'OnProfileReset', 'OnProfileReset') + E.charSettings = E.Libs.AceDB:New('ElvPrivateDB', E.privateVars) + E.charSettings.RegisterCallback(E, 'OnProfileChanged', ReloadUI) + E.charSettings.RegisterCallback(E, 'OnProfileCopied', ReloadUI) + E.charSettings.RegisterCallback(E, 'OnProfileReset', 'OnPrivateProfileReset') + E.private = E.charSettings.profile + E.global = E.data.global + E.db = E.data.profile + E.Libs.DualSpec:EnhanceDatabase(E.data, 'ElvUI') + + -- default the non thing pixel border color to 191919, otherwise its 000000 + if not E.PixelMode then P.general.bordercolor = { r = 0.1, g = 0.1, b = 0.1 } end + if not E.db.unitframe.thinBorders then P.unitframe.colors.borderColor = { r = 0.1, g = 0.1, b = 0.1 } end + + E:DBConversions() + E:UIScale() + E:BuildPrefixValues() + E:LoadAPI() + E:LoadCommands() + E:InitializeModules() + E:RefreshModulesDB() + E:LoadMovers() + E:UpdateMedia() + E:UpdateCooldownSettings('all') + E:Tutorials() + E.initialized = true + + if E.db.general.smoothingAmount and (E.db.general.smoothingAmount ~= 0.33) then + E:SetSmoothingAmount(E.db.general.smoothingAmount) + end + + if not E.private.install_complete then + E:Install() + end + + if E:HelloKittyFixCheck() then + E:HelloKittyFix() + end + + if E.db.general.kittys then + E:CreateKittys() + E:Delay(5, E.Print, E, L["Type /hellokitty to revert to old settings."]) + end + + if GetCVarBool('scriptProfile') then + E:StaticPopup_Show('SCRIPT_PROFILE') + end + + if E.db.general.loginmessage then + local msg = format(L["LOGIN_MSG"], E.version) + if Chat.Initialized then msg = select(2, Chat:FindURL('CHAT_MSG_DUMMY', msg)) end + print(msg) + print(L["LOGIN_MSG_HELP"]) + end +end diff --git a/Core/Distributor.lua b/Core/Distributor.lua new file mode 100644 index 0000000..2224d29 --- /dev/null +++ b/Core/Distributor.lua @@ -0,0 +1,628 @@ +local E, L, V, P, G = unpack(select(2, ...)); --Import: Engine, Locales, PrivateDB, ProfileDB, GlobalDB +local D = E:GetModule('Distributor') +local NP = E:GetModule('NamePlates') +local LibCompress = E.Libs.Compress +local LibBase64 = E.Libs.Base64 + +local _G = _G +local tonumber, type, gsub, pairs, pcall, loadstring = tonumber, type, gsub, pairs, pcall, loadstring +local len, format, split, find = strlen, format, strsplit, strfind + +local CreateFrame = CreateFrame +local IsInRaid, UnitInRaid = IsInRaid, UnitInRaid +local IsInGroup, UnitInParty = IsInGroup, UnitInParty +local LE_PARTY_CATEGORY_HOME = LE_PARTY_CATEGORY_HOME +local LE_PARTY_CATEGORY_INSTANCE = LE_PARTY_CATEGORY_INSTANCE +local ACCEPT, CANCEL, YES, NO = ACCEPT, CANCEL, YES, NO +-- GLOBALS: ElvDB, ElvPrivateDB + +local REQUEST_PREFIX = 'ELVUI_REQUEST' +local REPLY_PREFIX = 'ELVUI_REPLY' +local TRANSFER_PREFIX = 'ELVUI_TRANSFER' +local TRANSFER_COMPLETE_PREFIX = 'ELVUI_COMPLETE' + +-- The active downloads +local Downloads = {} +local Uploads = {} + +function D:Initialize() + self.Initialized = true + + D:UpdateSettings() + + self.statusBar = CreateFrame('StatusBar', 'ElvUI_Download', E.UIParent) + self.statusBar:CreateBackdrop() + self.statusBar:SetStatusBarTexture(E.media.normTex) + self.statusBar:SetStatusBarColor(0.95, 0.15, 0.15) + self.statusBar:Size(250, 18) + self.statusBar.text = self.statusBar:CreateFontString(nil, 'OVERLAY') + self.statusBar.text:FontTemplate() + self.statusBar.text:Point('CENTER') + self.statusBar:Hide() + E:RegisterStatusBar(self.statusBar) +end + +function D:UpdateSettings() + if E.global.general.allowDistributor then + self:RegisterComm(REQUEST_PREFIX) + self:RegisterEvent('CHAT_MSG_ADDON') + else + self:UnregisterComm(REQUEST_PREFIX) + self:UnregisterEvent('CHAT_MSG_ADDON') + end +end + +-- Used to start uploads +function D:Distribute(target, otherServer, isGlobal) + local profileKey, data + if not isGlobal then + profileKey = ElvDB.profileKeys and ElvDB.profileKeys[E.mynameRealm] + data = ElvDB.profiles[profileKey] + else + profileKey = 'global' + data = ElvDB.global + end + + if not data then return end + + local serialData = self:Serialize(data) + local length = len(serialData) + local message = format('%s:%d:%s', profileKey, length, target) + + Uploads[profileKey] = {serialData = serialData, target = target} + + if otherServer then + if IsInRaid() and UnitInRaid('target') then + self:SendCommMessage(REQUEST_PREFIX, message, (not IsInRaid(LE_PARTY_CATEGORY_HOME) and IsInRaid(LE_PARTY_CATEGORY_INSTANCE)) and 'INSTANCE_CHAT' or 'RAID') + elseif IsInGroup() and UnitInParty('target') then + self:SendCommMessage(REQUEST_PREFIX, message, (not IsInGroup(LE_PARTY_CATEGORY_HOME) and IsInGroup(LE_PARTY_CATEGORY_INSTANCE)) and 'INSTANCE_CHAT' or 'PARTY') + else + E:Print(L["Must be in group with the player if he isn't on the same server as you."]) + return + end + else + self:SendCommMessage(REQUEST_PREFIX, message, 'WHISPER', target) + end + + self:RegisterComm(REPLY_PREFIX) + E:StaticPopup_Show('DISTRIBUTOR_WAITING') +end + +function D:CHAT_MSG_ADDON(_, prefix, message, _, sender) + if prefix ~= TRANSFER_PREFIX or not Downloads[sender] then return end + local cur = len(message) + local max = Downloads[sender].length + Downloads[sender].current = Downloads[sender].current + cur + + if Downloads[sender].current > max then + Downloads[sender].current = max + end + + self.statusBar:SetValue(Downloads[sender].current) +end + +function D:OnCommReceived(prefix, msg, dist, sender) + if prefix == REQUEST_PREFIX then + local profile, length, sendTo = split(':', msg) + + if dist ~= 'WHISPER' and sendTo ~= E.myname then + return + end + + if self.statusBar:IsShown() then + self:SendCommMessage(REPLY_PREFIX, profile..':NO', dist, sender) + return + end + + local textString = format(L["%s is attempting to share the profile %s with you. Would you like to accept the request?"], sender, profile) + if profile == 'global' then + textString = format(L["%s is attempting to share his filters with you. Would you like to accept the request?"], sender) + end + + E.PopupDialogs.DISTRIBUTOR_RESPONSE = { + text = textString, + OnAccept = function() + self.statusBar:SetMinMaxValues(0, length) + self.statusBar:SetValue(0) + self.statusBar.text:SetFormattedText(L["Data From: %s"], sender) + E:StaticPopupSpecial_Show(self.statusBar) + self:SendCommMessage(REPLY_PREFIX, profile..':YES', dist, sender) + end, + OnCancel = function() + self:SendCommMessage(REPLY_PREFIX, profile..':NO', dist, sender) + end, + button1 = ACCEPT, + button2 = CANCEL, + timeout = 30, + whileDead = 1, + hideOnEscape = 1, + } + + E:StaticPopup_Show('DISTRIBUTOR_RESPONSE') + + Downloads[sender] = { + current = 0, + length = tonumber(length), + profile = profile, + } + + self:RegisterComm(TRANSFER_PREFIX) + elseif prefix == REPLY_PREFIX then + self:UnregisterComm(REPLY_PREFIX) + E:StaticPopup_Hide('DISTRIBUTOR_WAITING') + + local profileKey, response = split(':', msg) + if response == 'YES' then + self:RegisterComm(TRANSFER_COMPLETE_PREFIX) + self:SendCommMessage(TRANSFER_PREFIX, Uploads[profileKey].serialData, dist, Uploads[profileKey].target) + else + E:StaticPopup_Show('DISTRIBUTOR_REQUEST_DENIED') + end + + Uploads[profileKey] = nil + elseif prefix == TRANSFER_PREFIX then + self:UnregisterComm(TRANSFER_PREFIX) + E:StaticPopupSpecial_Hide(self.statusBar) + + local profileKey = Downloads[sender].profile + local success, data = self:Deserialize(msg) + + if success then + local textString = format(L["Profile download complete from %s, would you like to load the profile %s now?"], sender, profileKey) + + if profileKey == 'global' then + textString = format(L["Filter download complete from %s, would you like to apply changes now?"], sender) + else + if not ElvDB.profiles[profileKey] then + ElvDB.profiles[profileKey] = data + else + textString = format(L["Profile download complete from %s, but the profile %s already exists. Change the name or else it will overwrite the existing profile."], sender, profileKey) + E.PopupDialogs.DISTRIBUTOR_CONFIRM = { + text = textString, + button1 = ACCEPT, + hasEditBox = 1, + editBoxWidth = 350, + maxLetters = 127, + OnAccept = function(popup) + ElvDB.profiles[popup.editBox:GetText()] = data + E.Libs.AceAddon:GetAddon('ElvUI').data:SetProfile(popup.editBox:GetText()) + E:StaggeredUpdateAll(nil, true) + Downloads[sender] = nil + end, + OnShow = function(popup) popup.editBox:SetText(profileKey) popup.editBox:SetFocus() end, + timeout = 0, + exclusive = 1, + whileDead = 1, + hideOnEscape = 1, + preferredIndex = 3 + } + + E:StaticPopup_Show('DISTRIBUTOR_CONFIRM') + self:SendCommMessage(TRANSFER_COMPLETE_PREFIX, 'COMPLETE', dist, sender) + return + end + end + + E.PopupDialogs.DISTRIBUTOR_CONFIRM = { + text = textString, + OnAccept = function() + if profileKey == 'global' then + E:CopyTable(ElvDB.global, data) + E:StaggeredUpdateAll(nil, true) + else + E.Libs.AceAddon:GetAddon('ElvUI').data:SetProfile(profileKey) + end + Downloads[sender] = nil + end, + OnCancel = function() + Downloads[sender] = nil + end, + button1 = YES, + button2 = NO, + whileDead = 1, + hideOnEscape = 1, + } + + E:StaticPopup_Show('DISTRIBUTOR_CONFIRM') + self:SendCommMessage(TRANSFER_COMPLETE_PREFIX, 'COMPLETE', dist, sender) + else + E:StaticPopup_Show('DISTRIBUTOR_FAILED') + self:SendCommMessage(TRANSFER_COMPLETE_PREFIX, 'FAILED', dist, sender) + end + elseif prefix == TRANSFER_COMPLETE_PREFIX then + self:UnregisterComm(TRANSFER_COMPLETE_PREFIX) + if msg == 'COMPLETE' then + E:StaticPopup_Show('DISTRIBUTOR_SUCCESS') + else + E:StaticPopup_Show('DISTRIBUTOR_FAILED') + end + end +end + +--Keys that should not be exported +local blacklistedKeys = { + profile = { + gridSize = true, + general = { + numberPrefixStyle = true + }, + chat = { + hideVoiceButtons = true + } + }, + private = {}, + global = { + profileCopy = true, + general = { + AceGUI = true, + UIScale = true, + locale = true, + version = true, + eyefinity = true, + ultrawide = true, + disableTutorialButtons = true, + showMissingTalentAlert = true, + allowDistributor = true + }, + chat = { + classColorMentionExcludedNames = true + }, + datatexts = { + newPanelInfo = true + }, + nameplate = { + effectiveHealth = true, + effectivePower = true, + effectiveAura = true, + effectiveHealthSpeed = true, + effectivePowerSpeed = true, + effectiveAuraSpeed = true, + filters = true + }, + unitframe = { + aurafilters = true, + aurawatch = true, + effectiveHealth = true, + effectivePower = true, + effectiveAura = true, + effectiveHealthSpeed = true, + effectivePowerSpeed = true, + effectiveAuraSpeed = true, + spellRangeCheck = true + } + }, +} + +--Keys that auto or user generated tables. +D.GeneratedKeys = { + profile = { + movers = true, + nameplates = { -- this is supposed to have an 's' because yeah, oh well + filters = true + }, + datatexts = { + panels = true, + }, + unitframe = { + units = {} -- required for the scope below for customTexts + } + }, + private = { + theme = true, + install_complete = true + }, + global = { + datatexts = { + customPanels = true, + customCurrencies = true + }, + unitframe = { + aurafilters = true, + aurawatch = true + }, + nameplate = { + filters = true + } + } +} + +do + local units = D.GeneratedKeys.profile.unitframe.units + for unit in pairs(P.unitframe.units) do + units[unit] = {customTexts = true} + end +end + +local function GetProfileData(profileType) + if not profileType or type(profileType) ~= 'string' then + E:Print('Bad argument #1 to "GetProfileData" (string expected)') + return + end + + local profileData, profileKey = {} + if profileType == 'profile' then + --Copy current profile data + profileKey = ElvDB.profileKeys and ElvDB.profileKeys[E.mynameRealm] + profileData = E:CopyTable(profileData, ElvDB.profiles[profileKey]) + + --This table will also hold all default values, not just the changed settings. + --This makes the table huge, and will cause the WoW client to lock up for several seconds. + --We compare against the default table and remove all duplicates from our table. The table is now much smaller. + profileData = E:RemoveTableDuplicates(profileData, P, D.GeneratedKeys.profile) + profileData = E:FilterTableFromBlacklist(profileData, blacklistedKeys.profile) + elseif profileType == 'private' then + local privateKey = ElvPrivateDB.profileKeys and ElvPrivateDB.profileKeys[E.mynameRealm] + profileData = E:CopyTable(profileData, ElvPrivateDB.profiles[privateKey]) + profileData = E:RemoveTableDuplicates(profileData, V, D.GeneratedKeys.private) + profileData = E:FilterTableFromBlacklist(profileData, blacklistedKeys.private) + profileKey = 'private' + elseif profileType == 'global' then + profileData = E:CopyTable(profileData, ElvDB.global) + profileData = E:RemoveTableDuplicates(profileData, G, D.GeneratedKeys.global) + profileData = E:FilterTableFromBlacklist(profileData, blacklistedKeys.global) + profileKey = 'global' + elseif profileType == 'filters' then + profileData.unitframe = {} + profileData.unitframe.aurafilters = {} + profileData.unitframe.aurafilters = E:CopyTable(profileData.unitframe.aurafilters, ElvDB.global.unitframe.aurafilters) + profileData.unitframe.aurawatch = {} + profileData.unitframe.aurawatch = E:CopyTable(profileData.unitframe.aurawatch, ElvDB.global.unitframe.aurawatch) + profileData = E:RemoveTableDuplicates(profileData, G, D.GeneratedKeys.global) + profileKey = 'filters' + elseif profileType == 'styleFilters' then + profileKey = 'styleFilters' + profileData.nameplate = {} + profileData.nameplate.filters = {} + profileData.nameplate.filters = E:CopyTable(profileData.nameplate.filters, ElvDB.global.nameplate.filters) + NP:StyleFilterClearDefaults(profileData.nameplate.filters) + profileData = E:RemoveTableDuplicates(profileData, G, D.GeneratedKeys.global) + end + + return profileKey, profileData +end + +local function GetProfileExport(profileType, exportFormat) + local profileExport, exportString + local profileKey, profileData = GetProfileData(profileType) + + if not profileKey or not profileData or (profileData and type(profileData) ~= 'table') then + E:Print('Error getting data from "GetProfileData"') + return + end + + if exportFormat == 'text' then + local serialData = D:Serialize(profileData) + exportString = D:CreateProfileExport(serialData, profileType, profileKey) + local compressedData = LibCompress:Compress(exportString) + local encodedData = LibBase64:Encode(compressedData) + profileExport = encodedData + + elseif exportFormat == 'luaTable' then + exportString = E:TableToLuaString(profileData) + profileExport = D:CreateProfileExport(exportString, profileType, profileKey) + + elseif exportFormat == 'luaPlugin' then + profileExport = E:ProfileTableToPluginFormat(profileData, profileType) + end + + return profileKey, profileExport +end + +function D:CreateProfileExport(dataString, profileType, profileKey) + local returnString + + if profileType == 'profile' then + returnString = format('%s::%s::%s', dataString, profileType, profileKey) + else + returnString = format('%s::%s', dataString, profileType) + end + + return returnString +end + +function D:GetImportStringType(dataString) + local stringType = '' + + if LibBase64:IsBase64(dataString) then + stringType = 'Base64' + elseif find(dataString, '{') then --Basic check to weed out obviously wrong strings + stringType = 'Table' + end + + return stringType +end + +function D:Decode(dataString) + local profileInfo, profileType, profileKey, profileData + local stringType = self:GetImportStringType(dataString) + + if stringType == 'Base64' then + local decodedData = LibBase64:Decode(dataString) + local decompressedData, decompressedMessage = LibCompress:Decompress(decodedData) + + if not decompressedData then + E:Print('Error decompressing data:', decompressedMessage) + return + end + + local serializedData, success + serializedData, profileInfo = E:SplitString(decompressedData, '^^::') -- '^^' indicates the end of the AceSerializer string + + if not profileInfo then + E:Print('Error importing profile. String is invalid or corrupted!') + return + end + + serializedData = format('%s%s', serializedData, '^^') --Add back the AceSerializer terminator + profileType, profileKey = E:SplitString(profileInfo, '::') + success, profileData = D:Deserialize(serializedData) + + if not success then + E:Print('Error deserializing:', profileData) + return + end + elseif stringType == 'Table' then + local profileDataAsString + profileDataAsString, profileInfo = E:SplitString(dataString, '}::') -- '}::' indicates the end of the table + + if not profileInfo then + E:Print('Error extracting profile info. Invalid import string!') + return + end + + if not profileDataAsString then + E:Print('Error extracting profile data. Invalid import string!') + return + end + + profileDataAsString = format('%s%s', profileDataAsString, '}') --Add back the missing '}' + profileDataAsString = gsub(profileDataAsString, '\124\124', '\124') --Remove escape pipe characters + profileType, profileKey = E:SplitString(profileInfo, '::') + + local profileMessage + local profileToTable = loadstring(format('%s %s', 'return', profileDataAsString)) + if profileToTable then profileMessage, profileData = pcall(profileToTable) end + + if profileMessage and (not profileData or type(profileData) ~= 'table') then + E:Print('Error converting lua string to table:', profileMessage) + return + end + end + + return profileType, profileKey, profileData +end + +local function SetImportedProfile(profileType, profileKey, profileData, force) + if profileType == 'profile' then + profileData = E:FilterTableFromBlacklist(profileData, blacklistedKeys.profile) --Remove unwanted options from import + + if not ElvDB.profiles[profileKey] or force then + if force and E.data.keys.profile == profileKey then + --Overwriting an active profile doesn't update when calling SetProfile + --So make it look like we use a different profile + E.data.keys.profile = profileKey..'_Temp' + end + + ElvDB.profiles[profileKey] = profileData + + --Calling SetProfile will now update all settings correctly + E.data:SetProfile(profileKey) + else + E:StaticPopup_Show('IMPORT_PROFILE_EXISTS', nil, nil, {profileKey = profileKey, profileType = profileType, profileData = profileData}) + end + elseif profileType == 'private' then + local privateKey = ElvPrivateDB.profileKeys and ElvPrivateDB.profileKeys[E.mynameRealm] + if privateKey then + profileData = E:FilterTableFromBlacklist(profileData, blacklistedKeys.private) --Remove unwanted options from import + ElvPrivateDB.profiles[privateKey] = profileData + E:StaticPopup_Show('IMPORT_RL') + end + elseif profileType == 'global' then + profileData = E:FilterTableFromBlacklist(profileData, blacklistedKeys.global) --Remove unwanted options from import + E:CopyTable(ElvDB.global, profileData) + E:StaticPopup_Show('IMPORT_RL') + elseif profileType == 'filters' then + E:CopyTable(ElvDB.global.unitframe, profileData.unitframe) + E:StaggeredUpdateAll(nil, true) + elseif profileType == 'styleFilters' then + E:CopyTable(ElvDB.global.nameplate, profileData.nameplate) + E:StaggeredUpdateAll(nil, true) + end +end + +function D:ExportProfile(profileType, exportFormat) + if not profileType or not exportFormat then + E:Print('Bad argument to "ExportProfile" (string expected)') + return + end + + local profileKey, profileExport = GetProfileExport(profileType, exportFormat) + + return profileKey, profileExport +end + +function D:ImportProfile(dataString) + local profileType, profileKey, profileData = self:Decode(dataString) + + if not profileData or type(profileData) ~= 'table' then + E:Print('Error: something went wrong when converting string to table!') + return + end + + if profileType and ((profileType == 'profile' and profileKey) or profileType ~= 'profile') then + SetImportedProfile(profileType, profileKey, profileData) + end + + return true +end + +E.PopupDialogs.DISTRIBUTOR_SUCCESS = { + text = L["Your profile was successfully recieved by the player."], + whileDead = 1, + hideOnEscape = 1, + button1 = _G.OKAY, +} + +E.PopupDialogs.DISTRIBUTOR_WAITING = { + text = L["Profile request sent. Waiting for response from player."], + whileDead = 1, + hideOnEscape = 1, + timeout = 20, +} + +E.PopupDialogs.DISTRIBUTOR_REQUEST_DENIED = { + text = L["Request was denied by user."], + whileDead = 1, + hideOnEscape = 1, + button1 = _G.OKAY, +} + +E.PopupDialogs.DISTRIBUTOR_FAILED = { + text = L["Lord! It's a miracle! The download up and vanished like a fart in the wind! Try Again!"], + whileDead = 1, + hideOnEscape = 1, + button1 = _G.OKAY, +} + +E.PopupDialogs.DISTRIBUTOR_RESPONSE = {} +E.PopupDialogs.DISTRIBUTOR_CONFIRM = {} + +E.PopupDialogs.IMPORT_PROFILE_EXISTS = { + text = L["The profile you tried to import already exists. Choose a new name or accept to overwrite the existing profile."], + button1 = ACCEPT, + button2 = CANCEL, + hasEditBox = 1, + editBoxWidth = 350, + maxLetters = 127, + OnAccept = function(self, data) + SetImportedProfile(data.profileType, self.editBox:GetText(), data.profileData, true) + end, + EditBoxOnTextChanged = function(self) + if self:GetText() == '' then + self:GetParent().button1:Disable() + else + self:GetParent().button1:Enable() + end + end, + OnShow = function(self, data) + self.editBox:SetText(data.profileKey) + self.editBox:SetFocus() + end, + timeout = 0, + whileDead = 1, + hideOnEscape = true, + preferredIndex = 3 +} + +E.PopupDialogs.IMPORT_RL = { + text = L["You have imported settings which may require a UI reload to take effect. Reload now?"], + button1 = ACCEPT, + button2 = CANCEL, + OnAccept = _G.ReloadUI, + timeout = 0, + whileDead = 1, + hideOnEscape = false, + preferredIndex = 3 +} + +E:RegisterModule(D:GetName()) diff --git a/Core/Dropdown.lua b/Core/Dropdown.lua new file mode 100644 index 0000000..b0b7d52 --- /dev/null +++ b/Core/Dropdown.lua @@ -0,0 +1,86 @@ +local E, L, V, P, G = unpack(select(2, ...)); --Import: Engine, Locales, PrivateDB, ProfileDB, GlobalDB + +local _G = _G +local tinsert = tinsert +local ToggleFrame = ToggleFrame +local GetCursorPosition = GetCursorPosition +local CreateFrame = CreateFrame + +local PADDING = 10 +local BUTTON_HEIGHT = 16 +local BUTTON_WIDTH = 135 + +local function OnClick(btn) + btn.func() + btn:GetParent():Hide() +end + +local function OnEnter(btn) + btn.hoverTex:Show() +end + +local function OnLeave(btn) + btn.hoverTex:Hide() +end + +function E:DropDown(list, frame, xOffset, yOffset) + if not frame.buttons then + frame.buttons = {} + frame:SetFrameStrata('DIALOG') + frame:SetClampedToScreen(true) + tinsert(_G.UISpecialFrames, frame:GetName()) + frame:Hide() + end + + xOffset = xOffset or 0 + yOffset = yOffset or 0 + + for i = 1, #frame.buttons do + frame.buttons[i]:Hide() + end + + for i = 1, #list do + if not frame.buttons[i] then + frame.buttons[i] = CreateFrame('Button', nil, frame) + + frame.buttons[i].hoverTex = frame.buttons[i]:CreateTexture(nil, 'OVERLAY') + frame.buttons[i].hoverTex:SetAllPoints() + frame.buttons[i].hoverTex:SetTexture([[Interface\QuestFrame\UI-QuestTitleHighlight]]) + frame.buttons[i].hoverTex:SetBlendMode('ADD') + frame.buttons[i].hoverTex:Hide() + + frame.buttons[i].text = frame.buttons[i]:CreateFontString(nil, 'BORDER') + frame.buttons[i].text:SetAllPoints() + frame.buttons[i].text:FontTemplate(nil, nil, '') + frame.buttons[i].text:SetJustifyH('LEFT') + + frame.buttons[i]:SetScript('OnEnter', OnEnter) + frame.buttons[i]:SetScript('OnLeave', OnLeave) + end + + frame.buttons[i]:Show() + frame.buttons[i]:Height(BUTTON_HEIGHT) + frame.buttons[i]:Width(BUTTON_WIDTH) + frame.buttons[i].text:SetText(list[i].text) + frame.buttons[i].func = list[i].func + frame.buttons[i]:SetScript('OnClick', OnClick) + + if i == 1 then + frame.buttons[i]:Point('TOPLEFT', frame, 'TOPLEFT', PADDING, -PADDING) + else + frame.buttons[i]:Point('TOPLEFT', frame.buttons[i-1], 'BOTTOMLEFT') + end + end + + frame:Height((#list * BUTTON_HEIGHT) + PADDING * 2) + frame:Width(BUTTON_WIDTH + PADDING * 2) + + local UIScale = _G.UIParent:GetScale() + local x, y = GetCursorPosition() + x = x/UIScale + y = y/UIScale + frame:ClearAllPoints() + frame:Point('TOPLEFT', _G.UIParent, 'BOTTOMLEFT', x + xOffset, y + yOffset) + + ToggleFrame(frame) +end diff --git a/Core/Fonts.lua b/Core/Fonts.lua new file mode 100644 index 0000000..a4da0f2 --- /dev/null +++ b/Core/Fonts.lua @@ -0,0 +1,162 @@ +local E, L, V, P, G = unpack(select(2, ...)); --Import: Engine, Locales, PrivateDB, ProfileDB, GlobalDB +local LSM = E.Libs.LSM + +local _G = _G +local min, max = min, max +local strmatch = strmatch + +local function SetFont(obj, font, size, style, sr, sg, sb, sa, sox, soy, r, g, b) + if not obj then return end + obj:SetFont(font, size, style) + + if sr and sg and sb then + obj:SetShadowColor(sr, sg, sb, sa) + end + + if sox and soy then + obj:SetShadowOffset(sox, soy) + end + + if r and g and b then + obj:SetTextColor(r, g, b) + elseif r then + obj:SetAlpha(r) + end +end + +local chatFontHeights = {6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20} +function E:UpdateBlizzardFonts() + local NORMAL = E.media.normFont + local NUMBER = E.media.normFont + local COMBAT = LSM:Fetch('font', E.private.general.dmgfont) + local NAMEFONT = LSM:Fetch('font', E.private.general.namefont) + local BUBBLE = LSM:Fetch('font', E.private.general.chatBubbleFont) + + _G.CHAT_FONT_HEIGHTS = chatFontHeights + + if (E.eyefinity or E.ultrawide) then COMBAT = E.Media.Fonts.Invisible end -- set an invisible font for xp, honor kill, etc + if E.private.general.replaceNameFont then _G.UNIT_NAME_FONT = NAMEFONT end + if E.private.general.replaceCombatFont then _G.DAMAGE_TEXT_FONT = COMBAT end + if E.private.general.replaceBlizzFonts then + _G.STANDARD_TEXT_FONT = NORMAL + --_G.NAMEPLATE_FONT = NAMEFONT + + local size = E.db.general.fontSize + local enormous = size * 1.9 + local mega = size * 1.7 + local huge = size * 1.5 + local large = size * 1.3 + local medium = size * 1.1 + local small = size * 0.9 + local tiny = size * 0.8 + + local s = not E.private.general.unifiedBlizzFonts + local mono = strmatch(E.db.general.fontStyle, 'MONOCHROME') and 'MONOCHROME' or '' + local thick, outline = mono..'THICKOUTLINE', mono..'OUTLINE' + + SetFont(_G.ChatBubbleFont, BUBBLE, E.private.general.chatBubbleFontSize, E.private.general.chatBubbleFontOutline) -- 13 + SetFont(_G.AchievementFont_Small, NORMAL, s and small or size) -- 10 Achiev dates + SetFont(_G.BossEmoteNormalHuge, NORMAL, 24) -- Talent Title + SetFont(_G.CoreAbilityFont, NORMAL, 26) -- 32 Core abilities(title) + SetFont(_G.DestinyFontHuge, NORMAL, 32) -- Garrison Mission Report + SetFont(_G.DestinyFontMed, NORMAL, 14) -- Added in 7.3.5 used for ? + SetFont(_G.Fancy12Font, NORMAL, 12) -- Added in 7.3.5 used for ? + SetFont(_G.Fancy14Font, NORMAL, 14) -- Added in 7.3.5 used for ? + SetFont(_G.Fancy22Font, NORMAL, s and 22 or 20) -- Talking frame Title font + SetFont(_G.Fancy24Font, NORMAL, s and 24 or 20) -- Artifact frame - weapon name + SetFont(_G.FriendsFont_11, NORMAL, 11) + SetFont(_G.FriendsFont_Large, NORMAL, s and large or size) -- 14 + SetFont(_G.FriendsFont_Normal, NORMAL, size) -- 12 + SetFont(_G.FriendsFont_Small, NORMAL, s and small or size) -- 10 + SetFont(_G.FriendsFont_UserText, NORMAL, size) -- 11 + SetFont(_G.Game10Font_o1, NORMAL, 10, 'OUTLINE') + SetFont(_G.Game120Font, NORMAL, 120) + SetFont(_G.Game12Font, NORMAL, 12) -- PVP Stuff + SetFont(_G.Game13FontShadow, NORMAL, s and 13 or 14) -- InspectPvpFrame + SetFont(_G.Game15Font_o1, NORMAL, 15) -- CharacterStatsPane (ItemLevelFrame) + SetFont(_G.Game16Font, NORMAL, 16) -- Added in 7.3.5 used for ? + SetFont(_G.Game18Font, NORMAL, 18) -- MissionUI Bonus Chance + SetFont(_G.Game24Font, NORMAL, 24) -- Garrison Mission level (in detail frame) + SetFont(_G.Game30Font, NORMAL, 30) -- Mission Level + SetFont(_G.Game40Font, NORMAL, 40) + SetFont(_G.Game42Font, NORMAL, 42) -- PVP Stuff + SetFont(_G.Game46Font, NORMAL, 46) -- Added in 7.3.5 used for ? + SetFont(_G.Game48Font, NORMAL, 48) + SetFont(_G.Game48FontShadow, NORMAL, 48) + SetFont(_G.Game60Font, NORMAL, 60) + SetFont(_G.Game72Font, NORMAL, 72) + SetFont(_G.GameFont_Gigantic, NORMAL, 32) -- Used at the install steps + SetFont(_G.GameFontHighlightMedium, NORMAL, s and medium or 15) -- 14 Fix QuestLog Title mouseover + SetFont(_G.GameFontHighlightSmall2, NORMAL, s and small or size) -- 11 Skill or Recipe description on TradeSkill frame + SetFont(_G.GameFontNormalHuge2, NORMAL, s and huge or 24) -- 24 Mythic weekly best dungeon name + SetFont(_G.GameFontNormalLarge, NORMAL, s and large or 16) -- 16 + SetFont(_G.GameFontNormalLarge2, NORMAL, s and large or 15) -- 18 Garrison Follower Names + SetFont(_G.GameFontNormalMed1, NORMAL, s and medium or 14) -- 13 WoW Token Info + SetFont(_G.GameFontNormalMed2, NORMAL, s and medium or medium) -- 14 Quest tracker + SetFont(_G.GameFontNormalMed3, NORMAL, s and medium or 15) -- 14 + SetFont(_G.GameFontNormalSmall2, NORMAL, s and small or 12) -- 11 MissionUI Followers names + SetFont(_G.GameTooltipHeader, NORMAL, size) -- 14 + SetFont(_G.InvoiceFont_Med, NORMAL, s and size or 12) -- 12 Mail + SetFont(_G.InvoiceFont_Small, NORMAL, s and small or size) -- 10 Mail + SetFont(_G.MailFont_Large, NORMAL, 14) -- 10 Mail + SetFont(_G.Number11Font, NORMAL, 11) + SetFont(_G.Number11Font, NUMBER, 11) + SetFont(_G.Number12Font, NORMAL, 12) + SetFont(_G.Number12Font_o1, NUMBER, 12, 'OUTLINE') + SetFont(_G.Number13Font, NUMBER, 13) + SetFont(_G.Number13FontGray, NUMBER, 13) + SetFont(_G.Number13FontWhite, NUMBER, 13) + SetFont(_G.Number13FontYellow, NUMBER, 13) + SetFont(_G.Number14FontGray, NUMBER, 14) + SetFont(_G.Number14FontWhite, NUMBER, 14) + SetFont(_G.Number15Font, NORMAL, 15) + SetFont(_G.Number18Font, NUMBER, 18) + SetFont(_G.Number18FontWhite, NUMBER, 18) + SetFont(_G.NumberFont_Outline_Huge, NUMBER, s and huge or 28, thick) -- 30 + SetFont(_G.NumberFont_Outline_Large, NUMBER, s and large or 15, outline) -- 16 + SetFont(_G.NumberFont_Outline_Med, NUMBER, medium, 'OUTLINE') -- 14 + SetFont(_G.NumberFont_OutlineThick_Mono_Small, NUMBER, size, 'OUTLINE') -- 12 + SetFont(_G.NumberFont_Shadow_Med, NORMAL, s and medium or size) -- 14 Chat EditBox + SetFont(_G.NumberFont_Shadow_Small, NORMAL, s and small or size) -- 12 + SetFont(_G.NumberFontNormalSmall, NORMAL, s and small or 11, 'OUTLINE') -- 12 Calendar, EncounterJournal + SetFont(_G.PriceFont, NORMAL, 13) + SetFont(_G.PVPArenaTextString, NORMAL, 22, outline) + SetFont(_G.PVPInfoTextString, NORMAL, 22, outline) + SetFont(_G.QuestFont, NORMAL, size) -- 13 + SetFont(_G.QuestFont_Enormous, NORMAL, s and enormous or 24) -- 30 Garrison Titles + SetFont(_G.QuestFont_Huge, NORMAL, s and huge or 15) -- 18 Quest rewards title(Rewards) + SetFont(_G.QuestFont_Large, NORMAL, s and large or 14) -- 14 + SetFont(_G.QuestFont_Shadow_Huge, NORMAL, s and huge or 15) -- 18 Quest Title + SetFont(_G.QuestFont_Shadow_Small, NORMAL, s and size or 14) -- 14 + SetFont(_G.QuestFont_Super_Huge, NORMAL, s and mega or 22) -- 24 + SetFont(_G.ReputationDetailFont, NORMAL, size) -- 10 Rep Desc when clicking a rep + SetFont(_G.SpellFont_Small, NORMAL, 10) + SetFont(_G.SubSpellFont, NORMAL, 10) -- Spellbook Sub Names + SetFont(_G.SubZoneTextFont, NORMAL, 24, outline) -- 26 World Map(SubZone) + SetFont(_G.SubZoneTextString, NORMAL, 25, outline) -- 26 + SetFont(_G.SystemFont_Huge1, NORMAL, 20) -- Garrison Mission XP + SetFont(_G.SystemFont_Huge1_Outline, NORMAL, 18, outline) -- 20 Garrison Mission Chance + SetFont(_G.SystemFont_Large, NORMAL, s and 16 or 15) + SetFont(_G.SystemFont_Med1, NORMAL, size) -- 12 + SetFont(_G.SystemFont_Med3, NORMAL, medium) -- 14 + SetFont(_G.SystemFont_Outline, NORMAL, s and size or 13, outline) -- 13 Pet level on World map + SetFont(_G.SystemFont_Outline_Small, NUMBER, s and small or size, 'OUTLINE') -- 10 + SetFont(_G.SystemFont_OutlineThick_Huge2, NORMAL, s and huge or 20, thick) -- 22 + SetFont(_G.SystemFont_OutlineThick_WTF, NORMAL, s and enormous or 32, outline) -- 32 World Map + SetFont(_G.SystemFont_Shadow_Huge1, NORMAL, 20, outline) -- Raid Warning, Boss emote frame too + SetFont(_G.SystemFont_Shadow_Huge3, NORMAL, 22) -- 25 FlightMap + SetFont(_G.SystemFont_Shadow_Huge4, NORMAL, 27, nil, nil, nil, nil, nil, 1, -1) + SetFont(_G.SystemFont_Shadow_Large, NORMAL, 15) + SetFont(_G.SystemFont_Shadow_Large2, NORMAL, 18) -- Auction House ItemDisplay + SetFont(_G.SystemFont_Shadow_Large_Outline, NUMBER, 20, 'OUTLINE') -- 16 + SetFont(_G.SystemFont_Shadow_Med1, NORMAL, size) -- 12 + SetFont(_G.SystemFont_Shadow_Med2, NORMAL, s and medium or 14.3) -- 14 Shows Order resourses on OrderHallTalentFrame + SetFont(_G.SystemFont_Shadow_Med3, NORMAL, medium) -- 14 + SetFont(_G.SystemFont_Shadow_Small, NORMAL, small) -- 10 + SetFont(_G.SystemFont_Small, NORMAL, s and small or size) -- 10 + SetFont(_G.SystemFont_Tiny, NORMAL, s and tiny or size) -- 09 + SetFont(_G.Tooltip_Med, NORMAL, size) -- 12 + SetFont(_G.Tooltip_Small, NORMAL, s and small or size) -- 10 + SetFont(_G.ZoneTextString, NORMAL, s and enormous or 32, outline) -- 32 + end +end diff --git a/Core/Install.lua b/Core/Install.lua new file mode 100644 index 0000000..9a69360 --- /dev/null +++ b/Core/Install.lua @@ -0,0 +1,882 @@ +local E, L, V, P, G =unpack(select(2, ...)); --Import: Engine, Locales, PrivateDB, ProfileDB, GlobalDB, Localize Underscore +local NP = E:GetModule('NamePlates') +local UF = E:GetModule('UnitFrames') +local CH = E:GetModule('Chat') +local S = E:GetModule('Skins') + +local _G = _G +local unpack = unpack +local format = format +local pairs = pairs +local ipairs = ipairs +local tinsert = tinsert + +local CreateFrame = CreateFrame +local SetCVar = SetCVar +local PlaySound = PlaySound +local ReloadUI = ReloadUI +local UIFrameFadeOut = UIFrameFadeOut +local ChatFrame_AddMessageGroup = ChatFrame_AddMessageGroup +local ChatFrame_RemoveAllMessageGroups = ChatFrame_RemoveAllMessageGroups +local ChatFrame_AddChannel = ChatFrame_AddChannel +local ChatFrame_RemoveChannel = ChatFrame_RemoveChannel +local ChangeChatColor = ChangeChatColor +local ToggleChatColorNamesByClassGroup = ToggleChatColorNamesByClassGroup +local FCF_ResetChatWindows = FCF_ResetChatWindows +local FCF_UnDockFrame = FCF_UnDockFrame +local FCF_OpenNewWindow = FCF_OpenNewWindow +local FCF_SavePositionAndDimensions = FCF_SavePositionAndDimensions +local FCF_SetWindowName = FCF_SetWindowName +local FCF_StopDragging = FCF_StopDragging +local FCF_SetChatWindowFontSize = FCF_SetChatWindowFontSize +local CLASS, CONTINUE, PREVIOUS = CLASS, CONTINUE, PREVIOUS +local LOOT, GENERAL, TRADE = LOOT, GENERAL, TRADE +local GUILD_EVENT_LOG = GUILD_EVENT_LOG +-- GLOBALS: ElvUIInstallFrame + +local CURRENT_PAGE = 0 +local MAX_PAGE = 9 + +local PLAYER_NAME = format('%s-%s', E.myname, E:ShortenRealm(E.myrealm)) +local ELV_TOONS = { + ['Elv-Spirestone'] = true, + ['Elvz-Spirestone'] = true, + ['Fleshlite-Spirestone'] = true, + ['Elvidan-Spirestone'] = true, + ['Elvilas-Spirestone'] = true, + ['Fraku-Spirestone'] = true, + ['Jarvix-Spirestone'] = true, + ['Watermelon-Spirestone'] = true, + ['Zinxbe-Spirestone'] = true, + ['Whorlock-Spirestone'] = true, +} + +function E:SetupChat(noDisplayMsg) + FCF_ResetChatWindows() + FCF_OpenNewWindow(LOOT) + FCF_UnDockFrame(_G.ChatFrame3) + + for _, name in ipairs(_G.CHAT_FRAMES) do + local frame = _G[name] + local id = frame:GetID() + + if E.private.chat.enable then + CH:FCFTab_UpdateColors(CH:GetTab(_G[name])) + end + + -- move general bottom left + if id == 1 then + frame:ClearAllPoints() + frame:Point('BOTTOMLEFT', _G.LeftChatToggleButton, 'TOPLEFT', 1, 3) + elseif id == 3 then + frame:ClearAllPoints() + frame:Point('BOTTOMLEFT', _G.RightChatDataPanel, 'TOPLEFT', 1, 3) + end + + FCF_SavePositionAndDimensions(frame) + FCF_StopDragging(frame) + FCF_SetChatWindowFontSize(nil, frame, 12) + + -- rename windows general because moved to chat #3 + if id == 1 then + FCF_SetWindowName(frame, GENERAL) + elseif id == 2 then + FCF_SetWindowName(frame, GUILD_EVENT_LOG) + elseif id == 3 then + FCF_SetWindowName(frame, LOOT..' / '..TRADE) + end + end + + -- keys taken from `ChatTypeGroup` but doesnt add: 'OPENING', 'TRADESKILLS', 'PET_INFO', 'COMBAT_MISC_INFO', 'COMMUNITIES_CHANNEL', 'PET_BATTLE_COMBAT_LOG', 'PET_BATTLE_INFO', 'TARGETICONS' + local chatGroup = { 'SYSTEM', 'CHANNEL', 'SAY', 'EMOTE', 'YELL', 'WHISPER', 'PARTY', 'PARTY_LEADER', 'RAID', 'RAID_LEADER', 'RAID_WARNING', 'INSTANCE_CHAT', 'INSTANCE_CHAT_LEADER', 'GUILD', 'OFFICER', 'MONSTER_SAY', 'MONSTER_YELL', 'MONSTER_EMOTE', 'MONSTER_WHISPER', 'MONSTER_BOSS_EMOTE', 'MONSTER_BOSS_WHISPER', 'ERRORS', 'AFK', 'DND', 'IGNORED', 'BG_HORDE', 'BG_ALLIANCE', 'BG_NEUTRAL', 'ACHIEVEMENT', 'GUILD_ACHIEVEMENT', 'BN_WHISPER', 'BN_INLINE_TOAST_ALERT' } + ChatFrame_RemoveAllMessageGroups(_G.ChatFrame1) + for _, v in ipairs(chatGroup) do + ChatFrame_AddMessageGroup(_G.ChatFrame1, v) + end + + -- keys taken from `ChatTypeGroup` which weren't added above to ChatFrame1 + chatGroup = { 'COMBAT_XP_GAIN', 'COMBAT_HONOR_GAIN', 'COMBAT_FACTION_CHANGE', 'SKILL', 'LOOT', 'CURRENCY', 'MONEY' } + ChatFrame_RemoveAllMessageGroups(_G.ChatFrame3) + for _, v in ipairs(chatGroup) do + ChatFrame_AddMessageGroup(_G.ChatFrame3, v) + end + + ChatFrame_AddChannel(_G.ChatFrame1, GENERAL) + ChatFrame_RemoveChannel(_G.ChatFrame1, TRADE) + ChatFrame_AddChannel(_G.ChatFrame3, TRADE) + + -- set the chat groups names in class color to enabled for all chat groups which players names appear + chatGroup = { 'SAY', 'EMOTE', 'YELL', 'WHISPER', 'PARTY', 'PARTY_LEADER', 'RAID', 'RAID_LEADER', 'RAID_WARNING', 'INSTANCE_CHAT', 'INSTANCE_CHAT_LEADER', 'GUILD', 'OFFICER', 'ACHIEVEMENT', 'GUILD_ACHIEVEMENT', 'COMMUNITIES_CHANNEL' } + for i = 1, _G.MAX_WOW_CHAT_CHANNELS do + tinsert(chatGroup, 'CHANNEL'..i) + end + for _, v in ipairs(chatGroup) do + ToggleChatColorNamesByClassGroup(true, v) + end + + -- Adjust Chat Colors + ChangeChatColor('CHANNEL1', 195/255, 230/255, 232/255) -- General + ChangeChatColor('CHANNEL2', 232/255, 158/255, 121/255) -- Trade + ChangeChatColor('CHANNEL3', 232/255, 228/255, 121/255) -- Local Defense + + if E.private.chat.enable then + CH:PositionChats() + end + + if E.db.RightChatPanelFaded then + _G.RightChatToggleButton:Click() + end + + if E.db.LeftChatPanelFaded then + _G.LeftChatToggleButton:Click() + end + + if ELV_TOONS[PLAYER_NAME] then + SetCVar('scriptErrors', 1) + end + + if _G.InstallStepComplete and not noDisplayMsg then + _G.InstallStepComplete.message = L["Chat Set"] + _G.InstallStepComplete:Show() + end +end + +function E:SetupCVars(noDisplayMsg) + SetCVar('statusTextDisplay', 'BOTH') + SetCVar('screenshotQuality', 10) + SetCVar('chatMouseScroll', 1) + SetCVar('chatStyle', 'classic') + SetCVar('whisperMode', 'inline') + SetCVar('wholeChatWindowClickable', 0) + SetCVar('showTutorials', 0) + SetCVar('showNPETutorials', 0) + SetCVar('UberTooltips', 1) + SetCVar('threatWarning', 3) + SetCVar('alwaysShowActionBars', 1) + SetCVar('lockActionBars', 1) + SetCVar('spamFilter', 0) + SetCVar('cameraDistanceMaxZoomFactor', 2.6) + SetCVar('showQuestTrackingTooltips', 1) + SetCVar('fstack_preferParentKeys', 0) --Add back the frame names via fstack! + + NP:CVarReset() + + _G.InterfaceOptionsActionBarsPanelPickupActionKeyDropDown:SetValue('SHIFT') + _G.InterfaceOptionsActionBarsPanelPickupActionKeyDropDown:RefreshValue() + + if _G.InstallStepComplete and not noDisplayMsg then + _G.InstallStepComplete.message = L["CVars Set"] + _G.InstallStepComplete:Show() + end +end + +function E:GetColor(r, g, b, a) + return { r = r, b = b, g = g, a = a } +end + +function E:SetupTheme(theme, noDisplayMsg) + E.private.theme = theme + + local classColor + + --Set colors + if theme == 'classic' then + E.db.general.bordercolor = (E.PixelMode and E:GetColor(0, 0, 0) or E:GetColor(.31, .31, .31)) + E.db.general.backdropcolor = E:GetColor(.1, .1, .1) + E.db.general.backdropfadecolor = E:GetColor(0.13, 0.13, 0.13, 0.69) + E.db.unitframe.colors.borderColor = (E.PixelMode and E:GetColor(0, 0, 0) or E:GetColor(.31, .31, .31)) + E.db.unitframe.colors.healthclass = false + E.db.unitframe.colors.health = E:GetColor(.31, .31, .31) + E.db.unitframe.colors.auraBarBuff = E:GetColor(.31, .31, .31) + E.db.unitframe.colors.castColor = E:GetColor(.31, .31, .31) + E.db.unitframe.colors.castClassColor = false + elseif theme == 'class' then + classColor = E:ClassColor(E.myclass, true) + + E.db.general.bordercolor = (E.PixelMode and E:GetColor(0, 0, 0) or E:GetColor(.31, .31, .31)) + E.db.general.backdropcolor = E:GetColor(.1, .1, .1) + E.db.general.backdropfadecolor = E:GetColor(.06, .06, .06, .8) + E.db.unitframe.colors.borderColor = (E.PixelMode and E:GetColor(0, 0, 0) or E:GetColor(.31, .31, .31)) + E.db.unitframe.colors.auraBarBuff = E:GetColor(classColor.r, classColor.g, classColor.b) + E.db.unitframe.colors.healthclass = true + E.db.unitframe.colors.castClassColor = true + else + E.db.general.bordercolor = (E.PixelMode and E:GetColor(0, 0, 0) or E:GetColor(.1, .1, .1)) + E.db.general.backdropcolor = E:GetColor(.1, .1, .1) + E.db.general.backdropfadecolor = E:GetColor(.054, .054, .054, .8) + E.db.unitframe.colors.borderColor = (E.PixelMode and E:GetColor(0, 0, 0) or E:GetColor(.1, .1, .1)) + E.db.unitframe.colors.auraBarBuff = E:GetColor(.1, .1, .1) + E.db.unitframe.colors.healthclass = false + E.db.unitframe.colors.health = E:GetColor(.1, .1, .1) + E.db.unitframe.colors.castColor = E:GetColor(.1, .1, .1) + E.db.unitframe.colors.castClassColor = false + end + + --Value Color + if theme == 'class' then + E.db.general.valuecolor = E:GetColor(classColor.r, classColor.g, classColor.b) + else + E.db.general.valuecolor = E:GetColor(23/255, 132/255, 209/255) + end + + E:UpdateStart(true, true) + + if _G.InstallStepComplete and not noDisplayMsg then + _G.InstallStepComplete.message = L["Theme Set"] + _G.InstallStepComplete:Show() + end +end + +function E:SetupLayout(layout, noDataReset, noDisplayMsg) + if not noDataReset then + E.db.layoutSet = layout + E.db.layoutSetting = layout + + --Unitframes + E:CopyTable(E.db.unitframe.units, P.unitframe.units) + + --Shared base layout, tweaks to individual layouts will be below + E:ResetMovers() + if not E.db.movers then + E.db.movers = {} + end + + --ActionBars + E.db.actionbar.bar1.buttons = 8 + E.db.actionbar.bar1.buttonsize = 50 + E.db.actionbar.bar1.buttonspacing = 1 + E.db.actionbar.bar2.buttons = 9 + E.db.actionbar.bar2.buttonsize = 38 + E.db.actionbar.bar2.buttonspacing = 1 + E.db.actionbar.bar2.enabled = true + E.db.actionbar.bar2.visibility = '[petbattle] hide; show' + E.db.actionbar.bar3.buttons = 8 + E.db.actionbar.bar3.buttonsize = 50 + E.db.actionbar.bar3.buttonspacing = 1 + E.db.actionbar.bar3.buttonsPerRow = 10 + E.db.actionbar.bar3.visibility = '[petbattle] hide; show' + E.db.actionbar.bar4.enabled = false + E.db.actionbar.bar4.visibility = '[petbattle] hide; show' + E.db.actionbar.bar5.enabled = false + E.db.actionbar.bar5.visibility = '[petbattle] hide; show' + E.db.actionbar.bar6.visibility = '[petbattle] hide; show' + --Auras + E.db.auras.buffs.countFontSize = 10 + E.db.auras.buffs.size = 40 + E.db.auras.debuffs.countFontSize = 10 + E.db.auras.debuffs.size = 40 + --Bags + E.db.bags.bagSize = 42 + E.db.bags.bagWidth = 474 + E.db.bags.bankSize = 42 + E.db.bags.bankWidth = 474 + E.db.bags.itemLevelCustomColorEnable = true + E.db.bags.scrapIcon = true + --Chat + E.db.chat.fontSize = 10 + E.db.chat.separateSizes = false + E.db.chat.panelHeight = 236 + E.db.chat.panelWidth = 472 + E.db.chat.tabFontSize = 10 + --DataTexts + E.db.datatexts.panels.LeftChatDataPanel[3] = 'QuickJoin' + --General + E.db.general.bonusObjectivePosition = 'AUTO' + E.db.general.minimap.size = 220 + E.db.general.objectiveFrameHeight = 400 + E.db.general.talkingHeadFrameScale = 1 + E.db.general.totems.growthDirection = 'HORIZONTAL' + E.db.general.totems.size = 50 + E.db.general.totems.spacing = 8 + + --Movers + for mover, position in pairs(E.LayoutMoverPositions.ALL) do + E.db.movers[mover] = position + E:SaveMoverDefaultPosition(mover) + end + --Tooltip + E.db.tooltip.healthBar.fontOutline = 'MONOCHROMEOUTLINE' + E.db.tooltip.healthBar.height = 12 + --UnitFrames + E.db.unitframe.smoothbars = true + E.db.unitframe.thinBorders = true + --Player + E.db.unitframe.units.player.aurabar.height = 26 + E.db.unitframe.units.player.buffs.perrow = 7 + E.db.unitframe.units.player.castbar.height = 40 + E.db.unitframe.units.player.castbar.insideInfoPanel = false + E.db.unitframe.units.player.castbar.width = 405 + E.db.unitframe.units.player.classbar.height = 14 + E.db.unitframe.units.player.debuffs.perrow = 7 + E.db.unitframe.units.player.disableMouseoverGlow = true + E.db.unitframe.units.player.healPrediction.showOverAbsorbs = false + E.db.unitframe.units.player.health.attachTextTo = 'InfoPanel' + E.db.unitframe.units.player.height = 82 + E.db.unitframe.units.player.infoPanel.enable = true + E.db.unitframe.units.player.power.attachTextTo = 'InfoPanel' + E.db.unitframe.units.player.power.height = 22 + --Target + E.db.unitframe.units.target.aurabar.height = 26 + E.db.unitframe.units.target.buffs.anchorPoint = 'TOPLEFT' + E.db.unitframe.units.target.buffs.perrow = 7 + E.db.unitframe.units.target.castbar.height = 40 + E.db.unitframe.units.target.castbar.insideInfoPanel = false + E.db.unitframe.units.target.castbar.width = 405 + E.db.unitframe.units.target.debuffs.anchorPoint = 'TOPLEFT' + E.db.unitframe.units.target.debuffs.attachTo = 'FRAME' + E.db.unitframe.units.target.debuffs.enable = false + E.db.unitframe.units.target.debuffs.maxDuration = 0 + E.db.unitframe.units.target.debuffs.perrow = 7 + E.db.unitframe.units.target.disableMouseoverGlow = true + E.db.unitframe.units.target.healPrediction.showOverAbsorbs = false + E.db.unitframe.units.target.health.attachTextTo = 'InfoPanel' + E.db.unitframe.units.target.height = 82 + E.db.unitframe.units.target.infoPanel.enable = true + E.db.unitframe.units.target.name.attachTextTo = 'InfoPanel' + E.db.unitframe.units.target.orientation = 'LEFT' + E.db.unitframe.units.target.power.attachTextTo = 'InfoPanel' + E.db.unitframe.units.target.power.height = 22 + --TargetTarget + E.db.unitframe.units.targettarget.debuffs.anchorPoint = 'TOPRIGHT' + E.db.unitframe.units.targettarget.debuffs.enable = false + E.db.unitframe.units.targettarget.disableMouseoverGlow = true + E.db.unitframe.units.targettarget.power.enable = false + E.db.unitframe.units.targettarget.raidicon.attachTo = 'LEFT' + E.db.unitframe.units.targettarget.raidicon.enable = false + E.db.unitframe.units.targettarget.raidicon.xOffset = 2 + E.db.unitframe.units.targettarget.raidicon.yOffset = 0 + E.db.unitframe.units.targettarget.threatStyle = 'GLOW' + E.db.unitframe.units.targettarget.width = 270 + --Focus + E.db.unitframe.units.focus.castbar.width = 270 + E.db.unitframe.units.focus.width = 270 + --Pet + E.db.unitframe.units.pet.castbar.iconSize = 32 + E.db.unitframe.units.pet.castbar.width = 270 + E.db.unitframe.units.pet.debuffs.anchorPoint = 'TOPRIGHT' + E.db.unitframe.units.pet.debuffs.enable = true + E.db.unitframe.units.pet.disableTargetGlow = false + E.db.unitframe.units.pet.infoPanel.height = 14 + E.db.unitframe.units.pet.portrait.camDistanceScale = 2 + E.db.unitframe.units.pet.width = 270 + --Boss + E.db.unitframe.units.boss.buffs.maxDuration = 300 + E.db.unitframe.units.boss.buffs.sizeOverride = 27 + E.db.unitframe.units.boss.buffs.yOffset = 16 + E.db.unitframe.units.boss.castbar.width = 246 + E.db.unitframe.units.boss.debuffs.maxDuration = 300 + E.db.unitframe.units.boss.debuffs.numrows = 1 + E.db.unitframe.units.boss.debuffs.sizeOverride = 27 + E.db.unitframe.units.boss.debuffs.yOffset = -16 + E.db.unitframe.units.boss.height = 60 + E.db.unitframe.units.boss.infoPanel.height = 17 + E.db.unitframe.units.boss.portrait.camDistanceScale = 2 + E.db.unitframe.units.boss.portrait.width = 45 + E.db.unitframe.units.boss.width = 246 + --Party + E.db.unitframe.units.party.height = 74 + E.db.unitframe.units.party.power.height = 13 + E.db.unitframe.units.party.rdebuffs.font = 'PT Sans Narrow' + E.db.unitframe.units.party.width = 231 + --Raid + E.db.unitframe.units.raid.growthDirection = 'RIGHT_UP' + E.db.unitframe.units.raid.infoPanel.enable = true + E.db.unitframe.units.raid.name.attachTextTo = 'InfoPanel' + E.db.unitframe.units.raid.name.position = 'BOTTOMLEFT' + E.db.unitframe.units.raid.name.xOffset = 2 + E.db.unitframe.units.raid.numGroups = 8 + E.db.unitframe.units.raid.rdebuffs.font = 'PT Sans Narrow' + E.db.unitframe.units.raid.rdebuffs.size = 30 + E.db.unitframe.units.raid.rdebuffs.xOffset = 30 + E.db.unitframe.units.raid.rdebuffs.yOffset = 25 + E.db.unitframe.units.raid.resurrectIcon.attachTo = 'BOTTOMRIGHT' + E.db.unitframe.units.raid.roleIcon.attachTo = 'InfoPanel' + E.db.unitframe.units.raid.roleIcon.position = 'BOTTOMRIGHT' + E.db.unitframe.units.raid.roleIcon.size = 12 + E.db.unitframe.units.raid.roleIcon.xOffset = 0 + E.db.unitframe.units.raid.visibility = '[@raid6,noexists] hide;show' + E.db.unitframe.units.raid.width = 92 + --Raid40 + E.db.unitframe.units.raid40.enable = false + E.db.unitframe.units.raid40.rdebuffs.font = 'PT Sans Narrow' + + --[[ + Layout Tweaks will be handled below, + These are changes that deviate from the shared base layout. + ]] + if E.LayoutMoverPositions[layout] then + for mover, position in pairs(E.LayoutMoverPositions[layout]) do + E.db.movers[mover] = position + E:SaveMoverDefaultPosition(mover) + end + end + end + + E:StaggeredUpdateAll(nil, true) + + if _G.InstallStepComplete and not noDisplayMsg then + _G.InstallStepComplete.message = L["Layout Set"] + _G.InstallStepComplete:Show() + end +end + +function E:SetupAuras(style, noDisplayMsg) + local frame = UF.player + E:CopyTable(E.db.unitframe.units.player.buffs, P.unitframe.units.player.buffs) + E:CopyTable(E.db.unitframe.units.player.debuffs, P.unitframe.units.player.debuffs) + E:CopyTable(E.db.unitframe.units.player.aurabar, P.unitframe.units.player.aurabar) + if frame then + UF:Configure_AllAuras(frame) + UF:Configure_AuraBars(frame) + end + + frame = UF.target + E:CopyTable(E.db.unitframe.units.target.buffs, P.unitframe.units.target.buffs) + E:CopyTable(E.db.unitframe.units.target.debuffs, P.unitframe.units.target.debuffs) + E:CopyTable(E.db.unitframe.units.target.aurabar, P.unitframe.units.target.aurabar) + if frame then + UF:Configure_AllAuras(frame) + UF:Configure_AuraBars(frame) + end + + frame = UF.focus + E:CopyTable(E.db.unitframe.units.focus.buffs, P.unitframe.units.focus.buffs) + E:CopyTable(E.db.unitframe.units.focus.debuffs, P.unitframe.units.focus.debuffs) + E:CopyTable(E.db.unitframe.units.focus.aurabar, P.unitframe.units.focus.aurabar) + if frame then + UF:Configure_AllAuras(frame) + UF:Configure_AuraBars(frame) + end + + if not style then + --PLAYER + E.db.unitframe.units.player.buffs.enable = true + E.db.unitframe.units.player.buffs.attachTo = 'FRAME' + E.db.unitframe.units.player.debuffs.attachTo = 'BUFFS' + E.db.unitframe.units.player.aurabar.enable = false + if E.private.unitframe.enable then + UF:CreateAndUpdateUF('player') + end + + --TARGET + E.db.unitframe.units.target.debuffs.enable = true + E.db.unitframe.units.target.aurabar.enable = false + if E.private.unitframe.enable then + UF:CreateAndUpdateUF('target') + end + end + + if _G.InstallStepComplete and not noDisplayMsg then + _G.InstallStepComplete.message = L["Auras Set"] + _G.InstallStepComplete:Show() + end +end + +local function InstallComplete() + E.private.install_complete = E.version + + ReloadUI() +end + +local function ResetAll() + _G.InstallNextButton:Disable() + _G.InstallPrevButton:Disable() + _G.InstallOption1Button:Hide() + _G.InstallOption1Button:SetScript('OnClick', nil) + _G.InstallOption1Button:SetText('') + _G.InstallOption2Button:Hide() + _G.InstallOption2Button:SetScript('OnClick', nil) + _G.InstallOption2Button:SetText('') + _G.InstallOption3Button:Hide() + _G.InstallOption3Button:SetScript('OnClick', nil) + _G.InstallOption3Button:SetText('') + _G.InstallOption4Button:Hide() + _G.InstallOption4Button:SetScript('OnClick', nil) + _G.InstallOption4Button:SetText('') + _G.InstallSlider:Hide() + _G.InstallSlider.Min:SetText('') + _G.InstallSlider.Max:SetText('') + _G.InstallSlider.Cur:SetText('') + ElvUIInstallFrame.SubTitle:SetText('') + ElvUIInstallFrame.Desc1:SetText('') + ElvUIInstallFrame.Desc2:SetText('') + ElvUIInstallFrame.Desc3:SetText('') + ElvUIInstallFrame:Size(550, 400) +end + +function E:SetPage(PageNum) + CURRENT_PAGE = PageNum + ResetAll() + + _G.InstallStatus.anim.progress:SetChange(PageNum) + _G.InstallStatus.anim.progress:Play() + _G.InstallStatus.text:SetText(CURRENT_PAGE..' / '..MAX_PAGE) + + local r, g, b = E:ColorGradient(CURRENT_PAGE / MAX_PAGE, 1, 0, 0, 1, 1, 0, 0, 1, 0) + ElvUIInstallFrame.Status:SetStatusBarColor(r, g, b) + + if PageNum == MAX_PAGE then + _G.InstallNextButton:Disable() + else + _G.InstallNextButton:Enable() + end + + if PageNum == 1 then + _G.InstallPrevButton:Disable() + else + _G.InstallPrevButton:Enable() + end + + local InstallOption1Button = _G.InstallOption1Button + local InstallOption2Button = _G.InstallOption2Button + local InstallOption3Button = _G.InstallOption3Button + local InstallSlider = _G.InstallSlider + + local f = ElvUIInstallFrame + if PageNum == 1 then + f.SubTitle:SetFormattedText(L["Welcome to ElvUI version %s!"], E.version) + f.Desc1:SetText(L["This install process will help you learn some of the features in ElvUI has to offer and also prepare your user interface for usage."]) + f.Desc2:SetText(L["The in-game configuration menu can be accessed by typing the /ec command. Press the button below if you wish to skip the installation process."]) + f.Desc3:SetText(L["Please press the continue button to go onto the next step."]) + InstallOption1Button:Show() + InstallOption1Button:SetScript('OnClick', InstallComplete) + InstallOption1Button:SetText(L["Skip Process"]) + elseif PageNum == 2 then + f.SubTitle:SetText(L["CVars"]) + f.Desc1:SetText(L["This part of the installation process sets up your World of Warcraft default options it is recommended you should do this step for everything to behave properly."]) + f.Desc2:SetText(L["Please click the button below to setup your CVars."]) + f.Desc3:SetText(L["Importance: |cffFF3333High|r"]) + InstallOption1Button:Show() + InstallOption1Button:SetScript('OnClick', function() E:SetupCVars() end) + InstallOption1Button:SetText(L["Setup CVars"]) + elseif PageNum == 3 then + f.SubTitle:SetText(L["Chat"]) + f.Desc1:SetText(L["This part of the installation process sets up your chat windows names, positions and colors."]) + f.Desc2:SetText(L["The chat windows function the same as Blizzard standard chat windows, you can right click the tabs and drag them around, rename, etc. Please click the button below to setup your chat windows."]) + f.Desc3:SetText(L["Importance: |cffD3CF00Medium|r"]) + InstallOption1Button:Show() + InstallOption1Button:SetScript('OnClick', function() E:SetupChat() end) + InstallOption1Button:SetText(L["Setup Chat"]) + elseif PageNum == 4 then + f.SubTitle:SetText(L["Profile Settings Setup"]) + f.Desc1:SetText(L["Please click the button below to setup your Profile Settings."]) + InstallOption1Button:Show() + InstallOption1Button:SetScript('OnClick', function() + E.data:SetProfile('Default') + E:NextPage() + end) + InstallOption1Button:SetText(L["Shared Profile"]) + InstallOption2Button:Show() + InstallOption2Button:SetScript('OnClick', function() + E.data:SetProfile(E.mynameRealm) + E:NextPage() + end) + InstallOption2Button:SetText(L["New Profile"]) + elseif PageNum == 5 then + f.SubTitle:SetText(L["Theme Setup"]) + f.Desc1:SetText(L["Choose a theme layout you wish to use for your initial setup."]) + f.Desc2:SetText(L["You can always change fonts and colors of any element of ElvUI from the in-game configuration."]) + f.Desc3:SetText(L["Importance: |cFF33FF33Low|r"]) + InstallOption1Button:Show() + InstallOption1Button:SetScript('OnClick', function() E:SetupTheme('classic') end) + InstallOption1Button:SetText(L["Classic"]) + InstallOption2Button:Show() + InstallOption2Button:SetScript('OnClick', function() E:SetupTheme('default') end) + InstallOption2Button:SetText(L["Dark"]) + InstallOption3Button:Show() + InstallOption3Button:SetScript('OnClick', function() E:SetupTheme('class') end) + InstallOption3Button:SetText(CLASS) + elseif PageNum == 6 then + f.SubTitle:SetText(_G.UISCALE) + f.Desc1:SetFormattedText(L["Adjust the UI Scale to fit your screen, press the autoscale button to set the UI Scale automatically."]) + InstallSlider:Show() + InstallSlider:SetValueStep(0.01) + InstallSlider:SetObeyStepOnDrag(true) + InstallSlider:SetMinMaxValues(0.4, 1.15) + + local value = E.global.general.UIScale + InstallSlider:SetValue(value) + InstallSlider.Cur:SetText(value) + InstallSlider:SetScript('OnValueChanged', function(self) + local val = E:Round(self:GetValue(), 2) + E.global.general.UIScale = val + InstallSlider.Cur:SetText(val) + end) + + InstallSlider.Min:SetText(0.4) + InstallSlider.Max:SetText(1.15) + InstallOption1Button:Show() + InstallOption1Button:SetScript('OnClick', function() + local scale = E:PixelBestSize() + + -- this is to just keep the slider in place, the values need updated again afterwards + InstallSlider:SetValue(scale) + + -- update the values with deeper accuracy + E.global.general.UIScale = scale + InstallSlider.Cur:SetText(E.global.general.UIScale) + end) + + InstallOption1Button:SetText(L["Auto Scale"]) + InstallOption2Button:Show() + InstallOption2Button:SetScript('OnClick', E.PixelScaleChanged) + + InstallOption2Button:SetText(L["Preview"]) + f.Desc3:SetText(L["Importance: |cffFF3333High|r"]) + elseif PageNum == 7 then + f.SubTitle:SetText(L["Layout"]) + f.Desc1:SetText(L["You can now choose what layout you wish to use based on your combat role."]) + f.Desc2:SetText(L["This will change the layout of your unitframes and actionbars."]) + f.Desc3:SetText(L["Importance: |cffD3CF00Medium|r"]) + InstallOption1Button:Show() + InstallOption1Button:SetScript('OnClick', function() E.db.layoutSet = nil; E:SetupLayout('tank') end) + InstallOption1Button:SetText(L["Tank / Physical DPS"]) + InstallOption2Button:Show() + InstallOption2Button:SetScript('OnClick', function() E.db.layoutSet = nil; E:SetupLayout('healer') end) + InstallOption2Button:SetText(L["Healer"]) + InstallOption3Button:Show() + InstallOption3Button:SetScript('OnClick', function() E.db.layoutSet = nil; E:SetupLayout('dpsCaster') end) + InstallOption3Button:SetText(L["Caster DPS"]) + elseif PageNum == 8 then + f.SubTitle:SetText(L["Auras"]) + f.Desc1:SetText(L["Select the type of aura system you want to use with ElvUI's unitframes. Set to Aura Bar & Icons to use both aura bars and icons, set to icons only to only see icons."]) + f.Desc2:SetText(L["If you have an icon or aurabar that you don't want to display simply hold down shift and right click the icon for it to disapear."]) + f.Desc3:SetText(L["Importance: |cffD3CF00Medium|r"]) + InstallOption1Button:Show() + InstallOption1Button:SetScript('OnClick', function() E:SetupAuras(true) end) + InstallOption1Button:SetText(L["Aura Bars & Icons"]) + InstallOption2Button:Show() + InstallOption2Button:SetScript('OnClick', function() E:SetupAuras() end) + InstallOption2Button:SetText(L["Icons Only"]) + elseif PageNum == 9 then + f.SubTitle:SetText(L["Installation Complete"]) + f.Desc1:SetText(L["You are now finished with the installation process. If you are in need of technical support please visit us at http://www.tukui.org."]) + f.Desc2:SetText(L["Please click the button below so you can setup variables and ReloadUI."]) + InstallOption1Button:Show() + InstallOption1Button:SetScript('OnClick', function() E:StaticPopup_Show('ELVUI_EDITBOX', nil, nil, 'https://discord.gg/xFWcfgE') end) + InstallOption1Button:SetText(L["Discord"]) + InstallOption2Button:Show() + InstallOption2Button:SetScript('OnClick', InstallComplete) + InstallOption2Button:SetText(L["Finished"]) + ElvUIInstallFrame:Size(550, 350) + end +end + +function E:NextPage() + if CURRENT_PAGE ~= MAX_PAGE then + CURRENT_PAGE = CURRENT_PAGE + 1 + E:SetPage(CURRENT_PAGE) + end +end + +function E:PreviousPage() + if CURRENT_PAGE ~= 1 then + CURRENT_PAGE = CURRENT_PAGE - 1 + E:SetPage(CURRENT_PAGE) + end +end + +--Install UI +function E:Install() + if not _G.InstallStepComplete then + local imsg = CreateFrame('Frame', 'InstallStepComplete', E.UIParent) + imsg:Size(418, 72) + imsg:Point('TOP', 0, -190) + imsg:Hide() + imsg:SetScript('OnShow', function(f) + if f.message then + PlaySound(888) + f.text:SetText(f.message) + UIFrameFadeOut(f, 3.5, 1, 0) + E:Delay(4, f.Hide, f) + f.message = nil + else + f:Hide() + end + end) + + imsg.firstShow = false + + imsg.bg = imsg:CreateTexture(nil, 'BACKGROUND') + imsg.bg:SetTexture([[Interface\LevelUp\LevelUpTex]]) + imsg.bg:Point('BOTTOM') + imsg.bg:Size(326, 103) + imsg.bg:SetTexCoord(0.00195313, 0.63867188, 0.03710938, 0.23828125) + imsg.bg:SetVertexColor(1, 1, 1, 0.6) + + imsg.lineTop = imsg:CreateTexture(nil, 'BACKGROUND') + imsg.lineTop:SetDrawLayer('BACKGROUND', 2) + imsg.lineTop:SetTexture([[Interface\LevelUp\LevelUpTex]]) + imsg.lineTop:Point('TOP') + imsg.lineTop:Size(418, 7) + imsg.lineTop:SetTexCoord(0.00195313, 0.81835938, 0.01953125, 0.03320313) + + imsg.lineBottom = imsg:CreateTexture(nil, 'BACKGROUND') + imsg.lineBottom:SetDrawLayer('BACKGROUND', 2) + imsg.lineBottom:SetTexture([[Interface\LevelUp\LevelUpTex]]) + imsg.lineBottom:Point('BOTTOM') + imsg.lineBottom:Size(418, 7) + imsg.lineBottom:SetTexCoord(0.00195313, 0.81835938, 0.01953125, 0.03320313) + + imsg.text = imsg:CreateFontString(nil, 'ARTWORK', 'GameFont_Gigantic') + imsg.text:Point('BOTTOM', 0, 12) + imsg.text:SetTextColor(1, 0.82, 0) + imsg.text:SetJustifyH('CENTER') + end + + --Create Frame + if not ElvUIInstallFrame then + local f = CreateFrame('Button', 'ElvUIInstallFrame', E.UIParent, 'BackdropTemplate') + f.SetPage = E.SetPage + f:Size(550, 400) + f:SetTemplate('Transparent') + f:Point('CENTER') + f:SetFrameStrata('TOOLTIP') + + f:SetMovable(true) + f:EnableMouse(true) + f:RegisterForDrag('LeftButton') + f:SetScript('OnDragStart', function(frame) frame:StartMoving() frame:SetUserPlaced(false) end) + f:SetScript('OnDragStop', function(frame) frame:StopMovingOrSizing() end) + + f.Title = f:CreateFontString(nil, 'OVERLAY') + f.Title:FontTemplate(nil, 17, nil) + f.Title:Point('TOP', 0, -5) + f.Title:SetText(L["ElvUI Installation"]) + + f.Next = CreateFrame('Button', 'InstallNextButton', f, 'UIPanelButtonTemplate, BackdropTemplate') + f.Next:Size(110, 25) + f.Next:Point('BOTTOMRIGHT', -5, 5) + f.Next:SetText(CONTINUE) + f.Next:Disable() + f.Next:SetScript('OnClick', E.NextPage) + S:HandleButton(f.Next, true) + + f.Prev = CreateFrame('Button', 'InstallPrevButton', f, 'UIPanelButtonTemplate, BackdropTemplate') + f.Prev:Size(110, 25) + f.Prev:Point('BOTTOMLEFT', 5, 5) + f.Prev:SetText(PREVIOUS) + f.Prev:Disable() + f.Prev:SetScript('OnClick', E.PreviousPage) + S:HandleButton(f.Prev, true) + + f.Status = CreateFrame('StatusBar', 'InstallStatus', f) + f.Status:SetFrameLevel(f.Status:GetFrameLevel() + 2) + f.Status:CreateBackdrop() + f.Status:SetStatusBarTexture(E.media.normTex) + E:RegisterStatusBar(f.Status) + f.Status:SetStatusBarColor(1, 0, 0) + f.Status:SetMinMaxValues(0, MAX_PAGE) + f.Status:Point('TOPLEFT', f.Prev, 'TOPRIGHT', 6, -2) + f.Status:Point('BOTTOMRIGHT', f.Next, 'BOTTOMLEFT', -6, 2) + + -- Setup StatusBar Animation + f.Status.anim = _G.CreateAnimationGroup(f.Status) + f.Status.anim.progress = f.Status.anim:CreateAnimation('Progress') + f.Status.anim.progress:SetEasing('Out') + f.Status.anim.progress:SetDuration(.3) + + f.Status.text = f.Status:CreateFontString(nil, 'OVERLAY') + f.Status.text:FontTemplate(nil, 14, 'OUTLINE') + f.Status.text:Point('CENTER') + f.Status.text:SetText(CURRENT_PAGE..' / '..MAX_PAGE) + + f.Slider = CreateFrame('Slider', 'InstallSlider', f, 'BackdropTemplate') + f.Slider:SetOrientation('HORIZONTAL') + f.Slider:Height(15) + f.Slider:Width(400) + f.Slider:SetHitRectInsets(0, 0, -10, 0) + f.Slider:Point('CENTER', 0, 45) + S:HandleSliderFrame(f.Slider) + f.Slider:Hide() + + f.Slider.Min = f.Slider:CreateFontString(nil, 'ARTWORK', 'GameFontHighlightSmall') + f.Slider.Min:Point('RIGHT', f.Slider, 'LEFT', -3, 0) + f.Slider.Max = f.Slider:CreateFontString(nil, 'ARTWORK', 'GameFontHighlightSmall') + f.Slider.Max:Point('LEFT', f.Slider, 'RIGHT', 3, 0) + f.Slider.Cur = f.Slider:CreateFontString(nil, 'ARTWORK', 'GameFontHighlightSmall') + f.Slider.Cur:Point('BOTTOM', f.Slider, 'TOP', 0, 10) + f.Slider.Cur:FontTemplate(nil, 30, nil) + + f.Option1 = CreateFrame('Button', 'InstallOption1Button', f, 'UIPanelButtonTemplate, BackdropTemplate') + f.Option1:Size(160, 30) + f.Option1:Point('BOTTOM', 0, 45) + f.Option1:SetText('') + f.Option1:Hide() + S:HandleButton(f.Option1, true) + + f.Option2 = CreateFrame('Button', 'InstallOption2Button', f, 'UIPanelButtonTemplate, BackdropTemplate') + f.Option2:Size(110, 30) + f.Option2:Point('BOTTOMLEFT', f, 'BOTTOM', 4, 45) + f.Option2:SetText('') + f.Option2:Hide() + f.Option2:SetScript('OnShow', function() f.Option1:Width(110); f.Option1:ClearAllPoints(); f.Option1:Point('BOTTOMRIGHT', f, 'BOTTOM', -4, 45) end) + f.Option2:SetScript('OnHide', function() f.Option1:Width(160); f.Option1:ClearAllPoints(); f.Option1:Point('BOTTOM', 0, 45) end) + S:HandleButton(f.Option2, true) + + f.Option3 = CreateFrame('Button', 'InstallOption3Button', f, 'UIPanelButtonTemplate, BackdropTemplate') + f.Option3:Size(100, 30) + f.Option3:Point('LEFT', f.Option2, 'RIGHT', 4, 0) + f.Option3:SetText('') + f.Option3:Hide() + f.Option3:SetScript('OnShow', function() f.Option1:Width(100); f.Option1:ClearAllPoints(); f.Option1:Point('RIGHT', f.Option2, 'LEFT', -4, 0); f.Option2:Width(100); f.Option2:ClearAllPoints(); f.Option2:Point('BOTTOM', f, 'BOTTOM', 0, 45) end) + f.Option3:SetScript('OnHide', function() f.Option1:Width(160); f.Option1:ClearAllPoints(); f.Option1:Point('BOTTOM', 0, 45); f.Option2:Width(110); f.Option2:ClearAllPoints(); f.Option2:Point('BOTTOMLEFT', f, 'BOTTOM', 4, 45) end) + S:HandleButton(f.Option3, true) + + f.Option4 = CreateFrame('Button', 'InstallOption4Button', f, 'UIPanelButtonTemplate, BackdropTemplate') + f.Option4:Size(100, 30) + f.Option4:Point('LEFT', f.Option3, 'RIGHT', 4, 0) + f.Option4:SetText('') + f.Option4:Hide() + f.Option4:SetScript('OnShow', function() + f.Option1:Width(100) + f.Option2:Width(100) + f.Option1:ClearAllPoints() + f.Option1:Point('RIGHT', f.Option2, 'LEFT', -4, 0) + f.Option2:ClearAllPoints() + f.Option2:Point('BOTTOMRIGHT', f, 'BOTTOM', -4, 45) + end) + f.Option4:SetScript('OnHide', function() f.Option1:Width(160); f.Option1:ClearAllPoints(); f.Option1:Point('BOTTOM', 0, 45); f.Option2:Width(110); f.Option2:ClearAllPoints(); f.Option2:Point('BOTTOMLEFT', f, 'BOTTOM', 4, 45) end) + S:HandleButton(f.Option4, true) + + f.SubTitle = f:CreateFontString(nil, 'OVERLAY') + f.SubTitle:FontTemplate(nil, 15, nil) + f.SubTitle:Point('TOP', 0, -40) + + f.Desc1 = f:CreateFontString(nil, 'OVERLAY') + f.Desc1:FontTemplate() + f.Desc1:Point('TOPLEFT', 20, -75) + f.Desc1:Width(f:GetWidth() - 40) + + f.Desc2 = f:CreateFontString(nil, 'OVERLAY') + f.Desc2:FontTemplate() + f.Desc2:Point('TOPLEFT', 20, -125) + f.Desc2:Width(f:GetWidth() - 40) + + f.Desc3 = f:CreateFontString(nil, 'OVERLAY') + f.Desc3:FontTemplate() + f.Desc3:Point('TOPLEFT', 20, -175) + f.Desc3:Width(f:GetWidth() - 40) + + local close = CreateFrame('Button', 'InstallCloseButton', f, 'UIPanelCloseButton, BackdropTemplate') + close:Point('TOPRIGHT', f, 'TOPRIGHT') + close:SetScript('OnClick', function() f:Hide() end) + S:HandleCloseButton(close) + + local logo = f:CreateTexture('InstallTutorialImage', 'OVERLAY') + logo:Size(256, 128) + logo:SetTexture(E.Media.Textures.LogoTop) + logo:Point('BOTTOM', 0, 70) + f.tutorialImage = logo + + local logo2 = f:CreateTexture('InstallTutorialImage2', 'OVERLAY') + logo2:Size(256, 128) + logo2:SetTexture(E.Media.Textures.LogoBottom) + logo2:Point('BOTTOM', 0, 70) + f.tutorialImage2 = logo2 + end + + ElvUIInstallFrame.tutorialImage:SetVertexColor(unpack(E.media.rgbvaluecolor)) + ElvUIInstallFrame:Show() + E:NextPage() +end diff --git a/Core/ItemLevel.lua b/Core/ItemLevel.lua new file mode 100644 index 0000000..01aab6c --- /dev/null +++ b/Core/ItemLevel.lua @@ -0,0 +1,220 @@ +local E, L, V, P, G = unpack(select(2, ...)); --Import: Engine, Locales, PrivateDB, ProfileDB, GlobalDB + +local _G = _G +local tinsert, strfind, strmatch = tinsert, strfind, strmatch +local select, tonumber, format = select, tonumber, format +local next, max, wipe = next, max, wipe +local utf8sub = string.utf8sub + +local UnitIsUnit = UnitIsUnit +local GetCVarBool = GetCVarBool +local GetItemInfo = GetItemInfo +local GetAverageItemLevel = GetAverageItemLevel +local GetInventoryItemLink = GetInventoryItemLink +local GetInventoryItemTexture = GetInventoryItemTexture +local GetInspectSpecialization = GetInspectSpecialization +local RETRIEVING_ITEM_INFO = RETRIEVING_ITEM_INFO + +local ITEM_SPELL_TRIGGER_ONEQUIP = ITEM_SPELL_TRIGGER_ONEQUIP +local ESSENCE_DESCRIPTION = GetSpellDescription(277253) + +local MATCH_ITEM_LEVEL = ITEM_LEVEL:gsub('%%d', '(%%d+)') +local MATCH_ITEM_LEVEL_ALT = ITEM_LEVEL_ALT:gsub('%%d(%s?)%(%%d%)', '%%d+%1%%((%%d+)%%)') +local MATCH_ENCHANT = ENCHANTED_TOOLTIP_LINE:gsub('%%s', '(.+)') +local X2_INVTYPES, X2_EXCEPTIONS, ARMOR_SLOTS = { + INVTYPE_2HWEAPON = true, + INVTYPE_RANGEDRIGHT = true, + INVTYPE_RANGED = true, +}, { + [2] = 19, -- wands, use INVTYPE_RANGEDRIGHT, but are 1H +}, {1, 2, 3, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15} + +function E:InspectGearSlot(line, lineText, slotInfo) + local enchant = strmatch(lineText, MATCH_ENCHANT) + if enchant then + slotInfo.enchantText = enchant + slotInfo.enchantTextShort = utf8sub(enchant, 1, 18) + + local lr, lg, lb = line:GetTextColor() + slotInfo.enchantColors[1] = lr + slotInfo.enchantColors[2] = lg + slotInfo.enchantColors[3] = lb + end + + local itemLevel = lineText and (strmatch(lineText, MATCH_ITEM_LEVEL_ALT) or strmatch(lineText, MATCH_ITEM_LEVEL)) + if itemLevel then + slotInfo.iLvl = tonumber(itemLevel) + + local tr, tg, tb = _G.ElvUI_ScanTooltipTextLeft1:GetTextColor() + slotInfo.itemLevelColors[1] = tr + slotInfo.itemLevelColors[2] = tg + slotInfo.itemLevelColors[3] = tb + end +end + +function E:CollectEssenceInfo(index, lineText, slotInfo) + local step = 1 + local essence = slotInfo.essences[step] + if essence and next(essence) and (strfind(lineText, ITEM_SPELL_TRIGGER_ONEQUIP, nil, true) and strfind(lineText, ESSENCE_DESCRIPTION, nil, true)) then + for i = 5, 2, -1 do + local line = _G['ElvUI_ScanTooltipTextLeft'..index - i] + local text = line and line:GetText() + + if text and (not strmatch(text, '^[ +]')) and essence and next(essence) then + local r, g, b = line:GetTextColor() + + essence[4] = E:RGBToHex(r, g, b) + essence[5] = text + + step = step + 1 + essence = slotInfo.essences[step] + end + end + end +end + +function E:GetGearSlotInfo(unit, slot, deepScan) + local tt = E.ScanTooltip + tt:SetOwner(_G.UIParent, 'ANCHOR_NONE') + tt:SetInventoryItem(unit, slot) + tt:Show() + + if not tt.slotInfo then tt.slotInfo = {} else wipe(tt.slotInfo) end + local slotInfo = tt.slotInfo + + if deepScan then + slotInfo.gems, slotInfo.essences = E:ScanTooltipTextures() + + if not tt.enchantColors then tt.enchantColors = {} else wipe(tt.enchantColors) end + if not tt.itemLevelColors then tt.itemLevelColors = {} else wipe(tt.itemLevelColors) end + slotInfo.enchantColors = tt.enchantColors + slotInfo.itemLevelColors = tt.itemLevelColors + + for x = 1, tt:NumLines() do + local line = _G['ElvUI_ScanTooltipTextLeft'..x] + if line then + local lineText = line:GetText() + if x == 1 and lineText == RETRIEVING_ITEM_INFO then + return 'tooSoon' + else + E:InspectGearSlot(line, lineText, slotInfo) + E:CollectEssenceInfo(x, lineText, slotInfo) + end + end + end + else + local firstLine = _G.ElvUI_ScanTooltipTextLeft1:GetText() + if firstLine == RETRIEVING_ITEM_INFO then + return 'tooSoon' + end + + local colorblind = GetCVarBool('colorblindmode') and 4 or 3 + for x = 2, colorblind do + local line = _G['ElvUI_ScanTooltipTextLeft'..x] + if line then + local lineText = line:GetText() + local itemLevel = lineText and (strmatch(lineText, MATCH_ITEM_LEVEL_ALT) or strmatch(lineText, MATCH_ITEM_LEVEL)) + if itemLevel then + slotInfo.iLvl = tonumber(itemLevel) + end + end + end + end + + tt:Hide() + + return slotInfo +end + +--Credit ls & Acidweb +function E:CalculateAverageItemLevel(iLevelDB, unit) + local spec = GetInspectSpecialization(unit) + local isOK, total, link = true, 0 + + if not spec or spec == 0 then + isOK = false + end + + -- Armor + for _, id in next, ARMOR_SLOTS do + link = GetInventoryItemLink(unit, id) + if link then + local cur = iLevelDB[id] + if cur and cur > 0 then + total = total + cur + end + elseif GetInventoryItemTexture(unit, id) then + isOK = false + end + end + + -- Main hand + local mainItemLevel, mainQuality, mainEquipLoc, mainItemClass, mainItemSubClass, _ = 0 + link = GetInventoryItemLink(unit, 16) + if link then + mainItemLevel = iLevelDB[16] + _, _, mainQuality, _, _, _, _, _, mainEquipLoc, _, _, mainItemClass, mainItemSubClass = GetItemInfo(link) + elseif GetInventoryItemTexture(unit, 16) then + isOK = false + end + + -- Off hand + local offItemLevel, offEquipLoc = 0 + link = GetInventoryItemLink(unit, 17) + if link then + offItemLevel = iLevelDB[17] + _, _, _, _, _, _, _, _, offEquipLoc = GetItemInfo(link) + elseif GetInventoryItemTexture(unit, 17) then + isOK = false + end + + if mainItemLevel and offItemLevel then + if mainQuality == 6 or (not offEquipLoc and X2_INVTYPES[mainEquipLoc] and X2_EXCEPTIONS[mainItemClass] ~= mainItemSubClass and spec ~= 72) then + mainItemLevel = max(mainItemLevel, offItemLevel) + total = total + mainItemLevel * 2 + else + total = total + mainItemLevel + offItemLevel + end + end + + -- at the beginning of an arena match no info might be available, + -- so despite having equipped gear a person may appear naked + if total == 0 then + isOK = false + end + + return isOK and format('%0.2f', E:Round(total / 16, 2)) +end + +function E:GetPlayerItemLevel() + return format('%0.2f', E:Round((select(2, GetAverageItemLevel())), 2)) +end + +do + local iLevelDB, tryAgain = {}, {} + function E:GetUnitItemLevel(unit) + if UnitIsUnit('player', unit) then + return E:GetPlayerItemLevel() + end + + if next(iLevelDB) then wipe(iLevelDB) end + if next(tryAgain) then wipe(tryAgain) end + + for i = 1, 17 do + if i ~= 4 then + local slotInfo = E:GetGearSlotInfo(unit, i) + if slotInfo == 'tooSoon' then + tinsert(tryAgain, i) + else + iLevelDB[i] = slotInfo.iLvl + end + end + end + + if next(tryAgain) then + return 'tooSoon', unit, tryAgain, iLevelDB + end + + return E:CalculateAverageItemLevel(iLevelDB, unit) + end +end diff --git a/Core/Load_Core.xml b/Core/Load_Core.xml new file mode 100644 index 0000000..12a1fdf --- /dev/null +++ b/Core/Load_Core.xml @@ -0,0 +1,26 @@ + + \ No newline at end of file diff --git a/init.lua b/init.lua new file mode 100644 index 0000000..e3be9b2 --- /dev/null +++ b/init.lua @@ -0,0 +1,259 @@ +--[[ + ~AddOn Engine~ + To load the AddOn engine add this to the top of your file: + local E, L, V, P, G = unpack(select(2, ...)); --Import: Engine, Locales, PrivateDB, ProfileDB, GlobalDB + + To load the AddOn engine inside another addon add this to the top of your file: + local E, L, V, P, G = unpack(ElvUI); --Import: Engine, Locales, PrivateDB, ProfileDB, GlobalDB +]] + +local _G = _G +local unpack = unpack +local format, gsub, pairs, type = format, gsub, pairs, type + +local BAG_ITEM_QUALITY_COLORS = BAG_ITEM_QUALITY_COLORS +local CreateFrame = CreateFrame +local InCombatLockdown = InCombatLockdown +local GetAddOnEnableState = GetAddOnEnableState +local GetAddOnMetadata = GetAddOnMetadata +local GetLocale = GetLocale +local GetTime = GetTime +local HideUIPanel = HideUIPanel +local hooksecurefunc = hooksecurefunc +local IsAddOnLoaded = IsAddOnLoaded +local DisableAddOn = DisableAddOn +local ReloadUI = ReloadUI + +local GameMenuButtonAddons = GameMenuButtonAddons +local GameMenuButtonLogout = GameMenuButtonLogout +local GameMenuFrame = GameMenuFrame +-- GLOBALS: ElvCharacterDB, ElvPrivateDB, ElvDB, ElvCharacterData, ElvPrivateData, ElvData + +_G.BINDING_HEADER_ELVUI = GetAddOnMetadata(..., 'Title') + +local AceAddon, AceAddonMinor = _G.LibStub('AceAddon-3.0') +local CallbackHandler = _G.LibStub('CallbackHandler-1.0') + +local AddOnName, Engine = ... +local E = AceAddon:NewAddon(AddOnName, 'AceConsole-3.0', 'AceEvent-3.0', 'AceTimer-3.0', 'AceHook-3.0') +E.DF = {profile = {}, global = {}}; E.privateVars = {profile = {}} -- Defaults +E.Options = {type = 'group', args = {}, childGroups = 'ElvUI_HiddenTree'} +E.callbacks = E.callbacks or CallbackHandler:New(E) + +Engine[1] = E +Engine[2] = {} +Engine[3] = E.privateVars.profile +Engine[4] = E.DF.profile +Engine[5] = E.DF.global +_G.ElvUI = Engine + +E.oUF = Engine.oUF +E.ActionBars = E:NewModule('ActionBars','AceHook-3.0','AceEvent-3.0') +E.AFK = E:NewModule('AFK','AceEvent-3.0','AceTimer-3.0') +E.Auras = E:NewModule('Auras','AceHook-3.0','AceEvent-3.0') +E.Bags = E:NewModule('Bags','AceHook-3.0','AceEvent-3.0','AceTimer-3.0') +E.Blizzard = E:NewModule('Blizzard','AceEvent-3.0','AceHook-3.0') +E.Chat = E:NewModule('Chat','AceTimer-3.0','AceHook-3.0','AceEvent-3.0') +E.DataBars = E:NewModule('DataBars','AceEvent-3.0') +E.DataTexts = E:NewModule('DataTexts','AceTimer-3.0','AceHook-3.0','AceEvent-3.0') +E.DebugTools = E:NewModule('DebugTools','AceEvent-3.0','AceHook-3.0') +E.Distributor = E:NewModule('Distributor','AceEvent-3.0','AceTimer-3.0','AceComm-3.0','AceSerializer-3.0') +E.Layout = E:NewModule('Layout','AceEvent-3.0') +E.Minimap = E:NewModule('Minimap','AceHook-3.0','AceEvent-3.0','AceTimer-3.0') +E.Misc = E:NewModule('Misc','AceEvent-3.0','AceTimer-3.0') +E.ModuleCopy = E:NewModule('ModuleCopy','AceEvent-3.0','AceTimer-3.0','AceComm-3.0','AceSerializer-3.0') +E.NamePlates = E:NewModule('NamePlates','AceHook-3.0','AceEvent-3.0','AceTimer-3.0') +E.PluginInstaller = E:NewModule('PluginInstaller') +E.RaidUtility = E:NewModule('RaidUtility','AceEvent-3.0') +E.Skins = E:NewModule('Skins','AceTimer-3.0','AceHook-3.0','AceEvent-3.0') +E.Tooltip = E:NewModule('Tooltip','AceTimer-3.0','AceHook-3.0','AceEvent-3.0') +E.TotemBar = E:NewModule('Totems','AceEvent-3.0') +E.UnitFrames = E:NewModule('UnitFrames','AceTimer-3.0','AceEvent-3.0','AceHook-3.0') +E.WorldMap = E:NewModule('WorldMap','AceHook-3.0','AceEvent-3.0','AceTimer-3.0') + +E.twoPixelsPlease = false -- changing this option is not supported! :P + +-- Item Qualitiy stuff - used by MerathilisUI +E.QualityColors = {} +local qualityColors = BAG_ITEM_QUALITY_COLORS +for index, value in pairs(qualityColors) do + E.QualityColors[index] = {r = value.r, g = value.g, b = value.b} +end +E.QualityColors[-1] = {r = 0, g = 0, b = 0} +E.QualityColors[Enum.ItemQuality.Poor] = {r = .61, g = .61, b = .61} +E.QualityColors[Enum.ItemQuality.Common] = {r = 0, g = 0, b = 0} + +do + local locale = GetLocale() + local convert = {enGB = 'enUS', esES = 'esMX', itIT = 'enUS'} + local gameLocale = convert[locale] or locale or 'enUS' + + function E:GetLocale() + return gameLocale + end +end + +do + E.Libs = {} + E.LibsMinor = {} + function E:AddLib(name, major, minor) + if not name then return end + + -- in this case: `major` is the lib table and `minor` is the minor version + if type(major) == 'table' and type(minor) == 'number' then + E.Libs[name], E.LibsMinor[name] = major, minor + else -- in this case: `major` is the lib name and `minor` is the silent switch + E.Libs[name], E.LibsMinor[name] = _G.LibStub(major, minor) + end + end + + E:AddLib('AceAddon', AceAddon, AceAddonMinor) + E:AddLib('AceDB', 'AceDB-3.0') + E:AddLib('EP', 'LibElvUIPlugin-1.0') + E:AddLib('LSM', 'LibSharedMedia-3.0') + E:AddLib('ACL', 'AceLocale-3.0-ElvUI') + E:AddLib('LAB', 'LibActionButton-1.0-ElvUI') + E:AddLib('LDB', 'LibDataBroker-1.1') + E:AddLib('DualSpec', 'LibDualSpec-1.0') + E:AddLib('SimpleSticky', 'LibSimpleSticky-1.0') + E:AddLib('SpellRange', 'SpellRange-1.0') + E:AddLib('ButtonGlow', 'LibButtonGlow-1.0', true) + E:AddLib('ItemSearch', 'LibItemSearch-1.2-ElvUI') + E:AddLib('Compress', 'LibCompress') + E:AddLib('Base64', 'LibBase64-1.0-ElvUI') + E:AddLib('Masque', 'Masque', true) + E:AddLib('Translit', 'LibTranslit-1.0') + -- added on ElvUI_OptionsUI load: AceGUI, AceConfig, AceConfigDialog, AceConfigRegistry, AceDBOptions + + -- backwards compatible for plugins + E.LSM = E.Libs.LSM + E.UnitFrames.LSM = E.Libs.LSM + E.Masque = E.Libs.Masque +end + +do + local a1,a2,a3 = '','([%(%)%.%%%+%-%*%?%[%^%$])','%%%1' + function E:EscapeString(s) return gsub(s,a2,a3) end + + local a4,a5,a6,a7 = '|c[fF][fF]%x%x%x%x%x%x','|r','|[TA].-|[ta]','^%s*' + function E:StripString(s) + return gsub(gsub(gsub(gsub(s,a4,a1),a5,a1),a6,a1),a7,a1) + end +end + +do + DisableAddOn('ElvUI_VisualAuraTimers') + DisableAddOn('ElvUI_ExtraActionBars') + DisableAddOn('ElvUI_CastBarOverlay') + DisableAddOn('ElvUI_EverySecondCounts') + DisableAddOn('ElvUI_AuraBarsMovers') + DisableAddOn('ElvUI_CustomTweaks') + DisableAddOn('ElvUI_DTBars2') + DisableAddOn('ElvUI_QuestXP') +end + +function E:OnEnable() + E:Initialize() +end + +function E:OnInitialize() + if not ElvCharacterDB then + ElvCharacterDB = {} + end + + ElvCharacterData = nil --Depreciated + ElvPrivateData = nil --Depreciated + ElvData = nil --Depreciated + + E.db = E:CopyTable({}, E.DF.profile) + E.global = E:CopyTable({}, E.DF.global) + E.private = E:CopyTable({}, E.privateVars.profile) + + if ElvDB then + if ElvDB.global then + E:CopyTable(E.global, ElvDB.global) + end + + local key = ElvDB.profileKeys and ElvDB.profileKeys[E.mynameRealm] + if key and ElvDB.profiles and ElvDB.profiles[key] then + E:CopyTable(E.db, ElvDB.profiles[key]) + end + end + + if ElvPrivateDB then + local key = ElvPrivateDB.profileKeys and ElvPrivateDB.profileKeys[E.mynameRealm] + if key and ElvPrivateDB.profiles and ElvPrivateDB.profiles[key] then + E:CopyTable(E.private, ElvPrivateDB.profiles[key]) + end + end + + E.ScanTooltip = CreateFrame('GameTooltip', 'ElvUI_ScanTooltip', _G.UIParent, 'GameTooltipTemplate') + E.PixelMode = E.twoPixelsPlease or E.private.general.pixelPerfect -- keep this over `UIScale` + E.Border = (E.PixelMode and not E.twoPixelsPlease) and 1 or 2 + E.Spacing = E.PixelMode and 0 or 1 + + E:UIScale(true) + E:UpdateMedia() + E:Contruct_StaticPopups() + E:InitializeInitialModules() + + if E.private.general.minimap.enable then + E.Minimap:SetGetMinimapShape() + _G.Minimap:SetMaskTexture(130937) -- interface/chatframe/chatframebackground.blp + else + _G.Minimap:SetMaskTexture(186178) -- textures/minimapmask.blp + end + + if GetAddOnEnableState(E.myname, 'Tukui') == 2 then + E:StaticPopup_Show('TUKUI_ELVUI_INCOMPATIBLE') + end + + local GameMenuButton = CreateFrame('Button', nil, GameMenuFrame, 'GameMenuButtonTemplate, BackdropTemplate') + GameMenuButton:SetScript('OnClick', function() + E:ToggleOptionsUI() --We already prevent it from opening in combat + if not InCombatLockdown() then + HideUIPanel(GameMenuFrame) + end + end) + GameMenuFrame[E.name] = GameMenuButton + + if not IsAddOnLoaded('ConsolePortUI_Menu') then -- #390 + GameMenuButton:Size(GameMenuButtonLogout:GetWidth(), GameMenuButtonLogout:GetHeight()) + GameMenuButton:Point('TOPLEFT', GameMenuButtonAddons, 'BOTTOMLEFT', 0, -1) + hooksecurefunc('GameMenuFrame_UpdateVisibleButtons', E.PositionGameMenuButton) + end + + E.loadedtime = GetTime() +end + +function E:PositionGameMenuButton() + GameMenuFrame.Header.Text:SetTextColor(unpack(E.media.rgbvaluecolor)) + GameMenuFrame:Height(GameMenuFrame:GetHeight() + GameMenuButtonLogout:GetHeight() - 4) + + local button = GameMenuFrame[E.name] + button:SetText(format('%s%s|r', E.media.hexvaluecolor, E.name)) + + local _, relTo, _, _, offY = GameMenuButtonLogout:GetPoint() + if relTo ~= button then + button:ClearAllPoints() + button:Point('TOPLEFT', relTo, 'BOTTOMLEFT', 0, -1) + GameMenuButtonLogout:ClearAllPoints() + GameMenuButtonLogout:Point('TOPLEFT', button, 'BOTTOMLEFT', 0, offY) + end +end + +function E:ResetProfile() + E:StaggeredUpdateAll() +end + +function E:OnProfileReset() + E:StaticPopup_Show('RESET_PROFILE_PROMPT') +end + +function E:ResetPrivateProfile() + ReloadUI() +end + +function E:OnPrivateProfileReset() + E:StaticPopup_Show('RESET_PRIVATE_PROFILE_PROMPT') +end