diff --git a/AGENTS.md b/AGENTS.md old mode 100644 new mode 100755 index 413acf2..e38d18c --- a/AGENTS.md +++ b/AGENTS.md @@ -19,7 +19,16 @@ The addon is split into modular components, sharing a single private namespace ( local AddonName, CT = ... -- CT is the shared table across all files ``` -2. **SavedVariables:** Use `CooldownTrackerDB` for persistence. Initialize it in `ADDON_LOADED` in `Core.lua`. +2. **SavedVariables:** Use `CooldownTrackerDB` for persistence. Initialize it in `ADDON_LOADED` in `Core.lua`. Current keys: + | Key | Type | Default | Purpose | + |---|---|---|---| + | `columns` | number | `1` | Grid column count (1–9) | + | `classCounts` | table | `{}` | Per-class player counts (1–5) | + | `customDurations` | table | `{}` | Per-spell duration overrides (seconds) | + | `disabledSpells` | table | `{}` | Set of spell IDs hidden from tracker | + | `playSoundOnReady` | boolean | `true` | Play alert sound on cooldown expiry | + | `frameLocked` | boolean | `false` | Lock window position (disable drag) | + | `point`, `relPoint`, `x`, `y` | mixed | nil | Saved window position | 3. **Slash Commands:** Register slash commands via the `SlashCmdList` table. Handle arguments cleanly. 4. **No Third-Party Libraries:** The addon intentionally does not use Ace3 or other framework libraries to remain lightweight. Rely on the standard WoW API. @@ -34,5 +43,7 @@ World of Warcraft has a strict "taint" system for UI frames. Violating these rul ## 🎨 UI & Layout Preferences - **Grid vs Vertical:** The UI supports both a wide-row vertical layout (columns = 1) and a compact square-card grid layout (columns 2-9). Use `CT:LayoutRows()` to reflow. +- **Clickable Rows:** Each spell row is a `Button` frame (not a plain `Frame`). The entire row is the click target — there is no separate "Used"/"Reset" button. `OnClick` is set once at creation time and reads `row.cd` dynamically to avoid taint. - **Font:** Use `Fonts\\FRIZQT__.TTF` with an `"OUTLINE"` flag for clear, readable text in the tracker. -- **Backdrops:** Use the `BackdropTemplate` mixin for frames requiring backgrounds/borders. +- **Backdrops:** Use the `BackdropTemplate` mixin for frames requiring backgrounds/borders. Main frame backdrop alpha is `0.55` (translucent); title bar is `0.65`. +- **Title Bar Controls:** The title bar contains a close button (`UIPanelCloseButton`, 18px) and a lock toggle button (plain `Button`, 32px) anchored to its left. The lock button uses `Interface\\BUTTONS\\LockButton-Locked-Up` / `LockButton-Unlocked-Up` textures and toggles `CooldownTrackerDB.frameLocked`. Always guard `OnDragStart` with a `frameLocked` check to avoid a Lua error when the frame is not movable. diff --git a/Core.lua b/Core.lua old mode 100644 new mode 100755 index 9ae5544..09be810 --- a/Core.lua +++ b/Core.lua @@ -24,7 +24,14 @@ eventFrame:SetScript("OnEvent", function(_, event, name) if event == "ADDON_LOADED" and name == AddonName then -- Initialise saved variables CooldownTrackerDB = CooldownTrackerDB or {} - CooldownTrackerDB.classCounts = CooldownTrackerDB.classCounts or {} + CooldownTrackerDB.classCounts = CooldownTrackerDB.classCounts or {} + CooldownTrackerDB.disabledSpells = CooldownTrackerDB.disabledSpells or {} + if CooldownTrackerDB.playSoundOnReady == nil then + CooldownTrackerDB.playSoundOnReady = true + end + if CooldownTrackerDB.frameLocked == nil then + CooldownTrackerDB.frameLocked = false + end -- Register the settings panel (applies saved durations to COOLDOWNS) CT:InitSettings() -- Expand cooldowns before building UI (so copies inherit saved durations) diff --git a/Data.lua b/Data.lua old mode 100644 new mode 100755 index ce19d6b..c84b194 --- a/Data.lua +++ b/Data.lua @@ -80,6 +80,37 @@ CT.COOLDOWNS = { r = 0.0, g = 0.44, b = 0.87, }, + -- ------------------------------------------------------------------------- + -- Priest (class colour: white) + -- ------------------------------------------------------------------------- + { + id = "priest_apotheosis", + class = "Priest", + name = "Apotheosis", + duration = 120, + defaultDuration = 120, + icon = "Interface\\Icons\\ability_priest_ascension", + r = 1.0, g = 1.0, b = 1.0, + }, + { + id = "priest_divine_hymn", + class = "Priest", + name = "Divine Hymn", + duration = 180, + defaultDuration = 180, + icon = "Interface\\Icons\\spell_holy_divinehymn", + r = 1.0, g = 1.0, b = 1.0, + }, + { + id = "priest_halo", + class = "Priest", + name = "Halo", + duration = 40, + defaultDuration = 40, + icon = "Interface\\Icons\\ability_priest_halo", + r = 1.0, g = 1.0, b = 1.0, + }, + -- ------------------------------------------------------------------------- -- Warrior (class colour: tan/gold) -- ------------------------------------------------------------------------- @@ -93,6 +124,32 @@ CT.COOLDOWNS = { r = 0.78, g = 0.61, b = 0.23, }, + -- ------------------------------------------------------------------------- + -- Death Knight (class colour: red) + -- ------------------------------------------------------------------------- + { + 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, + }, + + -- ------------------------------------------------------------------------- + -- Demon Hunter (class colour: purple) + -- ------------------------------------------------------------------------- + { + id = "dh_darkness", + class = "Demon Hunter", + name = "Darkness", + duration = 300, + defaultDuration = 300, + icon = "Interface\\Icons\\ability_demonhunter_darkness", + r = 0.64, g = 0.19, b = 0.79, + }, + -- ------------------------------------------------------------------------- -- Evoker (class colour: teal) -- ------------------------------------------------------------------------- @@ -105,17 +162,13 @@ CT.COOLDOWNS = { 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, - defaultDuration = 120, - icon = "Interface\\Icons\\spell_deathknight_antimagiczone", - r = 0.77, g = 0.12, b = 0.23, + id = "evoker_spatial_paradox", + class = "Evoker", + name = "Spatial Paradox", + duration = 180, + defaultDuration = 180, + icon = "Interface\\Icons\\ability_evoker_spatialparadox", + r = 0.2, g = 0.58, b = 0.5, }, } diff --git a/README.md b/README.md old mode 100644 new mode 100755 index 4836e9c..cca0e4f --- a/README.md +++ b/README.md @@ -2,14 +2,18 @@ A World of Warcraft **Midnight** addon for raid leaders to manually track healer cooldowns. -Because Midnight restricts addons from reading real-time combat data, this addon takes a manual approach: your healers call out on comms, you click a button, and the addon counts down the cooldown for you. +Because Midnight restricts addons from reading real-time combat data, this addon takes a manual approach: your healers call out on comms, you click a spell row, and the addon counts down the cooldown for you. ## Features -- One-click timer start/reset per ability +- Click anywhere on a spell row to start or reset its timer - Countdown display (`M:SS`) with colour-coded progress bar (green → yellow → red) -- Audible alert when a cooldown becomes available +- Audible alert when a cooldown becomes ready (toggleable) +- Grid or vertical layout with configurable column count (1–9) +- Per-spell visibility toggles — hide abilities you're not tracking +- Lockable window — prevent accidental dragging mid-raid - Draggable window with position saved between sessions +- In-game settings panel (Escape → Options → AddOns → Healer Cooldown Tracker) - Tooltips showing cooldown details on hover ## Installation @@ -26,10 +30,23 @@ Because Midnight restricts addons from reading real-time combat data, this addon |---|---| | `/cdt` | Toggle the tracker window | | `/cdt reset` | Reset all running timers | +| `/cdt columns N` | Set grid columns (1–9) | +| `/cdt settings` | Print reminder to open settings manually | -- Click **Used** when a healer uses an ability → timer starts -- Click **Reset** to cancel a running timer early +- Click any spell row to start the cooldown timer +- Click the same row again to reset a running timer - Drag the title bar to reposition; position saves on drag-stop +- Click the **lock icon** (top-right of title bar) to lock/unlock the window position + +## Settings Panel + +Open via **Escape → Options → AddOns → Healer Cooldown Tracker**: + +- **Columns** — number of grid columns (1 = vertical stack) +- **Play sound when cooldown is ready** — toggle the audible alert +- **Class Roster** — set how many of each class are in the raid; abilities duplicate per player (up to 5) +- **Show checkboxes** — hide individual spells from the tracker +- **Cooldown durations** — override any ability's cooldown in seconds; revert with the Default button ## Adding More Cooldowns @@ -37,12 +54,13 @@ Open `Data.lua` and add a new entry to the `CT.COOLDOWNS` table: ```lua { - id = "priest_divine_hymn", -- unique key - class = "Priest", - name = "Divine Hymn", - duration = 180, -- seconds - icon = "Interface\\Icons\\spell_holy_divinehymn", - r = 1.0, g = 0.8, b = 0.2, -- accent colour (RGB 0-1) + id = "priest_divine_hymn", -- unique key + class = "Priest", + name = "Divine Hymn", + duration = 180, -- seconds (overrideable in settings) + defaultDuration = 180, -- used by the "Default" reset button + icon = "Interface\\Icons\\spell_holy_divinehymn", + r = 1.0, g = 1.0, b = 1.0, -- accent colour (RGB 0-1) }, ``` @@ -55,7 +73,9 @@ CooldownTracker/ ├── CooldownTracker.toc — Addon manifest & metadata ├── Data.lua — Cooldown definitions (edit this to add abilities) ├── UI.lua — Frame, row widgets, timer rendering +├── Settings.lua — In-game options panel ├── Core.lua — Init, events, slash commands +├── AGENTS.md — AI agent coding guidelines └── README.md — This file ``` diff --git a/Settings.lua b/Settings.lua old mode 100644 new mode 100755 index 3296b08..d330b83 --- a/Settings.lua +++ b/Settings.lua @@ -109,9 +109,22 @@ local function CreateSettingsPanel() end) colBox:SetScript("OnLeave", function() GameTooltip:Hide() end) + -- ----- Sound toggle ----------------------------------------------------- + local soundCheck = CreateFrame("CheckButton", "CTSettingsSoundCheck", panel, "UICheckButtonTemplate") + soundCheck:SetSize(24, 24) + soundCheck:SetPoint("TOPLEFT", colLabel, "BOTTOMLEFT", 0, -14) + soundCheck:SetChecked(CooldownTrackerDB.playSoundOnReady ~= false) + soundCheck:SetScript("OnClick", function(self) + CooldownTrackerDB.playSoundOnReady = self:GetChecked() + end) + + local soundLabel = panel:CreateFontString(nil, "ARTWORK", "GameFontHighlight") + soundLabel:SetPoint("LEFT", soundCheck, "RIGHT", 4, 0) + soundLabel:SetText("Play sound when cooldown is ready") + local layoutDivider = panel:CreateTexture(nil, "ARTWORK") layoutDivider:SetHeight(1) - layoutDivider:SetPoint("TOPLEFT", colLabel, "BOTTOMLEFT", 0, -10) + layoutDivider:SetPoint("TOPLEFT", soundCheck, "BOTTOMLEFT", 0, -10) layoutDivider:SetWidth(CONTENT_WIDTH) layoutDivider:SetColorTexture(0.3, 0.3, 0.4, 0.4) @@ -206,12 +219,17 @@ local function CreateSettingsPanel() -- ----- Cooldown duration headers ---------------------------------------- local hdrAbility = panel:CreateFontString(nil, "ARTWORK", "GameFontNormalSmall") - hdrAbility:SetPoint("TOPLEFT", rosterDivider, "BOTTOMLEFT", ICON_SIZE + 12, -8) + hdrAbility:SetPoint("TOPLEFT", rosterDivider, "BOTTOMLEFT", 28 + ICON_SIZE + 12, -8) hdrAbility:SetText("Ability") hdrAbility:SetTextColor(0.7, 0.7, 0.7) + local hdrShow = panel:CreateFontString(nil, "ARTWORK", "GameFontNormalSmall") + hdrShow:SetPoint("LEFT", rosterDivider, "BOTTOMLEFT", 4, -8) + hdrShow:SetText("Show") + hdrShow:SetTextColor(0.7, 0.7, 0.7) + local hdrDur = panel:CreateFontString(nil, "ARTWORK", "GameFontNormalSmall") - hdrDur:SetPoint("LEFT", hdrAbility, "LEFT", 260, 0) + hdrDur:SetPoint("LEFT", hdrAbility, "LEFT", 248, 0) hdrDur:SetText("Cooldown (seconds)") hdrDur:SetTextColor(0.7, 0.7, 0.7) @@ -224,6 +242,7 @@ local function CreateSettingsPanel() -- editBoxes and RefreshAllEditBoxes must be declared here so the OnShow -- closure below can reference them (Lua requires locals before use). local editBoxes = {} + local spellCheckboxes = {} local function RefreshAllEditBoxes() C_Timer.After(0, function() for _, cd in ipairs(CT.COOLDOWNS) do @@ -240,10 +259,15 @@ local function CreateSettingsPanel() panel:SetScript("OnShow", function() C_Timer.After(0, function() colBox:SetText(tostring(CooldownTrackerDB.columns or 1)) - local counts = CooldownTrackerDB.classCounts or {} + soundCheck:SetChecked(CooldownTrackerDB.playSoundOnReady ~= false) + local counts = CooldownTrackerDB.classCounts or {} + local disabled = CooldownTrackerDB.disabledSpells or {} for clsName, box in pairs(classCountBoxes) do box:SetText(tostring(counts[clsName] or 1)) end + for spellId, cb in pairs(spellCheckboxes) do + cb:SetChecked(not disabled[spellId]) + end RefreshAllEditBoxes() end) end) @@ -281,10 +305,26 @@ local function CreateSettingsPanel() strip:SetPoint("LEFT", row, "LEFT", 0, 0) strip:SetColorTexture(cd.r, cd.g, cd.b, 0.9) + -- Spell visibility checkbox + local spellCB = CreateFrame("CheckButton", "CTSettingsSpell_" .. cd.id, row, "UICheckButtonTemplate") + spellCB:SetSize(24, 24) + spellCB:SetPoint("LEFT", row, "LEFT", 4, 0) + spellCB:SetChecked(not (CooldownTrackerDB.disabledSpells or {})[cd.id]) + spellCB:SetScript("OnClick", function(self) + CooldownTrackerDB.disabledSpells = CooldownTrackerDB.disabledSpells or {} + if self:GetChecked() then + CooldownTrackerDB.disabledSpells[cd.id] = nil + else + CooldownTrackerDB.disabledSpells[cd.id] = true + end + if CT.RebuildUI then CT:RebuildUI() end + end) + spellCheckboxes[cd.id] = spellCB + -- Icon local icon = row:CreateTexture(nil, "ARTWORK") icon:SetSize(ICON_SIZE, ICON_SIZE) - icon:SetPoint("LEFT", row, "LEFT", 8, 0) + icon:SetPoint("LEFT", row, "LEFT", 36, 0) icon:SetTexture(cd.icon) icon:SetTexCoord(0.08, 0.92, 0.08, 0.92) @@ -304,7 +344,7 @@ local function CreateSettingsPanel() -- 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:SetPoint("LEFT", row, "LEFT", 308, 0) editBox:SetAutoFocus(false) editBox:SetFontObject("ChatFontNormal") editBox:SetJustifyH("CENTER") @@ -329,6 +369,14 @@ local function CreateSettingsPanel() CooldownTrackerDB.customDurations = CooldownTrackerDB.customDurations or {} CooldownTrackerDB.customDurations[cd.id] = (val ~= cd.defaultDuration) and val or nil cd.duration = val + if CT.expandedCooldowns then + local prefix = cd.id .. "_" + for _, ecd in ipairs(CT.expandedCooldowns) do + if ecd.id == cd.id or ecd.id:sub(1, #prefix) == prefix then + ecd.duration = val + end + end + end end end editBox:SetScript("OnTextChanged", function(self, userInput) diff --git a/UI.lua b/UI.lua old mode 100644 new mode 100755 index 432962c..33b5902 --- a/UI.lua +++ b/UI.lua @@ -21,13 +21,11 @@ local ROW_PADDING = 6 local WIDE_ROW_H = 36 local WIDE_W = 300 local ICON_SIZE = 28 -local BUTTON_W = 52 - -- Card mode (columns ≥ 2) local CARD_W = 100 local CARD_H = 72 local CARD_PAD = 6 -local CARD_ICON = 26 +local CARD_ICON = 44 -- --------------------------------------------------------------------------- -- Local helpers @@ -40,10 +38,6 @@ local function FormatTime(seconds) else return string.format("0:%02d", s) end end -local function SetBtnColor(btn, r, g, b) - local tex = btn:GetNormalTexture() - if tex then tex:SetVertexColor(r, g, b) end -end local function SavePosition(frame) CooldownTrackerDB = CooldownTrackerDB or {} @@ -84,11 +78,11 @@ local function ApplyWideLayout(row) row.timerLabel:ClearAllPoints() row.timerLabel:SetFont("Fonts\\FRIZQT__.TTF", 13, "OUTLINE") - row.timerLabel:SetPoint("RIGHT", row, "RIGHT", -(BUTTON_W + 8), 0) + row.timerLabel:SetPoint("RIGHT", row, "RIGHT", -6, 0) row.bar:Show() row.bar:ClearAllPoints() - row.bar:SetSize(math.max(4, rowW - ICON_SIZE - BUTTON_W - 30), 3) + row.bar:SetSize(math.max(4, rowW - ICON_SIZE - 60), 3) row.bar:SetPoint("BOTTOMLEFT", row.iconTex, "BOTTOMRIGHT", 6, 2) row.barFill:Show() @@ -96,9 +90,7 @@ local function ApplyWideLayout(row) row.barFill:SetSize(row.bar:GetWidth(), 3) row.barFill:SetPoint("LEFT", row.bar, "LEFT", 0, 0) - row.button:ClearAllPoints() - row.button:SetSize(BUTTON_W, 22) - row.button:SetPoint("RIGHT", row, "RIGHT", -2, 0) + row.button:Hide() row.isWide = true end @@ -112,25 +104,21 @@ local function ApplyCardLayout(row, cW, cH) row.strip:SetSize(cW, 3) row.strip:SetPoint("TOP", row, "TOP", 0, 0) - local cd = row.cd + row.nameLabel:Hide() + row.classLabel:Hide() + row.iconTex:ClearAllPoints() row.iconTex:SetSize(CARD_ICON, CARD_ICON) - row.iconTex:SetPoint("TOP", row, "TOP", 0, -6) - - row.nameLabel:Hide() -- not enough room - row.classLabel:Hide() + row.iconTex:SetPoint("CENTER", row, "CENTER", 0, 6) row.timerLabel:ClearAllPoints() row.timerLabel:SetFont("Fonts\\FRIZQT__.TTF", 12, "OUTLINE") - row.timerLabel:SetPoint("CENTER", row, "CENTER", 0, 4) + row.timerLabel:SetPoint("BOTTOM", row, "BOTTOM", 0, 6) row.bar:Hide() row.barFill:Hide() - local btnW = math.max(40, cW - 12) - row.button:ClearAllPoints() - row.button:SetSize(btnW, 20) - row.button:SetPoint("BOTTOM", row, "BOTTOM", 0, 4) + row.button:Hide() row.isWide = false end @@ -151,10 +139,10 @@ local function UpdateRow(row, now) row.barFill:SetWidth(row.bar:GetWidth()) row.barFill:SetVertexColor(0.2, 0.9, 0.2) end - row.button:SetText("Used") - SetBtnColor(row.button, 0.3, 0.85, 0.3) row.iconTex:SetAlpha(1) - PlaySound(SOUNDKIT.ALARM_CLOCK_WARNING_3) + if CooldownTrackerDB.playSoundOnReady ~= false then + PlaySound(SOUNDKIT.ALARM_CLOCK_WARNING_3) + end else local frac = remaining / cd.duration if row.isWide then @@ -165,8 +153,6 @@ local function UpdateRow(row, now) else row.barFill:SetVertexColor(cd.r, cd.g, cd.b) end end row.timerLabel:SetText("|cffff8040" .. FormatTime(remaining) .. "|r") - row.button:SetText("Reset") - SetBtnColor(row.button, 0.8, 0.3, 0.3) end else row.timerLabel:SetText("|cff00ff00Ready|r") @@ -174,8 +160,6 @@ local function UpdateRow(row, now) row.barFill:SetWidth(row.bar:GetWidth()) row.barFill:SetVertexColor(0.2, 0.9, 0.2) end - row.button:SetText("Used") - SetBtnColor(row.button, 0.3, 0.85, 0.3) end end @@ -183,8 +167,9 @@ end -- Row construction (creates all child frames; layout applied separately) -- --------------------------------------------------------------------------- local function CreateRow(parent, cd) - local row = CreateFrame("Frame", nil, parent) + local row = CreateFrame("Button", nil, parent) row.cd = cd + row:RegisterForClicks("LeftButtonUp") local bg = row:CreateTexture(nil, "BACKGROUND") bg:SetAllPoints(row) @@ -224,10 +209,12 @@ local function CreateRow(parent, cd) fill:SetColorTexture(cd.r, cd.g, cd.b) row.barFill = fill - local btn = CreateFrame("Button", nil, row, "UIPanelButtonTemplate") - btn:SetText("Used") - SetBtnColor(btn, 0.3, 0.85, 0.3) - btn:SetScript("OnClick", function() + -- Invisible placeholder kept so RebuildUI doesn't need changes (button ref still safe to hide) + local btn = CreateFrame("Frame", nil, row) + btn:Hide() + row.button = btn + + row:SetScript("OnClick", function() local mycd = row.cd if CT.activeTimers[mycd.id] then CT.activeTimers[mycd.id] = nil @@ -236,7 +223,6 @@ local function CreateRow(parent, cd) end UpdateRow(row, GetTime()) end) - row.button = btn row:SetScript("OnEnter", function() local mycd = row.cd @@ -277,19 +263,22 @@ end -- count>1: N copies with unique IDs and "#N" appended to name -- --------------------------------------------------------------------------- function CT:BuildExpandedCooldowns() - local counts = CooldownTrackerDB.classCounts or {} + local counts = CooldownTrackerDB.classCounts or {} + local disabled = CooldownTrackerDB.disabledSpells or {} CT.expandedCooldowns = {} for _, cd in ipairs(CT.COOLDOWNS) do - local count = math.max(1, math.min(5, counts[cd.class] or 1)) - if count == 1 then - table.insert(CT.expandedCooldowns, cd) - else - for n = 1, count do - local copy = {} - for k, v in pairs(cd) do copy[k] = v end - copy.id = cd.id .. "_" .. n - copy.name = cd.name .. " #" .. n - table.insert(CT.expandedCooldowns, copy) + if not disabled[cd.id] then + local count = math.max(1, math.min(5, counts[cd.class] or 1)) + if count == 1 then + table.insert(CT.expandedCooldowns, cd) + else + for n = 1, count do + local copy = {} + for k, v in pairs(cd) do copy[k] = v end + copy.id = cd.id .. "_" .. n + copy.name = cd.name .. " #" .. n + table.insert(CT.expandedCooldowns, copy) + end end end end @@ -325,8 +314,6 @@ function CT:RebuildUI() row.classLabel:SetTextColor(cd.r, cd.g, cd.b) row.timerLabel:SetText("|cff00ff00Ready|r") row.barFill:SetVertexColor(cd.r, cd.g, cd.b) - row.button:SetText("Used") - SetBtnColor(row.button, 0.3, 0.85, 0.3) end -- Hide unused pool frames @@ -424,10 +411,14 @@ function CT:BuildUI() f:SetMovable(true) f:EnableMouse(true) f:RegisterForDrag("LeftButton") - f:SetScript("OnDragStart", f.StartMoving) + f:SetScript("OnDragStart", function(self) + if not CooldownTrackerDB.frameLocked then self:StartMoving() end + end) f:SetScript("OnDragStop", function(self) - self:StopMovingOrSizing() - SavePosition(self) + if not CooldownTrackerDB.frameLocked then + self:StopMovingOrSizing() + SavePosition(self) + end end) f:SetFrameStrata("MEDIUM") f:SetClampedToScreen(true) @@ -439,7 +430,7 @@ function CT:BuildUI() tile = true, tileSize = 16, edgeSize = 16, insets = { left = 4, right = 4, top = 4, bottom = 4 }, }) - f:SetBackdropColor(0.05, 0.05, 0.08, 0.92) + f:SetBackdropColor(0.05, 0.05, 0.08, 0.55) f:SetBackdropBorderColor(0.3, 0.3, 0.4, 0.8) end @@ -448,7 +439,7 @@ function CT:BuildUI() titleBg:SetHeight(TITLE_HEIGHT) titleBg:SetPoint("TOPLEFT", f, "TOPLEFT", 0, 0) titleBg:SetPoint("TOPRIGHT", f, "TOPRIGHT", 0, 0) - titleBg:SetColorTexture(0.08, 0.08, 0.15, 0.95) + titleBg:SetColorTexture(0.08, 0.08, 0.15, 0.65) -- Title text local titleText = f:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall") @@ -462,6 +453,47 @@ function CT:BuildUI() closeBtn:SetPoint("TOPRIGHT", f, "TOPRIGHT", -2, -3) closeBtn:SetScript("OnClick", function() f:Hide() end) + -- Lock button + local lockBtn = CreateFrame("Button", nil, f) + lockBtn:SetSize(32, 32) + lockBtn:SetPoint("RIGHT", closeBtn, "LEFT", -2, 0) + + local lockTex = lockBtn:CreateTexture(nil, "ARTWORK") + lockTex:SetAllPoints(lockBtn) + lockBtn.lockTex = lockTex + + local function UpdateLockVisual() + if CooldownTrackerDB.frameLocked then + lockTex:SetTexture("Interface\\BUTTONS\\LockButton-Locked-Up") + lockTex:SetVertexColor(0.9, 0.7, 0.2) + else + lockTex:SetTexture("Interface\\BUTTONS\\LockButton-Unlocked-Up") + lockTex:SetVertexColor(0.6, 0.6, 0.6) + end + end + + lockBtn:SetScript("OnClick", function() + CooldownTrackerDB.frameLocked = not CooldownTrackerDB.frameLocked + f:SetMovable(not CooldownTrackerDB.frameLocked) + UpdateLockVisual() + end) + lockBtn:SetScript("OnEnter", function() + GameTooltip:SetOwner(lockBtn, "ANCHOR_BOTTOMLEFT") + if CooldownTrackerDB.frameLocked then + GameTooltip:SetText("Frame Locked") + GameTooltip:AddLine("Click to unlock and allow dragging.", 0.8, 0.8, 0.8) + else + GameTooltip:SetText("Frame Unlocked") + GameTooltip:AddLine("Click to lock the frame in place.", 0.8, 0.8, 0.8) + end + GameTooltip:Show() + end) + lockBtn:SetScript("OnLeave", function() GameTooltip:Hide() end) + + -- Apply saved lock state + f:SetMovable(not CooldownTrackerDB.frameLocked) + UpdateLockVisual() + -- Divider local divider = f:CreateTexture(nil, "ARTWORK") divider:SetHeight(1)