Timer bars: buffwatch, loot roll, infobox and five-second rule drain from duration objects

Each bar hands its fill to StatusBar:SetTimerDuration instead of a per-frame
SetValue. Buffwatch's time text moves to a duration text binding holding the
cooldown formatter, now exposed as GetTimeFormatter (replacing
GetColoredTimeString, whose only caller this was). The five-second rule's
overlay style becomes a color-filled StatusBar too, so both styles share one
path; energytick's OnUpdate keeps only the tick spark.
This commit is contained in:
Brues
2026-11-02 16:12:15 -06:00
parent 9f5ebf367f
commit a219bd13dc
5 changed files with 68 additions and 68 deletions
+6 -5
View File
@@ -196,9 +196,10 @@ function pfUI.api.GetAuraTimer(duration, expirationTime)
return start, dur return start, dur
end end
-- [ GetColoredTimeString ] -- -- [ GetTimeFormatter ] --
-- 'remaining' [number] the time in seconds that should be converted -- The formatter cooldown numbers use, for other timers to show time the same
-- return [string] the time as cooldown numbers show it -- way. Hold it rather than a copy: a settings change updates it in place.
function pfUI.api.GetColoredTimeString(remaining) -- return [formatter] a NumericRuleFormatter
return GetFormatter():FormatNumber(remaining) function pfUI.api.GetTimeFormatter()
return GetFormatter()
end end
+4 -9
View File
@@ -834,10 +834,7 @@ function pfUI.api.CreateInfoBox(text, time, parent, height)
end) end)
infobox:SetScript("OnUpdate", function() infobox:SetScript("OnUpdate", function()
local time = infobox.lastshow + infobox.duration - GetTime() if infobox.duration:HasExpired() then
infobox.timeout:SetValue(time)
if GetTime() > infobox.lastshow + infobox.duration then
if not infobox.fade:IsPlaying() then infobox.fade:Play() end if not infobox.fade:IsPlaying() then infobox.fade:Play() end
elseif MouseIsOverHitRect(this) then elseif MouseIsOverHitRect(this) then
this:SetAlpha(max(0.4, this:GetAlpha() - .1)) this:SetAlpha(max(0.4, this:GetAlpha() - .1))
@@ -857,6 +854,8 @@ function pfUI.api.CreateInfoBox(text, time, parent, height)
infobox.timeout = CreateFrame("StatusBar", nil, infobox) infobox.timeout = CreateFrame("StatusBar", nil, infobox)
infobox.timeout:SetStatusBarTexture(pfUI.media["img:bar"]) infobox.timeout:SetStatusBarTexture(pfUI.media["img:bar"])
infobox.timeout:SetStatusBarColor(.3,1,.8,1) infobox.timeout:SetStatusBarColor(.3,1,.8,1)
infobox.duration = C_DurationUtil.CreateDuration()
infobox.timeout:SetTimerDuration(infobox.duration, Enum.StatusBarInterpolation.Immediate, Enum.StatusBarTimerDirection.RemainingTime)
infobox:ClearAllPoints() infobox:ClearAllPoints()
infobox.text:SetAllPoints(infobox) infobox.text:SetAllPoints(infobox)
@@ -872,11 +871,7 @@ function pfUI.api.CreateInfoBox(text, time, parent, height)
end end
infobox.text:SetText(text) infobox.text:SetText(text)
infobox.timeout:SetMinMaxValues(0, time) infobox.duration:SetTimeFromStart(GetTime(), time)
infobox.timeout:SetValue(time)
infobox.duration = time
infobox.lastshow = GetTime()
infobox:SetSize(infobox.text:GetStringWidth() + 50, height) infobox:SetSize(infobox.text:GetStringWidth() + 50, height)
infobox:SetParent(parent) infobox:SetParent(parent)
+24 -15
View File
@@ -130,15 +130,14 @@ pfUI:RegisterModule("buffwatch", function ()
GameTooltip:Hide() GameTooltip:Hide()
end end
local function StatusBarOnUpdate() -- an enabled text binding writes every frame, shown or not
local remaining = this.endtime - GetTime() local function StatusBarOnShow()
this.bar:SetValue(remaining > 0 and remaining or 0) this.timer:Enable()
this.parent:RefreshPosition()
if ( this.tick or 1) > GetTime() then return else this.tick = GetTime() + .1 end
this.time:SetText(remaining > 0 and GetColoredTimeString(remaining) or "")
end end
local function StatusBarRefreshParent() local function StatusBarOnHide()
this.timer:Disable()
this.parent:RefreshPosition() this.parent:RefreshPosition()
end end
@@ -183,6 +182,16 @@ pfUI:RegisterModule("buffwatch", function ()
frame.time:SetTextColor(1,1,1,1) frame.time:SetTextColor(1,1,1,1)
frame.time:SetJustifyH("RIGHT") frame.time:SetJustifyH("RIGHT")
-- the bar drains and the time counts down from this, engine-side
frame.duration = C_DurationUtil.CreateDuration()
frame.bar:SetTimerDuration(frame.duration, Enum.StatusBarInterpolation.Immediate, Enum.StatusBarTimerDirection.RemainingTime)
frame.timer = C_DurationUtil.CreateDurationTextBinding()
frame.timer:SetFontString(frame.time)
frame.timer:SetFormatter(GetTimeFormatter())
frame.timer:SetDuration(frame.duration)
frame.timer:SetExpiredText("")
frame.timer:SetZeroDurationText("")
frame.icon = frame:CreateTexture(nil, "OVERLAY") frame.icon = frame:CreateTexture(nil, "OVERLAY")
frame.icon:SetSize(height, height) frame.icon:SetSize(height, height)
frame.icon:SetPoint("LEFT", frame, "LEFT", 0, 0) frame.icon:SetPoint("LEFT", frame, "LEFT", 0, 0)
@@ -195,9 +204,8 @@ pfUI:RegisterModule("buffwatch", function ()
frame.stacks:SetJustifyV("CENTER") frame.stacks:SetJustifyV("CENTER")
frame.parent = parent frame.parent = parent
frame:SetScript("OnUpdate", StatusBarOnUpdate) frame:SetScript("OnShow", StatusBarOnShow)
frame:SetScript("OnShow", StatusBarRefreshParent) frame:SetScript("OnHide", StatusBarOnHide)
frame:SetScript("OnHide", StatusBarRefreshParent)
frame:RegisterForClicks("LeftButtonUp", "RightButtonUp") frame:RegisterForClicks("LeftButtonUp", "RightButtonUp")
frame:SetScript("OnClick", StatusBarOnClick) frame:SetScript("OnClick", StatusBarOnClick)
@@ -261,7 +269,6 @@ pfUI:RegisterModule("buffwatch", function ()
frame.bars[bar].id = data[2] frame.bars[bar].id = data[2]
frame.bars[bar].unit = frame.unit frame.bars[bar].unit = frame.unit
frame.bars[bar].type = frame.type frame.bars[bar].type = frame.type
frame.bars[bar].endtime = GetTime() + ( data[1] > 0 and data[1] or -1 )
-- update max duration the cached remaining values is less than -- update max duration the cached remaining values is less than
-- the real one, indicates a buff renewal -- the real one, indicates a buff renewal
@@ -313,10 +320,12 @@ pfUI:RegisterModule("buffwatch", function ()
frame.bars[bar].icon:SetTexture(data[4]) frame.bars[bar].icon:SetTexture(data[4])
end end
-- cache maxduration -- the bar spans the longest time left seen for this aura; one without
if frame.bars[bar].cacheMaxDuration ~= frame.durations[uuid][2] then -- a time left (threshold -1) shows an empty bar and no time
frame.bars[bar].cacheMaxDuration = frame.durations[uuid][2] if data[1] > 0 then
frame.bars[bar].bar:SetMinMaxValues(0, frame.durations[uuid][2]) frame.bars[bar].duration:SetTimeFromEnd(GetTime() + data[1], frame.durations[uuid][2])
else
frame.bars[bar].duration:Reset()
end end
-- set stacks -- set stacks
+28 -29
View File
@@ -166,8 +166,8 @@ pfUI:RegisterModule("energytick", function()
local cost = C_Spell.GetSpellPowerCost(arg3) local cost = C_Spell.GetSpellPowerCost(arg3)
cost = cost and cost[1] cost = cost and cost[1]
if cost and cost.type == Enum.PowerType.Mana and cost.cost > 0 then if cost and cost.type == Enum.PowerType.Mana and cost.cost > 0 then
this.fsrSpell, this.fsrEnd = arg3, GetTime() + FIVE_SECOND_RULE this.fsrSpell, this.fsrGain = arg3, nil
this.fsrGain = nil this.fsr:SetTimeFromStart(GetTime(), FIVE_SECOND_RULE)
end end
return return
end end
@@ -175,7 +175,8 @@ pfUI:RegisterModule("energytick", function()
-- Unit::Update won't expire the rule while the spending spell still channels -- Unit::Update won't expire the rule while the spending spell still channels
if event == "UNIT_SPELLCAST_CHANNEL_STOP" and arg1 == "player" then if event == "UNIT_SPELLCAST_CHANNEL_STOP" and arg1 == "player" then
if this.fsrSpell and this.fsrSpell == arg3 then if this.fsrSpell and this.fsrSpell == arg3 then
this.fsrEnd, this.fsrGain = GetTime() + FIVE_SECOND_RULE, nil this.fsrGain = nil
this.fsr:SetTimeFromStart(GetTime(), FIVE_SECOND_RULE)
end end
return return
end end
@@ -189,7 +190,7 @@ pfUI:RegisterModule("energytick", function()
-- only a gain can be the tick; a spend never touches the phase -- only a gain can be the tick; a spend never touches the phase
if diff > 0 and lockTick(this) then if diff > 0 and lockTick(this) then
-- a tick inside the window proves regen continues through it -- a tick inside the window proves regen continues through it
if this.fsrEnd and this.fsrEnd > GetTime() then if this.fsr:IsActive() then
this.fsrGain = true this.fsrGain = true
end end
end end
@@ -208,25 +209,15 @@ pfUI:RegisterModule("energytick", function()
end end
this.tick = GetTime() + 0.020 -- ~50 FPS this.tick = GetTime() + 0.020 -- ~50 FPS
-- five-second rule drains to nothing. The display is cosmetic and can be -- five-second rule: fsrbar drains from this.fsr by itself. The display is
-- hidden on its own; the window itself (fsrEnd/fsrGain) keeps running -- cosmetic and can be hidden on its own; the window itself (fsr/fsrGain)
-- either way -- it still dims the sweep spark below. -- keeps running either way -- it still dims the sweep spark below.
local remaining = this.fsrEnd and (this.fsrEnd - GetTime()) or 0 local fsrUp = this.mode == "MANA" and this.fsr:IsActive()
if this.mode == "MANA" and remaining > 0 then if not fsrUp and this.fsrSpell then
if C.unitframes.player.showfsr == "1" then this.fsrSpell, this.fsrGain = nil, nil
if fsrOverlay then this.fsr:Reset()
this.fsrbar:SetWidth(getBarWidth() * remaining / FIVE_SECOND_RULE)
else
this.fsrbar:SetValue(remaining)
end
this.fsrbar:Show()
else
this.fsrbar:Hide()
end
else
this.fsrSpell, this.fsrEnd, this.fsrGain = nil, nil, nil
this.fsrbar:Hide()
end end
this.fsrbar:SetShown(fsrUp and C.unitframes.player.showfsr == "1")
if not this.start then if not this.start then
this.spark:SetAlpha(0) this.spark:SetAlpha(0)
@@ -249,7 +240,7 @@ pfUI:RegisterModule("energytick", function()
end end
-- dim while the rule is up and nothing has ticked inside it yet -- dim while the rule is up and nothing has ticked inside it yet
this.spark:SetAlpha((remaining > 0 and not this.fsrGain) and .4 or 1) this.spark:SetAlpha((fsrUp and not this.fsrGain) and .4 or 1)
if not C.unitframes.player.pheight then if not C.unitframes.player.pheight then
return return
@@ -259,19 +250,21 @@ pfUI:RegisterModule("energytick", function()
this.spark:SetPoint("LEFT", pos - ((C.unitframes.player.pheight + 5) / 2), 0) this.spark:SetPoint("LEFT", pos - ((C.unitframes.player.pheight + 5) / 2), 0)
end) end)
energytick.fsr = C_DurationUtil.CreateDuration()
if fsrOverlay then if fsrOverlay then
-- classic style: a translucent shade on top of the power bar's own fill -- classic style: a translucent shade on top of the power bar's own fill,
energytick.fsrbar = energytick:CreateTexture(nil, "ARTWORK") -- under the spark
energytick.fsrbar:SetColorTexture(1, 1, 1, .15) energytick.fsrbar = CreateFrame("StatusBar", nil, pfUI.uf.player.power.bar)
energytick.fsrbar:SetPoint("TOPLEFT", 0, 0) energytick.fsrbar:SetAllPoints(energytick)
energytick.fsrbar:SetPoint("BOTTOMLEFT", 0, 0) energytick.fsrbar:SetColorFill(1, 1, 1, .15)
energytick:SetFrameLevel(energytick.fsrbar:GetFrameLevel() + 1)
else else
-- a real bar below the power bar, not an overlay on top of it -- the same -- a real bar below the power bar, not an overlay on top of it -- the same
-- treatment nameplate castbars get relative to the health bar, so the -- treatment nameplate castbars get relative to the health bar, so the
-- window can't be mistaken for missing mana -- window can't be mistaken for missing mana
energytick.fsrbar = CreateFrame("StatusBar", nil, pfUI.uf.player) energytick.fsrbar = CreateFrame("StatusBar", nil, pfUI.uf.player)
energytick.fsrbar:SetStatusBarTexture(cbtexture) energytick.fsrbar:SetStatusBarTexture(cbtexture)
energytick.fsrbar:SetMinMaxValues(0, FIVE_SECOND_RULE)
energytick.fsrbar:SetPoint("TOPLEFT", pfUI.uf.player.power.bar, "BOTTOMLEFT", 0, -default_border * 2) energytick.fsrbar:SetPoint("TOPLEFT", pfUI.uf.player.power.bar, "BOTTOMLEFT", 0, -default_border * 2)
energytick.fsrbar:SetPoint("TOPRIGHT", pfUI.uf.player.power.bar, "BOTTOMRIGHT", 0, -default_border * 2) energytick.fsrbar:SetPoint("TOPRIGHT", pfUI.uf.player.power.bar, "BOTTOMRIGHT", 0, -default_border * 2)
energytick.fsrbar:SetHeight(tonumber(C.unitframes.player.fsrheight) or 4) energytick.fsrbar:SetHeight(tonumber(C.unitframes.player.fsrheight) or 4)
@@ -279,8 +272,14 @@ pfUI:RegisterModule("energytick", function()
CreateBackdrop(energytick.fsrbar, default_border) CreateBackdrop(energytick.fsrbar, default_border)
CreateBackdropShadow(energytick.fsrbar) CreateBackdropShadow(energytick.fsrbar)
end end
energytick.fsrbar:SetTimerDuration(energytick.fsr, Enum.StatusBarInterpolation.Immediate, Enum.StatusBarTimerDirection.RemainingTime)
energytick.fsrbar:Hide() energytick.fsrbar:Hide()
-- the bar isn't parented to energytick, and OnUpdate stops with it
energytick:SetScript("OnHide", function()
this.fsrbar:Hide()
end)
energytick.spark = energytick:CreateTexture(nil, "OVERLAY") energytick.spark = energytick:CreateTexture(nil, "OVERLAY")
energytick.spark:SetTexture("Interface\\CastingBar\\UI-CastingBar-Spark") energytick.spark:SetTexture("Interface\\CastingBar\\UI-CastingBar-Spark")
energytick.spark:SetHeight(C.unitframes.player.pheight + 15) energytick.spark:SetHeight(C.unitframes.player.pheight + 15)
+6 -10
View File
@@ -238,17 +238,11 @@ pfUI:RegisterModule("roll", function ()
f.time.bar = CreateFrame("StatusBar", "pfLootRollFrame" .. id .. "TimeBar", f.time) f.time.bar = CreateFrame("StatusBar", "pfLootRollFrame" .. id .. "TimeBar", f.time)
f.time.bar:SetAllPoints(f.time) f.time.bar:SetAllPoints(f.time)
f.time.bar:SetStatusBarTexture(pfUI.media["img:bar"]) f.time.bar:SetStatusBarTexture(pfUI.media["img:bar"])
f.time.bar:SetMinMaxValues(0, 100)
local r, g, b, a = GetStringColor(C.appearance.border.color) local r, g, b, a = GetStringColor(C.appearance.border.color)
f.time.bar:SetStatusBarColor(r, g, b) f.time.bar:SetStatusBarColor(r, g, b)
f.time.bar:SetValue(20) -- drains by itself; UpdateLootRoll sets the roll's time span
f.time.bar:SetScript("OnUpdate", function() f.time.duration = C_DurationUtil.CreateDuration()
if not this:GetParent():GetParent().rollID then return end f.time.bar:SetTimerDuration(f.time.duration, Enum.StatusBarInterpolation.Immediate, Enum.StatusBarTimerDirection.RemainingTime)
local left = GetLootRollTimeLeft(this:GetParent():GetParent().rollID)
local min, max = this:GetMinMaxValues()
if left < min or left > max then left = min end
this:SetValue(left)
end)
return f return f
end end
@@ -299,7 +293,9 @@ pfUI:RegisterModule("roll", function ()
pfUI.roll.frames[id].name.text:SetTextColor(color.r, color.g, color.b, 1) pfUI.roll.frames[id].name.text:SetTextColor(color.r, color.g, color.b, 1)
pfUI.roll.frames[id].icon.tex:SetTexture(texture) pfUI.roll.frames[id].icon.tex:SetTexture(texture)
pfUI.roll.frames[id].backdrop:SetBackdropBorderColor(color.r, color.g, color.b) pfUI.roll.frames[id].backdrop:SetBackdropBorderColor(color.r, color.g, color.b)
pfUI.roll.frames[id].time.bar:SetMinMaxValues(0, pfUI.roll.frames[id].rollTime) -- both in milliseconds
local left = GetLootRollTimeLeft(pfUI.roll.frames[id].rollID)
pfUI.roll.frames[id].time.duration:SetTimeFromEnd(GetTime() + left / 1000, pfUI.roll.frames[id].rollTime / 1000)
if C.loot.raritytimer == "1" then if C.loot.raritytimer == "1" then
pfUI.roll.frames[id].time.bar:SetStatusBarColor(color.r, color.g, color.b, .5) pfUI.roll.frames[id].time.bar:SetStatusBarColor(color.r, color.g, color.b, .5)