From 0f46cb487b81da67fb21e741db12c15a509acd90 Mon Sep 17 00:00:00 2001 From: Ray Date: Wed, 12 Aug 2026 03:30:28 +0300 Subject: [PATCH 1/7] beta8 --- Core/API.lua | 86 ++ Core/Animation.lua | 40 +- Core/Config.lua | 1192 +++++++++++++++++++++- Core/Toolkit.lua | 8 + Init.lua | 166 +-- Locales/enUS.lua | 3 + Locales/ruRU.lua | 45 + Media/SharedMedia.lua | 5 + Media/Textures/LogoBottom.tga | Bin 0 -> 64568 bytes Media/Textures/LogoBottomSmall.tga | Bin 0 -> 9112 bytes Media/Textures/LogoTop.tga | Bin 0 -> 18936 bytes Media/Textures/LogoTopSmall.tga | Bin 0 -> 2910 bytes Media/Textures/Resize2.tga | Bin 0 -> 1038 bytes Modules/ActionBars/ActionBars.lua | 9 +- Modules/Bags/Bags.lua | 130 ++- Modules/Blizzard/Blizzard.lua | 4 +- Modules/Blizzard/GuildBank.lua | 107 ++ Modules/Blizzard/Load_Blizzard.xml | 1 + Modules/DataBars/DataBars.lua | 226 +++- Modules/DataBars/Experience.lua | 50 +- Modules/DataBars/Honor_Sirus.lua | 35 +- Modules/DataBars/PetExperience.lua | 27 +- Modules/DataBars/Reputation.lua | 27 +- Modules/DataTexts/Agility.lua | 30 + Modules/DataTexts/Ammo.lua | 179 ++++ Modules/DataTexts/CallToArms.lua | 126 +++ Modules/DataTexts/CombatIndicator.lua | 26 + Modules/DataTexts/Currencies.lua | 56 + Modules/DataTexts/Date.lua | 36 + Modules/DataTexts/Defense.lua | 29 + Modules/DataTexts/Difficulty.lua | 79 ++ Modules/DataTexts/Dodge.lua | 29 + Modules/DataTexts/EnergyRegen.lua | 27 + Modules/DataTexts/EquipmentSets.lua | 95 ++ Modules/DataTexts/Experience.lua | 59 ++ Modules/DataTexts/HealPower.lua | 27 + Modules/DataTexts/Intellect.lua | 29 + Modules/DataTexts/ItemLevel.lua | 63 ++ Modules/DataTexts/Load_DataTexts.xml | 31 + Modules/DataTexts/Location.lua | 61 ++ Modules/DataTexts/Mail.lua | 48 + Modules/DataTexts/MicroBar.lua | 50 + Modules/DataTexts/MovementSpeed.lua | 55 + Modules/DataTexts/Parry.lua | 29 + Modules/DataTexts/PrimaryStat.lua | 52 + Modules/DataTexts/Quests.lua | 90 ++ Modules/DataTexts/Reputation.lua | 72 ++ Modules/DataTexts/Speed.lua | 44 + Modules/DataTexts/SpellHaste.lua | 28 + Modules/DataTexts/SpellHit.lua | 29 + Modules/DataTexts/Spirit.lua | 29 + Modules/DataTexts/Stamina.lua | 29 + Modules/DataTexts/Strength.lua | 29 + Modules/DataTexts/Volume.lua | 159 +++ Modules/Nameplates/Nameplates.lua | 66 ++ Modules/Skins/Addons/Ace3.lua | 971 +++++++++++------- Modules/Skins/Blizzard/CombatLog.lua | 50 + Modules/Skins/Blizzard/Debug.lua | 115 +++ Modules/Skins/Blizzard/LFD.lua | 2 +- Modules/Skins/Blizzard/Load_Blizzard.xml | 1 + Modules/Skins/Skins.lua | 84 ++ Settings/Profile.lua | 112 +- 62 files changed, 4709 insertions(+), 578 deletions(-) create mode 100644 Media/Textures/LogoBottom.tga create mode 100644 Media/Textures/LogoBottomSmall.tga create mode 100644 Media/Textures/LogoTop.tga create mode 100644 Media/Textures/LogoTopSmall.tga create mode 100644 Media/Textures/Resize2.tga create mode 100644 Modules/Blizzard/GuildBank.lua create mode 100644 Modules/DataTexts/Agility.lua create mode 100644 Modules/DataTexts/Ammo.lua create mode 100644 Modules/DataTexts/CallToArms.lua create mode 100644 Modules/DataTexts/CombatIndicator.lua create mode 100644 Modules/DataTexts/Currencies.lua create mode 100644 Modules/DataTexts/Date.lua create mode 100644 Modules/DataTexts/Defense.lua create mode 100644 Modules/DataTexts/Difficulty.lua create mode 100644 Modules/DataTexts/Dodge.lua create mode 100644 Modules/DataTexts/EnergyRegen.lua create mode 100644 Modules/DataTexts/EquipmentSets.lua create mode 100644 Modules/DataTexts/Experience.lua create mode 100644 Modules/DataTexts/HealPower.lua create mode 100644 Modules/DataTexts/Intellect.lua create mode 100644 Modules/DataTexts/ItemLevel.lua create mode 100644 Modules/DataTexts/Location.lua create mode 100644 Modules/DataTexts/Mail.lua create mode 100644 Modules/DataTexts/MicroBar.lua create mode 100644 Modules/DataTexts/MovementSpeed.lua create mode 100644 Modules/DataTexts/Parry.lua create mode 100644 Modules/DataTexts/PrimaryStat.lua create mode 100644 Modules/DataTexts/Quests.lua create mode 100644 Modules/DataTexts/Reputation.lua create mode 100644 Modules/DataTexts/Speed.lua create mode 100644 Modules/DataTexts/SpellHaste.lua create mode 100644 Modules/DataTexts/SpellHit.lua create mode 100644 Modules/DataTexts/Spirit.lua create mode 100644 Modules/DataTexts/Stamina.lua create mode 100644 Modules/DataTexts/Strength.lua create mode 100644 Modules/DataTexts/Volume.lua create mode 100644 Modules/Skins/Blizzard/CombatLog.lua diff --git a/Core/API.lua b/Core/API.lua index 7cfe475d7..6047bd503 100644 --- a/Core/API.lua +++ b/Core/API.lua @@ -15,8 +15,16 @@ local GetTalentTabInfo = GetTalentTabInfo local RequestBattlefieldScoreData = RequestBattlefieldScoreData local UnitGroupRolesAssigned = UnitGroupRolesAssigned local UnitHasVehicleUI = UnitHasVehicleUI +local UnitExists = UnitExists +local UnitGUID = UnitGUID +local UnitThreatSituation = UnitThreatSituation local IsInInstance = IsInInstance +local IsInGroup = IsInGroup +local IsInRaid = IsInRaid local IsSpellKnown = IsSpellKnown +local GetNumGroupMembers = GetNumGroupMembers +local GetNumSubgroupMembers = GetNumSubgroupMembers +local GetPartyAssignment = GetPartyAssignment local MAX_TALENT_TABS = MAX_TALENT_TABS local NONE = NONE @@ -109,6 +117,78 @@ function E:GetTalentSpecInfo(isInspect) return specIdx, specName, specIcon end +E.GroupRoles = {} +E.GroupUnitsByRole = { + TANK = {}, + HEALER = {}, + DAMAGER = {}, + NONE = {} +} + +function E:UnitTankedByGroup(unit) + for _, unitToken in next, E.GroupUnitsByRole.TANK do + if E:GetThreatSituation(unit, unitToken) == 3 then + return unitToken + end + end +end + +function E:GetThreatSituation(unit, feedbackUnit) + if not unit or not UnitExists(unit) then return end + + if feedbackUnit and feedbackUnit ~= unit and UnitExists(feedbackUnit) then + return UnitThreatSituation(feedbackUnit, unit) + else + return UnitThreatSituation(unit) + end +end + +function E:PARTY_MEMBERS_CHANGED() + local isInGroup = IsInGroup() + E.IsInGroup = isInGroup + + wipe(E.GroupRoles) + + for _, units in next, E.GroupUnitsByRole do + wipe(units) + end + + if E.IsInGroup then + for i = 1, GetNumSubgroupMembers() do + local unit = "party"..i + local guid = UnitGUID(unit) + local role = guid and ((GetPartyAssignment("MAINTANK", unit) and "TANK" or "NONE") or UnitGroupRolesAssigned(unit)) + if role then + E.GroupRoles[guid] = role + E.GroupUnitsByRole[role][guid] = unit + end + end + end +end + +function E:RAID_ROSTER_UPDATE() + local isInRaid = IsInRaid() + E.IsInGroup = isInRaid + + wipe(E.GroupRoles) + + for _, units in next, E.GroupUnitsByRole do + wipe(units) + end + + if E.IsInGroup then + for i = 1, GetNumGroupMembers() do + local unit = "raid"..i + local guid = UnitGUID(unit) + local role = guid and ((GetPartyAssignment("MAINTANK", unit) and "TANK" or "NONE") or UnitGroupRolesAssigned(unit)) + if role then + E.GroupRoles[guid] = role + E.GroupUnitsByRole[role][guid] = unit + end + end + end +end + function E:CheckRole(event) local talentTree = self:GetTalentSpecInfo() local role @@ -127,6 +207,8 @@ function E:CheckRole(event) if not role then role = "Melee" end + self.myrole = self:GetPlayerRole() + if self.Role ~= role then self.Role = role self.TalentTree = talentTree @@ -472,6 +554,10 @@ end function E:LoadAPI() self:RegisterEvent("PLAYER_LEVEL_UP") self:RegisterEvent("PLAYER_ENTERING_WORLD") + self:RegisterEvent("PARTY_MEMBERS_CHANGED") + self:RegisterEvent("RAID_ROSTER_UPDATE") + self:PARTY_MEMBERS_CHANGED() + self:RAID_ROSTER_UPDATE() self:RegisterEvent("SPELL_UPDATE_USABLE", "CheckRole") self:RegisterEvent("ACTIVE_TALENT_GROUP_CHANGED", "CheckRole") self:RegisterEvent("PLAYER_TALENT_UPDATE", "CheckRole") diff --git a/Core/Animation.lua b/Core/Animation.lua index 5e43656f0..84048e5c9 100644 --- a/Core/Animation.lua +++ b/Core/Animation.lua @@ -9,6 +9,12 @@ 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} +E.AnimElastic = { + function(anim) anim:Stop() anim.elastic[2]:Play() end, + function(anim) anim:Stop() if anim.loop then anim.elastic[1]:Play() end end, + function(anim) anim:Stop() anim.elastic[4]:Play() end, + function(anim) anim:Stop() if anim.loop then anim.elastic[3]:Play() end end +} function E:FlashLoopFinished(requested) if not requested then self:Play() end @@ -63,6 +69,22 @@ function E:SetUpAnimGroup(obj, Type, ...) shake.path[4]:SetOrder(4) shake.path[5]:SetOrder(5) shake.path[6]:SetOrder(6) + elseif Type == "Elastic" then + local width, height, duration, loop = ... + local elastic = _G.CreateAnimationGroup(obj) + obj.elastic = elastic + + for i = 1, 4 do + local anim = 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) + anim:SetScript("OnFinished", E.AnimElastic[i]) + anim.elastic = elastic + anim.loop = loop + + elastic[i] = anim + end else local x, y, duration, customName = ... if not customName then customName = "anim" end @@ -291,4 +313,20 @@ function E:UIFrameFadeRemoveFrame(frame) FADEFRAMES[frame] = nil end -end \ No newline at end of file +end + +function E:Elasticize(obj, width, height) + if not obj.elastic then + E:SetUpAnimGroup(obj, "Elastic", width or obj:GetWidth(), height or obj:GetHeight(), 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 diff --git a/Core/Config.lua b/Core/Config.lua index a0ea89348..0617d5a8c 100644 --- a/Core/Config.lua +++ b/Core/Config.lua @@ -6,6 +6,7 @@ local _G = _G local unpack = unpack local type, ipairs, tonumber = type, ipairs, tonumber local floor, select = floor, select +local max, min = max, min --WoW API / Variables local CreateFrame = CreateFrame local IsAddOnLoaded = IsAddOnLoaded @@ -14,6 +15,7 @@ local EditBox_ClearFocus = EditBox_ClearFocus local RESET = RESET local selectedValue, grid = "ALL" +local statusTextHooked = {} E.ConfigModeLayouts = { "ALL", @@ -506,4 +508,1192 @@ function E:CreateMoverPopup() end) S:HandleNextPrevButton(rightButton) rightButton:SetSize(22, 22) -end \ No newline at end of file +end--=================== ElvUI 7.x window chrome (ported from ElvUI-development) =================== +local hooksecurefunc = hooksecurefunc +local next, sort, gsub, wipe = next, sort, gsub, wipe +local strsplit, strmatch, strtrim, strlower = strsplit, strmatch, strtrim, strlower +local pairs, tinsert, tContains = pairs, tinsert, tContains +local min = min +local EditBox_HighlightText = EditBox_HighlightText +local EnableAddOn, GetAddOnInfo = EnableAddOn, GetAddOnInfo +local LoadAddOn = LoadAddOn +local GetMouseFocus = GetMouseFocus +local UIParent = UIParent + +if not E.ConfigTooltip then + E.ConfigTooltip = CreateFrame('GameTooltip', 'ElvUI_ConfigTooltip', UIParent, 'GameTooltipTemplate') +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_GetStatus(frame) + local status = frame and frame.obj and frame.obj.status + local selected = status and status.groups and status.groups.selected + + return status, selected +end + +function E:Config_UpdateSize(reset) + local frame = E:Config_GetWindow() + if not frame then return end + + local maxWidth, maxHeight = self.UIParent:GetSize() + if frame.SetResizeBounds then + frame:SetResizeBounds(800, 600, maxWidth-50, maxHeight-50) + else + frame:SetMinResize(800, 600) + frame:SetMaxResize(maxWidth-50, maxHeight-50) + end + + self.Libs.AceConfigDialog:SetDefaultSize('ElvUI', E:Config_GetDefaultSize()) + + local status = E:Config_GetStatus(frame) + 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 + if not (frame and frame.obj and frame.obj.status) then + frame = self and self.GetParent and self:GetParent() + end + + local status = frame and E:Config_GetStatus(frame) + if 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 + +function E:Config_ButtonOnEnter() + local name = self.info and self.info.name + if type(name) == 'function' then name = name() end + + if not self.desc and not name then return end + + local current = self:GetText() + E.ConfigTooltip:SetOwner(self, 'ANCHOR_TOPRIGHT', 0, 2) + + -- show the full name when the button label was truncated + if name and name ~= current then + E.ConfigTooltip:AddLine(E:StripString(name), 1, 1, 1, true) + end + + if self.desc then + E.ConfigTooltip:AddLine(self.desc, 1, 1, 1, true) + end + + E.ConfigTooltip:Show() +end + +function E:Config_ButtonOnLeave() + E.ConfigTooltip:Hide() +end + +function E:Config_RepositionOnEnter() + if self.highlight then + self.highlight:Show() + else + local r, g, b = unpack(E.media.rgbvaluecolor) + self.texture:SetVertexColor(r, g, b, 1) + end + + E.Config_ButtonOnEnter(self) +end + +function E:Config_RepositionOnLeave() + if self.highlight then + self.highlight:Hide() + else + self.texture:SetVertexColor(1, 1, 1, 0.8) + end + + E.Config_ButtonOnLeave() +end + +function E:Config_PreviousLocation(editbox) + local _, selected = E:Config_GetStatus(editbox.frame) + if selected ~= 'search' then + editbox.selected = selected or nil + end +end + +function E:Config_SearchUpdate(userInput) + if not userInput then return end + + local C = E.Config[1] + C:Search_ClearResults() + + local text = self:GetText() + if strmatch(text, '%S+') then + C.SearchText = strtrim(strlower(text)) + + C:Search_Config() + C:Search_AddResults() + + local ACD = E.Libs.AceConfigDialog + if ACD then + ACD:SelectGroup('ElvUI', 'search') -- trigger update + end + end +end + +function E:Config_SearchClear() + if not self.ClearFocus then + self = self:GetParent() + end + + local C = E.Config[1] + C:Search_ClearResults() + + local _, selected = E:Config_GetStatus(self.frame) + if selected == 'search' then + local ACD = E.Libs.AceConfigDialog + if ACD then + ACD:SelectGroup('ElvUI', self.selected or 'general') -- try to stay or swap back to general if it cant + end + end + + self:SetText("") + EditBox_ClearFocus(self) +end + +function E:Config_SearchFocusGained() + EditBox_HighlightText(self) + E:Config_PreviousLocation(self) +end + +function E:Config_SearchFocusLost() + EditBox_ClearFocus(self) +end + +function E:Config_SearchOnEvent() + local frame = self:HasFocus() and GetMouseFocus() + if frame and (frame ~= self and frame ~= self.clearButton) then + EditBox_ClearFocus(self) + end +end + +function E:Config_SliderOnMouseWheel(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 + +function E:Config_SliderOnValueChanged(value) + self:SetValue(value) + self.buttons:Point('TOPLEFT', 0, value * 30) +end + +function E:Config_TruncateButtonText(btn) + local fs = btn:GetFontString() + if not fs then return end + + -- Sirus client: the raw fontstring may have no font yet; ensure one is set + -- before measuring, otherwise SetText fails with "Font not set" + local _, fontHeight = fs:GetFont() + if not fontHeight and fs.FontTemplate then + fs:FontTemplate(nil, 12) + end + + fs:SetWordWrap(false) + fs:SetJustifyH('CENTER') + + local maxWidth = btn:GetWidth() and (btn:GetWidth() - 20) or 0 + if maxWidth <= 0 then return end + + local name = btn:GetText() or '' + if name:gsub('|c[fF][fF]%x%x%x%x%x%x',''):gsub('|r',''):gsub('%s','') == '' then return end + + fs:SetText(name) + + if fs:GetStringWidth() > maxWidth then + local cut = #name + local truncated = name + while cut > 0 and fs:GetStringWidth() > maxWidth do + cut = cut - 1 + truncated = name:sub(1, cut)..'...' + fs:SetText(truncated) + end + btn:SetText(truncated) + end +end + +function E:Config_SetButtonText(btn, noColor) + local name = btn.info.name + if type(name) == 'function' then name = name() end + + if noColor then + name = name:gsub('|c[fF][fF]%x%x%x%x%x%x',''):gsub('|r','') + end + + btn:SetText(name) +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) + btn:SetEnabled(not disabled) + + if not btn:GetFontString() then return end + + if disabled then + btn:GetFontString():SetTextColor(1, 1, 1) + E:Config_SetButtonText(btn, true) + + if btn.SetBackdropColor then + btn:SetBackdropColor(1, .82, 0, 0.4) + btn:SetBackdropBorderColor(1, .82, 0, 1) + end + else + btn:GetFontString():SetTextColor(1, .82, 0) + E:Config_SetButtonText(btn) + + if btn.SetBackdropColor then + local r1, g1, b1 = unpack(E.media.backdropcolor) + btn:SetBackdropColor(r1, g1, b1, 1) + + local r2, g2, b2 = unpack(E.media.bordercolor) + btn:SetBackdropBorderColor(r2, g2, b2, 1) + end + end +end + +function E:Config_UpdateSliderPosition(btn) + local left = btn and btn.frame and btn.frame.leftHolder + if left and left.slider then + E.Config_SliderOnValueChanged(left.slider, btn.sliderValue or 0) + end +end + +function E:Config_CreateFrame(info, frame, unskinned, frameType, ...) + local element = CreateFrame(frameType, ...) + element.frame = frame + element.desc = info.desc + element.info = info + + if frameType == 'Button' then + if not unskinned then + S:HandleButton(element) + end + + element:SetScript('OnClick', info.func) + + if element then + E:Config_SetButtonText(element) + E:Config_SetButtonColor(element, element.info.key == 'general') + element:HookScript('OnEnter', E.Config_ButtonOnEnter) + element:HookScript('OnLeave', E.Config_ButtonOnLeave) + + -- Sirus client: template buttons have no font on the raw fontstring + -- until the button machinery applies it; set one up front so the + -- width is measured correctly and truncation does not hit + -- "Font not set" (bottom buttons only; left buttons get theirs + -- in Config_HandleLeftButton) + if not info.key then + local btext = element:GetFontString() + if btext and btext.FontTemplate then + btext:FontTemplate() + end + end + + -- dev: width = text + 40, height 22. Bottom buttons (no info.key) + -- get a safety cap so russian labels never overlap the search box; + -- left buttons override the width later (Config_HandleLeftButton) + local width = element:GetTextWidth() + 40 + if not info.key then + width = min(width, 160) + end + element:Size(width, 22) + + if not info.key then + E:Config_TruncateButtonText(element) + end + end + elseif frameType == 'EditBox' then + element:FontTemplate() -- Define Font properties for the Editbox text field to show written characters + element:SetAutoFocus(false) + + S:HandleSearchBox(element, unskinned) + + element:HookScript('OnTextChanged', info.update) + element:SetScript('OnEscapePressed', info.clear) + element:SetScript('OnEditFocusLost', info.focusLost) + element:SetScript('OnEditFocusGained', info.focusGained) + element.clearButton:HookScript('OnClick', info.clear) + + element:Size(220, 22) + end + + return element +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 _, selected = E:Config_GetStatus(frame) + for _, btn in next, 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 next, 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.holder:Hide() + else + slider.thumb.holder:Show() + end +end + +function E:Config_SaveOldFramelevel(frame) + if not frame.oldFramelevel then + frame.oldFramelevel = frame:GetFrameLevel() + end +end + +function E:Config_RestoreOldFramelevel(frame) + if frame.oldFramelevel then + frame:SetFrameLevel(frame.oldFramelevel) + + frame.oldFramelevel = nil + 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 not position then return end + + frame:ClearAllPoints() + + for i = 1, #position do + frame:Point(unpack(position[i])) + end + + frame.oldPosition = nil +end + +function E:Config_HandleLeftButton(info, frame, unskinned, buttons, last, index) + local btn = E:Config_CreateFrame(info, frame, unskinned, 'Button', nil, buttons, 'UIPanelButtonTemplate') + + -- plugin groups (not part of core options) are visually nested + local submenu = (info.order or 0) >= 6 and not tContains(E.OriginalOptions, info.key) + + btn:Width(submenu and 164 or 176) + + if btn.GetFontString and btn:GetFontString() then + local fs = btn:GetFontString() + if fs.FontTemplate then + fs:FontTemplate(nil, 11) + end + end + + E:Config_TruncateButtonText(btn) + + if not last then + btn:Point('TOP', buttons, 'TOP', submenu and 11 or -1, 0) + elseif last.IsObjectType and last:IsObjectType('FontString') then + -- x offset 0: vertical chain only; the horizontal indent for plugins + -- comes from the "Plugins" section label, so rows never staircase + btn:Point('TOP', last, 'BOTTOM', 0, -4) + else + btn:Point('TOP', last, 'BOTTOM', 0, (last.separator and -6) or -4) + end + + buttons[index] = btn + + return btn +end + +function E: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 E:Config_StripNameColor(A3.name) < E:Config_StripNameColor(B3.name) + end + end + + return A1 < B1 + end +end + +function E:Config_CreateLeftButtons(frame, unskinned, options) + local opts = {} + -- plugin groups (not part of core options) go to the end of the list, visually nested + local pluginHeaderShown = false + for key, info in pairs(options) do + if not tContains(E.OriginalOptions, key) then + info.order = 100 + (info.order or 0) + 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 or order == 20) and order < opt[1] then + last = E:Config_CreateSeparatorLine(frame, last) + end + + -- first plugin group gets a "Plugins" heading (non-clickable) + if (opt[1] or 0) >= 100 and not pluginHeaderShown then + pluginHeaderShown = true + last = E:Config_CreateSectionLabel(frame, last, L["Plugins"]) + end + + order = opt[1] + + info.key = key + info.func = function() + local ACD = E.Libs.AceConfigDialog + if ACD then ACD:SelectGroup('ElvUI', key) end + end + + if key ~= 'search' then + last = E:Config_HandleLeftButton(info, frame, unskinned, buttons, last, index) + end + end +end + +function E:Config_CreateSectionLabel(frame, lastButton, text) + local label = frame.leftHolder.buttons:CreateFontString(nil, 'OVERLAY') + label:FontTemplate(nil, 11, 'OUTLINE') + label:SetTextColor(1, .82, 0) + label:SetText(text) + label:Point('TOPLEFT', lastButton, 'BOTTOMLEFT', 12, -10) + label:Width(164) + label:SetJustifyH('LEFT') + return label +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 + + E.ConfigTooltip:Hide() +end + +function E:Config_OpenWindow() + local ACD = E.Libs.AceConfigDialog + if ACD then ACD:Open('ElvUI') end + + E.ConfigTooltip:Hide() +end + +function E:Config_GetWindow() + local ACD = E.Libs.AceConfigDialog + local ConfigOpen = ACD and ACD.OpenFrames and ACD.OpenFrames.ElvUI + return ConfigOpen and ConfigOpen.frame +end + +local ConfigLogoTop +local function ConfigLogoUpdate(_, 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 +E.valueColorUpdateFuncs[ConfigLogoUpdate] = 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) + + local unskinned = not E.private.skins.ace3.enable + local statusParent = self.statusText and self.statusText.parent + if statusParent then + statusParent:Show() + + E:Config_RestoreOldPosition(statusParent) + + if unskinned then + E:Config_RestoreOldFramelevel(statusParent) + end + end + + if E.ShowPopup then + E:StaticPopup_Show('CONFIG_RL') + E.ShowPopup = nil + end + end +end + +function E:Config_ContentPlacement(frame, content, unskinned, statusShown) + content:ClearAllPoints() + content:Point('TOPLEFT', frame, 'TOPLEFT', unskinned and 13 or 7, -(frame.bottomHolder:GetHeight() + (unskinned and 46 or 41))) + content:Point('BOTTOMRIGHT', frame, 'BOTTOMRIGHT', -(unskinned and 18 or 8), (statusShown and (unskinned and 32 or 25)) or (unskinned and 12) or 2) +end + +function E:Config_SetStatusText(text) + if not ConfigLogoTop or not self.parent then return end + + local shown = text and text ~= '' + self.parent:SetShown(shown) + + E:Config_ContentPlacement(self.frame, self.content, not E.private.skins.ace3.enable, shown) +end + +-- Defensive: hide the AceConfigDialog treeframe for the ElvUI root group even +-- if the Ace3 skin hook did not run (the chrome renders its own left menu). +function E:Config_HideAceTree(frame) + if not (frame and frame.obj) then return end + + local function walk(widget) + if not widget then return end + if widget.treeframe and widget.userdata and widget.userdata.option + and widget.userdata.option.childGroups == 'ElvUI_HiddenTree' + and widget.treeframe:IsShown() then + widget.treeframe:Hide() + end + if widget.children then + for _, child in ipairs(widget.children) do + walk(child) + end + end + end + + walk(frame.obj) +end + +function E:Config_WindowOpened(frame) + E:Config_HideAceTree(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() + + local logoColor = E.media.rgbvaluecolor or {1, .82, 0} + frame.leftHolder.LogoTop:SetVertexColor(unpack(logoColor)) + frame.leftHolder.LogoBottom:SetVertexColor(unpack(logoColor)) + frame.leftHolder.LogoTop:Show() + frame.leftHolder.LogoBottom:Show() + ConfigLogoTop = frame.leftHolder.LogoTop + + -- decorative bounce-in; if the animation framework fails the logo + -- must still stay visible at its full size (pcall guards it) + pcall(E.Elasticize, E, frame.leftHolder.LogoTop, 128, 64) + pcall(E.Elasticize, E, frame.leftHolder.LogoBottom, 128, 64) + + local unskinned = not E.private.skins.ace3.enable + 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 content = frame.obj.content + E:Config_SaveOldPosition(content) + E:Config_ContentPlacement(frame, content, unskinned) + + local titlebg = frame.obj.titlebg + E:Config_SaveOldPosition(titlebg) + titlebg:ClearAllPoints() + titlebg:SetPoint('TOPLEFT', frame) + titlebg:SetPoint('TOPRIGHT', frame) + titlebg:SetTexture(nil) -- hide the default dialog header strip (the chrome renders its own top bar) + + local statusParent = frame.statusText and frame.statusText.parent + if statusParent then + if unskinned then -- this lets the arrow work properly without finding it + E:Config_SaveOldFramelevel(statusParent) + + statusParent:OffsetFrameLevel(-1) + end + + E:Config_SaveOldPosition(statusParent) + + statusParent:ClearAllPoints() + statusParent:Point('TOPLEFT', frame.leftHolder, 'BOTTOMRIGHT', unskinned and 11 or 1, unskinned and 38 or 22) + statusParent:Point('BOTTOMRIGHT', frame, -2, 2) + end + end +end + +function E:Config_CreateBottomButtons(frame, unskinned) + local L = E.Libs.ACL:GetLocale('ElvUI', E.global.general.locale or 'enUS') + local C = E.Config[1] + + local last, search + for index, info in ipairs({ + { + var = 'Install', + name = L["Install"], + desc = L["Run the installation process."], + func = function() + E:Install() + E:ToggleOptions() + end + }, + { + var = 'ShowStatusReport', + name = L["Status"], + desc = L["Shows a frame with needed info for support."], + func = function() + E:ShowStatusReport() + E:ToggleOptions() + E.StatusReportToggled = true + end + }, + { + var = 'ToggleAnchors', + name = L["Movers"], + desc = L["Unlock various elements of the UI to be repositioned."], + func = function() + E:ToggleMoveMode() + E.ConfigurationToggled = true + end + }, + { + editBox = 'InputBoxTemplate', + clear = E.Config_SearchClear, + update = E.Config_SearchUpdate, + focusLost = E.Config_SearchFocusLost, + focusGained = E.Config_SearchFocusGained, + event = E.Config_SearchOnEvent, + var = 'Search', + name = L["Search"] + }, + { + var = 'WhatsNew', + name = L["Whats New"], + hidden = function() + return C.SearchText ~= "" or next(C.SearchCache) + end, + func = function() + if search then + E:Config_PreviousLocation(search) + end + + C:Search_ClearResults() + C:Search_Config(nil, nil, nil, true) + C:Search_AddResults() + + local ACD = E.Libs.AceConfigDialog + if ACD then + ACD:SelectGroup('ElvUI', 'search') -- trigger update + end + end + }, + { + texture = true, + var = 'RepositionWindow', + name = L["Reposition Window"], + desc = L["Reset the size and position of this frame."], + func = function() E:Config_UpdateSize(true) end + } + }) do + local element + if info.var == 'RepositionWindow' then + element = E:Config_CreateFrame(info, frame, true, 'Button', nil, frame.bottomHolder) + element:Size(unskinned and 34 or 18) + + local texture = element:CreateTexture() + texture:SetTexture(unskinned and [[Interface\ChatFrame\UI-ChatIcon-Maximize-Up]] or E.Media.Textures.Resize2) + texture:SetAllPoints() + element.texture = texture + + if unskinned then + local highlight = element:CreateTexture() + highlight:SetTexture([[Interface\Buttons\UI-Common-MouseHilight]]) + highlight:SetBlendMode('ADD') + highlight:SetAllPoints() + highlight:Hide() + element.highlight = highlight + else + texture:SetVertexColor(1, 1, 1, 0.8) + end + + element:HookScript('OnEnter', E.Config_RepositionOnEnter) + element:HookScript('OnLeave', E.Config_RepositionOnLeave) + elseif info.editBox then + element = E:Config_CreateFrame(info, frame, unskinned, 'EditBox', nil, frame.bottomHolder, info.editbox) + else + element = E:Config_CreateFrame(info, frame, unskinned, 'Button', nil, frame.bottomHolder, 'UIPanelButtonTemplate') + end + + if not search and (info.var == 'Search') then + search = element + + search:RegisterEvent('CURSOR_UPDATE') + search:SetScript('OnEvent', info.event) + search:SetScript('OnMouseDown', info.event) + end + + local offset = unskinned and 14 or 10 + + if not last then + element:Point('BOTTOMLEFT', frame.bottomHolder, 'BOTTOMLEFT', unskinned and 24 or offset, offset) + elseif info.var == 'RepositionWindow' then + element:Point('TOPRIGHT', frame.topHolder, 'TOPRIGHT', -(unskinned and 46 or 32), -(unskinned and 4 or 2)) + elseif index == 4 then -- Search + element:Point('BOTTOMRIGHT', frame.bottomHolder, 'BOTTOMRIGHT', -(unskinned and 24 or offset), offset) + elseif index > 4 then + element:Point('RIGHT', last, 'LEFT', -(index == 5 and (unskinned and 16 or 20) or (unskinned and 6 or 12)), 0) + else + element:Point('LEFT', last, 'RIGHT', unskinned and 6 or 12, 0) + end + + last = element + + frame.bottomHolder[info.var] = element + 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:ToggleOptions(msg) + if InCombatLockdown() and E.db.general.showWhenInCombat == false then + E:Print(ERR_NOT_IN_COMBAT) + E:RegisterEvent("PLAYER_REGEN_ENABLED") + return + end + + if not IsAddOnLoaded('ElvUI_OptionsUI') then + local noConfig + local _, _, _, _, reason = GetAddOnInfo("ElvUI_OptionsUI") + if reason ~= "MISSING" and reason ~= "DISABLED" then + E.GUIFrame = false + LoadAddOn("ElvUI_OptionsUI") + + --For some reason, GetAddOnInfo reason is "DEMAND_LOADED" even if the addon is disabled. + --Workaround: Try to load addon and check if it is loaded right after. + if not IsAddOnLoaded("ElvUI_OptionsUI") then noConfig = true end + + -- version check elvui options if it's actually enabled + if (not noConfig) and GetAddOnMetadata("ElvUI_OptionsUI", "Version") ~= "1.35" then + E:StaticPopup_Show("CLIENT_UPDATE_REQUEST") + end + else + noConfig = true + end + + if noConfig then + E:Print("|cffff0000Error -- Addon 'ElvUI_OptionsUI' не найден или выключен.|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(ACD, 'Open', E.Config_DialogOpened) + ACD.OpenHookedElvUI = true + end + + ACD[mode](ACD, 'ElvUI') + 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(ACR, 'NotifyChange', E.Config_UpdateLeftButtons) + ACR.NotifyHookedElvUI = true + E:Config_UpdateSize() + end + + local unskinned = not E.private.skins.ace3.enable + if not frame.bottomHolder then -- window was released or never opened + frame:HookScript('OnHide', E.Config_WindowClosed) + + for _, child in next, { frame:GetChildren() } do + local button = child:IsObjectType('Button') + if button and child:GetText() == _G.CLOSE then + frame.originalClose = child + child:Hide() + elseif button or child:IsObjectType('Frame') then + if unskinned and child.GetBackdrop then + local info = child:GetBackdrop() + if info and info.edgeFile == [[Interface\Tooltips\UI-Tooltip-Border]] then + child:SetBackdrop(nil) + end + end + + local point = not unskinned and not frame.resizeArrow and child:GetPoint() + if point == 'BOTTOMRIGHT' then + for _, region in next, { child:GetRegions() } do + local texture = region:IsObjectType('Texture') and region:GetTexture() + if texture == [[Interface\Tooltips\UI-Tooltip-Border]] then + if not child.resizeTexture then + region:SetTexture(E.Media.Textures.ArrowUp) + region:SetTexCoord(0, 1, 0, 1) + region:SetRotation(-2.35) + region:SetAllPoints() + + child.resizeTexture = region + elseif texture then -- this is the smaller texture, we don"t need it + region:SetAlpha(0) + end + end + end + + child:Size(24) + child:Point('BOTTOMRIGHT', 1, -1) + child:SetFrameLevel(200) + + frame.resizeArrow = child + end + + if child:HasScript('OnMouseUp') then + child:HookScript('OnMouseUp', E.Config_StopMoving) + end + end + end + + local close = CreateFrame('Button', nil, frame, 'UIPanelCloseButton') + close:SetScript('OnClick', E.Config_CloseClicked) + close:SetFrameLevel(1000) + close:Point('TOPRIGHT', unskinned and -12 or 1, unskinned and -12 or 2) + close:Size(unskinned and 30 or 32) + close.originalClose = frame.originalClose + frame.closeButton = close + + local statusText = frame.obj.statustext + if statusText then + frame.statusText = statusText + + statusText.parent = statusText:GetParent() + statusText.content = frame.obj.content + statusText.frame = frame + + if not statusTextHooked[statusText] then + statusTextHooked[statusText] = true + + hooksecurefunc(statusText, 'SetText', E.Config_SetStatusText) + end + end + + local left = CreateFrame('Frame', nil, frame) + left:Point('TOPLEFT', unskinned and 10 or 2, unskinned and -6 or -2) + left:Point('BOTTOMRIGHT', frame, 'BOTTOMLEFT', 182, 2) + frame.leftHolder = left + + local top = CreateFrame('Frame', nil, frame) + 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 bottom = CreateFrame('Frame', nil, frame) + bottom:Point('TOPLEFT', top, 'BOTTOMLEFT', unskinned and -15 or 0, -(unskinned and 15 or 1)) + bottom:Point('TOPRIGHT', top, 'BOTTOMRIGHT', unskinned and 10 or 0, -(unskinned and 15 or 1)) + bottom:Height(37) + frame.bottomHolder = bottom + + local LogoBottom = left:CreateTexture() + LogoBottom:SetTexture(E.Media.Textures.LogoBottomSmall or E.Media.Textures.Logo) + 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 or E.Media.Textures.Logo) + 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('TOPLEFT', unskinned and 4 or 1, -70) + buttonsHolder:Point('BOTTOMRIGHT', unskinned and 6 or 1, unskinned and 10 or 0) + left.buttonsHolder = buttonsHolder + + local buttonsScrollFrame = CreateFrame('ScrollFrame', nil, buttonsHolder) -- SetClipsChildren does not exist in 3.3.5, lets do this instead + buttonsScrollFrame:SetAllPoints(buttonsHolder) + left.buttonsScrollFrame = buttonsScrollFrame + + local buttonsScrollFrameChild = CreateFrame('Frame', nil, buttonsScrollFrame) + buttonsScrollFrame:SetScrollChild(buttonsScrollFrameChild) + buttonsScrollFrameChild:SetAllPoints(buttonsScrollFrame) + left.buttonsScrollFrameChild = buttonsScrollFrameChild + + local buttons = CreateFrame('Frame', nil, buttonsScrollFrameChild) + buttons:Point('BOTTOMRIGHT') + buttons:Point('TOPLEFT', 0, 0) + left.buttons = buttons + + local slider = CreateFrame('Slider', nil, frame) + slider:SetThumbTexture(E.Media.Textures.White8x8) + slider:EnableMouseWheel(true) + slider:SetScript('OnMouseWheel', E.Config_SliderOnMouseWheel) + slider:SetScript('OnValueChanged', E.Config_SliderOnValueChanged) + slider:SetOrientation('VERTICAL') + slider:SetValueStep(1) + slider:SetValue(0) + slider:Width(192) + slider:Point('TOPLEFT', buttons, 'TOPLEFT', 0, 0) + slider:Point('BOTTOMRIGHT', left, 'BOTTOMRIGHT', unskinned and 3 or 0, unskinned and 10 or 1) + slider.buttons = buttons + left.slider = slider + + local thumb = slider:GetThumbTexture() + thumb:Point('LEFT', left, 'RIGHT', unskinned and 6 or 2, 0) + thumb:Size(8, 12) + thumb:SetAlpha(0) -- hide this one, its under the unskinned buttons + left.slider.thumb = thumb + + local thumbHolder = CreateFrame('Frame', nil, left) + thumbHolder:SetFrameLevel(200) + thumbHolder:SetAllPoints(thumb) + thumb.holder = thumbHolder + + local thumbTexture = thumbHolder:CreateTexture() + thumbTexture:SetTexture(E.media.blankTex) + thumbTexture:SetDrawLayer('OVERLAY') + thumbTexture:SetVertexColor(1, 1, 1, 0.5) + thumbTexture:SetAllPoints() + thumbHolder.texture = thumbTexture + + if not unskinned then + local statusParent = statusText and statusText.parent + if statusParent then + statusParent:Hide() + statusParent:SetTemplate('Transparent') + end + + bottom:SetTemplate('Transparent') + left:SetTemplate('Transparent') + top:SetTemplate('Transparent') + + S:HandleCloseButton(close) + else + for _, region in next, { frame:GetRegions() } do + local texture = region:IsObjectType('Texture') and region:GetTexture() + if texture == [[Interface\DialogFrame\UI-DialogBox-Header]] then + region:SetAlpha(0) + end + end + 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('ElvUI', unpack(pages)) + end + + if not E.GUIFrame then + E.GUIFrame = frame + ElvUIGUIFrame = E.GUIFrame + hooksecurefunc(frame, 'StopMovingOrSizing', E.Config_StopMoving) + end + end + + GameTooltip:Hide() --Just in case you're mouseovered something and it closes. +end + +-- aliases so existing Sirus callers (PixelPerfect, OptionsUI Core, commands) keep working +E.ToggleOptionsUI = E.ToggleOptions +E.UpdateConfigSize = E.Config_UpdateSize +E.GetConfigDefaultSize = E.Config_GetDefaultSize +E.GetConfigSize = E.Config_GetSize +E.GetConfigPosition = E.Config_GetPosition +E.ResetConfigSettings = E.Config_ResetSettings +E.ConfigStopMovingOrSizing = E.Config_StopMoving diff --git a/Core/Toolkit.lua b/Core/Toolkit.lua index 1b08a15a0..c40199c8e 100644 --- a/Core/Toolkit.lua +++ b/Core/Toolkit.lua @@ -344,6 +344,13 @@ local function GetNamedChild(frame, childName, index) return _G[name..childName..(index or "")] end +local function OffsetFrameLevel(frame, offset, secondary) + if not secondary then secondary = frame end + + local level = secondary:GetFrameLevel() + frame:SetFrameLevel(level + (offset or 0)) +end + local function addapi(object) local mt = getmetatable(object).__index if not object.Size then mt.Size = Size end @@ -361,6 +368,7 @@ local function addapi(object) if not object.StyleButton then mt.StyleButton = StyleButton end if not object.CreateCloseButton then mt.CreateCloseButton = CreateCloseButton end if not object.GetNamedChild then mt.GetNamedChild = GetNamedChild end + if not object.OffsetFrameLevel then mt.OffsetFrameLevel = OffsetFrameLevel end end local handled = {["Frame"] = true} diff --git a/Init.lua b/Init.lua index 4777bbccf..18cc013af 100644 --- a/Init.lua +++ b/Init.lua @@ -33,7 +33,7 @@ local AddOnName, Engine = ... local AddOn = AceAddon:NewAddon(AddOnName, "AceConsole-3.0", "AceEvent-3.0", "AceTimer-3.0", "AceHook-3.0") AddOn.callbacks = AddOn.callbacks or CallbackHandler:New(AddOn) AddOn.DF = {profile = {}, global = {}}; AddOn.privateVars = {profile = {}} -- Defaults -AddOn.Options = {type = "group", name = AddOnName, args = {}} +AddOn.Options = {type = "group", name = AddOnName, args = {}, childGroups = 'ElvUI_HiddenTree'} Engine[1] = AddOn Engine[2] = {} @@ -290,167 +290,3 @@ end function AddOn:OnProfileReset() AddOn:StaticPopup_Show("RESET_PROFILE_PROMPT") end - -function AddOn:ResetConfigSettings() - AddOn.configSavedPositionTop, AddOn.configSavedPositionLeft = nil, nil - AddOn.global.general.AceGUI = AddOn:CopyTable({}, AddOn.DF.global.general.AceGUI) -end - -function AddOn:GetConfigPosition() - return AddOn.configSavedPositionTop, AddOn.configSavedPositionLeft -end - -function AddOn:GetConfigSize() - return AddOn.global.general.AceGUI.width, AddOn.global.general.AceGUI.height -end - -function AddOn:UpdateConfigSize(reset) - local frame = self.GUIFrame - if not frame then return end - - local maxWidth, maxHeight = self.UIParent:GetSize() - frame:SetMinResize(600, 500) - frame:SetMaxResize(maxWidth-50, maxHeight-50) - - self.Libs.AceConfigDialog:SetDefaultSize(AddOnName, self:GetConfigDefaultSize()) - - local status = frame.obj and frame.obj.status - if status then - if reset then - self:ResetConfigSettings() - - status.top, status.left = self:GetConfigPosition() - status.width, status.height = self:GetConfigDefaultSize() - - frame.obj:ApplyStatus() - else - local top, left = self:GetConfigPosition() - if top and left then - status.top, status.left = top, left - - frame.obj:ApplyStatus() - end - end - end -end - -function AddOn:GetConfigDefaultSize() - local width, height = AddOn:GetConfigSize() - local maxWidth, maxHeight = AddOn.UIParent:GetSize() - width, height = min(maxWidth - 50, width), min(maxHeight - 50, height) - return width, height -end - -function AddOn:ConfigStopMovingOrSizing() - if self.obj and self.obj.status then - AddOn.configSavedPositionTop, AddOn.configSavedPositionLeft = AddOn:Round(self:GetTop(), 2), AddOn:Round(self:GetLeft(), 2) - AddOn.global.general.AceGUI.width, AddOn.global.general.AceGUI.height = AddOn:Round(self:GetWidth(), 2), AddOn:Round(self:GetHeight(), 2) - end -end - -local pageNodes = {} --- local E, L, V, P, G = unpack(select(2, ...)); --Import: Engine, Locales, PrivateDB, ProfileDB, GlobalDB -function AddOn:ToggleOptionsUI(msg) - if InCombatLockdown() and AddOn.db.general.showWhenInCombat == false then - self:Print(ERR_NOT_IN_COMBAT) - self:RegisterEvent("PLAYER_REGEN_ENABLED") - return - end - - if not IsAddOnLoaded("ElvUI_OptionsUI") then - local noConfig - local _, _, _, _, reason = GetAddOnInfo("ElvUI_OptionsUI") - if reason ~= "MISSING" and reason ~= "DISABLED" then - self.GUIFrame = false - LoadAddOn("ElvUI_OptionsUI") - - --For some reason, GetAddOnInfo reason is "DEMAND_LOADED" even if the addon is disabled. - --Workaround: Try to load addon and check if it is loaded right after. - if not IsAddOnLoaded("ElvUI_OptionsUI") then noConfig = true end - - -- version check elvui options if it's actually enabled - if (not noConfig) and GetAddOnMetadata("ElvUI_OptionsUI", "Version") ~= "1.35" then - self:StaticPopup_Show("CLIENT_UPDATE_REQUEST") - end - else - noConfig = true - end - - if noConfig then - self:Print("|cffff0000Error -- Addon 'ElvUI_OptionsUI' не найден или выключен.|r") - return - end - end - - local ACD = self.Libs.AceConfigDialog - local ConfigOpen = ACD and ACD.OpenFrames and ACD.OpenFrames[AddOnName] - - local pages, msgStr - if msg and msg ~= "" then - pages = {strsplit(",", msg)} - msgStr = gsub(msg, ",","\001") - end - - local mode = "Close" - if not ConfigOpen or (pages ~= nil) then - if pages ~= nil then - 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 ("^"..self: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..self: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 ConfigOpen and ((not index and mainSel and mainSel == msg) or (index and pageNodes and pageNodes[index])) then - mode = "Close" - else - mode = "Open" - end - else - mode = "Open" - end - end - - if ACD then - ACD[mode](ACD, AddOnName) - end - - if mode == "Open" then - ConfigOpen = ACD and ACD.OpenFrames and ACD.OpenFrames[AddOnName] - if ConfigOpen then - local frame = ConfigOpen.frame - if frame and not self.GUIFrame then - self.GUIFrame = frame - ElvUIGUIFrame = self.GUIFrame - - self:UpdateConfigSize() - hooksecurefunc(frame, "StopMovingOrSizing", AddOn.ConfigStopMovingOrSizing) - end - end - - if ACD and pages then - ACD:SelectGroup(AddOnName, unpack(pages)) - end - end - - GameTooltip:Hide() --Just in case you're mouseovered something and it closes. -end diff --git a/Locales/enUS.lua b/Locales/enUS.lua index 2454bf38c..933f633c7 100644 --- a/Locales/enUS.lua +++ b/Locales/enUS.lua @@ -13,6 +13,9 @@ L["A raid marker feature is available by pressing Escape -> Keybinds scroll to t L["A setting you have changed will change an option for this character only. This setting that you have changed will be uneffected by changing user profiles. Changing this setting requires that you reload your User Interface."] = true L["ABOVE_THREAT_FORMAT"] = "%s: %.0f%% [%.0f%% above |cff%02x%02x%02x%s|r]" L["AFK"] = true +L["Current Target:"] = true +L["Threat Bar"] = true +L["Tank Colors"] = true L["Accepting this will reset the UnitFrame settings for %s. Are you sure?"] = true L["Accepting this will reset your Filter Priority lists for all auras on NamePlates. Are you sure?"] = true L["Accepting this will reset your Filter Priority lists for all auras on UnitFrames. Are you sure?"] = true diff --git a/Locales/ruRU.lua b/Locales/ruRU.lua index 0b024adbd..043748d03 100644 --- a/Locales/ruRU.lua +++ b/Locales/ruRU.lua @@ -327,6 +327,40 @@ L["silverabbrev"] = "|cffc7c7cfс|r" L["whispers"] = "шепчет" L["yells"] = "кричит" L["|cFFE30000Lua error recieved. You can view the error message when you exit combat."] = "|cFFE30000Обнаружена ошибка lua. Вы получите отчет о ней после завершения боя." +-- Ported from ElvUI-development +L["Ammo/Shard Counter"] = "Счетчик патронов/осколков" +L["Completed XP:"] = "Завершено опыта:" +L["Color the border of items by quality."] = "Окрашивать рамку предметов по качеству." +L["Current Difficulty"] = "Текущая сложность" +L["Display Item Level"] = "Показывать уровень предмета" +L["Displays item level on equippable items."] = "Показывает уровень предмета на надеваемых предметах." +L["Energy Regen"] = "Восст. энергии" +L["Equipment Sets"] = "Наборы экипировки" +L["Guild Bank"] = "Банк гильдии" +L["Heal Power"] = "Сила исцеления" +L["In Combat"] = "В бою" +L["Item Count"] = "Количество предметов" +L["Item Level Font"] = "Шрифт уровня предмета" +L["Item Level Position"] = "Позиция уровня предмета" +L["Item Level Threshold"] = "Порог уровня предмета" +L["Item Quality"] = "Качество предмета" +L["Max Level"] = "Макс. уровень" +L["Mov. Speed"] = "Скор. движения" +L["New Mail"] = "Новая почта" +L["No Mail"] = "Нет почты" +L["No Set Equipped"] = "Набор не надет" +L["Out of Combat"] = "Вне боя" +L["Position"] = "Позиция" +L["Primary Stat"] = "Основная характеристика" +L["Quest Log"] = "Список заданий" +L["Spell Haste"] = "Скорость заклинаний" +L["Spell Hit"] = "Меткость заклинаний" +L["The minimum item level required for it to be shown."] = "Минимальный уровень предмета для отображения." +L["Total Gold:"] = "Всего золота:" +L["Total XP:"] = "Всего опыта:" +L["X-Offset"] = "Смещение X" +L["Y-Offset"] = "Смещение Y" +L["iLvL"] = "Ур. предм." ---------------------------------- L["RED_ENABLE"] = "|cFFff3333Включить|r" @@ -469,6 +503,9 @@ L["Realm"] = "Область" L["Status"] = "Статус" L["Target"] = "Цель" L["Threat"] = "Угроза" +L["Current Target:"] = "Текущая цель:" +L["Threat Bar"] = "Полоса угрозы" +L["Tank Colors"] = "Цвета танка" L["Miscellaneous"] = "Разное" L["Collections"] = "Коллекции" L["EncounterJ"] = "Приключения" @@ -516,6 +553,14 @@ L["PowerCostDisplay"] = "Предсказание траты ресурса" L["PowerCostDisplaydesc"] = "Предсказание траты ресурса на панели ресурса, удар героя у воина, применение заклинаний и т.д" L["Zodiac:"] = "Зодиак:" L["Speed"] = "Скорость" +L["Custom StatusBar"] = "Своя текстура" +L["StatusBar Texture"] = "Текстура полосы" +L["Click Through"] = "Пропускать клики" +L["Frame Strata"] = "Слой фрейма" +L["Frame Level"] = "Уровень фрейма" +L["Display Text"] = "Показывать текст" +L["Transparent"] = "Прозрачный" +L["Automatic"] = "Автоматически" --~afkwords diff --git a/Media/SharedMedia.lua b/Media/SharedMedia.lua index 45f28580d..b9c2d2486 100644 --- a/Media/SharedMedia.lua +++ b/Media/SharedMedia.lua @@ -190,6 +190,11 @@ E.Media = { Leader = M..[[Textures\Leader.tga]], LevelUpTex = M..[[Textures\LevelUpTex.blp]], Logo = M..[[Textures\Logo.tga]], + LogoBottom = M..[[Textures\LogoBottom.tga]], + LogoBottomSmall = M..[[Textures\LogoBottomSmall.tga]], + LogoTop = M..[[Textures\LogoTop.tga]], + LogoTopSmall = M..[[Textures\LogoTopSmall.tga]], + Resize2 = M..[[Textures\Resize2.tga]], Mail = M..[[Textures\Mail.tga]], Melli = M..[[Textures\Melli.tga]], Minimalist = M..[[Textures\Minimalist.tga]], diff --git a/Media/Textures/LogoBottom.tga b/Media/Textures/LogoBottom.tga new file mode 100644 index 0000000000000000000000000000000000000000..f4454c233d8fbc75afb924d030730cf354060424 GIT binary patch literal 64568 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 literal 0 HcmV?d00001 diff --git a/Media/Textures/LogoBottomSmall.tga b/Media/Textures/LogoBottomSmall.tga new file mode 100644 index 0000000000000000000000000000000000000000..6c7a61b5bda009aa078e4419565388ad7f79ee61 GIT binary patch literal 9112 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 AXaE2J literal 0 HcmV?d00001 diff --git a/Media/Textures/LogoTop.tga b/Media/Textures/LogoTop.tga new file mode 100644 index 0000000000000000000000000000000000000000..6bfe33170e2996656604ea4112c6acc9f06b21c2 GIT binary patch literal 18936 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 y`Klq;zL*R-*Voq&nqw1gl literal 0 HcmV?d00001 diff --git a/Media/Textures/Resize2.tga b/Media/Textures/Resize2.tga new file mode 100644 index 0000000000000000000000000000000000000000..5180b5f5fdf5403138a5fedca25ac7aee4f69cc6 GIT binary patch literal 1038 zcmZQz;9`IQ1qL8gVEF(4Kf@f5SR;r<1h)gEcp^~o|3*}8_$5Hb|L=wAz^ZrXC~1O+ zK{GBN5vv5v)A-FBL5kb4q%d%pBEo= 1) and ((i - 1 <= numContainerSlots))) then if not f.ContainerHolder[i] then @@ -657,6 +659,20 @@ function B:Layout(isBank) end f.ContainerHolder[i].iconTexture:SetInside() f.ContainerHolder[i].iconTexture:SetTexCoord(unpack(E.TexCoords)) + + f.ContainerHolder[i].isBank = isBank + + if bagID ~= 0 and bagID ~= -1 then + f.ContainerHolder[i].shownIcon = f.ContainerHolder[i]:CreateTexture(nil, "OVERLAY", nil, 1) + f.ContainerHolder[i].shownIcon:Size(16) + f.ContainerHolder[i].shownIcon:Point("BOTTOMLEFT", 1, 1) + B:SetBagShownTexture(f.ContainerHolder[i].shownIcon, bagShown) + f.ContainerHolder[i]:HookScript("OnClick", function(holder) + if IsShiftKeyDown() and not CursorHasItem() then + B:ToggleContainer(holder) + end + end) + end end f.ContainerHolder:Size(((buttonSize + buttonSpacing) * (isBank and i - 1 or i)) + buttonSpacing, buttonSize + (buttonSpacing * 2)) @@ -679,7 +695,7 @@ function B:Layout(isBank) --Bag Slots local numSlots = GetContainerNumSlots(bagID) - if numSlots > 0 then + if numSlots > 0 and bagShown then if not f.Bags[bagID] then f.Bags[bagID] = CreateFrame("Frame", f:GetName().."Bag"..bagID, f.holderFrame) -- f.Bags[bagID]:SetBagID(bagID) @@ -1523,6 +1539,102 @@ function B:ToggleBags(id) end end +function B:SetBagShownTexture(icon, shown) + if not icon then return end + local texture = shown and (_G.READY_CHECK_READY_TEXTURE or READY_TEX) or (_G.READY_CHECK_NOT_READY_TEXTURE or NOT_READY_TEX) + icon:SetTexture(texture) +end + +function B:IsBagShown(bagID) + return bagID and B.db.shownBags["bag"..bagID] ~= false +end + +function B:SetBagShown(bagID, shown) + B.db.shownBags["bag"..bagID] = shown +end + +function B:ToggleContainer(holder) + if not holder then return end + + local swap = not B:IsBagShown(holder.id) + + B:SetBagShown(holder.id, swap) + B:SetBagShownTexture(holder.shownIcon, swap) + + if B:AnyBagsShown() then + B:Layout(holder.isBank) + return true + else + B:CloseAllBags() + end +end + +function B:AnyBagsShown() + if not B.BagFrame then return true end + + for _, bagID in ipairs(B.BagFrame.BagIDs) do + if B.db.shownBags["bag"..bagID] ~= false then + return true + end + end +end + +function B:CloseAllBags() + if not B.BagFrame or not B.BagFrame:IsShown() then return false end + + B:CloseBags() + return true +end + +B.AutoToggleEvents = { + AUCTION_HOUSE_SHOW = "auctionHouse", + AUCTION_HOUSE_CLOSED = "auctionHouse", + TRADE_SKILL_SHOW = "professions", + TRADE_SKILL_CLOSE = "professions", + TRADE_SHOW = "trade", + TRADE_CLOSED = "trade" +} + +B.AutoToggleClose = { + AUCTION_HOUSE_CLOSED = true, + TRADE_SKILL_CLOSE = true, + TRADE_CLOSED = true +} + +function B:HandleOpenAllBags(frame) + if not frame then return end + + local mail = frame == _G.MailFrame and frame:IsShown() + local vendor = frame == _G.MerchantFrame and frame:IsShown() + + if (not mail and not vendor) or (mail and B.db.autoToggle.mail) or (vendor and B.db.autoToggle.vendor) then + B:OpenBags() + else + B:CloseBags() + end +end + +function B:AutoToggleFunction() + local option = B.AutoToggleEvents[self] + if not option then return end + + if B.db.autoToggle[option] and not B.AutoToggleClose[self] then + B:OpenBags() + else + B:CloseBags() + end +end + +function B:SetupAutoToggle() + for event in next, B.AutoToggleEvents do + if B.db.autoToggle.enable then + B:RegisterEvent(event, B.AutoToggleFunction) + else + B:UnregisterEvent(event) + end + end +end + function B:ToggleBackpack() if IsOptionFrameOpen() then return end @@ -1576,7 +1688,9 @@ function B:OpenBank() --Call :Layout first so all elements are created before we update B:Layout(true) - B:OpenBags() + if not B.db.autoToggle or B.db.autoToggle.bank then + B:OpenBags() + end B:UpdateTokens() B.BankFrame:Show() @@ -1906,7 +2020,7 @@ function B:Initialize() B.BagFrame = B:ContructContainerFrame("ElvUI_ContainerFrame") --Hook onto Blizzard Functions - B:SecureHook("OpenAllBags", "ToggleBackpack") + B:SecureHook("OpenAllBags", "HandleOpenAllBags") B:SecureHook("CloseAllBags", "CloseBags") B:SecureHook("ToggleBag", "ToggleBags") B:SecureHook("OpenBackpack", "OpenBags") @@ -1924,6 +2038,16 @@ function B:Initialize() B:RegisterEvent("BANKFRAME_CLOSED", "CloseBank") B:RegisterEvent("PLAYERBANKBAGSLOTS_CHANGED") B:RegisterEvent("GUILDBANKBAGSLOTS_CHANGED") + B:SetupAutoToggle() + + local guildBankFrame = _G.GuildBankFrame + if guildBankFrame and B.db.autoToggle.guildBank then + guildBankFrame:HookScript("OnShow", function() + if guildBankFrame:IsShown() then + B:OpenBags() + end + end) + end end local function InitializeCallback() diff --git a/Modules/Blizzard/Blizzard.lua b/Modules/Blizzard/Blizzard.lua index 207bc4eb0..8c5e011f6 100644 --- a/Modules/Blizzard/Blizzard.lua +++ b/Modules/Blizzard/Blizzard.lua @@ -19,8 +19,8 @@ function B:ADDON_LOADED(_, addon) ChatFrameEditBox:Insert(GetTradeSkillListLink()) end) - - self:UnregisterEvent("ADDON_LOADED") + elseif addon == "Blizzard_GuildBankUI" then + self:ImproveGuildBank() end end diff --git a/Modules/Blizzard/GuildBank.lua b/Modules/Blizzard/GuildBank.lua new file mode 100644 index 000000000..57d3fd2af --- /dev/null +++ b/Modules/Blizzard/GuildBank.lua @@ -0,0 +1,107 @@ +local E, L, V, P, G = unpack(select(2, ...)) --Import: Engine, Locales, PrivateDB, ProfileDB, GlobalDB +local BL = E:GetModule("Blizzard") +local B = E:GetModule("Bags") +local LSM = E.Libs.LSM + +--Lua functions +local _G = _G +local unpack = unpack +--WoW API / Variables +local GetCurrentGuildBankTab = GetCurrentGuildBankTab +local GetGuildBankItemLink = GetGuildBankItemLink +local GetItemInfo = GetItemInfo +local GetItemQualityColor = GetItemQualityColor +local hooksecurefunc = hooksecurefunc + +local NUM_SLOTS_PER_GUILDBANK_GROUP = 14 +local NUM_GUILDBANK_COLUMNS = 7 + +local function IsItemEligibleForItemLevelDisplay(classID, subclassID, itemEquipLoc, rarity) + return (rarity and rarity > 1) + and (itemEquipLoc ~= nil and itemEquipLoc ~= "" and itemEquipLoc ~= "INVTYPE_AMMO" and itemEquipLoc ~= "INVTYPE_BAG" and itemEquipLoc ~= "INVTYPE_QUIVER" and itemEquipLoc ~= "INVTYPE_TABARD") + and (classID and subclassID) +end + +function BL:GuildBank_ItemLevel(button) + local db = E.db.general.guildBank + if not db then return end + + if not button.itemLevel then + button.itemLevel = button:CreateFontString(nil, "ARTWORK", nil, 1) + end + + button.itemLevel:ClearAllPoints() + button.itemLevel:Point(db.itemLevelPosition, db.itemLevelxOffset, db.itemLevelyOffset) + button.itemLevel:FontTemplate(LSM:Fetch("font", db.itemLevelFont), db.itemLevelFontSize, db.itemLevelFontOutline) + + local ilvl, r, g, b + local tab = db.itemLevel and GetCurrentGuildBankTab() + local itemlink = tab and GetGuildBankItemLink(tab, button:GetID()) + if itemlink then + local _, _, rarity, itemLevel, _, _, _, _, itemEquipLoc, _, _, classID, subclassID = GetItemInfo(itemlink) + if rarity and rarity > 1 then + r, g, b = GetItemQualityColor(rarity) + end + + if rarity and rarity > 1 and db.itemQuality then + button:SetBackdropBorderColor(r, g, b) + else + button:SetBackdropBorderColor(unpack(E.media.bordercolor)) + end + + local canShowItemLevel = IsItemEligibleForItemLevelDisplay(classID, subclassID, itemEquipLoc, rarity) + if canShowItemLevel and db.itemLevel then + local custom = db.itemLevelCustomColorEnable and db.itemLevelCustomColor + if custom then + r, g, b = custom.r, custom.g, custom.b + elseif rarity and rarity > 1 then -- we already do this above otherwise + r, g, b = GetItemQualityColor(rarity) + end + + ilvl = itemLevel + end + else + button:SetBackdropBorderColor(unpack(E.media.bordercolor)) + end + + button.itemLevel:SetText(ilvl and ilvl >= db.itemLevelThreshold and ilvl or "") + button.itemLevel:SetTextColor(r or 1, g or 1, b or 1) +end + +function BL:GuildBank_CountText(button) + local db = E.db.general.guildBank + if not db then return end + + button.Count = _G[button:GetName().."Count"] + button.Count:ClearAllPoints() + button.Count:Point(db.countPosition, db.countxOffset, db.countyOffset) + button.Count:FontTemplate(LSM:Fetch("font", db.countFont), db.countFontSize, db.countFontOutline) + button.Count:SetTextColor(db.countFontColor.r, db.countFontColor.g, db.countFontColor.b) +end + +function BL:GuildBank_Update() + local frame = _G.GuildBankFrame + if not frame or not frame:IsShown() then return end + + if frame.mode ~= "bank" then + frame.inset:Point("BOTTOMRIGHT", -29, 62) + return + else + frame.inset:Point("BOTTOMRIGHT", -8, 62) + + _G.GuildBankColumn1:Point("TOPLEFT", 20, -70) + end + + for i = 1, NUM_GUILDBANK_COLUMNS do + for x = 1, NUM_SLOTS_PER_GUILDBANK_GROUP do + local button = _G["GuildBankColumn"..i.."Button"..x] + + BL:GuildBank_ItemLevel(button) + BL:GuildBank_CountText(button) + end + end +end + +function BL:ImproveGuildBank() + hooksecurefunc("GuildBankFrame_Update", BL.GuildBank_Update) +end diff --git a/Modules/Blizzard/Load_Blizzard.xml b/Modules/Blizzard/Load_Blizzard.xml index 27ef0f806..5667e9e3f 100644 --- a/Modules/Blizzard/Load_Blizzard.xml +++ b/Modules/Blizzard/Load_Blizzard.xml @@ -9,4 +9,5 @@