diff --git a/CHANGELOG.md b/CHANGELOG.md index a275570c..c0ffd018 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -29,6 +29,8 @@ * (Aura Designer) Text-only icons no longer draw a leftover border (static or expiring). (by Krathe) * (Aura Designer) Aura icon and square borders from older profiles keep their original look after the border rework, instead of appearing thinner or floating in a gap. (by Krathe) * (Buff/Debuff) Icon borders from older profiles no longer float in a gap after the border rework — they hug the icon as before. (by Krathe) +* (Pinned & Raid Frames) Fixed frames cascading into a diagonal "staircase" when switching their grow direction between Horizontal and Vertical — the layout now re-flows cleanly without a `/reload`. Pinned layout changes (direction, spacing, size) made during combat are also applied when combat ends, instead of being dropped until the next change. (by Krathe) +* (Test Mode) Fixed pinned-frame preview issues — frames past the fourth showing blank, the resource bar overflowing the frame border, and the AFK timer not counting up. (by Krathe) ## [4.4.1] diff --git a/Features/FlatRaidFrames.lua b/Features/FlatRaidFrames.lua index eb41f940..a731f793 100644 --- a/Features/FlatRaidFrames.lua +++ b/Features/FlatRaidFrames.lua @@ -684,10 +684,24 @@ function FlatRaidFrames:ApplyLayoutSettings(skipRefresh) xOff = 0 end + -- CRITICAL: clear every child's anchor points BEFORE changing the layout + -- attributes below. Each SetAttribute("point"/"columnAnchorPoint"/…) fires + -- SecureGroupHeader_Update while the header is visible, and that function + -- re-anchors displayed children with SetPoint WITHOUT ClearAllPoints-ing them + -- first (Blizzard_RestrictedAddOnEnvironment/SecureGroupHeaders.lua). So flipping + -- the growth point (Horizontal LEFT -> Vertical TOP) leaves each child anchored by + -- both points, cascading diagonally (the "staircase"). Clearing first means every + -- re-layout SetPoint lands clean. Mirrors DF:UpdateRaidHeaderLayoutAttributes / + -- PinnedFrames:ApplyLayoutSettings. + for i = 1, 40 do + local child = header:GetAttribute("child" .. i) + if child then child:ClearAllPoints() end + end + header:SetAttribute("point", point) header:SetAttribute("xOffset", xOff) header:SetAttribute("yOffset", yOff) - + -- Column anchor point local colAnchorPoint, colSpacing if horizontal then diff --git a/Features/PinnedFrames.lua b/Features/PinnedFrames.lua index 76aa4d5a..aa1bd8c8 100644 --- a/Features/PinnedFrames.lua +++ b/Features/PinnedFrames.lua @@ -1584,7 +1584,16 @@ end function PinnedFrames:ApplyLayoutSettings(setIndex) local set = GetSetDB(setIndex) if not set then return end - if InCombatLockdown() then return end + -- Layout changes touch secure header attributes (point/xOffset/size/anchor), + -- which are combat-restricted. Defer to PLAYER_REGEN_ENABLED instead of silently + -- dropping the change — otherwise a Direction/spacing/size tweak made mid-combat + -- (e.g. between pulls in a follower dungeon) never reaches the live header until + -- the next settings poke or a /reload. Mirrors FlatRaidFrames.pendingLayoutUpdate. + if InCombatLockdown() then + self.pendingLayoutUpdate = self.pendingLayoutUpdate or {} + self.pendingLayoutUpdate[setIndex] = true + return + end -- Refresh Test Mode frames regardless of frame type. Cheapest correct -- approach: full Exit+Enter cycle, same as the test count slider uses. @@ -1674,6 +1683,22 @@ function PinnedFrames:ApplyLayoutSettings(setIndex) xOff = 0 end + -- CRITICAL: clear every child's anchor points BEFORE we change the layout + -- attributes below. Each SetAttribute("point"/"columnAnchorPoint"/…) fires + -- SecureGroupHeader_OnAttributeChanged, which (while the header is visible) + -- synchronously runs SecureGroupHeader_Update — and that function re-anchors + -- displayed children with SetPoint WITHOUT ClearAllPoints-ing them first + -- (confirmed in Blizzard_RestrictedAddOnEnvironment/SecureGroupHeaders.lua). + -- So when the growth point flips (Horizontal LEFT -> Vertical TOP) each child + -- keeps its stale anchor AND gains the new one, cascading diagonally (the + -- "staircase"); a /reload only hides it by rebuilding the frames. Clearing the + -- points first means every re-layout SetPoint lands on a clean child. Mirrors + -- DF:UpdateRaidHeaderLayoutAttributes (Frames/Headers.lua). + for i = 1, 40 do + local child = header:GetAttribute("child" .. i) + if child then child:ClearAllPoints() end + end + header:SetAttribute("point", point) header:SetAttribute("xOffset", xOff) header:SetAttribute("yOffset", yOff) @@ -2522,6 +2547,16 @@ eventFrame:SetScript("OnEvent", function(self, event, arg1, ...) PinnedFrames.pendingVisibilityUpdate = nil end + -- Replay layout changes (Direction/spacing/size/anchor) that were attempted + -- in combat. Skipped harmlessly when pendingReinitialize already ran above + -- (it returns early and re-applies every set's layout via ProcessAllSets). + if PinnedFrames.pendingLayoutUpdate then + for idx in pairs(PinnedFrames.pendingLayoutUpdate) do + PinnedFrames:ApplyLayoutSettings(idx) + end + PinnedFrames.pendingLayoutUpdate = nil + end + -- Reset slot allocator + reapply layout now that we're out of combat. -- Fresh pull starts with all slots free; any frames still visible -- (rare — e.g. we left combat mid-add) re-enter via onBossShow. @@ -2992,8 +3027,9 @@ function PinnedFrames:EnsurePlayerTestFramePool(setIndex, count, isRaidMode, isB local container = self.testContainers[setIndex] if not container then return end if count < 1 then count = 1 end - -- Boss mode caps at 8 (WoW API limit); player mode caps at 40 (max raid) - local cap = isBossSet and 8 or 40 + -- Boss mode caps at 8 (WoW API limit); raid player sets at 40 (max raid); + -- party player sets at 5 (a party can't exceed 5). + local cap = isBossSet and 8 or (isRaidMode and 40 or 5) if count > cap then count = cap end self.testFrames[setIndex] = self.testFrames[setIndex] or {} @@ -3043,8 +3079,10 @@ function PinnedFrames:ApplyPlayerTestLayout(setIndex, set, isRaidMode) local anchor = GetContainerAnchorPoint(set) local n = set.testCount or 3 + -- Boss: 8 (WoW limit); raid player: 40 (max raid); party player: 5 (party max). + local maxN = IsBossSet(set) and 8 or (isRaidMode and 40 or 5) if n < 1 then n = 1 end - if n > 40 then n = 40 end + if n > maxN then n = maxN end -- Size container to fit N frames in the set's layout (mirrors -- ResizeContainer for real pinned sets). Frames anchor inside at the @@ -3165,7 +3203,7 @@ function PinnedFrames:EnterTestMode() if set and set.enabled then local isBossSet = IsBossSet(set) local n = set.testCount or 3 - local cap = isBossSet and 8 or 40 + local cap = isBossSet and 8 or (isRaidMode and 40 or 5) if n < 1 then n = 1 end if n > cap then n = cap end diff --git a/Frames/Bars.lua b/Frames/Bars.lua index b50cc972..00990f84 100644 --- a/Frames/Bars.lua +++ b/Frames/Bars.lua @@ -67,154 +67,133 @@ function DF:ShouldShowResourceBar(unit, db) return true end -function DF:ApplyResourceBarLayout(frame) - if not frame then return end - - -- Use raid DB for raid frames, party DB for party frames - local db = DF:GetFrameDB(frame) - - -- The power bar is created in Frames/Create.lua - if not frame.dfPowerBar then return end - - local bar = frame.dfPowerBar - - -- Check if resource bar should be shown - if not db.resourceBarEnabled then - bar:Hide() - return +-- Shared resource-bar GEOMETRY + appearance: texture, orientation/fill, size +-- (incl. the border inset), anchor, frame level, background, and border. Both the +-- live layout (DF:ApplyResourceBarLayout) and the test render (DF:UpdateTestPowerBar) +-- call this so the two can never drift. The caller does the enabled/role checks and +-- sets the bar VALUE + fill COLOUR (live UnitPower vs test mock) — the only per-caller +-- part. Assumes the bar should be shown. +function DF:LayoutResourceBar(frame, db) + local bar = frame and frame.dfPowerBar + if not bar then return end + + bar:Show() + bar:ClearAllPoints() + + -- Fill texture (configurable; defaults to the DF house texture) + DF:SafeSetStatusBarTexture(bar, db.resourceBarTexture or "Interface\\AddOns\\DandersFrames\\Media\\DF_Minimalist") + + -- Orientation & Fill Direction + bar:SetOrientation(db.resourceBarOrientation or "HORIZONTAL") + bar:SetReverseFill(db.resourceBarReverseFill) + + local isVertical = (db.resourceBarOrientation == "VERTICAL") + local length = db.resourceBarWidth or 50 + local thickness = db.resourceBarHeight or 4 + local ppLength = db.pixelPerfect and DF:PixelPerfect(length) or length + local ppThickness = db.pixelPerfect and DF:PixelPerfect(thickness) or thickness + + -- Compute health bar dimensions from settings instead of GetWidth/GetHeight + -- which can return stale values before WoW's layout engine processes anchor changes. + -- Prefer a pinned set's resolved size (Match baseline + Width/Height override) so a + -- "Match Frame Width" resource bar tracks the pinned frame, not the shared per-mode + -- db width. Main frames have no stamp and use the mode db. + local padding = db.framePadding or 0 + local frameWidth = frame.dfPinnedWidth or db.frameWidth or 120 + local frameHeight = frame.dfPinnedHeight or db.frameHeight or 50 + if db.pixelPerfect and DF.PixelPerfect then + frameWidth = DF:PixelPerfect(frameWidth) + frameHeight = DF:PixelPerfect(frameHeight) + padding = DF:PixelPerfect(padding) + end + local healthBarWidth = frameWidth - (2 * padding) + local healthBarHeight = frameHeight - (2 * padding) + + -- Account for frame border inset (matches other bar calculations) + local borderInset = 0 + if db.frameShowBorder ~= false then + borderInset = db.frameBorderSize or 1 + end + -- When Pixel Perfect is on, borderInset must be snapped to a whole screen pixel + -- before being subtracted from healthBarWidth — otherwise barW is fractional and + -- WoW rounds it differently per frame, producing a 1px gap alternating left/right. + local bInset = db.pixelPerfect and DF:PixelPerfect(borderInset) or borderInset + + if isVertical then + -- SWAP: "Width" applies to Height (Length), "Height" applies to Width (Thickness) + bar:SetWidth(ppThickness) + bar:SetHeight(ppLength) + if db.resourceBarMatchWidth and healthBarHeight > 1 then + bar:SetHeight(healthBarHeight - bInset * 2) + end + else + bar:SetWidth(ppLength) + bar:SetHeight(ppThickness) + if db.resourceBarMatchWidth and healthBarWidth > 1 then + bar:SetWidth(healthBarWidth - bInset * 2) + end end - - -- Need unit for role check - if not frame.unit then - bar:Hide() - return + + local anchor = db.resourceBarAnchor or "CENTER" + bar:SetPoint(anchor, frame, anchor, db.resourceBarX or 0, db.resourceBarY or 0) + + -- Frame level - relative to the main frame. Default 2 puts it below the frame + -- border (at +10); values above 10 render above it. + local frameLevelOffset = db.resourceBarFrameLevel or 2 + bar:SetFrameLevel(frame:GetFrameLevel() + frameLevelOffset) + if bar.border then + bar.border:SetFrameLevel(bar:GetFrameLevel() + 1) end - -- Check role-based filtering - if not DF:ShouldShowResourceBar(frame.unit, db) then - bar:Hide() - return + -- Background visibility and color + if bar.bg then + if db.resourceBarBackgroundEnabled ~= false then -- Default to enabled + bar.bg:Show() + local bgC = db.resourceBarBackgroundColor or {r = 0.1, g = 0.1, b = 0.1, a = 0.8} + bar.bg:SetColorTexture(bgC.r, bgC.g, bgC.b, bgC.a or 0.8) + else + bar.bg:Hide() + end end - -- Bar is visible - apply layout - do - bar:Show() - bar:ClearAllPoints() + -- Border via unified DF.Border backend (Stage 4.2). ctx.unit / ctx.frame let the + -- Class / Role colour resolvers fire on live and test frames alike (test frames + -- resolve via ctx.frame's dfIsTestFrame). + if bar.border then + DF.Border:Apply(bar.border, DF.Border:BuildSpec(db, "resourceBar", { + unit = frame.unit, + frame = frame, + })) + end +end - -- Fill texture (configurable; defaults to the DF house texture) - DF:SafeSetStatusBarTexture(bar, db.resourceBarTexture or "Interface\\AddOns\\DandersFrames\\Media\\DF_Minimalist") +function DF:ApplyResourceBarLayout(frame) + if not frame then return end - -- Orientation & Fill Direction - bar:SetOrientation(db.resourceBarOrientation or "HORIZONTAL") - bar:SetReverseFill(db.resourceBarReverseFill) + -- Use raid DB for raid frames, party DB for party frames + local db = DF:GetFrameDB(frame) - local isVertical = (db.resourceBarOrientation == "VERTICAL") - local length = db.resourceBarWidth or 50 - local thickness = db.resourceBarHeight or 4 + -- The power bar is created in Frames/Create.lua + if not frame.dfPowerBar then return end + local bar = frame.dfPowerBar - -- Apply pixel-perfect adjustments - local ppLength = db.pixelPerfect and DF:PixelPerfect(length) or length - local ppThickness = db.pixelPerfect and DF:PixelPerfect(thickness) or thickness - - -- Compute health bar dimensions from settings instead of GetWidth/GetHeight - -- which can return stale values before WoW's layout engine processes anchor changes - local padding = db.framePadding or 0 - -- Prefer a pinned set's resolved size (Match baseline + Width/Height override) so a - -- "Match Frame Width" resource bar tracks the pinned frame, not the shared - -- per-mode db width. Main frames have no stamp and use the mode db. - local frameWidth = frame.dfPinnedWidth or db.frameWidth or 120 - local frameHeight = frame.dfPinnedHeight or db.frameHeight or 50 - if db.pixelPerfect and DF.PixelPerfect then - frameWidth = DF:PixelPerfect(frameWidth) - frameHeight = DF:PixelPerfect(frameHeight) - padding = DF:PixelPerfect(padding) - end - local healthBarWidth = frameWidth - (2 * padding) - local healthBarHeight = frameHeight - (2 * padding) - - -- Account for frame border inset (matches other bar calculations) - local borderInset = 0 - if db.frameShowBorder ~= false then - borderInset = db.frameBorderSize or 1 - end + -- Enabled + unit + role gates + if not db.resourceBarEnabled then bar:Hide() return end + if not frame.unit then bar:Hide() return end + if not DF:ShouldShowResourceBar(frame.unit, db) then bar:Hide() return end - if isVertical then - -- SWAP: "Width" Value applies to Height (Length), "Height" value applies to Width (Thickness) - bar:SetWidth(ppThickness) - bar:SetHeight(ppLength) - else - -- NORMAL: "Width" Value applies to Width, "Height" value applies to Height - bar:SetWidth(ppLength) - bar:SetHeight(ppThickness) - end - - -- When Pixel Perfect is on, borderInset must be snapped to a whole screen pixel - -- before being subtracted from healthBarWidth. Without this, barW is a fractional - -- screen-pixel value and WoW rounds it differently per frame depending on screen - -- position, producing a 1-pixel gap alternating left/right. Snapping borderInset - -- guarantees (FW - barW) = 2*(P+K) screen pixels (always even), so centering - -- always lands on a clean pixel boundary regardless of frame width parity. - local bInset = db.pixelPerfect and DF:PixelPerfect(borderInset) or borderInset - - if isVertical then - if db.resourceBarMatchWidth then - if healthBarHeight > 1 then - bar:SetHeight(healthBarHeight - bInset * 2) - end - end - else - if db.resourceBarMatchWidth then - if healthBarWidth > 1 then - bar:SetWidth(healthBarWidth - bInset * 2) - end - end - end + -- Shared geometry/appearance (size, anchor, level, background, border). + DF:LayoutResourceBar(frame, db) - local anchor = db.resourceBarAnchor or "CENTER" - bar:SetPoint(anchor, frame, anchor, db.resourceBarX or 0, db.resourceBarY or 0) - - -- Frame level - relative to the main frame, not health bar - -- Default of 2 puts it below the frame border (which is at +10) - -- Values above 10 will render above the frame border - local frameLevelOffset = db.resourceBarFrameLevel or 2 - bar:SetFrameLevel(frame:GetFrameLevel() + frameLevelOffset) - - -- Border frame level needs to be above the bar itself - if bar.border then - bar.border:SetFrameLevel(bar:GetFrameLevel() + 1) - end - - -- Background visibility and color - if bar.bg then - if db.resourceBarBackgroundEnabled ~= false then -- Default to enabled - bar.bg:Show() - local bgC = db.resourceBarBackgroundColor or {r = 0.1, g = 0.1, b = 0.1, a = 0.8} - bar.bg:SetColorTexture(bgC.r, bgC.g, bgC.b, bgC.a or 0.8) - else - bar.bg:Hide() - end - end - - -- Border via unified DF.Border backend (Stage 4.2). BuildSpec reads - -- canonical resourceBar*Border* keys; ctx.unit / ctx.frame let the - -- Class / Role colour resolvers fire on live and test frames alike. - if bar.border then - DF.Border:Apply(bar.border, DF.Border:BuildSpec(db, "resourceBar", { - unit = frame.unit, - frame = frame, - })) - end - - -- Set power value and color immediately so the bar doesn't appear white - local unit = frame.unit - if unit and UnitExists(unit) then - local power = UnitPower(unit) - local maxPower = UnitPowerMax(unit) - bar:SetMinMaxValues(0, maxPower) - bar:SetValue(power) - local cr, cg, cb = DF:GetResourceBarColor(unit, db) - bar:SetStatusBarColor(cr, cg, cb, 1) - end + -- Live value + fill colour (the only part the test path does differently). + local unit = frame.unit + if unit and UnitExists(unit) then + local power = UnitPower(unit) + local maxPower = UnitPowerMax(unit) + bar:SetMinMaxValues(0, maxPower) + bar:SetValue(power) + local cr, cg, cb = DF:GetResourceBarColor(unit, db) + bar:SetStatusBarColor(cr, cg, cb, 1) end end diff --git a/Frames/StatusIcons.lua b/Frames/StatusIcons.lua index 26ab349c..71956a95 100644 --- a/Frames/StatusIcons.lua +++ b/Frames/StatusIcons.lua @@ -1215,6 +1215,30 @@ afkTickerFrame:SetScript("OnUpdate", function(self, elapsed) end end end + + -- Pinned test frames live in their own per-set pools (not testPartyFrames/ + -- testRaidFrames), so the loops above never reach them — without this their AFK + -- countdown freezes while the main party/raid test frames tick. Mirror the same + -- pass: index by the frame's own dfTestIndex + mode, render via UpdateTestStatusIcons. + if DF.PinnedFrames and DF.PinnedFrames.IsTestModeActive and DF.PinnedFrames:IsTestModeActive() then + local pools = DF.PinnedFrames.testFrames + if pools then + for setIndex = 1, (DF.PinnedFrames.MAX_SETS or 4) do + local pool = pools[setIndex] + if pool then + for i = 1, #pool do + local frame = pool[i] + if frame and frame.dfTestIndex and frame.afkIcon and frame.afkIcon:IsShown() then + local testData = DF:GetTestUnitData(frame.dfTestIndex, frame.isRaidFrame, frame.isPinnedBossFrame) + if testData and testData.isAFK then + DF:UpdateTestStatusIcons(frame, testData) + end + end + end + end + end + end + end end) -- ============================================================ diff --git a/Options/Options.lua b/Options/Options.lua index af85f6cb..78f11cea 100644 --- a/Options/Options.lua +++ b/Options/Options.lua @@ -3575,9 +3575,9 @@ function DF:SetupGUIPages(GUI, CreateCategory, CreateSubTab, BuildPage) ) -- Test Count slider: how many test frames show when Test Mode is - -- active. Boss mode: 1–8 (hard WoW limit). Player mode: 1–10 - -- (covers typical pinned set sizes; range kept modest since pinned - -- sets rarely need more than that for layout verification). + -- active. Boss mode: 1–8 (hard WoW limit). Party player sets: 1–5 + -- (a party can't exceed 5). Raid player sets: 1–10 (covers typical + -- pinned set sizes; range kept modest for layout verification). local function OnTestCountChanged() if not DF.PinnedFrames then return end if DF.PinnedFrames.IsTestModeActive and DF.PinnedFrames:IsTestModeActive() then @@ -3585,7 +3585,7 @@ function DF:SetupGUIPages(GUI, CreateCategory, CreateSubTab, BuildPage) DF.PinnedFrames:EnterTestMode() end end - local testMax = IsCurrentBossMode() and 8 or 10 + local testMax = IsCurrentBossMode() and 8 or (GUI.SelectedMode == "raid" and 10 or 5) frameTypeGroup:AddWidget( CreateRefreshableSlider(self.child, L["Test Count"], 1, testMax, 1, "testCount", OnTestCountChanged), 55 diff --git a/TestMode/TestMode.lua b/TestMode/TestMode.lua index 5e452284..df28076c 100644 --- a/TestMode/TestMode.lua +++ b/TestMode/TestMode.lua @@ -274,9 +274,13 @@ function DF:GetTestUnitData(index, isRaid, isBoss) return result end - -- Party test data (original logic) - local data = DF.TestData.units[index + 1] - if not data then return nil end + -- Party test data. DF.TestData.units holds the 5 real-party scenarios (a party + -- maxes at 5). Main party frames are 0-based (index 0-4 -> units[1-5]); pinned + -- PLAYER frames are 1-based, so a raw index+1 lookup overruns the table by one and + -- blanks the last frame. Wrap into the table so every party-mode frame maps to a + -- distinct scenario instead of nil. (Counts are capped at 5 at the call sites.) + local units = DF.TestData.units + local data = units[(index % #units) + 1] local result = { index = index, -- Include index for test mode features @@ -368,7 +372,7 @@ function DF:StartTestAnimation() -- both player-mode and boss-mode pinned sets). Lightweight. if DF.PinnedFrames and DF.PinnedFrames.IsTestModeActive and DF.PinnedFrames:IsTestModeActive() then - for setIndex = 1, 2 do + for setIndex = 1, (DF.PinnedFrames.MAX_SETS or 4) do local pool = DF.PinnedFrames.testFrames[setIndex] if pool then for i = 1, #pool do @@ -1704,7 +1708,7 @@ function DF:UpdateTestStatusIcons(frame, testData) if showTimer and testAFKStartTimes[frameKey] then local elapsed = math.floor(GetTime() - testAFKStartTimes[frameKey]) local timerStr = FormatTestAFKTime(elapsed) - + if db.afkIconShowText then -- Text mode: show "AFK 1:23" statusText = statusText .. " " .. timerStr @@ -3465,69 +3469,21 @@ function DF:UpdateTestPowerBar(frame, testData) -- Power bar should already exist from Frames/Create.lua if not frame.dfPowerBar then return end - - local bar = frame.dfPowerBar - local powerHeight = db.resourceBarHeight or 4 - local powerAnchor = db.resourceBarAnchor or "BOTTOM" - - -- Position power bar (floating style - anchored to a point, not spanning frame) - bar:ClearAllPoints() - local orientation = db.resourceBarOrientation or "HORIZONTAL" - bar:SetOrientation(orientation) - bar:SetReverseFill(db.resourceBarReverseFill or false) + -- Shared geometry/appearance (size incl. border inset, anchor, frame level, + -- background, border) — identical to the live DF:ApplyResourceBarLayout, so the + -- live and test renders can never drift. + DF:LayoutResourceBar(frame, db) - local isVertical = (orientation == "VERTICAL") - local length = db.resourceBarWidth or 50 - local thickness = db.resourceBarHeight or 4 - - if db.pixelPerfect and DF.PixelPerfect then - length = DF:PixelPerfect(length) - thickness = DF:PixelPerfect(thickness) - end - - -- Compute health bar dimensions from settings (not GetWidth/GetHeight which - -- can return stale values before WoW layout processes anchor changes). - -- Prefer a pinned test frame's resolved size so a "Match Frame Width" resource - -- bar tracks the pinned size, not the shared per-mode db width. - local padding = db.framePadding or 0 - local frameWidth = frame.dfPinnedWidth or db.frameWidth or 120 - local frameHeight = frame.dfPinnedHeight or db.frameHeight or 50 - if db.pixelPerfect and DF.PixelPerfect then - frameWidth = DF:PixelPerfect(frameWidth) - frameHeight = DF:PixelPerfect(frameHeight) - padding = DF:PixelPerfect(padding) - end - local healthBarWidth = frameWidth - (2 * padding) - local healthBarHeight = frameHeight - (2 * padding) - - if isVertical then - bar:SetWidth(thickness) - bar:SetHeight(length) - if db.resourceBarMatchWidth then - if healthBarHeight > 1 then - bar:SetHeight(healthBarHeight) - end - end - else - bar:SetWidth(length) - bar:SetHeight(thickness) - if db.resourceBarMatchWidth then - if healthBarWidth > 1 then - bar:SetWidth(healthBarWidth) - end - end - end + local bar = frame.dfPowerBar - local offsetX = db.resourceBarX or 0 - local offsetY = db.resourceBarY or 0 - bar:SetPoint(powerAnchor, frame, powerAnchor, offsetX, offsetY) - - -- Set value + -- Value (mock — there's no real unit to query). bar:SetMinMaxValues(0, 1) bar:SetValue(testData.powerPercent or 0.8) - - -- Set color based on role using customizable power colors + + -- Fill colour: mirror DF:GetResourceBarColor's mode resolution (Power / Class / + -- Custom) using the mock unit's testData (reads resourceBarColorMode so the + -- preview reflects a "Class" pick from the Color Mode dropdown). local powerToken if testData.role == "HEALER" then powerToken = "MANA" @@ -3536,11 +3492,6 @@ function DF:UpdateTestPowerBar(frame, testData) else powerToken = "ENERGY" end - - -- Mirror DF:GetResourceBarColor's mode resolution (Power / Class / Custom), - -- but using the mock unit's testData since there's no real unit to query. - -- Reads resourceBarColorMode (not just the legacy resourceBarClassColor - -- boolean) so the preview reflects a "Class" pick from the Color Mode dropdown. local mode = db.resourceBarColorMode or (db.resourceBarClassColor and "CLASS" or "POWER_TYPE") local classColor = mode == "CLASS" and testData.class and DF:GetClassColor(testData.class) if mode == "CUSTOM" then @@ -3552,32 +3503,7 @@ function DF:UpdateTestPowerBar(frame, testData) local powerColor = DF:GetPowerColor(powerToken) bar:SetStatusBarColor(powerColor.r, powerColor.g, powerColor.b, 1) end - - -- Background visibility and color - if bar.bg then - if db.resourceBarBackgroundEnabled ~= false then - bar.bg:Show() - local bgC = db.resourceBarBackgroundColor or {r = 0.1, g = 0.1, b = 0.1, a = 0.8} - bar.bg:SetColorTexture(bgC.r, bgC.g, bgC.b, bgC.a or 0.8) - else - bar.bg:Hide() - end - end - - -- Frame level - relative to main frame, not health bar - local frameLevelOffset = db.resourceBarFrameLevel or 2 - bar:SetFrameLevel(frame:GetFrameLevel() + frameLevelOffset) - - -- Border via unified DF.Border backend (Stage 4.2). ctx.frame is the - -- test unit frame (carries dfIsTestFrame + index + isRaidFrame) so - -- Class / Role resolvers pull test class/role from GetTestUnitData. - if bar.border then - bar.border:SetFrameLevel(bar:GetFrameLevel() + 1) - DF.Border:Apply(bar.border, DF.Border:BuildSpec(db, "resourceBar", { - frame = frame, - })) - end - + -- Apply dead / health-based / OOR alpha local alpha = 1.0 if frame.dfTestDeadFadeAlphas and frame.dfTestDeadFadeAlphas.power then