Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,10 @@ All Lua files start with:
- Test load order mirrors TOC load order. Test files mirror source paths; library tests live under `Libs/<Name>/Tests/`.
- Test production code directly. Do not mirror production logic in specs or preserve table-attached helpers just so specs can call them.
- Prefer testing the real public flow, or the actual shared owner of the logic.
- Tests and shared stubs must model the supported Retail runtime strictly. Do not add fallback fields, compatibility aliases, or helper behavior that production code does not receive.
- Shared test helpers default to the current Retail API shape. If a test needs a non-Retail shape to cover a compatibility branch or malformed input, keep that stub local to the test and document why.
- Stub the canonical function, not a wrapper or alias. If a stub diverges from real behavior, fix the stub.
- If a test double masked a production bug, fix the shared helper first and add a regression that exercises the real runtime shape before considering the bug closed.
- Do not guard production APIs only to satisfy tests. If an API exists in the supported runtime/load order, tests must stub it.
- Reuse `Tests/TestHelpers.lua` before creating new shared helpers.
- `StaticPopup_Show` stubs forward `(name, text1, text2, data)` and call `OnAccept(self, data)`.
Expand Down
7 changes: 7 additions & 0 deletions ClassUtil.lua
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,12 @@ function ClassUtil.GetPlayerResourceType()
return ClassUtil.GetResourceType(class, GetSpecialization(), GetShapeshiftForm())
end

--- Returns whether the player is a Death Knight.
function ClassUtil.IsDeathKnight()
local _, class = UnitClass("player")
return class == "DEATHKNIGHT"
end

--- Gets the max Maelstrom value that can diff based on talents
local function getMaelstromWeaponMax()
if C_SpellBook.IsSpellKnown(C.RESOURCEBAR_RAGING_MAELSTROM_SPELLID) then
Expand Down Expand Up @@ -135,6 +141,7 @@ function ClassUtil.GetCurrentMaxResourceValues(resourceType)
if resourceType then
local max = UnitPowerMax("player", resourceType)
local current = UnitPower("player", resourceType)
---@type number|nil
local safeMax = max
if issecretvalue(max) then
safeMax = nil
Expand Down
14 changes: 0 additions & 14 deletions ColorUtil.lua
Original file line number Diff line number Diff line change
Expand Up @@ -7,20 +7,6 @@ local _, ns = ...
local ColorUtil = {}
ns.ColorUtil = ColorUtil

--- Compares two ECM_Color tables for equality.
---@param c1 ECM_Color|nil
---@param c2 ECM_Color|nil
---@return boolean
function ColorUtil.AreEqual(c1, c2)
if c1 == c2 then
return true
end
if not c1 or not c2 then
return false
end
return c1.r == c2.r and c1.g == c2.g and c1.b == c2.b and c1.a == c2.a
end

local function clamp(v, minV, maxV)
return math.max(minV, math.min(maxV, v))
end
Expand Down
47 changes: 20 additions & 27 deletions Constants.lua
Original file line number Diff line number Diff line change
Expand Up @@ -177,19 +177,19 @@ local constants = {

--- Predefined icon stacks resolved at runtime by stackKey.
--- Each entry defines an icon kind and its candidate sources.
local BUILTIN_STACKS = {
constants.BUILTIN_STACKS = {
trinket1 = { kind = "equipSlot", slotId = 13, label = "Trinket 1" },
trinket2 = { kind = "equipSlot", slotId = 14, label = "Trinket 2" },
}

--- Default display order for builtin stack keys (matches default viewers.utility order).
local BUILTIN_STACK_ORDER = { "trinket1", "trinket2" }
constants.BUILTIN_STACK_ORDER = { "trinket1", "trinket2" }

local DRACTHYR_WING_BUFFET_IDS = { 357214, 368970 } -- Base and enhanced evoker variants.
local dracthyrWingBuffetIds = { 357214, 368970 } -- Base and enhanced evoker variants.

--- Racial ability lookup keyed by UnitRace("player") raceFileName.
--- One primary active racial per race.
local RACIAL_ABILITIES = {
constants.RACIAL_ABILITIES = {
Human = { spellId = 59752 }, -- Every Man for Himself
Orc = { spellId = 33697 }, -- Blood Fury
Dwarf = { spellId = 20594 }, -- Stoneform
Expand All @@ -213,25 +213,25 @@ local RACIAL_ABILITIES = {
Vulpera = { spellId = 312411 }, -- Bag of Tricks
MagharOrc = { spellId = 274738 }, -- Ancestral Call
Mechagnome = { spellId = 312924 }, -- Hyper Organic Light Originator
Dracthyr = { spellIds = DRACTHYR_WING_BUFFET_IDS }, -- Wing Buffet
Dracthyr = { spellIds = dracthyrWingBuffetIds }, -- Wing Buffet
EarthenDwarf = { spellId = 436717 }, -- Azerite Surge
}

--- Some racial abilities have different spell IDs. For example, Dracthyr evokers
--- have a more potent wing buffet compared to other classes.
local RACIAL_SPELL_ALIASES = {
[357214] = DRACTHYR_WING_BUFFET_IDS,
[368970] = DRACTHYR_WING_BUFFET_IDS,
constants.RACIAL_SPELL_ALIASES = {
[357214] = dracthyrWingBuffetIds,
[368970] = dracthyrWingBuffetIds,
}

local BLIZZARD_FRAMES = {
constants.BLIZZARD_FRAMES = {
"EssentialCooldownViewer",
"UtilityCooldownViewer",
"BuffIconCooldownViewer",
"BuffBarCooldownViewer",
}

local CLASS_COLORS = {
constants.CLASS_COLORS = {
DEATHKNIGHT = "C41F3B",
DEMONHUNTER = "A330C9",
DRUID = "FF7D0A",
Expand All @@ -249,19 +249,19 @@ local CLASS_COLORS = {

-- Resource types that support a separate color when at maximum value.
-- Code-level gate; user toggle is stored in the profile (maxColorsEnabled).
local resourceBarMaxColorTypes = {
constants.RESOURCEBAR_MAX_COLOR_TYPES = {
[constants.RESOURCEBAR_TYPE_ICICLES] = true,
[constants.RESOURCEBAR_TYPE_DEVOURER_META] = true,
[constants.RESOURCEBAR_TYPE_DEVOURER_NORMAL] = true,
}

local resourceBarCastableMaxColorSpells = {
constants.RESOURCEBAR_CASTABLE_MAX_COLOR_SPELLS = {
[constants.RESOURCEBAR_TYPE_DEVOURER_META] = constants.SPELLID_COLLAPSING_STAR,
[constants.RESOURCEBAR_TYPE_DEVOURER_NORMAL] = constants.SPELLID_VOID_META,
}

--- Authoritative mapping from module name to its profile config key.
local moduleConfigKeys = {
--- Maps modules to their respective profile config key.
constants.MODULE_CONFIG_KEYS = {
[constants.POWERBAR] = "powerBar",
[constants.RESOURCEBAR] = "resourceBar",
[constants.RUNEBAR] = "runeBar",
Expand All @@ -273,33 +273,26 @@ local moduleConfigKeys = {
--- Returns the profile config key for a module name.
--- Uses the authoritative lookup; falls back to lowercasing the first character.
function constants.ConfigKeyForModule(name)
return moduleConfigKeys[name] or (name:sub(1, 1):lower() .. name:sub(2))
return constants.MODULE_CONFIG_KEYS[name] or (name:sub(1, 1):lower() .. name:sub(2))
end

local chainOrder = {
-- Defines the ordering of modules for chained anchoring.
constants.CHAIN_ORDER = {
constants.POWERBAR,
constants.RESOURCEBAR,
constants.RUNEBAR,
constants.BUFFBARS,
constants.EXTERNALBARS,
}
constants.CHAIN_ORDER = chainOrder
constants.MODULE_ORDER = {

-- Controls the order that modules are loaded in.
constants.MODULE_LOAD_ORDER = {
constants.POWERBAR,
constants.RESOURCEBAR,
constants.RUNEBAR,
constants.BUFFBARS,
constants.EXTERNALBARS,
constants.EXTRAICONS,
}
constants.MODULE_CONFIG_KEYS = moduleConfigKeys
constants.BLIZZARD_FRAMES = BLIZZARD_FRAMES
constants.BUILTIN_STACKS = BUILTIN_STACKS
constants.BUILTIN_STACK_ORDER = BUILTIN_STACK_ORDER
constants.RACIAL_ABILITIES = RACIAL_ABILITIES
constants.RACIAL_SPELL_ALIASES = RACIAL_SPELL_ALIASES
constants.RESOURCEBAR_CASTABLE_MAX_COLOR_SPELLS = resourceBarCastableMaxColorSpells
constants.CLASS_COLORS = CLASS_COLORS
constants.RESOURCEBAR_MAX_COLOR_TYPES = resourceBarMaxColorTypes

ns.Constants = constants
6 changes: 0 additions & 6 deletions ECM.lua
Original file line number Diff line number Diff line change
Expand Up @@ -51,12 +51,6 @@ function ns.AreWarningsEnabled()
return not gc or gc.warnings ~= false
end

--- Returns whether the player is a Death Knight.
function ns.IsDeathKnight()
local _, class = UnitClass("player")
return class == "DEATHKNIGHT"
end

local function getAddonVersion()
return C_AddOns.GetAddOnMetadata(ADDON_NAME, C.ADDON_METADATA_VERSION_KEY)
end
Expand Down
3 changes: 2 additions & 1 deletion EnhancedCooldownManager.code-workspace
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,8 @@
"SettingsPanel",
"SettingsSliderControlMixin",
"CreateSettingsListSectionHeaderInitializer",
"Round"
"Round",
"CLASS_COLORS"
],
"Lua.diagnostics.disable": [
"assign-type-mismatch"
Expand Down
2 changes: 1 addition & 1 deletion EnhancedCooldownManager.toc
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
## Title: Enhanced Cooldown Manager |cff9c9c9cby|r |cffa855f7A|r|cff7a84f7r|r|cff4cc9f0g|r|cff22c55ei|r
## Notes: Add resource, power and buff bars, new spells and item icons to the built-in Cooldown Manager.
## Author: Argi
## Version: v0.8.5
## Version: v0.8.6
## SavedVariables: EnhancedCooldownManagerDB
## Category-enUS: User Interface
## IconTexture: Interface\AddOns\EnhancedCooldownManager\Media\icon
Expand Down
108 changes: 64 additions & 44 deletions Libs/LibSettingsBuilder/Interop/Enhancements.lua
Original file line number Diff line number Diff line change
Expand Up @@ -451,52 +451,71 @@ local function getCategoryDefaultsButton()
return header and header.DefaultsButton or nil
end

function interop.installCategoryDefaultsOverride(onClick, enabledPredicate, confirmDefaults, pageName)
local function isPageDefaultsEnabled(cbs)
return (cbs.onDefault or cbs.defaultSettings and #cbs.defaultSettings > 0)
and (not cbs.onDefaultEnabled or cbs.onDefaultEnabled())
end

local function resetPageDefaults(cbs)
if cbs.onDefault then
cbs.onDefault()
return
end

for _, setting in ipairs(cbs.defaultSettings or {}) do
setting:SetValue(setting._lsbDefaultValue)
end
end

local function getPageDefaultsConfirmText(cbs)
local text = cbs.defaultsConfirmText
if not text:find("%%s") then return text end
return string.format(text, tostring(cbs.pageName or ""):lower())
end

function interop.installCategoryDefaultsHide()
local button = getCategoryDefaultsButton()
if not button then
return function() end
end

local originalOnClick = button:GetScript("OnClick")
local originalEnabled = button:IsEnabled()
local wasShown = button:IsShown()
button:Hide()
interop.refreshVisibleSettingsFrames()

local function applyEnabled()
if enabledPredicate then
button:SetEnabled(enabledPredicate() and true or false)
elseif not onClick then
button:SetEnabled(originalEnabled)
else
button:SetEnabled(true)
return function()
if wasShown then
button:Show()
interop.refreshVisibleSettingsFrames()
end
end
end

function interop.installCategoryDefaultsOverride(cbs)
local button = getCategoryDefaultsButton()
if not button then
return function() end
end

button:SetScript("OnClick", function(self)
if enabledPredicate and not enabledPredicate() then
local originalScript = button:GetScript("OnClick")
local wasEnabled = button:IsEnabled()
button:SetEnabled(isPageDefaultsEnabled(cbs))
button:SetScript("OnClick", function()
if not isPageDefaultsEnabled(cbs) then
return
end

local function reset()
if onClick then
onClick()
applyEnabled()
elseif originalOnClick then
originalOnClick(self)
end
end

if confirmDefaults then
confirmDefaults(pageName, reset)
else
reset()
end
interop.showConfirmDialog(cbs.defaultsDialogName, getPageDefaultsConfirmText(cbs), {
onAccept = function()
resetPageDefaults(cbs)
interop.refreshVisibleSettingsFrames()
end,
})
end)
applyEnabled()

return function()
if button:GetScript("OnClick") then
button:SetScript("OnClick", originalOnClick)
end
button:SetEnabled(originalEnabled)
button:SetScript("OnClick", originalScript)
button:SetEnabled(wasEnabled)
end
end

Expand All @@ -505,9 +524,13 @@ local function notifyLifecycleHidden(category)
if not cbs then
return
end
if cbs._defaultsRestore then
cbs._defaultsRestore()
cbs._defaultsRestore = nil
if cbs._defaultsHideRestore then
cbs._defaultsHideRestore()
cbs._defaultsHideRestore = nil
end
if cbs._defaultsOverrideRestore then
cbs._defaultsOverrideRestore()
cbs._defaultsOverrideRestore = nil
end
if cbs.onHide then
cbs.onHide()
Expand All @@ -533,13 +556,10 @@ function interop.installPageLifecycleHooks()
lib._activeLifecycleCategory = category
local cbs = category and lib._pageLifecycleCallbacks[category] or nil
if cbs then
if cbs.onDefault or cbs.confirmDefaults then
cbs._defaultsRestore = interop.installCategoryDefaultsOverride(
cbs.onDefault,
cbs.onDefaultEnabled,
cbs.confirmDefaults,
cbs.pageName
)
if cbs.hideDefaults then
cbs._defaultsHideRestore = interop.installCategoryDefaultsHide()
elseif cbs.onDefault or cbs.defaultSettings and #cbs.defaultSettings > 0 then
cbs._defaultsOverrideRestore = interop.installCategoryDefaultsOverride(cbs)
end
if cbs.onShow then
cbs.onShow()
Expand Down Expand Up @@ -583,12 +603,12 @@ function interop.refreshVisibleSettingsFrames()
interop.forEachVisibleSettingsFrame(interop.refreshSettingsFrame)
end

function interop.ensureConfirmDialog(name)
function interop.ensureConfirmDialog(name, button1, button2)
if not StaticPopupDialogs[name] then
StaticPopupDialogs[name] = {
text = "%s",
button1 = YES,
button2 = NO,
button1 = button1 or YES,
button2 = button2 or NO,
OnAccept = function(_, data)
if data and data.onAccept then
data.onAccept()
Expand Down
3 changes: 2 additions & 1 deletion Libs/LibSettingsBuilder/Interop/ListRows.lua
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,8 @@ local function applyHeaderFrame(frame, data)
ensureHeaderRowWidgets(frame)
local settingsHeader = data.attachToCategoryHeader and getSettingsListHeader() or nil
local actionParent = settingsHeader or frame
local rightAnchor = settingsHeader and settingsHeader.DefaultsButton or nil
local defaultsButton = settingsHeader and settingsHeader.DefaultsButton or nil
local rightAnchor = defaultsButton and defaultsButton:IsShown() and defaultsButton or nil

if frame._lsbHeaderTitle then
frame._lsbHeaderTitle:ClearAllPoints()
Expand Down
Loading