-- Modules/Tracker/TrackerOptions.lua local ADDON_NAME = "HailMaryGuildTools" local HMGT = LibStub("AceAddon-3.0"):GetAddon(ADDON_NAME) if not HMGT then return end if not HMGT_Config or not HMGT_Config.RegisterOptionsProvider then return end local L = LibStub("AceLocale-3.0"):GetLocale(ADDON_NAME) local LSM = LibStub("LibSharedMedia-3.0", true) local AceConfigRegistry = LibStub("AceConfigRegistry-3.0", true) local trackerOptionsGroup local trackedSpellsOptionsGroup local RefreshTrackerOptionArgs local RefreshTrackedSpellsOptionArgs local ANCHOR_POINT_VALUES = { TOPLEFT = "TOPLEFT", TOP = "TOP", TOPRIGHT = "TOPRIGHT", LEFT = "LEFT", CENTER = "CENTER", RIGHT = "RIGHT", BOTTOMLEFT = "BOTTOMLEFT", BOTTOM = "BOTTOM", BOTTOMRIGHT = "BOTTOMRIGHT", } local FONT_OUTLINE_VALUES = { [""] = L["OPT_OUTLINE_NONE"] or "None", OUTLINE = L["OPT_OUTLINE_NORMAL"] or "Outline", THICKOUTLINE = L["OPT_OUTLINE_THICK"] or "Thick Outline", MONOCHROME = L["OPT_OUTLINE_MONO"] or "Monochrome", } local ROLE_FILTER_VALUES = { ALL = L["OPT_ROLE_FILTER_ALL"] or "All", TANK = L["OPT_ROLE_FILTER_TANK"] or "Tank", HEALER = L["OPT_ROLE_FILTER_HEALER"] or "Healer", DAMAGER = L["OPT_ROLE_FILTER_DAMAGER"] or "Damage", } local function NotifyOptionsChanged() if AceConfigRegistry and type(AceConfigRegistry.NotifyChange) == "function" then AceConfigRegistry:NotifyChange(ADDON_NAME) end end local function TriggerTrackerUpdate(rebuildArgs) if rebuildArgs then if RefreshTrackerOptionArgs then RefreshTrackerOptionArgs() end if RefreshTrackedSpellsOptionArgs then RefreshTrackedSpellsOptionArgs() end end HMGT:TriggerTrackerUpdate() NotifyOptionsChanged() end local function RefreshAnchors(rebuildArgs) if rebuildArgs and RefreshTrackerOptionArgs then RefreshTrackerOptionArgs() end HMGT:RefreshFrameAnchors(true) NotifyOptionsChanged() end local function CopyGroup(group, overrides) if type(group) ~= "table" then return nil end local out = {} for key, value in pairs(group) do out[key] = value end for key, value in pairs(overrides or {}) do out[key] = value end return out end local function GetSortedTrackers() local trackers = {} for _, tracker in ipairs(HMGT:GetTrackerConfigs()) do trackers[#trackers + 1] = tracker end table.sort(trackers, function(a, b) local aId = tonumber(a and a.id) or 0 local bId = tonumber(b and b.id) or 0 if aId ~= bId then return aId < bId end return tostring(a and a.name or "") < tostring(b and b.name or "") end) return trackers end local function GetTrackerSettings(trackerId) return HMGT:GetTrackerConfigById(trackerId) end local function GetTrackerLabel(tracker) if type(tracker) ~= "table" then return L["OPT_TRACKER"] or "Tracker" end local name = tostring(tracker.name or ""):gsub("^%s+", ""):gsub("%s+$", "") if name ~= "" then return name end return string.format("%s %d", L["OPT_TRACKER"] or "Tracker", tonumber(tracker.id) or 0) end local function GetTrackerCategoryValues() if HMGT_SpellData and type(HMGT_SpellData.GetTrackerCategoryValues) == "function" then return HMGT_SpellData.GetTrackerCategoryValues() end return { interrupt = L["CAT_interrupt"] or "Interrupts", defensive = L["CAT_defensive"] or "Defensive Cooldowns", offensive = L["CAT_offensive"] or "Offensive Cooldowns", cc = L["CAT_cc"] or "Crowd Control", raid = L["CAT_raid"] or "Raid Cooldowns", } end local function GetCategoryLabel(category) return GetTrackerCategoryValues()[tostring(category or "")] or tostring(category or "utility") end local function GetTrackerCategoriesSummary(tracker) local labels = {} for _, category in ipairs((tracker and tracker.categories) or {}) do labels[#labels + 1] = GetCategoryLabel(category) end if #labels == 0 then return "No categories selected." end return table.concat(labels, ", ") end local function IsPartyAttachMode(tracker) return type(tracker) == "table" and tracker.trackerType == "group" and tracker.attachToPartyFrame == true end local function IsGroupTracker(tracker) return type(tracker) == "table" and tracker.trackerType == "group" end local TRACKER_TYPE_VALUES = { normal = L["OPT_TRACKER_TYPE_NORMAL"] or "Normal tracker", group = L["OPT_TRACKER_TYPE_GROUP"] or "Group-based tracker", } local function GetTrackerVisibilitySummary(tracker) local parts = {} if tracker.showInSolo ~= false then parts[#parts + 1] = L["OPT_SHOW_SOLO"] or "Solo" end if tracker.showInGroup ~= false then parts[#parts + 1] = L["OPT_SHOW_GROUP"] or "Group" end if tracker.showInRaid ~= false then parts[#parts + 1] = L["OPT_SHOW_RAID"] or "Raid" end if #parts == 0 then return L["OPT_VISIBILITY_NONE"] or "Hidden everywhere" end return table.concat(parts, ", ") end local function GetTrackerSummaryText(tracker) if type(tracker) ~= "table" then return L["OPT_TRACKERS_EMPTY"] or "No tracker selected." end local modeLabel if tracker.testMode then modeLabel = L["OPT_TEST_MODE"] or "Test mode" elseif tracker.demoMode then modeLabel = L["OPT_DEMO_MODE"] or "Demo mode" elseif tracker.enabled ~= false then modeLabel = L["OPT_ENABLED"] or "Enabled" else modeLabel = L["OPT_DISABLED"] or "Disabled" end local display = tracker.showBar and (L["OPT_DISPLAY_BAR"] or "Progress bars") or (L["OPT_DISPLAY_ICON"] or "Icons") return table.concat({ string.format("|cffffd100%s|r: %s", L["OPT_TRACKER_TYPE"] or "Tracker type", TRACKER_TYPE_VALUES[tracker.trackerType or "normal"] or (L["OPT_TRACKER_TYPE_NORMAL"] or "Normal tracker")), string.format("|cffffd100%s|r: %s", L["OPT_TRACKER_CATEGORIES"] or "Categories", GetTrackerCategoriesSummary(tracker)), string.format("|cffffd100%s|r: %s", L["OPT_STATUS_MODE"] or "Mode", modeLabel), string.format("|cffffd100%s|r: %s", L["OPT_STATUS_DISPLAY"] or "Display", display), string.format("|cffffd100%s|r: %s", L["OPT_STATUS_VISIBILITY"] or "Visibility", GetTrackerVisibilitySummary(tracker)), }, "\n") end local function NormalizeSearchText(value) local text = tostring(value or ""):lower() text = text:gsub("^%s+", "") text = text:gsub("%s+$", "") return text end local function LocalizedClassName(classToken) return (LOCALIZED_CLASS_NAMES_MALE and LOCALIZED_CLASS_NAMES_MALE[classToken]) or (LOCALIZED_CLASS_NAMES_FEMALE and LOCALIZED_CLASS_NAMES_FEMALE[classToken]) or classToken end local function GetSpellNameById(spellId) local sid = tonumber(spellId) if not sid or sid <= 0 then return nil end if C_Spell and type(C_Spell.GetSpellName) == "function" then local ok, name = pcall(C_Spell.GetSpellName, sid) if ok and type(name) == "string" and name ~= "" then return name end end if type(GetSpellInfo) == "function" then local name = GetSpellInfo(sid) if type(name) == "string" and name ~= "" then return name end end return nil end local function GetSpellIconPath(spellId) local icon = HMGT_SpellData and HMGT_SpellData.GetSpellIcon and HMGT_SpellData.GetSpellIcon(spellId) if not icon or icon == "" then icon = "Interface\\Icons\\INV_Misc_QuestionMark" end return icon end local function SpellLabel(entry) local spellId = tonumber(entry and entry.spellId) or 0 return tostring(entry and entry.name or GetSpellNameById(spellId) or ("Spell " .. spellId)) end local function SpellDesc(entry) local cooldown = tonumber(HMGT_SpellData and HMGT_SpellData.GetBaseCooldown and HMGT_SpellData.GetBaseCooldown(entry)) or tonumber(entry and entry.cooldown) or 0 local specs = {} if type(entry and entry.specs) == "table" then for _, spec in ipairs(entry.specs) do specs[#specs + 1] = tostring(spec) end end return table.concat({ string.format("SpellID: %d", tonumber(entry and entry.spellId) or 0), string.format("%s: %s", L["OPT_TRACKER_CATEGORIES"] or "Categories", GetCategoryLabel(entry and entry.category or "utility")), string.format("CD: %ss", cooldown), #specs > 0 and ("Specs: " .. table.concat(specs, ", ")) or "Specs: All", }, " | ") end local function GetOrderedValues(preferredOrder, availableMap) local ordered = {} local seen = {} for _, key in ipairs(preferredOrder or {}) do if availableMap[key] then ordered[#ordered + 1] = key seen[key] = true end end local extras = {} for key in pairs(availableMap or {}) do if not seen[key] then extras[#extras + 1] = key end end table.sort(extras) for _, key in ipairs(extras) do ordered[#ordered + 1] = key end return ordered end local function GetSpellFilterState(filterKey) HMGT._genericTrackerSpellFilters = HMGT._genericTrackerSpellFilters or {} local key = tostring(filterKey or "default") HMGT._genericTrackerSpellFilters[key] = HMGT._genericTrackerSpellFilters[key] or { search = "", enabledOnly = false, } return HMGT._genericTrackerSpellFilters[key] end local function BuildSpellBrowserArgs(config) config = type(config) == "table" and config or {} local filterKey = tostring(config.filterKey or "default") local emptyText = tostring(config.emptyText or (L["OPT_TRACKERS_EMPTY"] or "No tracked spells available.")) local spellDescription = config.spellDescription or SpellDesc local applyChanges = config.applyChanges or function() TriggerTrackerUpdate(false) end local pool = {} if type(config.poolProvider) == "function" then pool = config.poolProvider() or {} elseif type(config.pool) == "table" then pool = config.pool end if type(config.isSpellEnabled) ~= "function" or type(config.setSpellEnabled) ~= "function" then return { missing = { type = "description", order = 1, width = "full", name = emptyText, }, } end local browserClassSpells = {} local entryBySpellId = {} for _, entry in ipairs(pool) do for _, classToken in ipairs(entry.classes or {}) do browserClassSpells[classToken] = browserClassSpells[classToken] or {} local category = tostring(entry.category or "utility") browserClassSpells[classToken][category] = browserClassSpells[classToken][category] or {} local duplicate = false for _, existing in ipairs(browserClassSpells[classToken][category]) do if existing.spellId == entry.spellId then duplicate = true break end end if not duplicate then browserClassSpells[classToken][category][#browserClassSpells[classToken][category] + 1] = entry end end local spellId = tonumber(entry and entry.spellId) if spellId and spellId > 0 then entryBySpellId[spellId] = entry end end local classOrder = GetOrderedValues(HMGT_SpellData and HMGT_SpellData.ClassOrder or {}, browserClassSpells) if #classOrder == 0 then return { missing = { type = "description", order = 1, width = "full", name = emptyText, }, } end local function IsEntryVisible(entry, classToken, category) local spellId = tonumber(entry and entry.spellId) if not spellId or spellId <= 0 then return false end local filterState = GetSpellFilterState(filterKey) if filterState.enabledOnly and not config.isSpellEnabled(spellId, entry) then return false end local search = NormalizeSearchText(filterState.search) if search == "" then return true end local haystack = table.concat({ tostring(entry.name or GetSpellNameById(spellId) or ""), tostring(spellId), tostring(LocalizedClassName(classToken)), tostring(GetCategoryLabel(category)), }, " "):lower() return haystack:find(search, 1, true) ~= nil end local function CollectVisibleSpellIds(classToken, category) local ids = {} local categories = {} if category then categories[1] = category else categories = GetOrderedValues(HMGT_SpellData and HMGT_SpellData.CategoryOrder or {}, browserClassSpells[classToken] or {}) end for _, currentCategory in ipairs(categories) do for _, entry in ipairs((browserClassSpells[classToken] and browserClassSpells[classToken][currentCategory]) or {}) do if IsEntryVisible(entry, classToken, currentCategory) then ids[#ids + 1] = tonumber(entry.spellId) end end end return ids end local function CountVisible(classToken, category) local total = 0 local enabled = 0 for _, spellId in ipairs(CollectVisibleSpellIds(classToken, category)) do total = total + 1 if config.isSpellEnabled(spellId, entryBySpellId[spellId]) then enabled = enabled + 1 end end return total, enabled end local function CountAllVisible() local total = 0 local enabled = 0 for _, classToken in ipairs(classOrder) do local classTotal, classEnabled = CountVisible(classToken) total = total + classTotal enabled = enabled + classEnabled end return total, enabled end local function SetSpellIdsEnabled(spellIds, value) for _, spellId in ipairs(spellIds) do config.setSpellEnabled(spellId, value, entryBySpellId[spellId]) end applyChanges() end local browserArgs = { header = { type = "header", order = 1, name = L["OPT_SPELL_BROWSER"] or "Spell Browser", }, info = { type = "description", order = 2, width = "full", name = function() local total, enabled = CountAllVisible() if type(config.infoText) == "function" then return config.infoText(total, enabled) end return string.format( "%s\n\n|cffffd100%s|r: %d\n|cffffd100%s|r: %d", L["OPT_SPELL_BROWSER_DESC"] or "Filter tracked spells by name or Spell ID and apply quick actions to the visible results.", L["OPT_SPELLS_VISIBLE"] or "Visible spells", total, L["OPT_SPELLS_ENABLED_COUNT"] or "Enabled", enabled ) end, }, search = { type = "input", order = 3, width = "full", name = L["OPT_FILTER_SEARCH"] or "Search", desc = L["OPT_FILTER_SEARCH_DESC"] or "Search by spell name or Spell ID", get = function() return GetSpellFilterState(filterKey).search or "" end, set = function(_, val) GetSpellFilterState(filterKey).search = val or "" NotifyOptionsChanged() end, }, enabledOnly = { type = "toggle", order = 4, width = "full", name = L["OPT_FILTER_ENABLED_ONLY"] or "Show enabled spells only", get = function() return GetSpellFilterState(filterKey).enabledOnly == true end, set = function(_, val) GetSpellFilterState(filterKey).enabledOnly = val and true or false NotifyOptionsChanged() end, }, selectVisible = { type = "execute", order = 5, width = "full", name = L["OPT_SELECT_VISIBLE"] or "Select visible", func = function() local spellIds = {} for _, classToken in ipairs(classOrder) do for _, spellId in ipairs(CollectVisibleSpellIds(classToken)) do spellIds[#spellIds + 1] = spellId end end SetSpellIdsEnabled(spellIds, true) end, }, deselectVisible = { type = "execute", order = 6, width = "full", name = L["OPT_DESELECT_VISIBLE"] or "Deselect visible", func = function() local spellIds = {} for _, classToken in ipairs(classOrder) do for _, spellId in ipairs(CollectVisibleSpellIds(classToken)) do spellIds[#spellIds + 1] = spellId end end SetSpellIdsEnabled(spellIds, false) end, }, resetFilters = { type = "execute", order = 7, width = "full", name = L["OPT_FILTER_RESET"] or "Reset filters", func = function() local filterState = GetSpellFilterState(filterKey) filterState.search = "" filterState.enabledOnly = false NotifyOptionsChanged() end, }, } local function BuildClassArgs(classToken) local categories = browserClassSpells[classToken] or {} local categoryOrder = GetOrderedValues(HMGT_SpellData and HMGT_SpellData.CategoryOrder or {}, categories) local classArgs = { browserHeader = { type = "header", order = 1, name = L["OPT_SPELL_BROWSER"] or "Spell Browser", }, search = { type = "input", order = 2, width = "full", name = L["OPT_FILTER_SEARCH"] or "Search", desc = L["OPT_FILTER_SEARCH_DESC"] or "Search by spell name or Spell ID", get = function() return GetSpellFilterState(filterKey).search or "" end, set = function(_, val) GetSpellFilterState(filterKey).search = val or "" NotifyOptionsChanged() end, }, enabledOnly = { type = "toggle", order = 3, width = "full", name = L["OPT_FILTER_ENABLED_ONLY"] or "Show enabled spells only", get = function() return GetSpellFilterState(filterKey).enabledOnly == true end, set = function(_, val) GetSpellFilterState(filterKey).enabledOnly = val and true or false NotifyOptionsChanged() end, }, resetFilters = { type = "execute", order = 4, width = "full", name = L["OPT_FILTER_RESET"] or "Reset filters", func = function() local filterState = GetSpellFilterState(filterKey) filterState.search = "" filterState.enabledOnly = false NotifyOptionsChanged() end, }, selectionHeader = { type = "header", order = 5, name = L["OPT_SELECTION"] or "Selection", }, summary = { type = "description", order = 6, width = "full", name = function() local total, enabled = CountVisible(classToken) return string.format( "|cffffd100%s|r: %d |cffffd100%s|r: %d", L["OPT_SPELLS_VISIBLE"] or "Visible spells", total, L["OPT_SPELLS_ENABLED_COUNT"] or "Enabled", enabled ) end, }, selectVisible = { type = "execute", order = 7, width = "full", name = L["OPT_SELECT_VISIBLE"] or "Select visible", func = function() SetSpellIdsEnabled(CollectVisibleSpellIds(classToken), true) end, }, deselectVisible = { type = "execute", order = 8, width = "full", name = L["OPT_DESELECT_VISIBLE"] or "Deselect visible", func = function() SetSpellIdsEnabled(CollectVisibleSpellIds(classToken), false) end, }, } local categoryOrderIndex = 10 for _, category in ipairs(categoryOrder) do if categories[category] then local categoryArgs = { summary = { type = "description", order = 1, width = "full", name = function() local total, enabled = CountVisible(classToken, category) return string.format( "|cffffd100%s|r: %d |cffffd100%s|r: %d", L["OPT_SPELLS_VISIBLE"] or "Visible spells", total, L["OPT_SPELLS_ENABLED_COUNT"] or "Enabled", enabled ) end, }, } local spellOrder = 2 for _, entry in ipairs(categories[category]) do local spellId = tonumber(entry.spellId) local spellIcon = GetSpellIconPath(spellId) categoryArgs["spell_" .. tostring(spellId)] = { type = "toggle", order = spellOrder, name = SpellLabel(entry), image = spellIcon, imageCoords = { 0.07, 0.93, 0.07, 0.93 }, desc = spellDescription(entry), hidden = function() return not IsEntryVisible(entry, classToken, category) end, get = function() return config.isSpellEnabled(spellId, entry) end, set = function(_, val) config.setSpellEnabled(spellId, val, entry) applyChanges() end, } spellOrder = spellOrder + 1 end classArgs["cat_" .. category] = { type = "group", order = categoryOrderIndex, inline = true, name = " ", hidden = function() local total = CountVisible(classToken, category) return total == 0 end, args = { header = { type = "header", order = 1, name = GetCategoryLabel(category), }, }, } for key, value in pairs(categoryArgs) do classArgs["cat_" .. category].args[key] = value end categoryOrderIndex = categoryOrderIndex + 1 end end return classArgs end for index, classToken in ipairs(classOrder) do browserArgs["class_" .. classToken] = { type = "group", order = 10 + index, hidden = function() return CountVisible(classToken) == 0 end, name = function() local total, enabled = CountVisible(classToken) return string.format("%s (%d/%d)", LocalizedClassName(classToken), enabled, total) end, args = BuildClassArgs(classToken), } end return browserArgs end local function GetAllTrackedSpellPool() local categoryMap = {} for _, tracker in ipairs(GetSortedTrackers()) do for _, category in ipairs(tracker.categories or {}) do categoryMap[category] = true end end local orderedCategories = GetOrderedValues(HMGT_SpellData and HMGT_SpellData.CategoryOrder or {}, categoryMap) if HMGT_SpellData and type(HMGT_SpellData.GetSpellPoolForCategories) == "function" then return HMGT_SpellData.GetSpellPoolForCategories(orderedCategories) end return {} end local function EntryMatchesTracker(entry, tracker) if not entry or not tracker then return false end if HMGT_SpellData and type(HMGT_SpellData.EntryMatchesCategories) == "function" then return HMGT_SpellData.EntryMatchesCategories(entry, tracker.categories) end local trackerCategories = {} for _, category in ipairs(tracker.categories or {}) do trackerCategories[tostring(category)] = true end if trackerCategories[tostring(entry.category or "utility")] then return true end for _, tag in ipairs(entry.trackerTags or {}) do if trackerCategories[tostring(tag)] then return true end end return false end local function GetApplicableTrackersForSpell(entry) local trackers = {} for _, tracker in ipairs(GetSortedTrackers()) do if EntryMatchesTracker(entry, tracker) then trackers[#trackers + 1] = tracker end end return trackers end local function GetApplicableTrackerLabels(entry) local labels = {} for _, tracker in ipairs(GetApplicableTrackersForSpell(entry)) do labels[#labels + 1] = GetTrackerLabel(tracker) end return labels end local function IsGlobalSpellEnabled(spellId, entry) local trackers = GetApplicableTrackersForSpell(entry) if #trackers == 0 then return false end for _, tracker in ipairs(trackers) do if tracker.enabledSpells[spellId] == false then return false end end return true end local function SetGlobalSpellEnabled(spellId, value, entry) for _, tracker in ipairs(GetApplicableTrackersForSpell(entry)) do tracker.enabledSpells[spellId] = value end end local function BuildGlobalSpellDescription(entry) local base = SpellDesc(entry) local labels = GetApplicableTrackerLabels(entry) if #labels == 0 then return base end return string.format("%s | %s: %s", base, L["OPT_TRACKERS"] or "Tracker Bars", table.concat(labels, ", ")) end local function BuildGlobalSpellBrowserArgs() return BuildSpellBrowserArgs({ filterKey = "global-tracked-spells", emptyText = L["OPT_TRACKED_SPELLS_EMPTY"] or "No spells are currently tracked by your tracker bars.", poolProvider = GetAllTrackedSpellPool, isSpellEnabled = IsGlobalSpellEnabled, setSpellEnabled = SetGlobalSpellEnabled, spellDescription = BuildGlobalSpellDescription, infoText = function(total, enabled) return string.format( "%s\n\n%s\n\n|cffffd100%s|r: %d\n|cffffd100%s|r: %d", L["OPT_SPELL_BROWSER_DESC"] or "Filter tracked spells by name or Spell ID and apply quick actions to the visible results.", L["OPT_TRACKED_SPELLS_DESC"] or "Changes here apply to all tracker bars that use the spell's categories.", L["OPT_SPELLS_VISIBLE"] or "Visible spells", total, L["OPT_SPELLS_ENABLED_COUNT"] or "Enabled", enabled ) end, applyChanges = function() TriggerTrackerUpdate(false) end, }) end local function BuildTrackerOverviewArgs() return { description = { type = "description", order = 1, width = "full", name = function() local trackers = GetSortedTrackers() local names = {} for _, tracker in ipairs(trackers) do names[#names + 1] = GetTrackerLabel(tracker) end local body = L["OPT_TRACKERS_DESC"] or "Create tracker bars and bind them to one or more spell categories." if #names == 0 then return string.format("%s\n\n%s", body, L["OPT_TRACKERS_EMPTY"] or "No tracker bars configured yet.") end return string.format("%s\n\n%s (%d): %s", body, L["OPT_TRACKERS"] or "Tracker Bars", #trackers, table.concat(names, ", ")) end, }, addTracker = { type = "execute", order = 2, width = "full", name = L["OPT_ADD_TRACKER"] or "Add tracker", func = function() local nextId = HMGT:GetNextTrackerId() local tracker = HMGT:CreateTrackerConfig(nextId, { name = string.format("%s %d", L["OPT_TRACKER"] or "Tracker", nextId), }) HMGT.db.profile.trackers = HMGT.db.profile.trackers or {} HMGT.db.profile.trackers[#HMGT.db.profile.trackers + 1] = tracker TriggerTrackerUpdate(true) end, }, } end local function BuildTrackerGroup(trackerId, order) local function s() return GetTrackerSettings(trackerId) end local function SetCategoryEnabled(category, enabled) local tracker = s() if not tracker then return end local selected = {} for _, existing in ipairs(tracker.categories or {}) do selected[existing] = true end if enabled then selected[category] = true else local count = 0 for _ in pairs(selected) do count = count + 1 end if count <= 1 then return end selected[category] = nil end local ordered = {} local seen = {} for _, key in ipairs(HMGT_SpellData and HMGT_SpellData.CategoryOrder or {}) do if selected[key] then ordered[#ordered + 1] = key seen[key] = true end end local extras = {} for key in pairs(selected) do if not seen[key] then extras[#extras + 1] = key end end table.sort(extras) for _, key in ipairs(extras) do ordered[#ordered + 1] = key end tracker.categories = ordered TriggerTrackerUpdate(true) end local function RemoveTracker() local profileTrackers = HMGT.db.profile.trackers or {} local removedAnchorKey = HMGT:GetTrackerAnchorKey(trackerId) for index = #profileTrackers, 1, -1 do if tonumber(profileTrackers[index].id) == tonumber(trackerId) then table.remove(profileTrackers, index) break end end for _, tracker in ipairs(profileTrackers) do if tracker.anchorTo == removedAnchorKey then tracker.anchorTo = "UIParent" tracker.anchorCustom = "" end end HMGT:RefreshFrameAnchors(true) TriggerTrackerUpdate(true) end local function GetRemoveTrackerConfirmationText() local tracker = s() local label = tracker and GetTrackerLabel(tracker) or (L["OPT_TRACKER"] or "Tracker") return string.format( L["OPT_REMOVE_TRACKER_CONFIRM"] or 'Really remove tracker "%s"?', tostring(label) ) end return { type = "group", order = order, childGroups = "tab", name = function() local tracker = s() return tracker and GetTrackerLabel(tracker) or (L["OPT_TRACKER"] or "Tracker") end, args = { header = { type = "header", order = 1, name = function() local tracker = s() return tracker and GetTrackerLabel(tracker) or (L["OPT_TRACKER"] or "Tracker") end, }, summaryGroup = { type = "group", order = 2, inline = true, name = " ", args = { summary = { type = "description", order = 1, width = "full", name = function() local tracker = s() return tracker and GetTrackerSummaryText(tracker) or (L["OPT_TRACKERS_EMPTY"] or "No tracker selected.") end, }, }, }, removeTracker = { type = "execute", order = 3, width = "full", name = L["OPT_REMOVE_TRACKER"] or "Remove tracker", confirm = GetRemoveTrackerConfirmationText, func = RemoveTracker, }, mode = { type = "group", order = 10, name = L["OPT_UI_GROUP_MODE"] or "Mode", args = { name = { type = "input", order = 1, width = "full", name = L["OPT_TRACKER_NAME"] or "Tracker name", get = function() local tracker = s() return tracker and tracker.name or "" end, set = function(_, val) local tracker = s() if tracker then local trimmed = tostring(val or ""):gsub("^%s+", ""):gsub("%s+$", "") if trimmed == "" then trimmed = string.format("%s %d", L["OPT_TRACKER"] or "Tracker", tonumber(tracker.id) or 0) end tracker.name = trimmed NotifyOptionsChanged() end end, }, trackerType = { type = "select", order = 2, width = "full", name = L["OPT_TRACKER_TYPE"] or "Tracker type", desc = L["OPT_TRACKER_TYPE_DESC"] or "Choose whether this tracker uses one shared frame or separate frames per group member.", values = TRACKER_TYPE_VALUES, get = function() local tracker = s() return (tracker and tracker.trackerType) or "normal" end, set = function(_, val) local tracker = s() if tracker then tracker.trackerType = (val == "group") and "group" or "normal" tracker.perGroupMember = tracker.trackerType == "group" if tracker.trackerType ~= "group" then tracker.attachToPartyFrame = false end NotifyOptionsChanged() RefreshAnchors(true) TriggerTrackerUpdate(false) end end, }, showBar = { type = "select", order = 2.5, width = "full", name = L["OPT_DISPLAY_MODE"] or "Display mode", desc = L["OPT_DISPLAY_MODE_DESC"] or "Show as progress bars or icons", values = { bar = L["OPT_DISPLAY_BAR"] or "Progress bars", icon = L["OPT_DISPLAY_ICON"] or "Icons", }, get = function() local tracker = s() return (tracker and tracker.showBar) and "bar" or "icon" end, set = function(_, val) local tracker = s() if tracker then tracker.showBar = (val == "bar") if tracker.showBar and (tracker.growDirection == "LEFT" or tracker.growDirection == "RIGHT") then tracker.growDirection = "DOWN" end TriggerTrackerUpdate(false) end end, }, enabled = { type = "toggle", order = 3, width = "half", name = L["OPT_ENABLED"] or "Enabled", desc = L["OPT_ENABLED_DESC"] or "Enable or disable this tracker", get = function() local tracker = s() return tracker and tracker.enabled ~= false end, set = function(_, val) local tracker = s() if tracker then tracker.enabled = val and true or false TriggerTrackerUpdate(false) end end, }, includeSelfFrame = { type = "toggle", order = 4, width = "full", name = L["OPT_INCLUDE_SELF_FRAME"] or "Create a frame for your own player too", desc = L["OPT_INCLUDE_SELF_FRAME_DESC"] or "Also provision a per-member tracker frame for your own player.", hidden = function() local tracker = s() return not tracker or not IsGroupTracker(tracker) end, get = function() local tracker = s() return tracker and tracker.includeSelfFrame == true end, set = function(_, val) local tracker = s() if tracker then tracker.includeSelfFrame = val and true or false NotifyOptionsChanged() RefreshAnchors(true) TriggerTrackerUpdate(false) end end, }, categories = { type = "multiselect", order = 5, width = "full", name = L["OPT_TRACKER_CATEGORIES"] or "Categories", desc = L["OPT_TRACKER_CATEGORIES_DESC"] or "Select which spell categories this tracker should display.", values = function() return GetTrackerCategoryValues() end, get = function(_, key) local tracker = s() if not tracker then return false end for _, category in ipairs(tracker.categories or {}) do if category == key then return true end end return false end, set = function(_, key, val) SetCategoryEnabled(key, val) end, }, }, }, visibility = { type = "group", order = 20, name = L["OPT_UI_GROUP_VISIBILITY"] or "Visibility", args = { showInSolo = { type = "toggle", order = 1, name = L["OPT_SHOW_SOLO"] or "Show when solo", desc = L["OPT_SHOW_SOLO_DESC"] or "Show this tracker when not in a group", get = function() local tracker = s() return tracker and tracker.showInSolo ~= false end, set = function(_, val) local tracker = s() if tracker then tracker.showInSolo = val and true or false TriggerTrackerUpdate(false) end end, }, showInGroup = { type = "toggle", order = 2, name = L["OPT_SHOW_GROUP"] or "Show in group", desc = L["OPT_SHOW_GROUP_DESC"] or "Show this tracker in a party", get = function() local tracker = s() return tracker and tracker.showInGroup ~= false end, set = function(_, val) local tracker = s() if tracker then tracker.showInGroup = val and true or false TriggerTrackerUpdate(false) end end, }, showInRaid = { type = "toggle", order = 3, name = L["OPT_SHOW_RAID"] or "Show in raid", desc = L["OPT_SHOW_RAID_DESC"] or "Show this tracker in a raid group", get = function() local tracker = s() return tracker and tracker.showInRaid ~= false end, set = function(_, val) local tracker = s() if tracker then tracker.showInRaid = val and true or false TriggerTrackerUpdate(false) end end, }, showOnlyReady = { type = "toggle", order = 4, width = "full", name = L["OPT_SHOW_ONLY_READY"] or "Show only ready cooldowns", desc = L["OPT_SHOW_ONLY_READY_DESC"] or "Only show entries that are currently ready", get = function() local tracker = s() return tracker and tracker.showOnlyReady == true end, set = function(_, val) local tracker = s() if tracker then tracker.showOnlyReady = val and true or false TriggerTrackerUpdate(false) end end, }, readySoonSec = { type = "range", order = 5, min = 0, max = 300, step = 1, width = "full", name = L["OPT_READY_SOON_SEC"] or "Ready soon threshold (sec)", desc = L["OPT_READY_SOON_SEC_DESC"] or "Show only cooldowns that are ready or below this remaining time (0 = disabled)", get = function() local tracker = s() return tracker and tracker.readySoonSec or 0 end, set = function(_, val) local tracker = s() if tracker then tracker.readySoonSec = val TriggerTrackerUpdate(false) end end, }, roleFilter = { type = "select", order = 6, name = L["OPT_ROLE_FILTER"] or "Role filter", values = ROLE_FILTER_VALUES, get = function() local tracker = s() return tracker and tracker.roleFilter or "ALL" end, set = function(_, val) local tracker = s() if tracker then tracker.roleFilter = val TriggerTrackerUpdate(false) end end, }, rangeCheck = { type = "toggle", order = 7, name = L["OPT_RANGE_CHECK"] or "Range check", get = function() local tracker = s() return tracker and tracker.rangeCheck == true end, set = function(_, val) local tracker = s() if tracker then tracker.rangeCheck = val and true or false TriggerTrackerUpdate(false) end end, }, hideOutOfRange = { type = "toggle", order = 8, name = L["OPT_HIDE_OOR"] or "Hide out of range", disabled = function() local tracker = s() return not tracker or tracker.rangeCheck ~= true end, get = function() local tracker = s() return tracker and tracker.hideOutOfRange == true end, set = function(_, val) local tracker = s() if tracker then tracker.hideOutOfRange = val and true or false TriggerTrackerUpdate(false) end end, }, outOfRangeAlpha = { type = "range", order = 9, min = 0.1, max = 1, step = 0.05, name = L["OPT_OOR_ALPHA"] or "Out of range alpha", disabled = function() local tracker = s() return not tracker or tracker.rangeCheck ~= true end, get = function() local tracker = s() return tracker and tracker.outOfRangeAlpha or 0.4 end, set = function(_, val) local tracker = s() if tracker then tracker.outOfRangeAlpha = val TriggerTrackerUpdate(false) end end, }, }, }, placement = { type = "group", order = 30, name = L["OPT_UI_GROUP_PLACEMENT"] or "Placement", args = { locked = { type = "toggle", order = 1, name = L["OPT_LOCKED"] or "Lock frame", desc = L["OPT_LOCKED_DESC"] or "Prevent the frame from being moved", get = function() local tracker = s() return tracker and tracker.locked == true end, set = function(_, val) local tracker = s() if tracker then tracker.locked = val and true or false TriggerTrackerUpdate(false) end end, }, growDirection = { type = "select", order = 2, name = L["OPT_GROW_DIR"] or "Grow direction", values = function() local tracker = s() if tracker and tracker.showBar then return { DOWN = L["OPT_GROW_DOWN"] or "Downward", UP = L["OPT_GROW_UP"] or "Upward", } end return { DOWN = L["OPT_GROW_DOWN"] or "Downward", UP = L["OPT_GROW_UP"] or "Upward", LEFT = L["OPT_GROW_LEFT"] or "Leftward", RIGHT = L["OPT_GROW_RIGHT"] or "Rightward", } end, get = function() local tracker = s() if not tracker then return "DOWN" end local dir = tracker.growDirection or "DOWN" if tracker.showBar and (dir == "LEFT" or dir == "RIGHT") then return "DOWN" end return dir end, set = function(_, val) local tracker = s() if tracker then tracker.growDirection = val TriggerTrackerUpdate(false) end end, }, attachToPartyFrame = { type = "toggle", order = 3, width = "full", name = L["OPT_ATTACH_PARTY_FRAME"] or "Attach to Party Frame", desc = L["OPT_ATTACH_PARTY_FRAME_DESC"] or "Anchor each group cooldown frame to its corresponding party unit frame", hidden = function() local tracker = s() return not tracker or not IsGroupTracker(tracker) end, get = function() local tracker = s() return tracker and tracker.attachToPartyFrame == true end, set = function(_, val) local tracker = s() if tracker then tracker.attachToPartyFrame = val and true or false NotifyOptionsChanged() RefreshAnchors(true) TriggerTrackerUpdate(false) end end, }, partyAttachSide = { type = "select", order = 4, name = L["OPT_ATTACH_PARTY_SIDE"] or "Attach side", values = { LEFT = L["OPT_ATTACH_LEFT"] or "Left", RIGHT = L["OPT_ATTACH_RIGHT"] or "Right", }, hidden = function() local tracker = s() return not tracker or not IsGroupTracker(tracker) or tracker.attachToPartyFrame ~= true end, get = function() local tracker = s() return tracker and tracker.partyAttachSide or "RIGHT" end, set = function(_, val) local tracker = s() if tracker then tracker.partyAttachSide = val RefreshAnchors(true) end end, }, partyAttachOffsetX = { type = "range", order = 5, min = -200, max = 200, step = 1, name = function() local tracker = s() return string.format("%s: %s", L["OPT_ATTACH_PARTY_OFFSET_X"] or "Attach X offset", tostring(tracker and tracker.partyAttachOffsetX or 8)) end, hidden = function() local tracker = s() return not tracker or not IsGroupTracker(tracker) or tracker.attachToPartyFrame ~= true end, get = function() local tracker = s() return tracker and tracker.partyAttachOffsetX or 8 end, set = function(_, val) local tracker = s() if tracker then tracker.partyAttachOffsetX = val RefreshAnchors(true) end end, }, partyAttachOffsetY = { type = "range", order = 6, min = -200, max = 200, step = 1, name = function() local tracker = s() return string.format("%s: %s", L["OPT_ATTACH_PARTY_OFFSET_Y"] or "Attach Y offset", tostring(tracker and tracker.partyAttachOffsetY or 0)) end, hidden = function() local tracker = s() return not tracker or not IsGroupTracker(tracker) or tracker.attachToPartyFrame ~= true end, get = function() local tracker = s() return tracker and tracker.partyAttachOffsetY or 0 end, set = function(_, val) local tracker = s() if tracker then tracker.partyAttachOffsetY = val RefreshAnchors(true) end end, }, anchorTo = { type = "select", order = 7, name = L["OPT_ANCHOR_TO"] or "Anchor to", desc = L["OPT_ANCHOR_TO_DESC"] or "Attach this frame to another frame or to the screen", hidden = function() return IsPartyAttachMode(s()) end, values = function() local tracker = s() return tracker and HMGT:GetAnchorTargetOptions(tracker.id, tracker.anchorTo) or {} end, get = function() local tracker = s() if not tracker then return "UIParent" end local current = tracker.anchorTo or "UIParent" local values = HMGT:GetAnchorTargetOptions(tracker.id, current) if values[current] then return current end return "CUSTOM" end, set = function(_, val) local tracker = s() if tracker then local selfAnchor = HMGT:GetTrackerAnchorKey(tracker.id) if val == selfAnchor then val = "UIParent" end if val ~= "CUSTOM" then tracker.anchorCustom = tracker.anchorCustom or "" end tracker.anchorTo = val RefreshAnchors(false) end end, }, anchorCustom = { type = "input", order = 8, width = "full", name = L["OPT_ANCHOR_CUSTOM_NAME"] or "Custom frame", desc = L["OPT_ANCHOR_CUSTOM_NAME_DESC"] or "Global frame name, e.g. ElvUF_Player", hidden = function() local tracker = s() return not tracker or IsPartyAttachMode(tracker) or (tracker.anchorTo or "UIParent") ~= "CUSTOM" end, get = function() local tracker = s() return tracker and tracker.anchorCustom or "" end, set = function(_, val) local tracker = s() if tracker then tracker.anchorCustom = tostring(val or ""):gsub("^%s+", ""):gsub("%s+$", "") RefreshAnchors(false) end end, }, anchorPoint = { type = "select", order = 9, name = L["OPT_ANCHOR_POINT"] or "Anchor point", desc = L["OPT_ANCHOR_POINT_DESC"] or "Point on this frame to use for anchoring", values = ANCHOR_POINT_VALUES, hidden = function() return IsPartyAttachMode(s()) end, get = function() local tracker = s() return tracker and tracker.anchorPoint or "TOPLEFT" end, set = function(_, val) local tracker = s() if tracker then tracker.anchorPoint = val RefreshAnchors(false) end end, }, anchorRelativePoint = { type = "select", order = 10, name = L["OPT_ANCHOR_REL_POINT"] or "Relative point", desc = L["OPT_ANCHOR_REL_POINT_DESC"] or "Point on the target frame to anchor to", values = ANCHOR_POINT_VALUES, hidden = function() return IsPartyAttachMode(s()) end, get = function() local tracker = s() return tracker and tracker.anchorRelPoint or "TOPLEFT" end, set = function(_, val) local tracker = s() if tracker then tracker.anchorRelPoint = val RefreshAnchors(false) end end, }, anchorX = { type = "range", order = 11, min = -2000, max = 2000, step = 1, name = L["OPT_ANCHOR_X"] or "X offset", hidden = function() return IsPartyAttachMode(s()) end, get = function() local tracker = s() if not tracker then return 0 end if tracker.anchorX ~= nil then return tracker.anchorX end return tracker.posX or 0 end, set = function(_, val) local tracker = s() if tracker then tracker.anchorX = val RefreshAnchors(false) end end, }, anchorY = { type = "range", order = 12, min = -2000, max = 2000, step = 1, name = L["OPT_ANCHOR_Y"] or "Y offset", hidden = function() return IsPartyAttachMode(s()) end, get = function() local tracker = s() if not tracker then return 0 end if tracker.anchorY ~= nil then return tracker.anchorY end return tracker.posY or 0 end, set = function(_, val) local tracker = s() if tracker then tracker.anchorY = val RefreshAnchors(false) end end, }, }, }, layout = { type = "group", order = 40, name = L["OPT_UI_GROUP_LAYOUT"] or "Layout", args = { width = { type = "range", order = 1, min = 100, max = 600, step = 1, name = L["OPT_WIDTH"] or "Width (bars)", hidden = function() local tracker = s() return not tracker or tracker.showBar ~= true end, get = function() local tracker = s() return tracker and tracker.width or 250 end, set = function(_, val) local tracker = s() if tracker then tracker.width = val TriggerTrackerUpdate(false) end end, }, barHeight = { type = "range", order = 2, min = 10, max = 60, step = 1, name = L["OPT_BAR_HEIGHT"] or "Bar height", hidden = function() local tracker = s() return not tracker or tracker.showBar ~= true end, get = function() local tracker = s() return tracker and tracker.barHeight or 20 end, set = function(_, val) local tracker = s() if tracker then tracker.barHeight = val TriggerTrackerUpdate(false) end end, }, barSpacing = { type = "range", order = 3, min = 0, max = 20, step = 1, name = L["OPT_BAR_SPACING"] or "Bar spacing", hidden = function() local tracker = s() return not tracker or tracker.showBar ~= true end, get = function() local tracker = s() return tracker and tracker.barSpacing or 2 end, set = function(_, val) local tracker = s() if tracker then tracker.barSpacing = val TriggerTrackerUpdate(false) end end, }, barTexture = { type = "select", order = 4, name = L["OPT_BAR_TEXTURE"] or "Texture", desc = L["OPT_BAR_TEXTURE_DESC"] or "Texture of the progress bar", dialogControl = "LSM30_Statusbar", hidden = function() local tracker = s() return not tracker or tracker.showBar ~= true end, values = AceGUIWidgetLSMlists and AceGUIWidgetLSMlists.statusbar or (LSM and LSM:HashTable("statusbar")) or {}, get = function() local tracker = s() return tracker and tracker.barTexture or "Blizzard" end, set = function(_, val) local tracker = s() if tracker then tracker.barTexture = val TriggerTrackerUpdate(false) end end, }, iconSize = { type = "range", order = 5, min = 12, max = 100, step = 1, name = L["OPT_ICON_SIZE"] or "Icon size", hidden = function() local tracker = s() return not tracker or tracker.showBar == true end, get = function() local tracker = s() return tracker and tracker.iconSize or 32 end, set = function(_, val) local tracker = s() if tracker then tracker.iconSize = val TriggerTrackerUpdate(false) end end, }, iconSpacing = { type = "range", order = 6, min = 0, max = 20, step = 1, name = L["OPT_ICON_SPACING"] or "Icon spacing", hidden = function() local tracker = s() return not tracker or tracker.showBar == true end, get = function() local tracker = s() return tracker and tracker.iconSpacing or 2 end, set = function(_, val) local tracker = s() if tracker then tracker.iconSpacing = val TriggerTrackerUpdate(false) end end, }, iconCols = { type = "range", order = 7, min = 1, max = 20, step = 1, name = L["OPT_ICON_COLS"] or "Icons per row", desc = L["OPT_ICON_COLS_DESC"] or "Number of icons per row (DOWN/UP) or per column (LEFT/RIGHT)", hidden = function() local tracker = s() return not tracker or tracker.showBar == true end, get = function() local tracker = s() return tracker and tracker.iconCols or 6 end, set = function(_, val) local tracker = s() if tracker then tracker.iconCols = val TriggerTrackerUpdate(false) end end, }, iconOverlay = { type = "select", order = 8, name = L["OPT_ICON_OVERLAY"] or "Cooldown display", desc = L["OPT_ICON_OVERLAY_DESC"] or "How to show remaining cooldown on icons", hidden = function() local tracker = s() return not tracker or tracker.showBar == true end, values = { sweep = L["OPT_ICON_OVERLAY_SWEEP"] or "Cooldown sweep", timer = L["OPT_ICON_OVERLAY_TIMER"] or "Text timer (MM:SS)", }, get = function() local tracker = s() return tracker and tracker.iconOverlay or "sweep" end, set = function(_, val) local tracker = s() if tracker then tracker.iconOverlay = val TriggerTrackerUpdate(false) end end, }, textAnchor = { type = "select", order = 9, name = L["OPT_TEXT_ANCHOR"] or "Text position", desc = L["OPT_TEXT_ANCHOR_DESC"] or "Where to show name and timer relative to the icon", hidden = function() local tracker = s() return not tracker or tracker.showBar == true end, values = { onIcon = L["OPT_ANCHOR_ON_ICON"] or "On icon (overlay)", above = L["OPT_ANCHOR_ABOVE"] or "Above icon", below = L["OPT_ANCHOR_BELOW"] or "Below icon", left = L["OPT_ANCHOR_LEFT"] or "Left of icon", right = L["OPT_ANCHOR_RIGHT"] or "Right of icon", }, get = function() local tracker = s() return tracker and tracker.textAnchor or "below" end, set = function(_, val) local tracker = s() if tracker then tracker.textAnchor = val TriggerTrackerUpdate(false) end end, }, showReadyText = { type = "toggle", order = 10, name = L["OPT_SHOW_READY_TEXT"] or "Show ready text", hidden = function() local tracker = s() return not tracker or tracker.showBar == true end, get = function() local tracker = s() return tracker and tracker.showReadyText ~= false end, set = function(_, val) local tracker = s() if tracker then tracker.showReadyText = val and true or false TriggerTrackerUpdate(false) end end, }, showChargesOnIcon = { type = "toggle", order = 11, name = L["OPT_SHOW_CHARGES_ON_ICON"] or "Show charges on icon", hidden = function() local tracker = s() return not tracker or tracker.showBar == true end, get = function() local tracker = s() return tracker and tracker.showChargesOnIcon == true end, set = function(_, val) local tracker = s() if tracker then tracker.showChargesOnIcon = val and true or false TriggerTrackerUpdate(false) end end, }, showRemainingOnIcon = { type = "toggle", order = 12, name = L["OPT_SHOW_REMAINING_ON_ICON"] or "Show remaining time on icon", hidden = function() local tracker = s() return not tracker or tracker.showBar == true or (tracker.iconOverlay or "sweep") == "sweep" end, get = function() local tracker = s() return tracker and tracker.showRemainingOnIcon == true end, set = function(_, val) local tracker = s() if tracker then tracker.showRemainingOnIcon = val and true or false TriggerTrackerUpdate(false) end end, }, }, }, appearance = { type = "group", order = 50, name = L["OPT_UI_GROUP_APPEARANCE"] or "Appearance", args = { showPlayerName = { type = "toggle", order = 1, name = L["OPT_SHOW_NAME"] or "Show player names", get = function() local tracker = s() return tracker and tracker.showPlayerName ~= false end, set = function(_, val) local tracker = s() if tracker then tracker.showPlayerName = val and true or false TriggerTrackerUpdate(false) end end, }, colorByClass = { type = "toggle", order = 2, name = L["OPT_CLASS_COLOR"] or "Use class colours", get = function() local tracker = s() return tracker and tracker.colorByClass ~= false end, set = function(_, val) local tracker = s() if tracker then tracker.colorByClass = val and true or false TriggerTrackerUpdate(false) end end, }, font = { type = "select", order = 3, name = L["OPT_FONT"] or "Typeface", dialogControl = "LSM30_Font", values = AceGUIWidgetLSMlists and AceGUIWidgetLSMlists.font or (LSM and LSM:HashTable("font")) or {}, get = function() local tracker = s() return tracker and tracker.font or "Friz Quadrata TT" end, set = function(_, val) local tracker = s() if tracker then tracker.font = val TriggerTrackerUpdate(false) end end, }, fontSize = { type = "range", order = 4, min = 6, max = 24, step = 1, name = L["OPT_FONT_SIZE"] or "Font size", get = function() local tracker = s() return tracker and tracker.fontSize or 12 end, set = function(_, val) local tracker = s() if tracker then tracker.fontSize = val TriggerTrackerUpdate(false) end end, }, fontOutline = { type = "select", order = 5, name = L["OPT_FONT_OUTLINE"] or "Font outline", values = FONT_OUTLINE_VALUES, get = function() local tracker = s() return tracker and tracker.fontOutline or "OUTLINE" end, set = function(_, val) local tracker = s() if tracker then tracker.fontOutline = val TriggerTrackerUpdate(false) end end, }, borderEnabled = { type = "toggle", order = 6, name = L["OPT_BORDER_ENABLED"] or "Show border", desc = L["OPT_BORDER_ENABLED_DESC"] or "Show a 1px border around progress bars and icons", get = function() local tracker = s() return tracker and tracker.borderEnabled == true end, set = function(_, val) local tracker = s() if tracker then tracker.borderEnabled = val and true or false TriggerTrackerUpdate(false) end end, }, borderColor = { type = "color", order = 7, hasAlpha = true, name = L["OPT_BORDER_COLOR"] or "Border color", desc = L["OPT_BORDER_COLOR_DESC"] or "Color of the 1px border", get = function() local tracker = s() local color = tracker and tracker.borderColor or {} return color.r or color[1] or 1, color.g or color[2] or 1, color.b or color[3] or 1, color.a or color[4] or 1 end, set = function(_, r, g, b, a) local tracker = s() if tracker then tracker.borderColor = { r = r, g = g, b = b, a = a } TriggerTrackerUpdate(false) end end, }, }, }, }, } end RefreshTrackerOptionArgs = function() if not trackerOptionsGroup then return end trackerOptionsGroup.args = BuildTrackerOverviewArgs() local trackers = GetSortedTrackers() for index, tracker in ipairs(trackers) do local trackerId = tonumber(tracker.id) if trackerId then trackerOptionsGroup.args["tracker_" .. trackerId] = BuildTrackerGroup(trackerId, 10 + index) end end end RefreshTrackedSpellsOptionArgs = function() if not trackedSpellsOptionsGroup then return end trackedSpellsOptionsGroup.args = BuildGlobalSpellBrowserArgs() end trackerOptionsGroup = { type = "group", name = L["OPT_TRACKERS"] or "Tracker Bars", order = 10, childGroups = "tree", args = {}, } trackedSpellsOptionsGroup = { type = "group", name = L["OPT_SECTION_SPELLS"] or "Tracked Spells", order = 20, childGroups = "tree", args = {}, } RefreshTrackerOptionArgs() RefreshTrackedSpellsOptionArgs() HMGT_Config:RegisterOptionsProvider("tracker.trackers", function() if RefreshTrackerOptionArgs then RefreshTrackerOptionArgs() end return { path = "tracker.trackers", order = 10, group = trackerOptionsGroup, } end) HMGT_Config:RegisterOptionsProvider("tracker.spells", function() if RefreshTrackedSpellsOptionArgs then RefreshTrackedSpellsOptionArgs() end return { path = "tracker.spells", order = 20, group = trackedSpellsOptionsGroup, } end) HMGT_Config:RegisterOptionsProvider("tracker", function() if RefreshTrackerOptionArgs then RefreshTrackerOptionArgs() end if RefreshTrackedSpellsOptionArgs then RefreshTrackedSpellsOptionArgs() end local group = { type = "group", name = L["OPT_MODULE_TRACKER"] or "Tracker", order = 10, childGroups = "tree", args = { actionsHeader = { type = "header", name = L["OPT_TRACKER_ACTIONS"] or "Tracker actions", order = 1, }, lockAll = { type = "execute", order = 1.1, width = "full", name = L["OPT_LOCK_ALL"], func = function() if HMGT_Config and HMGT_Config.SetAllTrackerFramesLocked then HMGT_Config.SetAllTrackerFramesLocked(true) end end, }, unlockAll = { type = "execute", order = 1.2, width = "full", name = L["OPT_UNLOCK_ALL"], func = function() if HMGT_Config and HMGT_Config.SetAllTrackerFramesLocked then HMGT_Config.SetAllTrackerFramesLocked(false) end end, }, demoMode = { type = "execute", order = 1.3, width = "full", name = L["OPT_DEMO_MODE"], desc = L["OPT_DEMO_MODE_DESC"], func = function() HMGT:DemoMode() end, }, testMode = { type = "execute", order = 1.4, width = "full", name = L["OPT_TEST_MODE"], desc = L["OPT_TEST_MODE_DESC"], func = function() HMGT:TestMode() end, }, trackerBars = CopyGroup(trackerOptionsGroup, { order = 10, name = trackerOptionsGroup.name or (L["OPT_TRACKERS"] or "Tracker Bars"), }), trackedSpells = CopyGroup(trackedSpellsOptionsGroup, { order = 20, name = trackedSpellsOptionsGroup.name or (L["OPT_SECTION_SPELLS"] or "Tracked Spells"), }), }, } return { path = "tracker", order = 10, group = group, } end)