diff --git a/CHANGELOG.md b/CHANGELOG.md index d41c4d8..1c3b41a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # DandersFrames Changelog +## [Unreleased] + +### New Features + +* (Frames) Missing textures now fall back to a bundled default instead of rendering black. If a profile you import references a custom or shared-media texture you don't have installed (or the addon that provided it was removed), the affected health bar, background or other bar now uses DandersFrames' default texture and shows a one-time notice — rather than a black/blank bar. (Requires WoW 12.0.7; on earlier versions there is no change.) (by Krathe) + ## [4.4.0] * Behind-the-scenes improvements and groundwork for upcoming features. diff --git a/Core.lua b/Core.lua index 8405f14..1a6a93e 100644 --- a/Core.lua +++ b/Core.lua @@ -432,6 +432,73 @@ function DF:LightweightUpdateBackgroundAlpha() IterateFramesInMode(mode, UpdateBG) end +-- ============================================================ +-- SAFE TEXTURE SETTERS — graceful missing-texture fallback +-- A configured texture can be missing when a profile imported from another user +-- references a 3rd-party/SharedMedia texture this client doesn't have (or the +-- providing addon was removed) — leaving a black/blank bar. C_UIFileAsset +-- (NEW in WoW 12.0.7) — IsKnownFile(asset) reports whether a path is known to +-- the client (shipped OR a known loose addon file); when it says the asset is +-- unknown we substitute a guaranteed-present stock texture. (Note: the API +-- doesn't verify a known loose file still exists on disk, but an uninstalled +-- addon's path is simply not "known", which is exactly the import case we want.) +-- The SetTexture/SetStatusBarTexture `success` bool does NOT work for this — +-- it returns true for any well-formed path even when the file is absent. +-- Feature-detected: on clients without C_UIFileAsset this is INERT (behaves +-- exactly as before), so it's safe to ship now and self-activates on 12.0.7. +-- ============================================================ +-- DF's own bundled default bar texture — ships with the addon, so it's always +-- present when our code runs. This is the "fall back to our default" target. +DF.STOCK_BAR_TEXTURE = "Interface\\AddOns\\DandersFrames\\Media\\DF_Minimalist" +local _df_warnedMissingTexture = {} + +-- false -> asset (texture path or fileID) is definitively NOT known to the client +-- true -> known/present +-- nil -> validation API unavailable (caller leaves the texture as-is) +local function textureKnown(asset) + if asset == nil then return nil end + local api = C_UIFileAsset + if not (api and api.IsKnownFile) then return nil end + local ok, known = pcall(api.IsKnownFile, asset) + if not ok then return nil end + return known and true or false +end + +local function warnMissingTexture(path) + if not path or _df_warnedMissingTexture[path] then return end + _df_warnedMissingTexture[path] = true + if DF.Debug then DF:Debug("TEXTURE", "Missing texture '%s' — using stock fallback", tostring(path)) end + if not DF._warnedAnyMissingTexture then + DF._warnedAnyMissingTexture = true + print("|cff66ccffDandersFrames|r: a configured texture couldn't be loaded and was replaced with a stock texture. Check your texture settings (an imported profile may reference a texture you don't have).") + end +end + +-- StatusBar texture with stock fallback. Returns true if the requested texture +-- loaded, false if the stock fallback was substituted, nil if bar was missing. +function DF:SafeSetStatusBarTexture(bar, path, stock) + if not bar then return end + if textureKnown(path) == false then + bar:SetStatusBarTexture(stock or DF.STOCK_BAR_TEXTURE) + warnMissingTexture(path) + return false + end + bar:SetStatusBarTexture(path) + return true +end + +-- Plain Texture region with stock fallback (same semantics). +function DF:SafeSetTexture(region, path, stock) + if not region then return end + if textureKnown(path) == false then + region:SetTexture(stock or DF.STOCK_BAR_TEXTURE) + warnMissingTexture(path) + return false + end + region:SetTexture(path) + return true +end + -- Update only health bar texture function DF:LightweightUpdateHealthTexture() local mode = DF.GUI and DF.GUI.SelectedMode or "party" @@ -442,7 +509,7 @@ function DF:LightweightUpdateHealthTexture() local function UpdateTex(frame) if frame and frame.healthBar then - frame.healthBar:SetStatusBarTexture(tex) + DF:SafeSetStatusBarTexture(frame.healthBar, tex) end end diff --git a/Frames/Bars.lua b/Frames/Bars.lua index 4704143..dc008e4 100644 --- a/Frames/Bars.lua +++ b/Frames/Bars.lua @@ -631,7 +631,7 @@ function DF:UpdateAbsorb(frame, testIndex) barTex:SetDrawLayer("ARTWORK", 2) end else - customBar:SetStatusBarTexture(tex) + DF:SafeSetStatusBarTexture(customBar, tex) local barTex = customBar:GetStatusBarTexture() if barTex then barTex:SetHorizTile(false) @@ -1106,7 +1106,7 @@ function DF:UpdateAbsorb(frame, testIndex) if type(texture) == "table" then texture = texture.path or "Interface\\TargetingFrame\\UI-StatusBar" end - overflowBar:SetStatusBarTexture(texture) + DF:SafeSetStatusBarTexture(overflowBar, texture) local color = db.absorbBarColor or {r = 1, g = 1, b = 1, a = 0.7} overflowBar:SetStatusBarColor(color.r, color.g, color.b, color.a or 0.7) @@ -1395,7 +1395,7 @@ function DF:UpdateHealAbsorb(frame, testIndex) -- Apply texture only if changed to prevent flickering if bar.currentTexture ~= tex then bar.currentTexture = tex - bar:SetStatusBarTexture(tex) + DF:SafeSetStatusBarTexture(bar, tex) local barTex = bar:GetStatusBarTexture() if barTex then barTex:SetHorizTile(false) @@ -1674,7 +1674,7 @@ end local function StyleHealPredSegment(seg, tex, blendMode, color) if seg.currentTexture ~= tex then seg.currentTexture = tex - seg:SetStatusBarTexture(tex) + DF:SafeSetStatusBarTexture(seg, tex) local t = seg:GetStatusBarTexture() if t then t:SetHorizTile(false); t:SetVertTile(false) @@ -1887,7 +1887,7 @@ function DF:UpdateHealPrediction(frame, testIndex) -- Apply texture if bar.currentTexture ~= tex then bar.currentTexture = tex - bar:SetStatusBarTexture(tex) + DF:SafeSetStatusBarTexture(bar, tex) local barTex = bar:GetStatusBarTexture() if barTex then barTex:SetHorizTile(false) diff --git a/Frames/Core.lua b/Frames/Core.lua index 7e92674..eb5b1dd 100644 --- a/Frames/Core.lua +++ b/Frames/Core.lua @@ -155,8 +155,8 @@ local function SetMissingHealthBarValue(bar, unit, frame) if not texture or texture == "" then texture = db and db.healthTexture or "Interface\\TargetingFrame\\UI-StatusBar" end - bar:SetStatusBarTexture(texture) - + DF:SafeSetStatusBarTexture(bar, texture) + -- Update color based on color mode local colorMode = db and db.missingHealthColorMode or "CUSTOM" local r, g, b, a diff --git a/Frames/Create.lua b/Frames/Create.lua index d956218..5bf4533 100755 --- a/Frames/Create.lua +++ b/Frames/Create.lua @@ -737,7 +737,7 @@ function DF:CreateFrameElements(frame, isRaid) local padding = db.framePadding or 0 frame.missingHealthBar:SetPoint("TOPLEFT", padding, -padding) frame.missingHealthBar:SetPoint("BOTTOMRIGHT", -padding, padding) - frame.missingHealthBar:SetStatusBarTexture(db.healthTexture or "Interface\\TargetingFrame\\UI-StatusBar") + DF:SafeSetStatusBarTexture(frame.missingHealthBar, db.healthTexture or "Interface\\TargetingFrame\\UI-StatusBar") frame.missingHealthBar:SetMinMaxValues(0, 1) frame.missingHealthBar:SetValue(0) frame.missingHealthBar:SetReverseFill(true) @@ -752,7 +752,7 @@ function DF:CreateFrameElements(frame, isRaid) frame.healthBar = CreateFrame("StatusBar", nil, frame) frame.healthBar:SetPoint("TOPLEFT", padding, -padding) frame.healthBar:SetPoint("BOTTOMRIGHT", -padding, padding) - frame.healthBar:SetStatusBarTexture(db.healthTexture or "Interface\\TargetingFrame\\UI-StatusBar") + DF:SafeSetStatusBarTexture(frame.healthBar, db.healthTexture or "Interface\\TargetingFrame\\UI-StatusBar") frame.healthBar:SetMinMaxValues(0, 1) frame.healthBar:SetValue(1) @@ -1421,7 +1421,7 @@ function DF:CreateUnitFrame(unit, index, isRaid) local padding = db.framePadding or 0 frame.missingHealthBar:SetPoint("TOPLEFT", padding, -padding) frame.missingHealthBar:SetPoint("BOTTOMRIGHT", -padding, padding) - frame.missingHealthBar:SetStatusBarTexture(db.healthTexture or "Interface\\TargetingFrame\\UI-StatusBar") + DF:SafeSetStatusBarTexture(frame.missingHealthBar, db.healthTexture or "Interface\\TargetingFrame\\UI-StatusBar") frame.missingHealthBar:SetMinMaxValues(0, 1) -- Will be updated dynamically with UnitHealthMax frame.missingHealthBar:SetValue(0) frame.missingHealthBar:SetReverseFill(true) -- Fill from right side (where health is missing) @@ -1437,7 +1437,7 @@ function DF:CreateUnitFrame(unit, index, isRaid) frame.healthBar = CreateFrame("StatusBar", nil, frame) frame.healthBar:SetPoint("TOPLEFT", padding, -padding) frame.healthBar:SetPoint("BOTTOMRIGHT", -padding, padding) - frame.healthBar:SetStatusBarTexture(db.healthTexture or "Interface\\TargetingFrame\\UI-StatusBar") + DF:SafeSetStatusBarTexture(frame.healthBar, db.healthTexture or "Interface\\TargetingFrame\\UI-StatusBar") frame.healthBar:SetMinMaxValues(0, 1) frame.healthBar:SetValue(1) diff --git a/Frames/Pets.lua b/Frames/Pets.lua index 24ed7de..5c5f674 100644 --- a/Frames/Pets.lua +++ b/Frames/Pets.lua @@ -487,8 +487,8 @@ function DF:ApplyPetFrameStyle(frame) -- Update health bar texture - use path directly (dropdown saves paths) local texture = db.petTexture or "Interface\\TargetingFrame\\UI-StatusBar" - frame.healthBar:SetStatusBarTexture(texture) - frame.healthBar.bg:SetTexture(texture) + DF:SafeSetStatusBarTexture(frame.healthBar, texture) + DF:SafeSetTexture(frame.healthBar.bg, texture) -- Background color local bgColor = db.petBackgroundColor or {r = 0.1, g = 0.1, b = 0.1, a = 0.8} diff --git a/Frames/ReducedMaxHealth.lua b/Frames/ReducedMaxHealth.lua index 7870231..9daf750 100644 --- a/Frames/ReducedMaxHealth.lua +++ b/Frames/ReducedMaxHealth.lua @@ -87,7 +87,7 @@ function DF:UpdateReducedMaxHealth(frame) texturePath = DF:ResolveMediaTexture(texturePath) or texturePath end if texturePath then - bar:SetStatusBarTexture(texturePath) + DF:SafeSetStatusBarTexture(bar, texturePath) end local c = db.reducedMaxHealthColor or DEFAULT_BAR_COLOR diff --git a/Frames/Update.lua b/Frames/Update.lua index 8f43750..850d37d 100644 --- a/Frames/Update.lua +++ b/Frames/Update.lua @@ -402,7 +402,7 @@ function DF:ApplyFrameLayout(frame) else -- Textured background - only call SetTexture if texture path changed if frame.dfCurrentBgTexture ~= bgTexture then - frame.background:SetTexture(bgTexture) + DF:SafeSetTexture(frame.background, bgTexture) frame.background:SetHorizTile(false) frame.background:SetVertTile(false) frame.dfCurrentBgTexture = bgTexture @@ -456,7 +456,7 @@ function DF:ApplyFrameLayout(frame) else -- Textured background - only call SetTexture if texture path changed if frame.dfCurrentBgTexture ~= bgTexture then - frame.background:SetTexture(bgTexture) + DF:SafeSetTexture(frame.background, bgTexture) frame.background:SetHorizTile(false) frame.background:SetVertTile(false) frame.dfCurrentBgTexture = bgTexture @@ -760,7 +760,7 @@ function DF:UpdateUnitFrame(frame, source) -- Textured background - only call SetTexture if texture path changed -- This prevents flickering on every health update if frame.dfCurrentBgTexture ~= bgTexture then - frame.background:SetTexture(bgTexture) + DF:SafeSetTexture(frame.background, bgTexture) frame.background:SetHorizTile(false) frame.background:SetVertTile(false) frame.dfCurrentBgTexture = bgTexture