From c8c4a803d3e494b385bc7700c0c0d675f876a2bf Mon Sep 17 00:00:00 2001 From: Adams Date: Mon, 6 Apr 2026 12:20:10 +0300 Subject: [PATCH 01/18] Add TBC Anniversary (20505) compatibility Port compatibility fixes originally implemented for the TBC Anniversary fork by u/Nihilistzsche (https://www.reddit.com/r/classicwow/comments/1qgduwp/). Adapted to run on top of the current 4.73 codebase rather than 4.62. Init.lua: - Restore legacy addon globals (IsAddOnLoaded, GetAddOnMetadata, etc.) from C_AddOns when they are nil - Anniversary client moved them behind C_AddOns but many libraries and skin files still reference the bare globals - Fix GetAddOnEnableState argument order: Classic API is (characterName, addon), opposite of Retail; treat any positive state as enabled to handle per-character quirks on Anniversary - Guard IsAddOnLoaded call with nil check before use - Initialize AS.ScreenWidth/Height, AS.UIScale, AS.Mult, AS.ClassColor which Skins.lua relies on Core/Core.lua: - Safe IsAddOnLoaded initialization with fallback stub in case it is nil - Improved CheckAddOn: prefer runtime loaded state over cached enable-state (Anniversary per-character enablement is unreliable); special-case ElvUI detection via global presence; fall back to live GetAddOnEnableState - Add AS:Scale() and AS:OrderedPairs() used by TBC Blizzard skin files - Fix CheckOption vararg iteration to avoid table allocation on each call - SkinDebug option wired into CallSkin alongside existing AS.Debug flag - UpdateMedia: try 'background' category for Solid texture before 'statusbar' - Use strfind instead of strmatch for Blizzard_ prefix check (plain find) - Init: use ADDON_LOADED addon argument as reliable load signal instead of IsAddOnLoaded(AddOnName) which can be nil on Anniversary - Resolve LibElvUIPlugin lazily in PLAYER_LOGIN so it is available even when ElvUI_Libraries loads after AddOnSkins Core/Options.lua: - Safe GetAddOnMetadata via C_AddOns fallback - Add SkinDebug toggle to options UI and profile defaults - Force AS:GetOptions() via C_Timer after RegisterPlugin to handle Anniversary where LibElvUIPlugin may not fire the callback if ElvUI_Options is already loaded Core/ElvUI.lua: - Lazy E initialization guard in UpdateMedia - Sync AS.BackdropColor/BorderColor/Color from ElvUI media so skins that reference AS-level fields pick up the correct ElvUI theme values --- AddOnSkins/Core/Core.lua | 78 ++++++++++++++++++++++++++++--------- AddOnSkins/Core/ElvUI.lua | 7 ++++ AddOnSkins/Core/Options.lua | 8 +++- AddOnSkins/Init.lua | 34 ++++++++++++++-- 4 files changed, 104 insertions(+), 23 deletions(-) diff --git a/AddOnSkins/Core/Core.lua b/AddOnSkins/Core/Core.lua index 2ffc3be8..127c0d73 100644 --- a/AddOnSkins/Core/Core.lua +++ b/AddOnSkins/Core/Core.lua @@ -6,18 +6,25 @@ local AddOnName = ... local ES = AS.EmbedSystem local _G = _G -local pairs, ipairs, type, pcall, tinsert = pairs, ipairs, type, pcall, tinsert -local floor, print, format, strlower, strmatch, strlen = floor, print, format, strlower, strmatch, strlen +local select, pairs, ipairs, type, pcall, tinsert = select, pairs, ipairs, type, pcall, tinsert +local floor, print, format, strlower, strfind, strmatch, strlen = floor, print, format, strlower, strfind, strmatch, strlen +local sort = sort local geterrorhandler = geterrorhandler -local IsAddOnLoaded, C_Timer = C_AddOns.IsAddOnLoaded, C_Timer +local C_AddOns = C_AddOns +local IsAddOnLoaded = (C_AddOns and C_AddOns.IsAddOnLoaded) or _G.IsAddOnLoaded +if not IsAddOnLoaded then + IsAddOnLoaded = function() return false end +end +local C_Timer = C_Timer AS.SkinErrors = {} local Validator = CreateFrame('Frame') function AS:CheckOption(optionName, ...) - for _, addon in next, {...} do + for i = 1, select('#', ...) do + local addon = select(i, ...) if not addon then break end if not AS:CheckAddOn(addon) then return false end end @@ -63,7 +70,18 @@ function AS:Delay(delay, func) end function AS:CheckAddOn(addon) - return AS.AddOns[strlower(addon)] or false + local key = strlower(addon or '') + -- Prefer runtime loaded state: enable-state APIs can be unreliable on Anniversary + -- (per-character enablement stored differently than on Retail). + local loaded = (C_AddOns and C_AddOns.IsAddOnLoaded and C_AddOns.IsAddOnLoaded(addon)) + or (_G.IsAddOnLoaded and _G.IsAddOnLoaded(addon)) + if loaded then return true end + -- ElvUI may not appear enabled in the addon list on Anniversary but its global is present. + if key == 'elvui' then return _G.ElvUI ~= nil end + if AS.AddOns[key] ~= nil then return AS.AddOns[key] end + local state = (C_AddOns and C_AddOns.GetAddOnEnableState and C_AddOns.GetAddOnEnableState(AS.MyName, addon)) + or (_G.GetAddOnEnableState and _G.GetAddOnEnableState(AS.MyName, addon)) + return (state or 0) > 0 end function AS:GetAddOnVersion(addon) @@ -83,6 +101,24 @@ function AS:Round(num, idp) return floor(num * mult + 0.5) / mult end +function AS:Scale(Number) + return AS.Mult * floor(Number / AS.Mult + .5) +end + +function AS:OrderedPairs(t, f) + local a = {} + for n in pairs(t) do tinsert(a, n) end + sort(a, f) + local i = 0 + local iter = function() + i = i + 1 + if a[i] == nil then return nil + else return a[i], t[a[i]] + end + end + return iter +end + function AS:RegisterForPetBattleHide(frame) RegisterStateDriver(frame, 'visibility', '[petbattle] hide; show') end @@ -159,7 +195,7 @@ local function errorhandler(err) end function AS:CallSkin(addonName, func, event, ...) - if AS.Debug then + if AS.Debug or AS:CheckOption('SkinDebug') then local args = {...} xpcall(function() func(self, event, unpack(args)) end, errorhandler) else @@ -196,7 +232,7 @@ function AS:UnregisterSkinEvent(addonName, event) end function AS:UpdateMedia() - AS.Blank = AS.Libs.LSM:Fetch('statusbar', 'Solid') + AS.Blank = AS.Libs.LSM:Fetch('background', 'Solid') or AS.Libs.LSM:Fetch('statusbar', 'Solid') AS.Font = AS.Libs.LSM:Fetch('font', "Friz Quadrata TT") AS.PixelFont = AS.Libs.LSM:Fetch('font', "Arial Narrow") AS.NormTex = AS.Libs.LSM:Fetch('statusbar', "Blizzard") @@ -233,7 +269,7 @@ function AS:StartUp(event, ...) -- Check Blizzard for already loaded for addonName, funcs in next, AS.skins do - if strmatch(addonName, '^Blizzard_') and AS:CheckOption(addonName) then + if strfind(addonName, '^Blizzard_') and AS:CheckOption(addonName) then for _, func in ipairs(funcs) do if IsAddOnLoaded(addonName) then AS:CallSkin(addonName, func, 'ADDON_LOADED', addonName) @@ -259,25 +295,31 @@ function AS:StartUp(event, ...) end function AS:Init(event, addon) - if event == 'ADDON_LOADED' and (AS.Initialized or IsAddOnLoaded(AddOnName)) then - if addon == AddOnName then - AS.Initialized = true - AS:BuildProfile() - AS:UpdateMedia() - - for addonName, funcs in next, AS.preload do - if AS.AlreadyLoaded[addonName] then - AS:RunPreload(addonName) - end + -- IsAddOnLoaded can be nil on Anniversary; use the addon argument as the reliable signal. + if event == 'ADDON_LOADED' and addon == AddOnName then + AS.Initialized = true + AS:BuildProfile() + AS:UpdateMedia() + + for addonName in next, AS.preload do + if AS.AlreadyLoaded[addonName] then + AS:RunPreload(addonName) end end + end + if event == 'ADDON_LOADED' and AS.Initialized then AS:RunPreload(addon) end if event == 'PLAYER_LOGIN' then AS:BuildOptions() + -- Resolve EP lazily: ElvUI_Libraries may load after AddOnSkins Init.lua runs. + if not AS.Libs.EP then + AS.Libs.EP = LibStub('LibElvUIPlugin-1.0', true) + end + for addOnEvent in pairs(AS.events) do AS:RegisterEvent(addOnEvent, 'SkinEvent') end diff --git a/AddOnSkins/Core/ElvUI.lua b/AddOnSkins/Core/ElvUI.lua index a0ba5b96..6ccb78f4 100644 --- a/AddOnSkins/Core/ElvUI.lua +++ b/AddOnSkins/Core/ElvUI.lua @@ -12,6 +12,8 @@ local ES = AS.EmbedSystem local E, L = unpack(ElvUI) function AS:UpdateMedia() + if not E then E = unpack(ElvUI) end + S.Media.Blank = AS.Libs.LSM:Fetch('background', 'ElvUI Blank') S.Media.StatusBar = AS.Libs.LSM:Fetch('statusbar', E.private.general.normTex) @@ -20,6 +22,11 @@ function AS:UpdateMedia() S.Media.borderColor = E.media.bordercolor S.Media.valueColor = E.media.rgbvaluecolor + -- Also update the AS-level fields used by skins that reference AS.BackdropColor etc. + AS.BackdropColor = E.media.backdropcolor + AS.BorderColor = E.media.bordercolor + AS.Color = E.media.rgbvaluecolor or AS.ClassColor + S.Media.TexCoords = { 0, 1, 0, 1 } local modifier = 0.04 * E.db.general.cropIcon for i, v in ipairs(S.Media.TexCoords) do diff --git a/AddOnSkins/Core/Options.lua b/AddOnSkins/Core/Options.lua index c7c60859..bb8859ee 100644 --- a/AddOnSkins/Core/Options.lua +++ b/AddOnSkins/Core/Options.lua @@ -12,7 +12,8 @@ local strlower = strlower local strtrim = strtrim local unpack = unpack -local GetAddOnMetadata = C_AddOns and C_AddOns.GetAddOnMetadata or GetAddOnMetadata +local C_AddOns = C_AddOns +local GetAddOnMetadata = (C_AddOns and C_AddOns.GetAddOnMetadata) or _G.GetAddOnMetadata local GENERAL = GENERAL local hooksecurefunc = hooksecurefunc local tContains = tContains @@ -109,6 +110,7 @@ AS.Options.args.general = ACH:Group(GENERAL, nil, 0, nil, function(info) return AS.Options.args.general.args.general = ACH:Group(' ', nil, 1) AS.Options.args.general.args.general.inline = true AS.Options.args.general.args.general.args.LoginMsg = ACH:Toggle(L["Login Message"], nil, 1) +AS.Options.args.general.args.general.args.SkinDebug = ACH:Toggle('Enable Skin Debugging', nil, 2) AS.Options.args.general.args.Theme = ACH:Select(L["Themes"], nil, 2, { PixelPerfect = L["Thin Border"], TwoPixel = L["Two Pixel"], ThickBorder = L["Thick Border"] }) AS.Options.args.general.args.SkinTemplate = ACH:Select(L["Template"], nil, 3, function() local tbl = CopyTable(DefaultTemplates) if AS:CheckOption('ElvUIStyle', 'ElvUI') then tbl.Custom = nil end return tbl end) @@ -223,6 +225,7 @@ function AS:BuildProfile() HideChatFrame = 'NONE', HighlightColor = { 1, .8, .1 }, LoginMsg = false, + SkinDebug = false, Parchment = false, SelectedColor = { 0, 0.44, .87 }, Shadows = true, @@ -288,6 +291,9 @@ function AS:BuildOptions() if AS.Libs.EP and AS:CheckAddOn('ElvUI') then AS.Libs.EP:RegisterPlugin('AddOnSkins', AS.GetOptions) + -- On Anniversary, LibElvUIPlugin may not fire the callback if ElvUI_Options + -- is already loaded by this point. Force one call to ensure hooks are set up. + C_Timer.After(0, function() pcall(AS.GetOptions, AS) end) else AS.Libs.AC:RegisterOptionsTable('AddOnSkins', AS.Options) AS.Libs.ACD:AddToBlizOptions('AddOnSkins', 'AddOnSkins') diff --git a/AddOnSkins/Init.lua b/AddOnSkins/Init.lua index d6aca94e..efdcec3d 100644 --- a/AddOnSkins/Init.lua +++ b/AddOnSkins/Init.lua @@ -1,7 +1,22 @@ local _G = _G -local format, strlower = format, strlower +local format, strlower, select = format, strlower, select + +local C_AddOns = C_AddOns + +-- TBC Anniversary: C_AddOns namespace exists but legacy globals may be nil. +-- Restore them so skin files and libraries that reference the globals directly work. +if not _G.IsAddOnLoaded and C_AddOns and C_AddOns.IsAddOnLoaded then _G.IsAddOnLoaded = C_AddOns.IsAddOnLoaded end +if not _G.GetAddOnMetadata and C_AddOns and C_AddOns.GetAddOnMetadata then _G.GetAddOnMetadata = C_AddOns.GetAddOnMetadata end +if not _G.GetAddOnInfo and C_AddOns and C_AddOns.GetAddOnInfo then _G.GetAddOnInfo = C_AddOns.GetAddOnInfo end +if not _G.GetNumAddOns and C_AddOns and C_AddOns.GetNumAddOns then _G.GetNumAddOns = C_AddOns.GetNumAddOns end +if not _G.GetAddOnEnableState and C_AddOns and C_AddOns.GetAddOnEnableState then _G.GetAddOnEnableState = C_AddOns.GetAddOnEnableState end + +local GetAddOnEnableState = (C_AddOns and C_AddOns.GetAddOnEnableState) or _G.GetAddOnEnableState +local GetAddOnInfo = (C_AddOns and C_AddOns.GetAddOnInfo) or _G.GetAddOnInfo +local GetAddOnMetadata = (C_AddOns and C_AddOns.GetAddOnMetadata) or _G.GetAddOnMetadata +local GetNumAddOns = (C_AddOns and C_AddOns.GetNumAddOns) or _G.GetNumAddOns +local IsAddOnLoaded = (C_AddOns and C_AddOns.IsAddOnLoaded) or _G.IsAddOnLoaded -local GetAddOnEnableState, GetAddOnInfo, GetAddOnMetadata, GetNumAddOns, IsAddOnLoaded = C_AddOns.GetAddOnEnableState, C_AddOns.GetAddOnInfo, C_AddOns.GetAddOnMetadata, C_AddOns.GetNumAddOns, C_AddOns.IsAddOnLoaded local UnitName, GetRealmName, UnitClass, UnitFactionGroup = UnitName, GetRealmName, UnitClass, UnitFactionGroup local UIParent, CreateFrame = UIParent, CreateFrame @@ -43,6 +58,15 @@ AS.Noop = function() end AS.TexCoords = { .08, .92, .08, .92 } AS.Faction = UnitFactionGroup('player') +local screenW, screenH = GetPhysicalScreenSize and GetPhysicalScreenSize() +AS.ScreenWidth = screenW or 1920 +AS.ScreenHeight = screenH or 1080 +AS.UIScale = UIParent:GetScale() +AS.Mult = 1 + +local classColor = _G.RAID_CLASS_COLORS[AS.MyClass] +AS.ClassColor = { classColor.r, classColor.g, classColor.b } + AS.preload = {} AS.skins = {} AS.events = {} @@ -55,8 +79,10 @@ AS.AlreadyLoaded = {} for i = 1, GetNumAddOns() do local Name, _, _, _, Reason = GetAddOnInfo(i) local LoweredName = strlower(Name) - AS.AddOns[LoweredName] = GetAddOnEnableState(Name, AS.MyName) == 2 and (not Reason or Reason ~= 'DEMAND_LOADED') - AS.AlreadyLoaded[Name] = IsAddOnLoaded(Name) + -- Classic API argument order is (characterName, addonName), opposite of Retail. + -- Treat any positive state as enabled to handle per-character quirks on Anniversary. + AS.AddOns[LoweredName] = (GetAddOnEnableState(AS.MyName, Name) or 0) > 0 and (not Reason or Reason ~= 'DEMAND_LOADED') + AS.AlreadyLoaded[Name] = IsAddOnLoaded and IsAddOnLoaded(Name) or false AS.AddOnVersion[LoweredName] = GetAddOnMetadata(Name, 'Version') end From 2df509766989277fefdff05c53ef2d6dfc88054c Mon Sep 17 00:00:00 2001 From: Adams Date: Mon, 6 Apr 2026 14:46:03 +0300 Subject: [PATCH 02/18] Fix existing skins for TBC Anniversary client Core/Skins.lua: - HandleCloseButton: fix guard that skipped SetTexture when button.Texture already existed as a parentKey from UIPanelCloseButton but was empty after StripTextures. Affects any addon using UIPanelCloseButton. Skins/AddOns/Atlas.lua: - Full rewrite for the TBC Anniversary Atlas fork (v3.x). Dropdowns use WowStyle1DropdownTemplate (DropdownButton frameType) whose NineSlice border pieces are child frames, not texture regions - hide them explicitly. Arrow is a Texture region; hover changes its vertex color and the frame border color. Boss/NPC buttons are created lazily on map refresh and are caught via a hook on Atlas_MapRefresh. Skins/AddOns/AtlasLoot.lua: - Full rewrite for the sliccer AtlasLootClassic fork used on TBC Anniversary. GUI is created lazily so skinning is deferred and hooks are used to catch first-open. Dropdown widgets use a custom BackdropTemplate Button with a nested arrow Button; hook SetBackdropColor to prevent AtlasLoot from resetting the backdrop on every selection. BiS footer uses SetScale(1.5) on checkboxes - reset to 1 before skinning and expand the footer height to fit. Skins/AddOns/RareScanner.lua: - Rewrite for the TBC Anniversary RareScanner structure. The original skin referenced non-existent frame names. Replace parchment NormalTexture and default tooltip border with ElvUI style. Filter buttons keep their custom icon textures; only the backdrop border is added. Skins/AddOns/Zygor.lua: - Rewrite for ZygorGuidesViewerClassicTBCAnniv. Zygor uses a fully custom widget system (SkinData / ApplySkin) so stripping internal frames breaks its own theming. Only the outer border color is overridden to match ElvUI, and standard Blizzard-template children (CloseButton, search backdrop) are skinned. --- AddOnSkins/Core/Skins.lua | 14 +- AddOnSkins/Skins/AddOns/Atlas.lua | 263 ++++++++++++--- AddOnSkins/Skins/AddOns/AtlasLoot.lua | 432 ++++++++++++++++++++---- AddOnSkins/Skins/AddOns/RareScanner.lua | 52 ++- AddOnSkins/Skins/AddOns/Zygor.lua | 94 +++++- 5 files changed, 709 insertions(+), 146 deletions(-) diff --git a/AddOnSkins/Core/Skins.lua b/AddOnSkins/Core/Skins.lua index 0616f4cc..c0b3a2fe 100644 --- a/AddOnSkins/Core/Skins.lua +++ b/AddOnSkins/Core/Skins.lua @@ -1488,14 +1488,18 @@ do function S:HandleCloseButton(button, point, x, y) S:StripTextures(button) - if not button.Texture then - button.Texture = button:CreateTexture(nil, 'OVERLAY') + -- button.Texture may already exist as a parentKey from UIPanelCloseButton + -- but be empty after StripTextures - check for actual texture content too + if not button.Texture or not button.Texture:GetTexture() then + if not button.Texture then + button.Texture = button:CreateTexture(nil, 'OVERLAY') + button:HookScript('OnEnter', closeOnEnter) + button:HookScript('OnLeave', closeOnLeave) + button:SetHitRectInsets(6, 6, 7, 7) + end S:Point(button.Texture, 'CENTER') button.Texture:SetTexture(Media.Close) S:Size(button.Texture, 12, 12) - button:HookScript('OnEnter', closeOnEnter) - button:HookScript('OnLeave', closeOnLeave) - button:SetHitRectInsets(6, 6, 7, 7) end if point then diff --git a/AddOnSkins/Skins/AddOns/Atlas.lua b/AddOnSkins/Skins/AddOns/Atlas.lua index 96c0f6eb..1850875f 100644 --- a/AddOnSkins/Skins/AddOns/Atlas.lua +++ b/AddOnSkins/Skins/AddOns/Atlas.lua @@ -1,49 +1,218 @@ -local AS, L, S, R = unpack(AddOnSkins) - -function R:Atlas(event, addon) - S:HandleFrame(AtlasFrame) - S:HandleFrame(AtlasFrameSmall) - - -- Skin Elements - S:HandleCloseButton(AtlasFrameCloseButton) - S:HandleCloseButton(AtlasFrameSmallCloseButton) - S:HandleButton(AtlasFrameOptionsButton) - S:HandleButton(AtlasFrameSmallOptionsButton) - S:HandleButton(AtlasSearchButton) - S:HandleButton(AtlasSearchClearButton) - S:HandleButton(AtlasSwitchButton) - S:HandleDropDownBox(AtlasFrameDropDown) - S:HandleDropDownBox(AtlasFrameDropDownType) - S:HandleDropDownBox(AtlasFrameSmallDropDown) - S:HandleDropDownBox(AtlasFrameSmallDropDownType) - S:HandleEditBox(AtlasSearchEditBox) - S:HandleScrollBar(AtlasScrollBarScrollBar) - - --Reposition Elements - AtlasFrameCloseButton:ClearAllPoints() - AtlasFrameCloseButton:SetPoint('TOPRIGHT', AtlasFrame, 'TOPRIGHT', -5, -5) - AtlasFrameSmallCloseButton:ClearAllPoints() - AtlasFrameSmallCloseButton:SetPoint('TOPRIGHT', AtlasFrameSmall, 'TOPRIGHT', -5, -5) - AtlasFrameCollapseButton:ClearAllPoints() - AtlasFrameCollapseButton:SetPoint('BOTTOM', AtlasFrame, 'BOTTOM', 0, 12) - AtlasFrameSmallExpandButton:ClearAllPoints() - AtlasFrameSmallExpandButton:SetPoint('BOTTOMRIGHT', AtlasFrameSmall, 'BOTTOMRIGHT', -10, 10) - AtlasFrameOptionsButton:ClearAllPoints() - AtlasFrameOptionsButton:SetPoint('LEFT', AtlasFrameLockButton, 'RIGHT', 4, 0) - AtlasFrameSmallOptionsButton:ClearAllPoints() - AtlasFrameSmallOptionsButton:SetPoint('LEFT', AtlasFrameSmallLockButton, 'RIGHT', 4, 0) - AtlasFrameLockButton:ClearAllPoints() - AtlasFrameLockButton:SetPoint('TOPLEFT', AtlasFrame, 'TOPLEFT', 5, -5) - AtlasFrameSmallLockButton:ClearAllPoints() - AtlasFrameSmallLockButton:SetPoint('TOPLEFT', AtlasFrameSmall, 'TOPLEFT', 10, -10) - AtlasSearchClearButton:ClearAllPoints() - AtlasSearchClearButton:SetPoint('RIGHT', AtlasSwitchButton, 'LEFT', -3, 0) - AtlasSearchButton:ClearAllPoints() - AtlasSearchButton:SetPoint('RIGHT', AtlasSearchClearButton, 'LEFT', -3, 0) - AtlasSearchEditBox:ClearAllPoints() - AtlasSearchEditBox:SetPoint('BOTTOM', AtlasFrame, 'BOTTOM', 95, 7) - AtlasSwitchButton:ClearAllPoints() - AtlasSwitchButton:SetPoint('BOTTOMRIGHT', AtlasFrame, 'BOTTOMRIGHT', -10, 10) +-- Atlas skin for AddOnSkins +-- Supports the Retail-engine Atlas fork used on TBC Anniversary servers. +-- WowStyle1DropdownTemplate dropdowns are skinned manually since SkinDropDownBox +-- targets the legacy UIDropDownMenuTemplate structure. + +local AS = unpack(AddOnSkins) + +local ARROW_TEX = [[Interface\AddOns\AddOnSkins\Media\Textures\Arrow]] + +local function Safe(func, name) + local f = type(name) == "string" and _G[name] or name + if f then func(AS, f) end +end + +local ARROW_ROTATION_DOWN = 3.14 + +local function SkinWowStyleDropdown(name) + local f = _G[name] + if not f or f._atlasSkinned then return end + f._atlasSkinned = true + + -- NineSlice border pieces are child frames, not texture regions - hide them all + for _, child in ipairs({f:GetChildren()}) do + child:Hide() + end + + -- Clear texture regions except Arrow, Text, Label + for i = 1, f:GetNumRegions() do + local r = select(i, f:GetRegions()) + if r and r ~= f.Arrow and r ~= f.Text and r ~= f.Label then + if r.SetTexture then r:SetTexture(nil) end + if r.SetAtlas then pcall(r.SetAtlas, r, '') end + r:SetAlpha(0) + end + end + + AS:SetTemplate(f) + + if f.Arrow then + if f.Arrow.SetAtlas then pcall(f.Arrow.SetAtlas, f.Arrow, nil) end + f.Arrow:SetTexture(ARROW_TEX, 'CLAMPTOBLACKADDITIVE', 'CLAMPTOBLACKADDITIVE') + f.Arrow:SetRotation(ARROW_ROTATION_DOWN) + f.Arrow:SetVertexColor(1, 1, 1) + f.Arrow:SetAlpha(1) + + if f.Arrow.SetAtlas then + hooksecurefunc(f.Arrow, 'SetAtlas', function(self) + self:SetTexture(ARROW_TEX, 'CLAMPTOBLACKADDITIVE', 'CLAMPTOBLACKADDITIVE') + self:SetRotation(ARROW_ROTATION_DOWN) + self:SetVertexColor(1, 1, 1) + end) + end + + f:HookScript('OnEnter', function() + f.Arrow:SetVertexColor(unpack(AS.Color)) + if f.SetBackdropBorderColor then f:SetBackdropBorderColor(unpack(AS.Color)) end + end) + f:HookScript('OnLeave', function() + f.Arrow:SetVertexColor(1, 1, 1) + if f.SetBackdropBorderColor then f:SetBackdropBorderColor(unpack(AS.BorderColor)) end + end) + f:HookScript('OnMouseDown', function() + f.Arrow:SetVertexColor(unpack(AS.Color)) + end) + f:HookScript('OnMouseUp', function() + f.Arrow:SetVertexColor(1, 1, 1) + end) + end +end + +function AS:Atlas(event, addon) + local loaded = (C_AddOns and C_AddOns.IsAddOnLoaded and C_AddOns.IsAddOnLoaded('Atlas')) + or (_G.IsAddOnLoaded and _G.IsAddOnLoaded('Atlas')) + if not loaded then return end + + local AtlasFrame = _G['AtlasFrame'] + local AtlasFrameSmall = _G['AtlasFrameSmall'] + + if AtlasFrame then + AS:StripTextures(AtlasFrame) + AS:SetTemplate(AtlasFrame) + AS:CreateShadow(AtlasFrame) + if AtlasFrame.Portrait then AtlasFrame.Portrait:Hide() end + if AtlasFrame.PortraitContainer then AtlasFrame.PortraitContainer:Hide() end + end + + if AtlasFrameSmall then + AS:StripTextures(AtlasFrameSmall) + AS:SetTemplate(AtlasFrameSmall) + AS:CreateShadow(AtlasFrameSmall) + if AtlasFrameSmall.Portrait then AtlasFrameSmall.Portrait:Hide() end + end + + Safe(AS.CreateBackdrop, 'AtlasFrameTopInset') + Safe(AS.CreateBackdrop, 'AtlasFrameBottomInset') + + Safe(AS.SkinCloseButton, 'AtlasFrameCloseButton') + Safe(AS.SkinCloseButton, 'AtlasFrameSmallCloseButton') + + -- Switch buttons (UIPanelButtonTemplate) - shown under the map when only one map variant exists + -- (SwitchDropdown is handled separately by SkinWowStyleDropdown) + Safe(AS.SkinButton, 'AtlasFrameSwitchButton') + Safe(AS.SkinButton, 'AtlasFrameSmallSwitchButton') + + local closeBtn = _G['AtlasFrameCloseButton'] + local lockBtn = _G['AtlasFrameLockButton'] + if lockBtn and closeBtn then + lockBtn:SetSize(18, 18) + AS:SkinButton(lockBtn) + lockBtn:ClearAllPoints() + lockBtn:SetPoint('RIGHT', closeBtn, 'LEFT', -6, 0) + end + + local optsBtn = _G['AtlasFrameOptionsButton'] + if optsBtn and lockBtn then + AS:SkinButton(optsBtn) + optsBtn:ClearAllPoints() + optsBtn:SetPoint('RIGHT', lockBtn, 'LEFT', -4, 0) + end + + -- Collapse / Expand buttons + local collapseBtn = _G['AtlasFrameCollapseButton'] + if collapseBtn then + collapseBtn:SetSize(20, 20) + AS:SkinButton(collapseBtn) + end + local expandBtn = _G['AtlasFrameSmallExpandButton'] + if expandBtn then + expandBtn:SetSize(20, 20) + AS:SkinButton(expandBtn) + end + + -- Navigation arrows - PrevMap/NextMap live in PrevNextContainer, not directly on the parent frame + for _, containerName in ipairs({'AtlasFramePrevNextContainer', 'AtlasFrameSmallPrevNextContainer'}) do + local container = _G[containerName] + if container then + if container.PrevMap then AS:SkinArrowButton(container.PrevMap, 'left') end + if container.NextMap then AS:SkinArrowButton(container.NextMap, 'right') end + end + end + + -- Icon buttons with .Icon parentKey (AdventureJournalMap, AdventureJournal, AtlasLoot) + -- SkinIconButton saves the texture BEFORE stripping, then restores it + for _, parent in ipairs({AtlasFrame, AtlasFrameSmall}) do + if parent then + local aj = parent.AdventureJournalMap or _G[parent:GetName()..'AdventureJournalMapButton'] + local ej = parent.AdventureJournal or _G[parent:GetName()..'AdventureJournalButton'] + local al = parent.AtlasLoot or _G[parent:GetName()..'AtlasLootButton'] + if aj then AS:SkinIconButton(aj) end + if ej then AS:SkinIconButton(ej) end + if al then AS:SkinIconButton(al) end + + -- LFG button uses a named Texture ($parentTexture), not parentKey="Icon" + -- Use Strip=false so the eye texture is never cleared by StripTextures; + -- SkinButton still clears NormalTexture/HighlightTexture via SetNormalTexture(''). + local lfg = _G[parent:GetName()..'LFGButton'] + if lfg then + AS:SkinButton(lfg) -- no strip: preserve the ARTWORK-layer eye texture + local eyeTex = lfg:GetName() and _G[lfg:GetName()..'Texture'] + if eyeTex then + eyeTex:SetAlpha(1) + AS:SetInside(eyeTex, lfg) + end + end + end + end + + local search = _G['AtlasSearchEditBox'] + if search and AtlasFrame then + AS:SkinEditBox(search) + search:ClearAllPoints() + search:SetPoint('BOTTOMRIGHT', AtlasFrame, 'BOTTOMRIGHT', -10, 8) + search:SetSize(180, 20) + end + + C_Timer.After(0, function() + SkinWowStyleDropdown('AtlasFrameDropDownType') + SkinWowStyleDropdown('AtlasFrameDropDown') + SkinWowStyleDropdown('AtlasFrameSmallDropDownType') + SkinWowStyleDropdown('AtlasFrameSmallDropDown') + + if AtlasFrame then + for _, child in ipairs({AtlasFrame:GetChildren()}) do + local n = child.GetName and child:GetName() + if n and n:find('SwitchDropdown') then + SkinWowStyleDropdown(n) + end + end + end + end) + + -- Boss/NPC map buttons are created lazily on each map refresh. + -- Hook Atlas_MapRefresh to skin new buttons as they appear. + local function SkinBossButtons() + -- AS:SkinButton doesn't set isSkinned, so use a custom flag to avoid + -- re-skinning (and accumulating hooks) on every Atlas_MapRefresh call. + local i, btn = 1 + btn = _G['AtlasMapBossButton'..i] + while btn do + if not btn._asSkinned then btn._asSkinned = true AS:SkinButton(btn) end + i = i + 1 + btn = _G['AtlasMapBossButton'..i] + end + i = 1 + btn = _G['AtlasMapBossButtonS'..i] + while btn do + if not btn._asSkinned then btn._asSkinned = true AS:SkinButton(btn) end + i = i + 1 + btn = _G['AtlasMapBossButtonS'..i] + end + end + + if _G['Atlas_MapRefresh'] then + hooksecurefunc('Atlas_MapRefresh', function() C_Timer.After(0, SkinBossButtons) end) + end end -AS:RegisterSkin('Atlas') +AS:RegisterSkin('Atlas', AS.Atlas) diff --git a/AddOnSkins/Skins/AddOns/AtlasLoot.lua b/AddOnSkins/Skins/AddOns/AtlasLoot.lua index 1b301ff7..d0a0aafa 100644 --- a/AddOnSkins/Skins/AddOns/AtlasLoot.lua +++ b/AddOnSkins/Skins/AddOns/AtlasLoot.lua @@ -1,88 +1,376 @@ -local AS, L, S, R = unpack(AddOnSkins) - -function R:AtlasLoot(event, addon) - S:HandleFrame(AtlasLootTooltip) - AtlasLootTooltip:HookScript('OnShow', function(self) - local Link = select(2, self:GetItem()) - local Quality = Link and select(3, GetItemInfo(Link)) - if (Quality and Quality >= 2) then - R, G, B = GetItemQualityColor(Quality) - self:SetBackdropBorderColor(R, G, B) - else - self:SetBackdropBorderColor(unpack(AS.BorderColor)) - end - self:SetBackdropColor(unpack(AS.BackdropColor)) - if _G["AtlasLoot-MountToolTip"] then - S:HandleFrame(_G["AtlasLoot-MountToolTip"], nil, true) - S:HandleIcon(_G["AtlasLoot-MountToolTip"].icon) - _G["AtlasLoot-MountToolTip"]:SetBackdropBorderColor(self:GetBackdropBorderColor()) - end - if _G["AtlasLoot-PetToolTip"] then - S:HandleFrame(_G["AtlasLoot-PetToolTip"], nil, true) - S:HandleIcon(_G["AtlasLoot-PetToolTip"].icon) - _G["AtlasLoot-PetToolTip"]:SetBackdropBorderColor(self:GetBackdropBorderColor()) - end - if _G["AtlasLoot-FactionToolTip"] then - S:HandleFrame(_G["AtlasLoot-FactionToolTip"], nil, true) - S:HandleIcon(_G["AtlasLoot-FactionToolTip"].icon) - _G["AtlasLoot-FactionToolTip"]:SetBackdropBorderColor(self:GetBackdropBorderColor()) +-- AtlasLootClassic skin for AddOnSkins +-- Targets the sliccer fork (AtlasLootClassic) used on TBC Anniversary servers. +-- GUI frames and BiS footer are created lazily, so skinning is deferred and hooked. + +local AS = unpack(AddOnSkins) + +local skinned = false +local footerSkinned = false + +local ddHooked = {} + +local function SkinALDropDownPopup() + local i = 1 + while true do + local f = _G['AtlasLoot-DropDown-CatFrame'..i] + if not f then break end + if f:IsShown() then + -- Re-apply template (frame is reused from cache, backdrop gets reset by AtlasLoot) + AS:SetTemplate(f) + -- Hook SetBackdropColor once per frame object + if not ddHooked[f] then + ddHooked[f] = true + hooksecurefunc(f, 'SetBackdropColor', function(self, r, g, b, a) + local br, bg, bb, ba = unpack(AS.BackdropColor) + if r ~= br or g ~= bg or b ~= bb then + self:SetBackdropColor(br, bg, bb, ba) + end + end) + end + if f.buttons then + for _, btn in ipairs(f.buttons) do + if not btn._asSkinned then + btn._asSkinned = true + btn:SetHighlightTexture(AS.NormTex or [[Interface\Buttons\UI-Listbox-Highlight]]) + local hl = btn:GetHighlightTexture() + if hl then + hl:SetVertexColor(unpack(AS.Color)) + hl:SetAlpha(0.35) + end + end + end + end + end + i = i + 1 + end +end + +local function SkinALDropDown(widget) + if not widget or not widget.frame then return end + local f = widget.frame + AS:StripTextures(f) + AS:SetTemplate(f) + -- Lock backdrop color - AtlasLoot resets it on every SetSelected + hooksecurefunc(f, 'SetBackdropColor', function(self, r, g, b, a) + local br, bg, bb, ba = unpack(AS.BackdropColor) + if r ~= br or g ~= bg or b ~= bb then + self:SetBackdropColor(br, bg, bb, ba) end end) + if f.button then + local btn = f.button + -- Hide original button state textures directly - SetNormalTexture('') does not + -- clear textures set via C-code on TBC client + for _, getter in ipairs({'GetNormalTexture','GetPushedTexture','GetHighlightTexture','GetDisabledTexture'}) do + local tex = btn[getter] and btn[getter](btn) + if tex then tex:SetTexture(nil) tex:Hide() end + end + for i = 1, btn:GetNumRegions() do + local r = select(i, btn:GetRegions()) + if r and r.SetTexture then r:SetTexture(nil) r:Hide() end + end + -- Draw arrow as texture on main frame so hover logic mirrors Atlas dropdowns + local arrow = f:CreateTexture(nil, 'OVERLAY') + arrow:SetTexture([[Interface\AddOns\AddOnSkins\Media\Textures\Arrow]]) + arrow:SetRotation(3.14) + arrow:SetVertexColor(1, 1, 1) + arrow:SetPoint('RIGHT', f, 'RIGHT', -4, 0) + arrow:SetSize(12, 12) + f._arrow = arrow + local function onEnter() + arrow:SetVertexColor(unpack(AS.Color)) + f:SetBackdropBorderColor(unpack(AS.Color)) + end + local function onLeave() + arrow:SetVertexColor(1, 1, 1) + f:SetBackdropBorderColor(unpack(AS.BorderColor)) + end + f:HookScript('OnEnter', onEnter) + f:HookScript('OnLeave', onLeave) + -- btn sits on top of f and blocks its OnEnter - hook btn too + btn:HookScript('OnEnter', onEnter) + btn:HookScript('OnLeave', onLeave) + -- Skin popup on open + btn:HookScript('OnClick', function() + C_Timer.After(0, SkinALDropDownPopup) + end) + end +end + +local function SkinALSelect(widget) + if not widget or not widget.frame then return end + AS:StripTextures(widget.frame) + AS:SetTemplate(widget.frame) +end - local AtlasLootFrame = _G["AtlasLoot_GUI-Frame"] - S:HandleFrame(AtlasLootFrame) - S:HandleCloseButton(AtlasLootFrame.CloseButton) - S:StripTextures(AtlasLootFrame.titleFrame) - - local function SkinDropDown(Frame) - S:HandleFrame(_G[Frame]) - S:HandleNextPrevButton(_G[Frame..'-button']) - local a, b, c, d, e = _G[Frame..'-button']:GetPoint() - _G[Frame..'-button']:SetPoint(a, b, c, d - 4, 0) - _G[Frame]:HookScript('OnUpdate', function(self) - for i = 1, 3 do - local CatFrame = _G['AtlasLoot-DropDown-CatFrame'..i] - if CatFrame and not CatFrame.IsSkinned then - local r, g, b = CatFrame:GetBackdropColor() - S:HandleFrame(CatFrame) - CatFrame:SetBackdropColor(r, g, b) - - CatFrame:HookScript('OnShow', function(self) - local a, f, c, d, e = self:GetPoint() - self:SetPoint(a, f, c, d, e - 3) - end) - - CatFrame:GetScript('OnShow')(CatFrame) - CatFrame.IsSkinned = true +local function SkinCheckBoxElvUI(cb) + if not cb or cb._asSkinned then return end + cb._asSkinned = true -- own guard; do NOT set isSkinned here - AS:SkinCheckBox checks it + if cb.SetNormalTexture then cb:SetNormalTexture('') end + if cb.SetHighlightTexture then cb:SetHighlightTexture('') end + if cb.SetPushedTexture then cb:SetPushedTexture('') end + if cb.SetDisabledTexture then cb:SetDisabledTexture('') end + local function kill(tex) + if not tex then return end + if tex.SetTexture then tex:SetTexture(nil) end + if tex.SetAtlas then tex:SetAtlas('') end + tex:SetAlpha(0) tex:Hide() + end + kill(cb.GetNormalTexture and cb:GetNormalTexture()) + kill(cb.GetHighlightTexture and cb:GetHighlightTexture()) + kill(cb.GetPushedTexture and cb:GetPushedTexture()) + for i = 1, cb:GetNumRegions() do + local r = select(i, cb:GetRegions()) + if r then + if r.SetTexture then r:SetTexture(nil) end + if r.SetAtlas then r:SetAtlas('') end + end + end + if cb.SetBackdrop then cb:SetBackdrop(nil) end + AS:SkinCheckBox(cb) + local blankTex = (_G.ElvUI and _G.ElvUI[1].media.blankTex) or [[Interface\Buttons\WHITE8X8]] + cb:SetCheckedTexture(blankTex) + local ct = cb.GetCheckedTexture and cb:GetCheckedTexture() + if ct then + ct:SetVertexColor(unpack(AS.Color)) + ct:SetAlpha(1) + if cb.Backdrop then AS:SetInside(ct, cb.Backdrop) end + end + if cb.Backdrop then + cb:HookScript('OnShow', function(f) + if f:GetChecked() then + f.Backdrop:SetBackdropBorderColor(unpack(AS.Color)) + else + f.Backdrop:SetBackdropBorderColor(unpack(AS.BorderColor)) + end + end) + hooksecurefunc(cb, 'SetChecked', function(f, checked) + if f.Backdrop then + if checked then + f.Backdrop:SetBackdropBorderColor(unpack(AS.Color)) + else + f.Backdrop:SetBackdropBorderColor(unpack(AS.BorderColor)) end end end) end +end + +local function SkinCheckButtonsInFrame(parent) + if not parent then return end + for _, child in ipairs({parent:GetChildren()}) do + if child.GetObjectType and child:GetObjectType() == 'CheckButton' then + -- Reset scale before skinning - AtlasLoot BiS footer uses SetScale(1.5) + -- which causes the skinned checkbox to overflow the footer bounds + if child:GetScale() ~= 1 then child:SetScale(1) end + SkinCheckBoxElvUI(child) + end + SkinCheckButtonsInFrame(child) + end +end - SkinDropDown('AtlasLoot-DropDown-1') - SkinDropDown('AtlasLoot-DropDown-2') +local function SkinFooter() + if footerSkinned then return end + local footer = _G['AtlasLoot_BiS_Footer'] + if not footer then return end + footerSkinned = true + AS:StripTextures(footer) + -- No full border on footer - it sits directly under the main frame whose + -- bottom border already acts as the separator. + footer:SetBackdrop({ + bgFile = [[Interface\Buttons\WHITE8X8]], + tileSize = 8, + tile = true, + }) + footer:SetBackdropColor(unpack(AS.BackdropColor)) + SkinCheckButtonsInFrame(footer) - for i = 1, 3 do - S:HandleFrame(_G['AtlasLoot-Select-'..i]) + -- centerGroup is 44px tall designed for scale(1.5) children inside a 24px footer. + -- After resetting child scale to 1, expand footer and re-center the group. + footer:SetHeight(52) + local cg = ({footer:GetChildren()})[1] + if cg and cg:GetNumPoints() > 0 then + local _, rel = cg:GetPoint() + cg:SetHeight(34) + cg:ClearAllPoints() + cg:SetPoint('CENTER', rel or footer, 'CENTER', 0, -2) + end + -- Move footer 1px below main frame so its top border sits just under the main frame's border. + local parentFrame = _G['AtlasLoot_GUI-Frame'] + if parentFrame then + footer:ClearAllPoints() + footer:SetPoint('TOPLEFT', parentFrame, 'BOTTOMLEFT', 0, -1) + footer:SetPoint('TOPRIGHT', parentFrame, 'BOTTOMRIGHT', 0, -1) end +end + +-- Skin the class filter selection popup (created on first right-click) +local function SkinClassFilterPopup(clasFilterButton) + if not clasFilterButton then return end + local origOnClick = clasFilterButton:GetScript('OnClick') + if not origOnClick then return end + clasFilterButton:HookScript('OnClick', function(self, mouseButton) + if mouseButton ~= 'RightButton' then return end + local sf = self.selectionFrame + if not sf or sf._asSkinned then return end + sf._asSkinned = true + AS:StripTextures(sf) + AS:SetTemplate(sf) + if sf.buttons then + for _, btn in ipairs(sf.buttons) do + AS:StripTextures(btn) + AS:SetTemplate(btn) + if btn.icon then AS:SkinTexture(btn.icon) end + btn:SetHighlightTexture(AS.NormTex or [[Interface\Buttons\UI-Listbox-Highlight]]) + local hl = btn:GetHighlightTexture() + if hl then + hl:SetVertexColor(unpack(AS.Color)) + hl:SetAlpha(0.35) + end + end + end + end) +end - local AtlasLootItemFrame = _G["AtlasLoot_GUI-ItemFrame"] - S:CreateBackdrop(AtlasLootItemFrame) - S:HandleNextPrevButton(AtlasLootItemFrame.nextPageButton) - S:HandleButton(AtlasLootItemFrame.modelButton) - S:HandleButton(AtlasLootItemFrame.soundsButton) - S:HandleNextPrevButton(AtlasLootItemFrame.prevPageButton) - S:HandleButton(AtlasLootItemFrame.itemsButton) - S:HandleButton(AtlasLootItemFrame.clasFilterButton) - S:SetInside(AtlasLootItemFrame.clasFilterButton.texture) - AtlasLootItemFrame.clasFilterButton:HookScript('OnUpdate', function(self) - if self.texture:GetTexture() == "Interface\\Glues\\CharacterCreate\\UI-CharacterCreate-Classes" then - self.texture:SetTexCoord(CLASS_ICON_TCOORDS[AS.MyClass][1] + 0.015, CLASS_ICON_TCOORDS[AS.MyClass][2] - 0.02, CLASS_ICON_TCOORDS[AS.MyClass][3] + 0.018, CLASS_ICON_TCOORDS[AS.MyClass][4] - .02) - else - S:HandleIcon(self.texture) +-- Skin the game version selection popup (created on first click) +local function SkinGameVersionPopup(gvButton) + if not gvButton then return end + gvButton:HookScript('OnClick', function(self) + local sf = self.selectionFrame + if not sf or sf._asSkinned then return end + sf._asSkinned = true + AS:StripTextures(sf) + AS:SetTemplate(sf) + end) +end + +local function SkinAtlasLoot() + if skinned then return end + local ALFrame = _G['AtlasLoot_GUI-Frame'] + if not ALFrame then return end + skinned = true + + local AL = AtlasLoot + local GUI = AL and AL.GUI + + AS:SkinFrame(ALFrame) + if ALFrame.titleFrame then AS:StripTextures(ALFrame.titleFrame) end + local closeBtn = _G['AtlasLoot_GUI-Frame-CloseButton'] + if closeBtn then AS:SkinCloseButton(closeBtn) end + + -- Info button + if ALFrame.titleFrame and ALFrame.titleFrame.infoButton then + AS:SkinButton(ALFrame.titleFrame.infoButton) + end + + if GUI and GUI.frame then + SkinALDropDown(GUI.frame.moduleSelect) + SkinALDropDown(GUI.frame.subCatSelect) + SkinALSelect(GUI.frame.difficulty) + SkinALSelect(GUI.frame.boss) + SkinALSelect(GUI.frame.extra) + + -- Game version button + local gvBtn = GUI.frame.gameVersionButton + if gvBtn then + -- Hide the box lines, apply a proper template + if gvBtn.Box then + for _, line in ipairs(gvBtn.Box) do + line:Hide() + end + end + AS:CreateBackdrop(gvBtn) + if gvBtn.texture or GUI.frame.gameVersionLogo then + local tex = gvBtn.texture or GUI.frame.gameVersionLogo + AS:SetInside(tex, gvBtn.Backdrop or gvBtn) + end + SkinGameVersionPopup(gvBtn) + end + end + + local contentFrame = _G['AtlasLoot_GUI-ItemFrame'] + if contentFrame then + AS:CreateBackdrop(contentFrame) + + -- Hide the AtlasLoot background textures; the backdrop replaces them. + -- Also hook SetAlpha/SetColorTexture to prevent RefreshContentBackGround from restoring them. + for _, key in ipairs({'topBG', 'downBG', 'itemBG'}) do + local tex = contentFrame[key] + if tex then + tex:SetAlpha(0) + tex:Hide() + hooksecurefunc(tex, 'SetAlpha', function(self) if self:GetAlpha() > 0 then self:Hide() end end) + hooksecurefunc(tex, 'SetColorTexture', function(self) self:Hide() end) + end + end + + if contentFrame.nextPageButton then AS:SkinArrowButton(contentFrame.nextPageButton) end + if contentFrame.prevPageButton then AS:SkinArrowButton(contentFrame.prevPageButton) end + if contentFrame.modelButton then AS:SkinButton(contentFrame.modelButton) end + if contentFrame.soundsButton then AS:SkinButton(contentFrame.soundsButton) end + if contentFrame.itemsButton then AS:SkinButton(contentFrame.itemsButton) end + + -- Class filter button (icon button) + if contentFrame.clasFilterButton then + AS:CreateBackdrop(contentFrame.clasFilterButton) + if contentFrame.clasFilterButton.texture then + AS:SkinTexture(contentFrame.clasFilterButton.texture) + AS:SetInside(contentFrame.clasFilterButton.texture, contentFrame.clasFilterButton.Backdrop or contentFrame.clasFilterButton) + end + SkinClassFilterPopup(contentFrame.clasFilterButton) + end + + -- Content phase button (icon button) + if contentFrame.contentPhaseButton then + AS:CreateBackdrop(contentFrame.contentPhaseButton) + if contentFrame.contentPhaseButton.texture then + AS:SkinTexture(contentFrame.contentPhaseButton.texture) + AS:SetInside(contentFrame.contentPhaseButton.texture, contentFrame.contentPhaseButton.Backdrop or contentFrame.contentPhaseButton) + end + end + + -- Map button + if contentFrame.mapButton then + AS:SkinButton(contentFrame.mapButton) + if contentFrame.mapButton.texture then + AS:SetInside(contentFrame.mapButton.texture, contentFrame.mapButton.Backdrop or contentFrame.mapButton) + end + end + + if contentFrame.searchBox then + AS:SkinEditBox(contentFrame.searchBox) + contentFrame.searchBox:SetHeight(20) + contentFrame.searchBox:SetWidth(160) + end + end + + if GUI and GUI.Create then + hooksecurefunc(GUI, 'Create', function() + C_Timer.After(0, SkinFooter) + end) + end + + SkinFooter() +end + +function AS:AtlasLoot(event, addon) + local loaded = (C_AddOns and C_AddOns.IsAddOnLoaded and C_AddOns.IsAddOnLoaded('AtlasLootClassic')) + or (_G.IsAddOnLoaded and _G.IsAddOnLoaded('AtlasLootClassic')) + if not loaded then return end + + C_Timer.After(0, function() + SkinAtlasLoot() + + if not skinned and AtlasLoot and AtlasLoot.GUI and AtlasLoot.GUI.Toggle then + local orig = AtlasLoot.GUI.Toggle + AtlasLoot.GUI.Toggle = function(...) + orig(...) + SkinAtlasLoot() + C_Timer.After(0, SkinFooter) + end end end) end -AS:RegisterSkin('AtlasLoot') +AS:RegisterSkin('AtlasLootClassic', AS.AtlasLoot) \ No newline at end of file diff --git a/AddOnSkins/Skins/AddOns/RareScanner.lua b/AddOnSkins/Skins/AddOns/RareScanner.lua index 146b0102..52dc60bb 100644 --- a/AddOnSkins/Skins/AddOns/RareScanner.lua +++ b/AddOnSkins/Skins/AddOns/RareScanner.lua @@ -1,18 +1,40 @@ -local AS, L, S, R = unpack(AddOnSkins) +local AS = unpack(AddOnSkins) -function R:RareScanner() - S:HandleFrame(RARESCANNER_BUTTON, 'Default') - S:HandleButton(RARESCANNER_BUTTON.CloseButton) - RARESCANNER_BUTTON.CloseButton:ClearAllPoints() - RARESCANNER_BUTTON.CloseButton:SetPoint("TOPRIGHT", -5, -5) - S:HandleButton(RARESCANNER_BUTTON.FilterEntityButton) - RARESCANNER_BUTTON.FilterEntityButton:SetNormalTexture([[Interface\WorldMap\Dash_64Grey]]) - RARESCANNER_BUTTON.FilterEntityButton:ClearAllPoints() - RARESCANNER_BUTTON.FilterEntityButton:SetPoint("TOPLEFT", 5, -5) - S:HandleButton(RARESCANNER_BUTTON.UnfilterEnabledButton) - RARESCANNER_BUTTON.FilterEnabledTexture:SetTexture([[Interface\WorldMap\Skull_64]]) - RARESCANNER_BUTTON.UnfilterEnabledButton:ClearAllPoints() - RARESCANNER_BUTTON.UnfilterEnabledButton:SetPoint("TOPLEFT", 5, -5) +local function SkinRareScanner() + local btn = _G['RARESCANNER_BUTTON'] + if not btn or btn._asSkinned then return end + btn._asSkinned = true + + -- Replace parchment background and default tooltip border with ElvUI style + btn:SetNormalTexture('') + btn:SetPushedTexture('') + btn:SetHighlightTexture('') + AS:StripTextures(btn) + AS:SetTemplate(btn) + AS:CreateShadow(btn) + + -- Close button + if btn.CloseButton then + AS:SkinCloseButton(btn.CloseButton) + btn.CloseButton:ClearAllPoints() + btn.CloseButton:SetPoint('TOPRIGHT', btn, 'TOPRIGHT', -4, -4) + end + + -- Filter buttons use custom icon textures from RareScanner media - only skin the frame border + if btn.FilterEntityButton then + AS:CreateBackdrop(btn.FilterEntityButton) + end + if btn.UnFilterEntityButton then + AS:CreateBackdrop(btn.UnFilterEntityButton) + end +end + +function AS:RareScanner(event, addon) + local loaded = (C_AddOns and C_AddOns.IsAddOnLoaded and C_AddOns.IsAddOnLoaded('RareScanner')) + or (_G.IsAddOnLoaded and _G.IsAddOnLoaded('RareScanner')) + if not loaded then return end + + C_Timer.After(0, SkinRareScanner) end -AS:RegisterSkin('RareScanner') +AS:RegisterSkin('RareScanner', AS.RareScanner) diff --git a/AddOnSkins/Skins/AddOns/Zygor.lua b/AddOnSkins/Skins/AddOns/Zygor.lua index 857fac0c..fc198b5f 100644 --- a/AddOnSkins/Skins/AddOns/Zygor.lua +++ b/AddOnSkins/Skins/AddOns/Zygor.lua @@ -1,12 +1,92 @@ -local AS, L, S, R = unpack(AddOnSkins) +-- Zygor Guides skin for AddOnSkins (TBC Anniversary: ZygorGuidesViewerClassicTBCAnniv) +-- Zygor uses a fully custom widget system (UI:Create / SkinData), so we avoid +-- stripping or restyling internal content frames - doing so breaks Zygor's own +-- theme rendering. Instead we only override the outer frame border colours and +-- skin the standard Blizzard-template elements (close button, search backdrop). -function R:ZygorGuidesViewer() - S:StripTextures(ZygorGuidesViewerFrame_Border, true) - S:HandleFrame(ZygorGuidesViewerFrame, nil, nil, true) +local AS = unpack(AddOnSkins) - for i = 1, 6 do - S:HandleFrame(_G['ZygorGuidesViewerFrame_Step'..i], true) +-- Guide Browser +-- ZygorGuidesViewer_GuideMenu is the guide-browser window (Home/Featured/…). +-- Its outer frame uses BackdropTemplate + Zygor's SkinData colours. We just +-- override the border colour so it matches the ElvUI theme colour, and skin the +-- close button and search edit back-frame. + +local guideMenuSkinned = false +local function SkinGuideMenuFrame() + if guideMenuSkinned then return end + local MF = _G['ZygorGuidesViewer_GuideMenu'] + if not MF then return end + guideMenuSkinned = true + + -- Only override the BORDER colour - leave background/texture alone so + -- Zygor's own theme (Stealth, Starlight, …) is not broken. + MF:SetBackdropBorderColor(unpack(AS.BorderColor)) + + -- Close button (standard Blizzard ButtonTemplate child) + if MF.Header and MF.Header.CloseButton then + AS:SkinCloseButton(MF.Header.CloseButton) + end + + -- Search edit box: Zygor EditBox keeps its backdrop in a child .back frame + local search = MF.MenuGuides and MF.MenuGuides.SearchEdit + if search and search.back then + AS:StripTextures(search.back) + AS:SetTemplate(search.back) + search:SetTextColor(1, 1, 1) + end +end + +-- Step Viewer +-- ZygorGuidesViewerFrame is the visual skin frame (child of FrameMaster). +-- Its ApplySkin() sets backdrop colours from SkinData. We hook it to override +-- just the border colour with the ElvUI value without breaking anything else. + +local stepViewerHooked = false +local function HookStepViewer() + if stepViewerHooked then return end + local ZGV = _G['ZygorGuidesViewer'] + if not ZGV or not ZGV.Frame then return end + stepViewerHooked = true + + local f = ZGV.Frame + local function ApplyElvUIBorder() + if f.Border then + f.Border:SetBackdropBorderColor(unpack(AS.BorderColor)) + end + end + + if f.ApplySkin then + hooksecurefunc(f, 'ApplySkin', ApplyElvUIBorder) end + ApplyElvUIBorder() +end + +-- Entry point +function AS:Zygor(event, addon) + local loaded = (C_AddOns and C_AddOns.IsAddOnLoaded and ( + C_AddOns.IsAddOnLoaded('ZygorGuidesViewer') or + C_AddOns.IsAddOnLoaded('ZygorGuidesViewerClassic') or + C_AddOns.IsAddOnLoaded('ZygorGuidesViewerClassicTBCAnniv'))) + or (_G.IsAddOnLoaded and ( + _G.IsAddOnLoaded('ZygorGuidesViewer') or + _G.IsAddOnLoaded('ZygorGuidesViewerClassic') or + _G.IsAddOnLoaded('ZygorGuidesViewerClassicTBCAnniv'))) + if not loaded then return end + + C_Timer.After(0.5, function() + HookStepViewer() + SkinGuideMenuFrame() + + -- Guide browser may be opened before our timer; also hook Open so we + -- catch it if it is first shown after our timer runs. + local ZGV = _G['ZygorGuidesViewer'] + if ZGV and ZGV.GuideMenu and ZGV.GuideMenu.Open then + hooksecurefunc(ZGV.GuideMenu, 'Open', function() + C_Timer.After(0, SkinGuideMenuFrame) + end) + end + end) end -AS:RegisterSkin('ZygorGuidesViewer') +AS:RegisterSkin('Zygor', AS.Zygor) From bc75330489015a6ba40fff8ec377d079c02eca9a Mon Sep 17 00:00:00 2001 From: Adams Date: Thu, 9 Apr 2026 19:18:13 +0300 Subject: [PATCH 03/18] Fix GetAddOnEnableState argument order for C_AddOns namespace C_AddOns.GetAddOnEnableState(name, character) has the opposite argument order from the legacy global GetAddOnEnableState(character, name), as documented in patch 10.2.0 API changes. On TBC Anniversary C_AddOns is present with the new signature, but we were calling it with the legacy order - causing every addon to return state 0 and AS.AddOns to be populated as false for all entries. Consequence: CheckAddOn() always returned false, Embed/Details.lua hit its early return, ES:Details was never defined, and any attempt to embed Details produced 'attempt to call method Details (a nil value)'. - Init.lua: call C_AddOns.GetAddOnEnableState(Name, AS.MyName) in the startup loop; wrap the legacy global with a shim that translates the old (character, name) call convention to the new one instead of aliasing the function directly - Core.lua: same fix in CheckAddOn() runtime path; also fix SkinDebug mode to print errors with stack trace and suppress auto-disable --- AddOnSkins/Core/Core.lua | 15 +++++++++++---- AddOnSkins/Init.lua | 19 ++++++++++++++----- 2 files changed, 25 insertions(+), 9 deletions(-) diff --git a/AddOnSkins/Core/Core.lua b/AddOnSkins/Core/Core.lua index 127c0d73..04ab2b89 100644 --- a/AddOnSkins/Core/Core.lua +++ b/AddOnSkins/Core/Core.lua @@ -79,8 +79,12 @@ function AS:CheckAddOn(addon) -- ElvUI may not appear enabled in the addon list on Anniversary but its global is present. if key == 'elvui' then return _G.ElvUI ~= nil end if AS.AddOns[key] ~= nil then return AS.AddOns[key] end - local state = (C_AddOns and C_AddOns.GetAddOnEnableState and C_AddOns.GetAddOnEnableState(AS.MyName, addon)) - or (_G.GetAddOnEnableState and _G.GetAddOnEnableState(AS.MyName, addon)) + local state + if C_AddOns and C_AddOns.GetAddOnEnableState then + state = C_AddOns.GetAddOnEnableState(addon, AS.MyName) + else + state = _G.GetAddOnEnableState and _G.GetAddOnEnableState(AS.MyName, addon) + end return (state or 0) > 0 end @@ -197,7 +201,10 @@ end function AS:CallSkin(addonName, func, event, ...) if AS.Debug or AS:CheckOption('SkinDebug') then local args = {...} - xpcall(function() func(self, event, unpack(args)) end, errorhandler) + local ok, err = xpcall(function() func(self, event, unpack(args)) end, function(e) return e..'\n'..debugstack() end) + if not ok then + AS:Print('SkinDebug ['..addonName..']: '..tostring(err)) + end else local pass = pcall(func, self, event, ...) if not pass then @@ -254,7 +261,7 @@ function AS:StartUp(event, ...) AS:SecureHook(_G.ElvUI[1], 'UpdateMedia') end - if not AS.Debug then + if not AS.Debug and not AS:CheckOption('SkinDebug') then for Version, SkinTable in pairs(_G.AddOnSkinsDS) do if Version == AS.Version or Version < AS.Version then if Version < AS.Version then diff --git a/AddOnSkins/Init.lua b/AddOnSkins/Init.lua index efdcec3d..1285333f 100644 --- a/AddOnSkins/Init.lua +++ b/AddOnSkins/Init.lua @@ -9,9 +9,14 @@ if not _G.IsAddOnLoaded and C_AddOns and C_AddOns.IsAddOnLoaded then if not _G.GetAddOnMetadata and C_AddOns and C_AddOns.GetAddOnMetadata then _G.GetAddOnMetadata = C_AddOns.GetAddOnMetadata end if not _G.GetAddOnInfo and C_AddOns and C_AddOns.GetAddOnInfo then _G.GetAddOnInfo = C_AddOns.GetAddOnInfo end if not _G.GetNumAddOns and C_AddOns and C_AddOns.GetNumAddOns then _G.GetNumAddOns = C_AddOns.GetNumAddOns end -if not _G.GetAddOnEnableState and C_AddOns and C_AddOns.GetAddOnEnableState then _G.GetAddOnEnableState = C_AddOns.GetAddOnEnableState end +-- Do NOT alias C_AddOns.GetAddOnEnableState into the legacy global: they have +-- opposite argument orders. Callers that need the legacy (character, name) +-- signature must use _G.GetAddOnEnableState directly; callers that use the +-- C_AddOns namespace call it as (name, character). +if not _G.GetAddOnEnableState and C_AddOns and C_AddOns.GetAddOnEnableState then + _G.GetAddOnEnableState = function(character, name) return C_AddOns.GetAddOnEnableState(name, character) end +end -local GetAddOnEnableState = (C_AddOns and C_AddOns.GetAddOnEnableState) or _G.GetAddOnEnableState local GetAddOnInfo = (C_AddOns and C_AddOns.GetAddOnInfo) or _G.GetAddOnInfo local GetAddOnMetadata = (C_AddOns and C_AddOns.GetAddOnMetadata) or _G.GetAddOnMetadata local GetNumAddOns = (C_AddOns and C_AddOns.GetNumAddOns) or _G.GetNumAddOns @@ -79,9 +84,13 @@ AS.AlreadyLoaded = {} for i = 1, GetNumAddOns() do local Name, _, _, _, Reason = GetAddOnInfo(i) local LoweredName = strlower(Name) - -- Classic API argument order is (characterName, addonName), opposite of Retail. - -- Treat any positive state as enabled to handle per-character quirks on Anniversary. - AS.AddOns[LoweredName] = (GetAddOnEnableState(AS.MyName, Name) or 0) > 0 and (not Reason or Reason ~= 'DEMAND_LOADED') + local enableState + if C_AddOns and C_AddOns.GetAddOnEnableState then + enableState = C_AddOns.GetAddOnEnableState(Name, AS.MyName) + else + enableState = _G.GetAddOnEnableState and _G.GetAddOnEnableState(AS.MyName, Name) + end + AS.AddOns[LoweredName] = (enableState or 0) > 0 and (not Reason or Reason ~= 'DEMAND_LOADED') AS.AlreadyLoaded[Name] = IsAddOnLoaded and IsAddOnLoaded(Name) or false AS.AddOnVersion[LoweredName] = GetAddOnMetadata(Name, 'Version') end From 1fb77857bb66b4a1468029f7984d216bd03dc211 Mon Sep 17 00:00:00 2001 From: Adams Date: Thu, 9 Apr 2026 19:19:39 +0300 Subject: [PATCH 04/18] EmbedSystem: guard embed method calls against nil Embed/Details.lua and friends define ES:Details() etc. only when the addon passes CheckAddOn(). Guard each call so ES:Check() is safe even if an embed module was not loaded. --- AddOnSkins/Core/EmbedSystem.lua | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/AddOnSkins/Core/EmbedSystem.lua b/AddOnSkins/Core/EmbedSystem.lua index f6bd0fc1..b68fa09e 100644 --- a/AddOnSkins/Core/EmbedSystem.lua +++ b/AddOnSkins/Core/EmbedSystem.lua @@ -134,11 +134,11 @@ function ES:Check(Message) Window:SetFrameLevel(AS:CheckOption('EmbedFrameLevel')) end - if AS:CheckEmbed('Details') then ES:Details() end - if AS:CheckEmbed('Omen') then ES:Omen() end - if AS:CheckEmbed('Skada') then ES:Skada() end - if AS:CheckEmbed('TinyDPS') then ES:TinyDPS() end - if AS:CheckEmbed('Recount') then ES:Recount() end + if AS:CheckEmbed('Details') and ES.Details then ES:Details() end + if AS:CheckEmbed('Omen') and ES.Omen then ES:Omen() end + if AS:CheckEmbed('Skada') and ES.Skada then ES:Skada() end + if AS:CheckEmbed('TinyDPS') and ES.TinyDPS then ES:TinyDPS() end + if AS:CheckEmbed('Recount') and ES.Recount then ES:Recount() end if Message and AS:CheckOption('EmbedSystemMessage') then if AS:CheckOption('EmbedMain') then AS:Print(format(L["Embed System: Main: '%s'"], AS:CheckOption('EmbedMain'))) end From 0ae772c0a515c1f284f0775cb1e0592ccaad077a Mon Sep 17 00:00:00 2001 From: Adams Date: Thu, 9 Apr 2026 19:19:44 +0300 Subject: [PATCH 05/18] Skins: fix HandleCloseButton skipping SetTexture on Classic clients button.Texture from UIPanelCloseButton template survives StripTextures as a parentKey holding a numeric fileID. The previous guard treated any non-nil Texture as already skinned and skipped SetTexture, leaving close buttons unskinned. Check for a string path instead - that is the reliable signal that HandleCloseButton already ran on this button. --- AddOnSkins/Core/Skins.lua | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/AddOnSkins/Core/Skins.lua b/AddOnSkins/Core/Skins.lua index c0b3a2fe..9ad5645f 100644 --- a/AddOnSkins/Core/Skins.lua +++ b/AddOnSkins/Core/Skins.lua @@ -1488,9 +1488,10 @@ do function S:HandleCloseButton(button, point, x, y) S:StripTextures(button) - -- button.Texture may already exist as a parentKey from UIPanelCloseButton - -- but be empty after StripTextures - check for actual texture content too - if not button.Texture or not button.Texture:GetTexture() then + -- button.Texture may exist as a parentKey from UIPanelCloseButton but still + -- hold a numeric fileID after StripTextures. Only skip if it's a string path + -- (i.e. was already set by a previous HandleCloseButton call). + if not button.Texture or type(button.Texture:GetTexture()) ~= 'string' then if not button.Texture then button.Texture = button:CreateTexture(nil, 'OVERLAY') button:HookScript('OnEnter', closeOnEnter) From be4cf75f78a585cbbefcf6ae8f07c4fe9881d865 Mon Sep 17 00:00:00 2001 From: Adams Date: Thu, 9 Apr 2026 19:19:54 +0300 Subject: [PATCH 06/18] Ace3: rewrite CheckBox and Dropdown skinning for TBC Anniversary The existing skin relied on S:CreateBackdrop / S:HandleNextPrevButton and the UIDropDownMenuTemplate frame structure (Left/Middle/Right textures, button_cover). On TBC Anniversary Ace3 uses a different widget layout and those helpers either error or silently no-op. CheckBox: draw a plain border+fill pair with vertex-color toggling instead of hooking into the internal checkbg/check regions, which have a different hierarchy on Classic Ace3. Dropdown: detect the UIDropDownMenuTemplate name-based texture slots and hide them, apply SetTemplate directly on the dropdown frame, replace the arrow button with the AddOnSkins arrow texture, and add hover color callbacks matching the Atlas/AtlasLoot style. Statusbg (Button child without label): strip and re-template so it picks up the ElvUI border instead of the default Blizzard texture. --- AddOnSkins/Skins/AddOns/Ace3.lua | 175 +++++++++++++++++++++---------- 1 file changed, 118 insertions(+), 57 deletions(-) diff --git a/AddOnSkins/Skins/AddOns/Ace3.lua b/AddOnSkins/Skins/AddOns/Ace3.lua index a20f3b80..be45b959 100644 --- a/AddOnSkins/Skins/AddOns/Ace3.lua +++ b/AddOnSkins/Skins/AddOns/Ace3.lua @@ -10,6 +10,115 @@ function AS:Ace3() local oldRegisterAsWidget = AceGUI.RegisterAsWidget local ColorBlind = GetCVarBool('colorblindmode') + local BLANK = (_G.ElvUI and _G.ElvUI[1] and _G.ElvUI[1].media.blankTex) + or [[Interface\Buttons\WHITE8X8]] + + local function SkinAceCheckBox(widget) + local frame = widget.frame + if frame._asSkinned then return end + frame._asSkinned = true + + -- Wipe button-state slots + frame:SetNormalTexture('') + frame:SetPushedTexture('') + frame:SetHighlightTexture('') + frame:SetCheckedTexture(BLANK) + local ct = frame:GetCheckedTexture() + if ct then ct:SetAlpha(0) end + + -- Hide all texture regions (checkbg, check, highlight are Textures on frame) + for i = 1, frame:GetNumRegions() do + local r = select(i, frame:GetRegions()) + if r and r.GetObjectType and r:GetObjectType() ~= 'FontString' then + r:SetTexture(nil) + r:SetAlpha(0) + r:Hide() + end + end + -- Prevent SetType() from restoring them + widget.checkbg.SetTexture = AS.noop + widget.check.SetTexture = AS.noop + widget.highlight.SetTexture = AS.noop + + local border = frame:CreateTexture(nil, 'BACKGROUND') + border:SetPoint('TOPLEFT', frame, 'TOPLEFT', 2, -5) + border:SetSize(13, 13) + border:SetTexture(BLANK) + border:SetVertexColor(unpack(AS.BorderColor)) + frame._aceBorder = border + + local fill = frame:CreateTexture(nil, 'ARTWORK') + fill:SetPoint('TOPLEFT', border, 'TOPLEFT', 1, -1) + fill:SetSize(11, 11) + fill:SetTexture(BLANK) + fill:SetVertexColor(unpack(AS.Color)) + fill:Hide() + frame._aceFill = fill + + local function Update(f) + if f:GetChecked() then + f._aceBorder:SetVertexColor(unpack(AS.Color)) + f._aceFill:Show() + else + f._aceBorder:SetVertexColor(unpack(AS.BorderColor)) + f._aceFill:Hide() + end + end + + frame:HookScript('OnClick', Update) + hooksecurefunc(frame, 'SetChecked', function(f) + C_Timer.After(0, function() Update(f) end) + end) + C_Timer.After(0, function() Update(frame) end) + end + + local function SkinAceDropdown(widget) + local dd = widget.dropdown -- UIDropDownMenuTemplate frame + if dd._asSkinned then return end + dd._asSkinned = true + + -- Strip the three UIDropDownMenu background textures + local name = dd:GetName() + for _, suffix in ipairs({'Left','Middle','Right'}) do + local tex = _G[name..suffix] + if tex then tex:SetTexture(nil) tex:Hide() end + end + + -- Apply backdrop directly on the dropdown frame + AS:SetTemplate(dd) + dd:SetBackdropColor(unpack(AS.BackdropColor)) + + -- Arrow button + local btn = _G[name..'Button'] + if btn then + for _, getter in ipairs({'GetNormalTexture','GetPushedTexture','GetHighlightTexture','GetDisabledTexture'}) do + local tex = btn[getter] and btn[getter](btn) + if tex then tex:SetTexture(nil) tex:Hide() end + end + local arrow = dd:CreateTexture(nil, 'OVERLAY') + arrow:SetTexture([[Interface\AddOns\AddOnSkins\Media\Textures\Arrow]]) + arrow:SetRotation(3.14) + arrow:SetSize(12, 12) + arrow:SetPoint('RIGHT', dd, 'RIGHT', -4, 0) + arrow:SetVertexColor(1, 1, 1) + dd:HookScript('OnEnter', function() + arrow:SetVertexColor(unpack(AS.Color)) + dd:SetBackdropBorderColor(unpack(AS.Color)) + end) + dd:HookScript('OnLeave', function() + arrow:SetVertexColor(1, 1, 1) + dd:SetBackdropBorderColor(unpack(AS.BorderColor)) + end) + end + + -- Label color fix + if widget.label then + hooksecurefunc(widget.label, 'SetTextColor', function(self, r, g, b) + if r == 1 and g == 0.82 and b == 0 then self:SetTextColor(1,1,1,1) end + end) + end + end + AceGUI.RegisterAsWidget = function(self, widget) local TYPE = widget.type if TYPE == 'MultiLineEditBox' then @@ -22,63 +131,9 @@ function AS:Ace3() widget.scrollBG:SetPoint('BOTTOMLEFT', widget.button, 'TOPLEFT') widget.scrollFrame:SetPoint('BOTTOMRIGHT', widget.scrollBG, 'BOTTOMRIGHT', -4, 8) elseif TYPE == 'CheckBox' then - S:CreateBackdrop(widget.checkbg) - S:SetInside(widget.checkbg.backdrop, widget.checkbg, 4, 4) - widget.checkbg.backdrop:SetFrameLevel(widget.checkbg.backdrop:GetFrameLevel() + 1) - - widget.checkbg:SetTexture('') - widget.highlight:SetTexture('') - - if not ColorBlind then - S:SetInside(widget.checkbg.backdrop, widget.checkbg, 5, 5) - - widget.check:SetTexture(AS.NormTex) - - hooksecurefunc(widget.check, "SetDesaturated", function(self, value) - if value == true then - self:SetVertexColor(.6, .6, .6, .8) - else - self:SetVertexColor(unpack(S.Media.valueColor)) - end - end) - - widget.check.SetTexture = S.noop - S:SetInside(widget.check, widget.checkbg.backdrop) - else - S:SetOutside(widget.check, widget.checkbg.backdrop, 3, 3) - end - - widget.checkbg.SetTexture = S.noop - widget.highlight.SetTexture = AS.noop + SkinAceCheckBox(widget) elseif TYPE == 'Dropdown' then - local frame = widget.dropdown - local button = widget.button - local text = widget.text - - S:HandleFrame(frame, true) - frame.backdrop:SetPoint('TOPLEFT', 15, -2) - frame.backdrop:SetPoint("BOTTOMRIGHT", -21, 0) - - S:HandleNextPrevButton(button) - - widget.label:ClearAllPoints() - widget.label:SetPoint('BOTTOMLEFT', frame.backdrop, 'TOPLEFT', 2, 0) - hooksecurefunc(widget.label, 'SetTextColor', function(self, r, g, b, a) - if r == 1 and g == 0.82 and b == 0 then - self:SetTextColor(1, 1, 1, 1) - end - end) - - button:SetSize(20, 20) - button:ClearAllPoints() - button:SetPoint('RIGHT', frame.backdrop, 'RIGHT', -2, 0) - - text:ClearAllPoints() - text:SetJustifyH("RIGHT") - text:SetPoint('RIGHT', button, 'LEFT', -3, 0) - - button:HookScript('PostClick', function(s) S:SetTemplate(s.obj.pullout.frame) end) - widget.button_cover:HookScript('PostClick', function(s) S:SetTemplate(s.obj.pullout.frame) end) + SkinAceDropdown(widget) elseif TYPE == 'LSM30_Font' or TYPE == 'LSM30_Sound' or TYPE == 'LSM30_Border' or TYPE == 'LSM30_Background' or TYPE == 'LSM30_Statusbar' then local frame = widget.frame local button = frame.dropButton @@ -218,8 +273,14 @@ function AS:Ace3() for i = 1, frame:GetNumChildren() do local child = select(i, frame:GetChildren()) - if child:GetObjectType() == 'Button' and child:GetText() then + local childType = child:GetObjectType() + local childText = childType == 'Button' and child:GetText() + if childText and childText ~= '' then S:HandleButton(child) + elseif childType == 'Button' then + -- statusbg: Button without label text, has its own backdrop + S:StripTextures(child) + S:SetTemplate(child) else S:StripTextures(child) end From 3a5d031761a53eb530710f3722199c3ae8da8be7 Mon Sep 17 00:00:00 2001 From: Adams Date: Fri, 10 Apr 2026 10:40:54 +0300 Subject: [PATCH 07/18] Core/API: fix SkinTooltip infinite recursion AS:SkinTooltip was calling itself recursively instead of delegating to S:HandleTooltip. This caused a stack overflow whenever any addon called AS:SkinTooltip (e.g. MinimapButtonButton), making tooltip backdrops disappear entirely - only the border remained visible. Fix: replace the self-referential AS:SkinTooltip call with S:HandleTooltip. --- AddOnSkins/Core/API.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/AddOnSkins/Core/API.lua b/AddOnSkins/Core/API.lua index 2b9a0dd5..6fc383ea 100644 --- a/AddOnSkins/Core/API.lua +++ b/AddOnSkins/Core/API.lua @@ -119,7 +119,7 @@ function AS:Desaturate(frame) end function AS:SkinTooltip(tooltip, scale) - return AS:SkinTooltip(tooltip, scale) + return S:HandleTooltip(tooltip, scale) end function AS:AdjustForTheme(number, offset) @@ -128,4 +128,4 @@ end function AS:EnumObjects(enumFuncs, yieldFunc) return S:EnumObjects(enumFuncs, yieldFunc) -end +end \ No newline at end of file From e7a104085b4648b771c8da7a4f82535046d962f1 Mon Sep 17 00:00:00 2001 From: Adams Date: Fri, 10 Apr 2026 10:41:57 +0300 Subject: [PATCH 08/18] Core/Skins: fix tooltip backdrop fill disappearing after HandleTooltip HandleBlizzardRegions hides .Center (listed in BlizzardRegions), but SetTemplate relies on .Center as the backdrop fill texture. After the hide, the backdrop border was drawn but the background was invisible. Show .Center after SetTemplate and again in the OnShow hook so it survives any subsequent Blizzard resets. --- AddOnSkins/Core/Skins.lua | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/AddOnSkins/Core/Skins.lua b/AddOnSkins/Core/Skins.lua index 9ad5645f..b950901d 100644 --- a/AddOnSkins/Core/Skins.lua +++ b/AddOnSkins/Core/Skins.lua @@ -1675,8 +1675,19 @@ function S:HandleTooltip(tooltip, scale, showHook) S:HandleBlizzardRegions(tooltip) S:SetTemplate(tooltip, nil, nil, nil, nil, nil, nil, nil, true) + -- HandleBlizzardRegions hides .Center (it's in BlizzardRegions list), + -- but SetTemplate needs it as the backdrop fill texture. Re-show it. + if tooltip.Center and not tooltip.Center:IsShown() then + tooltip.Center:Show() + end + if showHook then - tooltip:HookScript('OnShow', function(tt) S:SetTemplate(tt) end) + tooltip:HookScript('OnShow', function(tt) + S:SetTemplate(tt) + if tt.Center and not tt.Center:IsShown() then + tt.Center:Show() + end + end) end if scale then @@ -2300,4 +2311,4 @@ function S:PLAYER_LOGIN() end end -S:RegisterEvent('PLAYER_LOGIN') +S:RegisterEvent('PLAYER_LOGIN') \ No newline at end of file From 86a35f6c1ba8fdb2bdb291b8b34caf30dea159b7 Mon Sep 17 00:00:00 2001 From: Adams Date: Fri, 10 Apr 2026 10:42:13 +0300 Subject: [PATCH 09/18] Atlas: rework skin for TBC Anniversary layout Replace SetTemplate(AtlasFrame) with CreateBackdrop so the backdrop renders at frameLevel-1, below AtlasFrameMapFrame. The previous SetTemplate placed the Center texture at the same level as the map, causing a dark tint over map content. Hide NineSlice (renders at ~level 500, above all content) and PortraitFrameTemplate decorations (portrait, title bg, borders). Hook OnShow to keep NineSlice hidden after Atlas refreshes. Rework SkinCollapseBtn/SkinExpandBtn: replace SkinButton with a plain arrow texture using ARROW_TEX, hide all native button textures via SetAlpha so Blizzard state changes cannot restore them. Hover tints the arrow only. Rework nav arrow hover: use _navHovered flag and hook SetNormalTexture so tint is reapplied if Atlas resets the texture after a floor change. SkinNoGlowBtn: replace AS:SkinButton with ApplyButtonSkin (no gloss, no highlight texture) for SwitchButton and floor series buttons. Add SwitchDropdown to the per-frame loop so both SwitchButton and SwitchDropdown are handled alongside the nav container. --- AddOnSkins/Skins/AddOns/Atlas.lua | 586 ++++++++++++++++++++++++------ 1 file changed, 483 insertions(+), 103 deletions(-) diff --git a/AddOnSkins/Skins/AddOns/Atlas.lua b/AddOnSkins/Skins/AddOns/Atlas.lua index 1850875f..cb7709ec 100644 --- a/AddOnSkins/Skins/AddOns/Atlas.lua +++ b/AddOnSkins/Skins/AddOns/Atlas.lua @@ -1,35 +1,34 @@ --- Atlas skin for AddOnSkins +-- Atlas skin for AddOnSkins. -- Supports the Retail-engine Atlas fork used on TBC Anniversary servers. --- WowStyle1DropdownTemplate dropdowns are skinned manually since SkinDropDownBox --- targets the legacy UIDropDownMenuTemplate structure. local AS = unpack(AddOnSkins) local ARROW_TEX = [[Interface\AddOns\AddOnSkins\Media\Textures\Arrow]] +local ARROW_ROTATION_DOWN = 3.14 local function Safe(func, name) local f = type(name) == "string" and _G[name] or name if f then func(AS, f) end end -local ARROW_ROTATION_DOWN = 3.14 +-- ---------------------------------------------------------------- +-- WowStyle1DropdownTemplate skin +-- ---------------------------------------------------------------- local function SkinWowStyleDropdown(name) local f = _G[name] if not f or f._atlasSkinned then return end f._atlasSkinned = true - -- NineSlice border pieces are child frames, not texture regions - hide them all for _, child in ipairs({f:GetChildren()}) do child:Hide() end - -- Clear texture regions except Arrow, Text, Label for i = 1, f:GetNumRegions() do local r = select(i, f:GetRegions()) if r and r ~= f.Arrow and r ~= f.Text and r ~= f.Label then if r.SetTexture then r:SetTexture(nil) end - if r.SetAtlas then pcall(r.SetAtlas, r, '') end + if r.SetAtlas then pcall(r.SetAtlas, r, '') end r:SetAlpha(0) end end @@ -59,108 +58,480 @@ local function SkinWowStyleDropdown(name) f.Arrow:SetVertexColor(1, 1, 1) if f.SetBackdropBorderColor then f:SetBackdropBorderColor(unpack(AS.BorderColor)) end end) - f:HookScript('OnMouseDown', function() - f.Arrow:SetVertexColor(unpack(AS.Color)) - end) - f:HookScript('OnMouseUp', function() - f.Arrow:SetVertexColor(1, 1, 1) - end) end end +-- ---------------------------------------------------------------- +-- Header layout +-- ---------------------------------------------------------------- + +--[[ + Header layout: + + [Cat v] [Zone v] [Options] [AtlasQuest] [Lock][X] + + Options and AtlasQuest are centred on the visible dropdown control + (not its label). WowStyle1DropdownTemplate has the Label anchored + 14px above the frame top, so the visible control centre sits at + frame.CENTRE - no extra Y offset needed when anchoring to CENTER. + + Close and Lock sit flush in the top-right corner. The icon visible + to their left (AtlasLoot/LFG) is hidden by default and not part of + the normal header flow. +--]] + +-- Apply ElvUI-style dark backdrop + accent border on hover to a button. +-- Hides all existing button textures (UIPanelButtonTemplate gloss, highlight, etc.) +-- without calling StripTextures which breaks Classic backdrop creation. +-- keepIcon: if true, skips hiding regions (for Lock/LFG which have an icon texture). +local function ApplyButtonSkin(btn, keepIcon) + if not btn then return end + -- Clear Blizzard template textures. + if not keepIcon and btn.SetNormalTexture then btn:SetNormalTexture('') end + if btn.SetHighlightTexture then btn:SetHighlightTexture('') end + if btn.SetPushedTexture then btn:SetPushedTexture('') end + if btn.SetDisabledTexture then btn:SetDisabledTexture('') end + -- Hide any remaining region textures (e.g. UIPanelButtonTemplate .Center, gloss). + if not keepIcon then + for i = 1, btn:GetNumRegions() do + local r = select(i, btn:GetRegions()) + if r and r.IsObjectType and r:IsObjectType('Texture') then r:Hide() end + end + end + -- Apply dark backdrop + border. glossTex=false = no statusbar gloss layer. + if not btn.SetBackdrop and BackdropTemplateMixin then + Mixin(btn, BackdropTemplateMixin) + end + AS.Skins:SetTemplate(btn, nil, false) + -- Accent border on hover, plain border on leave. + if not btn._atlasHoverHooked then + btn._atlasHoverHooked = true + btn:HookScript('OnEnter', AS.Skins.SetModifiedBackdrop) + btn:HookScript('OnLeave', AS.Skins.SetOriginalBackdrop) + end +end + +local function SkinNoGlowBtn(btn) + ApplyButtonSkin(btn, false) +end + +local function SkinTransparentBtn(btn) + if not btn then return end + ApplyButtonSkin(btn, true) -- keepIcon: preserve lock/LFG icon region + if btn.iborder then btn.iborder:Hide() end + if btn.oborder then btn.oborder:Hide() end + if btn.SetBackdrop then btn:SetBackdrop(nil) end +end + +local function LayoutHeader(atlas) + local pn = atlas:GetName() + local mf = _G[pn..'MapFrame'] + + -- Dropdowns: keep their original TOPLEFT anchor (set by Atlas Templates.lua). + -- We only override the X to flush left; Y stays as Atlas placed them. + local cat = _G[pn..'DropDownType'] + local zone = _G[pn..'DropDown'] + if cat then + cat:ClearAllPoints() + cat:SetPoint('TOPLEFT', atlas, 'TOPLEFT', 4, -44) + end + if cat and zone then + zone:ClearAllPoints() + zone:SetPoint('LEFT', cat, 'RIGHT', 8, 0) + end + + local ref = zone or cat + + -- Options and Close/Lock: all deferred so GetBottom() returns real values. + local optsBtn = _G[pn..'OptionsButton'] + local closeBtn = _G[pn..'CloseButton'] + local lockBtn = _G[pn..'LockButton'] + + if optsBtn then SkinNoGlowBtn(optsBtn); optsBtn:SetSize(80, 20) end + if closeBtn then closeBtn:SetSize(20, 20) end + if lockBtn then lockBtn:SetSize(20, 20); SkinTransparentBtn(lockBtn) end + + C_Timer.After(0, function() + if not ref then return end + + -- Options/AQ: centre on dropdown control. + -- Dropdown h=24, button h=20 -> bottom of button must be 2px above dd bottom + -- so their centres align: dd_centre = dd_bot+12, btn_centre = btn_bot+10 -> offset=2. + local btnCentreOffset = (ref:GetHeight() - 20) / 2 + if optsBtn then + optsBtn:ClearAllPoints() + optsBtn:SetPoint('LEFT', ref, 'RIGHT', 10, 0) + optsBtn:SetPoint('BOTTOM', ref, 'BOTTOM', 0, btnCentreOffset) + end + + -- Close/Lock/LFG: centred on AtlasFrameTitleContainer (the dark title bar). + local titleBar = _G[pn..'TitleContainer'] or _G[pn..'TitleBg'] + local function TopOffsetHeader(btn) + if titleBar then + local tCentre = titleBar:GetTop() - titleBar:GetHeight() / 2 + return tCentre - atlas:GetTop() + btn:GetHeight() / 2 + else + -- Fallback: use header strip centre + local hH = atlas:GetTop() - (mf and mf:GetTop() or (atlas:GetTop() - 74)) + return -(hH / 2 - btn:GetHeight() / 2) + end + end + if closeBtn then + closeBtn:ClearAllPoints() + closeBtn:SetPoint('RIGHT', atlas, 'RIGHT', -4, 0) + closeBtn:SetPoint('TOP', atlas, 'TOP', 0, TopOffsetHeader(closeBtn)) + end + if lockBtn and closeBtn then + lockBtn:ClearAllPoints() + lockBtn:SetPoint('RIGHT', closeBtn, 'LEFT', -4, 0) + lockBtn:SetPoint('TOP', atlas, 'TOP', 0, TopOffsetHeader(lockBtn)) + end + local lfg = _G[pn..'LFGButton'] + if lfg and lockBtn then + lfg:ClearAllPoints() + lfg:SetPoint('RIGHT', lockBtn, 'LEFT', -4, 0) + lfg:SetPoint('TOP', atlas, 'TOP', 0, TopOffsetHeader(lfg)) + end + end) + + -- AtlasQuest button is a child of AtlasQuestButtonFrame which resets its + -- XML anchor on every Show. Hook AtlasFrame:OnShow to reapply our layout. + local function PositionAQButton() + local aqBtn = _G['AQ_AtlasToggle'] + if not aqBtn or not ref then return end + aqBtn:SetSize(90, 20) + local anchor = optsBtn or ref + local ddCY = ref:GetBottom() + ref:GetHeight() / 2 + local topOff = ddCY - atlas:GetTop() + aqBtn:GetHeight() / 2 + aqBtn:ClearAllPoints() + aqBtn:SetPoint('LEFT', anchor, 'RIGHT', 4, 0) + aqBtn:SetPoint('TOP', atlas, 'TOP', 0, topOff) + + -- Clear Blizzard template textures every call (AQ restores them on Show). + if aqBtn.SetNormalTexture then aqBtn:SetNormalTexture('') end + if aqBtn.SetHighlightTexture then aqBtn:SetHighlightTexture('') end + if aqBtn.SetPushedTexture then aqBtn:SetPushedTexture('') end + for i = 1, aqBtn:GetNumRegions() do + local r = select(i, aqBtn:GetRegions()) + if r and r.IsObjectType and r:IsObjectType('Texture') then r:Hide() end + end + -- Backdrop as a sibling frame so AtlasQuestInsideFrame can't cover it. + if not aqBtn._asBD then + -- Parent to aqBtn so it moves with it; frameLevel 0 within the button + -- ensures it draws behind the button's own FontString regions. + local bd = CreateFrame('Frame', nil, aqBtn, 'BackdropTemplate') + bd:SetAllPoints(aqBtn) + bd:SetFrameLevel(0) + AS.Skins:SetTemplate(bd, nil, false) + aqBtn._asBD = bd + aqBtn:HookScript('OnEnter', function() + bd:SetBackdropBorderColor(unpack(AS.Color)) + end) + aqBtn:HookScript('OnLeave', function() + bd:SetBackdropBorderColor(unpack(AS.BorderColor)) + end) + end + + + -- Block AtlasQuest from restoring textures — hook once per button object. + if not aqBtn._aqTexHooked then + aqBtn._aqTexHooked = true + hooksecurefunc(aqBtn, 'SetNormalTexture', function(self, tex) + if tex ~= '' and not self._aqSkipHook then + self._aqSkipHook = true + self:SetNormalTexture('') + self._aqSkipHook = false + end + end) + hooksecurefunc(aqBtn, 'SetHighlightTexture', function(self, tex) + if tex ~= '' and not self._aqSkipHook then + self._aqSkipHook = true + self:SetHighlightTexture('') + self._aqSkipHook = false + end + end) + end + + -- Re-apply backdrop when AtlasQuestInsideFrame shows (actual parent of the button). + local aqInsideFrame = _G['AtlasQuestInsideFrame'] + if aqInsideFrame and not aqInsideFrame._asSkinHooked then + aqInsideFrame._asSkinHooked = true + aqInsideFrame:HookScript('OnShow', function() + local btn = _G['AQ_AtlasToggle'] + if btn then AS.Skins:SetTemplate(btn, nil, false) end + end) + end + end + -- Initial placement (AQ addon loads after Atlas, so defer slightly). + C_Timer.After(1.5, PositionAQButton) + -- Reapply on every subsequent Show of AtlasFrame. + atlas:HookScript('OnShow', function() + C_Timer.After(0.5, PositionAQButton) + end) +end + +-- ---------------------------------------------------------------- +-- Main skin +-- ---------------------------------------------------------------- + function AS:Atlas(event, addon) local loaded = (C_AddOns and C_AddOns.IsAddOnLoaded and C_AddOns.IsAddOnLoaded('Atlas')) or (_G.IsAddOnLoaded and _G.IsAddOnLoaded('Atlas')) if not loaded then return end - local AtlasFrame = _G['AtlasFrame'] + local AtlasFrame = _G['AtlasFrame'] local AtlasFrameSmall = _G['AtlasFrameSmall'] - if AtlasFrame then - AS:StripTextures(AtlasFrame) - AS:SetTemplate(AtlasFrame) - AS:CreateShadow(AtlasFrame) - if AtlasFrame.Portrait then AtlasFrame.Portrait:Hide() end - if AtlasFrame.PortraitContainer then AtlasFrame.PortraitContainer:Hide() end + -- NineSlice piece names created by PortraitFrameTemplate. + local NINE_SLICE = { + 'TopLeftCorner','TopRightCorner','BottomLeftCorner','BottomRightCorner', + 'TopEdge','BottomEdge','LeftEdge','RightEdge','Center', + 'TopBorder','BottomBorder','LeftBorder','RightBorder', + 'TopLeftBorder','TopRightBorder','BottomLeftBorder','BottomRightBorder', + } + local function HideNineSlice(f) + if not f then return end + for _, piece in ipairs(NINE_SLICE) do + local p = _G[f:GetName()..piece] + if p then p:Hide() end + end end - if AtlasFrameSmall then - AS:StripTextures(AtlasFrameSmall) - AS:SetTemplate(AtlasFrameSmall) - AS:CreateShadow(AtlasFrameSmall) - if AtlasFrameSmall.Portrait then AtlasFrameSmall.Portrait:Hide() end + local function SkinAtlasFrame(f, mapFrameName) + if not f then return end + AS:StripTextures(f) + HideNineSlice(f) + -- CreateBackdrop instead of SetTemplate: the backdrop frame sits at + -- frameLevel-1, below MapFrame children, so it won't tint the map. + -- SetTemplate creates a Center texture on AtlasFrame itself which + -- shares frameLevel with MapFrame and draws over the map image. + if f.SetBackdrop then f:SetBackdrop(nil) end + if f.Center then f.Center:Hide() end + AS:CreateBackdrop(f) + if f.oborder then f.oborder:Hide() end + AS:CreateShadow(f) + if f.Portrait then f.Portrait:Hide() end + if f.PortraitContainer then f.PortraitContainer:Hide() end + -- Hide named PortraitFrameTemplate textures StripTextures may miss. + local pn = f:GetName() + if pn then + for _, suffix in ipairs({ + 'Bg', 'TitleBg', 'Portrait', 'PortraitFrame', 'TitleText', + 'TopTileStreaks', 'BotLeftCorner', 'BotRightCorner', + }) do + local tex = _G[pn..suffix] + if tex and tex.Hide then tex:Hide() end + end + end + local mf = _G[mapFrameName] + if mf then + if mf.NineSlice then mf.NineSlice:Hide() end + if mf.backdrop then mf.backdrop:Hide() end + if mf.SetBackdrop then mf:SetBackdrop(nil) end + end end - Safe(AS.CreateBackdrop, 'AtlasFrameTopInset') - Safe(AS.CreateBackdrop, 'AtlasFrameBottomInset') + SkinAtlasFrame(AtlasFrame, 'AtlasFrameMapFrame') + SkinAtlasFrame(AtlasFrameSmall, 'AtlasFrameSmallMapFrame') + -- Keep NineSlice hidden even if Atlas re-shows it on refresh. + for _, f in ipairs({ AtlasFrame, AtlasFrameSmall }) do + if f and f.NineSlice then + f:HookScript('OnShow', function() f.NineSlice:Hide() end) + end + end + -- Raise SmallMapFrame above AtlasFrameSmall's backdrop so it doesn't bleed through + local smf = _G['AtlasFrameSmallMapFrame'] + if smf and AtlasFrameSmall then + -- backdrop is placed at parent level-1; children default to parent+1. + -- We need smf above the backdrop, so raise it well above AtlasFrameSmall. + smf:SetFrameLevel(AtlasFrameSmall:GetFrameLevel() + 10) + end + + -- Strip inset border textures and create ElvUI backdrops + local function SkinInset(name) + local f = _G[name] + if not f then return end + if f.NineSlice then f.NineSlice:Hide() end + local bg = _G[name..'Bg'] + if bg then bg:Hide() end + AS:CreateBackdrop(f) + end + SkinInset('AtlasFrameTopInset') + SkinInset('AtlasFrameBottomInset') + -- Hide ScrollBox Shadows in BottomInset (they cover the backdrop making it opaque) + do + local bi = _G['AtlasFrameBottomInset'] + if bi then + local function FixBotInset() + if bi.ScrollBox and bi.ScrollBox.Shadows then + bi.ScrollBox.Shadows:Hide() + end + if bi.backdrop then + local r, g, b, a = bi.backdrop:GetBackdropColor() + if r then + bi.backdrop:SetBackdropColor(r + 0.06, g + 0.06, b + 0.06, a) + end + end + end + AtlasFrame:HookScript('OnShow', FixBotInset) + FixBotInset() + end + end + + -- Atlas XML leaves a 6px gap on the right side of TopInset/BottomInset + -- (MapFrame 519 + TopInset 496 = 1015 vs AtlasFrame 1023 - 2px left = 1021). + -- Stretch them to the frame edge so left and right margins are equal (2px). + -- Atlas XML leaves a 6px gap on the right of the right panel (TopInset/ + -- BottomInset). Fix right edge to match the 2px left margin of MapFrame. + -- Atlas XML leaves a 6px gap on the right of the right panel. + -- Anchor the right edge of each inset to atlas BOTTOMRIGHT/TOPRIGHT + -- so the panel fills to within 2px of the frame edge (matching left margin). + local topInset = _G['AtlasFrameTopInset'] + local botInset = _G['AtlasFrameBottomInset'] + local atlMapFrame = _G['AtlasFrameMapFrame'] + if topInset and atlMapFrame and AtlasFrame then + topInset:ClearAllPoints() + topInset:SetPoint('TOPLEFT', atlMapFrame, 'TOPRIGHT', 0, 0) + topInset:SetPoint('TOPRIGHT', AtlasFrame, 'TOPRIGHT', -2, -74) + -- Height stays as-is via content; don't set BOTTOM to avoid breaking scroll + topInset:SetHeight(120) + end + if botInset and topInset and AtlasFrame then + botInset:ClearAllPoints() + botInset:SetPoint('TOPLEFT', topInset, 'BOTTOMLEFT', 0, 0) + botInset:SetPoint('BOTTOMRIGHT', AtlasFrame,'BOTTOMRIGHT', -2, 0) + end Safe(AS.SkinCloseButton, 'AtlasFrameCloseButton') Safe(AS.SkinCloseButton, 'AtlasFrameSmallCloseButton') - -- Switch buttons (UIPanelButtonTemplate) - shown under the map when only one map variant exists - -- (SwitchDropdown is handled separately by SkinWowStyleDropdown) - Safe(AS.SkinButton, 'AtlasFrameSwitchButton') - Safe(AS.SkinButton, 'AtlasFrameSmallSwitchButton') + -- AtlasQuestButtonFrame is a 1x1 mouse-capturing frame anchored TOPRIGHT + -- that would intercept clicks in the corner; its child AQ_AtlasToggle is + -- repositioned into the header row, so the container frame can be hidden. + C_Timer.After(1, function() + local aqf = _G['AtlasQuestButtonFrame'] + if aqf then aqf:EnableMouse(false) end + end) - local closeBtn = _G['AtlasFrameCloseButton'] - local lockBtn = _G['AtlasFrameLockButton'] - if lockBtn and closeBtn then - lockBtn:SetSize(18, 18) - AS:SkinButton(lockBtn) - lockBtn:ClearAllPoints() - lockBtn:SetPoint('RIGHT', closeBtn, 'LEFT', -6, 0) - end + SkinNoGlowBtn(_G['AtlasFrameSwitchButton']) + SkinNoGlowBtn(_G['AtlasFrameSmallSwitchButton']) - local optsBtn = _G['AtlasFrameOptionsButton'] - if optsBtn and lockBtn then - AS:SkinButton(optsBtn) - optsBtn:ClearAllPoints() - optsBtn:SetPoint('RIGHT', lockBtn, 'LEFT', -4, 0) - end + local ARROW_LEFT = math.pi / 2 + local ARROW_RIGHT = -math.pi / 2 - -- Collapse / Expand buttons - local collapseBtn = _G['AtlasFrameCollapseButton'] - if collapseBtn then - collapseBtn:SetSize(20, 20) - AS:SkinButton(collapseBtn) - end - local expandBtn = _G['AtlasFrameSmallExpandButton'] - if expandBtn then - expandBtn:SetSize(20, 20) - AS:SkinButton(expandBtn) + local function SkinCollapseBtn(btn, mf, rotation) + if not btn then return end + btn:SetSize(20, 20) + -- Arrow only, no backdrop, with padding matching other nav buttons + local PAD = 4 + local arrow = btn:CreateTexture(nil, 'OVERLAY', nil, 7) + arrow:SetTexture(ARROW_TEX, 'CLAMPTOBLACKADDITIVE', 'CLAMPTOBLACKADDITIVE') + arrow:SetRotation(rotation) + arrow:SetPoint('TOPLEFT', btn, 'TOPLEFT', PAD, -PAD) + arrow:SetPoint('BOTTOMRIGHT', btn, 'BOTTOMRIGHT', -PAD, PAD) + btn._arrow = arrow + -- Hide all native textures by alpha so WoW restoring them has no effect + local function KillNativeTex() + local getters = {'GetNormalTexture','GetPushedTexture','GetHighlightTexture','GetDisabledTexture'} + for _, g in ipairs(getters) do + local t = btn[g] and btn[g](btn) + if t and t ~= arrow then t:SetAlpha(0) end + end + for i = 1, btn:GetNumRegions() do + local r = select(i, btn:GetRegions()) + if r and r ~= arrow and r.IsObjectType and r:IsObjectType('Texture') then + r:SetAlpha(0) + end + end + end + KillNativeTex() + -- Run on every possible state change + btn:HookScript('OnShow', KillNativeTex) + btn:HookScript('OnMouseDown', KillNativeTex) + btn:HookScript('OnMouseUp', KillNativeTex) + -- Hover: tint arrow only + btn:HookScript('OnEnter', function(self) + self._arrow:SetVertexColor(unpack(AS.Color)) + end) + btn:HookScript('OnLeave', function(self) + self._arrow:SetVertexColor(1, 1, 1) + end) + -- Anchor above AtlasDraw VP (mf+25 > VP mf+20) + if mf then + btn:ClearAllPoints() + btn:SetPoint('BOTTOMRIGHT', mf, 'BOTTOMRIGHT', -6, 6) + btn:SetFrameLevel(mf:GetFrameLevel() + 25) + end end - -- Navigation arrows - PrevMap/NextMap live in PrevNextContainer, not directly on the parent frame - for _, containerName in ipairs({'AtlasFramePrevNextContainer', 'AtlasFrameSmallPrevNextContainer'}) do - local container = _G[containerName] + local mfMain = _G['AtlasFrameMapFrame'] + local mfSmall = _G['AtlasFrameSmallMapFrame'] + -- CollapseButton on large frame: legend visible → arrow points left (collapse) + -- ExpandButton on small frame: legend hidden → arrow points right (expand) + SkinCollapseBtn(_G['AtlasFrameCollapseButton'], mfMain, ARROW_LEFT) + SkinCollapseBtn(_G['AtlasFrameSmallExpandButton'], mfSmall, ARROW_RIGHT) + + for _, names in ipairs({ + { 'AtlasFramePrevNextContainer', 'AtlasFrameSwitchButton', 'AtlasFrameSwitchDropdown', 'AtlasFrameMapFrame', AtlasFrame }, + { 'AtlasFrameSmallPrevNextContainer', 'AtlasFrameSmallSwitchButton', 'AtlasFrameSmallSwitchDropdown', 'AtlasFrameSmallMapFrame', AtlasFrameSmall }, + }) do + local container = _G[names[1]] + local switchBtn = _G[names[2]] + local switchDD = _G[names[3]] + local mapFrame = _G[names[4]] + local atlasFrame = names[5] + local navPrev = container and container.PrevMap + local navNext = container and container.NextMap if container then - if container.PrevMap then AS:SkinArrowButton(container.PrevMap, 'left') end - if container.NextMap then AS:SkinArrowButton(container.NextMap, 'right') end + local prev = navPrev + local next = navNext + local function SkinNav(btn, dir) + if not btn then return end + AS:SkinArrowButton(btn, dir) + btn:SetSize(24, 24) + btn._navHovered = false + local function applyTint() + if not btn._navHovered then return end + local t = btn:GetNormalTexture() + if t then t:SetVertexColor(unpack(AS.Color)) end + if btn.SetBackdropBorderColor then btn:SetBackdropBorderColor(unpack(AS.Color)) end + end + btn:HookScript('OnEnter', function(self) + self._navHovered = true + applyTint() + end) + btn:HookScript('OnLeave', function(self) + self._navHovered = false + local t = self:GetNormalTexture() + if t then t:SetVertexColor(1, 1, 1) end + if self.SetBackdropBorderColor then self:SetBackdropBorderColor(unpack(AS.BorderColor)) end + end) + -- Re-apply tint whenever Atlas resets the texture. + hooksecurefunc(btn, 'SetNormalTexture', function() applyTint() end) + end + SkinNav(prev, 'left') + SkinNav(next, 'right') end + end - -- Icon buttons with .Icon parentKey (AdventureJournalMap, AdventureJournal, AtlasLoot) - -- SkinIconButton saves the texture BEFORE stripping, then restores it for _, parent in ipairs({AtlasFrame, AtlasFrameSmall}) do if parent then - local aj = parent.AdventureJournalMap or _G[parent:GetName()..'AdventureJournalMapButton'] - local ej = parent.AdventureJournal or _G[parent:GetName()..'AdventureJournalButton'] - local al = parent.AtlasLoot or _G[parent:GetName()..'AtlasLootButton'] + local pn = parent:GetName() + local aj = parent.AdventureJournalMap or _G[pn..'AdventureJournalMapButton'] + local ej = parent.AdventureJournal or _G[pn..'AdventureJournalButton'] + local al = parent.AtlasLoot or _G[pn..'AtlasLootButton'] if aj then AS:SkinIconButton(aj) end if ej then AS:SkinIconButton(ej) end if al then AS:SkinIconButton(al) end - -- LFG button uses a named Texture ($parentTexture), not parentKey="Icon" - -- Use Strip=false so the eye texture is never cleared by StripTextures; - -- SkinButton still clears NormalTexture/HighlightTexture via SetNormalTexture(''). - local lfg = _G[parent:GetName()..'LFGButton'] + -- LFG: skin and resize; position is handled in LayoutHeader deferred block. + local lfg = _G[pn..'LFGButton'] if lfg then - AS:SkinButton(lfg) -- no strip: preserve the ARTWORK-layer eye texture + SkinTransparentBtn(lfg) local eyeTex = lfg:GetName() and _G[lfg:GetName()..'Texture'] - if eyeTex then - eyeTex:SetAlpha(1) - AS:SetInside(eyeTex, lfg) - end + if eyeTex then eyeTex:SetAlpha(1); AS:SetInside(eyeTex, lfg) end + lfg:SetSize(20, 20) end end end @@ -169,45 +540,54 @@ function AS:Atlas(event, addon) if search and AtlasFrame then AS:SkinEditBox(search) search:ClearAllPoints() - search:SetPoint('BOTTOMRIGHT', AtlasFrame, 'BOTTOMRIGHT', -10, 8) search:SetSize(180, 20) + -- Align vertically with Options/AtlasQuest buttons in the header row. + -- Buttons bottom = atlas.top - 66; search is 20px tall so same bottom. + -- Keep X position on the right side, just move Y up to header level. + C_Timer.After(0.5, function() + local ref2 = _G['AtlasFrameDropDown'] + if ref2 and AtlasFrame then + local ddCY = ref2:GetBottom() + ref2:GetHeight() / 2 + local topOff = ddCY - AtlasFrame:GetTop() + search:GetHeight() / 2 + search:ClearAllPoints() + search:SetPoint('RIGHT', AtlasFrame, 'RIGHT', -10, 0) + search:SetPoint('TOP', AtlasFrame, 'TOP', 0, topOff) + end + end) end + -- Skin + reposition dropdowns, then lay out the full header row C_Timer.After(0, function() - SkinWowStyleDropdown('AtlasFrameDropDownType') - SkinWowStyleDropdown('AtlasFrameDropDown') - SkinWowStyleDropdown('AtlasFrameSmallDropDownType') - SkinWowStyleDropdown('AtlasFrameSmallDropDown') - - if AtlasFrame then - for _, child in ipairs({AtlasFrame:GetChildren()}) do - local n = child.GetName and child:GetName() - if n and n:find('SwitchDropdown') then - SkinWowStyleDropdown(n) + for _, name in ipairs({ + 'AtlasFrameDropDownType', 'AtlasFrameDropDown', + 'AtlasFrameSmallDropDownType', 'AtlasFrameSmallDropDown', + }) do + SkinWowStyleDropdown(name) + end + + for _, f in ipairs({AtlasFrame, AtlasFrameSmall}) do + if f then + for _, child in ipairs({f:GetChildren()}) do + local n = child.GetName and child:GetName() + if n and n:find('SwitchDropdown') then SkinWowStyleDropdown(n) end end end end + + if AtlasFrame then LayoutHeader(AtlasFrame) end + if AtlasFrameSmall then LayoutHeader(AtlasFrameSmall) end end) - -- Boss/NPC map buttons are created lazily on each map refresh. - -- Hook Atlas_MapRefresh to skin new buttons as they appear. local function SkinBossButtons() - -- AS:SkinButton doesn't set isSkinned, so use a custom flag to avoid - -- re-skinning (and accumulating hooks) on every Atlas_MapRefresh call. - local i, btn = 1 - btn = _G['AtlasMapBossButton'..i] - while btn do - if not btn._asSkinned then btn._asSkinned = true AS:SkinButton(btn) end - i = i + 1 - btn = _G['AtlasMapBossButton'..i] - end - i = 1 - btn = _G['AtlasMapBossButtonS'..i] - while btn do - if not btn._asSkinned then btn._asSkinned = true AS:SkinButton(btn) end - i = i + 1 - btn = _G['AtlasMapBossButtonS'..i] + local function SkinSeries(prefix) + local i, btn = 1, _G[prefix..1] + while btn do + if not btn._asSkinned then btn._asSkinned = true; btn.isSkinned = nil; SkinNoGlowBtn(btn) end + i = i + 1; btn = _G[prefix..i] + end end + SkinSeries('AtlasMapBossButton') + SkinSeries('AtlasMapBossButtonS') end if _G['Atlas_MapRefresh'] then @@ -215,4 +595,4 @@ function AS:Atlas(event, addon) end end -AS:RegisterSkin('Atlas', AS.Atlas) +AS:RegisterSkin('Atlas', AS.Atlas) \ No newline at end of file From 178044bff2ec85aef985b43b316ca1d350cb9ed2 Mon Sep 17 00:00:00 2001 From: Adams Date: Fri, 10 Apr 2026 14:49:30 +0300 Subject: [PATCH 10/18] Atlas, AtlasLoot: rework skins for TBC Anniversary Atlas: - SkinTransparentBtn: border-only hover (no fill) for Lock/LFG buttons - Expose ApplyAtlasButtonSkin for use by dependent skins (AtlasQuest) - BottomInset: skin WowClassicScrollBar via HandleTrimScrollBar, stretch ScrollBox and scrollbar to fill the inset dynamically - BottomInset: fix ScrollBox/scrollbar anchor cycle (anchor both to bi) AtlasLoot (new skin): - Full ElvUI-style skin for AtlasLootClassic (sliccer TBC Anniversary fork) - Dropdown widgets: backdrop lock against AtlasLoot color resets, custom arrow texture, hover border highlight - Select widgets (difficulty/boss/extra): text color hover, override OnEnterButton/OnLeaveButton to survive SetScript calls, no tooltips - Title bar: match Atlas font/color, tighten layout, reposition close and info buttons - Content frame: skin background textures, nav/icon buttons, search box, map button tex coord fix, class filter popup - Footer (AtlasLoot_BiS): skin checkboxes via ElvUI path, expand height, reset child scale - Game version popup: skin selection frame and buttons - Atlas integration: add AtlasLoot button next to AtlasQuest that opens AtlasLoot on the instance matching the current Atlas map selection --- AddOnSkins/Skins/AddOns/Atlas.lua | 92 +++-- AddOnSkins/Skins/AddOns/AtlasLoot.lua | 471 +++++++++++++++++++++----- 2 files changed, 464 insertions(+), 99 deletions(-) diff --git a/AddOnSkins/Skins/AddOns/Atlas.lua b/AddOnSkins/Skins/AddOns/Atlas.lua index cb7709ec..f7000f53 100644 --- a/AddOnSkins/Skins/AddOns/Atlas.lua +++ b/AddOnSkins/Skins/AddOns/Atlas.lua @@ -114,13 +114,35 @@ end local function SkinNoGlowBtn(btn) ApplyButtonSkin(btn, false) end +AS.ApplyAtlasButtonSkin = ApplyButtonSkin -- exposed for dependent skins (AtlasQuest) local function SkinTransparentBtn(btn) if not btn then return end - ApplyButtonSkin(btn, true) -- keepIcon: preserve lock/LFG icon region - if btn.iborder then btn.iborder:Hide() end - if btn.oborder then btn.oborder:Hide() end - if btn.SetBackdrop then btn:SetBackdrop(nil) end + -- Kill glow/highlight: SetHighlightTexture('') may not work on XML-defined textures, + -- so also hide the texture object directly. + if btn.SetHighlightTexture then btn:SetHighlightTexture('') end + if btn.SetPushedTexture then btn:SetPushedTexture('') end + if btn.SetDisabledTexture then btn:SetDisabledTexture('') end + local ht = btn.GetHighlightTexture and btn:GetHighlightTexture() + if ht then ht:SetTexture(nil); ht:Hide() end + -- Apply backdrop for border-only hover (no fill, border hidden until hover). + if not btn.SetBackdrop and BackdropTemplateMixin then Mixin(btn, BackdropTemplateMixin) end + AS.Skins:SetTemplate(btn, nil, false) + btn:SetBackdropColor(0, 0, 0, 0) + btn:SetBackdropBorderColor(0, 0, 0, 0) + if btn.iborder then btn.iborder:Hide() end + if btn.oborder then btn.oborder:Hide() end + -- Border-only hover (no glow). + if not btn._transparentHoverHooked then + btn._transparentHoverHooked = true + btn._atlasHoverHooked = true + btn:HookScript('OnEnter', function(self) + self:SetBackdropBorderColor(unpack(AS.Color)) + end) + btn:HookScript('OnLeave', function(self) + self:SetBackdropBorderColor(0, 0, 0, 0) + end) + end end local function LayoutHeader(atlas) @@ -148,8 +170,12 @@ local function LayoutHeader(atlas) local lockBtn = _G[pn..'LockButton'] if optsBtn then SkinNoGlowBtn(optsBtn); optsBtn:SetSize(80, 20) end - if closeBtn then closeBtn:SetSize(20, 20) end - if lockBtn then lockBtn:SetSize(20, 20); SkinTransparentBtn(lockBtn) end + if closeBtn then closeBtn:SetSize(32, 32) end + if lockBtn then lockBtn:SetSize(20, 20); SkinTransparentBtn(lockBtn) end + local lfgBtn = _G[pn..'LFGButton'] + if lfgBtn then lfgBtn:SetSize(20, 20); SkinTransparentBtn(lfgBtn) end + -- Add proper border-highlight hover to close, lock, lfg. + C_Timer.After(0, function() if not ref then return end @@ -178,12 +204,12 @@ local function LayoutHeader(atlas) end if closeBtn then closeBtn:ClearAllPoints() - closeBtn:SetPoint('RIGHT', atlas, 'RIGHT', -4, 0) + closeBtn:SetPoint('RIGHT', atlas, 'RIGHT', 0, 0) closeBtn:SetPoint('TOP', atlas, 'TOP', 0, TopOffsetHeader(closeBtn)) end if lockBtn and closeBtn then lockBtn:ClearAllPoints() - lockBtn:SetPoint('RIGHT', closeBtn, 'LEFT', -4, 0) + lockBtn:SetPoint('RIGHT', closeBtn, 'LEFT', 4, 0) lockBtn:SetPoint('TOP', atlas, 'TOP', 0, TopOffsetHeader(lockBtn)) end local lfg = _G[pn..'LFGButton'] @@ -262,12 +288,9 @@ local function LayoutHeader(atlas) end) end end - -- Initial placement (AQ addon loads after Atlas, so defer slightly). - C_Timer.After(1.5, PositionAQButton) - -- Reapply on every subsequent Show of AtlasFrame. - atlas:HookScript('OnShow', function() - C_Timer.After(0.5, PositionAQButton) - end) + -- AQ loads after Atlas; defer initial placement and reapply on every Show. + C_Timer.After(1, PositionAQButton) + atlas:HookScript('OnShow', function() C_Timer.After(0, PositionAQButton) end) end -- ---------------------------------------------------------------- @@ -362,30 +385,48 @@ function AS:Atlas(event, addon) do local bi = _G['AtlasFrameBottomInset'] if bi then + local sbSkinned = false local function FixBotInset() if bi.ScrollBox and bi.ScrollBox.Shadows then bi.ScrollBox.Shadows:Hide() end if bi.backdrop then + -- Lighten slightly: ScrollBox content bleeds through at default alpha. local r, g, b, a = bi.backdrop:GetBackdropColor() if r then bi.backdrop:SetBackdropColor(r + 0.06, g + 0.06, b + 0.06, a) end end + -- Skin the WowClassicScrollBar and stretch ScrollBox to fill botInset. + if not sbSkinned and bi.ScrollBox then + local sb + for _, child in ipairs({bi:GetChildren()}) do + if child.Track and child.GetThumb then + sb = child + break + end + end + if sb then + AS.Skins:HandleTrimScrollBar(sb) + -- Stretch both ScrollBox and scrollbar to fill botInset. + local sbW = sb:GetWidth() + sb:ClearAllPoints() + sb:SetPoint('TOPRIGHT', bi, 'TOPRIGHT', -2, -5) + sb:SetPoint('BOTTOMRIGHT', bi, 'BOTTOMRIGHT', -2, 5) + bi.ScrollBox:ClearAllPoints() + bi.ScrollBox:SetPoint('TOPLEFT', bi, 'TOPLEFT', 15, -5) + bi.ScrollBox:SetPoint('BOTTOMRIGHT', bi, 'BOTTOMRIGHT', -(sbW+4), 5) + sbSkinned = true + end + end end AtlasFrame:HookScript('OnShow', FixBotInset) FixBotInset() end end - -- Atlas XML leaves a 6px gap on the right side of TopInset/BottomInset - -- (MapFrame 519 + TopInset 496 = 1015 vs AtlasFrame 1023 - 2px left = 1021). - -- Stretch them to the frame edge so left and right margins are equal (2px). - -- Atlas XML leaves a 6px gap on the right of the right panel (TopInset/ - -- BottomInset). Fix right edge to match the 2px left margin of MapFrame. - -- Atlas XML leaves a 6px gap on the right of the right panel. - -- Anchor the right edge of each inset to atlas BOTTOMRIGHT/TOPRIGHT - -- so the panel fills to within 2px of the frame edge (matching left margin). + -- Atlas XML leaves a 6px gap on the right of TopInset/BottomInset. + -- Anchor their right edge to the frame edge to match the 2px left margin. local topInset = _G['AtlasFrameTopInset'] local botInset = _G['AtlasFrameBottomInset'] local atlMapFrame = _G['AtlasFrameMapFrame'] @@ -404,6 +445,12 @@ function AS:Atlas(event, addon) Safe(AS.SkinCloseButton, 'AtlasFrameCloseButton') Safe(AS.SkinCloseButton, 'AtlasFrameSmallCloseButton') + do + local cb = _G['AtlasFrameCloseButton'] + if cb then cb:SetSize(32, 32) end + local cbs = _G['AtlasFrameSmallCloseButton'] + if cbs then cbs:SetSize(32, 32) end + end -- AtlasQuestButtonFrame is a 1x1 mouse-capturing frame anchored TOPRIGHT -- that would intercept clicks in the corner; its child AQ_AtlasToggle is @@ -456,7 +503,6 @@ function AS:Atlas(event, addon) btn:HookScript('OnLeave', function(self) self._arrow:SetVertexColor(1, 1, 1) end) - -- Anchor above AtlasDraw VP (mf+25 > VP mf+20) if mf then btn:ClearAllPoints() btn:SetPoint('BOTTOMRIGHT', mf, 'BOTTOMRIGHT', -6, 6) diff --git a/AddOnSkins/Skins/AddOns/AtlasLoot.lua b/AddOnSkins/Skins/AddOns/AtlasLoot.lua index d0a0aafa..3261038e 100644 --- a/AddOnSkins/Skins/AddOns/AtlasLoot.lua +++ b/AddOnSkins/Skins/AddOns/AtlasLoot.lua @@ -7,7 +7,27 @@ local AS = unpack(AddOnSkins) local skinned = false local footerSkinned = false -local ddHooked = {} + +-- Dark backdrop + accent border on hover; no gloss, no highlight texture. +-- Mirrors ApplyButtonSkin in Atlas.lua for consistent button style. +local function SkinNoGlowBtn(btn) + if not btn then return end + if btn.SetNormalTexture then btn:SetNormalTexture('') end + if btn.SetHighlightTexture then btn:SetHighlightTexture('') end + if btn.SetPushedTexture then btn:SetPushedTexture('') end + if btn.SetDisabledTexture then btn:SetDisabledTexture('') end + for i = 1, btn:GetNumRegions() do + local r = select(i, btn:GetRegions()) + if r and r.IsObjectType and r:IsObjectType('Texture') then r:Hide() end + end + if not btn.SetBackdrop and BackdropTemplateMixin then Mixin(btn, BackdropTemplateMixin) end + AS.Skins:SetTemplate(btn, nil, false) + if not btn._alHoverHooked then + btn._alHoverHooked = true + btn:HookScript('OnEnter', AS.Skins.SetModifiedBackdrop) + btn:HookScript('OnLeave', AS.Skins.SetOriginalBackdrop) + end +end local function SkinALDropDownPopup() local i = 1 @@ -15,28 +35,16 @@ local function SkinALDropDownPopup() local f = _G['AtlasLoot-DropDown-CatFrame'..i] if not f then break end if f:IsShown() then - -- Re-apply template (frame is reused from cache, backdrop gets reset by AtlasLoot) + -- Re-apply template every open: SetBackdrop is C-func, can't be hooked. + -- Do NOT lock SetBackdropColor - categories use bgColor intentionally. AS:SetTemplate(f) - -- Hook SetBackdropColor once per frame object - if not ddHooked[f] then - ddHooked[f] = true - hooksecurefunc(f, 'SetBackdropColor', function(self, r, g, b, a) - local br, bg, bb, ba = unpack(AS.BackdropColor) - if r ~= br or g ~= bg or b ~= bb then - self:SetBackdropColor(br, bg, bb, ba) - end - end) - end if f.buttons then for _, btn in ipairs(f.buttons) do if not btn._asSkinned then btn._asSkinned = true btn:SetHighlightTexture(AS.NormTex or [[Interface\Buttons\UI-Listbox-Highlight]]) local hl = btn:GetHighlightTexture() - if hl then - hl:SetVertexColor(unpack(AS.Color)) - hl:SetAlpha(0.35) - end + if hl then hl:SetVertexColor(unpack(AS.Color)); hl:SetAlpha(0.35) end end end end @@ -50,11 +58,20 @@ local function SkinALDropDown(widget) local f = widget.frame AS:StripTextures(f) AS:SetTemplate(f) - -- Lock backdrop color - AtlasLoot resets it on every SetSelected + if f.title then + f.title:ClearAllPoints() + f.title:SetPoint('BOTTOMLEFT', f, 'TOPLEFT', 0, 4) + end + -- Lock backdrop color - AtlasLoot resets it on every SetSelected. + -- Guard against recursion: hook calls SetBackdropColor which re-triggers hook. + local _lockingBG = false hooksecurefunc(f, 'SetBackdropColor', function(self, r, g, b, a) + if _lockingBG then return end local br, bg, bb, ba = unpack(AS.BackdropColor) if r ~= br or g ~= bg or b ~= bb then + _lockingBG = true self:SetBackdropColor(br, bg, bb, ba) + _lockingBG = false end end) @@ -91,17 +108,64 @@ local function SkinALDropDown(widget) -- btn sits on top of f and blocks its OnEnter - hook btn too btn:HookScript('OnEnter', onEnter) btn:HookScript('OnLeave', onLeave) - -- Skin popup on open - btn:HookScript('OnClick', function() + -- Skin popup on open (hook the main frame, not the inner btn) + f:HookScript('OnClick', function() C_Timer.After(0, SkinALDropDownPopup) end) end end +local selectBtnHooked = {} +local function SkinALSelectBtn(btn) + if not btn or selectBtnHooked[btn] then return end + selectBtnHooked[btn] = true + -- Kill textures via alpha - hooks on Set* would affect other button types reusing same cache. + local hl = btn.GetHighlightTexture and btn:GetHighlightTexture() + if hl then hl:SetAlpha(0) end + local ct = btn.GetCheckedTexture and btn:GetCheckedTexture() + if ct then ct:SetAlpha(0) end + if btn.label then + btn.label:SetTextColor(1, 1, 1) + local function applyState() + if btn:GetChecked() then + btn.label:SetTextColor(unpack(AS.Color)) + else + btn.label:SetTextColor(1, 1, 1) + end + end + btn:HookScript('OnEnter', function() btn.label:SetTextColor(unpack(AS.Color)) end) + btn:HookScript('OnLeave', function() applyState() end) + -- Use OnShow to catch state set by UpdateScroll when button is reused from cache. + btn:HookScript('OnShow', function() C_Timer.After(0, applyState) end) + end +end + local function SkinALSelect(widget) if not widget or not widget.frame then return end - AS:StripTextures(widget.frame) - AS:SetTemplate(widget.frame) + local f = widget.frame + AS:StripTextures(f) + AS:SetTemplate(f) + if f.scrollbar then AS:SkinScrollBar(f.scrollbar) end + -- Wrap OnEnterButton/OnLeaveButton on the widget object so SetScript can't kill our hook. + widget.OnEnterButton = function(btn) + if btn.label then btn.label:SetTextColor(unpack(AS.Color)) end + end + widget.OnLeaveButton = function(btn) + if btn.label then + if btn:GetChecked() then + btn.label:SetTextColor(unpack(AS.Color)) + else + btn.label:SetTextColor(1, 1, 1) + end + end + end + -- Skin existing buttons and hook UpdateContent to catch future ones. + for _, btn in ipairs(widget.buttons or {}) do SkinALSelectBtn(btn) end + local origUpdate = widget.UpdateContent + widget.UpdateContent = function(self, ...) + origUpdate(self, ...) + for _, btn in ipairs(self.buttons or {}) do SkinALSelectBtn(btn) end + end end local function SkinCheckBoxElvUI(cb) @@ -159,14 +223,19 @@ end local function SkinCheckButtonsInFrame(parent) if not parent then return end + -- footer -> centerGroup -> CheckButtons (two levels max) for _, child in ipairs({parent:GetChildren()}) do if child.GetObjectType and child:GetObjectType() == 'CheckButton' then - -- Reset scale before skinning - AtlasLoot BiS footer uses SetScale(1.5) - -- which causes the skinned checkbox to overflow the footer bounds if child:GetScale() ~= 1 then child:SetScale(1) end SkinCheckBoxElvUI(child) + else + for _, grandchild in ipairs({child:GetChildren()}) do + if grandchild.GetObjectType and grandchild:GetObjectType() == 'CheckButton' then + if grandchild:GetScale() ~= 1 then grandchild:SetScale(1) end + SkinCheckBoxElvUI(grandchild) + end + end end - SkinCheckButtonsInFrame(child) end end @@ -176,25 +245,23 @@ local function SkinFooter() if not footer then return end footerSkinned = true AS:StripTextures(footer) - -- No full border on footer - it sits directly under the main frame whose - -- bottom border already acts as the separator. - footer:SetBackdrop({ - bgFile = [[Interface\Buttons\WHITE8X8]], - tileSize = 8, - tile = true, - }) - footer:SetBackdropColor(unpack(AS.BackdropColor)) + AS:CreateBackdrop(footer) + -- Footer sits flush under the main frame; suppress the top border so it + -- doesn't double up with the main frame's bottom border. + if footer.Backdrop then + footer.Backdrop:SetBackdropBorderColor(0, 0, 0, 0) + end SkinCheckButtonsInFrame(footer) -- centerGroup is 44px tall designed for scale(1.5) children inside a 24px footer. -- After resetting child scale to 1, expand footer and re-center the group. - footer:SetHeight(52) + footer:SetHeight(60) local cg = ({footer:GetChildren()})[1] if cg and cg:GetNumPoints() > 0 then local _, rel = cg:GetPoint() - cg:SetHeight(34) + cg:SetHeight(44) cg:ClearAllPoints() - cg:SetPoint('CENTER', rel or footer, 'CENTER', 0, -2) + cg:SetPoint('CENTER', rel or footer, 'CENTER', 0, 0) end -- Move footer 1px below main frame so its top border sits just under the main frame's border. local parentFrame = _G['AtlasLoot_GUI-Frame'] @@ -242,6 +309,26 @@ local function SkinGameVersionPopup(gvButton) sf._asSkinned = true AS:StripTextures(sf) AS:SetTemplate(sf) + for _, btn in ipairs(sf.buttons or {}) do + local hl = btn.GetHighlightTexture and btn:GetHighlightTexture() + if hl then hl:SetAlpha(0) end + AS:CreateBackdrop(btn) + local bd = btn.backdrop or btn.Backdrop + if bd then + btn:HookScript('OnEnter', function(self) + local b = self.backdrop or self.Backdrop + if b then b:SetBackdropBorderColor(unpack(AS.Color)) end + end) + btn:HookScript('OnLeave', function(self) + local b = self.backdrop or self.Backdrop + if b then b:SetBackdropBorderColor(unpack(AS.BorderColor)) end + end) + end + if btn.texture then + AS:SkinTexture(btn.texture) + AS:SetInside(btn.texture, btn.backdrop or btn.Backdrop or btn) + end + end end) end @@ -254,15 +341,87 @@ local function SkinAtlasLoot() local AL = AtlasLoot local GUI = AL and AL.GUI + -- contentFrame is 510px tall anchored at -70 from top = 580px used; frame is 600px → 20px gap at bottom. + ALFrame:SetHeight(ALFrame:GetHeight() - 16) AS:SkinFrame(ALFrame) - if ALFrame.titleFrame then AS:StripTextures(ALFrame.titleFrame) end + if ALFrame.titleFrame then + AS:StripTextures(ALFrame.titleFrame) + if ALFrame.titleFrame.SetBackdrop then ALFrame.titleFrame:SetBackdrop(nil) end + -- Tighten titleFrame: remove top gap, reduce height to match Atlas titlebar. + ALFrame.titleFrame:ClearAllPoints() + ALFrame.titleFrame:SetPoint('TOPLEFT', ALFrame, 'TOPLEFT', 10, -2) + ALFrame.titleFrame:SetPoint('TOPRIGHT', ALFrame, 'TOPRIGHT', -30, -2) + ALFrame.titleFrame:SetHeight(18) + -- Match Atlas title style: same font object as PortraitFrameTemplate TitleText. + if ALFrame.titleFrame.text then + local ref = AtlasFrame and AtlasFrame.TitleText + if ref then + local font, size, flags = ref:GetFont() + if font then ALFrame.titleFrame.text:SetFont(font, size, flags) end + local r, g, b, a = ref:GetTextColor() + ALFrame.titleFrame.text:SetTextColor(r, g, b, a) + else + ALFrame.titleFrame.text:SetFontObject(GameFontNormal) + end + end + if ALFrame.titleFrame.version then + local ver = ALFrame.titleFrame.version + ver:ClearAllPoints() + ver:SetPoint('LEFT', ALFrame.titleFrame, 'LEFT', 8, 0) + ver:SetJustifyH('LEFT') + end + end local closeBtn = _G['AtlasLoot_GUI-Frame-CloseButton'] - if closeBtn then AS:SkinCloseButton(closeBtn) end - - -- Info button + if closeBtn then + AS:SkinCloseButton(closeBtn) + closeBtn:SetSize(32, 32) + end if ALFrame.titleFrame and ALFrame.titleFrame.infoButton then - AS:SkinButton(ALFrame.titleFrame.infoButton) + local ib = ALFrame.titleFrame.infoButton + if ib.SetHighlightTexture then ib:SetHighlightTexture('') end + if ib.SetPushedTexture then ib:SetPushedTexture('') end + if ib.SetDisabledTexture then ib:SetDisabledTexture('') end + local ht = ib.GetHighlightTexture and ib:GetHighlightTexture() + if ht then ht:SetTexture(nil); ht:Hide() end + + ib:SetSize(16, 16) + -- Texture is anchored TOPLEFT in XML, not SetAllPoints — fix after resize. + local ibtex = ib.texture + if ibtex then + ibtex:SetAllPoints(ib) + -- Block OnMouseDown offset script. + ib:HookScript('OnMouseDown', function() ibtex:SetAllPoints(ib) end) + ib:HookScript('OnMouseUp', function() ibtex:SetAllPoints(ib) end) + end + do + local bd = CreateFrame('Frame', nil, ib, BackdropTemplateMixin and 'BackdropTemplate' or nil) + bd:SetAllPoints(ib) + bd:SetFrameLevel(max(0, ib:GetFrameLevel() - 1)) + AS.Skins:SetTemplate(bd, nil, false) + bd:SetBackdropColor(0, 0, 0, 0) + bd:SetBackdropBorderColor(0, 0, 0, 0) + ib:HookScript('OnEnter', function() bd:SetBackdropBorderColor(unpack(AS.Color)) end) + ib:HookScript('OnLeave', function() bd:SetBackdropBorderColor(0, 0, 0, 0) end) + end end + C_Timer.After(0, function() + local tf = ALFrame.titleFrame + if not tf then return end + local tCentreY = tf:GetTop() - tf:GetHeight() / 2 + local function yOff(btn) + return tCentreY - ALFrame:GetTop() + btn:GetHeight() / 2 + end + if closeBtn then + closeBtn:ClearAllPoints() + closeBtn:SetPoint('RIGHT', ALFrame, 'RIGHT', 0, 0) + closeBtn:SetPoint('TOP', ALFrame, 'TOP', 0, yOff(closeBtn)) + end + local ib = tf.infoButton + if ib and closeBtn then + ib:ClearAllPoints() + ib:SetPoint('CENTER', closeBtn, 'CENTER', -(closeBtn:GetWidth()/2 + 2 + ib:GetWidth()/2), 0) + end + end) if GUI and GUI.frame then SkinALDropDown(GUI.frame.moduleSelect) @@ -274,17 +433,22 @@ local function SkinAtlasLoot() -- Game version button local gvBtn = GUI.frame.gameVersionButton if gvBtn then - -- Hide the box lines, apply a proper template if gvBtn.Box then - for _, line in ipairs(gvBtn.Box) do - line:Hide() - end + for _, line in ipairs(gvBtn.Box) do line:Hide() end end + local gvHl = gvBtn.GetHighlightTexture and gvBtn:GetHighlightTexture() + if gvHl then gvHl:SetAlpha(0) end AS:CreateBackdrop(gvBtn) - if gvBtn.texture or GUI.frame.gameVersionLogo then - local tex = gvBtn.texture or GUI.frame.gameVersionLogo - AS:SetInside(tex, gvBtn.Backdrop or gvBtn) - end + gvBtn:HookScript('OnEnter', function(self) + local bd = self.backdrop or self.Backdrop + if bd and bd.SetBackdropBorderColor then bd:SetBackdropBorderColor(unpack(AS.Color)) end + end) + gvBtn:HookScript('OnLeave', function(self) + local bd = self.backdrop or self.Backdrop + if bd and bd.SetBackdropBorderColor then bd:SetBackdropBorderColor(unpack(AS.BorderColor)) end + end) + local tex = gvBtn.texture or GUI.frame.gameVersionLogo + if tex then AS:SetInside(tex, gvBtn.Backdrop or gvBtn) end SkinGameVersionPopup(gvBtn) end end @@ -305,36 +469,86 @@ local function SkinAtlasLoot() end end - if contentFrame.nextPageButton then AS:SkinArrowButton(contentFrame.nextPageButton) end - if contentFrame.prevPageButton then AS:SkinArrowButton(contentFrame.prevPageButton) end - if contentFrame.modelButton then AS:SkinButton(contentFrame.modelButton) end - if contentFrame.soundsButton then AS:SkinButton(contentFrame.soundsButton) end - if contentFrame.itemsButton then AS:SkinButton(contentFrame.itemsButton) end - - -- Class filter button (icon button) - if contentFrame.clasFilterButton then - AS:CreateBackdrop(contentFrame.clasFilterButton) - if contentFrame.clasFilterButton.texture then - AS:SkinTexture(contentFrame.clasFilterButton.texture) - AS:SetInside(contentFrame.clasFilterButton.texture, contentFrame.clasFilterButton.Backdrop or contentFrame.clasFilterButton) + -- Unified button height for the bottom bar. + -- Text buttons: 22px. Icon buttons: 20px (CreateBackdrop adds 1px border each side). + local BTN_H = 22 + local ICON_H = 20 + + local function SkinPageBtn(btn, dir) + if not btn then return end + AS:SkinArrowButton(btn, dir) + btn:SetSize(BTN_H, BTN_H) + btn._navHovered = false + local function applyTint() + if not btn._navHovered then return end + local t = btn:GetNormalTexture() + if t then t:SetVertexColor(unpack(AS.Color)) end + if btn.SetBackdropBorderColor then btn:SetBackdropBorderColor(unpack(AS.Color)) end end - SkinClassFilterPopup(contentFrame.clasFilterButton) + btn:HookScript('OnEnter', function(self) self._navHovered = true; applyTint() end) + btn:HookScript('OnLeave', function(self) + self._navHovered = false + local t = self:GetNormalTexture() + if t then t:SetVertexColor(1, 1, 1) end + if self.SetBackdropBorderColor then self:SetBackdropBorderColor(unpack(AS.BorderColor)) end + end) + hooksecurefunc(btn, 'SetNormalTexture', function() applyTint() end) end - - -- Content phase button (icon button) - if contentFrame.contentPhaseButton then - AS:CreateBackdrop(contentFrame.contentPhaseButton) - if contentFrame.contentPhaseButton.texture then - AS:SkinTexture(contentFrame.contentPhaseButton.texture) - AS:SetInside(contentFrame.contentPhaseButton.texture, contentFrame.contentPhaseButton.Backdrop or contentFrame.contentPhaseButton) + SkinPageBtn(contentFrame.prevPageButton, 'left') + SkinPageBtn(contentFrame.nextPageButton, 'right') + if contentFrame.itemsButton then SkinNoGlowBtn(contentFrame.itemsButton); contentFrame.itemsButton:SetHeight(BTN_H) end + if contentFrame.modelButton then SkinNoGlowBtn(contentFrame.modelButton); contentFrame.modelButton:SetHeight(BTN_H) end + if contentFrame.soundsButton then SkinNoGlowBtn(contentFrame.soundsButton); contentFrame.soundsButton:SetHeight(BTN_H) end + + -- Icon buttons: ICON_H size, backdrop with border hover, skinned texture. + local function SkinIconBtn(btn, popupFn) + if not btn then return end + btn:SetSize(ICON_H, ICON_H) + local hl = btn.GetHighlightTexture and btn:GetHighlightTexture() + if hl then hl:SetAlpha(0) end + AS:CreateBackdrop(btn) + local function hoverEnter(self) + local bd = self.backdrop or self.Backdrop + if bd then bd:SetBackdropBorderColor(unpack(AS.Color)) end + end + local function hoverLeave(self) + local bd = self.backdrop or self.Backdrop + if bd then bd:SetBackdropBorderColor(unpack(AS.BorderColor)) end + end + btn:HookScript('OnEnter', hoverEnter) + btn:HookScript('OnLeave', hoverLeave) + if btn.texture then + AS:SkinTexture(btn.texture) + AS:SetInside(btn.texture, btn.backdrop or btn.Backdrop or btn) end + if popupFn then popupFn(btn) end end + SkinIconBtn(contentFrame.clasFilterButton, SkinClassFilterPopup) + SkinIconBtn(contentFrame.contentPhaseButton) - -- Map button if contentFrame.mapButton then - AS:SkinButton(contentFrame.mapButton) - if contentFrame.mapButton.texture then - AS:SetInside(contentFrame.mapButton.texture, contentFrame.mapButton.Backdrop or contentFrame.mapButton) + local mb = contentFrame.mapButton + SkinIconBtn(mb) + if mb.highlight then mb.highlight:Hide() end + local tex = mb.texture + if tex then + tex:ClearAllPoints() + local bd = mb.backdrop or mb.Backdrop + tex:SetAllPoints(bd or mb) + AS:SkinTexture(tex) + local function fixTexCoord(self) + tex:SetTexCoord(0.125, 0.875, 0.0, 0.5) + local b = self.backdrop or self.Backdrop + tex:SetAllPoints(b or self) + end + mb:HookScript('OnShow', fixTexCoord) + mb:HookScript('OnMouseUp', fixTexCoord) + fixTexCoord(mb) + end + -- Add gap between mapButton and modelButton (natively 0px). + if contentFrame.modelButton then + contentFrame.modelButton:ClearAllPoints() + contentFrame.modelButton:SetPoint('RIGHT', mb, 'LEFT', -4, 0) end end @@ -363,14 +577,119 @@ function AS:AtlasLoot(event, addon) SkinAtlasLoot() if not skinned and AtlasLoot and AtlasLoot.GUI and AtlasLoot.GUI.Toggle then - local orig = AtlasLoot.GUI.Toggle - AtlasLoot.GUI.Toggle = function(...) - orig(...) + hooksecurefunc(AtlasLoot.GUI, 'Toggle', function() SkinAtlasLoot() C_Timer.After(0, SkinFooter) + end) + end + end) +end + +AS:RegisterSkin('AtlasLootClassic', AS.AtlasLoot) + +-- ----------------------------------------------------------------------- +-- Atlas integration: "AtlasLoot" button next to AtlasQuest +-- ----------------------------------------------------------------------- +local alAtlasBtn + +local function OpenAtlasLootForCurrentMap() + if not AtlasLoot or not AtlasLoot.GUI then return end + local atlas = Atlas + if not atlas or not atlas.db then return end + + -- Get current zoneID from Atlas dropdown selection. + local profile = atlas.db.profile + local mod = profile.options.dropdowns.module + local zone = profile.options.dropdowns.zone + local zoneID = ATLAS_DROPDOWNS and ATLAS_DROPDOWNS[mod] and ATLAS_DROPDOWNS[mod][zone] + if not zoneID then return end + + -- Search all AtlasLoot modules for a record whose AtlasMapFile contains zoneID. + local foundModule, foundDataID + for modName, modData in pairs(AtlasLoot.ItemDB.Storage or {}) do + if type(modData) == 'table' then + for dataID, entry in pairs(modData) do + if type(entry) == 'table' and entry.AtlasMapFile then + local files = entry.AtlasMapFile + if type(files) == 'string' then files = {files} end + for _, f in ipairs(files) do + if f == zoneID then + foundModule = modName + foundDataID = dataID + break + end + end + end + if foundDataID then break end end end + if foundDataID then break end + end + + local db = AtlasLoot.db.GUI + if not AtlasLoot.GUI.frame:IsVisible() then + AtlasLoot.GUI.frame:Show() + end + + if foundModule and foundDataID then + if foundModule ~= db.selected[1] then + AtlasLoot.GUI.frame.moduleSelect:SetSelected(foundModule) + end + if foundDataID ~= db.selected[2] then + AtlasLoot.GUI.frame.subCatSelect:SetSelected(foundDataID) + end + AtlasLoot.GUI.ItemFrame:Refresh(true) + end +end + +local function CreateAtlasLootAtlasButton() + if alAtlasBtn then return end + local atlas = _G['AtlasFrame'] + if not atlas then return end + local aqBtn = _G['AQ_AtlasToggle'] + if not aqBtn then return end + + alAtlasBtn = CreateFrame('Button', 'AS_AtlasLootButton', atlas) + alAtlasBtn:SetSize(90, 20) + alAtlasBtn:SetPoint('LEFT', aqBtn, 'RIGHT', 4, 0) + alAtlasBtn:SetPoint('TOP', aqBtn, 'TOP', 0, 0) + + -- FontString (same style as AQ_AtlasToggle which uses UIPanelButtonTemplate) + local fs = alAtlasBtn:CreateFontString(nil, 'OVERLAY', 'GameFontNormal') + fs:SetAllPoints(alAtlasBtn) + fs:SetText('AtlasLoot') + + -- Backdrop as child at frameLevel 0 — draws behind FontString, same as AQ pattern. + local bd = CreateFrame('Frame', nil, alAtlasBtn, 'BackdropTemplate') + bd:SetAllPoints(alAtlasBtn) + bd:SetFrameLevel(0) + AS.Skins:SetTemplate(bd, nil, false) + + -- Normal text color from GameFontNormal (ElvUI sets this to orange). + local nr, ng, nb = GameFontNormal:GetTextColor() + fs:SetTextColor(nr, ng, nb) + alAtlasBtn:HookScript('OnEnter', function() + bd:SetBackdropBorderColor(unpack(AS.Color)) + fs:SetTextColor(1, 1, 1) + end) + alAtlasBtn:HookScript('OnLeave', function() + bd:SetBackdropBorderColor(unpack(AS.BorderColor)) + fs:SetTextColor(nr, ng, nb) end) + alAtlasBtn:SetScript('OnClick', OpenAtlasLootForCurrentMap) end -AS:RegisterSkin('AtlasLootClassic', AS.AtlasLoot) \ No newline at end of file +-- Register hook: create button once both Atlas and AtlasLootClassic are loaded. +local alAtlasFrame = CreateFrame('Frame') +alAtlasFrame:RegisterEvent('ADDON_LOADED') +alAtlasFrame:SetScript('OnEvent', function(self, event, name) + if name == 'AtlasLootClassic' or name == 'Atlas' or name == 'AtlasQuest' then + local atlasLoaded = (C_AddOns and C_AddOns.IsAddOnLoaded or IsAddOnLoaded)('Atlas') + local alLoaded = (C_AddOns and C_AddOns.IsAddOnLoaded or IsAddOnLoaded)('AtlasLootClassic') + if atlasLoaded and alLoaded then + -- Defer until AQ_AtlasToggle exists (AtlasQuest may load slightly later). + C_Timer.After(2, CreateAtlasLootAtlasButton) + self:UnregisterEvent('ADDON_LOADED') + end + end +end) \ No newline at end of file From 0ed29a5fad83417a3df4ac4c2708bef411ee1f9b Mon Sep 17 00:00:00 2001 From: Adams Date: Sun, 12 Apr 2026 05:45:34 +0300 Subject: [PATCH 11/18] ItemRack, TrinketMenu: fix icon disappearing when skinning is enabled ActionButtonTemplate buttons store the item icon as $parentIcon (e.g. ItemRackButton0Icon). HandleItemButton called StripTextures on the whole button which wiped the icon texture before ItemRack/TrinketMenu had set it, leaving the icon blank. Replace HandleItemButton with a targeted skin function that strips only non-icon regions, then applies backdrop + StyleButton + HandleIcon. The addon sets the icon texture on its own schedule and is no longer interfered with. --- AddOnSkins/Skins/AddOns/ItemRack.lua | 53 +++++++++++++++++++------ AddOnSkins/Skins/AddOns/TrinketMenu.lua | 40 ++++++++++++++++--- 2 files changed, 75 insertions(+), 18 deletions(-) diff --git a/AddOnSkins/Skins/AddOns/ItemRack.lua b/AddOnSkins/Skins/AddOns/ItemRack.lua index 720f58fc..aa1679b5 100644 --- a/AddOnSkins/Skins/AddOns/ItemRack.lua +++ b/AddOnSkins/Skins/AddOns/ItemRack.lua @@ -1,23 +1,52 @@ local AS, L, S, R = unpack(AddOnSkins) function R:ItemRack() - local function SkinButton(idx,itemID) - if _G["ItemRackMenu"..idx] then - S:HandleItemButton(_G["ItemRackMenu"..idx]) + -- ItemRack buttons inherit ActionButtonTemplate. The icon texture is + -- $parentIcon (e.g. ItemRackButton0Icon / ItemRackMenu1Icon). + -- HandleItemButton calls StripTextures on the whole button which wipes + -- the icon before ItemRack has had a chance to set it, and the saved + -- texture is nil at hook time. Use a targeted skin instead: backdrop + + -- StyleButton only, then locate the icon and hook SetTexture so it + -- stays visible after ItemRack updates it. + local function SkinRackButton(button) + if not button or button._irSkinned then return end + button._irSkinned = true + + local name = button:GetName() + local icon = name and _G[name .. 'Icon'] + + -- Strip only the non-icon regions (border, gloss, etc.). + for _, region in next, { button:GetRegions() } do + if region and region ~= icon and region.IsObjectType and region:IsObjectType('Texture') then + region:SetTexture('') + end + end + + S:CreateBackdrop(button, nil, true, nil, nil, nil, nil, nil, true) + S:StyleButton(button) + + if icon then + S:HandleIcon(icon) + S:SetInside(icon, button) + icon:SetParent(button.backdrop or button) end end - local function SkinButton2() - for i=0,20 do - if _G["ItemRackButton"..i] then - S:HandleItemButton(_G["ItemRackButton"..i]) - end + + local function SkinMenuButton(idx) + SkinRackButton(_G['ItemRackMenu' .. idx]) + end + + local function SkinAllMainButtons() + for i = 0, 20 do + SkinRackButton(_G['ItemRackButton' .. i]) end end - hooksecurefunc(ItemRack, 'CreateMenuButton', SkinButton) - hooksecurefunc(ItemRack, 'InitButtons', SkinButton2) - SkinButton(1) - SkinButton2() + hooksecurefunc(ItemRack, 'CreateMenuButton', function(idx) SkinMenuButton(idx) end) + hooksecurefunc(ItemRack, 'InitButtons', function() SkinAllMainButtons() end) + + SkinMenuButton(1) + SkinAllMainButtons() end AS:RegisterSkin('ItemRack') diff --git a/AddOnSkins/Skins/AddOns/TrinketMenu.lua b/AddOnSkins/Skins/AddOns/TrinketMenu.lua index 7b0a2fac..54f51d8c 100644 --- a/AddOnSkins/Skins/AddOns/TrinketMenu.lua +++ b/AddOnSkins/Skins/AddOns/TrinketMenu.lua @@ -2,7 +2,6 @@ local AS, L, S, R = unpack(AddOnSkins) function R:TrinketMenu() -- Config Panel - local TrinketMenu = _G.TrinketMenu S:HandleFrame(TrinketMenu_OptFrame) TrinketMenu_OptFrame:SetWidth(380) S:HandleFrame(TrinketMenu_SubOptFrame) @@ -65,13 +64,42 @@ function R:TrinketMenu() S:HandleSliderFrame(slider) end - -- Main Frame - S:HandleItemButton(TrinketMenu_Trinket0) - S:HandleItemButton(TrinketMenu_Trinket1) + -- TrinketMenu buttons inherit ActionButtonTemplate. The icon is $parentIcon + -- (e.g. TrinketMenu_Trinket0Icon, TrinketMenu_Menu3Icon). + -- HandleItemButton calls StripTextures which wipes the icon before TrinketMenu + -- sets it, leaving an empty texture. Skin without touching the icon texture. + local function SkinTrinketButton(button) + if not button or button._tmSkinned then return end + button._tmSkinned = true + + local name = button:GetName() + local icon = name and _G[name .. 'Icon'] + + -- Strip only non-icon regions. + for _, region in next, { button:GetRegions() } do + if region and region ~= icon and region.IsObjectType and region:IsObjectType('Texture') then + region:SetTexture('') + end + end + + S:CreateBackdrop(button, nil, true, nil, nil, nil, nil, nil, true) + S:StyleButton(button) + + if icon then + S:HandleIcon(icon) + S:SetInside(icon, button) + icon:SetParent(button.backdrop or button) + end + end + + -- Main Frame (equipped trinkets) + SkinTrinketButton(TrinketMenu_Trinket0) + SkinTrinketButton(TrinketMenu_Trinket1) + -- Menu buttons (inventory trinkets) - created dynamically + local TrinketMenu = _G.TrinketMenu for i = (TrinketMenu.NumberOfTrinkets + 1), TrinketMenu.MaxTrinkets do - local trinkets = _G['TrinketMenu_Menu'..i] - S:HandleItemButton(trinkets) + SkinTrinketButton(_G['TrinketMenu_Menu'..i]) end end From af4f010fa9b2d83445cb60eb8b7e46e320cfe01a Mon Sep 17 00:00:00 2001 From: Adams Date: Sun, 12 Apr 2026 05:45:45 +0300 Subject: [PATCH 12/18] EmbedSystem: fix HideChat not applying on login ES.Main is created visible by default. On login ES:Check() called SetShown(true) on an already-visible frame, which did not fire OnShow, so ToggleChatFrame was never called and the chat frame remained visible alongside the embed. Call ToggleChatFrame explicitly in ES:Check() after SetShown so the chat hide state is always synced regardless of whether the frame visibility actually changed. --- AddOnSkins/Core/EmbedSystem.lua | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/AddOnSkins/Core/EmbedSystem.lua b/AddOnSkins/Core/EmbedSystem.lua index b68fa09e..924e6c39 100644 --- a/AddOnSkins/Core/EmbedSystem.lua +++ b/AddOnSkins/Core/EmbedSystem.lua @@ -127,7 +127,9 @@ function ES:Check(Message) if not (AS:CheckOption('EmbedSystem') or AS:CheckOption('EmbedSystemDual')) then return end ES:Resize() - ES.Main:SetShown(not (AS:CheckOption('EmbedIsHidden') or AS:CheckOption('EmbedOoC'))) + local shouldShow = not (AS:CheckOption('EmbedIsHidden') or AS:CheckOption('EmbedOoC')) + ES.Main:SetShown(shouldShow) + ES:ToggleChatFrame(shouldShow) for _, Window in next, ES.Windows do Window:SetFrameStrata(strsub(AS:CheckOption('EmbedFrameStrata'), 3)) From c3abba32f15a5ae84dc24a3664f40b31e371514c Mon Sep 17 00:00:00 2001 From: Adams Date: Sun, 12 Apr 2026 05:54:12 +0300 Subject: [PATCH 13/18] ElvUI: fix RightChatToggleButton tooltip missing Right Click line The OnEnter script with the embed tooltip was set inside ES:Hooks(), which only runs when EmbedSystem is enabled. On a fresh install the tooltip showed only the Left Click line until the user enabled embed and relogged. Move the tooltip and fade-restore logic to a new ES:HookToggleButtonTooltip() that uses HookScript and runs unconditionally from StartUp. ES:Hooks() retains only the OnClick handler and the embed-specific Check() hooks. --- AddOnSkins/Core/Core.lua | 1 + AddOnSkins/Core/ElvUI.lua | 44 +++++++++++++++++++++++---------------- 2 files changed, 27 insertions(+), 18 deletions(-) diff --git a/AddOnSkins/Core/Core.lua b/AddOnSkins/Core/Core.lua index 04ab2b89..2f1acf0e 100644 --- a/AddOnSkins/Core/Core.lua +++ b/AddOnSkins/Core/Core.lua @@ -297,6 +297,7 @@ function AS:StartUp(event, ...) end ES:Initialize() + ES:HookToggleButtonTooltip() AS.RunOnce = true end diff --git a/AddOnSkins/Core/ElvUI.lua b/AddOnSkins/Core/ElvUI.lua index 6ccb78f4..98e1cd3e 100644 --- a/AddOnSkins/Core/ElvUI.lua +++ b/AddOnSkins/Core/ElvUI.lua @@ -51,7 +51,6 @@ function ES:Hooks() hooksecurefunc(E:GetModule('Layout'), 'ToggleChatPanels', function() ES:Check() end) if RightChatToggleButton then - RightChatToggleButton:RegisterForClicks('AnyDown') RightChatToggleButton:SetScript('OnClick', function(s, btn) if btn == 'RightButton' then if ES.Main:IsShown() then @@ -77,26 +76,35 @@ function ES:Hooks() end end end) + end +end - RightChatToggleButton:SetScript('OnEnter', function(s) - if E.db[s.parent:GetName()..'Faded'] then - s.parent:Show() - UIFrameFadeIn(s.parent, 0.2, s.parent:GetAlpha(), 1) - UIFrameFadeIn(s, 0.2, s:GetAlpha(), 1) - if not AS:CheckOption('EmbedIsHidden') then - ES.Main:Show() - end +-- Hook RightChatToggleButton tooltip unconditionally so the Right Click +-- line appears even when the embed system is disabled. +function ES:HookToggleButtonTooltip() + if not (E and RightChatToggleButton) then return end + if RightChatToggleButton._asTipHooked then return end + RightChatToggleButton._asTipHooked = true + + RightChatToggleButton:RegisterForClicks('AnyDown') + RightChatToggleButton:HookScript('OnEnter', function(s) + if E.db[s.parent:GetName()..'Faded'] then + s.parent:Show() + UIFrameFadeIn(s.parent, 0.2, s.parent:GetAlpha(), 1) + UIFrameFadeIn(s, 0.2, s:GetAlpha(), 1) + if not AS:CheckOption('EmbedIsHidden') then + ES.Main:Show() end + end - if not s.parent.editboxforced then - _G.GameTooltip:SetOwner(s, 'ANCHOR_TOPLEFT', 0, 4) - _G.GameTooltip:ClearLines() - _G.GameTooltip:AddDoubleLine(L["Left Click:"], L["Toggle Chat Frame"], 1, 1, 1) - _G.GameTooltip:AddDoubleLine(L["Right Click:"], L["Toggle Embedded Addon"], 1, 1, 1) - _G.GameTooltip:Show() - end - end) - end + if not s.parent.editboxforced then + _G.GameTooltip:SetOwner(s, 'ANCHOR_TOPLEFT', 0, 4) + _G.GameTooltip:ClearLines() + _G.GameTooltip:AddDoubleLine(L["Left Click:"], L["Toggle Chat Frame"], 1, 1, 1) + _G.GameTooltip:AddDoubleLine(L["Right Click:"], L["Toggle Embedded Addon"], 1, 1, 1) + _G.GameTooltip:Show() + end + end) end function ES:Resize() From 32debca72f64e36852e31a2a911d2f2de7f1a1bc Mon Sep 17 00:00:00 2001 From: Adams Date: Mon, 6 Apr 2026 14:46:52 +0300 Subject: [PATCH 14/18] Add skins for AtlasQuest and TellMeWhen (TBC Anniversary) Skins/AddOns/AtlasQuest.lua (new): - Skins AtlasQuestFrame sidebar and AtlasQuestInsideFrame overlay. CheckButtons (Alliance/Horde/Finished filters) are fully stripped including Atlas-variant texture slots before SkinCheckBox runs. Quest item icon frames get a backdrop border colored to match item quality via a SetVertexColor hook. Quest list buttons receive an ElvUI-style highlight texture. Skinning of list buttons and item frames is deferred to catch frames created in OnEnable. Skins/AddOns/TellMeWhen.lua (new): - Skins the TellMeWhen_IconEditor config window and all child widgets. Handles: - TellMeWhen_SimpleDialog-based windows (editor, color picker, dialogs) via StripTextures + SetTemplate; persistent across reloads via _tmwDialogSkinned. - CheckBoxes: custom 13px border+fill textures because BackdropTemplate timing is unreliable; border color follows checked state. - TMW custom dropdowns (Frame with Background + nested Button + Text): detected by parentKeys, skinned via SetTemplate with arrow texture overlay. - In-game group borders (TellMeWhen_GenericBorder): SetColor override; caught both at startup and via CreateFrame hook for groups added later. - Config-mode icon borders: TMW_ICON_SETUP_POST callback adds an ElvUI backdrop frame behind each icon when in config mode; flags are reset on TMW_LOCK_TOGGLED so borders reappear each time config mode is entered. - TellMeWhen_Options loads on demand - caught via ADDON_LOADED if not yet present. Skins/AddOns/Load_Skins.xml: - Register AtlasQuest.lua and TellMeWhen.lua --- AddOnSkins/Skins/AddOns/AtlasQuest.lua | 147 ++++++++++ AddOnSkins/Skins/AddOns/Load_Skins.xml | 297 ++++++++++---------- AddOnSkins/Skins/AddOns/TellMeWhen.lua | 359 +++++++++++++++++++++++++ 3 files changed, 661 insertions(+), 142 deletions(-) create mode 100644 AddOnSkins/Skins/AddOns/AtlasQuest.lua create mode 100644 AddOnSkins/Skins/AddOns/TellMeWhen.lua diff --git a/AddOnSkins/Skins/AddOns/AtlasQuest.lua b/AddOnSkins/Skins/AddOns/AtlasQuest.lua new file mode 100644 index 00000000..df6cff50 --- /dev/null +++ b/AddOnSkins/Skins/AddOns/AtlasQuest.lua @@ -0,0 +1,147 @@ +-- AtlasQuest skin for AddOnSkins +-- AtlasQuestFrame inherits BackdropTemplate and is a child of AtlasFrame. +-- AtlasQuestInsideFrame overlays the map area with quest details. + +local AS = unpack(AddOnSkins) + +local function Safe(func, name) + local f = type(name) == "string" and _G[name] or name + if f then func(AS, f) end +end + +-- Clears all button-state textures including Atlas variants used by the retail engine, +-- then delegates to AS:SkinCheckBox for the inset child backdrop. +local function SkinCheckBox(cb) + if not cb or cb._asSkinned then return end + cb._asSkinned = true + + if cb.SetNormalTexture then cb:SetNormalTexture('') end + if cb.SetHighlightTexture then cb:SetHighlightTexture('') end + if cb.SetPushedTexture then cb:SetPushedTexture('') end + if cb.SetDisabledTexture then cb:SetDisabledTexture('') end + + local function kill(tex) + if not tex then return end + if tex.SetTexture then tex:SetTexture(nil) end + if tex.SetAtlas then tex:SetAtlas('') end + tex:SetAlpha(0) tex:Hide() + end + kill(cb.GetNormalTexture and cb:GetNormalTexture()) + kill(cb.GetHighlightTexture and cb:GetHighlightTexture()) + kill(cb.GetPushedTexture and cb:GetPushedTexture()) + + -- Do NOT SetAlpha(0) on GetRegions() results - CheckedTexture lives there on the retail + -- engine and zeroing its alpha persists after AS:SkinCheckBox restores the texture path. + for i = 1, cb:GetNumRegions() do + local r = select(i, cb:GetRegions()) + if r then + if r.SetTexture then r:SetTexture(nil) end + if r.SetAtlas then r:SetAtlas('') end + end + end + + if cb.SetBackdrop then cb:SetBackdrop(nil) end + + AS:SkinCheckBox(cb) + + local blankTex = (_G.ElvUI and _G.ElvUI[1].media.blankTex) or [[Interface\Buttons\WHITE8X8]] + cb:SetCheckedTexture(blankTex) + local ct = cb.GetCheckedTexture and cb:GetCheckedTexture() + if ct then + ct:SetVertexColor(unpack(AS.Color)) + ct:SetAlpha(1) + if cb.Backdrop then AS:SetInside(ct, cb.Backdrop) end + end + if cb.Backdrop then + cb:HookScript('OnShow', function(f) + if f:GetChecked() then + f.Backdrop:SetBackdropBorderColor(unpack(AS.Color)) + else + f.Backdrop:SetBackdropBorderColor(unpack(AS.BorderColor)) + end + end) + hooksecurefunc(cb, 'SetChecked', function(f, checked) + if f.Backdrop then + if checked then + f.Backdrop:SetBackdropBorderColor(unpack(AS.Color)) + else + f.Backdrop:SetBackdropBorderColor(unpack(AS.BorderColor)) + end + end + end) + end +end + +local function SkinQuestItemFrames() + for i = 1, 6 do + local item = _G['AQ_QuestItem_'..i] + if not item or item._asSkinned then break end + item._asSkinned = true + + if item.icon then + AS:SkinTexture(item.icon) + item.icon:SetSize(24, 24) + local iconBD = CreateFrame('Frame', nil, item) + iconBD:SetPoint('TOPLEFT', item.icon, -1, 1) + iconBD:SetPoint('BOTTOMRIGHT', item.icon, 1, -1) + AS:SetTemplate(iconBD) + iconBD:SetFrameLevel(item:GetFrameLevel() - 1) + end + + if item.qualityBorder then + item.qualityBorder:SetTexture(nil) + hooksecurefunc(item.qualityBorder, 'SetVertexColor', function(self, r, g, b) + if item.icon and item.icon:GetParent() then + -- iconBD uses SetTemplate (inline backdrop), not a child .Backdrop frame + item.icon:GetParent():SetBackdropBorderColor(r, g, b) + end + end) + end + end +end + +local function SkinQuestListButtons() + for i = 1, 23 do + local btn = _G['AQButton_'..i] + if not btn or btn._asSkinned then break end + btn._asSkinned = true + + btn:SetHighlightTexture(AS.NormTex or [[Interface\Buttons\UI-Listbox-Highlight]]) + local hl = btn:GetHighlightTexture() + if hl then + hl:SetVertexColor(unpack(AS.Color)) + hl:SetAlpha(0.35) + end + end +end + +function AS:AtlasQuest(event, addon) + local loaded = (C_AddOns and C_AddOns.IsAddOnLoaded and C_AddOns.IsAddOnLoaded('AtlasQuest')) + or (_G.IsAddOnLoaded and _G.IsAddOnLoaded('AtlasQuest')) + if not loaded then return end + + Safe(AS.SkinFrame, 'AtlasQuestFrame') + Safe(AS.SkinCloseButton, 'AQ_SidebarClose') + Safe(AS.SkinButton, 'AQ_OptionsButton') + + SkinCheckBox(_G['AQ_AllianceCheck']) + SkinCheckBox(_G['AQ_HordeCheck']) + + local insideFrame = _G['AtlasQuestInsideFrame'] + if insideFrame then + AS:StripTextures(insideFrame) + AS:SetTemplate(insideFrame) + end + + Safe(AS.SkinCloseButton, 'AQ_QuestClose') + SkinCheckBox(_G['AQ_FinishedQuestCheck']) + Safe(AS.SkinButton, 'AQ_AtlasToggle') + + -- Quest list buttons and item frames are created in OnEnable, defer skinning + C_Timer.After(0.1, function() + SkinQuestListButtons() + SkinQuestItemFrames() + end) +end + +AS:RegisterSkin('AtlasQuest', AS.AtlasQuest) diff --git a/AddOnSkins/Skins/AddOns/Load_Skins.xml b/AddOnSkins/Skins/AddOns/Load_Skins.xml index 6f590f1a..2171f030 100644 --- a/AddOnSkins/Skins/AddOns/Load_Skins.xml +++ b/AddOnSkins/Skins/AddOns/Load_Skins.xml @@ -1,142 +1,155 @@ - -