diff --git a/api/api.lua b/api/api.lua index f0de9340..07b1e57b 100644 --- a/api/api.lua +++ b/api/api.lua @@ -1425,70 +1425,52 @@ function pfUI.api.DisableAutohide(frame) frame:SetAlpha(1) end +-- [ GetCountdownFormatter ] -- +-- The C_StringUtil formatter behind pfUI's times: the number in its unit's +-- cooldown color, rounded up, with a unit (m/h/d) once the unit below +-- passes 99. Cooldown frames share it instead of copying it, so new +-- breakpoints reach every countdown on its next update. +-- return the shared NumericRuleFormatter +local countdown_formatter + +local function ColorCode(color) + local r, g, b = pfUI.api.GetStringColor(color) + return pfUI.api.rgbhex(r, g, b) +end + +local function UpdateCountdownFormatter() + local cd = C.appearance.cd + local up = Enum.NumericRuleFormatRounding.Up + local tenths = cd.milliseconds == "1" + + countdown_formatter:SetBreakpoints({ + { threshold = 0, step = tenths and .1 or 1, rounding = up, format = ColorCode(cd.lowcolor) .. (tenths and "%.1f" or "%d") .. "|r" }, + { threshold = 5, step = 1, rounding = up, format = ColorCode(cd.normalcolor) .. "%d|r" }, + { threshold = 99, format = ColorCode(cd.minutecolor) .. "%d|rm", components = { { div = 60, step = 1, rounding = up } } }, + { threshold = 5940, format = ColorCode(cd.hourcolor) .. "%d|rh", components = { { div = 3600, step = 1, rounding = up } } }, + { threshold = 356400, format = ColorCode(cd.daycolor) .. "%d|rd", components = { { div = 86400, step = 1, rounding = up } } }, + }) +end + +function pfUI.api.GetCountdownFormatter() + if not countdown_formatter then + countdown_formatter = C_StringUtil.CreateNumericRuleFormatter() + UpdateCountdownFormatter() + end + return countdown_formatter +end + +pfUI.events:RegisterCallback("config:changed", function(_, category) + if countdown_formatter and category == C.appearance.cd then + UpdateCountdownFormatter() + end +end) + -- [ GetColoredTime ] -- -- 'remaining' the time in seconds that should be converted -- return a colored string including a time unit (m/h/d) -local color_day, color_hour, color_minute, color_low, color_normal function pfUI.api.GetColoredTimeString(remaining) - if not remaining then return "" end - - -- Show days if remaining is > 99 Hours (99 * 60 * 60) - if remaining > 356400 then - if not color_day then - local r,g,b,a = pfUI.api.GetStringColor(C.appearance.cd.daycolor) - color_day = pfUI.api.rgbhex(r,g,b) - end - - return color_day .. round(remaining / 86400) .. "|rd" - - -- Show hours if remaining is > 99 Minutes (99 * 60) - elseif remaining > 5940 then - if not color_hour then - local r,g,b,a = pfUI.api.GetStringColor(C.appearance.cd.hourcolor) - color_hour = pfUI.api.rgbhex(r,g,b) - end - - return color_hour .. round(remaining / 3600) .. "|rh" - - -- Show minutes if remaining is > 99 Seconds (99) - elseif remaining > 99 then - if not color_minute then - local r,g,b,a = pfUI.api.GetStringColor(C.appearance.cd.minutecolor) - color_minute = pfUI.api.rgbhex(r,g,b) - end - - return color_minute .. round(remaining / 60) .. "|rm" - - -- Show milliseconds on low - elseif remaining <= 5 and pfUI_config.appearance.cd.milliseconds == "1" then - if not color_low then - local r,g,b,a = pfUI.api.GetStringColor(C.appearance.cd.lowcolor) - color_low = pfUI.api.rgbhex(r,g,b) - end - - return color_low .. string.format("%.1f", round(remaining,1)) - - -- Show seconds on low - elseif remaining <= 5 then - if not color_low then - local r,g,b,a = pfUI.api.GetStringColor(C.appearance.cd.lowcolor) - color_low = pfUI.api.rgbhex(r,g,b) - end - - return color_low .. round(remaining) - - -- Show seconds on normal - elseif remaining >= 0 then - if not color_normal then - local r, g, b, a = pfUI.api.GetStringColor(C.appearance.cd.normalcolor) - color_normal = pfUI.api.rgbhex(r,g,b) - end - return color_normal .. round(remaining) - - -- Return empty - else - return "" - end + return pfUI.api.GetCountdownFormatter():FormatNumber(remaining) end -- [ GetColorGradient ] -- diff --git a/api/unitframes.lua b/api/unitframes.lua index 66ee509d..1b0f969e 100644 --- a/api/unitframes.lua +++ b/api/unitframes.lua @@ -928,12 +928,10 @@ function pfUI.uf:UpdateConfig() f.buffs[i].cd:SetDrawSwipe(cooldown_anim == 1) f.buffs[i].cd:SetDrawBling(cooldown_anim == 1) f.buffs[i].cd:SetReverse(cooldown_reverse) - + -- immediately show/hide existing cooldown text - if f.buffs[i].cd.pfCooldownText then - f.buffs[i].cd.pfCooldownText:SetShown(cooldown_text == 1) - end - + f.buffs[i].cd:SetHideCountdownNumbers(cooldown_text == 0) + f.buffs[i].id = i f.buffs[i]:Hide() @@ -990,12 +988,10 @@ function pfUI.uf:UpdateConfig() f.debuffs[i].cd:SetDrawSwipe(cooldown_anim == 1) f.debuffs[i].cd:SetDrawBling(cooldown_anim == 1) f.debuffs[i].cd:SetReverse(cooldown_reverse) - + -- immediately show/hide existing cooldown text - if f.debuffs[i].cd.pfCooldownText then - f.debuffs[i].cd.pfCooldownText:SetShown(cooldown_text == 1) - end - + f.debuffs[i].cd:SetHideCountdownNumbers(cooldown_text == 0) + f.debuffs[i].id = i f.debuffs[i]:Hide() diff --git a/modules/buff.lua b/modules/buff.lua index 79ed1c12..8349cc76 100644 --- a/modules/buff.lua +++ b/modules/buff.lua @@ -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) diff --git a/modules/cooldown.lua b/modules/cooldown.lua index c9f7102c..1d9606ea 100644 --- a/modules/cooldown.lua +++ b/modules/cooldown.lua @@ -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) \ No newline at end of file +end) diff --git a/modules/gui.lua b/modules/gui.lua index 39abeaeb..a6e16143 100644 --- a/modules/gui.lua +++ b/modules/gui.lua @@ -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") diff --git a/modules/skin.lua b/modules/skin.lua index 69081ee8..3824a044 100644 --- a/modules/skin.lua +++ b/modules/skin.lua @@ -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"]) diff --git a/pfUI.lua b/pfUI.lua index 0acb886a..af6c675c 100644 --- a/pfUI.lua +++ b/pfUI.lua @@ -467,6 +467,7 @@ function pfUI.SetupCVars() SetCVar("autoSelfCast", "1") SetCVar("profanityFilter", "0") + SetCVar("countdownForCooldowns", "1") MultiActionBar_ShowAllGrids() ALWAYS_SHOW_MULTIBARS = "1" diff --git a/skins/blizzard/character.lua b/skins/blizzard/character.lua index 34b179e4..04c870f5 100644 --- a/skins/blizzard/character.lua +++ b/skins/blizzard/character.lua @@ -193,6 +193,15 @@ pfUI:RegisterSkin("Character", function () HandleIcon(frame.backdrop, _G["Character"..slotName.."IconTexture"]) + -- native cooldown frame in place of the template's indicator Model, + -- which has no countdown numbers. PaperDollItemSlotButton_Update reads + -- the "