diff --git a/CooldownTracker.toc b/CooldownTracker.toc index 08b870c..0f2f2c0 100644 --- a/CooldownTracker.toc +++ b/CooldownTracker.toc @@ -2,9 +2,10 @@ ## Title: Healer Cooldown Tracker ## Notes: Manually track healer cooldowns for raid leaders. Click to start a timer when a healer calls out on comms. ## Author: Reube -## Version: 1.0.0 +## Version: 1.1.0 ## SavedVariables: CooldownTrackerDB Data.lua UI.lua +Settings.lua Core.lua diff --git a/Core.lua b/Core.lua index f230beb..88ae64a 100644 --- a/Core.lua +++ b/Core.lua @@ -6,6 +6,7 @@ -- Slash commands: -- /cdt — toggle the tracker window -- /cdt reset — reset all running timers +-- /cdt settings — open the settings panel -------------------------------------------------------------------------------- local AddonName, CT = ... @@ -25,6 +26,8 @@ eventFrame:SetScript("OnEvent", function(_, event, name) -- Build the UI (defined in UI.lua) then restore the saved position CT:BuildUI() CT:RestorePosition() + -- Register the settings panel (defined in Settings.lua) + CT:InitSettings() end end) @@ -40,6 +43,8 @@ SlashCmdList["COOLDOWNTRACKER"] = function(msg) CT.activeTimers[cd.id] = nil end print("|cffaaddff[CooldownTracker]|r All timers reset.") + elseif cmd == "settings" then + CT:OpenSettings() else if CT.mainFrame:IsShown() then CT.mainFrame:Hide() diff --git a/Data.lua b/Data.lua index 8a451f1..ce19d6b 100644 --- a/Data.lua +++ b/Data.lua @@ -2,12 +2,13 @@ -- Data.lua -- Cooldown definitions. Add new healer abilities here — no other file needs -- to change. Each entry requires: --- id - unique string key --- class - class name (display only) --- name - ability name (display only) --- duration - cooldown length in seconds --- icon - WoW texture path (Interface\Icons\...) --- r, g, b - accent colour using WoW class colours (0-1 range) +-- id - unique string key +-- class - class name (display only) +-- name - ability name (display only) +-- duration - cooldown length in seconds (may be overridden by user) +-- defaultDuration - original duration used by the Reset button in settings +-- icon - WoW texture path (Interface\Icons\...) +-- r, g, b - accent colour using WoW class colours (0-1 range) -------------------------------------------------------------------------------- local AddonName, CT = ... @@ -17,95 +18,104 @@ CT.COOLDOWNS = { -- Druid (class colour: orange) -- ------------------------------------------------------------------------- { - id = "druid_convoke", - class = "Druid", - name = "Convoke the Spirits", - duration = 60, - icon = "Interface\\Icons\\ability_ardenweald_druid", - r = 1.0, g = 0.49, b = 0.04, + id = "druid_convoke", + class = "Druid", + name = "Convoke the Spirits", + duration = 60, + defaultDuration = 60, + icon = "Interface\\Icons\\ability_ardenweald_druid", + r = 1.0, g = 0.49, b = 0.04, }, { - id = "druid_tranquility", - class = "Druid", - name = "Tranquility", - duration = 180, - icon = "Interface\\Icons\\spell_nature_tranquility", - r = 1.0, g = 0.49, b = 0.04, + id = "druid_tranquility", + class = "Druid", + name = "Tranquility", + duration = 180, + defaultDuration = 180, + icon = "Interface\\Icons\\spell_nature_tranquility", + r = 1.0, g = 0.49, b = 0.04, }, -- ------------------------------------------------------------------------- -- Paladin (class colour: pink) -- ------------------------------------------------------------------------- { - id = "paladin_avenging_wrath", - class = "Paladin", - name = "Avenging Wrath", - duration = 120, - icon = "Interface\\Icons\\spell_holy_avenginewrath", - r = 0.96, g = 0.55, b = 0.73, + id = "paladin_avenging_wrath", + class = "Paladin", + name = "Avenging Wrath", + duration = 120, + defaultDuration = 120, + icon = "Interface\\Icons\\spell_holy_avenginewrath", + r = 0.96, g = 0.55, b = 0.73, }, { - id = "paladin_aura_mastery", - class = "Paladin", - name = "Aura Mastery", - duration = 120, - icon = "Interface\\Icons\\spell_holy_auramastery", - r = 0.96, g = 0.55, b = 0.73, + id = "paladin_aura_mastery", + class = "Paladin", + name = "Aura Mastery", + duration = 120, + defaultDuration = 120, + icon = "Interface\\Icons\\spell_holy_auramastery", + r = 0.96, g = 0.55, b = 0.73, }, -- ------------------------------------------------------------------------- -- Shaman (class colour: blue) -- ------------------------------------------------------------------------- { - id = "shaman_ascendance", - class = "Shaman", - name = "Ascendance", - duration = 180, - icon = "Interface\\Icons\\spell_fire_elementaldevastation", - r = 0.0, g = 0.44, b = 0.87, + id = "shaman_ascendance", + class = "Shaman", + name = "Ascendance", + duration = 180, + defaultDuration = 180, + icon = "Interface\\Icons\\spell_fire_elementaldevastation", + r = 0.0, g = 0.44, b = 0.87, }, { - id = "shaman_spirit_link", - class = "Shaman", - name = "Spirit Link Totem", - duration = 180, - icon = "Interface\\Icons\\spell_shaman_spiritlink", - r = 0.0, g = 0.44, b = 0.87, + id = "shaman_spirit_link", + class = "Shaman", + name = "Spirit Link Totem", + duration = 180, + defaultDuration = 180, + icon = "Interface\\Icons\\spell_shaman_spiritlink", + r = 0.0, g = 0.44, b = 0.87, }, -- ------------------------------------------------------------------------- -- Warrior (class colour: tan/gold) -- ------------------------------------------------------------------------- { - id = "warrior_rallying_cry", - class = "Warrior", - name = "Rallying Cry", - duration = 180, - icon = "Interface\\Icons\\ability_warrior_rallyingcry", - r = 0.78, g = 0.61, b = 0.23, + id = "warrior_rallying_cry", + class = "Warrior", + name = "Rallying Cry", + duration = 180, + defaultDuration = 180, + icon = "Interface\\Icons\\ability_warrior_rallyingcry", + r = 0.78, g = 0.61, b = 0.23, }, -- ------------------------------------------------------------------------- -- Evoker (class colour: teal) -- ------------------------------------------------------------------------- { - id = "evoker_zephyr", - class = "Evoker", - name = "Zephyr", - duration = 120, - icon = "Interface\\Icons\\ability_evoker_hoverblack", - r = 0.2, g = 0.58, b = 0.5, + id = "evoker_zephyr", + class = "Evoker", + name = "Zephyr", + duration = 120, + defaultDuration = 120, + icon = "Interface\\Icons\\ability_evoker_hoverblack", + r = 0.2, g = 0.58, b = 0.5, }, -- ------------------------------------------------------------------------- -- Death Knight (class colour: red) -- ------------------------------------------------------------------------- { - id = "dk_amz", - class = "Death Knight", - name = "Anti-Magic Zone", - duration = 120, - icon = "Interface\\Icons\\spell_deathknight_antimagiczone", - r = 0.77, g = 0.12, b = 0.23, + id = "dk_amz", + class = "Death Knight", + name = "Anti-Magic Zone", + duration = 120, + defaultDuration = 120, + icon = "Interface\\Icons\\spell_deathknight_antimagiczone", + r = 0.77, g = 0.12, b = 0.23, }, } diff --git a/Settings.lua b/Settings.lua new file mode 100644 index 0000000..6c5cad9 --- /dev/null +++ b/Settings.lua @@ -0,0 +1,250 @@ +-------------------------------------------------------------------------------- +-- Settings.lua +-- In-game configuration panel for CooldownTracker, registered with WoW's +-- modern Settings API (Escape -> Options -> AddOns -> Healer Cooldown Tracker). +-- +-- Exposes: +-- CT:InitSettings() - called once on ADDON_LOADED to apply saved values +-- and register the panel +-- CT:OpenSettings() - programmatically opens the panel (slash command) +-------------------------------------------------------------------------------- + +local AddonName, CT = ... + +-- --------------------------------------------------------------------------- +-- Helpers +-- --------------------------------------------------------------------------- + +-- Returns the effective duration for a cooldown: saved custom value or default. +local function GetEffectiveDuration(cd) + local custom = CooldownTrackerDB and CooldownTrackerDB.customDurations + return (custom and custom[cd.id]) or cd.defaultDuration +end + +-- Applies all saved custom durations to the live CT.COOLDOWNS table. +local function ApplyCustomDurations() + local custom = CooldownTrackerDB.customDurations or {} + for _, cd in ipairs(CT.COOLDOWNS) do + cd.duration = custom[cd.id] or cd.defaultDuration + end +end + +-- --------------------------------------------------------------------------- +-- Panel construction +-- --------------------------------------------------------------------------- + +local PANEL_ROW_HEIGHT = 42 +local PANEL_ROW_PAD = 2 +local ICON_SIZE = 28 +local EDIT_WIDTH = 80 +local CONTENT_WIDTH = 520 + +local function CreateSettingsPanel() + local panel = CreateFrame("Frame") + panel.name = "Healer Cooldown Tracker" + + -- ----- Header ----------------------------------------------------------- + local title = panel:CreateFontString(nil, "ARTWORK", "GameFontNormalLarge") + title:SetPoint("TOPLEFT", 16, -16) + title:SetText("|cffaaddffHealer Cooldown Tracker|r — Settings") + + local desc = panel:CreateFontString(nil, "ARTWORK", "GameFontHighlightSmall") + desc:SetPoint("TOPLEFT", title, "BOTTOMLEFT", 0, -6) + desc:SetWidth(CONTENT_WIDTH) + desc:SetText("Override cooldown durations to match your raiders' current talents.\nType a new value and press Enter to confirm. Changes apply immediately.") + desc:SetJustifyH("LEFT") + + -- Column header labels + local hdrAbility = panel:CreateFontString(nil, "ARTWORK", "GameFontNormalSmall") + hdrAbility:SetPoint("TOPLEFT", desc, "BOTTOMLEFT", ICON_SIZE + 12, -10) + hdrAbility:SetText("Ability") + hdrAbility:SetTextColor(0.7, 0.7, 0.7) + + local hdrDur = panel:CreateFontString(nil, "ARTWORK", "GameFontNormalSmall") + hdrDur:SetPoint("LEFT", hdrAbility, "LEFT", 260, 0) + hdrDur:SetText("Cooldown (seconds)") + hdrDur:SetTextColor(0.7, 0.7, 0.7) + + local divider = panel:CreateTexture(nil, "ARTWORK") + divider:SetHeight(1) + divider:SetPoint("TOPLEFT", hdrAbility, "BOTTOMLEFT", -ICON_SIZE - 12, -4) + divider:SetWidth(CONTENT_WIDTH) + divider:SetColorTexture(0.3, 0.3, 0.4, 0.6) + + -- ----- Scroll frame ----------------------------------------------------- + local scrollFrame = CreateFrame("ScrollFrame", "CTSettingsScrollFrame", panel, "UIPanelScrollFrameTemplate") + scrollFrame:SetPoint("TOPLEFT", divider, "BOTTOMLEFT", 0, -6) + scrollFrame:SetPoint("BOTTOMRIGHT", panel, "BOTTOMRIGHT", -28, 50) + + local contentHeight = #CT.COOLDOWNS * (PANEL_ROW_HEIGHT + PANEL_ROW_PAD) + local content = CreateFrame("Frame", nil, scrollFrame) + content:SetSize(CONTENT_WIDTH, contentHeight) + scrollFrame:SetScrollChild(content) + + -- Track edit boxes so Reset All / refresh can update them + local editBoxes = {} + + -- Populates every edit box. Uses C_Timer.After(0) to defer to next frame, + -- because WoW's Settings canvas clears EditBox text during its own show + -- sequence, so we must run AFTER that completes. + local function RefreshAllEditBoxes() + C_Timer.After(0, function() + for _, cd in ipairs(CT.COOLDOWNS) do + local eb = editBoxes[cd.id] + if eb then + eb:SetText(tostring(GetEffectiveDuration(cd))) + eb:SetCursorPosition(0) + end + end + end) + end + + for i, cd in ipairs(CT.COOLDOWNS) do + local yOff = -((i - 1) * (PANEL_ROW_HEIGHT + PANEL_ROW_PAD)) + + local row = CreateFrame("Frame", nil, content) + row:SetHeight(PANEL_ROW_HEIGHT) + row:SetPoint("TOPLEFT", content, "TOPLEFT", 0, yOff) + row:SetPoint("TOPRIGHT", content, "TOPRIGHT", 0, yOff) + + -- Alternating background + local bg = row:CreateTexture(nil, "BACKGROUND") + bg:SetAllPoints(row) + if i % 2 == 0 then + bg:SetColorTexture(0.12, 0.12, 0.14, 0.4) + else + bg:SetColorTexture(0.07, 0.07, 0.09, 0.25) + end + + -- Accent left strip + local strip = row:CreateTexture(nil, "BACKGROUND") + strip:SetSize(3, PANEL_ROW_HEIGHT) + strip:SetPoint("LEFT", row, "LEFT", 0, 0) + strip:SetColorTexture(cd.r, cd.g, cd.b, 0.9) + + -- Icon + local icon = row:CreateTexture(nil, "ARTWORK") + icon:SetSize(ICON_SIZE, ICON_SIZE) + icon:SetPoint("LEFT", row, "LEFT", 8, 0) + icon:SetTexture(cd.icon) + icon:SetTexCoord(0.08, 0.92, 0.08, 0.92) + + -- Ability name + local nameLabel = row:CreateFontString(nil, "OVERLAY", "GameFontNormal") + nameLabel:SetPoint("TOPLEFT", icon, "TOPRIGHT", 8, -2) + nameLabel:SetText(cd.name) + nameLabel:SetTextColor(1, 1, 1) + + -- Class tag + local classLabel = row:CreateFontString(nil, "OVERLAY") + classLabel:SetPoint("BOTTOMLEFT", icon, "BOTTOMRIGHT", 8, 2) + classLabel:SetFont("Fonts\\FRIZQT__.TTF", 9, "OUTLINE") + classLabel:SetText(cd.class) + classLabel:SetTextColor(cd.r, cd.g, cd.b) + + -- Duration edit box — built manually to avoid template init issues + local editBox = CreateFrame("EditBox", "CTSettingsEdit_" .. cd.id, row, "BackdropTemplate") + editBox:SetSize(EDIT_WIDTH, 24) + editBox:SetPoint("LEFT", row, "LEFT", 280, 0) + editBox:SetAutoFocus(false) + editBox:SetFontObject("ChatFontNormal") + editBox:SetJustifyH("CENTER") + if editBox.SetBackdrop then + editBox:SetBackdrop({ + bgFile = "Interface\\Tooltips\\UI-Tooltip-Background", + edgeFile = "Interface\\Tooltips\\UI-Tooltip-Border", + tile = true, tileSize = 16, edgeSize = 12, + insets = { left = 3, right = 3, top = 3, bottom = 3 }, + }) + editBox:SetBackdropColor(0.1, 0.1, 0.1, 0.8) + editBox:SetBackdropBorderColor(0.4, 0.4, 0.4, 0.8) + end + editBox:SetTextInsets(6, 6, 2, 2) + + -- Text is set by RefreshAllEditBoxes via C_Timer.After — not here, + -- because anything set here gets wiped by the Settings canvas. + + local function CommitEdit() + local val = tonumber(editBox:GetText()) + if val and val > 0 then + CooldownTrackerDB.customDurations = CooldownTrackerDB.customDurations or {} + CooldownTrackerDB.customDurations[cd.id] = (val ~= cd.defaultDuration) and val or nil + cd.duration = val + end + end + editBox:SetScript("OnTextChanged", function(self, userInput) + -- Only commit on actual keystrokes, not programmatic SetText + if userInput then CommitEdit() end + end) + editBox:SetScript("OnEnterPressed", function() editBox:ClearFocus() end) + editBox:SetScript("OnEscapePressed", function() + editBox:SetText(tostring(GetEffectiveDuration(cd))) + editBox:ClearFocus() + end) + + editBoxes[cd.id] = editBox + + -- "Default" reset button + local resetBtn = CreateFrame("Button", nil, row, "UIPanelButtonTemplate") + resetBtn:SetSize(70, 22) + resetBtn:SetPoint("LEFT", editBox, "RIGHT", 10, 0) + resetBtn:SetText("Default") + resetBtn:SetScript("OnClick", function() + CooldownTrackerDB.customDurations = CooldownTrackerDB.customDurations or {} + CooldownTrackerDB.customDurations[cd.id] = nil + cd.duration = cd.defaultDuration + editBox:SetText(tostring(cd.defaultDuration)) + end) + + -- Tooltip on the edit box + editBox:SetScript("OnEnter", function() + GameTooltip:SetOwner(editBox, "ANCHOR_RIGHT") + GameTooltip:SetText("Cooldown Duration") + GameTooltip:AddLine("Type a duration in seconds and press Enter.", 1, 1, 1) + GameTooltip:AddLine(string.format("Default: %d seconds", cd.defaultDuration), 0.8, 0.8, 0.8) + GameTooltip:Show() + end) + editBox:SetScript("OnLeave", function() GameTooltip:Hide() end) + end + + -- ----- Reset All button ------------------------------------------------- + local resetAllBtn = CreateFrame("Button", nil, panel, "UIPanelButtonTemplate") + resetAllBtn:SetSize(110, 26) + resetAllBtn:SetPoint("BOTTOMLEFT", panel, "BOTTOMLEFT", 16, 16) + resetAllBtn:SetText("Reset All Defaults") + resetAllBtn:SetScript("OnClick", function() + CooldownTrackerDB.customDurations = {} + for _, cd in ipairs(CT.COOLDOWNS) do + cd.duration = cd.defaultDuration + if editBoxes[cd.id] then + editBoxes[cd.id]:SetText(tostring(cd.defaultDuration)) + end + end + print("|cffaaddff[CooldownTracker]|r All durations reset to defaults.") + end) + + -- When the Settings canvas shows our panel, wait one frame then fill boxes + panel:SetScript("OnShow", RefreshAllEditBoxes) + + return panel +end + +-- --------------------------------------------------------------------------- +-- Public API +-- --------------------------------------------------------------------------- + +function CT:InitSettings() + CooldownTrackerDB.customDurations = CooldownTrackerDB.customDurations or {} + ApplyCustomDurations() + + local panel = CreateSettingsPanel() + local category = Settings.RegisterCanvasLayoutCategory(panel, "Healer Cooldown Tracker") + Settings.RegisterAddOnCategory(category) + CT.settingsCategory = category +end + +function CT:OpenSettings() + if CT.settingsCategory then + Settings.OpenToCategory(CT.settingsCategory:GetID()) + end +end