Show cooldown text through ClassicAPI's native countdown

The cooldown module no longer builds its own text frame with a 10 Hz
OnUpdate per cooldown. The CooldownFrame_SetTimer hook now configures
each Cooldown frame's own countdown instead: SetHideCountdownNumbers
for the per-frame text toggles and omnicc's noCooldownCount,
SetMinimumCountdownDuration for the GCD threshold, and
GetCountdownFontString for the pfUI font and size. The engine ticks
the numbers, gated by the countdownForCooldowns cvar. The cvar gets a
checkbox in the Cooldown settings, and pfUI.SetupCVars turns it on.

The text comes from one shared C_StringUtil numeric rule formatter,
given to each frame with SetCountdownFormatter. Its breakpoints keep
pfUI's format: the number in its unit's cooldown color, a unit (m/h/d)
once the unit below passes 99, and tenths in the low color when
milliseconds are on. Values now round up. GetColoredTimeString formats
through the same formatter, so the buff, buffwatch and tooltip timers
match. A config:changed callback rebuilds the breakpoints, and every
frame picks them up on its next update.

The paper-doll slots swap their indicator Models for Cooldown frames,
as the spellbook and bags already do. They and the spellbook take the
BLIZZARD tag when they are created, replacing the per-update hooks in
the skin module. Models get no numbers. Hide Foreign Cooldown
Animations clears the draw flags instead of setting alpha to 0, so the
numbers stay visible. The buff module's sweep hides its numbers
because buff.timer carries the text.
This commit is contained in:
Brues
2026-10-31 18:45:17 -05:00
parent 84da90c7d4
commit 93ab07a662
9 changed files with 97 additions and 207 deletions
+3 -2
View File
@@ -130,7 +130,7 @@ pfUI:RegisterModule("buff", function ()
-- overlay lifts the texts above the sweep: a child frame renders above
-- all of its parent's own regions, so the timer would dim under the
-- dark wedge otherwise (same reason pfCooldownText is its own frame)
-- dark wedge otherwise
buff.overlay = CreateFrame("Frame", nil, buff)
buff.overlay:SetAllPoints(buff)
@@ -148,8 +148,9 @@ pfUI:RegisterModule("buff", function ()
-- radial sweep (the template covers the button and starts hidden).
-- RefreshBuffButton drives it through the frame's own methods, never
-- CooldownFrame_SetTimer, so the cooldown module's hook leaves it alone;
-- text stays with buff.timer
-- text stays with buff.timer, so the frame's own numbers are hidden
buff.cd = CreateFrame("Cooldown", buttonName .. "Cooldown", buff, "CooldownFrameTemplate")
buff.cd:SetHideCountdownNumbers(true)
buff.cd:SetFrameLevel(buff:GetFrameLevel() + 1)
buff.overlay:SetFrameLevel(buff:GetFrameLevel() + 2)
+34 -123
View File
@@ -1,71 +1,17 @@
pfUI:RegisterModule("cooldown", function ()
-- cache values
-- local lowcolor = {strsplit(",", C.appearance.cd.lowcolor)}
-- local normalcolor = {strsplit(",", C.appearance.cd.normalcolor)}
-- local minutecolor = {strsplit(",", C.appearance.cd.minutecolor)}
-- local hourcolor = {strsplit(",", C.appearance.cd.hourcolor)}
-- local daycolor = {strsplit(",", C.appearance.cd.daycolor)}
local function pfCooldownOnUpdate()
-- Throttle FIRST. One of these runs per visible cooldown text, every frame,
-- so anything above this gate is multiplied by the frame rate and by how
-- many cooldowns are ticking.
local now = GetTime()
if (this.tick or 0) > now then return end
this.tick = now + .1
local parent = this:GetParent()
if not parent then this:Hide() return end
-- avoid to set cooldowns on invalid frames. The cooldown frame is stashed
-- at creation: resolving it as _G[parent:GetName() .. "Cooldown"] built and
-- interned that string twice per call, and this is the hottest path in the
-- UI. The stashed reference is also the frame itself rather than a guess
-- from its parent's name, so it holds for cooldowns named anything else.
if this.cooldown and not this.cooldown:IsShown() then
this:Hide()
return
end
-- fix own alpha value (should be inherited, but somehow isn't always)
if this:GetAlpha() ~= parent:GetAlpha() then
this:SetAlpha(parent:GetAlpha())
end
if this.start < now then
-- calculating remaining time as it should be
local remaining = this.duration - (now - this.start)
if remaining >= 0 then
this.text:SetText(GetColoredTimeString(remaining))
else
this:Hide()
end
else
-- I have absolutely no idea, but it works:
-- https://github.com/Stanzilla/WoWUIBugs/issues/47
local currentTime = time()
local startupTime = currentTime - now
-- just a simplification of: ((2^32) - (start * 1000)) / 1000
local cdTime = (2 ^ 32) / 1000 - this.start
local cdStartTime = startupTime - cdTime
local cdEndTime = cdStartTime + this.duration
local remaining = cdEndTime - currentTime
if remaining >= 0 then
this.text:SetText(GetColoredTimeString(remaining))
else
this:Hide()
end
end
end
-- The numbers are the Cooldown frame's own countdown (ClassicAPI): the
-- engine ticks them, gated by the countdownForCooldowns cvar, and writes
-- them through pfUI's shared formatter. pfUI only configures each frame as
-- its timer is set.
local function GetCooldownHeight(cooldown)
return cooldown:GetParent() and cooldown:GetParent():GetHeight() or cooldown:GetHeight() or 0
end
-- sizes the text to the cooldown's current height; the dynamic size follows
-- the frame, so SetCooldown calls this again whenever that height changes
local function pfUpdateCooldownFont(cooldown)
-- sizes the numbers to the cooldown's current height; the dynamic size
-- follows the frame, so SetCooldown calls this again whenever that height
-- changes
local function UpdateCountdownFont(cooldown)
local size
if not cooldown.pfCooldownType then
size = tonumber(C.appearance.cd.font_size_foreign)
@@ -77,88 +23,53 @@ pfUI:RegisterModule("cooldown", function ()
size = tonumber(C.appearance.cd.font_size)
end
local height = GetCooldownHeight(cooldown)
cooldown.pfCountdownHeight = height
-- enforce dynamic font size
if C.appearance.cd.dynamicsize == "1" then
local height = GetCooldownHeight(cooldown)
cooldown.pfCooldownText.height = height
size = math.max((height > 0 and height * .64 or 16), size)
end
cooldown.pfCooldownText.text:SetFont(pfUI.media[C.appearance.cd.font], size, "OUTLINE")
end
local textcount = 0
local function pfCreateCoolDown(cooldown, start, duration)
textcount = textcount + 1
local name = cooldown.GetName and cooldown:GetName() or "pfCooldown" .. textcount
cooldown.pfCooldownText = CreateFrame("Frame", name .. "Text", cooldown:GetParent())
cooldown.pfCooldownText.cooldown = cooldown
cooldown.pfCooldownText:SetAllPoints(cooldown)
cooldown.pfCooldownText:SetFrameLevel(cooldown:GetParent():GetFrameLevel() + 2)
cooldown.pfCooldownText.text = cooldown.pfCooldownText:CreateFontString(name .. "TextString", "OVERLAY")
pfUpdateCooldownFont(cooldown)
cooldown.pfCooldownText.text:SetPoint("CENTER", cooldown.pfCooldownText, "CENTER", 0, 0)
cooldown.pfCooldownText:SetScript("OnUpdate", pfCooldownOnUpdate)
cooldown:GetCountdownFontString():SetFont(pfUI.media[C.appearance.cd.font], size, "OUTLINE")
end
-- hook
local function SetCooldown(this, start, duration, enable)
-- abort on unknown frames
if C.appearance.cd.foreign == "0" and not this.pfCooldownType then
local function SetCooldown(this)
-- 1.12's indicator Models have no countdown and keep the stock timer
if not this:IsObjectType("Cooldown") then return end
-- no numbers on unknown frames, nor on frames with omnicc's disable flag
if (C.appearance.cd.foreign == "0" and not this.pfCooldownType) or this.noCooldownCount then
this:SetHideCountdownNumbers(true)
return
end
-- add support for omnicc's disable flag
if this.noCooldownCount then
return
this:SetHideCountdownNumbers(this.pfCooldownStyleText == 0)
-- no numbers for global cooldowns, except on frames that time auras
if not this.pfCooldownType or this.pfCooldownType == "NOGCD" then
this:SetMinimumCountdownDuration(tonumber(C.appearance.cd.threshold) * 1000)
else
this:SetMinimumCountdownDuration(0)
end
-- realign the indicator Models that stock buttons build; the model is
-- drawn at 36px and needs scaling to its button. A Cooldown draws its
-- sweep to its own rect.
local parent = this.GetParent and not this:IsObjectType("Cooldown") and this:GetParent()
if parent and parent:GetWidth() / 36 > 0 then
this:SetScale(parent:GetWidth() / 36)
this:SetPoint("TOPLEFT", parent, "TOPLEFT", -1, 1)
this:SetPoint("BOTTOMRIGHT", parent, "BOTTOMRIGHT", 1, -1)
end
-- don't draw global cooldowns
if this.pfCooldownType == "NOGCD" and duration < tonumber(C.appearance.cd.threshold) then
start, duration = 0, 0
end
-- disable GCDs on non pfUI frames
if not this.pfCooldownType and duration < tonumber(C.appearance.cd.threshold) then
start, duration = 0, 0
end
this:SetCountdownFormatter(GetCountdownFormatter())
-- hide foreign animations; frames that set pfCooldownStyleAnimation
-- configure their own sweep (SetDrawSwipe, SetReverse)
-- configure their own sweep. The draw flags, not alpha, so the numbers
-- stay visible
if not this.pfCooldownStyleAnimation and C.appearance.cd.hideanim == "1" then
this:SetAlpha(0)
this:SetDrawSwipe(false)
this:SetDrawBling(false)
end
-- print time as text on cooldown frames
if ( not this.pfCooldownStyleText or this.pfCooldownStyleText == 1)
and start > 0 and duration > 0 and (not enable or enable > 0) then
if( not this.pfCooldownText ) then
pfCreateCoolDown(this, start, duration)
elseif C.appearance.cd.dynamicsize == "1" and GetCooldownHeight(this) ~= this.pfCooldownText.height then
pfUpdateCooldownFont(this)
end
this.pfCooldownText.start = start
this.pfCooldownText.duration = duration
this.pfCooldownText:Show()
elseif(this.pfCooldownText) then
this.pfCooldownText:Hide()
if GetCooldownHeight(this) ~= this.pfCountdownHeight then
UpdateCountdownFont(this)
end
end
-- hook the SetTimer function that every cooldown, Cooldown frame or
-- stock indicator Model, is driven through.
hooksecurefunc("CooldownFrame_SetTimer", SetCooldown)
end)
end)
+1
View File
@@ -1597,6 +1597,7 @@ pfUI:RegisterModule("gui", function ()
end)
CreateGUIEntry(T["Settings"], T["Cooldown"], function()
CreateConfig(nil, COUNTDOWN_FOR_COOLDOWNS_TEXT, "CVAR", "countdownForCooldowns", "checkbox")
CreateConfig(U["buff"], T["Show Milliseconds When Timer Runs Out"], C.appearance.cd, "milliseconds", "checkbox")
CreateConfig(nil, T["Cooldown Color (Less than 3 Sec)"], C.appearance.cd, "lowcolor", "color")
CreateConfig(nil, T["Cooldown Color (Seconds)"], C.appearance.cd, "normalcolor", "color")
-12
View File
@@ -15,18 +15,6 @@ pfUI:RegisterModule("skin", function ()
DurabilityFrame:SetFrameLevel(1)
DurabilityFrame.SetPoint = function() return end
if C.appearance.cd.blizzard == "1" then
hooksecurefunc("PaperDollItemSlotButton_Update", function()
local cooldown = _G[this:GetName().."Cooldown"]
if cooldown then cooldown.pfCooldownType = "BLIZZARD" end
end)
hooksecurefunc("SpellButton_UpdateButton", function()
local cooldown = _G[this:GetName().."Cooldown"]
if cooldown then cooldown.pfCooldownType = "BLIZZARD" end
end)
end
for i = 1, UIDROPDOWNMENU_MAXLEVELS do
CreateBackdrop(_G["DropDownList"..i.."MenuBackdrop"], nil, true, .8)
CreateBackdropShadow(_G["DropDownList"..i.."MenuBackdrop"])