diff --git a/EllesmereUICooldownManager/EUI_CooldownManager_Options.lua b/EllesmereUICooldownManager/EUI_CooldownManager_Options.lua index 02e12177..69bd9722 100644 --- a/EllesmereUICooldownManager/EUI_CooldownManager_Options.lua +++ b/EllesmereUICooldownManager/EUI_CooldownManager_Options.lua @@ -186,10 +186,16 @@ initFrame:SetScript("OnEvent", function(self) -- Preview glow state tracking local _bgPreviewGlowActive = {} local _bgPreviewGlowOverlays = {} + local _bgPreviewDurTickers = {} -- [pvKey] = { ticker, startTime } local _bgSpellPickerMenu EllesmereUI:RegisterOnHide(function() if _bgSpellPickerMenu then _bgSpellPickerMenu:Hide() end + -- Cancel any running preview countdown tickers + for _, info in pairs(_bgPreviewDurTickers) do + if info.ticker then info.ticker:Cancel() end + end + wipe(_bgPreviewDurTickers) end) local function ShowBarGlowSpellPicker(anchorFrame, barIdx, btnIdx, onChanged) @@ -370,6 +376,11 @@ initFrame:SetScript("OnEvent", function(self) glowColor = { r = 1, g = 0.82, b = 0.1 }, classColor = false, mode = "ACTIVE", + showDuration = false, + durationSize = 12, + durationOffsetX = 0, + durationOffsetY = 0, + durationR = 1, durationG = 1, durationB = 1, } local prefix = BAR_BUTTON_PREFIXES[barIdx] local realBtn = prefix and _G[prefix .. btnIdx] @@ -469,12 +480,16 @@ initFrame:SetScript("OnEvent", function(self) ------------------------------------------------------------------- EllesmereUI:ClearContentHeader() - -- Stop any lingering preview glows + -- Stop any lingering preview glows and countdown tickers for idx, ov in pairs(_bgPreviewGlowOverlays) do ns.StopNativeGlow(ov) end + for _, info in pairs(_bgPreviewDurTickers) do + if info.ticker then info.ticker:Cancel() end + end wipe(_bgPreviewGlowOverlays) wipe(_bgPreviewGlowActive) + wipe(_bgPreviewDurTickers) _glowHeaderBuilder = function(headerFrame, width) -- Re-read current state each build @@ -995,8 +1010,82 @@ initFrame:SetScript("OnEvent", function(self) end end - -- Helper: resolve current glow color and restart preview if active + -- Helper: format and refresh the countdown timer on the preview glow local pvKey = assignKey .. "_" .. aIdx + local function FormatPreviewDuration(remaining) + return string.format("%d", math.ceil(remaining)) + end + + local function RefreshPreviewDuration() + local ov = _bgPreviewGlowOverlays[pvKey] + if not ov or not _bgPreviewGlowActive[pvKey] then + -- Cancel any running ticker + local info = _bgPreviewDurTickers[pvKey] + if info then + if info.ticker then info.ticker:Cancel() end + _bgPreviewDurTickers[pvKey] = nil + end + return + end + local previewBtn = _glowBtnFrames[curBtn] + if not previewBtn then return end + if entry.showDuration then + if not ov._pvDurationText then + ov._pvDurationText = ov:CreateFontString(nil, "OVERLAY") + end + local fs = ov._pvDurationText + fs:SetFont(FONT_PATH, entry.durationSize or 12, "OUTLINE") + fs:SetShadowOffset(0, 0) + fs:ClearAllPoints() + fs:SetPoint("BOTTOMLEFT", previewBtn, "BOTTOMLEFT", + 3 + (entry.durationOffsetX or 0), 4 + (entry.durationOffsetY or 0)) + fs:SetTextColor(entry.durationR or 1, entry.durationG or 1, entry.durationB or 1) + -- Start or continue countdown from 12.0 + local info = _bgPreviewDurTickers[pvKey] + if not info then + local startTime = GetTime() + local DURATION = 12.0 + local ticker + ticker = C_Timer.NewTicker(0.1, function() + if not ov._pvDurationText or not _bgPreviewGlowActive[pvKey] then + ticker:Cancel() + _bgPreviewDurTickers[pvKey] = nil + return + end + local elapsed = GetTime() - startTime + local remaining = DURATION - elapsed + if remaining <= 0 then + -- Loop back to 12 + startTime = GetTime() + remaining = DURATION + local i2 = _bgPreviewDurTickers[pvKey] + if i2 then i2.startTime = startTime end + end + ov._pvDurationText:SetText(FormatPreviewDuration(remaining)) + end) + _bgPreviewDurTickers[pvKey] = { ticker = ticker, startTime = startTime } + fs:SetText(FormatPreviewDuration(DURATION)) + else + -- Ticker already running; just update font/position/color, keep countdown + local remaining = 12.0 - (GetTime() - info.startTime) + if remaining <= 0 then remaining = 12.0 end + remaining = remaining % 12.0 + if remaining <= 0 then remaining = 12.0 end + fs:SetText(FormatPreviewDuration(remaining)) + end + fs:Show() + else + -- Duration disabled: cancel ticker and hide + local info = _bgPreviewDurTickers[pvKey] + if info then + if info.ticker then info.ticker:Cancel() end + _bgPreviewDurTickers[pvKey] = nil + end + if ov._pvDurationText then ov._pvDurationText:Hide() end + end + end + + -- Helper: resolve current glow color and restart preview if active local function RefreshPreviewGlow() if not _bgPreviewGlowActive[pvKey] then return end local ov = _bgPreviewGlowOverlays[pvKey] @@ -1077,6 +1166,13 @@ initFrame:SetScript("OnEvent", function(self) if _bgPreviewGlowActive[pvKey] then ns.StopNativeGlow(ov) _bgPreviewGlowActive[pvKey] = false + -- Stop countdown ticker and hide preview duration text + local durInfo = _bgPreviewDurTickers[pvKey] + if durInfo then + if durInfo.ticker then durInfo.ticker:Cancel() end + _bgPreviewDurTickers[pvKey] = nil + end + if ov._pvDurationText then ov._pvDurationText:Hide() end -- Restore accent border if previewBtn._accentBrd then previewBtn._accentBrd:Show() end else @@ -1092,6 +1188,8 @@ initFrame:SetScript("OnEvent", function(self) _bgPreviewGlowActive[pvKey] = true -- Hide accent border so glow is visible if previewBtn._accentBrd then previewBtn._accentBrd:Hide() end + -- Show preview duration text when showDuration is on + RefreshPreviewDuration() end RefreshEye() end) @@ -1126,6 +1224,91 @@ initFrame:SetScript("OnEvent", function(self) PP.Point(glowSwatch, "RIGHT", toggle, "LEFT", -8, 0) end end + + -- Row 3: Show Duration (with swatch + cog) + local durRow + durRow, h = W:DualRow(parent, y, + { type = "toggle", text = "Show Duration", + getValue = function() return entry.showDuration == true end, + setValue = function(v) + entry.showDuration = v + Refresh() + RefreshPreviewDuration() + EllesmereUI:RefreshPage() + end, + }, + { type = "label", text = "" } + ); y = y - h + + -- Inline color swatch + cog for duration text (on left region of row 3) + do + local leftRgn = durRow._leftRegion + local ctrl = leftRgn and leftRgn._control + + -- Color swatch + local durSwatch + if ctrl and EllesmereUI.BuildColorSwatch then + durSwatch = EllesmereUI.BuildColorSwatch( + leftRgn, durRow:GetFrameLevel() + 3, + function() + return entry.durationR or 1, entry.durationG or 1, entry.durationB or 1 + end, + function(r, g, b) + entry.durationR = r; entry.durationG = g; entry.durationB = b + Refresh() + RefreshPreviewDuration() + EllesmereUI:RefreshPage() + end, + false, 20) + PP.Point(durSwatch, "RIGHT", ctrl, "LEFT", -8, 0) + end + + -- Cog: size + x/y offsets + local _, durCogShow = EllesmereUI.BuildCogPopup({ + title = "Duration Text Settings", + rows = { + { type = "slider", label = "Text Size", min = 6, max = 20, step = 1, + get = function() return entry.durationSize or 12 end, + set = function(v) entry.durationSize = v; Refresh(); RefreshPreviewDuration(); EllesmereUI:RefreshPage() end }, + { type = "slider", label = "X Offset", min = -30, max = 30, step = 1, + get = function() return entry.durationOffsetX or 0 end, + set = function(v) entry.durationOffsetX = v; Refresh(); RefreshPreviewDuration(); EllesmereUI:RefreshPage() end }, + { type = "slider", label = "Y Offset", min = -30, max = 30, step = 1, + get = function() return entry.durationOffsetY or 0 end, + set = function(v) entry.durationOffsetY = v; Refresh(); RefreshPreviewDuration(); EllesmereUI:RefreshPage() end }, + }, + }) + -- Cog button + do + local anchor = durSwatch or ctrl + if anchor and leftRgn then + local cogBtn = CreateFrame("Button", nil, leftRgn) + cogBtn:SetSize(26, 26) + cogBtn:SetPoint("RIGHT", anchor, "LEFT", -8, 0) + cogBtn:SetFrameLevel(leftRgn:GetFrameLevel() + 5) + cogBtn:SetAlpha(0.4) + local cogTex = cogBtn:CreateTexture(nil, "OVERLAY") + cogTex:SetAllPoints() + cogTex:SetTexture(EllesmereUI.RESIZE_ICON) + cogBtn:SetScript("OnEnter", function(self) self:SetAlpha(0.7) end) + cogBtn:SetScript("OnLeave", function(self) self:SetAlpha(0.4) end) + cogBtn:SetScript("OnClick", function(self) durCogShow(self) end) + end + end + + -- Blocking overlay when Show Duration is off + if durSwatch then + local swatchBlock = CreateFrame("Frame", nil, durSwatch) + swatchBlock:SetAllPoints() + swatchBlock:SetFrameLevel(durSwatch:GetFrameLevel() + 10) + swatchBlock:EnableMouse(true) + EllesmereUI.RegisterWidgetRefresh(function() + local on = entry.showDuration == true + durSwatch:SetAlpha(on and 1 or 0.3) + if on then swatchBlock:Hide() else swatchBlock:Show() end + end) + end + end end end end diff --git a/EllesmereUICooldownManager/EllesmereUICdmBarGlows.lua b/EllesmereUICooldownManager/EllesmereUICdmBarGlows.lua index 84b6e352..c1dc3808 100644 --- a/EllesmereUICooldownManager/EllesmereUICdmBarGlows.lua +++ b/EllesmereUICooldownManager/EllesmereUICdmBarGlows.lua @@ -339,6 +339,23 @@ local function SetupOverlays() overlay:SetAlpha(1) overlay._assignEntry = entry overlay:Show() + + -- Duration text FontString (created once per overlay) + if not overlay._durationText then + local fs = overlay:CreateFontString(nil, "OVERLAY") + overlay._durationText = fs + end + do + local fs = overlay._durationText + local sz = entry.durationSize or 12 + fs:SetFont(EllesmereUI.GetFontPath("cdm"), sz, "OUTLINE") + fs:SetShadowOffset(0, 0) + fs:ClearAllPoints() + fs:SetPoint("BOTTOMLEFT", btn, "BOTTOMLEFT", + 3 + (entry.durationOffsetX or 0), 4 + (entry.durationOffsetY or 0)) + fs:SetTextColor(entry.durationR or 1, entry.durationG or 1, entry.durationB or 1) + fs:Hide() + end activeKeys[key] = true anyActive = true end @@ -422,6 +439,51 @@ UpdateOverlayVisuals = function() end end end + + -- Duration text: update every tick when glow is active + showDuration on + local dfs = overlay._durationText + if dfs then + if shouldGlow and entry.showDuration and mode ~= "MISSING" and spellID and spellID > 0 then + local blzChild = (ns._tickBlizzBuffChildCache and ns._tickBlizzBuffChildCache[spellID]) + or (ns._tickBlizzAllChildCache and ns._tickBlizzAllChildCache[spellID]) + local durText + if blzChild then + local auraID = blzChild.auraInstanceID + local auraUnit = blzChild.auraDataUnit or "player" + if auraID then + local ok, durObj = pcall(C_UnitAuras.GetAuraDuration, auraUnit, auraID) + if ok and durObj and durObj.GetRemainingDuration then + local rok, remaining = pcall(durObj.GetRemainingDuration, durObj) + if rok and remaining then + if issecretvalue and issecretvalue(remaining) then + -- Can't do math on secret values; %.0f rounds to + -- nearest which is close enough to ceil + local fok, fstr = pcall(string.format, "%.0f", remaining) + if fok and fstr then + durText = fstr + end + else + durText = string.format("%d", math.ceil(remaining)) + end + end + end + end + end + if durText then + local isSecret = issecretvalue and issecretvalue(durText) + if isSecret or dfs._lastText ~= durText then + dfs._lastText = not isSecret and durText or nil + dfs:SetText(durText) + end + dfs:Show() + else + dfs._lastText = nil + dfs:Hide() + end + else + dfs:Hide() + end + end end end end