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
2 changes: 2 additions & 0 deletions ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -296,6 +296,8 @@ ECM uses LibSettingsBuilder as a single declarative registration tree:

`ExtraIconsOptions` owns the main viewer-management page, while `ItemStacksOptions` appends the Item Stacks subpage under the same Extra Icons section. Viewer entries reference item stacks by stable profile IDs so renames do not mutate viewer rows.

Resource bar color rows prepend a fixed two-icon prefix so shared resources like Combo Points can show multiple class icons while keeping the icon column right-aligned.

`UI/SpellColorsPage.lua` owns the shared Spell Colors subcategory. `BuffBarsOptions` registers the page once, and both `BuffBars` and `ExternalBars` register scoped sections into it, so the two modules share one editor without sharing saved color pools.

ECM only consumes the documented public surface (`LSB.New`, `lsb:GetSection`, `lsb:GetRootPage`, `lsb:GetPage`, `lsb:HasCategory`, `page:GetId`, `page:Refresh`) and registers pages through raw declarative row tables — no builder-level helper constructors and no deprecated transition namespaces.
Expand Down
62 changes: 50 additions & 12 deletions Tests/UI/ResourceBarOptions_spec.lua
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,15 @@ local TestHelpers =
describe("ResourceBarOptions getters/setters/defaults", function()
local originalGlobals
local profile, defaults, SB, ns, settings, capturedPage
local emptyIcon = "|TInterface\\Common\\spacer:14:14|t"

local function classIcon(className)
return "|A:classicon-" .. className .. ":14:14|a"
end

local function resourceLabel(prefix, colorClass, labelKey)
return prefix .. " |cff" .. ns.Constants.CLASS_COLORS[colorClass] .. ns.L[labelKey] .. "|r"
end

local function getPageRow(path)
for _, row in ipairs(capturedPage.rows) do
Expand Down Expand Up @@ -157,19 +166,47 @@ describe("ResourceBarOptions getters/setters/defaults", function()
end)
it("prefixes each resource label with its class icon and color", function()
local defsByKey = {}
local demonHunterIcon = emptyIcon .. " " .. classIcon("demonhunter")
for _, def in ipairs(assert(getPageRow("colors")).defs) do
defsByKey[def.key] = def.name
end

assert.are.equal("|A:classicon-demonhunter:14:14|a |cff" .. ns.Constants.CLASS_COLORS.DEMONHUNTER .. ns.L["RESOURCE_SOUL_FRAGMENTS_DH"] .. "|r", defsByKey[ns.Constants.RESOURCEBAR_TYPE_VENGEANCE_SOULS])
assert.are.equal("|A:classicon-demonhunter:14:14|a |cff" .. ns.Constants.CLASS_COLORS.DEMONHUNTER .. ns.L["RESOURCE_SOUL_FRAGMENTS_DEVOURER"] .. "|r", defsByKey[ns.Constants.RESOURCEBAR_TYPE_DEVOURER_NORMAL])
assert.are.equal("|A:classicon-mage:14:14|a |cff" .. ns.Constants.CLASS_COLORS.MAGE .. ns.L["RESOURCE_ICICLES"] .. "|r", defsByKey[ns.Constants.RESOURCEBAR_TYPE_ICICLES])
assert.are.equal("|A:classicon-monk:14:14|a |cff" .. ns.Constants.CLASS_COLORS.MONK .. ns.L["RESOURCE_CHI"] .. "|r", defsByKey[Enum.PowerType.Chi])
assert.are.equal("|A:classicon-rogue:14:14|a |cff" .. ns.Constants.CLASS_COLORS.ROGUE .. ns.L["RESOURCE_COMBO_POINTS"] .. "|r", defsByKey[Enum.PowerType.ComboPoints])
assert.are.equal("|A:classicon-evoker:14:14|a |cff" .. ns.Constants.CLASS_COLORS.EVOKER .. ns.L["RESOURCE_ESSENCE"] .. "|r", defsByKey[Enum.PowerType.Essence])
assert.are.equal("|A:classicon-paladin:14:14|a |cff" .. ns.Constants.CLASS_COLORS.PALADIN .. ns.L["RESOURCE_HOLY_POWER"] .. "|r", defsByKey[Enum.PowerType.HolyPower])
assert.are.equal("|A:classicon-shaman:14:14|a |cff" .. ns.Constants.CLASS_COLORS.SHAMAN .. ns.L["RESOURCE_MAELSTROM_WEAPON"] .. "|r", defsByKey[ns.Constants.RESOURCEBAR_TYPE_MAELSTROM_WEAPON])
assert.are.equal("|A:classicon-warlock:14:14|a |cff" .. ns.Constants.CLASS_COLORS.WARLOCK .. ns.L["RESOURCE_SOUL_SHARDS"] .. "|r", defsByKey[Enum.PowerType.SoulShards])
assert.are.equal(
resourceLabel(demonHunterIcon, "DEMONHUNTER", "RESOURCE_SOUL_FRAGMENTS_DH"),
defsByKey[ns.Constants.RESOURCEBAR_TYPE_VENGEANCE_SOULS]
)
assert.are.equal(
resourceLabel(demonHunterIcon, "DEMONHUNTER", "RESOURCE_SOUL_FRAGMENTS_DEVOURER"),
defsByKey[ns.Constants.RESOURCEBAR_TYPE_DEVOURER_NORMAL]
)
assert.are.equal(
resourceLabel(emptyIcon .. " " .. classIcon("mage"), "MAGE", "RESOURCE_ICICLES"),
defsByKey[ns.Constants.RESOURCEBAR_TYPE_ICICLES]
)
assert.are.equal(
resourceLabel(emptyIcon .. " " .. classIcon("monk"), "MONK", "RESOURCE_CHI"),
defsByKey[Enum.PowerType.Chi]
)
assert.are.equal(
resourceLabel(classIcon("druid") .. " " .. classIcon("rogue"), "ROGUE", "RESOURCE_COMBO_POINTS"),
defsByKey[Enum.PowerType.ComboPoints]
)
assert.are.equal(
resourceLabel(emptyIcon .. " " .. classIcon("evoker"), "EVOKER", "RESOURCE_ESSENCE"),
defsByKey[Enum.PowerType.Essence]
)
assert.are.equal(
resourceLabel(emptyIcon .. " " .. classIcon("paladin"), "PALADIN", "RESOURCE_HOLY_POWER"),
defsByKey[Enum.PowerType.HolyPower]
)
assert.are.equal(
resourceLabel(emptyIcon .. " " .. classIcon("shaman"), "SHAMAN", "RESOURCE_MAELSTROM_WEAPON"),
defsByKey[ns.Constants.RESOURCEBAR_TYPE_MAELSTROM_WEAPON]
)
assert.are.equal(
resourceLabel(emptyIcon .. " " .. classIcon("warlock"), "WARLOCK", "RESOURCE_SOUL_SHARDS"),
defsByKey[Enum.PowerType.SoulShards]
)
assert.is_nil(defsByKey[Enum.PowerType.ArcaneCharges])
end)
end)
Expand Down Expand Up @@ -209,20 +246,21 @@ describe("ResourceBarOptions getters/setters/defaults", function()
end)
it("reuses the icon-prefixed names for capped resource rows", function()
local defsByKey = {}
local demonHunterIcon = emptyIcon .. " " .. classIcon("demonhunter")
for _, def in ipairs(assert(getPageRow("maxColors")).defs) do
defsByKey[def.key] = def.name
end

assert.are.equal(
"|A:classicon-demonhunter:14:14|a |cff" .. ns.Constants.CLASS_COLORS.DEMONHUNTER .. ns.L["RESOURCE_SOUL_FRAGMENTS_DEVOURER"] .. "|r",
resourceLabel(demonHunterIcon, "DEMONHUNTER", "RESOURCE_SOUL_FRAGMENTS_DEVOURER"),
defsByKey[ns.Constants.RESOURCEBAR_TYPE_DEVOURER_NORMAL]
)
assert.are.equal(
"|A:classicon-demonhunter:14:14|a |cff" .. ns.Constants.CLASS_COLORS.DEMONHUNTER .. ns.L["RESOURCE_VOID_FRAGMENTS_DEVOURER"] .. "|r",
resourceLabel(demonHunterIcon, "DEMONHUNTER", "RESOURCE_VOID_FRAGMENTS_DEVOURER"),
defsByKey[ns.Constants.RESOURCEBAR_TYPE_DEVOURER_META]
)
assert.are.equal(
"|A:classicon-mage:14:14|a |cff" .. ns.Constants.CLASS_COLORS.MAGE .. ns.L["RESOURCE_ICICLES"] .. "|r",
resourceLabel(emptyIcon .. " " .. classIcon("mage"), "MAGE", "RESOURCE_ICICLES"),
defsByKey[ns.Constants.RESOURCEBAR_TYPE_ICICLES]
)
end)
Expand Down
50 changes: 36 additions & 14 deletions UI/ResourceBarOptions.lua
Original file line number Diff line number Diff line change
Expand Up @@ -6,53 +6,75 @@ local _, ns = ...
local C = ns.Constants
local L = ns.L
local COLOR_WHITE_HEX = C.COLOR_WHITE_HEX or "FFFFFF"
local RESOURCE_ICON_SIZE = 14
local RESOURCE_ICON_SLOTS = 2
-- Transparent texture slot for right-aligning rows with fewer class icons.
local EMPTY_RESOURCE_ICON =
"|TInterface\\Common\\spacer:"
.. RESOURCE_ICON_SIZE
.. ":"
.. RESOURCE_ICON_SIZE
.. "|t"

local function createResourceColorName(className, label)
local color = (C.CLASS_COLORS and C.CLASS_COLORS[className]) or COLOR_WHITE_HEX
local icon = className and ("|A:classicon-" .. string.lower(className) .. ":14:14|a ") or ""
return icon .. "|cff" .. color .. label .. "|r"
local function createResourceColorName(colorClassName, label, iconClasses)
local color = (C.CLASS_COLORS and C.CLASS_COLORS[colorClassName]) or COLOR_WHITE_HEX
local icons = {}
local iconCount = math.min(#iconClasses, RESOURCE_ICON_SLOTS)
local padding = math.max(0, RESOURCE_ICON_SLOTS - iconCount)
local startIndex = math.max(1, #iconClasses - RESOURCE_ICON_SLOTS + 1)

for _ = 1, padding do
icons[#icons + 1] = EMPTY_RESOURCE_ICON
end

for i = startIndex, #iconClasses do
local iconClass = string.lower(iconClasses[i])
icons[#icons + 1] = "|A:classicon-" .. iconClass .. ":" .. RESOURCE_ICON_SIZE .. ":" .. RESOURCE_ICON_SIZE .. "|a"
end

return table.concat(icons, " ") .. " |cff" .. color .. label .. "|r"
end

local RESOURCE_COLOR_DEFS = {
{
key = C.RESOURCEBAR_TYPE_VENGEANCE_SOULS,
name = createResourceColorName("DEMONHUNTER", L["RESOURCE_SOUL_FRAGMENTS_DH"]),
name = createResourceColorName("DEMONHUNTER", L["RESOURCE_SOUL_FRAGMENTS_DH"], { "DEMONHUNTER" }),
},
{
key = C.RESOURCEBAR_TYPE_DEVOURER_NORMAL,
name = createResourceColorName("DEMONHUNTER", L["RESOURCE_SOUL_FRAGMENTS_DEVOURER"]),
name = createResourceColorName("DEMONHUNTER", L["RESOURCE_SOUL_FRAGMENTS_DEVOURER"], { "DEMONHUNTER" }),
},
{
key = C.RESOURCEBAR_TYPE_DEVOURER_META,
name = createResourceColorName("DEMONHUNTER", L["RESOURCE_VOID_FRAGMENTS_DEVOURER"]),
name = createResourceColorName("DEMONHUNTER", L["RESOURCE_VOID_FRAGMENTS_DEVOURER"], { "DEMONHUNTER" }),
},
{
key = C.RESOURCEBAR_TYPE_ICICLES,
name = createResourceColorName("MAGE", L["RESOURCE_ICICLES"]),
name = createResourceColorName("MAGE", L["RESOURCE_ICICLES"], { "MAGE" }),
},
{
key = Enum.PowerType.Chi,
name = createResourceColorName("MONK", L["RESOURCE_CHI"]),
name = createResourceColorName("MONK", L["RESOURCE_CHI"], { "MONK" }),
},
{
key = Enum.PowerType.ComboPoints,
name = createResourceColorName("ROGUE", L["RESOURCE_COMBO_POINTS"]),
name = createResourceColorName("ROGUE", L["RESOURCE_COMBO_POINTS"], { "DRUID", "ROGUE" }),
},
{
key = Enum.PowerType.Essence,
name = createResourceColorName("EVOKER", L["RESOURCE_ESSENCE"]),
name = createResourceColorName("EVOKER", L["RESOURCE_ESSENCE"], { "EVOKER" }),
},
{
key = Enum.PowerType.HolyPower,
name = createResourceColorName("PALADIN", L["RESOURCE_HOLY_POWER"]),
name = createResourceColorName("PALADIN", L["RESOURCE_HOLY_POWER"], { "PALADIN" }),
},
{
key = C.RESOURCEBAR_TYPE_MAELSTROM_WEAPON,
name = createResourceColorName("SHAMAN", L["RESOURCE_MAELSTROM_WEAPON"]),
name = createResourceColorName("SHAMAN", L["RESOURCE_MAELSTROM_WEAPON"], { "SHAMAN" }),
},
{
key = Enum.PowerType.SoulShards,
name = createResourceColorName("WARLOCK", L["RESOURCE_SOUL_SHARDS"]),
name = createResourceColorName("WARLOCK", L["RESOURCE_SOUL_SHARDS"], { "WARLOCK" }),
},
}

Expand Down