-------------------------------------------------------------------------------- -- Nanami-UI: Pet Stable Skin (PetStableSkin.lua) -- Skins the Blizzard PetStableFrame with Nanami-UI theme -------------------------------------------------------------------------------- SFrames = SFrames or {} SFrames.PetStableSkin = {} SFramesDB = SFramesDB or {} local T = SFrames.ActiveTheme local skinned = false local function GetFont() if SFrames and SFrames.GetFont then return SFrames:GetFont() end return "Fonts\\ARIALN.TTF" end -------------------------------------------------------------------------------- -- Helpers -------------------------------------------------------------------------------- local function NukeTextures(frame, exceptions) local regions = { frame:GetRegions() } for _, r in ipairs(regions) do if r:IsObjectType("Texture") and not r.sfKeep then local rName = r:GetName() local skip = false if exceptions and rName then for _, exc in ipairs(exceptions) do if string.find(rName, exc) then skip = true break end end end if not skip then r:SetTexture(nil) r:SetAlpha(0) r:Hide() r.sfNuked = true r:ClearAllPoints() r:SetPoint("CENTER", UIParent, "CENTER", 9999, 9999) end end end end local function NukeChildTextures(frame) local children = { frame:GetChildren() } for _, child in ipairs(children) do local cName = child:GetName() or "" if string.find(cName, "Inset") or string.find(cName, "Bg") then NukeTextures(child) if child.SetBackdrop then child:SetBackdrop(nil) end end end end local function SetRoundBackdrop(frame) frame:SetBackdrop({ bgFile = "Interface\\Tooltips\\UI-Tooltip-Background", edgeFile = "Interface\\Tooltips\\UI-Tooltip-Border", tile = true, tileSize = 16, edgeSize = 14, insets = { left = 3, right = 3, top = 3, bottom = 3 }, }) frame:SetBackdropColor(T.panelBg[1], T.panelBg[2], T.panelBg[3], T.panelBg[4]) frame:SetBackdropBorderColor(T.panelBorder[1], T.panelBorder[2], T.panelBorder[3], T.panelBorder[4]) end local function CreateShadow(parent) local s = CreateFrame("Frame", nil, parent) s:SetPoint("TOPLEFT", parent, "TOPLEFT", -4, 4) s:SetPoint("BOTTOMRIGHT", parent, "BOTTOMRIGHT", 4, -4) s:SetBackdrop({ bgFile = "Interface\\Tooltips\\UI-Tooltip-Background", edgeFile = "Interface\\Tooltips\\UI-Tooltip-Border", tile = true, tileSize = 16, edgeSize = 16, insets = { left = 4, right = 4, top = 4, bottom = 4 }, }) s:SetBackdropColor(0, 0, 0, 0.45) s:SetBackdropBorderColor(0, 0, 0, 0.6) s:SetFrameLevel(math.max(0, parent:GetFrameLevel() - 1)) return s end local function MarkBackdropRegions(frame) local regions = { frame:GetRegions() } for _, r in ipairs(regions) do if not r.sfNuked then r.sfKeep = true end end end -------------------------------------------------------------------------------- -- Slot styling (ItemButton-type pet slot buttons) -------------------------------------------------------------------------------- local function StyleSlot(btn) if not btn or btn.sfSkinned then return end btn.sfSkinned = true local bname = btn:GetName() or "" local normalTex = _G[bname .. "NormalTexture"] if normalTex then normalTex:SetAlpha(0) end NukeTextures(btn, { "Icon", "Count" }) local iconTex = _G[bname .. "IconTexture"] if iconTex then iconTex:SetTexCoord(0.08, 0.92, 0.08, 0.92) iconTex.sfKeep = true end local bg = btn:CreateTexture(nil, "BACKGROUND") bg:SetTexture("Interface\\Tooltips\\UI-Tooltip-Background") bg:SetAllPoints() bg:SetVertexColor(T.slotBg[1], T.slotBg[2], T.slotBg[3], T.slotBg[4] or 0.8) bg.sfKeep = true local border = CreateFrame("Frame", nil, btn) border:SetPoint("TOPLEFT", btn, "TOPLEFT", -2, 2) border:SetPoint("BOTTOMRIGHT", btn, "BOTTOMRIGHT", 2, -2) border:SetBackdrop({ edgeFile = "Interface\\Tooltips\\UI-Tooltip-Border", edgeSize = 10, insets = { left = 2, right = 2, top = 2, bottom = 2 }, }) border:SetBackdropBorderColor(T.slotBorder[1], T.slotBorder[2], T.slotBorder[3], T.slotBorder[4]) btn.sfBorder = border local origEnter = btn:GetScript("OnEnter") local origLeave = btn:GetScript("OnLeave") btn:SetScript("OnEnter", function() if origEnter then origEnter() end if this.sfBorder then this.sfBorder:SetBackdropBorderColor( T.slotHover[1], T.slotHover[2], T.slotHover[3], T.slotHover[4] or 1) end end) btn:SetScript("OnLeave", function() if origLeave then origLeave() end if this.sfBorder then this.sfBorder:SetBackdropBorderColor( T.slotBorder[1], T.slotBorder[2], T.slotBorder[3], T.slotBorder[4]) end end) end -------------------------------------------------------------------------------- -- Close button styling -------------------------------------------------------------------------------- local function StyleCloseButton(btn) if not btn or btn.sfSkinned then return end btn.sfSkinned = true btn:SetWidth(22) btn:SetHeight(22) btn:ClearAllPoints() btn:SetPoint("TOPRIGHT", btn:GetParent(), "TOPRIGHT", -6, -6) if btn.GetNormalTexture and btn:GetNormalTexture() then btn:GetNormalTexture():SetAlpha(0) end if btn.GetPushedTexture and btn:GetPushedTexture() then btn:GetPushedTexture():SetAlpha(0) end if btn.GetHighlightTexture and btn:GetHighlightTexture() then btn:GetHighlightTexture():SetAlpha(0) end btn:SetBackdrop({ bgFile = "Interface\\Tooltips\\UI-Tooltip-Background", edgeFile = "Interface\\Tooltips\\UI-Tooltip-Border", tile = true, tileSize = 8, edgeSize = 8, insets = { left = 2, right = 2, top = 2, bottom = 2 }, }) local cbg = { 0.5, 0.1, 0.1, 0.85 } local cbd = { 0.6, 0.2, 0.2, 0.8 } local cbgH = { 0.7, 0.15, 0.15, 1 } local cbdH = { 0.9, 0.3, 0.3, 1 } btn:SetBackdropColor(cbg[1], cbg[2], cbg[3], cbg[4]) btn:SetBackdropBorderColor(cbd[1], cbd[2], cbd[3], cbd[4]) local xLabel = btn:CreateFontString(nil, "OVERLAY") xLabel:SetFont(GetFont(), 11, "OUTLINE") xLabel:SetPoint("CENTER", 0, 0) xLabel:SetText("X") xLabel:SetTextColor(0.9, 0.8, 0.8) local origEnter = btn:GetScript("OnEnter") local origLeave = btn:GetScript("OnLeave") btn:SetScript("OnEnter", function() if origEnter then origEnter() end this:SetBackdropColor(cbgH[1], cbgH[2], cbgH[3], cbgH[4]) this:SetBackdropBorderColor(cbdH[1], cbdH[2], cbdH[3], cbdH[4]) end) btn:SetScript("OnLeave", function() if origLeave then origLeave() end this:SetBackdropColor(cbg[1], cbg[2], cbg[3], cbg[4]) this:SetBackdropBorderColor(cbd[1], cbd[2], cbd[3], cbd[4]) end) end -------------------------------------------------------------------------------- -- UIPanelButton styling (purchase button etc.) -------------------------------------------------------------------------------- local function StyleActionButton(btn) if not btn or btn.sfSkinned then return end btn.sfSkinned = true btn:SetBackdrop({ bgFile = "Interface\\Tooltips\\UI-Tooltip-Background", edgeFile = "Interface\\Tooltips\\UI-Tooltip-Border", tile = true, tileSize = 16, edgeSize = 14, insets = { left = 3, right = 3, top = 3, bottom = 3 }, }) local bg = T.btnBg local bd = T.btnBorder btn:SetBackdropColor(bg[1], bg[2], bg[3], bg[4]) btn:SetBackdropBorderColor(bd[1], bd[2], bd[3], bd[4]) if btn.GetNormalTexture and btn:GetNormalTexture() then btn:GetNormalTexture():SetTexture(nil) end if btn.GetPushedTexture and btn:GetPushedTexture() then btn:GetPushedTexture():SetTexture(nil) end if btn.GetHighlightTexture and btn:GetHighlightTexture() then btn:GetHighlightTexture():SetAlpha(0) end if btn.GetDisabledTexture and btn:GetDisabledTexture() then btn:GetDisabledTexture():SetTexture(nil) end local name = btn:GetName() or "" for _, suffix in ipairs({ "Left", "Right", "Middle" }) do local tex = _G[name .. suffix] if tex then tex:SetAlpha(0); tex:Hide() end end local fs = btn:GetFontString() if fs then fs:SetFont(GetFont(), 11, "OUTLINE") fs:SetTextColor(T.text[1], T.text[2], T.text[3]) end local hoverBg = T.btnHoverBg local hoverBd = T.btnHoverBd local origEnter = btn:GetScript("OnEnter") local origLeave = btn:GetScript("OnLeave") btn:SetScript("OnEnter", function() if origEnter then origEnter() end this:SetBackdropColor(hoverBg[1], hoverBg[2], hoverBg[3], hoverBg[4]) this:SetBackdropBorderColor(hoverBd[1], hoverBd[2], hoverBd[3], hoverBd[4]) end) btn:SetScript("OnLeave", function() if origLeave then origLeave() end this:SetBackdropColor(bg[1], bg[2], bg[3], bg[4]) this:SetBackdropBorderColor(bd[1], bd[2], bd[3], bd[4]) end) end -------------------------------------------------------------------------------- -- Style all FontStrings on a frame with our themed font -------------------------------------------------------------------------------- local function StyleFontStrings(frame, size, color) local regions = { frame:GetRegions() } local font = GetFont() for _, r in ipairs(regions) do if r:IsObjectType("FontString") then r:SetFont(font, size or 11, "OUTLINE") if color then r:SetTextColor(color[1], color[2], color[3]) end end end end -------------------------------------------------------------------------------- -- Main skin application -------------------------------------------------------------------------------- local function ApplySkin() if skinned then return end if not PetStableFrame then return end if SFramesDB.enablePetStable == false then return end skinned = true local frame = PetStableFrame local font = GetFont() -- 1) Remove Blizzard decorative textures from main frame NukeTextures(frame, { "Icon", "Food", "Diet", "Selected" }) -- 2) Remove textures from sub-inset frames NukeChildTextures(frame) -- 3) Apply themed backdrop SetRoundBackdrop(frame) MarkBackdropRegions(frame) -- 4) Shadow for depth (tagged so auto-compact ignores it) local shadow = CreateShadow(frame) shadow.sfOverlay = true -- 5) Free drag support with position persistence frame:SetMovable(true) frame:EnableMouse(true) frame:SetClampedToScreen(true) frame:RegisterForDrag("LeftButton") frame:SetScript("OnDragStart", function() this:StartMoving() end) frame:SetScript("OnDragStop", function() this:StopMovingOrSizing() local point, _, relPoint, xOfs, yOfs = this:GetPoint() SFramesDB.petStablePos = { point = point, relPoint = relPoint, x = xOfs, y = yOfs, } end) -- 6) Title local titleFS = PetStableFrameTitleText if titleFS then titleFS:SetFont(font, 13, "OUTLINE") titleFS:SetTextColor(T.title[1], T.title[2], T.title[3]) titleFS.sfKeep = true end -- 7) Close button if PetStableFrameCloseButton then StyleCloseButton(PetStableFrameCloseButton) end -- 8) Style pet info FontStrings (try various naming patterns) local infoFS = { "PetStablePetName", "PetStableSelectedPetName", "PetStablePetLevel", "PetStableSelectedPetLevel", "PetStablePetFamily", "PetStableSelectedPetFamily", "PetStablePetLoyalty", "PetStableSelectedPetLoyalty", } for _, n in ipairs(infoFS) do local fs = _G[n] if fs and fs.SetFont then fs:SetFont(font, 11, "OUTLINE") fs:SetTextColor(T.text[1], T.text[2], T.text[3]) end end local labelFS = { "PetStableDietLabel", "PetStableTypeLabel", "PetStableNameLabel", "PetStableLevelLabel", "PetStableLoyaltyLabel", "PetStableFamilyLabel", "PetStableDietText", } for _, n in ipairs(labelFS) do local fs = _G[n] if fs and fs.SetFont then fs:SetFont(font, 10, "OUTLINE") fs:SetTextColor(T.dimText[1], T.dimText[2], T.dimText[3]) end end -- Also style any remaining FontStrings on the main frame StyleFontStrings(frame, 11, T.text) -- 9) Current pet slot if PetStableCurrentPet then StyleSlot(PetStableCurrentPet) end -- 10) Stable slots (dynamic count for Turtle WoW, check up to 20) for i = 1, 20 do local slot = _G["PetStableStableSlot" .. i] if slot then StyleSlot(slot) end end -- Alternate naming pattern for i = 1, 20 do local slot = _G["PetStableSlot" .. i] if slot and not slot.sfSkinned then StyleSlot(slot) end end -- 11) Purchase button if PetStablePurchaseButton then StyleActionButton(PetStablePurchaseButton) end -- 12) Model frame background (tagged so auto-compact ignores it) if PetStableModel then local modelBg = CreateFrame("Frame", nil, frame) modelBg.sfOverlay = true modelBg:SetPoint("TOPLEFT", PetStableModel, "TOPLEFT", -3, 3) modelBg:SetPoint("BOTTOMRIGHT", PetStableModel, "BOTTOMRIGHT", 3, -3) modelBg:SetBackdrop({ bgFile = "Interface\\Tooltips\\UI-Tooltip-Background", edgeFile = "Interface\\Tooltips\\UI-Tooltip-Border", tile = true, tileSize = 16, edgeSize = 10, insets = { left = 2, right = 2, top = 2, bottom = 2 }, }) modelBg:SetBackdropColor( T.modelBg[1], T.modelBg[2], T.modelBg[3], T.modelBg[4] or 0.5) modelBg:SetBackdropBorderColor( T.modelBorder[1], T.modelBorder[2], T.modelBorder[3], T.modelBorder[4]) modelBg:SetFrameLevel(math.max(0, PetStableModel:GetFrameLevel() - 1)) end -- 13) Food/diet icons for i = 1, 10 do local food = _G["PetStableFood" .. i] or _G["PetStableDietIcon" .. i] if food and food.SetTexCoord then food:SetTexCoord(0.08, 0.92, 0.08, 0.92) end end -- 14) Any additional child frames that look like insets local insets = { "PetStableLeftInset", "PetStableBottomInset", "PetStableRightInset", "PetStableTopInset", } for _, iname in ipairs(insets) do local inset = _G[iname] if inset then NukeTextures(inset) if inset.SetBackdrop then inset:SetBackdrop(nil) end end end -- 14b) Aggressive cleanup: clear backdrops/textures on ALL non-essential children local knownFrames = {} if PetStableModel then knownFrames[PetStableModel] = true end if PetStableFrameCloseButton then knownFrames[PetStableFrameCloseButton] = true end if PetStablePurchaseButton then knownFrames[PetStablePurchaseButton] = true end if PetStableCurrentPet then knownFrames[PetStableCurrentPet] = true end for si = 1, 20 do local ss = _G["PetStableStableSlot" .. si] if ss then knownFrames[ss] = true end local ps = _G["PetStableSlot" .. si] if ps then knownFrames[ps] = true end end local allCh = { frame:GetChildren() } for _, child in ipairs(allCh) do if not knownFrames[child] and not child.sfSkinned and not child.sfOverlay and not child.sfBorder then NukeTextures(child) if child.SetBackdrop then child:SetBackdrop(nil) end local subCh = { child:GetChildren() } for _, sc in ipairs(subCh) do NukeTextures(sc) if sc.SetBackdrop then sc:SetBackdrop(nil) end end end end -- 15) Auto-compact: measure left padding, then apply uniformly to right & bottom local frameTop = frame:GetTop() local frameLeft = frame:GetLeft() local frameRight = frame:GetRight() local frameBot = frame:GetBottom() if frameTop and frameLeft and frameRight and frameBot then local contentLeft = frameRight local contentRight = frameLeft local contentBot = frameTop local function Scan(obj) if not obj:IsShown() then return end local l = obj.GetLeft and obj:GetLeft() local r = obj.GetRight and obj:GetRight() local b = obj.GetBottom and obj:GetBottom() if l and l < contentLeft then contentLeft = l end if r and r > contentRight then contentRight = r end if b and b < contentBot then contentBot = b end end local children = { frame:GetChildren() } for _, child in ipairs(children) do if not child.sfOverlay and not child.sfBorder then Scan(child) end end local regions = { frame:GetRegions() } for _, r in ipairs(regions) do if not r.sfNuked and not r.sfKeep then Scan(r) end end -- Also scan named Blizzard content elements directly local contentNames = { "PetStableCurrentPet", "PetStablePurchaseButton", "PetStableModel", "PetStableFrameCloseButton", } for i = 1, 20 do table.insert(contentNames, "PetStableStableSlot" .. i) table.insert(contentNames, "PetStableSlot" .. i) end for _, n in ipairs(contentNames) do local obj = _G[n] if obj and obj.IsShown and obj:IsShown() then Scan(obj) end end -- Scan visible FontStrings on the frame (they are content) for _, r in ipairs(regions) do if r:IsObjectType("FontString") and r:IsShown() and r:GetText() and r:GetText() ~= "" then local l = r:GetLeft() local ri = r:GetRight() local b = r:GetBottom() if l and l < contentLeft then contentLeft = l end if ri and ri > contentRight then contentRight = ri end if b and b < contentBot then contentBot = b end end end local leftPad = contentLeft - frameLeft if leftPad < 4 then leftPad = 4 end if leftPad > 16 then leftPad = 16 end local newW = (contentRight - frameLeft) + leftPad local newH = (frameTop - contentBot) + leftPad if newW < frame:GetWidth() then frame:SetWidth(newW) end if newH < frame:GetHeight() then frame:SetHeight(newH) end end end -------------------------------------------------------------------------------- -- Restore saved position (runs every time the frame is shown) -------------------------------------------------------------------------------- local function RestorePosition() if not PetStableFrame then return end if not SFramesDB.petStablePos then return end local pos = SFramesDB.petStablePos PetStableFrame:ClearAllPoints() PetStableFrame:SetPoint(pos.point, UIParent, pos.relPoint, pos.x, pos.y) end -------------------------------------------------------------------------------- -- Event-driven skin application + position restore -------------------------------------------------------------------------------- local eventFrame = CreateFrame("Frame") eventFrame:RegisterEvent("PET_STABLE_SHOW") eventFrame:SetScript("OnEvent", function() if event == "PET_STABLE_SHOW" then ApplySkin() RestorePosition() end end) if PetStableFrame then local origOnShow = PetStableFrame:GetScript("OnShow") PetStableFrame:SetScript("OnShow", function() if origOnShow then origOnShow() end ApplySkin() RestorePosition() end) end